Skip to main content

plotlars_core/components/
axis.rs

1use crate::components::{Rgb, TickDirection, ValueExponent};
2
3/// A structure representing a customizable axis.
4///
5/// # Example
6///
7/// ```rust
8/// use polars::prelude::*;
9/// use plotlars::{Axis, Plot, Rgb, ScatterPlot, Text, TickDirection};
10///
11/// let dataset = LazyCsvReader::new(PlRefPath::new("data/penguins.csv"))
12///     .finish()
13///     .unwrap()
14///     .select([
15///         col("species"),
16///         col("sex").alias("gender"),
17///         col("flipper_length_mm").cast(DataType::Int16),
18///         col("body_mass_g").cast(DataType::Int16),
19///     ])
20///     .collect()
21///     .unwrap();
22///
23/// let axis = Axis::new()
24///     .show_line(true)
25///     .tick_direction(TickDirection::OutSide)
26///     .value_thousands(true)
27///     .show_grid(false);
28///
29/// ScatterPlot::builder()
30///     .data(&dataset)
31///     .x("body_mass_g")
32///     .y("flipper_length_mm")
33///     .group("species")
34///     .colors(vec![
35///         Rgb(255, 0, 0),
36///         Rgb(0, 255, 0),
37///         Rgb(0, 0, 255),
38///     ])
39///     .opacity(0.5)
40///     .size(20)
41///     .plot_title(
42///         Text::from("Scatter Plot")
43///             .font("Arial")
44///             .size(20)
45///             .x(0.045)
46///     )
47///     .x_title("body mass (g)")
48///     .y_title("flipper length (mm)")
49///     .legend_title("species")
50///     .x_axis(&axis)
51///     .y_axis(&axis)
52///     .build()
53///     .plot();
54/// ```
55///
56/// ![example](https://imgur.com/P24E1ND.png)
57#[derive(Default, Clone)]
58pub struct Axis {
59    pub show_axis: Option<bool>,
60    pub axis_side: Option<AxisSide>,
61    pub axis_position: Option<f64>,
62    pub axis_type: Option<AxisType>,
63    pub value_color: Option<Rgb>,
64    pub value_range: Option<[f64; 2]>,
65    pub value_thousands: Option<bool>,
66    pub value_exponent: Option<ValueExponent>,
67    pub tick_values: Option<Vec<f64>>,
68    pub tick_labels: Option<Vec<String>>,
69    pub tick_direction: Option<TickDirection>,
70    pub tick_length: Option<usize>,
71    pub tick_width: Option<usize>,
72    pub tick_color: Option<Rgb>,
73    pub tick_angle: Option<f64>,
74    pub tick_font: Option<String>,
75    pub show_line: Option<bool>,
76    pub line_color: Option<Rgb>,
77    pub line_width: Option<usize>,
78    pub show_grid: Option<bool>,
79    pub grid_color: Option<Rgb>,
80    pub grid_width: Option<usize>,
81    pub show_zero_line: Option<bool>,
82    pub zero_line_color: Option<Rgb>,
83    pub zero_line_width: Option<usize>,
84}
85
86impl Axis {
87    /// Creates a new `Axis` instance with default values.
88    pub fn new() -> Self {
89        Self::default()
90    }
91
92    /// Sets the visibility of the axis.
93    ///
94    /// # Argument
95    ///
96    /// * `bool` - A boolean value indicating whether the axis should be visible.
97    pub fn show_axis(mut self, bool: bool) -> Self {
98        self.show_axis = Some(bool);
99        self
100    }
101
102    /// Sets the side of the axis.
103    ///
104    /// # Argument
105    ///
106    /// * `side` - An `AxisSide` enum value representing the side of the axis.
107    pub fn axis_side(mut self, side: AxisSide) -> Self {
108        self.axis_side = Some(side);
109        self
110    }
111
112    /// Sets the position of the axis.
113    ///
114    /// # Argument
115    ///
116    /// * `position` - A `f64` value representing the position of the axis.
117    pub fn axis_position(mut self, position: f64) -> Self {
118        self.axis_position = Some(position);
119        self
120    }
121
122    /// Sets the type of the axis.
123    ///
124    /// # Argument
125    ///
126    /// * `axis_type` - An `AxisType` enum value representing the type of the axis.
127    pub fn axis_type(mut self, axis_type: AxisType) -> Self {
128        self.axis_type = Some(axis_type);
129        self
130    }
131
132    /// Sets the color of the axis values.
133    ///
134    /// # Argument
135    ///
136    /// * `color` - An `Rgb` struct representing the color of the axis values.
137    pub fn value_color(mut self, color: Rgb) -> Self {
138        self.value_color = Some(color);
139        self
140    }
141
142    /// Sets the range of values displayed on the axis.
143    ///
144    /// # Arguments
145    ///
146    /// * `min` - The minimum value of the axis range.
147    /// * `max` - The maximum value of the axis range.
148    pub fn value_range(mut self, min: f64, max: f64) -> Self {
149        self.value_range = Some([min, max]);
150        self
151    }
152
153    /// Sets whether to use thousands separators for values.
154    ///
155    /// # Argument
156    ///
157    /// * `bool` - A boolean value indicating whether to use thousands separators.
158    pub fn value_thousands(mut self, bool: bool) -> Self {
159        self.value_thousands = Some(bool);
160        self
161    }
162
163    /// Sets the exponent format for values on the axis.
164    ///
165    /// # Argument
166    ///
167    /// * `exponent` - A `ValueExponent` enum value representing the exponent format.
168    pub fn value_exponent(mut self, exponent: ValueExponent) -> Self {
169        self.value_exponent = Some(exponent);
170        self
171    }
172
173    /// Sets the tick values for the axis.
174    ///
175    /// # Argument
176    ///
177    /// * `values` - A vector of `f64` values representing the tick values.
178    pub fn tick_values(mut self, values: Vec<f64>) -> Self {
179        self.tick_values = Some(values);
180        self
181    }
182
183    /// Sets the tick labels for the axis.
184    ///
185    /// # Argument
186    ///
187    /// * `labels` - A vector of values that can be converted into `String`, representing the tick labels.
188    pub fn tick_labels(mut self, labels: Vec<impl Into<String>>) -> Self {
189        self.tick_labels = Some(labels.into_iter().map(|x| x.into()).collect());
190        self
191    }
192
193    /// Sets the direction of the axis ticks.
194    ///
195    /// # Argument
196    ///
197    /// * `direction` - A `TickDirection` enum value representing the direction of the ticks.
198    pub fn tick_direction(mut self, direction: TickDirection) -> Self {
199        self.tick_direction = Some(direction);
200        self
201    }
202
203    /// Sets the length of the axis ticks.
204    ///
205    /// # Argument
206    ///
207    /// * `length` - A `usize` value representing the length of the ticks.
208    pub fn tick_length(mut self, length: usize) -> Self {
209        self.tick_length = Some(length);
210        self
211    }
212
213    /// Sets the width of the axis ticks.
214    ///
215    /// # Argument
216    ///
217    /// * `width` - A `usize` value representing the width of the ticks.
218    pub fn tick_width(mut self, width: usize) -> Self {
219        self.tick_width = Some(width);
220        self
221    }
222
223    /// Sets the color of the axis ticks.
224    ///
225    /// # Argument
226    ///
227    /// * `color` - An `Rgb` struct representing the color of the ticks.
228    pub fn tick_color(mut self, color: Rgb) -> Self {
229        self.tick_color = Some(color);
230        self
231    }
232
233    /// Sets the angle of the axis ticks.
234    ///
235    /// # Argument
236    ///
237    /// * `angle` - A `f64` value representing the angle of the ticks in degrees.
238    pub fn tick_angle(mut self, angle: f64) -> Self {
239        self.tick_angle = Some(angle);
240        self
241    }
242
243    /// Sets the font of the axis tick labels.
244    ///
245    /// # Argument
246    ///
247    /// * `font` - A value that can be converted into a `String`, representing the font name for the tick labels.
248    pub fn tick_font(mut self, font: impl Into<String>) -> Self {
249        self.tick_font = Some(font.into());
250        self
251    }
252
253    /// Sets whether to show the axis line.
254    ///
255    /// # Argument
256    ///
257    /// * `bool` - A boolean value indicating whether the axis line should be visible.
258    pub fn show_line(mut self, bool: bool) -> Self {
259        self.show_line = Some(bool);
260        self
261    }
262
263    /// Sets the color of the axis line.
264    ///
265    /// # Argument
266    ///
267    /// * `color` - An `Rgb` struct representing the color of the axis line.
268    pub fn line_color(mut self, color: Rgb) -> Self {
269        self.line_color = Some(color);
270        self
271    }
272
273    /// Sets the width of the axis line.
274    ///
275    /// # Argument
276    ///
277    /// * `width` - A `usize` value representing the width of the axis line.
278    pub fn line_width(mut self, width: usize) -> Self {
279        self.line_width = Some(width);
280        self
281    }
282
283    /// Sets whether to show the grid lines on the axis.
284    ///
285    /// # Argument
286    ///
287    /// * `bool` - A boolean value indicating whether the grid lines should be visible.
288    pub fn show_grid(mut self, bool: bool) -> Self {
289        self.show_grid = Some(bool);
290        self
291    }
292
293    /// Sets the color of the grid lines on the axis.
294    ///
295    /// # Argument
296    ///
297    /// * `color` - An `Rgb` struct representing the color of the grid lines.
298    pub fn grid_color(mut self, color: Rgb) -> Self {
299        self.grid_color = Some(color);
300        self
301    }
302
303    /// Sets the width of the grid lines on the axis.
304    ///
305    /// # Argument
306    ///
307    /// * `width` - A `usize` value representing the width of the grid lines.
308    pub fn grid_width(mut self, width: usize) -> Self {
309        self.grid_width = Some(width);
310        self
311    }
312
313    /// Sets whether to show the zero line on the axis.
314    ///
315    /// # Argument
316    ///
317    /// * `bool` - A boolean value indicating whether the zero line should be visible.
318    pub fn show_zero_line(mut self, bool: bool) -> Self {
319        self.show_zero_line = Some(bool);
320        self
321    }
322
323    /// Sets the color of the zero line on the axis.
324    ///
325    /// # Argument
326    ///
327    /// * `color` - An `Rgb` struct representing the color of the zero line.
328    pub fn zero_line_color(mut self, color: Rgb) -> Self {
329        self.zero_line_color = Some(color);
330        self
331    }
332
333    /// Sets the width of the zero line on the axis.
334    ///
335    /// # Argument
336    ///
337    /// * `width` - A `usize` value representing the width of the zero line.
338    pub fn zero_line_width(mut self, width: usize) -> Self {
339        self.zero_line_width = Some(width);
340        self
341    }
342}
343
344#[cfg(test)]
345mod tests {
346    use super::*;
347
348    #[test]
349    fn test_new_all_none() {
350        let a = Axis::new();
351        assert!(a.show_axis.is_none());
352        assert!(a.show_grid.is_none());
353        assert!(a.value_range.is_none());
354        assert!(a.tick_labels.is_none());
355        assert!(a.show_line.is_none());
356    }
357
358    #[test]
359    fn test_show_axis() {
360        let a = Axis::new().show_axis(true);
361        assert_eq!(a.show_axis, Some(true));
362        assert!(a.show_grid.is_none());
363        assert!(a.value_range.is_none());
364    }
365
366    #[test]
367    fn test_show_grid() {
368        let a = Axis::new().show_grid(false);
369        assert_eq!(a.show_grid, Some(false));
370        assert!(a.show_axis.is_none());
371    }
372
373    #[test]
374    fn test_value_range() {
375        let a = Axis::new().value_range(0.0, 100.0);
376        let range = a.value_range.unwrap();
377        assert!((range[0] - 0.0).abs() < 1e-6);
378        assert!((range[1] - 100.0).abs() < 1e-6);
379    }
380
381    #[test]
382    fn test_tick_labels_converts() {
383        let a = Axis::new().tick_labels(vec!["a", "b"]);
384        let labels = a.tick_labels.unwrap();
385        assert_eq!(labels, vec!["a".to_string(), "b".to_string()]);
386    }
387
388    #[test]
389    fn test_builder_chaining() {
390        let a = Axis::new()
391            .show_axis(true)
392            .show_grid(false)
393            .show_line(true)
394            .value_range(1.0, 50.0)
395            .tick_labels(vec!["x", "y", "z"]);
396        assert_eq!(a.show_axis, Some(true));
397        assert_eq!(a.show_grid, Some(false));
398        assert_eq!(a.show_line, Some(true));
399        assert_eq!(a.value_range.unwrap().len(), 2);
400        assert_eq!(a.tick_labels.unwrap().len(), 3);
401    }
402}
403
404/// Enumeration representing the position of the axis.
405///
406/// # Example
407///
408/// ```rust
409/// use polars::prelude::*;
410/// use plotlars::{Axis, AxisSide, Legend, Line, Plot, Rgb, Shape, Text, TimeSeriesPlot};
411///
412/// let dataset = LazyCsvReader::new(PlRefPath::new("data/revenue_and_cost.csv"))
413///     .finish()
414///     .unwrap()
415///     .select([
416///         col("Date").cast(DataType::String),
417///         col("Revenue").cast(DataType::Int32),
418///         col("Cost").cast(DataType::Int32),
419///     ])
420///     .collect()
421///     .unwrap();
422///
423/// TimeSeriesPlot::builder()
424///     .data(&dataset)
425///     .x("Date")
426///     .y("Revenue")
427///     .additional_series(vec!["Cost"])
428///     .size(8)
429///     .colors(vec![Rgb(255, 0, 0), Rgb(0, 255, 0)])
430///     .lines(vec![Line::Dash, Line::Solid])
431///     .shapes(vec![Shape::Circle, Shape::Square])
432///     .plot_title(
433///         Text::from("Time Series Plot")
434///             .font("Arial")
435///             .size(18)
436///     )
437///     .y_axis(
438///         &Axis::new()
439///             .axis_side(AxisSide::Right)
440///     )
441///     .legend(
442///         &Legend::new()
443///             .x(0.05)
444///             .y(0.9)
445///     )
446///     .build()
447///     .plot();
448/// ```
449///
450/// ![Example](https://imgur.com/Ok0c5R5.png)
451#[derive(Clone)]
452pub enum AxisSide {
453    Top,
454    Bottom,
455    Left,
456    Right,
457}
458
459/// Enumeration representing the type of the axis.
460///
461/// # Example
462///
463/// ```rust
464/// use polars::prelude::*;
465/// use plotlars::{Axis, AxisType, LinePlot, Plot};
466///
467/// let linear_values = vec![
468///     1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
469///     20, 30, 40, 50, 60, 70, 80, 90, 100,
470///     200, 300, 400, 500, 600, 700, 800, 900, 1000
471/// ];
472///
473/// let logarithms = vec![
474///     0.0000, 0.3010, 0.4771, 0.6021, 0.6990,
475///     0.7782, 0.8451, 0.9031, 0.9542, 1.0000,
476///     1.3010, 1.4771, 1.6021, 1.6990, 1.7782,
477///     1.8451, 1.9031, 1.9542, 2.0000,
478///     2.3010, 2.4771, 2.6021, 2.6990,
479///     2.7782, 2.8451, 2.9031, 2.9542, 3.0000
480/// ];
481///
482/// let dataset = DataFrame::new(linear_values.len(), vec![
483///     Column::new("linear_values".into(), linear_values),
484///     Column::new("logarithms".into(), logarithms),
485/// ]).unwrap();
486///
487/// let axis = Axis::new()
488///     .axis_type(AxisType::Log)
489///     .show_line(true);
490///
491/// LinePlot::builder()
492///     .data(&dataset)
493///     .x("linear_values")
494///     .y("logarithms")
495///     .y_title("log₁₀ x")
496///     .x_title("x")
497///     .y_axis(&axis)
498///     .x_axis(&axis)
499///     .build()
500///     .plot();
501/// ```
502///
503/// ![Example](https://imgur.com/rjNNO5q.png)
504#[derive(Clone)]
505pub enum AxisType {
506    Default,
507    Linear,
508    Log,
509    Date,
510    Category,
511    MultiCategory,
512}