plotters/element/
errorbar.rs

1use std::marker::PhantomData;
2
3use crate::element::{Drawable, PointCollection};
4use crate::style::ShapeStyle;
5use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
6
7/**
8Used to reuse code between horizontal and vertical error bars.
9
10This is used internally by Plotters and should probably not be included in user code.
11See [`ErrorBar`] for more information and examples.
12*/
13pub trait ErrorBarOrient<K, V> {
14    type XType;
15    type YType;
16
17    fn make_coord(key: K, val: V) -> (Self::XType, Self::YType);
18    fn ending_coord(coord: BackendCoord, w: u32) -> (BackendCoord, BackendCoord);
19}
20
21/**
22Used for the production of horizontal error bars.
23
24This is used internally by Plotters and should probably not be included in user code.
25See [`ErrorBar`] for more information and examples.
26*/
27pub struct ErrorBarOrientH<K, V>(PhantomData<(K, V)>);
28
29/**
30Used for the production of vertical error bars.
31
32This is used internally by Plotters and should probably not be included in user code.
33See [`ErrorBar`] for more information and examples.
34*/
35pub struct ErrorBarOrientV<K, V>(PhantomData<(K, V)>);
36
37impl<K, V> ErrorBarOrient<K, V> for ErrorBarOrientH<K, V> {
38    type XType = V;
39    type YType = K;
40
41    fn make_coord(key: K, val: V) -> (V, K) {
42        (val, key)
43    }
44
45    fn ending_coord(coord: BackendCoord, w: u32) -> (BackendCoord, BackendCoord) {
46        (
47            (coord.0, coord.1 - w as i32 / 2),
48            (coord.0, coord.1 + w as i32 / 2),
49        )
50    }
51}
52
53impl<K, V> ErrorBarOrient<K, V> for ErrorBarOrientV<K, V> {
54    type XType = K;
55    type YType = V;
56
57    fn make_coord(key: K, val: V) -> (K, V) {
58        (key, val)
59    }
60
61    fn ending_coord(coord: BackendCoord, w: u32) -> (BackendCoord, BackendCoord) {
62        (
63            (coord.0 - w as i32 / 2, coord.1),
64            (coord.0 + w as i32 / 2, coord.1),
65        )
66    }
67}
68
69/**
70An error bar, which visualizes the minimum, average, and maximum of a dataset.
71
72Unlike [`crate::series::Histogram`], the `ErrorBar` code does not classify or aggregate data.
73These operations must be done before building error bars.
74
75# Examples
76
77```
78use plotters::prelude::*;
79let data = [(1.0, 3.3), (2., 2.1), (3., 1.5), (4., 1.9), (5., 1.0)];
80let drawing_area = SVGBackend::new("error_bars_vertical.svg", (300, 200)).into_drawing_area();
81drawing_area.fill(&WHITE).unwrap();
82let mut chart_builder = ChartBuilder::on(&drawing_area);
83chart_builder.margin(10).set_left_and_bottom_label_area_size(20);
84let mut chart_context = chart_builder.build_cartesian_2d(0.0..6.0, 0.0..6.0).unwrap();
85chart_context.configure_mesh().draw().unwrap();
86chart_context.draw_series(data.map(|(x, y)| {
87    ErrorBar::new_vertical(x, y - 0.4, y, y + 0.3, BLUE.filled(), 10)
88})).unwrap();
89chart_context.draw_series(data.map(|(x, y)| {
90    ErrorBar::new_vertical(x, y + 1.0, y + 1.9, y + 2.4, RED, 10)
91})).unwrap();
92```
93
94This code produces two series of five error bars each, showing minima, maxima, and average values:
95
96![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@06d370f/apidoc/error_bars_vertical.svg)
97
98[`ErrorBar::new_vertical()`] is used to create vertical error bars. Here is an example using
99[`ErrorBar::new_horizontal()`] instead:
100
101![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@06d370f/apidoc/error_bars_horizontal.svg)
102*/
103pub struct ErrorBar<K, V, O: ErrorBarOrient<K, V>> {
104    style: ShapeStyle,
105    width: u32,
106    key: K,
107    values: [V; 3],
108    _p: PhantomData<O>,
109}
110
111impl<K, V> ErrorBar<K, V, ErrorBarOrientV<K, V>> {
112    /**
113    Creates a vertical error bar.
114    `
115    - `key`: Horizontal position of the bar
116    - `min`: Minimum of the data
117    - `avg`: Average of the data
118    - `max`: Maximum of the data
119    - `style`: Color, transparency, and fill of the error bar. See [`ShapeStyle`] for more information and examples.
120    - `width`: Width of the error marks in backend coordinates.
121
122    See [`ErrorBar`] for more information and examples.
123    */
124    pub fn new_vertical<S: Into<ShapeStyle>>(
125        key: K,
126        min: V,
127        avg: V,
128        max: V,
129        style: S,
130        width: u32,
131    ) -> Self {
132        Self {
133            style: style.into(),
134            width,
135            key,
136            values: [min, avg, max],
137            _p: PhantomData,
138        }
139    }
140}
141
142impl<K, V> ErrorBar<K, V, ErrorBarOrientH<K, V>> {
143    /**
144    Creates a horizontal error bar.
145
146    - `key`: Vertical position of the bar
147    - `min`: Minimum of the data
148    - `avg`: Average of the data
149    - `max`: Maximum of the data
150    - `style`: Color, transparency, and fill of the error bar. See [`ShapeStyle`] for more information and examples.
151    - `width`: Width of the error marks in backend coordinates.
152
153    See [`ErrorBar`] for more information and examples.
154    */
155    pub fn new_horizontal<S: Into<ShapeStyle>>(
156        key: K,
157        min: V,
158        avg: V,
159        max: V,
160        style: S,
161        width: u32,
162    ) -> Self {
163        Self {
164            style: style.into(),
165            width,
166            key,
167            values: [min, avg, max],
168            _p: PhantomData,
169        }
170    }
171}
172
173impl<'a, K: Clone, V: Clone, O: ErrorBarOrient<K, V>> PointCollection<'a, (O::XType, O::YType)>
174    for &'a ErrorBar<K, V, O>
175{
176    type Point = (O::XType, O::YType);
177    type IntoIter = Vec<Self::Point>;
178    fn point_iter(self) -> Self::IntoIter {
179        self.values
180            .iter()
181            .map(|v| O::make_coord(self.key.clone(), v.clone()))
182            .collect()
183    }
184}
185
186impl<K, V, O: ErrorBarOrient<K, V>, DB: DrawingBackend> Drawable<DB> for ErrorBar<K, V, O> {
187    fn draw<I: Iterator<Item = BackendCoord>>(
188        &self,
189        points: I,
190        backend: &mut DB,
191        _: (u32, u32),
192    ) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
193        let points: Vec<_> = points.take(3).collect();
194
195        let (from, to) = O::ending_coord(points[0], self.width);
196        backend.draw_line(from, to, &self.style)?;
197
198        let (from, to) = O::ending_coord(points[2], self.width);
199        backend.draw_line(from, to, &self.style)?;
200
201        backend.draw_line(points[0], points[2], &self.style)?;
202
203        backend.draw_circle(points[1], self.width / 2, &self.style, self.style.filled)?;
204
205        Ok(())
206    }
207}
208
209#[cfg(test)]
210#[test]
211fn test_preserve_stroke_width() {
212    let v = ErrorBar::new_vertical(100, 20, 50, 70, WHITE.filled().stroke_width(5), 3);
213    let h = ErrorBar::new_horizontal(100, 20, 50, 70, WHITE.filled().stroke_width(5), 3);
214
215    use crate::prelude::*;
216    let da = crate::create_mocked_drawing_area(300, 300, |m| {
217        m.check_draw_line(|_, w, _, _| {
218            assert_eq!(w, 5);
219        });
220    });
221    da.draw(&h).expect("Drawing Failure");
222    da.draw(&v).expect("Drawing Failure");
223}