Skip to main content

spatialrust_viz/
style.rs

1use crate::{LinearRgba, VizError, VizResult};
2
3/// Built-in scalar color maps with stable names.
4#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub enum ColorMap {
7    /// Perceptually uniform purple-to-yellow map.
8    #[default]
9    Viridis,
10    /// Dark-blue through red to yellow map.
11    Turbo,
12    /// Monochrome black-to-white map.
13    Gray,
14}
15
16/// Color source for point primitives.
17#[derive(Clone, Debug, PartialEq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub enum PointColor {
20    /// One color for every point.
21    Uniform(LinearRgba),
22    /// Use the primitive's borrowed RGB columns.
23    Rgb,
24    /// Map the primitive's borrowed scalar column through a color map.
25    Scalar {
26        /// Inclusive lower display bound.
27        min: f32,
28        /// Inclusive upper display bound.
29        max: f32,
30        /// Color map.
31        map: ColorMap,
32    },
33}
34
35/// Point rendering style.
36#[derive(Clone, Debug, PartialEq)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub struct PointStyle {
39    /// Point diameter in logical pixels.
40    pub size: f32,
41    /// Point color source.
42    pub color: PointColor,
43}
44
45impl PointStyle {
46    /// Creates a validated point style.
47    pub fn try_new(size: f32, color: PointColor) -> VizResult<Self> {
48        if !size.is_finite() || size <= 0.0 {
49            return Err(VizError::InvalidStyle("point size must be finite and positive".into()));
50        }
51        if let PointColor::Scalar { min, max, .. } = &color {
52            if !min.is_finite() || !max.is_finite() || max <= min {
53                return Err(VizError::InvalidStyle(
54                    "scalar display range must be finite with min < max".into(),
55                ));
56            }
57        }
58        Ok(Self { size, color })
59    }
60}
61
62impl Default for PointStyle {
63    fn default() -> Self {
64        Self { size: 1.0, color: PointColor::Uniform(LinearRgba::WHITE) }
65    }
66}
67
68/// Style applied to a visual primitive.
69#[derive(Clone, Debug, PartialEq)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
71pub enum VisualStyle {
72    /// Point-specific style.
73    Points(PointStyle),
74    /// Uniform color for lines or triangle wireframes.
75    Uniform(LinearRgba),
76}
77
78#[cfg(test)]
79mod tests {
80    use super::{ColorMap, PointColor, PointStyle};
81
82    #[test]
83    fn validates_point_size_and_scalar_range() {
84        assert!(PointStyle::try_new(0.0, PointColor::Rgb).is_err());
85        assert!(PointStyle::try_new(
86            2.0,
87            PointColor::Scalar { min: 1.0, max: 1.0, map: ColorMap::Viridis }
88        )
89        .is_err());
90    }
91}