Skip to main content

rudof_rdf/rdf_core/visualizer/
visual_rdf_graph.rs

1use crate::rdf_core::{
2    NeighsRDF, RDFError,
3    term::Triple,
4    visualizer::{
5        RDFVisualizationConfig, VisualRDFEdge, VisualRDFNode,
6        errors::RdfVisualizerError,
7        uml_converter::{UmlConverter, UmlGenerationMode, errors::UmlConverterError},
8        utils::UsageCount,
9    },
10};
11
12use std::collections::{HashMap, HashSet};
13use std::fmt::Display;
14use std::io::Write;
15
16/// A visual representation of an RDF graph that can be converted to PlantUML diagrams.
17///
18/// This struct maintains mappings between RDF terms and visual nodes, tracks usage
19/// counts for different roles (subject, predicate, object), and manages the edges
20/// between nodes for visualization purposes.
21pub struct VisualRDFGraph {
22    /// Counter for generating unique node IDs
23    node_counter: usize,
24    /// Mapping from visual nodes to their unique IDs
25    nodes_map: HashMap<VisualRDFNode, NodeId>,
26    /// Usage counts for each node in different contexts
27    usage_count: HashMap<VisualRDFNode, UsageCount>,
28    /// Set of edges between nodes in the graph
29    edges: HashSet<(NodeId, VisualRDFEdge, NodeId)>,
30    /// Configuration for visualization styling and behavior
31    config: RDFVisualizationConfig,
32}
33
34impl VisualRDFGraph {
35    /// Creates a new empty visual RDF graph with the given configuration.
36    ///
37    /// # Arguments
38    /// * `config` - Configuration settings for visualization
39    ///
40    /// # Returns
41    /// * A new `VisualRDFGraph` instance
42    pub fn new(config: RDFVisualizationConfig) -> Self {
43        VisualRDFGraph {
44            node_counter: 0,
45            nodes_map: HashMap::new(),
46            usage_count: HashMap::new(),
47            edges: HashSet::new(),
48            config,
49        }
50    }
51
52    /// Creates a visual RDF graph from an RDF data source.
53    ///
54    /// This method iterates through all triples in the RDF source and creates
55    /// corresponding visual nodes and edges.
56    ///
57    /// # Arguments
58    /// * `rdf` - The RDF data source implementing `NeighsRDF`
59    /// * `config` - Configuration for visualization
60    ///
61    /// # Returns
62    /// * `Result<Self, RDFError>` - The constructed graph or an error
63    pub fn from_rdf<R: NeighsRDF>(rdf: &R, config: RDFVisualizationConfig) -> Result<Self, RDFError> {
64        let mut graph = VisualRDFGraph::new(config);
65        let triples = rdf
66            .triples()
67            .map_err(|e| RDFError::ObtainingTriples { error: e.to_string() })?;
68
69        // Reserve capacity based on size hint to reduce reallocations
70        if let Some(upper_bound) = triples.size_hint().1 {
71            graph.nodes_map.reserve(upper_bound.saturating_mul(3)); // Estimate 3 nodes per triple
72            graph.usage_count.reserve(upper_bound.saturating_mul(3));
73            graph.edges.reserve(upper_bound);
74        }
75
76        for triple in triples {
77            let (subject, predicate, object) = triple.into_components();
78            graph.create_triple(rdf, subject, predicate, object)?;
79        }
80        Ok(graph)
81    }
82
83    /// Creates a visual representation of an RDF triple in the graph.
84    ///
85    /// This method converts RDF subject, predicate, and object into visual nodes,
86    /// creates edges between them, and updates usage counts.
87    ///
88    /// # Arguments
89    /// * `rdf` - The RDF data source
90    /// * `subject` - The subject of the triple
91    /// * `predicate` - The predicate of the triple
92    /// * `object` - The object of the triple
93    ///
94    /// # Returns
95    /// * `Result<VisualRDFNode, RDFError>` - The created triple node or an error
96    pub fn create_triple<R: NeighsRDF>(
97        &mut self,
98        rdf: &R,
99        subject: R::Subject,
100        predicate: R::IRI,
101        object: R::Term,
102    ) -> Result<VisualRDFNode, RDFError> {
103        let subject_node = VisualRDFNode::from_subject(rdf, &subject, self)?;
104        self.increment_usage_count_as_subject(&subject_node);
105        let subject_id = self.get_or_create_node(subject_node.clone());
106
107        let edge_node = VisualRDFNode::from_predicate(rdf, &predicate);
108        self.increment_usage_count_as_predicate(&edge_node);
109        let edge = VisualRDFEdge::from_iri(rdf, &predicate);
110
111        let object_node = VisualRDFNode::from_term(rdf, &object, self)?;
112        self.increment_usage_count_as_object(&object_node);
113        let object_id = self.get_or_create_node(object_node.clone());
114        self.edges.insert((subject_id, edge, object_id));
115
116        Ok(VisualRDFNode::non_asserted_triple(subject_node, edge_node, object_node))
117    }
118
119    /// Creates a visual representation of an RDF triple as a term (for RDF-star).
120    ///
121    /// Similar to `create_triple` but handles triple terms differently,
122    /// without creating edges in the visual graph.
123    ///
124    /// # Arguments
125    /// * `rdf` - The RDF data source
126    /// * `subject` - The subject of the triple
127    /// * `predicate` - The predicate of the triple
128    /// * `object` - The object of the triple
129    ///
130    /// # Returns
131    /// * `Result<VisualRDFNode, RDFError>` - The created triple term node or an error
132    pub fn create_triple_term<R: NeighsRDF>(
133        &mut self,
134        rdf: &R,
135        subject: R::Subject,
136        predicate: R::IRI,
137        object: R::Term,
138    ) -> Result<VisualRDFNode, RDFError> {
139        let subject_node = VisualRDFNode::from_subject(rdf, &subject, self)?;
140        self.increment_usage_count_as_subject_in_triple(&subject_node);
141        self.get_or_create_node(subject_node.clone());
142
143        let edge_node = VisualRDFNode::from_predicate(rdf, &predicate);
144        self.increment_usage_count_as_predicate_in_triple(&edge_node);
145        self.get_or_create_node(edge_node.clone());
146
147        let object_node = VisualRDFNode::from_term(rdf, &object, self)?;
148        self.increment_usage_count_as_object_in_triple(&object_node);
149        self.get_or_create_node(object_node.clone());
150
151        let subject_str = subject.to_string();
152        let predicate_str = predicate.to_string();
153        let object_str = object.to_string();
154        let asserted = rdf
155            .contains(&subject, &predicate, &object)
156            .map_err(|e| RDFError::FailedCheckingAssertion {
157                subject: subject_str.to_string(),
158                predicate: predicate_str.to_string(),
159                object: object_str.to_string(),
160                error: e.to_string(),
161            })?;
162        let triple = if asserted {
163            VisualRDFNode::asserted_triple(subject_node, edge_node, object_node)
164        } else {
165            VisualRDFNode::non_asserted_triple(subject_node, edge_node, object_node)
166        };
167        Ok(triple)
168    }
169
170    /// Increments the usage count for a node when used as a subject.
171    ///
172    /// # Arguments
173    /// * `node` - The node to increment the count for
174    #[inline]
175    pub fn increment_usage_count_as_subject(&mut self, node: &VisualRDFNode) {
176        let count = self.usage_count.entry(node.clone()).or_default();
177        count.increment_as_subject();
178    }
179
180    /// Increments the usage count for a node when used as a subject in a triple term.
181    ///
182    /// # Arguments
183    /// * `node` - The node to increment the count for
184    #[inline]
185    pub fn increment_usage_count_as_subject_in_triple(&mut self, node: &VisualRDFNode) {
186        let count = self.usage_count.entry(node.clone()).or_default();
187        count.increment_as_subject_in_triple();
188    }
189
190    /// Increments the usage count for a node when used as a predicate.
191    ///
192    /// # Arguments
193    /// * `node` - The node to increment the count for
194    #[inline]
195    pub fn increment_usage_count_as_predicate(&mut self, node: &VisualRDFNode) {
196        let count = self.usage_count.entry(node.clone()).or_default();
197        count.increment_as_predicate();
198    }
199
200    /// Increments the usage count for a node when used as a predicate in a triple term.
201    ///
202    /// # Arguments
203    /// * `node` - The node to increment the count for
204    #[inline]
205    pub fn increment_usage_count_as_predicate_in_triple(&mut self, node: &VisualRDFNode) {
206        let count = self.usage_count.entry(node.clone()).or_default();
207        count.increment_as_predicate_in_triple();
208    }
209
210    /// Increments the usage count for a node when used as an object.
211    ///
212    /// # Arguments
213    /// * `node` - The node to increment the count for
214    #[inline]
215    pub fn increment_usage_count_as_object(&mut self, node: &VisualRDFNode) {
216        let count = self.usage_count.entry(node.clone()).or_default();
217        count.increment_as_object();
218    }
219
220    /// Increments the usage count for a node when used as an object in a triple term.
221    ///
222    /// # Arguments
223    /// * `node` - The node to increment the count for
224    #[inline]
225    pub fn increment_usage_count_as_object_in_triple(&mut self, node: &VisualRDFNode) {
226        let count = self.usage_count.entry(node.clone()).or_default();
227        count.increment_as_object_in_triple();
228    }
229
230    /// Gets the ID of a node, creating it if it doesn't exist.
231    ///
232    /// # Arguments
233    /// * `node` - The node to get or create an ID for
234    ///
235    /// # Returns
236    /// * `NodeId` - The unique ID for the node
237    pub fn get_or_create_node(&mut self, node: VisualRDFNode) -> NodeId {
238        *self.nodes_map.entry(node).or_insert_with(|| {
239            let id = self.node_counter;
240            self.node_counter += 1;
241            NodeId { id }
242        })
243    }
244
245    /// Gets the ID of an existing node.
246    ///
247    /// # Arguments
248    /// * `node` - The node to get the ID for
249    ///
250    /// # Returns
251    /// * `Result<NodeId, RdfVisualizerError>` - The node ID or an error if not found
252    pub fn get_node_id(&self, node: &VisualRDFNode) -> Result<NodeId, RdfVisualizerError> {
253        match self.nodes_map.get(node) {
254            Some(id) => Ok(*id),
255            None => Err(RdfVisualizerError::NodeNotFound { node: node.clone() }),
256        }
257    }
258
259    /// Converts the visual graph to PlantUML format and writes it to the given writer.
260    ///
261    /// # Arguments
262    /// * `writer` - The writer to output the PlantUML code to
263    /// * `_mode` - The generation mode (currently unused)
264    ///
265    /// # Returns
266    /// * `Result<(), RdfVisualizerError>` - Ok if successful, Err with details on failure
267    pub fn as_plantuml<W: Write>(&self, writer: &mut W, _mode: &UmlGenerationMode) -> Result<(), RdfVisualizerError> {
268        let style = self.config.get_style();
269        writeln!(writer, "@startuml\n")?;
270        writeln!(writer, "{}", style.as_uml())?;
271
272        // Add nodes
273        for (node, node_id) in &self.nodes_map {
274            let show_node = self.show_node(node);
275            let node_uml = node.as_plantuml(*node_id, show_node, self)?;
276            writeln!(writer, "{node_uml}\n")?;
277        }
278        // Add edges
279        for (source, edge, target) in &self.edges {
280            writeln!(writer, "{source} --> {target} : {}\n", edge.as_plantuml_link())?;
281        }
282
283        // Add edges from triples
284        for (node, node_id) in &self.nodes_map {
285            match node {
286                VisualRDFNode::NonAssertedTriple(subj, pred, obj) => {
287                    triple_term_as_plantuml(writer, self, node_id, subj, pred, obj)?;
288                },
289                VisualRDFNode::AssertedTriple(subj, pred, obj) => {
290                    triple_term_as_plantuml(writer, self, node_id, subj, pred, obj)?;
291                },
292                _ => {},
293            }
294        }
295
296        writeln!(writer, "@enduml\n")?;
297        Ok(())
298    }
299
300    /// Determines whether a node should be shown in the visualization.
301    ///
302    /// Some nodes (like predicates) are only shown if they appear in triple terms.
303    ///
304    /// # Arguments
305    /// * `node` - The node to check
306    ///
307    /// # Returns
308    /// * `bool` - True if the node should be visualized
309    pub fn show_node(&self, node: &VisualRDFNode) -> bool {
310        match node {
311            VisualRDFNode::Predicate { .. } | VisualRDFNode::Reifies => match self.usage_count.get(node) {
312                Some(usage_count) => usage_count.in_triple(),
313                None => false,
314            },
315            // All nodes are visualized by default
316            _ => true,
317        }
318    }
319}
320
321/// Unique identifier for nodes in the visual graph.
322#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
323pub struct NodeId {
324    /// The unique numeric identifier
325    id: usize,
326}
327
328impl Display for NodeId {
329    /// Formats the node ID as a string.
330    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331        write!(f, "{}", self.id)
332    }
333}
334
335/// Unique identifier for edges in the visual graph.
336#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
337pub struct EdgeId {
338    /// The unique numeric identifier
339    id: usize,
340}
341
342impl Display for EdgeId {
343    /// Formats the edge ID as a string.
344    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
345        write!(f, "{}", self.id)
346    }
347}
348
349impl Display for VisualRDFGraph {
350    /// Formats the visual graph for debugging and logging purposes.
351    ///
352    /// Shows the number of nodes and edges, plus details about each node
353    /// and edge in the graph.
354    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
355        write!(
356            f,
357            "VisualRDFGraph with {} nodes and {} edges",
358            self.nodes_map.len(),
359            self.edges.len()
360        )?;
361        let zero = UsageCount::new();
362        for (node, id) in &self.nodes_map {
363            let count = self.usage_count.get(node).unwrap_or(&zero);
364            write!(f, "\nNode {id}: {node}")?;
365            write!(f, "\n     count: {count}")?;
366        }
367        for (source, edge, target) in &self.edges {
368            write!(f, "\nEdge {edge}: {source} --> {target}")?;
369        }
370        Ok(())
371    }
372}
373
374impl UmlConverter for VisualRDFGraph {
375    /// Converts the visual graph to PlantUML format.
376    ///
377    /// This implementation delegates to the struct's own `as_plantuml` method.
378    fn as_plantuml<W: Write>(&self, writer: &mut W, mode: &UmlGenerationMode) -> Result<(), UmlConverterError> {
379        self.as_plantuml(writer, mode)
380            .map_err(|e| UmlConverterError::UmlError { error: e.to_string() })
381    }
382}
383
384/// Generates PlantUML representation for triple terms (RDF-star triples).
385///
386/// This function creates the visual connections between a triple node and its
387/// constituent subject, predicate, and object nodes.
388///
389/// # Arguments
390/// * `writer` - The writer to output PlantUML code to
391/// * `graph` - The visual graph containing the nodes
392/// * `triple_id` - ID of the triple node
393/// * `subj` - The subject node
394/// * `pred` - The predicate node
395/// * `obj` - The object node
396///
397/// # Returns
398/// * `Result<(), RdfVisualizerError>` - Ok if successful, Err with details on failure
399fn triple_term_as_plantuml<W: Write>(
400    writer: &mut W,
401    graph: &VisualRDFGraph,
402    triple_id: &NodeId,
403    subj: &VisualRDFNode,
404    pred: &VisualRDFNode,
405    obj: &VisualRDFNode,
406) -> Result<(), RdfVisualizerError> {
407    let subj_id = graph.get_node_id(subj)?;
408    let pred_id = graph.get_node_id(pred)?;
409    let obj_id = graph.get_node_id(obj)?;
410    writeln!(
411        writer,
412        "{triple_id}-->{subj_id} {} : {} \n",
413        graph.config.get_subject_arrow_style().as_plantuml(),
414        graph.config.get_subject_text()
415    )?;
416    writeln!(
417        writer,
418        "{triple_id}-->{pred_id} {} : {}\n",
419        graph.config.get_predicate_arrow_style().as_plantuml(),
420        graph.config.get_predicate_text()
421    )?;
422    writeln!(
423        writer,
424        "{triple_id}-->{obj_id} {} : {}\n",
425        graph.config.get_object_arrow_style().as_plantuml(),
426        graph.config.get_object_text()
427    )?;
428    Ok(())
429}