1use std::fmt::Write;
5
6use esoc_chart::express::{
7 area, bar, boxplot, grouped_bar, heatmap, histogram, line, pie_labeled, scatter, stacked_bar,
8};
9use esoc_chart::grammar::annotation::Annotation;
10use esoc_chart::grammar::chart::Chart;
11use esoc_chart::grammar::coord::CoordSystem;
12use esoc_chart::grammar::layer::{Layer, MarkType};
13use esoc_chart::grammar::stat::Stat;
14
15fn main() -> esoc_chart::error::Result<()> {
16 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 sections: Vec<(&str, String)> = Vec::new();
33 let mut rng = Rng(42);
34
35 {
37 let x = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
38 let y = vec![2.3, 4.1, 3.0, 5.8, 4.9, 7.2, 6.5, 8.1];
39 let svg = scatter(&x, &y)
40 .title("Scatter Plot")
41 .x_label("X")
42 .y_label("Y")
43 .size(500.0, 350.0)
44 .to_svg()?;
45 sections.push(("Scatter", svg));
46 }
47
48 {
50 let x = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
51 let y = vec![2.3, 4.1, 3.0, 5.8, 4.9, 7.2, 6.5, 8.1];
52 let cats = vec!["A", "B", "A", "B", "A", "B", "A", "B"];
53 let svg = scatter(&x, &y)
54 .color_by(&cats)
55 .title("Colored Scatter")
56 .x_label("X")
57 .y_label("Y")
58 .size(500.0, 350.0)
59 .to_svg()?;
60 sections.push(("Scatter (colored)", svg));
61 }
62
63 {
65 let n = 300;
66 let x: Vec<f64> = (0..n).map(|_| rng.normal() * 3.0 + 5.0).collect();
67 let y: Vec<f64> = x.iter().map(|&xi| xi * 0.8 + rng.normal() * 2.0).collect();
68 let svg = scatter(&x, &y)
69 .title("Dense Scatter (auto opacity)")
70 .x_label("Feature A")
71 .y_label("Feature B")
72 .size(500.0, 350.0)
73 .to_svg()?;
74 sections.push(("Dense Scatter", svg));
75 }
76
77 {
79 let x: Vec<f64> = (0..20).map(|i| f64::from(i) * 0.5).collect();
80 let y: Vec<f64> = x.iter().map(|&v| (v * 0.8).sin() * 3.0 + v).collect();
81 let svg = line(&x, &y)
82 .title("Line Chart")
83 .x_label("Time")
84 .y_label("Value")
85 .size(500.0, 350.0)
86 .to_svg()?;
87 sections.push(("Line", svg));
88 }
89
90 {
92 let x: Vec<f64> = (0..30).map(|i| f64::from(i) * 0.5).collect();
93 let y1: Vec<f64> = x.iter().map(|&v| (v * 0.4).sin() * 5.0 + 10.0).collect();
94 let y2: Vec<f64> = x.iter().map(|&v| (v * 0.4).cos() * 4.0 + 12.0).collect();
95 let y3: Vec<f64> = x.iter().map(|&v| v * 0.5 + 5.0).collect();
96
97 let chart = Chart::new()
98 .layer(
99 Layer::new(MarkType::Line)
100 .with_x(x.clone())
101 .with_y(y1)
102 .with_label("sin"),
103 )
104 .layer(
105 Layer::new(MarkType::Line)
106 .with_x(x.clone())
107 .with_y(y2)
108 .with_label("cos"),
109 )
110 .layer(
111 Layer::new(MarkType::Line)
112 .with_x(x)
113 .with_y(y3)
114 .with_label("linear"),
115 )
116 .title("Multi-Line Chart")
117 .x_label("Time")
118 .y_label("Signal")
119 .size(500.0, 350.0);
120 sections.push(("Multi-Line", chart.to_svg()?));
121 }
122
123 {
125 let x: Vec<f64> = (0..10).map(f64::from).collect();
126 let y_data: Vec<f64> = vec![2.1, 3.8, 3.2, 5.5, 4.8, 7.1, 6.3, 8.0, 7.5, 9.2];
127 let y_trend: Vec<f64> = x.iter().map(|&v| v * 0.8 + 2.0).collect();
128
129 let chart = Chart::new()
130 .layer(
131 Layer::new(MarkType::Point)
132 .with_x(x.clone())
133 .with_y(y_data)
134 .with_label("Data"),
135 )
136 .layer(
137 Layer::new(MarkType::Line)
138 .with_x(x)
139 .with_y(y_trend)
140 .with_label("Trend"),
141 )
142 .title("Scatter + Trend Line")
143 .x_label("X")
144 .y_label("Y")
145 .size(500.0, 350.0);
146 sections.push(("Scatter + Line Overlay", chart.to_svg()?));
147 }
148
149 {
151 let x: Vec<f64> = (0..40).map(|i| f64::from(i) * 0.25).collect();
152 let y: Vec<f64> = x
153 .iter()
154 .map(|&v| (v * 0.5).sin() * 3.0 + rng.normal() * 0.8)
155 .collect();
156
157 let chart = Chart::new()
158 .layer(
159 Layer::new(MarkType::Point)
160 .with_x(x.clone())
161 .with_y(y.clone())
162 .with_label("Raw"),
163 )
164 .layer(
165 Layer::new(MarkType::Line)
166 .with_x(x)
167 .with_y(y)
168 .stat(Stat::Smooth { bandwidth: 0.3 })
169 .with_label("LOESS"),
170 )
171 .title("LOESS Smoothing")
172 .x_label("X")
173 .y_label("Y")
174 .size(500.0, 350.0);
175 sections.push(("LOESS Smooth", chart.to_svg()?));
176 }
177
178 {
180 let cats = vec!["Rust", "Python", "Go", "Java", "C++"];
181 let vals = vec![42.0, 35.0, 28.0, 22.0, 18.0];
182 let svg = bar(&cats, &vals)
183 .title("Language Popularity")
184 .x_label("Language")
185 .y_label("Score")
186 .size(500.0, 350.0)
187 .to_svg()?;
188 sections.push(("Bar", svg));
189 }
190
191 {
193 let chart = Chart::new()
194 .layer(
195 Layer::new(MarkType::Bar)
196 .with_x(vec![0.0, 1.0, 2.0, 3.0, 4.0])
197 .with_y(vec![42.0, 35.0, 28.0, 22.0, 18.0])
198 .with_categories(vec![
199 "Rust".into(),
200 "Python".into(),
201 "Go".into(),
202 "Java".into(),
203 "C++".into(),
204 ]),
205 )
206 .coord(CoordSystem::Flipped)
207 .title("Horizontal Bars")
208 .x_label("Score")
209 .y_label("Language")
210 .size(500.0, 350.0);
211 sections.push(("Horizontal Bar", chart.to_svg()?));
212 }
213
214 {
216 let data: Vec<f64> = (0..300).map(|_| rng.normal() * 1.5 + 10.0).collect();
217 let svg = histogram(&data)
218 .bins(20)
219 .title("Histogram")
220 .x_label("Value")
221 .y_label("Count")
222 .size(500.0, 350.0)
223 .to_svg()?;
224 sections.push(("Histogram", svg));
225 }
226
227 {
229 let x: Vec<f64> = (0..30).map(f64::from).collect();
230 let y: Vec<f64> = x
231 .iter()
232 .map(|&v| (v * 0.3).sin().abs() * 20.0 + 5.0)
233 .collect();
234 let svg = area(&x, &y)
235 .title("Area Chart")
236 .x_label("Day")
237 .y_label("Traffic")
238 .size(500.0, 350.0)
239 .to_svg()?;
240 sections.push(("Area", svg));
241 }
242
243 {
245 let vals = vec![35.0, 25.0, 20.0, 15.0, 5.0];
246 let labels = vec!["Chrome", "Firefox", "Safari", "Edge", "Other"];
247 let svg = pie_labeled(&labels, &vals)
248 .title("Browser Share")
249 .size(400.0, 400.0)
250 .to_svg()?;
251 sections.push(("Pie", svg));
252 }
253
254 {
256 let vals = vec![60.0, 25.0, 15.0];
257 let labels = vec!["Pass", "Warn", "Fail"];
258 let svg = pie_labeled(&labels, &vals)
259 .donut(0.5)
260 .title("Test Results")
261 .size(400.0, 400.0)
262 .to_svg()?;
263 sections.push(("Donut", svg));
264 }
265
266 {
268 let cats = vec!["Q1", "Q2", "Q3", "Q4", "Q1", "Q2", "Q3", "Q4"];
269 let groups = vec![
270 "2024", "2024", "2024", "2024", "2025", "2025", "2025", "2025",
271 ];
272 let vals = vec![12.0, 18.0, 22.0, 15.0, 14.0, 20.0, 28.0, 19.0];
273 let svg = grouped_bar(&cats, &groups, &vals)
274 .title("Quarterly Revenue")
275 .x_label("Quarter")
276 .y_label("Revenue ($M)")
277 .size(500.0, 350.0)
278 .to_svg()?;
279 sections.push(("Grouped Bar", svg));
280 }
281
282 {
284 let cats = vec!["Q1", "Q2", "Q3", "Q1", "Q2", "Q3"];
285 let groups = vec![
286 "Product", "Product", "Product", "Service", "Service", "Service",
287 ];
288 let vals = vec![10.0, 15.0, 20.0, 5.0, 8.0, 12.0];
289 let svg = stacked_bar(&cats, &groups, &vals)
290 .title("Revenue by Segment")
291 .x_label("Quarter")
292 .y_label("Revenue ($M)")
293 .size(500.0, 350.0)
294 .to_svg()?;
295 sections.push(("Stacked Bar", svg));
296 }
297
298 {
300 let mut cats = Vec::new();
301 let mut vals = Vec::new();
302 for label in &["Control", "Treatment A", "Treatment B"] {
303 let base = match *label {
304 "Control" => 50.0,
305 "Treatment A" => 65.0,
306 _ => 70.0,
307 };
308 for _ in 0..30 {
309 vals.push(base + (rng.uniform() - 0.5) * 30.0);
310 cats.push(*label);
311 }
312 }
313 let svg = boxplot(&cats, &vals)
314 .title("Treatment Comparison")
315 .x_label("Group")
316 .y_label("Response")
317 .size(500.0, 350.0)
318 .to_svg()?;
319 sections.push(("Box Plot", svg));
320 }
321
322 {
324 let x: Vec<f64> = (0..20).map(f64::from).collect();
325 let y: Vec<f64> = x.iter().map(|&v| v * 1.5 + rng.normal() * 3.0).collect();
326 let chart = scatter(&x, &y)
327 .title("Annotations Demo")
328 .x_label("X")
329 .y_label("Y")
330 .size(500.0, 350.0)
331 .build()
332 .annotate(Annotation::hline(15.0).with_label("Target"))
333 .annotate(Annotation::vline(10.0).with_label("Midpoint"))
334 .annotate(Annotation::band(10.0, 20.0))
335 .annotate(Annotation::text(15.0, 25.0, "Peak zone"));
336 sections.push(("Annotations", chart.to_svg()?));
337 }
338
339 {
341 let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
342 let y = vec![10.0, 25.0, 18.0, 32.0, 28.0];
343 let chart = Chart::new()
344 .layer(Layer::new(MarkType::Line).with_x(x).with_y(y))
345 .title("Monthly Sales")
346 .subtitle("Jan–May 2026")
347 .caption("Source: internal data")
348 .x_label("Month")
349 .y_label("Revenue ($K)")
350 .size(500.0, 350.0);
351 sections.push(("Subtitle + Caption", chart.to_svg()?));
352 }
353
354 {
356 let mut x = Vec::new();
357 let mut y = Vec::new();
358 let mut facets = Vec::new();
359 for panel in &["East", "West", "North", "South"] {
360 for _ in 0..20 {
361 x.push(rng.uniform() * 10.0);
362 y.push(rng.uniform() * 10.0);
363 facets.push(*panel);
364 }
365 }
366 let svg = scatter(&x, &y)
367 .facet_wrap(&facets, 2)
368 .title("Regional Data")
369 .x_label("X")
370 .y_label("Y")
371 .size(500.0, 400.0)
372 .to_svg()?;
373 sections.push(("Faceted Scatter", svg));
374 }
375
376 {
378 let data = vec![
379 vec![1.0, 2.0, 3.0, 4.0, 5.0],
380 vec![5.0, 4.0, 3.0, 2.0, 1.0],
381 vec![2.0, 8.0, 6.0, 4.0, 2.0],
382 vec![3.0, 3.0, 9.0, 3.0, 3.0],
383 ];
384 let svg = heatmap(data)
385 .annotate()
386 .with_row_labels(&["A", "B", "C", "D"])
387 .with_col_labels(&["v1", "v2", "v3", "v4", "v5"])
388 .title("Heatmap")
389 .x_label("Variable")
390 .y_label("Group")
391 .size(450.0, 380.0)
392 .to_svg()?;
393 sections.push(("Heatmap", svg));
394 }
395
396 {
398 let data = vec![
399 vec![45.0, 3.0, 2.0],
400 vec![1.0, 40.0, 5.0],
401 vec![0.0, 4.0, 50.0],
402 ];
403 let svg = heatmap(data)
404 .annotate()
405 .with_row_labels(&["Cat", "Dog", "Bird"])
406 .with_col_labels(&["Cat", "Dog", "Bird"])
407 .title("Confusion Matrix")
408 .x_label("Predicted")
409 .y_label("Actual")
410 .size(400.0, 400.0)
411 .to_svg()?;
412 sections.push(("Confusion Matrix", svg));
413 }
414
415 let mut html = String::from(
417 r#"<!DOCTYPE html>
418<html lang="en">
419<head>
420<meta charset="UTF-8">
421<meta name="viewport" content="width=device-width, initial-scale=1.0">
422<title>esoc-chart Gallery</title>
423<style>
424 * { margin: 0; padding: 0; box-sizing: border-box; }
425 body { font-family: system-ui, -apple-system, sans-serif; background: #f5f5f5; color: #333; }
426 header { background: #1a1a2e; color: white; padding: 2rem; text-align: center; }
427 header h1 { font-size: 2rem; font-weight: 300; }
428 header p { margin-top: 0.5rem; opacity: 0.7; }
429 .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(480px, 1fr)); gap: 1.5rem; padding: 2rem; max-width: 1400px; margin: 0 auto; }
430 .card { background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); overflow: hidden; }
431 .card h2 { font-size: 0.9rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: #666; padding: 1rem 1.5rem 0; }
432 .card svg { display: block; width: 100%; height: auto; padding: 0.5rem 1rem 1rem; }
433 .feedback { padding: 0 1rem 1rem; }
434 .feedback textarea { width: 100%; min-height: 60px; border: 1px solid #ddd; border-radius: 4px; padding: 0.5rem; font-family: inherit; font-size: 0.85rem; resize: vertical; }
435 .feedback textarea:focus { outline: none; border-color: #1a1a2e; }
436 .feedback .status { font-size: 0.75rem; color: #999; margin-top: 0.25rem; }
437 .actions { padding: 1.5rem 2rem; text-align: center; }
438 .actions button { background: #1a1a2e; color: white; border: none; border-radius: 4px; padding: 0.6rem 1.5rem; font-size: 0.9rem; cursor: pointer; }
439 .actions button:hover { background: #2a2a4e; }
440</style>
441<script>
442 const feedback = {};
443 function loadFeedback() {
444 try { Object.assign(feedback, JSON.parse(localStorage.getItem('chart_feedback') || '{}')); } catch {}
445 document.querySelectorAll('.feedback textarea').forEach(ta => {
446 const key = ta.dataset.chart;
447 if (feedback[key]) ta.value = feedback[key];
448 });
449 }
450 function saveFeedback(key, value) {
451 feedback[key] = value;
452 localStorage.setItem('chart_feedback', JSON.stringify(feedback));
453 }
454 function exportFeedback() {
455 const blob = new Blob([JSON.stringify(feedback, null, 2)], {type: 'application/json'});
456 const a = document.createElement('a');
457 a.href = URL.createObjectURL(blob);
458 a.download = 'chart_feedback.json';
459 a.click();
460 }
461 window.addEventListener('DOMContentLoaded', loadFeedback);
462</script>
463</head>
464<body>
465<header>
466 <h1>esoc-chart Gallery</h1>
467 <p>All charts generated with the express & grammar APIs</p>
468</header>
469<div class="grid">
470"#,
471 );
472
473 for (title, svg) in §ions {
474 let key = title.to_lowercase().replace(' ', "_");
475 write!(
476 html,
477 concat!(
478 "<div class=\"card\">\n",
479 " <h2>{title}</h2>\n",
480 " {svg}\n",
481 " <div class=\"feedback\">\n",
482 " <textarea data-chart=\"{key}\" placeholder=\"Feedback on {title}…\" ",
483 "oninput=\"saveFeedback('{key}', this.value)\"></textarea>\n",
484 " <div class=\"status\">Auto-saved to browser</div>\n",
485 " </div>\n",
486 "</div>\n",
487 ),
488 title = title,
489 svg = svg,
490 key = key,
491 )
492 .unwrap();
493 }
494
495 html.push_str(concat!(
496 "</div>\n",
497 "<div class=\"actions\">\n",
498 " <button onclick=\"exportFeedback()\">Export Feedback as JSON</button>\n",
499 "</div>\n",
500 "</body>\n</html>\n",
501 ));
502
503 std::fs::write("chart_gallery.html", &html).expect("failed to write HTML");
504 println!("Saved chart_gallery.html ({} charts)", sections.len());
505
506 Ok(())
507}