Skip to content

Commit 4a10b61

Browse files
committed
Make the surface series better
1 parent 9758ac6 commit 4a10b61

File tree

3 files changed

+197
-82
lines changed

3 files changed

+197
-82
lines changed

examples/3d-plot.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
2020
chart.configure_axes().draw()?;
2121

2222
chart
23-
.draw_series(SurfaceSeries::<f64, _, f64>::new(
24-
x_axis.values(),
25-
z_axis.values(),
26-
|&x, &z| (x * x + z * z).cos(),
27-
&BLUE.mix(0.2),
28-
))?
23+
.draw_series(
24+
SurfaceSeries::xoz(
25+
(-30..30).map(|f| f as f64 / 10.0),
26+
(-30..30).map(|f| f as f64 / 10.0),
27+
|x, z| (x * x + z * z).cos(),
28+
)
29+
.style(BLUE.mix(0.2).filled()),
30+
)?
2931
.label("Surface")
3032
.legend(|(x, y)| Rectangle::new([(x + 5, y - 5), (x + 15, y + 5)], BLUE.mix(0.5).filled()));
3133

examples/3d-plot2.rs

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
2626

2727
chart.configure_axes().draw()?;
2828

29-
let series = (-15..15)
30-
.map(|x| std::iter::repeat(x).zip(-15..15))
31-
.flatten()
32-
.map(|(x, z)| {
33-
let x = x as f64 / 5.0;
34-
let z = z as f64 / 5.0;
35-
Polygon::new(
36-
vec![
37-
(x, pdf(x, z), z),
38-
(x + 0.2, pdf(x + 0.2, z), z),
39-
(x + 0.2, pdf(x + 0.2, z + 0.2), z + 0.2),
40-
(x, pdf(x, z + 0.2), z + 0.2),
41-
],
42-
&HSLColor(240.0 / 360.0 - 240.0 / 360.0 * pdf(x, z) / 5.0, 1.0, 0.7),
43-
)
44-
});
29+
chart.draw_series(
30+
SurfaceSeries::xoy(
31+
(-15..=15).map(|x| x as f64 / 5.0),
32+
(-15..=15).map(|x| x as f64 / 5.0),
33+
pdf,
34+
)
35+
.style_func(&|&v| {
36+
(&HSLColor(240.0 / 360.0 - 240.0 / 360.0 * v / 5.0, 1.0, 0.7)).into()
37+
}),
38+
)?;
4539

46-
chart.draw_series(series)?;
4740
root.present()?;
4841
}
4942

src/series/surface.rs

