1 use crate::element::Polygon;
2 use crate::style::{colors::BLUE, Color, ShapeStyle};
3 use std::marker::PhantomData;
4 
5 /// Any type that describe a surface orientation
6 pub 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
make_coord( free_vars: (Self::Input1Type, Self::Input2Type), result: Self::OutputType, ) -> (X, Y, Z)15     fn make_coord(
16         free_vars: (Self::Input1Type, Self::Input2Type),
17         result: Self::OutputType,
18     ) -> (X, Y, Z);
19 }
20 
21 macro_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 
39 define_panel_descriptor!(XOY, X, Y, Z, (x, y) -> z = (x,y,z));
40 define_panel_descriptor!(XOZ, X, Z, Y, (x, z) -> y = (x,y,z));
41 define_panel_descriptor!(YOZ, Y, Z, X, (y, z) -> x = (x,y,z));
42 
43 enum StyleConfig<'a, T> {
44     Fixed(ShapeStyle),
45     Function(&'a dyn Fn(&T) -> ShapeStyle),
46 }
47 
48 impl<T> StyleConfig<'_, T> {
get_style(&self, v: &T) -> ShapeStyle49     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 /**
58 Represents functions of two variables.
59 
60 # Examples
61 
62 ```
63 use plotters::prelude::*;
64 let drawing_area = SVGBackend::new("surface_series_xoz.svg", (640, 480)).into_drawing_area();
65 drawing_area.fill(&WHITE).unwrap();
66 let 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();
70 chart_context.configure_axes().draw().unwrap();
71 let axis_title_style = ("sans-serif", 20, &BLACK).into_text_style(&drawing_area);
72 chart_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();
74 chart_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 
81 The code above with [`SurfaceSeries::xoy()`] produces a surface that depends on x and y and
82 points in the z direction:
83 
84 ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@10ace42/apidoc/surface_series_xoy.svg)
85 
86 The code above with [`SurfaceSeries::xoz()`] produces a surface that depends on x and z and
87 points in the y direction:
88 
89 ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@10ace42/apidoc/surface_series_xoz.svg)
90 
91 The code above with [`SurfaceSeries::yoz()`] produces a surface that depends on y and z and
92 points in the x direction:
93 
94 ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@10ace42/apidoc/surface_series_yoz.svg)
95 */
96 pub struct SurfaceSeries<'a, X, Y, Z, D, SurfaceFunc>
97 where
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 
110 impl<'a, X, Y, Z, D, SurfaceFunc> SurfaceSeries<'a, X, Y, Z, D, SurfaceFunc>
111 where
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
new<IterA: Iterator<Item = D::Input1Type>, IterB: Iterator<Item = D::Input2Type>>( first_iter: IterA, second_iter: IterB, func: SurfaceFunc, ) -> Self116     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     */
style_func<F: Fn(&D::OutputType) -> ShapeStyle>(mut self, f: &'a F) -> Self162     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.
style<S: Into<ShapeStyle>>(mut self, s: S) -> Self168     pub fn style<S: Into<ShapeStyle>>(mut self, s: S) -> Self {
169         self.style = StyleConfig::Fixed(s.into());
170         self
171     }
172 }
173 
174 macro_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 
195 impl_constructor!(XOY, xoy);
196 impl_constructor!(XOZ, xoz);
197 impl_constructor!(YOZ, yoz);
198 impl<'a, X, Y, Z, D, SurfaceFunc> Iterator for SurfaceSeries<'a, X, Y, Z, D, SurfaceFunc>
199 where
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)>;
next(&mut self) -> Option<Self::Item>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