Skip to content

Commit 3a4c675

Browse files
authored
Merge pull request #483 from KmolYuan/dash
Add dashed line style.
2 parents d540c33 + 62dbd7b commit 3a4c675

File tree

4 files changed

+182
-7
lines changed

4 files changed

+182
-7
lines changed

plotters/src/element/basic_shapes.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,131 @@ 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+
let mut dist = 0.;
193+
let mut is_solid = true;
194+
let mut queue = vec![to_i(start)];
195+
for curr in points {
196+
let curr_f = to_f(curr);
197+
let (dx, dy) = (curr_f.0 - start.0, curr_f.1 - start.1);
198+
let d = dx.hypot(dy).max(f32::EPSILON);
199+
dist += d;
200+
if is_solid {
201+
if dist < size {
202+
queue.push(curr);
203+
start = curr_f;
204+
} else {
205+
let t = (dist - size) / d;
206+
start = (start.0 + dx * t, start.1 + dy * t);
207+
queue.push(to_i(start));
208+
backend.draw_path(queue.drain(..), &self.style)?;
209+
dist = 0.;
210+
is_solid = false;
211+
}
212+
} else if dist < spacing {
213+
start = curr_f;
214+
} else {
215+
let t = (dist - spacing) / d;
216+
start = (start.0 + dx * t, start.1 + dy * t);
217+
queue.push(to_i(start));
218+
dist = 0.;
219+
is_solid = true;
220+
}
221+
}
222+
if queue.len() > 1 {
223+
backend.draw_path(queue, &self.style)?;
224+
}
225+
Ok(())
226+
}
227+
}
228+
229+
#[cfg(test)]
230+
#[test]
231+
fn test_dashed_path_element() {
232+
use crate::prelude::*;
233+
let check_list = std::cell::RefCell::new(vec![
234+
vec![(100, 100), (100, 103), (100, 118)],
235+
vec![(100, 105), (100, 110)],
236+
vec![(100, 112), (100, 117)],
237+
vec![(100, 119), (100, 120)],
238+
]);
239+
let da = crate::create_mocked_drawing_area(300, 300, |m| {
240+
m.check_draw_path(move |c, s, path| {
241+
assert_eq!(c, BLUE.to_rgba());
242+
assert_eq!(s, 7);
243+
assert_eq!(path, check_list.borrow_mut().remove(0));
244+
});
245+
m.drop_check(|b| {
246+
assert_eq!(b.num_draw_path_call, 1);
247+
assert_eq!(b.draw_count, 1);
248+
});
249+
});
250+
da.draw(&DashedPathElement::new(
251+
vec![(100, 100), (100, 103), (100, 120)],
252+
5.,
253+
2.,
254+
BLUE.stroke_width(7),
255+
))
256+
.expect("Drawing Failure");
257+
}
258+
134259
/// A rectangle element
135260
pub struct Rectangle<Coord> {
136261
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: 54 additions & 4 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, 9);
148+
assert_eq!(b.draw_count, 9);
107149
});
108150
});
109151

@@ -117,5 +159,13 @@ mod test {
117159
Into::<ShapeStyle>::into(RED).stroke_width(3),
118160
))
119161
.expect("Drawing Error");
162+
chart
163+
.draw_series(DashedLineSeries::new(
164+
(0..=50).map(|x| (0, x)),
165+
10,
166+
5,
167+
Into::<ShapeStyle>::into(RED).stroke_width(3),
168+
))
169+
.expect("Drawing Error");
120170
}
121171
}

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)