plotlars/components/
axis.rs

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