embedded_charts/
render.rs

1//! Rendering utilities for chart components.
2
3use crate::error::{RenderError, RenderResult};
4use crate::style::{FillStyle, GradientDirection, LineStyle, StrokeStyle};
5use embedded_graphics::{
6    draw_target::DrawTarget,
7    prelude::*,
8    primitives::{Circle, Line, PrimitiveStyle, PrimitiveStyleBuilder, Rectangle},
9};
10
11#[cfg(not(feature = "std"))]
12use micromath::F32Ext;
13
14/// Main renderer for chart components
15pub struct ChartRenderer;
16
17impl ChartRenderer {
18    /// Draw a line with the specified style
19    pub fn draw_line<C, D>(
20        start: Point,
21        end: Point,
22        style: &LineStyle<C>,
23        target: &mut D,
24    ) -> RenderResult<()>
25    where
26        C: PixelColor,
27        D: DrawTarget<Color = C>,
28    {
29        let primitive_style = PrimitiveStyleBuilder::new()
30            .stroke_color(style.color)
31            .stroke_width(style.width)
32            .build();
33
34        Line::new(start, end)
35            .into_styled(primitive_style)
36            .draw(target)
37            .map_err(|_| RenderError::DrawingFailed)?;
38
39        Ok(())
40    }
41
42    /// Draw a series of connected lines (polyline)
43    pub fn draw_polyline<C, D>(
44        points: &[Point],
45        style: &LineStyle<C>,
46        target: &mut D,
47    ) -> RenderResult<()>
48    where
49        C: PixelColor,
50        D: DrawTarget<Color = C>,
51    {
52        if points.len() < 2 {
53            return Ok(());
54        }
55
56        for window in points.windows(2) {
57            if let [p1, p2] = window {
58                Self::draw_line(*p1, *p2, style, target)?;
59            }
60        }
61
62        Ok(())
63    }
64
65    /// Draw a filled rectangle
66    pub fn draw_filled_rectangle<C, D>(
67        rect: Rectangle,
68        fill_style: &FillStyle<C>,
69        target: &mut D,
70    ) -> RenderResult<()>
71    where
72        C: PixelColor,
73        D: DrawTarget<Color = C>,
74    {
75        use crate::style::FillPattern;
76
77        match &fill_style.pattern {
78            FillPattern::Solid(color) => {
79                let primitive_style = PrimitiveStyle::with_fill(*color);
80                rect.into_styled(primitive_style)
81                    .draw(target)
82                    .map_err(|_| RenderError::DrawingFailed)?;
83            }
84            FillPattern::LinearGradient(gradient) => {
85                Self::draw_linear_gradient_rect(rect, gradient, target)?;
86            }
87            FillPattern::RadialGradient(gradient) => {
88                Self::draw_radial_gradient_rect(rect, gradient, target)?;
89            }
90            FillPattern::Pattern(pattern) => {
91                Self::draw_pattern_rect(rect, pattern, target)?;
92            }
93        }
94        Ok(())
95    }
96
97    /// Draw a rectangle with stroke and optional fill
98    pub fn draw_rectangle<C, D>(
99        rect: Rectangle,
100        stroke_style: Option<&StrokeStyle<C>>,
101        fill_style: Option<&FillStyle<C>>,
102        target: &mut D,
103    ) -> RenderResult<()>
104    where
105        C: PixelColor,
106        D: DrawTarget<Color = C>,
107    {
108        let mut style_builder = PrimitiveStyleBuilder::new();
109
110        if let Some(fill) = fill_style {
111            if let Some(color) = fill.solid_color() {
112                style_builder = style_builder.fill_color(color);
113            }
114        }
115
116        if let Some(stroke) = stroke_style {
117            style_builder = style_builder
118                .stroke_color(stroke.color)
119                .stroke_width(stroke.width);
120        }
121
122        rect.into_styled(style_builder.build())
123            .draw(target)
124            .map_err(|_| RenderError::DrawingFailed)?;
125
126        Ok(())
127    }
128
129    /// Draw a circle
130    pub fn draw_circle<C, D>(
131        center: Point,
132        radius: u32,
133        stroke_style: Option<&StrokeStyle<C>>,
134        fill_style: Option<&FillStyle<C>>,
135        target: &mut D,
136    ) -> RenderResult<()>
137    where
138        C: PixelColor,
139        D: DrawTarget<Color = C>,
140    {
141        let mut style_builder = PrimitiveStyleBuilder::new();
142
143        if let Some(fill) = fill_style {
144            if let Some(color) = fill.solid_color() {
145                style_builder = style_builder.fill_color(color);
146            }
147        }
148
149        if let Some(stroke) = stroke_style {
150            style_builder = style_builder
151                .stroke_color(stroke.color)
152                .stroke_width(stroke.width);
153        }
154
155        let circle = Circle::new(
156            Point::new(center.x - radius as i32, center.y - radius as i32),
157            radius * 2,
158        );
159
160        circle
161            .into_styled(style_builder.build())
162            .draw(target)
163            .map_err(|_| RenderError::DrawingFailed)?;
164
165        Ok(())
166    }
167
168    /// Draw a grid
169    pub fn draw_grid<C, D>(
170        area: Rectangle,
171        grid_spacing: Size,
172        style: &LineStyle<C>,
173        target: &mut D,
174    ) -> RenderResult<()>
175    where
176        C: PixelColor,
177        D: DrawTarget<Color = C>,
178    {
179        // Draw vertical lines
180        let mut x = area.top_left.x;
181        while x <= area.top_left.x + area.size.width as i32 {
182            let start = Point::new(x, area.top_left.y);
183            let end = Point::new(x, area.top_left.y + area.size.height as i32);
184            Self::draw_line(start, end, style, target)?;
185            x += grid_spacing.width as i32;
186        }
187
188        // Draw horizontal lines
189        let mut y = area.top_left.y;
190        while y <= area.top_left.y + area.size.height as i32 {
191            let start = Point::new(area.top_left.x, y);
192            let end = Point::new(area.top_left.x + area.size.width as i32, y);
193            Self::draw_line(start, end, style, target)?;
194            y += grid_spacing.height as i32;
195        }
196
197        Ok(())
198    }
199
200    /// Clear an area with a background color
201    pub fn clear_area<C, D>(area: Rectangle, color: C, target: &mut D) -> RenderResult<()>
202    where
203        C: PixelColor,
204        D: DrawTarget<Color = C>,
205    {
206        let fill_style = FillStyle::solid(color);
207        Self::draw_filled_rectangle(area, &fill_style, target)
208    }
209
210    /// Draw a rectangle filled with a linear gradient
211    fn draw_linear_gradient_rect<C, D, const N: usize>(
212        rect: Rectangle,
213        gradient: &crate::style::LinearGradient<C, N>,
214        target: &mut D,
215    ) -> RenderResult<()>
216    where
217        C: PixelColor,
218        D: DrawTarget<Color = C>,
219    {
220        if !gradient.is_valid() {
221            return Ok(());
222        }
223
224        match gradient.direction() {
225            GradientDirection::Horizontal => {
226                // Draw vertical lines with interpolated colors
227                for x in 0..rect.size.width {
228                    let t = x as f32 / (rect.size.width - 1) as f32;
229                    if let Some(color) = gradient.color_at(t) {
230                        let line_start = Point::new(rect.top_left.x + x as i32, rect.top_left.y);
231                        let line_end = Point::new(
232                            rect.top_left.x + x as i32,
233                            rect.top_left.y + rect.size.height as i32 - 1,
234                        );
235                        Line::new(line_start, line_end)
236                            .into_styled(PrimitiveStyle::with_stroke(color, 1))
237                            .draw(target)
238                            .map_err(|_| RenderError::DrawingFailed)?;
239                    }
240                }
241            }
242            GradientDirection::Vertical => {
243                // Draw horizontal lines with interpolated colors
244                for y in 0..rect.size.height {
245                    let t = y as f32 / (rect.size.height - 1) as f32;
246                    if let Some(color) = gradient.color_at(t) {
247                        Self::draw_horizontal_line(
248                            Point::new(rect.top_left.x, rect.top_left.y + y as i32),
249                            rect.size.width,
250                            color,
251                            target,
252                        )?;
253                    }
254                }
255            }
256            GradientDirection::Diagonal | GradientDirection::ReverseDiagonal => {
257                // Use the same diagonal line approach as the optimized version
258                let total = rect.size.width + rect.size.height - 2;
259                let step_size = if total > 100 { 2 } else { 1 }; // Skip pixels for large gradients
260
261                for y in (0..rect.size.height).step_by(step_size as usize) {
262                    for x in (0..rect.size.width).step_by(step_size as usize) {
263                        let t = if gradient.direction() == GradientDirection::Diagonal {
264                            (x + y) as f32 / total as f32
265                        } else {
266                            (rect.size.width - 1 - x + y) as f32 / total as f32
267                        };
268
269                        if let Some(color) = gradient.color_at(t) {
270                            // Draw a small filled rectangle instead of single pixel
271                            let pixel_rect = Rectangle::new(
272                                Point::new(rect.top_left.x + x as i32, rect.top_left.y + y as i32),
273                                Size::new(step_size, step_size),
274                            );
275                            pixel_rect
276                                .into_styled(PrimitiveStyle::with_fill(color))
277                                .draw(target)
278                                .map_err(|_| RenderError::DrawingFailed)?;
279                        }
280                    }
281                }
282            }
283        }
284        Ok(())
285    }
286
287    /// Draw a rectangle filled with a radial gradient
288    fn draw_radial_gradient_rect<C, D, const N: usize>(
289        rect: Rectangle,
290        gradient: &crate::style::RadialGradient<C, N>,
291        target: &mut D,
292    ) -> RenderResult<()>
293    where
294        C: PixelColor,
295        D: DrawTarget<Color = C>,
296    {
297        if !gradient.is_valid() {
298            return Ok(());
299        }
300
301        let center = gradient.center();
302        let center_x = rect.top_left.x + (rect.size.width as i32 * center.x / 100);
303        let center_y = rect.top_left.y + (rect.size.height as i32 * center.y / 100);
304
305        // Calculate maximum distance from center to corners
306        let max_dist = {
307            let dx1 = (rect.top_left.x - center_x).abs();
308            let dx2 = (rect.top_left.x + rect.size.width as i32 - center_x).abs();
309            let dy1 = (rect.top_left.y - center_y).abs();
310            let dy2 = (rect.top_left.y + rect.size.height as i32 - center_y).abs();
311            let max_dx = dx1.max(dx2) as f32;
312            let max_dy = dy1.max(dy2) as f32;
313            (max_dx * max_dx + max_dy * max_dy).sqrt()
314        };
315
316        // Draw each pixel with color based on distance from center
317        for y in 0..rect.size.height {
318            for x in 0..rect.size.width {
319                let px = rect.top_left.x + x as i32;
320                let py = rect.top_left.y + y as i32;
321                let dx = (px - center_x) as f32;
322                let dy = (py - center_y) as f32;
323                let dist = (dx * dx + dy * dy).sqrt();
324                let t = (dist / max_dist).clamp(0.0, 1.0);
325
326                if let Some(color) = gradient.color_at_distance(t) {
327                    Pixel(Point::new(px, py), color)
328                        .draw(target)
329                        .map_err(|_| RenderError::DrawingFailed)?;
330                }
331            }
332        }
333        Ok(())
334    }
335
336    /// Draw a rectangle filled with a pattern
337    fn draw_pattern_rect<C, D>(
338        rect: Rectangle,
339        pattern: &crate::style::PatternFill<C>,
340        target: &mut D,
341    ) -> RenderResult<()>
342    where
343        C: PixelColor,
344        D: DrawTarget<Color = C>,
345    {
346        // Draw each pixel with pattern color
347        for y in 0..rect.size.height {
348            for x in 0..rect.size.width {
349                let color = pattern.color_at(x as i32, y as i32);
350                Pixel(
351                    Point::new(rect.top_left.x + x as i32, rect.top_left.y + y as i32),
352                    color,
353                )
354                .draw(target)
355                .map_err(|_| RenderError::DrawingFailed)?;
356            }
357        }
358        Ok(())
359    }
360
361    /// Draw a horizontal line (optimized for gradient rendering)
362    fn draw_horizontal_line<C, D>(
363        start: Point,
364        width: u32,
365        color: C,
366        target: &mut D,
367    ) -> RenderResult<()>
368    where
369        C: PixelColor,
370        D: DrawTarget<Color = C>,
371    {
372        Line::new(start, Point::new(start.x + width as i32 - 1, start.y))
373            .into_styled(PrimitiveStyle::with_stroke(color, 1))
374            .draw(target)
375            .map_err(|_| RenderError::DrawingFailed)?;
376        Ok(())
377    }
378
379    /// Draw a rectangle filled with a linear gradient (Rgb565 optimized version)
380    #[cfg(feature = "color-support")]
381    pub fn draw_linear_gradient_rect_rgb565<D, const N: usize>(
382        rect: Rectangle,
383        gradient: &crate::style::LinearGradient<embedded_graphics::pixelcolor::Rgb565, N>,
384        target: &mut D,
385    ) -> RenderResult<()>
386    where
387        D: DrawTarget<Color = embedded_graphics::pixelcolor::Rgb565>,
388    {
389        use crate::style::GradientInterpolation;
390
391        if !gradient.is_valid() {
392            return Ok(());
393        }
394
395        match gradient.direction() {
396            GradientDirection::Horizontal => {
397                // Draw vertical lines with interpolated colors
398                for x in 0..rect.size.width {
399                    let t = x as f32 / (rect.size.width - 1) as f32;
400                    if let Some(color) = gradient.interpolated_color_at(t) {
401                        let line_start = Point::new(rect.top_left.x + x as i32, rect.top_left.y);
402                        let line_end = Point::new(
403                            rect.top_left.x + x as i32,
404                            rect.top_left.y + rect.size.height as i32 - 1,
405                        );
406                        Line::new(line_start, line_end)
407                            .into_styled(PrimitiveStyle::with_stroke(color, 1))
408                            .draw(target)
409                            .map_err(|_| RenderError::DrawingFailed)?;
410                    }
411                }
412            }
413            GradientDirection::Vertical => {
414                // Draw horizontal lines with interpolated colors
415                for y in 0..rect.size.height {
416                    let t = y as f32 / (rect.size.height - 1) as f32;
417                    if let Some(color) = gradient.interpolated_color_at(t) {
418                        Self::draw_horizontal_line(
419                            Point::new(rect.top_left.x, rect.top_left.y + y as i32),
420                            rect.size.width,
421                            color,
422                            target,
423                        )?;
424                    }
425                }
426            }
427            GradientDirection::Diagonal | GradientDirection::ReverseDiagonal => {
428                // Draw diagonal gradient using small rectangles
429                let step = 3; // Size of each rectangle
430
431                for y in (0..rect.size.height).step_by(step) {
432                    for x in (0..rect.size.width).step_by(step) {
433                        // Calculate position along diagonal
434                        let t = if gradient.direction() == GradientDirection::Diagonal {
435                            (x + y) as f32 / (rect.size.width + rect.size.height - 2) as f32
436                        } else {
437                            (rect.size.width - 1 - x + y) as f32
438                                / (rect.size.width + rect.size.height - 2) as f32
439                        };
440
441                        if let Some(color) = gradient.interpolated_color_at(t) {
442                            Rectangle::new(
443                                Point::new(rect.top_left.x + x as i32, rect.top_left.y + y as i32),
444                                Size::new(step as u32, step as u32),
445                            )
446                            .into_styled(PrimitiveStyle::with_fill(color))
447                            .draw(target)
448                            .map_err(|_| RenderError::DrawingFailed)?;
449                        }
450                    }
451                }
452            }
453        }
454        Ok(())
455    }
456
457    /// Draw a rectangle filled with a radial gradient (Rgb565 optimized version)
458    #[cfg(feature = "color-support")]
459    pub fn draw_radial_gradient_rect_rgb565<D, const N: usize>(
460        rect: Rectangle,
461        gradient: &crate::style::RadialGradient<embedded_graphics::pixelcolor::Rgb565, N>,
462        target: &mut D,
463    ) -> RenderResult<()>
464    where
465        D: DrawTarget<Color = embedded_graphics::pixelcolor::Rgb565>,
466    {
467        use crate::style::RadialGradientInterpolation;
468
469        if !gradient.is_valid() {
470            return Ok(());
471        }
472
473        let center = gradient.center();
474        let center_x = rect.top_left.x + (rect.size.width as i32 * center.x / 100);
475        let center_y = rect.top_left.y + (rect.size.height as i32 * center.y / 100);
476
477        // Calculate maximum distance from center to corners
478        let max_dist = {
479            let dx1 = (rect.top_left.x - center_x).abs();
480            let dx2 = (rect.top_left.x + rect.size.width as i32 - center_x).abs();
481            let dy1 = (rect.top_left.y - center_y).abs();
482            let dy2 = (rect.top_left.y + rect.size.height as i32 - center_y).abs();
483            let max_dx = dx1.max(dx2) as f32;
484            let max_dy = dy1.max(dy2) as f32;
485            (max_dx * max_dx + max_dy * max_dy).sqrt()
486        };
487
488        // Draw radial gradient using filled rectangles for better performance
489        // We'll use a lower resolution for speed
490        let step_size = 3;
491
492        for y in (0..rect.size.height).step_by(step_size) {
493            for x in (0..rect.size.width).step_by(step_size) {
494                let px = rect.top_left.x + x as i32;
495                let py = rect.top_left.y + y as i32;
496                let dx = (px - center_x) as f32;
497                let dy = (py - center_y) as f32;
498                let dist = (dx * dx + dy * dy).sqrt();
499                let t = (dist / max_dist).clamp(0.0, 1.0);
500
501                if let Some(color) = gradient.interpolated_color_at_distance(t) {
502                    Rectangle::new(
503                        Point::new(px, py),
504                        Size::new(step_size as u32, step_size as u32),
505                    )
506                    .into_styled(PrimitiveStyle::with_fill(color))
507                    .draw(target)
508                    .map_err(|_| RenderError::DrawingFailed)?;
509                }
510            }
511        }
512        Ok(())
513    }
514}
515
516/// Clipping utilities for efficient rendering
517pub struct ClippingRenderer;
518
519impl ClippingRenderer {
520    /// Check if a point is within the clipping bounds
521    pub fn is_point_visible(point: Point, bounds: Rectangle) -> bool {
522        point.x >= bounds.top_left.x
523            && point.x < bounds.top_left.x + bounds.size.width as i32
524            && point.y >= bounds.top_left.y
525            && point.y < bounds.top_left.y + bounds.size.height as i32
526    }
527
528    /// Check if a rectangle intersects with the clipping bounds
529    pub fn is_rectangle_visible(rect: Rectangle, bounds: Rectangle) -> bool {
530        !(rect.top_left.x >= bounds.top_left.x + bounds.size.width as i32
531            || rect.top_left.x + rect.size.width as i32 <= bounds.top_left.x
532            || rect.top_left.y >= bounds.top_left.y + bounds.size.height as i32
533            || rect.top_left.y + rect.size.height as i32 <= bounds.top_left.y)
534    }
535
536    /// Clip a line to the bounds (simplified Cohen-Sutherland algorithm)
537    pub fn clip_line(start: Point, end: Point, bounds: Rectangle) -> Option<(Point, Point)> {
538        let mut x1 = start.x;
539        let mut y1 = start.y;
540        let mut x2 = end.x;
541        let mut y2 = end.y;
542
543        let xmin = bounds.top_left.x;
544        let ymin = bounds.top_left.y;
545        let xmax = bounds.top_left.x + bounds.size.width as i32;
546        let ymax = bounds.top_left.y + bounds.size.height as i32;
547
548        // Outcodes for the endpoints
549        let mut outcode1 = Self::compute_outcode(x1, y1, xmin, ymin, xmax, ymax);
550        let mut outcode2 = Self::compute_outcode(x2, y2, xmin, ymin, xmax, ymax);
551
552        loop {
553            if (outcode1 | outcode2) == 0 {
554                // Both points inside
555                return Some((Point::new(x1, y1), Point::new(x2, y2)));
556            } else if (outcode1 & outcode2) != 0 {
557                // Both points outside same region
558                return None;
559            } else {
560                // Line needs clipping
561                let outcode_out = if outcode1 != 0 { outcode1 } else { outcode2 };
562
563                let (x, y) = if (outcode_out & 8) != 0 {
564                    // Point is above
565                    let x = x1 + (x2 - x1) * (ymax - y1) / (y2 - y1);
566                    (x, ymax)
567                } else if (outcode_out & 4) != 0 {
568                    // Point is below
569                    let x = x1 + (x2 - x1) * (ymin - y1) / (y2 - y1);
570                    (x, ymin)
571                } else if (outcode_out & 2) != 0 {
572                    // Point is to the right
573                    let y = y1 + (y2 - y1) * (xmax - x1) / (x2 - x1);
574                    (xmax, y)
575                } else {
576                    // Point is to the left
577                    let y = y1 + (y2 - y1) * (xmin - x1) / (x2 - x1);
578                    (xmin, y)
579                };
580
581                if outcode_out == outcode1 {
582                    x1 = x;
583                    y1 = y;
584                    outcode1 = Self::compute_outcode(x1, y1, xmin, ymin, xmax, ymax);
585                } else {
586                    x2 = x;
587                    y2 = y;
588                    outcode2 = Self::compute_outcode(x2, y2, xmin, ymin, xmax, ymax);
589                }
590            }
591        }
592    }
593
594    /// Compute outcode for Cohen-Sutherland clipping
595    fn compute_outcode(x: i32, y: i32, xmin: i32, ymin: i32, xmax: i32, ymax: i32) -> u8 {
596        let mut code = 0;
597
598        if x < xmin {
599            code |= 1; // Left
600        } else if x > xmax {
601            code |= 2; // Right
602        }
603
604        if y < ymin {
605            code |= 4; // Below
606        } else if y > ymax {
607            code |= 8; // Above
608        }
609
610        code
611    }
612}
613
614/// Text rendering utilities (when fonts feature is enabled)
615pub mod text {
616    use super::*;
617    use embedded_graphics::mono_font::{MonoFont, MonoTextStyle};
618    use embedded_graphics::text::{Baseline, Text};
619
620    /// Text renderer for chart labels and titles
621    pub struct TextRenderer;
622
623    impl TextRenderer {
624        /// Draw text at the specified position
625        pub fn draw_text<C, D>(
626            text: &str,
627            position: Point,
628            style: &MonoTextStyle<C>,
629            target: &mut D,
630        ) -> RenderResult<()>
631        where
632            C: PixelColor,
633            D: DrawTarget<Color = C>,
634        {
635            Text::with_baseline(text, position, *style, Baseline::Top)
636                .draw(target)
637                .map_err(|_| RenderError::TextRenderingFailed)?;
638
639            Ok(())
640        }
641
642        /// Calculate the size of text when rendered
643        pub fn text_size<C>(text: &str, font: &MonoFont) -> Size {
644            let char_size = font.character_size;
645            Size::new(char_size.width * text.len() as u32, char_size.height)
646        }
647
648        /// Draw centered text within a rectangle
649        pub fn draw_centered_text<C, D>(
650            text: &str,
651            container: Rectangle,
652            style: &MonoTextStyle<C>,
653            font: &MonoFont,
654            target: &mut D,
655        ) -> RenderResult<()>
656        where
657            C: PixelColor,
658            D: DrawTarget<Color = C>,
659        {
660            let text_size = Self::text_size::<C>(text, font);
661            let x =
662                container.top_left.x + (container.size.width as i32 - text_size.width as i32) / 2;
663            let y =
664                container.top_left.y + (container.size.height as i32 - text_size.height as i32) / 2;
665
666            Self::draw_text(text, Point::new(x, y), style, target)
667        }
668    }
669}
670
671/// Primitive drawing utilities for custom shapes
672pub struct PrimitiveRenderer;
673
674impl PrimitiveRenderer {
675    /// Draw a triangle
676    pub fn draw_triangle<C, D>(
677        p1: Point,
678        p2: Point,
679        p3: Point,
680        stroke_style: Option<&StrokeStyle<C>>,
681        fill_style: Option<&FillStyle<C>>,
682        target: &mut D,
683    ) -> RenderResult<()>
684    where
685        C: PixelColor,
686        D: DrawTarget<Color = C>,
687    {
688        // For simplicity, draw triangle as three lines
689        // A full implementation would use a proper triangle primitive
690        if let Some(stroke) = stroke_style {
691            let line_style = LineStyle::solid(stroke.color).width(stroke.width);
692            ChartRenderer::draw_line(p1, p2, &line_style, target)?;
693            ChartRenderer::draw_line(p2, p3, &line_style, target)?;
694            ChartRenderer::draw_line(p3, p1, &line_style, target)?;
695        }
696
697        // Fill triangle using scanline algorithm
698        if let Some(fill) = fill_style {
699            Self::fill_triangle(p1, p2, p3, fill, target)?;
700        }
701
702        Ok(())
703    }
704
705    /// Fill a triangle using scanline algorithm
706    fn fill_triangle<C, D>(
707        p1: Point,
708        p2: Point,
709        p3: Point,
710        fill_style: &FillStyle<C>,
711        target: &mut D,
712    ) -> RenderResult<()>
713    where
714        C: PixelColor,
715        D: DrawTarget<Color = C>,
716    {
717        // Sort points by Y coordinate (p1.y <= p2.y <= p3.y)
718        let mut points = [p1, p2, p3];
719        // Manual sorting for no_std compatibility
720        if points[0].y > points[1].y {
721            points.swap(0, 1);
722        }
723        if points[1].y > points[2].y {
724            points.swap(1, 2);
725        }
726        if points[0].y > points[1].y {
727            points.swap(0, 1);
728        }
729        let [top, mid, bottom] = points;
730
731        // Handle degenerate cases
732        if top.y == bottom.y {
733            // All points on same horizontal line
734            let min_x = top.x.min(mid.x).min(bottom.x);
735            let max_x = top.x.max(mid.x).max(bottom.x);
736            if let Some(color) = fill_style.solid_color() {
737                Self::draw_horizontal_line(min_x, max_x, top.y, color, target)?;
738            }
739            return Ok(());
740        }
741
742        // Calculate slopes for the three edges
743        let total_height = bottom.y - top.y;
744
745        // Draw upper part of triangle (from top to mid)
746        if mid.y > top.y {
747            let segment_height = mid.y - top.y;
748            for y in top.y..mid.y {
749                let alpha = (y - top.y) as f32 / total_height as f32;
750                let beta = (y - top.y) as f32 / segment_height as f32;
751
752                let x1 = top.x + ((bottom.x - top.x) as f32 * alpha) as i32;
753                let x2 = top.x + ((mid.x - top.x) as f32 * beta) as i32;
754
755                let (min_x, max_x) = if x1 < x2 { (x1, x2) } else { (x2, x1) };
756                if let Some(color) = fill_style.solid_color() {
757                    Self::draw_horizontal_line(min_x, max_x, y, color, target)?;
758                }
759            }
760        }
761
762        // Draw lower part of triangle (from mid to bottom)
763        if bottom.y > mid.y {
764            let segment_height = bottom.y - mid.y;
765            for y in mid.y..=bottom.y {
766                let alpha = (y - top.y) as f32 / total_height as f32;
767                let beta = (y - mid.y) as f32 / segment_height as f32;
768
769                let x1 = top.x + ((bottom.x - top.x) as f32 * alpha) as i32;
770                let x2 = mid.x + ((bottom.x - mid.x) as f32 * beta) as i32;
771
772                let (min_x, max_x) = if x1 < x2 { (x1, x2) } else { (x2, x1) };
773                if let Some(color) = fill_style.solid_color() {
774                    Self::draw_horizontal_line(min_x, max_x, y, color, target)?;
775                }
776            }
777        }
778
779        Ok(())
780    }
781
782    /// Draw a horizontal line from x1 to x2 at y coordinate
783    fn draw_horizontal_line<C, D>(
784        x1: i32,
785        x2: i32,
786        y: i32,
787        color: C,
788        target: &mut D,
789    ) -> RenderResult<()>
790    where
791        C: PixelColor,
792        D: DrawTarget<Color = C>,
793    {
794        if x1 == x2 {
795            // Single pixel
796            target
797                .draw_iter(core::iter::once(Pixel(Point::new(x1, y), color)))
798                .map_err(|_| RenderError::DrawingFailed)?;
799        } else {
800            // Horizontal line
801            let line_style = PrimitiveStyle::with_stroke(color, 1);
802            Line::new(Point::new(x1, y), Point::new(x2, y))
803                .into_styled(line_style)
804                .draw(target)
805                .map_err(|_| RenderError::DrawingFailed)?;
806        }
807        Ok(())
808    }
809
810    /// Draw a diamond shape
811    pub fn draw_diamond<C, D>(
812        center: Point,
813        size: u32,
814        stroke_style: Option<&StrokeStyle<C>>,
815        fill_style: Option<&FillStyle<C>>,
816        target: &mut D,
817    ) -> RenderResult<()>
818    where
819        C: PixelColor,
820        D: DrawTarget<Color = C>,
821    {
822        let half_size = size as i32 / 2;
823        let top = Point::new(center.x, center.y - half_size);
824        let right = Point::new(center.x + half_size, center.y);
825        let bottom = Point::new(center.x, center.y + half_size);
826        let left = Point::new(center.x - half_size, center.y);
827
828        Self::draw_triangle(top, right, bottom, stroke_style, fill_style, target)?;
829        Self::draw_triangle(top, bottom, left, stroke_style, fill_style, target)?;
830
831        Ok(())
832    }
833}
834
835/// Animation frame renderer for coordinating animated chart rendering
836#[cfg(feature = "animations")]
837pub struct AnimationFrameRenderer {
838    /// Target frame rate
839    frame_rate: u32,
840    /// Time accumulator for frame timing
841    time_accumulator: crate::time::Milliseconds,
842    /// Last frame timestamp
843    last_frame_time: Option<crate::time::Milliseconds>,
844}
845
846#[cfg(feature = "animations")]
847impl AnimationFrameRenderer {
848    /// Create a new animation frame renderer
849    pub fn new(frame_rate: u32) -> Self {
850        Self {
851            frame_rate: frame_rate.clamp(1, 120),
852            time_accumulator: 0,
853            last_frame_time: None,
854        }
855    }
856
857    /// Update the frame renderer with elapsed time
858    pub fn update(&mut self, current_time: crate::time::Milliseconds) -> bool {
859        let frame_duration = 1000 / self.frame_rate;
860
861        if let Some(last_time) = self.last_frame_time {
862            let delta = current_time.saturating_sub(last_time);
863            self.time_accumulator += delta;
864        }
865
866        self.last_frame_time = Some(current_time);
867
868        if self.time_accumulator >= frame_duration {
869            self.time_accumulator = self.time_accumulator.saturating_sub(frame_duration);
870            true // Frame should be rendered
871        } else {
872            false
873        }
874    }
875
876    /// Get the current frame rate
877    pub fn frame_rate(&self) -> u32 {
878        self.frame_rate
879    }
880
881    /// Set the target frame rate
882    pub fn set_frame_rate(&mut self, fps: u32) {
883        self.frame_rate = fps.clamp(1, 120);
884    }
885
886    /// Reset the frame timing
887    pub fn reset(&mut self) {
888        self.time_accumulator = 0;
889        self.last_frame_time = None;
890    }
891
892    /// Render an animated chart frame
893    pub fn render_animated_chart<C, D, T>(
894        &self,
895        chart: &T,
896        data: &T::Data,
897        config: &T::Config,
898        viewport: embedded_graphics::primitives::Rectangle,
899        target: &mut D,
900    ) -> crate::error::RenderResult<()>
901    where
902        C: PixelColor,
903        D: DrawTarget<Color = C>,
904        T: crate::chart::traits::AnimatedChart<C> + crate::chart::traits::AnimationRenderer<C>,
905    {
906        if chart.needs_frame_update() {
907            chart
908                .draw_animated(data, config, viewport, target, 0)
909                .map_err(|_| crate::error::RenderError::DrawingFailed)?;
910        }
911        Ok(())
912    }
913
914    /// Check if any charts need frame updates
915    pub fn charts_need_update<C>(
916        &self,
917        charts: &[&dyn crate::chart::traits::AnimationRenderer<C>],
918    ) -> bool
919    where
920        C: PixelColor,
921    {
922        charts.iter().any(|chart| chart.needs_frame_update())
923    }
924}
925
926#[cfg(feature = "animations")]
927impl Default for AnimationFrameRenderer {
928    fn default() -> Self {
929        Self::new(60) // Default 60 FPS
930    }
931}
932
933/// Enhanced chart renderer with animation support
934pub struct EnhancedChartRenderer;
935
936impl EnhancedChartRenderer {
937    /// Render a chart with optional animation support
938    pub fn render_chart<C, D, T>(
939        chart: &T,
940        data: &T::Data,
941        config: &T::Config,
942        viewport: embedded_graphics::primitives::Rectangle,
943        target: &mut D,
944    ) -> crate::error::RenderResult<()>
945    where
946        C: PixelColor,
947        D: DrawTarget<Color = C>,
948        T: crate::chart::traits::Chart<C>,
949        T::Data: crate::data::DataSeries,
950        <T::Data as crate::data::DataSeries>::Item: crate::data::DataPoint,
951        <<T::Data as crate::data::DataSeries>::Item as crate::data::DataPoint>::X:
952            Into<f32> + Copy + PartialOrd,
953        <<T::Data as crate::data::DataSeries>::Item as crate::data::DataPoint>::Y:
954            Into<f32> + Copy + PartialOrd,
955    {
956        chart
957            .draw(data, config, viewport, target)
958            .map_err(|_| crate::error::RenderError::DrawingFailed)
959    }
960
961    /// Render an animated chart
962    #[cfg(feature = "animations")]
963    pub fn render_animated_chart<C, D, T>(
964        chart: &T,
965        data: &T::Data,
966        config: &T::Config,
967        viewport: embedded_graphics::primitives::Rectangle,
968        target: &mut D,
969    ) -> crate::error::RenderResult<()>
970    where
971        C: PixelColor,
972        D: DrawTarget<Color = C>,
973        T: crate::chart::traits::AnimatedChart<C>,
974    {
975        chart
976            .draw_animated(data, config, viewport, target, 0)
977            .map_err(|_| crate::error::RenderError::DrawingFailed)
978    }
979
980    /// Update and render an animated chart with timing
981    #[cfg(feature = "animations")]
982    pub fn update_and_render<C, D, T>(
983        chart: &mut T,
984        data: &T::Data,
985        _delta_time: crate::time::Milliseconds,
986        config: &T::Config,
987        viewport: embedded_graphics::primitives::Rectangle,
988        target: &mut D,
989    ) -> crate::error::RenderResult<()>
990    where
991        C: PixelColor,
992        D: DrawTarget<Color = C>,
993        T: crate::chart::traits::AnimatedChart<C>,
994    {
995        // Render the animated frame (animation state is controlled externally)
996        Self::render_animated_chart(chart, data, config, viewport, target)
997    }
998
999    /// Clear a rectangular area with a background color
1000    pub fn clear_viewport<C, D>(
1001        viewport: embedded_graphics::primitives::Rectangle,
1002        color: C,
1003        target: &mut D,
1004    ) -> crate::error::RenderResult<()>
1005    where
1006        C: PixelColor,
1007        D: DrawTarget<Color = C>,
1008    {
1009        ChartRenderer::clear_area(viewport, color, target)
1010    }
1011}
1012
1013#[cfg(test)]
1014mod tests {
1015    use super::*;
1016    use embedded_graphics::mock_display::MockDisplay;
1017    use embedded_graphics::pixelcolor::Rgb565;
1018
1019    #[test]
1020    fn test_clipping_point_visibility() {
1021        let bounds = Rectangle::new(Point::new(10, 10), Size::new(100, 80));
1022
1023        assert!(ClippingRenderer::is_point_visible(
1024            Point::new(50, 50),
1025            bounds
1026        ));
1027        assert!(!ClippingRenderer::is_point_visible(
1028            Point::new(5, 50),
1029            bounds
1030        ));
1031        assert!(!ClippingRenderer::is_point_visible(
1032            Point::new(150, 50),
1033            bounds
1034        ));
1035    }
1036
1037    #[test]
1038    fn test_clipping_rectangle_visibility() {
1039        let bounds = Rectangle::new(Point::new(0, 0), Size::new(100, 100));
1040
1041        // Completely inside
1042        let inside = Rectangle::new(Point::new(10, 10), Size::new(20, 20));
1043        assert!(ClippingRenderer::is_rectangle_visible(inside, bounds));
1044
1045        // Completely outside
1046        let outside = Rectangle::new(Point::new(150, 150), Size::new(20, 20));
1047        assert!(!ClippingRenderer::is_rectangle_visible(outside, bounds));
1048
1049        // Partially overlapping
1050        let overlapping = Rectangle::new(Point::new(90, 90), Size::new(20, 20));
1051        assert!(ClippingRenderer::is_rectangle_visible(overlapping, bounds));
1052    }
1053
1054    #[test]
1055    fn test_line_clipping() {
1056        let bounds = Rectangle::new(Point::new(0, 0), Size::new(100, 100));
1057
1058        // Line completely inside
1059        let inside_line =
1060            ClippingRenderer::clip_line(Point::new(10, 10), Point::new(50, 50), bounds);
1061        assert!(inside_line.is_some());
1062
1063        // Line completely outside
1064        let outside_line =
1065            ClippingRenderer::clip_line(Point::new(150, 150), Point::new(200, 200), bounds);
1066        assert!(outside_line.is_none());
1067    }
1068
1069    #[test]
1070    fn test_chart_renderer_line() {
1071        let mut display = MockDisplay::<Rgb565>::new();
1072        let style = LineStyle::solid(Rgb565::RED).width(1);
1073
1074        let result =
1075            ChartRenderer::draw_line(Point::new(0, 0), Point::new(10, 10), &style, &mut display);
1076
1077        assert!(result.is_ok());
1078    }
1079
1080    #[test]
1081    fn test_chart_renderer_rectangle() {
1082        let mut display = MockDisplay::<Rgb565>::new();
1083        let stroke = StrokeStyle::new(Rgb565::BLUE, 1);
1084        let fill = FillStyle::solid(Rgb565::GREEN);
1085
1086        let rect = Rectangle::new(Point::new(5, 5), Size::new(20, 15));
1087        let result = ChartRenderer::draw_rectangle(rect, Some(&stroke), Some(&fill), &mut display);
1088
1089        assert!(result.is_ok());
1090    }
1091}