use std::collections::{BTreeMap, BTreeSet};
use crate::model::{FlowchartAst, ObjectId};
use crate::render::scene::GraphModel;
use super::flowchart::{layout_flowchart, FlowEdgeEndpoint, FlowchartLayout, FlowchartLayoutError};
pub fn layout_graph(model: &GraphModel) -> Result<FlowchartLayout, FlowchartLayoutError> {
layout_flowchart(&model.to_flowchart_ast())
}
pub fn layout_general_graph(model: &GraphModel) -> Result<FlowchartLayout, FlowchartLayoutError> {
let source = model.to_flowchart_ast();
let mut backbone = FlowchartAst::default();
backbone.nodes_mut().extend(source.nodes().clone());
let mut outgoing = BTreeMap::<ObjectId, BTreeSet<ObjectId>>::new();
for node_id in source.nodes().keys() {
outgoing.insert(node_id.clone(), BTreeSet::new());
}
fn reaches(
outgoing: &BTreeMap<ObjectId, BTreeSet<ObjectId>>,
start: &ObjectId,
target: &ObjectId,
) -> bool {
let mut pending = vec![start.clone()];
let mut seen = BTreeSet::new();
while let Some(current) = pending.pop() {
if ¤t == target {
return true;
}
if !seen.insert(current.clone()) {
continue;
}
pending.extend(outgoing.get(¤t).into_iter().flatten().rev().cloned());
}
false
}
for (edge_id, edge) in source.edges() {
let from = edge.from_node_id();
let to = edge.to_node_id();
if !source.nodes().contains_key(from) {
return Err(FlowchartLayoutError::UnknownNode {
edge_id: edge_id.clone(),
endpoint: FlowEdgeEndpoint::From,
node_id: from.clone(),
});
}
if !source.nodes().contains_key(to) {
return Err(FlowchartLayoutError::UnknownNode {
edge_id: edge_id.clone(),
endpoint: FlowEdgeEndpoint::To,
node_id: to.clone(),
});
}
if from == to || reaches(&outgoing, to, from) {
continue;
}
outgoing.get_mut(from).expect("validated graph node").insert(to.clone());
backbone.edges_mut().insert(edge_id.clone(), edge.clone());
}
layout_flowchart(&backbone)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{FlowEdge, FlowNode};
use crate::render::lower::lower_flowchart;
fn oid(value: &str) -> ObjectId {
ObjectId::new(value).expect("object id")
}
#[test]
fn general_graph_layout_accepts_cycles_and_self_relations() {
let mut ast = FlowchartAst::default();
for id in ["n:a", "n:b", "n:c"] {
ast.nodes_mut().insert(oid(id), FlowNode::new(id));
}
for (edge_id, from, to) in [
("e:ab", "n:a", "n:b"),
("e:bc", "n:b", "n:c"),
("e:ca", "n:c", "n:a"),
("e:aa", "n:a", "n:a"),
] {
ast.edges_mut().insert(oid(edge_id), FlowEdge::new(oid(from), oid(to)));
}
let layout = layout_general_graph(&lower_flowchart(&ast)).expect("general graph layout");
assert_eq!(layout.node_placements().len(), 3);
}
}