use crate::Error;
use charts_rs::{LineChart, Series, svg_to_png};
use std::fs;
use std::ops::Range;
use std::path::Path;
pub(crate) fn new_line_chart(
series_list: Vec<Series>,
x_labels: Vec<String>,
width: u32,
height: u32,
title: &str,
) -> LineChart {
let mut chart = LineChart::new(series_list, x_labels);
chart.width = width as f32;
chart.height = height as f32;
chart.title_text = title.to_string();
chart.legend_show = Some(false);
chart.series_symbol = None;
chart.x_boundary_gap = Some(false);
chart
}
pub(crate) fn write_png(chart: &LineChart, path: &Path) -> Result<(), Error> {
let png = svg_to_png(&chart.svg()?)?;
if let Some(parent) = path.parent().filter(|p| !p.as_os_str().is_empty()) {
fs::create_dir_all(parent)?;
}
fs::write(path, png)?;
Ok(())
}
pub(crate) fn ensure_finite_and_non_empty(
values: impl IntoIterator<Item = f32>,
) -> Result<(), Error> {
let mut empty = true;
for value in values {
empty = false;
if !value.is_finite() {
return Err(Error::InvalidData(format!(
"input contains non-finite value {value}"
)));
}
}
if empty {
return Err(Error::InvalidData("input is empty".to_string()));
}
Ok(())
}
pub(crate) fn ensure_valid_y_range(range: &Range<f32>) -> Result<(), Error> {
if !range.start.is_finite() || !range.end.is_finite() || range.start >= range.end {
return Err(Error::InvalidData(format!(
"y range {range:?} must be finite and non-empty"
)));
}
Ok(())
}
pub(crate) fn set_y_range(chart: &mut LineChart, range: &Range<f32>) {
let min = if range.start > 0.0 {
range.start.next_down()
} else {
range.start
};
chart.y_axis_configs[0].axis_min = Some(min);
chart.y_axis_configs[0].axis_max = Some(range.end.next_up());
}
#[cfg(test)]
pub(crate) fn numeric_labels(svg: &str) -> Vec<f32> {
svg.split("<text")
.skip(1)
.filter_map(|s| s.split_once('>')?.1.split_once("</text>"))
.filter_map(|(label, _)| label.trim().parse().ok())
.collect()
}