use crate::preview::mermaid::chart::quadrant::QuadrantChart;
use crate::preview::mermaid::flowchart::Stroke;
use crate::preview::mermaid::layout::Point;
use super::super::{normalise, Diagram, Glyph, Label, PlacedNode, RenderError, Size, Theme};
use super::{add_title, label_node, rule, text_node, POINT_RADIUS, TICK_GAP};
pub const SIDE: f64 = 340.0;
pub const WORD_GAP: f64 = 8.0;
pub const CORNER_PAD: f64 = 8.0;
pub const DOT_RADIUS: f64 = 4.5;
pub fn render(code: &str, theme: &str) -> Result<String, RenderError> {
let chart = crate::preview::mermaid::chart::quadrant::parse(code)?;
let diagram = lay_out(&chart)?;
Ok(super::super::svg::emit(&diagram, &Theme::named(theme)))
}
pub fn place(left: f64, top: f64, side: f64, x: f64, y: f64) -> Point {
Point::new(left + x * side, top + (1.0 - y) * side)
}
fn square_side(chart: &QuadrantChart) -> f64 {
let w = |t: &str| Label::measure(t).width;
[
w(&chart.x_left) + w(&chart.x_right),
w(&chart.quadrant2) + w(&chart.quadrant1),
w(&chart.quadrant3) + w(&chart.quadrant4),
]
.into_iter()
.fold(SIDE, |side, pair| side.max(pair + WORD_GAP * 2.0))
}
pub fn lay_out(chart: &QuadrantChart) -> Result<Diagram, RenderError> {
if !crate::preview::mermaid::text_metrics::fonts_available() {
return Err(RenderError::NoFonts);
}
let (left, top) = (0.0, 0.0);
let side = square_side(chart);
let (right, bottom) = (side, side);
let mid_x = left + side / 2.0;
let mid_y = top + side / 2.0;
let mut nodes: Vec<PlacedNode> = Vec::new();
let mut edges = Vec::new();
nodes.push(PlacedNode {
id: "frame".to_string(),
shape: Glyph::PlotFrame,
center: Point::new(mid_x, mid_y),
size: Size::new(side, side),
label: Label::measure(""),
panel: None,
series: None,
mark: None,
});
edges.push(rule(
Point::new(mid_x, top),
Point::new(mid_x, bottom),
Stroke::Normal,
None,
));
edges.push(rule(
Point::new(left, mid_y),
Point::new(right, mid_y),
Stroke::Normal,
None,
));
for (text, qx, qy) in [
(&chart.quadrant1, 1.0, 1.0),
(&chart.quadrant2, 0.0, 1.0),
(&chart.quadrant3, 0.0, 0.0),
(&chart.quadrant4, 1.0, 0.0),
] {
let label = Label::measure(text);
if label.is_blank() {
continue;
}
let cx = left + side * 0.25 + qx * side * 0.5;
let band_top = top + (1.0 - qy) * side * 0.5;
let cy = band_top + CORNER_PAD + label.height / 2.0;
if let Some(n) = label_node(
format!(
"quadrant#{}",
if qy > 0.5 { qx as u8 } else { 2 + qx as u8 }
),
label,
Point::new(cx, cy),
None,
) {
nodes.push(n);
}
}
for (i, p) in chart.points.iter().enumerate() {
let c = place(left, top, side, p.x, p.y);
nodes.push(PlacedNode {
id: format!("point#{i}"),
shape: Glyph::ChartPoint,
center: c.clone(),
size: Size::new(DOT_RADIUS * 2.0, DOT_RADIUS * 2.0),
label: Label::measure(""),
panel: None,
series: Some(i),
mark: None,
});
let label = Label::measure(&p.label);
if let Some(n) = label_node(
format!("point#{i}#label"),
label.clone(),
Point::new(c.x, c.y + DOT_RADIUS + TICK_GAP + label.height / 2.0),
None,
) {
nodes.push(n);
}
}
let x_label_gap = POINT_RADIUS + TICK_GAP * 2.0;
for (text, cx, below) in [
(&chart.x_left, left + side * 0.25, true),
(&chart.x_right, left + side * 0.75, true),
] {
let label = Label::measure(text);
if let Some(n) = label_node(
format!(
"xaxis#{}",
if below && cx < mid_x { "left" } else { "right" }
),
label.clone(),
Point::new(cx, bottom + x_label_gap + label.height / 2.0),
None,
) {
nodes.push(n);
}
}
for (text, cy, name) in [
(&chart.y_bottom, top + side * 0.75, "bottom"),
(&chart.y_top, top + side * 0.25, "top"),
] {
let label = Label::measure(text);
if let Some(n) = label_node(
format!("yaxis#{name}"),
label.clone(),
Point::new(left - x_label_gap - label.width / 2.0, cy),
None,
) {
nodes.push(n);
}
}
let _ = text_node("", "", Point::new(0.0, 0.0), None);
let mut diagram = Diagram {
nodes,
edges,
..Diagram::default()
};
add_title(&mut diagram, &chart.preamble);
normalise(&mut diagram);
Ok(diagram)
}