plotpy/
fill_between.rs

1use super::{vector_to_array, AsVector, GraphMaker};
2use num_traits::Num;
3use std::fmt::Write;
4
5/// Fills the area between two curves
6///
7/// # Examples
8///
9/// ```
10/// use plotpy::{Curve, FillBetween, Plot, StrError, linspace};
11///
12/// fn main() -> Result<(), StrError> {
13///     // data and curve
14///     let x = linspace(-1.0, 2.0, 21);
15///     let y: Vec<_> = x.iter().map(|&x| x * x).collect();
16///     let mut curve = Curve::new();
17///     curve.set_line_color("black").draw(&x, &y);
18///
19///     // draw area between curve and x-axis
20///     // (note that we have to use "y1" as variable name for the curve)
21///     let mut fb = FillBetween::new();
22///     fb.set_where("y1>=0.5").set_extra("alpha=0.5").draw(&x, &y, None);
23///
24///     // add curve and fb to plot
25///     let mut plot = Plot::new();
26///     plot.add(&curve).add(&fb);
27///
28///     // save figure
29///     plot.save("/tmp/plotpy/doc_tests/doc_fill_between.svg")?;
30///     Ok(())
31/// }
32/// ```
33///
34/// ![doc_fill_between.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/doc_fill_between.svg)
35pub struct FillBetween {
36    where_condition: String,
37    facecolor: String,
38    interpolate: bool,
39    extra: String,
40    buffer: String,
41}
42
43impl FillBetween {
44    /// Allocates a new instance
45    pub fn new() -> Self {
46        FillBetween {
47            where_condition: String::new(),
48            facecolor: String::new(),
49            interpolate: false,
50            extra: String::new(),
51            buffer: String::new(),
52        }
53    }
54
55    /// Draws the filled area between two curves
56    ///
57    /// * `x` - x values
58    /// * `y1` - y values of the first curve
59    /// * `y2` - optional y values of the second curve. If None, fills area between y1 and x-axis
60    ///
61    pub fn draw<'a, T, U>(&mut self, x: &'a T, y1: &'a T, y2: Option<&'a T>)
62    where
63        T: AsVector<'a, U>,
64        U: 'a + std::fmt::Display + Num,
65    {
66        let opt = self.options();
67        vector_to_array(&mut self.buffer, "x", x);
68        vector_to_array(&mut self.buffer, "y1", y1);
69        match y2 {
70            Some(y2) => {
71                vector_to_array(&mut self.buffer, "y2", y2);
72                write!(&mut self.buffer, "plt.fill_between(x,y1,y2{})\n", &opt).unwrap();
73            }
74            None => {
75                write!(&mut self.buffer, "plt.fill_between(x,y1{})\n", &opt).unwrap();
76            }
77        }
78    }
79
80    /// Sets the condition to select the area to be filled.
81    ///
82    /// For example: "y2>=y1" or "y2<=y1"
83    ///
84    /// **WARNING:** `condition` must use `y1` and `y2` as variable names for the two curves.
85    pub fn set_where(&mut self, condition: &str) -> &mut Self {
86        self.where_condition = condition.to_string();
87        self
88    }
89
90    /// Sets the face color of the filled area.
91    pub fn set_facecolor(&mut self, color: &str) -> &mut Self {
92        self.facecolor = color.to_string();
93        self
94    }
95
96    /// Calculates the actual intersection point and extend the filled region up to this point.
97    ///
98    /// From <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.fill_between.html>:
99    ///
100    /// "This option is only relevant if where is used and the two curves are crossing each other. Semantically,
101    /// `where` is often used for y1 > y2 or similar. By default, the nodes of the polygon defining the filled
102    /// region will only be placed at the positions in the x array. Such a polygon cannot describe the above
103    /// semantics close to the intersection. The x-sections containing the intersection are simply clipped."
104    ///
105    /// Default is false.
106    pub fn set_interpolate(&mut self, interpolate: bool) -> &mut Self {
107        self.interpolate = interpolate;
108        self
109    }
110
111    /// Fills the area between two curves
112    ///
113    /// **WARNING:** `where_condition` must use `y1` and `y2` as variable names for the two curves.
114    /// For example:
115    ///
116    /// ```text
117    /// curve.fill_between(x, y1, y2, "y2>=y1", "#ffaabb", true, "");
118    /// curve.fill_between(x, y1, y2, "y2>=y1", "#ffaabb", true, "");
119    /// curve.fill_between(x, y1, y2b, "y2<=y1", "#c1e3ff", true, "");
120    /// ```
121    ///
122    /// **Note:** This method does not use the options of the Curve object.
123    ///
124    /// See more options in <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.fill_between.html>
125    pub fn set_extra(&mut self, extra: &str) -> &mut Self {
126        self.extra = extra.to_string();
127        self
128    }
129
130    /// Returns the options
131    fn options(&self) -> String {
132        let mut opt = String::new();
133        if self.facecolor != "" {
134            write!(&mut opt, ",facecolor='{}'", self.facecolor).unwrap();
135        }
136        if self.where_condition != "" {
137            write!(&mut opt, ",where={}", self.where_condition).unwrap();
138        }
139        if self.interpolate {
140            write!(&mut opt, ",interpolate=True").unwrap();
141        }
142        if self.extra != "" {
143            write!(&mut opt, ",{}", self.extra).unwrap();
144        }
145        opt
146    }
147}
148
149impl GraphMaker for FillBetween {
150    fn get_buffer<'a>(&'a self) -> &'a String {
151        &self.buffer
152    }
153    fn clear_buffer(&mut self) {
154        self.buffer.clear();
155    }
156}
157
158////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
159
160#[cfg(test)]
161mod tests {
162    use super::FillBetween;
163
164    #[test]
165    fn new_works() {
166        let fill_between = FillBetween::new();
167        assert_eq!(fill_between.where_condition, "");
168        assert_eq!(fill_between.facecolor, "");
169        assert_eq!(fill_between.interpolate, false);
170        assert_eq!(fill_between.extra, "");
171        assert_eq!(fill_between.buffer.len(), 0);
172    }
173}