use super::*;
pub(super) fn render_node(out: &mut String, n: &LayoutNode) {
let x = n.x - n.width / 2.0;
let y = n.y - n.height / 2.0;
let _ = write!(
out,
r#"<rect class="node-box" x="{}" y="{}" width="{}" height="{}" />"#,
fmt(x),
fmt(y),
fmt(n.width.max(1.0)),
fmt(n.height.max(1.0))
);
let _ = write!(
out,
r#"<text class="node-label" x="{}" y="{}">{}</text>"#,
fmt(n.x),
fmt(n.y),
escape_xml(&n.id)
);
}
pub(super) fn render_state_node(out: &mut String, n: &LayoutNode) {
let is_small_circle = (n.width - n.height).abs() < 1e-6 && n.width <= 20.0 && n.height <= 20.0;
if is_small_circle {
let r = (n.width / 2.0).max(1.0);
let _ = write!(
out,
r#"<circle class="node-circle" cx="{}" cy="{}" r="{}" />"#,
fmt(n.x),
fmt(n.y),
fmt(r)
);
} else {
let x = n.x - n.width / 2.0;
let y = n.y - n.height / 2.0;
let _ = write!(
out,
r#"<rect class="node-box" x="{}" y="{}" width="{}" height="{}" />"#,
fmt(x),
fmt(y),
fmt(n.width.max(1.0)),
fmt(n.height.max(1.0))
);
}
let _ = write!(
out,
r#"<text class="node-label" x="{}" y="{}">{}</text>"#,
fmt(n.x),
fmt(n.y),
escape_xml(&n.id)
);
}
pub(super) fn render_cluster(out: &mut String, c: &LayoutCluster, include_markers: bool) {
let x = c.x - c.width / 2.0;
let y = c.y - c.height / 2.0;
let _ = write!(
out,
r#"<g id="cluster-{}" data-diff="{}" data-offset-y="{}">"#,
escape_attr(&c.id),
fmt_debug_3dp(c.diff),
fmt_debug_3dp(c.offset_y)
);
let _ = write!(
out,
r#"<rect class="cluster-box" x="{}" y="{}" width="{}" height="{}" />"#,
fmt(x),
fmt(y),
fmt(c.width.max(1.0)),
fmt(c.height.max(1.0))
);
let _ = write!(
out,
r#"<text class="cluster-title" x="{}" y="{}">{}</text>"#,
fmt(c.title_label.x),
fmt(c.title_label.y),
escape_xml(&c.title)
);
if include_markers {
let ox = c.x + c.diff - c.width / 2.0;
let oy = c.y - c.height / 2.0 - c.padding;
debug_cross(out, ox, oy, 6.0);
}
out.push_str("</g>\n");
}
fn debug_cross(out: &mut String, x: f64, y: f64, size: f64) {
let s = size.abs();
let _ = write!(
out,
r#"<line class="debug-cross" x1="{}" y1="{}" x2="{}" y2="{}" />"#,
fmt(x - s),
fmt(y),
fmt(x + s),
fmt(y)
);
let _ = write!(
out,
r#"<line class="debug-cross" x1="{}" y1="{}" x2="{}" y2="{}" />"#,
fmt(x),
fmt(y - s),
fmt(x),
fmt(y + s)
);
}
pub(super) fn compute_layout_bounds(
clusters: &[LayoutCluster],
nodes: &[LayoutNode],
edges: &[crate::model::LayoutEdge],
) -> Option<Bounds> {
let mut b: Option<Bounds> = None;
let mut include_rect = |min_x: f64, min_y: f64, max_x: f64, max_y: f64| {
if let Some(ref mut cur) = b {
cur.min_x = cur.min_x.min(min_x);
cur.min_y = cur.min_y.min(min_y);
cur.max_x = cur.max_x.max(max_x);
cur.max_y = cur.max_y.max(max_y);
} else {
b = Some(Bounds {
min_x,
min_y,
max_x,
max_y,
});
}
};
for c in clusters {
let hw = c.width / 2.0;
let hh = c.height / 2.0;
include_rect(c.x - hw, c.y - hh, c.x + hw, c.y + hh);
let lhw = c.title_label.width / 2.0;
let lhh = c.title_label.height / 2.0;
include_rect(
c.title_label.x - lhw,
c.title_label.y - lhh,
c.title_label.x + lhw,
c.title_label.y + lhh,
);
}
for n in nodes {
let hw = n.width / 2.0;
let hh = n.height / 2.0;
include_rect(n.x - hw, n.y - hh, n.x + hw, n.y + hh);
}
for e in edges {
for p in &e.points {
include_rect(p.x, p.y, p.x, p.y);
}
for lbl in [
e.label.as_ref(),
e.start_label_left.as_ref(),
e.start_label_right.as_ref(),
e.end_label_left.as_ref(),
e.end_label_right.as_ref(),
]
.into_iter()
.flatten()
{
let hw = lbl.width / 2.0;
let hh = lbl.height / 2.0;
include_rect(lbl.x - hw, lbl.y - hh, lbl.x + hw, lbl.y + hh);
}
}
b
}