Skip to content

Commit 17f57de

Browse files
committed
Add 3d support
1 parent 89880b7 commit 17f57de

File tree

9 files changed

+840
-4
lines changed

9 files changed

+840
-4
lines changed

src/chart/axes3d.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
use std::marker::PhantomData;
2+
3+
use super::ChartContext;
4+
use crate::coord::cartesian::Cartesian3d;
5+
use crate::coord::ranged1d::{BoldPoints, LightPoints, Ranged, ValueFormatter};
6+
use crate::style::colors::{BLACK, TRANSPARENT};
7+
use crate::style::Color;
8+
use crate::style::{AsRelative, ShapeStyle, SizeDesc, TextStyle};
9+
10+
use super::Coord3D;
11+
12+
use crate::drawing::DrawingAreaErrorKind;
13+
14+
use plotters_backend::DrawingBackend;
15+
16+
pub struct Axes3dStyle<'a, 'b, X: Ranged, Y: Ranged, Z: Ranged, DB: DrawingBackend> {
17+
pub(super) parent_size: (u32, u32),
18+
pub(super) target: Option<&'b mut ChartContext<'a, DB, Cartesian3d<X, Y, Z>>>,
19+
pub(super) tick_size: i32,
20+
pub(super) n_labels: [usize; 3],
21+
pub(super) bold_line_style: ShapeStyle,
22+
pub(super) light_line_style: ShapeStyle,
23+
pub(super) axis_panel_style: ShapeStyle,
24+
pub(super) axis_style: ShapeStyle,
25+
pub(super) label_style: TextStyle<'b>,
26+
pub(super) format_x: &'b dyn Fn(&X::ValueType) -> String,
27+
pub(super) format_y: &'b dyn Fn(&Y::ValueType) -> String,
28+
pub(super) format_z: &'b dyn Fn(&Z::ValueType) -> String,
29+
_phantom: PhantomData<&'a (X, Y, Z)>,
30+
}
31+
32+
impl<'a, 'b, X, Y, Z, XT, YT, ZT, DB> Axes3dStyle<'a, 'b, X, Y, Z, DB>
33+
where
34+
X: Ranged<ValueType = XT> + ValueFormatter<XT>,
35+
Y: Ranged<ValueType = YT> + ValueFormatter<YT>,
36+
Z: Ranged<ValueType = ZT> + ValueFormatter<ZT>,
37+
DB: DrawingBackend,
38+
{
39+
pub fn tick_size<Size: SizeDesc>(&mut self, size: Size) -> &mut Self {
40+
let actual_size = size.in_pixels(&self.parent_size);
41+
self.tick_size = actual_size;
42+
self
43+
}
44+
45+
pub fn x_labels(&mut self, n: usize) -> &mut Self {
46+
self.n_labels[0] = n;
47+
self
48+
}
49+
50+
pub fn y_labels(&mut self, n: usize) -> &mut Self {
51+
self.n_labels[1] = n;
52+
self
53+
}
54+
55+
pub fn z_labels(&mut self, n: usize) -> &mut Self {
56+
self.n_labels[2] = n;
57+
self
58+
}
59+
60+
pub fn axis_panel_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self{
61+
self.axis_panel_style = style.into();
62+
self
63+
}
64+
65+
pub fn bold_grid_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self {
66+
self.bold_line_style = style.into();
67+
self
68+
}
69+
70+
pub fn light_grid_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self {
71+
self.light_line_style = style.into();
72+
self
73+
}
74+
75+
pub fn label_style<S: Into<TextStyle<'b>>>(&mut self, style: S) -> &mut Self {
76+
self.label_style = style.into();
77+
self
78+
}
79+
80+
pub fn x_formatter<F: Fn(&X::ValueType) -> String>(&mut self, f: &'b F) -> &mut Self {
81+
self.format_x = f;
82+
self
83+
}
84+
85+
pub fn y_formatter<F: Fn(&Y::ValueType) -> String>(&mut self, f: &'b F) -> &mut Self {
86+
self.format_y = f;
87+
self
88+
}
89+
90+
pub fn z_formatter<F: Fn(&Z::ValueType) -> String>(&mut self, f: &'b F) -> &mut Self {
91+
self.format_z = f;
92+
self
93+
}
94+
95+
pub(crate) fn new(chart: &'b mut ChartContext<'a, DB, Cartesian3d<X, Y, Z>>) -> Self {
96+
let parent_size = chart.drawing_area.dim_in_pixel();
97+
let base_tick_size = (5u32).percent().max(5).in_pixels(chart.plotting_area());
98+
let tick_size = base_tick_size;
99+
Self {
100+
parent_size,
101+
tick_size,
102+
n_labels: [10, 10, 10],
103+
bold_line_style: Into::<ShapeStyle>::into(&BLACK.mix(0.2)),
104+
light_line_style: Into::<ShapeStyle>::into(&TRANSPARENT),
105+
axis_panel_style: Into::<ShapeStyle>::into(&BLACK.mix(0.1)),
106+
axis_style: Into::<ShapeStyle>::into(&BLACK.mix(0.8)),
107+
label_style: ("sans-serf", (12).percent().max(12).in_pixels(&parent_size)).into(),
108+
format_x: &X::format,
109+
format_y: &Y::format,
110+
format_z: &Z::format,
111+
_phantom: PhantomData,
112+
target: Some(chart),
113+
}
114+
}
115+
pub fn draw(&mut self) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
116+
where
117+
XT: Clone,
118+
YT: Clone,
119+
ZT: Clone,
120+
{
121+
let chart = self.target.take().unwrap();
122+
let kps_bold = chart.get_key_points(
123+
BoldPoints(self.n_labels[0]),
124+
BoldPoints(self.n_labels[1]),
125+
BoldPoints(self.n_labels[2]),
126+
);
127+
let kps_light = chart.get_key_points(
128+
LightPoints::new(self.n_labels[0], self.n_labels[0] * 10),
129+
LightPoints::new(self.n_labels[1], self.n_labels[1] * 10),
130+
LightPoints::new(self.n_labels[2], self.n_labels[2] * 10),
131+
);
132+
133+
let panels = chart.draw_axis_panels(
134+
&kps_bold,
135+
&kps_light,
136+
self.axis_panel_style.clone(),
137+
self.bold_line_style.clone(),
138+
self.light_line_style.clone(),
139+
)?;
140+
141+
for i in 0..3 {
142+
let axis = chart.draw_axis(i, &panels, self.axis_style.clone())?;
143+
let labels: Vec<_> = match i {
144+
0 => kps_bold.x_points.iter().map(|x| {
145+
let x_text = (self.format_x)(x);
146+
let mut p = axis[0].clone();
147+
p[0] = Coord3D::X(x.clone());
148+
(p, x_text)
149+
}).collect(),
150+
1 => kps_bold.y_points.iter().map(|y| {
151+
let y_text = (self.format_y)(y);
152+
let mut p = axis[0].clone();
153+
p[1] = Coord3D::Y(y.clone());
154+
(p, y_text)
155+
}).collect(),
156+
_ => kps_bold.z_points.iter().map(|z| {
157+
let z_text = (self.format_z)(z);
158+
let mut p = axis[0].clone();
159+
p[2] = Coord3D::Z(z.clone());
160+
(p, z_text)
161+
}).collect(),
162+
};
163+
chart.draw_axis_ticks(axis, &labels[..], self.tick_size, self.axis_style.clone(), self.label_style.clone())?;
164+
}
165+
166+
Ok(())
167+
}
168+
}

