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