1use super::cache::{ChartCache, ChartDirtyFlags};
48use super::data::downsample_data;
49use super::rect::Rect;
50use super::renderers::{
51 GPU_RENDER_THRESHOLD, GpuChartAreaRenderer, GpuChartBarRenderer, GpuChartLineRenderer,
52 GpuChartScatterRenderer,
53};
54use super::types::{AxisId, Chart, ChartType, DataPoint};
55use astrelis_render::{GraphicsContext, Viewport, wgpu};
56use std::sync::Arc;
57
58#[cfg(feature = "chart-text")]
59use super::text::ChartTextRenderer;
60
61#[cfg(feature = "chart-text")]
62use astrelis_text::FontSystem;
63
64#[derive(Debug, Clone, Default)]
66pub struct SeriesDirtyState {
67 pub last_rendered_count: usize,
69 pub total_pushed: u64,
71 pub needs_full_rebuild: bool,
73 pub dirty_range: Option<(usize, usize)>,
75}
76
77#[derive(Debug, Clone)]
79pub struct PrepareResult {
80 pub updated: bool,
82 pub series_updates: Vec<SeriesUpdateInfo>,
84}
85
86#[derive(Debug, Clone)]
88pub struct SeriesUpdateInfo {
89 pub index: usize,
91 pub full_rebuild: bool,
93 pub new_points: usize,
95}
96
97#[derive(Debug)]
125pub struct StreamingChart {
126 chart: Chart,
127 cache: ChartCache,
128 series_dirty: Vec<SeriesDirtyState>,
130 auto_scroll_config: Vec<(AxisId, f64)>,
132}
133
134impl StreamingChart {
135 pub fn new(chart: Chart) -> Self {
137 let series_count = chart.series.len();
138 Self {
139 chart,
140 cache: ChartCache::new(),
141 series_dirty: vec![SeriesDirtyState::default(); series_count],
142 auto_scroll_config: Vec::new(),
143 }
144 }
145
146 pub fn chart(&self) -> &Chart {
148 &self.chart
149 }
150
151 pub fn chart_mut(&mut self) -> &mut Chart {
156 self.cache.invalidate();
157 &mut self.chart
158 }
159
160 pub fn cache(&self) -> &ChartCache {
162 &self.cache
163 }
164
165 pub fn cache_mut(&mut self) -> &mut ChartCache {
167 &mut self.cache
168 }
169
170 pub fn needs_rebuild(&self) -> bool {
172 self.cache.needs_rebuild()
173 }
174
175 pub fn prepare_render(&mut self, bounds: &Rect) {
177 if self.cache.needs_rebuild() {
178 self.cache.rebuild(&self.chart, bounds);
179 }
180 }
181
182 pub fn append_data(&mut self, series_idx: usize, points: &[DataPoint]) {
186 let old_len = self.chart.series_len(series_idx);
187 self.chart.append_data(series_idx, points);
188 let new_len = self.chart.series_len(series_idx);
189
190 if new_len > old_len {
191 self.cache.mark_data_appended(series_idx, new_len);
192 }
193 }
194
195 pub fn push_point(&mut self, series_idx: usize, point: DataPoint, max_points: Option<usize>) {
200 let old_len = self.chart.series_len(series_idx);
201 self.chart.push_point(series_idx, point, max_points);
202 let new_len = self.chart.series_len(series_idx);
203
204 if new_len <= old_len && max_points.is_some() {
206 self.cache.mark_data_changed();
207 } else {
208 self.cache.mark_data_appended(series_idx, new_len);
209 }
210 }
211
212 pub fn set_data(&mut self, series_idx: usize, data: Vec<DataPoint>) {
214 self.chart.set_data(series_idx, data);
215 self.cache.mark_data_changed();
216 }
217
218 pub fn clear_data(&mut self, series_idx: usize) {
220 self.chart.clear_data(series_idx);
221 self.cache.mark_data_changed();
222 }
223
224 pub fn mark_view_changed(&mut self) {
229 self.cache.mark_view_changed();
230 }
231
232 pub fn mark_style_changed(&mut self) {
234 self.cache.mark_style_changed();
235 }
236
237 pub fn mark_axes_changed(&mut self) {
239 self.cache.mark_axes_changed();
240 }
241
242 pub fn mark_bounds_changed(&mut self) {
244 self.cache.mark_bounds_changed();
245 }
246
247 pub fn dirty_flags(&self) -> ChartDirtyFlags {
249 self.cache.dirty_flags()
250 }
251
252 pub fn clear_dirty(&mut self) {
254 self.cache.clear_dirty();
255 }
256
257 pub fn push_time_point(&mut self, series_idx: usize, time: f64, value: f64) {
265 self.push_point(series_idx, DataPoint::new(time, value), None);
266 }
267
268 pub fn push_time_point_windowed(
270 &mut self,
271 series_idx: usize,
272 time: f64,
273 value: f64,
274 max_points: usize,
275 ) {
276 self.push_point(series_idx, DataPoint::new(time, value), Some(max_points));
277 }
278
279 pub fn auto_scroll(&mut self, axis_id: AxisId, window_size: f64) {
291 self.auto_scroll_config.retain(|(id, _)| *id != axis_id);
293 self.auto_scroll_config.push((axis_id, window_size));
294 }
295
296 pub fn disable_auto_scroll(&mut self, axis_id: AxisId) {
298 self.auto_scroll_config.retain(|(id, _)| *id != axis_id);
299 }
300
301 pub fn apply_auto_scroll(&mut self) {
305 for (axis_id, window_size) in &self.auto_scroll_config {
306 let max_value = self.find_max_for_axis(*axis_id);
308
309 if let Some(max) = max_value {
310 if let Some(axis) = self.chart.get_axis_mut(*axis_id) {
312 axis.min = Some(max - window_size);
313 axis.max = Some(max);
314 }
315 self.cache.mark_view_changed();
316 }
317 }
318 }
319
320 fn find_max_for_axis(&self, axis_id: AxisId) -> Option<f64> {
322 let mut max = None;
323
324 for series in &self.chart.series {
325 let uses_axis = series.x_axis == axis_id || series.y_axis == axis_id;
327 if !uses_axis {
328 continue;
329 }
330
331 if let Some(last_point) = series.data.last() {
332 let value = if series.x_axis == axis_id {
333 last_point.x
334 } else {
335 last_point.y
336 };
337
338 max = Some(max.map_or(value, |m: f64| m.max(value)));
339 }
340 }
341
342 max
343 }
344
345 pub fn prepare_render_with_result(&mut self, bounds: &Rect) -> PrepareResult {
347 while self.series_dirty.len() < self.chart.series.len() {
349 self.series_dirty.push(SeriesDirtyState::default());
350 }
351
352 self.apply_auto_scroll();
354
355 let updated = self.cache.needs_rebuild();
356
357 let series_updates: Vec<SeriesUpdateInfo> = self
359 .chart
360 .series
361 .iter()
362 .enumerate()
363 .filter_map(|(idx, series)| {
364 let dirty_state = &self.series_dirty[idx];
365 if series.data.len() != dirty_state.last_rendered_count {
366 Some(SeriesUpdateInfo {
367 index: idx,
368 full_rebuild: dirty_state.needs_full_rebuild,
369 new_points: series
370 .data
371 .len()
372 .saturating_sub(dirty_state.last_rendered_count),
373 })
374 } else {
375 None
376 }
377 })
378 .collect();
379
380 if self.cache.needs_rebuild() {
382 self.cache.rebuild(&self.chart, bounds);
383 }
384
385 for (idx, series) in self.chart.series.iter().enumerate() {
387 if idx < self.series_dirty.len() {
388 self.series_dirty[idx].last_rendered_count = series.data.len();
389 self.series_dirty[idx].needs_full_rebuild = false;
390 self.series_dirty[idx].dirty_range = None;
391 }
392 }
393
394 PrepareResult {
395 updated,
396 series_updates,
397 }
398 }
399
400 pub fn get_display_data(&self, series_idx: usize, pixel_width: f32) -> Vec<DataPoint> {
415 let Some(series) = self.chart.series.get(series_idx) else {
416 return Vec::new();
417 };
418
419 let target_points = (pixel_width * 2.0) as usize; if series.data.len() <= target_points {
422 series.data.clone()
423 } else {
424 downsample_data(&series.data, target_points)
425 }
426 }
427
428 pub fn get_all_display_data(&self, pixel_width: f32) -> Vec<Vec<DataPoint>> {
430 (0..self.chart.series.len())
431 .map(|idx| self.get_display_data(idx, pixel_width))
432 .collect()
433 }
434
435 pub fn statistics(&self) -> StreamingStatistics {
437 let total_points: usize = self.chart.series.iter().map(|s| s.data.len()).sum();
438 let series_counts: Vec<usize> = self.chart.series.iter().map(|s| s.data.len()).collect();
439
440 StreamingStatistics {
441 total_points,
442 series_counts,
443 cache_dirty: self.cache.needs_rebuild(),
444 auto_scroll_active: !self.auto_scroll_config.is_empty(),
445 }
446 }
447}
448
449#[derive(Debug, Clone)]
451pub struct StreamingStatistics {
452 pub total_points: usize,
454 pub series_counts: Vec<usize>,
456 pub cache_dirty: bool,
458 pub auto_scroll_active: bool,
460}
461
462impl From<Chart> for StreamingChart {
463 fn from(chart: Chart) -> Self {
464 Self::new(chart)
465 }
466}
467
468impl std::ops::Deref for StreamingChart {
469 type Target = Chart;
470
471 fn deref(&self) -> &Self::Target {
472 &self.chart
473 }
474}
475
476#[derive(Debug, Clone, Copy)]
478pub struct SlidingWindowConfig {
479 pub max_points: usize,
481 pub auto_scale: bool,
483}
484
485impl Default for SlidingWindowConfig {
486 fn default() -> Self {
487 Self {
488 max_points: 1000,
489 auto_scale: true,
490 }
491 }
492}
493
494impl SlidingWindowConfig {
495 pub fn new(max_points: usize) -> Self {
497 Self {
498 max_points,
499 auto_scale: true,
500 }
501 }
502
503 pub fn with_auto_scale(mut self, auto_scale: bool) -> Self {
505 self.auto_scale = auto_scale;
506 self
507 }
508}
509
510pub struct GpuStreamingChart {
572 streaming: StreamingChart,
574 line_renderer: GpuChartLineRenderer,
576 scatter_renderer: GpuChartScatterRenderer,
578 bar_renderer: GpuChartBarRenderer,
580 area_renderer: GpuChartAreaRenderer,
582 gpu_enabled: bool,
584 force_gpu: bool,
586 #[cfg(feature = "chart-text")]
588 text_renderer: Option<ChartTextRenderer>,
589}
590
591impl std::fmt::Debug for GpuStreamingChart {
592 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
593 let mut s = f.debug_struct("GpuStreamingChart");
594 s.field("streaming", &self.streaming)
595 .field("line_renderer", &self.line_renderer)
596 .field("scatter_renderer", &self.scatter_renderer)
597 .field("bar_renderer", &self.bar_renderer)
598 .field("area_renderer", &self.area_renderer)
599 .field("gpu_enabled", &self.gpu_enabled)
600 .field("force_gpu", &self.force_gpu);
601 #[cfg(feature = "chart-text")]
602 s.field("has_text_renderer", &self.text_renderer.is_some());
603 s.finish()
604 }
605}
606
607impl GpuStreamingChart {
608 pub fn new(
612 chart: Chart,
613 context: Arc<GraphicsContext>,
614 target_format: wgpu::TextureFormat,
615 ) -> Self {
616 Self {
617 streaming: StreamingChart::new(chart),
618 line_renderer: GpuChartLineRenderer::new(context.clone(), target_format),
619 scatter_renderer: GpuChartScatterRenderer::new(context.clone(), target_format),
620 bar_renderer: GpuChartBarRenderer::new(context.clone(), target_format),
621 area_renderer: GpuChartAreaRenderer::new(context, target_format),
622 gpu_enabled: false,
623 force_gpu: false,
624 #[cfg(feature = "chart-text")]
625 text_renderer: None,
626 }
627 }
628
629 pub fn from_streaming(
633 streaming: StreamingChart,
634 context: Arc<GraphicsContext>,
635 target_format: wgpu::TextureFormat,
636 ) -> Self {
637 Self {
638 streaming,
639 line_renderer: GpuChartLineRenderer::new(context.clone(), target_format),
640 scatter_renderer: GpuChartScatterRenderer::new(context.clone(), target_format),
641 bar_renderer: GpuChartBarRenderer::new(context.clone(), target_format),
642 area_renderer: GpuChartAreaRenderer::new(context, target_format),
643 gpu_enabled: false,
644 force_gpu: false,
645 #[cfg(feature = "chart-text")]
646 text_renderer: None,
647 }
648 }
649
650 #[cfg(feature = "chart-text")]
664 pub fn with_text(mut self, context: Arc<GraphicsContext>, font_system: FontSystem) -> Self {
665 self.text_renderer = Some(ChartTextRenderer::new(context, font_system));
666 self
667 }
668
669 #[cfg(feature = "chart-text")]
671 pub fn text_renderer(&self) -> Option<&ChartTextRenderer> {
672 self.text_renderer.as_ref()
673 }
674
675 #[cfg(feature = "chart-text")]
677 pub fn text_renderer_mut(&mut self) -> Option<&mut ChartTextRenderer> {
678 self.text_renderer.as_mut()
679 }
680
681 pub fn force_gpu_rendering(mut self, force: bool) -> Self {
685 self.force_gpu = force;
686 self
687 }
688
689 pub fn chart(&self) -> &Chart {
691 self.streaming.chart()
692 }
693
694 pub fn chart_mut(&mut self) -> &mut Chart {
696 self.streaming.chart_mut()
697 }
698
699 pub fn streaming(&self) -> &StreamingChart {
701 &self.streaming
702 }
703
704 pub fn streaming_mut(&mut self) -> &mut StreamingChart {
706 &mut self.streaming
707 }
708
709 pub fn line_renderer(&self) -> &GpuChartLineRenderer {
711 &self.line_renderer
712 }
713
714 pub fn line_renderer_mut(&mut self) -> &mut GpuChartLineRenderer {
716 &mut self.line_renderer
717 }
718
719 pub fn scatter_renderer(&self) -> &GpuChartScatterRenderer {
721 &self.scatter_renderer
722 }
723
724 pub fn bar_renderer(&self) -> &GpuChartBarRenderer {
726 &self.bar_renderer
727 }
728
729 pub fn area_renderer(&self) -> &GpuChartAreaRenderer {
731 &self.area_renderer
732 }
733
734 pub fn is_gpu_enabled(&self) -> bool {
736 self.gpu_enabled
737 }
738
739 pub fn append_data(&mut self, series_idx: usize, points: &[DataPoint]) {
745 self.streaming.append_data(series_idx, points);
746 self.mark_all_renderers_data_changed();
747 }
748
749 pub fn push_point(&mut self, series_idx: usize, point: DataPoint, max_points: Option<usize>) {
751 self.streaming.push_point(series_idx, point, max_points);
752 self.mark_all_renderers_data_changed();
753 }
754
755 pub fn set_data(&mut self, series_idx: usize, data: Vec<DataPoint>) {
757 self.streaming.set_data(series_idx, data);
758 self.mark_all_renderers_data_changed();
759 }
760
761 pub fn clear_data(&mut self, series_idx: usize) {
763 self.streaming.clear_data(series_idx);
764 self.mark_all_renderers_data_changed();
765 }
766
767 pub fn push_time_point(&mut self, series_idx: usize, time: f64, value: f64) {
769 self.streaming.push_time_point(series_idx, time, value);
770 self.mark_all_renderers_data_changed();
771 }
772
773 pub fn push_time_point_windowed(
775 &mut self,
776 series_idx: usize,
777 time: f64,
778 value: f64,
779 max_points: usize,
780 ) {
781 self.streaming
782 .push_time_point_windowed(series_idx, time, value, max_points);
783 self.mark_all_renderers_data_changed();
784 }
785
786 fn mark_all_renderers_data_changed(&mut self) {
788 self.line_renderer.mark_data_changed();
789 self.scatter_renderer.mark_data_changed();
790 self.bar_renderer.mark_data_changed();
791 self.area_renderer.mark_data_changed();
792 }
793
794 pub fn auto_scroll(&mut self, axis_id: AxisId, window_size: f64) {
796 self.streaming.auto_scroll(axis_id, window_size);
797 }
798
799 pub fn disable_auto_scroll(&mut self, axis_id: AxisId) {
801 self.streaming.disable_auto_scroll(axis_id);
802 }
803
804 pub fn mark_view_changed(&mut self) {
806 self.streaming.mark_view_changed();
807 }
808
809 pub fn mark_style_changed(&mut self) {
811 self.streaming.mark_style_changed();
812 }
813
814 pub fn mark_bounds_changed(&mut self) {
816 self.streaming.mark_bounds_changed();
817 }
818
819 pub fn dirty_flags(&self) -> ChartDirtyFlags {
821 self.streaming.dirty_flags()
822 }
823
824 fn should_use_gpu(&self) -> bool {
830 if self.force_gpu {
831 return true;
832 }
833
834 self.streaming
836 .chart()
837 .series
838 .iter()
839 .any(|s| s.data.len() > GPU_RENDER_THRESHOLD)
840 }
841
842 pub fn prepare_render(&mut self, bounds: &Rect) {
847 self.streaming.prepare_render(bounds);
849
850 self.gpu_enabled = self.should_use_gpu();
852
853 if self.gpu_enabled {
855 self.prepare_gpu_renderer();
856 }
857 }
858
859 pub fn prepare_render_with_result(&mut self, bounds: &Rect) -> PrepareResult {
861 let result = self.streaming.prepare_render_with_result(bounds);
862
863 self.gpu_enabled = self.should_use_gpu();
865
866 if self.gpu_enabled {
868 self.prepare_gpu_renderer();
869 }
870
871 result
872 }
873
874 fn prepare_gpu_renderer(&mut self) {
876 let chart = self.streaming.chart();
877 match chart.chart_type {
878 ChartType::Line => {
879 self.line_renderer.prepare(chart);
880 }
881 ChartType::Scatter => {
882 self.scatter_renderer.prepare(chart);
883 }
884 ChartType::Bar => {
885 self.bar_renderer.prepare(chart);
886 }
887 ChartType::Area => {
888 self.area_renderer.prepare(chart);
889 }
890 }
891 }
892
893 pub fn render(
905 &self,
906 pass: &mut wgpu::RenderPass,
907 viewport: Viewport,
908 geometry_renderer: &mut crate::GeometryRenderer,
909 bounds: &Rect,
910 ) {
911 use crate::chart::ChartRenderer;
912
913 let plot_area = bounds.inset(self.streaming.chart().padding);
915 let chart = self.streaming.chart();
916
917 {
918 let mut chart_renderer = ChartRenderer::new(geometry_renderer);
919 if self.gpu_enabled {
920 chart_renderer.draw_with_gpu_lines(chart, *bounds);
922 } else {
923 chart_renderer.draw(chart, *bounds);
925 }
926 chart_renderer.render(pass, viewport);
927 }
928
929 if self.gpu_enabled {
931 self.render_gpu_series(pass, viewport, &plot_area, chart);
932 }
933 }
934
935 #[cfg(feature = "chart-text")]
947 pub fn render_with_text(
948 &mut self,
949 pass: &mut wgpu::RenderPass,
950 viewport: Viewport,
951 geometry_renderer: &mut crate::GeometryRenderer,
952 bounds: &Rect,
953 ) {
954 use crate::chart::ChartRenderer;
955
956 let chart = self.streaming.chart();
957 let plot_area = bounds.inset(chart.padding);
958
959 let text_margins = self
961 .text_renderer
962 .as_ref()
963 .map(|tr| tr.calculate_margins(chart))
964 .unwrap_or_default();
965
966 let adjusted_plot_area = Rect::new(
968 plot_area.x + text_margins.left,
969 plot_area.y + text_margins.top,
970 (plot_area.width - text_margins.left - text_margins.right).max(1.0),
971 (plot_area.height - text_margins.top - text_margins.bottom).max(1.0),
972 );
973
974 {
976 let mut chart_renderer = ChartRenderer::new(geometry_renderer);
977 if self.gpu_enabled {
978 chart_renderer.draw_with_gpu_lines(chart, *bounds);
979 } else {
980 chart_renderer.draw(chart, *bounds);
981 }
982 chart_renderer.render(pass, viewport);
983 }
984
985 if self.gpu_enabled {
987 self.render_gpu_series(pass, viewport, &adjusted_plot_area, chart);
988 }
989
990 if let Some(text_renderer) = &mut self.text_renderer {
992 text_renderer.set_viewport(viewport);
993
994 text_renderer.draw_title(chart, bounds);
996
997 text_renderer.draw_tick_labels(chart, &adjusted_plot_area);
999
1000 text_renderer.draw_axis_labels(chart, &adjusted_plot_area);
1002
1003 text_renderer.draw_legend(chart, &adjusted_plot_area, geometry_renderer);
1005
1006 geometry_renderer.render(pass, viewport);
1008
1009 text_renderer.render(pass);
1011 }
1012 }
1013
1014 fn render_gpu_series(
1016 &self,
1017 pass: &mut wgpu::RenderPass,
1018 viewport: Viewport,
1019 plot_area: &Rect,
1020 chart: &Chart,
1021 ) {
1022 match chart.chart_type {
1023 ChartType::Line => {
1024 if self.line_renderer.segment_count() > 0 {
1025 self.line_renderer.render(pass, viewport, plot_area, chart);
1026 }
1027 }
1028 ChartType::Scatter => {
1029 if self.scatter_renderer.point_count() > 0 {
1030 self.scatter_renderer
1031 .render(pass, viewport, plot_area, chart);
1032 }
1033 }
1034 ChartType::Bar => {
1035 if self.bar_renderer.quad_count() > 0 {
1036 self.bar_renderer.render(pass, viewport, plot_area, chart);
1037 }
1038 }
1039 ChartType::Area => {
1040 if self.area_renderer.quad_count() > 0 || self.area_renderer.segment_count() > 0 {
1041 self.area_renderer.render(pass, viewport, plot_area, chart);
1042 }
1043 }
1044 }
1045 }
1046
1047 pub fn statistics(&self) -> GpuStreamingStatistics {
1049 let base = self.streaming.statistics();
1050 let chart = self.streaming.chart();
1051
1052 let gpu_element_count = match chart.chart_type {
1054 ChartType::Line => self.line_renderer.segment_count(),
1055 ChartType::Scatter => self.scatter_renderer.point_count(),
1056 ChartType::Bar => self.bar_renderer.quad_count(),
1057 ChartType::Area => self.area_renderer.quad_count() + self.area_renderer.segment_count(),
1058 };
1059
1060 GpuStreamingStatistics {
1061 total_points: base.total_points,
1062 series_counts: base.series_counts,
1063 cache_dirty: base.cache_dirty,
1064 auto_scroll_active: base.auto_scroll_active,
1065 gpu_enabled: self.gpu_enabled,
1066 gpu_segment_count: gpu_element_count,
1067 }
1068 }
1069
1070 pub fn get_display_data(&self, series_idx: usize, pixel_width: f32) -> Vec<DataPoint> {
1072 self.streaming.get_display_data(series_idx, pixel_width)
1073 }
1074}
1075
1076impl std::ops::Deref for GpuStreamingChart {
1077 type Target = Chart;
1078
1079 fn deref(&self) -> &Self::Target {
1080 self.streaming.chart()
1081 }
1082}
1083
1084impl From<(Chart, Arc<GraphicsContext>, wgpu::TextureFormat)> for GpuStreamingChart {
1085 fn from(
1086 (chart, context, target_format): (Chart, Arc<GraphicsContext>, wgpu::TextureFormat),
1087 ) -> Self {
1088 Self::new(chart, context, target_format)
1089 }
1090}
1091
1092#[derive(Debug, Clone)]
1094pub struct GpuStreamingStatistics {
1095 pub total_points: usize,
1097 pub series_counts: Vec<usize>,
1099 pub cache_dirty: bool,
1101 pub auto_scroll_active: bool,
1103 pub gpu_enabled: bool,
1105 pub gpu_segment_count: usize,
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111 use super::*;
1112 use crate::chart::ChartBuilder;
1113
1114 #[test]
1115 fn test_streaming_chart_append() {
1116 let chart = ChartBuilder::line()
1117 .add_series("Test", &[(0.0_f64, 1.0_f64)])
1118 .build();
1119
1120 let mut streaming = StreamingChart::new(chart);
1121
1122 assert_eq!(streaming.chart.series_len(0), 1);
1123
1124 streaming.append_data(0, &[DataPoint::new(1.0, 2.0), DataPoint::new(2.0, 3.0)]);
1125
1126 assert_eq!(streaming.chart.series_len(0), 3);
1127 assert!(
1128 streaming
1129 .cache
1130 .dirty_flags()
1131 .contains(ChartDirtyFlags::DATA_APPENDED)
1132 );
1133 }
1134
1135 #[test]
1136 fn test_streaming_chart_sliding_window() {
1137 let chart = ChartBuilder::line()
1138 .add_series("Test", &[] as &[(f64, f64)])
1139 .build();
1140
1141 let mut streaming = StreamingChart::new(chart);
1142
1143 for i in 0..5 {
1145 streaming.push_point(0, DataPoint::new(i as f64, i as f64), Some(3));
1146 }
1147
1148 assert_eq!(streaming.chart.series_len(0), 3);
1150 assert!(
1152 streaming
1153 .cache
1154 .dirty_flags()
1155 .contains(ChartDirtyFlags::DATA_CHANGED)
1156 );
1157 }
1158}