Skip to main content

stress_test/
stress_test.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Stress test: exercises every chart type, option, theme, annotation, facet mode,
3//! position, stat, and edge case to surface visual issues.
4
5use std::fmt::Write;
6
7use esoc_chart::express::{
8    area, bar, boxplot, grouped_bar, heatmap, histogram, line, pie_labeled, scatter, stacked_bar,
9};
10use esoc_chart::grammar::annotation::Annotation;
11use esoc_chart::grammar::chart::Chart;
12use esoc_chart::grammar::coord::CoordSystem;
13use esoc_chart::grammar::facet::{Facet, FacetScales};
14use esoc_chart::grammar::layer::{Layer, MarkType};
15use esoc_chart::grammar::position::Position;
16use esoc_chart::grammar::stat::{AggregateFunc, Stat};
17use esoc_chart::new_theme::NewTheme;
18use esoc_color::{Color, ColorScale, Palette};
19
20fn main() -> esoc_chart::error::Result<()> {
21    // ── Simple RNG ───────────────────────────────────────────────────
22    struct Rng(u64);
23    impl Rng {
24        fn uniform(&mut self) -> f64 {
25            self.0 = self
26                .0
27                .wrapping_mul(6_364_136_223_846_793_005)
28                .wrapping_add(1);
29            (self.0 >> 11) as f64 / (1u64 << 53) as f64
30        }
31        fn normal(&mut self) -> f64 {
32            let u1 = self.uniform().max(1e-15);
33            let u2 = self.uniform();
34            (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
35        }
36        fn range(&mut self, lo: f64, hi: f64) -> f64 {
37            lo + self.uniform() * (hi - lo)
38        }
39    }
40    let mut sections: Vec<(&str, String)> = Vec::new();
41    let mut rng = Rng(12345);
42
43    // ═════════════════════════════════════════════════════════════════
44    //  SECTION 1: SCATTER VARIATIONS
45    // ═════════════════════════════════════════════════════════════════
46
47    // 1a. Tiny scatter (2 points)
48    {
49        let svg = scatter(&[1.0, 2.0], &[3.0, 4.0])
50            .title("2-Point Scatter")
51            .x_label("X")
52            .y_label("Y")
53            .size(400.0, 300.0)
54            .to_svg()?;
55        sections.push(("Scatter — 2 Points", svg));
56    }
57
58    // 1b. Large scatter with auto-opacity
59    {
60        let n = 1000;
61        let x: Vec<f64> = (0..n).map(|_| rng.normal() * 5.0 + 50.0).collect();
62        let y: Vec<f64> = x
63            .iter()
64            .map(|&xi| xi * 0.6 + rng.normal() * 8.0 + 10.0)
65            .collect();
66        let svg = scatter(&x, &y)
67            .title("Dense Scatter (n=1000)")
68            .x_label("Income ($K)")
69            .y_label("Spending ($K)")
70            .size(500.0, 400.0)
71            .to_svg()?;
72        sections.push(("Scatter — Dense 1K", svg));
73    }
74
75    // 1c. Scatter with 6 categories
76    {
77        let mut x = Vec::new();
78        let mut y = Vec::new();
79        let mut cats = Vec::new();
80        let names = [
81            "Setosa",
82            "Versicolor",
83            "Virginica",
84            "Hybrid-A",
85            "Hybrid-B",
86            "Unknown",
87        ];
88        for (i, name) in names.iter().enumerate() {
89            let cx = 2.0 + i as f64 * 1.5;
90            let cy = 3.0 + (i as f64 * 0.7).sin() * 2.0;
91            for _ in 0..20 {
92                x.push(cx + rng.normal() * 0.5);
93                y.push(cy + rng.normal() * 0.5);
94                cats.push(*name);
95            }
96        }
97        let svg = scatter(&x, &y)
98            .color_by(&cats)
99            .title("Scatter — 6 Categories (Iris-like)")
100            .x_label("Sepal Length")
101            .y_label("Petal Width")
102            .size(550.0, 400.0)
103            .to_svg()?;
104        sections.push(("Scatter — 6 Categories", svg));
105    }
106
107    // 1d. Scatter with jitter position
108    {
109        let x: Vec<f64> = (0..60).map(|i| f64::from(i % 3)).collect();
110        let y: Vec<f64> = x
111            .iter()
112            .map(|&xi| xi * 2.0 + rng.normal() * 0.5 + 5.0)
113            .collect();
114        let cats: Vec<String> = (0..60)
115            .map(|i| ["Low", "Med", "High"][i % 3].into())
116            .collect();
117        let chart = Chart::new()
118            .layer(
119                Layer::new(MarkType::Point)
120                    .with_x(x)
121                    .with_y(y)
122                    .with_categories(cats)
123                    .position(Position::Jitter {
124                        x_amount: 0.2,
125                        y_amount: 0.0,
126                    }),
127            )
128            .title("Scatter — Jittered (strip plot)")
129            .x_label("Group")
130            .y_label("Value")
131            .size(450.0, 350.0);
132        sections.push(("Scatter — Jitter", chart.to_svg()?));
133    }
134
135    // 1e. Scatter with all annotations
136    {
137        let x: Vec<f64> = (0..30).map(|_| rng.range(0.0, 100.0)).collect();
138        let y: Vec<f64> = x.iter().map(|&xi| xi * 0.8 + rng.normal() * 10.0).collect();
139        let chart = scatter(&x, &y)
140            .title("Scatter — All Annotation Types")
141            .x_label("X")
142            .y_label("Y")
143            .size(550.0, 400.0)
144            .build()
145            .annotate(
146                Annotation::hline(40.0)
147                    .with_label("Mean")
148                    .with_color(Color::from_hex("#e74c3c").unwrap()),
149            )
150            .annotate(
151                Annotation::vline(50.0)
152                    .with_label("Midpoint")
153                    .with_color(Color::from_hex("#3498db").unwrap()),
154            )
155            .annotate(
156                Annotation::band(20.0, 60.0)
157                    .with_label("Normal range")
158                    .with_color(Color::from_hex("#2ecc71").unwrap().with_alpha(0.12)),
159            )
160            .annotate(Annotation::text(70.0, 70.0, "Outlier zone"));
161        sections.push(("Scatter — All Annotations", chart.to_svg()?));
162    }
163
164    // 1f. Scatter with LOESS smooth overlay
165    {
166        let x: Vec<f64> = (0..50).map(|i| f64::from(i) * 0.2).collect();
167        let y: Vec<f64> = x
168            .iter()
169            .map(|&v| (v * 0.5).sin() * 4.0 + v * 0.3 + rng.normal() * 1.0)
170            .collect();
171        let chart = Chart::new()
172            .layer(
173                Layer::new(MarkType::Point)
174                    .with_x(x.clone())
175                    .with_y(y.clone())
176                    .with_label("Raw"),
177            )
178            .layer(
179                Layer::new(MarkType::Line)
180                    .with_x(x)
181                    .with_y(y)
182                    .stat(Stat::Smooth { bandwidth: 0.25 })
183                    .with_label("LOESS (bw=0.25)"),
184            )
185            .title("Scatter + LOESS Smooth")
186            .x_label("Time")
187            .y_label("Signal")
188            .size(500.0, 380.0);
189        sections.push(("Scatter — LOESS Overlay", chart.to_svg()?));
190    }
191
192    // 1g. Scatter with subtitle, caption, description
193    {
194        let x = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
195        let y = vec![2.1, 3.8, 3.2, 5.5, 4.8, 7.1, 6.3, 8.0, 7.5, 9.2];
196        let chart = Chart::new()
197            .layer(Layer::new(MarkType::Point).with_x(x).with_y(y))
198            .title("Monthly Revenue")
199            .subtitle("Jan–Oct 2026, all regions")
200            .caption("Source: internal CRM")
201            .description("Scatter plot of monthly revenue showing upward trend")
202            .x_label("Month")
203            .y_label("Revenue ($M)")
204            .size(500.0, 400.0);
205        sections.push(("Scatter — Subtitle+Caption", chart.to_svg()?));
206    }
207
208    // ═════════════════════════════════════════════════════════════════
209    //  SECTION 2: LINE VARIATIONS
210    // ═════════════════════════════════════════════════════════════════
211
212    // 2a. Single line
213    {
214        let x: Vec<f64> = (0..100).map(|i| f64::from(i) * 0.1).collect();
215        let y: Vec<f64> = x.iter().map(|&v| (v * 0.5).sin() * 3.0 + v * 0.2).collect();
216        let svg = line(&x, &y)
217            .title("Smooth Sine Wave")
218            .x_label("Time (s)")
219            .y_label("Amplitude")
220            .size(600.0, 300.0)
221            .to_svg()?;
222        sections.push(("Line — Single", svg));
223    }
224
225    // 2b. Multi-line (5 series)
226    {
227        let x: Vec<f64> = (0..40).map(f64::from).collect();
228        let chart = Chart::new()
229            .layer(
230                Layer::new(MarkType::Line)
231                    .with_x(x.clone())
232                    .with_y(x.iter().map(|&v| (v * 0.2).sin() * 10.0 + 20.0).collect())
233                    .with_label("Server A"),
234            )
235            .layer(
236                Layer::new(MarkType::Line)
237                    .with_x(x.clone())
238                    .with_y(x.iter().map(|&v| (v * 0.15).cos() * 8.0 + 25.0).collect())
239                    .with_label("Server B"),
240            )
241            .layer(
242                Layer::new(MarkType::Line)
243                    .with_x(x.clone())
244                    .with_y(x.iter().map(|&v| v * 0.5 + 10.0).collect())
245                    .with_label("Server C"),
246            )
247            .layer(
248                Layer::new(MarkType::Line)
249                    .with_x(x.clone())
250                    .with_y(x.iter().map(|&v| 30.0 - v * 0.3).collect())
251                    .with_label("Server D"),
252            )
253            .layer(
254                Layer::new(MarkType::Line)
255                    .with_x(x.clone())
256                    .with_y(
257                        x.iter()
258                            .map(|&v| ((v * 0.3).sin() * 5.0 + 18.0).max(5.0))
259                            .collect(),
260                    )
261                    .with_label("Server E"),
262            )
263            .title("5-Server Latency Dashboard")
264            .x_label("Minute")
265            .y_label("Latency (ms)")
266            .size(650.0, 400.0);
267        sections.push(("Line — 5 Series", chart.to_svg()?));
268    }
269
270    // 2c. Line + scatter overlay (actual vs predicted)
271    {
272        let x: Vec<f64> = (0..15).map(f64::from).collect();
273        let actual: Vec<f64> = vec![
274            3.0, 5.2, 4.1, 7.8, 6.5, 9.1, 8.3, 10.2, 9.8, 12.1, 11.5, 13.7, 12.9, 15.0, 14.2,
275        ];
276        let predicted: Vec<f64> = x.iter().map(|&v| v * 0.9 + 2.5).collect();
277        let chart = Chart::new()
278            .layer(
279                Layer::new(MarkType::Point)
280                    .with_x(x.clone())
281                    .with_y(actual)
282                    .with_label("Actual"),
283            )
284            .layer(
285                Layer::new(MarkType::Line)
286                    .with_x(x)
287                    .with_y(predicted)
288                    .with_label("Predicted"),
289            )
290            .title("Actual vs Predicted")
291            .x_label("Week")
292            .y_label("Sales ($K)")
293            .size(500.0, 380.0);
294        sections.push(("Line — Actual vs Predicted", chart.to_svg()?));
295    }
296
297    // 2d. Noisy line with wide LOESS
298    {
299        let x: Vec<f64> = (0..80).map(|i| f64::from(i) * 0.125).collect();
300        let y: Vec<f64> = x
301            .iter()
302            .map(|&v| (v * 0.8).sin() * 5.0 + rng.normal() * 2.5)
303            .collect();
304        let chart = Chart::new()
305            .layer(
306                Layer::new(MarkType::Line)
307                    .with_x(x.clone())
308                    .with_y(y.clone())
309                    .with_label("Raw"),
310            )
311            .layer(
312                Layer::new(MarkType::Line)
313                    .with_x(x.clone())
314                    .with_y(y.clone())
315                    .stat(Stat::Smooth { bandwidth: 0.15 })
316                    .with_label("bw=0.15"),
317            )
318            .layer(
319                Layer::new(MarkType::Line)
320                    .with_x(x)
321                    .with_y(y)
322                    .stat(Stat::Smooth { bandwidth: 0.5 })
323                    .with_label("bw=0.5"),
324            )
325            .title("LOESS Bandwidth Comparison")
326            .x_label("X")
327            .y_label("Y")
328            .size(550.0, 380.0);
329        sections.push(("Line — LOESS Bandwidths", chart.to_svg()?));
330    }
331
332    // ═════════════════════════════════════════════════════════════════
333    //  SECTION 3: BAR VARIATIONS
334    // ═════════════════════════════════════════════════════════════════
335
336    // 3a. 3 bars — minimal
337    {
338        let svg = bar(&["A", "B", "C"], &[10.0, 20.0, 15.0])
339            .title("3-Bar Minimal")
340            .size(350.0, 280.0)
341            .to_svg()?;
342        sections.push(("Bar — 3 Bars", svg));
343    }
344
345    // 3b. Single bar
346    {
347        let svg = bar(&["Total"], &[42.0])
348            .title("Single Bar")
349            .y_label("Count")
350            .size(300.0, 280.0)
351            .to_svg()?;
352        sections.push(("Bar — Single", svg));
353    }
354
355    // 3c. Many bars (20 categories, label rotation stress)
356    {
357        let cats: Vec<String> = (0..20).map(|i| format!("Department {}", i + 1)).collect();
358        let vals: Vec<f64> = (0..20)
359            .map(|i| 10.0 + (f64::from(i) * 2.7).sin().abs() * 40.0)
360            .collect();
361        let cat_refs: Vec<&str> = cats.iter().map(|s| s.as_str()).collect();
362        let svg = bar(&cat_refs, &vals)
363            .title("20 Departments — Label Rotation Stress")
364            .x_label("Department")
365            .y_label("Budget ($K)")
366            .size(700.0, 400.0)
367            .to_svg()?;
368        sections.push(("Bar — 20 Categories", svg));
369    }
370
371    // 3d. Very long category names
372    {
373        let cats = [
374            "Engineering & Product Development",
375            "Marketing & Communications",
376            "Human Resources Management",
377            "Finance & Accounting Dept.",
378        ];
379        let vals = vec![85.0, 62.0, 45.0, 71.0];
380        let svg = bar(&cats, &vals)
381            .title("Long Category Names")
382            .x_label("Department")
383            .y_label("Headcount")
384            .size(550.0, 400.0)
385            .to_svg()?;
386        sections.push(("Bar — Long Names", svg));
387    }
388
389    // 3e. Horizontal bar
390    {
391        let chart = Chart::new()
392            .layer(
393                Layer::new(MarkType::Bar)
394                    .with_x(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0])
395                    .with_y(vec![95.0, 87.0, 76.0, 68.0, 55.0, 42.0])
396                    .with_categories(vec![
397                        "Rust".into(),
398                        "Go".into(),
399                        "Python".into(),
400                        "Java".into(),
401                        "C++".into(),
402                        "JavaScript".into(),
403                    ]),
404            )
405            .coord(CoordSystem::Flipped)
406            .title("Horizontal Bars — Performance Score")
407            .x_label("Score")
408            .y_label("Language")
409            .size(500.0, 380.0);
410        sections.push(("Bar — Horizontal", chart.to_svg()?));
411    }
412
413    // 3f. Horizontal bar with very long labels
414    {
415        let chart = Chart::new()
416            .layer(
417                Layer::new(MarkType::Bar)
418                    .with_x(vec![0.0, 1.0, 2.0, 3.0])
419                    .with_y(vec![88.0, 72.0, 65.0, 91.0])
420                    .with_categories(vec![
421                        "Customer Satisfaction Index".into(),
422                        "Net Promoter Score".into(),
423                        "Employee Engagement Rate".into(),
424                        "Year-over-Year Revenue Growth".into(),
425                    ]),
426            )
427            .coord(CoordSystem::Flipped)
428            .title("Horizontal — Long Y Labels")
429            .x_label("Percentage")
430            .y_label("KPI")
431            .size(550.0, 350.0);
432        sections.push(("Bar — Horiz Long Labels", chart.to_svg()?));
433    }
434
435    // 3g. Grouped bar (3 groups × 4 quarters)
436    {
437        let cats = vec![
438            "Q1", "Q2", "Q3", "Q4", "Q1", "Q2", "Q3", "Q4", "Q1", "Q2", "Q3", "Q4",
439        ];
440        let groups = vec![
441            "2023", "2023", "2023", "2023", "2024", "2024", "2024", "2024", "2025", "2025", "2025",
442            "2025",
443        ];
444        let vals = vec![
445            10.0, 14.0, 18.0, 12.0, 12.0, 18.0, 22.0, 15.0, 14.0, 20.0, 28.0, 19.0,
446        ];
447        let svg = grouped_bar(&cats, &groups, &vals)
448            .title("Grouped Bar — 3 Years × 4 Quarters")
449            .x_label("Quarter")
450            .y_label("Revenue ($M)")
451            .size(550.0, 380.0)
452            .to_svg()?;
453        sections.push(("Bar — Grouped 3×4", svg));
454    }
455
456    // 3h. Grouped bar — many groups (5 groups)
457    {
458        let mut cats = Vec::new();
459        let mut groups = Vec::new();
460        let mut vals = Vec::new();
461        let regions = ["North", "South", "East", "West", "Central"];
462        let quarters = ["Q1", "Q2", "Q3"];
463        for q in quarters {
464            for (i, r) in regions.iter().enumerate() {
465                cats.push(q);
466                groups.push(*r);
467                vals.push(10.0 + i as f64 * 5.0 + rng.range(0.0, 15.0));
468            }
469        }
470        let svg = grouped_bar(&cats, &groups, &vals)
471            .title("Grouped Bar — 5 Groups (crowded)")
472            .x_label("Quarter")
473            .y_label("Sales ($K)")
474            .size(600.0, 380.0)
475            .to_svg()?;
476        sections.push(("Bar — Grouped 5 Groups", svg));
477    }
478
479    // 3i. Stacked bar
480    {
481        let cats = vec![
482            "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
483        ];
484        let groups = vec![
485            "Online", "Online", "Online", "Online", "Online", "Online", "Retail", "Retail",
486            "Retail", "Retail", "Retail", "Retail",
487        ];
488        let vals = vec![
489            20.0, 25.0, 30.0, 28.0, 35.0, 40.0, 15.0, 12.0, 18.0, 20.0, 16.0, 14.0,
490        ];
491        let svg = stacked_bar(&cats, &groups, &vals)
492            .title("Stacked Bar — Online vs Retail")
493            .x_label("Month")
494            .y_label("Revenue ($K)")
495            .size(550.0, 380.0)
496            .to_svg()?;
497        sections.push(("Bar — Stacked", svg));
498    }
499
500    // 3j. Stacked bar — 4 groups (tall stack)
501    {
502        let mut cats = Vec::new();
503        let mut groups = Vec::new();
504        let mut vals = Vec::new();
505        let products = ["Widget", "Gadget", "Doohickey", "Thingamajig"];
506        let months = ["Jan", "Feb", "Mar", "Apr"];
507        for m in months {
508            for p in products {
509                cats.push(m);
510                groups.push(p);
511                vals.push(rng.range(5.0, 30.0));
512            }
513        }
514        let svg = stacked_bar(&cats, &groups, &vals)
515            .title("Stacked — 4 Product Lines")
516            .x_label("Month")
517            .y_label("Units Sold")
518            .size(500.0, 380.0)
519            .to_svg()?;
520        sections.push(("Bar — Stacked 4 Groups", svg));
521    }
522
523    // 3k. Diverging stack (positive + negative)
524    {
525        let chart = Chart::new()
526            .layer(
527                Layer::new(MarkType::Bar)
528                    .with_x(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0])
529                    .with_y(vec![15.0, 22.0, 18.0, 25.0, 20.0, 28.0])
530                    .with_label("Revenue")
531                    .position(Position::Stack),
532            )
533            .layer(
534                Layer::new(MarkType::Bar)
535                    .with_x(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0])
536                    .with_y(vec![-8.0, -12.0, -6.0, -10.0, -14.0, -9.0])
537                    .with_label("Costs")
538                    .position(Position::Stack),
539            )
540            .layer(
541                Layer::new(MarkType::Bar)
542                    .with_x(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0])
543                    .with_y(vec![-3.0, -4.0, -5.0, -3.0, -2.0, -4.0])
544                    .with_label("Tax")
545                    .position(Position::Stack),
546            )
547            .title("Diverging Stack — Revenue/Costs/Tax")
548            .x_label("Month")
549            .y_label("$M")
550            .size(550.0, 380.0);
551        sections.push(("Bar — Diverging 3 Layers", chart.to_svg()?));
552    }
553
554    // 3l. Bars with values near zero
555    {
556        let svg = bar(&["A", "B", "C", "D", "E"], &[0.1, 0.05, 0.2, 0.01, 0.15])
557            .title("Bars — Very Small Values")
558            .y_label("Rate")
559            .size(450.0, 320.0)
560            .to_svg()?;
561        sections.push(("Bar — Small Values", svg));
562    }
563
564    // 3m. Bars with huge variance
565    {
566        let svg = bar(&["Tiny", "Medium", "Huge"], &[2.0, 50.0, 980.0])
567            .title("Bars — Huge Variance")
568            .y_label("Count")
569            .size(400.0, 320.0)
570            .to_svg()?;
571        sections.push(("Bar — Huge Variance", svg));
572    }
573
574    // ═════════════════════════════════════════════════════════════════
575    //  SECTION 4: HISTOGRAM VARIATIONS
576    // ═════════════════════════════════════════════════════════════════
577
578    // 4a. Normal distribution
579    {
580        let data: Vec<f64> = (0..800).map(|_| rng.normal() * 3.0 + 50.0).collect();
581        let svg = histogram(&data)
582            .bins(30)
583            .title("Histogram — Normal (n=800, 30 bins)")
584            .x_label("Value")
585            .y_label("Frequency")
586            .size(500.0, 350.0)
587            .to_svg()?;
588        sections.push(("Histogram — Normal", svg));
589    }
590
591    // 4b. Bimodal distribution
592    {
593        let mut data: Vec<f64> = (0..400).map(|_| rng.normal() * 2.0 + 30.0).collect();
594        data.extend((0..400).map(|_| rng.normal() * 2.0 + 45.0));
595        let svg = histogram(&data)
596            .bins(35)
597            .title("Histogram — Bimodal")
598            .x_label("Measurement")
599            .y_label("Count")
600            .size(500.0, 350.0)
601            .to_svg()?;
602        sections.push(("Histogram — Bimodal", svg));
603    }
604
605    // 4c. Skewed (exponential-like)
606    {
607        let data: Vec<f64> = (0..600)
608            .map(|_| (-rng.uniform().max(1e-10).ln()) * 5.0)
609            .collect();
610        let svg = histogram(&data)
611            .bins(40)
612            .title("Histogram — Skewed (Exponential)")
613            .x_label("Wait Time (s)")
614            .y_label("Count")
615            .size(500.0, 350.0)
616            .to_svg()?;
617        sections.push(("Histogram — Skewed", svg));
618    }
619
620    // 4d. Few bins
621    {
622        let data: Vec<f64> = (0..200).map(|_| rng.normal() * 5.0 + 100.0).collect();
623        let svg = histogram(&data)
624            .bins(5)
625            .title("Histogram — 5 Bins Only")
626            .x_label("Score")
627            .y_label("Count")
628            .size(450.0, 320.0)
629            .to_svg()?;
630        sections.push(("Histogram — 5 Bins", svg));
631    }
632
633    // 4e. Many bins (tiny bars)
634    {
635        let data: Vec<f64> = (0..500).map(|_| rng.normal() * 2.0).collect();
636        let svg = histogram(&data)
637            .bins(80)
638            .title("Histogram — 80 Bins (sparse)")
639            .x_label("X")
640            .y_label("Count")
641            .size(600.0, 320.0)
642            .to_svg()?;
643        sections.push(("Histogram — 80 Bins", svg));
644    }
645
646    // ═════════════════════════════════════════════════════════════════
647    //  SECTION 5: AREA VARIATIONS
648    // ═════════════════════════════════════════════════════════════════
649
650    // 5a. Basic area
651    {
652        let x: Vec<f64> = (0..50).map(f64::from).collect();
653        let y: Vec<f64> = x
654            .iter()
655            .map(|&v| (v * 0.2).sin().abs() * 30.0 + 5.0 + rng.normal() * 2.0)
656            .collect();
657        let svg = area(&x, &y)
658            .title("Area — Website Traffic")
659            .x_label("Day")
660            .y_label("Visitors (K)")
661            .size(550.0, 350.0)
662            .to_svg()?;
663        sections.push(("Area — Basic", svg));
664    }
665
666    // 5b. Stacked area (2 series)
667    {
668        let x: Vec<f64> = (0..30).map(f64::from).collect();
669        let y1: Vec<f64> = x
670            .iter()
671            .map(|&v| (v * 0.15).sin().abs() * 10.0 + 8.0)
672            .collect();
673        let y2: Vec<f64> = x
674            .iter()
675            .map(|&v| (v * 0.2).cos().abs() * 7.0 + 4.0)
676            .collect();
677        let chart = Chart::new()
678            .layer(
679                Layer::new(MarkType::Area)
680                    .with_x(x.clone())
681                    .with_y(y1)
682                    .with_label("Organic")
683                    .position(Position::Stack),
684            )
685            .layer(
686                Layer::new(MarkType::Area)
687                    .with_x(x)
688                    .with_y(y2)
689                    .with_label("Paid")
690                    .position(Position::Stack),
691            )
692            .title("Stacked Area — Traffic Sources")
693            .x_label("Day")
694            .y_label("Sessions")
695            .size(550.0, 380.0);
696        sections.push(("Area — Stacked 2", chart.to_svg()?));
697    }
698
699    // 5c. Stacked area (4 series)
700    {
701        let x: Vec<f64> = (0..25).map(f64::from).collect();
702        let chart = Chart::new()
703            .layer(
704                Layer::new(MarkType::Area)
705                    .with_x(x.clone())
706                    .with_y(
707                        x.iter()
708                            .map(|&v| (v * 0.3).sin().abs() * 8.0 + 3.0)
709                            .collect(),
710                    )
711                    .with_label("Direct")
712                    .position(Position::Stack),
713            )
714            .layer(
715                Layer::new(MarkType::Area)
716                    .with_x(x.clone())
717                    .with_y(
718                        x.iter()
719                            .map(|&v| (v * 0.2).cos().abs() * 6.0 + 2.0)
720                            .collect(),
721                    )
722                    .with_label("Social")
723                    .position(Position::Stack),
724            )
725            .layer(
726                Layer::new(MarkType::Area)
727                    .with_x(x.clone())
728                    .with_y(
729                        x.iter()
730                            .map(|&v| (v * 0.15).sin().abs() * 4.0 + 1.5)
731                            .collect(),
732                    )
733                    .with_label("Email")
734                    .position(Position::Stack),
735            )
736            .layer(
737                Layer::new(MarkType::Area)
738                    .with_x(x.clone())
739                    .with_y(
740                        x.iter()
741                            .map(|&v| (v * 0.25).cos().abs() * 5.0 + 2.0)
742                            .collect(),
743                    )
744                    .with_label("Referral")
745                    .position(Position::Stack),
746            )
747            .title("Stacked Area — 4 Channels")
748            .x_label("Week")
749            .y_label("Visits (K)")
750            .size(600.0, 400.0);
751        sections.push(("Area — Stacked 4", chart.to_svg()?));
752    }
753
754    // ═════════════════════════════════════════════════════════════════
755    //  SECTION 6: PIE / DONUT VARIATIONS
756    // ═════════════════════════════════════════════════════════════════
757
758    // 6a. Basic pie (5 slices)
759    {
760        let svg = pie_labeled(
761            &["Chrome", "Firefox", "Safari", "Edge", "Other"],
762            &[35.0, 25.0, 20.0, 12.0, 8.0],
763        )
764        .title("Browser Market Share")
765        .size(400.0, 400.0)
766        .to_svg()?;
767        sections.push(("Pie — 5 Slices", svg));
768    }
769
770    // 6b. Pie with many slices (10)
771    {
772        let vals: Vec<f64> = (0..10).map(|i| 20.0 - f64::from(i) * 1.5).collect();
773        let labels: Vec<String> = (0..10).map(|i| format!("Slice {}", i + 1)).collect();
774        let label_refs: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
775        let svg = pie_labeled(&label_refs, &vals)
776            .title("Pie — 10 Slices")
777            .size(450.0, 450.0)
778            .to_svg()?;
779        sections.push(("Pie — 10 Slices", svg));
780    }
781
782    // 6c. Pie with one dominant slice
783    {
784        let svg = pie_labeled(
785            &["Dominant", "Small-A", "Small-B", "Tiny"],
786            &[90.0, 5.0, 3.0, 2.0],
787        )
788        .title("Pie — One Dominant (90%)")
789        .size(400.0, 400.0)
790        .to_svg()?;
791        sections.push(("Pie — Dominant Slice", svg));
792    }
793
794    // 6d. Equal slices
795    {
796        let svg = pie_labeled(
797            &["North", "South", "East", "West"],
798            &[25.0, 25.0, 25.0, 25.0],
799        )
800        .title("Pie — Equal Slices")
801        .size(400.0, 400.0)
802        .to_svg()?;
803        sections.push(("Pie — Equal", svg));
804    }
805
806    // 6e. Donut variants
807    {
808        let svg = pie_labeled(&["Pass", "Warn", "Fail"], &[60.0, 25.0, 15.0])
809            .donut(0.55)
810            .title("Donut — 55% hole")
811            .size(380.0, 380.0)
812            .to_svg()?;
813        sections.push(("Donut — 55%", svg));
814    }
815    {
816        let svg = pie_labeled(&["A", "B", "C", "D"], &[40.0, 30.0, 20.0, 10.0])
817            .donut(0.8)
818            .title("Donut — 80% hole (thin ring)")
819            .size(380.0, 380.0)
820            .to_svg()?;
821        sections.push(("Donut — 80% (thin)", svg));
822    }
823
824    // ═════════════════════════════════════════════════════════════════
825    //  SECTION 7: BOX PLOT VARIATIONS
826    // ═════════════════════════════════════════════════════════════════
827
828    // 7a. 3 groups
829    {
830        let mut cats = Vec::new();
831        let mut vals = Vec::new();
832        for (label, center, spread) in &[
833            ("Control", 50.0, 10.0),
834            ("Drug A", 65.0, 8.0),
835            ("Drug B", 70.0, 15.0),
836        ] {
837            for _ in 0..50 {
838                vals.push(center + rng.normal() * spread);
839                cats.push(*label);
840            }
841            vals.push(center + spread * 4.0); // outlier
842            cats.push(*label);
843        }
844        let svg = boxplot(&cats, &vals)
845            .title("Box Plot — Clinical Trial")
846            .x_label("Treatment")
847            .y_label("Response")
848            .size(500.0, 380.0)
849            .to_svg()?;
850        sections.push(("Box Plot — 3 Groups", svg));
851    }
852
853    // 7b. Many groups (7)
854    {
855        let mut cats = Vec::new();
856        let mut vals = Vec::new();
857        let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
858        for (i, day) in days.iter().enumerate() {
859            let center = 40.0 + (i as f64 * 0.9).sin() * 15.0;
860            let spread = 5.0 + i as f64 * 1.5;
861            for _ in 0..30 {
862                vals.push(center + rng.normal() * spread);
863                cats.push(*day);
864            }
865        }
866        let svg = boxplot(&cats, &vals)
867            .title("Box Plot — Daily Response Times")
868            .x_label("Day of Week")
869            .y_label("Time (ms)")
870            .size(600.0, 380.0)
871            .to_svg()?;
872        sections.push(("Box Plot — 7 Groups", svg));
873    }
874
875    // 7c. Box plot with tight distributions (minimal spread)
876    {
877        let mut cats = Vec::new();
878        let mut vals = Vec::new();
879        for (label, center) in &[("Batch 1", 100.0), ("Batch 2", 100.5), ("Batch 3", 99.8)] {
880            for _ in 0..40 {
881                vals.push(center + rng.normal() * 0.3);
882                cats.push(*label);
883            }
884        }
885        let svg = boxplot(&cats, &vals)
886            .title("Box Plot — Tight Distributions")
887            .x_label("Batch")
888            .y_label("Weight (g)")
889            .size(450.0, 350.0)
890            .to_svg()?;
891        sections.push(("Box Plot — Tight", svg));
892    }
893
894    // ═════════════════════════════════════════════════════════════════
895    //  SECTION 8: HEATMAP VARIATIONS
896    // ═════════════════════════════════════════════════════════════════
897
898    // 8a. Small heatmap with annotations
899    {
900        let data = vec![
901            vec![1.0, 5.0, 9.0],
902            vec![2.0, 6.0, 7.0],
903            vec![3.0, 4.0, 8.0],
904        ];
905        let svg = heatmap(data)
906            .annotate()
907            .with_row_labels(&["X", "Y", "Z"])
908            .with_col_labels(&["A", "B", "C"])
909            .title("Heatmap — 3×3 Annotated")
910            .x_label("Feature")
911            .y_label("Sample")
912            .size(350.0, 350.0)
913            .to_svg()?;
914        sections.push(("Heatmap — 3×3", svg));
915    }
916
917    // 8b. Large heatmap (8×8)
918    {
919        let data: Vec<Vec<f64>> = (0..8)
920            .map(|r| {
921                (0..8)
922                    .map(|c| {
923                        ((f64::from(r) * 0.5 + f64::from(c) * 0.3).sin() * 50.0 + 50.0).round()
924                    })
925                    .collect()
926            })
927            .collect();
928        let rows: Vec<String> = (0..8).map(|i| format!("Gene {}", i + 1)).collect();
929        let cols: Vec<String> = (0..8).map(|i| format!("Sample {}", i + 1)).collect();
930        let svg = heatmap(data)
931            .annotate()
932            .with_row_labels(&rows)
933            .with_col_labels(&cols)
934            .title("Heatmap — 8×8 Gene Expression")
935            .x_label("Sample")
936            .y_label("Gene")
937            .size(600.0, 500.0)
938            .to_svg()?;
939        sections.push(("Heatmap — 8×8", svg));
940    }
941
942    // 8c. Confusion matrix (4×4)
943    {
944        let data = vec![
945            vec![85.0, 3.0, 1.0, 2.0],
946            vec![2.0, 78.0, 5.0, 1.0],
947            vec![0.0, 4.0, 90.0, 3.0],
948            vec![1.0, 2.0, 3.0, 82.0],
949        ];
950        let labels: Vec<String> = vec!["Cat".into(), "Dog".into(), "Bird".into(), "Fish".into()];
951        let svg = heatmap(data)
952            .annotate()
953            .with_row_labels(&labels)
954            .with_col_labels(&labels)
955            .title("Confusion Matrix — 4 Classes")
956            .x_label("Predicted")
957            .y_label("Actual")
958            .size(450.0, 450.0)
959            .to_svg()?;
960        sections.push(("Heatmap — Confusion 4×4", svg));
961    }
962
963    // 8d. Heatmap with RdBu color scale
964    {
965        let data = vec![
966            vec![-1.0, -0.5, 0.0, 0.5, 1.0],
967            vec![0.5, -0.3, 0.8, -0.7, 0.2],
968            vec![0.1, 0.9, -0.8, 0.3, -0.4],
969        ];
970        let mut theme = NewTheme::light();
971        theme.color_scale = Some(ColorScale::rdbu());
972        let svg = heatmap(data)
973            .annotate()
974            .title("Heatmap — RdBu Diverging Scale")
975            .theme(theme)
976            .size(450.0, 350.0)
977            .to_svg()?;
978        sections.push(("Heatmap — RdBu", svg));
979    }
980
981    // 8e. Heatmap no annotations (just gradient)
982    {
983        let data: Vec<Vec<f64>> = (0..6)
984            .map(|r| (0..10).map(|c| f64::from(r * 10 + c)).collect())
985            .collect();
986        let svg = heatmap(data)
987            .title("Heatmap — 6×10 No Annotations")
988            .x_label("Time")
989            .y_label("Sensor")
990            .size(600.0, 400.0)
991            .to_svg()?;
992        sections.push(("Heatmap — Plain 6×10", svg));
993    }
994
995    // ═════════════════════════════════════════════════════════════════
996    //  SECTION 9: FACETED CHARTS
997    // ═════════════════════════════════════════════════════════════════
998
999    // 9a. Faceted scatter — 4 panels
1000    {
1001        let mut x = Vec::new();
1002        let mut y = Vec::new();
1003        let mut facets = Vec::new();
1004        for panel in &["Q1 2025", "Q2 2025", "Q3 2025", "Q4 2025"] {
1005            for _ in 0..30 {
1006                x.push(rng.range(0.0, 100.0));
1007                y.push(rng.range(0.0, 100.0));
1008                facets.push(*panel);
1009            }
1010        }
1011        let svg = scatter(&x, &y)
1012            .facet_wrap(&facets, 2)
1013            .title("Faceted Scatter — Quarterly")
1014            .x_label("Impressions")
1015            .y_label("Clicks")
1016            .size(600.0, 500.0)
1017            .to_svg()?;
1018        sections.push(("Facet — Scatter 2×2", svg));
1019    }
1020
1021    // 9b. Faceted scatter with categories + legend
1022    {
1023        let mut x = Vec::new();
1024        let mut y = Vec::new();
1025        let mut cats = Vec::new();
1026        let mut facets = Vec::new();
1027        for region in &["US", "EU", "APAC"] {
1028            for segment in &["Enterprise", "SMB", "Consumer"] {
1029                for _ in 0..10 {
1030                    x.push(rng.range(10.0, 90.0));
1031                    y.push(rng.range(10.0, 90.0));
1032                    cats.push(*segment);
1033                    facets.push(*region);
1034                }
1035            }
1036        }
1037        let chart = Chart::new()
1038            .layer(
1039                Layer::new(MarkType::Point)
1040                    .with_x(x)
1041                    .with_y(y)
1042                    .with_categories(cats.iter().map(|s| s.to_string()).collect())
1043                    .with_facet_values(facets.iter().map(|s| s.to_string()).collect()),
1044            )
1045            .facet(Facet::Wrap { ncol: 3 })
1046            .title("Faceted — Region × Segment")
1047            .x_label("Deal Size ($K)")
1048            .y_label("Win Rate (%)")
1049            .size(700.0, 350.0);
1050        sections.push(("Facet — Categories+Legend", chart.to_svg()?));
1051    }
1052
1053    // 9c. Faceted scatter — FreeY scales
1054    {
1055        let mut x = Vec::new();
1056        let mut y = Vec::new();
1057        let mut facets = Vec::new();
1058        // Panel A: small y range
1059        for _ in 0..25 {
1060            x.push(rng.range(0.0, 10.0));
1061            y.push(rng.range(0.0, 5.0));
1062            facets.push("Small Range");
1063        }
1064        // Panel B: large y range
1065        for _ in 0..25 {
1066            x.push(rng.range(0.0, 10.0));
1067            y.push(rng.range(0.0, 500.0));
1068            facets.push("Large Range");
1069        }
1070        let chart = Chart::new()
1071            .layer(
1072                Layer::new(MarkType::Point)
1073                    .with_x(x)
1074                    .with_y(y)
1075                    .with_facet_values(facets.iter().map(|s| s.to_string()).collect()),
1076            )
1077            .facet(Facet::Wrap { ncol: 2 })
1078            .facet_scales(FacetScales::FreeY)
1079            .title("Faceted — FreeY (different Y scales)")
1080            .x_label("X")
1081            .y_label("Y")
1082            .size(600.0, 350.0);
1083        sections.push(("Facet — FreeY", chart.to_svg()?));
1084    }
1085
1086    // 9d. Faceted with FreeX
1087    {
1088        let mut x = Vec::new();
1089        let mut y = Vec::new();
1090        let mut facets = Vec::new();
1091        for _ in 0..25 {
1092            x.push(rng.range(0.0, 10.0));
1093            y.push(rng.range(0.0, 50.0));
1094            facets.push("Narrow X");
1095        }
1096        for _ in 0..25 {
1097            x.push(rng.range(0.0, 1000.0));
1098            y.push(rng.range(0.0, 50.0));
1099            facets.push("Wide X");
1100        }
1101        let chart = Chart::new()
1102            .layer(
1103                Layer::new(MarkType::Point)
1104                    .with_x(x)
1105                    .with_y(y)
1106                    .with_facet_values(facets.iter().map(|s| s.to_string()).collect()),
1107            )
1108            .facet(Facet::Wrap { ncol: 2 })
1109            .facet_scales(FacetScales::FreeX)
1110            .title("Faceted — FreeX (different X scales)")
1111            .x_label("X")
1112            .y_label("Y")
1113            .size(600.0, 350.0);
1114        sections.push(("Facet — FreeX", chart.to_svg()?));
1115    }
1116
1117    // 9e. Faceted with Free (both axes)
1118    {
1119        let mut x = Vec::new();
1120        let mut y = Vec::new();
1121        let mut facets = Vec::new();
1122        for _ in 0..20 {
1123            x.push(rng.range(0.0, 5.0));
1124            y.push(rng.range(0.0, 5.0));
1125            facets.push("Small");
1126        }
1127        for _ in 0..20 {
1128            x.push(rng.range(100.0, 200.0));
1129            y.push(rng.range(1000.0, 2000.0));
1130            facets.push("Large");
1131        }
1132        let chart = Chart::new()
1133            .layer(
1134                Layer::new(MarkType::Point)
1135                    .with_x(x)
1136                    .with_y(y)
1137                    .with_facet_values(facets.iter().map(|s| s.to_string()).collect()),
1138            )
1139            .facet(Facet::Wrap { ncol: 2 })
1140            .facet_scales(FacetScales::Free)
1141            .title("Faceted — Free (both axes independent)")
1142            .x_label("X")
1143            .y_label("Y")
1144            .size(600.0, 350.0);
1145        sections.push(("Facet — Free Both", chart.to_svg()?));
1146    }
1147
1148    // 9f. 6 panels (3 cols)
1149    {
1150        let mut x = Vec::new();
1151        let mut y = Vec::new();
1152        let mut facets = Vec::new();
1153        let panels = ["Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta"];
1154        for panel in panels {
1155            for _ in 0..15 {
1156                x.push(rng.range(0.0, 10.0));
1157                y.push(rng.range(0.0, 10.0));
1158                facets.push(panel);
1159            }
1160        }
1161        let svg = scatter(&x, &y)
1162            .facet_wrap(&facets, 3)
1163            .title("6 Panels (3 cols)")
1164            .x_label("X")
1165            .y_label("Y")
1166            .size(700.0, 500.0)
1167            .to_svg()?;
1168        sections.push(("Facet — 6 Panels", svg));
1169    }
1170
1171    // ═════════════════════════════════════════════════════════════════
1172    //  SECTION 10: THEME VARIATIONS
1173    // ═════════════════════════════════════════════════════════════════
1174
1175    // 10a. Dark theme — scatter
1176    {
1177        let x: Vec<f64> = (0..40).map(|_| rng.range(0.0, 100.0)).collect();
1178        let y: Vec<f64> = x.iter().map(|&xi| xi * 0.7 + rng.normal() * 10.0).collect();
1179        let svg = scatter(&x, &y)
1180            .title("Dark Theme — Scatter")
1181            .x_label("X")
1182            .y_label("Y")
1183            .theme(NewTheme::dark())
1184            .size(500.0, 380.0)
1185            .to_svg()?;
1186        sections.push(("Theme — Dark Scatter", svg));
1187    }
1188
1189    // 10b. Dark theme — multi-line
1190    {
1191        let x: Vec<f64> = (0..30).map(f64::from).collect();
1192        let chart = Chart::new()
1193            .layer(
1194                Layer::new(MarkType::Line)
1195                    .with_x(x.clone())
1196                    .with_y(x.iter().map(|&v| (v * 0.3).sin() * 5.0 + 10.0).collect())
1197                    .with_label("CPU"),
1198            )
1199            .layer(
1200                Layer::new(MarkType::Line)
1201                    .with_x(x.clone())
1202                    .with_y(x.iter().map(|&v| (v * 0.2).cos() * 4.0 + 8.0).collect())
1203                    .with_label("Memory"),
1204            )
1205            .layer(
1206                Layer::new(MarkType::Line)
1207                    .with_x(x.clone())
1208                    .with_y(x.iter().map(|&v| v * 0.3 + 2.0).collect())
1209                    .with_label("Disk"),
1210            )
1211            .title("Dark Theme — System Monitor")
1212            .x_label("Time (s)")
1213            .y_label("Usage (%)")
1214            .theme(NewTheme::dark())
1215            .size(550.0, 380.0);
1216        sections.push(("Theme — Dark Lines", chart.to_svg()?));
1217    }
1218
1219    // 10c. Dark theme — bar
1220    {
1221        let svg = bar(
1222            &["Mon", "Tue", "Wed", "Thu", "Fri"],
1223            &[120.0, 95.0, 150.0, 88.0, 110.0],
1224        )
1225        .title("Dark Theme — Bars")
1226        .x_label("Day")
1227        .y_label("Tickets Closed")
1228        .theme(NewTheme::dark())
1229        .size(500.0, 350.0)
1230        .to_svg()?;
1231        sections.push(("Theme — Dark Bar", svg));
1232    }
1233
1234    // 10d. Dark theme — area
1235    {
1236        let x: Vec<f64> = (0..30).map(f64::from).collect();
1237        let y: Vec<f64> = x
1238            .iter()
1239            .map(|&v| (v * 0.2).sin().abs() * 20.0 + 5.0)
1240            .collect();
1241        let svg = area(&x, &y)
1242            .title("Dark Theme — Area")
1243            .x_label("Day")
1244            .y_label("Events")
1245            .theme(NewTheme::dark())
1246            .size(500.0, 350.0)
1247            .to_svg()?;
1248        sections.push(("Theme — Dark Area", svg));
1249    }
1250
1251    // 10e. Publication theme — scatter
1252    {
1253        let x: Vec<f64> = (0..30).map(|_| rng.range(0.0, 10.0)).collect();
1254        let y: Vec<f64> = x.iter().map(|&xi| xi * 1.2 + rng.normal() * 1.5).collect();
1255        let svg = scatter(&x, &y)
1256            .title("Publication Theme")
1257            .x_label("Independent Variable")
1258            .y_label("Dependent Variable")
1259            .theme(NewTheme::publication())
1260            .size(500.0, 380.0)
1261            .to_svg()?;
1262        sections.push(("Theme — Publication", svg));
1263    }
1264
1265    // 10f. Custom theme — big fonts
1266    {
1267        let mut theme = NewTheme::light();
1268        theme.title_font_size = 22.0;
1269        theme.label_font_size = 16.0;
1270        theme.tick_font_size = 14.0;
1271        theme.line_width = 3.0;
1272        theme.point_size = 8.0;
1273        theme.grid_width = 1.5;
1274
1275        let x: Vec<f64> = (0..10).map(f64::from).collect();
1276        let y: Vec<f64> = vec![3.0, 5.0, 4.0, 7.0, 6.0, 9.0, 8.0, 10.0, 9.5, 12.0];
1277        let svg = scatter(&x, &y)
1278            .title("Custom Theme — Big Fonts")
1279            .x_label("Week")
1280            .y_label("Score")
1281            .theme(theme)
1282            .size(500.0, 400.0)
1283            .to_svg()?;
1284        sections.push(("Theme — Big Fonts", svg));
1285    }
1286
1287    // 10g. Custom theme — no grid
1288    {
1289        let mut theme = NewTheme::light();
1290        theme.show_grid = false;
1291
1292        let svg = bar(&["A", "B", "C", "D"], &[30.0, 45.0, 25.0, 55.0])
1293            .title("Custom — No Grid")
1294            .y_label("Value")
1295            .theme(theme)
1296            .size(400.0, 320.0)
1297            .to_svg()?;
1298        sections.push(("Theme — No Grid", svg));
1299    }
1300
1301    // 10h. Custom palette
1302    {
1303        let mut theme = NewTheme::light();
1304        theme.palette = Palette::diverging(
1305            Color::from_hex("#e74c3c").unwrap(),
1306            Color::from_hex("#f1c40f").unwrap(),
1307            Color::from_hex("#2ecc71").unwrap(),
1308            6,
1309        );
1310
1311        let mut x = Vec::new();
1312        let mut y = Vec::new();
1313        let mut cats = Vec::new();
1314        for (i, name) in ["Bad", "Poor", "Ok", "Good", "Great", "Excellent"]
1315            .iter()
1316            .enumerate()
1317        {
1318            for _ in 0..10 {
1319                x.push(i as f64 + rng.normal() * 0.3);
1320                y.push(rng.range(0.0, 10.0));
1321                cats.push(*name);
1322            }
1323        }
1324        let svg = scatter(&x, &y)
1325            .color_by(&cats)
1326            .title("Custom Diverging Palette")
1327            .x_label("Rating")
1328            .y_label("Score")
1329            .theme(theme)
1330            .size(550.0, 380.0)
1331            .to_svg()?;
1332        sections.push(("Theme — Custom Palette", svg));
1333    }
1334
1335    // ═════════════════════════════════════════════════════════════════
1336    //  SECTION 11: EDGE CASES & REALISTIC SCENARIOS
1337    // ═════════════════════════════════════════════════════════════════
1338
1339    // 11a. All negative values
1340    {
1341        let svg = bar(&["Loss A", "Loss B", "Loss C"], &[-15.0, -30.0, -22.0])
1342            .title("All Negative Values")
1343            .y_label("P&L ($K)")
1344            .size(400.0, 320.0)
1345            .to_svg()?;
1346        sections.push(("Edge — All Negative", svg));
1347    }
1348
1349    // 11b. Mixed positive/negative bar (non-stacked)
1350    {
1351        let svg = bar(
1352            &["Jan", "Feb", "Mar", "Apr", "May"],
1353            &[10.0, -5.0, 15.0, -3.0, 8.0],
1354        )
1355        .title("Mixed +/- Bar (no stack)")
1356        .y_label("Net Change")
1357        .size(450.0, 320.0)
1358        .to_svg()?;
1359        sections.push(("Edge — Mixed +/-", svg));
1360    }
1361
1362    // 11c. Very wide chart
1363    {
1364        let x: Vec<f64> = (0..100).map(f64::from).collect();
1365        let y: Vec<f64> = x.iter().map(|&v| (v * 0.1).sin() * 10.0 + 50.0).collect();
1366        let svg = line(&x, &y)
1367            .title("Very Wide Chart (900×200)")
1368            .x_label("Sample")
1369            .y_label("Value")
1370            .size(900.0, 200.0)
1371            .to_svg()?;
1372        sections.push(("Edge — Very Wide", svg));
1373    }
1374
1375    // 11d. Very tall chart
1376    {
1377        let svg = bar(&["A", "B", "C"], &[100.0, 200.0, 150.0])
1378            .title("Very Tall (300×600)")
1379            .y_label("Val")
1380            .size(300.0, 600.0)
1381            .to_svg()?;
1382        sections.push(("Edge — Very Tall", svg));
1383    }
1384
1385    // 11e. Tiny chart
1386    {
1387        let svg = scatter(&[1.0, 2.0, 3.0], &[1.0, 4.0, 2.0])
1388            .title("Tiny (200×150)")
1389            .size(200.0, 150.0)
1390            .to_svg()?;
1391        sections.push(("Edge — Tiny", svg));
1392    }
1393
1394    // 11f. Very long title
1395    {
1396        let svg = scatter(&[1.0, 2.0, 3.0], &[1.0, 4.0, 2.0])
1397            .title("This Is an Extremely Long Title That Might Overflow the Chart Boundary — How Does It Look?")
1398            .x_label("X Axis With A Somewhat Long Label Too")
1399            .y_label("Y Label")
1400            .size(500.0, 380.0)
1401            .to_svg()?;
1402        sections.push(("Edge — Long Title", svg));
1403    }
1404
1405    // 11g. Scatter with identical Y values (flat line)
1406    {
1407        let x: Vec<f64> = (0..10).map(f64::from).collect();
1408        let y = vec![5.0; 10];
1409        let svg = scatter(&x, &y)
1410            .title("Flat — All Y=5")
1411            .x_label("X")
1412            .y_label("Y")
1413            .size(400.0, 300.0)
1414            .to_svg()?;
1415        sections.push(("Edge — Flat Y", svg));
1416    }
1417
1418    // 11h. Aggregate stat — mean
1419    {
1420        let x = vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0];
1421        let y = vec![10.0, 12.0, 11.0, 20.0, 22.0, 18.0, 15.0, 17.0, 16.0];
1422        let cats: Vec<String> = vec!["A", "A", "A", "B", "B", "B", "C", "C", "C"]
1423            .into_iter()
1424            .map(String::from)
1425            .collect();
1426        let chart = Chart::new()
1427            .layer(
1428                Layer::new(MarkType::Bar)
1429                    .with_x(x)
1430                    .with_y(y)
1431                    .with_categories(cats)
1432                    .stat(Stat::Aggregate {
1433                        func: AggregateFunc::Mean,
1434                    }),
1435            )
1436            .title("Aggregate — Mean per Category")
1437            .x_label("Category")
1438            .y_label("Mean Value")
1439            .size(450.0, 350.0);
1440        sections.push(("Stat — Aggregate Mean", chart.to_svg()?));
1441    }
1442
1443    // 11i. Fill position (100% stacked)
1444    {
1445        let chart = Chart::new()
1446            .layer(
1447                Layer::new(MarkType::Bar)
1448                    .with_x(vec![0.0, 1.0, 2.0])
1449                    .with_y(vec![30.0, 40.0, 25.0])
1450                    .with_label("TypeA")
1451                    .position(Position::Fill),
1452            )
1453            .layer(
1454                Layer::new(MarkType::Bar)
1455                    .with_x(vec![0.0, 1.0, 2.0])
1456                    .with_y(vec![70.0, 60.0, 75.0])
1457                    .with_label("TypeB")
1458                    .position(Position::Fill),
1459            )
1460            .title("100% Stacked (Fill Position)")
1461            .x_label("Group")
1462            .y_label("Proportion")
1463            .size(450.0, 350.0);
1464        sections.push(("Position — Fill", chart.to_svg()?));
1465    }
1466
1467    // 11j. Line + Area combo (confidence band look)
1468    {
1469        let x: Vec<f64> = (0..30).map(f64::from).collect();
1470        let y_main: Vec<f64> = x
1471            .iter()
1472            .map(|&v| v * 0.5 + 10.0 + (v * 0.3).sin() * 3.0)
1473            .collect();
1474        let y_area: Vec<f64> = x
1475            .iter()
1476            .map(|&v| v * 0.5 + 10.0 + (v * 0.3).sin() * 3.0 + 5.0)
1477            .collect();
1478        let chart = Chart::new()
1479            .layer(
1480                Layer::new(MarkType::Area)
1481                    .with_x(x.clone())
1482                    .with_y(y_area)
1483                    .with_label("Upper Bound"),
1484            )
1485            .layer(
1486                Layer::new(MarkType::Line)
1487                    .with_x(x.clone())
1488                    .with_y(y_main.clone())
1489                    .with_label("Forecast"),
1490            )
1491            .layer(
1492                Layer::new(MarkType::Point)
1493                    .with_x(x)
1494                    .with_y(y_main)
1495                    .with_label("Data"),
1496            )
1497            .title("Forecast with Confidence Band")
1498            .x_label("Day")
1499            .y_label("Metric")
1500            .size(550.0, 380.0);
1501        sections.push(("Combo — Area+Line+Point", chart.to_svg()?));
1502    }
1503
1504    // ═════════════════════════════════════════════════════════════════
1505    //  BUILD HTML
1506    // ═════════════════════════════════════════════════════════════════
1507
1508    let mut html = String::from(
1509        r#"<!DOCTYPE html>
1510<html lang="en">
1511<head>
1512<meta charset="UTF-8">
1513<meta name="viewport" content="width=device-width, initial-scale=1.0">
1514<title>esoc-chart Stress Test</title>
1515<style>
1516  * { margin: 0; padding: 0; box-sizing: border-box; }
1517  body { font-family: system-ui, -apple-system, sans-serif; background: #f0f0f4; color: #333; }
1518  header { background: linear-gradient(135deg, #2d1b69, #11998e); color: white; padding: 2.5rem 2rem; text-align: center; }
1519  header h1 { font-size: 2rem; font-weight: 300; letter-spacing: 0.02em; }
1520  header p { margin-top: 0.5rem; opacity: 0.7; font-size: 0.95rem; }
1521  .stats { display: flex; justify-content: center; gap: 2rem; margin-top: 1rem; flex-wrap: wrap; }
1522  .stats span { background: rgba(255,255,255,0.15); padding: 0.3rem 0.8rem; border-radius: 4px; font-size: 0.85rem; }
1523  .section-title { font-size: 1.2rem; font-weight: 600; color: #555; padding: 1.5rem 2rem 0.5rem; max-width: 1600px; margin: 0 auto; border-top: 2px solid #ddd; margin-top: 1rem; }
1524  .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(480px, 1fr)); gap: 1.5rem; padding: 1rem 2rem; max-width: 1600px; margin: 0 auto; }
1525  .card { background: white; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.06); overflow: hidden; transition: box-shadow 0.2s; }
1526  .card:hover { box-shadow: 0 4px 20px rgba(0,0,0,0.12); }
1527  .card h2 { font-size: 0.85rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: #555; padding: 1rem 1.5rem 0; }
1528  .card .chart-wrap { padding: 0.5rem 1rem 0.75rem; }
1529  .card svg { display: block; width: 100%; height: auto; }
1530  .card.dark-bg .chart-wrap { background: #1e1e2e; border-radius: 0 0 8px 8px; }
1531  .feedback { padding: 0 1rem 1rem; }
1532  .feedback textarea { width: 100%; min-height: 50px; border: 1px solid #e0e0e0; border-radius: 4px; padding: 0.5rem; font-family: inherit; font-size: 0.82rem; resize: vertical; }
1533  .feedback textarea:focus { outline: none; border-color: #2d1b69; }
1534  .feedback .status { font-size: 0.72rem; color: #aaa; margin-top: 0.2rem; }
1535  .actions { padding: 1.5rem 2rem; text-align: center; }
1536  .actions button { background: #2d1b69; color: white; border: none; border-radius: 4px; padding: 0.6rem 1.5rem; font-size: 0.9rem; cursor: pointer; margin: 0 0.5rem; }
1537  .actions button:hover { background: #3d2b79; }
1538</style>
1539<script>
1540  const feedback = {};
1541  function loadFeedback() {
1542    try { Object.assign(feedback, JSON.parse(localStorage.getItem('stress_test_feedback') || '{}')); } catch {}
1543    document.querySelectorAll('.feedback textarea').forEach(ta => {
1544      const key = ta.dataset.chart;
1545      if (feedback[key]) ta.value = feedback[key];
1546    });
1547  }
1548  function saveFeedback(key, value) {
1549    feedback[key] = value;
1550    localStorage.setItem('stress_test_feedback', JSON.stringify(feedback));
1551  }
1552  function exportFeedback() {
1553    const blob = new Blob([JSON.stringify(feedback, null, 2)], {type: 'application/json'});
1554    const a = document.createElement('a'); a.href = URL.createObjectURL(blob);
1555    a.download = 'stress_test_feedback.json'; a.click();
1556  }
1557  window.addEventListener('DOMContentLoaded', loadFeedback);
1558</script>
1559</head>
1560<body>
1561<header>
1562  <h1>esoc-chart Stress Test</h1>
1563  <p>Comprehensive exercise of every chart type, option, theme, position, stat, and edge case</p>
1564  <div class="stats">
1565"#,
1566    );
1567
1568    writeln!(html, "    <span>{} charts</span>", sections.len()).unwrap();
1569    html.push_str("    <span>11 categories</span>\n");
1570    html.push_str("    <span>3 themes</span>\n");
1571    html.push_str("    <span>5 positions</span>\n");
1572    html.push_str("    <span>4 facet scale modes</span>\n");
1573    html.push_str("  </div>\n</header>\n");
1574
1575    for (title, svg) in &sections {
1576        let key = title
1577            .to_lowercase()
1578            .replace([' ', '–', '—', '+', '/', '(', ')', '%'], "_")
1579            .replace("__", "_");
1580        let dark_class = if title.contains("Dark") {
1581            " dark-bg"
1582        } else {
1583            ""
1584        };
1585        html.push_str("<div class=\"grid\">\n");
1586        write!(
1587            html,
1588            concat!(
1589                "<div class=\"card{dark_class}\">\n",
1590                "  <h2>{title}</h2>\n",
1591                "  <div class=\"chart-wrap\">{svg}</div>\n",
1592                "  <div class=\"feedback\">\n",
1593                "    <textarea data-chart=\"{key}\" placeholder=\"Notes on {title}…\" ",
1594                "oninput=\"saveFeedback('{key}', this.value)\"></textarea>\n",
1595                "    <div class=\"status\">Auto-saved</div>\n",
1596                "  </div>\n",
1597                "</div>\n",
1598            ),
1599            title = title,
1600            svg = svg,
1601            key = key,
1602            dark_class = dark_class,
1603        )
1604        .unwrap();
1605        html.push_str("</div>\n");
1606    }
1607
1608    html.push_str(concat!(
1609        "<div class=\"actions\">\n",
1610        "  <button onclick=\"exportFeedback()\">Export Feedback JSON</button>\n",
1611        "</div>\n",
1612        "</body>\n</html>\n",
1613    ));
1614
1615    let out_path = "stress_test.html";
1616    std::fs::write(out_path, &html).expect("failed to write HTML");
1617    println!("Saved {} ({} charts)", out_path, sections.len());
1618
1619    Ok(())
1620}