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 | |
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 | /** |
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 | |
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 | |
53 | impl<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 | /** |
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 | */ |
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 | |
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 | */ |
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 | |
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>; |
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 | |
186 | impl<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] |
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 | |