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/// 
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/// 
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/// 
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}