use std::collections::HashMap;
use crate::js::map::Ordered;
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct WardleyNode {
pub(crate) id: String,
pub(crate) label: String,
pub(crate) x: f64,
pub(crate) y: f64,
pub(crate) class_name: Option<&'static str>,
pub(crate) label_offset_x: Option<f64>,
pub(crate) label_offset_y: Option<f64>,
pub(crate) in_pipeline: bool,
pub(crate) is_pipeline_parent: bool,
pub(crate) inertia: Option<bool>,
pub(crate) source_strategy: Option<String>,
}
pub(crate) struct NodeSet {
pub(crate) node: WardleyNode,
pub(crate) sets_inertia: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct WardleyLink {
pub(crate) source: String,
pub(crate) target: String,
pub(crate) dashed: bool,
pub(crate) label: Option<String>,
pub(crate) flow: Option<&'static str>,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct WardleyTrend {
pub(crate) node_id: String,
pub(crate) target_x: f64,
pub(crate) target_y: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct WardleyPipeline {
pub(crate) node_id: String,
pub(crate) component_ids: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct WardleyAnnotation {
pub(crate) number: f64,
pub(crate) coordinates: Vec<(f64, f64)>,
pub(crate) text: String,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct WardleyNote {
pub(crate) text: String,
pub(crate) x: f64,
pub(crate) y: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct WardleyForce {
pub(crate) name: String,
pub(crate) x: f64,
pub(crate) y: f64,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct WardleyAxesConfig {
pub(crate) x_label: Option<String>,
pub(crate) y_label: Option<String>,
pub(crate) stages: Option<Vec<String>>,
pub(crate) stage_boundaries: Option<Vec<f64>>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct WardleyBuildResult {
pub(crate) nodes: Vec<WardleyNode>,
pub(crate) links: Vec<WardleyLink>,
pub(crate) trends: Vec<WardleyTrend>,
pub(crate) pipelines: Vec<WardleyPipeline>,
pub(crate) annotations: Vec<WardleyAnnotation>,
pub(crate) notes: Vec<WardleyNote>,
pub(crate) accelerators: Vec<WardleyForce>,
pub(crate) deaccelerators: Vec<WardleyForce>,
pub(crate) annotations_box: Option<(f64, f64)>,
pub(crate) axes: WardleyAxesConfig,
pub(crate) size: Option<(f64, f64)>,
}
#[derive(Default)]
pub(crate) struct WardleyBuilder {
nodes: Ordered<WardleyNode>,
labels: Option<HashMap<String, String>>,
links: Vec<WardleyLink>,
trends: Ordered<WardleyTrend>,
pipelines: Ordered<WardleyPipeline>,
annotations: Vec<WardleyAnnotation>,
notes: Vec<WardleyNote>,
accelerators: Vec<WardleyForce>,
deaccelerators: Vec<WardleyForce>,
annotations_box: Option<(f64, f64)>,
axes: WardleyAxesConfig,
size: Option<(f64, f64)>,
}
impl WardleyBuilder {
pub(crate) fn add_node(&mut self, set: NodeSet) {
let node = set.node;
let mut next = match self.nodes.get(&node.id) {
Some(existing) => existing.clone(),
None => WardleyNode {
id: node.id.clone(),
label: node.label.clone(),
x: node.x,
y: node.y,
class_name: None,
label_offset_x: None,
label_offset_y: None,
in_pipeline: false,
is_pipeline_parent: false,
inertia: None,
source_strategy: None,
},
};
next.label = node.label;
next.x = node.x;
next.y = node.y;
next.class_name = node.class_name.or(next.class_name);
next.label_offset_x = node.label_offset_x.or(next.label_offset_x);
next.label_offset_y = node.label_offset_y.or(next.label_offset_y);
if set.sets_inertia {
next.inertia = node.inertia;
next.source_strategy = node.source_strategy;
}
self.nodes.set(&node.id, next);
self.labels = None;
}
pub(crate) fn add_link(&mut self, link: WardleyLink) {
self.links.push(link);
}
pub(crate) fn add_trend(&mut self, trend: WardleyTrend) {
let id = trend.node_id.clone();
self.trends.set(&id, trend);
}
pub(crate) fn start_pipeline(&mut self, node_id: &str) {
let pipeline = WardleyPipeline {
node_id: node_id.to_string(),
component_ids: Vec::new(),
};
self.pipelines.set(node_id, pipeline);
if let Some(node) = self.nodes.get_mut(node_id) {
node.is_pipeline_parent = true;
}
}
pub(crate) fn add_pipeline_component(&mut self, pipeline_node_id: &str, component_id: &str) {
if let Some(p) = self.pipelines.get_mut(pipeline_node_id) {
p.component_ids.push(component_id.to_string());
}
if let Some(node) = self.nodes.get_mut(component_id) {
node.in_pipeline = true;
}
}
pub(crate) fn add_annotation(&mut self, annotation: WardleyAnnotation) {
self.annotations.push(annotation);
}
pub(crate) fn add_note(&mut self, note: WardleyNote) {
self.notes.push(note);
}
pub(crate) fn add_accelerator(&mut self, accelerator: WardleyForce) {
self.accelerators.push(accelerator);
}
pub(crate) fn add_deaccelerator(&mut self, deaccelerator: WardleyForce) {
self.deaccelerators.push(deaccelerator);
}
pub(crate) fn set_annotations_box(&mut self, x: f64, y: f64) {
self.annotations_box = Some((x, y));
}
pub(crate) fn set_axes(&mut self, stages: Vec<String>, stage_boundaries: Vec<f64>) {
self.axes.stages = Some(stages);
self.axes.stage_boundaries = Some(stage_boundaries);
}
pub(crate) fn set_size(&mut self, width: f64, height: f64) {
self.size = Some((width, height));
}
pub(crate) fn get_node(&self, id: &str) -> Option<&WardleyNode> {
self.nodes.get(id)
}
pub(crate) fn resolve_node_id(&mut self, name: &str) -> String {
if self.nodes.contains(name) {
return name.to_string();
}
let nodes = &self.nodes;
let labels = self.labels.get_or_insert_with(|| {
let mut labels = HashMap::new();
for node in nodes.values() {
labels
.entry(node.label.clone())
.or_insert_with(|| node.id.clone());
}
labels
});
labels
.get(name)
.cloned()
.unwrap_or_else(|| name.to_string())
}
pub(crate) fn build(self) -> WardleyBuildResult {
WardleyBuildResult {
nodes: self.nodes.values().cloned().collect(),
links: self.links,
trends: self.trends.values().cloned().collect(),
pipelines: self.pipelines.values().cloned().collect(),
annotations: self.annotations,
notes: self.notes,
accelerators: self.accelerators,
deaccelerators: self.deaccelerators,
annotations_box: self.annotations_box,
axes: self.axes,
size: self.size,
}
}
}