use std::path::Path;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use crate::{Edge, Node, NodeId, Port, RectStyling, Style};
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct PlotConfig {
beta: bool,
layout: Layout,
lines: Lines,
orientation: Orientation,
spacing: Spacing,
style: Style,
url: String,
}
impl PlotConfig {
pub fn from_json(path: &Path) -> Result<Self> {
let json = std::fs::read_to_string(path).context("Error reading JSON with plot Config")?;
serde_json::from_str(&json).context("Error parsing JSON to plot Config")
}
pub fn get_layout(&self) -> Layout {
self.layout
}
pub fn get_lines(&self) -> Lines {
self.lines
}
pub fn get_orientation(&self) -> Orientation {
self.orientation
}
pub fn get_spacing(&self) -> Spacing {
self.spacing
}
pub fn get_style(&self) -> &Style {
&self.style
}
pub fn get_url(&self) -> String {
let channel = match self.beta {
true => "beta",
false => "stable",
};
match self.url.ends_with("/") {
true => format!("{}{channel}", self.url),
false => format!("{}/{channel}", self.url),
}
}
pub fn beta(mut self) -> Self {
self.beta = true;
self
}
pub fn layout(mut self, new_layout: Layout) -> Self {
self.layout = new_layout;
self
}
pub fn lines(mut self, new_lines: Lines) -> Self {
self.lines = new_lines;
self
}
pub fn orientation(mut self, new_orientation: Orientation) -> Self {
self.orientation = new_orientation;
self
}
pub fn dev(mut self) -> Self {
self.url = "http://0.0.0.0:9000".to_string();
self
}
pub fn spacing(mut self, configure_spacing: impl FnOnce(Spacing) -> Spacing) -> Self {
self.spacing = configure_spacing(self.spacing);
self
}
pub fn style(mut self, configure_style: impl FnOnce(Style) -> Style) -> Self {
self.style = configure_style(self.style);
self
}
}
impl Default for PlotConfig {
fn default() -> Self {
Self::mode(true)
}
}
impl PlotConfig {
fn mode(dark: bool) -> Self {
Self {
beta: false,
layout: Layout::default(),
lines: Lines::default(),
orientation: Orientation::default(),
spacing: Spacing::default(),
style: if dark { Style::dark() } else { Style::light() },
url: "https://api.graphplot.io/".into(),
}
}
pub fn print() -> Self {
let config = Self::mode(false);
config.style(|s| s.background_color("#ffffffff").graph(|g| g.background_color("#ffffffff")))
}
pub fn light() -> Self {
Self::mode(false)
}
pub fn dark() -> Self {
Self::mode(true)
}
pub fn flowgraph(dark: bool) -> (impl Fn(NodeId, NodeId) -> Edge, Self) {
let create_edge_fn = |from, to| Edge::from(from, to).headport(Port::West).tailport(Port::East).floating();
(
create_edge_fn,
PlotConfig::mode(dark)
.orientation(Orientation::Lr)
.spacing(|spacing| spacing.vertical(Space::Static(200.0)))
.style(|theme| {
theme
.edge(|e| e.line(|l| l.arrowsize(7)))
.edge_highlighted(|e| e.line(|l| l.arrowsize(7)))
.node(|node| node.margin(-1.0).width(60))
.node_highlighted(|node| node.margin(-1.0))
}),
)
}
pub fn tree(dark: bool) -> (impl Fn() -> Node, Self) {
let theme = PlotConfig::mode(dark)
.spacing(|s| s.vertical(Space::Static(16.0)))
.lines(Lines::Line)
.style(|t| t.node(|n| n.margin(-0.3)).node_highlighted(|n| n.margin(-0.3)));
let pink_nodestyle = theme
.get_style()
.get_node()
.clone()
.background_color("#d7bef7")
.background_opacity(0.3)
.border_radius(2.0)
.padding(2.5)
.frame(|f| f.enable().color("#d7bef7"));
let create_root_node_fn = move || Node::new().style(pink_nodestyle.clone());
(
create_root_node_fn,
theme.style(|t| t.node(|n| n.padding(2.0)).node_highlighted(|n| n.padding(2.0))),
)
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Layout {
Circular,
Forcedirected,
ForcedirectedScaled,
#[default]
Layered,
Radial,
Spring,
Structured,
}
#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Lines {
#[default]
Spline,
None,
Line,
Curved,
Polyline,
Technical,
TechnicalRounded(f32),
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Orientation {
#[default]
Tb,
Bt,
Lr,
Rl,
}
#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct Spacing {
horizontal: Space,
vertical: Space,
}
impl Spacing {
pub fn get_horizontal(&self) -> Space {
self.horizontal
}
pub fn get_vertical(&self) -> Space {
self.vertical
}
pub fn horizontal(mut self, spacing_option: Space) -> Self {
self.horizontal = spacing_option;
self
}
pub fn vertical(mut self, spacing_option: Space) -> Self {
self.vertical = spacing_option;
self
}
}
#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum Space {
#[default]
Auto,
Static(f32),
}