1use crate::axes::traits::Axis;
84use crate::chart::traits::AxisChart;
85use crate::chart::traits::{Chart, ChartBuilder, ChartConfig, Margins};
86use crate::data::{DataBounds, DataPoint, DataSeries};
87use crate::error::{ChartError, ChartResult};
88use crate::math::NumericConversion;
89use embedded_graphics::{
90 draw_target::DrawTarget,
91 prelude::*,
92 primitives::{Circle, Line, PrimitiveStyle, Rectangle},
93};
94
95#[derive(Debug)]
150pub struct LineChart<C: PixelColor> {
151 style: LineChartStyle<C>,
152 config: ChartConfig<C>,
153 grid: Option<crate::grid::GridSystem<C>>,
154 x_axis: Option<crate::axes::LinearAxis<f32, C>>,
155 y_axis: Option<crate::axes::LinearAxis<f32, C>>,
156}
157
158#[derive(Debug, Clone)]
180pub struct LineChartStyle<C: PixelColor> {
181 pub line_color: C,
183 pub line_width: u32,
187 pub fill_area: bool,
191 pub fill_color: Option<C>,
195 pub markers: Option<MarkerStyle<C>>,
199 pub smooth: bool,
205 pub smooth_subdivisions: u32,
207}
208
209#[derive(Debug, Clone, Copy)]
229pub struct MarkerStyle<C: PixelColor> {
230 pub shape: MarkerShape,
232 pub size: u32,
237 pub color: C,
239 pub visible: bool,
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258pub enum MarkerShape {
259 Circle,
261 Square,
263 Diamond,
265 Triangle,
267}
268
269impl<C: PixelColor> LineChart<C>
270where
271 C: From<embedded_graphics::pixelcolor::Rgb565>,
272{
273 pub fn new() -> Self {
292 Self {
293 style: LineChartStyle::default(),
294 config: ChartConfig::default(),
295 grid: None,
296 x_axis: None,
297 y_axis: None,
298 }
299 }
300
301 pub fn builder() -> LineChartBuilder<C> {
320 LineChartBuilder::new()
321 }
322
323 pub fn set_style(&mut self, style: LineChartStyle<C>) {
351 self.style = style;
352 }
353
354 pub fn style(&self) -> &LineChartStyle<C> {
363 &self.style
364 }
365
366 pub fn set_config(&mut self, config: ChartConfig<C>) {
375 self.config = config;
376 }
377
378 pub fn config(&self) -> &ChartConfig<C> {
384 &self.config
385 }
386
387 pub fn set_grid(&mut self, grid: Option<crate::grid::GridSystem<C>>) {
409 self.grid = grid;
410 }
411
412 pub fn grid(&self) -> Option<&crate::grid::GridSystem<C>> {
418 self.grid.as_ref()
419 }
420
421 fn transform_point<P>(
423 &self,
424 point: &P,
425 data_bounds: &DataBounds<P::X, P::Y>,
426 viewport: Rectangle,
427 ) -> Point
428 where
429 P: DataPoint,
430 P::X: NumericConversion<P::X> + Into<f32> + Copy,
431 P::Y: NumericConversion<P::Y> + Into<f32> + Copy,
432 {
433 let data_x = point.x().into().to_number();
435 let data_y = point.y().into().to_number();
436
437 let (min_x, max_x) = if let Some(ref x_axis) = self.x_axis {
439 let axis_min: f32 = x_axis.min();
440 let axis_max: f32 = x_axis.max();
441 (axis_min.to_number(), axis_max.to_number())
442 } else {
443 (
444 data_bounds.min_x.into().to_number(),
445 data_bounds.max_x.into().to_number(),
446 )
447 };
448
449 let (min_y, max_y) = if let Some(ref y_axis) = self.y_axis {
450 let axis_min: f32 = y_axis.min();
451 let axis_max: f32 = y_axis.max();
452 (axis_min.to_number(), axis_max.to_number())
453 } else {
454 (
455 data_bounds.min_y.into().to_number(),
456 data_bounds.max_y.into().to_number(),
457 )
458 };
459
460 let draw_area = self.config.margins.apply_to(viewport);
462
463 let norm_x = if f32::from_number(max_x) > f32::from_number(min_x) {
465 let range_x = f32::from_number(max_x - min_x);
466 let offset_x = f32::from_number(data_x - min_x);
467 (offset_x / range_x).to_number()
468 } else {
469 0.5f32.to_number()
470 };
471
472 let norm_y = if f32::from_number(max_y) > f32::from_number(min_y) {
473 let range_y = f32::from_number(max_y - min_y);
474 let offset_y = f32::from_number(data_y - min_y);
475 (offset_y / range_y).to_number()
476 } else {
477 0.5f32.to_number()
478 };
479
480 let norm_x_f32 = f32::from_number(norm_x);
482 let norm_y_f32 = f32::from_number(norm_y);
483
484 let screen_x =
485 draw_area.top_left.x + (norm_x_f32 * (draw_area.size.width as f32 - 1.0)) as i32;
486 let screen_y = draw_area.top_left.y + draw_area.size.height as i32
487 - 1
488 - (norm_y_f32 * (draw_area.size.height as f32 - 1.0)) as i32;
489
490 Point::new(screen_x, screen_y)
491 }
492
493 fn draw_markers<D>(
495 &self,
496 data: &crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>,
497 data_bounds: &DataBounds<f32, f32>,
498 viewport: Rectangle,
499 target: &mut D,
500 ) -> ChartResult<()>
501 where
502 D: DrawTarget<Color = C>,
503 {
504 if let Some(marker_style) = &self.style.markers {
505 if marker_style.visible {
506 for point in data.iter() {
507 let screen_point = self.transform_point(&point, data_bounds, viewport);
508 self.draw_marker(screen_point, marker_style, target)?;
509 }
510 }
511 }
512 Ok(())
513 }
514
515 fn draw_marker<D>(
517 &self,
518 center: Point,
519 marker_style: &MarkerStyle<C>,
520 target: &mut D,
521 ) -> ChartResult<()>
522 where
523 D: DrawTarget<Color = C>,
524 {
525 let style = PrimitiveStyle::with_fill(marker_style.color);
526 let radius = marker_style.size / 2;
527
528 match marker_style.shape {
529 MarkerShape::Circle => {
530 Circle::new(
531 Point::new(center.x - radius as i32, center.y - radius as i32),
532 marker_style.size,
533 )
534 .into_styled(style)
535 .draw(target)
536 .map_err(|_| ChartError::RenderingError)?;
537 }
538 MarkerShape::Square => {
539 Rectangle::new(
540 Point::new(center.x - radius as i32, center.y - radius as i32),
541 Size::new(marker_style.size, marker_style.size),
542 )
543 .into_styled(style)
544 .draw(target)
545 .map_err(|_| ChartError::RenderingError)?;
546 }
547 MarkerShape::Diamond => {
548 use crate::render::PrimitiveRenderer;
549 use crate::style::FillStyle;
550
551 let fill_style = FillStyle::solid(marker_style.color);
552 PrimitiveRenderer::draw_diamond(
553 center,
554 marker_style.size,
555 None,
556 Some(&fill_style),
557 target,
558 )
559 .map_err(|_| ChartError::RenderingError)?;
560 }
561 MarkerShape::Triangle => {
562 use crate::render::PrimitiveRenderer;
563 use crate::style::FillStyle;
564
565 let fill_style = FillStyle::solid(marker_style.color);
566 let half_size = marker_style.size as i32 / 2;
567 let p1 = Point::new(center.x, center.y - half_size);
568 let p2 = Point::new(center.x - half_size, center.y + half_size);
569 let p3 = Point::new(center.x + half_size, center.y + half_size);
570
571 PrimitiveRenderer::draw_triangle(p1, p2, p3, None, Some(&fill_style), target)
572 .map_err(|_| ChartError::RenderingError)?;
573 }
574 }
575
576 Ok(())
577 }
578
579 fn draw_area_fill<D>(
581 &self,
582 screen_points: &heapless::Vec<Point, 512>,
583 fill_color: C,
584 viewport: Rectangle,
585 _data_bounds: &DataBounds<f32, f32>,
586 target: &mut D,
587 ) -> ChartResult<()>
588 where
589 D: DrawTarget<Color = C>,
590 {
591 if screen_points.len() < 2 {
592 return Ok(());
593 }
594
595 let chart_area = self.config.margins.apply_to(viewport);
597 let baseline_y = chart_area.top_left.y + chart_area.size.height as i32 - 1;
598
599 use embedded_graphics::primitives::{Line, PrimitiveStyle};
600 let line_style = PrimitiveStyle::with_stroke(fill_color, 1);
601
602 let min_x = screen_points
604 .iter()
605 .map(|p| p.x)
606 .min()
607 .unwrap_or(chart_area.top_left.x);
608 let max_x = screen_points
609 .iter()
610 .map(|p| p.x)
611 .max()
612 .unwrap_or(chart_area.top_left.x);
613
614 for x in min_x..=max_x {
616 if x < chart_area.top_left.x
617 || x >= chart_area.top_left.x + chart_area.size.width as i32
618 {
619 continue;
620 }
621
622 let mut curve_y = baseline_y;
624
625 for window in screen_points.windows(2) {
627 if let [p1, p2] = window {
628 if (p1.x <= x && x <= p2.x) || (p2.x <= x && x <= p1.x) {
629 if p1.x == p2.x {
630 curve_y = p1.y.min(p2.y);
631 } else {
632 let t = (x - p1.x) as f32 / (p2.x - p1.x) as f32;
633 curve_y = (p1.y as f32 + t * (p2.y - p1.y) as f32) as i32;
634 }
635 break;
636 }
637 }
638 }
639
640 curve_y = curve_y.clamp(
642 chart_area.top_left.y,
643 chart_area.top_left.y + chart_area.size.height as i32 - 1,
644 );
645
646 if curve_y <= baseline_y {
648 let top_point = Point::new(x, curve_y);
649 let bottom_point = Point::new(x, baseline_y);
650
651 Line::new(top_point, bottom_point)
652 .into_styled(line_style)
653 .draw(target)
654 .map_err(|_| ChartError::RenderingError)?;
655 }
656 }
657
658 Ok(())
659 }
660}
661
662impl<C: PixelColor> Default for LineChart<C>
663where
664 C: From<embedded_graphics::pixelcolor::Rgb565>,
665{
666 fn default() -> Self {
667 Self::new()
668 }
669}
670
671impl<C: PixelColor + 'static> Chart<C> for LineChart<C>
672where
673 C: From<embedded_graphics::pixelcolor::Rgb565>,
674{
675 type Data = crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>;
676 type Config = ChartConfig<C>;
677
678 fn draw<D>(
679 &self,
680 data: &Self::Data,
681 config: &Self::Config,
682 viewport: Rectangle,
683 target: &mut D,
684 ) -> ChartResult<()>
685 where
686 D: DrawTarget<Color = C>,
687 Self::Data: DataSeries,
688 <Self::Data as DataSeries>::Item: DataPoint,
689 <<Self::Data as DataSeries>::Item as DataPoint>::X: Into<f32> + Copy + PartialOrd,
690 <<Self::Data as DataSeries>::Item as DataPoint>::Y: Into<f32> + Copy + PartialOrd,
691 {
692 if data.is_empty() {
693 return Err(ChartError::InsufficientData);
694 }
695
696 let data_bounds = data.bounds()?;
698
699 if let Some(bg_color) = config.background_color {
701 Rectangle::new(viewport.top_left, viewport.size)
702 .into_styled(PrimitiveStyle::with_fill(bg_color))
703 .draw(target)
704 .map_err(|_| ChartError::RenderingError)?;
705 }
706
707 {
709 let chart_area = config.margins.apply_to(viewport);
710
711 if let Some(ref x_axis) = self.x_axis {
713 x_axis.draw_grid_lines(chart_area, chart_area, target)?;
714 }
715
716 if let Some(ref y_axis) = self.y_axis {
718 y_axis.draw_grid_lines(chart_area, chart_area, target)?;
719 }
720 }
721
722 if let Some(ref grid) = self.grid {
724 let chart_area = config.margins.apply_to(viewport);
725 grid.draw(chart_area, target)?;
726 }
727
728 let data_to_render = if self.style.smooth && data.len() > 2 {
730 use crate::math::interpolation::{
732 CurveInterpolator, InterpolationConfig, InterpolationType,
733 };
734
735 let mut input_points = heapless::Vec::<crate::data::Point2D, 256>::new();
736 for point in data.iter() {
737 input_points
738 .push(point)
739 .map_err(|_| ChartError::MemoryFull)?;
740 }
741
742 let interpolation_config = InterpolationConfig {
743 interpolation_type: InterpolationType::CatmullRom,
744 subdivisions: self.style.smooth_subdivisions,
745 tension: 0.5,
746 closed: false,
747 };
748
749 let interpolated =
750 CurveInterpolator::interpolate(&input_points, &interpolation_config)?;
751
752 let mut smooth_data = crate::data::series::StaticDataSeries::new();
754 for point in interpolated.iter() {
755 smooth_data
756 .push(*point)
757 .map_err(|_| ChartError::MemoryFull)?;
758 }
759 smooth_data
760 } else {
761 data.clone()
763 };
764
765 let mut screen_points = heapless::Vec::<Point, 512>::new();
767 for point in data_to_render.iter() {
768 let screen_point = self.transform_point(&point, &data_bounds, viewport);
769 screen_points
770 .push(screen_point)
771 .map_err(|_| ChartError::MemoryFull)?;
772 }
773
774 if self.style.fill_area {
776 if let Some(fill_color) = self.style.fill_color {
777 self.draw_area_fill(&screen_points, fill_color, viewport, &data_bounds, target)?;
778 }
779 }
780
781 let line_style = PrimitiveStyle::with_stroke(self.style.line_color, self.style.line_width);
783 for window in screen_points.windows(2) {
784 if let [p1, p2] = window {
785 Line::new(*p1, *p2)
786 .into_styled(line_style)
787 .draw(target)
788 .map_err(|_| ChartError::RenderingError)?;
789 }
790 }
791
792 self.draw_markers(data, &data_bounds, viewport, target)?;
794
795 {
797 let chart_area = config.margins.apply_to(viewport);
798
799 if let Some(ref x_axis) = self.x_axis {
801 x_axis.draw_axis_only(chart_area, target)?;
802 }
803
804 if let Some(ref y_axis) = self.y_axis {
806 y_axis.draw_axis_only(chart_area, target)?;
807 }
808 }
809
810 Ok(())
811 }
812}
813
814impl<C: PixelColor> Default for LineChartStyle<C>
815where
816 C: From<embedded_graphics::pixelcolor::Rgb565>,
817{
818 fn default() -> Self {
819 Self {
820 line_color: embedded_graphics::pixelcolor::Rgb565::BLUE.into(),
821 line_width: 1,
822 fill_area: false,
823 fill_color: None,
824 markers: None,
825 smooth: false,
826 smooth_subdivisions: 8,
827 }
828 }
829}
830
831impl<C: PixelColor> Default for MarkerStyle<C>
832where
833 C: From<embedded_graphics::pixelcolor::Rgb565>,
834{
835 fn default() -> Self {
836 Self {
837 shape: MarkerShape::Circle,
838 size: 4,
839 color: embedded_graphics::pixelcolor::Rgb565::RED.into(),
840 visible: true,
841 }
842 }
843}
844
845#[derive(Debug)]
847pub struct LineChartBuilder<C: PixelColor> {
848 style: LineChartStyle<C>,
849 config: ChartConfig<C>,
850 grid: Option<crate::grid::GridSystem<C>>,
851 x_axis: Option<crate::axes::LinearAxis<f32, C>>,
852 y_axis: Option<crate::axes::LinearAxis<f32, C>>,
853}
854
855impl<C: PixelColor> LineChartBuilder<C>
856where
857 C: From<embedded_graphics::pixelcolor::Rgb565>,
858{
859 pub fn new() -> Self {
861 Self {
862 style: LineChartStyle::default(),
863 config: ChartConfig::default(),
864 grid: None,
865 x_axis: None,
866 y_axis: None,
867 }
868 }
869
870 pub fn line_color(mut self, color: C) -> Self {
872 self.style.line_color = color;
873 self
874 }
875
876 pub fn line_width(mut self, width: u32) -> Self {
878 self.style.line_width = width.clamp(1, 10);
879 self
880 }
881
882 pub fn fill_area(mut self, color: C) -> Self {
884 self.style.fill_area = true;
885 self.style.fill_color = Some(color);
886 self
887 }
888
889 pub fn with_markers(mut self, marker_style: MarkerStyle<C>) -> Self {
891 self.style.markers = Some(marker_style);
892 self
893 }
894
895 pub fn with_title(mut self, title: &str) -> Self {
897 if let Ok(title_string) = heapless::String::try_from(title) {
898 self.config.title = Some(title_string);
899 }
900 self
901 }
902
903 pub fn background_color(mut self, color: C) -> Self {
905 self.config.background_color = Some(color);
906 self
907 }
908
909 pub fn margins(mut self, margins: Margins) -> Self {
911 self.config.margins = margins;
912 self
913 }
914
915 pub fn smooth(mut self, smooth: bool) -> Self {
917 self.style.smooth = smooth;
918 self
919 }
920
921 pub fn smooth_subdivisions(mut self, subdivisions: u32) -> Self {
923 self.style.smooth_subdivisions = subdivisions.clamp(2, 16);
924 self
925 }
926
927 pub fn with_grid(mut self, grid: crate::grid::GridSystem<C>) -> Self {
929 self.grid = Some(grid);
930 self
931 }
932
933 pub fn with_x_axis(mut self, axis: crate::axes::LinearAxis<f32, C>) -> Self {
935 self.x_axis = Some(axis);
936 self
937 }
938
939 pub fn with_y_axis(mut self, axis: crate::axes::LinearAxis<f32, C>) -> Self {
941 self.y_axis = Some(axis);
942 self
943 }
944}
945
946impl<C: PixelColor + 'static> ChartBuilder<C> for LineChartBuilder<C>
947where
948 C: From<embedded_graphics::pixelcolor::Rgb565>,
949{
950 type Chart = LineChart<C>;
951 type Error = ChartError;
952
953 fn build(self) -> Result<Self::Chart, Self::Error> {
954 Ok(LineChart {
955 style: self.style,
956 config: self.config,
957 grid: self.grid,
958 x_axis: self.x_axis,
959 y_axis: self.y_axis,
960 })
961 }
962}
963
964impl<C: PixelColor> Default for LineChartBuilder<C>
965where
966 C: From<embedded_graphics::pixelcolor::Rgb565>,
967{
968 fn default() -> Self {
969 Self::new()
970 }
971}
972
973#[cfg(test)]
974mod tests {
975 use super::*;
976 use embedded_graphics::pixelcolor::Rgb565;
977
978 #[test]
979 fn test_line_chart_creation() {
980 let chart: LineChart<Rgb565> = LineChart::new();
981 assert_eq!(chart.style().line_width, 1);
982 }
983
984 #[test]
985 fn test_line_chart_builder() {
986 let chart = LineChart::builder()
987 .line_color(Rgb565::RED)
988 .line_width(3)
989 .build()
990 .unwrap();
991
992 assert_eq!(chart.style().line_color, Rgb565::RED);
993 assert_eq!(chart.style().line_width, 3);
994 }
995
996 #[test]
997 fn test_marker_style() {
998 let marker = MarkerStyle {
999 shape: MarkerShape::Diamond,
1000 size: 8,
1001 color: Rgb565::GREEN,
1002 visible: true,
1003 };
1004
1005 assert_eq!(marker.shape, MarkerShape::Diamond);
1006 assert_eq!(marker.size, 8);
1007 }
1008}
1009
1010impl<C: PixelColor + 'static> AxisChart<C> for LineChart<C>
1011where
1012 C: From<embedded_graphics::pixelcolor::Rgb565>,
1013{
1014 type XAxis = crate::axes::LinearAxis<f32, C>;
1015 type YAxis = crate::axes::LinearAxis<f32, C>;
1016
1017 fn set_x_axis(&mut self, axis: crate::axes::LinearAxis<f32, C>) {
1018 self.x_axis = Some(axis);
1019 }
1020
1021 fn set_y_axis(&mut self, axis: crate::axes::LinearAxis<f32, C>) {
1022 self.y_axis = Some(axis);
1023 }
1024
1025 fn x_axis(&self) -> ChartResult<&crate::axes::LinearAxis<f32, C>> {
1026 self.x_axis.as_ref().ok_or(ChartError::InvalidConfiguration)
1027 }
1028
1029 fn y_axis(&self) -> ChartResult<&crate::axes::LinearAxis<f32, C>> {
1030 self.y_axis.as_ref().ok_or(ChartError::InvalidConfiguration)
1031 }
1032}
1033
1034#[cfg(feature = "animations")]
1036#[derive(Debug)]
1037pub struct AnimatedLineChart<C: PixelColor> {
1038 base_chart: LineChart<C>,
1040 current_data: Option<crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>>,
1042}
1043
1044#[cfg(feature = "animations")]
1045impl<C: PixelColor + 'static> AnimatedLineChart<C>
1046where
1047 C: From<embedded_graphics::pixelcolor::Rgb565>,
1048{
1049 pub fn new() -> Self {
1051 Self {
1052 base_chart: LineChart::new(),
1053 current_data: None,
1054 }
1055 }
1056
1057 pub fn builder() -> AnimatedLineChartBuilder<C> {
1059 AnimatedLineChartBuilder::new()
1060 }
1061
1062 pub fn set_style(&mut self, style: LineChartStyle<C>) {
1064 self.base_chart.set_style(style);
1065 }
1066
1067 pub fn style(&self) -> &LineChartStyle<C> {
1069 self.base_chart.style()
1070 }
1071
1072 pub fn set_config(&mut self, config: ChartConfig<C>) {
1074 self.base_chart.set_config(config);
1075 }
1076
1077 pub fn config(&self) -> &ChartConfig<C> {
1079 self.base_chart.config()
1080 }
1081
1082 pub fn set_grid(&mut self, grid: Option<crate::grid::GridSystem<C>>) {
1084 self.base_chart.set_grid(grid);
1085 }
1086
1087 pub fn grid(&self) -> Option<&crate::grid::GridSystem<C>> {
1089 self.base_chart.grid()
1090 }
1091
1092 pub fn set_animated_data(
1094 &mut self,
1095 data: Option<crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>>,
1096 ) {
1097 self.current_data = data;
1098 }
1099
1100 pub fn animated_data(
1102 &self,
1103 ) -> Option<&crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>> {
1104 self.current_data.as_ref()
1105 }
1106
1107 pub fn base_chart(&self) -> &LineChart<C> {
1109 &self.base_chart
1110 }
1111
1112 pub fn base_chart_mut(&mut self) -> &mut LineChart<C> {
1114 &mut self.base_chart
1115 }
1116
1117 pub fn interpolate_with_animator(
1119 animator: &crate::animation::ChartAnimator<
1120 crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>,
1121 >,
1122 progress: crate::animation::Progress,
1123 ) -> Option<crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>> {
1124 animator.value_at(progress)
1125 }
1126}
1127
1128#[cfg(feature = "animations")]
1129impl<C: PixelColor + 'static> Default for AnimatedLineChart<C>
1130where
1131 C: From<embedded_graphics::pixelcolor::Rgb565>,
1132{
1133 fn default() -> Self {
1134 Self::new()
1135 }
1136}
1137
1138#[cfg(feature = "animations")]
1139impl<C: PixelColor + 'static> Chart<C> for AnimatedLineChart<C>
1140where
1141 C: From<embedded_graphics::pixelcolor::Rgb565>,
1142{
1143 type Data = crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>;
1144 type Config = ChartConfig<C>;
1145
1146 fn draw<D>(
1147 &self,
1148 data: &Self::Data,
1149 config: &Self::Config,
1150 viewport: Rectangle,
1151 target: &mut D,
1152 ) -> ChartResult<()>
1153 where
1154 D: DrawTarget<Color = C>,
1155 Self::Data: crate::data::DataSeries,
1156 <Self::Data as crate::data::DataSeries>::Item: crate::data::DataPoint,
1157 <<Self::Data as crate::data::DataSeries>::Item as crate::data::DataPoint>::X:
1158 Into<f32> + Copy + PartialOrd,
1159 <<Self::Data as crate::data::DataSeries>::Item as crate::data::DataPoint>::Y:
1160 Into<f32> + Copy + PartialOrd,
1161 {
1162 if let Some(ref animated_data) = self.current_data {
1164 self.base_chart
1165 .draw(animated_data, config, viewport, target)
1166 } else {
1167 self.base_chart.draw(data, config, viewport, target)
1168 }
1169 }
1170}
1171
1172#[cfg(feature = "animations")]
1173impl<C: PixelColor + 'static> crate::chart::traits::AnimatedChart<C> for AnimatedLineChart<C>
1174where
1175 C: From<embedded_graphics::pixelcolor::Rgb565>,
1176{
1177 type AnimatedData = crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>;
1178
1179 fn draw_animated<D>(
1180 &self,
1181 data: &Self::Data,
1182 config: &Self::Config,
1183 viewport: Rectangle,
1184 target: &mut D,
1185 _progress: crate::animation::Progress,
1186 ) -> ChartResult<()>
1187 where
1188 D: DrawTarget<Color = C>,
1189 {
1190 self.base_chart.draw(data, config, viewport, target)
1192 }
1193
1194 fn create_transition_animator(
1195 &self,
1196 from_data: Self::AnimatedData,
1197 to_data: Self::AnimatedData,
1198 easing: crate::animation::EasingFunction,
1199 ) -> crate::animation::ChartAnimator<Self::AnimatedData> {
1200 crate::animation::ChartAnimator::new(from_data, to_data, easing)
1201 }
1202
1203 fn extract_animated_data(&self, data: &Self::Data) -> ChartResult<Self::AnimatedData> {
1204 Ok(data.clone())
1206 }
1207}
1208
1209#[cfg(feature = "animations")]
1211#[derive(Debug)]
1212pub struct AnimatedLineChartBuilder<C: PixelColor> {
1213 base_builder: LineChartBuilder<C>,
1214 frame_rate: u32,
1215}
1216
1217#[cfg(feature = "animations")]
1218impl<C: PixelColor + 'static> AnimatedLineChartBuilder<C>
1219where
1220 C: From<embedded_graphics::pixelcolor::Rgb565>,
1221{
1222 pub fn new() -> Self {
1224 Self {
1225 base_builder: LineChartBuilder::new(),
1226 frame_rate: 60,
1227 }
1228 }
1229
1230 pub fn frame_rate(mut self, fps: u32) -> Self {
1232 self.frame_rate = fps.clamp(1, 120);
1233 self
1234 }
1235
1236 pub fn line_color(mut self, color: C) -> Self {
1238 self.base_builder = self.base_builder.line_color(color);
1239 self
1240 }
1241
1242 pub fn line_width(mut self, width: u32) -> Self {
1244 self.base_builder = self.base_builder.line_width(width);
1245 self
1246 }
1247
1248 pub fn fill_area(mut self, color: C) -> Self {
1250 self.base_builder = self.base_builder.fill_area(color);
1251 self
1252 }
1253
1254 pub fn with_markers(mut self, marker_style: MarkerStyle<C>) -> Self {
1256 self.base_builder = self.base_builder.with_markers(marker_style);
1257 self
1258 }
1259
1260 pub fn with_title(mut self, title: &str) -> Self {
1262 self.base_builder = self.base_builder.with_title(title);
1263 self
1264 }
1265
1266 pub fn background_color(mut self, color: C) -> Self {
1268 self.base_builder = self.base_builder.background_color(color);
1269 self
1270 }
1271
1272 pub fn margins(mut self, margins: Margins) -> Self {
1274 self.base_builder = self.base_builder.margins(margins);
1275 self
1276 }
1277
1278 pub fn smooth(mut self, smooth: bool) -> Self {
1280 self.base_builder = self.base_builder.smooth(smooth);
1281 self
1282 }
1283
1284 pub fn smooth_subdivisions(mut self, subdivisions: u32) -> Self {
1286 self.base_builder = self.base_builder.smooth_subdivisions(subdivisions);
1287 self
1288 }
1289
1290 pub fn with_grid(mut self, grid: crate::grid::GridSystem<C>) -> Self {
1292 self.base_builder = self.base_builder.with_grid(grid);
1293 self
1294 }
1295
1296 pub fn with_x_axis(mut self, axis: crate::axes::LinearAxis<f32, C>) -> Self {
1298 self.base_builder = self.base_builder.with_x_axis(axis);
1299 self
1300 }
1301
1302 pub fn with_y_axis(mut self, axis: crate::axes::LinearAxis<f32, C>) -> Self {
1304 self.base_builder = self.base_builder.with_y_axis(axis);
1305 self
1306 }
1307
1308 pub fn build(self) -> ChartResult<AnimatedLineChart<C>> {
1310 let base_chart = self.base_builder.build()?;
1311
1312 Ok(AnimatedLineChart {
1313 base_chart,
1314 current_data: None,
1315 })
1316 }
1317}
1318
1319#[cfg(feature = "animations")]
1320impl<C: PixelColor + 'static> Default for AnimatedLineChartBuilder<C>
1321where
1322 C: From<embedded_graphics::pixelcolor::Rgb565>,
1323{
1324 fn default() -> Self {
1325 Self::new()
1326 }
1327}