use super::{
approx_text_width, find_optimal_y_interval, render_centered_title, svg_footer, svg_header,
svg_rect_filled, svg_rect_stroked, svg_text, ChartColor, ChartLayout, BOLD_WIDTH_SCALE,
LINE_COLOURS,
};
pub struct LineGraphData {
pub data: Vec<Vec<f64>>,
pub min_y: f64,
pub max_y: f64,
pub x_label: String,
pub series_names: Vec<String>,
pub x_categories: Vec<String>,
pub title: String,
}
pub fn render_line_graph(params: &LineGraphData) -> String {
let y_interval = find_optimal_y_interval(params.max_y);
let layout = ChartLayout::new(params.min_y, params.max_y, y_interval);
let num_points = if params.data.is_empty() || params.data[0].is_empty() {
1
} else {
params.data[0].len()
};
let base_width = layout.base_width(num_points);
let half_bw = layout.half_base_width(num_points);
let mut svg = svg_header(layout.width, layout.height);
layout.render_background(&mut svg);
layout.render_y_labels(&mut svg);
render_centered_title(&mut svg, ¶ms.title, layout.x_offset, layout.width);
layout.render_axes(&mut svg);
layout.render_x_axis_label(&mut svg, ¶ms.x_label);
let mut last_x_label_end: f64 = 0.0;
for i in 0..num_points {
if i % 2 != 0 {
svg.push_str(&svg_rect_filled(
layout.x_offset + base_width * i as f64,
40.0,
base_width,
layout.height - 80.0,
&ChartColor::new(230, 230, 230),
));
}
if i < params.x_categories.len() {
last_x_label_end = layout.render_x_category_label_at(
&mut svg,
¶ms.x_categories[i],
i,
base_width,
last_x_label_end,
);
}
}
layout.render_gridlines(&mut svg);
for (d, series) in params.data.iter().enumerate() {
let color = &LINE_COLOURS[d % LINE_COLOURS.len()];
if series.len() < 2 {
continue;
}
let mut prev_x = 0i32;
let mut prev_y = 0i32;
for (i, &val) in series.iter().enumerate() {
let x = (half_bw + layout.x_offset + (base_width * i as f64)) as i32;
let y = layout.get_y(val) as i32;
if i > 0 {
svg.push_str(&format!(
"<line x1=\"{}\" y1=\"{}\" x2=\"{}\" y2=\"{}\" stroke=\"{}\" stroke-width=\"2\"/>\n",
prev_x, prev_y, x, y, color.to_rgb_string()
));
}
prev_x = x;
prev_y = y;
}
}
if !params.series_names.is_empty() {
let mut widest_label: f64 = 0.0;
for name in ¶ms.series_names {
let w = approx_text_width(name) * BOLD_WIDTH_SCALE;
if w > widest_label {
widest_label = w;
}
}
widest_label += 6.0;
let legend_x = (layout.width - 10.0) - widest_label;
let legend_height = 3.0 + 20.0 * params.series_names.len() as f64;
svg.push_str(&svg_rect_filled(
legend_x,
40.0,
widest_label,
legend_height,
&ChartColor::new(255, 255, 255),
));
svg.push_str(&svg_rect_stroked(
legend_x,
40.0,
widest_label,
legend_height,
&ChartColor::new(192, 192, 192),
));
for (t, name) in params.series_names.iter().enumerate() {
let color = &LINE_COLOURS[t % LINE_COLOURS.len()];
let text_x = legend_x + 3.0;
let text_y = 35.0 + 20.0 * (t as f64 + 1.0);
svg.push_str(&svg_text(text_x, text_y, name, color, true));
}
}
svg.push_str(svg_footer());
svg
}