1use ggplot_rs::prelude::*;
7use polars::prelude::*;
8use std::f64::consts::PI;
9
10const W: u32 = 640;
11const H: u32 = 480;
12const TW: u32 = 480;
14const TH: u32 = 340;
15
16fn out(name: &str) -> String {
17 format!("assets/gallery/{name}.png")
18}
19
20fn main() -> Result<(), Box<dyn std::error::Error>> {
21 std::fs::create_dir_all("assets/gallery")?;
22
23 scatter()?;
24 smooth()?;
25 histogram()?;
26 bar()?;
27 boxplot()?;
28 violin()?;
29 continuous_color()?;
30 facet()?;
31 density()?;
32 contour_filled()?;
33 hexbin()?;
34 heatmap()?;
35 jitter()?;
36 ribbon()?;
37 area_stack()?;
38 themes()?;
39
40 println!("Gallery written to assets/gallery/");
41 Ok(())
42}
43
44fn scatter() -> Result<(), Box<dyn std::error::Error>> {
46 let n = 150;
47 let x: Vec<f64> = (0..n).map(|i| 4.5 + i as f64 * 0.02).collect();
48 let y: Vec<f64> = (0..n)
49 .map(|i| 2.5 + (i as f64 * 0.15).sin() + (i % 3) as f64 * 0.6)
50 .collect();
51 let species: Vec<&str> = (0..n)
52 .map(|i| ["setosa", "versicolor", "virginica"][i % 3])
53 .collect();
54
55 let df = df! { "x" => x, "y" => y, "species" => species }?;
56 GGPlot::new(df)
57 .aes(Aes::new().x("x").y("y").color("species"))
58 .geom_point()
59 .scale_color_brewer(PaletteName::Set1)
60 .title("Grouped Scatter")
61 .xlab("Sepal Length")
62 .ylab("Sepal Width")
63 .theme_minimal()
64 .save_with_size(&out("scatter"), W, H)?;
65 Ok(())
66}
67
68fn smooth() -> Result<(), Box<dyn std::error::Error>> {
70 let n = 120;
71 let x: Vec<f64> = (0..n).map(|i| i as f64 * 0.1).collect();
72 let y: Vec<f64> = (0..n)
73 .map(|i| {
74 let t = i as f64 * 0.1;
75 (t * 0.6).sin() * 3.0 + t * 0.2 + ((i * 7919 % 100) as f64 / 100.0 - 0.5) * 1.5
76 })
77 .collect();
78
79 let df = df! { "x" => x, "y" => y }?;
80 GGPlot::new(df)
81 .aes(Aes::new().x("x").y("y"))
82 .geom_point()
83 .geom_smooth_with(GeomSmooth {
84 method: SmoothMethod::Loess { span: 0.5 },
85 ..Default::default()
86 })
87 .title("LOESS Smoothing")
88 .xlab("x")
89 .ylab("y")
90 .theme_bw()
91 .save_with_size(&out("smooth"), W, H)?;
92 Ok(())
93}
94
95fn histogram() -> Result<(), Box<dyn std::error::Error>> {
97 let values: Vec<f64> = (0..1500)
98 .map(|i: i32| {
99 let r: f64 = (0..6)
100 .map(|k| ((i * (1237 + k * 311) + 5678) % 1000) as f64 / 1000.0)
101 .sum();
102 (r - 3.0) * 2.0
103 })
104 .collect();
105
106 let df = df! { "measurement" => values }?;
107 GGPlot::new(df)
108 .aes(Aes::new().x("measurement"))
109 .geom_histogram_with(GeomHistogram {
110 bins: 30,
111 ..Default::default()
112 })
113 .title("Histogram")
114 .xlab("Value")
115 .ylab("Count")
116 .theme_minimal()
117 .save_with_size(&out("histogram"), W, H)?;
118 Ok(())
119}
120
121fn bar() -> Result<(), Box<dyn std::error::Error>> {
123 let mut fruit: Vec<&str> = Vec::new();
124 for (f, c) in [
125 ("Apple", 8),
126 ("Banana", 5),
127 ("Cherry", 11),
128 ("Date", 3),
129 ("Elder", 7),
130 ] {
131 for _ in 0..c {
132 fruit.push(f);
133 }
134 }
135 let df = df! { "fruit" => fruit }?;
136 GGPlot::new(df)
137 .aes(Aes::new().x("fruit").fill("fruit"))
138 .geom_bar()
139 .scale_fill_brewer(PaletteName::Set2)
140 .title("Bar Chart")
141 .xlab("Fruit")
142 .ylab("Count")
143 .theme_minimal()
144 .save_with_size(&out("bar"), W, H)?;
145 Ok(())
146}
147
148fn boxplot() -> Result<(), Box<dyn std::error::Error>> {
150 let n = 240;
151 let group: Vec<&str> = (0..n).map(|i| ["A", "B", "C", "D"][i % 4]).collect();
152 let value: Vec<f64> = (0..n)
153 .map(|i| {
154 let base = (i % 4) as f64 * 1.5;
155 base + (i as f64 * 0.4).sin() * 1.2 + ((i * 6151 % 100) as f64 / 100.0 - 0.5) * 2.0
156 })
157 .collect();
158
159 let df = df! { "group" => group, "value" => value }?;
160 GGPlot::new(df)
161 .aes(Aes::new().x("group").y("value"))
162 .geom_boxplot_with(GeomBoxplot {
163 fill: (70, 130, 180),
164 ..Default::default()
165 })
166 .title("Boxplot")
167 .xlab("Group")
168 .ylab("Value")
169 .theme_bw()
170 .save_with_size(&out("boxplot"), W, H)?;
171 Ok(())
172}
173
174fn violin() -> Result<(), Box<dyn std::error::Error>> {
176 let n = 360;
177 let group: Vec<&str> = (0..n).map(|i| ["X", "Y", "Z"][i % 3]).collect();
178 let value: Vec<f64> = (0..n)
179 .map(|i| {
180 let g = (i % 3) as f64;
181 g * 2.0 + (i as f64 * 0.5).sin() * 1.5 + ((i * 4231 % 100) as f64 / 100.0 - 0.5) * 2.5
182 })
183 .collect();
184
185 let df = df! { "group" => group, "value" => value }?;
186 GGPlot::new(df)
187 .aes(Aes::new().x("group").y("value").fill("group"))
188 .geom_violin()
189 .scale_fill_brewer(PaletteName::Accent)
190 .title("Violin")
191 .xlab("Group")
192 .ylab("Value")
193 .theme_minimal()
194 .save_with_size(&out("violin"), W, H)?;
195 Ok(())
196}
197
198fn continuous_color() -> Result<(), Box<dyn std::error::Error>> {
200 let n = 400;
201 let x: Vec<f64> = (0..n)
202 .map(|i| {
203 let t = i as f64 * 0.05;
204 t.cos() * (1.0 + t * 0.12)
205 })
206 .collect();
207 let y: Vec<f64> = (0..n)
208 .map(|i| {
209 let t = i as f64 * 0.05;
210 t.sin() * (1.0 + t * 0.12)
211 })
212 .collect();
213 let z: Vec<f64> = (0..n).map(|i| i as f64 * 0.05).collect();
214
215 let df = df! { "x" => x, "y" => y, "z" => z }?;
216 GGPlot::new(df)
217 .aes(Aes::new().x("x").y("y").color("z"))
218 .geom_point()
219 .scale_color_viridis_c()
220 .title("Continuous Color (viridis)")
221 .xlab("x")
222 .ylab("y")
223 .theme_minimal()
224 .save_with_size(&out("continuous_color"), W, H)?;
225 Ok(())
226}
227
228fn facet() -> Result<(), Box<dyn std::error::Error>> {
230 let n = 180;
231 let x: Vec<f64> = (0..n).map(|i| (i as f64 * 0.1).cos() * 3.0).collect();
232 let y: Vec<f64> = (0..n)
233 .map(|i| (i as f64 * 0.1).sin() * 3.0 + (i % 3) as f64)
234 .collect();
235 let species: Vec<&str> = (0..n)
236 .map(|i| ["setosa", "versicolor", "virginica"][i % 3])
237 .collect();
238
239 let df = df! { "x" => x, "y" => y, "species" => species }?;
240 GGPlot::new(df)
241 .aes(Aes::new().x("x").y("y").color("species"))
242 .geom_point()
243 .facet_wrap("species", Some(3))
244 .scale_color_brewer(PaletteName::Set1)
245 .title("Facet Wrap")
246 .xlab("x")
247 .ylab("y")
248 .theme_bw()
249 .save_with_size(&out("facet"), W, H)?;
250 Ok(())
251}
252
253fn density() -> Result<(), Box<dyn std::error::Error>> {
255 let n = 600;
256 let group: Vec<&str> = (0..n).map(|i| ["Group 1", "Group 2"][i % 2]).collect();
257 let value: Vec<f64> = (0..n)
258 .map(|i| {
259 let shift = (i % 2) as f64 * 2.5;
260 let t = i as f64 * 0.05;
261 shift + (t.sin() + (t * 1.7).cos()) + ((i * 3319 % 100) as f64 / 100.0 - 0.5) * PI
262 })
263 .collect();
264
265 let df = df! { "value" => value, "group" => group }?;
266 GGPlot::new(df)
267 .aes(Aes::new().x("value").fill("group").color("group"))
268 .geom_density()
269 .scale_fill_brewer(PaletteName::Set1)
270 .scale_color_brewer(PaletteName::Set1)
271 .title("Density by Group")
272 .xlab("Value")
273 .ylab("Density")
274 .theme_minimal()
275 .save_with_size(&out("density"), W, H)?;
276 Ok(())
277}
278
279fn contour_filled() -> Result<(), Box<dyn std::error::Error>> {
281 let (mut x, mut y, mut z) = (Vec::new(), Vec::new(), Vec::new());
282 for i in 0..40 {
283 for j in 0..40 {
284 let xv = i as f64 * 0.25 - 5.0;
285 let yv = j as f64 * 0.25 - 5.0;
286 x.push(xv);
287 y.push(yv);
288 z.push((xv * 0.6).sin() * (yv * 0.6).cos() + (-(xv * xv + yv * yv) / 30.0).exp());
289 }
290 }
291 let df = df! { "x" => x, "y" => y, "z" => z }?;
292 GGPlot::new(df)
293 .aes(Aes::new().x("x").y("y"))
294 .geom_contour_filled()
295 .scale_fill_viridis_c()
296 .title("Filled Contours")
297 .theme_minimal()
298 .save_with_size(&out("contour_filled"), W, H)?;
299 Ok(())
300}
301
302fn hexbin() -> Result<(), Box<dyn std::error::Error>> {
304 let n = 4000;
305 let x: Vec<f64> = (0..n)
306 .map(|i| {
307 let t = i as f64;
308 (t * 0.017).sin() * 2.0 + ((i * 7919 % 1000) as f64 / 1000.0 - 0.5) * 3.0
309 })
310 .collect();
311 let y: Vec<f64> = (0..n)
312 .map(|i| {
313 let t = i as f64;
314 (t * 0.017).cos() * 2.0 + ((i * 6323 % 1000) as f64 / 1000.0 - 0.5) * 3.0
315 })
316 .collect();
317 let df = df! { "x" => x, "y" => y }?;
318 GGPlot::new(df)
319 .aes(Aes::new().x("x").y("y"))
320 .geom_hex()
321 .scale_fill_viridis_c()
322 .title("Hex Binning")
323 .theme_minimal()
324 .save_with_size(&out("hexbin"), W, H)?;
325 Ok(())
326}
327
328fn heatmap() -> Result<(), Box<dyn std::error::Error>> {
330 let (mut x, mut y, mut fill) = (Vec::new(), Vec::new(), Vec::new());
331 for i in 0..14 {
332 for j in 0..14 {
333 x.push(i as f64);
334 y.push(j as f64);
335 fill.push((i as f64 * 0.5).sin() * (j as f64 * 0.5).cos());
336 }
337 }
338 let df = df! { "x" => x, "y" => y, "fill" => fill }?;
339 GGPlot::new(df)
340 .aes(Aes::new().x("x").y("y").fill("fill"))
341 .geom_tile()
342 .scale_fill_viridis_c()
343 .title("Heatmap")
344 .theme_minimal()
345 .save_with_size(&out("heatmap"), W, H)?;
346 Ok(())
347}
348
349fn jitter() -> Result<(), Box<dyn std::error::Error>> {
351 let n = 300;
352 let group: Vec<&str> = (0..n).map(|i| ["Control", "Low", "High"][i % 3]).collect();
353 let value: Vec<f64> = (0..n)
354 .map(|i| {
355 let base = (i % 3) as f64 * 1.5;
356 base + ((i * 5701 % 1000) as f64 / 1000.0 - 0.5) * 2.0
357 })
358 .collect();
359 let df = df! { "group" => group, "value" => value }?;
360 GGPlot::new(df)
361 .aes(Aes::new().x("group").y("value").color("group"))
362 .geom_jitter()
363 .scale_color_brewer(PaletteName::Dark2)
364 .title("Jittered Points")
365 .theme_minimal()
366 .save_with_size(&out("jitter"), W, H)?;
367 Ok(())
368}
369
370fn ribbon() -> Result<(), Box<dyn std::error::Error>> {
372 let n = 80;
373 let x: Vec<f64> = (0..n).map(|i| i as f64 * 0.15).collect();
374 let y: Vec<f64> = x.iter().map(|v| v.sin() + v * 0.1).collect();
375 let ymin: Vec<f64> = y.iter().map(|v| v - 0.4).collect();
376 let ymax: Vec<f64> = y.iter().map(|v| v + 0.4).collect();
377 let df = df! { "x" => x, "y" => y, "ymin" => ymin, "ymax" => ymax }?;
378 GGPlot::new(df)
379 .aes(Aes::new().x("x").y("y").ymin("ymin").ymax("ymax"))
380 .geom_ribbon()
381 .geom_line()
382 .primary_color((49, 130, 189))
383 .title("Ribbon + Line")
384 .theme_minimal()
385 .save_with_size(&out("ribbon"), W, H)?;
386 Ok(())
387}
388
389fn area_stack() -> Result<(), Box<dyn std::error::Error>> {
391 let n = 40;
392 let mut x = Vec::new();
393 let mut y = Vec::new();
394 let mut g = Vec::new();
395 for grp in ["North", "South", "East"] {
396 for i in 0..n {
397 x.push(i as f64);
398 let base = match grp {
399 "North" => 2.0,
400 "South" => 3.0,
401 _ => 1.5,
402 };
403 y.push(base + (i as f64 * 0.2).sin().abs() * base);
404 g.push(grp);
405 }
406 }
407 let df = df! { "x" => x, "y" => y, "g" => g }?;
408 GGPlot::new(df)
409 .aes(Aes::new().x("x").y("y").fill("g"))
410 .geom_area()
411 .scale_fill_brewer(PaletteName::Set2)
412 .title("Stacked Area")
413 .theme_minimal()
414 .save_with_size(&out("area"), W, H)?;
415 Ok(())
416}
417
418fn themes() -> Result<(), Box<dyn std::error::Error>> {
420 let n = 90;
421 let x: Vec<f64> = (0..n).map(|i| i as f64 * 0.1).collect();
422 let y: Vec<f64> = (0..n)
423 .map(|i| (i as f64 * 0.1).sin() + (i % 3) as f64 * 0.5)
424 .collect();
425 let g: Vec<&str> = (0..n).map(|i| ["A", "B", "C"][i % 3]).collect();
426 let df = df! { "x" => x, "y" => y, "g" => g }?;
427
428 type ThemeFn = fn() -> Theme;
429 let variants: [(&str, ThemeFn); 8] = [
430 ("gray", theme_gray),
431 ("bw", theme_bw),
432 ("minimal", theme_minimal),
433 ("classic", theme_classic),
434 ("light", theme_light),
435 ("dark", theme_dark),
436 ("linedraw", theme_linedraw),
437 ("void", theme_void),
438 ];
439 for (name, make) in variants {
440 GGPlot::new(df.clone())
441 .aes(Aes::new().x("x").y("y").color("g"))
442 .geom_point()
443 .theme(make())
444 .scale_color_brewer(PaletteName::Dark2)
445 .title(&format!("theme_{name}"))
446 .save_with_size(&out(&format!("theme_{name}")), TW, TH)?;
447 }
448 Ok(())
449}