Skip to main content

readme_charts/
readme_charts.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Generate SVG images for the README.
3
4use esoc_chart::express::{
5    area, bar, boxplot, grouped_bar, heatmap, histogram, line, pie_labeled, scatter, stacked_bar,
6    treemap,
7};
8use esoc_chart::grammar::annotation::Annotation;
9use esoc_chart::grammar::chart::Chart;
10use esoc_chart::grammar::coord::CoordSystem;
11use esoc_chart::grammar::layer::{Layer, MarkType};
12use esoc_chart::grammar::stat::Stat;
13use esoc_chart::new_theme::NewTheme;
14
15fn main() -> esoc_chart::error::Result<()> {
16    // Simple deterministic RNG
17    struct Rng(u64);
18    impl Rng {
19        fn uniform(&mut self) -> f64 {
20            self.0 = self
21                .0
22                .wrapping_mul(6_364_136_223_846_793_005)
23                .wrapping_add(1);
24            (self.0 >> 11) as f64 / (1u64 << 53) as f64
25        }
26        fn normal(&mut self) -> f64 {
27            let u1 = self.uniform().max(1e-15);
28            let u2 = self.uniform();
29            (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
30        }
31    }
32    let mut rng = Rng(42);
33
34    let dir = "crates/esoc-chart/images";
35    std::fs::create_dir_all(dir).unwrap();
36
37    // ── 1. Scatter ──────────────────────────────────────────────────
38    {
39        let n = 80;
40        let x: Vec<f64> = (0..n).map(|_| rng.uniform() * 10.0).collect();
41        let y: Vec<f64> = x
42            .iter()
43            .map(|&xi| 0.4 * xi * xi - 2.0 * xi + 3.0 + rng.normal() * 2.0)
44            .collect();
45        scatter(&x, &y)
46            .title("Quadratic Trend")
47            .x_label("x")
48            .y_label("y")
49            .size(560.0, 380.0)
50            .save_svg(format!("{dir}/scatter.svg"))?;
51    }
52
53    // ── 2. Scatter with categories ──────────────────────────────────
54    {
55        let mut x = Vec::new();
56        let mut y = Vec::new();
57        let mut cats = Vec::new();
58        for (label, cx, cy) in [("Setosa", 5.0, 3.4), ("Versicolor", 5.9, 2.8), ("Virginica", 6.6, 3.0)] {
59            for _ in 0..40 {
60                x.push(cx + rng.normal() * 0.4);
61                y.push(cy + rng.normal() * 0.3);
62                cats.push(label);
63            }
64        }
65        scatter(&x, &y)
66            .color_by(&cats)
67            .title("Iris Clusters")
68            .x_label("Sepal Length")
69            .y_label("Sepal Width")
70            .size(560.0, 380.0)
71            .save_svg(format!("{dir}/scatter_categories.svg"))?;
72    }
73
74    // ── 3. Line chart ───────────────────────────────────────────────
75    {
76        let x: Vec<f64> = (0..50).map(|i| f64::from(i) * 0.2).collect();
77        let y: Vec<f64> = x.iter().map(|&v| (v * 0.8).sin() * 3.0 + v * 0.5).collect();
78        line(&x, &y)
79            .title("Signal + Trend")
80            .x_label("Time (s)")
81            .y_label("Amplitude")
82            .size(560.0, 380.0)
83            .save_svg(format!("{dir}/line.svg"))?;
84    }
85
86    // ── 4. Multi-line (grammar API) ─────────────────────────────────
87    {
88        let epochs: Vec<f64> = (1..=30).map(f64::from).collect();
89        let train_loss: Vec<f64> = epochs.iter().map(|&e| 2.5 * (-e / 8.0).exp() + 0.1).collect();
90        let val_loss: Vec<f64> = epochs
91            .iter()
92            .map(|&e| 2.5 * (-e / 10.0).exp() + 0.25 + rng.normal() * 0.05)
93            .collect();
94
95        let chart = Chart::new()
96            .layer(
97                Layer::new(MarkType::Line)
98                    .with_x(epochs.clone())
99                    .with_y(train_loss)
100                    .with_label("Train"),
101            )
102            .layer(
103                Layer::new(MarkType::Line)
104                    .with_x(epochs)
105                    .with_y(val_loss)
106                    .with_label("Validation"),
107            )
108            .title("Training Curves")
109            .x_label("Epoch")
110            .y_label("Loss")
111            .size(560.0, 380.0);
112        chart.save_svg_to(format!("{dir}/multi_line.svg"))?;
113    }
114
115    // ── 5. Bar chart ────────────────────────────────────────────────
116    {
117        let cats = ["Rust", "Python", "Go", "TypeScript", "Java"];
118        let vals = [92.0, 87.0, 79.0, 73.0, 68.0];
119        bar(&cats, &vals)
120            .title("Developer Satisfaction")
121            .x_label("Language")
122            .y_label("Score (%)")
123            .size(560.0, 380.0)
124            .save_svg(format!("{dir}/bar.svg"))?;
125    }
126
127    // ── 6. Grouped bar ──────────────────────────────────────────────
128    {
129        let cats = ["Q1", "Q2", "Q3", "Q4", "Q1", "Q2", "Q3", "Q4"];
130        let groups = ["2024", "2024", "2024", "2024", "2025", "2025", "2025", "2025"];
131        let vals = [12.0, 18.0, 22.0, 15.0, 14.0, 20.0, 28.0, 19.0];
132        grouped_bar(&cats, &groups, &vals)
133            .title("Quarterly Revenue")
134            .x_label("Quarter")
135            .y_label("Revenue ($M)")
136            .size(560.0, 380.0)
137            .save_svg(format!("{dir}/grouped_bar.svg"))?;
138    }
139
140    // ── 7. Stacked bar ──────────────────────────────────────────────
141    {
142        let cats = ["Q1", "Q2", "Q3", "Q4", "Q1", "Q2", "Q3", "Q4"];
143        let groups = [
144            "Product", "Product", "Product", "Product",
145            "Service", "Service", "Service", "Service",
146        ];
147        let vals = [10.0, 15.0, 20.0, 18.0, 5.0, 8.0, 12.0, 10.0];
148        stacked_bar(&cats, &groups, &vals)
149            .title("Revenue by Segment")
150            .x_label("Quarter")
151            .y_label("Revenue ($M)")
152            .size(560.0, 380.0)
153            .save_svg(format!("{dir}/stacked_bar.svg"))?;
154    }
155
156    // ── 8. Histogram ────────────────────────────────────────────────
157    {
158        let data: Vec<f64> = (0..500).map(|_| rng.normal() * 1.5 + 10.0).collect();
159        histogram(&data)
160            .bins(25)
161            .title("Feature Distribution")
162            .x_label("Value")
163            .y_label("Count")
164            .size(560.0, 380.0)
165            .save_svg(format!("{dir}/histogram.svg"))?;
166    }
167
168    // ── 9. Area chart ───────────────────────────────────────────────
169    {
170        let x: Vec<f64> = (0..40).map(f64::from).collect();
171        let y: Vec<f64> = x
172            .iter()
173            .map(|&v| (v * 0.2).sin().abs() * 25.0 + 8.0 + rng.normal() * 1.5)
174            .collect();
175        area(&x, &y)
176            .title("Daily Active Users")
177            .x_label("Day")
178            .y_label("Users (k)")
179            .size(560.0, 380.0)
180            .save_svg(format!("{dir}/area.svg"))?;
181    }
182
183    // ── 10. Pie chart ───────────────────────────────────────────────
184    {
185        let labels = ["Chrome", "Firefox", "Safari", "Edge", "Other"];
186        let vals = [64.0, 12.0, 10.0, 8.0, 6.0];
187        pie_labeled(&labels, &vals)
188            .title("Browser Market Share")
189            .size(420.0, 420.0)
190            .save_svg(format!("{dir}/pie.svg"))?;
191    }
192
193    // ── 11. Donut chart ─────────────────────────────────────────────
194    {
195        let labels = ["Pass", "Warn", "Fail"];
196        let vals = [72.0, 18.0, 10.0];
197        pie_labeled(&labels, &vals)
198            .donut(0.5)
199            .title("Test Suite Results")
200            .size(420.0, 420.0)
201            .save_svg(format!("{dir}/donut.svg"))?;
202    }
203
204    // ── 12. Box plot ────────────────────────────────────────────────
205    {
206        let mut cats = Vec::new();
207        let mut vals = Vec::new();
208        for (label, center, spread) in [
209            ("Control", 50.0, 12.0),
210            ("Treatment A", 62.0, 10.0),
211            ("Treatment B", 71.0, 8.0),
212        ] {
213            for _ in 0..40 {
214                vals.push(center + rng.normal() * spread);
215                cats.push(label);
216            }
217        }
218        boxplot(&cats, &vals)
219            .title("Treatment Comparison")
220            .x_label("Group")
221            .y_label("Response")
222            .size(560.0, 380.0)
223            .save_svg(format!("{dir}/boxplot.svg"))?;
224    }
225
226    // ── 13. Heatmap ─────────────────────────────────────────────────
227    {
228        let data = vec![
229            vec![0.92, 0.05, 0.03],
230            vec![0.04, 0.88, 0.08],
231            vec![0.02, 0.06, 0.92],
232        ];
233        heatmap(data)
234            .annotate()
235            .with_row_labels(&["Cat", "Dog", "Bird"])
236            .with_col_labels(&["Cat", "Dog", "Bird"])
237            .title("Confusion Matrix")
238            .x_label("Predicted")
239            .y_label("Actual")
240            .size(420.0, 420.0)
241            .save_svg(format!("{dir}/heatmap.svg"))?;
242    }
243
244    // ── 14. Treemap ─────────────────────────────────────────────────
245    {
246        let labels = ["AWS", "Azure", "GCP", "Alibaba", "Oracle", "IBM"];
247        let vals = [32.0, 23.0, 11.0, 5.0, 3.0, 2.0];
248        treemap(&labels, &vals)
249            .title("Cloud Market Share (%)")
250            .size(560.0, 380.0)
251            .save_svg(format!("{dir}/treemap.svg"))?;
252    }
253
254    // ── 15. LOESS smooth ────────────────────────────────────────────
255    {
256        let x: Vec<f64> = (0..60).map(|i| f64::from(i) * 0.15).collect();
257        let y: Vec<f64> = x
258            .iter()
259            .map(|&v| (v * 0.5).sin() * 3.0 + rng.normal() * 0.8)
260            .collect();
261        let chart = Chart::new()
262            .layer(
263                Layer::new(MarkType::Point)
264                    .with_x(x.clone())
265                    .with_y(y.clone())
266                    .with_label("Raw"),
267            )
268            .layer(
269                Layer::new(MarkType::Line)
270                    .with_x(x)
271                    .with_y(y)
272                    .stat(Stat::Smooth { bandwidth: 0.3 })
273                    .with_label("LOESS"),
274            )
275            .title("LOESS Smoothing")
276            .x_label("x")
277            .y_label("y")
278            .size(560.0, 380.0);
279        chart.save_svg_to(format!("{dir}/loess.svg"))?;
280    }
281
282    // ── 16. Annotations ─────────────────────────────────────────────
283    {
284        let x: Vec<f64> = (0..30).map(f64::from).collect();
285        let y: Vec<f64> = x
286            .iter()
287            .map(|&v| v * 1.2 + rng.normal() * 3.0 + 5.0)
288            .collect();
289        let chart = scatter(&x, &y)
290            .title("Annotated Scatter")
291            .x_label("Day")
292            .y_label("Metric")
293            .size(560.0, 380.0)
294            .build()
295            .annotate(Annotation::hline(25.0).with_label("Target"))
296            .annotate(Annotation::band(15.0, 25.0));
297        chart.save_svg_to(format!("{dir}/annotations.svg"))?;
298    }
299
300    // ── 17. Dark theme ──────────────────────────────────────────────
301    {
302        let epochs: Vec<f64> = (1..=25).map(f64::from).collect();
303        let loss: Vec<f64> = epochs.iter().map(|&e| 3.0 * (-e / 6.0).exp() + 0.15).collect();
304        let acc: Vec<f64> = epochs
305            .iter()
306            .map(|&e| 0.95 * (1.0 - (-e / 5.0).exp()))
307            .collect();
308
309        let chart = Chart::new()
310            .layer(
311                Layer::new(MarkType::Line)
312                    .with_x(epochs.clone())
313                    .with_y(loss)
314                    .with_label("Loss"),
315            )
316            .layer(
317                Layer::new(MarkType::Line)
318                    .with_x(epochs)
319                    .with_y(acc)
320                    .with_label("Accuracy"),
321            )
322            .title("Model Training")
323            .x_label("Epoch")
324            .y_label("Value")
325            .theme(NewTheme::dark())
326            .size(560.0, 380.0);
327        chart.save_svg_to(format!("{dir}/dark_theme.svg"))?;
328    }
329
330    // ── 18. Horizontal bar ──────────────────────────────────────────
331    {
332        let chart = Chart::new()
333            .layer(
334                Layer::new(MarkType::Bar)
335                    .with_x(vec![0.0, 1.0, 2.0, 3.0, 4.0])
336                    .with_y(vec![92.0, 87.0, 79.0, 73.0, 68.0])
337                    .with_categories(vec![
338                        "Rust".into(),
339                        "Python".into(),
340                        "Go".into(),
341                        "TypeScript".into(),
342                        "Java".into(),
343                    ]),
344            )
345            .coord(CoordSystem::Flipped)
346            .title("Satisfaction Scores")
347            .x_label("Score (%)")
348            .y_label("Language")
349            .size(560.0, 380.0);
350        chart.save_svg_to(format!("{dir}/horizontal_bar.svg"))?;
351    }
352
353    println!("Generated 18 SVGs in {dir}/");
354    Ok(())
355}