1use crate::aes::Aesthetic;
2use crate::data::Value;
3
4use super::Scale;
5
6#[derive(Clone, Debug)]
8pub struct ScaleSizeContinuous {
9 name: String,
10 min: f64,
11 max: f64,
12 trained: bool,
13 range: (f64, f64),
15 locale: Option<crate::locale::Locale>,
17}
18
19impl ScaleSizeContinuous {
20 pub fn new() -> Self {
21 ScaleSizeContinuous {
22 name: String::new(),
23 min: f64::INFINITY,
24 max: f64::NEG_INFINITY,
25 trained: false,
26 range: (1.0, 6.0),
27 locale: None,
28 }
29 }
30
31 pub fn with_range(mut self, min: f64, max: f64) -> Self {
32 self.range = (min, max);
33 self
34 }
35
36 pub fn with_name(mut self, name: &str) -> Self {
37 self.name = name.to_string();
38 self
39 }
40}
41
42impl Default for ScaleSizeContinuous {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl Scale for ScaleSizeContinuous {
49 fn set_default_locale(&mut self, locale: &crate::locale::Locale) {
50 if self.locale.is_none() {
51 self.locale = Some(locale.clone());
52 }
53 }
54
55 fn locale(&self) -> Option<&crate::locale::Locale> {
56 self.locale.as_ref()
57 }
58
59 fn format_value(&self, value: &Value) -> Option<String> {
60 let f = value.as_f64()?;
61 if !f.is_finite() {
62 return None;
63 }
64 Some(super::util::format_number_opt(f, self.locale.as_ref()))
65 }
66
67 fn aesthetic(&self) -> Aesthetic {
68 Aesthetic::Size
69 }
70
71 fn train(&mut self, values: &[Value]) {
72 for v in values {
73 if let Some(f) = v.as_f64() {
74 if f.is_finite() {
75 if f < self.min {
76 self.min = f;
77 }
78 if f > self.max {
79 self.max = f;
80 }
81 }
82 }
83 }
84 self.trained = true;
85 }
86
87 fn map(&self, value: &Value) -> f64 {
88 let f = match value.as_f64() {
89 Some(f) => f,
90 None => return 0.0,
91 };
92 let range = self.max - self.min;
93 if range.abs() < f64::EPSILON {
94 0.5
95 } else {
96 ((f - self.min) / range).clamp(0.0, 1.0)
97 }
98 }
99
100 fn breaks(&self) -> Vec<(f64, String)> {
101 if !self.trained || self.min > self.max {
102 return vec![];
103 }
104 let range = self.max - self.min;
105 if super::util::is_degenerate_range(self.min, self.max) {
106 return vec![(
107 0.5,
108 super::util::format_number_opt(self.min, self.locale.as_ref()),
109 )];
110 }
111 let n_breaks = 4;
112 let step = super::util::nice_step(range / n_breaks as f64);
113 let start = (self.min / step).ceil() * step;
114 let values = super::util::stepped_breaks(start, self.max, step);
115 let labels = super::util::format_numbers_opt(&values, self.locale.as_ref());
116 values
117 .iter()
118 .zip(labels)
119 .map(|(&v, l)| (self.map(&Value::Float(v)), l))
120 .collect()
121 }
122
123 fn name(&self) -> &str {
124 &self.name
125 }
126
127 fn set_name(&mut self, name: &str) {
128 self.name = name.to_string();
129 }
130
131 fn map_to_size(&self, value: &Value) -> Option<f64> {
132 let t = self.map(value);
133 let (lo, hi) = self.range;
134 Some(lo + t * (hi - lo))
135 }
136
137 fn clone_box(&self) -> Box<dyn Scale> {
138 Box::new(self.clone())
139 }
140
141 fn reset_training(&mut self) {
142 self.min = f64::INFINITY;
143 self.max = f64::NEG_INFINITY;
144 self.trained = false;
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn format_value_formats_a_finite_number_and_rejects_non_finite() {
154 let s = ScaleSizeContinuous::new();
155 assert_eq!(
156 s.format_value(&Value::Float(1234.0)),
157 Some("1234".to_string())
158 );
159 assert_eq!(s.format_value(&Value::Float(f64::NAN)), None);
160 assert_eq!(s.format_value(&Value::Na), None);
161 }
162}