1use crate::axes::{
4 linear::LinearAxis,
5 style::AxisStyle,
6 ticks::{CustomTickGenerator, LinearTickGenerator},
7 traits::{AxisValue, TickGenerator},
8 AxisOrientation, AxisPosition,
9};
10use crate::error::ChartError;
11use embedded_graphics::prelude::*;
12
13#[derive(Debug)]
15pub struct LinearAxisBuilder<T, C: PixelColor> {
16 min: Option<T>,
17 max: Option<T>,
18 orientation: AxisOrientation,
19 position: AxisPosition,
20 tick_generator: LinearTickGenerator,
21 style: AxisStyle<C>,
22 show_line: bool,
23 show_ticks: bool,
24 show_labels: bool,
25 show_grid: bool,
26}
27
28impl<T, C> LinearAxisBuilder<T, C>
29where
30 T: AxisValue,
31 C: PixelColor + From<embedded_graphics::pixelcolor::Rgb565>,
32{
33 pub fn new(orientation: AxisOrientation, position: AxisPosition) -> Self {
35 Self {
36 min: None,
37 max: None,
38 orientation,
39 position,
40 tick_generator: LinearTickGenerator::new(5),
41 style: AxisStyle::new(),
42 show_line: true,
43 show_ticks: true,
44 show_labels: true,
45 show_grid: false,
46 }
47 }
48
49 pub fn range(mut self, min: T, max: T) -> Self {
51 self.min = Some(min);
52 self.max = Some(max);
53 self
54 }
55
56 pub fn min(mut self, min: T) -> Self {
58 self.min = Some(min);
59 self
60 }
61
62 pub fn max(mut self, max: T) -> Self {
64 self.max = Some(max);
65 self
66 }
67
68 pub fn tick_count(mut self, count: usize) -> Self {
70 <LinearTickGenerator as TickGenerator<T>>::set_preferred_tick_count(
71 &mut self.tick_generator,
72 count,
73 );
74 self
75 }
76
77 pub fn with_minor_ticks(mut self, ratio: usize) -> Self {
79 self.tick_generator = self.tick_generator.with_minor_ticks(ratio);
80 self
81 }
82
83 pub fn without_minor_ticks(mut self) -> Self {
85 self.tick_generator = self.tick_generator.without_minor_ticks();
86 self
87 }
88
89 pub fn style(mut self, style: AxisStyle<C>) -> Self {
91 self.style = style;
92 self
93 }
94
95 pub fn minimal_style(mut self) -> Self {
97 self.style = AxisStyle::minimal();
98 self
99 }
100
101 pub fn professional_style(mut self) -> Self {
103 self.style = AxisStyle::professional();
104 self
105 }
106
107 pub fn show_line(mut self, show: bool) -> Self {
109 self.show_line = show;
110 self
111 }
112
113 pub fn show_ticks(mut self, show: bool) -> Self {
115 self.show_ticks = show;
116 self
117 }
118
119 pub fn show_labels(mut self, show: bool) -> Self {
121 self.show_labels = show;
122 self
123 }
124
125 pub fn show_grid(mut self, show: bool) -> Self {
127 self.show_grid = show;
128 self
129 }
130
131 pub fn build(self) -> Result<LinearAxis<T, C>, ChartError> {
133 let min = self.min.ok_or(ChartError::ConfigurationError)?;
134 let max = self.max.ok_or(ChartError::ConfigurationError)?;
135
136 if min.to_f32() >= max.to_f32() {
137 return Err(ChartError::ConfigurationError);
138 }
139
140 let axis = LinearAxis::new(min, max, self.orientation, self.position)
141 .with_tick_generator(self.tick_generator)
142 .with_style(self.style)
143 .show_line(self.show_line)
144 .show_ticks(self.show_ticks)
145 .show_labels(self.show_labels)
146 .show_grid(self.show_grid);
147
148 Ok(axis)
149 }
150}
151
152#[derive(Debug)]
154pub struct CustomAxisBuilder<T, C: PixelColor> {
155 min: Option<T>,
156 max: Option<T>,
157 orientation: AxisOrientation,
158 position: AxisPosition,
159 tick_generator: CustomTickGenerator<T>,
160 style: AxisStyle<C>,
161 show_line: bool,
162 show_ticks: bool,
163 show_labels: bool,
164 show_grid: bool,
165}
166
167impl<T, C> CustomAxisBuilder<T, C>
168where
169 T: AxisValue,
170 C: PixelColor + From<embedded_graphics::pixelcolor::Rgb565>,
171{
172 pub fn new(orientation: AxisOrientation, position: AxisPosition) -> Self {
174 Self {
175 min: None,
176 max: None,
177 orientation,
178 position,
179 tick_generator: CustomTickGenerator::new(),
180 style: AxisStyle::new(),
181 show_line: true,
182 show_ticks: true,
183 show_labels: true,
184 show_grid: false,
185 }
186 }
187
188 pub fn range(mut self, min: T, max: T) -> Self {
190 self.min = Some(min);
191 self.max = Some(max);
192 self
193 }
194
195 pub fn add_major_tick(mut self, value: T, label: &str) -> Self {
197 self.tick_generator = self.tick_generator.add_major_tick(value, label);
198 self
199 }
200
201 pub fn add_minor_tick(mut self, value: T) -> Self {
203 self.tick_generator = self.tick_generator.add_minor_tick(value);
204 self
205 }
206
207 pub fn style(mut self, style: AxisStyle<C>) -> Self {
209 self.style = style;
210 self
211 }
212
213 pub fn show_line(mut self, show: bool) -> Self {
215 self.show_line = show;
216 self
217 }
218
219 pub fn show_ticks(mut self, show: bool) -> Self {
221 self.show_ticks = show;
222 self
223 }
224
225 pub fn show_labels(mut self, show: bool) -> Self {
227 self.show_labels = show;
228 self
229 }
230
231 pub fn show_grid(mut self, show: bool) -> Self {
233 self.show_grid = show;
234 self
235 }
236
237 pub fn build(self) -> Result<LinearAxis<T, C>, ChartError> {
239 let min = self.min.ok_or(ChartError::ConfigurationError)?;
240 let max = self.max.ok_or(ChartError::ConfigurationError)?;
241
242 if min.to_f32() >= max.to_f32() {
243 return Err(ChartError::ConfigurationError);
244 }
245
246 let axis = LinearAxis::new(min, max, self.orientation, self.position)
248 .with_style(self.style)
249 .show_line(self.show_line)
250 .show_ticks(self.show_ticks)
251 .show_labels(self.show_labels)
252 .show_grid(self.show_grid);
253
254 Ok(axis)
258 }
259}
260
261pub mod presets {
263 use super::*;
264 use embedded_graphics::pixelcolor::Rgb565;
265
266 pub fn x_axis_bottom<T: AxisValue>(min: T, max: T) -> LinearAxisBuilder<T, Rgb565> {
268 LinearAxisBuilder::new(AxisOrientation::Horizontal, AxisPosition::Bottom).range(min, max)
269 }
270
271 pub fn y_axis_left<T: AxisValue>(min: T, max: T) -> LinearAxisBuilder<T, Rgb565> {
273 LinearAxisBuilder::new(AxisOrientation::Vertical, AxisPosition::Left).range(min, max)
274 }
275
276 pub fn minimal_x_axis<T: AxisValue>(min: T, max: T) -> LinearAxisBuilder<T, Rgb565> {
278 x_axis_bottom(min, max)
279 .minimal_style()
280 .tick_count(3)
281 .without_minor_ticks()
282 }
283
284 pub fn minimal_y_axis<T: AxisValue>(min: T, max: T) -> LinearAxisBuilder<T, Rgb565> {
286 y_axis_left(min, max)
287 .minimal_style()
288 .tick_count(3)
289 .without_minor_ticks()
290 }
291
292 pub fn professional_x_axis<T: AxisValue>(min: T, max: T) -> LinearAxisBuilder<T, Rgb565> {
294 x_axis_bottom(min, max)
295 .professional_style()
296 .show_grid(true)
297 .with_minor_ticks(4)
298 }
299
300 pub fn professional_y_axis<T: AxisValue>(min: T, max: T) -> LinearAxisBuilder<T, Rgb565> {
302 y_axis_left(min, max)
303 .professional_style()
304 .show_grid(true)
305 .with_minor_ticks(4)
306 }
307
308 pub fn time_axis<T: AxisValue>(start: T, end: T) -> LinearAxisBuilder<T, Rgb565> {
310 x_axis_bottom(start, end).tick_count(6).show_grid(true)
311 }
312
313 pub fn percentage_axis() -> LinearAxisBuilder<f32, Rgb565> {
315 y_axis_left(0.0, 100.0).tick_count(6).show_grid(true)
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322 use crate::axes::traits::Axis;
323 use embedded_graphics::pixelcolor::Rgb565;
324
325 #[test]
326 fn test_linear_axis_builder() {
327 let axis = LinearAxisBuilder::<f32, Rgb565>::new(
328 AxisOrientation::Horizontal,
329 AxisPosition::Bottom,
330 )
331 .range(0.0f32, 10.0f32)
332 .tick_count(5)
333 .show_grid(true)
334 .build()
335 .unwrap();
336
337 assert_eq!(axis.min(), 0.0);
338 assert_eq!(axis.max(), 10.0);
339 assert_eq!(axis.orientation(), AxisOrientation::Horizontal);
340 }
341
342 #[test]
343 fn test_builder_validation() {
344 let result = LinearAxisBuilder::<f32, Rgb565>::new(
346 AxisOrientation::Horizontal,
347 AxisPosition::Bottom,
348 )
349 .tick_count(5)
350 .build();
351 assert!(result.is_err());
352
353 let result = LinearAxisBuilder::<f32, Rgb565>::new(
355 AxisOrientation::Horizontal,
356 AxisPosition::Bottom,
357 )
358 .range(10.0f32, 5.0f32)
359 .build();
360 assert!(result.is_err());
361 }
362
363 #[test]
364 fn test_custom_axis_builder() {
365 let axis =
366 CustomAxisBuilder::<f32, Rgb565>::new(AxisOrientation::Vertical, AxisPosition::Left)
367 .range(0.0f32, 100.0f32)
368 .add_major_tick(0.0, "0%")
369 .add_major_tick(50.0, "50%")
370 .add_major_tick(100.0, "100%")
371 .add_minor_tick(25.0)
372 .add_minor_tick(75.0)
373 .build()
374 .unwrap();
375
376 assert_eq!(axis.min(), 0.0);
377 assert_eq!(axis.max(), 100.0);
378 }
379
380 #[test]
381 fn test_preset_axes() {
382 let x_axis = presets::x_axis_bottom(0.0f32, 10.0f32).build().unwrap();
383 assert_eq!(x_axis.orientation(), AxisOrientation::Horizontal);
384 assert_eq!(x_axis.position(), AxisPosition::Bottom);
385
386 let y_axis = presets::y_axis_left(-5.0f32, 5.0f32).build().unwrap();
387 assert_eq!(y_axis.orientation(), AxisOrientation::Vertical);
388 assert_eq!(y_axis.position(), AxisPosition::Left);
389 }
390
391 #[test]
392 fn test_minimal_preset() {
393 let _axis: LinearAxis<f32, Rgb565> =
394 presets::minimal_x_axis(0.0f32, 100.0f32).build().unwrap();
395 }
398
399 #[test]
400 fn test_professional_preset() {
401 let _axis = presets::professional_y_axis(0.0f32, 1000.0f32)
402 .build()
403 .unwrap();
404 }
407}