Skip to content

Commit fa5bfb5

Browse files
authored
Merge branch 'master' into patch-doc_cfg
2 parents bc04161 + bb25570 commit fa5bfb5

File tree

6 files changed

+275
-37
lines changed

6 files changed

+275
-37
lines changed

plotters/blub.png

Lines changed: 3 additions & 0 deletions
Loading

plotters/src/element/basic_shapes.rs

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

plotters/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -853,7 +853,7 @@ pub mod prelude {
853853
pub use crate::series::SurfaceSeries;
854854
#[cfg(feature = "line_series")]
855855
#[cfg_attr(doc_cfg, doc(cfg(feature = "line_series")))]
856-
pub use crate::series::{DashedLineSeries, LineSeries};
856+
pub use crate::series::{DashedLineSeries, DottedLineSeries, LineSeries};
857857

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

plotters/src/series/line_series.rs

Lines changed: 54 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,51 @@ 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+
shift: Size,
135+
spacing: Size,
136+
func: Box<dyn Fn(BackendCoord) -> Marker>,
137+
}
138+
139+
impl<I: Iterator + Clone, Size: SizeDesc, Marker> DottedLineSeries<I, Size, Marker> {
140+
/// Create a new line series from
141+
/// - `points`: The iterator of the points
142+
/// - `shift`: The shift of the first marker
143+
/// - `spacing`: The spacing between markers
144+
/// - `func`: The marker function
145+
/// - returns the created element
146+
pub fn new<I0, F>(points: I0, shift: Size, spacing: Size, func: F) -> Self
147+
where
148+
I0: IntoIterator<IntoIter = I>,
149+
F: Fn(BackendCoord) -> Marker + 'static,
150+
{
151+
Self {
152+
points: points.into_iter(),
153+
shift,
154+
spacing,
155+
func: Box::new(func),
156+
}
157+
}
158+
}
159+
160+
impl<I: Iterator + Clone, Size: SizeDesc, Marker: 'static> IntoIterator
161+
for DottedLineSeries<I, Size, Marker>
162+
{
163+
type Item = DottedPathElement<I, Size, Marker>;
164+
type IntoIter = std::iter::Once<Self::Item>;
165+
166+
fn into_iter(self) -> Self::IntoIter {
167+
std::iter::once(DottedPathElement::new(
168+
self.points,
169+
self.shift,
170+
self.spacing,
171+
self.func,
172+
))
173+
}
174+
}
175+
129176
#[cfg(test)]
130177
mod test {
131178
use crate::prelude::*;
@@ -145,7 +192,7 @@ mod test {
145192

146193
m.drop_check(|b| {
147194
assert_eq!(b.num_draw_path_call, 8);
148-
assert_eq!(b.draw_count, 8);
195+
assert_eq!(b.draw_count, 27);
149196
});
150197
});
151198

@@ -167,5 +214,9 @@ mod test {
167214
Into::<ShapeStyle>::into(RED).stroke_width(3),
168215
))
169216
.expect("Drawing Error");
217+
let mk_f = |c| Circle::new(c, 3, Into::<ShapeStyle>::into(RED).filled());
218+
chart
219+
.draw_series(DottedLineSeries::new((0..=50).map(|x| (x, 0)), 5, 5, mk_f))
220+
.expect("Drawing Error");
170221
}
171222
}

plotters/src/series/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub use area_series::AreaSeries;
2929
pub use histogram::Histogram;
3030
#[cfg(feature = "line_series")]
3131
#[cfg_attr(doc_cfg, doc(cfg(feature = "line_series")))]
32-
pub use line_series::{DashedLineSeries, LineSeries};
32+
pub use line_series::{DashedLineSeries, DottedLineSeries, LineSeries};
3333
#[cfg(feature = "point_series")]
3434
#[cfg_attr(doc_cfg, doc(cfg(feature = "point_series")))]
3535
pub use point_series::PointSeries;

0 commit comments

Comments
 (0)