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 super::util::is_degenerate_range(self.min, self.max) {
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        super::util::stepped_breaks(start, self.max, step)
92            .into_iter()
93            .map(|v| (self.map(&Value::Float(v)), format_number(v)))
94            .collect()
95    }
96
97    fn name(&self) -> &str {
98        &self.name
99    }
100
101    fn set_name(&mut self, name: &str) {
102        self.name = name.to_string();
103    }
104
105    fn map_to_alpha(&self, value: &Value) -> Option<f64> {
106        let t = self.map(value);
107        let (lo, hi) = self.range;
108        Some(lo + t * (hi - lo))
109    }
110
111    fn clone_box(&self) -> Box<dyn Scale> {
112        Box::new(self.clone())
113    }
114
115    fn reset_training(&mut self) {
116        self.min = f64::INFINITY;
117        self.max = f64::NEG_INFINITY;
118        self.trained = false;
119    }
120}