algograph/algorithm/
graphviz.rs

1//! Visualize tagged graphs in the graphviz format.
2use crate::{graph::*, tagged::Edge as _Edge};
3use ahash::RandomState;
4use std::collections::HashMap;
5
6/**
7 * Provides graphviz labels for vertices.
8 *
9 * See [DumpInGraphviz] for details.
10 */
11pub trait GraphvizLabelForVertex {
12    /**
13     * Returns a string for graphviz name and an optional label.
14     */
15    fn label(&self) -> (String, Option<String>);
16}
17
18/**
19 * Provides graphviz labels for edges.
20 *
21 * See [DumpInGraphviz] for details.
22 */
23pub trait GraphvizLabelForEdge: crate::tagged::Edge {
24    /**
25     * Returns an optional label.
26     */
27    fn label(&self) -> Option<String>;
28}
29
30/**
31 * Dumps a directed/undirected graph into graphviz format.
32 *
33 * # Examples
34 *
35 * ```rust
36 * use algograph::{
37 *     algorithm::graphviz::*,
38 *     graph::*,
39 *     tagged::*,
40 * };
41 *
42 * #[derive(Debug, Clone, Hash, PartialEq, Eq)]
43 * enum Shape {
44 *     Default,
45 *     Rectangle,
46 * }
47 *
48 * #[derive(Debug, Clone, Hash, PartialEq, Eq)]
49 * struct ShapedVertex {
50 *     key: usize,
51 *     shape: Shape,
52 * }
53 *
54 * impl GraphvizLabelForVertex for ShapedVertex {
55 *     fn label(&self) -> (String, Option<String>) {
56 *         let name = format!("{}", self.key);
57 *         let label = match self.shape {
58 *             Shape::Rectangle => Some("shape=rectangle".to_owned()),
59 *             _ => None,
60 *         };
61 *         (name, label)
62 *     }
63 * }
64 *
65 * #[derive(Debug, Clone, Hash, PartialEq, Eq)]
66 * enum Color {
67 *     Default,
68 *     Red,
69 * }
70 *
71 * #[derive(Debug, Clone, Hash, PartialEq, Eq)]
72 * struct ColoredEdge {
73 *     src: VertexId,
74 *     snk: VertexId,
75 *     color: Color,
76 * }
77 *
78 * impl algograph::tagged::Edge for ColoredEdge {
79 *     fn source(&self) -> VertexId {
80 *         self.src
81 *     }
82 *     fn sink(&self) -> VertexId {
83 *         self.snk
84 *     }
85 * }
86 *
87 * impl GraphvizLabelForEdge for ColoredEdge {
88 *     fn label(&self) -> Option<String> {
89 *         match self.color {
90 *             Color::Red => Some("color=red".to_owned()),
91 *             _ => None,
92 *         }
93 *     }
94 * }
95 *
96 * // for a directed graph
97 * let mut dg = NaiveTaggedGraph::<ShapedVertex, ColoredEdge>::new();
98 * let v0 = dg.overwrite_vertex(ShapedVertex {
99 *     key: 0,
100 *     shape: Shape::Default,
101 * });
102 * let v1 = dg.overwrite_vertex(ShapedVertex {
103 *     key: 1,
104 *     shape: Shape::Rectangle,
105 * });
106 * let _ = dg.add_edge(ColoredEdge {
107 *     src: v0,
108 *     snk: v1,
109 *     color: Color::Red,
110 * });
111 * let _ = dg.add_edge(ColoredEdge {
112 *     src: v0,
113 *     snk: v0,
114 *     color: Color::Default,
115 * });
116 * let trial = {
117 *     let mut buf = vec![];
118 *     dg.dump_in_graphviz(&mut buf, "trial").unwrap();
119 *     String::from_utf8(buf).unwrap()
120 * };
121 * assert_eq!(
122 *      trial,
123 *      r#"digraph trial {
124 *   0 ;
125 *   1 [shape=rectangle] ;
126 *   0 -> 1 [color=red] ;
127 *   0 -> 0 ;
128 * }
129 * "#);
130 *
131 * // for an undirected graph
132 * let mut udg =
133 *     NaiveTaggedGraph::<ShapedVertex, ColoredEdge, undirected::TreeBackedGraph>::new();
134 * let v0 = udg.overwrite_vertex(ShapedVertex {
135 *     key: 0,
136 *     shape: Shape::Default,
137 * });
138 * let v1 = udg.overwrite_vertex(ShapedVertex {
139 *     key: 1,
140 *     shape: Shape::Rectangle,
141 * });
142 * let _ = udg.add_edge(ColoredEdge {
143 *     src: v0,
144 *     snk: v1,
145 *     color: Color::Red,
146 * });
147 * let _ = udg.add_edge(ColoredEdge {
148 *     src: v0,
149 *     snk: v0,
150 *     color: Color::Default,
151 * });
152 * let trial = {
153 *     let mut buf = vec![];
154 *     udg.dump_in_graphviz(&mut buf, "trial").unwrap();
155 *     String::from_utf8(buf).unwrap()
156 * };
157 * println!("{}", trial);
158 * assert_eq!(
159 *     trial,
160 *     r#"graph trial {
161 *   0 ;
162 *   1 [shape=rectangle] ;
163 *   0 -- 1 [color=red] ;
164 *   0 -- 0 ;
165 * }
166 * "#
167 * );
168 * ```
169 */
170pub trait DumpInGraphviz
171where
172    Self: crate::tagged::QueryableTaggedGraph + DirectedOrNot,
173    Self::LowerGraph: QueryableGraph,
174    Self::Vertex: GraphvizLabelForVertex,
175    Self::Edge: GraphvizLabelForEdge,
176{
177    /**
178     * Dumps a directed/undirected graph to a `std::io::Write` object in the graphviz format.
179     *
180     */
181    fn dump_in_graphviz<W>(&self, out: &mut W, graph_name: &str) -> std::io::Result<()>
182    where
183        W: std::io::Write,
184    {
185        if Self::DIRECTED_OR_NOT {
186            writeln!(out, "digraph {} {{", graph_name)?;
187        } else {
188            writeln!(out, "graph {} {{", graph_name)?;
189        }
190        let mut vkey = HashMap::with_hasher(RandomState::new());
191        for (vid, vert) in self.iter_vertices() {
192            let (key, label) = vert.label();
193            if let Some(label) = label {
194                writeln!(out, "  {} [{}] ;", key, label)?;
195            } else {
196                writeln!(out, "  {} ;", key)?;
197            }
198            vkey.insert(vid, key);
199        }
200        let dir = if Self::DIRECTED_OR_NOT { "->" } else { "--" };
201        for (_, e) in self.iter_edges() {
202            let src = vkey.get(&e.source()).unwrap();
203            let snk = vkey.get(&e.sink()).unwrap();
204            if let Some(label) = e.label() {
205                writeln!(out, "  {} {} {} [{}] ;", src, dir, snk, label)?;
206            } else {
207                writeln!(out, "  {} {} {} ;", src, dir, snk)?;
208            }
209        }
210        writeln!(out, "}}")?;
211        Ok(())
212    }
213}
214
215impl<G> DumpInGraphviz for G
216where
217    G: crate::tagged::QueryableTaggedGraph + DirectedOrNot,
218    G::LowerGraph: QueryableGraph,
219    G::Vertex: GraphvizLabelForVertex,
220    G::Edge: GraphvizLabelForEdge,
221{
222}