1 use std::marker::PhantomData;
2 
3 use crate::element::{Drawable, PointCollection};
4 use crate::style::ShapeStyle;
5 use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
6 
7 /**
8 Used to reuse code between horizontal and vertical error bars.
9 
10 This is used internally by Plotters and should probably not be included in user code.
11 See [`ErrorBar`] for more information and examples.
12 */
13 pub trait ErrorBarOrient<K, V> {
14     type XType;
15     type YType;
16 
make_coord(key: K, val: V) -> (Self::XType, Self::YType)17     fn make_coord(key: K, val: V) -> (Self::XType, Self::YType);
ending_coord(coord: BackendCoord, w: u32) -> (BackendCoord, BackendCoord)18     fn ending_coord(coord: BackendCoord, w: u32) -> (BackendCoord, BackendCoord);
19 }
20 
21 /**
22 Used for the production of horizontal error bars.
23 
24 This is used internally by Plotters and should probably not be included in user code.
25 See [`ErrorBar`] for more information and examples.
26 */
27 pub struct ErrorBarOrientH<K, V>(PhantomData<(K, V)>);
28 
29 /**
30 Used for the production of vertical error bars.
31 
32 This is used internally by Plotters and should probably not be included in user code.
33 See [`ErrorBar`] for more information and examples.
34 */
35 pub struct ErrorBarOrientV<K, V>(PhantomData<(K, V)>);
36 
37 impl<K, V> ErrorBarOrient<K, V> for ErrorBarOrientH<K, V> {
38     type XType = V;
39     type YType = K;
40 
make_coord(key: K, val: V) -> (V, K)41     fn make_coord(key: K, val: V) -> (V, K) {
42         (val, key)
43     }
44 
ending_coord(coord: BackendCoord, w: u32) -> (BackendCoord, BackendCoord)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 
53 impl<K, V> ErrorBarOrient<K, V> for ErrorBarOrientV<K, V> {
54     type XType = K;
55     type YType = V;
56 
make_coord(key: K, val: V) -> (K, V)57     fn make_coord(key: K, val: V) -> (K, V) {
58         (key, val)
59     }
60 
ending_coord(coord: BackendCoord, w: u32) -> (BackendCoord, BackendCoord)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 /**
70 An error bar, which visualizes the minimum, average, and maximum of a dataset.
71 
72 Unlike [`crate::series::Histogram`], the `ErrorBar` code does not classify or aggregate data.
73 These operations must be done before building error bars.
74 
75 # Examples
76 
77 ```
78 use plotters::prelude::*;
79 let data = [(1.0, 3.3), (2., 2.1), (3., 1.5), (4., 1.9), (5., 1.0)];
80 let drawing_area = SVGBackend::new("error_bars_vertical.svg", (300, 200)).into_drawing_area();
81 drawing_area.fill(&WHITE).unwrap();
82 let mut chart_builder = ChartBuilder::on(&drawing_area);
83 chart_builder.margin(10).set_left_and_bottom_label_area_size(20);
84 let mut chart_context = chart_builder.build_cartesian_2d(0.0..6.0, 0.0..6.0).unwrap();
85 chart_context.configure_mesh().draw().unwrap();
86 chart_context.draw_series(data.map(|(x, y)| {
87     ErrorBar::new_vertical(x, y - 0.4, y, y + 0.3, BLUE.filled(), 10)
88 })).unwrap();
89 chart_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 
94 This 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 */
103 pub 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 
111 impl<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     */
new_vertical<S: Into<ShapeStyle>>( key: K, min: V, avg: V, max: V, style: S, width: u32, ) -> Self124     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 
142 impl<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     */
new_horizontal<S: Into<ShapeStyle>>( key: K, min: V, avg: V, max: V, style: S, width: u32, ) -> Self155     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 
173 impl<'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>;
point_iter(self) -> Self::IntoIter178     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 
186 impl<K, V, O: ErrorBarOrient<K, V>, DB: DrawingBackend> Drawable<DB> for ErrorBar<K, V, O> {
draw<I: Iterator<Item = BackendCoord>>( &self, points: I, backend: &mut DB, _: (u32, u32), ) -> Result<(), DrawingErrorKind<DB::ErrorType>>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]
test_preserve_stroke_width()211 fn 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 }
224