this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 342 lines 12 kB view raw
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}