1use num_traits::Float;
4
5pub fn calculate_range<T: Float + Copy>(data: &[T]) -> (T, T) {
7 if data.is_empty() {
8 return (T::zero(), T::one());
9 }
10
11 let mut min_val = data[0];
12 let mut max_val = data[0];
13
14 for &value in data.iter() {
15 if value < min_val {
16 min_val = value;
17 }
18 if value > max_val {
19 max_val = value;
20 }
21 }
22
23 if min_val == max_val {
25 let padding = if min_val == T::zero() { T::one() } else { min_val * T::from(0.1).unwrap() };
26 min_val = min_val - padding;
27 max_val = max_val + padding;
28 }
29
30 (min_val, max_val)
31}
32
33pub fn generate_ticks(min: f64, max: f64, target_count: usize) -> Vec<f64> {
35 if min >= max || target_count == 0 {
36 return vec![min, max];
37 }
38
39 let range = max - min;
40 let raw_step = range / (target_count - 1) as f64;
41
42 let magnitude = 10.0_f64.powf(raw_step.log10().floor());
44 let normalized_step = raw_step / magnitude;
45
46 let nice_step = if normalized_step <= 1.0 {
47 1.0
48 } else if normalized_step <= 2.0 {
49 2.0
50 } else if normalized_step <= 5.0 {
51 5.0
52 } else {
53 10.0
54 } * magnitude;
55
56 let start = (min / nice_step).floor() * nice_step;
58 let mut ticks = Vec::new();
59 let mut current = start;
60
61 while current <= max + nice_step * 0.001 {
62 if current >= min - nice_step * 0.001 {
63 ticks.push(current);
64 }
65 current += nice_step;
66 }
67
68 if ticks.is_empty() {
69 vec![min, max]
70 } else {
71 ticks
72 }
73}
74
75pub fn lerp(a: f64, b: f64, t: f64) -> f64 {
77 a + (b - a) * t
78}
79
80pub fn map_range(value: f64, from_min: f64, from_max: f64, to_min: f64, to_max: f64) -> f64 {
82 if from_max == from_min {
83 return to_min;
84 }
85 let t = (value - from_min) / (from_max - from_min);
86 lerp(to_min, to_max, t)
87}
88
89pub fn format_number(value: f64) -> String {
91 if value.abs() < 1e-10 {
92 "0".to_string()
93 } else if value.abs() >= 1e6 || value.abs() < 1e-3 {
94 format!("{:.2e}", value)
95 } else if value.fract() == 0.0 {
96 format!("{:.0}", value)
97 } else {
98 format!("{:.3}", value).trim_end_matches('0').trim_end_matches('.').to_string()
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn test_calculate_range() {
108 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
109 let (min, max) = calculate_range(&data);
110 assert_eq!(min, 1.0);
111 assert_eq!(max, 5.0);
112 }
113
114 #[test]
115 fn test_generate_ticks() {
116 let ticks = generate_ticks(0.0, 10.0, 6);
117 assert!(!ticks.is_empty());
118 assert!(ticks[0] <= 0.0);
119 assert!(ticks[ticks.len() - 1] >= 10.0);
120 }
121
122 #[test]
123 fn test_map_range() {
124 assert_eq!(map_range(5.0, 0.0, 10.0, 0.0, 100.0), 50.0);
125 assert_eq!(map_range(0.0, 0.0, 10.0, 0.0, 100.0), 0.0);
126 assert_eq!(map_range(10.0, 0.0, 10.0, 0.0, 100.0), 100.0);
127 }
128}