Skip to main content

ggplot_rs/scale/
alpha.rs

1use crate::aes::Aesthetic;
2use crate::data::Value;
3
4use super::util::format_number;
5use super::Scale;
6
7/// Continuous alpha scale — maps data values to an opacity range.
8#[derive(Clone, Debug)]
9pub struct ScaleAlphaContinuous {
10    name: String,
11    min: f64,
12    max: f64,
13    trained: bool,
14    /// Output range: (min_alpha, max_alpha) in [0, 1].
15    range: (f64, f64),
16}
17
18impl ScaleAlphaContinuous {
19    pub fn new() -> Self {
20        ScaleAlphaContinuous {
21            name: String::new(),
22            min: f64::INFINITY,
23            max: f64::NEG_INFINITY,
24            trained: false,
25            range: (0.1, 1.0),
26        }
27    }
28
29    pub fn with_range(mut self, min: f64, max: f64) -> Self {
30        self.range = (min, max);
31        self
32    }
33
34    pub fn with_name(mut self, name: &str) -> Self {
35        self.name = name.to_string();
36        self
37    }
38}
39
40impl Default for ScaleAlphaContinuous {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46impl Scale for ScaleAlphaContinuous {
47    fn aesthetic(&self) -> Aesthetic {
48        Aesthetic::Alpha
49    }
50
51    fn train(&mut self, values: &[Value]) {
52        for v in values {
53            if let Some(f) = v.as_f64() {
54                if f.is_finite() {
55                    if f < self.min {
56                        self.min = f;
57                    }
58                    if f > self.max {
59                        self.max = f;
60                    }
61                }
62            }
63        }
64        self.trained = true;
65    }
66
67    fn map(&self, value: &Value) -> f64 {
68        let f = match value.as_f64() {
69            Some(f) => f,
70            None => return 0.0,
71        };
72        let range = self.max - self.min;
73        if range.abs() < f64::EPSILON {
74            0.5
75        } else {
76            ((f - self.min) / range).clamp(0.0, 1.0)
77        }
78    }
79
80    fn breaks(&self) -> Vec<(f64, String)> {
81        if !self.trained || self.min > self.max {
82            return vec![];
83        }
84        let range = self.max - self.min;
85        if range.abs() < f64::EPSILON {
86            return vec![(0.5, format_number(self.min))];
87        }
88        let n_breaks = 4;
89        let step = super::util::nice_step(range / n_breaks as f64);
90        let start = (self.min / step).ceil() * step;
91        let mut breaks = Vec::new();
92        let mut v = start;
93        while v <= self.max + step * 0.001 {
94            let pos = self.map(&Value::Float(v));
95            breaks.push((pos, format_number(v)));
96            v += step;
97        }
98        breaks
99    }
100
101    fn name(&self) -> &str {
102        &self.name
103    }
104
105    fn set_name(&mut self, name: &str) {
106        self.name = name.to_string();
107    }
108
109    fn map_to_alpha(&self, value: &Value) -> Option<f64> {
110        let t = self.map(value);
111        let (lo, hi) = self.range;
112        Some(lo + t * (hi - lo))
113    }
114
115    fn clone_box(&self) -> Box<dyn Scale> {
116        Box::new(self.clone())
117    }
118
119    fn reset_training(&mut self) {
120        self.min = f64::INFINITY;
121        self.max = f64::NEG_INFINITY;
122        self.trained = false;
123    }
124}