Skip to content

Commit

Permalink
Merge pull request #483 from KmolYuan/dash
Browse files Browse the repository at this point in the history
Add dashed line style.
  • Loading branch information
AaronErhardt authored Jan 8, 2024
2 parents d540c33 + 62dbd7b commit 3a4c675
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 7 deletions.
125 changes: 125 additions & 0 deletions plotters/src/element/basic_shapes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,131 @@ fn test_path_element() {
.expect("Drawing Failure");
}

/// An element of a series of connected lines in dash style.
///
/// It's similar to [`PathElement`] but has a dash style.
pub struct DashedPathElement<I: Iterator + Clone, Size: SizeDesc> {
points: I,
size: Size,
spacing: Size,
style: ShapeStyle,
}

impl<I: Iterator + Clone, Size: SizeDesc> DashedPathElement<I, Size> {
/// Create a new path
/// - `points`: The iterator of the points
/// - `size`: The dash size
/// - `spacing`: The dash-to-dash spacing (gap size)
/// - `style`: The shape style
/// - returns the created element
pub fn new<I0, S>(points: I0, size: Size, spacing: Size, style: S) -> Self
where
I0: IntoIterator<IntoIter = I>,
S: Into<ShapeStyle>,
{
Self {
points: points.into_iter(),
size,
spacing,
style: style.into(),
}
}
}

impl<'a, I: Iterator + Clone, Size: SizeDesc> PointCollection<'a, I::Item>
for &'a DashedPathElement<I, Size>
{
type Point = I::Item;
type IntoIter = I;
fn point_iter(self) -> Self::IntoIter {
self.points.clone()
}
}

impl<I0: Iterator + Clone, Size: SizeDesc, DB: DrawingBackend> Drawable<DB>
for DashedPathElement<I0, Size>
{
fn draw<I: Iterator<Item = BackendCoord>>(
&self,
mut points: I,
backend: &mut DB,
ps: (u32, u32),
) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
let to_i = |(x, y): (f32, f32)| (x.round() as i32, y.round() as i32);
let to_f = |(x, y): (i32, i32)| (x as f32, y as f32);
let mut start = match points.next() {
Some(c) => to_f(c),
None => return Ok(()),
};
let size = self.size.in_pixels(&ps).max(0) as f32;
let spacing = self.spacing.in_pixels(&ps).max(0) as f32;
let mut dist = 0.;
let mut is_solid = true;
let mut queue = vec![to_i(start)];
for curr in points {
let curr_f = to_f(curr);
let (dx, dy) = (curr_f.0 - start.0, curr_f.1 - start.1);
let d = dx.hypot(dy).max(f32::EPSILON);
dist += d;
if is_solid {
if dist < size {
queue.push(curr);
start = curr_f;
} else {
let t = (dist - size) / d;
start = (start.0 + dx * t, start.1 + dy * t);
queue.push(to_i(start));
backend.draw_path(queue.drain(..), &self.style)?;
dist = 0.;
is_solid = false;
}
} else if dist < spacing {
start = curr_f;
} else {
let t = (dist - spacing) / d;
start = (start.0 + dx * t, start.1 + dy * t);
queue.push(to_i(start));
dist = 0.;
is_solid = true;
}
}
if queue.len() > 1 {
backend.draw_path(queue, &self.style)?;
}
Ok(())
}
}

#[cfg(test)]
#[test]
fn test_dashed_path_element() {
use crate::prelude::*;
let check_list = std::cell::RefCell::new(vec![
vec![(100, 100), (100, 103), (100, 118)],
vec![(100, 105), (100, 110)],
vec![(100, 112), (100, 117)],
vec![(100, 119), (100, 120)],
]);
let da = crate::create_mocked_drawing_area(300, 300, |m| {
m.check_draw_path(move |c, s, path| {
assert_eq!(c, BLUE.to_rgba());
assert_eq!(s, 7);
assert_eq!(path, check_list.borrow_mut().remove(0));
});
m.drop_check(|b| {
assert_eq!(b.num_draw_path_call, 1);
assert_eq!(b.draw_count, 1);
});
});
da.draw(&DashedPathElement::new(
vec![(100, 100), (100, 103), (100, 120)],
5.,
2.,
BLUE.stroke_width(7),
))
.expect("Drawing Failure");
}

