plotters/series/line_series.rs
1use crate::element::{
2 Circle, DashedPathElement, DottedPathElement, DynElement, IntoDynElement, PathElement,
3};
4use crate::style::{ShapeStyle, SizeDesc};
5use plotters_backend::{BackendCoord, DrawingBackend};
6use std::marker::PhantomData;
7
8/**
9The line series object, which takes an iterator of data points in guest coordinate system
10and creates appropriate lines and points with the given style.
11
12# Example
13
14```
15use plotters::prelude::*;
16let x_values = [0.0f64, 1., 2., 3., 4.];
17let drawing_area = SVGBackend::new("line_series_point_size.svg", (300, 200)).into_drawing_area();
18drawing_area.fill(&WHITE).unwrap();
19let mut chart_builder = ChartBuilder::on(&drawing_area);
20chart_builder.margin(10).set_left_and_bottom_label_area_size(20);
21let mut chart_context = chart_builder.build_cartesian_2d(0.0..4.0, 0.0..3.0).unwrap();
22chart_context.configure_mesh().draw().unwrap();
23chart_context.draw_series(LineSeries::new(x_values.map(|x| (x, 0.3 * x)), BLACK)).unwrap();
24chart_context.draw_series(LineSeries::new(x_values.map(|x| (x, 2.5 - 0.05 * x * x)), RED)
25 .point_size(5)).unwrap();
26chart_context.draw_series(LineSeries::new(x_values.map(|x| (x, 2. - 0.1 * x * x)), BLUE.filled())
27 .point_size(4)).unwrap();
28```
29
30The result is a chart with three line series; two of them have their data points highlighted:
31
32
33*/
34pub 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
42impl<DB: DrawingBackend, Coord: Clone + 'static> Iterator for LineSeries<DB, Coord> {
43 type Item = DynElement<'static, DB, Coord>;
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
62impl<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 */
68 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 */
83 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/// ```
126pub struct DashedLineSeries<I: Iterator + Clone, Size: SizeDesc> {
127 points: I,
128 size: Size,
129 spacing: Size,
130 style: ShapeStyle,
131}
132
133impl<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
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
153impl<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
157 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.
168pub 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
175impl<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
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
196impl<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
202 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)]
213mod test {
214 use crate::prelude::*;
215
216 #[test]
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}