1use crate::{LinearRgba, VizError, VizResult};
2
3#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub enum ColorMap {
7 #[default]
9 Viridis,
10 Turbo,
12 Gray,
14}
15
16#[derive(Clone, Debug, PartialEq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub enum PointColor {
20 Uniform(LinearRgba),
22 Rgb,
24 Scalar {
26 min: f32,
28 max: f32,
30 map: ColorMap,
32 },
33}
34
35#[derive(Clone, Debug, PartialEq)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub struct PointStyle {
39 pub size: f32,
41 pub color: PointColor,
43}
44
45impl PointStyle {
46 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#[derive(Clone, Debug, PartialEq)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
71pub enum VisualStyle {
72 Points(PointStyle),
74 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}