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/// 
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/// 
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/// 
504#[derive(Clone)]
505pub enum AxisType {
506 Default,
507 Linear,
508 Log,
509 Date,
510 Category,
511 MultiCategory,
512}