Skip to main content

plotly_subplot_grid/
plotly_subplot_grid.rs

1use plotlars::polars::prelude::*;
2use plotlars::{
3    Arrangement, Axis, BarPlot, BoxPlot, CandlestickPlot, ColorBar, CsvReader, Direction, HeatMap,
4    Histogram, Legend, Line, Mode, Orientation, Palette, Rgb, SankeyDiagram, Scatter3dPlot,
5    ScatterGeo, ScatterMap, ScatterPlot, ScatterPolar, Shape, SubplotGrid, Text, TickDirection,
6    TimeSeriesPlot, ValueExponent,
7};
8
9fn main() {
10    regular_grid_example();
11    irregular_grid_example();
12    mixed_grid_example();
13}
14
15fn regular_grid_example() {
16    let dataset1 = CsvReader::new("data/animal_statistics.csv")
17        .finish()
18        .unwrap();
19
20    let plot1 = BarPlot::builder()
21        .data(&dataset1)
22        .labels("animal")
23        .values("value")
24        .orientation(Orientation::Vertical)
25        .group("gender")
26        .sort_groups_by(|a, b| a.len().cmp(&b.len()))
27        .error("error")
28        .colors(vec![Rgb(255, 127, 80), Rgb(64, 224, 208)])
29        .plot_title(Text::from("Bar Plot").x(-0.05).y(1.35).size(14))
30        .y_title(Text::from("value").x(-0.055).y(0.76))
31        .x_title(Text::from("animal").x(0.97).y(-0.2))
32        .legend(
33            &Legend::new()
34                .orientation(Orientation::Horizontal)
35                .x(0.4)
36                .y(1.2),
37        )
38        .build();
39
40    let dataset2 = CsvReader::new("data/penguins.csv")
41        .finish()
42        .unwrap()
43        .lazy()
44        .select([
45            col("species"),
46            col("sex").alias("gender"),
47            col("flipper_length_mm").cast(DataType::Int16),
48            col("body_mass_g").cast(DataType::Int16),
49        ])
50        .collect()
51        .unwrap();
52
53    let axis = Axis::new()
54        .show_line(true)
55        .tick_direction(TickDirection::OutSide)
56        .value_thousands(true);
57
58    let plot2 = ScatterPlot::builder()
59        .data(&dataset2)
60        .x("body_mass_g")
61        .y("flipper_length_mm")
62        .group("species")
63        .sort_groups_by(|a, b| {
64            if a.len() == b.len() {
65                a.cmp(b)
66            } else {
67                a.len().cmp(&b.len())
68            }
69        })
70        .opacity(0.5)
71        .size(12)
72        .colors(vec![Rgb(178, 34, 34), Rgb(65, 105, 225), Rgb(255, 140, 0)])
73        .shapes(vec![Shape::Circle, Shape::Square, Shape::Diamond])
74        .plot_title(Text::from("Scatter Plot").x(-0.075).y(1.35).size(14))
75        .x_title(Text::from("body mass (g)").y(-0.4))
76        .y_title(Text::from("flipper length (mm)").x(-0.078).y(0.5))
77        .legend_title("species")
78        .x_axis(&axis.clone().value_range(2500.0, 6500.0))
79        .y_axis(&axis.clone().value_range(170.0, 240.0))
80        .legend(&Legend::new().x(0.98).y(0.95))
81        .build();
82
83    let dataset3 = CsvReader::new("data/debilt_2023_temps.csv")
84        .has_header(true)
85        .try_parse_dates(true)
86        .finish()
87        .unwrap()
88        .lazy()
89        .with_columns(vec![
90            (col("tavg") / lit(10)).alias("avg"),
91            (col("tmin") / lit(10)).alias("min"),
92            (col("tmax") / lit(10)).alias("max"),
93        ])
94        .collect()
95        .unwrap();
96
97    let plot3 = TimeSeriesPlot::builder()
98        .data(&dataset3)
99        .x("date")
100        .y("avg")
101        .additional_series(vec!["min", "max"])
102        .colors(vec![Rgb(128, 128, 128), Rgb(0, 122, 255), Rgb(255, 128, 0)])
103        .lines(vec![Line::Solid, Line::Dot, Line::Dot])
104        .plot_title(Text::from("Time Series Plot").x(-0.05).y(1.35).size(14))
105        .y_title(Text::from("temperature (ÂșC)").x(-0.055).y(0.6))
106        .legend(&Legend::new().x(0.9).y(1.25))
107        .build();
108
109    let plot4 = BoxPlot::builder()
110        .data(&dataset2)
111        .labels("species")
112        .values("body_mass_g")
113        .orientation(Orientation::Vertical)
114        .group("gender")
115        .box_points(true)
116        .point_offset(-1.5)
117        .jitter(0.01)
118        .opacity(0.1)
119        .colors(vec![Rgb(0, 191, 255), Rgb(57, 255, 20), Rgb(255, 105, 180)])
120        .plot_title(Text::from("Box Plot").x(-0.075).y(1.35).size(14))
121        .x_title(Text::from("species").y(-0.3))
122        .y_title(Text::from("body mass (g)").x(-0.08).y(0.5))
123        .legend_title(Text::from("gender").size(12))
124        .y_axis(&Axis::new().value_thousands(true))
125        .legend(&Legend::new().x(1.0))
126        .build();
127
128    SubplotGrid::regular()
129        .plots(vec![&plot1, &plot2, &plot3, &plot4])
130        .rows(2)
131        .cols(2)
132        .v_gap(0.4)
133        .title(
134            Text::from("Regular Subplot Grid")
135                .size(16)
136                .font("Arial Black")
137                .y(0.95),
138        )
139        .build()
140        .plot();
141}
142
143fn irregular_grid_example() {
144    let dataset1 = CsvReader::new("data/penguins.csv")
145        .finish()
146        .unwrap()
147        .lazy()
148        .select([
149            col("species"),
150            col("sex").alias("gender"),
151            col("flipper_length_mm").cast(DataType::Int16),
152            col("body_mass_g").cast(DataType::Int16),
153        ])
154        .collect()
155        .unwrap();
156
157    let axis = Axis::new()
158        .show_line(true)
159        .show_grid(true)
160        .value_thousands(true)
161        .tick_direction(TickDirection::OutSide);
162
163    let plot1 = Histogram::builder()
164        .data(&dataset1)
165        .x("body_mass_g")
166        .group("species")
167        .opacity(0.5)
168        .colors(vec![Rgb(255, 165, 0), Rgb(147, 112, 219), Rgb(46, 139, 87)])
169        .plot_title(Text::from("Histogram").x(0.0).y(1.35).size(14))
170        .x_title(Text::from("body mass (g)").x(0.94).y(-0.35))
171        .y_title(Text::from("count").x(-0.062).y(0.83))
172        .x_axis(&axis)
173        .y_axis(&axis)
174        .legend_title(Text::from("species"))
175        .legend(&Legend::new().x(0.87).y(1.2))
176        .build();
177
178    let dataset2 = CsvReader::new("data/stock_prices.csv").finish().unwrap();
179
180    let increasing = Direction::new()
181        .line_color(Rgb(0, 200, 100))
182        .line_width(0.5);
183
184    let decreasing = Direction::new()
185        .line_color(Rgb(200, 50, 50))
186        .line_width(0.5);
187
188    let plot2 = CandlestickPlot::builder()
189        .data(&dataset2)
190        .dates("date")
191        .open("open")
192        .high("high")
193        .low("low")
194        .close("close")
195        .increasing(&increasing)
196        .decreasing(&decreasing)
197        .whisker_width(0.1)
198        .plot_title(Text::from("Candlestick").x(0.0).y(1.35).size(14))
199        .y_title(Text::from("price ($)").x(-0.06).y(0.76))
200        .y_axis(&Axis::new().show_axis(true).show_grid(true))
201        .build();
202
203    let dataset3 = CsvReader::new("data/heatmap.csv").finish().unwrap();
204
205    let plot3 = HeatMap::builder()
206        .data(&dataset3)
207        .x("x")
208        .y("y")
209        .z("z")
210        .color_bar(
211            &ColorBar::new()
212                .value_exponent(ValueExponent::None)
213                .separate_thousands(true)
214                .tick_length(5)
215                .tick_step(5000.0),
216        )
217        .plot_title(Text::from("Heat Map").x(0.0).y(1.35).size(14))
218        .color_scale(Palette::Viridis)
219        .build();
220
221    SubplotGrid::irregular()
222        .plots(vec![
223            (&plot1, 0, 0, 1, 1),
224            (&plot2, 0, 1, 1, 1),
225            (&plot3, 1, 0, 1, 2),
226        ])
227        .rows(2)
228        .cols(2)
229        .v_gap(0.35)
230        .h_gap(0.05)
231        .title(
232            Text::from("Irregular Subplot Grid")
233                .size(16)
234                .font("Arial Black")
235                .y(0.95),
236        )
237        .build()
238        .plot();
239}
240
241fn mixed_grid_example() {
242    // 2D cartesian scatter (baseline)
243    let penguins = CsvReader::new("data/penguins.csv")
244        .finish()
245        .unwrap()
246        .lazy()
247        .select([
248            col("species"),
249            col("bill_length_mm"),
250            col("flipper_length_mm"),
251            col("body_mass_g"),
252        ])
253        .collect()
254        .unwrap();
255
256    let scatter_2d = ScatterPlot::builder()
257        .data(&penguins)
258        .x("bill_length_mm")
259        .y("flipper_length_mm")
260        .group("species")
261        .opacity(0.65)
262        .size(10)
263        .plot_title(Text::from("Penguins 2D").y(1.3))
264        .build();
265
266    // 3D scene subplot
267    let scatter_3d = Scatter3dPlot::builder()
268        .data(&penguins)
269        .x("bill_length_mm")
270        .y("flipper_length_mm")
271        .z("body_mass_g")
272        .group("species")
273        .opacity(0.35)
274        .size(6)
275        .plot_title(Text::from("Penguins 3D").y(1.45))
276        .build();
277
278    // Polar subplot
279    let polar_df = CsvReader::new("data/product_comparison_polar.csv")
280        .finish()
281        .unwrap();
282
283    let polar = ScatterPolar::builder()
284        .data(&polar_df)
285        .theta("angle")
286        .r("score")
287        .group("product")
288        .mode(Mode::LinesMarkers)
289        .size(10)
290        .plot_title(Text::from("Product Comparison (Polar)").y(1.5).x(0.72))
291        .legend(&Legend::new().x(0.8))
292        .build();
293
294    // Domain-based subplot (Sankey)
295    let sankey_df = CsvReader::new("data/energy_transition.csv")
296        .finish()
297        .unwrap();
298
299    let sankey = SankeyDiagram::builder()
300        .data(&sankey_df)
301        .sources("source")
302        .targets("target")
303        .values("value")
304        .orientation(Orientation::Horizontal)
305        .arrangement(Arrangement::Freeform)
306        .plot_title(Text::from("Energy Flow").y(1.2))
307        .build();
308
309    // Mapbox subplot
310    let map_df = CsvReader::new("data/cities.csv").finish().unwrap();
311
312    let scatter_map = ScatterMap::builder()
313        .data(&map_df)
314        .latitude("latitude")
315        .longitude("longitude")
316        .group("city")
317        .zoom(4)
318        .center([50.0, 5.0])
319        .opacity(0.8)
320        .plot_title(Text::from("Cities (Mapbox)").y(1.2))
321        .build();
322
323    // Geo subplot
324    let geo_df = CsvReader::new("data/world_cities.csv").finish().unwrap();
325
326    let scatter_geo = ScatterGeo::builder()
327        .data(&geo_df)
328        .lat("lat")
329        .lon("lon")
330        .group("continent")
331        .mode(Mode::Markers)
332        .size(10)
333        .color(Rgb(255, 140, 0))
334        .shape(Shape::Circle)
335        .plot_title(Text::from("Global Cities (Geo)").x(0.65).y(1.2))
336        .legend(&Legend::new().x(0.8))
337        .build();
338
339    SubplotGrid::regular()
340        .plots(vec![
341            &scatter_2d,
342            &scatter_3d,
343            &polar,
344            &sankey,
345            &scatter_map,
346            &scatter_geo,
347        ])
348        .rows(2)
349        .cols(3)
350        .h_gap(0.12)
351        .v_gap(0.22)
352        .title(
353            Text::from("Mixed Subplot Grid")
354                .size(16)
355                .font("Arial Black")
356                .y(0.95),
357        )
358        .build()
359        .plot();
360}