embedded_charts/grid/
mod.rs

1//! Grid system for embedded graphics charts.
2//!
3//! This module provides a comprehensive grid system that integrates with the chart
4//! and axis systems to provide professional-looking grid lines for data visualization.
5
6pub mod builder;
7pub mod style;
8pub mod traits;
9pub mod types;
10
11#[cfg(all(feature = "no_std", not(feature = "std")))]
12extern crate alloc;
13
14#[cfg(all(feature = "no_std", not(feature = "std")))]
15use alloc::boxed::Box;
16
17#[cfg(not(all(feature = "no_std", not(feature = "std"))))]
18use std::boxed::Box;
19
20// Re-export main types
21pub use builder::{CustomGridBuilder, GridBuilder, LinearGridBuilder, TickBasedGridBuilder};
22pub use style::{GridLineStyle, GridStyle, GridVisibility, MajorGridStyle, MinorGridStyle};
23pub use traits::{DefaultGridRenderer, Grid, GridConfiguration, GridOrientation, GridRenderer};
24pub use types::{CustomGrid, GridSpacing, GridType, LinearGrid, TickBasedGrid};
25
26pub use traits::TickAlignedGrid;
27
28use crate::axes::traits::TickGenerator;
29use crate::error::{ChartError, ChartResult};
30use embedded_graphics::{
31    prelude::*,
32    primitives::{Line, PrimitiveStyle, Rectangle},
33};
34
35/// Main grid renderer that coordinates different grid types
36#[derive(Debug)]
37pub struct GridSystem<C: PixelColor> {
38    /// Horizontal grid configuration
39    pub horizontal: Option<GridContainer<C>>,
40    /// Vertical grid configuration
41    pub vertical: Option<GridContainer<C>>,
42    /// Overall grid style
43    pub style: GridStyle<C>,
44    /// Whether the grid is enabled
45    pub enabled: bool,
46}
47
48/// Container for different grid types
49#[derive(Debug)]
50pub enum GridContainer<C: PixelColor> {
51    /// Linear grid
52    Linear(LinearGrid<C>),
53    /// Tick-based grid for f32 values
54    TickBasedF32(TickBasedGrid<f32, C>),
55    /// Tick-based grid for i32 values
56    TickBasedI32(TickBasedGrid<i32, C>),
57    /// Custom grid
58    Custom(Box<CustomGrid<C>>),
59}
60
61impl<C: PixelColor + 'static> GridContainer<C> {
62    /// Draw the grid
63    pub fn draw<D>(&self, viewport: Rectangle, target: &mut D) -> ChartResult<()>
64    where
65        D: DrawTarget<Color = C>,
66    {
67        match self {
68            GridContainer::Linear(grid) => grid.draw(viewport, target),
69            GridContainer::TickBasedF32(grid) => grid.draw(viewport, target),
70            GridContainer::TickBasedI32(grid) => grid.draw(viewport, target),
71            GridContainer::Custom(grid) => grid.draw(viewport, target),
72        }
73    }
74
75    /// Get grid orientation
76    pub fn orientation(&self) -> traits::GridOrientation {
77        match self {
78            GridContainer::Linear(grid) => grid.orientation(),
79            GridContainer::TickBasedF32(grid) => grid.orientation(),
80            GridContainer::TickBasedI32(grid) => grid.orientation(),
81            GridContainer::Custom(grid) => grid.orientation(),
82        }
83    }
84
85    /// Check if grid is visible
86    pub fn is_visible(&self) -> bool {
87        match self {
88            GridContainer::Linear(grid) => grid.is_visible(),
89            GridContainer::TickBasedF32(grid) => grid.is_visible(),
90            GridContainer::TickBasedI32(grid) => grid.is_visible(),
91            GridContainer::Custom(grid) => grid.is_visible(),
92        }
93    }
94}
95
96impl<C: PixelColor + 'static> GridSystem<C>
97where
98    C: From<embedded_graphics::pixelcolor::Rgb565>,
99{
100    /// Create a new empty grid system
101    pub fn new() -> Self {
102        Self {
103            horizontal: None,
104            vertical: None,
105            style: GridStyle::default(),
106            enabled: true,
107        }
108    }
109
110    /// Create a builder for configuring the grid system
111    pub fn builder() -> GridBuilder<C> {
112        GridBuilder::new()
113    }
114
115    /// Set the horizontal grid
116    pub fn set_horizontal_grid(&mut self, grid: GridContainer<C>) {
117        self.horizontal = Some(grid);
118    }
119
120    /// Set the vertical grid
121    pub fn set_vertical_grid(&mut self, grid: GridContainer<C>) {
122        self.vertical = Some(grid);
123    }
124
125    /// Enable or disable the grid
126    pub fn set_enabled(&mut self, enabled: bool) {
127        self.enabled = enabled;
128    }
129
130    /// Check if the grid is enabled
131    pub fn is_enabled(&self) -> bool {
132        self.enabled
133    }
134
135    /// Draw the grid to the target
136    pub fn draw<D>(&self, viewport: Rectangle, target: &mut D) -> ChartResult<()>
137    where
138        D: DrawTarget<Color = C>,
139    {
140        if !self.enabled {
141            return Ok(());
142        }
143
144        // Draw horizontal grid lines
145        if let Some(ref horizontal_grid) = self.horizontal {
146            horizontal_grid.draw(viewport, target)?;
147        }
148
149        // Draw vertical grid lines
150        if let Some(ref vertical_grid) = self.vertical {
151            vertical_grid.draw(viewport, target)?;
152        }
153
154        Ok(())
155    }
156
157    /// Draw grid lines that align with axis ticks
158    pub fn draw_with_axes<T, D, XA, YA>(
159        &self,
160        viewport: Rectangle,
161        x_axis: Option<&XA>,
162        y_axis: Option<&YA>,
163        target: &mut D,
164    ) -> ChartResult<()>
165    where
166        T: crate::axes::traits::AxisValue,
167        D: DrawTarget<Color = C>,
168        XA: crate::axes::traits::Axis<T, C>,
169        YA: crate::axes::traits::Axis<T, C>,
170    {
171        if !self.enabled {
172            return Ok(());
173        }
174
175        // Draw grid lines aligned with axis ticks
176        if let Some(x_axis) = x_axis {
177            // Draw vertical grid lines at X-axis tick positions
178            let ticks = TickGenerator::generate_ticks(
179                x_axis.tick_generator(),
180                x_axis.min(),
181                x_axis.max(),
182                10, // max ticks
183            );
184
185            for tick in &ticks {
186                let x_pos = x_axis.transform_value(tick.value, viewport);
187                if x_pos >= viewport.top_left.x
188                    && x_pos <= viewport.top_left.x + viewport.size.width as i32
189                {
190                    let start = Point::new(x_pos, viewport.top_left.y);
191                    let end = Point::new(x_pos, viewport.top_left.y + viewport.size.height as i32);
192
193                    Line::new(start, end)
194                        .into_styled(PrimitiveStyle::with_stroke(
195                            self.style.major.line.line_style.color,
196                            self.style.major.line.line_style.width,
197                        ))
198                        .draw(target)
199                        .map_err(|_| ChartError::RenderingError)?;
200                }
201            }
202        }
203
204        if let Some(y_axis) = y_axis {
205            // Draw horizontal grid lines at Y-axis tick positions
206            let ticks = TickGenerator::generate_ticks(
207                y_axis.tick_generator(),
208                y_axis.min(),
209                y_axis.max(),
210                10, // max ticks
211            );
212
213            for tick in &ticks {
214                let y_pos = y_axis.transform_value(tick.value, viewport);
215                if y_pos >= viewport.top_left.y
216                    && y_pos <= viewport.top_left.y + viewport.size.height as i32
217                {
218                    let start = Point::new(viewport.top_left.x, y_pos);
219                    let end = Point::new(viewport.top_left.x + viewport.size.width as i32, y_pos);
220
221                    Line::new(start, end)
222                        .into_styled(PrimitiveStyle::with_stroke(
223                            self.style.major.line.line_style.color,
224                            self.style.major.line.line_style.width,
225                        ))
226                        .draw(target)
227                        .map_err(|_| ChartError::RenderingError)?;
228                }
229            }
230        }
231
232        Ok(())
233    }
234}
235
236impl<C: PixelColor + 'static> Default for GridSystem<C>
237where
238    C: From<embedded_graphics::pixelcolor::Rgb565>,
239{
240    fn default() -> Self {
241        Self::new()
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248    use embedded_graphics::pixelcolor::Rgb565;
249
250    #[test]
251    fn test_grid_system_creation() {
252        let grid: GridSystem<Rgb565> = GridSystem::new();
253        assert!(grid.is_enabled());
254        assert!(grid.horizontal.is_none());
255        assert!(grid.vertical.is_none());
256    }
257
258    #[test]
259    fn test_grid_system_enable_disable() {
260        let mut grid: GridSystem<Rgb565> = GridSystem::new();
261        assert!(grid.is_enabled());
262
263        grid.set_enabled(false);
264        assert!(!grid.is_enabled());
265
266        grid.set_enabled(true);
267        assert!(grid.is_enabled());
268    }
269}