embedded_charts/axes/
traits.rs1use crate::axes::{AxisOrientation, AxisPosition};
4use crate::error::ChartResult;
5use crate::math::{Math, NumericConversion};
6use embedded_graphics::{prelude::*, primitives::Rectangle};
7
8pub trait Axis<T, C: PixelColor> {
10 type TickGenerator: TickGenerator<T>;
12 type Style;
14
15 fn min(&self) -> T;
17
18 fn max(&self) -> T;
20
21 fn orientation(&self) -> AxisOrientation;
23
24 fn position(&self) -> AxisPosition;
26
27 fn transform_value(&self, value: T, viewport: Rectangle) -> i32;
33
34 fn inverse_transform(&self, coordinate: i32, viewport: Rectangle) -> T;
40
41 fn tick_generator(&self) -> &Self::TickGenerator;
43
44 fn style(&self) -> &Self::Style;
46
47 fn draw<D>(&self, viewport: Rectangle, target: &mut D) -> ChartResult<()>
53 where
54 D: DrawTarget<Color = C>;
55
56 fn required_space(&self) -> u32;
58}
59
60pub trait TickGenerator<T> {
62 fn generate_ticks(&self, min: T, max: T, max_ticks: usize) -> heapless::Vec<Tick<T>, 32>;
69
70 fn preferred_tick_count(&self) -> usize;
72
73 fn set_preferred_tick_count(&mut self, count: usize);
75}
76
77pub trait AxisRenderer<C: PixelColor> {
79 fn draw_axis_line<D>(
87 &self,
88 start: Point,
89 end: Point,
90 style: &crate::style::LineStyle<C>,
91 target: &mut D,
92 ) -> ChartResult<()>
93 where
94 D: DrawTarget<Color = C>;
95
96 fn draw_tick<D>(
105 &self,
106 position: Point,
107 length: u32,
108 orientation: AxisOrientation,
109 style: &crate::style::LineStyle<C>,
110 target: &mut D,
111 ) -> ChartResult<()>
112 where
113 D: DrawTarget<Color = C>;
114
115 fn draw_grid_line<D>(
123 &self,
124 start: Point,
125 end: Point,
126 style: &crate::style::LineStyle<C>,
127 target: &mut D,
128 ) -> ChartResult<()>
129 where
130 D: DrawTarget<Color = C>;
131
132 fn draw_label<D>(&self, text: &str, position: Point, target: &mut D) -> ChartResult<()>
139 where
140 D: DrawTarget<Color = C>;
141}
142
143#[derive(Debug, Clone, PartialEq)]
145pub struct Tick<T> {
146 pub value: T,
148 pub is_major: bool,
150 pub label: Option<heapless::String<16>>,
152}
153
154impl<T> Tick<T> {
155 pub fn major(value: T, label: &str) -> Self {
157 Self {
158 value,
159 is_major: true,
160 label: heapless::String::try_from(label).ok(),
161 }
162 }
163
164 pub fn minor(value: T) -> Self {
166 Self {
167 value,
168 is_major: false,
169 label: None,
170 }
171 }
172
173 pub fn major_unlabeled(value: T) -> Self {
175 Self {
176 value,
177 is_major: true,
178 label: None,
179 }
180 }
181}
182
183pub trait AxisValue: Copy + PartialOrd + core::fmt::Display {
185 fn to_f32(self) -> f32;
187
188 fn from_f32(value: f32) -> Self;
190
191 fn nice_step(range: Self) -> Self;
193
194 fn format(&self) -> heapless::String<16>;
196}
197
198impl AxisValue for f32 {
199 fn to_f32(self) -> f32 {
200 self
201 }
202
203 fn from_f32(value: f32) -> Self {
204 value
205 }
206
207 fn nice_step(range: Self) -> Self {
208 let range_num = range.to_number();
209 let abs_range = Math::abs(range_num);
210 let magnitude = Math::floor(Math::log10(abs_range));
211 let ten = 10.0f32.to_number();
212 let normalized = range_num / Math::pow(ten, magnitude);
213
214 let one = 1.0f32.to_number();
215 let two = 2.0f32.to_number();
216 let five = 5.0f32.to_number();
217 let ten_norm = 10.0f32.to_number();
218
219 let nice_normalized = if normalized <= one {
220 one
221 } else if normalized <= two {
222 two
223 } else if normalized <= five {
224 five
225 } else {
226 ten_norm
227 };
228
229 let result = if magnitude >= 0.0.to_number() && magnitude <= 10.0.to_number() {
230 nice_normalized * Math::pow(ten, magnitude)
231 } else {
232 nice_normalized
234 };
235 f32::from_number(result)
236 }
237
238 fn format(&self) -> heapless::String<16> {
239 let self_num = self.to_number();
241 let fract_part = self_num - Math::floor(self_num);
242 let zero = 0.0f32.to_number();
243
244 if fract_part == zero {
245 let int_val = *self as i32;
247 let mut result = heapless::String::new();
248 if int_val == 0 {
249 let _ = result.push('0');
250 } else {
251 let mut val = int_val.abs();
252 let mut digits = heapless::Vec::<u8, 16>::new();
253 while val > 0 {
254 let _ = digits.push((val % 10) as u8 + b'0');
255 val /= 10;
256 }
257 if int_val < 0 {
258 let _ = result.push('-');
259 }
260 for &digit in digits.iter().rev() {
261 let _ = result.push(digit as char);
262 }
263 }
264 result
265 } else {
266 let int_val = *self as i32;
268 let mut result = heapless::String::new();
269 if int_val == 0 {
270 let _ = result.push('0');
271 } else {
272 let mut val = int_val.abs();
273 let mut digits = heapless::Vec::<u8, 16>::new();
274 while val > 0 {
275 let _ = digits.push((val % 10) as u8 + b'0');
276 val /= 10;
277 }
278 if int_val < 0 {
279 let _ = result.push('-');
280 }
281 for &digit in digits.iter().rev() {
282 let _ = result.push(digit as char);
283 }
284 }
285 result
286 }
287 }
288}
289
290impl AxisValue for i32 {
291 fn to_f32(self) -> f32 {
292 self as f32
293 }
294
295 fn from_f32(value: f32) -> Self {
296 let value_num = value.to_number();
297 let rounded = Math::floor(value_num + 0.5f32.to_number());
298 f32::from_number(rounded) as i32
299 }
300
301 fn nice_step(range: Self) -> Self {
302 let range_f32 = range.abs() as f32;
303 let range_num = range_f32.to_number();
304 let magnitude = Math::floor(Math::log10(range_num));
305 let ten = 10.0f32.to_number();
306 let normalized = range_num / Math::pow(ten, magnitude);
307
308 let one = 1.0f32.to_number();
309 let two = 2.0f32.to_number();
310 let five = 5.0f32.to_number();
311 let ten_norm = 10.0f32.to_number();
312
313 let nice_normalized = if normalized <= one {
314 one
315 } else if normalized <= two {
316 two
317 } else if normalized <= five {
318 five
319 } else {
320 ten_norm
321 };
322
323 let result = if magnitude >= 0.0.to_number() && magnitude <= 10.0.to_number() {
324 nice_normalized * Math::pow(ten, magnitude)
325 } else {
326 nice_normalized
328 };
329 let rounded = Math::floor(result + 0.5f32.to_number());
330 f32::from_number(rounded) as i32
331 }
332
333 fn format(&self) -> heapless::String<16> {
334 let mut result = heapless::String::new();
335 if *self == 0 {
336 let _ = result.push('0');
337 } else {
338 let mut val = self.abs();
339 let mut digits = heapless::Vec::<u8, 16>::new();
340 while val > 0 {
341 let _ = digits.push((val % 10) as u8 + b'0');
342 val /= 10;
343 }
344 if *self < 0 {
345 let _ = result.push('-');
346 }
347 for &digit in digits.iter().rev() {
348 let _ = result.push(digit as char);
349 }
350 }
351 result
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 #[test]
360 fn test_tick_creation() {
361 let major_tick = Tick::major(5.0, "5.0");
362 assert!(major_tick.is_major);
363 assert_eq!(major_tick.value, 5.0);
364 assert!(major_tick.label.is_some());
365
366 let minor_tick = Tick::minor(2.5);
367 assert!(!minor_tick.is_major);
368 assert_eq!(minor_tick.value, 2.5);
369 assert!(minor_tick.label.is_none());
370 }
371
372 #[test]
373 #[cfg(not(any(feature = "fixed-point", feature = "integer-math")))] fn test_axis_value_f32() {
375 let value = core::f32::consts::PI;
376 assert_eq!(value.to_f32(), core::f32::consts::PI);
377 assert_eq!(f32::from_f32(core::f32::consts::PI), core::f32::consts::PI);
378
379 let step = f32::nice_step(7.3);
380 assert!(step > 0.0);
381 }
382
383 #[test]
384 #[cfg(not(any(feature = "fixed-point", feature = "integer-math")))] fn test_axis_value_i32() {
386 let value = 42i32;
387 assert_eq!(value.to_f32(), 42.0);
388 assert_eq!(i32::from_f32(42.7), 43);
389
390 let step = i32::nice_step(73);
391 assert!(step > 0);
392 }
393}