1use crate::axes::traits::{AxisValue, Tick, TickGenerator};
4use crate::math::{Math, NumericConversion};
5use heapless::Vec;
6
7#[derive(Debug, Clone)]
9pub struct LinearTickGenerator {
10 preferred_count: usize,
12 include_minor_ticks: bool,
14 minor_tick_ratio: usize,
16}
17
18impl LinearTickGenerator {
19 pub fn new(preferred_count: usize) -> Self {
21 Self {
22 preferred_count: preferred_count.clamp(2, 20),
23 include_minor_ticks: false,
24 minor_tick_ratio: 4,
25 }
26 }
27
28 pub fn with_minor_ticks(mut self, ratio: usize) -> Self {
33 self.include_minor_ticks = true;
34 self.minor_tick_ratio = ratio.clamp(1, 10);
35 self
36 }
37
38 pub fn without_minor_ticks(mut self) -> Self {
40 self.include_minor_ticks = false;
41 self
42 }
43
44 fn calculate_nice_step<T: AxisValue>(min: T, max: T, target_count: usize) -> T {
46 let min_f32 = min.to_f32();
47 let max_f32 = max.to_f32();
48 let range = max_f32 - min_f32;
49
50 if target_count <= 1 {
52 return T::from_f32(range.max(1.0)); }
54
55 if range <= 0.0 || !range.is_finite() {
56 return T::from_f32(1.0); }
58
59 let rough_step = range / (target_count - 1) as f32;
60
61 if rough_step < 1e-10 {
63 return T::from_f32(1e-6); }
65
66 let rough_step_num = rough_step.to_number();
68 let magnitude = Math::floor(Math::log10(rough_step_num));
69 let ten = 10.0f32.to_number();
70 let normalized_step = rough_step_num / Math::pow(ten, magnitude);
71
72 let one = 1.0f32.to_number();
74 let two = 2.0f32.to_number();
75 let five = 5.0f32.to_number();
76 let ten_norm = 10.0f32.to_number();
77
78 let nice_normalized = if normalized_step <= one {
79 one
80 } else if normalized_step <= two {
81 two
82 } else if normalized_step <= five {
83 five
84 } else {
85 ten_norm
86 };
87
88 let result = if magnitude >= 0.0.to_number() && magnitude <= 10.0.to_number() {
89 nice_normalized * Math::pow(ten, magnitude)
90 } else {
91 nice_normalized
93 };
94 let step_f32 = f32::from_number(result);
95
96 if step_f32 <= 0.0 || !step_f32.is_finite() {
98 return T::from_f32(range / target_count as f32); }
100
101 T::from_f32(step_f32)
102 }
103
104 fn generate_major_ticks<T: AxisValue>(&self, min: T, max: T) -> Vec<Tick<T>, 32> {
106 let mut ticks = Vec::new();
107
108 let step = Self::calculate_nice_step(min, max, self.preferred_count);
109 let step_f32 = step.to_f32();
110
111 if step_f32 <= 0.0 || step_f32 < 1e-10 || !step_f32.is_finite() {
113 let label_min = min.format();
115 let label_max = max.format();
116 let _ = ticks.push(Tick::major(min, label_min.as_str()));
117 if min.to_f32() != max.to_f32() {
118 let _ = ticks.push(Tick::major(max, label_max.as_str()));
119 }
120 return ticks;
121 }
122
123 let first_tick_value = {
125 let min_f32 = min.to_f32();
126 let min_num = min_f32.to_number();
127 let step_num = step_f32.to_number();
128 let first_tick_num = Math::floor(min_num / step_num) * step_num;
129 let first_tick_f32 = f32::from_number(first_tick_num);
130 T::from_f32(first_tick_f32)
131 };
132
133 let mut current = first_tick_value;
135 let mut iteration_count = 0;
136 let max_iterations = 100; while current.to_f32() <= max.to_f32()
139 && ticks.len() < 32
140 && iteration_count < max_iterations
141 {
142 if current.to_f32() >= min.to_f32() {
143 let label = current.format();
144
145 let _ = ticks.push(Tick::major(current, label.as_str()));
146 }
147
148 let prev_value = current.to_f32();
149 current = T::from_f32(current.to_f32() + step_f32);
150 iteration_count += 1;
151
152 if current.to_f32() <= prev_value {
154 break; }
156 }
157
158 ticks
159 }
160
161 fn generate_minor_ticks_for_range<T: AxisValue>(
163 &self,
164 min: T,
165 max: T,
166 major_ticks: &[Tick<T>],
167 ) -> Vec<Tick<T>, 32> {
168 let mut minor_ticks = Vec::new();
169
170 if major_ticks.len() < 2 {
171 return minor_ticks;
172 }
173
174 let major_step = major_ticks[1].value.to_f32() - major_ticks[0].value.to_f32();
176 let minor_step = major_step / (self.minor_tick_ratio + 1) as f32;
177
178 for window in major_ticks.windows(2) {
180 if let [tick1, tick2] = window {
181 for i in 1..=self.minor_tick_ratio {
182 let minor_value_f32 = tick1.value.to_f32() + minor_step * i as f32;
183
184 if minor_value_f32 >= min.to_f32() && minor_value_f32 <= max.to_f32() {
186 let distance_to_next_major = (tick2.value.to_f32() - minor_value_f32).abs();
188 if distance_to_next_major > 0.001 {
189 let minor_value = T::from_f32(minor_value_f32);
191 if minor_ticks.len() < 32 {
192 let _ = minor_ticks.push(Tick::minor(minor_value));
193 }
194 }
195 }
196 }
197 }
198 }
199
200 minor_ticks
201 }
202
203 #[allow(dead_code)]
205 fn generate_minor_ticks<T: AxisValue>(&self, major_ticks: &[Tick<T>]) -> Vec<Tick<T>, 32> {
206 let mut minor_ticks = Vec::new();
207
208 if major_ticks.len() < 2 {
209 return minor_ticks;
210 }
211
212 let major_step = major_ticks[1].value.to_f32() - major_ticks[0].value.to_f32();
214 let minor_step = major_step / (self.minor_tick_ratio + 1) as f32;
215
216 for window in major_ticks.windows(2) {
218 if let [tick1, _tick2] = window {
219 for i in 1..=self.minor_tick_ratio {
220 let minor_value = T::from_f32(tick1.value.to_f32() + minor_step * i as f32);
221 if minor_ticks.len() < 32 {
222 let _ = minor_ticks.push(Tick::minor(minor_value));
223 }
224 }
225 }
226 }
227
228 minor_ticks
229 }
230}
231
232impl<T: AxisValue> TickGenerator<T> for LinearTickGenerator {
233 fn generate_ticks(&self, min: T, max: T, max_ticks: usize) -> Vec<Tick<T>, 32> {
234 let mut all_ticks = Vec::new();
235
236 let major_ticks = self.generate_major_ticks(min, max);
238
239 for tick in &major_ticks {
241 if all_ticks.len() < max_ticks.min(32) {
242 let _ = all_ticks.push(tick.clone());
243 }
244 }
245
246 if self.include_minor_ticks {
248 let minor_ticks = self.generate_minor_ticks_for_range(min, max, &major_ticks);
249
250 for tick in minor_ticks {
251 if all_ticks.len() < max_ticks.min(32) {
252 let _ = all_ticks.push(tick);
253 }
254 }
255
256 let len = all_ticks.len();
258 for i in 0..len {
259 for j in 0..len - 1 - i {
260 let a_val = all_ticks[j].value.to_f32();
261 let b_val = all_ticks[j + 1].value.to_f32();
262 if a_val > b_val {
263 all_ticks.swap(j, j + 1);
264 }
265 }
266 }
267 }
268
269 all_ticks
270 }
271
272 fn preferred_tick_count(&self) -> usize {
273 self.preferred_count
274 }
275
276 fn set_preferred_tick_count(&mut self, count: usize) {
277 self.preferred_count = count.clamp(2, 20);
278 }
279}
280
281#[derive(Debug, Clone)]
283pub struct CustomTickGenerator<T> {
284 ticks: Vec<Tick<T>, 32>,
286}
287
288impl<T: Copy> CustomTickGenerator<T> {
289 pub fn new() -> Self {
291 Self { ticks: Vec::new() }
292 }
293
294 pub fn add_major_tick(mut self, value: T, label: &str) -> Self {
296 if self.ticks.len() < 32 {
297 let _ = self.ticks.push(Tick::major(value, label));
298 }
299 self
300 }
301
302 pub fn add_minor_tick(mut self, value: T) -> Self {
304 if self.ticks.len() < 32 {
305 let _ = self.ticks.push(Tick::minor(value));
306 }
307 self
308 }
309
310 pub fn clear(&mut self) {
312 self.ticks.clear();
313 }
314}
315
316impl<T: Copy + PartialOrd> TickGenerator<T> for CustomTickGenerator<T> {
317 fn generate_ticks(&self, min: T, max: T, max_ticks: usize) -> Vec<Tick<T>, 32> {
318 let mut result = Vec::new();
319
320 for tick in &self.ticks {
321 if tick.value >= min && tick.value <= max && result.len() < max_ticks.min(32) {
322 let _ = result.push(tick.clone());
323 }
324 }
325
326 result
327 }
328
329 fn preferred_tick_count(&self) -> usize {
330 self.ticks.len()
331 }
332
333 fn set_preferred_tick_count(&mut self, _count: usize) {
334 }
336}
337
338impl<T: Copy> Default for CustomTickGenerator<T> {
339 fn default() -> Self {
340 Self::new()
341 }
342}
343
344#[derive(Debug, Clone)]
346pub struct LogTickGenerator {
347 base: f32,
349 include_minor_ticks: bool,
351}
352
353impl LogTickGenerator {
354 pub fn new() -> Self {
356 Self {
357 base: 10.0,
358 include_minor_ticks: false,
359 }
360 }
361
362 pub fn with_base(base: f32) -> Self {
364 Self {
365 base: base.max(2.0),
366 include_minor_ticks: false,
367 }
368 }
369
370 pub fn with_minor_ticks(mut self) -> Self {
372 self.include_minor_ticks = true;
373 self
374 }
375}
376
377impl TickGenerator<f32> for LogTickGenerator {
378 fn generate_ticks(&self, min: f32, max: f32, max_ticks: usize) -> Vec<Tick<f32>, 32> {
379 let mut ticks = Vec::new();
380
381 if min <= 0.0 || max <= 0.0 {
382 return ticks; }
384
385 let min_num = min.to_number();
386 let max_num = max.to_number();
387 let base_num = self.base.to_number();
388
389 let log_min = Math::ln(min_num) / Math::ln(base_num);
390 let log_max = Math::ln(max_num) / Math::ln(base_num);
391
392 let start_power = f32::from_number(Math::floor(log_min)) as i32;
393 let end_power = f32::from_number(Math::ceil(log_max)) as i32;
394
395 for power in start_power..=end_power {
396 if ticks.len() >= max_ticks.min(32) {
397 break;
398 }
399
400 let power_num = (power as f32).to_number();
401 let value_num = Math::pow(base_num, power_num);
402 let value = f32::from_number(value_num);
403 if value >= min && value <= max {
404 let mut label = heapless::String::new();
406 if value >= 1000.0 {
407 let k_val = (value / 1000.0) as i32;
408 let mut val = k_val;
410 let mut digits = heapless::Vec::<u8, 8>::new();
411 if val == 0 {
412 let _ = digits.push(b'0');
413 } else {
414 while val > 0 {
415 let _ = digits.push((val % 10) as u8 + b'0');
416 val /= 10;
417 }
418 }
419 for &digit in digits.iter().rev() {
420 let _ = label.push(digit as char);
421 }
422 let _ = label.push('k');
423 } else if value >= 1.0 {
424 let int_val = value as i32;
425 let mut val = int_val;
426 let mut digits = heapless::Vec::<u8, 8>::new();
427 if val == 0 {
428 let _ = digits.push(b'0');
429 } else {
430 while val > 0 {
431 let _ = digits.push((val % 10) as u8 + b'0');
432 val /= 10;
433 }
434 }
435 for &digit in digits.iter().rev() {
436 let _ = label.push(digit as char);
437 }
438 } else {
439 let _ = label.push_str("0.1");
441 }
442
443 let _ = ticks.push(Tick {
444 value,
445 is_major: true,
446 label: Some(label),
447 });
448 }
449 }
450
451 ticks
452 }
453
454 fn preferred_tick_count(&self) -> usize {
455 5
456 }
457
458 fn set_preferred_tick_count(&mut self, _count: usize) {
459 }
461}
462
463impl Default for LogTickGenerator {
464 fn default() -> Self {
465 Self::new()
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472
473 #[test]
474 #[cfg(not(feature = "integer-math"))] fn test_linear_tick_generator() {
476 let generator = LinearTickGenerator::new(5);
477 let ticks = generator.generate_ticks(0.0f32, 10.0f32, 10);
478
479 assert!(!ticks.is_empty());
480 assert!(ticks.len() <= 10);
481
482 for window in ticks.windows(2) {
484 if let [tick1, tick2] = window {
485 assert!(tick1.value <= tick2.value);
486 }
487 }
488 }
489
490 #[test]
491 #[cfg(not(any(feature = "fixed-point", feature = "integer-math")))] fn test_linear_tick_generator_with_minor_ticks() {
493 let generator = LinearTickGenerator::new(3).with_minor_ticks(2);
494 let ticks = generator.generate_ticks(0.0f32, 10.0f32, 20);
495
496 assert!(!ticks.is_empty());
497
498 let major_count = ticks.iter().filter(|t| t.is_major).count();
499 let minor_count = ticks.iter().filter(|t| !t.is_major).count();
500
501 assert!(major_count > 0);
502 assert!(minor_count > 0);
503 }
504
505 #[test]
506 fn test_custom_tick_generator() {
507 let generator = CustomTickGenerator::new()
508 .add_major_tick(0.0, "Start")
509 .add_major_tick(5.0, "Middle")
510 .add_major_tick(10.0, "End")
511 .add_minor_tick(2.5);
512
513 let ticks = generator.generate_ticks(0.0f32, 10.0f32, 10);
514 assert_eq!(ticks.len(), 4);
515
516 let major_count = ticks.iter().filter(|t| t.is_major).count();
517 assert_eq!(major_count, 3);
518 }
519
520 #[test]
521 #[cfg(not(any(feature = "fixed-point", feature = "integer-math")))] fn test_log_tick_generator() {
523 let generator = LogTickGenerator::new();
524 let ticks = generator.generate_ticks(1.0f32, 1000.0f32, 10);
525
526 assert!(!ticks.is_empty());
527
528 assert!(ticks.iter().all(|t| t.is_major));
530
531 for tick in &ticks {
533 let value_num = tick.value.to_number();
534 let log_value = f32::from_number(Math::log10(value_num));
535 assert!((log_value.round() - log_value).abs() < 0.001);
536 }
537 }
538}