this repo has no description
1use crate::{Graph, Result as GigabrainResult, GigabrainError};
2use crate::{NodeId, RelationshipId};
3use std::collections::HashMap;
4use std::sync::Arc;
5
6pub mod ascii;
7pub mod dot;
8pub mod svg;
9
10pub use ascii::*;
11pub use dot::*;
12pub use svg::*;
13
14/// Graph visualization formats
15#[derive(Debug, Clone, Copy, PartialEq)]
16pub enum VisualizationFormat {
17 Ascii,
18 Dot,
19 Svg,
20 Json,
21}
22
23impl std::fmt::Display for VisualizationFormat {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 match self {
26 VisualizationFormat::Ascii => write!(f, "ascii"),
27 VisualizationFormat::Dot => write!(f, "dot"),
28 VisualizationFormat::Svg => write!(f, "svg"),
29 VisualizationFormat::Json => write!(f, "json"),
30 }
31 }
32}
33
34impl std::str::FromStr for VisualizationFormat {
35 type Err = String;
36
37 fn from_str(s: &str) -> Result<Self, Self::Err> {
38 match s.to_lowercase().as_str() {
39 "ascii" => Ok(VisualizationFormat::Ascii),
40 "dot" => Ok(VisualizationFormat::Dot),
41 "svg" => Ok(VisualizationFormat::Svg),
42 "json" => Ok(VisualizationFormat::Json),
43 _ => Err(format!("Unknown visualization format: {}", s)),
44 }
45 }
46}
47
48/// Visualization options
49#[derive(Debug, Clone)]
50pub struct VisualizationOptions {
51 pub format: VisualizationFormat,
52 pub max_nodes: Option<usize>,
53 pub max_relationships: Option<usize>,
54 pub include_properties: bool,
55 pub include_labels: bool,
56 pub layout: LayoutAlgorithm,
57 pub color_scheme: ColorScheme,
58 pub node_size: NodeSize,
59 pub font_size: FontSize,
60}
61
62impl Default for VisualizationOptions {
63 fn default() -> Self {
64 Self {
65 format: VisualizationFormat::Ascii,
66 max_nodes: Some(50),
67 max_relationships: Some(100),
68 include_properties: true,
69 include_labels: true,
70 layout: LayoutAlgorithm::Spring,
71 color_scheme: ColorScheme::Default,
72 node_size: NodeSize::Medium,
73 font_size: FontSize::Medium,
74 }
75 }
76}
77
78/// Layout algorithms for graph visualization
79#[derive(Debug, Clone, Copy, PartialEq)]
80pub enum LayoutAlgorithm {
81 Spring,
82 Hierarchical,
83 Circular,
84 Grid,
85 Random,
86}
87
88/// Color schemes for visualization
89#[derive(Debug, Clone, Copy, PartialEq)]
90pub enum ColorScheme {
91 Default,
92 Dark,
93 Light,
94 Colorful,
95 Monochrome,
96}
97
98/// Node size options
99#[derive(Debug, Clone, Copy, PartialEq)]
100pub enum NodeSize {
101 Small,
102 Medium,
103 Large,
104}
105
106/// Font size options
107#[derive(Debug, Clone, Copy, PartialEq)]
108pub enum FontSize {
109 Small,
110 Medium,
111 Large,
112}
113
114/// Main visualization engine
115pub struct GraphVisualizer {
116 graph: Arc<Graph>,
117 options: VisualizationOptions,
118}
119
120impl GraphVisualizer {
121 pub fn new(graph: Arc<Graph>) -> Self {
122 Self {
123 graph,
124 options: VisualizationOptions::default(),
125 }
126 }
127
128 pub fn with_options(graph: Arc<Graph>, options: VisualizationOptions) -> Self {
129 Self {
130 graph,
131 options,
132 }
133 }
134
135 /// Generate visualization in the specified format
136 pub async fn visualize(&self) -> GigabrainResult<String> {
137 match self.options.format {
138 VisualizationFormat::Ascii => self.generate_ascii().await,
139 VisualizationFormat::Dot => self.generate_dot().await,
140 VisualizationFormat::Svg => self.generate_svg().await,
141 VisualizationFormat::Json => self.generate_json().await,
142 }
143 }
144
145 /// Generate visualization and save to file
146 pub async fn visualize_to_file(&self, filename: &str) -> GigabrainResult<()> {
147 let content = self.visualize().await?;
148 std::fs::write(filename, content)
149 .map_err(|e| GigabrainError::Storage(format!("Failed to write visualization file: {}", e)))?;
150 Ok(())
151 }
152
153 /// Generate ASCII art visualization
154 async fn generate_ascii(&self) -> GigabrainResult<String> {
155 let ascii_renderer = AsciiRenderer::new(&self.options);
156 ascii_renderer.render(&self.graph).await
157 }
158
159 /// Generate DOT format for Graphviz
160 async fn generate_dot(&self) -> GigabrainResult<String> {
161 let dot_renderer = DotRenderer::new(&self.options);
162 dot_renderer.render(&self.graph).await
163 }
164
165 /// Generate SVG visualization
166 async fn generate_svg(&self) -> GigabrainResult<String> {
167 let svg_renderer = SvgRenderer::new(&self.options);
168 svg_renderer.render(&self.graph).await
169 }
170
171 /// Generate JSON representation for web visualization
172 async fn generate_json(&self) -> GigabrainResult<String> {
173 let nodes = self.graph.get_all_nodes();
174 let limited_nodes: Vec<NodeId> = if let Some(max) = self.options.max_nodes {
175 nodes.into_iter().take(max).collect()
176 } else {
177 nodes
178 };
179
180 let mut json_nodes = Vec::new();
181 let mut json_edges = Vec::new();
182 let mut edge_count = 0;
183 let max_edges = self.options.max_relationships.unwrap_or(usize::MAX);
184
185 // Collect nodes
186 for &node_id in &limited_nodes {
187 if let Some(node) = self.graph.get_node(node_id) {
188 let mut node_obj = serde_json::Map::new();
189 node_obj.insert("id".to_string(), serde_json::Value::String(format!("n{}", node_id.0)));
190 node_obj.insert("label".to_string(), serde_json::Value::String(format!("Node {}", node_id.0)));
191
192 if self.options.include_labels {
193 let labels: Vec<String> = node.labels.iter().map(|id| {
194 let schema = self.graph.schema().read();
195 schema.get_label_name(*id).map(|s| s.to_string()).unwrap_or_else(|| "Unknown".to_string())
196 }).collect();
197 node_obj.insert("labels".to_string(), serde_json::Value::Array(
198 labels.iter().map(|l| serde_json::Value::String(l.clone())).collect()
199 ));
200 }
201
202 if self.options.include_properties && !node.properties.is_empty() {
203 let mut props = serde_json::Map::new();
204 for (key_id, value) in &node.properties {
205 let schema = self.graph.schema().read();
206 if let Some(key_name) = schema.get_property_key_name(*key_id) {
207 props.insert(key_name.to_string(), serde_json::Value::String(format!("{:?}", value)));
208 }
209 }
210 node_obj.insert("properties".to_string(), serde_json::Value::Object(props));
211 }
212
213 json_nodes.push(serde_json::Value::Object(node_obj));
214 }
215 }
216
217 // Collect relationships
218 for &node_id in &limited_nodes {
219 if edge_count >= max_edges {
220 break;
221 }
222
223 let relationships = self.graph.get_node_relationships(
224 node_id,
225 crate::core::relationship::Direction::Outgoing,
226 None
227 );
228
229 for rel in relationships {
230 if edge_count >= max_edges {
231 break;
232 }
233
234 // Only include relationships between nodes in our limited set
235 if limited_nodes.contains(&rel.end_node) {
236 let mut edge_obj = serde_json::Map::new();
237 edge_obj.insert("source".to_string(), serde_json::Value::String(format!("n{}", rel.start_node.0)));
238 edge_obj.insert("target".to_string(), serde_json::Value::String(format!("n{}", rel.end_node.0)));
239
240 let schema = self.graph.schema().read();
241 if let Some(type_name) = schema.get_relationship_type_name(rel.rel_type) {
242 edge_obj.insert("label".to_string(), serde_json::Value::String(type_name.to_string()));
243 }
244
245 if self.options.include_properties && !rel.properties.is_empty() {
246 let mut props = serde_json::Map::new();
247 for (key_id, value) in &rel.properties {
248 if let Some(key_name) = schema.get_property_key_name(*key_id) {
249 props.insert(key_name.to_string(), serde_json::Value::String(format!("{:?}", value)));
250 }
251 }
252 edge_obj.insert("properties".to_string(), serde_json::Value::Object(props));
253 }
254
255 json_edges.push(serde_json::Value::Object(edge_obj));
256 edge_count += 1;
257 }
258 }
259 }
260
261 let result = serde_json::json!({
262 "nodes": json_nodes,
263 "edges": json_edges,
264 "metadata": {
265 "total_nodes": limited_nodes.len(),
266 "total_edges": edge_count,
267 "layout": format!("{:?}", self.options.layout),
268 "options": {
269 "include_properties": self.options.include_properties,
270 "include_labels": self.options.include_labels,
271 "max_nodes": self.options.max_nodes,
272 "max_relationships": self.options.max_relationships
273 }
274 }
275 });
276
277 serde_json::to_string_pretty(&result)
278 .map_err(|e| GigabrainError::Storage(format!("JSON serialization failed: {}", e)))
279 }
280
281 /// Update visualization options
282 pub fn set_options(&mut self, options: VisualizationOptions) {
283 self.options = options;
284 }
285
286 /// Get current options
287 pub fn get_options(&self) -> &VisualizationOptions {
288 &self.options
289 }
290}
291
292/// Graph statistics for visualization purposes
293#[derive(Debug)]
294pub struct VisualizationStats {
295 pub node_count: usize,
296 pub edge_count: usize,
297 pub avg_degree: f64,
298 pub max_degree: usize,
299 pub components: usize,
300 pub diameter: Option<usize>,
301}
302
303impl GraphVisualizer {
304 /// Get visualization statistics
305 pub fn get_stats(&self) -> VisualizationStats {
306 let nodes = self.graph.get_all_nodes();
307 let node_count = nodes.len();
308 let mut edge_count = 0;
309 let mut total_degree = 0;
310 let mut max_degree = 0;
311
312 for &node_id in &nodes {
313 let relationships = self.graph.get_node_relationships(
314 node_id,
315 crate::core::relationship::Direction::Both,
316 None
317 );
318 let degree = relationships.len();
319 total_degree += degree;
320 edge_count += relationships.len();
321 max_degree = max_degree.max(degree);
322 }
323
324 // Relationships are counted twice (once for each direction)
325 edge_count /= 2;
326
327 let avg_degree = if node_count > 0 {
328 total_degree as f64 / node_count as f64
329 } else {
330 0.0
331 };
332
333 VisualizationStats {
334 node_count,
335 edge_count,
336 avg_degree,
337 max_degree,
338 components: 1, // Placeholder - would need proper component analysis
339 diameter: None, // Placeholder - would need shortest path analysis
340 }
341 }
342}