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