1use std::cell::Cell;
44use std::cell::RefCell;
45use std::collections::HashMap;
46use std::rc::Rc;
47
48use teksilo_core::color_prop::ColorProp;
49use teksilo_core::{ObserverHandle, Signal};
50
51use crate::chart_change::{ChartChange, SeriesId};
52use crate::chart_model::{ChartDatum, ChartModel, SeriesView};
53
54type SeriesIdsFn = Rc<dyn Fn() -> Vec<SeriesId>>;
55type PointCountFn = Rc<dyn Fn(SeriesId) -> usize>;
56type WithPointFn<T> = Rc<dyn Fn(SeriesId, usize, &dyn Fn(&ChartDatum<T>))>;
57type WithSeriesFn = Rc<dyn Fn(SeriesId, &dyn Fn(&str, Option<&ColorProp>, bool))>;
58type ObserveChartFn = Rc<dyn Fn(Box<dyn Fn(&ChartChange)>) -> ObserverHandle>;
59
60struct ObserverEntry {
61 id: u64,
62 callback: Rc<dyn Fn(&ChartChange)>,
63}
64
65struct ChartWindowInner<T: 'static> {
66 source: ChartModel<T>,
67 structure_version: Signal<u64>,
68 series_ids_fn: SeriesIdsFn,
69 point_count_fn: PointCountFn,
70 with_point_fn: WithPointFn<T>,
71 with_series_fn: WithSeriesFn,
72 window_size: usize,
73 starts: HashMap<SeriesId, usize>,
75 divergence: HashMap<SeriesId, usize>,
78 observers: Vec<ObserverEntry>,
79 next_observer_id: u64,
80 _upstream_handle: Option<ObserverHandle>,
81}
82
83impl<T: 'static> ChartWindowInner<T> {
84 fn start_for(&self, series: SeriesId) -> usize {
85 let total = (self.point_count_fn)(series);
86 total.saturating_sub(self.window_size)
87 }
88
89 fn rebuild_series(&mut self, series: SeriesId) {
90 let start = self.start_for(series);
91 self.starts.insert(series, start);
92 self.divergence.insert(series, 0);
93 }
94
95 fn rebuild_all(&mut self) {
96 self.starts.clear();
97 self.divergence.clear();
98 for series in (self.series_ids_fn)() {
99 self.rebuild_series(series);
100 }
101 }
102
103 fn snapshot_callbacks(&self) -> Vec<Rc<dyn Fn(&ChartChange)>> {
104 self.observers.iter().map(|e| e.callback.clone()).collect()
105 }
106}
107
108pub struct ChartWindow<T: 'static> {
112 inner: Rc<RefCell<ChartWindowInner<T>>>,
113}
114
115impl<T: 'static> ChartWindow<T> {
116 pub fn new(source: ChartModel<T>, window_size: usize) -> Self {
119 let series_ids_fn: SeriesIdsFn = {
120 let m = source.clone();
121 Rc::new(move || m.series_ids())
122 };
123 let point_count_fn: PointCountFn = {
124 let m = source.clone();
125 Rc::new(move |series| m.point_count(series))
126 };
127 let with_point_fn: WithPointFn<T> = {
128 let m = source.clone();
129 Rc::new(move |series, idx, f| {
130 m.with_point(series, idx, |d| f(d));
131 })
132 };
133 let with_series_fn: WithSeriesFn = {
134 let m = source.clone();
135 Rc::new(move |series, f| {
136 m.with_series(series, |name, color, visible| f(name, color, visible));
137 })
138 };
139 let observe_fn: ObserveChartFn = {
140 let m = source.clone();
141 Rc::new(move |callback| m.observe_changes(move |change| callback(change)))
142 };
143
144 let inner = Rc::new(RefCell::new(ChartWindowInner {
145 source,
146 structure_version: Signal::new(0),
147 series_ids_fn,
148 point_count_fn,
149 with_point_fn,
150 with_series_fn,
151 window_size,
152 starts: HashMap::new(),
153 divergence: HashMap::new(),
154 observers: Vec::new(),
155 next_observer_id: 1,
156 _upstream_handle: None,
157 }));
158
159 inner.borrow_mut().rebuild_all();
160
161 let weak = Rc::downgrade(&inner);
162 let upstream_handle = (observe_fn)(Box::new(move |change| {
163 if let Some(strong) = weak.upgrade() {
164 translate_and_notify(&strong, change);
165 }
166 }));
167 inner.borrow_mut()._upstream_handle = Some(upstream_handle);
168
169 Self { inner }
170 }
171
172 pub fn window_size(&self) -> usize {
174 self.inner.borrow().window_size
175 }
176
177 pub fn set_window_size(&self, window_size: usize) {
180 let (callbacks, version) = {
181 let mut guard = self.inner.borrow_mut();
182 guard.window_size = window_size;
183 guard.rebuild_all();
184 (guard.snapshot_callbacks(), guard.structure_version.clone())
185 };
186 for cb in &callbacks {
187 cb(&ChartChange::Reset);
188 }
189 version.set(version.get().wrapping_add(1));
190 }
191
192 pub fn source(&self) -> ChartModel<T> {
195 self.inner.borrow().source.clone()
196 }
197
198 pub fn structure_version(&self) -> Signal<u64> {
200 self.inner.borrow().structure_version.clone()
201 }
202
203 pub fn style_version(&self) -> Signal<u64> {
205 self.source().style_version()
206 }
207
208 pub fn with_all_series<R>(&self, f: impl FnOnce(&[SeriesView<'_, T>]) -> R) -> R {
210 let source = self.source();
211 let size = self.window_size();
212 source.with_all_series(|views| {
213 let windowed: Vec<_> = views
214 .iter()
215 .map(|view| SeriesView {
216 id: view.id,
217 name: view.name,
218 color: view.color,
219 pattern: view.pattern,
220 visible: view.visible,
221 points: &view.points[view.points.len().saturating_sub(size)..],
222 })
223 .collect();
224 f(&windowed)
225 })
226 }
227
228 pub fn with_series_view<R>(
230 &self,
231 series: SeriesId,
232 f: impl FnOnce(SeriesView<'_, T>) -> R,
233 ) -> Option<R> {
234 let size = self.window_size();
235 self.source().with_series_view(series, |mut view| {
236 view.points = &view.points[view.points.len().saturating_sub(size)..];
237 f(view)
238 })
239 }
240
241 pub fn series_count(&self) -> usize {
243 (self.inner.borrow().series_ids_fn)().len()
244 }
245
246 pub fn series_ids(&self) -> Vec<SeriesId> {
248 (self.inner.borrow().series_ids_fn)()
249 }
250
251 pub fn point_count(&self, series: SeriesId) -> usize {
253 let guard = self.inner.borrow();
254 let total = (guard.point_count_fn)(series);
255 let start = guard
256 .starts
257 .get(&series)
258 .copied()
259 .unwrap_or_else(|| total.saturating_sub(guard.window_size));
260 total.saturating_sub(start)
261 }
262
263 pub fn with_series<R>(
266 &self,
267 series: SeriesId,
268 f: impl FnOnce(&str, Option<&ColorProp>, bool) -> R,
269 ) -> Option<R> {
270 let with_series_fn = self.inner.borrow().with_series_fn.clone();
271 let f_cell: Cell<Option<_>> = Cell::new(Some(f));
272 let slot: Cell<Option<R>> = Cell::new(None);
273 (with_series_fn)(series, &|name, color, visible| {
274 if let Some(f) = f_cell.take() {
275 slot.set(Some(f(name, color, visible)));
276 }
277 });
278 slot.into_inner()
279 }
280
281 pub fn with_point<R>(
284 &self,
285 series: SeriesId,
286 index: usize,
287 f: impl FnOnce(&ChartDatum<T>) -> R,
288 ) -> Option<R> {
289 let (with_point_fn, src_idx) = {
290 let guard = self.inner.borrow();
291 let total = (guard.point_count_fn)(series);
292 let start = guard
293 .starts
294 .get(&series)
295 .copied()
296 .unwrap_or_else(|| total.saturating_sub(guard.window_size));
297 let visible = total.saturating_sub(start);
298 if index >= visible {
303 return None;
304 }
305 (guard.with_point_fn.clone(), start + index)
306 };
307 let f_cell: Cell<Option<_>> = Cell::new(Some(f));
308 let slot: Cell<Option<R>> = Cell::new(None);
309 (with_point_fn)(series, src_idx, &|d: &ChartDatum<T>| {
310 if let Some(f) = f_cell.take() {
311 slot.set(Some(f(d)));
312 }
313 });
314 slot.into_inner()
315 }
316
317 pub fn observe_changes(&self, f: impl Fn(&ChartChange) + 'static) -> ObserverHandle {
320 let mut guard = self.inner.borrow_mut();
321 let id = guard.next_observer_id;
322 guard.next_observer_id += 1;
323 guard.observers.push(ObserverEntry {
324 id,
325 callback: Rc::new(f),
326 });
327 let inner = self.inner.clone();
328 ObserverHandle::new(
329 self.inner.clone(),
330 id,
331 Rc::new(move |observer_id| {
332 inner.borrow_mut().observers.retain(|e| e.id != observer_id);
333 }),
334 )
335 }
336
337 pub fn first_changed_index(&self, series: SeriesId) -> Option<usize> {
343 self.inner.borrow().divergence.get(&series).copied()
344 }
345}
346
347fn translate<T: 'static>(
350 inner: &mut ChartWindowInner<T>,
351 change: &ChartChange,
352) -> Vec<ChartChange> {
353 match change {
354 ChartChange::SeriesInserted { index, series } => {
355 let (index, series) = (*index, *series);
356 inner.starts.insert(series, 0);
357 inner.divergence.insert(series, 0);
358 vec![ChartChange::SeriesInserted { index, series }]
359 }
360 ChartChange::SeriesRemoved { series } => {
361 let series = *series;
362 inner.starts.remove(&series);
363 inner.divergence.remove(&series);
364 vec![ChartChange::SeriesRemoved { series }]
365 }
366 ChartChange::SeriesMoved { series, from, to } => vec![ChartChange::SeriesMoved {
367 series: *series,
368 from: *from,
369 to: *to,
370 }],
371 ChartChange::SeriesRenamed { series } => {
372 vec![ChartChange::SeriesRenamed { series: *series }]
373 }
374 ChartChange::SeriesColorChanged { series } => {
375 vec![ChartChange::SeriesColorChanged { series: *series }]
376 }
377 ChartChange::SeriesPatternChanged { series } => {
378 vec![ChartChange::SeriesPatternChanged { series: *series }]
379 }
380 ChartChange::SeriesVisibilityChanged { series } => {
381 vec![ChartChange::SeriesVisibilityChanged { series: *series }]
382 }
383 ChartChange::PointsInserted { series, range } => {
384 let series = *series;
385 let range = range.clone();
386 let window_size = inner.window_size;
387 let new_total = (inner.point_count_fn)(series);
388 let inserted = range.end - range.start;
389 let old_total = new_total - inserted;
390 let old_start = inner
391 .starts
392 .get(&series)
393 .copied()
394 .unwrap_or_else(|| old_total.saturating_sub(window_size));
395
396 if range.start != old_total {
397 inner.rebuild_series(series);
399 return vec![ChartChange::SeriesDataReplaced { series }];
400 }
401
402 let old_visible = old_total.saturating_sub(old_start);
412 let new_start = new_total.saturating_sub(window_size);
413 let new_visible = new_total.saturating_sub(new_start);
414 let dropped = new_start.saturating_sub(old_start).min(old_visible);
417 let remaining = old_visible - dropped;
418
419 let mut out = Vec::new();
420 if dropped > 0 {
421 out.push(ChartChange::PointsRemoved {
422 series,
423 range: 0..dropped,
424 });
425 }
426 if new_visible > remaining {
427 out.push(ChartChange::PointsInserted {
428 series,
429 range: remaining..new_visible,
430 });
431 }
432 inner
436 .divergence
437 .insert(series, if dropped > 0 { 0 } else { remaining });
438 inner.starts.insert(series, new_start);
439 out
440 }
441 ChartChange::PointsRemoved { series, range } => {
442 let series = *series;
443 let range = range.clone();
444 let window_size = inner.window_size;
445 let new_total = (inner.point_count_fn)(series);
446 let removed = range.end - range.start;
447 let old_total = new_total + removed;
448 let old_start = inner
449 .starts
450 .get(&series)
451 .copied()
452 .unwrap_or_else(|| old_total.saturating_sub(window_size));
453
454 if range.end != old_total {
455 inner.rebuild_series(series);
459 return vec![ChartChange::SeriesDataReplaced { series }];
460 }
461
462 let old_visible = old_total.saturating_sub(old_start);
471 let new_start = new_total.saturating_sub(window_size);
472 let new_visible = new_total.saturating_sub(new_start);
473 let survivors = new_total.saturating_sub(old_start).min(old_visible);
476 let removed_from_window = old_visible - survivors;
477 let revealed = old_start.saturating_sub(new_start);
478
479 let mut out = Vec::new();
480 if removed_from_window > 0 {
481 out.push(ChartChange::PointsRemoved {
482 series,
483 range: survivors..old_visible,
484 });
485 }
486 if revealed > 0 {
487 out.push(ChartChange::PointsInserted {
488 series,
489 range: 0..revealed,
490 });
491 }
492 inner.divergence.insert(
496 series,
497 if revealed > 0 {
498 0
499 } else {
500 survivors.min(new_visible)
501 },
502 );
503 inner.starts.insert(series, new_start);
504 out
505 }
506 ChartChange::PointUpdated { series, index } => {
507 let (series, index) = (*series, *index);
508 let start = inner.starts.get(&series).copied().unwrap_or(0);
509 if index >= start {
510 let local = index - start;
511 inner.divergence.insert(series, local);
512 vec![ChartChange::PointUpdated {
513 series,
514 index: local,
515 }]
516 } else {
517 vec![]
518 }
519 }
520 ChartChange::SeriesDataReplaced { series } => {
521 let series = *series;
522 inner.rebuild_series(series);
523 vec![ChartChange::SeriesDataReplaced { series }]
524 }
525 ChartChange::Reset => {
526 inner.rebuild_all();
527 vec![ChartChange::Reset]
528 }
529 }
530}
531
532fn translate_and_notify<T: 'static>(
533 inner: &Rc<RefCell<ChartWindowInner<T>>>,
534 change: &ChartChange,
535) {
536 let (changes, callbacks, version) = {
537 let mut guard = inner.borrow_mut();
538 let changes = translate(&mut guard, change);
539 (
540 changes,
541 guard.snapshot_callbacks(),
542 guard.structure_version.clone(),
543 )
544 };
545 for c in &changes {
546 for cb in &callbacks {
547 cb(c);
548 }
549 }
550 if changes.iter().any(|change| {
551 !matches!(
552 change,
553 ChartChange::SeriesColorChanged { .. } | ChartChange::SeriesPatternChanged { .. }
554 )
555 }) {
556 version.set(version.get().wrapping_add(1));
557 }
558}
559
560impl<T: 'static> Clone for ChartWindow<T> {
561 fn clone(&self) -> Self {
562 Self {
563 inner: self.inner.clone(),
564 }
565 }
566}
567
568impl<T: 'static> std::fmt::Debug for ChartWindow<T> {
569 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
570 let guard = self.inner.borrow();
571 f.debug_struct("ChartWindow")
572 .field("window_size", &guard.window_size)
573 .field("series_count", &(guard.series_ids_fn)().len())
574 .finish()
575 }
576}
577
578#[cfg(test)]
579mod tests {
580 use super::*;
581
582 fn one_series(n: usize) -> (ChartModel<i32>, SeriesId) {
583 let model: ChartModel<i32> = ChartModel::new();
584 let s = model.add_series("s");
585 for i in 0..n {
586 model.push_point(s, i as i32, i as f32);
587 }
588 (model, s)
589 }
590
591 fn track(window: &ChartWindow<i32>) -> (Rc<RefCell<Vec<ChartChange>>>, ObserverHandle) {
592 let log: Rc<RefCell<Vec<ChartChange>>> = Rc::new(RefCell::new(Vec::new()));
593 let l = log.clone();
594 let handle = window.observe_changes(move |c| l.borrow_mut().push(c.clone()));
595 (log, handle)
596 }
597
598 #[test]
599 fn initial_window_shows_only_the_tail() {
600 let (_model, s) = one_series(5);
601 let window = ChartWindow::new(_model, 3);
602 assert_eq!(window.point_count(s), 3);
603 let vals: Vec<f32> = (0..3)
604 .map(|i| window.with_point(s, i, |d| d.value).unwrap())
605 .collect();
606 assert_eq!(vals, vec![2.0, 3.0, 4.0]);
607 }
608
609 #[test]
610 fn with_point_out_of_range_returns_none_without_overflow() {
611 let (model, s) = one_series(5);
616 let window = ChartWindow::new(model, 3); assert_eq!(window.with_point(s, usize::MAX, |d| d.value), None);
618 }
619
620 #[test]
621 fn full_window_shift_emits_removed_then_inserted() {
622 let (model, s) = one_series(3);
623 let window = ChartWindow::new(model.clone(), 3);
624 assert_eq!(window.point_count(s), 3);
625 let (log, _h) = track(&window);
626
627 model.push_point(s, 3, 3.0);
628
629 let entries = log.borrow();
630 assert_eq!(entries.len(), 2);
631 assert_eq!(
632 entries[0],
633 ChartChange::PointsRemoved {
634 series: s,
635 range: 0..1
636 }
637 );
638 assert_eq!(
639 entries[1],
640 ChartChange::PointsInserted {
641 series: s,
642 range: 2..3
643 }
644 );
645 drop(entries);
646
647 assert_eq!(window.point_count(s), 3);
648 let vals: Vec<f32> = (0..3)
649 .map(|i| window.with_point(s, i, |d| d.value).unwrap())
650 .collect();
651 assert_eq!(vals, vec![1.0, 2.0, 3.0]);
652 }
653
654 #[test]
655 fn partial_window_tail_append_emits_plain_inserted() {
656 let (model, s) = one_series(1);
657 let window = ChartWindow::new(model.clone(), 5);
658 let (log, _h) = track(&window);
659
660 model.push_point(s, 1, 1.0);
661
662 let entries = log.borrow();
663 assert_eq!(entries.len(), 1);
664 assert_eq!(
665 entries[0],
666 ChartChange::PointsInserted {
667 series: s,
668 range: 1..2
669 }
670 );
671 drop(entries);
672 assert_eq!(window.point_count(s), 2);
673 }
674
675 #[test]
676 fn mid_series_insert_falls_back_to_replace() {
677 let (model, s) = one_series(3);
678 let window = ChartWindow::new(model.clone(), 5);
679 let (log, _h) = track(&window);
680
681 model.insert_point(s, 1, 99, 99.0);
682
683 let entries = log.borrow();
684 assert_eq!(entries.len(), 1);
685 assert_eq!(entries[0], ChartChange::SeriesDataReplaced { series: s });
686 drop(entries);
687 assert_eq!(window.point_count(s), 4);
688 }
689
690 #[test]
691 fn front_removal_falls_back_to_replace() {
692 let (model, s) = one_series(3);
695 let window = ChartWindow::new(model.clone(), 5);
696 let (log, _h) = track(&window);
697
698 model.remove_point(s, 0);
699
700 let entries = log.borrow();
701 assert_eq!(entries.len(), 1);
702 assert_eq!(entries[0], ChartChange::SeriesDataReplaced { series: s });
703 drop(entries);
704 assert_eq!(window.point_count(s), 2);
705 }
706
707 #[test]
708 fn partial_window_tail_removal_emits_plain_removed() {
709 let (model, s) = one_series(5);
712 let window = ChartWindow::new(model.clone(), 10);
713 let (log, _h) = track(&window);
714
715 model.remove_point(s, 4); let entries = log.borrow();
718 assert_eq!(entries.len(), 1);
719 assert_eq!(
720 entries[0],
721 ChartChange::PointsRemoved {
722 series: s,
723 range: 4..5
724 }
725 );
726 drop(entries);
727 assert_eq!(window.point_count(s), 4);
728 let vals: Vec<f32> = (0..4)
729 .map(|i| window.with_point(s, i, |d| d.value).unwrap())
730 .collect();
731 assert_eq!(vals, vec![0.0, 1.0, 2.0, 3.0]);
732 }
733
734 #[test]
735 fn full_window_tail_removal_emits_removed_then_inserted() {
736 let (model, s) = one_series(5); let window = ChartWindow::new(model.clone(), 3);
740 assert_eq!(window.point_count(s), 3);
741 let (log, _h) = track(&window);
742
743 model.remove_point(s, 4); let entries = log.borrow();
746 assert_eq!(entries.len(), 2);
747 assert_eq!(
748 entries[0],
749 ChartChange::PointsRemoved {
750 series: s,
751 range: 2..3
752 }
753 );
754 assert_eq!(
755 entries[1],
756 ChartChange::PointsInserted {
757 series: s,
758 range: 0..1
759 }
760 );
761 drop(entries);
762
763 assert_eq!(window.point_count(s), 3);
764 let vals: Vec<f32> = (0..3)
765 .map(|i| window.with_point(s, i, |d| d.value).unwrap())
766 .collect();
767 assert_eq!(vals, vec![1.0, 2.0, 3.0]);
768 }
769
770 #[test]
771 fn update_in_and_out_of_window() {
772 let (model, s) = one_series(5);
773 let window = ChartWindow::new(model.clone(), 2); let (log, _h) = track(&window);
775
776 model.update_point(s, 4, 4, 40.0); model.update_point(s, 0, 0, 5.0); let entries = log.borrow();
780 assert_eq!(entries.len(), 1);
781 assert_eq!(
782 entries[0],
783 ChartChange::PointUpdated {
784 series: s,
785 index: 1
786 }
787 );
788 drop(entries);
789 assert_eq!(window.with_point(s, 1, |d| d.value), Some(40.0));
790 }
791
792 #[test]
793 fn reset_passes_through_and_rebuilds() {
794 let (model, s) = one_series(3);
795 let window = ChartWindow::new(model.clone(), 2);
796 let (log, _h) = track(&window);
797
798 model.clear();
799
800 assert_eq!(log.borrow().last(), Some(&ChartChange::Reset));
801 assert_eq!(window.point_count(s), 0);
802 }
803
804 #[test]
805 fn dropping_window_unregisters_upstream_observer() {
806 let model: ChartModel<i32> = ChartModel::new();
807 let before = model.observer_count();
808 let window = ChartWindow::new(model.clone(), 3);
809 assert_eq!(model.observer_count(), before + 1);
810 drop(window);
811 assert_eq!(model.observer_count(), before);
812 }
813
814 #[test]
815 fn set_window_size_rebuilds_and_emits_reset() {
816 let (model, s) = one_series(5);
817 let window = ChartWindow::new(model, 2);
818 assert_eq!(window.point_count(s), 2);
819 let (log, _h) = track(&window);
820
821 window.set_window_size(4);
822 assert_eq!(window.point_count(s), 4);
823 assert_eq!(log.borrow().last(), Some(&ChartChange::Reset));
824 }
825
826 #[test]
827 fn series_inserted_and_removed_pass_through() {
828 let model: ChartModel<i32> = ChartModel::new();
829 let window = ChartWindow::new(model.clone(), 3);
830 let (log, _h) = track(&window);
831
832 let s = model.add_series("new");
833 assert_eq!(
834 log.borrow().last(),
835 Some(&ChartChange::SeriesInserted {
836 index: 0,
837 series: s
838 })
839 );
840
841 model.remove_series(s);
842 assert_eq!(
843 log.borrow().last(),
844 Some(&ChartChange::SeriesRemoved { series: s })
845 );
846 assert_eq!(window.series_count(), 0);
847 }
848
849 #[test]
850 fn translate_multi_point_insert_not_full_to_overflow() {
851 let model: ChartModel<i32> = ChartModel::new();
865 let s = model.add_series("s");
866 model.push_point(s, 0, 0.0);
867 model.push_point(s, 1, 1.0); let series_ids_fn: SeriesIdsFn = {
870 let m = model.clone();
871 Rc::new(move || m.series_ids())
872 };
873 let point_count_fn: PointCountFn = {
874 let m = model.clone();
875 Rc::new(move |series| m.point_count(series))
876 };
877 let with_point_fn: WithPointFn<i32> = {
878 let m = model.clone();
879 Rc::new(move |series, idx, f| {
880 m.with_point(series, idx, |d| f(d));
881 })
882 };
883 let with_series_fn: WithSeriesFn = {
884 let m = model.clone();
885 Rc::new(move |series, f| {
886 m.with_series(series, |name, color, visible| f(name, color, visible));
887 })
888 };
889
890 let mut inner = ChartWindowInner {
891 source: model.clone(),
892 structure_version: Signal::new(0),
893 series_ids_fn,
894 point_count_fn,
895 with_point_fn,
896 with_series_fn,
897 window_size: 3,
898 starts: HashMap::new(),
899 divergence: HashMap::new(),
900 observers: Vec::new(),
901 next_observer_id: 1,
902 _upstream_handle: None,
903 };
904 inner.rebuild_all(); model.push_point(s, 2, 2.0);
911 model.push_point(s, 3, 3.0);
912
913 let changes = translate(
914 &mut inner,
915 &ChartChange::PointsInserted {
916 series: s,
917 range: 2..4,
918 },
919 );
920
921 assert_eq!(
925 changes,
926 vec![
927 ChartChange::PointsRemoved {
928 series: s,
929 range: 0..1
930 },
931 ChartChange::PointsInserted {
932 series: s,
933 range: 1..3
934 },
935 ]
936 );
937 }
938
939 #[test]
940 fn translate_multi_point_tail_removal_from_full_to_not_full() {
941 let model: ChartModel<i32> = ChartModel::new();
951 let s = model.add_series("s");
952 for i in 0..5 {
953 model.push_point(s, i, i as f32);
954 }
955
956 let series_ids_fn: SeriesIdsFn = {
957 let m = model.clone();
958 Rc::new(move || m.series_ids())
959 };
960 let point_count_fn: PointCountFn = {
961 let m = model.clone();
962 Rc::new(move |series| m.point_count(series))
963 };
964 let with_point_fn: WithPointFn<i32> = {
965 let m = model.clone();
966 Rc::new(move |series, idx, f| {
967 m.with_point(series, idx, |d| f(d));
968 })
969 };
970 let with_series_fn: WithSeriesFn = {
971 let m = model.clone();
972 Rc::new(move |series, f| {
973 m.with_series(series, |name, color, visible| f(name, color, visible));
974 })
975 };
976
977 let mut inner = ChartWindowInner {
978 source: model.clone(),
979 structure_version: Signal::new(0),
980 series_ids_fn,
981 point_count_fn,
982 with_point_fn,
983 with_series_fn,
984 window_size: 3,
985 starts: HashMap::new(),
986 divergence: HashMap::new(),
987 observers: Vec::new(),
988 next_observer_id: 1,
989 _upstream_handle: None,
990 };
991 inner.rebuild_all(); model.remove_point(s, 4);
998 model.remove_point(s, 3);
999 model.remove_point(s, 2);
1000
1001 let changes = translate(
1002 &mut inner,
1003 &ChartChange::PointsRemoved {
1004 series: s,
1005 range: 2..5,
1006 },
1007 );
1008
1009 assert_eq!(
1013 changes,
1014 vec![
1015 ChartChange::PointsRemoved {
1016 series: s,
1017 range: 0..3
1018 },
1019 ChartChange::PointsInserted {
1020 series: s,
1021 range: 0..2
1022 },
1023 ]
1024 );
1025 }
1026}