1 use crate::element::{ 2 Circle, DashedPathElement, DottedPathElement, DynElement, IntoDynElement, PathElement, 3 }; 4 use crate::style::{ShapeStyle, SizeDesc}; 5 use plotters_backend::{BackendCoord, DrawingBackend}; 6 use std::marker::PhantomData; 7 8 /** 9 The line series object, which takes an iterator of data points in guest coordinate system 10 and creates appropriate lines and points with the given style. 11 12 # Example 13 14 ``` 15 use plotters::prelude::*; 16 let x_values = [0.0f64, 1., 2., 3., 4.]; 17 let drawing_area = SVGBackend::new("line_series_point_size.svg", (300, 200)).into_drawing_area(); 18 drawing_area.fill(&WHITE).unwrap(); 19 let mut chart_builder = ChartBuilder::on(&drawing_area); 20 chart_builder.margin(10).set_left_and_bottom_label_area_size(20); 21 let mut chart_context = chart_builder.build_cartesian_2d(0.0..4.0, 0.0..3.0).unwrap(); 22 chart_context.configure_mesh().draw().unwrap(); 23 chart_context.draw_series(LineSeries::new(x_values.map(|x| (x, 0.3 * x)), BLACK)).unwrap(); 24 chart_context.draw_series(LineSeries::new(x_values.map(|x| (x, 2.5 - 0.05 * x * x)), RED) 25 .point_size(5)).unwrap(); 26 chart_context.draw_series(LineSeries::new(x_values.map(|x| (x, 2. - 0.1 * x * x)), BLUE.filled()) 27 .point_size(4)).unwrap(); 28 ``` 29 30 The result is a chart with three line series; two of them have their data points highlighted: 31 32  33 */ 34 pub struct LineSeries<DB: DrawingBackend, Coord> { 35 style: ShapeStyle, 36 data: Vec<Coord>, 37 point_idx: usize, 38 point_size: u32, 39 phantom: PhantomData<DB>, 40 } 41 42 impl<DB: DrawingBackend, Coord: Clone + 'static> Iterator for LineSeries<DB, Coord> { 43 type Item = DynElement<'static, DB, Coord>; next(&mut self) -> Option<Self::Item>44 fn next(&mut self) -> Option<Self::Item> { 45 if !self.data.is_empty() { 46 if self.point_size > 0 && self.point_idx < self.data.len() { 47 let idx = self.point_idx; 48 self.point_idx += 1; 49 return Some( 50 Circle::new(self.data[idx].clone(), self.point_size, self.style).into_dyn(), 51 ); 52 } 53 let mut data = vec![]; 54 std::mem::swap(&mut self.data, &mut data); 55 Some(PathElement::new(data, self.style).into_dyn()) 56 } else { 57 None 58 } 59 } 60 } 61 62 impl<DB: DrawingBackend, Coord> LineSeries<DB, Coord> { 63 /** 64 Creates a new line series based on a data iterator and a given style. 65 66 See [`LineSeries`] for more information and examples. 67 */ new<I: IntoIterator<Item = Coord>, S: Into<ShapeStyle>>(iter: I, style: S) -> Self68 pub fn new<I: IntoIterator<Item = Coord>, S: Into<ShapeStyle>>(iter: I, style: S) -> Self { 69 Self { 70 style: style.into(), 71 data: iter.into_iter().collect(), 72 point_size: 0, 73 point_idx: 0, 74 phantom: PhantomData, 75 } 76 } 77 78 /** 79 Sets the size of the points in the series, in pixels. 80 81 See [`LineSeries`] for more information and examples. 82 */ point_size(mut self, size: u32) -> Self83 pub fn point_size(mut self, size: u32) -> Self { 84 self.point_size = size; 85 self 86 } 87 } 88 89 /// A dashed line series, map an iterable object to the dashed line element. Can be used to draw simple dashed and dotted lines. 90 /// 91 /// If you want to use more complex shapes as points in the line, you can use `plotters::series::line_series::DottedLineSeries`. 92 /// 93 /// # Examples 94 /// 95 /// Dashed line: 96 /// ```Rust 97 /// chart_context 98 /// .draw_series(DashedLineSeries::new( 99 /// data_series, 100 /// 5, /* size = length of dash */ 101 /// 10, /* spacing */ 102 /// ShapeStyle { 103 /// color: BLACK.mix(1.0), 104 /// filled: false, 105 /// stroke_width: 1, 106 /// }, 107 /// )) 108 /// .unwrap(); 109 /// ``` 110 /// 111 /// Dotted line: (keep `size` and `stroke_width` the same to achieve dots) 112 /// ```Rust 113 /// chart_context 114 /// .draw_series(DashedLineSeries::new( 115 /// data_series, 116 /// 1, /* size = length of dash */ 117 /// 4, /* spacing, best to keep this at least 1 larger than size */ 118 /// ShapeStyle { 119 /// color: BLACK.mix(1.0), 120 /// filled: false, 121 /// stroke_width: 1, 122 /// }, 123 /// )) 124 /// .unwrap(); 125 /// ``` 126 pub struct DashedLineSeries<I: Iterator + Clone, Size: SizeDesc> { 127 points: I, 128 size: Size, 129 spacing: Size, 130 style: ShapeStyle, 131 } 132 133 impl<I: Iterator + Clone, Size: SizeDesc> DashedLineSeries<I, Size> { 134 /// Create a new line series from 135 /// - `points`: The iterator of the points 136 /// - `size`: The dash size 137 /// - `spacing`: The dash-to-dash spacing (gap size) 138 /// - `style`: The shape style 139 /// - returns the created element new<I0>(points: I0, size: Size, spacing: Size, style: ShapeStyle) -> Self where I0: IntoIterator<IntoIter = I>,140 pub fn new<I0>(points: I0, size: Size, spacing: Size, style: ShapeStyle) -> Self 141 where 142 I0: IntoIterator<IntoIter = I>, 143 { 144 Self { 145 points: points.into_iter(), 146 size, 147 spacing, 148 style, 149 } 150 } 151 } 152 153 impl<I: Iterator + Clone, Size: SizeDesc> IntoIterator for DashedLineSeries<I, Size> { 154 type Item = DashedPathElement<I, Size>; 155 type IntoIter = std::iter::Once<Self::Item>; 156 into_iter(self) -> Self::IntoIter157 fn into_iter(self) -> Self::IntoIter { 158 std::iter::once(DashedPathElement::new( 159 self.points, 160 self.size, 161 self.spacing, 162 self.style, 163 )) 164 } 165 } 166 167 /// A dotted line series, map an iterable object to the dotted line element. 168 pub struct DottedLineSeries<I: Iterator + Clone, Size: SizeDesc, Marker> { 169 points: I, 170 shift: Size, 171 spacing: Size, 172 func: Box<dyn Fn(BackendCoord) -> Marker>, 173 } 174 175 impl<I: Iterator + Clone, Size: SizeDesc, Marker> DottedLineSeries<I, Size, Marker> { 176 /// Create a new line series from 177 /// - `points`: The iterator of the points 178 /// - `shift`: The shift of the first marker 179 /// - `spacing`: The spacing between markers 180 /// - `func`: The marker function 181 /// - returns the created element new<I0, F>(points: I0, shift: Size, spacing: Size, func: F) -> Self where I0: IntoIterator<IntoIter = I>, F: Fn(BackendCoord) -> Marker + 'static,182 pub fn new<I0, F>(points: I0, shift: Size, spacing: Size, func: F) -> Self 183 where 184 I0: IntoIterator<IntoIter = I>, 185 F: Fn(BackendCoord) -> Marker + 'static, 186 { 187 Self { 188 points: points.into_iter(), 189 shift, 190 spacing, 191 func: Box::new(func), 192 } 193 } 194 } 195 196 impl<I: Iterator + Clone, Size: SizeDesc, Marker: 'static> IntoIterator 197 for DottedLineSeries<I, Size, Marker> 198 { 199 type Item = DottedPathElement<I, Size, Marker>; 200 type IntoIter = std::iter::Once<Self::Item>; 201 into_iter(self) -> Self::IntoIter202 fn into_iter(self) -> Self::IntoIter { 203 std::iter::once(DottedPathElement::new( 204 self.points, 205 self.shift, 206 self.spacing, 207 self.func, 208 )) 209 } 210 } 211 212 #[cfg(test)] 213 mod test { 214 use crate::prelude::*; 215 216 #[test] test_line_series()217 fn test_line_series() { 218 let drawing_area = create_mocked_drawing_area(200, 200, |m| { 219 m.check_draw_path(|c, s, _path| { 220 assert_eq!(c, RED.to_rgba()); 221 assert_eq!(s, 3); 222 // TODO when cleanup the backend coordinate definition, then we uncomment the 223 // following check 224 //for i in 0..100 { 225 // assert_eq!(path[i], (i as i32 * 2, 199 - i as i32 * 2)); 226 //} 227 }); 228 229 m.drop_check(|b| { 230 assert_eq!(b.num_draw_path_call, 8); 231 assert_eq!(b.draw_count, 27); 232 }); 233 }); 234 235 let mut chart = ChartBuilder::on(&drawing_area) 236 .build_cartesian_2d(0..100, 0..100) 237 .expect("Build chart error"); 238 239 chart 240 .draw_series(LineSeries::new( 241 (0..100).map(|x| (x, x)), 242 Into::<ShapeStyle>::into(RED).stroke_width(3), 243 )) 244 .expect("Drawing Error"); 245 chart 246 .draw_series(DashedLineSeries::new( 247 (0..=50).map(|x| (0, x)), 248 10, 249 5, 250 Into::<ShapeStyle>::into(RED).stroke_width(3), 251 )) 252 .expect("Drawing Error"); 253 let mk_f = |c| Circle::new(c, 3, Into::<ShapeStyle>::into(RED).filled()); 254 chart 255 .draw_series(DottedLineSeries::new((0..=50).map(|x| (x, 0)), 5, 5, mk_f)) 256 .expect("Drawing Error"); 257 } 258 } 259