Skip to main content

audit_edge_cases/
audit_edge_cases.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Edge-case charts for audit: tests formatting issues at boundary conditions.
3
4use std::fmt::Write;
5
6use esoc_chart::express::{bar, heatmap, histogram, line, pie_labeled, scatter};
7use esoc_chart::new_theme::NewTheme;
8
9fn try_chart(
10    name: &str,
11    f: impl FnOnce() -> esoc_chart::error::Result<String>,
12) -> Option<(String, String)> {
13    match f() {
14        Ok(svg) => Some((name.to_string(), svg)),
15        Err(e) => {
16            eprintln!("FAIL [{name}]: {e}");
17            None
18        }
19    }
20}
21
22fn main() {
23    let mut sections: Vec<(String, String)> = Vec::new();
24
25    // 1. Single data point
26    if let Some(s) = try_chart("Single Point", || {
27        scatter(&[5.0], &[10.0])
28            .title("Single Point")
29            .size(400.0, 300.0)
30            .to_svg()
31    }) {
32        sections.push(s);
33    }
34
35    // 2. Two points
36    if let Some(s) = try_chart("Two Points", || {
37        scatter(&[0.0, 100.0], &[0.0, 100.0])
38            .title("Two Points")
39            .size(400.0, 300.0)
40            .to_svg()
41    }) {
42        sections.push(s);
43    }
44
45    // 3. Very long category labels
46    if let Some(s) = try_chart("Long Labels", || {
47        let cats = vec![
48            "This is an extremely long category label",
49            "Another very very long label here",
50            "Short",
51            "Medium length label",
52            "Yet another verbose category name",
53        ];
54        bar(&cats, &[10.0, 25.0, 15.0, 30.0, 20.0])
55            .title("Long Category Labels")
56            .x_label("Categories")
57            .y_label("Value")
58            .size(600.0, 400.0)
59            .to_svg()
60    }) {
61        sections.push(s);
62    }
63
64    // 4. Large numeric values (millions)
65    if let Some(s) = try_chart("Large Numbers", || {
66        let x: Vec<f64> = (0..10).map(|i| f64::from(i) * 1_000_000.0).collect();
67        let y: Vec<f64> = (0..10).map(|i| f64::from(i).powi(2) * 500_000.0).collect();
68        scatter(&x, &y)
69            .title("Large Numbers (Millions)")
70            .x_label("Revenue ($)")
71            .y_label("Profit ($)")
72            .size(600.0, 350.0)
73            .to_svg()
74    }) {
75        sections.push(s);
76    }
77
78    // 5. Very small numbers
79    if let Some(s) = try_chart("Small Numbers", || {
80        let x: Vec<f64> = (0..10).map(|i| f64::from(i) * 0.0001).collect();
81        let y: Vec<f64> = (0..10).map(|i| f64::from(i) * 0.00005).collect();
82        scatter(&x, &y)
83            .title("Small Numbers")
84            .size(600.0, 350.0)
85            .to_svg()
86    }) {
87        sections.push(s);
88    }
89
90    // 6. All-negative bars
91    if let Some(s) = try_chart("Negative Bars", || {
92        bar(&["A", "B", "C", "D"], &[-10.0, -25.0, -5.0, -30.0])
93            .title("All-Negative Bars")
94            .y_label("Loss")
95            .size(500.0, 350.0)
96            .to_svg()
97    }) {
98        sections.push(s);
99    }
100
101    // 7. Mixed +/- bars
102    if let Some(s) = try_chart("Mixed Bars", || {
103        bar(&["Q1", "Q2", "Q3", "Q4"], &[15.0, -10.0, 25.0, -5.0])
104            .title("Mixed +/- Bars")
105            .y_label("P&L")
106            .size(500.0, 350.0)
107            .to_svg()
108    }) {
109        sections.push(s);
110    }
111
112    // 8. Dense scatter (1000 points)
113    if let Some(s) = try_chart("Dense Scatter", || {
114        struct Rng(u64);
115        impl Rng {
116            fn next(&mut self) -> f64 {
117                self.0 = self
118                    .0
119                    .wrapping_mul(6_364_136_223_846_793_005)
120                    .wrapping_add(1);
121                (self.0 >> 11) as f64 / (1u64 << 53) as f64
122            }
123        }
124        let mut rng = Rng(123);
125        let x: Vec<f64> = (0..1000).map(|_| rng.next() * 100.0).collect();
126        let y: Vec<f64> = (0..1000).map(|_| rng.next() * 100.0).collect();
127        scatter(&x, &y)
128            .title("Dense Scatter (n=1000)")
129            .size(500.0, 400.0)
130            .to_svg()
131    }) {
132        sections.push(s);
133    }
134
135    // 9. Single bar
136    if let Some(s) = try_chart("Single Bar", || {
137        bar(&["Only One"], &[42.0])
138            .title("Single Bar")
139            .size(500.0, 350.0)
140            .to_svg()
141    }) {
142        sections.push(s);
143    }
144
145    // 10. Many categories (20 bars)
146    if let Some(s) = try_chart("Many Categories", || {
147        let cats: Vec<String> = (0..20).map(|i| format!("Cat_{i}")).collect();
148        let cats_ref: Vec<&str> = cats.iter().map(|s| s.as_str()).collect();
149        let vals: Vec<f64> = (0..20).map(|i| (f64::from(i) * 7.0) % 50.0 + 5.0).collect();
150        bar(&cats_ref, &vals)
151            .title("Many Categories (20)")
152            .size(700.0, 400.0)
153            .to_svg()
154    }) {
155        sections.push(s);
156    }
157
158    // 11. Narrow chart
159    if let Some(s) = try_chart("Narrow Chart", || {
160        let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
161        let y = vec![10.0, 20.0, 15.0, 25.0, 30.0];
162        line(&x, &y)
163            .title("Narrow Chart")
164            .x_label("X")
165            .y_label("Y")
166            .size(300.0, 400.0)
167            .to_svg()
168    }) {
169        sections.push(s);
170    }
171
172    // 12. Wide chart
173    if let Some(s) = try_chart("Wide Chart", || {
174        let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
175        let y = vec![10.0, 20.0, 15.0, 25.0, 30.0];
176        line(&x, &y)
177            .title("Wide Chart")
178            .x_label("X")
179            .y_label("Y")
180            .size(1000.0, 250.0)
181            .to_svg()
182    }) {
183        sections.push(s);
184    }
185
186    // 13. Histogram (few bins)
187    if let Some(s) = try_chart("Few Bins", || {
188        histogram(&[1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0])
189            .bins(3)
190            .title("Histogram (3 bins)")
191            .size(400.0, 300.0)
192            .to_svg()
193    }) {
194        sections.push(s);
195    }
196
197    // 14. Dark theme scatter
198    if let Some(s) = try_chart("Dark Scatter", || {
199        let x = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
200        let y = vec![2.3, 4.1, 3.0, 5.8, 4.9, 7.2, 6.5, 8.1];
201        let cats = vec!["A", "B", "A", "B", "A", "B", "A", "B"];
202        scatter(&x, &y)
203            .color_by(&cats)
204            .title("Dark Theme Scatter")
205            .x_label("X")
206            .y_label("Y")
207            .theme(NewTheme::dark())
208            .size(500.0, 350.0)
209            .to_svg()
210    }) {
211        sections.push(s);
212    }
213
214    // 15. Dark theme bars
215    if let Some(s) = try_chart("Dark Bars", || {
216        bar(&["A", "B", "C", "D"], &[10.0, 25.0, 15.0, 30.0])
217            .title("Dark Theme Bars")
218            .theme(NewTheme::dark())
219            .size(500.0, 350.0)
220            .to_svg()
221    }) {
222        sections.push(s);
223    }
224
225    // 16. Flat line (constant y)
226    if let Some(s) = try_chart("Flat Line", || {
227        let x: Vec<f64> = (0..10).map(f64::from).collect();
228        line(&x, &[5.0; 10])
229            .title("Constant Y")
230            .size(400.0, 300.0)
231            .to_svg()
232    }) {
233        sections.push(s);
234    }
235
236    // 17. Pie with many small slices
237    if let Some(s) = try_chart("Many Pie Slices", || {
238        let mut vals = vec![50.0, 30.0, 15.0];
239        vals.extend(std::iter::repeat_n(0.5, 10));
240        let labels: Vec<String> = (0..vals.len()).map(|i| format!("Slice {i}")).collect();
241        let labels_ref: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
242        pie_labeled(&labels_ref, &vals)
243            .title("Pie: Many Slices")
244            .size(500.0, 400.0)
245            .to_svg()
246    }) {
247        sections.push(s);
248    }
249
250    // 18. Heatmap
251    if let Some(s) = try_chart("Heatmap", || {
252        let data = vec![
253            vec![1.0, 2.0, 3.0, 4.0],
254            vec![5.0, 6.0, 7.0, 8.0],
255            vec![9.0, 10.0, 11.0, 12.0],
256        ];
257        heatmap(data)
258            .title("Basic Heatmap")
259            .with_row_labels(&["R1", "R2", "R3"])
260            .with_col_labels(&["C1", "C2", "C3", "C4"])
261            .size(500.0, 350.0)
262            .to_svg()
263    }) {
264        sections.push(s);
265    }
266
267    // ── Write HTML ─────────────────────────────────────────────────────
268    let mut html = String::from(
269        r#"<!DOCTYPE html><html><head><meta charset="utf-8">
270<title>Audit Edge Cases</title>
271<style>
272body { font-family: system-ui; background: #f5f5f5; padding: 20px; }
273.chart-card { background: white; border-radius: 8px; padding: 16px; margin: 16px 0;
274  box-shadow: 0 1px 3px rgba(0,0,0,0.12); display: inline-block; vertical-align: top; }
275h2 { color: #333; font-size: 14px; margin: 0 0 8px 0; }
276.fail { background: #fff0f0; border: 1px solid #fcc; }
277</style></head><body>
278<h1>Audit Edge Cases</h1>"#,
279    );
280    for (title, svg) in &sections {
281        writeln!(
282            html,
283            "<div class=\"chart-card\"><h2>{title}</h2>{svg}</div>"
284        )
285        .unwrap();
286    }
287    html.push_str("</body></html>");
288    std::fs::write("audit_edge_cases.html", &html).unwrap();
289    println!("Saved audit_edge_cases.html ({} charts)", sections.len());
290}