use embedded_charts::{
render::{ChartRenderer, ClippingRenderer, EnhancedChartRenderer, PrimitiveRenderer},
style::{FillStyle, LineStyle, StrokeStyle},
};
use embedded_graphics::{
mock_display::MockDisplay,
pixelcolor::{Rgb565, RgbColor},
prelude::*,
primitives::Rectangle,
};
#[cfg(feature = "animations")]
use embedded_charts::render::AnimationFrameRenderer;
fn create_test_display() -> MockDisplay<Rgb565> {
let mut display = MockDisplay::<Rgb565>::new();
display.set_allow_overdraw(true); display
}
fn verify_pixel_set(display: &MockDisplay<Rgb565>, point: Point, color: Rgb565) -> bool {
display.get_pixel(point) == Some(color)
}
#[test]
fn test_draw_line_comprehensive() {
{
let mut display = create_test_display();
let style = LineStyle::solid(Rgb565::RED).width(1);
let result =
ChartRenderer::draw_line(Point::new(0, 0), Point::new(10, 0), &style, &mut display);
assert!(result.is_ok());
}
{
let mut display = create_test_display();
let style = LineStyle::solid(Rgb565::RED).width(1);
let result =
ChartRenderer::draw_line(Point::new(5, 0), Point::new(5, 10), &style, &mut display);
assert!(result.is_ok());
}
{
let mut display = create_test_display();
let style = LineStyle::solid(Rgb565::RED).width(1);
let result =
ChartRenderer::draw_line(Point::new(0, 0), Point::new(10, 10), &style, &mut display);
assert!(result.is_ok());
}
{
let mut display = create_test_display();
let thick_style = LineStyle::solid(Rgb565::BLUE).width(3);
let result = ChartRenderer::draw_line(
Point::new(20, 20),
Point::new(30, 20),
&thick_style,
&mut display,
);
assert!(result.is_ok());
}
{
let mut display = create_test_display();
let style = LineStyle::solid(Rgb565::RED).width(1);
let result =
ChartRenderer::draw_line(Point::new(0, 0), Point::new(63, 63), &style, &mut display);
assert!(result.is_ok());
}
{
let mut display = create_test_display();
let style = LineStyle::solid(Rgb565::RED).width(1);
let result =
ChartRenderer::draw_line(Point::new(15, 15), Point::new(15, 15), &style, &mut display);
assert!(result.is_ok());
}
}
#[test]
fn test_draw_polyline_comprehensive() {
let style = LineStyle::solid(Rgb565::GREEN).width(2);
{
let mut display = create_test_display();
let empty_points: &[Point] = &[];
let result = ChartRenderer::draw_polyline(empty_points, &style, &mut display);
assert!(result.is_ok());
}
{
let mut display = create_test_display();
let single_point = &[Point::new(5, 5)];
let result = ChartRenderer::draw_polyline(single_point, &style, &mut display);
assert!(result.is_ok());
}
{
let mut display = create_test_display();
let two_points = &[Point::new(10, 10), Point::new(20, 20)];
let result = ChartRenderer::draw_polyline(two_points, &style, &mut display);
assert!(result.is_ok());
}
{
let mut display = create_test_display();
let complex_points = &[
Point::new(5, 5),
Point::new(15, 10),
Point::new(25, 5),
Point::new(35, 15),
Point::new(45, 10),
];
let result = ChartRenderer::draw_polyline(complex_points, &style, &mut display);
assert!(result.is_ok());
}
{
let mut display = create_test_display();
let closed_points = &[
Point::new(40, 40),
Point::new(50, 40),
Point::new(50, 50),
Point::new(40, 50),
Point::new(40, 40), ];
let result = ChartRenderer::draw_polyline(closed_points, &style, &mut display);
assert!(result.is_ok());
}
}
#[test]
fn test_draw_filled_rectangle() {
let mut display = create_test_display();
let fill_style = FillStyle::solid(Rgb565::YELLOW);
let rect = Rectangle::new(Point::new(10, 10), Size::new(20, 15));
let result = ChartRenderer::draw_filled_rectangle(rect, &fill_style, &mut display);
assert!(result.is_ok());
assert!(verify_pixel_set(
&display,
Point::new(15, 15),
Rgb565::YELLOW
));
assert!(verify_pixel_set(
&display,
Point::new(10, 10),
Rgb565::YELLOW
));
assert!(verify_pixel_set(
&display,
Point::new(29, 24),
Rgb565::YELLOW
));
let zero_rect = Rectangle::new(Point::new(50, 50), Size::new(0, 0));
let result = ChartRenderer::draw_filled_rectangle(zero_rect, &fill_style, &mut display);
assert!(result.is_ok());
let partial_rect = Rectangle::new(Point::new(50, 50), Size::new(10, 10));
let result = ChartRenderer::draw_filled_rectangle(partial_rect, &fill_style, &mut display);
assert!(result.is_ok());
}
#[test]
fn test_draw_rectangle_with_stroke_and_fill() {
let mut display = create_test_display();
let stroke_style = StrokeStyle::new(Rgb565::RED, 2);
let fill_style = FillStyle::solid(Rgb565::BLUE);
let rect = Rectangle::new(Point::new(20, 20), Size::new(30, 25));
let result =
ChartRenderer::draw_rectangle(rect, Some(&stroke_style), Some(&fill_style), &mut display);
assert!(result.is_ok());
let rect2 = Rectangle::new(Point::new(10, 40), Size::new(15, 15));
let result = ChartRenderer::draw_rectangle(rect2, Some(&stroke_style), None, &mut display);
assert!(result.is_ok());
let rect3 = Rectangle::new(Point::new(30, 40), Size::new(15, 15));
let result = ChartRenderer::draw_rectangle(rect3, None, Some(&fill_style), &mut display);
assert!(result.is_ok());
let rect4 = Rectangle::new(Point::new(45, 45), Size::new(10, 10));
let result = ChartRenderer::draw_rectangle(rect4, None, None, &mut display);
assert!(result.is_ok());
}
#[test]
fn test_draw_circle_comprehensive() {
let stroke_style = StrokeStyle::new(Rgb565::GREEN, 1);
let fill_style = FillStyle::solid(Rgb565::CYAN);
{
let mut display = create_test_display();
let result = ChartRenderer::draw_circle(
Point::new(32, 32),
10,
Some(&stroke_style),
Some(&fill_style),
&mut display,
);
assert!(result.is_ok());
}
{
let mut display = create_test_display();
let result = ChartRenderer::draw_circle(
Point::new(32, 20),
8,
Some(&stroke_style),
None,
&mut display,
);
assert!(result.is_ok());
}
{
let mut display = create_test_display();
let result = ChartRenderer::draw_circle(
Point::new(30, 30),
5,
None,
Some(&fill_style),
&mut display,
);
assert!(result.is_ok());
}
{
let mut display = create_test_display();
let result = ChartRenderer::draw_circle(
Point::new(20, 20),
0,
Some(&stroke_style),
Some(&fill_style),
&mut display,
);
assert!(result.is_ok());
}
{
let mut display = create_test_display();
let result = ChartRenderer::draw_circle(
Point::new(32, 32),
12,
Some(&stroke_style),
None,
&mut display,
);
assert!(result.is_ok());
}
}
#[test]
fn test_draw_grid() {
let mut display = create_test_display();
let grid_style = LineStyle::solid(Rgb565::MAGENTA).width(1);
let area = Rectangle::new(Point::new(0, 0), Size::new(60, 60));
let spacing = Size::new(10, 10);
let result = ChartRenderer::draw_grid(area, spacing, &grid_style, &mut display);
assert!(result.is_ok());
let mut display2 = create_test_display();
let area2 = Rectangle::new(Point::new(0, 0), Size::new(60, 60));
let spacing2 = Size::new(20, 15);
let result = ChartRenderer::draw_grid(area2, spacing2, &grid_style, &mut display2);
assert!(result.is_ok());
let mut display3 = create_test_display();
let area3 = Rectangle::new(Point::new(0, 0), Size::new(30, 30));
let spacing3 = Size::new(40, 40);
let result = ChartRenderer::draw_grid(area3, spacing3, &grid_style, &mut display3);
assert!(result.is_ok());
let mut display4 = create_test_display();
let area4 = Rectangle::new(Point::new(40, 40), Size::new(20, 20));
let spacing4 = Size::new(5, 5);
let result = ChartRenderer::draw_grid(area4, spacing4, &grid_style, &mut display4);
assert!(result.is_ok());
}
#[test]
fn test_clear_area() {
let mut display = create_test_display();
let color = Rgb565::BLACK;
let fill_style = FillStyle::solid(Rgb565::WHITE);
let rect = Rectangle::new(Point::new(10, 10), Size::new(40, 40));
ChartRenderer::draw_filled_rectangle(rect, &fill_style, &mut display).unwrap();
let clear_rect = Rectangle::new(Point::new(20, 20), Size::new(20, 20));
let result = ChartRenderer::clear_area(clear_rect, color, &mut display);
assert!(result.is_ok());
assert!(verify_pixel_set(
&display,
Point::new(30, 30),
Rgb565::BLACK
));
assert!(verify_pixel_set(
&display,
Point::new(15, 15),
Rgb565::WHITE
));
}
#[test]
fn test_clipping_point_visibility_edge_cases() {
let bounds = Rectangle::new(Point::new(10, 20), Size::new(50, 40));
assert!(ClippingRenderer::is_point_visible(
Point::new(10, 20),
bounds
)); assert!(ClippingRenderer::is_point_visible(
Point::new(59, 20),
bounds
)); assert!(ClippingRenderer::is_point_visible(
Point::new(10, 59),
bounds
)); assert!(ClippingRenderer::is_point_visible(
Point::new(59, 59),
bounds
));
assert!(!ClippingRenderer::is_point_visible(
Point::new(9, 20),
bounds
)); assert!(!ClippingRenderer::is_point_visible(
Point::new(60, 20),
bounds
)); assert!(!ClippingRenderer::is_point_visible(
Point::new(10, 19),
bounds
)); assert!(!ClippingRenderer::is_point_visible(
Point::new(10, 60),
bounds
));
assert!(!ClippingRenderer::is_point_visible(
Point::new(i32::MIN, i32::MIN),
bounds
));
assert!(!ClippingRenderer::is_point_visible(
Point::new(i32::MAX, i32::MAX),
bounds
));
}
#[test]
fn test_clipping_rectangle_visibility_edge_cases() {
let bounds = Rectangle::new(Point::new(0, 0), Size::new(100, 100));
let exact = Rectangle::new(Point::new(0, 0), Size::new(100, 100));
assert!(ClippingRenderer::is_rectangle_visible(exact, bounds));
let zero = Rectangle::new(Point::new(50, 50), Size::new(0, 0));
assert!(ClippingRenderer::is_rectangle_visible(zero, bounds));
let touch_right = Rectangle::new(Point::new(100, 50), Size::new(10, 10));
assert!(!ClippingRenderer::is_rectangle_visible(touch_right, bounds));
let touch_left = Rectangle::new(Point::new(-10, 50), Size::new(10, 10));
assert!(!ClippingRenderer::is_rectangle_visible(touch_left, bounds));
let touch_bottom = Rectangle::new(Point::new(50, 100), Size::new(10, 10));
assert!(!ClippingRenderer::is_rectangle_visible(
touch_bottom,
bounds
));
let touch_top = Rectangle::new(Point::new(50, -10), Size::new(10, 10));
assert!(!ClippingRenderer::is_rectangle_visible(touch_top, bounds));
let container = Rectangle::new(Point::new(-50, -50), Size::new(200, 200));
assert!(ClippingRenderer::is_rectangle_visible(container, bounds));
}
#[test]
fn test_line_clipping_comprehensive() {
let bounds = Rectangle::new(Point::new(10, 10), Size::new(80, 60));
let inside = ClippingRenderer::clip_line(Point::new(20, 20), Point::new(50, 40), bounds);
assert_eq!(inside, Some((Point::new(20, 20), Point::new(50, 40))));
let outside = ClippingRenderer::clip_line(Point::new(0, 0), Point::new(5, 5), bounds);
assert_eq!(outside, None);
let cross_left = ClippingRenderer::clip_line(Point::new(0, 40), Point::new(50, 40), bounds);
assert!(cross_left.is_some());
if let Some((p1, p2)) = cross_left {
assert_eq!(p1.x, 10); assert_eq!(p2, Point::new(50, 40));
}
let cross_right = ClippingRenderer::clip_line(Point::new(50, 40), Point::new(100, 40), bounds);
assert!(cross_right.is_some());
if let Some((p1, p2)) = cross_right {
assert_eq!(p1, Point::new(50, 40));
assert_eq!(p2.x, 90); }
let cross_top = ClippingRenderer::clip_line(Point::new(50, 0), Point::new(50, 40), bounds);
assert!(cross_top.is_some());
if let Some((p1, p2)) = cross_top {
assert_eq!(p1.y, 10); assert_eq!(p2, Point::new(50, 40));
}
let cross_bottom = ClippingRenderer::clip_line(Point::new(50, 40), Point::new(50, 80), bounds);
assert!(cross_bottom.is_some());
if let Some((p1, p2)) = cross_bottom {
assert_eq!(p1, Point::new(50, 40));
assert_eq!(p2.y, 70); }
let diagonal = ClippingRenderer::clip_line(Point::new(0, 0), Point::new(100, 80), bounds);
assert!(diagonal.is_some());
let vertical = ClippingRenderer::clip_line(Point::new(50, 0), Point::new(50, 100), bounds);
assert!(vertical.is_some());
let horizontal = ClippingRenderer::clip_line(Point::new(0, 40), Point::new(100, 40), bounds);
assert!(horizontal.is_some());
let point = ClippingRenderer::clip_line(Point::new(50, 40), Point::new(50, 40), bounds);
assert_eq!(point, Some((Point::new(50, 40), Point::new(50, 40))));
}
#[test]
fn test_primitive_renderer_triangle() {
let mut display = create_test_display();
let stroke_style = StrokeStyle::new(Rgb565::RED, 1);
let fill_style = FillStyle::solid(Rgb565::BLUE);
let p1 = Point::new(32, 10);
let p2 = Point::new(20, 30);
let p3 = Point::new(44, 30);
let result = PrimitiveRenderer::draw_triangle(
p1,
p2,
p3,
Some(&stroke_style),
Some(&fill_style),
&mut display,
);
assert!(result.is_ok());
let result = PrimitiveRenderer::draw_triangle(
Point::new(32, 35),
Point::new(20, 55),
Point::new(44, 55),
Some(&stroke_style),
None,
&mut display,
);
assert!(result.is_ok());
let result = PrimitiveRenderer::draw_triangle(
Point::new(55, 10),
Point::new(50, 25),
Point::new(60, 25),
None,
Some(&fill_style),
&mut display,
);
assert!(result.is_ok());
let result = PrimitiveRenderer::draw_triangle(
Point::new(10, 50),
Point::new(20, 50),
Point::new(30, 50),
Some(&stroke_style),
Some(&fill_style),
&mut display,
);
assert!(result.is_ok());
let result = PrimitiveRenderer::draw_triangle(
Point::new(5, 5),
Point::new(15, 5),
Point::new(10, 15),
Some(&stroke_style),
None,
&mut display,
);
assert!(result.is_ok());
}
#[test]
fn test_primitive_renderer_diamond() {
let mut display = create_test_display();
let stroke_style = StrokeStyle::new(Rgb565::GREEN, 2);
let fill_style = FillStyle::solid(Rgb565::YELLOW);
let result = PrimitiveRenderer::draw_diamond(
Point::new(32, 32),
10,
Some(&stroke_style),
Some(&fill_style),
&mut display,
);
assert!(result.is_ok());
let result = PrimitiveRenderer::draw_diamond(
Point::new(50, 15),
8,
Some(&stroke_style),
None,
&mut display,
);
assert!(result.is_ok());
let result = PrimitiveRenderer::draw_diamond(
Point::new(40, 20),
10,
None,
Some(&fill_style),
&mut display,
);
assert!(result.is_ok());
let result = PrimitiveRenderer::draw_diamond(
Point::new(50, 50),
0,
Some(&stroke_style),
Some(&fill_style),
&mut display,
);
assert!(result.is_ok());
let result = PrimitiveRenderer::draw_diamond(
Point::new(30, 30),
7,
Some(&stroke_style),
None,
&mut display,
);
assert!(result.is_ok());
}
#[test]
#[cfg(feature = "animations")]
fn test_animation_frame_renderer() {
let renderer = AnimationFrameRenderer::new(60);
assert_eq!(renderer.frame_rate(), 60);
let mut renderer = AnimationFrameRenderer::new(30);
assert_eq!(renderer.frame_rate(), 30);
let renderer_low = AnimationFrameRenderer::new(0);
assert_eq!(renderer_low.frame_rate(), 1);
let renderer_high = AnimationFrameRenderer::new(200);
assert_eq!(renderer_high.frame_rate(), 120);
renderer.reset();
assert!(!renderer.update(0));
assert!(!renderer.update(10));
assert!(!renderer.update(20));
assert!(!renderer.update(30));
assert!(renderer.update(33)); assert!(!renderer.update(40));
renderer.set_frame_rate(60);
assert_eq!(renderer.frame_rate(), 60);
renderer.reset();
assert!(!renderer.update(0));
assert!(!renderer.update(10));
assert!(renderer.update(17));
renderer.reset();
assert!(!renderer.update(0));
}
#[test]
#[cfg(feature = "animations")]
fn test_animation_frame_renderer_edge_cases() {
let mut renderer = AnimationFrameRenderer::new(60);
renderer.reset();
assert!(!renderer.update(0)); assert!(renderer.update(17)); assert!(!renderer.update(30));
assert!(renderer.update(34));
renderer.reset();
renderer.update(100);
renderer.update(50); assert!(!renderer.update(51));
renderer.reset();
let mut triggered = 0;
for t in 0..1000 {
if renderer.update(t as u32) {
triggered += 1;
}
}
assert!((50..=65).contains(&triggered));
}
#[test]
fn test_enhanced_chart_renderer() {
let mut display = create_test_display();
let viewport = Rectangle::new(Point::new(0, 0), Size::new(60, 60));
let bg_color = Rgb565::BLACK;
let result = EnhancedChartRenderer::clear_viewport(viewport, bg_color, &mut display);
assert!(result.is_ok());
assert!(verify_pixel_set(&display, Point::new(0, 0), Rgb565::BLACK));
assert!(verify_pixel_set(
&display,
Point::new(30, 30),
Rgb565::BLACK
));
assert!(verify_pixel_set(
&display,
Point::new(59, 59),
Rgb565::BLACK
));
}
#[test]
fn test_text_renderer() {
use embedded_charts::render::text::TextRenderer;
use embedded_graphics::mono_font::{ascii::FONT_6X10, MonoTextStyle};
let mut display = create_test_display();
let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE);
let result = TextRenderer::draw_text("Test", Point::new(10, 20), &text_style, &mut display);
assert!(result.is_ok());
let area = Rectangle::new(Point::new(0, 40), Size::new(60, 20));
let result =
TextRenderer::draw_centered_text("Center", area, &text_style, &FONT_6X10, &mut display);
assert!(result.is_ok());
let result = TextRenderer::draw_text("", Point::new(10, 80), &text_style, &mut display);
assert!(result.is_ok());
let long_text = "Text";
let result = TextRenderer::draw_text(long_text, Point::new(5, 50), &text_style, &mut display);
assert!(result.is_ok());
}
#[test]
#[cfg(feature = "std")]
fn test_performance_characteristics() {
use std::time::Instant;
let mut display = create_test_display();
let style = LineStyle::solid(Rgb565::RED).width(1);
let start = Instant::now();
for i in 0..30 {
let y = i * 2;
ChartRenderer::draw_line(Point::new(0, y), Point::new(60, y), &style, &mut display)
.unwrap();
}
let duration = start.elapsed();
println!("30 lines rendered in {duration:?}");
assert!(duration.as_millis() < 100);
let mut display2 = create_test_display();
let fill_style = FillStyle::solid(Rgb565::BLUE);
let start = Instant::now();
for x in (0..60).step_by(10) {
for y in (0..60).step_by(10) {
let rect = Rectangle::new(Point::new(x, y), Size::new(8, 8));
ChartRenderer::draw_filled_rectangle(rect, &fill_style, &mut display2).unwrap();
}
}
let duration = start.elapsed();
println!("36 rectangles rendered in {duration:?}");
assert!(duration.as_millis() < 200);
let bounds = Rectangle::new(Point::new(50, 50), Size::new(100, 100));
let start = Instant::now();
for _ in 0..1000 {
let p1 = Point::new(rand_coord(), rand_coord());
let p2 = Point::new(rand_coord(), rand_coord());
let _ = ClippingRenderer::clip_line(p1, p2, bounds);
}
let duration = start.elapsed();
println!("1000 line clips in {duration:?}");
assert!(duration.as_millis() < 50);
}
fn rand_coord() -> i32 {
static mut SEED: u32 = 12345;
unsafe {
SEED = SEED.wrapping_mul(1664525).wrapping_add(1013904223);
((SEED >> 16) & 0x3F) as i32 - 32 }
}
#[test]
fn test_error_handling() {
let mut display = create_test_display();
let style = LineStyle::solid(Rgb565::RED).width(1);
let result =
ChartRenderer::draw_line(Point::new(10, 10), Point::new(50, 50), &style, &mut display);
assert!(result.is_ok());
let huge_rect = Rectangle::new(Point::new(0, 0), Size::new(60, 60));
let fill_style = FillStyle::solid(Rgb565::BLUE);
let result = ChartRenderer::draw_filled_rectangle(huge_rect, &fill_style, &mut display);
assert!(result.is_ok()); }
#[test]
fn test_compute_outcode() {
let bounds = Rectangle::new(Point::new(10, 10), Size::new(50, 40));
let center_line = ClippingRenderer::clip_line(Point::new(35, 30), Point::new(35, 30), bounds);
assert!(center_line.is_some());
let left_line = ClippingRenderer::clip_line(Point::new(5, 30), Point::new(35, 30), bounds);
assert!(left_line.is_some());
let right_line = ClippingRenderer::clip_line(Point::new(65, 30), Point::new(35, 30), bounds);
assert!(right_line.is_some());
let below_line = ClippingRenderer::clip_line(Point::new(35, 5), Point::new(35, 30), bounds);
assert!(below_line.is_some());
let above_line = ClippingRenderer::clip_line(Point::new(35, 55), Point::new(35, 30), bounds);
assert!(above_line.is_some());
let tl_line = ClippingRenderer::clip_line(Point::new(5, 5), Point::new(35, 30), bounds);
assert!(tl_line.is_some());
let br_line = ClippingRenderer::clip_line(Point::new(65, 55), Point::new(35, 30), bounds);
assert!(br_line.is_some());
}
#[test]
fn test_triangle_fill_edge_cases() {
let mut display = create_test_display();
let fill_style = FillStyle::solid(Rgb565::GREEN);
let result = PrimitiveRenderer::draw_triangle(
Point::new(10, 30),
Point::new(20, 30),
Point::new(30, 30),
None,
Some(&fill_style),
&mut display,
);
assert!(result.is_ok());
let result = PrimitiveRenderer::draw_triangle(
Point::new(45, 10),
Point::new(45, 20),
Point::new(45, 30),
None,
Some(&fill_style),
&mut display,
);
assert!(result.is_ok());
let result = PrimitiveRenderer::draw_triangle(
Point::new(55, 55),
Point::new(56, 55),
Point::new(55, 56),
None,
Some(&fill_style),
&mut display,
);
assert!(result.is_ok());
let result = PrimitiveRenderer::draw_triangle(
Point::new(40, 40),
Point::new(40, 40),
Point::new(45, 45),
None,
Some(&fill_style),
&mut display,
);
assert!(result.is_ok());
let result = PrimitiveRenderer::draw_triangle(
Point::new(32, 60),
Point::new(28, 50),
Point::new(36, 50),
None,
Some(&fill_style),
&mut display,
);
assert!(result.is_ok());
}