1use 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 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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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); 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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
1055 let mut x = Vec::new();
1056 let mut y = Vec::new();
1057 let mut facets = Vec::new();
1058 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 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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 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 §ions {
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}