aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Kotur <qtr@google.com>2020-12-21 17:28:14 +0100
committerJakub Kotur <qtr@google.com>2021-03-05 15:07:06 +0100
commit835fea47b902cc9ae1f6283bc6e107f0cd83734e (patch)
treee0f621ccc416ebc1c69588afba361fef31b1ae2e
parent790fbb964d669b370a0017b6747f3b9f13a04af9 (diff)
downloadcriterion-plot-835fea47b902cc9ae1f6283bc6e107f0cd83734e.tar.gz
Initial import of criterion-plot-0.4.3.
Bug: 155309706 Change-Id: Ia9cfbc3f7d52994d45a3113e5bdfefa9733a80c8
-rw-r--r--.cargo_vcs_info.json5
-rwxr-xr-x.gitignore3
-rwxr-xr-xCONTRIBUTING.md1
-rw-r--r--Cargo.toml47
-rwxr-xr-xCargo.toml.orig26
-rwxr-xr-xREADME.md38
-rwxr-xr-xsrc/axis.rs198
-rwxr-xr-xsrc/candlestick.rs151
-rwxr-xr-xsrc/curve.rs270
-rwxr-xr-xsrc/data.rs174
-rwxr-xr-xsrc/display.rs139
-rwxr-xr-xsrc/errorbar.rs290
-rwxr-xr-xsrc/filledcurve.rs140
-rwxr-xr-xsrc/grid.rs46
-rwxr-xr-xsrc/key.rs218
-rwxr-xr-xsrc/lib.rs1090
-rwxr-xr-xsrc/map.rs168
-rwxr-xr-xsrc/prelude.rs13
-rwxr-xr-xsrc/proxy.rs47
-rwxr-xr-xsrc/traits.rs35
20 files changed, 3099 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..931fc83
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,5 @@
+{
+ "git": {
+ "sha1": "3fcbcd237e14306d102f2ea2f1285cd6285e086c"
+ }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100755
index 0000000..726c7b6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*.bk
+Cargo.lock
+target/*
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100755
index 0000000..44fcc63
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1 @@
+../CONTRIBUTING.md \ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..7ae1c42
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,47 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "criterion-plot"
+version = "0.4.3"
+authors = ["Jorge Aparicio <japaricious@gmail.com>", "Brook Heisler <brookheisler@gmail.com>"]
+description = "Criterion's plotting library"
+readme = "README.md"
+keywords = ["plotting", "gnuplot", "criterion"]
+categories = ["visualization"]
+license = "MIT/Apache-2.0"
+repository = "https://github.com/bheisler/criterion.rs"
+[dependencies.cast]
+version = "0.2"
+
+[dependencies.itertools]
+version = "0.9"
+[dev-dependencies.itertools-num]
+version = "0.1"
+
+[dev-dependencies.num-complex]
+version = "0.2"
+features = ["std"]
+default-features = false
+
+[dev-dependencies.rand]
+version = "0.4"
+[badges.appveyor]
+id = "4255ads9ctpupcl2"
+repository = "bheisler/criterion.rs"
+
+[badges.maintenance]
+status = "looking-for-maintainer"
+
+[badges.travis-ci]
+repository = "bheisler/criterion.rs"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100755
index 0000000..162931a
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,26 @@
+[package]
+authors = ["Jorge Aparicio <japaricious@gmail.com>", "Brook Heisler <brookheisler@gmail.com>"]
+name = "criterion-plot"
+version = "0.4.3"
+edition = "2018"
+
+description = "Criterion's plotting library"
+repository = "https://github.com/bheisler/criterion.rs"
+readme = "README.md"
+keywords = ["plotting", "gnuplot", "criterion"]
+categories = ["visualization"]
+license = "MIT/Apache-2.0"
+
+[dependencies]
+cast = "0.2"
+itertools = "0.9"
+
+[dev-dependencies]
+itertools-num = "0.1"
+num-complex = { version = "0.2", default-features = false, features = ["std"] }
+rand = "0.4"
+
+[badges]
+travis-ci = { repository = "bheisler/criterion.rs" }
+appveyor = { repository = "bheisler/criterion.rs", id = "4255ads9ctpupcl2" }
+maintenance = { status = "looking-for-maintainer" }
diff --git a/README.md b/README.md
new file mode 100755
index 0000000..6c403e0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,38 @@
+# `criterion-plot`
+
+> Graphing sub-crate of [Criterion.rs].
+
+This is an unstable implementation detail of [Criterion.rs]. Anything may change
+at any time with no warning, including the public API. For further information,
+see [Criterion.rs].
+
+`criterion-plot` is currently looking for a new maintainer. See
+[this thread](https://users.rust-lang.org/t/call-for-maintainers-criterion-plot/24413) for details.
+
+## License
+
+This project is licensed under either of
+
+* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+ ([LICENSE-APACHE](LICENSE-APACHE))
+
+* [MIT License](http://opensource.org/licenses/MIT)
+ ([LICENSE-MIT](LICENSE-MIT))
+
+at your option.
+
+## Contributing
+
+We welcome all people who want to contribute.
+Please see the [contributing instructions] for more information.
+
+Contributions in any form (issues, pull requests, etc.) to this project
+must adhere to Rust's [Code of Conduct].
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
+be dual licensed as above, without any additional terms or conditions.
+
+[Code of Conduct]: https://www.rust-lang.org/en-US/conduct.html
+[Criterion.rs]: https://github.com/bheisler/criterion.rs
+[contributing instructions]: CONTRIBUTING.md
diff --git a/src/axis.rs b/src/axis.rs
new file mode 100755
index 0000000..97fd0cb
--- /dev/null
+++ b/src/axis.rs
@@ -0,0 +1,198 @@
+//! Coordinate axis
+
+use std::borrow::Cow;
+use std::iter::IntoIterator;
+
+use crate::map;
+use crate::traits::{Configure, Data, Set};
+use crate::{
+ grid, Axis, Default, Display, Grid, Label, Range, Scale, ScaleFactor, Script, TicLabels,
+};
+
+/// Properties of the coordinate axes
+#[derive(Clone)]
+pub struct Properties {
+ grids: map::grid::Map<grid::Properties>,
+ hidden: bool,
+ label: Option<Cow<'static, str>>,
+ logarithmic: bool,
+ range: Option<(f64, f64)>,
+ scale_factor: f64,
+ tics: Option<String>,
+}
+
+impl Default for Properties {
+ fn default() -> Properties {
+ Properties {
+ grids: map::grid::Map::new(),
+ hidden: false,
+ label: None,
+ logarithmic: false,
+ range: None,
+ scale_factor: 1.,
+ tics: None,
+ }
+ }
+}
+
+impl Properties {
+ /// Hides the axis
+ ///
+ /// **Note** The `TopX` and `RightY` axes are hidden by default
+ pub fn hide(&mut self) -> &mut Properties {
+ self.hidden = true;
+ self
+ }
+
+ /// Makes the axis visible
+ ///
+ /// **Note** The `BottomX` and `LeftY` axes are visible by default
+ pub fn show(&mut self) -> &mut Properties {
+ self.hidden = false;
+ self
+ }
+}
+
+impl Configure<Grid> for Properties {
+ type Properties = grid::Properties;
+
+ /// Configures the gridlines
+ fn configure<F>(&mut self, grid: Grid, configure: F) -> &mut Properties
+ where
+ F: FnOnce(&mut grid::Properties) -> &mut grid::Properties,
+ {
+ if self.grids.contains_key(grid) {
+ configure(self.grids.get_mut(grid).unwrap());
+ } else {
+ let mut properties = Default::default();
+ configure(&mut properties);
+ self.grids.insert(grid, properties);
+ }
+
+ self
+ }
+}
+
+impl Set<Label> for Properties {
+ /// Attaches a label to the axis
+ fn set(&mut self, label: Label) -> &mut Properties {
+ self.label = Some(label.0);
+ self
+ }
+}
+
+impl Set<Range> for Properties {
+ /// Changes the range of the axis that will be shown
+ ///
+ /// **Note** All axes are auto-scaled by default
+ fn set(&mut self, range: Range) -> &mut Properties {
+ self.hidden = false;
+
+ match range {
+ Range::Auto => self.range = None,
+ Range::Limits(low, high) => self.range = Some((low, high)),
+ }
+
+ self
+ }
+}
+
+impl Set<Scale> for Properties {
+ /// Sets the scale of the axis
+ ///
+ /// **Note** All axes use a linear scale by default
+ fn set(&mut self, scale: Scale) -> &mut Properties {
+ self.hidden = false;
+
+ match scale {
+ Scale::Linear => self.logarithmic = false,
+ Scale::Logarithmic => self.logarithmic = true,
+ }
+
+ self
+ }
+}
+
+impl Set<ScaleFactor> for Properties {
+ /// Changes the *scale factor* of the axis.
+ ///
+ /// All the data plotted against this axis will have its corresponding coordinate scaled with
+ /// this factor before being plotted.
+ ///
+ /// **Note** The default scale factor is `1`.
+ fn set(&mut self, factor: ScaleFactor) -> &mut Properties {
+ self.scale_factor = factor.0;
+
+ self
+ }
+}
+
+impl<P, L> Set<TicLabels<P, L>> for Properties
+where
+ L: IntoIterator,
+ L::Item: AsRef<str>,
+ P: IntoIterator,
+ P::Item: Data,
+{
+ /// Attaches labels to the tics of an axis
+ fn set(&mut self, tics: TicLabels<P, L>) -> &mut Properties {
+ let TicLabels { positions, labels } = tics;
+
+ let pairs = positions
+ .into_iter()
+ .zip(labels.into_iter())
+ .map(|(pos, label)| format!("'{}' {}", label.as_ref(), pos.f64()))
+ .collect::<Vec<_>>();
+
+ if pairs.is_empty() {
+ self.tics = None
+ } else {
+ self.tics = Some(pairs.join(", "));
+ }
+
+ self
+ }
+}
+
+impl<'a> Script for (Axis, &'a Properties) {
+ fn script(&self) -> String {
+ let &(axis, properties) = self;
+ let axis_ = axis.display();
+
+ let mut script = if properties.hidden {
+ return format!("unset {}tics\n", axis_);
+ } else {
+ format!("set {}tics nomirror ", axis_)
+ };
+
+ if let Some(ref tics) = properties.tics {
+ script.push_str(&format!("({})", tics))
+ }
+
+ script.push('\n');
+
+ if let Some(ref label) = properties.label {
+ script.push_str(&format!("set {}label '{}'\n", axis_, label))
+ }
+
+ if let Some((low, high)) = properties.range {
+ script.push_str(&format!("set {}range [{}:{}]\n", axis_, low, high))
+ }
+
+ if properties.logarithmic {
+ script.push_str(&format!("set logscale {}\n", axis_));
+ }
+
+ for (grid, properties) in properties.grids.iter() {
+ script.push_str(&(axis, grid, properties).script());
+ }
+
+ script
+ }
+}
+
+impl crate::ScaleFactorTrait for Properties {
+ fn scale_factor(&self) -> f64 {
+ self.scale_factor
+ }
+}
diff --git a/src/candlestick.rs b/src/candlestick.rs
new file mode 100755
index 0000000..e0a5cbe
--- /dev/null
+++ b/src/candlestick.rs
@@ -0,0 +1,151 @@
+//! "Candlestick" plots
+
+use std::borrow::Cow;
+use std::iter::IntoIterator;
+
+use crate::data::Matrix;
+use crate::traits::{self, Data, Set};
+use crate::{Color, Default, Display, Figure, Label, LineType, LineWidth, Plot, Script};
+
+/// Properties common to candlestick plots
+pub struct Properties {
+ color: Option<Color>,
+ label: Option<Cow<'static, str>>,
+ line_type: LineType,
+ linewidth: Option<f64>,
+}
+
+impl Default for Properties {
+ fn default() -> Properties {
+ Properties {
+ color: None,
+ label: None,
+ line_type: LineType::Solid,
+ linewidth: None,
+ }
+ }
+}
+
+impl Script for Properties {
+ fn script(&self) -> String {
+ let mut script = String::from("with candlesticks ");
+
+ script.push_str(&format!("lt {} ", self.line_type.display()));
+
+ if let Some(lw) = self.linewidth {
+ script.push_str(&format!("lw {} ", lw))
+ }
+
+ if let Some(color) = self.color {
+ script.push_str(&format!("lc rgb '{}' ", color.display()));
+ }
+
+ if let Some(ref label) = self.label {
+ script.push_str("title '");
+ script.push_str(label);
+ script.push('\'')
+ } else {
+ script.push_str("notitle")
+ }
+
+ script
+ }
+}
+
+impl Set<Color> for Properties {
+ /// Sets the line color
+ fn set(&mut self, color: Color) -> &mut Properties {
+ self.color = Some(color);
+ self
+ }
+}
+
+impl Set<Label> for Properties {
+ /// Sets the legend label
+ fn set(&mut self, label: Label) -> &mut Properties {
+ self.label = Some(label.0);
+ self
+ }
+}
+
+impl Set<LineType> for Properties {
+ /// Changes the line type
+ ///
+ /// **Note** By default `Solid` lines are used
+ fn set(&mut self, lt: LineType) -> &mut Properties {
+ self.line_type = lt;
+ self
+ }
+}
+
+impl Set<LineWidth> for Properties {
+ /// Changes the width of the line
+ ///
+ /// # Panics
+ ///
+ /// Panics if `width` is a non-positive value
+ fn set(&mut self, lw: LineWidth) -> &mut Properties {
+ let lw = lw.0;
+
+ assert!(lw > 0.);
+
+ self.linewidth = Some(lw);
+ self
+ }
+}
+
+/// A candlestick consists of a box and two whiskers that extend beyond the box
+pub struct Candlesticks<X, WM, BM, BH, WH> {
+ /// X coordinate of the candlestick
+ pub x: X,
+ /// Y coordinate of the end point of the bottom whisker
+ pub whisker_min: WM,
+ /// Y coordinate of the bottom of the box
+ pub box_min: BM,
+ /// Y coordinate of the top of the box
+ pub box_high: BH,
+ /// Y coordinate of the end point of the top whisker
+ pub whisker_high: WH,
+}
+
+impl<X, WM, BM, BH, WH> traits::Plot<Candlesticks<X, WM, BM, BH, WH>> for Figure
+where
+ BH: IntoIterator,
+ BH::Item: Data,
+ BM: IntoIterator,
+ BM::Item: Data,
+ WH: IntoIterator,
+ WH::Item: Data,
+ WM: IntoIterator,
+ WM::Item: Data,
+ X: IntoIterator,
+ X::Item: Data,
+{
+ type Properties = Properties;
+
+ fn plot<F>(
+ &mut self,
+ candlesticks: Candlesticks<X, WM, BM, BH, WH>,
+ configure: F,
+ ) -> &mut Figure
+ where
+ F: FnOnce(&mut Properties) -> &mut Properties,
+ {
+ let (x_factor, y_factor) = crate::scale_factor(&self.axes, crate::Axes::BottomXLeftY);
+ let Candlesticks {
+ x,
+ whisker_min,
+ box_min,
+ box_high,
+ whisker_high,
+ } = candlesticks;
+
+ let data = Matrix::new(
+ izip!(x, box_min, whisker_min, whisker_high, box_high),
+ (x_factor, y_factor, y_factor, y_factor, y_factor),
+ );
+ self.plots
+ .push(Plot::new(data, configure(&mut Default::default())));
+ self
+ }
+}
diff --git a/src/curve.rs b/src/curve.rs
new file mode 100755
index 0000000..bbddeff
--- /dev/null
+++ b/src/curve.rs
@@ -0,0 +1,270 @@
+//! Simple "curve" like plots
+
+use std::borrow::Cow;
+use std::iter::IntoIterator;
+
+use crate::data::Matrix;
+use crate::traits::{self, Data, Set};
+use crate::{
+ Axes, Color, CurveDefault, Display, Figure, Label, LineType, LineWidth, Plot, PointSize,
+ PointType, Script,
+};
+
+/// Properties common to simple "curve" like plots
+pub struct Properties {
+ axes: Option<Axes>,
+ color: Option<Color>,
+ label: Option<Cow<'static, str>>,
+ line_type: LineType,
+ linewidth: Option<f64>,
+ point_type: Option<PointType>,
+ point_size: Option<f64>,
+ style: Style,
+}
+
+impl CurveDefault<Style> for Properties {
+ fn default(style: Style) -> Properties {
+ Properties {
+ axes: None,
+ color: None,
+ label: None,
+ line_type: LineType::Solid,
+ linewidth: None,
+ point_size: None,
+ point_type: None,
+ style,
+ }
+ }
+}
+
+impl Script for Properties {
+ fn script(&self) -> String {
+ let mut script = if let Some(axes) = self.axes {
+ format!("axes {} ", axes.display())
+ } else {
+ String::new()
+ };
+
+ script.push_str(&format!("with {} ", self.style.display()));
+ script.push_str(&format!("lt {} ", self.line_type.display()));
+
+ if let Some(lw) = self.linewidth {
+ script.push_str(&format!("lw {} ", lw))
+ }
+
+ if let Some(color) = self.color {
+ script.push_str(&format!("lc rgb '{}' ", color.display()))
+ }
+
+ if let Some(pt) = self.point_type {
+ script.push_str(&format!("pt {} ", pt.display()))
+ }
+
+ if let Some(ps) = self.point_size {
+ script.push_str(&format!("ps {} ", ps))
+ }
+
+ if let Some(ref label) = self.label {
+ script.push_str("title '");
+ script.push_str(label);
+ script.push('\'')
+ } else {
+ script.push_str("notitle")
+ }
+
+ script
+ }
+}
+
+impl Set<Axes> for Properties {
+ /// Select the axes to plot against
+ ///
+ /// **Note** By default, the `BottomXLeftY` axes are used
+ fn set(&mut self, axes: Axes) -> &mut Properties {
+ self.axes = Some(axes);
+ self
+ }
+}
+
+impl Set<Color> for Properties {
+ /// Sets the line color
+ fn set(&mut self, color: Color) -> &mut Properties {
+ self.color = Some(color);
+ self
+ }
+}
+
+impl Set<Label> for Properties {
+ /// Sets the legend label
+ fn set(&mut self, label: Label) -> &mut Properties {
+ self.label = Some(label.0);
+ self
+ }
+}
+
+impl Set<LineType> for Properties {
+ /// Changes the line type
+ ///
+ /// **Note** By default `Solid` lines are used
+ fn set(&mut self, lt: LineType) -> &mut Properties {
+ self.line_type = lt;
+ self
+ }
+}
+
+impl Set<LineWidth> for Properties {
+ /// Changes the width of the line
+ ///
+ /// # Panics
+ ///
+ /// Panics if `width` is a non-positive value
+ fn set(&mut self, lw: LineWidth) -> &mut Properties {
+ let lw = lw.0;
+
+ assert!(lw > 0.);
+
+ self.linewidth = Some(lw);
+ self
+ }
+}
+
+impl Set<PointSize> for Properties {
+ /// Changes the size of the points
+ ///
+ /// # Panics
+ ///
+ /// Panics if `size` is a non-positive value
+ fn set(&mut self, ps: PointSize) -> &mut Properties {
+ let ps = ps.0;
+
+ assert!(ps > 0.);
+
+ self.point_size = Some(ps);
+ self
+ }
+}
+
+impl Set<PointType> for Properties {
+ /// Changes the point type
+ fn set(&mut self, pt: PointType) -> &mut Properties {
+ self.point_type = Some(pt);
+ self
+ }
+}
+
+/// Types of "curve" plots
+pub enum Curve<X, Y> {
+ /// A minimally sized dot on each data point
+ Dots {
+ /// X coordinate of the data points
+ x: X,
+ /// Y coordinate of the data points
+ y: Y,
+ },
+ /// A vertical "impulse" on each data point
+ Impulses {
+ /// X coordinate of the data points
+ x: X,
+ /// Y coordinate of the data points
+ y: Y,
+ },
+ /// Line that joins the data points
+ Lines {
+ /// X coordinate of the data points
+ x: X,
+ /// Y coordinate of the data points
+ y: Y,
+ },
+ /// Line with a point on each data point
+ LinesPoints {
+ /// X coordinate of the data points
+ x: X,
+ /// Y coordinate of the data points
+ y: Y,
+ },
+ /// A point on each data point
+ Points {
+ /// X coordinate of the data points
+ x: X,
+ /// Y coordinate of the data points
+ y: Y,
+ },
+ /// An step `_|` between each data point
+ Steps {
+ /// X coordinate of the data points
+ x: X,
+ /// Y coordinate of the data points
+ y: Y,
+ },
+}
+
+impl<X, Y> Curve<X, Y> {
+ fn style(&self) -> Style {
+ match *self {
+ Curve::Dots { .. } => Style::Dots,
+ Curve::Impulses { .. } => Style::Impulses,
+ Curve::Lines { .. } => Style::Lines,
+ Curve::LinesPoints { .. } => Style::LinesPoints,
+ Curve::Points { .. } => Style::Points,
+ Curve::Steps { .. } => Style::Steps,
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+enum Style {
+ Dots,
+ Impulses,
+ Lines,
+ LinesPoints,
+ Points,
+ Steps,
+}
+
+impl Display<&'static str> for Style {
+ fn display(&self) -> &'static str {
+ match *self {
+ Style::Dots => "dots",
+ Style::Impulses => "impulses",
+ Style::Lines => "lines",
+ Style::LinesPoints => "linespoints",
+ Style::Points => "points",
+ Style::Steps => "steps",
+ }
+ }
+}
+
+impl<X, Y> traits::Plot<Curve<X, Y>> for Figure
+where
+ X: IntoIterator,
+ X::Item: Data,
+ Y: IntoIterator,
+ Y::Item: Data,
+{
+ type Properties = Properties;
+
+ fn plot<F>(&mut self, curve: Curve<X, Y>, configure: F) -> &mut Figure
+ where
+ F: FnOnce(&mut Properties) -> &mut Properties,
+ {
+ let style = curve.style();
+ let (x, y) = match curve {
+ Curve::Dots { x, y }
+ | Curve::Impulses { x, y }
+ | Curve::Lines { x, y }
+ | Curve::LinesPoints { x, y }
+ | Curve::Points { x, y }
+ | Curve::Steps { x, y } => (x, y),
+ };
+
+ let mut props = CurveDefault::default(style);
+ configure(&mut props);
+
+ let (x_factor, y_factor) =
+ crate::scale_factor(&self.axes, props.axes.unwrap_or(crate::Axes::BottomXLeftY));
+
+ let data = Matrix::new(izip!(x, y), (x_factor, y_factor));
+ self.plots.push(Plot::new(data, &props));
+ self
+ }
+}
diff --git a/src/data.rs b/src/data.rs
new file mode 100755
index 0000000..eb1f738
--- /dev/null
+++ b/src/data.rs
@@ -0,0 +1,174 @@
+#![allow(deprecated)]
+
+use std::mem;
+
+use cast::From as _0;
+
+use crate::traits::Data;
+
+macro_rules! impl_data {
+ ($($ty:ty),+) => {
+ $(
+ impl Data for $ty {
+ fn f64(self) -> f64 {
+ f64::cast(self)
+ }
+ }
+
+ impl<'a> Data for &'a $ty {
+ fn f64(self) -> f64 {
+ f64::cast(*self)
+ }
+ }
+ )+
+ }
+}
+
+impl_data!(f32, f64, i16, i32, i64, i8, isize, u16, u32, u64, u8, usize);
+
+#[derive(Clone)]
+pub struct Matrix {
+ bytes: Vec<u8>,
+ ncols: usize,
+ nrows: usize,
+}
+
+impl Matrix {
+ pub fn new<I>(rows: I, scale: <I::Item as Row>::Scale) -> Matrix
+ where
+ I: Iterator,
+ I::Item: Row,
+ {
+ let ncols = I::Item::ncols();
+ let bytes_per_row = ncols * mem::size_of::<f64>();
+ let mut bytes = Vec::with_capacity(rows.size_hint().0 * bytes_per_row);
+
+ let mut nrows = 0;
+ for row in rows {
+ nrows += 1;
+ row.append_to(&mut bytes, scale);
+ }
+
+ Matrix {
+ bytes,
+ ncols,
+ nrows,
+ }
+ }
+
+ pub fn bytes(&self) -> &[u8] {
+ &self.bytes
+ }
+
+ pub fn ncols(&self) -> usize {
+ self.ncols
+ }
+
+ pub fn nrows(&self) -> usize {
+ self.nrows
+ }
+}
+
+/// Data that can serve as a row of the data matrix
+pub trait Row {
+ /// Private
+ type Scale: Copy;
+
+ /// Append this row to a buffer
+ fn append_to(self, buffer: &mut Vec<u8>, scale: Self::Scale);
+ /// Number of columns of the row
+ fn ncols() -> usize;
+}
+
+fn write_f64(w: &mut impl std::io::Write, f: f64) -> std::io::Result<()> {
+ w.write_all(&f.to_bits().to_le_bytes())
+}
+
+impl<A, B> Row for (A, B)
+where
+ A: Data,
+ B: Data,
+{
+ type Scale = (f64, f64);
+
+ fn append_to(self, buffer: &mut Vec<u8>, scale: (f64, f64)) {
+ let (a, b) = self;
+
+ write_f64(buffer, a.f64() * scale.0).unwrap();
+ write_f64(buffer, b.f64() * scale.1).unwrap();
+ }
+
+ fn ncols() -> usize {
+ 2
+ }
+}
+
+impl<A, B, C> Row for (A, B, C)
+where
+ A: Data,
+ B: Data,
+ C: Data,
+{
+ type Scale = (f64, f64, f64);
+
+ fn append_to(self, buffer: &mut Vec<u8>, scale: (f64, f64, f64)) {
+ let (a, b, c) = self;
+
+ write_f64(buffer, a.f64() * scale.0).unwrap();
+ write_f64(buffer, b.f64() * scale.1).unwrap();
+ write_f64(buffer, c.f64() * scale.2).unwrap();
+ }
+
+ fn ncols() -> usize {
+ 3
+ }
+}
+
+impl<A, B, C, D> Row for (A, B, C, D)
+where
+ A: Data,
+ B: Data,
+ C: Data,
+ D: Data,
+{
+ type Scale = (f64, f64, f64, f64);
+
+ fn append_to(self, buffer: &mut Vec<u8>, scale: (f64, f64, f64, f64)) {
+ let (a, b, c, d) = self;
+
+ write_f64(buffer, a.f64() * scale.0).unwrap();
+ write_f64(buffer, b.f64() * scale.1).unwrap();
+ write_f64(buffer, c.f64() * scale.2).unwrap();
+ write_f64(buffer, d.f64() * scale.3).unwrap();
+ }
+
+ fn ncols() -> usize {
+ 4
+ }
+}
+
+impl<A, B, C, D, E> Row for (A, B, C, D, E)
+where
+ A: Data,
+ B: Data,
+ C: Data,
+ D: Data,
+ E: Data,
+{
+ type Scale = (f64, f64, f64, f64, f64);
+
+ #[cfg_attr(feature = "cargo-clippy", allow(clippy::many_single_char_names))]
+ fn append_to(self, buffer: &mut Vec<u8>, scale: (f64, f64, f64, f64, f64)) {
+ let (a, b, c, d, e) = self;
+
+ write_f64(buffer, a.f64() * scale.0).unwrap();
+ write_f64(buffer, b.f64() * scale.1).unwrap();
+ write_f64(buffer, c.f64() * scale.2).unwrap();
+ write_f64(buffer, d.f64() * scale.3).unwrap();
+ write_f64(buffer, e.f64() * scale.4).unwrap();
+ }
+
+ fn ncols() -> usize {
+ 5
+ }
+}
diff --git a/src/display.rs b/src/display.rs
new file mode 100755
index 0000000..8f6f1e1
--- /dev/null
+++ b/src/display.rs
@@ -0,0 +1,139 @@
+use std::borrow::Cow;
+
+use crate::key::{Horizontal, Justification, Order, Stacked, Vertical};
+use crate::{Axes, Axis, Color, Display, Grid, LineType, PointType, Terminal};
+
+impl Display<&'static str> for Axis {
+ fn display(&self) -> &'static str {
+ match *self {
+ Axis::BottomX => "x",
+ Axis::LeftY => "y",
+ Axis::RightY => "y2",
+ Axis::TopX => "x2",
+ }
+ }
+}
+
+impl Display<&'static str> for Axes {
+ fn display(&self) -> &'static str {
+ match *self {
+ Axes::BottomXLeftY => "x1y1",
+ Axes::BottomXRightY => "x1y2",
+ Axes::TopXLeftY => "x2y1",
+ Axes::TopXRightY => "x2y2",
+ }
+ }
+}
+
+impl Display<Cow<'static, str>> for Color {
+ fn display(&self) -> Cow<'static, str> {
+ match *self {
+ Color::Black => Cow::from("black"),
+ Color::Blue => Cow::from("blue"),
+ Color::Cyan => Cow::from("cyan"),
+ Color::DarkViolet => Cow::from("dark-violet"),
+ Color::ForestGreen => Cow::from("forest-green"),
+ Color::Gold => Cow::from("gold"),
+ Color::Gray => Cow::from("gray"),
+ Color::Green => Cow::from("green"),
+ Color::Magenta => Cow::from("magenta"),
+ Color::Red => Cow::from("red"),
+ Color::Rgb(r, g, b) => Cow::from(format!("#{:02x}{:02x}{:02x}", r, g, b)),
+ Color::White => Cow::from("white"),
+ Color::Yellow => Cow::from("yellow"),
+ }
+ }
+}
+
+impl Display<&'static str> for Grid {
+ fn display(&self) -> &'static str {
+ match *self {
+ Grid::Major => "",
+ Grid::Minor => "m",
+ }
+ }
+}
+
+impl Display<&'static str> for Horizontal {
+ fn display(&self) -> &'static str {
+ match *self {
+ Horizontal::Center => "center",
+ Horizontal::Left => "left",
+ Horizontal::Right => "right",
+ }
+ }
+}
+
+impl Display<&'static str> for Justification {
+ fn display(&self) -> &'static str {
+ match *self {
+ Justification::Left => "Left",
+ Justification::Right => "Right",
+ }
+ }
+}
+
+impl Display<&'static str> for LineType {
+ fn display(&self) -> &'static str {
+ match *self {
+ LineType::Dash => "2",
+ LineType::Dot => "3",
+ LineType::DotDash => "4",
+ LineType::DotDotDash => "5",
+ LineType::SmallDot => "0",
+ LineType::Solid => "1",
+ }
+ }
+}
+
+impl Display<&'static str> for Order {
+ fn display(&self) -> &'static str {
+ match *self {
+ Order::TextSample => "noreverse",
+ Order::SampleText => "reverse",
+ }
+ }
+}
+
+impl Display<&'static str> for PointType {
+ fn display(&self) -> &'static str {
+ match *self {
+ PointType::Circle => "6",
+ PointType::FilledCircle => "7",
+ PointType::FilledSquare => "5",
+ PointType::FilledTriangle => "9",
+ PointType::Plus => "1",
+ PointType::Square => "4",
+ PointType::Star => "3",
+ PointType::Triangle => "8",
+ PointType::X => "2",
+ }
+ }
+}
+
+impl Display<&'static str> for Stacked {
+ fn display(&self) -> &'static str {
+ match *self {
+ Stacked::Horizontally => "horizontal",
+ Stacked::Vertically => "vertical",
+ }
+ }
+}
+
+impl Display<&'static str> for Terminal {
+ fn display(&self) -> &'static str {
+ match *self {
+ Terminal::Svg => "svg dynamic",
+ }
+ }
+}
+
+impl Display<&'static str> for Vertical {
+ fn display(&self) -> &'static str {
+ match *self {
+ Vertical::Bottom => "bottom",
+ Vertical::Center => "center",
+ Vertical::Top => "top",
+ }
+ }
+}
diff --git a/src/errorbar.rs b/src/errorbar.rs
new file mode 100755
index 0000000..7efd23e
--- /dev/null
+++ b/src/errorbar.rs
@@ -0,0 +1,290 @@
+//! Error bar plots
+
+use std::borrow::Cow;
+use std::iter::IntoIterator;
+
+use crate::data::Matrix;
+use crate::traits::{self, Data, Set};
+use crate::{
+ Color, Display, ErrorBarDefault, Figure, Label, LineType, LineWidth, Plot, PointSize,
+ PointType, Script,
+};
+
+/// Properties common to error bar plots
+pub struct Properties {
+ color: Option<Color>,
+ label: Option<Cow<'static, str>>,
+ line_type: LineType,
+ linewidth: Option<f64>,
+ point_size: Option<f64>,
+ point_type: Option<PointType>,
+ style: Style,
+}
+
+impl ErrorBarDefault<Style> for Properties {
+ fn default(style: Style) -> Properties {
+ Properties {
+ color: None,
+ label: None,
+ line_type: LineType::Solid,
+ linewidth: None,
+ point_type: None,
+ point_size: None,
+ style,
+ }
+ }
+}
+
+impl Script for Properties {
+ fn script(&self) -> String {
+ let mut script = format!("with {} ", self.style.display());
+
+ script.push_str(&format!("lt {} ", self.line_type.display()));
+
+ if let Some(lw) = self.linewidth {
+ script.push_str(&format!("lw {} ", lw))
+ }
+
+ if let Some(color) = self.color {
+ script.push_str(&format!("lc rgb '{}' ", color.display()))
+ }
+
+ if let Some(pt) = self.point_type {
+ script.push_str(&format!("pt {} ", pt.display()))
+ }
+
+ if let Some(ps) = self.point_size {
+ script.push_str(&format!("ps {} ", ps))
+ }
+
+ if let Some(ref label) = self.label {
+ script.push_str("title '");
+ script.push_str(label);
+ script.push('\'')
+ } else {
+ script.push_str("notitle")
+ }
+
+ script
+ }
+}
+
+impl Set<Color> for Properties {
+ /// Changes the color of the error bars
+ fn set(&mut self, color: Color) -> &mut Properties {
+ self.color = Some(color);
+ self
+ }
+}
+
+impl Set<Label> for Properties {
+ /// Sets the legend label
+ fn set(&mut self, label: Label) -> &mut Properties {
+ self.label = Some(label.0);
+ self
+ }
+}
+
+impl Set<LineType> for Properties {
+ /// Change the line type
+ ///
+ /// **Note** By default `Solid` lines are used
+ fn set(&mut self, lt: LineType) -> &mut Properties {
+ self.line_type = lt;
+ self
+ }
+}
+
+impl Set<LineWidth> for Properties {
+ /// Changes the linewidth
+ ///
+ /// # Panics
+ ///
+ /// Panics if `lw` is a non-positive value
+ fn set(&mut self, lw: LineWidth) -> &mut Properties {
+ let lw = lw.0;
+
+ assert!(lw > 0.);
+
+ self.linewidth = Some(lw);
+ self
+ }
+}
+
+impl Set<PointSize> for Properties {
+ /// Changes the size of the points
+ ///
+ /// # Panics
+ ///
+ /// Panics if `size` is a non-positive value
+ fn set(&mut self, ps: PointSize) -> &mut Properties {
+ let ps = ps.0;
+
+ assert!(ps > 0.);
+
+ self.point_size = Some(ps);
+ self
+ }
+}
+
+impl Set<PointType> for Properties {
+ /// Changes the point type
+ fn set(&mut self, pt: PointType) -> &mut Properties {
+ self.point_type = Some(pt);
+ self
+ }
+}
+
+#[derive(Clone, Copy)]
+enum Style {
+ XErrorBars,
+ XErrorLines,
+ YErrorBars,
+ YErrorLines,
+}
+
+impl Display<&'static str> for Style {
+ fn display(&self) -> &'static str {
+ match *self {
+ Style::XErrorBars => "xerrorbars",
+ Style::XErrorLines => "xerrorlines",
+ Style::YErrorBars => "yerrorbars",
+ Style::YErrorLines => "yerrorlines",
+ }
+ }
+}
+
+/// Asymmetric error bar plots
+pub enum ErrorBar<X, Y, L, H> {
+ /// Horizontal error bars
+ XErrorBars {
+ /// X coordinate of the data points
+ x: X,
+ /// Y coordinate of the data points
+ y: Y,
+ /// X coordinate of the left end of the error bar
+ x_low: L,
+ /// Y coordinate of the right end of the error bar
+ x_high: H,
+ },
+ /// Horizontal error bars, where each point is joined by a line
+ XErrorLines {
+ /// X coordinate of the data points
+ x: X,
+ /// Y coordinate of the data points
+ y: Y,
+ /// X coordinate of the left end of the error bar
+ x_low: L,
+ /// Y coordinate of the right end of the error bar
+ x_high: H,
+ },
+ /// Vertical error bars
+ YErrorBars {
+ /// X coordinate of the data points
+ x: X,
+ /// Y coordinate of the data points
+ y: Y,
+ /// Y coordinate of the bottom of the error bar
+ y_low: L,
+ /// Y coordinate of the top of the error bar
+ y_high: H,
+ },
+ /// Vertical error bars, where each point is joined by a line
+ YErrorLines {
+ /// X coordinate of the data points
+ x: X,
+ /// Y coordinate of the data points
+ y: Y,
+ /// Y coordinate of the bottom of the error bar
+ y_low: L,
+ /// Y coordinate of the top of the error bar
+ y_high: H,
+ },
+}
+
+impl<X, Y, L, H> ErrorBar<X, Y, L, H> {
+ fn style(&self) -> Style {
+ match *self {
+ ErrorBar::XErrorBars { .. } => Style::XErrorBars,
+ ErrorBar::XErrorLines { .. } => Style::XErrorLines,
+ ErrorBar::YErrorBars { .. } => Style::YErrorBars,
+ ErrorBar::YErrorLines { .. } => Style::YErrorLines,
+ }
+ }
+}
+
+impl<X, Y, L, H> traits::Plot<ErrorBar<X, Y, L, H>> for Figure
+where
+ H: IntoIterator,
+ H::Item: Data,
+ L: IntoIterator,
+ L::Item: Data,
+ X: IntoIterator,
+ X::Item: Data,
+ Y: IntoIterator,
+ Y::Item: Data,
+{
+ type Properties = Properties;
+
+ fn plot<F>(&mut self, e: ErrorBar<X, Y, L, H>, configure: F) -> &mut Figure
+ where
+ F: FnOnce(&mut Properties) -> &mut Properties,
+ {
+ let (x_factor, y_factor) = crate::scale_factor(&self.axes, crate::Axes::BottomXLeftY);
+
+ let style = e.style();
+ let (x, y, length, height, e_factor) = match e {
+ ErrorBar::XErrorBars {
+ x,
+ y,
+ x_low,
+ x_high,
+ }
+ | ErrorBar::XErrorLines {
+ x,
+ y,
+ x_low,
+ x_high,
+ } => (x, y, x_low, x_high, x_factor),
+ ErrorBar::YErrorBars {
+ x,
+ y,
+ y_low,
+ y_high,
+ }
+ | ErrorBar::YErrorLines {
+ x,
+ y,
+ y_low,
+ y_high,
+ } => (x, y, y_low, y_high, y_factor),
+ };
+ let data = Matrix::new(
+ izip!(x, y, length, height),
+ (x_factor, y_factor, e_factor, e_factor),
+ );
+ self.plots.push(Plot::new(
+ data,
+ configure(&mut ErrorBarDefault::default(style)),
+ ));
+ self
+ }
+}
+
+// TODO XY error bar
+// pub struct XyErrorBar<X, Y, XL, XH, YL, YH> {
+// x: X,
+// y: Y,
+// x_low: XL,
+// x_high: XH,
+// y_low: YL,
+// y_high: YH,
+// }
+
+// TODO Symmetric error bars
+// pub enum SymmetricErrorBar {
+// XSymmetricErrorBar { x: X, y: Y, x_delta: D },
+// XSymmetricErrorLines { x: X, y: Y, x_delta: D },
+// YSymmetricErrorBar { x: X, y: Y, y_delta: D },
+// YSymmetricErrorLines { x: X, y: Y, y_delta: D },
+// }
diff --git a/src/filledcurve.rs b/src/filledcurve.rs
new file mode 100755
index 0000000..f79dbdd
--- /dev/null
+++ b/src/filledcurve.rs
@@ -0,0 +1,140 @@
+//! Filled curve plots
+
+use std::borrow::Cow;
+use std::iter::IntoIterator;
+
+use crate::data::Matrix;
+use crate::traits::{self, Data, Set};
+use crate::{Axes, Color, Default, Display, Figure, Label, Opacity, Plot, Script};
+
+/// Properties common to filled curve plots
+pub struct Properties {
+ axes: Option<Axes>,
+ color: Option<Color>,
+ label: Option<Cow<'static, str>>,
+ opacity: Option<f64>,
+}
+
+impl Default for Properties {
+ fn default() -> Properties {
+ Properties {
+ axes: None,
+ color: None,
+ label: None,
+ opacity: None,
+ }
+ }
+}
+
+impl Script for Properties {
+ fn script(&self) -> String {
+ let mut script = if let Some(axes) = self.axes {
+ format!("axes {} ", axes.display())
+ } else {
+ String::new()
+ };
+ script.push_str("with filledcurves ");
+
+ script.push_str("fillstyle ");
+
+ if let Some(opacity) = self.opacity {
+ script.push_str(&format!("solid {} ", opacity))
+ }
+
+ // TODO border shoulde be configurable
+ script.push_str("noborder ");
+
+ if let Some(color) = self.color {
+ script.push_str(&format!("lc rgb '{}' ", color.display()));
+ }
+
+ if let Some(ref label) = self.label {
+ script.push_str("title '");
+ script.push_str(label);
+ script.push('\'')
+ } else {
+ script.push_str("notitle")
+ }
+
+ script
+ }
+}
+
+impl Set<Axes> for Properties {
+ /// Select axes to plot against
+ ///
+ /// **Note** By default, the `BottomXLeftY` axes are used
+ fn set(&mut self, axes: Axes) -> &mut Properties {
+ self.axes = Some(axes);
+ self
+ }
+}
+
+impl Set<Color> for Properties {
+ /// Sets the fill color
+ fn set(&mut self, color: Color) -> &mut Properties {
+ self.color = Some(color);
+ self
+ }
+}
+
+impl Set<Label> for Properties {
+ /// Sets the legend label
+ fn set(&mut self, label: Label) -> &mut Properties {
+ self.label = Some(label.0);
+ self
+ }
+}
+
+impl Set<Opacity> for Properties {
+ /// Changes the opacity of the fill color
+ ///
+ /// **Note** By default, the fill color is totally opaque (`opacity = 1.0`)
+ ///
+ /// # Panics
+ ///
+ /// Panics if `opacity` is outside the range `[0, 1]`
+ fn set(&mut self, opacity: Opacity) -> &mut Properties {
+ self.opacity = Some(opacity.0);
+ self
+ }
+}
+
+/// Fills the area between two curves
+pub struct FilledCurve<X, Y1, Y2> {
+ /// X coordinate of the data points of both curves
+ pub x: X,
+ /// Y coordinate of the data points of the first curve
+ pub y1: Y1,
+ /// Y coordinate of the data points of the second curve
+ pub y2: Y2,
+}
+
+impl<X, Y1, Y2> traits::Plot<FilledCurve<X, Y1, Y2>> for Figure
+where
+ X: IntoIterator,
+ X::Item: Data,
+ Y1: IntoIterator,
+ Y1::Item: Data,
+ Y2: IntoIterator,
+ Y2::Item: Data,
+{
+ type Properties = Properties;
+
+ fn plot<F>(&mut self, fc: FilledCurve<X, Y1, Y2>, configure: F) -> &mut Figure
+ where
+ F: FnOnce(&mut Properties) -> &mut Properties,
+ {
+ let FilledCurve { x, y1, y2 } = fc;
+
+ let mut props = Default::default();
+ configure(&mut props);
+
+ let (x_factor, y_factor) =
+ crate::scale_factor(&self.axes, props.axes.unwrap_or(crate::Axes::BottomXLeftY));
+
+ let data = Matrix::new(izip!(x, y1, y2), (x_factor, y_factor, y_factor));
+ self.plots.push(Plot::new(data, &props));
+ self
+ }
+}
diff --git a/src/grid.rs b/src/grid.rs
new file mode 100755
index 0000000..b6adb2f
--- /dev/null
+++ b/src/grid.rs
@@ -0,0 +1,46 @@
+//! Gridline
+
+use crate::{Axis, Default, Display, Grid, Script};
+
+/// Gridline properties
+#[derive(Clone, Copy)]
+pub struct Properties {
+ hidden: bool,
+}
+
+impl Default for Properties {
+ fn default() -> Properties {
+ Properties { hidden: true }
+ }
+}
+
+// TODO Lots of configuration pending: linetype, linewidth, etc
+impl Properties {
+ /// Hides the gridlines
+ ///
+ /// **Note** Both `Major` and `Minor` gridlines are hidden by default
+ pub fn hide(&mut self) -> &mut Properties {
+ self.hidden = true;
+ self
+ }
+
+ /// Shows the gridlines
+ pub fn show(&mut self) -> &mut Properties {
+ self.hidden = false;
+ self
+ }
+}
+
+impl<'a> Script for (Axis, Grid, &'a Properties) {
+ fn script(&self) -> String {
+ let &(axis, grid, properties) = self;
+ let axis = axis.display();
+ let grid = grid.display();
+
+ if properties.hidden {
+ String::new()
+ } else {
+ format!("set grid {}{}tics\n", grid, axis)
+ }
+ }
+}
diff --git a/src/key.rs b/src/key.rs
new file mode 100755
index 0000000..e8847d6
--- /dev/null
+++ b/src/key.rs
@@ -0,0 +1,218 @@
+//! Key (or legend)
+
+use std::borrow::Cow;
+
+use crate::traits::Set;
+use crate::{Default, Display, Script, Title};
+
+/// Properties of the key
+#[derive(Clone)]
+pub struct Properties {
+ boxed: bool,
+ hidden: bool,
+ justification: Option<Justification>,
+ order: Option<Order>,
+ position: Option<Position>,
+ stacked: Option<Stacked>,
+ title: Option<Cow<'static, str>>,
+}
+
+impl Default for Properties {
+ fn default() -> Properties {
+ Properties {
+ boxed: false,
+ hidden: false,
+ justification: None,
+ order: None,
+ position: None,
+ stacked: None,
+ title: None,
+ }
+ }
+}
+
+impl Properties {
+ /// Hides the key
+ pub fn hide(&mut self) -> &mut Properties {
+ self.hidden = true;
+ self
+ }
+
+ /// Shows the key
+ ///
+ /// **Note** The key is shown by default
+ pub fn show(&mut self) -> &mut Properties {
+ self.hidden = false;
+ self
+ }
+}
+
+impl Script for Properties {
+ fn script(&self) -> String {
+ let mut script = if self.hidden {
+ return String::from("set key off\n");
+ } else {
+ String::from("set key on ")
+ };
+
+ match self.position {
+ None => {}
+ Some(Position::Inside(v, h)) => {
+ script.push_str(&format!("inside {} {} ", v.display(), h.display()))
+ }
+ Some(Position::Outside(v, h)) => {
+ script.push_str(&format!("outside {} {} ", v.display(), h.display()))
+ }
+ }
+
+ if let Some(stacked) = self.stacked {
+ script.push_str(stacked.display());
+ script.push(' ');
+ }
+
+ if let Some(justification) = self.justification {
+ script.push_str(justification.display());
+ script.push(' ');
+ }
+
+ if let Some(order) = self.order {
+ script.push_str(order.display());
+ script.push(' ');
+ }
+
+ if let Some(ref title) = self.title {
+ script.push_str(&format!("title '{}' ", title))
+ }
+
+ if self.boxed {
+ script.push_str("box ")
+ }
+
+ script.push('\n');
+ script
+ }
+}
+
+impl Set<Boxed> for Properties {
+ /// Select if the key will be surrounded with a box or not
+ ///
+ /// **Note** The key is not boxed by default
+ fn set(&mut self, boxed: Boxed) -> &mut Properties {
+ match boxed {
+ Boxed::No => self.boxed = false,
+ Boxed::Yes => self.boxed = true,
+ }
+
+ self
+ }
+}
+
+impl Set<Justification> for Properties {
+ /// Changes the justification of the text of each entry
+ ///
+ /// **Note** The text is `RightJustified` by default
+ fn set(&mut self, justification: Justification) -> &mut Properties {
+ self.justification = Some(justification);
+ self
+ }
+}
+
+impl Set<Order> for Properties {
+ /// How to order each entry
+ ///
+ /// **Note** The default order is `TextSample`
+ fn set(&mut self, order: Order) -> &mut Properties {
+ self.order = Some(order);
+ self
+ }
+}
+
+impl Set<Position> for Properties {
+ /// Selects where to place the key
+ ///
+ /// **Note** By default, the key is placed `Inside(Vertical::Top, Horizontal::Right)`
+ fn set(&mut self, position: Position) -> &mut Properties {
+ self.position = Some(position);
+ self
+ }
+}
+
+impl Set<Stacked> for Properties {
+ /// Changes how the entries of the key are stacked
+ fn set(&mut self, stacked: Stacked) -> &mut Properties {
+ self.stacked = Some(stacked);
+ self
+ }
+}
+
+impl Set<Title> for Properties {
+ fn set(&mut self, title: Title) -> &mut Properties {
+ self.title = Some(title.0);
+ self
+ }
+}
+
+/// Whether the key is surrounded by a box or not
+#[allow(missing_docs)]
+#[derive(Clone, Copy)]
+pub enum Boxed {
+ No,
+ Yes,
+}
+
+/// Horizontal position of the key
+#[derive(Clone, Copy)]
+pub enum Horizontal {
+ /// Center of the figure
+ Center,
+ /// Left border of the figure
+ Left,
+ /// Right border of the figure
+ Right,
+}
+
+/// Text justification of the key
+#[allow(missing_docs)]
+#[derive(Clone, Copy)]
+pub enum Justification {
+ Left,
+ Right,
+}
+
+/// Order of the elements of the key
+#[derive(Clone, Copy)]
+pub enum Order {
+ /// Sample first, then text
+ SampleText,
+ /// Text first, then sample
+ TextSample,
+}
+
+/// Position of the key
+// TODO XY position
+#[derive(Clone, Copy)]
+pub enum Position {
+ /// Inside the area surrounded by the four (BottomX, TopX, LeftY and RightY) axes
+ Inside(Vertical, Horizontal),
+ /// Outside of that area
+ Outside(Vertical, Horizontal),
+}
+
+/// How the entries of the key are stacked
+#[allow(missing_docs)]
+#[derive(Clone, Copy)]
+pub enum Stacked {
+ Horizontally,
+ Vertically,
+}
+
+/// Vertical position of the key
+#[derive(Clone, Copy)]
+pub enum Vertical {
+ /// Bottom border of the figure
+ Bottom,
+ /// Center of the figure
+ Center,
+ /// Top border of the figure
+ Top,
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100755
index 0000000..151f019
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,1090 @@
+//! [Criterion]'s plotting library.
+//!
+//! [Criterion]: https://github.com/bheisler/criterion.rs
+//!
+//! **WARNING** This library is criterion's implementation detail and there no plans to stabilize
+//! it. In other words, the API may break at any time without notice.
+//!
+//! # Examples
+//!
+//! - Simple "curves" (based on [`simple.dem`](http://gnuplot.sourceforge.net/demo/simple.html))
+//!
+//! ![Plot](curve.svg)
+//!
+//! ```
+//! # use std::fs;
+//! # use std::path::Path;
+//! use itertools_num::linspace;
+//! use criterion_plot::prelude::*;
+//!
+//! # if let Err(_) = criterion_plot::version() {
+//! # return;
+//! # }
+//! let ref xs = linspace::<f64>(-10., 10., 51).collect::<Vec<_>>();
+//!
+//! # fs::create_dir_all(Path::new("target/doc/criterion_plot")).unwrap();
+//! # assert_eq!(Some(String::new()),
+//! Figure::new()
+//! # .set(Font("Helvetica"))
+//! # .set(FontSize(12.))
+//! # .set(Output(Path::new("target/doc/criterion_plot/curve.svg")))
+//! # .set(Size(1280, 720))
+//! .configure(Key, |k| {
+//! k.set(Boxed::Yes)
+//! .set(Position::Inside(Vertical::Top, Horizontal::Left))
+//! })
+//! .plot(LinesPoints {
+//! x: xs,
+//! y: xs.iter().map(|x| x.sin()),
+//! },
+//! |lp| {
+//! lp.set(Color::DarkViolet)
+//! .set(Label("sin(x)"))
+//! .set(LineType::Dash)
+//! .set(PointSize(1.5))
+//! .set(PointType::Circle)
+//! })
+//! .plot(Steps {
+//! x: xs,
+//! y: xs.iter().map(|x| x.atan()),
+//! },
+//! |s| {
+//! s.set(Color::Rgb(0, 158, 115))
+//! .set(Label("atan(x)"))
+//! .set(LineWidth(2.))
+//! })
+//! .plot(Impulses {
+//! x: xs,
+//! y: xs.iter().map(|x| x.atan().cos()),
+//! },
+//! |i| {
+//! i.set(Color::Rgb(86, 180, 233))
+//! .set(Label("cos(atan(x))"))
+//! })
+//! .draw() // (rest of the chain has been omitted)
+//! # .ok()
+//! # .and_then(|gnuplot| {
+//! # gnuplot.wait_with_output().ok().and_then(|p| String::from_utf8(p.stderr).ok())
+//! # }));
+//! ```
+//!
+//! - error bars (based on
+//! [Julia plotting tutorial](https://plot.ly/julia/error-bars/#Colored-and-Styled-Error-Bars))
+//!
+//! ![Plot](error_bar.svg)
+//!
+//! ```
+//! # use std::fs;
+//! # use std::path::Path;
+//! use std::f64::consts::PI;
+//!
+//! use itertools_num::linspace;
+//! use rand::{Rng, XorShiftRng};
+//! use criterion_plot::prelude::*;
+//!
+//! fn sinc(mut x: f64) -> f64 {
+//! if x == 0. {
+//! 1.
+//! } else {
+//! x *= PI;
+//! x.sin() / x
+//! }
+//! }
+//!
+//! # if let Err(_) = criterion_plot::version() {
+//! # return;
+//! # }
+//! let ref xs_ = linspace::<f64>(-4., 4., 101).collect::<Vec<_>>();
+//!
+//! // Fake some data
+//! let ref mut rng: XorShiftRng = rand::thread_rng().gen();
+//! let xs = linspace::<f64>(-4., 4., 13).skip(1).take(11);
+//! let ys = xs.map(|x| sinc(x) + 0.05 * rng.gen::<f64>() - 0.025).collect::<Vec<_>>();
+//! let y_low = ys.iter().map(|&y| y - 0.025 - 0.075 * rng.gen::<f64>()).collect::<Vec<_>>();
+//! let y_high = ys.iter().map(|&y| y + 0.025 + 0.075 * rng.gen::<f64>()).collect::<Vec<_>>();
+//! let xs = linspace::<f64>(-4., 4., 13).skip(1).take(11);
+//! let xs = xs.map(|x| x + 0.2 * rng.gen::<f64>() - 0.1);
+//!
+//! # fs::create_dir_all(Path::new("target/doc/criterion_plot")).unwrap();
+//! # assert_eq!(Some(String::new()),
+//! Figure::new()
+//! # .set(Font("Helvetica"))
+//! # .set(FontSize(12.))
+//! # .set(Output(Path::new("target/doc/criterion_plot/error_bar.svg")))
+//! # .set(Size(1280, 720))
+//! .configure(Axis::BottomX, |a| {
+//! a.set(TicLabels {
+//! labels: &["-π", "0", "π"],
+//! positions: &[-PI, 0., PI],
+//! })
+//! })
+//! .configure(Key,
+//! |k| k.set(Position::Outside(Vertical::Top, Horizontal::Right)))
+//! .plot(Lines {
+//! x: xs_,
+//! y: xs_.iter().cloned().map(sinc),
+//! },
+//! |l| {
+//! l.set(Color::Rgb(0, 158, 115))
+//! .set(Label("sinc(x)"))
+//! .set(LineWidth(2.))
+//! })
+//! .plot(YErrorBars {
+//! x: xs,
+//! y: &ys,
+//! y_low: &y_low,
+//! y_high: &y_high,
+//! },
+//! |eb| {
+//! eb.set(Color::DarkViolet)
+//! .set(LineWidth(2.))
+//! .set(PointType::FilledCircle)
+//! .set(Label("measured"))
+//! })
+//! .draw() // (rest of the chain has been omitted)
+//! # .ok()
+//! # .and_then(|gnuplot| {
+//! # gnuplot.wait_with_output().ok().and_then(|p| String::from_utf8(p.stderr).ok())
+//! # }));
+//! ```
+//!
+//! - Candlesticks (based on
+//! [`candlesticks.dem`](http://gnuplot.sourceforge.net/demo/candlesticks.html))
+//!
+//! ![Plot](candlesticks.svg)
+//!
+//! ```
+//! # use std::fs;
+//! # use std::path::Path;
+//! use criterion_plot::prelude::*;
+//! use rand::Rng;
+//!
+//! # if let Err(_) = criterion_plot::version() {
+//! # return;
+//! # }
+//! let xs = 1..11;
+//!
+//! // Fake some data
+//! let mut rng = rand::thread_rng();
+//! let bh = xs.clone().map(|_| 5f64 + 2.5 * rng.gen::<f64>()).collect::<Vec<_>>();
+//! let bm = xs.clone().map(|_| 2.5f64 + 2.5 * rng.gen::<f64>()).collect::<Vec<_>>();
+//! let wh = bh.iter().map(|&y| y + (10. - y) * rng.gen::<f64>()).collect::<Vec<_>>();
+//! let wm = bm.iter().map(|&y| y * rng.gen::<f64>()).collect::<Vec<_>>();
+//! let m = bm.iter().zip(bh.iter()).map(|(&l, &h)| (h - l) * rng.gen::<f64>() + l)
+//! .collect::<Vec<_>>();
+//!
+//! # fs::create_dir_all(Path::new("target/doc/criterion_plot")).unwrap();
+//! # assert_eq!(Some(String::new()),
+//! Figure::new()
+//! # .set(Font("Helvetica"))
+//! # .set(FontSize(12.))
+//! # .set(Output(Path::new("target/doc/criterion_plot/candlesticks.svg")))
+//! # .set(Size(1280, 720))
+//! .set(BoxWidth(0.2))
+//! .configure(Axis::BottomX, |a| a.set(Range::Limits(0., 11.)))
+//! .plot(Candlesticks {
+//! x: xs.clone(),
+//! whisker_min: &wm,
+//! box_min: &bm,
+//! box_high: &bh,
+//! whisker_high: &wh,
+//! },
+//! |cs| {
+//! cs.set(Color::Rgb(86, 180, 233))
+//! .set(Label("Quartiles"))
+//! .set(LineWidth(2.))
+//! })
+//! // trick to plot the median
+//! .plot(Candlesticks {
+//! x: xs,
+//! whisker_min: &m,
+//! box_min: &m,
+//! box_high: &m,
+//! whisker_high: &m,
+//! },
+//! |cs| {
+//! cs.set(Color::Black)
+//! .set(LineWidth(2.))
+//! })
+//! .draw() // (rest of the chain has been omitted)
+//! # .ok()
+//! # .and_then(|gnuplot| {
+//! # gnuplot.wait_with_output().ok().and_then(|p| String::from_utf8(p.stderr).ok())
+//! # }));
+//! ```
+//!
+//! - Multiaxis (based on [`multiaxis.dem`](http://gnuplot.sourceforge.net/demo/multiaxis.html))
+//!
+//! ![Plot](multiaxis.svg)
+//!
+//! ```
+//! # use std::fs;
+//! # use std::path::Path;
+//! use std::f64::consts::PI;
+//!
+//! use itertools_num::linspace;
+//! use num_complex::Complex;
+//! use criterion_plot::prelude::*;
+//!
+//! fn tf(x: f64) -> Complex<f64> {
+//! Complex::new(0., x) / Complex::new(10., x) / Complex::new(1., x / 10_000.)
+//! }
+//!
+//! # if let Err(_) = criterion_plot::version() {
+//! # return;
+//! # }
+//! let (start, end): (f64, f64) = (1.1, 90_000.);
+//! let ref xs = linspace(start.ln(), end.ln(), 101).map(|x| x.exp()).collect::<Vec<_>>();
+//! let phase = xs.iter().map(|&x| tf(x).arg() * 180. / PI);
+//! let magnitude = xs.iter().map(|&x| tf(x).norm());
+//!
+//! # fs::create_dir_all(Path::new("target/doc/criterion_plot")).unwrap();
+//! # assert_eq!(Some(String::new()),
+//! Figure::new().
+//! # set(Font("Helvetica")).
+//! # set(FontSize(12.)).
+//! # set(Output(Path::new("target/doc/criterion_plot/multiaxis.svg"))).
+//! # set(Size(1280, 720)).
+//! set(Title("Frequency response")).
+//! configure(Axis::BottomX, |a| a.
+//! configure(Grid::Major, |g| g.
+//! show()).
+//! set(Label("Angular frequency (rad/s)")).
+//! set(Range::Limits(start, end)).
+//! set(Scale::Logarithmic)).
+//! configure(Axis::LeftY, |a| a.
+//! set(Label("Gain")).
+//! set(Scale::Logarithmic)).
+//! configure(Axis::RightY, |a| a.
+//! configure(Grid::Major, |g| g.
+//! show()).
+//! set(Label("Phase shift (°)"))).
+//! configure(Key, |k| k.
+//! set(Position::Inside(Vertical::Top, Horizontal::Center)).
+//! set(Title(" "))).
+//! plot(Lines {
+//! x: xs,
+//! y: magnitude,
+//! }, |l| l.
+//! set(Color::DarkViolet).
+//! set(Label("Magnitude")).
+//! set(LineWidth(2.))).
+//! plot(Lines {
+//! x: xs,
+//! y: phase,
+//! }, |l| l.
+//! set(Axes::BottomXRightY).
+//! set(Color::Rgb(0, 158, 115)).
+//! set(Label("Phase")).
+//! set(LineWidth(2.))).
+//! draw(). // (rest of the chain has been omitted)
+//! # ok().and_then(|gnuplot| {
+//! # gnuplot.wait_with_output().ok().and_then(|p| {
+//! # String::from_utf8(p.stderr).ok()
+//! # })
+//! # }));
+//! ```
+//! - Filled curves (based on
+//! [`transparent.dem`](http://gnuplot.sourceforge.net/demo/transparent.html))
+//!
+//! ![Plot](filled_curve.svg)
+//!
+//! ```
+//! # use std::fs;
+//! # use std::path::Path;
+//! use std::f64::consts::PI;
+//! use std::iter;
+//!
+//! use itertools_num::linspace;
+//! use criterion_plot::prelude::*;
+//!
+//! # if let Err(_) = criterion_plot::version() {
+//! # return;
+//! # }
+//! let (start, end) = (-5., 5.);
+//! let ref xs = linspace(start, end, 101).collect::<Vec<_>>();
+//! let zeros = iter::repeat(0);
+//!
+//! fn gaussian(x: f64, mu: f64, sigma: f64) -> f64 {
+//! 1. / (((x - mu).powi(2) / 2. / sigma.powi(2)).exp() * sigma * (2. * PI).sqrt())
+//! }
+//!
+//! # fs::create_dir_all(Path::new("target/doc/criterion_plot")).unwrap();
+//! # assert_eq!(Some(String::new()),
+//! Figure::new()
+//! # .set(Font("Helvetica"))
+//! # .set(FontSize(12.))
+//! # .set(Output(Path::new("target/doc/criterion_plot/filled_curve.svg")))
+//! # .set(Size(1280, 720))
+//! .set(Title("Transparent filled curve"))
+//! .configure(Axis::BottomX, |a| a.set(Range::Limits(start, end)))
+//! .configure(Axis::LeftY, |a| a.set(Range::Limits(0., 1.)))
+//! .configure(Key, |k| {
+//! k.set(Justification::Left)
+//! .set(Order::SampleText)
+//! .set(Position::Inside(Vertical::Top, Horizontal::Left))
+//! .set(Title("Gaussian Distribution"))
+//! })
+//! .plot(FilledCurve {
+//! x: xs,
+//! y1: xs.iter().map(|&x| gaussian(x, 0.5, 0.5)),
+//! y2: zeros.clone(),
+//! },
+//! |fc| {
+//! fc.set(Color::ForestGreen)
+//! .set(Label("μ = 0.5 σ = 0.5"))
+//! })
+//! .plot(FilledCurve {
+//! x: xs,
+//! y1: xs.iter().map(|&x| gaussian(x, 2.0, 1.0)),
+//! y2: zeros.clone(),
+//! },
+//! |fc| {
+//! fc.set(Color::Gold)
+//! .set(Label("μ = 2.0 σ = 1.0"))
+//! .set(Opacity(0.5))
+//! })
+//! .plot(FilledCurve {
+//! x: xs,
+//! y1: xs.iter().map(|&x| gaussian(x, -1.0, 2.0)),
+//! y2: zeros,
+//! },
+//! |fc| {
+//! fc.set(Color::Red)
+//! .set(Label("μ = -1.0 σ = 2.0"))
+//! .set(Opacity(0.5))
+//! })
+//! .draw()
+//! .ok()
+//! .and_then(|gnuplot| {
+//! gnuplot.wait_with_output().ok().and_then(|p| String::from_utf8(p.stderr).ok())
+//! }));
+//! ```
+
+#![deny(missing_docs)]
+#![deny(warnings)]
+#![deny(bare_trait_objects)]
+// This lint has lots of false positives ATM, see
+// https://github.com/Manishearth/rust-clippy/issues/761
+#![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
+// False positives with images
+#![cfg_attr(feature = "cargo-clippy", allow(clippy::doc_markdown))]
+#![cfg_attr(feature = "cargo-clippy", allow(clippy::many_single_char_names))]
+
+extern crate cast;
+#[macro_use]
+extern crate itertools;
+
+use std::borrow::Cow;
+use std::fmt;
+use std::fs::File;
+use std::io;
+use std::num::ParseIntError;
+use std::path::Path;
+use std::process::{Child, Command};
+use std::str;
+
+use crate::data::Matrix;
+use crate::traits::{Configure, Set};
+
+mod data;
+mod display;
+mod map;
+
+pub mod axis;
+pub mod candlestick;
+pub mod curve;
+pub mod errorbar;
+pub mod filledcurve;
+pub mod grid;
+pub mod key;
+pub mod prelude;
+pub mod proxy;
+pub mod traits;
+
+/// Plot container
+#[derive(Clone)]
+pub struct Figure {
+ alpha: Option<f64>,
+ axes: map::axis::Map<axis::Properties>,
+ box_width: Option<f64>,
+ font: Option<Cow<'static, str>>,
+ font_size: Option<f64>,
+ key: Option<key::Properties>,
+ output: Cow<'static, Path>,
+ plots: Vec<Plot>,
+ size: Option<(usize, usize)>,
+ terminal: Terminal,
+ tics: map::axis::Map<String>,
+ title: Option<Cow<'static, str>>,
+}
+
+impl Figure {
+ /// Creates an empty figure
+ pub fn new() -> Figure {
+ Figure {
+ alpha: None,
+ axes: map::axis::Map::new(),
+ box_width: None,
+ font: None,
+ font_size: None,
+ key: None,
+ output: Cow::Borrowed(Path::new("output.plot")),
+ plots: Vec::new(),
+ size: None,
+ terminal: Terminal::Svg,
+ tics: map::axis::Map::new(),
+ title: None,
+ }
+ }
+
+ fn script(&self) -> Vec<u8> {
+ let mut s = String::new();
+
+ s.push_str(&format!(
+ "set output '{}'\n",
+ self.output.display().to_string().replace("'", "''")
+ ));
+
+ if let Some(width) = self.box_width {
+ s.push_str(&format!("set boxwidth {}\n", width))
+ }
+
+ if let Some(ref title) = self.title {
+ s.push_str(&format!("set title '{}'\n", title))
+ }
+
+ for axis in self.axes.iter() {
+ s.push_str(&axis.script());
+ }
+
+ for (_, script) in self.tics.iter() {
+ s.push_str(script);
+ }
+
+ if let Some(ref key) = self.key {
+ s.push_str(&key.script())
+ }
+
+ if let Some(alpha) = self.alpha {
+ s.push_str(&format!("set style fill transparent solid {}\n", alpha))
+ }
+
+ s.push_str(&format!("set terminal {} dashed", self.terminal.display()));
+
+ if let Some((width, height)) = self.size {
+ s.push_str(&format!(" size {}, {}", width, height))
+ }
+
+ if let Some(ref name) = self.font {
+ if let Some(size) = self.font_size {
+ s.push_str(&format!(" font '{},{}'", name, size))
+ } else {
+ s.push_str(&format!(" font '{}'", name))
+ }
+ }
+
+ // TODO This removes the crossbars from the ends of error bars, but should be configurable
+ s.push_str("\nunset bars\n");
+
+ let mut is_first_plot = true;
+ for plot in &self.plots {
+ let data = plot.data();
+
+ if data.bytes().is_empty() {
+ continue;
+ }
+
+ if is_first_plot {
+ s.push_str("plot ");
+ is_first_plot = false;
+ } else {
+ s.push_str(", ");
+ }
+
+ s.push_str(&format!(
+ "'-' binary endian=little record={} format='%float64' using ",
+ data.nrows()
+ ));
+
+ let mut is_first_col = true;
+ for col in 0..data.ncols() {
+ if is_first_col {
+ is_first_col = false;
+ } else {
+ s.push(':');
+ }
+ s.push_str(&(col + 1).to_string());
+ }
+ s.push(' ');
+
+ s.push_str(plot.script());
+ }
+
+ let mut buffer = s.into_bytes();
+ let mut is_first = true;
+ for plot in &self.plots {
+ if is_first {
+ is_first = false;
+ buffer.push(b'\n');
+ }
+ buffer.extend_from_slice(plot.data().bytes());
+ }
+
+ buffer
+ }
+
+ /// Spawns a drawing child process
+ ///
+ /// NOTE: stderr, stdin, and stdout are piped
+ pub fn draw(&mut self) -> io::Result<Child> {
+ use std::process::Stdio;
+
+ let mut gnuplot = Command::new("gnuplot")
+ .stderr(Stdio::piped())
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn()?;
+ self.dump(gnuplot.stdin.as_mut().unwrap())?;
+ Ok(gnuplot)
+ }
+
+ /// Dumps the script required to produce the figure into `sink`
+ pub fn dump<W>(&mut self, sink: &mut W) -> io::Result<&mut Figure>
+ where
+ W: io::Write,
+ {
+ sink.write_all(&self.script())?;
+ Ok(self)
+ }
+
+ /// Saves the script required to produce the figure to `path`
+ pub fn save(&self, path: &Path) -> io::Result<&Figure> {
+ use std::io::Write;
+
+ File::create(path)?.write_all(&self.script())?;
+ Ok(self)
+ }
+}
+
+impl Configure<Axis> for Figure {
+ type Properties = axis::Properties;
+
+ /// Configures an axis
+ fn configure<F>(&mut self, axis: Axis, configure: F) -> &mut Figure
+ where
+ F: FnOnce(&mut axis::Properties) -> &mut axis::Properties,
+ {
+ if self.axes.contains_key(axis) {
+ configure(self.axes.get_mut(axis).unwrap());
+ } else {
+ let mut properties = Default::default();
+ configure(&mut properties);
+ self.axes.insert(axis, properties);
+ }
+ self
+ }
+}
+
+impl Configure<Key> for Figure {
+ type Properties = key::Properties;
+
+ /// Configures the key (legend)
+ fn configure<F>(&mut self, _: Key, configure: F) -> &mut Figure
+ where
+ F: FnOnce(&mut key::Properties) -> &mut key::Properties,
+ {
+ if self.key.is_some() {
+ configure(self.key.as_mut().unwrap());
+ } else {
+ let mut key = Default::default();
+ configure(&mut key);
+ self.key = Some(key);
+ }
+ self
+ }
+}
+
+impl Set<BoxWidth> for Figure {
+ /// Changes the box width of all the box related plots (bars, candlesticks, etc)
+ ///
+ /// **Note** The default value is 0
+ ///
+ /// # Panics
+ ///
+ /// Panics if `width` is a negative value
+ fn set(&mut self, width: BoxWidth) -> &mut Figure {
+ let width = width.0;
+
+ assert!(width >= 0.);
+
+ self.box_width = Some(width);
+ self
+ }
+}
+
+impl Set<Font> for Figure {
+ /// Changes the font
+ fn set(&mut self, font: Font) -> &mut Figure {
+ self.font = Some(font.0);
+ self
+ }
+}
+
+impl Set<FontSize> for Figure {
+ /// Changes the size of the font
+ ///
+ /// # Panics
+ ///
+ /// Panics if `size` is a non-positive value
+ fn set(&mut self, size: FontSize) -> &mut Figure {
+ let size = size.0;
+
+ assert!(size >= 0.);
+
+ self.font_size = Some(size);
+ self
+ }
+}
+
+impl Set<Output> for Figure {
+ /// Changes the output file
+ ///
+ /// **Note** The default output file is `output.plot`
+ fn set(&mut self, output: Output) -> &mut Figure {
+ self.output = output.0;
+ self
+ }
+}
+
+impl Set<Size> for Figure {
+ /// Changes the figure size
+ fn set(&mut self, size: Size) -> &mut Figure {
+ self.size = Some((size.0, size.1));
+ self
+ }
+}
+
+impl Set<Terminal> for Figure {
+ /// Changes the output terminal
+ ///
+ /// **Note** By default, the terminal is set to `Svg`
+ fn set(&mut self, terminal: Terminal) -> &mut Figure {
+ self.terminal = terminal;
+ self
+ }
+}
+
+impl Set<Title> for Figure {
+ /// Sets the title
+ fn set(&mut self, title: Title) -> &mut Figure {
+ self.title = Some(title.0);
+ self
+ }
+}
+
+impl Default for Figure {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// Box width for box-related plots: bars, candlesticks, etc
+#[derive(Clone, Copy)]
+pub struct BoxWidth(pub f64);
+
+/// A font name
+pub struct Font(Cow<'static, str>);
+
+/// The size of a font
+#[derive(Clone, Copy)]
+pub struct FontSize(pub f64);
+
+/// The key or legend
+#[derive(Clone, Copy)]
+pub struct Key;
+
+/// Plot label
+pub struct Label(Cow<'static, str>);
+
+/// Width of the lines
+#[derive(Clone, Copy)]
+pub struct LineWidth(pub f64);
+
+/// Fill color opacity
+#[derive(Clone, Copy)]
+pub struct Opacity(pub f64);
+
+/// Output file path
+pub struct Output(Cow<'static, Path>);
+
+/// Size of the points
+#[derive(Clone, Copy)]
+pub struct PointSize(pub f64);
+
+/// Axis range
+#[derive(Clone, Copy)]
+pub enum Range {
+ /// Autoscale the axis
+ Auto,
+ /// Set the limits of the axis
+ Limits(f64, f64),
+}
+
+/// Figure size
+#[derive(Clone, Copy)]
+pub struct Size(pub usize, pub usize);
+
+/// Labels attached to the tics of an axis
+pub struct TicLabels<P, L> {
+ /// Labels to attach to the tics
+ pub labels: L,
+ /// Position of the tics on the axis
+ pub positions: P,
+}
+
+/// Figure title
+pub struct Title(Cow<'static, str>);
+
+/// A pair of axes that define a coordinate system
+#[allow(missing_docs)]
+#[derive(Clone, Copy)]
+pub enum Axes {
+ BottomXLeftY,
+ BottomXRightY,
+ TopXLeftY,
+ TopXRightY,
+}
+
+/// A coordinate axis
+#[derive(Clone, Copy)]
+pub enum Axis {
+ /// X axis on the bottom side of the figure
+ BottomX,
+ /// Y axis on the left side of the figure
+ LeftY,
+ /// Y axis on the right side of the figure
+ RightY,
+ /// X axis on the top side of the figure
+ TopX,
+}
+
+impl Axis {
+ fn next(self) -> Option<Axis> {
+ use crate::Axis::*;
+
+ match self {
+ BottomX => Some(LeftY),
+ LeftY => Some(RightY),
+ RightY => Some(TopX),
+ TopX => None,
+ }
+ }
+}
+
+/// Color
+#[allow(missing_docs)]
+#[derive(Clone, Copy)]
+pub enum Color {
+ Black,
+ Blue,
+ Cyan,
+ DarkViolet,
+ ForestGreen,
+ Gold,
+ Gray,
+ Green,
+ Magenta,
+ Red,
+ /// Custom RGB color
+ Rgb(u8, u8, u8),
+ White,
+ Yellow,
+}
+
+/// Grid line
+#[derive(Clone, Copy)]
+pub enum Grid {
+ /// Major gridlines
+ Major,
+ /// Minor gridlines
+ Minor,
+}
+
+impl Grid {
+ fn next(self) -> Option<Grid> {
+ use crate::Grid::*;
+
+ match self {
+ Major => Some(Minor),
+ Minor => None,
+ }
+ }
+}
+
+/// Line type
+#[allow(missing_docs)]
+#[derive(Clone, Copy)]
+pub enum LineType {
+ Dash,
+ Dot,
+ DotDash,
+ DotDotDash,
+ /// Line made of minimally sized dots
+ SmallDot,
+ Solid,
+}
+
+/// Point type
+#[allow(missing_docs)]
+#[derive(Clone, Copy)]
+pub enum PointType {
+ Circle,
+ FilledCircle,
+ FilledSquare,
+ FilledTriangle,
+ Plus,
+ Square,
+ Star,
+ Triangle,
+ X,
+}
+
+/// Axis scale
+#[allow(missing_docs)]
+#[derive(Clone, Copy)]
+pub enum Scale {
+ Linear,
+ Logarithmic,
+}
+
+/// Axis scale factor
+#[allow(missing_docs)]
+#[derive(Clone, Copy)]
+pub struct ScaleFactor(pub f64);
+
+/// Output terminal
+#[allow(missing_docs)]
+#[derive(Clone, Copy)]
+pub enum Terminal {
+ Svg,
+}
+
+/// Not public version of `std::default::Default`, used to not leak default constructors into the
+/// public API
+trait Default {
+ /// Creates `Properties` with default configuration
+ fn default() -> Self;
+}
+
+/// Enums that can produce gnuplot code
+trait Display<S> {
+ /// Translates the enum in gnuplot code
+ fn display(&self) -> S;
+}
+
+/// Curve variant of Default
+trait CurveDefault<S> {
+ /// Creates `curve::Properties` with default configuration
+ fn default(s: S) -> Self;
+}
+
+/// Error bar variant of Default
+trait ErrorBarDefault<S> {
+ /// Creates `errorbar::Properties` with default configuration
+ fn default(s: S) -> Self;
+}
+
+/// Structs that can produce gnuplot code
+trait Script {
+ /// Translates some configuration struct into gnuplot code
+ fn script(&self) -> String;
+}
+
+#[derive(Clone)]
+struct Plot {
+ data: Matrix,
+ script: String,
+}
+
+impl Plot {
+ fn new<S>(data: Matrix, script: &S) -> Plot
+ where
+ S: Script,
+ {
+ Plot {
+ data,
+ script: script.script(),
+ }
+ }
+
+ fn data(&self) -> &Matrix {
+ &self.data
+ }
+
+ fn script(&self) -> &str {
+ &self.script
+ }
+}
+
+/// Possible errors when parsing gnuplot's version string
+#[derive(Debug)]
+pub enum VersionError {
+ /// The `gnuplot` command couldn't be executed
+ Exec(io::Error),
+ /// The `gnuplot` command returned an error message
+ Error(String),
+ /// The `gnuplot` command returned invalid utf-8
+ OutputError,
+ /// The `gnuplot` command returned an unparseable string
+ ParseError(String),
+}
+impl fmt::Display for VersionError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ VersionError::Exec(err) => write!(f, "`gnuplot --version` failed: {}", err),
+ VersionError::Error(msg) => {
+ write!(f, "`gnuplot --version` failed with error message:\n{}", msg)
+ }
+ VersionError::OutputError => write!(f, "`gnuplot --version` returned invalid utf-8"),
+ VersionError::ParseError(msg) => write!(
+ f,
+ "`gnuplot --version` returned an unparseable version string: {}",
+ msg
+ ),
+ }
+ }
+}
+impl ::std::error::Error for VersionError {
+ fn description(&self) -> &str {
+ match self {
+ VersionError::Exec(_) => "Execution Error",
+ VersionError::Error(_) => "Other Error",
+ VersionError::OutputError => "Output Error",
+ VersionError::ParseError(_) => "Parse Error",
+ }
+ }
+
+ fn cause(&self) -> Option<&dyn ::std::error::Error> {
+ match self {
+ VersionError::Exec(err) => Some(err),
+ _ => None,
+ }
+ }
+}
+
+/// Structure representing a gnuplot version number.
+pub struct Version {
+ /// The major version number
+ pub major: usize,
+ /// The minor version number
+ pub minor: usize,
+ /// The patch level
+ pub patch: String,
+}
+
+/// Returns `gnuplot` version
+pub fn version() -> Result<Version, VersionError> {
+ let command_output = Command::new("gnuplot")
+ .arg("--version")
+ .output()
+ .map_err(VersionError::Exec)?;
+ if !command_output.status.success() {
+ let error =
+ String::from_utf8(command_output.stderr).map_err(|_| VersionError::OutputError)?;
+ return Err(VersionError::Error(error));
+ }
+
+ let output = String::from_utf8(command_output.stdout).map_err(|_| VersionError::OutputError)?;
+
+ parse_version(&output).map_err(|_| VersionError::ParseError(output.clone()))
+}
+
+fn parse_version(version_str: &str) -> Result<Version, Option<ParseIntError>> {
+ let mut words = version_str.split_whitespace().skip(1);
+ let mut version = words.next().ok_or(None)?.split('.');
+ let major = version.next().ok_or(None)?.parse()?;
+ let minor = version.next().ok_or(None)?.parse()?;
+ let patchlevel = words.nth(1).ok_or(None)?.to_owned();
+
+ Ok(Version {
+ major,
+ minor,
+ patch: patchlevel,
+ })
+}
+
+fn scale_factor(map: &map::axis::Map<axis::Properties>, axes: Axes) -> (f64, f64) {
+ use crate::Axes::*;
+ use crate::Axis::*;
+
+ match axes {
+ BottomXLeftY => (
+ map.get(BottomX).map_or(1., ScaleFactorTrait::scale_factor),
+ map.get(LeftY).map_or(1., ScaleFactorTrait::scale_factor),
+ ),
+ BottomXRightY => (
+ map.get(BottomX).map_or(1., ScaleFactorTrait::scale_factor),
+ map.get(RightY).map_or(1., ScaleFactorTrait::scale_factor),
+ ),
+ TopXLeftY => (
+ map.get(TopX).map_or(1., ScaleFactorTrait::scale_factor),
+ map.get(LeftY).map_or(1., ScaleFactorTrait::scale_factor),
+ ),
+ TopXRightY => (
+ map.get(TopX).map_or(1., ScaleFactorTrait::scale_factor),
+ map.get(RightY).map_or(1., ScaleFactorTrait::scale_factor),
+ ),
+ }
+}
+
+// XXX :-1: to intra-crate privacy rules
+/// Private
+trait ScaleFactorTrait {
+ /// Private
+ fn scale_factor(&self) -> f64;
+}
+
+#[cfg(test)]
+mod test {
+ #[test]
+ fn version() {
+ if let Ok(version) = super::version() {
+ assert!(version.major >= 4);
+ } else {
+ println!("Gnuplot not installed.");
+ }
+ }
+
+ #[test]
+ fn test_parse_version_on_valid_string() {
+ let string = "gnuplot 5.0 patchlevel 7";
+ let version = super::parse_version(&string).unwrap();
+ assert_eq!(5, version.major);
+ assert_eq!(0, version.minor);
+ assert_eq!("7", &version.patch);
+ }
+
+ #[test]
+ fn test_parse_gentoo_version() {
+ let string = "gnuplot 5.2 patchlevel 5a (Gentoo revision r0)";
+ let version = super::parse_version(&string).unwrap();
+ assert_eq!(5, version.major);
+ assert_eq!(2, version.minor);
+ assert_eq!("5a", &version.patch);
+ }
+
+ #[test]
+ fn test_parse_version_returns_error_on_invalid_strings() {
+ let strings = [
+ "",
+ "foobar",
+ "gnuplot 50 patchlevel 7",
+ "gnuplot 5.0 patchlevel",
+ "gnuplot foo.bar patchlevel 7",
+ ];
+ for string in &strings {
+ assert!(super::parse_version(string).is_err());
+ }
+ }
+}
diff --git a/src/map.rs b/src/map.rs
new file mode 100755
index 0000000..7099a96
--- /dev/null
+++ b/src/map.rs
@@ -0,0 +1,168 @@
+//! Enum Maps
+
+pub mod axis {
+ use crate::Axis;
+
+ const LENGTH: usize = 4;
+
+ pub struct Items<'a, T>
+ where
+ T: 'a,
+ {
+ map: &'a Map<T>,
+ state: Option<Axis>,
+ }
+
+ impl<'a, T> Iterator for Items<'a, T> {
+ type Item = (Axis, &'a T);
+
+ fn next(&mut self) -> Option<(Axis, &'a T)> {
+ while let Some(key) = self.state {
+ self.state = key.next();
+
+ if let Some(value) = self.map.get(key) {
+ return Some((key, value));
+ }
+ }
+
+ None
+ }
+ }
+
+ pub struct Map<T>([Option<T>; LENGTH]);
+
+ impl<T> Default for Map<T> {
+ fn default() -> Self {
+ Self::new()
+ }
+ }
+
+ impl<T> Map<T> {
+ pub fn new() -> Map<T> {
+ Map([None, None, None, None])
+ }
+
+ pub fn contains_key(&self, key: Axis) -> bool {
+ self.0[key as usize].is_some()
+ }
+
+ pub fn get(&self, key: Axis) -> Option<&T> {
+ self.0[key as usize].as_ref()
+ }
+
+ pub fn get_mut(&mut self, key: Axis) -> Option<&mut T> {
+ self.0[key as usize].as_mut()
+ }
+
+ pub fn insert(&mut self, key: Axis, value: T) -> Option<T> {
+ let key = key as usize;
+ let old = self.0[key].take();
+
+ self.0[key] = Some(value);
+
+ old
+ }
+
+ pub fn iter(&self) -> Items<T> {
+ Items {
+ map: self,
+ state: Some(Axis::BottomX),
+ }
+ }
+ }
+
+ impl<T> Clone for Map<T>
+ where
+ T: Clone,
+ {
+ fn clone(&self) -> Map<T> {
+ Map([
+ self.0[0].clone(),
+ self.0[1].clone(),
+ self.0[2].clone(),
+ self.0[3].clone(),
+ ])
+ }
+ }
+}
+
+pub mod grid {
+ use crate::Grid;
+
+ const LENGTH: usize = 2;
+
+ pub struct Items<'a, T>
+ where
+ T: 'a,
+ {
+ map: &'a Map<T>,
+ state: Option<Grid>,
+ }
+
+ impl<'a, T> Iterator for Items<'a, T> {
+ type Item = (Grid, &'a T);
+
+ fn next(&mut self) -> Option<(Grid, &'a T)> {
+ while let Some(key) = self.state {
+ self.state = key.next();
+
+ if let Some(value) = self.map.get(key) {
+ return Some((key, value));
+ }
+ }
+
+ None
+ }
+ }
+
+ pub struct Map<T>([Option<T>; LENGTH]);
+
+ impl<T> Map<T> {
+ pub fn new() -> Map<T> {
+ Map([None, None])
+ }
+
+ pub fn contains_key(&self, key: Grid) -> bool {
+ self.0[key as usize].is_some()
+ }
+
+ pub fn get(&self, key: Grid) -> Option<&T> {
+ self.0[key as usize].as_ref()
+ }
+
+ pub fn get_mut(&mut self, key: Grid) -> Option<&mut T> {
+ self.0[key as usize].as_mut()
+ }
+
+ pub fn insert(&mut self, key: Grid, value: T) -> Option<T> {
+ let key = key as usize;
+ let old = self.0[key].take();
+
+ self.0[key] = Some(value);
+
+ old
+ }
+
+ pub fn iter(&self) -> Items<T> {
+ Items {
+ map: self,
+ state: Some(Grid::Major),
+ }
+ }
+ }
+
+ impl<T> Clone for Map<T>
+ where
+ T: Clone,
+ {
+ fn clone(&self) -> Map<T> {
+ Map([self.0[0].clone(), self.0[1].clone()])
+ }
+ }
+
+ impl<T> Default for Map<T> {
+ fn default() -> Self {
+ Self::new()
+ }
+ }
+}
diff --git a/src/prelude.rs b/src/prelude.rs
new file mode 100755
index 0000000..e42c45f
--- /dev/null
+++ b/src/prelude.rs
@@ -0,0 +1,13 @@
+//! A collection of the most used traits, structs and enums
+
+pub use crate::candlestick::Candlesticks;
+pub use crate::curve::Curve::{Dots, Impulses, Lines, LinesPoints, Points, Steps};
+pub use crate::errorbar::ErrorBar::{XErrorBars, XErrorLines, YErrorBars, YErrorLines};
+pub use crate::filledcurve::FilledCurve;
+pub use crate::key::{Boxed, Horizontal, Justification, Order, Position, Stacked, Vertical};
+pub use crate::proxy::{Font, Label, Output, Title};
+pub use crate::traits::{Configure, Plot, Set};
+pub use crate::{
+ Axes, Axis, BoxWidth, Color, Figure, FontSize, Grid, Key, LineType, LineWidth, Opacity,
+ PointSize, PointType, Range, Scale, ScaleFactor, Size, Terminal, TicLabels,
+};
diff --git a/src/proxy.rs b/src/proxy.rs
new file mode 100755
index 0000000..401b7f9
--- /dev/null
+++ b/src/proxy.rs
@@ -0,0 +1,47 @@
+//! Generic constructors for newtypes
+
+#![allow(non_snake_case)]
+
+use crate::{Font as FontType, Label as LabelType, Output as OutputType, Title as TitleType};
+use std::borrow::Cow;
+use std::path::Path;
+
+/// Generic constructor for `Font`
+#[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))]
+#[inline(always)]
+pub fn Font<S>(string: S) -> FontType
+where
+ S: Into<Cow<'static, str>>,
+{
+ FontType(string.into())
+}
+
+/// Generic constructor for `Label`
+#[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))]
+#[inline(always)]
+pub fn Label<S>(string: S) -> LabelType
+where
+ S: Into<Cow<'static, str>>,
+{
+ LabelType(string.into())
+}
+
+/// Generic constructor for `Title`
+#[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))]
+#[inline(always)]
+pub fn Title<S>(string: S) -> TitleType
+where
+ S: Into<Cow<'static, str>>,
+{
+ TitleType(string.into())
+}
+
+/// Generic constructor for `Output`
+#[cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))]
+#[inline(always)]
+pub fn Output<P>(path: P) -> OutputType
+where
+ P: Into<Cow<'static, Path>>,
+{
+ OutputType(path.into())
+}
diff --git a/src/traits.rs b/src/traits.rs
new file mode 100755
index 0000000..52d2233
--- /dev/null
+++ b/src/traits.rs
@@ -0,0 +1,35 @@
+//! Traits
+
+/// Overloaded `configure` method
+pub trait Configure<This> {
+ /// The properties of what's being configured
+ type Properties;
+
+ /// Configure some set of properties
+ fn configure<F>(&mut self, this: This, function: F) -> &mut Self
+ where
+ F: FnOnce(&mut Self::Properties) -> &mut Self::Properties;
+}
+
+/// Types that can be plotted
+pub trait Data {
+ /// Convert the type into a double precision float
+ fn f64(self) -> f64;
+}
+
+/// Overloaded `plot` method
+pub trait Plot<This> {
+ /// The properties associated to the plot
+ type Properties;
+
+ /// Plots some `data` with some `configuration`
+ fn plot<F>(&mut self, this: This, function: F) -> &mut Self
+ where
+ F: FnOnce(&mut Self::Properties) -> &mut Self::Properties;
+}
+
+/// Overloaded `set` method
+pub trait Set<T> {
+ /// Sets some property
+ fn set(&mut self, value: T) -> &mut Self;
+}