use crate::{canvas, Axis, Config, Utils};
use colorous;
use plotters::prelude::*;
use std::{iter::FromIterator, path::Path};
pub struct Plot;
impl Utils for Plot {}
impl FromIterator<(f64, Vec<f64>)> for Plot {
fn from_iter<I: IntoIterator<Item = (f64, Vec<f64>)>>(iter: I) -> Self {
let filename = "complot-plot";
let path = if cfg!(feature = "png") {
Path::new(filename).with_extension("png")
} else {
Path::new(filename).with_extension("svg")
};
let fig = canvas(path.to_str().unwrap(), (768, 512));
let xy: Vec<_> = iter.into_iter().collect();
let (x_max, y_max) = Self::xy_max(&xy);
let (x_min, y_min) = Self::xy_min(&xy);
let mut chart = ChartBuilder::on(&fig)
.set_label_area_size(LabelAreaPosition::Left, 50)
.set_label_area_size(LabelAreaPosition::Bottom, 40)
.margin(10)
.build_cartesian_2d(x_min..x_max, y_min..y_max)
.unwrap();
chart.configure_mesh().draw().unwrap();
let n_y = xy[0].1.len();
let data: Vec<_> = xy
.into_iter()
.flat_map(|(x, y)| y.into_iter().map(|y| (x, y)).collect::<Vec<(f64, f64)>>())
.collect();
let mut colors = colorous::TABLEAU10.iter().cycle();
for k in 0..n_y {
let this_color = colors.next().unwrap().as_tuple();
chart
.draw_series(LineSeries::new(
data.iter().skip(k).step_by(n_y).cloned(),
RGBColor(this_color.0, this_color.1, this_color.2),
))
.unwrap();
}
Plot {}
}
}
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
impl<I: Iterator<Item = (f64, Vec<f64>)>> From<(I, Option<Config>)> for Plot {
fn from((iter, config): (I, Option<Config>)) -> Self {
fn inner<I: Iterator<Item = (f64, Vec<f64>)>>(
(iter, config): (I, Option<Config>),
) -> Result<()> {
let config = config.unwrap_or_default();
let filename = config
.filename
.unwrap_or_else(|| "complot-plot".to_string());
let path = if cfg!(feature = "png") {
Path::new(&filename).with_extension("png")
} else {
Path::new(&filename).with_extension("svg")
};
let fig = canvas(path.to_str().unwrap(), (768, 512));
fig.fill(&WHITE)?;
let xy: Vec<_> = iter.collect();
let (x_max, y_max) = Plot::xy_max(&xy);
let (x_min, y_min) = Plot::xy_min(&xy);
assert!(
x_max > x_min,
"Incorrect x axis range: {:?}",
[x_min, x_max]
);
assert!(
y_max > y_min,
"Incorrect y axis range: {:?}",
[y_min, y_max]
);
let mut chart = ChartBuilder::on(&fig)
.set_label_area_size(LabelAreaPosition::Left, 50)
.set_label_area_size(LabelAreaPosition::Bottom, 40)
.margin(10)
.build_cartesian_2d(x_min..x_max, y_min..y_max)?;
let mut mesh = chart.configure_mesh();
if let Some(value) = config.xaxis.label {
mesh.x_desc(value);
}
if let Some(value) = config.yaxis.label {
mesh.y_desc(value);
}
mesh.draw()?;
let n_y = xy[0].1.len();
let data: Vec<_> = xy
.into_iter()
.flat_map(|(x, y)| y.into_iter().map(|y| (x, y)).collect::<Vec<(f64, f64)>>())
.collect();
let mut colors = colorous::TABLEAU10.iter().cycle();
if let Some(legend) = config.legend {
for (k, key) in (0..n_y).zip(legend.into_iter()) {
let this_color = colors
.next()
.ok_or("Couldn't get another color.")?
.as_tuple();
let rgb = RGBColor(this_color.0, this_color.1, this_color.2);
chart
.draw_series(LineSeries::new(
data.iter().skip(k).step_by(n_y).cloned(),
&rgb,
))?
.label(key)
.legend(move |(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &rgb));
}
chart
.configure_series_labels()
.border_style(&BLACK)
.background_style(&WHITE.mix(0.8))
.position(SeriesLabelPosition::UpperRight)
.draw()
.unwrap();
} else {
for k in 0..n_y {
let this_color = colors
.next()
.ok_or("Couldn't get another color.")?
.as_tuple();
chart.draw_series(LineSeries::new(
data.iter().skip(k).step_by(n_y).cloned(),
RGBColor(this_color.0, this_color.1, this_color.2),
))?;
}
}
Ok(())
}
if let Err(e) = inner((iter, config)) {
println!("Complot failed in Plot: {}", e);
}
Plot {}
}
}
pub struct LogLog;
impl Utils for LogLog {}
impl<I: Iterator<Item = (f64, Vec<f64>)>> From<(I, Option<Config>)> for LogLog {
fn from((iter, config): (I, Option<Config>)) -> Self {
let _: Plot = (
iter.map(|(x, y)| {
(
x.log10(),
y.into_iter().map(|y| y.log10()).collect::<Vec<f64>>(),
)
}),
Some(match config {
Some(config) => {
match (&config.filename, &config.xaxis.label, &config.yaxis.label) {
(None, None, None) => config.filename("complot-linlog.svg"),
(None, None, Some(label)) => {
let log10_label = format!("log10( {} )", label);
config
.filename("complot-linlog.svg")
.yaxis(Axis::new().label(log10_label))
}
(None, Some(label), None) => {
let log10_label = format!("log10( {} )", label);
config
.filename("complot-linlog.svg")
.xaxis(Axis::new().label(log10_label))
}
(None, Some(xlabel), Some(ylabel)) => {
let log10_xlabel = format!("log10( {} )", xlabel);
let log10_ylabel = format!("log10( {} )", ylabel);
config
.filename("complot-linlog.svg")
.xaxis(Axis::new().label(log10_xlabel))
.yaxis(Axis::new().label(log10_ylabel))
}
(Some(_), None, None) => config,
(Some(_), None, Some(label)) => {
let log10_label = format!("log10( {} )", label);
config.yaxis(Axis::new().label(log10_label))
}
(Some(_), Some(label), None) => {
let log10_label = format!("log10( {} )", label);
config.xaxis(Axis::new().label(log10_label))
}
(Some(_), Some(xlabel), Some(ylabel)) => {
let log10_xlabel = format!("log10( {} )", xlabel);
let log10_ylabel = format!("log10( {} )", ylabel);
config
.xaxis(Axis::new().label(log10_xlabel))
.yaxis(Axis::new().label(log10_ylabel))
}
}
}
None => Config::new().filename("complot-linlog.svg"),
}),
)
.into();
LogLog
}
}
pub struct LogLin;
impl Utils for LogLin {}
impl<I: Iterator<Item = (f64, Vec<f64>)>> From<(I, Option<Config>)> for LogLin {
fn from((iter, config): (I, Option<Config>)) -> Self {
let _: Plot = (
iter.map(|(x, y)| (x.log10(), y)),
Some(match config {
Some(config) => match (&config.filename, &config.xaxis.label) {
(None, None) => config.filename("complot-linlog.svg"),
(None, Some(label)) => {
let log10_label = format!("log10( {} )", label);
config
.filename("complot-linlog.svg")
.yaxis(Axis::new().label(log10_label))
}
(Some(_), Some(label)) => {
let log10_label = format!("log10( {} )", label);
config.yaxis(Axis::new().label(log10_label))
}
(Some(_), None) => config,
},
None => Config::new().filename("complot-linlog.svg"),
}),
)
.into();
LogLin
}
}
pub struct LinLog;
impl Utils for LinLog {}
impl<I: Iterator<Item = (f64, Vec<f64>)>> From<(I, Option<Config>)> for LinLog {
fn from((iter, config): (I, Option<Config>)) -> Self {
let _: Plot = (
iter.map(|(x, y)| (x, y.into_iter().map(|y| y.log10()).collect::<Vec<f64>>())),
Some(match config {
Some(config) => match (&config.filename, &config.yaxis.label) {
(None, None) => config.filename("complot-linlog.svg"),
(None, Some(label)) => {
let log10_label = format!("log10( {} )", label);
config
.filename("complot-linlog.svg")
.yaxis(Axis::new().label(log10_label))
}
(Some(_), Some(label)) => {
let log10_label = format!("log10( {} )", label);
config.yaxis(Axis::new().label(log10_label))
}
(Some(_), None) => config,
},
None => Config::new().filename("complot-linlog.svg"),
}),
)
.into();
LinLog
}
}