1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//!
//! Here's an example graph:
//!
//! ``` ignore
//! graph {
//!     0 [ label = "8" ]
//!     1 [ label = "3" ]
//!     2 [ label = "4" ]
//!     3 [ label = "5" ]
//!     4 [ label = "1" ]
//!     5 [ label = "7" ]
//!     6 [ label = "6" ]
//!     7 [ label = "2" ]
//!     4 -- 7 [ ]
//!     7 -- 1 [ ]
//!     1 -- 2 [ ]
//!     2 -- 4 [ ]
//!     3 -- 6 [ ]
//!     6 -- 5 [ ]
//!     5 -- 0 [ ]
//!     0 -- 3 [ ]
//!     4 -- 3 [ ]
//!     7 -- 6 [ ]
//!     1 -- 5 [ ]
//!     2 -- 0 [ ]
//! }
//! ```

use std::{collections::HashMap, error::Error, fmt};

use petgraph::{
    dot::{Config, Dot},
    graph::NodeIndex,
    stable_graph::StableGraph,
    visit::{EdgeRef, IntoEdgeReferences},
    Undirected,
};

use crate::ForceGraph;

/// Errors that can be returned by the functions in this module.
#[derive(Clone, Debug)]
pub enum DotParseError {
    /// Logically, this should never happen.
    IndexNotFound(String),
}

impl fmt::Display for DotParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::IndexNotFound(n) => write!(f, "Index for {n} was not found in the graph"),
        }
    }
}

impl Error for DotParseError {}

/// Convert a [`ForceGraph`] to the DOT
pub fn graph_to_dot<N, E>(graph: &ForceGraph<N, E>) -> Result<String, DotParseError> {
    let mut new_graph: StableGraph<String, (), Undirected> = StableGraph::default();
    let mut indices: HashMap<String, NodeIndex> = HashMap::new();

    for idx in graph.node_indices() {
        let node = &graph[idx];

        indices.insert(node.name.clone(), new_graph.add_node(node.name.clone()));
    }

    for edge in graph.edge_references() {
        let source = &graph[edge.source()].name;
        let target = &graph[edge.target()].name;

        let source_idx = match indices.get(source) {
            Some(idx) => *idx,
            None => return Err(DotParseError::IndexNotFound(source.clone())),
        };

        let target_idx = match indices.get(target) {
            Some(idx) => *idx,
            None => return Err(DotParseError::IndexNotFound(target.clone())),
        };

        new_graph.add_edge(source_idx, target_idx, ());
    }

    Ok(format!("{:?}", Dot::with_config(&new_graph, &[Config::EdgeNoLabel])).replace("\\\"", ""))
}