| 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 |  |
| 97 | |
| 98 | [`ErrorBar::new_vertical()`] is used to create vertical error bars. Here is an example using |
| 99 | [`ErrorBar::new_horizontal()`] instead: |
| 100 | |
| 101 |  |
| 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 | |