Skip to main content

gallery/
gallery.rs

1//! Generates the plot gallery shown in the README.
2//!
3//! Run with: `cargo run --example gallery`
4//! Writes PNGs to `assets/gallery/`.
5
6use ggplot_rs::prelude::*;
7use polars::prelude::*;
8use std::f64::consts::PI;
9
10const W: u32 = 640;
11const H: u32 = 480;
12
13fn out(name: &str) -> String {
14    format!("assets/gallery/{name}.png")
15}
16
17fn main() -> Result<(), Box<dyn std::error::Error>> {
18    std::fs::create_dir_all("assets/gallery")?;
19
20    scatter()?;
21    smooth()?;
22    histogram()?;
23    bar()?;
24    boxplot()?;
25    violin()?;
26    continuous_color()?;
27    facet()?;
28    density()?;
29
30    println!("Gallery written to assets/gallery/");
31    Ok(())
32}
33
34/// Grouped scatter with a qualitative Brewer palette.
35fn scatter() -> Result<(), Box<dyn std::error::Error>> {
36    let n = 150;
37    let x: Vec<f64> = (0..n).map(|i| 4.5 + i as f64 * 0.02).collect();
38    let y: Vec<f64> = (0..n)
39        .map(|i| 2.5 + (i as f64 * 0.15).sin() + (i % 3) as f64 * 0.6)
40        .collect();
41    let species: Vec<&str> = (0..n)
42        .map(|i| ["setosa", "versicolor", "virginica"][i % 3])
43        .collect();
44
45    let df = df! { "x" => x, "y" => y, "species" => species }?;
46    GGPlot::new(df)
47        .aes(Aes::new().x("x").y("y").color("species"))
48        .geom_point()
49        .scale_color_brewer(PaletteName::Set1)
50        .title("Grouped Scatter")
51        .xlab("Sepal Length")
52        .ylab("Sepal Width")
53        .theme_minimal()
54        .save_with_size(&out("scatter"), W, H)?;
55    Ok(())
56}
57
58/// Points overlaid with a LOESS trend line and confidence band.
59fn smooth() -> Result<(), Box<dyn std::error::Error>> {
60    let n = 120;
61    let x: Vec<f64> = (0..n).map(|i| i as f64 * 0.1).collect();
62    let y: Vec<f64> = (0..n)
63        .map(|i| {
64            let t = i as f64 * 0.1;
65            (t * 0.6).sin() * 3.0 + t * 0.2 + ((i * 7919 % 100) as f64 / 100.0 - 0.5) * 1.5
66        })
67        .collect();
68
69    let df = df! { "x" => x, "y" => y }?;
70    GGPlot::new(df)
71        .aes(Aes::new().x("x").y("y"))
72        .geom_point()
73        .geom_smooth_with(GeomSmooth {
74            method: SmoothMethod::Loess { span: 0.5 },
75            ..Default::default()
76        })
77        .title("LOESS Smoothing")
78        .xlab("x")
79        .ylab("y")
80        .theme_bw()
81        .save_with_size(&out("smooth"), W, H)?;
82    Ok(())
83}
84
85/// Histogram of an approximately-normal sample.
86fn histogram() -> Result<(), Box<dyn std::error::Error>> {
87    let values: Vec<f64> = (0..1500)
88        .map(|i: i32| {
89            let r: f64 = (0..6)
90                .map(|k| ((i * (1237 + k * 311) + 5678) % 1000) as f64 / 1000.0)
91                .sum();
92            (r - 3.0) * 2.0
93        })
94        .collect();
95
96    let df = df! { "measurement" => values }?;
97    GGPlot::new(df)
98        .aes(Aes::new().x("measurement"))
99        .geom_histogram_with(GeomHistogram {
100            bins: 30,
101            ..Default::default()
102        })
103        .title("Histogram")
104        .xlab("Value")
105        .ylab("Count")
106        .theme_minimal()
107        .save_with_size(&out("histogram"), W, H)?;
108    Ok(())
109}
110
111/// Bar chart of category counts with a fill palette.
112fn bar() -> Result<(), Box<dyn std::error::Error>> {
113    let mut fruit: Vec<&str> = Vec::new();
114    for (f, c) in [
115        ("Apple", 8),
116        ("Banana", 5),
117        ("Cherry", 11),
118        ("Date", 3),
119        ("Elder", 7),
120    ] {
121        for _ in 0..c {
122            fruit.push(f);
123        }
124    }
125    let df = df! { "fruit" => fruit }?;
126    GGPlot::new(df)
127        .aes(Aes::new().x("fruit").fill("fruit"))
128        .geom_bar()
129        .scale_fill_brewer(PaletteName::Set2)
130        .title("Bar Chart")
131        .xlab("Fruit")
132        .ylab("Count")
133        .theme_minimal()
134        .save_with_size(&out("bar"), W, H)?;
135    Ok(())
136}
137
138/// Grouped boxplots.
139fn boxplot() -> Result<(), Box<dyn std::error::Error>> {
140    let n = 240;
141    let group: Vec<&str> = (0..n).map(|i| ["A", "B", "C", "D"][i % 4]).collect();
142    let value: Vec<f64> = (0..n)
143        .map(|i| {
144            let base = (i % 4) as f64 * 1.5;
145            base + (i as f64 * 0.4).sin() * 1.2 + ((i * 6151 % 100) as f64 / 100.0 - 0.5) * 2.0
146        })
147        .collect();
148
149    let df = df! { "group" => group, "value" => value }?;
150    GGPlot::new(df)
151        .aes(Aes::new().x("group").y("value"))
152        .geom_boxplot_with(GeomBoxplot {
153            fill: (70, 130, 180),
154            ..Default::default()
155        })
156        .title("Boxplot")
157        .xlab("Group")
158        .ylab("Value")
159        .theme_bw()
160        .save_with_size(&out("boxplot"), W, H)?;
161    Ok(())
162}
163
164/// Violin plots of grouped distributions.
165fn violin() -> Result<(), Box<dyn std::error::Error>> {
166    let n = 360;
167    let group: Vec<&str> = (0..n).map(|i| ["X", "Y", "Z"][i % 3]).collect();
168    let value: Vec<f64> = (0..n)
169        .map(|i| {
170            let g = (i % 3) as f64;
171            g * 2.0 + (i as f64 * 0.5).sin() * 1.5 + ((i * 4231 % 100) as f64 / 100.0 - 0.5) * 2.5
172        })
173        .collect();
174
175    let df = df! { "group" => group, "value" => value }?;
176    GGPlot::new(df)
177        .aes(Aes::new().x("group").y("value").fill("group"))
178        .geom_violin()
179        .scale_fill_brewer(PaletteName::Accent)
180        .title("Violin")
181        .xlab("Group")
182        .ylab("Value")
183        .theme_minimal()
184        .save_with_size(&out("violin"), W, H)?;
185    Ok(())
186}
187
188/// Spiral scatter coloured by a continuous variable (viridis).
189fn continuous_color() -> Result<(), Box<dyn std::error::Error>> {
190    let n = 400;
191    let x: Vec<f64> = (0..n)
192        .map(|i| {
193            let t = i as f64 * 0.05;
194            t.cos() * (1.0 + t * 0.12)
195        })
196        .collect();
197    let y: Vec<f64> = (0..n)
198        .map(|i| {
199            let t = i as f64 * 0.05;
200            t.sin() * (1.0 + t * 0.12)
201        })
202        .collect();
203    let z: Vec<f64> = (0..n).map(|i| i as f64 * 0.05).collect();
204
205    let df = df! { "x" => x, "y" => y, "z" => z }?;
206    GGPlot::new(df)
207        .aes(Aes::new().x("x").y("y").color("z"))
208        .geom_point()
209        .scale_color_viridis_c()
210        .title("Continuous Color (viridis)")
211        .xlab("x")
212        .ylab("y")
213        .theme_minimal()
214        .save_with_size(&out("continuous_color"), W, H)?;
215    Ok(())
216}
217
218/// Faceted scatter, one panel per group.
219fn facet() -> Result<(), Box<dyn std::error::Error>> {
220    let n = 180;
221    let x: Vec<f64> = (0..n).map(|i| (i as f64 * 0.1).cos() * 3.0).collect();
222    let y: Vec<f64> = (0..n)
223        .map(|i| (i as f64 * 0.1).sin() * 3.0 + (i % 3) as f64)
224        .collect();
225    let species: Vec<&str> = (0..n)
226        .map(|i| ["setosa", "versicolor", "virginica"][i % 3])
227        .collect();
228
229    let df = df! { "x" => x, "y" => y, "species" => species }?;
230    GGPlot::new(df)
231        .aes(Aes::new().x("x").y("y").color("species"))
232        .geom_point()
233        .facet_wrap("species", Some(3))
234        .scale_color_brewer(PaletteName::Set1)
235        .title("Facet Wrap")
236        .xlab("x")
237        .ylab("y")
238        .theme_bw()
239        .save_with_size(&out("facet"), W, H)?;
240    Ok(())
241}
242
243/// Overlapping density curves by group.
244fn density() -> Result<(), Box<dyn std::error::Error>> {
245    let n = 600;
246    let group: Vec<&str> = (0..n).map(|i| ["Group 1", "Group 2"][i % 2]).collect();
247    let value: Vec<f64> = (0..n)
248        .map(|i| {
249            let shift = (i % 2) as f64 * 2.5;
250            let t = i as f64 * 0.05;
251            shift + (t.sin() + (t * 1.7).cos()) + ((i * 3319 % 100) as f64 / 100.0 - 0.5) * PI
252        })
253        .collect();
254
255    let df = df! { "value" => value, "group" => group }?;
256    GGPlot::new(df)
257        .aes(Aes::new().x("value").fill("group").color("group"))
258        .geom_density()
259        .scale_fill_brewer(PaletteName::Set1)
260        .scale_color_brewer(PaletteName::Set1)
261        .title("Density by Group")
262        .xlab("Value")
263        .ylab("Density")
264        .theme_minimal()
265        .save_with_size(&out("density"), W, H)?;
266    Ok(())
267}