use malevich::{Line, Plot, Points};
use crate::args::{Args, Command};
use crate::input::Table;
use crate::series::{self, Series};
pub struct Built {
pub plot: Plot<'static>,
pub unparsed: usize,
}
pub fn build(args: &Args, table: &Table) -> Built {
let (plot, unparsed) = match args.command {
Command::Line => value_plot(args, table, Kind::Line),
Command::Scatter => value_plot(args, table, Kind::Scatter),
Command::Hist => hist_plot(table, args.bins),
Command::Bar => bar_plot(table),
Command::Count => count_plot(table),
Command::Density => distribution(table, malevich::density),
Command::Ecdf => distribution(table, malevich::ecdf),
Command::Box => box_plot(table),
Command::Violin => violin_plot(table),
Command::Hist2d => hist2d_plot(args, table),
Command::Heatmap => heatmap_plot(table),
};
Built {
plot: furniture(plot, args),
unparsed,
}
}
#[derive(Clone, Copy)]
enum Kind {
Line,
Scatter,
}
fn value_plot(args: &Args, table: &Table, kind: Kind) -> (Plot<'static>, usize) {
let fmt = series::resolve_fmt(table, args.fmt);
let data = series::dataset(table, fmt, args.time_x);
let mut plot = Plot::new();
for series in data.series {
plot = layer(plot, series, kind);
}
(plot, data.unparsed)
}
fn layer(plot: Plot<'static>, series: Series, kind: Kind) -> Plot<'static> {
let Series { x, y, label } = series;
match (kind, x) {
(Kind::Line, Some(x)) => plot.layer(named(Line::xy(x, y), label, Line::label)),
(Kind::Line, None) => plot.layer(named(Line::y(y), label, Line::label)),
(Kind::Scatter, Some(x)) => plot.layer(named(Points::xy(x, y), label, Points::label)),
(Kind::Scatter, None) => plot.layer(named(Points::y(y), label, Points::label)),
}
}
fn named<M>(mark: M, label: Option<String>, set: impl FnOnce(M, String) -> M) -> M {
match label {
Some(text) => set(mark, text),
None => mark,
}
}
fn hist_plot(table: &Table, bins: Option<usize>) -> (Plot<'static>, usize) {
let (values, unparsed) = series::flatten(table);
let plot = match bins {
None => malevich::hist(values),
Some(count) => binned(&values, count),
};
(plot, unparsed)
}
fn binned(values: &[f64], count: usize) -> Plot<'static> {
use malevich::Bars;
use malevich::stat::Bins;
let (min, max) = values
.iter()
.filter(|value| value.is_finite())
.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), &value| {
(lo.min(value), hi.max(value))
});
if !min.is_finite() {
return Plot::new();
}
let (start, width) = if min == max {
(min - 0.5, 1.0)
} else {
(min, (max - min) / count as f64)
};
let mut histogram = Bins::new(start, width, count);
for &value in values {
histogram.add(value);
}
let counts: Vec<f64> = histogram.counts().iter().map(|&c| c as f64).collect();
Plot::new().layer(Bars::spans(histogram.start(), histogram.width(), counts))
}
fn distribution(table: &Table, preset: fn(Vec<f64>) -> Plot<'static>) -> (Plot<'static>, usize) {
let (values, unparsed) = series::flatten(table);
(preset(values), unparsed)
}
fn box_plot(table: &Table) -> (Plot<'static>, usize) {
let (categories, groups, unparsed) = series::groups(table);
(malevich::box_plot(categories, groups), unparsed)
}
fn violin_plot(table: &Table) -> (Plot<'static>, usize) {
let (categories, groups, unparsed) = series::groups(table);
(malevich::violin(categories, groups), unparsed)
}
fn hist2d_plot(args: &Args, table: &Table) -> (Plot<'static>, usize) {
let (x, y, unparsed) = series::xy(table, args.time_x);
(malevich::hist2d(x, y), unparsed)
}
fn heatmap_plot(table: &Table) -> (Plot<'static>, usize) {
let (columns, values, unparsed) = series::matrix(table);
if columns == 0 {
return (Plot::new(), unparsed);
}
(malevich::heatmap(columns, values), unparsed)
}
fn bar_plot(table: &Table) -> (Plot<'static>, usize) {
let (labels, values, unparsed) = series::labeled_values(table);
(malevich::bar(labels, values), unparsed)
}
fn count_plot(table: &Table) -> (Plot<'static>, usize) {
let (labels, values): (Vec<String>, Vec<f64>) = series::counts(table).into_iter().unzip();
(malevich::bar(labels, values), 0)
}
fn furniture(mut plot: Plot<'static>, args: &Args) -> Plot<'static> {
if let Some(title) = &args.title {
plot = plot.title(title);
}
if let Some(xlabel) = &args.xlabel {
plot = plot.x_label(xlabel);
}
if let Some(ylabel) = &args.ylabel {
plot = plot.y_label(ylabel);
}
if let Some((lo, hi)) = args.xlim {
plot = plot.x_domain(lo, hi);
}
if let Some((lo, hi)) = args.ylim {
plot = plot.y_domain(lo, hi);
}
if args.time_x {
plot = plot.time_x();
}
if args.log_x {
plot = plot.log_x();
}
if args.log_y {
plot = plot.log_y();
}
plot
}