embedded_charts/axes/
style.rs

1//! Styling configuration for axes.
2
3use crate::style::LineStyle;
4use embedded_graphics::prelude::*;
5
6/// Style configuration for an axis
7#[derive(Debug, Clone)]
8pub struct AxisStyle<C: PixelColor> {
9    /// Style for the main axis line
10    pub axis_line: LineStyle<C>,
11    /// Style for major tick marks
12    pub major_ticks: TickStyle<C>,
13    /// Style for minor tick marks
14    pub minor_ticks: TickStyle<C>,
15    /// Style for grid lines
16    pub grid_lines: Option<LineStyle<C>>,
17    /// Style for axis labels
18    pub labels: LabelStyle<C>,
19    /// Spacing between the axis and labels
20    pub label_offset: u32,
21}
22
23/// Style configuration for tick marks
24#[derive(Debug, Clone)]
25pub struct TickStyle<C: PixelColor> {
26    /// Line style for the tick mark
27    pub line: LineStyle<C>,
28    /// Length of the tick mark in pixels
29    pub length: u32,
30    /// Whether to show this type of tick
31    pub visible: bool,
32}
33
34/// Style configuration for axis labels
35#[derive(Debug, Clone)]
36pub struct LabelStyle<C: PixelColor> {
37    /// Text color
38    pub color: C,
39    /// Font size (if supported by the font system)
40    pub font_size: u32,
41    /// Whether to show labels
42    pub visible: bool,
43    /// Text alignment relative to tick position
44    pub alignment: TextAlignment,
45    /// Rotation angle in degrees (0, 90, 180, 270)
46    pub rotation: u16,
47}
48
49/// Text alignment options for labels
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum TextAlignment {
52    /// Align text to the left/top
53    Start,
54    /// Center text
55    Center,
56    /// Align text to the right/bottom
57    End,
58}
59
60impl<C: PixelColor> AxisStyle<C>
61where
62    C: From<embedded_graphics::pixelcolor::Rgb565>,
63{
64    /// Create a new axis style with default values
65    pub fn new() -> Self {
66        Self {
67            axis_line: LineStyle::solid(embedded_graphics::pixelcolor::Rgb565::RED.into()).width(3),
68            major_ticks: TickStyle::new(embedded_graphics::pixelcolor::Rgb565::RED.into(), 10),
69            minor_ticks: TickStyle::new(embedded_graphics::pixelcolor::Rgb565::BLUE.into(), 5),
70            grid_lines: None,
71            labels: LabelStyle::new(embedded_graphics::pixelcolor::Rgb565::BLACK.into()),
72            label_offset: 8,
73        }
74    }
75
76    /// Set the axis line style
77    pub fn with_axis_line(mut self, style: LineStyle<C>) -> Self {
78        self.axis_line = style;
79        self
80    }
81
82    /// Set the major tick style
83    pub fn with_major_ticks(mut self, style: TickStyle<C>) -> Self {
84        self.major_ticks = style;
85        self
86    }
87
88    /// Set the minor tick style
89    pub fn with_minor_ticks(mut self, style: TickStyle<C>) -> Self {
90        self.minor_ticks = style;
91        self
92    }
93
94    /// Enable grid lines with the specified style
95    pub fn with_grid_lines(mut self, style: LineStyle<C>) -> Self {
96        self.grid_lines = Some(style);
97        self
98    }
99
100    /// Disable grid lines
101    pub fn without_grid_lines(mut self) -> Self {
102        self.grid_lines = None;
103        self
104    }
105
106    /// Set the label style
107    pub fn with_labels(mut self, style: LabelStyle<C>) -> Self {
108        self.labels = style;
109        self
110    }
111
112    /// Set the label offset
113    pub fn with_label_offset(mut self, offset: u32) -> Self {
114        self.label_offset = offset;
115        self
116    }
117
118    /// Create a minimal style for small displays
119    pub fn minimal() -> Self {
120        Self {
121            axis_line: LineStyle::solid(embedded_graphics::pixelcolor::Rgb565::BLACK.into()),
122            major_ticks: TickStyle::new(embedded_graphics::pixelcolor::Rgb565::BLACK.into(), 3),
123            minor_ticks: TickStyle::new(embedded_graphics::pixelcolor::Rgb565::BLACK.into(), 1)
124                .hidden(),
125            grid_lines: None,
126            labels: LabelStyle::new(embedded_graphics::pixelcolor::Rgb565::BLACK.into())
127                .with_font_size(8),
128            label_offset: 4,
129        }
130    }
131
132    /// Create a professional style
133    pub fn professional() -> Self {
134        Self {
135            axis_line: LineStyle::solid(embedded_graphics::pixelcolor::Rgb565::BLACK.into()),
136            major_ticks: TickStyle::new(embedded_graphics::pixelcolor::Rgb565::BLACK.into(), 8),
137            minor_ticks: TickStyle::new(
138                embedded_graphics::pixelcolor::Rgb565::new(16, 32, 16).into(), // Gray
139                4,                                                             // Normal length
140            )
141            .with_width(1), // Normal width
142            grid_lines: Some(LineStyle::solid(
143                embedded_graphics::pixelcolor::Rgb565::new(25, 50, 25).into(),
144            )),
145            labels: LabelStyle::new(embedded_graphics::pixelcolor::Rgb565::BLACK.into()),
146            label_offset: 10,
147        }
148    }
149}
150
151impl<C: PixelColor> TickStyle<C> {
152    /// Create a new tick style
153    pub fn new(color: C, length: u32) -> Self {
154        Self {
155            line: LineStyle::solid(color),
156            length,
157            visible: true,
158        }
159    }
160
161    /// Set the tick color
162    pub fn with_color(mut self, color: C) -> Self {
163        self.line = self.line.color(color);
164        self
165    }
166
167    /// Set the tick width
168    pub fn with_width(mut self, width: u32) -> Self {
169        self.line = self.line.width(width);
170        self
171    }
172
173    /// Set the tick length
174    pub fn with_length(mut self, length: u32) -> Self {
175        self.length = length;
176        self
177    }
178
179    /// Hide this type of tick
180    pub fn hidden(mut self) -> Self {
181        self.visible = false;
182        self
183    }
184
185    /// Show this type of tick
186    pub fn visible(mut self) -> Self {
187        self.visible = true;
188        self
189    }
190}
191
192impl<C: PixelColor> LabelStyle<C> {
193    /// Create a new label style
194    pub fn new(color: C) -> Self {
195        Self {
196            color,
197            font_size: 12,
198            visible: true,
199            alignment: TextAlignment::Center,
200            rotation: 0,
201        }
202    }
203
204    /// Set the label color
205    pub fn with_color(mut self, color: C) -> Self {
206        self.color = color;
207        self
208    }
209
210    /// Set the font size
211    pub fn with_font_size(mut self, size: u32) -> Self {
212        self.font_size = size;
213        self
214    }
215
216    /// Set the text alignment
217    pub fn with_alignment(mut self, alignment: TextAlignment) -> Self {
218        self.alignment = alignment;
219        self
220    }
221
222    /// Set the rotation angle (0, 90, 180, 270 degrees)
223    pub fn with_rotation(mut self, degrees: u16) -> Self {
224        self.rotation = match degrees {
225            0..=45 => 0,
226            46..=135 => 90,
227            136..=225 => 180,
228            226..=315 => 270,
229            _ => 0,
230        };
231        self
232    }
233
234    /// Hide labels
235    pub fn hidden(mut self) -> Self {
236        self.visible = false;
237        self
238    }
239
240    /// Show labels
241    pub fn visible(mut self) -> Self {
242        self.visible = true;
243        self
244    }
245}
246
247impl<C: PixelColor> Default for AxisStyle<C>
248where
249    C: From<embedded_graphics::pixelcolor::Rgb565>,
250{
251    fn default() -> Self {
252        Self::new()
253    }
254}
255
256impl<C: PixelColor> Default for TickStyle<C>
257where
258    C: From<embedded_graphics::pixelcolor::Rgb565>,
259{
260    fn default() -> Self {
261        Self::new(embedded_graphics::pixelcolor::Rgb565::BLACK.into(), 4)
262    }
263}
264
265impl<C: PixelColor> Default for LabelStyle<C>
266where
267    C: From<embedded_graphics::pixelcolor::Rgb565>,
268{
269    fn default() -> Self {
270        Self::new(embedded_graphics::pixelcolor::Rgb565::BLACK.into())
271    }
272}
273
274impl Default for TextAlignment {
275    fn default() -> Self {
276        Self::Center
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283    use embedded_graphics::pixelcolor::Rgb565;
284
285    #[test]
286    fn test_axis_style_creation() {
287        let style: AxisStyle<Rgb565> = AxisStyle::new();
288        assert!(style.major_ticks.visible);
289        assert!(style.minor_ticks.visible);
290        assert!(style.grid_lines.is_none());
291    }
292
293    #[test]
294    fn test_tick_style_builder() {
295        let style = TickStyle::new(Rgb565::RED, 5).with_width(2).with_length(8);
296
297        assert_eq!(style.length, 8);
298        assert!(style.visible);
299    }
300
301    #[test]
302    fn test_label_style_builder() {
303        let style = LabelStyle::new(Rgb565::BLUE)
304            .with_font_size(14)
305            .with_alignment(TextAlignment::Start)
306            .with_rotation(90);
307
308        assert_eq!(style.font_size, 14);
309        assert_eq!(style.alignment, TextAlignment::Start);
310        assert_eq!(style.rotation, 90);
311    }
312
313    #[test]
314    fn test_professional_style() {
315        let style: AxisStyle<Rgb565> = AxisStyle::professional();
316        assert!(style.grid_lines.is_some());
317        assert_eq!(style.label_offset, 10);
318    }
319
320    #[test]
321    fn test_minimal_style() {
322        let style: AxisStyle<Rgb565> = AxisStyle::minimal();
323        assert!(!style.minor_ticks.visible);
324        assert_eq!(style.label_offset, 4);
325    }
326}