pedant_graph/analysis/
layout.rs1use crate::edge::{GraphCertainty, GraphEdgeKind};
14use crate::graph::CodeGraph;
15use crate::id::{GraphEdgeId, GraphNodeId, index_of, position};
16
17use super::betweenness::{self, BetweennessCentrality};
18use super::degree::{self, DegreeCentrality};
19use super::error::GraphAnalysisError;
20use super::index::{AnalysisContext, SelectedAdjacency, SelectedIndexes};
21
22#[derive(Clone, Copy, Debug)]
24pub struct LayoutNodeMetadata {
25 node: GraphNodeId,
26 parent: Option<GraphNodeId>,
27 partition: GraphNodeId,
28 incoming: u32,
29 outgoing: u32,
30 raw: f64,
31 normalized: f64,
32}
33
34impl LayoutNodeMetadata {
35 pub fn node(self) -> GraphNodeId {
37 self.node
38 }
39
40 pub fn containment_parent(self) -> Option<GraphNodeId> {
42 self.parent
43 }
44
45 pub fn partition(self) -> GraphNodeId {
47 self.partition
48 }
49
50 pub fn incoming_degree(self) -> u32 {
52 self.incoming
53 }
54
55 pub fn outgoing_degree(self) -> u32 {
57 self.outgoing
58 }
59
60 pub fn raw_betweenness(self) -> f64 {
62 self.raw
63 }
64
65 pub fn normalized_betweenness(self) -> f64 {
67 self.normalized
68 }
69}
70
71#[derive(Clone, Copy, Debug)]
73pub struct LayoutEdgeMetadata {
74 edge: GraphEdgeId,
75 source: GraphNodeId,
76 target: GraphNodeId,
77 kind: GraphEdgeKind,
78 certainty: GraphCertainty,
79}
80
81impl LayoutEdgeMetadata {
82 pub fn edge(self) -> GraphEdgeId {
84 self.edge
85 }
86
87 pub fn source(self) -> GraphNodeId {
89 self.source
90 }
91
92 pub fn target(self) -> GraphNodeId {
94 self.target
95 }
96
97 pub fn kind(self) -> GraphEdgeKind {
99 self.kind
100 }
101
102 pub fn certainty(self) -> GraphCertainty {
104 self.certainty
105 }
106}
107
108#[derive(Debug)]
110pub struct LayoutAssistMetadata {
111 nodes: Box<[LayoutNodeMetadata]>,
112 edges: Box<[LayoutEdgeMetadata]>,
113}
114
115impl LayoutAssistMetadata {
116 pub fn nodes(&self) -> &[LayoutNodeMetadata] {
118 &self.nodes
119 }
120
121 pub fn edges(&self) -> &[LayoutEdgeMetadata] {
123 &self.edges
124 }
125}
126
127pub(crate) fn layout_assist(
130 analysis: &AnalysisContext<'_>,
131) -> Result<LayoutAssistMetadata, GraphAnalysisError> {
132 let weights = betweenness::betweenness(analysis)?;
133 let degrees = degree::degree(analysis);
134 Ok(LayoutAssistMetadata {
135 nodes: described(analysis, (°rees, &weights)),
136 edges: connections(analysis),
137 })
138}
139
140fn described(
142 analysis: &AnalysisContext<'_>,
143 measured: (&[DegreeCentrality], &[BetweennessCentrality]),
144) -> Box<[LayoutNodeMetadata]> {
145 let (degrees, weights) = measured;
146 let indexes = analysis.indexes();
147 degrees
148 .iter()
149 .zip(weights)
150 .zip(analysis.declared_partition().owners())
151 .map(|((degree, weight), partition)| node_metadata(indexes, (*degree, *weight), *partition))
152 .collect()
153}
154
155fn node_metadata(
157 indexes: &SelectedIndexes,
158 measured: (DegreeCentrality, BetweennessCentrality),
159 partition: GraphNodeId,
160) -> LayoutNodeMetadata {
161 let (degree, weight) = measured;
162 LayoutNodeMetadata {
163 node: degree.node(),
164 parent: indexes.parent(degree.node()),
165 partition,
166 incoming: degree.incoming(),
167 outgoing: degree.outgoing(),
168 raw: weight.raw(),
169 normalized: weight.normalized(),
170 }
171}
172
173fn connections(analysis: &AnalysisContext<'_>) -> Box<[LayoutEdgeMetadata]> {
178 let indexes = analysis.indexes();
179 let graph = analysis.graph();
180 let mut found: Vec<LayoutEdgeMetadata> = Vec::with_capacity(index_of(indexes.selected_edges()));
181 found.extend(
182 (0..indexes.node_count())
183 .map(|at| GraphNodeId::new(position(at)))
184 .flat_map(|node| leaving(graph, indexes, node)),
185 );
186 found.sort_unstable_by_key(|stated| stated.edge);
187 found.into_boxed_slice()
188}
189
190fn leaving<'graph>(
192 graph: &'graph CodeGraph,
193 indexes: &'graph SelectedIndexes,
194 node: GraphNodeId,
195) -> impl Iterator<Item = LayoutEdgeMetadata> + 'graph {
196 indexes
197 .outgoing(node)
198 .iter()
199 .filter_map(move |entry| edge_metadata(graph, node, *entry))
200}
201
202fn edge_metadata(
209 graph: &CodeGraph,
210 source: GraphNodeId,
211 entry: SelectedAdjacency,
212) -> Option<LayoutEdgeMetadata> {
213 let found = graph.edge(entry.edge())?;
214 Some(LayoutEdgeMetadata {
215 edge: entry.edge(),
216 source,
217 target: entry.node(),
218 kind: found.kind(),
219 certainty: found.certainty(),
220 })
221}