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