use crate::js::chars::{drop_indents, trim, trim_start};
use crate::js::number::to_string;
#[derive(Clone, Debug, Default, PartialEq)]
pub(crate) struct BandAxis {
pub(crate) title: String,
pub(crate) categories: Vec<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct LinearAxis {
pub(crate) title: String,
pub(crate) min: f64,
pub(crate) max: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum XAxis {
Band(BandAxis),
Linear(LinearAxis),
}
impl XAxis {
pub(crate) fn title(&self) -> &str {
match self {
XAxis::Band(a) => &a.title,
XAxis::Linear(a) => &a.title,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct DataPoint {
pub(crate) value: f64,
pub(crate) label: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum PlotType {
Line,
Bar,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Plot {
pub(crate) kind: PlotType,
pub(crate) title: String,
pub(crate) data: Vec<(String, Option<f64>)>,
pub(crate) point_labels: Option<Vec<String>>,
}
#[derive(Clone, Debug)]
pub(crate) struct XyChartDb {
pub(crate) title: Option<String>,
pub(crate) acc_title: Option<String>,
pub(crate) acc_descr: Option<String>,
pub(crate) orientation: Option<&'static str>,
pub(crate) x_axis: XAxis,
pub(crate) y_axis: LinearAxis,
pub(crate) has_set_x_axis: bool,
pub(crate) has_set_y_axis: bool,
pub(crate) plots: Vec<Plot>,
}
impl Default for XyChartDb {
fn default() -> Self {
XyChartDb {
title: None,
acc_title: None,
acc_descr: None,
orientation: None,
x_axis: XAxis::Band(BandAxis::default()),
y_axis: LinearAxis {
title: String::new(),
min: f64::INFINITY,
max: f64::NEG_INFINITY,
},
has_set_x_axis: false,
has_set_y_axis: false,
plots: Vec::new(),
}
}
}
impl XyChartDb {
pub(crate) fn set_orientation(&mut self, orientation: &str) {
self.orientation = Some(if orientation == "horizontal" {
"horizontal"
} else {
"vertical"
});
}
pub(crate) fn set_x_axis_title(&mut self, title: &str) {
let title = trim(title).to_string();
match &mut self.x_axis {
XAxis::Band(a) => a.title = title,
XAxis::Linear(a) => a.title = title,
}
}
pub(crate) fn set_x_axis_range_data(&mut self, min: f64, max: f64) {
let title = self.x_axis.title().to_string();
self.x_axis = XAxis::Linear(LinearAxis { title, min, max });
self.has_set_x_axis = true;
}
pub(crate) fn set_x_axis_band(&mut self, categories: &[String]) {
self.x_axis = XAxis::Band(BandAxis {
title: self.x_axis.title().to_string(),
categories: categories.iter().map(|c| trim(c).to_string()).collect(),
});
self.has_set_x_axis = true;
}
pub(crate) fn set_y_axis_title(&mut self, title: &str) {
self.y_axis.title = trim(title).to_string();
}
pub(crate) fn set_y_axis_range_data(&mut self, min: f64, max: f64) {
self.y_axis.min = min;
self.y_axis.max = max;
self.has_set_y_axis = true;
}
fn plot_data(&mut self, mut values: Vec<f64>) -> Vec<(String, Option<f64>)> {
if values.is_empty() {
return Vec::new();
}
if !self.has_set_x_axis {
self.set_x_axis_range_data(1.0, values.len() as f64);
}
if let XAxis::Band(x) = &self.x_axis {
values.truncate(x.categories.len());
}
if !self.has_set_y_axis {
let (mut min, mut max) = (self.y_axis.min, self.y_axis.max);
for &value in &values {
if value < min {
min = value;
}
if value > max {
max = value;
}
}
self.y_axis.min = min;
self.y_axis.max = max;
}
let x = match &self.x_axis {
XAxis::Band(x) => {
return x
.categories
.iter()
.enumerate()
.map(|(i, c)| (c.clone(), values.get(i).copied()))
.collect();
}
XAxis::Linear(x) => x,
};
if values.len() == 1 {
return vec![(to_string(x.min), Some(values[0]))];
}
let step = (x.max - x.min) / (values.len() - 1) as f64;
values
.iter()
.enumerate()
.map(|(i, &v)| (to_string(x.min + i as f64 * step), Some(v)))
.collect()
}
fn add_plot(&mut self, kind: PlotType, title: &str, data: &[DataPoint]) {
let values = data.iter().map(|d| d.value).collect();
let mut plot = Plot {
kind,
title: trim(title).to_string(),
data: self.plot_data(values),
point_labels: None,
};
if kind == PlotType::Line && data.iter().any(|d| !trim(&d.label).is_empty()) {
plot.point_labels = Some(data.iter().map(|d| trim(&d.label).to_string()).collect());
}
self.plots.push(plot);
}
pub(crate) fn set_line_data(&mut self, title: &str, data: &[DataPoint]) {
self.add_plot(PlotType::Line, title, data);
}
pub(crate) fn set_bar_data(&mut self, title: &str, data: &[DataPoint]) {
self.add_plot(PlotType::Bar, title, data);
}
pub(crate) fn set_diagram_title(&mut self, text: &str) {
self.title = Some(text.to_string());
}
pub(crate) fn set_acc_title(&mut self, text: &str) {
self.acc_title = Some(trim_start(text).to_string());
}
pub(crate) fn set_acc_description(&mut self, text: &str) {
self.acc_descr = Some(drop_indents(text));
}
}