1use std::{cmp::Ordering, ops::Range, sync::Arc, time::Duration};
4
5use egui::Ui;
6use instant::Instant;
7use plotters::{
8 prelude::ChartBuilder,
9 series::LineSeries,
10 style::{
11 full_palette::{GREY, GREY_700, RED_900},
12 Color, FontDesc, RGBAColor, ShapeStyle, TextStyle, BLACK, WHITE,
13 },
14};
15use plotters_backend::{FontFamily, FontStyle};
16
17use crate::{mult_range, Chart, MouseConfig};
18
19const MIN_DELTA: f32 = 0.000_010;
20const DEFAULT_RATIO: f32 = 1.0;
21const X_MARGIN: i32 = 25;
22const Y_MARGIN: i32 = 25;
23const LABEL_AREA: i32 = 25;
24const CAPTION_SIZE: i32 = 10;
25
26#[derive(Clone)]
27struct XyTimeConfig {
28 points: Arc<[(f32, f32)]>,
30 range: (Range<f32>, Range<f32>),
32 line_style: ShapeStyle,
34 grid_style: ShapeStyle,
36 subgrid_style: ShapeStyle,
38 axes_style: ShapeStyle,
40 text_color: RGBAColor,
42 background_color: RGBAColor,
44 x_unit: Arc<str>,
46 y_unit: Arc<str>,
48 ratio: f32,
50 caption: Arc<str>,
52}
53
54pub struct XyTimeData {
73 playback_start: Option<Instant>,
74 pause_start: Option<Instant>,
75 playback_speed: f32,
76 points: Arc<[(f32, f32)]>,
77 ranges: Arc<[(Range<f32>, Range<f32>)]>,
78 times: Arc<[f32]>,
79 chart: Chart<XyTimeConfig>,
80}
81
82impl XyTimeData {
83 pub fn new(points: &[(f32, f32, f32)], x_unit: &str, y_unit: &str, caption: &str) -> Self {
85 let mut points = points.to_vec();
86
87 points.sort_by(|a, b| {
89 let (_, _, a) = a;
90 let (_, _, b) = b;
91
92 a.partial_cmp(b).unwrap_or(Ordering::Equal)
93 });
94
95 let times: Vec<f32> = points
96 .iter()
97 .map(|point| {
98 let (_, _, time) = point;
99
100 *time
101 })
102 .collect();
103
104 let points: Vec<(f32, f32)> = points
105 .iter()
106 .map(|point| {
107 let (x, y, _) = point;
108
109 (*x, *y)
110 })
111 .collect();
112
113 let mut ranges = Vec::<(Range<f32>, Range<f32>)>::with_capacity(points.len());
115
116 let mut min_x: f32 = f32::MAX;
117 let mut min_y: f32 = f32::MAX;
118 let mut max_x: f32 = f32::MIN;
119 let mut max_y: f32 = f32::MIN;
120
121 for point in &points {
122 let (x, y) = *point;
123
124 min_x = min_x.min(x);
125 min_y = min_y.min(y);
126 max_x = max_x.max(x);
127 max_y = max_y.max(y);
128
129 let range_x = min_x..max_x;
130 let range_y = min_y..max_y;
131
132 ranges.push((range_x, range_y));
133 }
134
135 let points: Arc<[(f32, f32)]> = points.into();
139 let ranges: Arc<[(Range<f32>, Range<f32>)]> = ranges.into();
140 let times: Arc<[f32]> = times.into();
141
142 let x_unit: Arc<str> = x_unit.into();
143 let y_unit: Arc<str> = y_unit.into();
144 let caption: Arc<str> = caption.into();
145
146 let grid_style = ShapeStyle {
147 color: GREY.to_rgba(),
148 filled: false,
149 stroke_width: 2,
150 };
151
152 let subgrid_style = ShapeStyle {
153 color: GREY_700.to_rgba(),
154 filled: false,
155 stroke_width: 1,
156 };
157
158 let axes_style = ShapeStyle {
159 color: BLACK.to_rgba(),
160 filled: false,
161 stroke_width: 2,
162 };
163
164 let line_style = ShapeStyle {
165 color: RED_900.to_rgba(),
166 filled: false,
167 stroke_width: 2,
168 };
169
170 let background_color = WHITE.to_rgba();
171 let text_color = BLACK.to_rgba();
172
173 let config = XyTimeConfig {
174 points: points.clone(),
175 range: ranges.last().unwrap().clone(),
176 line_style,
177 grid_style,
178 subgrid_style,
179 axes_style,
180 text_color,
181 background_color,
182 x_unit,
183 y_unit,
184 ratio: DEFAULT_RATIO,
185 caption,
186 };
187
188 let chart = Chart::new(config)
189 .mouse(MouseConfig::enabled())
190 .builder_cb(Box::new(|area, _t, data| {
191 let area_ratio = {
192 let (x_range, y_range) = area.get_pixel_range();
193
194 let x_delta =
195 ((x_range.end - x_range.start).abs() - (X_MARGIN * 2) - LABEL_AREA) as f32;
196 let y_delta = ((y_range.end - y_range.start).abs()
197 - (Y_MARGIN * 2)
198 - LABEL_AREA
199 - CAPTION_SIZE) as f32;
200
201 x_delta / y_delta
202 };
203
204 if !area_ratio.is_finite() {
206 return;
207 }
208
209 let (x_range, y_range) = data.range.clone();
210
211 let data_ratio = {
214 let x_delta = (x_range.end - x_range.start).abs();
215 let y_delta = (y_range.end - y_range.start).abs();
216
217 y_delta / x_delta
218 };
219
220 let display_ratio = data.ratio * data_ratio * area_ratio;
221
222 let (x_range, y_range) =
223 match display_ratio.partial_cmp(&1.0).unwrap_or(Ordering::Equal) {
224 Ordering::Equal => (x_range, y_range),
225 Ordering::Greater => (mult_range(x_range, display_ratio), y_range),
226 Ordering::Less => (x_range, mult_range(y_range, 1.0 / display_ratio)),
227 };
228
229 let font_style = FontStyle::Normal;
230 let font_family = FontFamily::Monospace;
231 let font_size = CAPTION_SIZE;
232
233 let font_desc = FontDesc::new(font_family, font_size as f64, font_style);
234
235 let text_style = TextStyle::from(font_desc).color(&data.text_color);
236
237 let mut chart = ChartBuilder::on(area)
238 .caption(data.caption.clone(), text_style.clone())
239 .x_label_area_size(LABEL_AREA)
240 .y_label_area_size(LABEL_AREA)
241 .margin_left(X_MARGIN)
242 .margin_right(X_MARGIN)
243 .margin_top(Y_MARGIN)
244 .margin_bottom(Y_MARGIN)
245 .build_cartesian_2d(x_range, y_range)
246 .unwrap();
247
248 chart
249 .configure_mesh()
250 .label_style(text_style.clone())
251 .bold_line_style(data.grid_style)
252 .light_line_style(data.subgrid_style)
253 .axis_style(data.axes_style)
254 .x_desc(&data.x_unit.to_string())
255 .set_all_tick_mark_size(4)
256 .y_desc(&data.y_unit.to_string())
257 .draw()
258 .unwrap();
259
260 chart
261 .draw_series(LineSeries::new(data.points.to_vec(), data.line_style))
262 .unwrap();
263 }));
264
265 Self {
266 playback_start: None,
267 pause_start: None,
268 playback_speed: 1.0,
269 points,
270 ranges,
271 times,
272 chart,
273 }
274 }
275
276 pub fn set_time(&mut self, time: f32) {
278 let start_time = Some(Instant::now() - Duration::from_secs_f32(time));
279 match self.playback_start {
280 Some(_) => {
281 if let Some(_) = self.pause_start {
282 self.pause_start = Some(Instant::now());
283 }
284
285 self.playback_start = start_time;
286 }
287 None => {
288 self.playback_start = start_time;
289 self.pause_start = Some(Instant::now());
290 }
291 }
292 }
293
294 #[inline]
295 pub fn time(mut self, time: f32) -> Self {
297 self.set_time(time);
298
299 self
300 }
301
302 #[inline]
303 pub fn set_playback_speed(&mut self, speed: f32) {
305 self.playback_speed = speed;
306 }
307
308 #[inline]
309 pub fn playback_speed(mut self, speed: f32) -> Self {
311 self.set_playback_speed(speed);
312
313 self
314 }
315
316 pub fn set_line_style(&mut self, line_style: ShapeStyle) {
318 self.chart.get_data_mut().line_style = line_style;
319 }
320
321 #[inline]
322 pub fn line_style(mut self, line_style: ShapeStyle) -> Self {
324 self.set_line_style(line_style);
325
326 self
327 }
328
329 pub fn set_grid_style(&mut self, grid_style: ShapeStyle) {
331 self.chart.get_data_mut().grid_style = grid_style
332 }
333
334 #[inline]
335 pub fn grid_style(mut self, grid_style: ShapeStyle) -> Self {
337 self.set_grid_style(grid_style);
338
339 self
340 }
341
342 pub fn set_subgrid_style(&mut self, subgrid_style: ShapeStyle) {
344 self.chart.get_data_mut().subgrid_style = subgrid_style
345 }
346
347 #[inline]
348 pub fn subgrid_style(mut self, subgrid_style: ShapeStyle) -> Self {
350 self.set_subgrid_style(subgrid_style);
351
352 self
353 }
354
355 pub fn set_axes_style(&mut self, axes_style: ShapeStyle) {
357 self.chart.get_data_mut().axes_style = axes_style
358 }
359
360 #[inline]
361 pub fn axes_style(mut self, axes_style: ShapeStyle) -> Self {
363 self.set_axes_style(axes_style);
364
365 self
366 }
367
368 pub fn set_text_color<T>(&mut self, color: T)
370 where
371 T: Into<RGBAColor>,
372 {
373 let color: RGBAColor = color.into();
374
375 self.chart.get_data_mut().text_color = color
376 }
377
378 #[inline]
379 pub fn text_color<T>(mut self, color: T) -> Self
381 where
382 T: Into<RGBAColor>,
383 {
384 self.set_text_color(color);
385
386 self
387 }
388
389 pub fn set_background_color<T>(&mut self, color: T)
391 where
392 T: Into<RGBAColor>,
393 {
394 let color: RGBAColor = color.into();
395
396 self.chart.get_data_mut().background_color = color
397 }
398
399 #[inline]
400 pub fn background_color<T>(mut self, color: T) -> Self
402 where
403 T: Into<RGBAColor>,
404 {
405 self.set_background_color(color);
406
407 self
408 }
409
410 #[inline]
411 pub fn set_ratio(&mut self, ratio: f32) {
413 self.chart.get_data_mut().ratio = ratio
414 }
415
416 #[inline]
417 pub fn ratio(mut self, ratio: f32) -> Self {
419 self.set_ratio(ratio);
420
421 self
422 }
423
424 pub fn draw(&mut self, ui: &Ui) {
427 if let Some(_) = self.playback_start {
428 let time = self.current_time();
429
430 let time_index = match self
431 .times
432 .binary_search_by(|probe| probe.partial_cmp(&time).unwrap_or(Ordering::Equal))
433 {
434 Ok(index) => index,
435 Err(index) => self.points.len().min(index),
436 };
437
438 let points = &self.points[..=time_index];
440 let range = self.ranges[time_index].clone();
441
442 let config = self.chart.get_data_mut();
443 config.points = points.into();
444 config.range = range;
445 }
446
447 self.chart.draw(ui);
448 }
449
450 #[inline]
451 pub fn start_playback(&mut self) {
453 self.playback_start = Some(Instant::now());
454 self.pause_start = None;
455 }
456
457 #[inline]
458 pub fn stop_playback(&mut self) {
460 self.playback_start = None;
461 self.pause_start = None;
462 }
463
464 pub fn toggle_playback(&mut self) {
466 match self.playback_start {
467 Some(playback_start) => match self.pause_start {
468 Some(pause_start) => {
469 let delta = Instant::now().duration_since(pause_start);
470
471 self.pause_start = None;
472 self.playback_start = Some(playback_start + delta);
473 }
474 None => self.pause_start = Some(Instant::now()),
475 },
476
477 None => {
478 self.start_playback();
479 }
480 }
481 }
482
483 #[inline]
484 pub fn is_playing(&self) -> bool {
486 self.playback_start != None && self.pause_start == None
487 }
488
489 #[inline]
490 pub fn start_time(&self) -> f32 {
492 let time_start = *self.times.first().unwrap();
493
494 time_start
495 }
496
497 pub fn current_time(&mut self) -> f32 {
499 if let Some(playback_start) = self.playback_start {
500 let now = Instant::now();
501
502 let time_start = self.start_time();
503 let time_end = self.end_time();
504
505 let base_delta = time_end - time_start;
506
507 let current_delta = MIN_DELTA
510 + self.playback_speed
511 * match self.pause_start {
512 Some(pause_start) => {
513 pause_start.duration_since(playback_start).as_secs_f32()
514 }
515 None => now.duration_since(playback_start).as_secs_f32(),
516 };
517
518 match base_delta > current_delta {
519 true => current_delta + time_start,
520 false => {
521 self.playback_start = None;
522
523 time_end
524 }
525 }
526 } else {
527 self.start_time()
528 }
529 }
530
531 #[inline]
532 pub fn end_time(&self) -> f32 {
534 let time_end = *self.times.last().unwrap();
535
536 time_end
537 }
538
539 #[inline]
540 pub fn get_playback_speed(&self) -> f32 {
542 self.playback_speed
543 }
544}