#[macro_export]
macro_rules! complot {
($filename:expr) => {
Some(complot::Config::new().filename($filename))
};
($filename:expr, xlabel=$xlabel:expr) => {
Some(
complot::Config::new()
.filename($filename)
.xaxis(complot::Axis::new().label($xlabel)),
)
};
($filename:expr, ylabel=$ylabel:expr) => {
Some(
complot::Config::new()
.filename($filename)
.yaxis(complot::Axis::new().label($ylabel)),
)
};
($filename:expr, xlabel=$xlabel:expr, ylabel=$ylabel:expr) => {
Some(
complot::Config::new()
.filename($filename)
.xaxis(complot::Axis::new().label($xlabel))
.yaxis(complot::Axis::new().label($ylabel)),
)
};
($filename:expr, xlabel=$xlabel:expr, ylabel=$ylabel:expr, title=$title:expr) => {
Some(
complot::Config::new()
.filename($filename)
.xaxis(complot::Axis::new().label($xlabel))
.yaxis(complot::Axis::new().label($ylabel))
.title($title),
)
};
}
mod line;
pub use line::{LinLog, LogLin, LogLog, Plot};
mod scatter;
pub use scatter::Scatter;
use std::ops::Range;
mod combo;
pub mod tri;
pub use combo::{Combo, Complot, Kind};
use plotters::{coord::Shift, prelude::*};
mod heatmap;
pub use heatmap::Heatmap;
#[cfg(feature = "svg")]
pub fn canvas(filename: &str, size: (u32, u32)) -> DrawingArea<SVGBackend, Shift> {
let plot = SVGBackend::new(filename, size).into_drawing_area();
plot.fill(&WHITE).unwrap();
plot
}
#[cfg(feature = "png")]
pub fn canvas(filename: &str, size: (u32, u32)) -> DrawingArea<BitMapBackend, Shift> {
let plot = BitMapBackend::new(filename, size).into_drawing_area();
plot.fill(&WHITE).unwrap();
plot
}
#[derive(Default, Clone, Debug)]
pub struct Axis {
label: Option<String>,
range: Option<Range<f64>>,
}
impl Axis {
pub fn new() -> Self {
Default::default()
}
pub fn label<S>(self, label: S) -> Self
where
S: Into<String>,
{
Self {
label: Some(label.into()),
..self
}
}
pub fn range(self, range: Range<f64>) -> Self {
Self {
range: Some(range),
..self
}
}
}
#[derive(Clone, Debug)]
pub struct Colorbar {
cmap: colorous::Gradient,
label: Option<String>,
range: Option<Range<f64>>,
}
impl Default for Colorbar {
fn default() -> Self {
Self {
cmap: colorous::VIRIDIS,
label: None,
range: None,
}
}
}
#[derive(Clone, Debug)]
pub struct Config {
filename: Option<String>,
title: Option<String>,
xaxis: Axis,
yaxis: Axis,
cmap: colorous::Gradient,
cmap_minmax: Option<(f64, f64)>,
colorbar: Option<Colorbar>,
osf: usize,
legend: Option<Vec<String>>,
}
impl Default for Config {
fn default() -> Self {
Self {
filename: Default::default(),
title: None,
xaxis: Default::default(),
yaxis: Default::default(),
cmap: colorous::VIRIDIS,
cmap_minmax: None,
colorbar: None,
osf: 2,
legend: None,
}
}
}
impl Config {
pub fn new() -> Self {
Default::default()
}
pub fn filename<T>(self, filename: T) -> Self
where
T: Into<String>,
{
Self {
filename: Some(filename.into()),
..self
}
}
pub fn title<S>(self, title: S) -> Self
where
S: Into<String>,
{
Self {
title: Some(title.into()),
..self
}
}
pub fn legend<S: Into<String>>(self, legend: Vec<S>) -> Self {
Self {
legend: Some(
legend
.into_iter()
.map(|x| x.into())
.collect::<Vec<String>>(),
),
..self
}
}
pub fn over_sampling_factor(self, osf: usize) -> Self {
Self { osf, ..self }
}
pub fn cmap_minmax(self, cmap_minmax: (f64, f64)) -> Self {
Self {
cmap_minmax: Some(cmap_minmax),
..self
}
}
pub fn xaxis(self, xaxis: Axis) -> Self {
Self { xaxis, ..self }
}
pub fn yaxis(self, yaxis: Axis) -> Self {
Self { yaxis, ..self }
}
pub fn axes(self, axis: Axis) -> Self {
Self {
xaxis: axis.clone(),
yaxis: axis,
..self
}
}
pub fn with_colorbar(self) -> Self {
if self.colorbar.is_none() {
Self {
colorbar: Default::default(),
..self
}
} else {
self
}
}
pub fn auto_range(&mut self, iters: Vec<&[(f64, Vec<f64>)]>) -> &mut Self {
let mut xrange = f64::INFINITY..f64::NEG_INFINITY;
let mut yrange = f64::INFINITY..f64::NEG_INFINITY;
for iter in &iters {
let xy = *iter;
let (it_xrange, it_yrange) = Plot::xy_range(&xy);
xrange.start = xrange.start.min(it_xrange.start);
xrange.end = xrange.end.max(it_xrange.end);
yrange.start = yrange.start.min(it_yrange.start);
yrange.end = yrange.end.max(it_yrange.end);
}
self.xaxis = self.xaxis.clone().range(xrange);
self.yaxis = self.yaxis.clone().range(yrange);
self
}
}
trait Utils {
fn xy_max(data: &[(f64, Vec<f64>)]) -> (f64, f64) {
data.iter().cloned().fold(
(f64::NEG_INFINITY, f64::NEG_INFINITY),
|(fx, fy), (x, y)| {
(
fx.max(x),
fy.max(y.iter().cloned().fold(f64::NEG_INFINITY, |fy, y| fy.max(y))),
)
},
)
}
fn xy_min(data: &[(f64, Vec<f64>)]) -> (f64, f64) {
data.iter()
.cloned()
.fold((f64::INFINITY, f64::INFINITY), |(fx, fy), (x, y)| {
(
fx.min(x),
fy.min(y.iter().cloned().fold(f64::INFINITY, |fy, y| fy.min(y))),
)
})
}
fn xy_range(data: &[(f64, Vec<f64>)]) -> (Range<f64>, Range<f64>) {
let (x_max, y_max) = Self::xy_max(data);
let (x_min, y_min) = Self::xy_min(data);
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]
);
(x_min..x_max, y_min..y_max)
}
}