use crate::edge::{GraphCertainty, GraphEdgeKind};
use crate::graph::CodeGraph;
use crate::id::{GraphEdgeId, GraphNodeId, index_of, position};
use super::betweenness::{self, BetweennessCentrality};
use super::degree::{self, DegreeCentrality};
use super::error::GraphAnalysisError;
use super::index::{AnalysisContext, SelectedAdjacency, SelectedIndexes};
#[derive(Clone, Copy, Debug)]
pub struct LayoutNodeMetadata {
node: GraphNodeId,
parent: Option<GraphNodeId>,
partition: GraphNodeId,
incoming: u32,
outgoing: u32,
raw: f64,
normalized: f64,
}
impl LayoutNodeMetadata {
pub fn node(self) -> GraphNodeId {
self.node
}
pub fn containment_parent(self) -> Option<GraphNodeId> {
self.parent
}
pub fn partition(self) -> GraphNodeId {
self.partition
}
pub fn incoming_degree(self) -> u32 {
self.incoming
}
pub fn outgoing_degree(self) -> u32 {
self.outgoing
}
pub fn raw_betweenness(self) -> f64 {
self.raw
}
pub fn normalized_betweenness(self) -> f64 {
self.normalized
}
}
#[derive(Clone, Copy, Debug)]
pub struct LayoutEdgeMetadata {
edge: GraphEdgeId,
source: GraphNodeId,
target: GraphNodeId,
kind: GraphEdgeKind,
certainty: GraphCertainty,
}
impl LayoutEdgeMetadata {
pub fn edge(self) -> GraphEdgeId {
self.edge
}
pub fn source(self) -> GraphNodeId {
self.source
}
pub fn target(self) -> GraphNodeId {
self.target
}
pub fn kind(self) -> GraphEdgeKind {
self.kind
}
pub fn certainty(self) -> GraphCertainty {
self.certainty
}
}
#[derive(Debug)]
pub struct LayoutAssistMetadata {
nodes: Box<[LayoutNodeMetadata]>,
edges: Box<[LayoutEdgeMetadata]>,
}
impl LayoutAssistMetadata {
pub fn nodes(&self) -> &[LayoutNodeMetadata] {
&self.nodes
}
pub fn edges(&self) -> &[LayoutEdgeMetadata] {
&self.edges
}
}
pub(crate) fn layout_assist(
analysis: &AnalysisContext<'_>,
) -> Result<LayoutAssistMetadata, GraphAnalysisError> {
let weights = betweenness::betweenness(analysis)?;
let degrees = degree::degree(analysis);
Ok(LayoutAssistMetadata {
nodes: described(analysis, (°rees, &weights)),
edges: connections(analysis),
})
}
fn described(
analysis: &AnalysisContext<'_>,
measured: (&[DegreeCentrality], &[BetweennessCentrality]),
) -> Box<[LayoutNodeMetadata]> {
let (degrees, weights) = measured;
let indexes = analysis.indexes();
degrees
.iter()
.zip(weights)
.zip(analysis.declared_partition().owners())
.map(|((degree, weight), partition)| node_metadata(indexes, (*degree, *weight), *partition))
.collect()
}
fn node_metadata(
indexes: &SelectedIndexes,
measured: (DegreeCentrality, BetweennessCentrality),
partition: GraphNodeId,
) -> LayoutNodeMetadata {
let (degree, weight) = measured;
LayoutNodeMetadata {
node: degree.node(),
parent: indexes.parent(degree.node()),
partition,
incoming: degree.incoming(),
outgoing: degree.outgoing(),
raw: weight.raw(),
normalized: weight.normalized(),
}
}
fn connections(analysis: &AnalysisContext<'_>) -> Box<[LayoutEdgeMetadata]> {
let indexes = analysis.indexes();
let graph = analysis.graph();
let mut found: Vec<LayoutEdgeMetadata> = Vec::with_capacity(index_of(indexes.selected_edges()));
found.extend(
(0..indexes.node_count())
.map(|at| GraphNodeId::new(position(at)))
.flat_map(|node| leaving(graph, indexes, node)),
);
found.sort_unstable_by_key(|stated| stated.edge);
found.into_boxed_slice()
}
fn leaving<'graph>(
graph: &'graph CodeGraph,
indexes: &'graph SelectedIndexes,
node: GraphNodeId,
) -> impl Iterator<Item = LayoutEdgeMetadata> + 'graph {
indexes
.outgoing(node)
.iter()
.filter_map(move |entry| edge_metadata(graph, node, *entry))
}
fn edge_metadata(
graph: &CodeGraph,
source: GraphNodeId,
entry: SelectedAdjacency,
) -> Option<LayoutEdgeMetadata> {
let found = graph.edge(entry.edge())?;
Some(LayoutEdgeMetadata {
edge: entry.edge(),
source,
target: entry.node(),
kind: found.kind(),
certainty: found.certainty(),
})
}