Lines changed: 179 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,202 @@
11
use crate::element::Polygon;
2-
use crate::style::ShapeStyle;
2+
use crate::style::{colors::BLUE, Color, ShapeStyle};
3+
use std::marker::PhantomData;
4+
pub trait Direction<X, Y, Z> {
5+
type Input1Type;
6+
type Input2Type;
7+
type OutputType;
8+
fn make_coord(
9+
free_vars: (Self::Input1Type, Self::Input2Type),
10+
result: Self::OutputType,
11+
) -> (X, Y, Z);
12+
}
13+
14+
macro_rules! define_panel_descriptor {
15+
($name: ident, $var1: ident, $var2: ident, $out: ident, ($first: ident, $second:ident) -> $result: ident = $output: expr) => {
16+
pub struct $name;
17+
impl<X, Y, Z> Direction<X, Y, Z> for $name {
18+
type Input1Type = $var1;
19+
type Input2Type = $var2;
20+
type OutputType = $out;
21+
fn make_coord(
22+
($first, $second): (Self::Input1Type, Self::Input2Type),
23+
$result: Self::OutputType,
24+
) -> (X, Y, Z) {
25+
$output
26+
}
27+
}
28+
};
29+
}
30+
31+
define_panel_descriptor!(XOY, X, Y, Z, (x, y) -> z = (x,y,z));
32+
define_panel_descriptor!(XOZ, X, Z, Y, (x, z) -> y = (x,y,z));
33+
define_panel_descriptor!(YOZ, Y, Z, X, (y, z) -> x = (x,y,z));
34+
35+
enum StyleConfig<'a, T> {
36+
Fixed(ShapeStyle),
37+
Function(&'a dyn Fn(&T) -> ShapeStyle),
38+
}
39+
40+
impl<T> StyleConfig<'_, T> {
41+
fn get_style(&self, v: &T) -> ShapeStyle {
42+
match self {
43+
StyleConfig::Fixed(s) => s.clone(),
44+
StyleConfig::Function(f) => f(v),
45+
}
46+
}
47+
}
48+
349
/// The surface series.
450
///
551
/// Currently the surface is representing any surface in form
652
/// y = f(x,z)
753
///
854
/// TODO: make this more general
9-
pub struct SurfaceSeries<X, Y, Z> {
10-
x_data: Vec<X>,
11-
y_data: Vec<Y>,
12-
z_data: Vec<Z>,
13-
style: ShapeStyle,
14-
size: usize,
15-
state: usize,
55+
pub struct SurfaceSeries<'a, X, Y, Z, D, SurfaceFunc>
56+
where
57+
D: Direction<X, Y, Z>,
58+
SurfaceFunc: Fn(D::Input1Type, D::Input2Type) -> D::OutputType,
59+
{
60+
free_var_1: Vec<D::Input1Type>,
61+
free_var_2: Vec<D::Input2Type>,
62+
surface_f: SurfaceFunc,
63+
style: StyleConfig<'a, D::OutputType>,
64+
adv: bool,
65+
vidx_1: usize,
66+
vidx_2: usize,
67+
_phantom: PhantomData<(X, Y, Z, D)>,
1668
}
1769

18-
impl<X, Y, Z> SurfaceSeries<X, Y, Z> {
19-
pub fn new<XS, ZS, YF, S>(xs: XS, zs: ZS, y_func: YF, style: S) -> Self
20-
where
21-
YF: Fn(&X, &Z) -> Y,
22-
XS: Iterator<Item = X>,
23-
ZS: Iterator<Item = Z>,
24-
S: Into<ShapeStyle>,
25-
{
26-
let x_data: Vec<_> = xs.collect();
27-
let z_data: Vec<_> = zs.collect();
28-
let y_data: Vec<_> = x_data
29-
.iter()
30-
.map(|x| z_data.iter().map(move |z| (x, z)))
31-
.flatten()
32-
.map(|(x, z)| y_func(x, z))
33-
.collect();
34-
let size = (x_data.len().max(1) - 1) * (z_data.len().max(1) - 1);
70+
impl<'a, X, Y, Z, D, SurfaceFunc> SurfaceSeries<'a, X, Y, Z, D, SurfaceFunc>
71+
where
72+
D: Direction<X, Y, Z>,
73+
SurfaceFunc: Fn(D::Input1Type, D::Input2Type) -> D::OutputType,
74+
{
75+
pub fn new<IterA: Iterator<Item = D::Input1Type>, IterB: Iterator<Item = D::Input2Type>>(
76+
first_iter: IterA,
77+
second_iter: IterB,
78+
func: SurfaceFunc,
79+
) -> Self {
3580
Self {
36-
x_data,
37-
y_data,
38-
z_data,
39-
style: style.into(),
40-
size,
41-
state: 0,
81+
free_var_1: first_iter.collect(),
82+
free_var_2: second_iter.collect(),
83+
surface_f: func,
84+
style: StyleConfig::Fixed(BLUE.mix(0.4).filled()),
85+
adv: true,
86+
vidx_1: 0,
87+
vidx_2: 0,
88+
_phantom: PhantomData,
4289
}
4390
}
4491

45-
fn point_at(&self, x: usize, z: usize) -> (X, Y, Z)
92+
pub fn style_func<F: Fn(&D::OutputType) -> ShapeStyle>(mut self, f: &'a F) -> Self {
93+
self.style = StyleConfig::Function(f);
94+
self
95+
}
96+
97+
pub fn style<S: Into<ShapeStyle>>(mut self, s: S) -> Self {
98+
self.style = StyleConfig::Fixed(s.into());
99+
self
100+
}
101+
102+
fn get_next_free_value_2(&mut self) -> Option<[D::Input2Type; 2]>
46103
where
47-
X: Clone,
48-
Y: Clone,
49-
Z: Clone,
104+
D::Input2Type: Clone,
50105
{
51-
(
52-
self.x_data[x].clone(),
53-
self.y_data[x * self.z_data.len() + z].clone(),
54-
self.z_data[z].clone(),
55-
)
106+
if self.adv {
107+
if self.vidx_2 + 1 < self.free_var_2.len() {
108+
self.vidx_2 += 1;
109+
return Some([
110+
self.free_var_2[self.vidx_2 - 1].clone(),
111+
self.free_var_2[self.vidx_2].clone(),
112+
]);
113+
}
114+
self.vidx_2 += 1;
115+
} else {
116+
if self.vidx_2 > 1 {
117+
self.vidx_2 -= 1;
118+
return Some([
119+
self.free_var_2[self.vidx_2 - 1].clone(),
120+
self.free_var_2[self.vidx_2].clone(),
121+
]);
122+
}
123+
self.vidx_2 = self.vidx_2.max(1) - 1;
124+
}
125+
None
56126
}
57127
}
58128

59-
impl<X: Clone, Y: Clone, Z: Clone> Iterator for SurfaceSeries<X, Y, Z> {
60-
type Item = Polygon<(X, Y, Z)>;
61-
62-
fn next(&mut self) -> Option<Self::Item> {
63-
if self.size <= self.state {
64-
return None;
129+
macro_rules! impl_constructor {
130+
($dir: ty, $name: ident) => {
131+
impl<'a, X, Y, Z, SurfaceFunc> SurfaceSeries<'a, X, Y, Z, $dir, SurfaceFunc>
132+
where
133+
SurfaceFunc: Fn(
134+
<$dir as Direction<X, Y, Z>>::Input1Type,
135+
<$dir as Direction<X, Y, Z>>::Input2Type,
136+
) -> <$dir as Direction<X, Y, Z>>::OutputType,
137+
{
138+
pub fn $name<IterA, IterB>(a: IterA, b: IterB, f: SurfaceFunc) -> Self
139+
where
140+
IterA: DoubleEndedIterator<Item = <$dir as Direction<X, Y, Z>>::Input1Type>,
141+
IterB: DoubleEndedIterator<Item = <$dir as Direction<X, Y, Z>>::Input2Type>,
142+
{
143+
Self::new(a, b, f)
144+
}
65145
}
146+
};
147+
}
66148

67-
let x = self.state / (self.z_data.len() - 1);
68-
let z = self.state % (self.z_data.len() - 1);
69-
70-
self.state += 1;
149+
impl_constructor!(XOY, xoy);
150+
impl_constructor!(XOZ, xoz);
151+
impl_constructor!(YOZ, yoz);
152+
impl<'a, X, Y, Z, D, SurfaceFunc> Iterator for SurfaceSeries<'a, X, Y, Z, D, SurfaceFunc>
153+
where
154+
D: Direction<X, Y, Z>,
155+
D::Input1Type: Clone,
156+
D::Input2Type: Clone,
157+
SurfaceFunc: Fn(D::Input1Type, D::Input2Type) -> D::OutputType,
158+
{
159+
type Item = Polygon<(X, Y, Z)>;
160+
fn next(&mut self) -> Option<Self::Item> {
161+
let (b0, b1) = if let Some([b0, b1]) = self.get_next_free_value_2() {
162+
(b0, b1)
163+
} else {
164+
self.vidx_1 += 1;
165+
self.adv = !self.adv;
166+
if let Some([b0, b1]) = self.get_next_free_value_2() {
167+
(b0, b1)
168+
} else {
169+
return None;
170+
}
171+
};
71172

72-
Some(Polygon::new(
73-
vec![
74-
self.point_at(x, z),
75-
self.point_at(x, z + 1),
76-
self.point_at(x + 1, z + 1),
77-
self.point_at(x + 1, z),
78-
],
79-
self.style.clone(),
80-
))
173+
match (
174+
self.free_var_1.get(self.vidx_1),
175+
self.free_var_1.get(self.vidx_1 + 1),
176+
) {
177+
(Some(a0), Some(a1)) => {
178+
let value = (self.surface_f)(a0.clone(), b0.clone());
179+
let style = self.style.get_style(&value);
180+
let vert = vec![
181+
D::make_coord((a0.clone(), b0.clone()), value),
182+
D::make_coord(
183+
(a0.clone(), b1.clone()),
184+
(self.surface_f)(a0.clone(), b1.clone()),
185+
),
186+
D::make_coord(
187+
(a1.clone(), b1.clone()),
188+
(self.surface_f)(a1.clone(), b1.clone()),
189+
),
190+
D::make_coord(
191+
(a1.clone(), b0.clone()),
192+
(self.surface_f)(a1.clone(), b0.clone()),
193+
),
194+
];
195+
return Some(Polygon::new(vert, style));
196+
}
197+
_ => {
198+
return None;
199+
}
200+
}
81201
}
82202
}

0 commit comments

Comments
 (0)