plotlars/components/
axis.rs

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