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![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@64e0a28/apidoc/line_series_point_size.svg)
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}