Skip to main content

oxidize_pdf/charts/
chart_builder.rs

1//! Generic chart builder and data structures
2
3use crate::graphics::Color;
4use crate::text::Font;
5
6/// Position of the chart legend
7#[derive(Debug, Clone, Copy, PartialEq, Default)]
8pub enum LegendPosition {
9    /// No legend
10    None,
11    /// Legend on the right side
12    #[default]
13    Right,
14    /// Legend at the bottom
15    Bottom,
16    /// Legend at the top
17    Top,
18    /// Legend on the left side
19    Left,
20}
21
22/// Chart type enumeration
23#[derive(Debug, Clone, Copy, PartialEq)]
24pub enum ChartType {
25    /// Vertical bar chart
26    VerticalBar,
27    /// Horizontal bar chart
28    HorizontalBar,
29    /// Pie chart
30    Pie,
31    /// Line chart
32    Line,
33    /// Area chart
34    Area,
35}
36
37/// Data point for charts
38#[derive(Debug, Clone)]
39pub struct ChartData {
40    /// Label for this data point
41    pub label: String,
42    /// Value for this data point
43    pub value: f64,
44    /// Custom color for this data point
45    pub color: Option<Color>,
46    /// Whether this data point should be highlighted
47    pub highlighted: bool,
48}
49
50impl ChartData {
51    /// Create a new chart data point
52    pub fn new<S: Into<String>>(label: S, value: f64) -> Self {
53        Self {
54            label: label.into(),
55            value,
56            color: None,
57            highlighted: false,
58        }
59    }
60
61    /// Set custom color for this data point
62    pub fn color(mut self, color: Color) -> Self {
63        self.color = Some(color);
64        self
65    }
66
67    /// Mark this data point as highlighted
68    pub fn highlighted(mut self) -> Self {
69        self.highlighted = true;
70        self
71    }
72}
73
74/// Generic chart configuration
75#[derive(Debug, Clone)]
76pub struct Chart {
77    /// Chart title
78    pub title: String,
79    /// Chart type
80    pub chart_type: ChartType,
81    /// Chart data
82    pub data: Vec<ChartData>,
83    /// Chart colors (used if data points don't have custom colors)
84    pub colors: Vec<Color>,
85    /// Title font
86    pub title_font: Font,
87    /// Title font size
88    pub title_font_size: f64,
89    /// Label font
90    pub label_font: Font,
91    /// Label font size
92    pub label_font_size: f64,
93    /// Legend position
94    pub legend_position: LegendPosition,
95    /// Background color
96    pub background_color: Option<Color>,
97    /// Whether to show values on chart elements
98    pub show_values: bool,
99    /// Whether to show grid lines
100    pub show_grid: bool,
101    /// Grid color
102    pub grid_color: Color,
103    /// Border color
104    pub border_color: Color,
105    /// Border width
106    pub border_width: f64,
107}
108
109impl Chart {
110    /// Create a new chart
111    pub fn new(chart_type: ChartType) -> Self {
112        Self {
113            title: String::new(),
114            chart_type,
115            data: Vec::new(),
116            colors: default_colors(),
117            title_font: Font::HelveticaBold,
118            title_font_size: 14.0,
119            label_font: Font::Helvetica,
120            label_font_size: 10.0,
121            legend_position: LegendPosition::Right,
122            background_color: None,
123            show_values: true,
124            show_grid: true,
125            grid_color: Color::rgb(0.9, 0.9, 0.9),
126            border_color: Color::black(),
127            border_width: 1.0,
128        }
129    }
130
131    /// Get the maximum value in the dataset
132    pub fn max_value(&self) -> f64 {
133        self.data.iter().map(|d| d.value).fold(0.0, f64::max)
134    }
135
136    /// Get the total value of all data points (useful for pie charts)
137    pub fn total_value(&self) -> f64 {
138        self.data.iter().map(|d| d.value).sum()
139    }
140
141    /// Get color for a data point at the given index
142    pub fn color_for_index(&self, index: usize) -> Color {
143        if let Some(data_point) = self.data.get(index) {
144            if let Some(color) = data_point.color {
145                return color;
146            }
147        }
148
149        self.colors
150            .get(index % self.colors.len())
151            .copied()
152            .unwrap_or(Color::rgb(0.5, 0.5, 0.5))
153    }
154}
155
156/// Builder for creating charts with fluent API
157pub struct ChartBuilder {
158    chart: Chart,
159}
160
161impl ChartBuilder {
162    /// Create a new chart builder
163    pub fn new(chart_type: ChartType) -> Self {
164        Self {
165            chart: Chart::new(chart_type),
166        }
167    }
168
169    /// Set chart title
170    pub fn title<S: Into<String>>(mut self, title: S) -> Self {
171        self.chart.title = title.into();
172        self
173    }
174
175    /// Add a single data point
176    pub fn add_data(mut self, data: ChartData) -> Self {
177        self.chart.data.push(data);
178        self
179    }
180
181    /// Set all data at once
182    pub fn data(mut self, data: Vec<ChartData>) -> Self {
183        self.chart.data = data;
184        self
185    }
186
187    /// Set chart colors
188    pub fn colors(mut self, colors: Vec<Color>) -> Self {
189        self.chart.colors = colors;
190        self
191    }
192
193    /// Set title font
194    pub fn title_font(mut self, font: Font, size: f64) -> Self {
195        self.chart.title_font = font;
196        self.chart.title_font_size = size;
197        self
198    }
199
200    /// Set label font
201    pub fn label_font(mut self, font: Font, size: f64) -> Self {
202        self.chart.label_font = font;
203        self.chart.label_font_size = size;
204        self
205    }
206
207    /// Set legend position
208    pub fn legend_position(mut self, position: LegendPosition) -> Self {
209        self.chart.legend_position = position;
210        self
211    }
212
213    /// Set background color
214    pub fn background_color(mut self, color: Color) -> Self {
215        self.chart.background_color = Some(color);
216        self
217    }
218
219    /// Show or hide values on chart elements
220    pub fn show_values(mut self, show: bool) -> Self {
221        self.chart.show_values = show;
222        self
223    }
224
225    /// Show or hide grid lines
226    pub fn show_grid(mut self, show: bool) -> Self {
227        self.chart.show_grid = show;
228        self
229    }
230
231    /// Set grid color
232    pub fn grid_color(mut self, color: Color) -> Self {
233        self.chart.grid_color = color;
234        self
235    }
236
237    /// Set border style
238    pub fn border(mut self, color: Color, width: f64) -> Self {
239        self.chart.border_color = color;
240        self.chart.border_width = width;
241        self
242    }
243
244    /// Add data from simple values with automatic labels
245    pub fn simple_data(mut self, values: Vec<f64>) -> Self {
246        for (i, value) in values.into_iter().enumerate() {
247            self.chart
248                .data
249                .push(ChartData::new(format!("Item {}", i + 1), value));
250        }
251        self
252    }
253
254    /// Add data from label-value pairs
255    pub fn labeled_data(mut self, data: Vec<(&str, f64)>) -> Self {
256        for (label, value) in data {
257            self.chart.data.push(ChartData::new(label, value));
258        }
259        self
260    }
261
262    /// Create a financial chart style
263    pub fn financial_style(mut self) -> Self {
264        self.chart.colors = vec![
265            Color::rgb(0.2, 0.6, 0.2), // Green
266            Color::rgb(0.8, 0.2, 0.2), // Red
267            Color::rgb(0.2, 0.4, 0.8), // Blue
268            Color::rgb(0.9, 0.6, 0.1), // Orange
269            Color::rgb(0.6, 0.2, 0.8), // Purple
270        ];
271        self.chart.show_grid = true;
272        self.chart.grid_color = Color::rgb(0.95, 0.95, 0.95);
273        self.chart.title_font_size = 16.0;
274        self
275    }
276
277    /// Create a minimal chart style
278    pub fn minimal_style(mut self) -> Self {
279        self.chart.show_grid = false;
280        self.chart.border_width = 0.0;
281        self.chart.background_color = None;
282        self
283    }
284
285    /// Build the final chart
286    pub fn build(self) -> Chart {
287        self.chart
288    }
289}
290
291/// Default color palette for charts
292fn default_colors() -> Vec<Color> {
293    vec![
294        Color::rgb(0.26, 0.45, 0.76), // Blue
295        Color::rgb(0.85, 0.37, 0.0),  // Orange
296        Color::rgb(0.18, 0.55, 0.34), // Green
297        Color::rgb(0.84, 0.15, 0.16), // Red
298        Color::rgb(0.58, 0.4, 0.74),  // Purple
299        Color::rgb(0.55, 0.34, 0.29), // Brown
300        Color::rgb(0.89, 0.47, 0.76), // Pink
301        Color::rgb(0.5, 0.5, 0.5),    // Gray
302        Color::rgb(0.74, 0.74, 0.13), // Olive
303        Color::rgb(0.09, 0.75, 0.81), // Cyan
304    ]
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    // ==================== LegendPosition Tests ====================
312
313    #[test]
314    fn test_legend_position_default() {
315        let pos: LegendPosition = Default::default();
316        assert_eq!(pos, LegendPosition::Right);
317    }
318
319    #[test]
320    fn test_legend_position_variants() {
321        assert_eq!(LegendPosition::None, LegendPosition::None);
322        assert_eq!(LegendPosition::Right, LegendPosition::Right);
323        assert_eq!(LegendPosition::Bottom, LegendPosition::Bottom);
324        assert_eq!(LegendPosition::Top, LegendPosition::Top);
325        assert_eq!(LegendPosition::Left, LegendPosition::Left);
326    }
327
328    #[test]
329    fn test_legend_position_clone() {
330        let pos = LegendPosition::Bottom;
331        let cloned = pos;
332        assert_eq!(pos, cloned);
333    }
334
335    #[test]
336    fn test_legend_position_debug() {
337        let debug_str = format!("{:?}", LegendPosition::Top);
338        assert!(debug_str.contains("Top"));
339    }
340
341    // ==================== ChartType Tests ====================
342
343    #[test]
344    fn test_chart_type_variants() {
345        assert_eq!(ChartType::VerticalBar, ChartType::VerticalBar);
346        assert_eq!(ChartType::HorizontalBar, ChartType::HorizontalBar);
347        assert_eq!(ChartType::Pie, ChartType::Pie);
348        assert_eq!(ChartType::Line, ChartType::Line);
349        assert_eq!(ChartType::Area, ChartType::Area);
350    }
351
352    #[test]
353    fn test_chart_type_not_equal() {
354        assert_ne!(ChartType::VerticalBar, ChartType::HorizontalBar);
355        assert_ne!(ChartType::Pie, ChartType::Line);
356    }
357
358    #[test]
359    fn test_chart_type_clone() {
360        let ct = ChartType::Area;
361        let cloned = ct;
362        assert_eq!(ct, cloned);
363    }
364
365    #[test]
366    fn test_chart_type_debug() {
367        let debug_str = format!("{:?}", ChartType::Line);
368        assert!(debug_str.contains("Line"));
369    }
370
371    // ==================== ChartData Tests ====================
372
373    #[test]
374    fn test_chart_data_creation() {
375        let data = ChartData::new("Test", 42.0);
376        assert_eq!(data.label, "Test");
377        assert_eq!(data.value, 42.0);
378        assert_eq!(data.color, None);
379        assert!(!data.highlighted);
380    }
381
382    #[test]
383    fn test_chart_data_with_color() {
384        let data = ChartData::new("Test", 42.0)
385            .color(Color::red())
386            .highlighted();
387
388        assert_eq!(data.color, Some(Color::red()));
389        assert!(data.highlighted);
390    }
391
392    #[test]
393    fn test_chart_data_from_string() {
394        let data = ChartData::new(String::from("Dynamic"), 99.9);
395        assert_eq!(data.label, "Dynamic");
396        assert_eq!(data.value, 99.9);
397    }
398
399    #[test]
400    fn test_chart_data_negative_value() {
401        let data = ChartData::new("Negative", -25.5);
402        assert_eq!(data.value, -25.5);
403    }
404
405    #[test]
406    fn test_chart_data_zero_value() {
407        let data = ChartData::new("Zero", 0.0);
408        assert_eq!(data.value, 0.0);
409    }
410
411    #[test]
412    fn test_chart_data_clone() {
413        let data = ChartData::new("Clone", 10.0).color(Color::blue());
414        let cloned = data.clone();
415        assert_eq!(cloned.label, "Clone");
416        assert_eq!(cloned.value, 10.0);
417        assert_eq!(cloned.color, Some(Color::blue()));
418    }
419
420    #[test]
421    fn test_chart_data_debug() {
422        let data = ChartData::new("Debug", 5.0);
423        let debug_str = format!("{:?}", data);
424        assert!(debug_str.contains("Debug"));
425        assert!(debug_str.contains("5"));
426    }
427
428    // ==================== Chart Tests ====================
429
430    #[test]
431    fn test_chart_new_vertical_bar() {
432        let chart = Chart::new(ChartType::VerticalBar);
433        assert_eq!(chart.chart_type, ChartType::VerticalBar);
434        assert!(chart.title.is_empty());
435        assert!(chart.data.is_empty());
436        assert_eq!(chart.legend_position, LegendPosition::Right);
437    }
438
439    #[test]
440    fn test_chart_new_pie() {
441        let chart = Chart::new(ChartType::Pie);
442        assert_eq!(chart.chart_type, ChartType::Pie);
443    }
444
445    #[test]
446    fn test_chart_new_line() {
447        let chart = Chart::new(ChartType::Line);
448        assert_eq!(chart.chart_type, ChartType::Line);
449    }
450
451    #[test]
452    fn test_chart_new_defaults() {
453        let chart = Chart::new(ChartType::Area);
454        assert_eq!(chart.title_font, Font::HelveticaBold);
455        assert_eq!(chart.title_font_size, 14.0);
456        assert_eq!(chart.label_font, Font::Helvetica);
457        assert_eq!(chart.label_font_size, 10.0);
458        assert!(chart.show_values);
459        assert!(chart.show_grid);
460        assert_eq!(chart.border_width, 1.0);
461        assert_eq!(chart.background_color, None);
462    }
463
464    #[test]
465    fn test_chart_max_value_empty() {
466        let chart = Chart::new(ChartType::VerticalBar);
467        assert_eq!(chart.max_value(), 0.0);
468    }
469
470    #[test]
471    fn test_chart_total_value_empty() {
472        let chart = Chart::new(ChartType::Pie);
473        assert_eq!(chart.total_value(), 0.0);
474    }
475
476    #[test]
477    fn test_chart_max_value_with_negatives() {
478        let mut chart = Chart::new(ChartType::Line);
479        chart.data = vec![
480            ChartData::new("A", -10.0),
481            ChartData::new("B", -5.0),
482            ChartData::new("C", -20.0),
483        ];
484        // Note: max_value uses fold(0.0, f64::max), so all-negative data returns 0.0
485        // This is the current behavior - chart assumes non-negative values
486        assert_eq!(chart.max_value(), 0.0);
487    }
488
489    #[test]
490    fn test_chart_total_value_with_negatives() {
491        let mut chart = Chart::new(ChartType::VerticalBar);
492        chart.data = vec![ChartData::new("A", 10.0), ChartData::new("B", -5.0)];
493        assert_eq!(chart.total_value(), 5.0);
494    }
495
496    #[test]
497    fn test_chart_color_for_index_out_of_bounds() {
498        let chart = Chart::new(ChartType::Pie);
499        // Should wrap around using modulo
500        let color = chart.color_for_index(100);
501        // Verify it returns a valid color (doesn't panic)
502        assert!(color.r() >= 0.0 && color.r() <= 1.0);
503    }
504
505    #[test]
506    #[should_panic(expected = "attempt to calculate the remainder with a divisor of zero")]
507    fn test_chart_color_for_index_with_empty_colors_panics() {
508        let mut chart = Chart::new(ChartType::Pie);
509        chart.colors = vec![];
510        // Current behavior: panics with empty colors array
511        // This documents the edge case - users should not empty the colors array
512        let _ = chart.color_for_index(0);
513    }
514
515    #[test]
516    fn test_chart_color_for_index_custom_overrides_default() {
517        let mut chart = Chart::new(ChartType::Pie);
518        chart.data = vec![ChartData::new("Custom", 10.0).color(Color::green())];
519        assert_eq!(chart.color_for_index(0), Color::green());
520    }
521
522    // ==================== ChartBuilder Tests ====================
523
524    #[test]
525    fn test_chart_builder() {
526        let chart = ChartBuilder::new(ChartType::VerticalBar)
527            .title("Test Chart")
528            .simple_data(vec![10.0, 20.0, 30.0])
529            .build();
530
531        assert_eq!(chart.title, "Test Chart");
532        assert_eq!(chart.chart_type, ChartType::VerticalBar);
533        assert_eq!(chart.data.len(), 3);
534        assert_eq!(chart.max_value(), 30.0);
535        assert_eq!(chart.total_value(), 60.0);
536    }
537
538    #[test]
539    fn test_chart_builder_add_data() {
540        let chart = ChartBuilder::new(ChartType::Pie)
541            .add_data(ChartData::new("A", 10.0))
542            .add_data(ChartData::new("B", 20.0))
543            .build();
544
545        assert_eq!(chart.data.len(), 2);
546        assert_eq!(chart.data[0].label, "A");
547        assert_eq!(chart.data[1].label, "B");
548    }
549
550    #[test]
551    fn test_chart_builder_data_replaces() {
552        let chart = ChartBuilder::new(ChartType::Line)
553            .add_data(ChartData::new("Old", 1.0))
554            .data(vec![ChartData::new("New", 2.0)])
555            .build();
556
557        assert_eq!(chart.data.len(), 1);
558        assert_eq!(chart.data[0].label, "New");
559    }
560
561    #[test]
562    fn test_chart_builder_colors() {
563        let custom_colors = vec![Color::red(), Color::blue()];
564        let chart = ChartBuilder::new(ChartType::VerticalBar)
565            .colors(custom_colors.clone())
566            .build();
567
568        assert_eq!(chart.colors.len(), 2);
569        assert_eq!(chart.colors[0], Color::red());
570        assert_eq!(chart.colors[1], Color::blue());
571    }
572
573    #[test]
574    fn test_chart_builder_title_font() {
575        let chart = ChartBuilder::new(ChartType::Pie)
576            .title_font(Font::TimesBold, 24.0)
577            .build();
578
579        assert_eq!(chart.title_font, Font::TimesBold);
580        assert_eq!(chart.title_font_size, 24.0);
581    }
582
583    #[test]
584    fn test_chart_builder_label_font() {
585        let chart = ChartBuilder::new(ChartType::Line)
586            .label_font(Font::Courier, 8.0)
587            .build();
588
589        assert_eq!(chart.label_font, Font::Courier);
590        assert_eq!(chart.label_font_size, 8.0);
591    }
592
593    #[test]
594    fn test_chart_builder_legend_position() {
595        let chart = ChartBuilder::new(ChartType::Pie)
596            .legend_position(LegendPosition::Bottom)
597            .build();
598
599        assert_eq!(chart.legend_position, LegendPosition::Bottom);
600    }
601
602    #[test]
603    fn test_chart_builder_legend_none() {
604        let chart = ChartBuilder::new(ChartType::VerticalBar)
605            .legend_position(LegendPosition::None)
606            .build();
607
608        assert_eq!(chart.legend_position, LegendPosition::None);
609    }
610
611    #[test]
612    fn test_chart_builder_background_color() {
613        let chart = ChartBuilder::new(ChartType::Line)
614            .background_color(Color::white())
615            .build();
616
617        assert_eq!(chart.background_color, Some(Color::white()));
618    }
619
620    #[test]
621    fn test_chart_builder_show_values() {
622        let chart = ChartBuilder::new(ChartType::VerticalBar)
623            .show_values(false)
624            .build();
625
626        assert!(!chart.show_values);
627    }
628
629    #[test]
630    fn test_chart_builder_show_grid() {
631        let chart = ChartBuilder::new(ChartType::Line).show_grid(false).build();
632
633        assert!(!chart.show_grid);
634    }
635
636    #[test]
637    fn test_chart_builder_grid_color() {
638        let chart = ChartBuilder::new(ChartType::Area)
639            .grid_color(Color::gray(0.5))
640            .build();
641
642        assert_eq!(chart.grid_color, Color::gray(0.5));
643    }
644
645    #[test]
646    fn test_chart_builder_border() {
647        let chart = ChartBuilder::new(ChartType::VerticalBar)
648            .border(Color::black(), 2.5)
649            .build();
650
651        assert_eq!(chart.border_color, Color::black());
652        assert_eq!(chart.border_width, 2.5);
653    }
654
655    #[test]
656    fn test_chart_builder_simple_data_labels() {
657        let chart = ChartBuilder::new(ChartType::VerticalBar)
658            .simple_data(vec![1.0, 2.0, 3.0])
659            .build();
660
661        assert_eq!(chart.data[0].label, "Item 1");
662        assert_eq!(chart.data[1].label, "Item 2");
663        assert_eq!(chart.data[2].label, "Item 3");
664    }
665
666    #[test]
667    fn test_chart_builder_labeled_data() {
668        let chart = ChartBuilder::new(ChartType::Pie)
669            .labeled_data(vec![("Q1", 100.0), ("Q2", 200.0)])
670            .build();
671
672        assert_eq!(chart.data.len(), 2);
673        assert_eq!(chart.data[0].label, "Q1");
674        assert_eq!(chart.data[0].value, 100.0);
675        assert_eq!(chart.data[1].label, "Q2");
676        assert_eq!(chart.data[1].value, 200.0);
677    }
678
679    #[test]
680    fn test_chart_builder_financial_style() {
681        let chart = ChartBuilder::new(ChartType::VerticalBar)
682            .financial_style()
683            .build();
684
685        assert_eq!(chart.colors.len(), 5);
686        assert!(chart.show_grid);
687        assert_eq!(chart.title_font_size, 16.0);
688    }
689
690    #[test]
691    fn test_chart_builder_minimal_style() {
692        let chart = ChartBuilder::new(ChartType::Line).minimal_style().build();
693
694        assert!(!chart.show_grid);
695        assert_eq!(chart.border_width, 0.0);
696        assert_eq!(chart.background_color, None);
697    }
698
699    #[test]
700    fn test_chart_builder_chained_styles() {
701        let chart = ChartBuilder::new(ChartType::VerticalBar)
702            .title("Financial Report")
703            .financial_style()
704            .labeled_data(vec![("2023", 1000.0), ("2024", 1500.0)])
705            .legend_position(LegendPosition::Bottom)
706            .build();
707
708        assert_eq!(chart.title, "Financial Report");
709        assert_eq!(chart.data.len(), 2);
710        assert_eq!(chart.legend_position, LegendPosition::Bottom);
711        assert!(chart.show_grid);
712    }
713
714    #[test]
715    fn test_color_for_index() {
716        let chart = ChartBuilder::new(ChartType::Pie)
717            .data(vec![
718                ChartData::new("A", 10.0).color(Color::red()),
719                ChartData::new("B", 20.0), // No custom color
720            ])
721            .build();
722
723        assert_eq!(chart.color_for_index(0), Color::red());
724        assert_eq!(chart.color_for_index(1), chart.colors[1]); // Default color
725    }
726
727    // ==================== default_colors Tests ====================
728
729    #[test]
730    fn test_default_colors_count() {
731        let colors = default_colors();
732        assert_eq!(colors.len(), 10);
733    }
734
735    #[test]
736    fn test_default_colors_valid_rgb() {
737        let colors = default_colors();
738        for color in colors {
739            assert!(color.r() >= 0.0 && color.r() <= 1.0);
740            assert!(color.g() >= 0.0 && color.g() <= 1.0);
741            assert!(color.b() >= 0.0 && color.b() <= 1.0);
742        }
743    }
744
745    #[test]
746    fn test_default_colors_unique() {
747        let colors = default_colors();
748        for i in 0..colors.len() {
749            for j in (i + 1)..colors.len() {
750                assert_ne!(
751                    colors[i], colors[j],
752                    "Colors at {} and {} should be different",
753                    i, j
754                );
755            }
756        }
757    }
758
759    // ==================== Edge Cases ====================
760
761    #[test]
762    fn test_empty_chart() {
763        let chart = ChartBuilder::new(ChartType::Pie).build();
764        assert!(chart.data.is_empty());
765        assert_eq!(chart.max_value(), 0.0);
766        assert_eq!(chart.total_value(), 0.0);
767    }
768
769    #[test]
770    fn test_single_data_point() {
771        let chart = ChartBuilder::new(ChartType::VerticalBar)
772            .add_data(ChartData::new("Only", 42.0))
773            .build();
774
775        assert_eq!(chart.data.len(), 1);
776        assert_eq!(chart.max_value(), 42.0);
777        assert_eq!(chart.total_value(), 42.0);
778    }
779
780    #[test]
781    fn test_large_dataset() {
782        let mut builder = ChartBuilder::new(ChartType::Line);
783        for i in 0..1000 {
784            builder = builder.add_data(ChartData::new(format!("Item {}", i), i as f64));
785        }
786        let chart = builder.build();
787
788        assert_eq!(chart.data.len(), 1000);
789        assert_eq!(chart.max_value(), 999.0);
790        assert_eq!(chart.total_value(), (0..1000).sum::<i32>() as f64);
791    }
792
793    #[test]
794    fn test_special_float_values() {
795        let chart = ChartBuilder::new(ChartType::VerticalBar)
796            .simple_data(vec![f64::MIN_POSITIVE, f64::MAX / 2.0])
797            .build();
798
799        assert_eq!(chart.data.len(), 2);
800        assert!(chart.max_value() > 0.0);
801    }
802}