Skip to content

Commit bce53e7

Browse files
committed
Add dotted line style
1 parent 9613a5a commit bce53e7

File tree

4 files changed

+172
-7
lines changed

4 files changed

+172
-7
lines changed

plotters/src/element/basic_shapes.rs

Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ use super::{Drawable, PointCollection};
22
use crate::style::{Color, ShapeStyle, SizeDesc};
33
use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
44

5+
#[inline]
6+
fn to_i((x, y): (f32, f32)) -> (i32, i32) {
7+
(x.round() as i32, y.round() as i32)
8+
}
9+
10+
#[inline]
11+
fn to_f((x, y): (i32, i32)) -> (f32, f32) {
12+
(x as f32, y as f32)
13+
}
14+
515
/**
616
An element representing a single pixel.
717
@@ -181,8 +191,6 @@ impl<I0: Iterator + Clone, Size: SizeDesc, DB: DrawingBackend> Drawable<DB>
181191
backend: &mut DB,
182192
ps: (u32, u32),
183193
) -> 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);
186194
let mut start = match points.next() {
187195
Some(c) => to_f(c),
188196
None => return Ok(()),
@@ -264,6 +272,120 @@ fn test_dashed_path_element() {
264272
.expect("Drawing Failure");
265273
}
266274

275+
/// An element of a series of connected lines in dot style for any markers.
276+
///
277+
/// It's similar to [`PathElement`] but use a marker function to draw markers with spacing.
278+
pub struct DottedPathElement<I: Iterator + Clone, Size: SizeDesc, Marker> {
279+
points: I,
280+
spacing: Size,
281+
func: Box<dyn Fn(BackendCoord) -> Marker>,
282+
}
283+
284+
impl<I: Iterator + Clone, Size: SizeDesc, Marker> DottedPathElement<I, Size, Marker> {
285+
/// Create a new path
286+
/// - `points`: The iterator of the points
287+
/// - `spacing`: The spacing between markers
288+
/// - `func`: The marker function
289+
/// - returns the created element
290+
pub fn new<I0, F>(points: I0, spacing: Size, func: F) -> Self
291+
where
292+
I0: IntoIterator<IntoIter = I>,
293+
F: Fn(BackendCoord) -> Marker + 'static,
294+
{
295+
Self {
296+
points: points.into_iter(),
297+
spacing,
298+
func: Box::new(func),
299+
}
300+
}
301+
}
302+
303+
impl<'a, I: Iterator + Clone, Size: SizeDesc, Marker> PointCollection<'a, I::Item>
304+
for &'a DottedPathElement<I, Size, Marker>
305+
{
306+
type Point = I::Item;
307+
type IntoIter = I;
308+
fn point_iter(self) -> Self::IntoIter {
309+
self.points.clone()
310+
}
311+
}
312+
313+
impl<I0, Size, DB, Marker> Drawable<DB> for DottedPathElement<I0, Size, Marker>
314+
where
315+
I0: Iterator + Clone,
316+
Size: SizeDesc,
317+
DB: DrawingBackend,
318+
Marker: crate::element::IntoDynElement<'static, DB, BackendCoord>,
319+
{
320+
fn draw<I: Iterator<Item = BackendCoord>>(
321+
&self,
322+
mut points: I,
323+
backend: &mut DB,
324+
ps: (u32, u32),
325+
) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
326+
let mut start = match points.next() {
327+
Some(start_i) => {
328+
(self.func)(start_i)
329+
.into_dyn()
330+
.draw(std::iter::once(start_i), backend, ps)?;
331+
to_f(start_i)
332+
}
333+
None => return Ok(()),
334+
};
335+
let spacing = self.spacing.in_pixels(&ps).max(0) as f32;
336+
if spacing == 0. {
337+
return Ok(());
338+
}
339+
let mut dist = 0.;
340+
for curr in points {
341+
let end = to_f(curr);
342+
// Loop for spacing
343+
while start != end {
344+
let (dx, dy) = (end.0 - start.0, end.1 - start.1);
345+
let d = dx.hypot(dy);
346+
let size = spacing;
347+
let left = size - dist;
348+
// Set next point to `start`
349+
if left < d {
350+
let t = left / d;
351+
start = (start.0 + dx * t, start.1 + dy * t);
352+
dist += left;
353+
} else {
354+
start = end;
355+
dist += d;
356+
}
357+
// Draw if needed
358+
if size <= dist {
359+
let start_i = to_i(start);
360+
(self.func)(start_i)
361+
.into_dyn()
362+
.draw(std::iter::once(start_i), backend, ps)?;
363+
dist = 0.;
364+
}
365+
}
366+
}
367+
Ok(())
368+
}
369+
}
370+
371+
#[cfg(test)]
372+
#[test]
373+
fn test_dotted_path_element() {
374+
use crate::prelude::*;
375+
let da = crate::create_mocked_drawing_area(300, 300, |m| {
376+
m.drop_check(|b| {
377+
assert_eq!(b.num_draw_path_call, 0);
378+
assert_eq!(b.draw_count, 8);
379+
});
380+
});
381+
da.draw(&DottedPathElement::new(
382+
vec![(100, 100), (105, 105), (150, 150)],
383+
10,
384+
|c| Circle::new(c, 5, Into::<ShapeStyle>::into(RED).filled()),
385+
))
386+
.expect("Drawing Failure");
387+
}
388+
267389
/// A rectangle element
268390
pub struct Rectangle<Coord> {
269391
points: [Coord; 2],

plotters/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -844,7 +844,7 @@ pub mod prelude {
844844
#[cfg(feature = "surface_series")]
845845
pub use crate::series::SurfaceSeries;
846846
#[cfg(feature = "line_series")]
847-
pub use crate::series::{DashedLineSeries, LineSeries};
847+
pub use crate::series::{DashedLineSeries, DottedLineSeries, 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: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
use crate::element::{Circle, DashedPathElement, DynElement, IntoDynElement, PathElement};
1+
use crate::element::{
2+
Circle, DashedPathElement, DottedPathElement, DynElement, IntoDynElement, PathElement,
3+
};
24
use crate::style::{ShapeStyle, SizeDesc};
3-
use plotters_backend::DrawingBackend;
5+
use plotters_backend::{BackendCoord, DrawingBackend};
46
use std::marker::PhantomData;
57

68
/**
@@ -126,6 +128,43 @@ impl<I: Iterator + Clone, Size: SizeDesc> IntoIterator for DashedLineSeries<I, S
126128
}
127129
}
128130

131+
/// A dotted line series, map an iterable object to the dotted line element.
132+
pub struct DottedLineSeries<I: Iterator + Clone, Size: SizeDesc, Marker> {
133+
points: I,
134+
spacing: Size,
135+
func: Box<dyn Fn(BackendCoord) -> Marker>,
136+
}
137+
138+
impl<I: Iterator + Clone, Size: SizeDesc, Marker> DottedLineSeries<I, Size, Marker> {
139+
/// Create a new line series from
140+
/// - `points`: The iterator of the points
141+
/// - `spacing`: The spacing between markers
142+
/// - `func`: The marker function
143+
/// - returns the created element
144+
pub fn new<I0, F>(points: I0, spacing: Size, func: F) -> Self
145+
where
146+
I0: IntoIterator<IntoIter = I>,
147+
F: Fn(BackendCoord) -> Marker + 'static,
148+
{
149+
Self {
150+
points: points.into_iter(),
151+
spacing,
152+
func: Box::new(func),
153+
}
154+
}
155+
}
156+
157+
impl<I: Iterator + Clone, Size: SizeDesc, Marker: 'static> IntoIterator
158+
for DottedLineSeries<I, Size, Marker>
159+
{
160+
type Item = DottedPathElement<I, Size, Marker>;
161+
type IntoIter = std::iter::Once<Self::Item>;
162+
163+
fn into_iter(self) -> Self::IntoIter {
164+
std::iter::once(DottedPathElement::new(self.points, self.spacing, self.func))
165+
}
166+
}
167+
129168
#[cfg(test)]
130169
mod test {
131170
use crate::prelude::*;
@@ -145,7 +184,7 @@ mod test {
145184

146185
m.drop_check(|b| {
147186
assert_eq!(b.num_draw_path_call, 8);
148-
assert_eq!(b.draw_count, 8);
187+
assert_eq!(b.draw_count, 28);
149188
});
150189
});
151190

@@ -167,5 +206,9 @@ mod test {
167206
Into::<ShapeStyle>::into(RED).stroke_width(3),
168207
))
169208
.expect("Drawing Error");
209+
let mk_f = |c| Circle::new(c, 3, Into::<ShapeStyle>::into(RED).filled());
210+
chart
211+
.draw_series(DottedLineSeries::new((0..=50).map(|x| (x, 0)), 5, mk_f))
212+
.expect("Drawing Error");
170213
}
171214
}

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::{DashedLineSeries, LineSeries};
29+
pub use line_series::{DashedLineSeries, DottedLineSeries, LineSeries};
3030
#[cfg(feature = "point_series")]
3131
pub use point_series::PointSeries;
3232
#[cfg(feature = "surface_series")]

0 commit comments

Comments
 (0)