use crate::axes::traits::Axis;
use crate::chart::traits::AxisChart;
use crate::chart::traits::{Chart, ChartBuilder, ChartConfig, Margins};
use crate::data::{DataBounds, DataPoint, DataSeries};
use crate::error::{ChartError, ChartResult};
use crate::math::NumericConversion;
use embedded_graphics::{
draw_target::DrawTarget,
prelude::*,
primitives::{Circle, Line, PrimitiveStyle, Rectangle},
};
#[derive(Debug)]
pub struct LineChart<C: PixelColor> {
style: LineChartStyle<C>,
config: ChartConfig<C>,
grid: Option<crate::grid::GridSystem<C>>,
x_axis: Option<crate::axes::LinearAxis<f32, C>>,
y_axis: Option<crate::axes::LinearAxis<f32, C>>,
}
#[derive(Debug, Clone)]
pub struct LineChartStyle<C: PixelColor> {
pub line_color: C,
pub line_width: u32,
pub fill_area: bool,
pub fill_color: Option<C>,
pub markers: Option<MarkerStyle<C>>,
pub smooth: bool,
pub smooth_subdivisions: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct MarkerStyle<C: PixelColor> {
pub shape: MarkerShape,
pub size: u32,
pub color: C,
pub visible: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MarkerShape {
Circle,
Square,
Diamond,
Triangle,
}
impl<C: PixelColor> LineChart<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
pub fn new() -> Self {
Self {
style: LineChartStyle::default(),
config: ChartConfig::default(),
grid: None,
x_axis: None,
y_axis: None,
}
}
pub fn builder() -> LineChartBuilder<C> {
LineChartBuilder::new()
}
pub fn set_style(&mut self, style: LineChartStyle<C>) {
self.style = style;
}
pub fn style(&self) -> &LineChartStyle<C> {
&self.style
}
pub fn set_config(&mut self, config: ChartConfig<C>) {
self.config = config;
}
pub fn config(&self) -> &ChartConfig<C> {
&self.config
}
pub fn set_grid(&mut self, grid: Option<crate::grid::GridSystem<C>>) {
self.grid = grid;
}
pub fn grid(&self) -> Option<&crate::grid::GridSystem<C>> {
self.grid.as_ref()
}
fn transform_point<P>(
&self,
point: &P,
data_bounds: &DataBounds<P::X, P::Y>,
viewport: Rectangle,
) -> Point
where
P: DataPoint,
P::X: NumericConversion<P::X> + Into<f32> + Copy,
P::Y: NumericConversion<P::Y> + Into<f32> + Copy,
{
let data_x = point.x().into().to_number();
let data_y = point.y().into().to_number();
let (min_x, max_x) = if let Some(ref x_axis) = self.x_axis {
let axis_min: f32 = x_axis.min();
let axis_max: f32 = x_axis.max();
(axis_min.to_number(), axis_max.to_number())
} else {
(
data_bounds.min_x.into().to_number(),
data_bounds.max_x.into().to_number(),
)
};
let (min_y, max_y) = if let Some(ref y_axis) = self.y_axis {
let axis_min: f32 = y_axis.min();
let axis_max: f32 = y_axis.max();
(axis_min.to_number(), axis_max.to_number())
} else {
(
data_bounds.min_y.into().to_number(),
data_bounds.max_y.into().to_number(),
)
};
let draw_area = self.config.margins.apply_to(viewport);
let norm_x = if f32::from_number(max_x) > f32::from_number(min_x) {
let range_x = f32::from_number(max_x - min_x);
let offset_x = f32::from_number(data_x - min_x);
(offset_x / range_x).to_number()
} else {
0.5f32.to_number()
};
let norm_y = if f32::from_number(max_y) > f32::from_number(min_y) {
let range_y = f32::from_number(max_y - min_y);
let offset_y = f32::from_number(data_y - min_y);
(offset_y / range_y).to_number()
} else {
0.5f32.to_number()
};
let norm_x_f32 = f32::from_number(norm_x);
let norm_y_f32 = f32::from_number(norm_y);
let screen_x =
draw_area.top_left.x + (norm_x_f32 * (draw_area.size.width as f32 - 1.0)) as i32;
let screen_y = draw_area.top_left.y + draw_area.size.height as i32
- 1
- (norm_y_f32 * (draw_area.size.height as f32 - 1.0)) as i32;
Point::new(screen_x, screen_y)
}
fn draw_markers<D>(
&self,
data: &crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>,
data_bounds: &DataBounds<f32, f32>,
viewport: Rectangle,
target: &mut D,
) -> ChartResult<()>
where
D: DrawTarget<Color = C>,
{
if let Some(marker_style) = &self.style.markers {
if marker_style.visible {
for point in data.iter() {
let screen_point = self.transform_point(&point, data_bounds, viewport);
self.draw_marker(screen_point, marker_style, target)?;
}
}
}
Ok(())
}
fn draw_marker<D>(
&self,
center: Point,
marker_style: &MarkerStyle<C>,
target: &mut D,
) -> ChartResult<()>
where
D: DrawTarget<Color = C>,
{
let style = PrimitiveStyle::with_fill(marker_style.color);
let radius = marker_style.size / 2;
match marker_style.shape {
MarkerShape::Circle => {
Circle::new(
Point::new(center.x - radius as i32, center.y - radius as i32),
marker_style.size,
)
.into_styled(style)
.draw(target)
.map_err(|_| ChartError::RenderingError)?;
}
MarkerShape::Square => {
Rectangle::new(
Point::new(center.x - radius as i32, center.y - radius as i32),
Size::new(marker_style.size, marker_style.size),
)
.into_styled(style)
.draw(target)
.map_err(|_| ChartError::RenderingError)?;
}
MarkerShape::Diamond => {
use crate::render::PrimitiveRenderer;
use crate::style::FillStyle;
let fill_style = FillStyle::solid(marker_style.color);
PrimitiveRenderer::draw_diamond(
center,
marker_style.size,
None,
Some(&fill_style),
target,
)
.map_err(|_| ChartError::RenderingError)?;
}
MarkerShape::Triangle => {
use crate::render::PrimitiveRenderer;
use crate::style::FillStyle;
let fill_style = FillStyle::solid(marker_style.color);
let half_size = marker_style.size as i32 / 2;
let p1 = Point::new(center.x, center.y - half_size);
let p2 = Point::new(center.x - half_size, center.y + half_size);
let p3 = Point::new(center.x + half_size, center.y + half_size);
PrimitiveRenderer::draw_triangle(p1, p2, p3, None, Some(&fill_style), target)
.map_err(|_| ChartError::RenderingError)?;
}
}
Ok(())
}
fn draw_area_fill<D>(
&self,
screen_points: &heapless::Vec<Point, 512>,
fill_color: C,
viewport: Rectangle,
_data_bounds: &DataBounds<f32, f32>,
target: &mut D,
) -> ChartResult<()>
where
D: DrawTarget<Color = C>,
{
if screen_points.len() < 2 {
return Ok(());
}
let chart_area = self.config.margins.apply_to(viewport);
let baseline_y = chart_area.top_left.y + chart_area.size.height as i32 - 1;
use embedded_graphics::primitives::{Line, PrimitiveStyle};
let line_style = PrimitiveStyle::with_stroke(fill_color, 1);
let min_x = screen_points
.iter()
.map(|p| p.x)
.min()
.unwrap_or(chart_area.top_left.x);
let max_x = screen_points
.iter()
.map(|p| p.x)
.max()
.unwrap_or(chart_area.top_left.x);
for x in min_x..=max_x {
if x < chart_area.top_left.x
|| x >= chart_area.top_left.x + chart_area.size.width as i32
{
continue;
}
let mut curve_y = baseline_y;
for window in screen_points.windows(2) {
if let [p1, p2] = window {
if (p1.x <= x && x <= p2.x) || (p2.x <= x && x <= p1.x) {
if p1.x == p2.x {
curve_y = p1.y.min(p2.y);
} else {
let t = (x - p1.x) as f32 / (p2.x - p1.x) as f32;
curve_y = (p1.y as f32 + t * (p2.y - p1.y) as f32) as i32;
}
break;
}
}
}
curve_y = curve_y.clamp(
chart_area.top_left.y,
chart_area.top_left.y + chart_area.size.height as i32 - 1,
);
if curve_y <= baseline_y {
let top_point = Point::new(x, curve_y);
let bottom_point = Point::new(x, baseline_y);
Line::new(top_point, bottom_point)
.into_styled(line_style)
.draw(target)
.map_err(|_| ChartError::RenderingError)?;
}
}
Ok(())
}
}
impl<C: PixelColor> Default for LineChart<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
fn default() -> Self {
Self::new()
}
}
impl<C: PixelColor + 'static> Chart<C> for LineChart<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
type Data = crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>;
type Config = ChartConfig<C>;
fn draw<D>(
&self,
data: &Self::Data,
config: &Self::Config,
viewport: Rectangle,
target: &mut D,
) -> ChartResult<()>
where
D: DrawTarget<Color = C>,
Self::Data: DataSeries,
<Self::Data as DataSeries>::Item: DataPoint,
<<Self::Data as DataSeries>::Item as DataPoint>::X: Into<f32> + Copy + PartialOrd,
<<Self::Data as DataSeries>::Item as DataPoint>::Y: Into<f32> + Copy + PartialOrd,
{
if data.is_empty() {
return Err(ChartError::InsufficientData);
}
let data_bounds = data.bounds()?;
if let Some(bg_color) = config.background_color {
Rectangle::new(viewport.top_left, viewport.size)
.into_styled(PrimitiveStyle::with_fill(bg_color))
.draw(target)
.map_err(|_| ChartError::RenderingError)?;
}
{
let chart_area = config.margins.apply_to(viewport);
if let Some(ref x_axis) = self.x_axis {
x_axis.draw_grid_lines(chart_area, chart_area, target)?;
}
if let Some(ref y_axis) = self.y_axis {
y_axis.draw_grid_lines(chart_area, chart_area, target)?;
}
}
if let Some(ref grid) = self.grid {
let chart_area = config.margins.apply_to(viewport);
grid.draw(chart_area, target)?;
}
let data_to_render = if self.style.smooth && data.len() > 2 {
use crate::math::interpolation::{
CurveInterpolator, InterpolationConfig, InterpolationType,
};
let mut input_points = heapless::Vec::<crate::data::Point2D, 256>::new();
for point in data.iter() {
input_points
.push(point)
.map_err(|_| ChartError::MemoryFull)?;
}
let interpolation_config = InterpolationConfig {
interpolation_type: InterpolationType::CatmullRom,
subdivisions: self.style.smooth_subdivisions,
tension: 0.5,
closed: false,
};
let interpolated =
CurveInterpolator::interpolate(&input_points, &interpolation_config)?;
let mut smooth_data = crate::data::series::StaticDataSeries::new();
for point in interpolated.iter() {
smooth_data
.push(*point)
.map_err(|_| ChartError::MemoryFull)?;
}
smooth_data
} else {
data.clone()
};
let mut screen_points = heapless::Vec::<Point, 512>::new();
for point in data_to_render.iter() {
let screen_point = self.transform_point(&point, &data_bounds, viewport);
screen_points
.push(screen_point)
.map_err(|_| ChartError::MemoryFull)?;
}
if self.style.fill_area {
if let Some(fill_color) = self.style.fill_color {
self.draw_area_fill(&screen_points, fill_color, viewport, &data_bounds, target)?;
}
}
let line_style = PrimitiveStyle::with_stroke(self.style.line_color, self.style.line_width);
for window in screen_points.windows(2) {
if let [p1, p2] = window {
Line::new(*p1, *p2)
.into_styled(line_style)
.draw(target)
.map_err(|_| ChartError::RenderingError)?;
}
}
self.draw_markers(data, &data_bounds, viewport, target)?;
{
let chart_area = config.margins.apply_to(viewport);
if let Some(ref x_axis) = self.x_axis {
x_axis.draw_axis_only(chart_area, target)?;
}
if let Some(ref y_axis) = self.y_axis {
y_axis.draw_axis_only(chart_area, target)?;
}
}
Ok(())
}
}
impl<C: PixelColor> Default for LineChartStyle<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
fn default() -> Self {
Self {
line_color: embedded_graphics::pixelcolor::Rgb565::BLUE.into(),
line_width: 1,
fill_area: false,
fill_color: None,
markers: None,
smooth: false,
smooth_subdivisions: 8,
}
}
}
impl<C: PixelColor> Default for MarkerStyle<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
fn default() -> Self {
Self {
shape: MarkerShape::Circle,
size: 4,
color: embedded_graphics::pixelcolor::Rgb565::RED.into(),
visible: true,
}
}
}
#[derive(Debug)]
pub struct LineChartBuilder<C: PixelColor> {
style: LineChartStyle<C>,
config: ChartConfig<C>,
grid: Option<crate::grid::GridSystem<C>>,
x_axis: Option<crate::axes::LinearAxis<f32, C>>,
y_axis: Option<crate::axes::LinearAxis<f32, C>>,
}
impl<C: PixelColor> LineChartBuilder<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
pub fn new() -> Self {
Self {
style: LineChartStyle::default(),
config: ChartConfig::default(),
grid: None,
x_axis: None,
y_axis: None,
}
}
pub fn line_color(mut self, color: C) -> Self {
self.style.line_color = color;
self
}
pub fn line_width(mut self, width: u32) -> Self {
self.style.line_width = width.clamp(1, 10);
self
}
pub fn fill_area(mut self, color: C) -> Self {
self.style.fill_area = true;
self.style.fill_color = Some(color);
self
}
pub fn with_markers(mut self, marker_style: MarkerStyle<C>) -> Self {
self.style.markers = Some(marker_style);
self
}
pub fn with_title(mut self, title: &str) -> Self {
if let Ok(title_string) = heapless::String::try_from(title) {
self.config.title = Some(title_string);
}
self
}
pub fn background_color(mut self, color: C) -> Self {
self.config.background_color = Some(color);
self
}
pub fn margins(mut self, margins: Margins) -> Self {
self.config.margins = margins;
self
}
pub fn smooth(mut self, smooth: bool) -> Self {
self.style.smooth = smooth;
self
}
pub fn smooth_subdivisions(mut self, subdivisions: u32) -> Self {
self.style.smooth_subdivisions = subdivisions.clamp(2, 16);
self
}
pub fn with_grid(mut self, grid: crate::grid::GridSystem<C>) -> Self {
self.grid = Some(grid);
self
}
pub fn with_x_axis(mut self, axis: crate::axes::LinearAxis<f32, C>) -> Self {
self.x_axis = Some(axis);
self
}
pub fn with_y_axis(mut self, axis: crate::axes::LinearAxis<f32, C>) -> Self {
self.y_axis = Some(axis);
self
}
}
impl<C: PixelColor + 'static> ChartBuilder<C> for LineChartBuilder<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
type Chart = LineChart<C>;
type Error = ChartError;
fn build(self) -> Result<Self::Chart, Self::Error> {
Ok(LineChart {
style: self.style,
config: self.config,
grid: self.grid,
x_axis: self.x_axis,
y_axis: self.y_axis,
})
}
}
impl<C: PixelColor> Default for LineChartBuilder<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use embedded_graphics::pixelcolor::Rgb565;
#[test]
fn test_line_chart_creation() {
let chart: LineChart<Rgb565> = LineChart::new();
assert_eq!(chart.style().line_width, 1);
}
#[test]
fn test_line_chart_builder() {
let chart = LineChart::builder()
.line_color(Rgb565::RED)
.line_width(3)
.build()
.unwrap();
assert_eq!(chart.style().line_color, Rgb565::RED);
assert_eq!(chart.style().line_width, 3);
}
#[test]
fn test_marker_style() {
let marker = MarkerStyle {
shape: MarkerShape::Diamond,
size: 8,
color: Rgb565::GREEN,
visible: true,
};
assert_eq!(marker.shape, MarkerShape::Diamond);
assert_eq!(marker.size, 8);
}
}
impl<C: PixelColor + 'static> AxisChart<C> for LineChart<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
type XAxis = crate::axes::LinearAxis<f32, C>;
type YAxis = crate::axes::LinearAxis<f32, C>;
fn set_x_axis(&mut self, axis: crate::axes::LinearAxis<f32, C>) {
self.x_axis = Some(axis);
}
fn set_y_axis(&mut self, axis: crate::axes::LinearAxis<f32, C>) {
self.y_axis = Some(axis);
}
fn x_axis(&self) -> ChartResult<&crate::axes::LinearAxis<f32, C>> {
self.x_axis.as_ref().ok_or(ChartError::InvalidConfiguration)
}
fn y_axis(&self) -> ChartResult<&crate::axes::LinearAxis<f32, C>> {
self.y_axis.as_ref().ok_or(ChartError::InvalidConfiguration)
}
}
#[cfg(feature = "animations")]
#[derive(Debug)]
pub struct AnimatedLineChart<C: PixelColor> {
base_chart: LineChart<C>,
current_data: Option<crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>>,
}
#[cfg(feature = "animations")]
impl<C: PixelColor + 'static> AnimatedLineChart<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
pub fn new() -> Self {
Self {
base_chart: LineChart::new(),
current_data: None,
}
}
pub fn builder() -> AnimatedLineChartBuilder<C> {
AnimatedLineChartBuilder::new()
}
pub fn set_style(&mut self, style: LineChartStyle<C>) {
self.base_chart.set_style(style);
}
pub fn style(&self) -> &LineChartStyle<C> {
self.base_chart.style()
}
pub fn set_config(&mut self, config: ChartConfig<C>) {
self.base_chart.set_config(config);
}
pub fn config(&self) -> &ChartConfig<C> {
self.base_chart.config()
}
pub fn set_grid(&mut self, grid: Option<crate::grid::GridSystem<C>>) {
self.base_chart.set_grid(grid);
}
pub fn grid(&self) -> Option<&crate::grid::GridSystem<C>> {
self.base_chart.grid()
}
pub fn set_animated_data(
&mut self,
data: Option<crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>>,
) {
self.current_data = data;
}
pub fn animated_data(
&self,
) -> Option<&crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>> {
self.current_data.as_ref()
}
pub fn base_chart(&self) -> &LineChart<C> {
&self.base_chart
}
pub fn base_chart_mut(&mut self) -> &mut LineChart<C> {
&mut self.base_chart
}
pub fn interpolate_with_animator(
animator: &crate::animation::ChartAnimator<
crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>,
>,
progress: crate::animation::Progress,
) -> Option<crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>> {
animator.value_at(progress)
}
}
#[cfg(feature = "animations")]
impl<C: PixelColor + 'static> Default for AnimatedLineChart<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "animations")]
impl<C: PixelColor + 'static> Chart<C> for AnimatedLineChart<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
type Data = crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>;
type Config = ChartConfig<C>;
fn draw<D>(
&self,
data: &Self::Data,
config: &Self::Config,
viewport: Rectangle,
target: &mut D,
) -> ChartResult<()>
where
D: DrawTarget<Color = C>,
Self::Data: crate::data::DataSeries,
<Self::Data as crate::data::DataSeries>::Item: crate::data::DataPoint,
<<Self::Data as crate::data::DataSeries>::Item as crate::data::DataPoint>::X:
Into<f32> + Copy + PartialOrd,
<<Self::Data as crate::data::DataSeries>::Item as crate::data::DataPoint>::Y:
Into<f32> + Copy + PartialOrd,
{
if let Some(ref animated_data) = self.current_data {
self.base_chart
.draw(animated_data, config, viewport, target)
} else {
self.base_chart.draw(data, config, viewport, target)
}
}
}
#[cfg(feature = "animations")]
impl<C: PixelColor + 'static> crate::chart::traits::AnimatedChart<C> for AnimatedLineChart<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
type AnimatedData = crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256>;
fn draw_animated<D>(
&self,
data: &Self::Data,
config: &Self::Config,
viewport: Rectangle,
target: &mut D,
_progress: crate::animation::Progress,
) -> ChartResult<()>
where
D: DrawTarget<Color = C>,
{
self.base_chart.draw(data, config, viewport, target)
}
fn create_transition_animator(
&self,
from_data: Self::AnimatedData,
to_data: Self::AnimatedData,
easing: crate::animation::EasingFunction,
) -> crate::animation::ChartAnimator<Self::AnimatedData> {
crate::animation::ChartAnimator::new(from_data, to_data, easing)
}
fn extract_animated_data(&self, data: &Self::Data) -> ChartResult<Self::AnimatedData> {
Ok(data.clone())
}
}
#[cfg(feature = "animations")]
#[derive(Debug)]
pub struct AnimatedLineChartBuilder<C: PixelColor> {
base_builder: LineChartBuilder<C>,
frame_rate: u32,
}
#[cfg(feature = "animations")]
impl<C: PixelColor + 'static> AnimatedLineChartBuilder<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
pub fn new() -> Self {
Self {
base_builder: LineChartBuilder::new(),
frame_rate: 60,
}
}
pub fn frame_rate(mut self, fps: u32) -> Self {
self.frame_rate = fps.clamp(1, 120);
self
}
pub fn line_color(mut self, color: C) -> Self {
self.base_builder = self.base_builder.line_color(color);
self
}
pub fn line_width(mut self, width: u32) -> Self {
self.base_builder = self.base_builder.line_width(width);
self
}
pub fn fill_area(mut self, color: C) -> Self {
self.base_builder = self.base_builder.fill_area(color);
self
}
pub fn with_markers(mut self, marker_style: MarkerStyle<C>) -> Self {
self.base_builder = self.base_builder.with_markers(marker_style);
self
}
pub fn with_title(mut self, title: &str) -> Self {
self.base_builder = self.base_builder.with_title(title);
self
}
pub fn background_color(mut self, color: C) -> Self {
self.base_builder = self.base_builder.background_color(color);
self
}
pub fn margins(mut self, margins: Margins) -> Self {
self.base_builder = self.base_builder.margins(margins);
self
}
pub fn smooth(mut self, smooth: bool) -> Self {
self.base_builder = self.base_builder.smooth(smooth);
self
}
pub fn smooth_subdivisions(mut self, subdivisions: u32) -> Self {
self.base_builder = self.base_builder.smooth_subdivisions(subdivisions);
self
}
pub fn with_grid(mut self, grid: crate::grid::GridSystem<C>) -> Self {
self.base_builder = self.base_builder.with_grid(grid);
self
}
pub fn with_x_axis(mut self, axis: crate::axes::LinearAxis<f32, C>) -> Self {
self.base_builder = self.base_builder.with_x_axis(axis);
self
}
pub fn with_y_axis(mut self, axis: crate::axes::LinearAxis<f32, C>) -> Self {
self.base_builder = self.base_builder.with_y_axis(axis);
self
}
pub fn build(self) -> ChartResult<AnimatedLineChart<C>> {
let base_chart = self.base_builder.build()?;
Ok(AnimatedLineChart {
base_chart,
current_data: None,
})
}
}
#[cfg(feature = "animations")]
impl<C: PixelColor + 'static> Default for AnimatedLineChartBuilder<C>
where
C: From<embedded_graphics::pixelcolor::Rgb565>,
{
fn default() -> Self {
Self::new()
}
}