omf/
colormap.rs

1use std::fmt::Display;
2
3use chrono::{DateTime, NaiveDate, Utc};
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    Array,
9    array::Constraint,
10    array_type,
11    validate::{Validate, Validator},
12};
13
14/// Specifies the minimum and maximum values of a number colormap.
15///
16/// Values outside this range will use the color at the ends of the gradient.
17/// The variant used should match the type of the number array.
18#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
19#[serde(untagged)]
20pub enum NumberRange {
21    Float {
22        min: f64,
23        max: f64,
24    },
25    Integer {
26        min: i64,
27        max: i64,
28    },
29    Date {
30        min: NaiveDate,
31        max: NaiveDate,
32    },
33    DateTime {
34        min: DateTime<Utc>,
35        max: DateTime<Utc>,
36    },
37}
38
39impl Display for NumberRange {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            NumberRange::Float { min, max } => write!(f, "[{min}, {max}]"),
43            NumberRange::Integer { min, max } => write!(f, "[{min}, {max}]"),
44            NumberRange::Date { min, max } => write!(f, "[{min}, {max}]"),
45            NumberRange::DateTime { min, max } => write!(f, "[{min}, {max}]"),
46        }
47    }
48}
49
50impl From<(f64, f64)> for NumberRange {
51    fn from((min, max): (f64, f64)) -> Self {
52        Self::Float { min, max }
53    }
54}
55
56impl From<(i64, i64)> for NumberRange {
57    fn from((min, max): (i64, i64)) -> Self {
58        Self::Integer { min, max }
59    }
60}
61
62impl From<(i32, i32)> for NumberRange {
63    fn from((min, max): (i32, i32)) -> Self {
64        Self::Integer {
65            min: min.into(),
66            max: max.into(),
67        }
68    }
69}
70
71impl From<(NaiveDate, NaiveDate)> for NumberRange {
72    fn from((min, max): (NaiveDate, NaiveDate)) -> Self {
73        Self::Date { min, max }
74    }
75}
76
77impl From<(DateTime<Utc>, DateTime<Utc>)> for NumberRange {
78    fn from((min, max): (DateTime<Utc>, DateTime<Utc>)) -> Self {
79        Self::DateTime { min, max }
80    }
81}
82
83/// Describes a mapping of floating-point value to colors.
84#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
85#[serde(tag = "type")]
86pub enum NumberColormap {
87    /// A continuous colormap linearly samples a color gradient within a defined range.
88    ///
89    /// A value X% of way between `min` and `max` should use the color from X% way down
90    /// gradient. When that X doesn't land directly on a color use the average of
91    /// the colors on either side, inverse-weighted by the distance to each.
92    ///
93    /// Values below the minimum use the first color in the gradient array. Values above
94    /// the maximum use the last.
95    ///
96    /// ![Diagram of a continuous colormap](../images/colormap_continuous.svg "Continuous colormap")
97    Continuous {
98        /// Value range.
99        range: NumberRange,
100        /// Array with `Gradient` type storing the smooth color gradient.
101        gradient: Array<array_type::Gradient>,
102    },
103    /// A discrete colormap divides the number line into adjacent but non-overlapping
104    /// ranges and gives a flat color to each range.
105    ///
106    /// Values above the last boundary use `end_color`.
107    Discrete {
108        /// Array with `Boundary` type storing the smooth color gradient, containing the value
109        /// and inclusiveness of each boundary. Values must increase along the array.
110        /// Boundary values type should match the type of the number array.
111        boundaries: Array<array_type::Boundary>,
112        /// Array with `Gradient` type storing the colors of the discrete ranges.
113        /// Length must be one more than `boundaries`, with the extra color used for values above
114        /// the last boundary.
115        gradient: Array<array_type::Gradient>,
116    },
117}
118
119impl Validate for NumberColormap {
120    fn validate_inner(&mut self, val: &mut Validator) {
121        match self {
122            NumberColormap::Continuous { range, gradient } => {
123                val.enter("NumberColormap::Continuous")
124                    .min_max(*range)
125                    .array(gradient, Constraint::Gradient, "gradient");
126            }
127            NumberColormap::Discrete {
128                boundaries,
129                gradient,
130            } => {
131                val.enter("NumberColormap::Discrete")
132                    .array(boundaries, Constraint::Boundary, "boundaries")
133                    .array(gradient, Constraint::Gradient, "gradient")
134                    .array_size(
135                        gradient.item_count(),
136                        boundaries.item_count() + 1,
137                        "gradient",
138                    );
139            }
140        }
141    }
142}