/// A rectangle element
pub struct Rectangle<Coord> {
points: [Coord; 2],
Expand Down
4 changes: 2 additions & 2 deletions plotters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -839,12 +839,12 @@ pub mod prelude {
pub use crate::series::AreaSeries;
#[cfg(feature = "histogram")]
pub use crate::series::Histogram;
#[cfg(feature = "line_series")]
pub use crate::series::LineSeries;
#[cfg(feature = "point_series")]
pub use crate::series::PointSeries;
#[cfg(feature = "surface_series")]
pub use crate::series::SurfaceSeries;
#[cfg(feature = "line_series")]
pub use crate::series::{DashedLineSeries, LineSeries};

// Styles
pub use crate::style::{BLACK, BLUE, CYAN, GREEN, MAGENTA, RED, TRANSPARENT, WHITE, YELLOW};
Expand Down
58 changes: 54 additions & 4 deletions plotters/src/series/line_series.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::element::{Circle, DynElement, IntoDynElement, PathElement};
use crate::style::ShapeStyle;
use crate::element::{Circle, DashedPathElement, DynElement, IntoDynElement, PathElement};
use crate::style::{ShapeStyle, SizeDesc};
use plotters_backend::DrawingBackend;
use std::marker::PhantomData;

Expand Down Expand Up @@ -84,6 +84,48 @@ impl<DB: DrawingBackend, Coord> LineSeries<DB, Coord> {
}
}

/// A dashed line series, map an iterable object to the dashed line element.
pub struct DashedLineSeries<I: Iterator + Clone, Size: SizeDesc> {
points: I,
size: Size,
spacing: Size,
style: ShapeStyle,
}

impl<I: Iterator + Clone, Size: SizeDesc> DashedLineSeries<I, Size> {
/// Create a new line series from
/// - `points`: The iterator of the points
/// - `size`: The dash size
/// - `spacing`: The dash-to-dash spacing (gap size)
/// - `style`: The shape style
/// - returns the created element
pub fn new<I0>(points: I0, size: Size, spacing: Size, style: ShapeStyle) -> Self
where
I0: IntoIterator<IntoIter = I>,
{
Self {
points: points.into_iter(),
size,
spacing,
style,
}
}
}

impl<I: Iterator + Clone, Size: SizeDesc> IntoIterator for DashedLineSeries<I, Size> {
type Item = DashedPathElement<I, Size>;
type IntoIter = std::iter::Once<Self::Item>;

fn into_iter(self) -> Self::IntoIter {
std::iter::once(DashedPathElement::new(
self.points,
self.size,
self.spacing,
self.style,
))
}
}

#[cfg(test)]
mod test {
use crate::prelude::*;
Expand All @@ -102,8 +144,8 @@ mod test {
});

m.drop_check(|b| {
assert_eq!(b.num_draw_path_call, 1);
assert_eq!(b.draw_count, 1);
assert_eq!(b.num_draw_path_call, 9);
assert_eq!(b.draw_count, 9);
});
});

Expand All @@ -117,5 +159,13 @@ mod test {
Into::<ShapeStyle>::into(RED).stroke_width(3),
))
.expect("Drawing Error");
chart
.draw_series(DashedLineSeries::new(
(0..=50).map(|x| (0, x)),
10,
5,
Into::<ShapeStyle>::into(RED).stroke_width(3),
))
.expect("Drawing Error");
}
}
2 changes: 1 addition & 1 deletion plotters/src/series/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub use area_series::AreaSeries;
#[cfg(feature = "histogram")]
pub use histogram::Histogram;
#[cfg(feature = "line_series")]
pub use line_series::LineSeries;
pub use line_series::{DashedLineSeries, LineSeries};
#[cfg(feature = "point_series")]
pub use point_series::PointSeries;
#[cfg(feature = "surface_series")]
Expand Down

0 comments on commit 3a4c675

Please sign in to comment.