1#[cfg(doctest)]
11mod test_readme {
12 macro_rules! external_doc_test {
13 ($x:expr) => {
14 #[doc = $x]
15 extern "C" {}
16 };
17 }
18
19 external_doc_test!(include_str!("../README.md"));
20}
21
22mod tick_finder;
23mod unixtime;
24
25use std::fmt::Display;
26
27use poloto::plotnum::*;
28use poloto::ticks::*;
29use poloto::*;
30
31use chrono::prelude::*;
32use chrono::DateTime;
33pub use unixtime::*;
34
35pub fn month_str(month: u32) -> &'static str {
39 match month {
40 1 => "Jan",
41 2 => "Feb",
42 3 => "Mar",
43 4 => "Apr",
44 5 => "May",
45 6 => "Jun",
46 7 => "Jul",
47 8 => "Aug",
48 9 => "Sep",
49 10 => "Oct",
50 11 => "Nov",
51 12 => "Dec",
52 _ => unreachable!(),
53 }
54}
55
56pub struct UnixTimeTickFmt<T: TimeZone> {
57 timezone: T,
58}
59
60impl UnixTimeTickFmt<Utc> {
61 pub fn new() -> Self {
62 Self::with_timezone(Utc)
63 }
64}
65impl Default for UnixTimeTickFmt<Utc> {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70impl<T: TimeZone> UnixTimeTickFmt<T> {
71 pub fn with_timezone(timezone: T) -> Self {
72 UnixTimeTickFmt { timezone }
73 }
74}
75
76pub struct UnixTimeFmt<T: TimeZone + Display> {
77 step: StepUnit,
78 timezone: T,
79 start: UnixTime,
80 footnote: char,
81}
82impl<T: TimeZone + Display> UnixTimeFmt<T> {
83 pub fn footnote(&self) -> char {
84 self.footnote
85 }
86 pub fn step(&self) -> &StepUnit {
87 &self.step
88 }
89 pub fn timezone(&self) -> &T {
90 &self.timezone
91 }
92 pub fn start(&self) -> &UnixTime {
93 &self.start
94 }
95}
96impl<T> crate::ticks::tick_fmt::TickFmt<UnixTime> for UnixTimeFmt<T>
97where
98 T: chrono::TimeZone + Display,
99 T::Offset: Display,
100{
101 fn write_tick(&self, writer: &mut dyn std::fmt::Write, val: &UnixTime) -> std::fmt::Result {
102 if *val == self.start {
103 write!(
104 writer,
105 "{}{}",
106 val.dynamic_format(&self.timezone, &self.step),
107 self.footnote
108 )
109 } else {
110 write!(writer, "{}", val.dynamic_format(&self.timezone, &self.step))
111 }
112 }
113
114 fn write_where(&self, writer: &mut dyn std::fmt::Write) -> std::fmt::Result {
115 let f = self.start.dynamic_where_format(&self.timezone, &self.step);
116
117 write!(
118 writer,
119 "{}{} in {} in TZ:{}",
120 self.footnote, f, self.step, &self.timezone
121 )
122 }
125}
126
127impl<T: TimeZone + Display> TickDistGen<UnixTime> for UnixTimeTickFmt<T>
128where
129 T::Offset: Display,
130{
131 type Res = TickDistribution<Vec<UnixTime>, UnixTimeFmt<T>>;
132
133 fn generate(
134 self,
135 data: &ticks::DataBound<UnixTime>,
136 canvas: &RenderFrameBound,
137 req: IndexRequester,
138 ) -> Self::Res {
139 let range = [data.min, data.max];
140
141 assert!(range[0] <= range[1]);
142 let ideal_num_steps = canvas.ideal_num_steps;
143
144 let ideal_num_steps = ideal_num_steps.max(2);
145
146 let [start, end] = range;
147 let mut t = tick_finder::BestTickFinder::new(end, ideal_num_steps);
148
149 let steps_yr = &[1, 2, 5, 10, 20, 25, 50, 100, 200, 500, 1000, 2000, 5000];
150 let steps_mo = &[1, 2, 3, 6];
151 let steps_dy = &[1, 2, 4, 5, 7];
152 let steps_hr = &[1, 2, 4, 6];
153 let steps_mi = &[1, 2, 10, 15, 30];
154 let steps_se = &[1, 2, 5, 10, 15, 30];
155
156 let d = start.datetime(&self.timezone);
157 use StepUnit::*;
158 t.consider_meta(YR, UnixYearGenerator { date: d.clone() }, steps_yr);
159 t.consider_meta(MO, UnixMonthGenerator { date: d.clone() }, steps_mo);
160 t.consider_meta(DY, UnixDayGenerator { date: d.clone() }, steps_dy);
161 t.consider_meta(HR, UnixHourGenerator { date: d.clone() }, steps_hr);
162 t.consider_meta(MI, UnixMinuteGenerator { date: d.clone() }, steps_mi);
163 t.consider_meta(SE, UnixSecondGenerator { date: d }, steps_se);
164
165 let ret = t.into_best().unwrap();
166
167 let ticks: Vec<_> = ret.ticks.into_iter().collect();
168
169 assert!(ticks.len() >= 2);
170
171 let start = ticks[0];
172
173 let index = req.request();
174
175 let footnote = match index {
176 0 => '¹',
177 1 => '²',
178 _ => unreachable!("There is a maximum of only two axis!"),
179 };
180
181 TickDistribution {
182 res: TickRes { dash_size: None },
183 iter: ticks,
184 fmt: UnixTimeFmt {
185 timezone: self.timezone,
186 step: ret.unit_data,
187 footnote,
188 start,
189 },
190 }
191 }
192}
193
194#[derive(Copy, Clone, Debug, Eq, PartialEq)]
198pub enum StepUnit {
199 YR,
200 MO,
201 DY,
202 HR,
203 MI,
204 SE,
205}
206
207impl StepUnit {
208 pub fn is_years(&self) -> bool {
209 *self == StepUnit::YR
210 }
211 pub fn is_months(&self) -> bool {
212 *self == StepUnit::MO
213 }
214 pub fn is_days(&self) -> bool {
215 *self == StepUnit::DY
216 }
217 pub fn is_hours(&self) -> bool {
218 *self == StepUnit::HR
219 }
220
221 pub fn is_minutes(&self) -> bool {
222 *self == StepUnit::MI
223 }
224
225 pub fn is_seconds(&self) -> bool {
226 *self == StepUnit::SE
227 }
228}
229impl std::fmt::Display for StepUnit {
230 fn fmt(&self, a: &mut std::fmt::Formatter) -> std::fmt::Result {
231 use StepUnit::*;
232 let val = match &self {
233 YR => "Years",
234 MO => "Months",
235 DY => "Days",
236 HR => "Hours",
237 MI => "Minutes",
238 SE => "Seconds",
239 };
240 write!(a, "{}", val)
241 }
242}
243
244impl plotnum::AsPlotnum for &UnixTime {
245 type Target = UnixTime;
246 fn as_plotnum(&self) -> &Self::Target {
247 self
248 }
249}
250
251impl plotnum::AsPlotnum for &mut UnixTime {
252 type Target = UnixTime;
253 fn as_plotnum(&self) -> &Self::Target {
254 self
255 }
256}
257
258impl HasDefaultTicks for UnixTime {
259 type DefaultTicks = UnixTimeTickFmt<Utc>;
260 fn default_ticks() -> Self::DefaultTicks {
261 UnixTimeTickFmt::new()
262 }
263}
264impl PlotNum for UnixTime {
265 #[inline(always)]
266 fn is_hole(&self) -> bool {
267 false
268 }
269
270 #[inline(always)]
271 fn scale(&self, range: &[UnixTime; 2], max: f64) -> f64 {
272 let val = *self;
273 let [val1, val2] = range;
274 let [val1, val2] = [val1.0, val2.0];
275 assert!(val1 <= val2);
276 let diff = (val2 - val1) as f64;
277 let scale = max / diff;
278 val.0 as f64 * scale
279 }
280 #[inline(always)]
281 fn unit_range(offset: Option<UnixTime>) -> [UnixTime; 2] {
282 if let Some(o) = offset {
283 [o, UnixTime(o.0 + 1)]
284 } else {
285 [UnixTime(0), UnixTime(1)]
286 }
287 }
288}