plotlars-core 0.12.0

Core types and traits for plotlars
Documentation
/// A structure describing the lighting model.
///
/// # Example
///
/// ```rust
/// use ndarray::Array;
/// use plotlars::{ColorBar, Lighting, Palette, Plot, SurfacePlot, Text};
/// use polars::prelude::*;
/// use std::iter;
///
/// let n: usize = 100;
/// let x_base: Vec<f64> = Array::linspace(-10.0, 10.0, n).into_raw_vec();
/// let y_base: Vec<f64> = Array::linspace(-10.0, 10.0, n).into_raw_vec();
///
/// let x = x_base
///     .iter()
///     .flat_map(|&xi| iter::repeat(xi).take(n))
///     .collect::<Vec<_>>();
///
/// let y = y_base
///     .iter()
///     .cycle()
///     .take(n * n)
///     .cloned()
///     .collect::<Vec<_>>();
///
/// let z = x_base
///     .iter()
///     .map(|i| {
///         y_base
///             .iter()
///             .map(|j| 1.0 / (j * j + 5.0) * j.sin() + 1.0 / (i * i + 5.0) * i.cos())
///             .collect::<Vec<_>>()
///     })
///     .flatten()
///     .collect::<Vec<_>>();
///
/// let dataset = df![
///         "x" => &x,
///         "y" => &y,
///         "z" => &z,
///     ]
///     .unwrap();
///
/// SurfacePlot::builder()
///     .data(&dataset)
///     .x("x")
///     .y("y")
///     .z("z")
///     .plot_title(
///         Text::from("Surface Plot")
///             .font("Arial")
///             .size(18),
///     )
///     .color_bar(
///         &ColorBar::new()
///             .border_width(1),
///     )
///     .color_scale(Palette::Cividis)
///     .reverse_scale(true)
///     .lighting(
///         &Lighting::new()
///             .position(1, 0, 0)
///             .ambient(1.0)
///             .diffuse(1.0)
///             .fresnel(1.0)
///             .roughness(1.0)
///             .specular(1.0),
///     )
///     .opacity(0.5)
///     .build()
///     .plot();
/// ```
///
/// ![example](https://imgur.com/LEjedUE.png)
#[derive(Default, Clone)]
pub struct Lighting {
    pub position: Option<[i32; 3]>,
    pub ambient: Option<f64>,
    pub diffuse: Option<f64>,
    pub fresnel: Option<f64>,
    pub roughness: Option<f64>,
    pub specular: Option<f64>,
}

impl Lighting {
    /// Creates a new `Lighting` instance with default values.
    pub fn new() -> Self {
        Self::default()
    }

    /// Sets the position of the virtual light source.
    ///
    /// # Arguments
    ///
    /// * `x` – An `i32` value representing the *x*‑coordinate of the light.
    /// * `y` – An `i32` value representing the *y*‑coordinate of the light.
    /// * `z` – An `i32` value representing the *z*‑coordinate of the light (positive z points toward the viewer).
    pub fn position(mut self, x: i32, y: i32, z: i32) -> Self {
        self.position = Some([x, y, z]);
        self
    }

    /// Sets the ambient light component.
    ///
    /// # Arguments
    ///
    /// * `value` – A `f64` value in the range 0.0 – 1.0 specifying the uniform tint strength.
    pub fn ambient(mut self, value: f64) -> Self {
        self.ambient = Some(value);
        self
    }

    /// Sets the diffuse light component.
    ///
    /// # Arguments
    ///
    /// * `value` – A `f64` value in the range 0.0 – 1.0 specifying the Lambertian reflection strength.
    pub fn diffuse(mut self, value: f64) -> Self {
        self.diffuse = Some(value);
        self
    }

    /// Sets the Fresnel (edge brightness) component.
    ///
    /// # Arguments
    ///
    /// * `value` – A `f64` value in the range 0.0 – 1.0 specifying the rim‑light intensity.
    pub fn fresnel(mut self, value: f64) -> Self {
        self.fresnel = Some(value);
        self
    }

    /// Sets the roughness of the material.
    ///
    /// # Arguments
    ///
    /// * `value` – A `f64` value in the range 0.0 – 1.0 that controls highlight width (0.0 = glossy, 1.0 = matte).
    pub fn roughness(mut self, value: f64) -> Self {
        self.roughness = Some(value);
        self
    }

    /// Sets the specular highlight intensity.
    ///
    /// # Arguments
    ///
    /// * `value` – A `f64` value in the range 0.0 – 1.0 specifying the mirror‑like highlight strength.
    pub fn specular(mut self, value: f64) -> Self {
        self.specular = Some(value);
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_default() {
        let light = Lighting::new();
        assert!(light.position.is_none());
        assert!(light.ambient.is_none());
        assert!(light.diffuse.is_none());
        assert!(light.fresnel.is_none());
        assert!(light.roughness.is_none());
        assert!(light.specular.is_none());
    }

    #[test]
    fn test_ambient() {
        let light = Lighting::new().ambient(0.5);
        assert!((light.ambient.unwrap() - 0.5).abs() < 1e-6);
    }

    #[test]
    fn test_diffuse() {
        let light = Lighting::new().diffuse(0.8);
        assert!((light.diffuse.unwrap() - 0.8).abs() < 1e-6);
    }

    #[test]
    fn test_specular() {
        let light = Lighting::new().specular(0.3);
        assert!((light.specular.unwrap() - 0.3).abs() < 1e-6);
    }

    #[test]
    fn test_builder_chaining() {
        let light = Lighting::new()
            .position(1, 2, 3)
            .ambient(0.4)
            .diffuse(0.6)
            .fresnel(0.2)
            .roughness(0.9)
            .specular(0.7);

        assert_eq!(light.position, Some([1, 2, 3]));
        assert!((light.ambient.unwrap() - 0.4).abs() < 1e-6);
        assert!((light.diffuse.unwrap() - 0.6).abs() < 1e-6);
        assert!((light.fresnel.unwrap() - 0.2).abs() < 1e-6);
        assert!((light.roughness.unwrap() - 0.9).abs() < 1e-6);
        assert!((light.specular.unwrap() - 0.7).abs() < 1e-6);
    }
}