• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@64e0a28/apidoc/line_series_point_size.svg)
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