Skip to content

Commit 54da537

Browse files
committed
Add dashed line style.
1 parent 7030c73 commit 54da537

File tree

4 files changed

+170
-8
lines changed

4 files changed

+170
-8
lines changed

plotters/src/element/basic_shapes.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,118 @@ fn test_path_element() {
131131
.expect("Drawing Failure");
132132
}
133133

134+
/// An element of a series of connected lines in dash style.
135+
///
136+
/// It's similar to [`PathElement`] but has a dash style.
137+
pub struct DashedPathElement<I: Iterator + Clone, Size: SizeDesc> {
138+
points: I,
139+
size: Size,
140+
spacing: Size,
141+
style: ShapeStyle,
142+
}
143+
144+
impl<I: Iterator + Clone, Size: SizeDesc> DashedPathElement<I, Size> {
145+
/// Create a new path
146+
/// - `points`: The iterator of the points
147+
/// - `size`: The dash size
148+
/// - `spacing`: The dash-to-dash spacing (gap size)
149+
/// - `style`: The shape style
150+
/// - returns the created element
151+
pub fn new<I0, S>(points: I0, size: Size, spacing: Size, style: S) -> Self
152+
where
153+
I0: IntoIterator<IntoIter = I>,
154+
S: Into<ShapeStyle>,
155+
{
156+
Self {
157+
points: points.into_iter(),
158+
size,
159+
spacing,
160+
style: style.into(),
161+
}
162+
}
163+
}
164+
165+
impl<'a, I: Iterator + Clone, Size: SizeDesc> PointCollection<'a, I::Item>
166+
for &'a DashedPathElement<I, Size>
167+
{
168+
type Point = I::Item;
169+
type IntoIter = I;
170+
fn point_iter(self) -> Self::IntoIter {
171+
self.points.clone()
172+
}
173+
}
174+
175+
impl<I0: Iterator + Clone, Size: SizeDesc, DB: DrawingBackend> Drawable<DB>
176+
for DashedPathElement<I0, Size>
177+
{
178+
fn draw<I: Iterator<Item = BackendCoord>>(
179+
&self,
180+
mut points: I,
181+
backend: &mut DB,
182+
ps: (u32, u32),
183+
) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
184+
let to_i = |(x, y): (f32, f32)| (x.round() as i32, y.round() as i32);
185+
let to_f = |(x, y): (i32, i32)| (x as f32, y as f32);
186+
let mut start = match points.next() {
187+
Some(c) => to_f(c),
188+
None => return Ok(()),
189+
};
190+
let size = self.size.in_pixels(&ps).max(0) as f32;
191+
let spacing = self.spacing.in_pixels(&ps).max(0) as f32;
192+
for curr in points {
193+
let curr_f = to_f(curr);
194+
let (dx, dy) = (curr_f.0 - start.0, curr_f.1 - start.1);
195+
let mut d = dx.hypot(dy);
196+
let scale = size / d;
197+
let gap_scale = spacing / d;
198+
while d >= size {
199+
// solid line
200+
let end = (start.0 + dx * scale, start.1 + dy * scale);
201+
backend.draw_path([to_i(start), to_i(end)], &self.style)?;
202+
// spacing
203+
start = (end.0 + dx * gap_scale, end.1 + dy * gap_scale);
204+
d -= size + spacing;
205+
}
206+
// the last point
207+
if d > 0. {
208+
backend.draw_path([to_i(start), curr], &self.style)?;
209+
}
210+
start = curr_f;
211+
}
212+
Ok(())
213+
}
214+
}
215+
216+
#[cfg(test)]
217+
#[test]
218+
fn test_dashed_path_element() {
219+
use crate::prelude::*;
220+
let check_list = std::cell::RefCell::new(vec![
221+
[(100, 100), (100, 105)],
222+
[(100, 107), (100, 112)],
223+
[(100, 114), (100, 119)],
224+
[(100, 121), (100, 126)],
225+
]);
226+
let da = crate::create_mocked_drawing_area(300, 300, |m| {
227+
m.check_draw_path(move |c, s, path| {
228+
assert_eq!(c, BLUE.to_rgba());
229+
assert_eq!(s, 7);
230+
assert_eq!(path, check_list.borrow_mut().remove(0));
231+
});
232+
m.drop_check(|b| {
233+
assert_eq!(b.num_draw_path_call, 4);
234+
assert_eq!(b.draw_count, 4);
235+
});
236+
});
237+
da.draw(&DashedPathElement::new(
238+
vec![(100, 100), (100, 103), (100, 120)],
239+
5.,
240+
2.,
241+
BLUE.stroke_width(7),
242+
))
243+
.expect("Drawing Failure");
244+
}
245+
134246
/// A rectangle element
135247
pub struct Rectangle<Coord> {
136248
points: [Coord; 2],

plotters/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -839,12 +839,12 @@ pub mod prelude {
839839
pub use crate::series::AreaSeries;
840840
#[cfg(feature = "histogram")]
841841
pub use crate::series::Histogram;
842-
#[cfg(feature = "line_series")]
843-
pub use crate::series::LineSeries;
844842
#[cfg(feature = "point_series")]
845843
pub use crate::series::PointSeries;
846844
#[cfg(feature = "surface_series")]
847845
pub use crate::series::SurfaceSeries;
846+
#[cfg(feature = "line_series")]
847+
pub use crate::series::{DashedLineSeries, LineSeries};
848848

849849
// Styles
850850
pub use crate::style::{BLACK, BLUE, CYAN, GREEN, MAGENTA, RED, TRANSPARENT, WHITE, YELLOW};

plotters/src/series/line_series.rs

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::element::{Circle, DynElement, IntoDynElement, PathElement};
2-
use crate::style::ShapeStyle;
1+
use crate::element::{Circle, DashedPathElement, DynElement, IntoDynElement, PathElement};
2+
use crate::style::{ShapeStyle, SizeDesc};
33
use plotters_backend::DrawingBackend;
44
use std::marker::PhantomData;
55

@@ -84,6 +84,48 @@ impl<DB: DrawingBackend, Coord> LineSeries<DB, Coord> {
8484
}
8585
}
8686

87+
/// A dashed line series, map an iterable object to the dashed line element.
88+
pub struct DashedLineSeries<I: Iterator + Clone, Size: SizeDesc> {
89+
points: I,
90+
size: Size,
91+
spacing: Size,
92+
style: ShapeStyle,
93+
}
94+
95+
impl<I: Iterator + Clone, Size: SizeDesc> DashedLineSeries<I, Size> {
96+
/// Create a new line series from
97+
/// - `points`: The iterator of the points
98+
/// - `size`: The dash size
99+
/// - `spacing`: The dash-to-dash spacing (gap size)
100+
/// - `style`: The shape style
101+
/// - returns the created element
102+
pub fn new<I0>(points: I0, size: Size, spacing: Size, style: ShapeStyle) -> Self
103+
where
104+
I0: IntoIterator<IntoIter = I>,
105+
{
106+
Self {
107+
points: points.into_iter(),
108+
size,
109+
spacing,
110+
style,
111+
}
112+
}
113+
}
114+
115+
impl<I: Iterator + Clone, Size: SizeDesc> IntoIterator for DashedLineSeries<I, Size> {
116+
type Item = DashedPathElement<I, Size>;
117+
type IntoIter = std::iter::Once<Self::Item>;
118+
119+
fn into_iter(self) -> Self::IntoIter {
120+
std::iter::once(DashedPathElement::new(
121+
self.points,
122+
self.size,
123+
self.spacing,
124+
self.style,
125+
))
126+
}
127+
}
128+
87129
#[cfg(test)]
88130
mod test {
89131
use crate::prelude::*;
@@ -102,8 +144,8 @@ mod test {
102144
});
103145

104146
m.drop_check(|b| {
105-
assert_eq!(b.num_draw_path_call, 1);
106-
assert_eq!(b.draw_count, 1);
147+
assert_eq!(b.num_draw_path_call, 11);
148+
assert_eq!(b.draw_count, 11);
107149
});
108150
});
109151

@@ -114,7 +156,15 @@ mod test {
114156
chart
115157
.draw_series(LineSeries::new(
116158
(0..100).map(|x| (x, x)),
117-
Into::<ShapeStyle>::into(&RED).stroke_width(3),
159+
RED.stroke_width(3),
160+
))
161+
.expect("Drawing Error");
162+
chart
163+
.draw_series(DashedLineSeries::new(
164+
(0..=50).map(|x| (x, x)),
165+
10,
166+
5,
167+
RED.stroke_width(3),
118168
))
119169
.expect("Drawing Error");
120170
}

plotters/src/series/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub use area_series::AreaSeries;
2626
#[cfg(feature = "histogram")]
2727
pub use histogram::Histogram;
2828
#[cfg(feature = "line_series")]
29-
pub use line_series::LineSeries;
29+
pub use line_series::{DashedLineSeries, LineSeries};
3030
#[cfg(feature = "point_series")]
3131
pub use point_series::PointSeries;
3232
#[cfg(feature = "surface_series")]

0 commit comments

Comments
 (0)