1use crate::error::ChartResult;
4use embedded_graphics::{prelude::*, primitives::Rectangle};
5
6use crate::math::{Math, NumericConversion};
7
8pub trait Grid<C: PixelColor> {
10 fn draw<D>(&self, viewport: Rectangle, target: &mut D) -> ChartResult<()>
16 where
17 D: DrawTarget<Color = C>;
18
19 fn orientation(&self) -> GridOrientation;
21
22 fn is_visible(&self) -> bool;
24
25 fn set_visible(&mut self, visible: bool);
27
28 fn style(&self) -> &crate::grid::style::GridStyle<C>;
30
31 fn set_style(&mut self, style: crate::grid::style::GridStyle<C>);
33
34 fn calculate_positions(&self, viewport: Rectangle) -> heapless::Vec<i32, 64>;
37
38 fn spacing(&self) -> f32;
40
41 fn set_spacing(&mut self, spacing: f32);
43
44 fn as_any(&self) -> &dyn core::any::Any;
46}
47
48pub trait GridRenderer<C: PixelColor> {
50 fn draw_major_line<D>(
58 &self,
59 start: Point,
60 end: Point,
61 style: &crate::style::LineStyle<C>,
62 target: &mut D,
63 ) -> ChartResult<()>
64 where
65 D: DrawTarget<Color = C>;
66
67 fn draw_minor_line<D>(
75 &self,
76 start: Point,
77 end: Point,
78 style: &crate::style::LineStyle<C>,
79 target: &mut D,
80 ) -> ChartResult<()>
81 where
82 D: DrawTarget<Color = C>;
83
84 fn draw_grid_line<D>(
92 &self,
93 start: Point,
94 end: Point,
95 style: &crate::style::LineStyle<C>,
96 target: &mut D,
97 ) -> ChartResult<()>
98 where
99 D: DrawTarget<Color = C>;
100}
101
102pub trait GridConfiguration<C: PixelColor> {
104 fn configure_major_grid(
111 &mut self,
112 enabled: bool,
113 spacing: f32,
114 style: crate::grid::style::MajorGridStyle<C>,
115 );
116
117 fn configure_minor_grid(
124 &mut self,
125 enabled: bool,
126 spacing: f32,
127 style: crate::grid::style::MinorGridStyle<C>,
128 );
129
130 fn set_grid_visible(&mut self, visible: bool);
135
136 fn grid_config(&self) -> &crate::grid::style::GridStyle<C>;
138}
139
140pub trait TickAlignedGrid<T, C>: Grid<C>
142where
143 T: crate::axes::traits::AxisValue,
144 C: PixelColor,
145{
146 fn draw_with_axis<D, A>(
153 &self,
154 viewport: Rectangle,
155 axis: &A,
156 target: &mut D,
157 ) -> ChartResult<()>
158 where
159 D: DrawTarget<Color = C>,
160 A: crate::axes::traits::Axis<T, C>;
161
162 fn calculate_tick_positions<A>(&self, viewport: Rectangle, axis: &A) -> heapless::Vec<i32, 64>
168 where
169 A: crate::axes::traits::Axis<T, C>;
170
171 fn set_major_ticks_only(&mut self, major_only: bool);
176
177 fn is_major_ticks_only(&self) -> bool;
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum GridOrientation {
184 Horizontal,
186 Vertical,
188}
189
190#[derive(Debug, Clone)]
192pub struct DefaultGridRenderer;
193
194impl<C: PixelColor> GridRenderer<C> for DefaultGridRenderer {
195 fn draw_major_line<D>(
196 &self,
197 start: Point,
198 end: Point,
199 style: &crate::style::LineStyle<C>,
200 target: &mut D,
201 ) -> ChartResult<()>
202 where
203 D: DrawTarget<Color = C>,
204 {
205 self.draw_grid_line(start, end, style, target)
206 }
207
208 fn draw_minor_line<D>(
209 &self,
210 start: Point,
211 end: Point,
212 style: &crate::style::LineStyle<C>,
213 target: &mut D,
214 ) -> ChartResult<()>
215 where
216 D: DrawTarget<Color = C>,
217 {
218 self.draw_grid_line(start, end, style, target)
219 }
220
221 fn draw_grid_line<D>(
222 &self,
223 start: Point,
224 end: Point,
225 style: &crate::style::LineStyle<C>,
226 target: &mut D,
227 ) -> ChartResult<()>
228 where
229 D: DrawTarget<Color = C>,
230 {
231 use crate::error::ChartError;
232 use embedded_graphics::primitives::{Line, PrimitiveStyle};
233
234 let primitive_style = PrimitiveStyle::with_stroke(style.color, style.width);
235
236 match style.pattern {
238 crate::style::LinePattern::Solid => {
239 Line::new(start, end)
240 .into_styled(primitive_style)
241 .draw(target)
242 .map_err(|_| ChartError::RenderingError)?;
243 }
244 crate::style::LinePattern::Dashed => {
245 self.draw_dashed_line(start, end, style, target)?;
246 }
247 crate::style::LinePattern::Dotted => {
248 self.draw_dotted_line(start, end, style, target)?;
249 }
250 crate::style::LinePattern::DashDot => {
251 self.draw_dash_dot_line(start, end, style, target)?;
252 }
253 crate::style::LinePattern::Custom => {
254 Line::new(start, end)
256 .into_styled(primitive_style)
257 .draw(target)
258 .map_err(|_| ChartError::RenderingError)?;
259 }
260 }
261
262 Ok(())
263 }
264}
265
266impl DefaultGridRenderer {
267 fn draw_dashed_line<C, D>(
269 &self,
270 start: Point,
271 end: Point,
272 style: &crate::style::LineStyle<C>,
273 target: &mut D,
274 ) -> ChartResult<()>
275 where
276 C: PixelColor,
277 D: DrawTarget<Color = C>,
278 {
279 use crate::error::ChartError;
280 use embedded_graphics::primitives::{Line, PrimitiveStyle};
281
282 let primitive_style = PrimitiveStyle::with_stroke(style.color, style.width);
283 let dash_length = 8;
284 let gap_length = 4;
285
286 let dx = end.x - start.x;
287 let dy = end.y - start.y;
288 let dx_f32 = dx as f32;
289 let dy_f32 = dy as f32;
290 let dx_num = dx_f32.to_number();
291 let dy_num = dy_f32.to_number();
292 let length_squared = dx_num * dx_num + dy_num * dy_num;
293 let length_num = Math::sqrt(length_squared);
294 let length = f32::from_number(length_num);
295
296 let one = 1.0f32.to_number();
297 if length_num < one {
298 return Ok(());
299 }
300
301 let unit_x = dx as f32 / length;
302 let unit_y = dy as f32 / length;
303
304 let mut current_pos = 0.0;
305 let mut drawing = true;
306
307 while current_pos < length {
308 let segment_length = if drawing {
309 dash_length as f32
310 } else {
311 gap_length as f32
312 };
313 let next_pos = (current_pos + segment_length).min(length);
314
315 if drawing {
316 let seg_start = Point::new(
317 start.x + (current_pos * unit_x) as i32,
318 start.y + (current_pos * unit_y) as i32,
319 );
320 let seg_end = Point::new(
321 start.x + (next_pos * unit_x) as i32,
322 start.y + (next_pos * unit_y) as i32,
323 );
324
325 Line::new(seg_start, seg_end)
326 .into_styled(primitive_style)
327 .draw(target)
328 .map_err(|_| ChartError::RenderingError)?;
329 }
330
331 current_pos = next_pos;
332 drawing = !drawing;
333 }
334
335 Ok(())
336 }
337
338 fn draw_dotted_line<C, D>(
340 &self,
341 start: Point,
342 end: Point,
343 style: &crate::style::LineStyle<C>,
344 target: &mut D,
345 ) -> ChartResult<()>
346 where
347 C: PixelColor,
348 D: DrawTarget<Color = C>,
349 {
350 use crate::error::ChartError;
351 use embedded_graphics::primitives::{Circle, PrimitiveStyle};
352
353 let primitive_style = PrimitiveStyle::with_fill(style.color);
354 let dot_spacing = 6;
355
356 let dx = end.x - start.x;
357 let dy = end.y - start.y;
358 let dx_f32 = dx as f32;
359 let dy_f32 = dy as f32;
360 let dx_num = dx_f32.to_number();
361 let dy_num = dy_f32.to_number();
362 let length_squared = dx_num * dx_num + dy_num * dy_num;
363 let length_num = Math::sqrt(length_squared);
364 let length = f32::from_number(length_num);
365
366 let one = 1.0f32.to_number();
367 if length_num < one {
368 return Ok(());
369 }
370
371 let unit_x = dx as f32 / length;
372 let unit_y = dy as f32 / length;
373
374 let mut current_pos = 0.0;
375
376 while current_pos <= length {
377 let dot_center = Point::new(
378 start.x + (current_pos * unit_x) as i32,
379 start.y + (current_pos * unit_y) as i32,
380 );
381
382 Circle::new(Point::new(dot_center.x - 1, dot_center.y - 1), 2)
383 .into_styled(primitive_style)
384 .draw(target)
385 .map_err(|_| ChartError::RenderingError)?;
386
387 current_pos += dot_spacing as f32;
388 }
389
390 Ok(())
391 }
392
393 fn draw_dash_dot_line<C, D>(
395 &self,
396 start: Point,
397 end: Point,
398 style: &crate::style::LineStyle<C>,
399 target: &mut D,
400 ) -> ChartResult<()>
401 where
402 C: PixelColor,
403 D: DrawTarget<Color = C>,
404 {
405 use crate::error::ChartError;
406 use embedded_graphics::primitives::{Circle, Line, PrimitiveStyle};
407
408 let line_style = PrimitiveStyle::with_stroke(style.color, style.width);
409 let dot_style = PrimitiveStyle::with_fill(style.color);
410 let dash_length = 8;
411 let gap_length = 3;
412 let dot_gap = 3;
413
414 let dx = end.x - start.x;
415 let dy = end.y - start.y;
416 let dx_f32 = dx as f32;
417 let dy_f32 = dy as f32;
418 let dx_num = dx_f32.to_number();
419 let dy_num = dy_f32.to_number();
420 let length_squared = dx_num * dx_num + dy_num * dy_num;
421 let length_num = Math::sqrt(length_squared);
422 let length = f32::from_number(length_num);
423
424 let one = 1.0f32.to_number();
425 if length_num < one {
426 return Ok(());
427 }
428
429 let unit_x = dx as f32 / length;
430 let unit_y = dy as f32 / length;
431
432 let mut current_pos = 0.0;
433 let pattern = [dash_length as f32, gap_length as f32, 2.0, dot_gap as f32]; let mut pattern_index = 0;
435
436 while current_pos < length {
437 let segment_length = pattern[pattern_index % pattern.len()];
438 let next_pos = (current_pos + segment_length).min(length);
439
440 match pattern_index % 4 {
441 0 => {
442 let seg_start = Point::new(
444 start.x + (current_pos * unit_x) as i32,
445 start.y + (current_pos * unit_y) as i32,
446 );
447 let seg_end = Point::new(
448 start.x + (next_pos * unit_x) as i32,
449 start.y + (next_pos * unit_y) as i32,
450 );
451
452 Line::new(seg_start, seg_end)
453 .into_styled(line_style)
454 .draw(target)
455 .map_err(|_| ChartError::RenderingError)?;
456 }
457 2 => {
458 let dot_center = Point::new(
460 start.x + (current_pos * unit_x) as i32,
461 start.y + (current_pos * unit_y) as i32,
462 );
463
464 Circle::new(Point::new(dot_center.x - 1, dot_center.y - 1), 2)
465 .into_styled(dot_style)
466 .draw(target)
467 .map_err(|_| ChartError::RenderingError)?;
468 }
469 _ => {
470 }
472 }
473
474 current_pos = next_pos;
475 pattern_index += 1;
476 }
477
478 Ok(())
479 }
480}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485
486 #[test]
487 fn test_grid_orientation() {
488 assert_eq!(GridOrientation::Horizontal, GridOrientation::Horizontal);
489 assert_ne!(GridOrientation::Horizontal, GridOrientation::Vertical);
490 }
491
492 #[test]
493 fn test_default_grid_renderer() {
494 let renderer = DefaultGridRenderer;
495 assert_eq!(core::mem::size_of_val(&renderer), 0); }
498}