1use super::grid::{DashPattern, GridConfig, GridLevel, GridSpacing};
33use super::style::{FillStyle, LineStyle, PointStyle, SeriesStyle, palette_color};
34use super::types::{
35 Axis, AxisId, AxisOrientation, AxisPosition, BarConfig, Chart, ChartTitle, ChartType,
36 DataPoint, FillRegion, LegendConfig, LegendPosition, LineAnnotation, Series, TextAnnotation,
37};
38use astrelis_render::Color;
39
40#[derive(Debug)]
42pub struct ChartBuilder {
43 chart: Chart,
44 series_count: usize,
45}
46
47impl Default for ChartBuilder {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53impl ChartBuilder {
54 pub fn new() -> Self {
56 Self {
57 chart: Chart::default(),
58 series_count: 0,
59 }
60 }
61
62 pub fn line() -> Self {
64 let mut builder = Self::new();
65 builder.chart.chart_type = ChartType::Line;
66 builder
67 }
68
69 pub fn bar() -> Self {
71 let mut builder = Self::new();
72 builder.chart.chart_type = ChartType::Bar;
73 builder
74 }
75
76 pub fn scatter() -> Self {
78 let mut builder = Self::new();
79 builder.chart.chart_type = ChartType::Scatter;
80 builder
81 }
82
83 pub fn area() -> Self {
85 let mut builder = Self::new();
86 builder.chart.chart_type = ChartType::Area;
87 builder
88 }
89
90 pub fn title(mut self, title: impl Into<String>) -> Self {
92 self.chart.title = Some(ChartTitle::new(title));
93 self
94 }
95
96 pub fn title_config(mut self, title: ChartTitle) -> Self {
98 self.chart.title = Some(title);
99 self
100 }
101
102 pub fn subtitle(mut self, subtitle: impl Into<String>) -> Self {
104 self.chart.subtitle = Some(ChartTitle::new(subtitle).with_font_size(12.0));
105 self
106 }
107
108 pub fn x_label(mut self, label: impl Into<String>) -> Self {
110 if let Some(axis) = self.chart.get_axis_mut(AxisId::X_PRIMARY) {
111 axis.label = Some(label.into());
112 }
113 self
114 }
115
116 pub fn y_label(mut self, label: impl Into<String>) -> Self {
118 if let Some(axis) = self.chart.get_axis_mut(AxisId::Y_PRIMARY) {
119 axis.label = Some(label.into());
120 }
121 self
122 }
123
124 pub fn x_axis_config(mut self, axis: Axis) -> Self {
126 let mut axis = axis;
127 axis.id = AxisId::X_PRIMARY;
128 axis.orientation = AxisOrientation::Horizontal;
129 axis.position = AxisPosition::Bottom;
130 self.chart.set_axis(axis);
131 self
132 }
133
134 pub fn y_axis_config(mut self, axis: Axis) -> Self {
136 let mut axis = axis;
137 axis.id = AxisId::Y_PRIMARY;
138 axis.orientation = AxisOrientation::Vertical;
139 axis.position = AxisPosition::Left;
140 self.chart.set_axis(axis);
141 self
142 }
143
144 pub fn secondary_y_axis(mut self, axis: Axis) -> Self {
146 let mut axis = axis;
147 axis.id = AxisId::Y_SECONDARY;
148 axis.orientation = AxisOrientation::Vertical;
149 axis.position = AxisPosition::Right;
150 self.chart.set_axis(axis);
151 self
152 }
153
154 pub fn secondary_x_axis(mut self, axis: Axis) -> Self {
156 let mut axis = axis;
157 axis.id = AxisId::X_SECONDARY;
158 axis.orientation = AxisOrientation::Horizontal;
159 axis.position = AxisPosition::Top;
160 self.chart.set_axis(axis);
161 self
162 }
163
164 pub fn add_axis(mut self, axis: Axis) -> Self {
166 self.chart.set_axis(axis);
167 self
168 }
169
170 pub fn x_range(mut self, min: f64, max: f64) -> Self {
172 if let Some(axis) = self.chart.get_axis_mut(AxisId::X_PRIMARY) {
173 axis.min = Some(min);
174 axis.max = Some(max);
175 }
176 self
177 }
178
179 pub fn y_range(mut self, min: f64, max: f64) -> Self {
181 if let Some(axis) = self.chart.get_axis_mut(AxisId::Y_PRIMARY) {
182 axis.min = Some(min);
183 axis.max = Some(max);
184 }
185 self
186 }
187
188 pub fn secondary_y_range(mut self, min: f64, max: f64) -> Self {
190 if let Some(axis) = self.chart.get_axis_mut(AxisId::Y_SECONDARY) {
191 axis.min = Some(min);
192 axis.max = Some(max);
193 } else {
194 self.chart
196 .set_axis(Axis::y_secondary().with_range(min, max));
197 }
198 self
199 }
200
201 pub fn add_series<T: Into<DataPoint> + Copy>(
203 mut self,
204 name: impl Into<String>,
205 data: &[T],
206 ) -> Self {
207 let color = palette_color(self.series_count);
208 let style = SeriesStyle::with_color(color);
209 self.chart
210 .series
211 .push(Series::from_tuples(name, data, style));
212 self.series_count += 1;
213 self
214 }
215
216 pub fn add_series_styled<T: Into<DataPoint> + Copy>(
218 mut self,
219 name: impl Into<String>,
220 data: &[T],
221 style: SeriesStyle,
222 ) -> Self {
223 self.chart
224 .series
225 .push(Series::from_tuples(name, data, style));
226 self.series_count += 1;
227 self
228 }
229
230 pub fn add_series_secondary_y<T: Into<DataPoint> + Copy>(
232 mut self,
233 name: impl Into<String>,
234 data: &[T],
235 ) -> Self {
236 let color = palette_color(self.series_count);
237 let style = SeriesStyle::with_color(color);
238 let series = Series::from_tuples(name, data, style)
239 .with_axes(AxisId::X_PRIMARY, AxisId::Y_SECONDARY);
240 self.chart.series.push(series);
241 self.series_count += 1;
242 self
243 }
244
245 pub fn add_series_with_axes<T: Into<DataPoint> + Copy>(
247 mut self,
248 name: impl Into<String>,
249 data: &[T],
250 x_axis: AxisId,
251 y_axis: AxisId,
252 ) -> Self {
253 let color = palette_color(self.series_count);
254 let style = SeriesStyle::with_color(color);
255 let series = Series::from_tuples(name, data, style).with_axes(x_axis, y_axis);
256 self.chart.series.push(series);
257 self.series_count += 1;
258 self
259 }
260
261 pub fn plot_function<F>(
263 mut self,
264 name: impl Into<String>,
265 f: F,
266 x_min: f64,
267 x_max: f64,
268 samples: usize,
269 ) -> Self
270 where
271 F: Fn(f64) -> f64,
272 {
273 let step = (x_max - x_min) / (samples - 1) as f64;
274 let data: Vec<DataPoint> = (0..samples)
275 .map(|i| {
276 let x = x_min + step * i as f64;
277 DataPoint::new(x, f(x))
278 })
279 .collect();
280
281 let color = palette_color(self.series_count);
282 let style = SeriesStyle::with_color(color);
283 self.chart.series.push(Series::new(name, data, style));
284 self.series_count += 1;
285 self
286 }
287
288 pub fn add_text_annotation(mut self, annotation: TextAnnotation) -> Self {
290 self.chart.text_annotations.push(annotation);
291 self
292 }
293
294 pub fn add_text_at(mut self, text: impl Into<String>, x: f64, y: f64) -> Self {
296 self.chart
297 .text_annotations
298 .push(TextAnnotation::at_data(text, x, y));
299 self
300 }
301
302 pub fn add_line_annotation(mut self, annotation: LineAnnotation) -> Self {
304 self.chart.line_annotations.push(annotation);
305 self
306 }
307
308 pub fn add_horizontal_line(mut self, y: f64, color: Color) -> Self {
310 let (x_min, x_max) = self.chart.x_range();
311 self.chart
312 .line_annotations
313 .push(LineAnnotation::horizontal(y, x_min, x_max).with_color(color));
314 self
315 }
316
317 pub fn add_vertical_line(mut self, x: f64, color: Color) -> Self {
319 let (y_min, y_max) = self.chart.y_range();
320 self.chart
321 .line_annotations
322 .push(LineAnnotation::vertical(x, y_min, y_max).with_color(color));
323 self
324 }
325
326 pub fn add_fill_region(mut self, region: FillRegion) -> Self {
328 self.chart.fill_regions.push(region);
329 self
330 }
331
332 pub fn add_horizontal_band(mut self, y_min: f64, y_max: f64, color: Color) -> Self {
334 self.chart
335 .fill_regions
336 .push(FillRegion::horizontal_band(y_min, y_max, color));
337 self
338 }
339
340 pub fn add_vertical_band(mut self, x_min: f64, x_max: f64, color: Color) -> Self {
342 self.chart
343 .fill_regions
344 .push(FillRegion::vertical_band(x_min, x_max, color));
345 self
346 }
347
348 pub fn fill_below_series(mut self, series_index: usize, baseline: f64, color: Color) -> Self {
350 self.chart
351 .fill_regions
352 .push(FillRegion::below_series(series_index, baseline, color));
353 self
354 }
355
356 pub fn fill_between_series(mut self, series1: usize, series2: usize, color: Color) -> Self {
358 self.chart
359 .fill_regions
360 .push(FillRegion::between_series(series1, series2, color));
361 self
362 }
363
364 pub fn with_grid(mut self) -> Self {
366 for axis in &mut self.chart.axes {
367 axis.grid_lines = true;
368 }
369 self
370 }
371
372 pub fn without_grid(mut self) -> Self {
374 for axis in &mut self.chart.axes {
375 axis.grid_lines = false;
376 }
377 self
378 }
379
380 pub fn with_legend(mut self, position: LegendPosition) -> Self {
382 self.chart.legend = Some(LegendConfig {
383 position,
384 padding: 10.0,
385 });
386 self
387 }
388
389 pub fn without_legend(mut self) -> Self {
391 self.chart.legend = None;
392 self
393 }
394
395 pub fn background(mut self, color: Color) -> Self {
397 self.chart.background_color = color;
398 self
399 }
400
401 pub fn padding(mut self, padding: f32) -> Self {
403 self.chart.padding = padding;
404 self
405 }
406
407 pub fn bar_config(mut self, config: BarConfig) -> Self {
409 self.chart.bar_config = config;
410 self
411 }
412
413 pub fn interactive(mut self, enabled: bool) -> Self {
415 self.chart.interactive.pan_enabled = enabled;
416 self.chart.interactive.zoom_enabled = enabled;
417 self
418 }
419
420 pub fn with_crosshair(mut self) -> Self {
422 self.chart.show_crosshair = true;
423 self
424 }
425
426 pub fn with_tooltips(mut self) -> Self {
428 self.chart.show_tooltips = true;
429 self
430 }
431
432 pub fn without_tooltips(mut self) -> Self {
434 self.chart.show_tooltips = false;
435 self
436 }
437
438 pub fn zoom_limits(mut self, min: f32, max: f32) -> Self {
440 self.chart.interactive.zoom_min = min;
441 self.chart.interactive.zoom_max = max;
442 self
443 }
444
445 pub fn build(self) -> Chart {
447 self.chart
448 }
449
450 pub fn x_axis<F>(self, f: F) -> Self
466 where
467 F: FnOnce(AxisBuilder) -> AxisBuilder,
468 {
469 let builder = AxisBuilder::new(AxisId::X_PRIMARY)
470 .orientation(AxisOrientation::Horizontal)
471 .position(AxisPosition::Bottom);
472 let configured = f(builder);
473 self.x_axis_config(configured.build())
474 }
475
476 pub fn y_axis<F>(self, f: F) -> Self
478 where
479 F: FnOnce(AxisBuilder) -> AxisBuilder,
480 {
481 let builder = AxisBuilder::new(AxisId::Y_PRIMARY)
482 .orientation(AxisOrientation::Vertical)
483 .position(AxisPosition::Left);
484 let configured = f(builder);
485 self.y_axis_config(configured.build())
486 }
487
488 pub fn add_custom_axis<F>(mut self, name: &str, f: F) -> Self
501 where
502 F: FnOnce(AxisBuilder) -> AxisBuilder,
503 {
504 let axis_id = AxisId::from_name(name);
505 let builder = AxisBuilder::new(axis_id).name(name);
506 let configured = f(builder);
507 self.chart.set_axis(configured.build());
508 self
509 }
510
511 pub fn add_series_with<F>(mut self, name: impl Into<String>, f: F) -> Self
524 where
525 F: FnOnce(SeriesBuilder) -> SeriesBuilder,
526 {
527 let color = palette_color(self.series_count);
528 let builder = SeriesBuilder::new(name).color(color);
529 let configured = f(builder);
530 self.chart.series.push(configured.build());
531 self.series_count += 1;
532 self
533 }
534
535 pub fn streaming_series<F>(mut self, name: impl Into<String>, capacity: usize, f: F) -> Self
548 where
549 F: FnOnce(SeriesBuilder) -> SeriesBuilder,
550 {
551 let color = palette_color(self.series_count);
552 let builder = SeriesBuilder::new(name).color(color).streaming(capacity);
553 let configured = f(builder);
554 self.chart.series.push(configured.build());
555 self.series_count += 1;
556 self
557 }
558}
559
560#[derive(Debug)]
566pub struct AxisBuilder {
567 axis: Axis,
568 grid_config: Option<GridConfig>,
569}
570
571impl AxisBuilder {
572 pub fn new(id: AxisId) -> Self {
574 Self {
575 axis: Axis {
576 id,
577 ..Default::default()
578 },
579 grid_config: None,
580 }
581 }
582
583 pub fn name(self, name: impl Into<String>) -> Self {
585 let _ = name.into();
587 self
588 }
589
590 pub fn label(mut self, label: impl Into<String>) -> Self {
592 self.axis.label = Some(label.into());
593 self
594 }
595
596 pub fn range(mut self, min: f64, max: f64) -> Self {
598 self.axis.min = Some(min);
599 self.axis.max = Some(max);
600 self
601 }
602
603 pub fn auto_range(mut self, padding: f64) -> Self {
605 self.axis.min = None;
606 self.axis.max = None;
607 let _ = padding;
609 self
610 }
611
612 pub fn orientation(mut self, orientation: AxisOrientation) -> Self {
614 self.axis.orientation = orientation;
615 self
616 }
617
618 pub fn position(mut self, position: AxisPosition) -> Self {
620 self.axis.position = position;
621 self
622 }
623
624 pub fn ticks(mut self, count: usize) -> Self {
626 self.axis.tick_count = count;
627 self
628 }
629
630 pub fn custom_ticks(mut self, ticks: Vec<(f64, String)>) -> Self {
632 self.axis.custom_ticks = Some(ticks);
633 self
634 }
635
636 pub fn show_grid(mut self, show: bool) -> Self {
638 self.axis.grid_lines = show;
639 self
640 }
641
642 pub fn grid<F>(mut self, f: F) -> Self
644 where
645 F: FnOnce(GridBuilder) -> GridBuilder,
646 {
647 let builder = GridBuilder::new();
648 let configured = f(builder);
649 self.grid_config = Some(configured.build());
650 self.axis.grid_lines = true;
651 self
652 }
653
654 pub fn visible(mut self, visible: bool) -> Self {
656 self.axis.visible = visible;
657 self
658 }
659
660 pub fn build(self) -> Axis {
662 self.axis
663 }
664}
665
666#[derive(Debug)]
672pub struct GridBuilder {
673 config: GridConfig,
674}
675
676impl GridBuilder {
677 pub fn new() -> Self {
679 Self {
680 config: GridConfig::default(),
681 }
682 }
683
684 pub fn major<F>(mut self, f: F) -> Self
686 where
687 F: FnOnce(GridLevelBuilder) -> GridLevelBuilder,
688 {
689 let builder = GridLevelBuilder::new(GridLevel::major());
690 let configured = f(builder);
691 self.config.major = configured.build();
692 self
693 }
694
695 pub fn minor<F>(mut self, f: F) -> Self
697 where
698 F: FnOnce(GridLevelBuilder) -> GridLevelBuilder,
699 {
700 let builder = GridLevelBuilder::new(GridLevel::minor());
701 let configured = f(builder);
702 self.config.minor = Some(configured.build());
703 self
704 }
705
706 pub fn tertiary<F>(mut self, f: F) -> Self
708 where
709 F: FnOnce(GridLevelBuilder) -> GridLevelBuilder,
710 {
711 let builder = GridLevelBuilder::new(GridLevel::tertiary());
712 let configured = f(builder);
713 self.config.tertiary = Some(configured.build());
714 self
715 }
716
717 pub fn divisions(mut self, count: usize) -> Self {
719 self.config.minor_divisions = count;
720 self
721 }
722
723 pub fn spacing(mut self, spacing: GridSpacing) -> Self {
725 self.config.spacing = spacing;
726 self
727 }
728
729 pub fn auto_spacing(mut self, count: usize) -> Self {
731 self.config.spacing = GridSpacing::auto(count);
732 self
733 }
734
735 pub fn fixed_spacing(mut self, interval: f64) -> Self {
737 self.config.spacing = GridSpacing::fixed(interval);
738 self
739 }
740
741 pub fn build(self) -> GridConfig {
743 self.config
744 }
745}
746
747impl Default for GridBuilder {
748 fn default() -> Self {
749 Self::new()
750 }
751}
752
753#[derive(Debug)]
755pub struct GridLevelBuilder {
756 level: GridLevel,
757}
758
759impl GridLevelBuilder {
760 pub fn new(level: GridLevel) -> Self {
762 Self { level }
763 }
764
765 pub fn thickness(mut self, thickness: f32) -> Self {
767 self.level.thickness = thickness;
768 self
769 }
770
771 pub fn color(mut self, color: Color) -> Self {
773 self.level.color = color;
774 self
775 }
776
777 pub fn dash(mut self, dash: DashPattern) -> Self {
779 self.level.dash = dash;
780 self
781 }
782
783 pub fn dotted(mut self) -> Self {
785 self.level.dash = DashPattern::dotted(2.0);
786 self
787 }
788
789 pub fn dashed(mut self) -> Self {
791 self.level.dash = DashPattern::medium_dash();
792 self
793 }
794
795 pub fn enabled(mut self, enabled: bool) -> Self {
797 self.level.enabled = enabled;
798 self
799 }
800
801 pub fn build(self) -> GridLevel {
803 self.level
804 }
805}
806
807#[derive(Debug)]
813pub struct SeriesBuilder {
814 name: String,
815 data: Vec<DataPoint>,
816 style: SeriesStyle,
817 x_axis: AxisId,
818 y_axis: AxisId,
819 is_streaming: bool,
820 streaming_capacity: usize,
821}
822
823impl SeriesBuilder {
824 pub fn new(name: impl Into<String>) -> Self {
826 Self {
827 name: name.into(),
828 data: Vec::new(),
829 style: SeriesStyle::default(),
830 x_axis: AxisId::X_PRIMARY,
831 y_axis: AxisId::Y_PRIMARY,
832 is_streaming: false,
833 streaming_capacity: 1000,
834 }
835 }
836
837 pub fn data<T: Into<DataPoint> + Copy>(mut self, data: &[T]) -> Self {
839 self.data = data.iter().map(|&d| d.into()).collect();
840 self
841 }
842
843 pub fn streaming(mut self, capacity: usize) -> Self {
845 self.is_streaming = true;
846 self.streaming_capacity = capacity;
847 self
848 }
849
850 pub fn color(mut self, color: Color) -> Self {
852 self.style.color = color;
853 self
854 }
855
856 pub fn line_width(mut self, width: f32) -> Self {
858 self.style.line_width = width;
859 self
860 }
861
862 pub fn dashed(mut self, dash_len: f32, gap_len: f32) -> Self {
864 self.style.line_style = LineStyle::Dashed;
865 let _ = (dash_len, gap_len); self
867 }
868
869 pub fn dotted(mut self) -> Self {
871 self.style.line_style = LineStyle::Dotted;
872 self
873 }
874
875 pub fn markers<F>(mut self, f: F) -> Self
877 where
878 F: FnOnce(MarkerBuilder) -> MarkerBuilder,
879 {
880 let builder = MarkerBuilder::new();
881 let configured = f(builder);
882 self.style.point_style = Some(configured.build());
883 self
884 }
885
886 pub fn with_markers(mut self) -> Self {
888 self.style.point_style = Some(PointStyle {
889 size: 6.0,
890 shape: super::style::MarkerShape::Circle,
891 color: self.style.color,
892 });
893 self
894 }
895
896 pub fn fill_to_baseline(mut self, color: Color) -> Self {
898 self.style.fill = Some(FillStyle {
899 color,
900 opacity: color.a,
901 });
902 self
903 }
904
905 pub fn x_axis(mut self, axis: AxisId) -> Self {
907 self.x_axis = axis;
908 self
909 }
910
911 pub fn y_axis(mut self, axis: AxisId) -> Self {
913 self.y_axis = axis;
914 self
915 }
916
917 pub fn axes(mut self, x_axis: AxisId, y_axis: AxisId) -> Self {
919 self.x_axis = x_axis;
920 self.y_axis = y_axis;
921 self
922 }
923
924 pub fn z_order(mut self, z_order: i32) -> Self {
926 self.style.z_order = z_order;
927 self
928 }
929
930 pub fn visible(mut self, visible: bool) -> Self {
932 self.style.visible = visible;
933 self
934 }
935
936 pub fn hide_from_legend(mut self) -> Self {
938 self.style.show_in_legend = false;
939 self
940 }
941
942 pub fn build(self) -> Series {
944 Series {
945 name: self.name,
946 data: self.data,
947 style: self.style,
948 x_axis: self.x_axis,
949 y_axis: self.y_axis,
950 }
951 }
952}
953
954#[derive(Debug)]
956pub struct MarkerBuilder {
957 style: PointStyle,
958}
959
960impl MarkerBuilder {
961 pub fn new() -> Self {
963 Self {
964 style: PointStyle::default(),
965 }
966 }
967
968 pub fn circle(mut self) -> Self {
970 self.style.shape = super::style::MarkerShape::Circle;
971 self
972 }
973
974 pub fn square(mut self) -> Self {
976 self.style.shape = super::style::MarkerShape::Square;
977 self
978 }
979
980 pub fn diamond(mut self) -> Self {
982 self.style.shape = super::style::MarkerShape::Diamond;
983 self
984 }
985
986 pub fn triangle(mut self) -> Self {
988 self.style.shape = super::style::MarkerShape::Triangle;
989 self
990 }
991
992 pub fn size(mut self, size: f32) -> Self {
994 self.style.size = size;
995 self
996 }
997
998 pub fn color(mut self, color: Color) -> Self {
1000 self.style.color = color;
1001 self
1002 }
1003
1004 pub fn build(self) -> PointStyle {
1006 self.style
1007 }
1008}
1009
1010impl Default for MarkerBuilder {
1011 fn default() -> Self {
1012 Self::new()
1013 }
1014}
1015
1016#[cfg(test)]
1017mod tests {
1018 use super::*;
1019
1020 #[test]
1021 fn test_line_chart_builder() {
1022 let chart = ChartBuilder::line()
1023 .title("Test Chart")
1024 .x_label("X")
1025 .y_label("Y")
1026 .add_series("Series 1", &[(0.0, 1.0), (1.0, 2.0), (2.0, 1.5)])
1027 .with_grid()
1028 .build();
1029
1030 assert_eq!(chart.chart_type, ChartType::Line);
1031 assert_eq!(
1032 chart.title.as_ref().map(|t| t.text.as_str()),
1033 Some("Test Chart")
1034 );
1035 assert_eq!(chart.series.len(), 1);
1036 }
1037
1038 #[test]
1039 fn test_function_plot() {
1040 let chart = ChartBuilder::line()
1041 .plot_function("sin(x)", |x| x.sin(), 0.0, std::f64::consts::TAU, 100)
1042 .build();
1043
1044 assert_eq!(chart.series.len(), 1);
1045 assert_eq!(chart.series[0].data.len(), 100);
1046 }
1047
1048 #[test]
1049 fn test_secondary_axis() {
1050 let chart = ChartBuilder::line()
1051 .add_series("Primary", &[(0.0, 1.0), (1.0, 2.0)])
1052 .secondary_y_axis(Axis::y_secondary().with_label("Secondary Y"))
1053 .secondary_y_range(0.0, 100.0)
1054 .add_series_secondary_y("Secondary", &[(0.0, 50.0), (1.0, 75.0)])
1055 .build();
1056
1057 assert_eq!(chart.series.len(), 2);
1058 assert_eq!(chart.series[1].y_axis, AxisId::Y_SECONDARY);
1059 assert!(chart.get_axis(AxisId::Y_SECONDARY).is_some());
1060 }
1061
1062 #[test]
1063 fn test_annotations() {
1064 let chart = ChartBuilder::line()
1065 .add_text_at("Peak", 1.0, 2.0)
1066 .add_horizontal_line(1.5, Color::RED)
1067 .add_horizontal_band(0.5, 1.0, Color::rgba(0.0, 1.0, 0.0, 0.2))
1068 .build();
1069
1070 assert_eq!(chart.text_annotations.len(), 1);
1071 assert_eq!(chart.line_annotations.len(), 1);
1072 assert_eq!(chart.fill_regions.len(), 1);
1073 }
1074}