Skip to main content

oxihuman_export/
dot_graph_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Graphviz DOT format scene graph export.
6
7#[derive(Debug, Clone)]
8pub struct DotNode {
9    pub id: String,
10    pub label: String,
11    pub shape: String,
12}
13
14#[derive(Debug, Clone)]
15pub struct DotEdge {
16    pub from: String,
17    pub to: String,
18    pub label: String,
19}
20
21#[derive(Debug, Clone)]
22pub struct DotGraph {
23    pub name: String,
24    pub directed: bool,
25    pub nodes: Vec<DotNode>,
26    pub edges: Vec<DotEdge>,
27}
28
29pub fn new_dot_graph(name: &str, directed: bool) -> DotGraph {
30    DotGraph {
31        name: name.to_string(),
32        directed,
33        nodes: Vec::new(),
34        edges: Vec::new(),
35    }
36}
37
38pub fn add_dot_node(graph: &mut DotGraph, id: &str, label: &str, shape: &str) {
39    graph.nodes.push(DotNode {
40        id: id.to_string(),
41        label: label.to_string(),
42        shape: shape.to_string(),
43    });
44}
45
46pub fn add_dot_edge(graph: &mut DotGraph, from: &str, to: &str, label: &str) {
47    graph.edges.push(DotEdge {
48        from: from.to_string(),
49        to: to.to_string(),
50        label: label.to_string(),
51    });
52}
53
54pub fn render_dot(graph: &DotGraph) -> String {
55    let kind = if graph.directed { "digraph" } else { "graph" };
56    let arrow = if graph.directed { "->" } else { "--" };
57    let mut s = format!("{} {} {{\n  rankdir=TB;\n", kind, graph.name);
58    for n in &graph.nodes {
59        s.push_str(&format!(
60            "  {} [label=\"{}\", shape={}];\n",
61            n.id, n.label, n.shape
62        ));
63    }
64    for e in &graph.edges {
65        let lbl = if e.label.is_empty() {
66            String::new()
67        } else {
68            format!(" [label=\"{}\"]", e.label)
69        };
70        s.push_str(&format!("  {} {} {}{};\n", e.from, arrow, e.to, lbl));
71    }
72    s.push_str("}\n");
73    s
74}
75
76pub fn export_dot(graph: &DotGraph) -> Vec<u8> {
77    render_dot(graph).into_bytes()
78}
79pub fn dot_node_count(graph: &DotGraph) -> usize {
80    graph.nodes.len()
81}
82pub fn dot_edge_count(graph: &DotGraph) -> usize {
83    graph.edges.len()
84}
85pub fn validate_dot_graph(graph: &DotGraph) -> bool {
86    !graph.name.is_empty()
87}
88pub fn dot_size_bytes(graph: &DotGraph) -> usize {
89    render_dot(graph).len()
90}
91
92pub fn scene_to_dot(nodes: &[(&str, Option<&str>)]) -> DotGraph {
93    let mut g = new_dot_graph("scene", true);
94    for (name, parent) in nodes {
95        add_dot_node(&mut g, name, name, "box");
96        if let Some(p) = parent {
97            add_dot_edge(&mut g, p, name, "");
98        }
99    }
100    g
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_new_dot_graph() {
109        let g = new_dot_graph("test", true);
110        assert_eq!(g.name, "test");
111        assert!(g.directed);
112    }
113
114    #[test]
115    fn test_add_node_edge() {
116        let mut g = new_dot_graph("g", true);
117        add_dot_node(&mut g, "a", "A", "box");
118        add_dot_edge(&mut g, "a", "b", "");
119        assert_eq!(dot_node_count(&g), 1);
120        assert_eq!(dot_edge_count(&g), 1);
121    }
122
123    #[test]
124    fn test_render_dot_contains_digraph() {
125        let g = new_dot_graph("mygraph", true);
126        let s = render_dot(&g);
127        assert!(s.contains("digraph"));
128    }
129
130    #[test]
131    fn test_render_dot_undirected() {
132        let g = new_dot_graph("g", false);
133        let s = render_dot(&g);
134        assert!(s.contains("graph"));
135        assert!(!s.contains("digraph"));
136    }
137
138    #[test]
139    fn test_export_dot_nonempty() {
140        let g = new_dot_graph("g", true);
141        assert!(!export_dot(&g).is_empty());
142    }
143
144    #[test]
145    fn test_validate_dot_graph() {
146        let g = new_dot_graph("v", true);
147        assert!(validate_dot_graph(&g));
148    }
149
150    #[test]
151    fn test_scene_to_dot() {
152        let nodes = vec![("root", None), ("child", Some("root"))];
153        let g = scene_to_dot(&nodes);
154        assert_eq!(dot_node_count(&g), 2);
155        assert_eq!(dot_edge_count(&g), 1);
156    }
157
158    #[test]
159    fn test_dot_size_bytes() {
160        let g = new_dot_graph("x", true);
161        assert!(dot_size_bytes(&g) > 0);
162    }
163}