use crate::error::ChartResult;
use embedded_graphics::{prelude::*, primitives::Rectangle};
use crate::math::{Math, NumericConversion};
pub trait Grid<C: PixelColor> {
fn draw<D>(&self, viewport: Rectangle, target: &mut D) -> ChartResult<()>
where
D: DrawTarget<Color = C>;
fn orientation(&self) -> GridOrientation;
fn is_visible(&self) -> bool;
fn set_visible(&mut self, visible: bool);
fn style(&self) -> &crate::grid::style::GridStyle<C>;
fn set_style(&mut self, style: crate::grid::style::GridStyle<C>);
fn calculate_positions(&self, viewport: Rectangle) -> heapless::Vec<i32, 64>;
fn spacing(&self) -> f32;
fn set_spacing(&mut self, spacing: f32);
fn as_any(&self) -> &dyn core::any::Any;
}
pub trait GridRenderer<C: PixelColor> {
fn draw_major_line<D>(
&self,
start: Point,
end: Point,
style: &crate::style::LineStyle<C>,
target: &mut D,
) -> ChartResult<()>
where
D: DrawTarget<Color = C>;
fn draw_minor_line<D>(
&self,
start: Point,
end: Point,
style: &crate::style::LineStyle<C>,
target: &mut D,
) -> ChartResult<()>
where
D: DrawTarget<Color = C>;
fn draw_grid_line<D>(
&self,
start: Point,
end: Point,
style: &crate::style::LineStyle<C>,
target: &mut D,
) -> ChartResult<()>
where
D: DrawTarget<Color = C>;
}
pub trait GridConfiguration<C: PixelColor> {
fn configure_major_grid(
&mut self,
enabled: bool,
spacing: f32,
style: crate::grid::style::MajorGridStyle<C>,
);
fn configure_minor_grid(
&mut self,
enabled: bool,
spacing: f32,
style: crate::grid::style::MinorGridStyle<C>,
);
fn set_grid_visible(&mut self, visible: bool);
fn grid_config(&self) -> &crate::grid::style::GridStyle<C>;
}
pub trait TickAlignedGrid<T, C>: Grid<C>
where
T: crate::axes::traits::AxisValue,
C: PixelColor,
{
fn draw_with_axis<D, A>(
&self,
viewport: Rectangle,
axis: &A,
target: &mut D,
) -> ChartResult<()>
where
D: DrawTarget<Color = C>,
A: crate::axes::traits::Axis<T, C>;
fn calculate_tick_positions<A>(&self, viewport: Rectangle, axis: &A) -> heapless::Vec<i32, 64>
where
A: crate::axes::traits::Axis<T, C>;
fn set_major_ticks_only(&mut self, major_only: bool);
fn is_major_ticks_only(&self) -> bool;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GridOrientation {
Horizontal,
Vertical,
}
#[derive(Debug, Clone)]
pub struct DefaultGridRenderer;
impl<C: PixelColor> GridRenderer<C> for DefaultGridRenderer {
fn draw_major_line<D>(
&self,
start: Point,
end: Point,
style: &crate::style::LineStyle<C>,
target: &mut D,
) -> ChartResult<()>
where
D: DrawTarget<Color = C>,
{
self.draw_grid_line(start, end, style, target)
}
fn draw_minor_line<D>(
&self,
start: Point,
end: Point,
style: &crate::style::LineStyle<C>,
target: &mut D,
) -> ChartResult<()>
where
D: DrawTarget<Color = C>,
{
self.draw_grid_line(start, end, style, target)
}
fn draw_grid_line<D>(
&self,
start: Point,
end: Point,
style: &crate::style::LineStyle<C>,
target: &mut D,
) -> ChartResult<()>
where
D: DrawTarget<Color = C>,
{
use crate::error::ChartError;
use embedded_graphics::primitives::{Line, PrimitiveStyle};
let primitive_style = PrimitiveStyle::with_stroke(style.color, style.width);
match style.pattern {
crate::style::LinePattern::Solid => {
Line::new(start, end)
.into_styled(primitive_style)
.draw(target)
.map_err(|_| ChartError::RenderingError)?;
}
crate::style::LinePattern::Dashed => {
self.draw_dashed_line(start, end, style, target)?;
}
crate::style::LinePattern::Dotted => {
self.draw_dotted_line(start, end, style, target)?;
}
crate::style::LinePattern::DashDot => {
self.draw_dash_dot_line(start, end, style, target)?;
}
crate::style::LinePattern::Custom => {
Line::new(start, end)
.into_styled(primitive_style)
.draw(target)
.map_err(|_| ChartError::RenderingError)?;
}
}
Ok(())
}
}
impl DefaultGridRenderer {
fn draw_dashed_line<C, D>(
&self,
start: Point,
end: Point,
style: &crate::style::LineStyle<C>,
target: &mut D,
) -> ChartResult<()>
where
C: PixelColor,
D: DrawTarget<Color = C>,
{
use crate::error::ChartError;
use embedded_graphics::primitives::{Line, PrimitiveStyle};
let primitive_style = PrimitiveStyle::with_stroke(style.color, style.width);
let dash_length = 8;
let gap_length = 4;
let dx = end.x - start.x;
let dy = end.y - start.y;
let dx_f32 = dx as f32;
let dy_f32 = dy as f32;
let dx_num = dx_f32.to_number();
let dy_num = dy_f32.to_number();
let length_squared = dx_num * dx_num + dy_num * dy_num;
let length_num = Math::sqrt(length_squared);
let length = f32::from_number(length_num);
let one = 1.0f32.to_number();
if length_num < one {
return Ok(());
}
let unit_x = dx as f32 / length;
let unit_y = dy as f32 / length;
let mut current_pos = 0.0;
let mut drawing = true;
while current_pos < length {
let segment_length = if drawing {
dash_length as f32
} else {
gap_length as f32
};
let next_pos = (current_pos + segment_length).min(length);
if drawing {
let seg_start = Point::new(
start.x + (current_pos * unit_x) as i32,
start.y + (current_pos * unit_y) as i32,
);
let seg_end = Point::new(
start.x + (next_pos * unit_x) as i32,
start.y + (next_pos * unit_y) as i32,
);
Line::new(seg_start, seg_end)
.into_styled(primitive_style)
.draw(target)
.map_err(|_| ChartError::RenderingError)?;
}
current_pos = next_pos;
drawing = !drawing;
}
Ok(())
}
fn draw_dotted_line<C, D>(
&self,
start: Point,
end: Point,
style: &crate::style::LineStyle<C>,
target: &mut D,
) -> ChartResult<()>
where
C: PixelColor,
D: DrawTarget<Color = C>,
{
use crate::error::ChartError;
use embedded_graphics::primitives::{Circle, PrimitiveStyle};
let primitive_style = PrimitiveStyle::with_fill(style.color);
let dot_spacing = 6;
let dx = end.x - start.x;
let dy = end.y - start.y;
let dx_f32 = dx as f32;
let dy_f32 = dy as f32;
let dx_num = dx_f32.to_number();
let dy_num = dy_f32.to_number();
let length_squared = dx_num * dx_num + dy_num * dy_num;
let length_num = Math::sqrt(length_squared);
let length = f32::from_number(length_num);
let one = 1.0f32.to_number();
if length_num < one {
return Ok(());
}
let unit_x = dx as f32 / length;
let unit_y = dy as f32 / length;
let mut current_pos = 0.0;
while current_pos <= length {
let dot_center = Point::new(
start.x + (current_pos * unit_x) as i32,
start.y + (current_pos * unit_y) as i32,
);
Circle::new(Point::new(dot_center.x - 1, dot_center.y - 1), 2)
.into_styled(primitive_style)
.draw(target)
.map_err(|_| ChartError::RenderingError)?;
current_pos += dot_spacing as f32;
}
Ok(())
}
fn draw_dash_dot_line<C, D>(
&self,
start: Point,
end: Point,
style: &crate::style::LineStyle<C>,
target: &mut D,
) -> ChartResult<()>
where
C: PixelColor,
D: DrawTarget<Color = C>,
{
use crate::error::ChartError;
use embedded_graphics::primitives::{Circle, Line, PrimitiveStyle};
let line_style = PrimitiveStyle::with_stroke(style.color, style.width);
let dot_style = PrimitiveStyle::with_fill(style.color);
let dash_length = 8;
let gap_length = 3;
let dot_gap = 3;
let dx = end.x - start.x;
let dy = end.y - start.y;
let dx_f32 = dx as f32;
let dy_f32 = dy as f32;
let dx_num = dx_f32.to_number();
let dy_num = dy_f32.to_number();
let length_squared = dx_num * dx_num + dy_num * dy_num;
let length_num = Math::sqrt(length_squared);
let length = f32::from_number(length_num);
let one = 1.0f32.to_number();
if length_num < one {
return Ok(());
}
let unit_x = dx as f32 / length;
let unit_y = dy as f32 / length;
let mut current_pos = 0.0;
let pattern = [dash_length as f32, gap_length as f32, 2.0, dot_gap as f32]; let mut pattern_index = 0;
while current_pos < length {
let segment_length = pattern[pattern_index % pattern.len()];
let next_pos = (current_pos + segment_length).min(length);
match pattern_index % 4 {
0 => {
let seg_start = Point::new(
start.x + (current_pos * unit_x) as i32,
start.y + (current_pos * unit_y) as i32,
);
let seg_end = Point::new(
start.x + (next_pos * unit_x) as i32,
start.y + (next_pos * unit_y) as i32,
);
Line::new(seg_start, seg_end)
.into_styled(line_style)
.draw(target)
.map_err(|_| ChartError::RenderingError)?;
}
2 => {
let dot_center = Point::new(
start.x + (current_pos * unit_x) as i32,
start.y + (current_pos * unit_y) as i32,
);
Circle::new(Point::new(dot_center.x - 1, dot_center.y - 1), 2)
.into_styled(dot_style)
.draw(target)
.map_err(|_| ChartError::RenderingError)?;
}
_ => {
}
}
current_pos = next_pos;
pattern_index += 1;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_grid_orientation() {
assert_eq!(GridOrientation::Horizontal, GridOrientation::Horizontal);
assert_ne!(GridOrientation::Horizontal, GridOrientation::Vertical);
}
#[test]
fn test_default_grid_renderer() {
let renderer = DefaultGridRenderer;
assert_eq!(core::mem::size_of_val(&renderer), 0); }
}