Skip to main content

plotlars_core/components/
dimensions.rs

1/// A structure representing plot dimensions and sizing behavior.
2///
3/// The `Dimensions` struct allows customization of plot size including width, height,
4/// and auto-sizing behavior. It is particularly useful when creating subplot grids or
5/// when you need precise control over plot dimensions.
6///
7/// # Example
8///
9/// ```rust
10/// use plotlars::{
11///     Axis, BarPlot, BoxPlot, Dimensions, Legend, Line, Orientation, Plot, Rgb, ScatterPlot, Shape,
12///     SubplotGrid, Text, TickDirection, TimeSeriesPlot,
13/// };
14/// use polars::prelude::*;
15///
16/// let penguins_dataset = LazyCsvReader::new(PlRefPath::new("data/penguins.csv"))
17///     .finish()
18///     .unwrap()
19///     .select([
20///         col("species"),
21///         col("sex").alias("gender"),
22///         col("flipper_length_mm").cast(DataType::Int16),
23///         col("body_mass_g").cast(DataType::Int16),
24///     ])
25///     .collect()
26///     .unwrap();
27///
28/// let temperature_dataset = LazyCsvReader::new(PlRefPath::new("data/debilt_2023_temps.csv"))
29///     .with_has_header(true)
30///     .with_try_parse_dates(true)
31///     .finish()
32///     .unwrap()
33///     .with_columns(vec![
34///         (col("tavg") / lit(10)).alias("tavg"),
35///         (col("tmin") / lit(10)).alias("tmin"),
36///         (col("tmax") / lit(10)).alias("tmax"),
37///     ])
38///     .collect()
39///     .unwrap();
40///
41/// let animals_dataset = LazyCsvReader::new(PlRefPath::new("data/animal_statistics.csv"))
42///     .finish()
43///     .unwrap()
44///     .collect()
45///     .unwrap();
46///
47/// let axis = Axis::new()
48///     .show_line(true)
49///     .tick_direction(TickDirection::OutSide)
50///     .value_thousands(true);
51///
52/// let plot1 = TimeSeriesPlot::builder()
53///     .data(&temperature_dataset)
54///     .x("date")
55///     .y("tavg")
56///     .additional_series(vec!["tmin", "tmax"])
57///     .colors(vec![Rgb(128, 128, 128), Rgb(0, 122, 255), Rgb(255, 128, 0)])
58///     .lines(vec![Line::Solid, Line::Dot, Line::Dot])
59///     .plot_title(
60///         Text::from("De Bilt Temperature 2023")
61///             .font("Arial Black")
62///             .size(16),
63///     )
64///     .y_title(Text::from("temperature (°C)").size(13).x(-0.08))
65///     .legend(&Legend::new().x(0.1).y(0.9))
66///     .build();
67///
68/// let plot2 = ScatterPlot::builder()
69///     .data(&penguins_dataset)
70///     .x("body_mass_g")
71///     .y("flipper_length_mm")
72///     .group("species")
73///     .sort_groups_by(|a, b| {
74///         if a.len() == b.len() {
75///             a.cmp(b)
76///         } else {
77///             a.len().cmp(&b.len())
78///         }
79///     })
80///     .opacity(0.6)
81///     .size(10)
82///     .colors(vec![Rgb(178, 34, 34), Rgb(65, 105, 225), Rgb(255, 140, 0)])
83///     .shapes(vec![Shape::Circle, Shape::Square, Shape::Diamond])
84///     .plot_title(Text::from("Penguin Morphology").font("Arial Black").size(16))
85///     .x_title(Text::from("body mass (g)").size(13))
86///     .y_title(Text::from("flipper length (mm)").size(13).x(-0.11))
87///     .legend_title(Text::from("Species").size(12))
88///     .x_axis(&axis.clone().value_range(2500.0, 6500.0))
89///     .y_axis(&axis.clone().value_range(170.0, 240.0))
90///     .legend(&Legend::new().x(0.85).y(0.4))
91///     .build();
92///
93/// let plot3 = BarPlot::builder()
94///     .data(&animals_dataset)
95///     .labels("animal")
96///     .values("value")
97///     .orientation(Orientation::Vertical)
98///     .group("gender")
99///     .sort_groups_by(|a, b| a.len().cmp(&b.len()))
100///     .error("error")
101///     .colors(vec![Rgb(255, 127, 80), Rgb(64, 224, 208)])
102///     .plot_title(Text::from("Animal Statistics").font("Arial Black").size(16))
103///     .x_title(Text::from("animal").size(13))
104///     .y_title(Text::from("value").size(13))
105///     .legend_title(Text::from("Gender").size(12))
106///     .legend(
107///         &Legend::new()
108///             .orientation(Orientation::Horizontal)
109///             .x(0.35)
110///             .y(0.9),
111///     )
112///     .build();
113///
114/// let plot4 = BoxPlot::builder()
115///     .data(&penguins_dataset)
116///     .labels("species")
117///     .values("body_mass_g")
118///     .orientation(Orientation::Vertical)
119///     .group("gender")
120///     .box_points(true)
121///     .point_offset(-1.5)
122///     .jitter(0.01)
123///     .opacity(0.15)
124///     .colors(vec![Rgb(0, 191, 255), Rgb(57, 255, 20), Rgb(255, 105, 180)])
125///     .plot_title(
126///         Text::from("Body Mass Distribution")
127///             .font("Arial Black")
128///             .size(16),
129///     )
130///     .x_title(Text::from("species").size(13))
131///     .y_title(Text::from("body mass (g)").size(13).x(-0.12))
132///     .legend_title(Text::from("Gender").size(12))
133///     .y_axis(&Axis::new().value_thousands(true))
134///     .legend(&Legend::new().x(0.85).y(0.9))
135///     .build();
136///
137/// let dimensions = Dimensions::new().width(1400).height(850).auto_size(false);
138///
139/// SubplotGrid::regular()
140///     .plots(vec![&plot1, &plot2, &plot3, &plot4])
141///     .rows(2)
142///     .cols(2)
143///     .v_gap(0.3)
144///     .h_gap(0.2)
145///     .dimensions(&dimensions)
146///     .title(
147///         Text::from("Scientific Data Visualization Dashboard")
148///             .size(26)
149///             .font("Arial Black"),
150///     )
151///     .build()
152///     .plot();
153/// ```
154///
155/// ![Example](https://imgur.com/hxwInxB.png)
156#[derive(Clone, Default)]
157pub struct Dimensions {
158    pub width: Option<usize>,
159    pub height: Option<usize>,
160    pub auto_size: Option<bool>,
161}
162
163impl Dimensions {
164    /// Creates a new `Dimensions` instance with default values.
165    pub fn new() -> Self {
166        Self::default()
167    }
168
169    /// Sets the width of the plot in pixels.
170    pub fn width(mut self, width: usize) -> Self {
171        self.width = Some(width);
172        self
173    }
174
175    /// Sets the height of the plot in pixels.
176    pub fn height(mut self, height: usize) -> Self {
177        self.height = Some(height);
178        self
179    }
180
181    /// Sets whether the plot should automatically resize.
182    pub fn auto_size(mut self, auto_size: bool) -> Self {
183        self.auto_size = Some(auto_size);
184        self
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn test_default() {
194        let dims = Dimensions::new();
195        assert!(dims.width.is_none());
196        assert!(dims.height.is_none());
197        assert!(dims.auto_size.is_none());
198    }
199
200    #[test]
201    fn test_width() {
202        let dims = Dimensions::new().width(800);
203        assert_eq!(dims.width, Some(800));
204    }
205
206    #[test]
207    fn test_height() {
208        let dims = Dimensions::new().height(600);
209        assert_eq!(dims.height, Some(600));
210    }
211
212    #[test]
213    fn test_auto_size() {
214        let dims = Dimensions::new().auto_size(true);
215        assert_eq!(dims.auto_size, Some(true));
216    }
217}