ggplot_rs/scale/
datetime.rs1use crate::aes::Aesthetic;
2use crate::data::Value;
3
4use super::Scale;
5
6#[derive(Clone, Debug)]
8pub struct ScaleDateTime {
9 aesthetic: Aesthetic,
10 name: String,
11 min: f64,
12 max: f64,
13 trained: bool,
14 expand: (f64, f64),
15}
16
17impl ScaleDateTime {
18 pub fn new() -> Self {
19 ScaleDateTime {
20 aesthetic: Aesthetic::X,
21 name: String::new(),
22 min: f64::INFINITY,
23 max: f64::NEG_INFINITY,
24 trained: false,
25 expand: (0.05, 0.0),
26 }
27 }
28
29 pub fn for_aesthetic(mut self, aes: Aesthetic) -> Self {
30 self.aesthetic = aes;
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 fn expanded_range(&self) -> (f64, f64) {
40 let range = self.max - self.min;
41 let mult = self.expand.0;
42 let add = self.expand.1;
43 (self.min - range * mult - add, self.max + range * mult + add)
44 }
45
46 fn nice_datetime_step(range_secs: f64) -> f64 {
48 const MINUTE: f64 = 60.0;
49 const HOUR: f64 = 3600.0;
50 const DAY: f64 = 86400.0;
51 const WEEK: f64 = 7.0 * DAY;
52 const MONTH: f64 = 30.0 * DAY;
53 const YEAR: f64 = 365.25 * DAY;
54
55 let candidates = [
56 1.0,
57 5.0,
58 10.0,
59 30.0,
60 MINUTE,
61 5.0 * MINUTE,
62 10.0 * MINUTE,
63 30.0 * MINUTE,
64 HOUR,
65 3.0 * HOUR,
66 6.0 * HOUR,
67 12.0 * HOUR,
68 DAY,
69 2.0 * DAY,
70 WEEK,
71 2.0 * WEEK,
72 MONTH,
73 3.0 * MONTH,
74 6.0 * MONTH,
75 YEAR,
76 2.0 * YEAR,
77 5.0 * YEAR,
78 10.0 * YEAR,
79 20.0 * YEAR,
80 50.0 * YEAR,
81 100.0 * YEAR,
82 ];
83
84 let target = range_secs / 5.0;
85 for &c in &candidates {
86 if c >= target {
87 return c;
88 }
89 }
90 let n = (target / (100.0 * YEAR)).ceil();
92 n * 100.0 * YEAR
93 }
94
95 fn format_datetime(secs: f64, _step: f64) -> String {
98 let epoch_secs = secs as i64;
99 crate::data::format_epoch_secs(epoch_secs)
100 }
101}
102
103impl Default for ScaleDateTime {
104 fn default() -> Self {
105 Self::new()
106 }
107}
108
109impl Scale for ScaleDateTime {
110 fn aesthetic(&self) -> Aesthetic {
111 self.aesthetic.clone()
112 }
113
114 fn train(&mut self, values: &[Value]) {
115 for v in values {
116 if let Some(f) = v.as_f64() {
117 if f.is_finite() {
118 if f < self.min {
119 self.min = f;
120 }
121 if f > self.max {
122 self.max = f;
123 }
124 }
125 }
126 }
127 self.trained = true;
128 }
129
130 fn map(&self, value: &Value) -> f64 {
131 let f = match value.as_f64() {
132 Some(f) => f,
133 None => return 0.0,
134 };
135 let (emin, emax) = self.expanded_range();
136 let range = emax - emin;
137 if range.abs() < f64::EPSILON {
138 0.5
139 } else {
140 (f - emin) / range
141 }
142 }
143
144 fn breaks(&self) -> Vec<(f64, String)> {
145 if !self.trained || self.min > self.max {
146 return vec![];
147 }
148
149 let range = self.max - self.min;
150 if range.abs() < f64::EPSILON {
151 let label = Self::format_datetime(self.min, 1.0);
152 return vec![(0.5, label)];
153 }
154
155 let (emin, emax) = self.expanded_range();
156 let step = Self::nice_datetime_step(range);
157
158 let start = (emin / step).ceil() * step;
159 let mut breaks = Vec::new();
160 let mut v = start;
161 while v <= emax + step * 0.001 {
162 let pos = self.map(&Value::Float(v));
163 let label = Self::format_datetime(v, step);
164 breaks.push((pos, label));
165 v += step;
166 }
167 breaks
168 }
169
170 fn name(&self) -> &str {
171 &self.name
172 }
173
174 fn set_name(&mut self, name: &str) {
175 self.name = name.to_string();
176 }
177
178 fn set_limits(&mut self, min: f64, max: f64) {
179 self.min = min;
180 self.max = max;
181 self.trained = true;
182 }
183
184 fn clone_box(&self) -> Box<dyn Scale> {
185 Box::new(self.clone())
186 }
187
188 fn reset_training(&mut self) {
189 self.min = f64::INFINITY;
190 self.max = f64::NEG_INFINITY;
191 self.trained = false;
192 }
193}