#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Option {
NoEdgeLabels,
NoNodeLabels,
NoEdgeStyles,
NoEdgeColors,
NoNodeStyles,
NoNodeColors,
NoArrows,
Fontname(String),
DarkTheme,
}
pub fn render<'a, N, E, S, G, W>(g: &'a G, w: &mut W) -> crate::Result
where
N: Clone + 'a,
E: Clone + 'a,
S: Clone + 'a,
G: crate::Labeller<'a, Node = N, Edge = E, Subgraph = S>
+ crate::GraphWalk<'a, Node = N, Edge = E, Subgraph = S>,
W: std::io::Write,
{
render_opts(g, w, &[])
}
pub fn render_opts<'a, N, E, S, G, W>(
g: &'a G,
w: &mut W,
options: &[self::Option],
) -> crate::Result
where
N: Clone + 'a,
E: Clone + 'a,
S: Clone + 'a,
G: crate::Labeller<'a, Node = N, Edge = E, Subgraph = S>
+ crate::GraphWalk<'a, Node = N, Edge = E, Subgraph = S>,
W: std::io::Write,
{
writeln!(w, "{} {} {{", g.kind(), g.graph_id()?)?;
render_subgraphs(g, &g.subgraphs(), w, options)?;
render_nodes(g, &g.nodes(), w, options)?;
render_edges(g, &g.edges(), w, options)?;
writeln!(w, "}}")?;
Ok(())
}
fn render_subgraphs<
'a,
N: Clone + 'a,
E: Clone + 'a,
S: Clone + 'a,
G: crate::Labeller<'a, Node = N, Edge = E, Subgraph = S>
+ crate::GraphWalk<'a, Node = N, Edge = E, Subgraph = S>,
W: std::io::Write,
>(
g: &'a G,
subgraphs: &crate::Subgraphs<'a, S>,
w: &mut W,
options: &[crate::render::Option],
) -> crate::Result {
for s in subgraphs.iter() {
let id = g
.subgraph_id(s)
.map(|x| format!("{} ", x.name))
.unwrap_or_default();
writeln!(w, " subgraph {id}{{")?;
if !options.contains(&crate::render::Option::NoNodeLabels) {
writeln!(w, " label={};", g.subgraph_label(s))?;
}
let style = g.subgraph_style(s);
if !options.contains(&crate::render::Option::NoNodeStyles) && style != crate::Style::None {
writeln!(w, r#" style="{style}";"#)?;
}
let color = g.subgraph_color(s);
if !options.contains(&crate::render::Option::NoNodeColors) {
if let Some(c) = color {
writeln!(w, " color={c};")?;
}
}
if let Some(s) = g.subgraph_shape(s) {
write!(w, r#" shape="{s}";"#)?;
}
writeln!(w)?;
for n in g.subgraph_nodes(s).iter() {
writeln!(w, " {};", g.node_id(n)?)?;
}
writeln!(w, " }}\n")?;
}
Ok(())
}
pub fn render_nodes<'a, N, E, S, G, W>(
g: &'a G,
nodes: &crate::Nodes<'a, N>,
w: &mut W,
options: &[crate::render::Option],
) -> crate::Result
where
N: Clone + 'a,
E: Clone + 'a,
S: Clone + 'a,
G: crate::Labeller<'a, Node = N, Edge = E, Subgraph = S>
+ crate::GraphWalk<'a, Node = N, Edge = E, Subgraph = S>,
W: std::io::Write,
{
let mut graph_attrs = Vec::new();
let mut content_attrs = Vec::new();
let font;
if let Some(fontname) = options.iter().find_map(|option| {
if let self::Option::Fontname(fontname) = option {
Some(fontname)
} else {
None
}
}) {
font = format!(r#"fontname="{fontname}""#);
graph_attrs.push(&font[..]);
content_attrs.push(&font[..]);
}
if options.contains(&self::Option::DarkTheme) {
graph_attrs.push(r#"bgcolor="black""#);
graph_attrs.push(r#"fontcolor="white""#);
content_attrs.push(r#"color="white""#);
content_attrs.push(r#"fontcolor="white""#);
}
if !(graph_attrs.is_empty() && content_attrs.is_empty()) {
writeln!(w, r#" graph[{}];"#, graph_attrs.join(" "))?;
let content_attrs_str = content_attrs.join(" ");
writeln!(w, r#" node[{content_attrs_str}];"#)?;
writeln!(w, r#" edge[{content_attrs_str}];"#)?;
}
for n in nodes.iter() {
write!(w, " ")?;
let id = g.node_id(n)?;
write!(w, "{id}")?;
if !options.contains(&self::Option::NoNodeLabels) {
write!(w, "[label={}]", g.node_label(n)?)?;
}
let style = g.node_style(n);
if !options.contains(&self::Option::NoNodeStyles) && style != crate::Style::None {
write!(w, r#"[style="{style}"]"#)?;
}
let color = g.node_color(n);
if !options.contains(&self::Option::NoNodeColors) {
if let Some(c) = color {
write!(w, "[color={c}]")?;
}
}
if let Some(s) = g.node_shape(n) {
write!(w, "[shape={s}]")?;
}
writeln!(w, ";")?;
}
Ok(())
}
pub fn render_edges<'a, N, E, S, G, W>(
g: &'a G,
edges: &crate::Edges<'a, E>,
w: &mut W,
options: &[crate::render::Option],
) -> crate::Result
where
N: Clone + 'a,
E: Clone + 'a,
S: Clone + 'a,
G: crate::Labeller<'a, Node = N, Edge = E, Subgraph = S>
+ crate::GraphWalk<'a, Node = N, Edge = E, Subgraph = S>,
W: std::io::Write,
{
for e in edges.iter() {
write!(w, " ")?;
let source = g.source(e);
let target = g.target(e);
let source_id = g.node_id(&source)?;
let target_id = g.node_id(&target)?;
write!(w, "{source_id} {} {target_id}", g.kind().edgeop(),)?;
if !options.contains(&self::Option::NoEdgeLabels) {
write!(w, "[label={}]", g.edge_label(e))?;
}
let style = g.edge_style(e);
if !options.contains(&self::Option::NoEdgeStyles) && style != crate::Style::None {
write!(w, r#"[style="{style}"]"#)?;
}
let color = g.edge_color(e);
if !options.contains(&self::Option::NoEdgeColors) {
if let Some(c) = color {
write!(w, "[color={c}]")?;
}
}
let start_arrow = g.edge_start_arrow(e);
let end_arrow = g.edge_end_arrow(e);
if !options.contains(&self::Option::NoArrows)
&& (!start_arrow.is_default() || !end_arrow.is_default())
{
write!(w, "[")?;
if !end_arrow.is_default() {
write!(w, r#"arrowhead="{end_arrow}""#)?;
}
if !start_arrow.is_default() {
write!(w, r#" dir="both" arrowtail="{start_arrow}""#)?;
}
write!(w, "]")?;
}
writeln!(w, ";")?;
}
Ok(())
}