use super::flowchart::{FlowDb, FlowGraph, GraphNode, Roles, Variant, layout_variant};
use crate::css::{Author, Resolved};
use crate::graphs::compound::{CEdge, CNode, CompoundResult, Dir};
use crate::graphs::lanes::{LANE_PAD, lane_layout};
use crate::graphs::layered::LayeredOptions;
use crate::layout::Options;
use crate::scene::style::{Paint, Style};
use crate::scene::text::Label;
use crate::scene::{Item, Kind, Painting, Part, Placed, Point, Rect, Scene};
use crate::value::Map;
#[cfg(test)]
mod tests;
pub(crate) const ROLE_LANE: &str = "swimlane.lane";
const DEFAULT_LANE: &str = "__swimlane_default__";
pub(crate) use super::flowchart::parse;
pub(crate) fn layout(db: &FlowDb, config: &Map, o: &Options) -> Scene {
layout_variant(db, config, o, Some(&VARIANT))
}
const VARIANT: Variant = Variant {
diagram: "swimlane",
roles: Roles {
casing: "swimlane.casing",
cluster: "swimlane.cluster",
cluster_title: "swimlane.cluster-title",
edge: "swimlane.edge",
edge_label: "swimlane.edge-label",
node: "swimlane.node",
title: "swimlane.title",
},
prepare,
layout: lay_lanes,
draw_group: Some(draw_lane),
filled_box: Some(lane_band),
};
fn lay_lanes(
nodes: &mut [CNode],
edges: &mut [CEdge],
dir: Dir,
opt: &LayeredOptions,
tight: bool,
) -> CompoundResult {
lane_layout(nodes, edges, dir, opt, if tight { 12.0 } else { LANE_PAD })
}
fn prepare(graph: &mut FlowGraph) {
let mut loose = false;
for node in &mut graph.nodes {
if node.is_group {
node.dir = None;
} else if node.parent_id.is_none() {
loose = true;
}
}
if !loose {
return;
}
for node in &mut graph.nodes {
if !node.is_group && node.parent_id.is_none() {
node.parent_id = Some(DEFAULT_LANE.to_string());
}
}
graph.nodes.push(GraphNode {
id: DEFAULT_LANE.to_string(),
shape: "rect".to_string(),
is_group: true,
..GraphNode::default()
});
}
fn draw_lane(
c: &CNode,
label: &Label,
style: &Resolved,
author: &Author,
class: Vec<String>,
id: &str,
dir: Dir,
) -> Item {
let left = c.x - c.w / 2.0;
let top = c.y - c.h / 2.0;
let sideways = matches!(dir, Dir::LR | Dir::RL);
let outline = Rect::new(left, top, c.w, c.h);
let (band, at) = if sideways {
(
Rect::new(left, top, c.pad_top, c.h),
Point::new(left + c.pad_top / 2.0, c.y),
)
} else {
(
Rect::new(left, top, c.w, c.pad_top),
Point::new(c.x, top + c.pad_top / 2.0),
)
};
let mut it = Item::new(Kind::Group, ROLE_LANE);
it.id = Some(id.to_string());
it.class = class;
it.bounds = outline;
it.parts = vec![
Part::rect(outline, false, Painting::StrokeOnly),
Part::rect(band, false, Painting::FillAndStroke),
];
it.style = Style {
fill: Some(Paint::SURFACE_ALT),
stroke: Some(Paint::BORDER),
text: Some(Paint::MUTED),
on_fill: style.inks_text(style),
..Style::default()
};
it.author = author.style.clone();
if !label.is_empty() {
let mut placed = Placed::new(label.clone(), at);
if sideways {
placed.rotate = -90.0;
}
it.label = Some(placed);
}
it
}
fn lane_band(c: &CNode, dir: Dir) -> [f64; 4] {
let left = c.x - c.w / 2.0;
let top = c.y - c.h / 2.0;
if matches!(dir, Dir::LR | Dir::RL) {
[left, top, left + c.pad_top, top + c.h]
} else {
[left, top, left + c.w, top + c.pad_top]
}
}