src/chart/builder.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::context::ChartContext;
22

3-
use crate::coord::cartesian::Cartesian2d;
3+
use crate::coord::cartesian::{Cartesian2d, Cartesian3d};
44
use crate::coord::ranged1d::AsRangedCoord;
55
use crate::coord::Shift;
66

@@ -306,6 +306,54 @@ impl<'a, 'b, DB: DrawingBackend> ChartBuilder<'a, 'b, DB> {
306306
),
307307
})
308308
}
309+
310+
pub fn build_cartesian_3d<X: AsRangedCoord, Y: AsRangedCoord, Z: AsRangedCoord>(
311+
&mut self,
312+
x_spec: X,
313+
y_spec: Y,
314+
z_spec: Z,
315+
) -> Result<
316+
ChartContext<'a, DB, Cartesian3d<X::CoordDescType, Y::CoordDescType, Z::CoordDescType>>,
317+
DrawingAreaErrorKind<DB::ErrorType>,
318+
> {
319+
let mut drawing_area = DrawingArea::clone(self.root_area);
320+
321+
if *self.margin.iter().max().unwrap_or(&0) > 0 {
322+
drawing_area = drawing_area.margin(
323+
self.margin[0] as i32,
324+
self.margin[1] as i32,
325+
self.margin[2] as i32,
326+
self.margin[3] as i32,
327+
);
328+
}
329+
330+
let (title_dx, title_dy) = if let Some((ref title, ref style)) = self.title {
331+
let (origin_dx, origin_dy) = drawing_area.get_base_pixel();
332+
drawing_area = drawing_area.titled(title, style.clone())?;
333+
let (current_dx, current_dy) = drawing_area.get_base_pixel();
334+
(current_dx - origin_dx, current_dy - origin_dy)
335+
} else {
336+
(0, 0)
337+
};
338+
339+
let pixel_range = drawing_area.get_pixel_range();
340+
341+
Ok(ChartContext {
342+
x_label_area: [None, None],
343+
y_label_area: [None, None],
344+
drawing_area: drawing_area.apply_coord_spec(Cartesian3d::new(
345+
x_spec,
346+
y_spec,
347+
z_spec,
348+
pixel_range,
349+
)),
350+
series_anno: vec![],
351+
drawing_area_pos: (
352+
title_dx + self.margin[2] as i32,
353+
title_dy + self.margin[0] as i32,
354+
),
355+
})
356+
}
309357
}
310358

311359
#[cfg(test)]

0 commit comments

Comments
 (0)