use crate::element::Polygon; use crate::style::{colors::BLUE, Color, ShapeStyle}; use std::marker::PhantomData; /// Any type that describe a surface orientation pub trait Direction { /// The type for the first input argument type Input1Type; /// The type for the second input argument type Input2Type; /// The output of the surface function type OutputType; /// The function that maps a point on surface into the coordinate system fn make_coord( free_vars: (Self::Input1Type, Self::Input2Type), result: Self::OutputType, ) -> (X, Y, Z); } macro_rules! define_panel_descriptor { ($name: ident, $var1: ident, $var2: ident, $out: ident, ($first: ident, $second:ident) -> $result: ident = $output: expr) => { #[allow(clippy::upper_case_acronyms)] pub struct $name; impl Direction for $name { type Input1Type = $var1; type Input2Type = $var2; type OutputType = $out; fn make_coord( ($first, $second): (Self::Input1Type, Self::Input2Type), $result: Self::OutputType, ) -> (X, Y, Z) { $output } } }; } define_panel_descriptor!(XOY, X, Y, Z, (x, y) -> z = (x,y,z)); define_panel_descriptor!(XOZ, X, Z, Y, (x, z) -> y = (x,y,z)); define_panel_descriptor!(YOZ, Y, Z, X, (y, z) -> x = (x,y,z)); enum StyleConfig<'a, T> { Fixed(ShapeStyle), Function(&'a dyn Fn(&T) -> ShapeStyle), } impl StyleConfig<'_, T> { fn get_style(&self, v: &T) -> ShapeStyle { match self { StyleConfig::Fixed(s) => *s, StyleConfig::Function(f) => f(v), } } } /** Represents functions of two variables. # Examples ``` use plotters::prelude::*; let drawing_area = SVGBackend::new("surface_series_xoz.svg", (640, 480)).into_drawing_area(); drawing_area.fill(&WHITE).unwrap(); let mut chart_context = ChartBuilder::on(&drawing_area) .margin(10) .build_cartesian_3d(-3.0..3.0f64, -3.0..3.0f64, -3.0..3.0f64) .unwrap(); chart_context.configure_axes().draw().unwrap(); let axis_title_style = ("sans-serif", 20, &BLACK).into_text_style(&drawing_area); chart_context.draw_series([("x", (3., -3., -3.)), ("y", (-3., 3., -3.)), ("z", (-3., -3., 3.))] .map(|(label, position)| Text::new(label, position, &axis_title_style))).unwrap(); chart_context.draw_series(SurfaceSeries::xoz( (-30..30).map(|v| v as f64 / 10.0), (-30..30).map(|v| v as f64 / 10.0), |x:f64,z:f64|(0.7 * (x * x + z * z)).cos()).style(&BLUE.mix(0.5)) ).unwrap(); ``` The code above with [`SurfaceSeries::xoy()`] produces a surface that depends on x and y and points in the z direction: ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@10ace42/apidoc/surface_series_xoy.svg) The code above with [`SurfaceSeries::xoz()`] produces a surface that depends on x and z and points in the y direction: ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@10ace42/apidoc/surface_series_xoz.svg) The code above with [`SurfaceSeries::yoz()`] produces a surface that depends on y and z and points in the x direction: ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@10ace42/apidoc/surface_series_yoz.svg) */ pub struct SurfaceSeries<'a, X, Y, Z, D, SurfaceFunc> where D: Direction, SurfaceFunc: Fn(D::Input1Type, D::Input2Type) -> D::OutputType, { free_var_1: Vec, free_var_2: Vec, surface_f: SurfaceFunc, style: StyleConfig<'a, D::OutputType>, vidx_1: usize, vidx_2: usize, _phantom: PhantomData<(X, Y, Z, D)>, } impl<'a, X, Y, Z, D, SurfaceFunc> SurfaceSeries<'a, X, Y, Z, D, SurfaceFunc> where D: Direction, SurfaceFunc: Fn(D::Input1Type, D::Input2Type) -> D::OutputType, { /// Create a new surface series, the surface orientation is determined by D pub fn new, IterB: Iterator>( first_iter: IterA, second_iter: IterB, func: SurfaceFunc, ) -> Self { Self { free_var_1: first_iter.collect(), free_var_2: second_iter.collect(), surface_f: func, style: StyleConfig::Fixed(BLUE.mix(0.4).filled()), vidx_1: 0, vidx_2: 0, _phantom: PhantomData, } } /** Sets the style as a function of the value of the dependent coordinate of the surface. # Examples ``` use plotters::prelude::*; let drawing_area = SVGBackend::new("surface_series_style_func.svg", (640, 480)).into_drawing_area(); drawing_area.fill(&WHITE).unwrap(); let mut chart_context = ChartBuilder::on(&drawing_area) .margin(10) .build_cartesian_3d(-3.0..3.0f64, -3.0..3.0f64, -3.0..3.0f64) .unwrap(); chart_context.configure_axes().draw().unwrap(); let axis_title_style = ("sans-serif", 20, &BLACK).into_text_style(&drawing_area); chart_context.draw_series([("x", (3., -3., -3.)), ("y", (-3., 3., -3.)), ("z", (-3., -3., 3.))] .map(|(label, position)| Text::new(label, position, &axis_title_style))).unwrap(); chart_context.draw_series(SurfaceSeries::xoz( (-30..30).map(|v| v as f64 / 10.0), (-30..30).map(|v| v as f64 / 10.0), |x:f64,z:f64|(0.4 * (x * x + z * z)).cos()).style_func( &|y| HSLColor(0.6666, y + 0.5, 0.5).mix(0.8).filled() ) ).unwrap(); ``` The resulting style varies from gray to blue according to the value of y: ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@da8400f/apidoc/surface_series_style_func.svg) */ pub fn style_func ShapeStyle>(mut self, f: &'a F) -> Self { self.style = StyleConfig::Function(f); self } /// Sets the style of the plot. See [`SurfaceSeries`] for more information and examples. pub fn style>(mut self, s: S) -> Self { self.style = StyleConfig::Fixed(s.into()); self } } macro_rules! impl_constructor { ($dir: ty, $name: ident) => { impl<'a, X, Y, Z, SurfaceFunc> SurfaceSeries<'a, X, Y, Z, $dir, SurfaceFunc> where SurfaceFunc: Fn( <$dir as Direction>::Input1Type, <$dir as Direction>::Input2Type, ) -> <$dir as Direction>::OutputType, { /// Implements the constructor. See [`SurfaceSeries`] for more information and examples. pub fn $name(a: IterA, b: IterB, f: SurfaceFunc) -> Self where IterA: Iterator>::Input1Type>, IterB: Iterator>::Input2Type>, { Self::new(a, b, f) } } }; } impl_constructor!(XOY, xoy); impl_constructor!(XOZ, xoz); impl_constructor!(YOZ, yoz); impl<'a, X, Y, Z, D, SurfaceFunc> Iterator for SurfaceSeries<'a, X, Y, Z, D, SurfaceFunc> where D: Direction, D::Input1Type: Clone, D::Input2Type: Clone, SurfaceFunc: Fn(D::Input1Type, D::Input2Type) -> D::OutputType, { type Item = Polygon<(X, Y, Z)>; fn next(&mut self) -> Option { let (b0, b1) = if let (Some(b0), Some(b1)) = ( self.free_var_2.get(self.vidx_2), self.free_var_2.get(self.vidx_2 + 1), ) { self.vidx_2 += 1; (b0, b1) } else { self.vidx_1 += 1; self.vidx_2 = 1; if let (Some(b0), Some(b1)) = (self.free_var_2.get(0), self.free_var_2.get(1)) { (b0, b1) } else { return None; } }; match ( self.free_var_1.get(self.vidx_1), self.free_var_1.get(self.vidx_1 + 1), ) { (Some(a0), Some(a1)) => { let value = (self.surface_f)(a0.clone(), b0.clone()); let style = self.style.get_style(&value); let vert = vec![ D::make_coord((a0.clone(), b0.clone()), value), D::make_coord( (a0.clone(), b1.clone()), (self.surface_f)(a0.clone(), b1.clone()), ), D::make_coord( (a1.clone(), b1.clone()), (self.surface_f)(a1.clone(), b1.clone()), ), D::make_coord( (a1.clone(), b0.clone()), (self.surface_f)(a1.clone(), b0.clone()), ), ]; Some(Polygon::new(vert, style)) } _ => None, } } }