Skip to main content

plotlars_core/components/
legend.rs

1use crate::components::{Orientation, Rgb};
2
3/// A structure representing a customizable plot legend.
4///
5/// # Example
6///
7/// ```rust
8/// use polars::prelude::*;
9/// use plotlars::{Histogram, Legend, Orientation, Plot, Rgb};
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 legend = Legend::new()
24///     .orientation(Orientation::Horizontal)
25///     .border_width(1)
26///     .x(0.78)
27///     .y(0.825);
28///
29/// Histogram::builder()
30///     .data(&dataset)
31///     .x("body_mass_g")
32///     .group("species")
33///     .colors(vec![
34///         Rgb(255, 0, 0),
35///         Rgb(0, 255, 0),
36///         Rgb(0, 0, 255),
37///     ])
38///     .opacity(0.5)
39///     .x_title("Body Mass (g)")
40///     .y_title("Frequency")
41///     .legend_title("Species")
42///     .legend(&legend)
43///     .build()
44///     .plot();
45/// ```
46///
47/// ![example](https://imgur.com/GpUsgli.png)
48#[derive(Clone)]
49pub struct Legend {
50    pub background_color: Option<Rgb>,
51    pub border_color: Option<Rgb>,
52    pub border_width: Option<usize>,
53    pub font: Option<String>,
54    pub orientation: Option<Orientation>,
55    pub x: Option<f64>,
56    pub y: Option<f64>,
57}
58
59impl Default for Legend {
60    fn default() -> Self {
61        Self {
62            background_color: Some(Rgb(255, 255, 255)),
63            border_color: None,
64            border_width: None,
65            font: None,
66            orientation: None,
67            x: None,
68            y: None,
69        }
70    }
71}
72
73impl Legend {
74    /// Creates a new `Legend` instance with default values.
75    pub fn new() -> Self {
76        Self::default()
77    }
78
79    /// Sets the background color of the legend.
80    ///
81    /// # Argument
82    ///
83    /// * `color` - An `Rgb` struct representing the background color.
84    pub fn background_color(mut self, color: Rgb) -> Self {
85        self.background_color = Some(color);
86        self
87    }
88
89    /// Sets the border color of the legend.
90    ///
91    /// # Argument
92    ///
93    /// * `color` - An `Rgb` struct representing the border color.
94    pub fn border_color(mut self, color: Rgb) -> Self {
95        self.border_color = Some(color);
96        self
97    }
98
99    /// Sets the border width of the legend.
100    ///
101    /// # Argument
102    ///
103    /// * `width` - A `usize` value representing the width of the border.
104    pub fn border_width(mut self, width: usize) -> Self {
105        self.border_width = Some(width);
106        self
107    }
108
109    /// Sets the font of the legend labels.
110    ///
111    /// # Argument
112    ///
113    /// * `font` - A value that can be converted into a `String`, representing the font name for the labels.
114    pub fn font(mut self, font: impl Into<String>) -> Self {
115        self.font = Some(font.into());
116        self
117    }
118
119    /// Sets the orientation of the legend.
120    ///
121    /// # Argument
122    ///
123    /// * `orientation` - An `Orientation` enum value representing the layout direction of the legend.
124    pub fn orientation(mut self, orientation: Orientation) -> Self {
125        self.orientation = Some(orientation);
126        self
127    }
128
129    /// Sets the horizontal position of the legend.
130    ///
131    /// # Argument
132    ///
133    /// * `x` - A `f64` value representing the horizontal position of the legend.
134    pub fn x(mut self, x: f64) -> Self {
135        self.x = Some(x);
136        self
137    }
138
139    /// Sets the vertical position of the legend.
140    ///
141    /// # Argument
142    ///
143    /// * `y` - A `f64` value representing the vertical position of the legend.
144    pub fn y(mut self, y: f64) -> Self {
145        self.y = Some(y);
146        self
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_default() {
156        let legend = Legend::new();
157        let bg = legend.background_color.unwrap();
158        assert_eq!(bg.0, 255);
159        assert_eq!(bg.1, 255);
160        assert_eq!(bg.2, 255);
161        assert!(legend.border_color.is_none());
162        assert!(legend.border_width.is_none());
163        assert!(legend.font.is_none());
164        assert!(legend.orientation.is_none());
165        assert!(legend.x.is_none());
166        assert!(legend.y.is_none());
167    }
168
169    #[test]
170    fn test_background_color() {
171        let legend = Legend::new().background_color(Rgb(200, 200, 200));
172        let bg = legend.background_color.unwrap();
173        assert_eq!(bg.0, 200);
174        assert_eq!(bg.1, 200);
175        assert_eq!(bg.2, 200);
176    }
177
178    #[test]
179    fn test_border_color() {
180        let legend = Legend::new().border_color(Rgb(0, 0, 0));
181        let bc = legend.border_color.unwrap();
182        assert_eq!(bc.0, 0);
183        assert_eq!(bc.1, 0);
184        assert_eq!(bc.2, 0);
185    }
186
187    #[test]
188    fn test_border_width() {
189        let legend = Legend::new().border_width(2);
190        assert_eq!(legend.border_width, Some(2));
191    }
192
193    #[test]
194    fn test_orientation() {
195        let legend = Legend::new().orientation(Orientation::Horizontal);
196        assert!(legend.orientation.is_some());
197    }
198
199    #[test]
200    fn test_builder_chaining() {
201        let legend = Legend::new()
202            .background_color(Rgb(100, 100, 100))
203            .border_color(Rgb(50, 50, 50))
204            .border_width(3)
205            .font("Arial")
206            .orientation(Orientation::Vertical)
207            .x(0.5)
208            .y(0.8);
209
210        let bg = legend.background_color.unwrap();
211        assert_eq!(bg.0, 100);
212        let bc = legend.border_color.unwrap();
213        assert_eq!(bc.0, 50);
214        assert_eq!(legend.border_width, Some(3));
215        assert_eq!(legend.font, Some("Arial".to_string()));
216        assert!(legend.orientation.is_some());
217        assert!((legend.x.unwrap() - 0.5).abs() < 1e-6);
218        assert!((legend.y.unwrap() - 0.8).abs() < 1e-6);
219    }
220}