use crate::chart_layout::{
self, ChartLayout, color_at, compute_bar_layout, compute_chart_layout, compute_legend,
compute_line_layout, compute_pie_layout, format_value,
};
use crate::data_resolve::ResolvedChartData;
use std::fmt::Write;
/// mm cinsinden chart SVG uret
pub fn render_svg(data: &ResolvedChartData, width_mm: f64, height_mm: f64) -> String {
let mut svg = String::with_capacity(4096);
let bg = data.style.background_color.as_deref().unwrap_or("#FFFFFF");
write!(
svg,
r##"");
svg
}
fn render_bar(svg: &mut String, data: &ResolvedChartData, cl: &ChartLayout) {
if data.categories.is_empty() || data.series.is_empty() {
return;
}
let bl = compute_bar_layout(data, cl);
// Y axis
render_y_axis_svg(svg, &bl.y_axis);
// Bars
for bar in &bl.bars {
let color = color_at(&cl.palette, bar.color_idx);
write!(
svg,
r##""##,
bar.x, bar.y, bar.w, bar.h, color
)
.unwrap();
if bl.show_labels {
if bl.stacked {
if bar.value > 0.0 {
write!(
svg,
r##"{}"##,
bar.label_x, bar.label_y, bl.label_font, bl.label_color, format_value(bar.value)
)
.unwrap();
}
} else {
write!(
svg,
r##"{}"##,
bar.label_x, bar.label_y, bl.label_font, bl.label_color, format_value(bar.value)
)
.unwrap();
}
}
}
// X axis labels
render_x_labels_svg(svg, &bl.x_labels);
// X axis line
write!(
svg,
r##""##,
bl.x_axis_x1, bl.x_axis_y, bl.x_axis_x2, bl.x_axis_y
)
.unwrap();
}
fn render_line(svg: &mut String, data: &ResolvedChartData, cl: &ChartLayout) {
if data.categories.is_empty() || data.series.is_empty() {
return;
}
let ll = compute_line_layout(data, cl);
// Y axis
render_y_axis_svg(svg, &ll.y_axis);
for series_layout in &ll.series {
let color = color_at(&cl.palette, series_layout.color_idx);
let mut points = String::new();
let mut point_circles = String::new();
for pt in &series_layout.points {
write!(points, "{:.2},{:.2} ", pt.x, pt.y).unwrap();
if ll.show_points {
write!(
point_circles,
r##""##,
pt.x, pt.y, color
)
.unwrap();
}
if ll.show_labels {
write!(
svg,
r##"{}"##,
pt.x, pt.y - 1.5, ll.label_font, ll.label_color, format_value(pt.value)
)
.unwrap();
}
}
write!(
svg,
r##""##,
points.trim(), color, ll.line_width
)
.unwrap();
svg.push_str(&point_circles);
}
// X axis labels
render_x_labels_svg(svg, &ll.x_labels);
// Axis line
write!(
svg,
r##""##,
ll.x_axis_x1, ll.x_axis_y, ll.x_axis_x2, ll.x_axis_y
)
.unwrap();
}
fn render_pie(svg: &mut String, data: &ResolvedChartData, cl: &ChartLayout) {
let pl = compute_pie_layout(data, cl);
if pl.slices.is_empty() {
return;
}
let cx = pl.cx;
let cy = pl.cy;
let radius = pl.radius;
let inner_r = pl.inner_radius;
for slice in &pl.slices {
let color = color_at(&cl.palette, slice.color_idx);
let large_arc = if slice.sweep > std::f64::consts::PI {
1
} else {
0
};
let x1 = cx + radius * slice.start_angle.cos();
let y1 = cy + radius * slice.start_angle.sin();
let x2 = cx + radius * slice.end_angle.cos();
let y2 = cy + radius * slice.end_angle.sin();
if inner_r > 0.0 {
let ix1 = cx + inner_r * slice.start_angle.cos();
let iy1 = cy + inner_r * slice.start_angle.sin();
let ix2 = cx + inner_r * slice.end_angle.cos();
let iy2 = cy + inner_r * slice.end_angle.sin();
write!(
svg,
r##""##,
x1, y1, radius, radius, large_arc, x2, y2,
ix2, iy2, inner_r, inner_r, large_arc, ix1, iy1,
color
)
.unwrap();
} else {
write!(
svg,
r##""##,
cx, cy, x1, y1, radius, radius, large_arc, x2, y2, color
)
.unwrap();
}
// Percentage label inside slice
if pl.show_labels {
write!(
svg,
r##"{}%"##,
slice.label_x, slice.label_y, pl.label_font, pl.label_color,
(slice.fraction * 100.0).round()
)
.unwrap();
}
// Category name label outside slice with leader line
if !slice.cat_label_text.is_empty() {
write!(
svg,
r##""##,
slice.leader_start_x, slice.leader_start_y,
slice.leader_end_x, slice.leader_end_y
)
.unwrap();
let anchor = if slice.cat_label_anchor_end {
"end"
} else {
"start"
};
write!(
svg,
r##"{}"##,
slice.cat_label_x, slice.cat_label_y, anchor, escape_xml(&slice.cat_label_text)
)
.unwrap();
}
}
}
fn render_legend(
svg: &mut String,
data: &ResolvedChartData,
cl: &ChartLayout,
total_w: f64,
total_h: f64,
) {
let legend = compute_legend(data, cl, 0.0, 0.0, total_w, total_h);
for item in &legend.items {
let color = color_at(&cl.palette, item.color_idx);
write!(
svg,
r##""##,
item.swatch_x, item.swatch_y, color
)
.unwrap();
write!(
svg,
r##"{}"##,
item.text_x,
item.text_y,
legend.font_size,
escape_xml(&item.name)
)
.unwrap();
}
}
// ---------------------------------------------------------------------------
// SVG-specific helper renderers that consume shared layout structs
// ---------------------------------------------------------------------------
fn render_y_axis_svg(svg: &mut String, y_axis: &chart_layout::YAxisLayout) {
for tick in &y_axis.ticks {
write!(
svg,
r##"{}"##,
y_axis.axis_x - 1.5, tick.y + 0.8, tick.label
)
.unwrap();
if y_axis.show_grid {
write!(
svg,
r##""##,
y_axis.axis_x, tick.y, y_axis.grid_end_x, tick.y, y_axis.grid_color
)
.unwrap();
}
}
// Y axis line
write!(
svg,
r##""##,
y_axis.axis_x, y_axis.axis_y_start, y_axis.axis_x, y_axis.axis_y_end
)
.unwrap();
}
fn render_x_labels_svg(svg: &mut String, x_labels: &chart_layout::XLabelLayout) {
for label in &x_labels.labels {
if x_labels.needs_rotate {
write!(
svg,
r##"{}"##,
label.x, label.y, label.x, label.y, escape_xml(&label.text)
)
.unwrap();
} else {
write!(
svg,
r##"{}"##,
label.x, label.y, escape_xml(&label.text)
)
.unwrap();
}
}
}
fn escape_xml(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
}