1use crate::element::Polygon;
2use crate::style::{colors::BLUE, Color, ShapeStyle};
3use std::marker::PhantomData;
4
5/// Any type that describe a surface orientation
6pub trait Direction<X, Y, Z> {
7 /// The type for the first input argument
8 type Input1Type;
9 /// The type for the second input argument
10 type Input2Type;
11 /// The output of the surface function
12 type OutputType;
13
14 /// The function that maps a point on surface into the coordinate system
15 fn make_coord(
16 free_vars: (Self::Input1Type, Self::Input2Type),
17 result: Self::OutputType,
18 ) -> (X, Y, Z);
19}
20
21macro_rules! define_panel_descriptor {
22 ($name: ident, $var1: ident, $var2: ident, $out: ident, ($first: ident, $second:ident) -> $result: ident = $output: expr) => {
23 #[allow(clippy::upper_case_acronyms)]
24 pub struct $name;
25 impl<X, Y, Z> Direction<X, Y, Z> for $name {
26 type Input1Type = $var1;
27 type Input2Type = $var2;
28 type OutputType = $out;
29 fn make_coord(
30 ($first, $second): (Self::Input1Type, Self::Input2Type),
31 $result: Self::OutputType,
32 ) -> (X, Y, Z) {
33 $output
34 }
35 }
36 };
37}
38
39define_panel_descriptor!(XOY, X, Y, Z, (x, y) -> z = (x,y,z));
40define_panel_descriptor!(XOZ, X, Z, Y, (x, z) -> y = (x,y,z));
41define_panel_descriptor!(YOZ, Y, Z, X, (y, z) -> x = (x,y,z));
42
43enum StyleConfig<'a, T> {
44 Fixed(ShapeStyle),
45 Function(&'a dyn Fn(&T) -> ShapeStyle),
46}
47
48impl<T> StyleConfig<'_, T> {
49 fn get_style(&self, v: &T) -> ShapeStyle {
50 match self {
51 StyleConfig::Fixed(s) => *s,
52 StyleConfig::Function(f) => f(v),
53 }
54 }
55}
56
57/**
58Represents functions of two variables.
59
60# Examples
61
62```
63use plotters::prelude::*;
64let drawing_area = SVGBackend::new("surface_series_xoz.svg", (640, 480)).into_drawing_area();
65drawing_area.fill(&WHITE).unwrap();
66let mut chart_context = ChartBuilder::on(&drawing_area)
67 .margin(10)
68 .build_cartesian_3d(-3.0..3.0f64, -3.0..3.0f64, -3.0..3.0f64)
69 .unwrap();
70chart_context.configure_axes().draw().unwrap();
71let axis_title_style = ("sans-serif", 20, &BLACK).into_text_style(&drawing_area);
72chart_context.draw_series([("x", (3., -3., -3.)), ("y", (-3., 3., -3.)), ("z", (-3., -3., 3.))]
73.map(|(label, position)| Text::new(label, position, &axis_title_style))).unwrap();
74chart_context.draw_series(SurfaceSeries::xoz(
75 (-30..30).map(|v| v as f64 / 10.0),
76 (-30..30).map(|v| v as f64 / 10.0),
77 |x:f64,z:f64|(0.7 * (x * x + z * z)).cos()).style(&BLUE.mix(0.5))
78).unwrap();
79```
80
81The code above with [`SurfaceSeries::xoy()`] produces a surface that depends on x and y and
82points in the z direction:
83
84![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@10ace42/apidoc/surface_series_xoy.svg)
85
86The code above with [`SurfaceSeries::xoz()`] produces a surface that depends on x and z and
87points in the y direction:
88
89![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@10ace42/apidoc/surface_series_xoz.svg)
90
91The code above with [`SurfaceSeries::yoz()`] produces a surface that depends on y and z and
92points in the x direction:
93
94![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@10ace42/apidoc/surface_series_yoz.svg)
95*/
96pub struct SurfaceSeries<'a, X, Y, Z, D, SurfaceFunc>
97where
98 D: Direction<X, Y, Z>,
99 SurfaceFunc: Fn(D::Input1Type, D::Input2Type) -> D::OutputType,
100{
101 free_var_1: Vec<D::Input1Type>,
102 free_var_2: Vec<D::Input2Type>,
103 surface_f: SurfaceFunc,
104 style: StyleConfig<'a, D::OutputType>,
105 vidx_1: usize,
106 vidx_2: usize,
107 _phantom: PhantomData<(X, Y, Z, D)>,
108}
109
110impl<'a, X, Y, Z, D, SurfaceFunc> SurfaceSeries<'a, X, Y, Z, D, SurfaceFunc>
111where
112 D: Direction<X, Y, Z>,
113 SurfaceFunc: Fn(D::Input1Type, D::Input2Type) -> D::OutputType,
114{
115 /// Create a new surface series, the surface orientation is determined by D
116 pub fn new<IterA: Iterator<Item = D::Input1Type>, IterB: Iterator<Item = D::Input2Type>>(
117 first_iter: IterA,
118 second_iter: IterB,
119 func: SurfaceFunc,
120 ) -> Self {
121 Self {
122 free_var_1: first_iter.collect(),
123 free_var_2: second_iter.collect(),
124 surface_f: func,
125 style: StyleConfig::Fixed(BLUE.mix(0.4).filled()),
126 vidx_1: 0,
127 vidx_2: 0,
128 _phantom: PhantomData,
129 }
130 }
131
132 /**
133 Sets the style as a function of the value of the dependent coordinate of the surface.
134
135 # Examples
136
137 ```
138 use plotters::prelude::*;
139 let drawing_area = SVGBackend::new("surface_series_style_func.svg", (640, 480)).into_drawing_area();
140 drawing_area.fill(&WHITE).unwrap();
141 let mut chart_context = ChartBuilder::on(&drawing_area)
142 .margin(10)
143 .build_cartesian_3d(-3.0..3.0f64, -3.0..3.0f64, -3.0..3.0f64)
144 .unwrap();
145 chart_context.configure_axes().draw().unwrap();
146 let axis_title_style = ("sans-serif", 20, &BLACK).into_text_style(&drawing_area);
147 chart_context.draw_series([("x", (3., -3., -3.)), ("y", (-3., 3., -3.)), ("z", (-3., -3., 3.))]
148 .map(|(label, position)| Text::new(label, position, &axis_title_style))).unwrap();
149 chart_context.draw_series(SurfaceSeries::xoz(
150 (-30..30).map(|v| v as f64 / 10.0),
151 (-30..30).map(|v| v as f64 / 10.0),
152 |x:f64,z:f64|(0.4 * (x * x + z * z)).cos()).style_func(
153 &|y| HSLColor(0.6666, y + 0.5, 0.5).mix(0.8).filled()
154 )
155 ).unwrap();
156 ```
157
158 The resulting style varies from gray to blue according to the value of y:
159
160 ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@da8400f/apidoc/surface_series_style_func.svg)
161 */
162 pub fn style_func<F: Fn(&D::OutputType) -> ShapeStyle>(mut self, f: &'a F) -> Self {
163 self.style = StyleConfig::Function(f);
164 self
165 }
166
167 /// Sets the style of the plot. See [`SurfaceSeries`] for more information and examples.
168 pub fn style<S: Into<ShapeStyle>>(mut self, s: S) -> Self {
169 self.style = StyleConfig::Fixed(s.into());
170 self
171 }
172}
173
174macro_rules! impl_constructor {
175 ($dir: ty, $name: ident) => {
176 impl<'a, X, Y, Z, SurfaceFunc> SurfaceSeries<'a, X, Y, Z, $dir, SurfaceFunc>
177 where
178 SurfaceFunc: Fn(
179 <$dir as Direction<X, Y, Z>>::Input1Type,
180 <$dir as Direction<X, Y, Z>>::Input2Type,
181 ) -> <$dir as Direction<X, Y, Z>>::OutputType,
182 {
183 /// Implements the constructor. See [`SurfaceSeries`] for more information and examples.
184 pub fn $name<IterA, IterB>(a: IterA, b: IterB, f: SurfaceFunc) -> Self
185 where
186 IterA: Iterator<Item = <$dir as Direction<X, Y, Z>>::Input1Type>,
187 IterB: Iterator<Item = <$dir as Direction<X, Y, Z>>::Input2Type>,
188 {
189 Self::new(a, b, f)
190 }
191 }
192 };
193}
194
195impl_constructor!(XOY, xoy);
196impl_constructor!(XOZ, xoz);
197impl_constructor!(YOZ, yoz);
198impl<'a, X, Y, Z, D, SurfaceFunc> Iterator for SurfaceSeries<'a, X, Y, Z, D, SurfaceFunc>
199where
200 D: Direction<X, Y, Z>,
201 D::Input1Type: Clone,
202 D::Input2Type: Clone,
203 SurfaceFunc: Fn(D::Input1Type, D::Input2Type) -> D::OutputType,
204{
205 type Item = Polygon<(X, Y, Z)>;
206 fn next(&mut self) -> Option<Self::Item> {
207 let (b0, b1) = if let (Some(b0), Some(b1)) = (
208 self.free_var_2.get(self.vidx_2),
209 self.free_var_2.get(self.vidx_2 + 1),
210 ) {
211 self.vidx_2 += 1;
212 (b0, b1)
213 } else {
214 self.vidx_1 += 1;
215 self.vidx_2 = 1;
216 if let (Some(b0), Some(b1)) = (self.free_var_2.get(0), self.free_var_2.get(1)) {
217 (b0, b1)
218 } else {
219 return None;
220 }
221 };
222
223 match (
224 self.free_var_1.get(self.vidx_1),
225 self.free_var_1.get(self.vidx_1 + 1),
226 ) {
227 (Some(a0), Some(a1)) => {
228 let value = (self.surface_f)(a0.clone(), b0.clone());
229 let style = self.style.get_style(&value);
230 let vert = vec![
231 D::make_coord((a0.clone(), b0.clone()), value),
232 D::make_coord(
233 (a0.clone(), b1.clone()),
234 (self.surface_f)(a0.clone(), b1.clone()),
235 ),
236 D::make_coord(
237 (a1.clone(), b1.clone()),
238 (self.surface_f)(a1.clone(), b1.clone()),
239 ),
240 D::make_coord(
241 (a1.clone(), b0.clone()),
242 (self.surface_f)(a1.clone(), b0.clone()),
243 ),
244 ];
245 Some(Polygon::new(vert, style))
246 }
247 _ => None,
248 }
249 }
250}
251