use crate::axes::{
linear::LinearAxis,
style::AxisStyle,
ticks::{CustomTickGenerator, LinearTickGenerator},
traits::{AxisValue, TickGenerator},
AxisOrientation, AxisPosition,
};
use crate::error::ChartError;
use embedded_graphics::prelude::*;
#[derive(Debug)]
pub struct LinearAxisBuilder<T, C: PixelColor> {
min: Option<T>,
max: Option<T>,
orientation: AxisOrientation,
position: AxisPosition,
tick_generator: LinearTickGenerator,
style: AxisStyle<C>,
show_line: bool,
show_ticks: bool,
show_labels: bool,
show_grid: bool,
}
impl<T, C> LinearAxisBuilder<T, C>
where
T: AxisValue,
C: PixelColor + From<embedded_graphics::pixelcolor::Rgb565>,
{
pub fn new(orientation: AxisOrientation, position: AxisPosition) -> Self {
Self {
min: None,
max: None,
orientation,
position,
tick_generator: LinearTickGenerator::new(5),
style: AxisStyle::new(),
show_line: true,
show_ticks: true,
show_labels: true,
show_grid: false,
}
}
pub fn range(mut self, min: T, max: T) -> Self {
self.min = Some(min);
self.max = Some(max);
self
}
pub fn min(mut self, min: T) -> Self {
self.min = Some(min);
self
}
pub fn max(mut self, max: T) -> Self {
self.max = Some(max);
self
}
pub fn tick_count(mut self, count: usize) -> Self {
<LinearTickGenerator as TickGenerator<T>>::set_preferred_tick_count(
&mut self.tick_generator,
count,
);
self
}
pub fn with_minor_ticks(mut self, ratio: usize) -> Self {
self.tick_generator = self.tick_generator.with_minor_ticks(ratio);
self
}
pub fn without_minor_ticks(mut self) -> Self {
self.tick_generator = self.tick_generator.without_minor_ticks();
self
}
pub fn style(mut self, style: AxisStyle<C>) -> Self {
self.style = style;
self
}
pub fn minimal_style(mut self) -> Self {
self.style = AxisStyle::minimal();
self
}
pub fn professional_style(mut self) -> Self {
self.style = AxisStyle::professional();
self
}
pub fn show_line(mut self, show: bool) -> Self {
self.show_line = show;
self
}
pub fn show_ticks(mut self, show: bool) -> Self {
self.show_ticks = show;
self
}
pub fn show_labels(mut self, show: bool) -> Self {
self.show_labels = show;
self
}
pub fn show_grid(mut self, show: bool) -> Self {
self.show_grid = show;
self
}
pub fn build(self) -> Result<LinearAxis<T, C>, ChartError> {
let min = self.min.ok_or(ChartError::ConfigurationError)?;
let max = self.max.ok_or(ChartError::ConfigurationError)?;
if min.to_f32() >= max.to_f32() {
return Err(ChartError::ConfigurationError);
}
let axis = LinearAxis::new(min, max, self.orientation, self.position)
.with_tick_generator(self.tick_generator)
.with_style(self.style)
.show_line(self.show_line)
.show_ticks(self.show_ticks)
.show_labels(self.show_labels)
.show_grid(self.show_grid);
Ok(axis)
}
}
#[derive(Debug)]
pub struct CustomAxisBuilder<T, C: PixelColor> {
min: Option<T>,
max: Option<T>,
orientation: AxisOrientation,
position: AxisPosition,
tick_generator: CustomTickGenerator<T>,
style: AxisStyle<C>,
show_line: bool,
show_ticks: bool,
show_labels: bool,
show_grid: bool,
}
impl<T, C> CustomAxisBuilder<T, C>
where
T: AxisValue,
C: PixelColor + From<embedded_graphics::pixelcolor::Rgb565>,
{
pub fn new(orientation: AxisOrientation, position: AxisPosition) -> Self {
Self {
min: None,
max: None,
orientation,
position,
tick_generator: CustomTickGenerator::new(),
style: AxisStyle::new(),
show_line: true,
show_ticks: true,
show_labels: true,
show_grid: false,
}
}
pub fn range(mut self, min: T, max: T) -> Self {
self.min = Some(min);
self.max = Some(max);
self
}
pub fn add_major_tick(mut self, value: T, label: &str) -> Self {
self.tick_generator = self.tick_generator.add_major_tick(value, label);
self
}
pub fn add_minor_tick(mut self, value: T) -> Self {
self.tick_generator = self.tick_generator.add_minor_tick(value);
self
}
pub fn style(mut self, style: AxisStyle<C>) -> Self {
self.style = style;
self
}
pub fn show_line(mut self, show: bool) -> Self {
self.show_line = show;
self
}
pub fn show_ticks(mut self, show: bool) -> Self {
self.show_ticks = show;
self
}
pub fn show_labels(mut self, show: bool) -> Self {
self.show_labels = show;
self
}
pub fn show_grid(mut self, show: bool) -> Self {
self.show_grid = show;
self
}
pub fn build(self) -> Result<LinearAxis<T, C>, ChartError> {
let min = self.min.ok_or(ChartError::ConfigurationError)?;
let max = self.max.ok_or(ChartError::ConfigurationError)?;
if min.to_f32() >= max.to_f32() {
return Err(ChartError::ConfigurationError);
}
let axis = LinearAxis::new(min, max, self.orientation, self.position)
.with_style(self.style)
.show_line(self.show_line)
.show_ticks(self.show_ticks)
.show_labels(self.show_labels)
.show_grid(self.show_grid);
Ok(axis)
}
}
pub mod presets {
use super::*;
use embedded_graphics::pixelcolor::Rgb565;
pub fn x_axis_bottom<T: AxisValue>(min: T, max: T) -> LinearAxisBuilder<T, Rgb565> {
LinearAxisBuilder::new(AxisOrientation::Horizontal, AxisPosition::Bottom).range(min, max)
}
pub fn y_axis_left<T: AxisValue>(min: T, max: T) -> LinearAxisBuilder<T, Rgb565> {
LinearAxisBuilder::new(AxisOrientation::Vertical, AxisPosition::Left).range(min, max)
}
pub fn minimal_x_axis<T: AxisValue>(min: T, max: T) -> LinearAxisBuilder<T, Rgb565> {
x_axis_bottom(min, max)
.minimal_style()
.tick_count(3)
.without_minor_ticks()
}
pub fn minimal_y_axis<T: AxisValue>(min: T, max: T) -> LinearAxisBuilder<T, Rgb565> {
y_axis_left(min, max)
.minimal_style()
.tick_count(3)
.without_minor_ticks()
}
pub fn professional_x_axis<T: AxisValue>(min: T, max: T) -> LinearAxisBuilder<T, Rgb565> {
x_axis_bottom(min, max)
.professional_style()
.show_grid(true)
.with_minor_ticks(4)
}
pub fn professional_y_axis<T: AxisValue>(min: T, max: T) -> LinearAxisBuilder<T, Rgb565> {
y_axis_left(min, max)
.professional_style()
.show_grid(true)
.with_minor_ticks(4)
}
pub fn time_axis<T: AxisValue>(start: T, end: T) -> LinearAxisBuilder<T, Rgb565> {
x_axis_bottom(start, end).tick_count(6).show_grid(true)
}
pub fn percentage_axis() -> LinearAxisBuilder<f32, Rgb565> {
y_axis_left(0.0, 100.0).tick_count(6).show_grid(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::axes::traits::Axis;
use embedded_graphics::pixelcolor::Rgb565;
#[test]
fn test_linear_axis_builder() {
let axis = LinearAxisBuilder::<f32, Rgb565>::new(
AxisOrientation::Horizontal,
AxisPosition::Bottom,
)
.range(0.0f32, 10.0f32)
.tick_count(5)
.show_grid(true)
.build()
.unwrap();
assert_eq!(axis.min(), 0.0);
assert_eq!(axis.max(), 10.0);
assert_eq!(axis.orientation(), AxisOrientation::Horizontal);
}
#[test]
fn test_builder_validation() {
let result = LinearAxisBuilder::<f32, Rgb565>::new(
AxisOrientation::Horizontal,
AxisPosition::Bottom,
)
.tick_count(5)
.build();
assert!(result.is_err());
let result = LinearAxisBuilder::<f32, Rgb565>::new(
AxisOrientation::Horizontal,
AxisPosition::Bottom,
)
.range(10.0f32, 5.0f32)
.build();
assert!(result.is_err());
}
#[test]
fn test_custom_axis_builder() {
let axis =
CustomAxisBuilder::<f32, Rgb565>::new(AxisOrientation::Vertical, AxisPosition::Left)
.range(0.0f32, 100.0f32)
.add_major_tick(0.0, "0%")
.add_major_tick(50.0, "50%")
.add_major_tick(100.0, "100%")
.add_minor_tick(25.0)
.add_minor_tick(75.0)
.build()
.unwrap();
assert_eq!(axis.min(), 0.0);
assert_eq!(axis.max(), 100.0);
}
#[test]
fn test_preset_axes() {
let x_axis = presets::x_axis_bottom(0.0f32, 10.0f32).build().unwrap();
assert_eq!(x_axis.orientation(), AxisOrientation::Horizontal);
assert_eq!(x_axis.position(), AxisPosition::Bottom);
let y_axis = presets::y_axis_left(-5.0f32, 5.0f32).build().unwrap();
assert_eq!(y_axis.orientation(), AxisOrientation::Vertical);
assert_eq!(y_axis.position(), AxisPosition::Left);
}
#[test]
fn test_minimal_preset() {
let _axis: LinearAxis<f32, Rgb565> =
presets::minimal_x_axis(0.0f32, 100.0f32).build().unwrap();
}
#[test]
fn test_professional_preset() {
let _axis = presets::professional_y_axis(0.0f32, 1000.0f32)
.build()
.unwrap();
}
}