| 1 | //! Filled curve plots |
| 2 | |
| 3 | use std::borrow::Cow; |
| 4 | use std::iter::IntoIterator; |
| 5 | |
| 6 | use crate::data::Matrix; |
| 7 | use crate::traits::{self, Data, Set}; |
| 8 | use crate::{Axes, Color, Default, Display, Figure, Label, Opacity, Plot, Script}; |
| 9 | |
| 10 | /// Properties common to filled curve plots |
| 11 | pub struct Properties { |
| 12 | axes: Option<Axes>, |
| 13 | color: Option<Color>, |
| 14 | label: Option<Cow<'static, str>>, |
| 15 | opacity: Option<f64>, |
| 16 | } |
| 17 | |
| 18 | impl Default for Properties { |
| 19 | fn default() -> Properties { |
| 20 | Properties { |
| 21 | axes: None, |
| 22 | color: None, |
| 23 | label: None, |
| 24 | opacity: None, |
| 25 | } |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | impl Script for Properties { |
| 30 | // Allow clippy::format_push_string even with older versions of rust (<1.62) which |
| 31 | // don't have it defined. |
| 32 | #[allow (clippy::all)] |
| 33 | fn script(&self) -> String { |
| 34 | let mut script = if let Some(axes) = self.axes { |
| 35 | format!("axes {} " , axes.display()) |
| 36 | } else { |
| 37 | String::new() |
| 38 | }; |
| 39 | script.push_str("with filledcurves " ); |
| 40 | |
| 41 | script.push_str("fillstyle " ); |
| 42 | |
| 43 | if let Some(opacity) = self.opacity { |
| 44 | script.push_str(&format!("solid {} " , opacity)) |
| 45 | } |
| 46 | |
| 47 | // TODO border shoulde be configurable |
| 48 | script.push_str("noborder " ); |
| 49 | |
| 50 | if let Some(color) = self.color { |
| 51 | script.push_str(&format!("lc rgb '{}' " , color.display())); |
| 52 | } |
| 53 | |
| 54 | if let Some(ref label) = self.label { |
| 55 | script.push_str("title '" ); |
| 56 | script.push_str(label); |
| 57 | script.push(' \'' ) |
| 58 | } else { |
| 59 | script.push_str("notitle" ) |
| 60 | } |
| 61 | |
| 62 | script |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | impl Set<Axes> for Properties { |
| 67 | /// Select axes to plot against |
| 68 | /// |
| 69 | /// **Note** By default, the `BottomXLeftY` axes are used |
| 70 | fn set(&mut self, axes: Axes) -> &mut Properties { |
| 71 | self.axes = Some(axes); |
| 72 | self |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | impl Set<Color> for Properties { |
| 77 | /// Sets the fill color |
| 78 | fn set(&mut self, color: Color) -> &mut Properties { |
| 79 | self.color = Some(color); |
| 80 | self |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | impl Set<Label> for Properties { |
| 85 | /// Sets the legend label |
| 86 | fn set(&mut self, label: Label) -> &mut Properties { |
| 87 | self.label = Some(label.0); |
| 88 | self |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | impl Set<Opacity> for Properties { |
| 93 | /// Changes the opacity of the fill color |
| 94 | /// |
| 95 | /// **Note** By default, the fill color is totally opaque (`opacity = 1.0`) |
| 96 | /// |
| 97 | /// # Panics |
| 98 | /// |
| 99 | /// Panics if `opacity` is outside the range `[0, 1]` |
| 100 | fn set(&mut self, opacity: Opacity) -> &mut Properties { |
| 101 | self.opacity = Some(opacity.0); |
| 102 | self |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | /// Fills the area between two curves |
| 107 | pub struct FilledCurve<X, Y1, Y2> { |
| 108 | /// X coordinate of the data points of both curves |
| 109 | pub x: X, |
| 110 | /// Y coordinate of the data points of the first curve |
| 111 | pub y1: Y1, |
| 112 | /// Y coordinate of the data points of the second curve |
| 113 | pub y2: Y2, |
| 114 | } |
| 115 | |
| 116 | impl<X, Y1, Y2> traits::Plot<FilledCurve<X, Y1, Y2>> for Figure |
| 117 | where |
| 118 | X: IntoIterator, |
| 119 | X::Item: Data, |
| 120 | Y1: IntoIterator, |
| 121 | Y1::Item: Data, |
| 122 | Y2: IntoIterator, |
| 123 | Y2::Item: Data, |
| 124 | { |
| 125 | type Properties = Properties; |
| 126 | |
| 127 | fn plot<F>(&mut self, fc: FilledCurve<X, Y1, Y2>, configure: F) -> &mut Figure |
| 128 | where |
| 129 | F: FnOnce(&mut Properties) -> &mut Properties, |
| 130 | { |
| 131 | let FilledCurve { x, y1, y2 } = fc; |
| 132 | |
| 133 | let mut props = Default::default(); |
| 134 | configure(&mut props); |
| 135 | |
| 136 | let (x_factor, y_factor) = |
| 137 | crate::scale_factor(&self.axes, props.axes.unwrap_or(crate::Axes::BottomXLeftY)); |
| 138 | |
| 139 | let data = Matrix::new(izip!(x, y1, y2), (x_factor, y_factor, y_factor)); |
| 140 | self.plots.push(Plot::new(data, &props)); |
| 141 | self |
| 142 | } |
| 143 | } |
| 144 | |