pub struct GGPlot { /* private fields */ }Expand description
The top-level plot specification — builder pattern.
Implementations§
Source§impl GGPlot
impl GGPlot
Sourcepub fn new(data: impl GGData) -> Self
pub fn new(data: impl GGData) -> Self
Create a new plot with the given data source.
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "fruit" => ["Apple", "Apple", "Apple", "Banana", "Banana",
7 "Cherry", "Cherry", "Cherry", "Cherry", "Date"],
8 }?;
9
10 GGPlot::new(df)
11 .aes(Aes::new().x("fruit"))
12 .geom_bar()
13 .title("Fruit Counts")
14 .xlab("Fruit")
15 .ylab("Count")
16 .save("bar_chart.svg")?;
17
18 println!("Saved bar_chart.svg");
19 Ok(())
20}More examples
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "language" => ["Rust", "Python", "JavaScript", "Go", "TypeScript", "Java", "C++"],
7 "satisfaction" => [92.0, 88.0, 85.0, 78.0, 82.0, 70.0, 75.0],
8 }?;
9
10 // Horizontal bar chart using coord_flip
11 GGPlot::new(df)
12 .aes(Aes::new().x("language").y("satisfaction"))
13 .geom_col()
14 .coord_flip()
15 .title("Developer Satisfaction by Language")
16 .xlab("Language")
17 .ylab("Satisfaction Score")
18 .save("coord_flip.svg")?;
19
20 println!("Saved coord_flip.svg");
21 Ok(())
22}45fn 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
68/// Points overlaid with a LOESS trend line and confidence band.
69fn 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
95/// Histogram of an approximately-normal sample.
96fn 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
121/// Bar chart of category counts with a fill palette.
122fn 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
148/// Grouped boxplots.
149fn 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
174/// Violin plots of grouped distributions.
175fn 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
198/// Spiral scatter coloured by a continuous variable (viridis).
199fn 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
228/// Faceted scatter, one panel per group.
229fn 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
253/// Overlapping density curves by group.
254fn 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
279/// Filled contour bands from a gridded surface.
280fn 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
302/// Hexagonal binning of a 2-D point cloud.
303fn 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
328/// Heatmap of a gridded value with `geom_tile`.
329fn 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
349/// Jittered categorical scatter (`geom_jitter`).
350fn 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
370/// A confidence band (`geom_ribbon`) under a line.
371fn 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
389/// Stacked areas by group.
390fn 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
418/// The same plot rendered under every built-in theme.
419fn 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}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Sales data with a notable spike
6 let month: Vec<f64> = (1..=12).map(|i| i as f64).collect();
7 let sales = vec![
8 120.0, 135.0, 150.0, 180.0, 210.0, 310.0, 280.0, 250.0, 190.0, 170.0, 155.0, 140.0,
9 ];
10
11 let df = df! {
12 "month" => month,
13 "sales" => sales,
14 }?;
15
16 GGPlot::new(df)
17 .aes(Aes::new().x("month").y("sales"))
18 .geom_line()
19 .geom_point()
20 // Highlight the peak region
21 .annotate_rect(4.5, 7.5, 100.0, 320.0)
22 // Label the peak
23 .annotate_text("Summer Peak", 6.0, 330.0)
24 // Draw an arrow-like segment pointing to the max
25 .annotate_segment(7.5, 330.0, 6.2, 312.0)
26 .title("Monthly Sales with Annotations")
27 .xlab("Month")
28 .ylab("Sales ($)")
29 .save("annotations.svg")?;
30
31 println!("Saved annotations.svg");
32 Ok(())
33}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate two overlapping distributions using deterministic pseudo-random values
6 let value: Vec<f64> = (0..200_u64)
7 .map(|i| {
8 let r = ((i.wrapping_mul(1103515245).wrapping_add(12345)) % (1 << 16)) as f64
9 / (1u64 << 16) as f64;
10 if i < 100 {
11 3.0 + r * 4.0
12 } else {
13 5.0 + r * 4.0
14 }
15 })
16 .collect();
17 let group: Vec<&str> = (0..200)
18 .map(|i| if i < 100 { "Group A" } else { "Group B" })
19 .collect();
20
21 let df = df! {
22 "value" => value,
23 "group" => group,
24 }?;
25
26 GGPlot::new(df)
27 .aes(Aes::new().x("value").color("group"))
28 .geom_density()
29 .title("Density Plot by Group")
30 .xlab("Value")
31 .ylab("Density")
32 .save("density.svg")?;
33
34 println!("Saved density.svg");
35 Ok(())
36}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let sepal_length: Vec<f64> = (0..50).map(|i| 4.5 + i as f64 * 0.05).collect();
6 let sepal_width: Vec<f64> = (0..50)
7 .map(|i| 2.0 + (i as f64 * 0.3).sin() + i as f64 * 0.02)
8 .collect();
9 let species: Vec<&str> = (0..50)
10 .map(|i| match i % 3 {
11 0 => "setosa",
12 1 => "versicolor",
13 _ => "virginica",
14 })
15 .collect();
16
17 let df = df! {
18 "sepal_length" => sepal_length,
19 "sepal_width" => sepal_width,
20 "species" => species,
21 }?;
22
23 GGPlot::new(df)
24 .aes(
25 Aes::new()
26 .x("sepal_length")
27 .y("sepal_width")
28 .color("species"),
29 )
30 .geom_point()
31 .title("Iris Scatter Plot")
32 .xlab("Sepal Length")
33 .ylab("Sepal Width")
34 .save("scatter.svg")?;
35
36 println!("Saved scatter.svg");
37 Ok(())
38}Sourcepub fn aes(self, mapping: Aes) -> Self
pub fn aes(self, mapping: Aes) -> Self
Set the plot-level aesthetic mapping.
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "fruit" => ["Apple", "Apple", "Apple", "Banana", "Banana",
7 "Cherry", "Cherry", "Cherry", "Cherry", "Date"],
8 }?;
9
10 GGPlot::new(df)
11 .aes(Aes::new().x("fruit"))
12 .geom_bar()
13 .title("Fruit Counts")
14 .xlab("Fruit")
15 .ylab("Count")
16 .save("bar_chart.svg")?;
17
18 println!("Saved bar_chart.svg");
19 Ok(())
20}More examples
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "language" => ["Rust", "Python", "JavaScript", "Go", "TypeScript", "Java", "C++"],
7 "satisfaction" => [92.0, 88.0, 85.0, 78.0, 82.0, 70.0, 75.0],
8 }?;
9
10 // Horizontal bar chart using coord_flip
11 GGPlot::new(df)
12 .aes(Aes::new().x("language").y("satisfaction"))
13 .geom_col()
14 .coord_flip()
15 .title("Developer Satisfaction by Language")
16 .xlab("Language")
17 .ylab("Satisfaction Score")
18 .save("coord_flip.svg")?;
19
20 println!("Saved coord_flip.svg");
21 Ok(())
22}45fn 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
68/// Points overlaid with a LOESS trend line and confidence band.
69fn 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
95/// Histogram of an approximately-normal sample.
96fn 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
121/// Bar chart of category counts with a fill palette.
122fn 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
148/// Grouped boxplots.
149fn 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
174/// Violin plots of grouped distributions.
175fn 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
198/// Spiral scatter coloured by a continuous variable (viridis).
199fn 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
228/// Faceted scatter, one panel per group.
229fn 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
253/// Overlapping density curves by group.
254fn 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
279/// Filled contour bands from a gridded surface.
280fn 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
302/// Hexagonal binning of a 2-D point cloud.
303fn 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
328/// Heatmap of a gridded value with `geom_tile`.
329fn 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
349/// Jittered categorical scatter (`geom_jitter`).
350fn 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
370/// A confidence band (`geom_ribbon`) under a line.
371fn 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
389/// Stacked areas by group.
390fn 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
418/// The same plot rendered under every built-in theme.
419fn 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}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Sales data with a notable spike
6 let month: Vec<f64> = (1..=12).map(|i| i as f64).collect();
7 let sales = vec![
8 120.0, 135.0, 150.0, 180.0, 210.0, 310.0, 280.0, 250.0, 190.0, 170.0, 155.0, 140.0,
9 ];
10
11 let df = df! {
12 "month" => month,
13 "sales" => sales,
14 }?;
15
16 GGPlot::new(df)
17 .aes(Aes::new().x("month").y("sales"))
18 .geom_line()
19 .geom_point()
20 // Highlight the peak region
21 .annotate_rect(4.5, 7.5, 100.0, 320.0)
22 // Label the peak
23 .annotate_text("Summer Peak", 6.0, 330.0)
24 // Draw an arrow-like segment pointing to the max
25 .annotate_segment(7.5, 330.0, 6.2, 312.0)
26 .title("Monthly Sales with Annotations")
27 .xlab("Month")
28 .ylab("Sales ($)")
29 .save("annotations.svg")?;
30
31 println!("Saved annotations.svg");
32 Ok(())
33}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate two overlapping distributions using deterministic pseudo-random values
6 let value: Vec<f64> = (0..200_u64)
7 .map(|i| {
8 let r = ((i.wrapping_mul(1103515245).wrapping_add(12345)) % (1 << 16)) as f64
9 / (1u64 << 16) as f64;
10 if i < 100 {
11 3.0 + r * 4.0
12 } else {
13 5.0 + r * 4.0
14 }
15 })
16 .collect();
17 let group: Vec<&str> = (0..200)
18 .map(|i| if i < 100 { "Group A" } else { "Group B" })
19 .collect();
20
21 let df = df! {
22 "value" => value,
23 "group" => group,
24 }?;
25
26 GGPlot::new(df)
27 .aes(Aes::new().x("value").color("group"))
28 .geom_density()
29 .title("Density Plot by Group")
30 .xlab("Value")
31 .ylab("Density")
32 .save("density.svg")?;
33
34 println!("Saved density.svg");
35 Ok(())
36}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let sepal_length: Vec<f64> = (0..50).map(|i| 4.5 + i as f64 * 0.05).collect();
6 let sepal_width: Vec<f64> = (0..50)
7 .map(|i| 2.0 + (i as f64 * 0.3).sin() + i as f64 * 0.02)
8 .collect();
9 let species: Vec<&str> = (0..50)
10 .map(|i| match i % 3 {
11 0 => "setosa",
12 1 => "versicolor",
13 _ => "virginica",
14 })
15 .collect();
16
17 let df = df! {
18 "sepal_length" => sepal_length,
19 "sepal_width" => sepal_width,
20 "species" => species,
21 }?;
22
23 GGPlot::new(df)
24 .aes(
25 Aes::new()
26 .x("sepal_length")
27 .y("sepal_width")
28 .color("species"),
29 )
30 .geom_point()
31 .title("Iris Scatter Plot")
32 .xlab("Sepal Length")
33 .ylab("Sepal Width")
34 .save("scatter.svg")?;
35
36 println!("Saved scatter.svg");
37 Ok(())
38}Sourcepub fn geom_point(self) -> Self
pub fn geom_point(self) -> Self
Examples found in repository?
45fn 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
68/// Points overlaid with a LOESS trend line and confidence band.
69fn 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
95/// Histogram of an approximately-normal sample.
96fn 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
121/// Bar chart of category counts with a fill palette.
122fn 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
148/// Grouped boxplots.
149fn 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
174/// Violin plots of grouped distributions.
175fn 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
198/// Spiral scatter coloured by a continuous variable (viridis).
199fn 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
228/// Faceted scatter, one panel per group.
229fn 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
253/// Overlapping density curves by group.
254fn 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
279/// Filled contour bands from a gridded surface.
280fn 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
302/// Hexagonal binning of a 2-D point cloud.
303fn 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
328/// Heatmap of a gridded value with `geom_tile`.
329fn 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
349/// Jittered categorical scatter (`geom_jitter`).
350fn 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
370/// A confidence band (`geom_ribbon`) under a line.
371fn 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
389/// Stacked areas by group.
390fn 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
418/// The same plot rendered under every built-in theme.
419fn 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}More examples
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Sales data with a notable spike
6 let month: Vec<f64> = (1..=12).map(|i| i as f64).collect();
7 let sales = vec![
8 120.0, 135.0, 150.0, 180.0, 210.0, 310.0, 280.0, 250.0, 190.0, 170.0, 155.0, 140.0,
9 ];
10
11 let df = df! {
12 "month" => month,
13 "sales" => sales,
14 }?;
15
16 GGPlot::new(df)
17 .aes(Aes::new().x("month").y("sales"))
18 .geom_line()
19 .geom_point()
20 // Highlight the peak region
21 .annotate_rect(4.5, 7.5, 100.0, 320.0)
22 // Label the peak
23 .annotate_text("Summer Peak", 6.0, 330.0)
24 // Draw an arrow-like segment pointing to the max
25 .annotate_segment(7.5, 330.0, 6.2, 312.0)
26 .title("Monthly Sales with Annotations")
27 .xlab("Month")
28 .ylab("Sales ($)")
29 .save("annotations.svg")?;
30
31 println!("Saved annotations.svg");
32 Ok(())
33}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let sepal_length: Vec<f64> = (0..50).map(|i| 4.5 + i as f64 * 0.05).collect();
6 let sepal_width: Vec<f64> = (0..50)
7 .map(|i| 2.0 + (i as f64 * 0.3).sin() + i as f64 * 0.02)
8 .collect();
9 let species: Vec<&str> = (0..50)
10 .map(|i| match i % 3 {
11 0 => "setosa",
12 1 => "versicolor",
13 _ => "virginica",
14 })
15 .collect();
16
17 let df = df! {
18 "sepal_length" => sepal_length,
19 "sepal_width" => sepal_width,
20 "species" => species,
21 }?;
22
23 GGPlot::new(df)
24 .aes(
25 Aes::new()
26 .x("sepal_length")
27 .y("sepal_width")
28 .color("species"),
29 )
30 .geom_point()
31 .title("Iris Scatter Plot")
32 .xlab("Sepal Length")
33 .ylab("Sepal Width")
34 .save("scatter.svg")?;
35
36 println!("Saved scatter.svg");
37 Ok(())
38}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate noisy sine wave data
6 let x: Vec<f64> = (0..80).map(|i| (i as f64) * 0.1).collect();
7 let y: Vec<f64> = (0..80)
8 .map(|i| {
9 let xv = (i as f64) * 0.1;
10 let noise = ((i * 17 + 3) % 11) as f64 / 11.0 - 0.5; // deterministic pseudo-noise
11 (xv * 0.8).sin() * 2.0 + noise * 1.5
12 })
13 .collect();
14
15 let df = df! {
16 "x" => &x,
17 "y" => &y,
18 }?;
19
20 // Linear smooth (default)
21 GGPlot::new(df.clone())
22 .aes(Aes::new().x("x").y("y"))
23 .geom_point()
24 .geom_smooth()
25 .title("Linear Smooth (method = lm)")
26 .save("smooth_lm.svg")?;
27
28 println!("Saved smooth_lm.svg");
29
30 // LOESS smooth
31 GGPlot::new(df)
32 .aes(Aes::new().x("x").y("y"))
33 .geom_point()
34 .geom_smooth_with(GeomSmooth::default().loess(0.3))
35 .title("LOESS Smooth (span = 0.3)")
36 .save("smooth_loess.svg")?;
37
38 println!("Saved smooth_loess.svg");
39 Ok(())
40}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Exponential growth data (e.g., population, bacteria count)
6 let year: Vec<f64> = (0..30).map(|i| 1990.0 + i as f64).collect();
7 let count: Vec<f64> = (0..30)
8 .map(|i| {
9 let base = 100.0 * (1.15_f64).powi(i); // 15% growth per year
10 let noise = ((i * 37 + 7) % 13) as f64 / 13.0 * 0.2 + 0.9;
11 base * noise
12 })
13 .collect();
14
15 let df = df! {
16 "year" => &year,
17 "count" => &count,
18 }?;
19
20 // Linear scale — exponential curve
21 GGPlot::new(df.clone())
22 .aes(Aes::new().x("year").y("count"))
23 .geom_point()
24 .geom_line()
25 .title("Exponential Growth (Linear Scale)")
26 .xlab("Year")
27 .ylab("Count")
28 .save("log_scale_linear.svg")?;
29
30 println!("Saved log_scale_linear.svg");
31
32 // Log10 y-axis — should appear roughly linear
33 GGPlot::new(df)
34 .aes(Aes::new().x("year").y("count"))
35 .geom_point()
36 .geom_line()
37 .scale_y_log10()
38 .title("Exponential Growth (Log10 Y Scale)")
39 .xlab("Year")
40 .ylab("Count (log10)")
41 .save("log_scale_log10.svg")?;
42
43 println!("Saved log_scale_log10.svg");
44 Ok(())
45}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate scatter data with a continuous variable for color
6 let x: Vec<f64> = (0..200)
7 .map(|i| {
8 let t = i as f64 * 0.05;
9 t.cos() * (1.0 + t * 0.3)
10 })
11 .collect();
12 let y: Vec<f64> = (0..200)
13 .map(|i| {
14 let t = i as f64 * 0.05;
15 t.sin() * (1.0 + t * 0.3)
16 })
17 .collect();
18 let z: Vec<f64> = (0..200).map(|i| i as f64 * 0.05).collect();
19
20 let df = df! {
21 "x" => &x,
22 "y" => &y,
23 "z" => &z,
24 }?;
25
26 // Default blue-to-red gradient
27 GGPlot::new(df.clone())
28 .aes(Aes::new().x("x").y("y").color("z"))
29 .geom_point()
30 .title("Continuous Color (default gradient)")
31 .xlab("X")
32 .ylab("Y")
33 .save("continuous_color.svg")?;
34
35 println!("Saved continuous_color.svg");
36
37 // Custom gradient: dark blue to yellow
38 GGPlot::new(df)
39 .aes(Aes::new().x("x").y("y").color("z"))
40 .geom_point()
41 .scale_color_gradient(RGBAColor::new(10, 30, 100), RGBAColor::new(255, 230, 50))
42 .title("Continuous Color (custom gradient)")
43 .xlab("X")
44 .ylab("Y")
45 .save("continuous_color_custom.svg")?;
46
47 println!("Saved continuous_color_custom.svg");
48 Ok(())
49}pub fn geom_point_with(self, geom: GeomPoint) -> Self
Sourcepub fn geom_line(self) -> Self
pub fn geom_line(self) -> Self
Examples found in repository?
371fn 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}More examples
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Sales data with a notable spike
6 let month: Vec<f64> = (1..=12).map(|i| i as f64).collect();
7 let sales = vec![
8 120.0, 135.0, 150.0, 180.0, 210.0, 310.0, 280.0, 250.0, 190.0, 170.0, 155.0, 140.0,
9 ];
10
11 let df = df! {
12 "month" => month,
13 "sales" => sales,
14 }?;
15
16 GGPlot::new(df)
17 .aes(Aes::new().x("month").y("sales"))
18 .geom_line()
19 .geom_point()
20 // Highlight the peak region
21 .annotate_rect(4.5, 7.5, 100.0, 320.0)
22 // Label the peak
23 .annotate_text("Summer Peak", 6.0, 330.0)
24 // Draw an arrow-like segment pointing to the max
25 .annotate_segment(7.5, 330.0, 6.2, 312.0)
26 .title("Monthly Sales with Annotations")
27 .xlab("Month")
28 .ylab("Sales ($)")
29 .save("annotations.svg")?;
30
31 println!("Saved annotations.svg");
32 Ok(())
33}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Exponential growth data (e.g., population, bacteria count)
6 let year: Vec<f64> = (0..30).map(|i| 1990.0 + i as f64).collect();
7 let count: Vec<f64> = (0..30)
8 .map(|i| {
9 let base = 100.0 * (1.15_f64).powi(i); // 15% growth per year
10 let noise = ((i * 37 + 7) % 13) as f64 / 13.0 * 0.2 + 0.9;
11 base * noise
12 })
13 .collect();
14
15 let df = df! {
16 "year" => &year,
17 "count" => &count,
18 }?;
19
20 // Linear scale — exponential curve
21 GGPlot::new(df.clone())
22 .aes(Aes::new().x("year").y("count"))
23 .geom_point()
24 .geom_line()
25 .title("Exponential Growth (Linear Scale)")
26 .xlab("Year")
27 .ylab("Count")
28 .save("log_scale_linear.svg")?;
29
30 println!("Saved log_scale_linear.svg");
31
32 // Log10 y-axis — should appear roughly linear
33 GGPlot::new(df)
34 .aes(Aes::new().x("year").y("count"))
35 .geom_point()
36 .geom_line()
37 .scale_y_log10()
38 .title("Exponential Growth (Log10 Y Scale)")
39 .xlab("Year")
40 .ylab("Count (log10)")
41 .save("log_scale_log10.svg")?;
42
43 println!("Saved log_scale_log10.svg");
44 Ok(())
45}pub fn geom_line_with(self, geom: GeomLine) -> Self
Sourcepub fn geom_bar(self) -> Self
pub fn geom_bar(self) -> Self
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "fruit" => ["Apple", "Apple", "Apple", "Banana", "Banana",
7 "Cherry", "Cherry", "Cherry", "Cherry", "Date"],
8 }?;
9
10 GGPlot::new(df)
11 .aes(Aes::new().x("fruit"))
12 .geom_bar()
13 .title("Fruit Counts")
14 .xlab("Fruit")
15 .ylab("Count")
16 .save("bar_chart.svg")?;
17
18 println!("Saved bar_chart.svg");
19 Ok(())
20}More examples
122fn 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}pub fn geom_bar_with(self, geom: GeomBar) -> Self
pub fn geom_histogram(self) -> Self
Sourcepub fn geom_histogram_with(self, geom: GeomHistogram) -> Self
pub fn geom_histogram_with(self, geom: GeomHistogram) -> Self
Examples found in repository?
96fn 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}More examples
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate approximate normal distribution using central limit theorem
6 let values: Vec<f64> = (0..1000)
7 .map(|i: i32| {
8 // Sum of pseudo-random uniform values → approx normal by CLT
9 let r1 = ((i * 1237 + 5678) % 1000) as f64 / 1000.0;
10 let r2 = ((i * 8731 + 4321) % 1000) as f64 / 1000.0;
11 let r3 = ((i * 4567 + 8901) % 1000) as f64 / 1000.0;
12 let r4 = ((i * 6543 + 2109) % 1000) as f64 / 1000.0;
13 let r5 = ((i * 3571 + 7654) % 1000) as f64 / 1000.0;
14 let r6 = ((i * 9137 + 3456) % 1000) as f64 / 1000.0;
15 // Sum of 6 uniforms: mean=3, std≈0.745; normalize to mean=0, std≈1.5
16 (r1 + r2 + r3 + r4 + r5 + r6 - 3.0) * 2.0
17 })
18 .collect();
19
20 let df = df! {
21 "measurement" => values,
22 }?;
23
24 GGPlot::new(df)
25 .aes(Aes::new().x("measurement"))
26 .geom_histogram_with(GeomHistogram {
27 bins: 25,
28 ..Default::default()
29 })
30 .title("Distribution of Measurements")
31 .xlab("Value")
32 .ylab("Frequency")
33 .save("histogram.svg")?;
34
35 println!("Saved histogram.svg");
36 Ok(())
37}pub fn geom_boxplot(self) -> Self
Sourcepub fn geom_boxplot_with(self, geom: GeomBoxplot) -> Self
pub fn geom_boxplot_with(self, geom: GeomBoxplot) -> Self
Examples found in repository?
149fn 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}Sourcepub fn geom_smooth(self) -> Self
pub fn geom_smooth(self) -> Self
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate noisy sine wave data
6 let x: Vec<f64> = (0..80).map(|i| (i as f64) * 0.1).collect();
7 let y: Vec<f64> = (0..80)
8 .map(|i| {
9 let xv = (i as f64) * 0.1;
10 let noise = ((i * 17 + 3) % 11) as f64 / 11.0 - 0.5; // deterministic pseudo-noise
11 (xv * 0.8).sin() * 2.0 + noise * 1.5
12 })
13 .collect();
14
15 let df = df! {
16 "x" => &x,
17 "y" => &y,
18 }?;
19
20 // Linear smooth (default)
21 GGPlot::new(df.clone())
22 .aes(Aes::new().x("x").y("y"))
23 .geom_point()
24 .geom_smooth()
25 .title("Linear Smooth (method = lm)")
26 .save("smooth_lm.svg")?;
27
28 println!("Saved smooth_lm.svg");
29
30 // LOESS smooth
31 GGPlot::new(df)
32 .aes(Aes::new().x("x").y("y"))
33 .geom_point()
34 .geom_smooth_with(GeomSmooth::default().loess(0.3))
35 .title("LOESS Smooth (span = 0.3)")
36 .save("smooth_loess.svg")?;
37
38 println!("Saved smooth_loess.svg");
39 Ok(())
40}Sourcepub fn geom_smooth_with(self, geom: GeomSmooth) -> Self
pub fn geom_smooth_with(self, geom: GeomSmooth) -> Self
Examples found in repository?
69fn 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}More examples
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate noisy sine wave data
6 let x: Vec<f64> = (0..80).map(|i| (i as f64) * 0.1).collect();
7 let y: Vec<f64> = (0..80)
8 .map(|i| {
9 let xv = (i as f64) * 0.1;
10 let noise = ((i * 17 + 3) % 11) as f64 / 11.0 - 0.5; // deterministic pseudo-noise
11 (xv * 0.8).sin() * 2.0 + noise * 1.5
12 })
13 .collect();
14
15 let df = df! {
16 "x" => &x,
17 "y" => &y,
18 }?;
19
20 // Linear smooth (default)
21 GGPlot::new(df.clone())
22 .aes(Aes::new().x("x").y("y"))
23 .geom_point()
24 .geom_smooth()
25 .title("Linear Smooth (method = lm)")
26 .save("smooth_lm.svg")?;
27
28 println!("Saved smooth_lm.svg");
29
30 // LOESS smooth
31 GGPlot::new(df)
32 .aes(Aes::new().x("x").y("y"))
33 .geom_point()
34 .geom_smooth_with(GeomSmooth::default().loess(0.3))
35 .title("LOESS Smooth (span = 0.3)")
36 .save("smooth_loess.svg")?;
37
38 println!("Saved smooth_loess.svg");
39 Ok(())
40}Sourcepub fn geom_col(self) -> Self
pub fn geom_col(self) -> Self
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "language" => ["Rust", "Python", "JavaScript", "Go", "TypeScript", "Java", "C++"],
7 "satisfaction" => [92.0, 88.0, 85.0, 78.0, 82.0, 70.0, 75.0],
8 }?;
9
10 // Horizontal bar chart using coord_flip
11 GGPlot::new(df)
12 .aes(Aes::new().x("language").y("satisfaction"))
13 .geom_col()
14 .coord_flip()
15 .title("Developer Satisfaction by Language")
16 .xlab("Language")
17 .ylab("Satisfaction Score")
18 .save("coord_flip.svg")?;
19
20 println!("Saved coord_flip.svg");
21 Ok(())
22}pub fn geom_col_with(self, geom: GeomCol) -> Self
pub fn geom_hline(self, yintercept: f64) -> Self
Sourcepub fn geom_hline_with(self, geom: GeomHline) -> Self
pub fn geom_hline_with(self, geom: GeomHline) -> Self
Add a horizontal reference line with custom styling (color/linetype/width).
pub fn geom_vline(self, xintercept: f64) -> Self
Sourcepub fn geom_vline_with(self, geom: GeomVline) -> Self
pub fn geom_vline_with(self, geom: GeomVline) -> Self
Add a vertical reference line with custom styling (color/linetype/width).
Examples found in repository?
137fn detail(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
138 let name = "Meridian Components";
139 let mine: Vec<&Po> = rows.iter().filter(|p| p.supplier == name).collect();
140
141 let attributable: Vec<f64> = mine
142 .iter()
143 .filter(|p| p.attributable)
144 .map(|p| p.lead)
145 .collect();
146 let p90 = percentile(attributable.clone(), 0.90);
147
148 let dens_data: Vec<(String, Vec<Value>)> = vec![(
149 "lead".to_string(),
150 attributable.iter().map(|v| Value::Float(*v)).collect(),
151 )];
152 let excused_data: Vec<(String, Vec<Value>)> = vec![(
153 "lead".to_string(),
154 mine.iter()
155 .filter(|p| !p.attributable)
156 .map(|p| Value::Float(p.lead))
157 .collect(),
158 )];
159
160 GGPlot::new(dens_data)
161 .aes(Aes::new().x("lead"))
162 .geom_density_with(GeomDensity {
163 fill: TEAL,
164 color: (20, 110, 98),
165 alpha: 0.45,
166 line_width: 1.5,
167 })
168 // SLA threshold — mass to the left is the on-time share.
169 .geom_vline_with(GeomVline {
170 xintercept: CONTRACT,
171 color: CONTRACT_RED,
172 width: 1.5,
173 linetype: Linetype::Dashed,
174 alpha: 1.0,
175 })
176 // p90 — the number to size safety stock against.
177 .geom_vline_with(GeomVline {
178 xintercept: p90,
179 color: P90_BLUE,
180 width: 1.5,
181 linetype: Linetype::Dashed,
182 alpha: 1.0,
183 })
184 // Excused deliveries, shown distinctly and kept out of the density.
185 .geom_rug_with(GeomRug {
186 color: MUTED,
187 alpha: 0.7,
188 length: 0.04,
189 sides: "b".to_string(),
190 })
191 .layer_data(excused_data)
192 .layer_aes(Aes::new().x("lead"))
193 .annotate_text(&format!("contract {CONTRACT:.0}d"), CONTRACT + 1.0, 0.075)
194 .annotate_text(&format!("p90 {p90:.0}d"), p90 + 1.0, 0.06)
195 .annotate_text("<- excused (external)", 48.0, 0.008)
196 .title(&format!("{name} — lead-time distribution"))
197 .subtitle("attributable deliveries only; excused shown as rug")
198 .xlab("Actual lead time (days)")
199 .ylab("Density")
200 .theme_minimal()
201 .save_with_size(&out("supplier_leadtime"), W, H)?;
202 Ok(())
203}
204
205/// ECDF for one supplier — P(lead <= contract) reads straight off the curve.
206fn ecdf(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
207 let name = "Meridian Components";
208 let attributable: Vec<f64> = rows
209 .iter()
210 .filter(|p| p.supplier == name && p.attributable)
211 .map(|p| p.lead)
212 .collect();
213 let on_time =
214 attributable.iter().filter(|&&l| l <= CONTRACT).count() as f64 / attributable.len() as f64;
215 let data: Vec<(String, Vec<Value>)> = vec![(
216 "lead".to_string(),
217 attributable.iter().map(|v| Value::Float(*v)).collect(),
218 )];
219
220 GGPlot::new(data)
221 .aes(Aes::new().x("lead"))
222 .geom_step()
223 .stat(StatEcdf)
224 .geom_vline_with(GeomVline {
225 xintercept: CONTRACT,
226 color: CONTRACT_RED,
227 width: 1.5,
228 linetype: Linetype::Dashed,
229 alpha: 1.0,
230 })
231 .annotate_text(
232 &format!("on-time rate {:.0}%", on_time * 100.0),
233 CONTRACT + 1.0,
234 0.15,
235 )
236 .title(&format!("{name} — on-time reliability (ECDF)"))
237 .subtitle("cumulative share at the contract line = P(lead <= 30d)")
238 .xlab("Actual lead time (days)")
239 .ylab("Cumulative share")
240 .theme_bw()
241 .save_with_size(&out("supplier_leadtime_ecdf"), W, H)?;
242 Ok(())
243}
244
245/// Compare suppliers: overlaid per-supplier densities against the contract line.
246fn compare(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
247 let lead: Vec<Value> = rows
248 .iter()
249 .filter(|p| p.attributable)
250 .map(|p| Value::Float(p.lead))
251 .collect();
252 let supplier: Vec<Value> = rows
253 .iter()
254 .filter(|p| p.attributable)
255 .map(|p| Value::Str(p.supplier.to_string()))
256 .collect();
257 let data: Vec<(String, Vec<Value>)> = vec![
258 ("lead".to_string(), lead),
259 ("supplier".to_string(), supplier),
260 ];
261
262 GGPlot::new(data)
263 .aes(Aes::new().x("lead").fill("supplier").color("supplier"))
264 .geom_density_with(GeomDensity {
265 alpha: 0.35,
266 line_width: 1.2,
267 ..Default::default()
268 })
269 .geom_vline_with(GeomVline {
270 xintercept: CONTRACT,
271 color: CONTRACT_RED,
272 width: 1.2,
273 linetype: Linetype::Dashed,
274 alpha: 1.0,
275 })
276 .scale_fill_brewer(PaletteName::Dark2)
277 .scale_color_brewer(PaletteName::Dark2)
278 .annotate_text("contract", CONTRACT + 1.0, 0.005)
279 .title("Lead-time distributions by supplier")
280 .subtitle("attributable deliveries; dashed line = contracted lead time")
281 .xlab("Actual lead time (days)")
282 .ylab("Density")
283 .theme_minimal()
284 .save_with_size(&out("supplier_leadtime_compare"), W, H)?;
285 Ok(())
286}pub fn geom_abline(self, slope: f64, intercept: f64) -> Self
Sourcepub fn geom_abline_with(self, geom: GeomAbline) -> Self
pub fn geom_abline_with(self, geom: GeomAbline) -> Self
Add a slope/intercept reference line with custom styling.
pub fn geom_text(self) -> Self
pub fn geom_text_with(self, geom: GeomText) -> Self
pub fn geom_label(self) -> Self
pub fn geom_label_with(self, geom: GeomLabel) -> Self
Sourcepub fn geom_area(self) -> Self
pub fn geom_area(self) -> Self
Examples found in repository?
390fn 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}pub fn geom_area_with(self, geom: GeomArea) -> Self
Sourcepub fn geom_ribbon(self) -> Self
pub fn geom_ribbon(self) -> Self
Examples found in repository?
371fn 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}pub fn geom_ribbon_with(self, geom: GeomRibbon) -> Self
pub fn geom_errorbar(self) -> Self
pub fn geom_errorbar_with(self, geom: GeomErrorbar) -> Self
pub fn geom_segment(self) -> Self
pub fn geom_segment_with(self, geom: GeomSegment) -> Self
Sourcepub fn geom_density(self) -> Self
pub fn geom_density(self) -> Self
Examples found in repository?
254fn 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}More examples
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate two overlapping distributions using deterministic pseudo-random values
6 let value: Vec<f64> = (0..200_u64)
7 .map(|i| {
8 let r = ((i.wrapping_mul(1103515245).wrapping_add(12345)) % (1 << 16)) as f64
9 / (1u64 << 16) as f64;
10 if i < 100 {
11 3.0 + r * 4.0
12 } else {
13 5.0 + r * 4.0
14 }
15 })
16 .collect();
17 let group: Vec<&str> = (0..200)
18 .map(|i| if i < 100 { "Group A" } else { "Group B" })
19 .collect();
20
21 let df = df! {
22 "value" => value,
23 "group" => group,
24 }?;
25
26 GGPlot::new(df)
27 .aes(Aes::new().x("value").color("group"))
28 .geom_density()
29 .title("Density Plot by Group")
30 .xlab("Value")
31 .ylab("Density")
32 .save("density.svg")?;
33
34 println!("Saved density.svg");
35 Ok(())
36}Sourcepub fn geom_density_with(self, geom: GeomDensity) -> Self
pub fn geom_density_with(self, geom: GeomDensity) -> Self
Examples found in repository?
137fn detail(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
138 let name = "Meridian Components";
139 let mine: Vec<&Po> = rows.iter().filter(|p| p.supplier == name).collect();
140
141 let attributable: Vec<f64> = mine
142 .iter()
143 .filter(|p| p.attributable)
144 .map(|p| p.lead)
145 .collect();
146 let p90 = percentile(attributable.clone(), 0.90);
147
148 let dens_data: Vec<(String, Vec<Value>)> = vec![(
149 "lead".to_string(),
150 attributable.iter().map(|v| Value::Float(*v)).collect(),
151 )];
152 let excused_data: Vec<(String, Vec<Value>)> = vec![(
153 "lead".to_string(),
154 mine.iter()
155 .filter(|p| !p.attributable)
156 .map(|p| Value::Float(p.lead))
157 .collect(),
158 )];
159
160 GGPlot::new(dens_data)
161 .aes(Aes::new().x("lead"))
162 .geom_density_with(GeomDensity {
163 fill: TEAL,
164 color: (20, 110, 98),
165 alpha: 0.45,
166 line_width: 1.5,
167 })
168 // SLA threshold — mass to the left is the on-time share.
169 .geom_vline_with(GeomVline {
170 xintercept: CONTRACT,
171 color: CONTRACT_RED,
172 width: 1.5,
173 linetype: Linetype::Dashed,
174 alpha: 1.0,
175 })
176 // p90 — the number to size safety stock against.
177 .geom_vline_with(GeomVline {
178 xintercept: p90,
179 color: P90_BLUE,
180 width: 1.5,
181 linetype: Linetype::Dashed,
182 alpha: 1.0,
183 })
184 // Excused deliveries, shown distinctly and kept out of the density.
185 .geom_rug_with(GeomRug {
186 color: MUTED,
187 alpha: 0.7,
188 length: 0.04,
189 sides: "b".to_string(),
190 })
191 .layer_data(excused_data)
192 .layer_aes(Aes::new().x("lead"))
193 .annotate_text(&format!("contract {CONTRACT:.0}d"), CONTRACT + 1.0, 0.075)
194 .annotate_text(&format!("p90 {p90:.0}d"), p90 + 1.0, 0.06)
195 .annotate_text("<- excused (external)", 48.0, 0.008)
196 .title(&format!("{name} — lead-time distribution"))
197 .subtitle("attributable deliveries only; excused shown as rug")
198 .xlab("Actual lead time (days)")
199 .ylab("Density")
200 .theme_minimal()
201 .save_with_size(&out("supplier_leadtime"), W, H)?;
202 Ok(())
203}
204
205/// ECDF for one supplier — P(lead <= contract) reads straight off the curve.
206fn ecdf(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
207 let name = "Meridian Components";
208 let attributable: Vec<f64> = rows
209 .iter()
210 .filter(|p| p.supplier == name && p.attributable)
211 .map(|p| p.lead)
212 .collect();
213 let on_time =
214 attributable.iter().filter(|&&l| l <= CONTRACT).count() as f64 / attributable.len() as f64;
215 let data: Vec<(String, Vec<Value>)> = vec![(
216 "lead".to_string(),
217 attributable.iter().map(|v| Value::Float(*v)).collect(),
218 )];
219
220 GGPlot::new(data)
221 .aes(Aes::new().x("lead"))
222 .geom_step()
223 .stat(StatEcdf)
224 .geom_vline_with(GeomVline {
225 xintercept: CONTRACT,
226 color: CONTRACT_RED,
227 width: 1.5,
228 linetype: Linetype::Dashed,
229 alpha: 1.0,
230 })
231 .annotate_text(
232 &format!("on-time rate {:.0}%", on_time * 100.0),
233 CONTRACT + 1.0,
234 0.15,
235 )
236 .title(&format!("{name} — on-time reliability (ECDF)"))
237 .subtitle("cumulative share at the contract line = P(lead <= 30d)")
238 .xlab("Actual lead time (days)")
239 .ylab("Cumulative share")
240 .theme_bw()
241 .save_with_size(&out("supplier_leadtime_ecdf"), W, H)?;
242 Ok(())
243}
244
245/// Compare suppliers: overlaid per-supplier densities against the contract line.
246fn compare(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
247 let lead: Vec<Value> = rows
248 .iter()
249 .filter(|p| p.attributable)
250 .map(|p| Value::Float(p.lead))
251 .collect();
252 let supplier: Vec<Value> = rows
253 .iter()
254 .filter(|p| p.attributable)
255 .map(|p| Value::Str(p.supplier.to_string()))
256 .collect();
257 let data: Vec<(String, Vec<Value>)> = vec![
258 ("lead".to_string(), lead),
259 ("supplier".to_string(), supplier),
260 ];
261
262 GGPlot::new(data)
263 .aes(Aes::new().x("lead").fill("supplier").color("supplier"))
264 .geom_density_with(GeomDensity {
265 alpha: 0.35,
266 line_width: 1.2,
267 ..Default::default()
268 })
269 .geom_vline_with(GeomVline {
270 xintercept: CONTRACT,
271 color: CONTRACT_RED,
272 width: 1.2,
273 linetype: Linetype::Dashed,
274 alpha: 1.0,
275 })
276 .scale_fill_brewer(PaletteName::Dark2)
277 .scale_color_brewer(PaletteName::Dark2)
278 .annotate_text("contract", CONTRACT + 1.0, 0.005)
279 .title("Lead-time distributions by supplier")
280 .subtitle("attributable deliveries; dashed line = contracted lead time")
281 .xlab("Actual lead time (days)")
282 .ylab("Density")
283 .theme_minimal()
284 .save_with_size(&out("supplier_leadtime_compare"), W, H)?;
285 Ok(())
286}pub fn geom_rug(self) -> Self
Sourcepub fn geom_rug_with(self, geom: GeomRug) -> Self
pub fn geom_rug_with(self, geom: GeomRug) -> Self
Examples found in repository?
137fn detail(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
138 let name = "Meridian Components";
139 let mine: Vec<&Po> = rows.iter().filter(|p| p.supplier == name).collect();
140
141 let attributable: Vec<f64> = mine
142 .iter()
143 .filter(|p| p.attributable)
144 .map(|p| p.lead)
145 .collect();
146 let p90 = percentile(attributable.clone(), 0.90);
147
148 let dens_data: Vec<(String, Vec<Value>)> = vec![(
149 "lead".to_string(),
150 attributable.iter().map(|v| Value::Float(*v)).collect(),
151 )];
152 let excused_data: Vec<(String, Vec<Value>)> = vec![(
153 "lead".to_string(),
154 mine.iter()
155 .filter(|p| !p.attributable)
156 .map(|p| Value::Float(p.lead))
157 .collect(),
158 )];
159
160 GGPlot::new(dens_data)
161 .aes(Aes::new().x("lead"))
162 .geom_density_with(GeomDensity {
163 fill: TEAL,
164 color: (20, 110, 98),
165 alpha: 0.45,
166 line_width: 1.5,
167 })
168 // SLA threshold — mass to the left is the on-time share.
169 .geom_vline_with(GeomVline {
170 xintercept: CONTRACT,
171 color: CONTRACT_RED,
172 width: 1.5,
173 linetype: Linetype::Dashed,
174 alpha: 1.0,
175 })
176 // p90 — the number to size safety stock against.
177 .geom_vline_with(GeomVline {
178 xintercept: p90,
179 color: P90_BLUE,
180 width: 1.5,
181 linetype: Linetype::Dashed,
182 alpha: 1.0,
183 })
184 // Excused deliveries, shown distinctly and kept out of the density.
185 .geom_rug_with(GeomRug {
186 color: MUTED,
187 alpha: 0.7,
188 length: 0.04,
189 sides: "b".to_string(),
190 })
191 .layer_data(excused_data)
192 .layer_aes(Aes::new().x("lead"))
193 .annotate_text(&format!("contract {CONTRACT:.0}d"), CONTRACT + 1.0, 0.075)
194 .annotate_text(&format!("p90 {p90:.0}d"), p90 + 1.0, 0.06)
195 .annotate_text("<- excused (external)", 48.0, 0.008)
196 .title(&format!("{name} — lead-time distribution"))
197 .subtitle("attributable deliveries only; excused shown as rug")
198 .xlab("Actual lead time (days)")
199 .ylab("Density")
200 .theme_minimal()
201 .save_with_size(&out("supplier_leadtime"), W, H)?;
202 Ok(())
203}Sourcepub fn geom_jitter(self) -> Self
pub fn geom_jitter(self) -> Self
Examples found in repository?
350fn 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}pub fn geom_jitter_with(self, geom: GeomJitter) -> Self
pub fn geom_path(self) -> Self
pub fn geom_path_with(self, geom: GeomPath) -> Self
Sourcepub fn stat_ellipse(self) -> Self
pub fn stat_ellipse(self) -> Self
Add a confidence-ellipse layer (default 95%) as a path per group.
Sourcepub fn stat_ellipse_level(self, level: f64) -> Self
pub fn stat_ellipse_level(self, level: f64) -> Self
Add a confidence-ellipse layer at the given level (0, 1).
Sourcepub fn geom_step(self) -> Self
pub fn geom_step(self) -> Self
Examples found in repository?
206fn ecdf(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
207 let name = "Meridian Components";
208 let attributable: Vec<f64> = rows
209 .iter()
210 .filter(|p| p.supplier == name && p.attributable)
211 .map(|p| p.lead)
212 .collect();
213 let on_time =
214 attributable.iter().filter(|&&l| l <= CONTRACT).count() as f64 / attributable.len() as f64;
215 let data: Vec<(String, Vec<Value>)> = vec![(
216 "lead".to_string(),
217 attributable.iter().map(|v| Value::Float(*v)).collect(),
218 )];
219
220 GGPlot::new(data)
221 .aes(Aes::new().x("lead"))
222 .geom_step()
223 .stat(StatEcdf)
224 .geom_vline_with(GeomVline {
225 xintercept: CONTRACT,
226 color: CONTRACT_RED,
227 width: 1.5,
228 linetype: Linetype::Dashed,
229 alpha: 1.0,
230 })
231 .annotate_text(
232 &format!("on-time rate {:.0}%", on_time * 100.0),
233 CONTRACT + 1.0,
234 0.15,
235 )
236 .title(&format!("{name} — on-time reliability (ECDF)"))
237 .subtitle("cumulative share at the contract line = P(lead <= 30d)")
238 .xlab("Actual lead time (days)")
239 .ylab("Cumulative share")
240 .theme_bw()
241 .save_with_size(&out("supplier_leadtime_ecdf"), W, H)?;
242 Ok(())
243}pub fn geom_step_with(self, geom: GeomStep) -> Self
pub fn geom_freqpoly(self) -> Self
pub fn geom_freqpoly_with(self, geom: GeomFreqpoly) -> Self
pub fn geom_linerange(self) -> Self
pub fn geom_linerange_with(self, geom: GeomLinerange) -> Self
pub fn geom_pointrange(self) -> Self
pub fn geom_pointrange_with(self, geom: GeomPointrange) -> Self
pub fn geom_crossbar(self) -> Self
pub fn geom_crossbar_with(self, geom: GeomCrossbar) -> Self
pub fn geom_spoke(self) -> Self
pub fn geom_spoke_with(self, geom: GeomSpoke) -> Self
pub fn geom_rect(self) -> Self
pub fn geom_rect_with(self, geom: GeomRect) -> Self
Sourcepub fn geom_tile(self) -> Self
pub fn geom_tile(self) -> Self
Examples found in repository?
329fn 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}pub fn geom_tile_with(self, geom: GeomTile) -> Self
Sourcepub fn geom_raster(self) -> Self
pub fn geom_raster(self) -> Self
Dense regular grid of filled cells (heatmap/raster) from x, y, fill.
pub fn geom_raster_with(self, geom: GeomRaster) -> Self
pub fn geom_polygon(self) -> Self
pub fn geom_polygon_with(self, geom: GeomPolygon) -> Self
pub fn geom_curve(self) -> Self
pub fn geom_curve_with(self, geom: GeomCurve) -> Self
Sourcepub fn geom_violin(self) -> Self
pub fn geom_violin(self) -> Self
Examples found in repository?
175fn 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}pub fn geom_violin_with(self, geom: GeomViolin) -> Self
pub fn geom_dotplot(self) -> Self
pub fn geom_dotplot_with(self, geom: GeomDotplot) -> Self
pub fn geom_qq(self) -> Self
pub fn geom_qq_with(self, geom: GeomQQ) -> Self
pub fn geom_qq_line(self) -> Self
pub fn geom_qq_line_with(self, geom: GeomQQLine) -> Self
pub fn geom_bin2d(self) -> Self
pub fn geom_bin2d_with(self, geom: GeomBin2d) -> Self
Sourcepub fn geom_hex(self) -> Self
pub fn geom_hex(self) -> Self
Examples found in repository?
303fn 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}pub fn geom_hex_with(self, geom: GeomHex) -> Self
pub fn geom_count(self) -> Self
pub fn geom_count_with(self, geom: GeomCount) -> Self
pub fn geom_contour(self) -> Self
pub fn geom_contour_with(self, geom: GeomContour) -> Self
Sourcepub fn geom_contour_filled(self) -> Self
pub fn geom_contour_filled(self) -> Self
Filled contour bands from gridded (x, y, z) data — draws polygons filled by
band level. Pair with a continuous fill scale (e.g. scale_fill_viridis_c).
Examples found in repository?
280fn 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}pub fn geom_density2d(self) -> Self
pub fn geom_density2d_with(self, geom: GeomDensity2d) -> Self
pub fn geom_blank(self) -> Self
Sourcepub fn stat(self, stat: impl Stat + 'static) -> Self
pub fn stat(self, stat: impl Stat + 'static) -> Self
Override the stat for the most recently added layer.
Examples found in repository?
206fn ecdf(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
207 let name = "Meridian Components";
208 let attributable: Vec<f64> = rows
209 .iter()
210 .filter(|p| p.supplier == name && p.attributable)
211 .map(|p| p.lead)
212 .collect();
213 let on_time =
214 attributable.iter().filter(|&&l| l <= CONTRACT).count() as f64 / attributable.len() as f64;
215 let data: Vec<(String, Vec<Value>)> = vec![(
216 "lead".to_string(),
217 attributable.iter().map(|v| Value::Float(*v)).collect(),
218 )];
219
220 GGPlot::new(data)
221 .aes(Aes::new().x("lead"))
222 .geom_step()
223 .stat(StatEcdf)
224 .geom_vline_with(GeomVline {
225 xintercept: CONTRACT,
226 color: CONTRACT_RED,
227 width: 1.5,
228 linetype: Linetype::Dashed,
229 alpha: 1.0,
230 })
231 .annotate_text(
232 &format!("on-time rate {:.0}%", on_time * 100.0),
233 CONTRACT + 1.0,
234 0.15,
235 )
236 .title(&format!("{name} — on-time reliability (ECDF)"))
237 .subtitle("cumulative share at the contract line = P(lead <= 30d)")
238 .xlab("Actual lead time (days)")
239 .ylab("Cumulative share")
240 .theme_bw()
241 .save_with_size(&out("supplier_leadtime_ecdf"), W, H)?;
242 Ok(())
243}Sourcepub fn position(self, pos: impl Position + 'static) -> Self
pub fn position(self, pos: impl Position + 'static) -> Self
Override the position for the most recently added layer.
Sourcepub fn layer_data(self, data: impl GGData) -> Self
pub fn layer_data(self, data: impl GGData) -> Self
Override the data for the most recently added layer.
Examples found in repository?
137fn detail(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
138 let name = "Meridian Components";
139 let mine: Vec<&Po> = rows.iter().filter(|p| p.supplier == name).collect();
140
141 let attributable: Vec<f64> = mine
142 .iter()
143 .filter(|p| p.attributable)
144 .map(|p| p.lead)
145 .collect();
146 let p90 = percentile(attributable.clone(), 0.90);
147
148 let dens_data: Vec<(String, Vec<Value>)> = vec![(
149 "lead".to_string(),
150 attributable.iter().map(|v| Value::Float(*v)).collect(),
151 )];
152 let excused_data: Vec<(String, Vec<Value>)> = vec![(
153 "lead".to_string(),
154 mine.iter()
155 .filter(|p| !p.attributable)
156 .map(|p| Value::Float(p.lead))
157 .collect(),
158 )];
159
160 GGPlot::new(dens_data)
161 .aes(Aes::new().x("lead"))
162 .geom_density_with(GeomDensity {
163 fill: TEAL,
164 color: (20, 110, 98),
165 alpha: 0.45,
166 line_width: 1.5,
167 })
168 // SLA threshold — mass to the left is the on-time share.
169 .geom_vline_with(GeomVline {
170 xintercept: CONTRACT,
171 color: CONTRACT_RED,
172 width: 1.5,
173 linetype: Linetype::Dashed,
174 alpha: 1.0,
175 })
176 // p90 — the number to size safety stock against.
177 .geom_vline_with(GeomVline {
178 xintercept: p90,
179 color: P90_BLUE,
180 width: 1.5,
181 linetype: Linetype::Dashed,
182 alpha: 1.0,
183 })
184 // Excused deliveries, shown distinctly and kept out of the density.
185 .geom_rug_with(GeomRug {
186 color: MUTED,
187 alpha: 0.7,
188 length: 0.04,
189 sides: "b".to_string(),
190 })
191 .layer_data(excused_data)
192 .layer_aes(Aes::new().x("lead"))
193 .annotate_text(&format!("contract {CONTRACT:.0}d"), CONTRACT + 1.0, 0.075)
194 .annotate_text(&format!("p90 {p90:.0}d"), p90 + 1.0, 0.06)
195 .annotate_text("<- excused (external)", 48.0, 0.008)
196 .title(&format!("{name} — lead-time distribution"))
197 .subtitle("attributable deliveries only; excused shown as rug")
198 .xlab("Actual lead time (days)")
199 .ylab("Density")
200 .theme_minimal()
201 .save_with_size(&out("supplier_leadtime"), W, H)?;
202 Ok(())
203}Sourcepub fn layer_aes(self, mapping: Aes) -> Self
pub fn layer_aes(self, mapping: Aes) -> Self
Override the aesthetic mapping for the most recently added layer.
Examples found in repository?
137fn detail(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
138 let name = "Meridian Components";
139 let mine: Vec<&Po> = rows.iter().filter(|p| p.supplier == name).collect();
140
141 let attributable: Vec<f64> = mine
142 .iter()
143 .filter(|p| p.attributable)
144 .map(|p| p.lead)
145 .collect();
146 let p90 = percentile(attributable.clone(), 0.90);
147
148 let dens_data: Vec<(String, Vec<Value>)> = vec![(
149 "lead".to_string(),
150 attributable.iter().map(|v| Value::Float(*v)).collect(),
151 )];
152 let excused_data: Vec<(String, Vec<Value>)> = vec![(
153 "lead".to_string(),
154 mine.iter()
155 .filter(|p| !p.attributable)
156 .map(|p| Value::Float(p.lead))
157 .collect(),
158 )];
159
160 GGPlot::new(dens_data)
161 .aes(Aes::new().x("lead"))
162 .geom_density_with(GeomDensity {
163 fill: TEAL,
164 color: (20, 110, 98),
165 alpha: 0.45,
166 line_width: 1.5,
167 })
168 // SLA threshold — mass to the left is the on-time share.
169 .geom_vline_with(GeomVline {
170 xintercept: CONTRACT,
171 color: CONTRACT_RED,
172 width: 1.5,
173 linetype: Linetype::Dashed,
174 alpha: 1.0,
175 })
176 // p90 — the number to size safety stock against.
177 .geom_vline_with(GeomVline {
178 xintercept: p90,
179 color: P90_BLUE,
180 width: 1.5,
181 linetype: Linetype::Dashed,
182 alpha: 1.0,
183 })
184 // Excused deliveries, shown distinctly and kept out of the density.
185 .geom_rug_with(GeomRug {
186 color: MUTED,
187 alpha: 0.7,
188 length: 0.04,
189 sides: "b".to_string(),
190 })
191 .layer_data(excused_data)
192 .layer_aes(Aes::new().x("lead"))
193 .annotate_text(&format!("contract {CONTRACT:.0}d"), CONTRACT + 1.0, 0.075)
194 .annotate_text(&format!("p90 {p90:.0}d"), p90 + 1.0, 0.06)
195 .annotate_text("<- excused (external)", 48.0, 0.008)
196 .title(&format!("{name} — lead-time distribution"))
197 .subtitle("attributable deliveries only; excused shown as rug")
198 .xlab("Actual lead time (days)")
199 .ylab("Density")
200 .theme_minimal()
201 .save_with_size(&out("supplier_leadtime"), W, H)?;
202 Ok(())
203}Sourcepub fn show_legend(self, show: bool) -> Self
pub fn show_legend(self, show: bool) -> Self
Control whether the most recently added layer contributes to the legend.
true = always show, false = always hide, default (None) = auto.
pub fn scale_x_continuous(self, s: ScaleContinuous) -> Self
pub fn scale_y_continuous(self, s: ScaleContinuous) -> Self
pub fn scale_x_discrete(self, s: ScaleDiscrete) -> Self
pub fn scale_y_discrete(self, s: ScaleDiscrete) -> Self
pub fn scale_color(self, s: impl Scale + 'static) -> Self
pub fn scale_fill(self, s: impl Scale + 'static) -> Self
Sourcepub fn scale_color_manual(self, values: Vec<(&str, RGBAColor)>) -> Self
pub fn scale_color_manual(self, values: Vec<(&str, RGBAColor)>) -> Self
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate grouped scatter data
6 let groups = ["Alpha", "Beta", "Gamma", "Delta", "Epsilon"];
7 let mut x_vals = Vec::new();
8 let mut y_vals = Vec::new();
9 let mut group_vals = Vec::new();
10
11 for (gi, &group) in groups.iter().enumerate() {
12 for j in 0..20 {
13 let base_x = gi as f64 * 2.0 + 1.0;
14 let base_y = (gi as f64 + 1.0) * 3.0;
15 let r = ((gi * 20 + j) * 7 + 13) % 17;
16 x_vals.push(base_x + (r as f64 / 17.0 - 0.5) * 2.0);
17 y_vals.push(base_y + ((r * 3 + 5) % 11) as f64 / 11.0 * 4.0 - 2.0);
18 group_vals.push(group);
19 }
20 }
21
22 let df = df! {
23 "x" => &x_vals,
24 "y" => &y_vals,
25 "group" => &group_vals,
26 }?;
27
28 // Viridis palette
29 GGPlot::new(df.clone())
30 .aes(Aes::new().x("x").y("y").color("group"))
31 .geom_point()
32 .scale_color_viridis()
33 .title("Viridis Palette")
34 .save("palette_viridis.svg")?;
35
36 println!("Saved palette_viridis.svg");
37
38 // Brewer Set1 palette
39 GGPlot::new(df.clone())
40 .aes(Aes::new().x("x").y("y").color("group"))
41 .geom_point()
42 .scale_color_brewer(PaletteName::Set1)
43 .title("Brewer Set1 Palette")
44 .save("palette_brewer_set1.svg")?;
45
46 println!("Saved palette_brewer_set1.svg");
47
48 // Brewer Dark2 palette
49 GGPlot::new(df.clone())
50 .aes(Aes::new().x("x").y("y").color("group"))
51 .geom_point()
52 .scale_color_brewer(PaletteName::Dark2)
53 .title("Brewer Dark2 Palette")
54 .save("palette_brewer_dark2.svg")?;
55
56 println!("Saved palette_brewer_dark2.svg");
57
58 // Manual colors
59 GGPlot::new(df)
60 .aes(Aes::new().x("x").y("y").color("group"))
61 .geom_point()
62 .scale_color_manual(vec![
63 ("Alpha", RGBAColor::new(255, 0, 0)),
64 ("Beta", RGBAColor::new(0, 180, 0)),
65 ("Gamma", RGBAColor::new(0, 0, 255)),
66 ("Delta", RGBAColor::new(255, 165, 0)),
67 ("Epsilon", RGBAColor::new(128, 0, 128)),
68 ])
69 .title("Manual Color Scale")
70 .save("palette_manual.svg")?;
71
72 println!("Saved palette_manual.svg");
73 Ok(())
74}pub fn scale_fill_manual(self, values: Vec<(&str, RGBAColor)>) -> Self
Sourcepub fn scale_color_viridis(self) -> Self
pub fn scale_color_viridis(self) -> Self
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate grouped scatter data
6 let groups = ["Alpha", "Beta", "Gamma", "Delta", "Epsilon"];
7 let mut x_vals = Vec::new();
8 let mut y_vals = Vec::new();
9 let mut group_vals = Vec::new();
10
11 for (gi, &group) in groups.iter().enumerate() {
12 for j in 0..20 {
13 let base_x = gi as f64 * 2.0 + 1.0;
14 let base_y = (gi as f64 + 1.0) * 3.0;
15 let r = ((gi * 20 + j) * 7 + 13) % 17;
16 x_vals.push(base_x + (r as f64 / 17.0 - 0.5) * 2.0);
17 y_vals.push(base_y + ((r * 3 + 5) % 11) as f64 / 11.0 * 4.0 - 2.0);
18 group_vals.push(group);
19 }
20 }
21
22 let df = df! {
23 "x" => &x_vals,
24 "y" => &y_vals,
25 "group" => &group_vals,
26 }?;
27
28 // Viridis palette
29 GGPlot::new(df.clone())
30 .aes(Aes::new().x("x").y("y").color("group"))
31 .geom_point()
32 .scale_color_viridis()
33 .title("Viridis Palette")
34 .save("palette_viridis.svg")?;
35
36 println!("Saved palette_viridis.svg");
37
38 // Brewer Set1 palette
39 GGPlot::new(df.clone())
40 .aes(Aes::new().x("x").y("y").color("group"))
41 .geom_point()
42 .scale_color_brewer(PaletteName::Set1)
43 .title("Brewer Set1 Palette")
44 .save("palette_brewer_set1.svg")?;
45
46 println!("Saved palette_brewer_set1.svg");
47
48 // Brewer Dark2 palette
49 GGPlot::new(df.clone())
50 .aes(Aes::new().x("x").y("y").color("group"))
51 .geom_point()
52 .scale_color_brewer(PaletteName::Dark2)
53 .title("Brewer Dark2 Palette")
54 .save("palette_brewer_dark2.svg")?;
55
56 println!("Saved palette_brewer_dark2.svg");
57
58 // Manual colors
59 GGPlot::new(df)
60 .aes(Aes::new().x("x").y("y").color("group"))
61 .geom_point()
62 .scale_color_manual(vec![
63 ("Alpha", RGBAColor::new(255, 0, 0)),
64 ("Beta", RGBAColor::new(0, 180, 0)),
65 ("Gamma", RGBAColor::new(0, 0, 255)),
66 ("Delta", RGBAColor::new(255, 165, 0)),
67 ("Epsilon", RGBAColor::new(128, 0, 128)),
68 ])
69 .title("Manual Color Scale")
70 .save("palette_manual.svg")?;
71
72 println!("Saved palette_manual.svg");
73 Ok(())
74}Sourcepub fn scale_color_brewer(self, name: PaletteName) -> Self
pub fn scale_color_brewer(self, name: PaletteName) -> Self
Examples found in repository?
45fn 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
68/// Points overlaid with a LOESS trend line and confidence band.
69fn 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
95/// Histogram of an approximately-normal sample.
96fn 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
121/// Bar chart of category counts with a fill palette.
122fn 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
148/// Grouped boxplots.
149fn 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
174/// Violin plots of grouped distributions.
175fn 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
198/// Spiral scatter coloured by a continuous variable (viridis).
199fn 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
228/// Faceted scatter, one panel per group.
229fn 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
253/// Overlapping density curves by group.
254fn 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
279/// Filled contour bands from a gridded surface.
280fn 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
302/// Hexagonal binning of a 2-D point cloud.
303fn 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
328/// Heatmap of a gridded value with `geom_tile`.
329fn 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
349/// Jittered categorical scatter (`geom_jitter`).
350fn 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
370/// A confidence band (`geom_ribbon`) under a line.
371fn 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
389/// Stacked areas by group.
390fn 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
418/// The same plot rendered under every built-in theme.
419fn 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}More examples
246fn compare(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
247 let lead: Vec<Value> = rows
248 .iter()
249 .filter(|p| p.attributable)
250 .map(|p| Value::Float(p.lead))
251 .collect();
252 let supplier: Vec<Value> = rows
253 .iter()
254 .filter(|p| p.attributable)
255 .map(|p| Value::Str(p.supplier.to_string()))
256 .collect();
257 let data: Vec<(String, Vec<Value>)> = vec![
258 ("lead".to_string(), lead),
259 ("supplier".to_string(), supplier),
260 ];
261
262 GGPlot::new(data)
263 .aes(Aes::new().x("lead").fill("supplier").color("supplier"))
264 .geom_density_with(GeomDensity {
265 alpha: 0.35,
266 line_width: 1.2,
267 ..Default::default()
268 })
269 .geom_vline_with(GeomVline {
270 xintercept: CONTRACT,
271 color: CONTRACT_RED,
272 width: 1.2,
273 linetype: Linetype::Dashed,
274 alpha: 1.0,
275 })
276 .scale_fill_brewer(PaletteName::Dark2)
277 .scale_color_brewer(PaletteName::Dark2)
278 .annotate_text("contract", CONTRACT + 1.0, 0.005)
279 .title("Lead-time distributions by supplier")
280 .subtitle("attributable deliveries; dashed line = contracted lead time")
281 .xlab("Actual lead time (days)")
282 .ylab("Density")
283 .theme_minimal()
284 .save_with_size(&out("supplier_leadtime_compare"), W, H)?;
285 Ok(())
286}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate grouped scatter data
6 let groups = ["Alpha", "Beta", "Gamma", "Delta", "Epsilon"];
7 let mut x_vals = Vec::new();
8 let mut y_vals = Vec::new();
9 let mut group_vals = Vec::new();
10
11 for (gi, &group) in groups.iter().enumerate() {
12 for j in 0..20 {
13 let base_x = gi as f64 * 2.0 + 1.0;
14 let base_y = (gi as f64 + 1.0) * 3.0;
15 let r = ((gi * 20 + j) * 7 + 13) % 17;
16 x_vals.push(base_x + (r as f64 / 17.0 - 0.5) * 2.0);
17 y_vals.push(base_y + ((r * 3 + 5) % 11) as f64 / 11.0 * 4.0 - 2.0);
18 group_vals.push(group);
19 }
20 }
21
22 let df = df! {
23 "x" => &x_vals,
24 "y" => &y_vals,
25 "group" => &group_vals,
26 }?;
27
28 // Viridis palette
29 GGPlot::new(df.clone())
30 .aes(Aes::new().x("x").y("y").color("group"))
31 .geom_point()
32 .scale_color_viridis()
33 .title("Viridis Palette")
34 .save("palette_viridis.svg")?;
35
36 println!("Saved palette_viridis.svg");
37
38 // Brewer Set1 palette
39 GGPlot::new(df.clone())
40 .aes(Aes::new().x("x").y("y").color("group"))
41 .geom_point()
42 .scale_color_brewer(PaletteName::Set1)
43 .title("Brewer Set1 Palette")
44 .save("palette_brewer_set1.svg")?;
45
46 println!("Saved palette_brewer_set1.svg");
47
48 // Brewer Dark2 palette
49 GGPlot::new(df.clone())
50 .aes(Aes::new().x("x").y("y").color("group"))
51 .geom_point()
52 .scale_color_brewer(PaletteName::Dark2)
53 .title("Brewer Dark2 Palette")
54 .save("palette_brewer_dark2.svg")?;
55
56 println!("Saved palette_brewer_dark2.svg");
57
58 // Manual colors
59 GGPlot::new(df)
60 .aes(Aes::new().x("x").y("y").color("group"))
61 .geom_point()
62 .scale_color_manual(vec![
63 ("Alpha", RGBAColor::new(255, 0, 0)),
64 ("Beta", RGBAColor::new(0, 180, 0)),
65 ("Gamma", RGBAColor::new(0, 0, 255)),
66 ("Delta", RGBAColor::new(255, 165, 0)),
67 ("Epsilon", RGBAColor::new(128, 0, 128)),
68 ])
69 .title("Manual Color Scale")
70 .save("palette_manual.svg")?;
71
72 println!("Saved palette_manual.svg");
73 Ok(())
74}Sourcepub fn scale_color_gradient(self, low: RGBAColor, high: RGBAColor) -> Self
pub fn scale_color_gradient(self, low: RGBAColor, high: RGBAColor) -> Self
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate scatter data with a continuous variable for color
6 let x: Vec<f64> = (0..200)
7 .map(|i| {
8 let t = i as f64 * 0.05;
9 t.cos() * (1.0 + t * 0.3)
10 })
11 .collect();
12 let y: Vec<f64> = (0..200)
13 .map(|i| {
14 let t = i as f64 * 0.05;
15 t.sin() * (1.0 + t * 0.3)
16 })
17 .collect();
18 let z: Vec<f64> = (0..200).map(|i| i as f64 * 0.05).collect();
19
20 let df = df! {
21 "x" => &x,
22 "y" => &y,
23 "z" => &z,
24 }?;
25
26 // Default blue-to-red gradient
27 GGPlot::new(df.clone())
28 .aes(Aes::new().x("x").y("y").color("z"))
29 .geom_point()
30 .title("Continuous Color (default gradient)")
31 .xlab("X")
32 .ylab("Y")
33 .save("continuous_color.svg")?;
34
35 println!("Saved continuous_color.svg");
36
37 // Custom gradient: dark blue to yellow
38 GGPlot::new(df)
39 .aes(Aes::new().x("x").y("y").color("z"))
40 .geom_point()
41 .scale_color_gradient(RGBAColor::new(10, 30, 100), RGBAColor::new(255, 230, 50))
42 .title("Continuous Color (custom gradient)")
43 .xlab("X")
44 .ylab("Y")
45 .save("continuous_color_custom.svg")?;
46
47 println!("Saved continuous_color_custom.svg");
48 Ok(())
49}pub fn scale_fill_gradient(self, low: RGBAColor, high: RGBAColor) -> Self
pub fn scale_color_gradient2( self, low: RGBAColor, mid: RGBAColor, high: RGBAColor, ) -> Self
pub fn scale_fill_gradient2( self, low: RGBAColor, mid: RGBAColor, high: RGBAColor, ) -> Self
pub fn scale_fill_viridis(self) -> Self
Sourcepub fn scale_color_viridis_c(self) -> Self
pub fn scale_color_viridis_c(self) -> Self
Continuous viridis color scale (for numeric data).
Examples found in repository?
199fn 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}Sourcepub fn scale_fill_viridis_c(self) -> Self
pub fn scale_fill_viridis_c(self) -> Self
Continuous viridis fill scale (for numeric data).
Examples found in repository?
280fn 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
302/// Hexagonal binning of a 2-D point cloud.
303fn 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
328/// Heatmap of a gridded value with `geom_tile`.
329fn 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}Sourcepub fn scale_color_gradientn(self, stops: Vec<(f64, RGBAColor)>) -> Self
pub fn scale_color_gradientn(self, stops: Vec<(f64, RGBAColor)>) -> Self
N-stop continuous color gradient.
Sourcepub fn scale_fill_gradientn(self, stops: Vec<(f64, RGBAColor)>) -> Self
pub fn scale_fill_gradientn(self, stops: Vec<(f64, RGBAColor)>) -> Self
N-stop continuous fill gradient.
Sourcepub fn scale_color_steps(
self,
low: RGBAColor,
high: RGBAColor,
n_bins: usize,
) -> Self
pub fn scale_color_steps( self, low: RGBAColor, high: RGBAColor, n_bins: usize, ) -> Self
Binned (stepped) two-colour continuous colour scale — buckets the mapped
variable into n_bins bins, each a discrete colour, with a stepped legend.
Sourcepub fn scale_color_stepsn(self, stops: Vec<RGBAColor>, n_bins: usize) -> Self
pub fn scale_color_stepsn(self, stops: Vec<RGBAColor>, n_bins: usize) -> Self
Binned N-stop continuous colour scale.
Sourcepub fn scale_color_fermenter(self, name: PaletteName, n_bins: usize) -> Self
pub fn scale_color_fermenter(self, name: PaletteName, n_bins: usize) -> Self
Binned ColorBrewer colour scale (R’s scale_color_fermenter).
Sourcepub fn scale_fill_steps(
self,
low: RGBAColor,
high: RGBAColor,
n_bins: usize,
) -> Self
pub fn scale_fill_steps( self, low: RGBAColor, high: RGBAColor, n_bins: usize, ) -> Self
Binned (stepped) two-colour continuous fill scale.
Sourcepub fn scale_fill_fermenter(self, name: PaletteName, n_bins: usize) -> Self
pub fn scale_fill_fermenter(self, name: PaletteName, n_bins: usize) -> Self
Binned ColorBrewer fill scale.
Sourcepub fn scale_fill_brewer(self, name: PaletteName) -> Self
pub fn scale_fill_brewer(self, name: PaletteName) -> Self
Examples found in repository?
122fn 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
148/// Grouped boxplots.
149fn 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
174/// Violin plots of grouped distributions.
175fn 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
198/// Spiral scatter coloured by a continuous variable (viridis).
199fn 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
228/// Faceted scatter, one panel per group.
229fn 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
253/// Overlapping density curves by group.
254fn 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
279/// Filled contour bands from a gridded surface.
280fn 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
302/// Hexagonal binning of a 2-D point cloud.
303fn 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
328/// Heatmap of a gridded value with `geom_tile`.
329fn 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
349/// Jittered categorical scatter (`geom_jitter`).
350fn 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
370/// A confidence band (`geom_ribbon`) under a line.
371fn 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
389/// Stacked areas by group.
390fn 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}More examples
246fn compare(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
247 let lead: Vec<Value> = rows
248 .iter()
249 .filter(|p| p.attributable)
250 .map(|p| Value::Float(p.lead))
251 .collect();
252 let supplier: Vec<Value> = rows
253 .iter()
254 .filter(|p| p.attributable)
255 .map(|p| Value::Str(p.supplier.to_string()))
256 .collect();
257 let data: Vec<(String, Vec<Value>)> = vec![
258 ("lead".to_string(), lead),
259 ("supplier".to_string(), supplier),
260 ];
261
262 GGPlot::new(data)
263 .aes(Aes::new().x("lead").fill("supplier").color("supplier"))
264 .geom_density_with(GeomDensity {
265 alpha: 0.35,
266 line_width: 1.2,
267 ..Default::default()
268 })
269 .geom_vline_with(GeomVline {
270 xintercept: CONTRACT,
271 color: CONTRACT_RED,
272 width: 1.2,
273 linetype: Linetype::Dashed,
274 alpha: 1.0,
275 })
276 .scale_fill_brewer(PaletteName::Dark2)
277 .scale_color_brewer(PaletteName::Dark2)
278 .annotate_text("contract", CONTRACT + 1.0, 0.005)
279 .title("Lead-time distributions by supplier")
280 .subtitle("attributable deliveries; dashed line = contracted lead time")
281 .xlab("Actual lead time (days)")
282 .ylab("Density")
283 .theme_minimal()
284 .save_with_size(&out("supplier_leadtime_compare"), W, H)?;
285 Ok(())
286}pub fn scale_linetype_manual(self, values: Vec<(&str, Linetype)>) -> Self
pub fn scale_shape_manual(self, values: Vec<(&str, PointShape)>) -> Self
pub fn scale_color_grey(self) -> Self
pub fn scale_fill_grey(self) -> Self
pub fn scale_color_grey_with(self, s: ScaleColorGrey) -> Self
pub fn scale_fill_grey_with(self, s: ScaleColorGrey) -> Self
pub fn scale_x_reverse(self) -> Self
pub fn scale_y_reverse(self) -> Self
pub fn scale_x_datetime(self, s: ScaleDateTime) -> Self
pub fn scale_y_datetime(self, s: ScaleDateTime) -> Self
pub fn scale_size(self, s: ScaleSizeContinuous) -> Self
pub fn scale_alpha(self, s: ScaleAlphaContinuous) -> Self
pub fn xlim(self, min: f64, max: f64) -> Self
pub fn ylim(self, min: f64, max: f64) -> Self
pub fn scale_x_log10(self) -> Self
Sourcepub fn scale_y_log10(self) -> Self
pub fn scale_y_log10(self) -> Self
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Exponential growth data (e.g., population, bacteria count)
6 let year: Vec<f64> = (0..30).map(|i| 1990.0 + i as f64).collect();
7 let count: Vec<f64> = (0..30)
8 .map(|i| {
9 let base = 100.0 * (1.15_f64).powi(i); // 15% growth per year
10 let noise = ((i * 37 + 7) % 13) as f64 / 13.0 * 0.2 + 0.9;
11 base * noise
12 })
13 .collect();
14
15 let df = df! {
16 "year" => &year,
17 "count" => &count,
18 }?;
19
20 // Linear scale — exponential curve
21 GGPlot::new(df.clone())
22 .aes(Aes::new().x("year").y("count"))
23 .geom_point()
24 .geom_line()
25 .title("Exponential Growth (Linear Scale)")
26 .xlab("Year")
27 .ylab("Count")
28 .save("log_scale_linear.svg")?;
29
30 println!("Saved log_scale_linear.svg");
31
32 // Log10 y-axis — should appear roughly linear
33 GGPlot::new(df)
34 .aes(Aes::new().x("year").y("count"))
35 .geom_point()
36 .geom_line()
37 .scale_y_log10()
38 .title("Exponential Growth (Log10 Y Scale)")
39 .xlab("Year")
40 .ylab("Count (log10)")
41 .save("log_scale_log10.svg")?;
42
43 println!("Saved log_scale_log10.svg");
44 Ok(())
45}pub fn scale_x_sqrt(self) -> Self
pub fn scale_y_sqrt(self) -> Self
pub fn scale_x_log2(self) -> Self
pub fn scale_y_log2(self) -> Self
pub fn scale_x_ln(self) -> Self
pub fn scale_y_ln(self) -> Self
Sourcepub fn scale_x_logit(self) -> Self
pub fn scale_x_logit(self) -> Self
Logit-transformed x axis (for proportions in (0, 1)).
pub fn scale_y_logit(self) -> Self
Sourcepub fn scale_x_probit(self) -> Self
pub fn scale_x_probit(self) -> Self
Probit-transformed x axis (inverse normal CDF, for proportions in (0, 1)).
pub fn scale_y_probit(self) -> Self
Sourcepub fn scale_x_pseudo_log(self) -> Self
pub fn scale_x_pseudo_log(self) -> Self
Sign-preserving pseudo-log x axis (handles zero and negative values).
pub fn scale_y_pseudo_log(self) -> Self
Sourcepub fn scale_x_reciprocal(self) -> Self
pub fn scale_x_reciprocal(self) -> Self
Reciprocal (1/x) x axis.
pub fn scale_y_reciprocal(self) -> Self
Sourcepub fn scale_x_exp(self) -> Self
pub fn scale_x_exp(self) -> Self
Exponential x axis (labels spaced logarithmically).
pub fn scale_y_exp(self) -> Self
Sourcepub fn scale_x_boxcox(self, lambda: f64) -> Self
pub fn scale_x_boxcox(self, lambda: f64) -> Self
Box–Cox x axis with the given lambda (x > 0).
pub fn scale_y_boxcox(self, lambda: f64) -> Self
Sourcepub fn facet_wrap(self, var: &str, ncol: Option<usize>) -> Self
pub fn facet_wrap(self, var: &str, ncol: Option<usize>) -> Self
Examples found in repository?
229fn 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}More examples
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate data with two grouping variables
6 let sepal_length: Vec<f64> = (0..120)
7 .map(|i| 4.0 + (i as f64) * 0.04 + (i as f64 * 0.5).sin() * 0.3)
8 .collect();
9 let sepal_width: Vec<f64> = (0..120)
10 .map(|i| 2.0 + (i as f64) * 0.015 + (i as f64 * 0.3).cos() * 0.4)
11 .collect();
12 let species: Vec<&str> = (0..120)
13 .map(|i| match i % 3 {
14 0 => "setosa",
15 1 => "versicolor",
16 _ => "virginica",
17 })
18 .collect();
19 let region: Vec<&str> = (0..120)
20 .map(|i| if i % 2 == 0 { "North" } else { "South" })
21 .collect();
22
23 let df = df! {
24 "sepal_length" => sepal_length,
25 "sepal_width" => sepal_width,
26 "species" => species,
27 "region" => region,
28 }?;
29
30 // facet_wrap: one variable, automatic grid layout
31 GGPlot::new(df.clone())
32 .aes(
33 Aes::new()
34 .x("sepal_length")
35 .y("sepal_width")
36 .color("species"),
37 )
38 .geom_point()
39 .facet_wrap("species", Some(2))
40 .title("Facet Wrap by Species")
41 .xlab("Sepal Length")
42 .ylab("Sepal Width")
43 .save("facet_wrap.svg")?;
44
45 println!("Saved facet_wrap.svg");
46
47 // facet_grid: two variables, row ~ col layout
48 GGPlot::new(df)
49 .aes(
50 Aes::new()
51 .x("sepal_length")
52 .y("sepal_width")
53 .color("species"),
54 )
55 .geom_point()
56 .facet_grid(Some("region"), Some("species"))
57 .title("Facet Grid: Region ~ Species")
58 .xlab("Sepal Length")
59 .ylab("Sepal Width")
60 .save("facet_grid.svg")?;
61
62 println!("Saved facet_grid.svg");
63 Ok(())
64}pub fn facet_wrap_free( self, var: &str, ncol: Option<usize>, scales: FacetScales, ) -> Self
pub fn facet_wrap_labeller( self, var: &str, ncol: Option<usize>, labeller: FacetLabeller, ) -> Self
Sourcepub fn facet_grid(self, row: Option<&str>, col: Option<&str>) -> Self
pub fn facet_grid(self, row: Option<&str>, col: Option<&str>) -> Self
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate data with two grouping variables
6 let sepal_length: Vec<f64> = (0..120)
7 .map(|i| 4.0 + (i as f64) * 0.04 + (i as f64 * 0.5).sin() * 0.3)
8 .collect();
9 let sepal_width: Vec<f64> = (0..120)
10 .map(|i| 2.0 + (i as f64) * 0.015 + (i as f64 * 0.3).cos() * 0.4)
11 .collect();
12 let species: Vec<&str> = (0..120)
13 .map(|i| match i % 3 {
14 0 => "setosa",
15 1 => "versicolor",
16 _ => "virginica",
17 })
18 .collect();
19 let region: Vec<&str> = (0..120)
20 .map(|i| if i % 2 == 0 { "North" } else { "South" })
21 .collect();
22
23 let df = df! {
24 "sepal_length" => sepal_length,
25 "sepal_width" => sepal_width,
26 "species" => species,
27 "region" => region,
28 }?;
29
30 // facet_wrap: one variable, automatic grid layout
31 GGPlot::new(df.clone())
32 .aes(
33 Aes::new()
34 .x("sepal_length")
35 .y("sepal_width")
36 .color("species"),
37 )
38 .geom_point()
39 .facet_wrap("species", Some(2))
40 .title("Facet Wrap by Species")
41 .xlab("Sepal Length")
42 .ylab("Sepal Width")
43 .save("facet_wrap.svg")?;
44
45 println!("Saved facet_wrap.svg");
46
47 // facet_grid: two variables, row ~ col layout
48 GGPlot::new(df)
49 .aes(
50 Aes::new()
51 .x("sepal_length")
52 .y("sepal_width")
53 .color("species"),
54 )
55 .geom_point()
56 .facet_grid(Some("region"), Some("species"))
57 .title("Facet Grid: Region ~ Species")
58 .xlab("Sepal Length")
59 .ylab("Sepal Width")
60 .save("facet_grid.svg")?;
61
62 println!("Saved facet_grid.svg");
63 Ok(())
64}pub fn facet_grid_free( self, row: Option<&str>, col: Option<&str>, scales: FacetScales, ) -> Self
Sourcepub fn facet_grid_multi(
self,
row: Option<&str>,
cols: &[&str],
scales: FacetScales,
space: FacetSpace,
) -> Self
pub fn facet_grid_multi( self, row: Option<&str>, cols: &[&str], scales: FacetScales, space: FacetSpace, ) -> Self
facet_grid over multiple column variables (R’s rows ~ b + c):
the columns become the combination of the given variables’ values.
Also accepts free scales + proportional space for full parity.
Sourcepub fn facet_grid_space(
self,
row: Option<&str>,
col: Option<&str>,
scales: FacetScales,
space: FacetSpace,
) -> Self
pub fn facet_grid_space( self, row: Option<&str>, col: Option<&str>, scales: FacetScales, space: FacetSpace, ) -> Self
facet_grid with proportional panel sizing (R’s space =): panels are
sized to their data range. Typically paired with free scales.
pub fn facet_grid_labeller( self, row: Option<&str>, col: Option<&str>, labeller: FacetLabeller, ) -> Self
Sourcepub fn coord_flip(self) -> Self
pub fn coord_flip(self) -> Self
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "language" => ["Rust", "Python", "JavaScript", "Go", "TypeScript", "Java", "C++"],
7 "satisfaction" => [92.0, 88.0, 85.0, 78.0, 82.0, 70.0, 75.0],
8 }?;
9
10 // Horizontal bar chart using coord_flip
11 GGPlot::new(df)
12 .aes(Aes::new().x("language").y("satisfaction"))
13 .geom_col()
14 .coord_flip()
15 .title("Developer Satisfaction by Language")
16 .xlab("Language")
17 .ylab("Satisfaction Score")
18 .save("coord_flip.svg")?;
19
20 println!("Saved coord_flip.svg");
21 Ok(())
22}pub fn coord_fixed(self, ratio: f64) -> Self
Sourcepub fn coord_trans(
self,
x: Option<ScaleTransform>,
y: Option<ScaleTransform>,
) -> Self
pub fn coord_trans( self, x: Option<ScaleTransform>, y: Option<ScaleTransform>, ) -> Self
Transform the coordinate space at draw time (R’s coord_trans) — stats are
computed on raw data but drawn on non-linear axes. Pass a per-axis
ScaleTransform (e.g. Some(ScaleTransform::Log10)), None to leave an
axis linear.
Sourcepub fn coord_trans_y(self, y: ScaleTransform) -> Self
pub fn coord_trans_y(self, y: ScaleTransform) -> Self
coord_trans on the y-axis only.
Sourcepub fn coord_trans_x(self, x: ScaleTransform) -> Self
pub fn coord_trans_x(self, x: ScaleTransform) -> Self
coord_trans on the x-axis only.
Sourcepub fn coord_cartesian_zoom(
self,
xlim: Option<(f64, f64)>,
ylim: Option<(f64, f64)>,
) -> Self
pub fn coord_cartesian_zoom( self, xlim: Option<(f64, f64)>, ylim: Option<(f64, f64)>, ) -> Self
Zoom into a region without filtering data (unlike xlim/ylim which filter).
pub fn coord_polar(self) -> Self
pub fn coord_polar_with(self, coord: CoordPolar) -> Self
Sourcepub fn theme(self, theme: Theme) -> Self
pub fn theme(self, theme: Theme) -> Self
Examples found in repository?
419fn 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}Sourcepub fn axis_text_x_angle(self, degrees: f64) -> Self
pub fn axis_text_x_angle(self, degrees: f64) -> Self
Rotate the x-axis tick labels by degrees (R’s
guides(x = guide_axis(angle = ...)) / axis.text.x = element_text(angle)).
Useful for long category labels. Call after any theme_*() preset.
Sourcepub fn axis_text_y_angle(self, degrees: f64) -> Self
pub fn axis_text_y_angle(self, degrees: f64) -> Self
Rotate the y-axis tick labels by degrees.
Sourcepub fn axis_text_x_dodge(self, n: usize) -> Self
pub fn axis_text_x_dodge(self, n: usize) -> Self
Stagger x-axis tick labels across n rows to avoid overlap (R’s
guides(x = guide_axis(n.dodge = n))). 1 = no dodging.
Sourcepub fn legend_position_inside(self, x: f64, y: f64) -> Self
pub fn legend_position_inside(self, x: f64, y: f64) -> Self
Set the brand/primary color used as the default for single-series geoms
that have no color/fill aesthetic mapped. Composes with any theme — one
render process can serve different tenants’ brands at render time.
Place the legend inside the panel at panel-relative coordinates
(0..1, 0..1) — (0,0) bottom-left, (1,1) top-right (R’s
legend.position = c(x, y)).
Sourcepub fn primary_color(self, color: (u8, u8, u8)) -> Self
pub fn primary_color(self, color: (u8, u8, u8)) -> Self
Examples found in repository?
371fn 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}Sourcepub fn theme_minimal(self) -> Self
pub fn theme_minimal(self) -> Self
Examples found in repository?
45fn 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
68/// Points overlaid with a LOESS trend line and confidence band.
69fn 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
95/// Histogram of an approximately-normal sample.
96fn 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
121/// Bar chart of category counts with a fill palette.
122fn 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
148/// Grouped boxplots.
149fn 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
174/// Violin plots of grouped distributions.
175fn 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
198/// Spiral scatter coloured by a continuous variable (viridis).
199fn 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
228/// Faceted scatter, one panel per group.
229fn 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
253/// Overlapping density curves by group.
254fn 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
279/// Filled contour bands from a gridded surface.
280fn 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
302/// Hexagonal binning of a 2-D point cloud.
303fn 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
328/// Heatmap of a gridded value with `geom_tile`.
329fn 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
349/// Jittered categorical scatter (`geom_jitter`).
350fn 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
370/// A confidence band (`geom_ribbon`) under a line.
371fn 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
389/// Stacked areas by group.
390fn 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}More examples
137fn detail(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
138 let name = "Meridian Components";
139 let mine: Vec<&Po> = rows.iter().filter(|p| p.supplier == name).collect();
140
141 let attributable: Vec<f64> = mine
142 .iter()
143 .filter(|p| p.attributable)
144 .map(|p| p.lead)
145 .collect();
146 let p90 = percentile(attributable.clone(), 0.90);
147
148 let dens_data: Vec<(String, Vec<Value>)> = vec![(
149 "lead".to_string(),
150 attributable.iter().map(|v| Value::Float(*v)).collect(),
151 )];
152 let excused_data: Vec<(String, Vec<Value>)> = vec![(
153 "lead".to_string(),
154 mine.iter()
155 .filter(|p| !p.attributable)
156 .map(|p| Value::Float(p.lead))
157 .collect(),
158 )];
159
160 GGPlot::new(dens_data)
161 .aes(Aes::new().x("lead"))
162 .geom_density_with(GeomDensity {
163 fill: TEAL,
164 color: (20, 110, 98),
165 alpha: 0.45,
166 line_width: 1.5,
167 })
168 // SLA threshold — mass to the left is the on-time share.
169 .geom_vline_with(GeomVline {
170 xintercept: CONTRACT,
171 color: CONTRACT_RED,
172 width: 1.5,
173 linetype: Linetype::Dashed,
174 alpha: 1.0,
175 })
176 // p90 — the number to size safety stock against.
177 .geom_vline_with(GeomVline {
178 xintercept: p90,
179 color: P90_BLUE,
180 width: 1.5,
181 linetype: Linetype::Dashed,
182 alpha: 1.0,
183 })
184 // Excused deliveries, shown distinctly and kept out of the density.
185 .geom_rug_with(GeomRug {
186 color: MUTED,
187 alpha: 0.7,
188 length: 0.04,
189 sides: "b".to_string(),
190 })
191 .layer_data(excused_data)
192 .layer_aes(Aes::new().x("lead"))
193 .annotate_text(&format!("contract {CONTRACT:.0}d"), CONTRACT + 1.0, 0.075)
194 .annotate_text(&format!("p90 {p90:.0}d"), p90 + 1.0, 0.06)
195 .annotate_text("<- excused (external)", 48.0, 0.008)
196 .title(&format!("{name} — lead-time distribution"))
197 .subtitle("attributable deliveries only; excused shown as rug")
198 .xlab("Actual lead time (days)")
199 .ylab("Density")
200 .theme_minimal()
201 .save_with_size(&out("supplier_leadtime"), W, H)?;
202 Ok(())
203}
204
205/// ECDF for one supplier — P(lead <= contract) reads straight off the curve.
206fn ecdf(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
207 let name = "Meridian Components";
208 let attributable: Vec<f64> = rows
209 .iter()
210 .filter(|p| p.supplier == name && p.attributable)
211 .map(|p| p.lead)
212 .collect();
213 let on_time =
214 attributable.iter().filter(|&&l| l <= CONTRACT).count() as f64 / attributable.len() as f64;
215 let data: Vec<(String, Vec<Value>)> = vec![(
216 "lead".to_string(),
217 attributable.iter().map(|v| Value::Float(*v)).collect(),
218 )];
219
220 GGPlot::new(data)
221 .aes(Aes::new().x("lead"))
222 .geom_step()
223 .stat(StatEcdf)
224 .geom_vline_with(GeomVline {
225 xintercept: CONTRACT,
226 color: CONTRACT_RED,
227 width: 1.5,
228 linetype: Linetype::Dashed,
229 alpha: 1.0,
230 })
231 .annotate_text(
232 &format!("on-time rate {:.0}%", on_time * 100.0),
233 CONTRACT + 1.0,
234 0.15,
235 )
236 .title(&format!("{name} — on-time reliability (ECDF)"))
237 .subtitle("cumulative share at the contract line = P(lead <= 30d)")
238 .xlab("Actual lead time (days)")
239 .ylab("Cumulative share")
240 .theme_bw()
241 .save_with_size(&out("supplier_leadtime_ecdf"), W, H)?;
242 Ok(())
243}
244
245/// Compare suppliers: overlaid per-supplier densities against the contract line.
246fn compare(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
247 let lead: Vec<Value> = rows
248 .iter()
249 .filter(|p| p.attributable)
250 .map(|p| Value::Float(p.lead))
251 .collect();
252 let supplier: Vec<Value> = rows
253 .iter()
254 .filter(|p| p.attributable)
255 .map(|p| Value::Str(p.supplier.to_string()))
256 .collect();
257 let data: Vec<(String, Vec<Value>)> = vec![
258 ("lead".to_string(), lead),
259 ("supplier".to_string(), supplier),
260 ];
261
262 GGPlot::new(data)
263 .aes(Aes::new().x("lead").fill("supplier").color("supplier"))
264 .geom_density_with(GeomDensity {
265 alpha: 0.35,
266 line_width: 1.2,
267 ..Default::default()
268 })
269 .geom_vline_with(GeomVline {
270 xintercept: CONTRACT,
271 color: CONTRACT_RED,
272 width: 1.2,
273 linetype: Linetype::Dashed,
274 alpha: 1.0,
275 })
276 .scale_fill_brewer(PaletteName::Dark2)
277 .scale_color_brewer(PaletteName::Dark2)
278 .annotate_text("contract", CONTRACT + 1.0, 0.005)
279 .title("Lead-time distributions by supplier")
280 .subtitle("attributable deliveries; dashed line = contracted lead time")
281 .xlab("Actual lead time (days)")
282 .ylab("Density")
283 .theme_minimal()
284 .save_with_size(&out("supplier_leadtime_compare"), W, H)?;
285 Ok(())
286}Sourcepub fn theme_bw(self) -> Self
pub fn theme_bw(self) -> Self
Examples found in repository?
69fn 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
95/// Histogram of an approximately-normal sample.
96fn 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
121/// Bar chart of category counts with a fill palette.
122fn 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
148/// Grouped boxplots.
149fn 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
174/// Violin plots of grouped distributions.
175fn 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
198/// Spiral scatter coloured by a continuous variable (viridis).
199fn 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
228/// Faceted scatter, one panel per group.
229fn 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}More examples
206fn ecdf(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
207 let name = "Meridian Components";
208 let attributable: Vec<f64> = rows
209 .iter()
210 .filter(|p| p.supplier == name && p.attributable)
211 .map(|p| p.lead)
212 .collect();
213 let on_time =
214 attributable.iter().filter(|&&l| l <= CONTRACT).count() as f64 / attributable.len() as f64;
215 let data: Vec<(String, Vec<Value>)> = vec![(
216 "lead".to_string(),
217 attributable.iter().map(|v| Value::Float(*v)).collect(),
218 )];
219
220 GGPlot::new(data)
221 .aes(Aes::new().x("lead"))
222 .geom_step()
223 .stat(StatEcdf)
224 .geom_vline_with(GeomVline {
225 xintercept: CONTRACT,
226 color: CONTRACT_RED,
227 width: 1.5,
228 linetype: Linetype::Dashed,
229 alpha: 1.0,
230 })
231 .annotate_text(
232 &format!("on-time rate {:.0}%", on_time * 100.0),
233 CONTRACT + 1.0,
234 0.15,
235 )
236 .title(&format!("{name} — on-time reliability (ECDF)"))
237 .subtitle("cumulative share at the contract line = P(lead <= 30d)")
238 .xlab("Actual lead time (days)")
239 .ylab("Cumulative share")
240 .theme_bw()
241 .save_with_size(&out("supplier_leadtime_ecdf"), W, H)?;
242 Ok(())
243}pub fn theme_gray(self) -> Self
pub fn theme_classic(self) -> Self
pub fn theme_linedraw(self) -> Self
pub fn theme_light(self) -> Self
pub fn theme_dark(self) -> Self
pub fn theme_void(self) -> Self
Sourcepub fn theme_update(self, update: ThemeUpdate) -> Self
pub fn theme_update(self, update: ThemeUpdate) -> Self
Apply incremental theme modifications on top of the current theme.
Like R’s + theme(axis.text.x = element_text(...)).
Sourcepub fn guides(self, guide: GuideLegend) -> Self
pub fn guides(self, guide: GuideLegend) -> Self
Configure legend guide (title, ncol, reverse).
pub fn labs(self, labels: Labels) -> Self
Sourcepub fn title(self, title: &str) -> Self
pub fn title(self, title: &str) -> Self
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "fruit" => ["Apple", "Apple", "Apple", "Banana", "Banana",
7 "Cherry", "Cherry", "Cherry", "Cherry", "Date"],
8 }?;
9
10 GGPlot::new(df)
11 .aes(Aes::new().x("fruit"))
12 .geom_bar()
13 .title("Fruit Counts")
14 .xlab("Fruit")
15 .ylab("Count")
16 .save("bar_chart.svg")?;
17
18 println!("Saved bar_chart.svg");
19 Ok(())
20}More examples
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "language" => ["Rust", "Python", "JavaScript", "Go", "TypeScript", "Java", "C++"],
7 "satisfaction" => [92.0, 88.0, 85.0, 78.0, 82.0, 70.0, 75.0],
8 }?;
9
10 // Horizontal bar chart using coord_flip
11 GGPlot::new(df)
12 .aes(Aes::new().x("language").y("satisfaction"))
13 .geom_col()
14 .coord_flip()
15 .title("Developer Satisfaction by Language")
16 .xlab("Language")
17 .ylab("Satisfaction Score")
18 .save("coord_flip.svg")?;
19
20 println!("Saved coord_flip.svg");
21 Ok(())
22}45fn 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
68/// Points overlaid with a LOESS trend line and confidence band.
69fn 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
95/// Histogram of an approximately-normal sample.
96fn 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
121/// Bar chart of category counts with a fill palette.
122fn 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
148/// Grouped boxplots.
149fn 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
174/// Violin plots of grouped distributions.
175fn 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
198/// Spiral scatter coloured by a continuous variable (viridis).
199fn 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
228/// Faceted scatter, one panel per group.
229fn 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
253/// Overlapping density curves by group.
254fn 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
279/// Filled contour bands from a gridded surface.
280fn 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
302/// Hexagonal binning of a 2-D point cloud.
303fn 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
328/// Heatmap of a gridded value with `geom_tile`.
329fn 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
349/// Jittered categorical scatter (`geom_jitter`).
350fn 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
370/// A confidence band (`geom_ribbon`) under a line.
371fn 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
389/// Stacked areas by group.
390fn 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
418/// The same plot rendered under every built-in theme.
419fn 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}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Sales data with a notable spike
6 let month: Vec<f64> = (1..=12).map(|i| i as f64).collect();
7 let sales = vec![
8 120.0, 135.0, 150.0, 180.0, 210.0, 310.0, 280.0, 250.0, 190.0, 170.0, 155.0, 140.0,
9 ];
10
11 let df = df! {
12 "month" => month,
13 "sales" => sales,
14 }?;
15
16 GGPlot::new(df)
17 .aes(Aes::new().x("month").y("sales"))
18 .geom_line()
19 .geom_point()
20 // Highlight the peak region
21 .annotate_rect(4.5, 7.5, 100.0, 320.0)
22 // Label the peak
23 .annotate_text("Summer Peak", 6.0, 330.0)
24 // Draw an arrow-like segment pointing to the max
25 .annotate_segment(7.5, 330.0, 6.2, 312.0)
26 .title("Monthly Sales with Annotations")
27 .xlab("Month")
28 .ylab("Sales ($)")
29 .save("annotations.svg")?;
30
31 println!("Saved annotations.svg");
32 Ok(())
33}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate two overlapping distributions using deterministic pseudo-random values
6 let value: Vec<f64> = (0..200_u64)
7 .map(|i| {
8 let r = ((i.wrapping_mul(1103515245).wrapping_add(12345)) % (1 << 16)) as f64
9 / (1u64 << 16) as f64;
10 if i < 100 {
11 3.0 + r * 4.0
12 } else {
13 5.0 + r * 4.0
14 }
15 })
16 .collect();
17 let group: Vec<&str> = (0..200)
18 .map(|i| if i < 100 { "Group A" } else { "Group B" })
19 .collect();
20
21 let df = df! {
22 "value" => value,
23 "group" => group,
24 }?;
25
26 GGPlot::new(df)
27 .aes(Aes::new().x("value").color("group"))
28 .geom_density()
29 .title("Density Plot by Group")
30 .xlab("Value")
31 .ylab("Density")
32 .save("density.svg")?;
33
34 println!("Saved density.svg");
35 Ok(())
36}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let sepal_length: Vec<f64> = (0..50).map(|i| 4.5 + i as f64 * 0.05).collect();
6 let sepal_width: Vec<f64> = (0..50)
7 .map(|i| 2.0 + (i as f64 * 0.3).sin() + i as f64 * 0.02)
8 .collect();
9 let species: Vec<&str> = (0..50)
10 .map(|i| match i % 3 {
11 0 => "setosa",
12 1 => "versicolor",
13 _ => "virginica",
14 })
15 .collect();
16
17 let df = df! {
18 "sepal_length" => sepal_length,
19 "sepal_width" => sepal_width,
20 "species" => species,
21 }?;
22
23 GGPlot::new(df)
24 .aes(
25 Aes::new()
26 .x("sepal_length")
27 .y("sepal_width")
28 .color("species"),
29 )
30 .geom_point()
31 .title("Iris Scatter Plot")
32 .xlab("Sepal Length")
33 .ylab("Sepal Width")
34 .save("scatter.svg")?;
35
36 println!("Saved scatter.svg");
37 Ok(())
38}Sourcepub fn tag(self, tag: &str) -> Self
pub fn tag(self, tag: &str) -> Self
Corner tag label (R’s labs(tag = ...)), drawn at the top-left — handy
for labelling figure panels (“A”, “B”, …).
Sourcepub fn subtitle(self, subtitle: &str) -> Self
pub fn subtitle(self, subtitle: &str) -> Self
Examples found in repository?
137fn detail(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
138 let name = "Meridian Components";
139 let mine: Vec<&Po> = rows.iter().filter(|p| p.supplier == name).collect();
140
141 let attributable: Vec<f64> = mine
142 .iter()
143 .filter(|p| p.attributable)
144 .map(|p| p.lead)
145 .collect();
146 let p90 = percentile(attributable.clone(), 0.90);
147
148 let dens_data: Vec<(String, Vec<Value>)> = vec![(
149 "lead".to_string(),
150 attributable.iter().map(|v| Value::Float(*v)).collect(),
151 )];
152 let excused_data: Vec<(String, Vec<Value>)> = vec![(
153 "lead".to_string(),
154 mine.iter()
155 .filter(|p| !p.attributable)
156 .map(|p| Value::Float(p.lead))
157 .collect(),
158 )];
159
160 GGPlot::new(dens_data)
161 .aes(Aes::new().x("lead"))
162 .geom_density_with(GeomDensity {
163 fill: TEAL,
164 color: (20, 110, 98),
165 alpha: 0.45,
166 line_width: 1.5,
167 })
168 // SLA threshold — mass to the left is the on-time share.
169 .geom_vline_with(GeomVline {
170 xintercept: CONTRACT,
171 color: CONTRACT_RED,
172 width: 1.5,
173 linetype: Linetype::Dashed,
174 alpha: 1.0,
175 })
176 // p90 — the number to size safety stock against.
177 .geom_vline_with(GeomVline {
178 xintercept: p90,
179 color: P90_BLUE,
180 width: 1.5,
181 linetype: Linetype::Dashed,
182 alpha: 1.0,
183 })
184 // Excused deliveries, shown distinctly and kept out of the density.
185 .geom_rug_with(GeomRug {
186 color: MUTED,
187 alpha: 0.7,
188 length: 0.04,
189 sides: "b".to_string(),
190 })
191 .layer_data(excused_data)
192 .layer_aes(Aes::new().x("lead"))
193 .annotate_text(&format!("contract {CONTRACT:.0}d"), CONTRACT + 1.0, 0.075)
194 .annotate_text(&format!("p90 {p90:.0}d"), p90 + 1.0, 0.06)
195 .annotate_text("<- excused (external)", 48.0, 0.008)
196 .title(&format!("{name} — lead-time distribution"))
197 .subtitle("attributable deliveries only; excused shown as rug")
198 .xlab("Actual lead time (days)")
199 .ylab("Density")
200 .theme_minimal()
201 .save_with_size(&out("supplier_leadtime"), W, H)?;
202 Ok(())
203}
204
205/// ECDF for one supplier — P(lead <= contract) reads straight off the curve.
206fn ecdf(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
207 let name = "Meridian Components";
208 let attributable: Vec<f64> = rows
209 .iter()
210 .filter(|p| p.supplier == name && p.attributable)
211 .map(|p| p.lead)
212 .collect();
213 let on_time =
214 attributable.iter().filter(|&&l| l <= CONTRACT).count() as f64 / attributable.len() as f64;
215 let data: Vec<(String, Vec<Value>)> = vec![(
216 "lead".to_string(),
217 attributable.iter().map(|v| Value::Float(*v)).collect(),
218 )];
219
220 GGPlot::new(data)
221 .aes(Aes::new().x("lead"))
222 .geom_step()
223 .stat(StatEcdf)
224 .geom_vline_with(GeomVline {
225 xintercept: CONTRACT,
226 color: CONTRACT_RED,
227 width: 1.5,
228 linetype: Linetype::Dashed,
229 alpha: 1.0,
230 })
231 .annotate_text(
232 &format!("on-time rate {:.0}%", on_time * 100.0),
233 CONTRACT + 1.0,
234 0.15,
235 )
236 .title(&format!("{name} — on-time reliability (ECDF)"))
237 .subtitle("cumulative share at the contract line = P(lead <= 30d)")
238 .xlab("Actual lead time (days)")
239 .ylab("Cumulative share")
240 .theme_bw()
241 .save_with_size(&out("supplier_leadtime_ecdf"), W, H)?;
242 Ok(())
243}
244
245/// Compare suppliers: overlaid per-supplier densities against the contract line.
246fn compare(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
247 let lead: Vec<Value> = rows
248 .iter()
249 .filter(|p| p.attributable)
250 .map(|p| Value::Float(p.lead))
251 .collect();
252 let supplier: Vec<Value> = rows
253 .iter()
254 .filter(|p| p.attributable)
255 .map(|p| Value::Str(p.supplier.to_string()))
256 .collect();
257 let data: Vec<(String, Vec<Value>)> = vec![
258 ("lead".to_string(), lead),
259 ("supplier".to_string(), supplier),
260 ];
261
262 GGPlot::new(data)
263 .aes(Aes::new().x("lead").fill("supplier").color("supplier"))
264 .geom_density_with(GeomDensity {
265 alpha: 0.35,
266 line_width: 1.2,
267 ..Default::default()
268 })
269 .geom_vline_with(GeomVline {
270 xintercept: CONTRACT,
271 color: CONTRACT_RED,
272 width: 1.2,
273 linetype: Linetype::Dashed,
274 alpha: 1.0,
275 })
276 .scale_fill_brewer(PaletteName::Dark2)
277 .scale_color_brewer(PaletteName::Dark2)
278 .annotate_text("contract", CONTRACT + 1.0, 0.005)
279 .title("Lead-time distributions by supplier")
280 .subtitle("attributable deliveries; dashed line = contracted lead time")
281 .xlab("Actual lead time (days)")
282 .ylab("Density")
283 .theme_minimal()
284 .save_with_size(&out("supplier_leadtime_compare"), W, H)?;
285 Ok(())
286}Sourcepub fn xlab(self, label: &str) -> Self
pub fn xlab(self, label: &str) -> Self
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "fruit" => ["Apple", "Apple", "Apple", "Banana", "Banana",
7 "Cherry", "Cherry", "Cherry", "Cherry", "Date"],
8 }?;
9
10 GGPlot::new(df)
11 .aes(Aes::new().x("fruit"))
12 .geom_bar()
13 .title("Fruit Counts")
14 .xlab("Fruit")
15 .ylab("Count")
16 .save("bar_chart.svg")?;
17
18 println!("Saved bar_chart.svg");
19 Ok(())
20}More examples
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "language" => ["Rust", "Python", "JavaScript", "Go", "TypeScript", "Java", "C++"],
7 "satisfaction" => [92.0, 88.0, 85.0, 78.0, 82.0, 70.0, 75.0],
8 }?;
9
10 // Horizontal bar chart using coord_flip
11 GGPlot::new(df)
12 .aes(Aes::new().x("language").y("satisfaction"))
13 .geom_col()
14 .coord_flip()
15 .title("Developer Satisfaction by Language")
16 .xlab("Language")
17 .ylab("Satisfaction Score")
18 .save("coord_flip.svg")?;
19
20 println!("Saved coord_flip.svg");
21 Ok(())
22}45fn 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
68/// Points overlaid with a LOESS trend line and confidence band.
69fn 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
95/// Histogram of an approximately-normal sample.
96fn 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
121/// Bar chart of category counts with a fill palette.
122fn 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
148/// Grouped boxplots.
149fn 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
174/// Violin plots of grouped distributions.
175fn 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
198/// Spiral scatter coloured by a continuous variable (viridis).
199fn 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
228/// Faceted scatter, one panel per group.
229fn 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
253/// Overlapping density curves by group.
254fn 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}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Sales data with a notable spike
6 let month: Vec<f64> = (1..=12).map(|i| i as f64).collect();
7 let sales = vec![
8 120.0, 135.0, 150.0, 180.0, 210.0, 310.0, 280.0, 250.0, 190.0, 170.0, 155.0, 140.0,
9 ];
10
11 let df = df! {
12 "month" => month,
13 "sales" => sales,
14 }?;
15
16 GGPlot::new(df)
17 .aes(Aes::new().x("month").y("sales"))
18 .geom_line()
19 .geom_point()
20 // Highlight the peak region
21 .annotate_rect(4.5, 7.5, 100.0, 320.0)
22 // Label the peak
23 .annotate_text("Summer Peak", 6.0, 330.0)
24 // Draw an arrow-like segment pointing to the max
25 .annotate_segment(7.5, 330.0, 6.2, 312.0)
26 .title("Monthly Sales with Annotations")
27 .xlab("Month")
28 .ylab("Sales ($)")
29 .save("annotations.svg")?;
30
31 println!("Saved annotations.svg");
32 Ok(())
33}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate two overlapping distributions using deterministic pseudo-random values
6 let value: Vec<f64> = (0..200_u64)
7 .map(|i| {
8 let r = ((i.wrapping_mul(1103515245).wrapping_add(12345)) % (1 << 16)) as f64
9 / (1u64 << 16) as f64;
10 if i < 100 {
11 3.0 + r * 4.0
12 } else {
13 5.0 + r * 4.0
14 }
15 })
16 .collect();
17 let group: Vec<&str> = (0..200)
18 .map(|i| if i < 100 { "Group A" } else { "Group B" })
19 .collect();
20
21 let df = df! {
22 "value" => value,
23 "group" => group,
24 }?;
25
26 GGPlot::new(df)
27 .aes(Aes::new().x("value").color("group"))
28 .geom_density()
29 .title("Density Plot by Group")
30 .xlab("Value")
31 .ylab("Density")
32 .save("density.svg")?;
33
34 println!("Saved density.svg");
35 Ok(())
36}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let sepal_length: Vec<f64> = (0..50).map(|i| 4.5 + i as f64 * 0.05).collect();
6 let sepal_width: Vec<f64> = (0..50)
7 .map(|i| 2.0 + (i as f64 * 0.3).sin() + i as f64 * 0.02)
8 .collect();
9 let species: Vec<&str> = (0..50)
10 .map(|i| match i % 3 {
11 0 => "setosa",
12 1 => "versicolor",
13 _ => "virginica",
14 })
15 .collect();
16
17 let df = df! {
18 "sepal_length" => sepal_length,
19 "sepal_width" => sepal_width,
20 "species" => species,
21 }?;
22
23 GGPlot::new(df)
24 .aes(
25 Aes::new()
26 .x("sepal_length")
27 .y("sepal_width")
28 .color("species"),
29 )
30 .geom_point()
31 .title("Iris Scatter Plot")
32 .xlab("Sepal Length")
33 .ylab("Sepal Width")
34 .save("scatter.svg")?;
35
36 println!("Saved scatter.svg");
37 Ok(())
38}Sourcepub fn ylab(self, label: &str) -> Self
pub fn ylab(self, label: &str) -> Self
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "fruit" => ["Apple", "Apple", "Apple", "Banana", "Banana",
7 "Cherry", "Cherry", "Cherry", "Cherry", "Date"],
8 }?;
9
10 GGPlot::new(df)
11 .aes(Aes::new().x("fruit"))
12 .geom_bar()
13 .title("Fruit Counts")
14 .xlab("Fruit")
15 .ylab("Count")
16 .save("bar_chart.svg")?;
17
18 println!("Saved bar_chart.svg");
19 Ok(())
20}More examples
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "language" => ["Rust", "Python", "JavaScript", "Go", "TypeScript", "Java", "C++"],
7 "satisfaction" => [92.0, 88.0, 85.0, 78.0, 82.0, 70.0, 75.0],
8 }?;
9
10 // Horizontal bar chart using coord_flip
11 GGPlot::new(df)
12 .aes(Aes::new().x("language").y("satisfaction"))
13 .geom_col()
14 .coord_flip()
15 .title("Developer Satisfaction by Language")
16 .xlab("Language")
17 .ylab("Satisfaction Score")
18 .save("coord_flip.svg")?;
19
20 println!("Saved coord_flip.svg");
21 Ok(())
22}45fn 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
68/// Points overlaid with a LOESS trend line and confidence band.
69fn 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
95/// Histogram of an approximately-normal sample.
96fn 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
121/// Bar chart of category counts with a fill palette.
122fn 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
148/// Grouped boxplots.
149fn 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
174/// Violin plots of grouped distributions.
175fn 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
198/// Spiral scatter coloured by a continuous variable (viridis).
199fn 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
228/// Faceted scatter, one panel per group.
229fn 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
253/// Overlapping density curves by group.
254fn 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}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Sales data with a notable spike
6 let month: Vec<f64> = (1..=12).map(|i| i as f64).collect();
7 let sales = vec![
8 120.0, 135.0, 150.0, 180.0, 210.0, 310.0, 280.0, 250.0, 190.0, 170.0, 155.0, 140.0,
9 ];
10
11 let df = df! {
12 "month" => month,
13 "sales" => sales,
14 }?;
15
16 GGPlot::new(df)
17 .aes(Aes::new().x("month").y("sales"))
18 .geom_line()
19 .geom_point()
20 // Highlight the peak region
21 .annotate_rect(4.5, 7.5, 100.0, 320.0)
22 // Label the peak
23 .annotate_text("Summer Peak", 6.0, 330.0)
24 // Draw an arrow-like segment pointing to the max
25 .annotate_segment(7.5, 330.0, 6.2, 312.0)
26 .title("Monthly Sales with Annotations")
27 .xlab("Month")
28 .ylab("Sales ($)")
29 .save("annotations.svg")?;
30
31 println!("Saved annotations.svg");
32 Ok(())
33}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate two overlapping distributions using deterministic pseudo-random values
6 let value: Vec<f64> = (0..200_u64)
7 .map(|i| {
8 let r = ((i.wrapping_mul(1103515245).wrapping_add(12345)) % (1 << 16)) as f64
9 / (1u64 << 16) as f64;
10 if i < 100 {
11 3.0 + r * 4.0
12 } else {
13 5.0 + r * 4.0
14 }
15 })
16 .collect();
17 let group: Vec<&str> = (0..200)
18 .map(|i| if i < 100 { "Group A" } else { "Group B" })
19 .collect();
20
21 let df = df! {
22 "value" => value,
23 "group" => group,
24 }?;
25
26 GGPlot::new(df)
27 .aes(Aes::new().x("value").color("group"))
28 .geom_density()
29 .title("Density Plot by Group")
30 .xlab("Value")
31 .ylab("Density")
32 .save("density.svg")?;
33
34 println!("Saved density.svg");
35 Ok(())
36}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let sepal_length: Vec<f64> = (0..50).map(|i| 4.5 + i as f64 * 0.05).collect();
6 let sepal_width: Vec<f64> = (0..50)
7 .map(|i| 2.0 + (i as f64 * 0.3).sin() + i as f64 * 0.02)
8 .collect();
9 let species: Vec<&str> = (0..50)
10 .map(|i| match i % 3 {
11 0 => "setosa",
12 1 => "versicolor",
13 _ => "virginica",
14 })
15 .collect();
16
17 let df = df! {
18 "sepal_length" => sepal_length,
19 "sepal_width" => sepal_width,
20 "species" => species,
21 }?;
22
23 GGPlot::new(df)
24 .aes(
25 Aes::new()
26 .x("sepal_length")
27 .y("sepal_width")
28 .color("species"),
29 )
30 .geom_point()
31 .title("Iris Scatter Plot")
32 .xlab("Sepal Length")
33 .ylab("Sepal Width")
34 .save("scatter.svg")?;
35
36 println!("Saved scatter.svg");
37 Ok(())
38}pub fn caption(self, caption: &str) -> Self
Sourcepub fn annotate(self, annotation: Annotation) -> Self
pub fn annotate(self, annotation: Annotation) -> Self
Add an annotation to the plot.
Sourcepub fn annotate_text(self, label: &str, x: f64, y: f64) -> Self
pub fn annotate_text(self, label: &str, x: f64, y: f64) -> Self
Add a text annotation at data coordinates.
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Sales data with a notable spike
6 let month: Vec<f64> = (1..=12).map(|i| i as f64).collect();
7 let sales = vec![
8 120.0, 135.0, 150.0, 180.0, 210.0, 310.0, 280.0, 250.0, 190.0, 170.0, 155.0, 140.0,
9 ];
10
11 let df = df! {
12 "month" => month,
13 "sales" => sales,
14 }?;
15
16 GGPlot::new(df)
17 .aes(Aes::new().x("month").y("sales"))
18 .geom_line()
19 .geom_point()
20 // Highlight the peak region
21 .annotate_rect(4.5, 7.5, 100.0, 320.0)
22 // Label the peak
23 .annotate_text("Summer Peak", 6.0, 330.0)
24 // Draw an arrow-like segment pointing to the max
25 .annotate_segment(7.5, 330.0, 6.2, 312.0)
26 .title("Monthly Sales with Annotations")
27 .xlab("Month")
28 .ylab("Sales ($)")
29 .save("annotations.svg")?;
30
31 println!("Saved annotations.svg");
32 Ok(())
33}More examples
137fn detail(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
138 let name = "Meridian Components";
139 let mine: Vec<&Po> = rows.iter().filter(|p| p.supplier == name).collect();
140
141 let attributable: Vec<f64> = mine
142 .iter()
143 .filter(|p| p.attributable)
144 .map(|p| p.lead)
145 .collect();
146 let p90 = percentile(attributable.clone(), 0.90);
147
148 let dens_data: Vec<(String, Vec<Value>)> = vec![(
149 "lead".to_string(),
150 attributable.iter().map(|v| Value::Float(*v)).collect(),
151 )];
152 let excused_data: Vec<(String, Vec<Value>)> = vec![(
153 "lead".to_string(),
154 mine.iter()
155 .filter(|p| !p.attributable)
156 .map(|p| Value::Float(p.lead))
157 .collect(),
158 )];
159
160 GGPlot::new(dens_data)
161 .aes(Aes::new().x("lead"))
162 .geom_density_with(GeomDensity {
163 fill: TEAL,
164 color: (20, 110, 98),
165 alpha: 0.45,
166 line_width: 1.5,
167 })
168 // SLA threshold — mass to the left is the on-time share.
169 .geom_vline_with(GeomVline {
170 xintercept: CONTRACT,
171 color: CONTRACT_RED,
172 width: 1.5,
173 linetype: Linetype::Dashed,
174 alpha: 1.0,
175 })
176 // p90 — the number to size safety stock against.
177 .geom_vline_with(GeomVline {
178 xintercept: p90,
179 color: P90_BLUE,
180 width: 1.5,
181 linetype: Linetype::Dashed,
182 alpha: 1.0,
183 })
184 // Excused deliveries, shown distinctly and kept out of the density.
185 .geom_rug_with(GeomRug {
186 color: MUTED,
187 alpha: 0.7,
188 length: 0.04,
189 sides: "b".to_string(),
190 })
191 .layer_data(excused_data)
192 .layer_aes(Aes::new().x("lead"))
193 .annotate_text(&format!("contract {CONTRACT:.0}d"), CONTRACT + 1.0, 0.075)
194 .annotate_text(&format!("p90 {p90:.0}d"), p90 + 1.0, 0.06)
195 .annotate_text("<- excused (external)", 48.0, 0.008)
196 .title(&format!("{name} — lead-time distribution"))
197 .subtitle("attributable deliveries only; excused shown as rug")
198 .xlab("Actual lead time (days)")
199 .ylab("Density")
200 .theme_minimal()
201 .save_with_size(&out("supplier_leadtime"), W, H)?;
202 Ok(())
203}
204
205/// ECDF for one supplier — P(lead <= contract) reads straight off the curve.
206fn ecdf(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
207 let name = "Meridian Components";
208 let attributable: Vec<f64> = rows
209 .iter()
210 .filter(|p| p.supplier == name && p.attributable)
211 .map(|p| p.lead)
212 .collect();
213 let on_time =
214 attributable.iter().filter(|&&l| l <= CONTRACT).count() as f64 / attributable.len() as f64;
215 let data: Vec<(String, Vec<Value>)> = vec![(
216 "lead".to_string(),
217 attributable.iter().map(|v| Value::Float(*v)).collect(),
218 )];
219
220 GGPlot::new(data)
221 .aes(Aes::new().x("lead"))
222 .geom_step()
223 .stat(StatEcdf)
224 .geom_vline_with(GeomVline {
225 xintercept: CONTRACT,
226 color: CONTRACT_RED,
227 width: 1.5,
228 linetype: Linetype::Dashed,
229 alpha: 1.0,
230 })
231 .annotate_text(
232 &format!("on-time rate {:.0}%", on_time * 100.0),
233 CONTRACT + 1.0,
234 0.15,
235 )
236 .title(&format!("{name} — on-time reliability (ECDF)"))
237 .subtitle("cumulative share at the contract line = P(lead <= 30d)")
238 .xlab("Actual lead time (days)")
239 .ylab("Cumulative share")
240 .theme_bw()
241 .save_with_size(&out("supplier_leadtime_ecdf"), W, H)?;
242 Ok(())
243}
244
245/// Compare suppliers: overlaid per-supplier densities against the contract line.
246fn compare(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
247 let lead: Vec<Value> = rows
248 .iter()
249 .filter(|p| p.attributable)
250 .map(|p| Value::Float(p.lead))
251 .collect();
252 let supplier: Vec<Value> = rows
253 .iter()
254 .filter(|p| p.attributable)
255 .map(|p| Value::Str(p.supplier.to_string()))
256 .collect();
257 let data: Vec<(String, Vec<Value>)> = vec![
258 ("lead".to_string(), lead),
259 ("supplier".to_string(), supplier),
260 ];
261
262 GGPlot::new(data)
263 .aes(Aes::new().x("lead").fill("supplier").color("supplier"))
264 .geom_density_with(GeomDensity {
265 alpha: 0.35,
266 line_width: 1.2,
267 ..Default::default()
268 })
269 .geom_vline_with(GeomVline {
270 xintercept: CONTRACT,
271 color: CONTRACT_RED,
272 width: 1.2,
273 linetype: Linetype::Dashed,
274 alpha: 1.0,
275 })
276 .scale_fill_brewer(PaletteName::Dark2)
277 .scale_color_brewer(PaletteName::Dark2)
278 .annotate_text("contract", CONTRACT + 1.0, 0.005)
279 .title("Lead-time distributions by supplier")
280 .subtitle("attributable deliveries; dashed line = contracted lead time")
281 .xlab("Actual lead time (days)")
282 .ylab("Density")
283 .theme_minimal()
284 .save_with_size(&out("supplier_leadtime_compare"), W, H)?;
285 Ok(())
286}Sourcepub fn annotate_rect(self, xmin: f64, xmax: f64, ymin: f64, ymax: f64) -> Self
pub fn annotate_rect(self, xmin: f64, xmax: f64, ymin: f64, ymax: f64) -> Self
Add a rectangle annotation at data coordinates.
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Sales data with a notable spike
6 let month: Vec<f64> = (1..=12).map(|i| i as f64).collect();
7 let sales = vec![
8 120.0, 135.0, 150.0, 180.0, 210.0, 310.0, 280.0, 250.0, 190.0, 170.0, 155.0, 140.0,
9 ];
10
11 let df = df! {
12 "month" => month,
13 "sales" => sales,
14 }?;
15
16 GGPlot::new(df)
17 .aes(Aes::new().x("month").y("sales"))
18 .geom_line()
19 .geom_point()
20 // Highlight the peak region
21 .annotate_rect(4.5, 7.5, 100.0, 320.0)
22 // Label the peak
23 .annotate_text("Summer Peak", 6.0, 330.0)
24 // Draw an arrow-like segment pointing to the max
25 .annotate_segment(7.5, 330.0, 6.2, 312.0)
26 .title("Monthly Sales with Annotations")
27 .xlab("Month")
28 .ylab("Sales ($)")
29 .save("annotations.svg")?;
30
31 println!("Saved annotations.svg");
32 Ok(())
33}Sourcepub fn annotate_segment(self, x: f64, y: f64, xend: f64, yend: f64) -> Self
pub fn annotate_segment(self, x: f64, y: f64, xend: f64, yend: f64) -> Self
Add a segment annotation between data coordinates.
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Sales data with a notable spike
6 let month: Vec<f64> = (1..=12).map(|i| i as f64).collect();
7 let sales = vec![
8 120.0, 135.0, 150.0, 180.0, 210.0, 310.0, 280.0, 250.0, 190.0, 170.0, 155.0, 140.0,
9 ];
10
11 let df = df! {
12 "month" => month,
13 "sales" => sales,
14 }?;
15
16 GGPlot::new(df)
17 .aes(Aes::new().x("month").y("sales"))
18 .geom_line()
19 .geom_point()
20 // Highlight the peak region
21 .annotate_rect(4.5, 7.5, 100.0, 320.0)
22 // Label the peak
23 .annotate_text("Summer Peak", 6.0, 330.0)
24 // Draw an arrow-like segment pointing to the max
25 .annotate_segment(7.5, 330.0, 6.2, 312.0)
26 .title("Monthly Sales with Annotations")
27 .xlab("Month")
28 .ylab("Sales ($)")
29 .save("annotations.svg")?;
30
31 println!("Saved annotations.svg");
32 Ok(())
33}Sourcepub fn try_build(self) -> Result<BuiltPlot, GGError>
pub fn try_build(self) -> Result<BuiltPlot, GGError>
Build the plot without rendering, returning errors on validation failure.
Sourcepub fn build(self) -> BuiltPlot
pub fn build(self) -> BuiltPlot
Build the plot without rendering (analogous to R’s ggplot_build()).
Returns the fully computed BuiltPlot with layer data ready for inspection.
Panics on validation errors — use try_build() for error handling.
Sourcepub fn save(self, path: &str) -> Result<(), GGError>
pub fn save(self, path: &str) -> Result<(), GGError>
Build and save the plot to a file. Format determined by extension.
Examples found in repository?
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "fruit" => ["Apple", "Apple", "Apple", "Banana", "Banana",
7 "Cherry", "Cherry", "Cherry", "Cherry", "Date"],
8 }?;
9
10 GGPlot::new(df)
11 .aes(Aes::new().x("fruit"))
12 .geom_bar()
13 .title("Fruit Counts")
14 .xlab("Fruit")
15 .ylab("Count")
16 .save("bar_chart.svg")?;
17
18 println!("Saved bar_chart.svg");
19 Ok(())
20}More examples
4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let df = df! {
6 "language" => ["Rust", "Python", "JavaScript", "Go", "TypeScript", "Java", "C++"],
7 "satisfaction" => [92.0, 88.0, 85.0, 78.0, 82.0, 70.0, 75.0],
8 }?;
9
10 // Horizontal bar chart using coord_flip
11 GGPlot::new(df)
12 .aes(Aes::new().x("language").y("satisfaction"))
13 .geom_col()
14 .coord_flip()
15 .title("Developer Satisfaction by Language")
16 .xlab("Language")
17 .ylab("Satisfaction Score")
18 .save("coord_flip.svg")?;
19
20 println!("Saved coord_flip.svg");
21 Ok(())
22}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Sales data with a notable spike
6 let month: Vec<f64> = (1..=12).map(|i| i as f64).collect();
7 let sales = vec![
8 120.0, 135.0, 150.0, 180.0, 210.0, 310.0, 280.0, 250.0, 190.0, 170.0, 155.0, 140.0,
9 ];
10
11 let df = df! {
12 "month" => month,
13 "sales" => sales,
14 }?;
15
16 GGPlot::new(df)
17 .aes(Aes::new().x("month").y("sales"))
18 .geom_line()
19 .geom_point()
20 // Highlight the peak region
21 .annotate_rect(4.5, 7.5, 100.0, 320.0)
22 // Label the peak
23 .annotate_text("Summer Peak", 6.0, 330.0)
24 // Draw an arrow-like segment pointing to the max
25 .annotate_segment(7.5, 330.0, 6.2, 312.0)
26 .title("Monthly Sales with Annotations")
27 .xlab("Month")
28 .ylab("Sales ($)")
29 .save("annotations.svg")?;
30
31 println!("Saved annotations.svg");
32 Ok(())
33}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate two overlapping distributions using deterministic pseudo-random values
6 let value: Vec<f64> = (0..200_u64)
7 .map(|i| {
8 let r = ((i.wrapping_mul(1103515245).wrapping_add(12345)) % (1 << 16)) as f64
9 / (1u64 << 16) as f64;
10 if i < 100 {
11 3.0 + r * 4.0
12 } else {
13 5.0 + r * 4.0
14 }
15 })
16 .collect();
17 let group: Vec<&str> = (0..200)
18 .map(|i| if i < 100 { "Group A" } else { "Group B" })
19 .collect();
20
21 let df = df! {
22 "value" => value,
23 "group" => group,
24 }?;
25
26 GGPlot::new(df)
27 .aes(Aes::new().x("value").color("group"))
28 .geom_density()
29 .title("Density Plot by Group")
30 .xlab("Value")
31 .ylab("Density")
32 .save("density.svg")?;
33
34 println!("Saved density.svg");
35 Ok(())
36}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 let sepal_length: Vec<f64> = (0..50).map(|i| 4.5 + i as f64 * 0.05).collect();
6 let sepal_width: Vec<f64> = (0..50)
7 .map(|i| 2.0 + (i as f64 * 0.3).sin() + i as f64 * 0.02)
8 .collect();
9 let species: Vec<&str> = (0..50)
10 .map(|i| match i % 3 {
11 0 => "setosa",
12 1 => "versicolor",
13 _ => "virginica",
14 })
15 .collect();
16
17 let df = df! {
18 "sepal_length" => sepal_length,
19 "sepal_width" => sepal_width,
20 "species" => species,
21 }?;
22
23 GGPlot::new(df)
24 .aes(
25 Aes::new()
26 .x("sepal_length")
27 .y("sepal_width")
28 .color("species"),
29 )
30 .geom_point()
31 .title("Iris Scatter Plot")
32 .xlab("Sepal Length")
33 .ylab("Sepal Width")
34 .save("scatter.svg")?;
35
36 println!("Saved scatter.svg");
37 Ok(())
38}4fn main() -> Result<(), Box<dyn std::error::Error>> {
5 // Generate noisy sine wave data
6 let x: Vec<f64> = (0..80).map(|i| (i as f64) * 0.1).collect();
7 let y: Vec<f64> = (0..80)
8 .map(|i| {
9 let xv = (i as f64) * 0.1;
10 let noise = ((i * 17 + 3) % 11) as f64 / 11.0 - 0.5; // deterministic pseudo-noise
11 (xv * 0.8).sin() * 2.0 + noise * 1.5
12 })
13 .collect();
14
15 let df = df! {
16 "x" => &x,
17 "y" => &y,
18 }?;
19
20 // Linear smooth (default)
21 GGPlot::new(df.clone())
22 .aes(Aes::new().x("x").y("y"))
23 .geom_point()
24 .geom_smooth()
25 .title("Linear Smooth (method = lm)")
26 .save("smooth_lm.svg")?;
27
28 println!("Saved smooth_lm.svg");
29
30 // LOESS smooth
31 GGPlot::new(df)
32 .aes(Aes::new().x("x").y("y"))
33 .geom_point()
34 .geom_smooth_with(GeomSmooth::default().loess(0.3))
35 .title("LOESS Smooth (span = 0.3)")
36 .save("smooth_loess.svg")?;
37
38 println!("Saved smooth_loess.svg");
39 Ok(())
40}Sourcepub fn save_with_size(self, path: &str, w: u32, h: u32) -> Result<(), GGError>
pub fn save_with_size(self, path: &str, w: u32, h: u32) -> Result<(), GGError>
Build and save with custom dimensions.
Examples found in repository?
45fn 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
68/// Points overlaid with a LOESS trend line and confidence band.
69fn 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
95/// Histogram of an approximately-normal sample.
96fn 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
121/// Bar chart of category counts with a fill palette.
122fn 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
148/// Grouped boxplots.
149fn 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
174/// Violin plots of grouped distributions.
175fn 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
198/// Spiral scatter coloured by a continuous variable (viridis).
199fn 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
228/// Faceted scatter, one panel per group.
229fn 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
253/// Overlapping density curves by group.
254fn 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
279/// Filled contour bands from a gridded surface.
280fn 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
302/// Hexagonal binning of a 2-D point cloud.
303fn 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
328/// Heatmap of a gridded value with `geom_tile`.
329fn 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
349/// Jittered categorical scatter (`geom_jitter`).
350fn 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
370/// A confidence band (`geom_ribbon`) under a line.
371fn 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
389/// Stacked areas by group.
390fn 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
418/// The same plot rendered under every built-in theme.
419fn 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}More examples
137fn detail(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
138 let name = "Meridian Components";
139 let mine: Vec<&Po> = rows.iter().filter(|p| p.supplier == name).collect();
140
141 let attributable: Vec<f64> = mine
142 .iter()
143 .filter(|p| p.attributable)
144 .map(|p| p.lead)
145 .collect();
146 let p90 = percentile(attributable.clone(), 0.90);
147
148 let dens_data: Vec<(String, Vec<Value>)> = vec![(
149 "lead".to_string(),
150 attributable.iter().map(|v| Value::Float(*v)).collect(),
151 )];
152 let excused_data: Vec<(String, Vec<Value>)> = vec![(
153 "lead".to_string(),
154 mine.iter()
155 .filter(|p| !p.attributable)
156 .map(|p| Value::Float(p.lead))
157 .collect(),
158 )];
159
160 GGPlot::new(dens_data)
161 .aes(Aes::new().x("lead"))
162 .geom_density_with(GeomDensity {
163 fill: TEAL,
164 color: (20, 110, 98),
165 alpha: 0.45,
166 line_width: 1.5,
167 })
168 // SLA threshold — mass to the left is the on-time share.
169 .geom_vline_with(GeomVline {
170 xintercept: CONTRACT,
171 color: CONTRACT_RED,
172 width: 1.5,
173 linetype: Linetype::Dashed,
174 alpha: 1.0,
175 })
176 // p90 — the number to size safety stock against.
177 .geom_vline_with(GeomVline {
178 xintercept: p90,
179 color: P90_BLUE,
180 width: 1.5,
181 linetype: Linetype::Dashed,
182 alpha: 1.0,
183 })
184 // Excused deliveries, shown distinctly and kept out of the density.
185 .geom_rug_with(GeomRug {
186 color: MUTED,
187 alpha: 0.7,
188 length: 0.04,
189 sides: "b".to_string(),
190 })
191 .layer_data(excused_data)
192 .layer_aes(Aes::new().x("lead"))
193 .annotate_text(&format!("contract {CONTRACT:.0}d"), CONTRACT + 1.0, 0.075)
194 .annotate_text(&format!("p90 {p90:.0}d"), p90 + 1.0, 0.06)
195 .annotate_text("<- excused (external)", 48.0, 0.008)
196 .title(&format!("{name} — lead-time distribution"))
197 .subtitle("attributable deliveries only; excused shown as rug")
198 .xlab("Actual lead time (days)")
199 .ylab("Density")
200 .theme_minimal()
201 .save_with_size(&out("supplier_leadtime"), W, H)?;
202 Ok(())
203}
204
205/// ECDF for one supplier — P(lead <= contract) reads straight off the curve.
206fn ecdf(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
207 let name = "Meridian Components";
208 let attributable: Vec<f64> = rows
209 .iter()
210 .filter(|p| p.supplier == name && p.attributable)
211 .map(|p| p.lead)
212 .collect();
213 let on_time =
214 attributable.iter().filter(|&&l| l <= CONTRACT).count() as f64 / attributable.len() as f64;
215 let data: Vec<(String, Vec<Value>)> = vec![(
216 "lead".to_string(),
217 attributable.iter().map(|v| Value::Float(*v)).collect(),
218 )];
219
220 GGPlot::new(data)
221 .aes(Aes::new().x("lead"))
222 .geom_step()
223 .stat(StatEcdf)
224 .geom_vline_with(GeomVline {
225 xintercept: CONTRACT,
226 color: CONTRACT_RED,
227 width: 1.5,
228 linetype: Linetype::Dashed,
229 alpha: 1.0,
230 })
231 .annotate_text(
232 &format!("on-time rate {:.0}%", on_time * 100.0),
233 CONTRACT + 1.0,
234 0.15,
235 )
236 .title(&format!("{name} — on-time reliability (ECDF)"))
237 .subtitle("cumulative share at the contract line = P(lead <= 30d)")
238 .xlab("Actual lead time (days)")
239 .ylab("Cumulative share")
240 .theme_bw()
241 .save_with_size(&out("supplier_leadtime_ecdf"), W, H)?;
242 Ok(())
243}
244
245/// Compare suppliers: overlaid per-supplier densities against the contract line.
246fn compare(rows: &[Po]) -> Result<(), Box<dyn std::error::Error>> {
247 let lead: Vec<Value> = rows
248 .iter()
249 .filter(|p| p.attributable)
250 .map(|p| Value::Float(p.lead))
251 .collect();
252 let supplier: Vec<Value> = rows
253 .iter()
254 .filter(|p| p.attributable)
255 .map(|p| Value::Str(p.supplier.to_string()))
256 .collect();
257 let data: Vec<(String, Vec<Value>)> = vec![
258 ("lead".to_string(), lead),
259 ("supplier".to_string(), supplier),
260 ];
261
262 GGPlot::new(data)
263 .aes(Aes::new().x("lead").fill("supplier").color("supplier"))
264 .geom_density_with(GeomDensity {
265 alpha: 0.35,
266 line_width: 1.2,
267 ..Default::default()
268 })
269 .geom_vline_with(GeomVline {
270 xintercept: CONTRACT,
271 color: CONTRACT_RED,
272 width: 1.2,
273 linetype: Linetype::Dashed,
274 alpha: 1.0,
275 })
276 .scale_fill_brewer(PaletteName::Dark2)
277 .scale_color_brewer(PaletteName::Dark2)
278 .annotate_text("contract", CONTRACT + 1.0, 0.005)
279 .title("Lead-time distributions by supplier")
280 .subtitle("attributable deliveries; dashed line = contracted lead time")
281 .xlab("Actual lead time (days)")
282 .ylab("Density")
283 .theme_minimal()
284 .save_with_size(&out("supplier_leadtime_compare"), W, H)?;
285 Ok(())
286}Sourcepub fn render_svg(self) -> Result<String, GGError>
pub fn render_svg(self) -> Result<String, GGError>
Render the plot to an in-memory SVG document (default 800x600).
Unlike save, this writes nothing to disk — handy for
serving charts from a web/MCP service.
Sourcepub fn render_svg_with_size(self, w: u32, h: u32) -> Result<String, GGError>
pub fn render_svg_with_size(self, w: u32, h: u32) -> Result<String, GGError>
Render the plot to an in-memory SVG document with custom dimensions.
Sourcepub fn render_png(self) -> Result<Vec<u8>, GGError>
pub fn render_png(self) -> Result<Vec<u8>, GGError>
Render the plot to in-memory PNG bytes (default 800x600).
Returns a fully-encoded PNG, ready to write to an HTTP response or embed as a data URI — no temp files involved.
Auto Trait Implementations§
impl !RefUnwindSafe for GGPlot
impl !UnwindSafe for GGPlot
impl Freeze for GGPlot
impl Send for GGPlot
impl Sync for GGPlot
impl Unpin for GGPlot
impl UnsafeUnpin for GGPlot
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more