1#![allow(unused_imports)] use crate::data::DataBounds;
13
14#[derive(Debug, Clone, Copy)]
16pub struct RangeCalculationConfig {
17 pub target_tick_count: usize,
19 pub zero_threshold: f32,
22 pub negative_margin: f32,
24 pub far_from_zero_margin: f32,
26}
27
28impl Default for RangeCalculationConfig {
29 fn default() -> Self {
30 Self {
31 target_tick_count: 5,
32 zero_threshold: 0.3,
33 negative_margin: 1.1,
34 far_from_zero_margin: 0.9,
35 }
36 }
37}
38
39pub fn calculate_nice_range(min: f32, max: f32, config: RangeCalculationConfig) -> (f32, f32) {
70 if max <= min {
71 if min == max {
73 if min == 0.0 {
74 return (0.0, 1.0);
75 } else if min > 0.0 {
76 return (0.0, min * 1.2);
77 } else {
78 return (min * 1.2, 0.0);
79 }
80 } else {
81 return (max, min); }
83 }
84
85 let nice_min = if min >= 0.0 && max > 0.0 {
87 if min <= max * config.zero_threshold {
89 0.0
90 } else {
91 #[cfg(feature = "std")]
93 let magnitude = 10.0_f32.powf((min * config.far_from_zero_margin).log10().floor());
94 #[cfg(all(
95 not(feature = "std"),
96 any(feature = "floating-point", feature = "libm-math")
97 ))]
98 let magnitude = {
99 #[cfg(feature = "floating-point")]
100 {
101 #[cfg(all(not(feature = "std"), not(test), not(doctest)))]
102 {
103 use micromath::F32Ext;
104 10.0_f32.powf((min * config.far_from_zero_margin).log10().floor())
105 }
106 #[cfg(any(feature = "std", test, doctest))]
107 {
108 10.0_f32.powf((min * config.far_from_zero_margin).log10().floor())
109 }
110 }
111 #[cfg(all(feature = "libm-math", not(feature = "floating-point")))]
112 {
113 libm::powf(
114 10.0_f32,
115 libm::floorf(libm::log10f(min * config.far_from_zero_margin)),
116 )
117 }
118 };
119 #[cfg(not(any(feature = "std", feature = "floating-point", feature = "libm-math")))]
120 let _magnitude = 1.0; #[cfg(feature = "std")]
123 let result = (min * config.far_from_zero_margin / magnitude).floor() * magnitude;
124 #[cfg(all(
125 not(feature = "std"),
126 any(feature = "floating-point", feature = "libm-math")
127 ))]
128 let result = {
129 #[cfg(feature = "floating-point")]
130 {
131 #[cfg(all(not(feature = "std"), not(test), not(doctest)))]
132 {
133 use micromath::F32Ext;
134 (min * config.far_from_zero_margin / magnitude).floor() * magnitude
135 }
136 #[cfg(any(feature = "std", test, doctest))]
137 {
138 (min * config.far_from_zero_margin / magnitude).floor() * magnitude
139 }
140 }
141 #[cfg(all(feature = "libm-math", not(feature = "floating-point")))]
142 {
143 libm::floorf(min * config.far_from_zero_margin / magnitude) * magnitude
144 }
145 };
146 #[cfg(not(any(feature = "std", feature = "floating-point", feature = "libm-math")))]
147 let result = min * config.far_from_zero_margin; result
149 }
150 } else {
151 min * config.negative_margin
153 };
154
155 let data_range = max - nice_min;
157 let rough_step = data_range / config.target_tick_count as f32;
158
159 #[cfg(feature = "std")]
161 let magnitude = 10.0_f32.powf(rough_step.log10().floor());
162 #[cfg(all(
163 not(feature = "std"),
164 any(feature = "floating-point", feature = "libm-math")
165 ))]
166 let magnitude = {
167 #[cfg(feature = "floating-point")]
168 {
169 #[cfg(all(not(feature = "std"), not(test), not(doctest)))]
170 {
171 use micromath::F32Ext;
172 10.0_f32.powf(rough_step.log10().floor())
173 }
174 #[cfg(any(feature = "std", test, doctest))]
175 {
176 10.0_f32.powf(rough_step.log10().floor())
177 }
178 }
179 #[cfg(all(feature = "libm-math", not(feature = "floating-point")))]
180 {
181 libm::powf(10.0_f32, libm::floorf(libm::log10f(rough_step)))
182 }
183 };
184 #[cfg(not(any(feature = "std", feature = "floating-point", feature = "libm-math")))]
185 let magnitude = 1.0; let normalized_step = rough_step / magnitude;
188 let nice_step = if normalized_step <= 1.0 {
189 magnitude
190 } else if normalized_step <= 2.0 {
191 2.0 * magnitude
192 } else if normalized_step <= 5.0 {
193 5.0 * magnitude
194 } else {
195 10.0 * magnitude
196 };
197
198 #[cfg(feature = "std")]
200 let ticks_from_min = ((max - nice_min) / nice_step).ceil();
201 #[cfg(all(
202 not(feature = "std"),
203 any(feature = "floating-point", feature = "libm-math")
204 ))]
205 let ticks_from_min = {
206 #[cfg(feature = "floating-point")]
207 {
208 #[cfg(all(not(feature = "std"), not(test), not(doctest)))]
209 {
210 use micromath::F32Ext;
211 ((max - nice_min) / nice_step).ceil()
212 }
213 #[cfg(any(feature = "std", test, doctest))]
214 {
215 ((max - nice_min) / nice_step).ceil()
216 }
217 }
218 #[cfg(all(feature = "libm-math", not(feature = "floating-point")))]
219 {
220 libm::ceilf((max - nice_min) / nice_step)
221 }
222 };
223 #[cfg(not(any(feature = "std", feature = "floating-point", feature = "libm-math")))]
224 let ticks_from_min = ((max - nice_min) / nice_step + 0.5) as i32 as f32; let nice_max = nice_min + (ticks_from_min * nice_step);
226
227 (nice_min, nice_max)
228}
229
230pub fn calculate_nice_ranges_from_bounds<X, Y>(
267 bounds: &DataBounds<X, Y>,
268 config: RangeCalculationConfig,
269) -> ((f32, f32), (f32, f32))
270where
271 X: Into<f32> + Copy + PartialOrd,
272 Y: Into<f32> + Copy + PartialOrd,
273{
274 let x_range = calculate_nice_range(bounds.min_x.into(), bounds.max_x.into(), config);
275 let y_range = calculate_nice_range(bounds.min_y.into(), bounds.max_y.into(), config);
276 (x_range, y_range)
277}
278
279pub fn calculate_nice_ranges_separate_config<X, Y>(
295 bounds: &DataBounds<X, Y>,
296 x_config: RangeCalculationConfig,
297 y_config: RangeCalculationConfig,
298) -> ((f32, f32), (f32, f32))
299where
300 X: Into<f32> + Copy + PartialOrd,
301 Y: Into<f32> + Copy + PartialOrd,
302{
303 let x_range = calculate_nice_range(bounds.min_x.into(), bounds.max_x.into(), x_config);
304 let y_range = calculate_nice_range(bounds.min_y.into(), bounds.max_y.into(), y_config);
305 (x_range, y_range)
306}
307
308pub mod presets {
310 use super::RangeCalculationConfig;
311
312 pub fn standard() -> RangeCalculationConfig {
314 RangeCalculationConfig::default()
315 }
316
317 pub fn tight() -> RangeCalculationConfig {
319 RangeCalculationConfig {
320 target_tick_count: 4,
321 zero_threshold: 0.1,
322 negative_margin: 1.05,
323 far_from_zero_margin: 0.95,
324 }
325 }
326
327 pub fn loose() -> RangeCalculationConfig {
329 RangeCalculationConfig {
330 target_tick_count: 6,
331 zero_threshold: 0.5,
332 negative_margin: 1.2,
333 far_from_zero_margin: 0.8,
334 }
335 }
336
337 pub fn time_series() -> RangeCalculationConfig {
339 RangeCalculationConfig {
340 target_tick_count: 6,
341 zero_threshold: 0.0, negative_margin: 1.1,
343 far_from_zero_margin: 0.9,
344 }
345 }
346
347 pub fn percentage() -> RangeCalculationConfig {
349 RangeCalculationConfig {
350 target_tick_count: 5,
351 zero_threshold: 1.0, negative_margin: 1.0, far_from_zero_margin: 1.0,
354 }
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361
362 #[test]
363 fn test_calculate_nice_range_positive_data() {
364 let config = RangeCalculationConfig::default();
365
366 let (min, max) = calculate_nice_range(8.0, 35.0, config);
368 assert_eq!(min, 0.0);
369 assert_eq!(max, 40.0);
370
371 let (min, max) = calculate_nice_range(0.0, 9.0, config);
373 assert_eq!(min, 0.0);
374 assert_eq!(max, 10.0);
375 }
376
377 #[test]
378 fn test_calculate_nice_range_large_positive_data() {
379 let config = RangeCalculationConfig::default();
380
381 let (min, max) = calculate_nice_range(100.0, 150.0, config);
383 assert!(min > 0.0);
384 assert!(min < 100.0);
385 assert!(max >= 150.0);
386 }
387
388 #[test]
389 fn test_calculate_nice_range_negative_data() {
390 let config = RangeCalculationConfig::default();
391
392 let (min, max) = calculate_nice_range(-50.0, -10.0, config);
394 assert!(min < -50.0);
395 assert!(max >= -10.0);
396 }
397
398 #[test]
399 fn test_calculate_nice_range_edge_cases() {
400 let config = RangeCalculationConfig::default();
401
402 let (min, max) = calculate_nice_range(5.0, 5.0, config);
404 assert!(min <= 5.0);
405 assert!(max >= 5.0);
406 assert!(max > min);
407
408 let (min, max) = calculate_nice_range(0.0, 0.0, config);
410 assert_eq!(min, 0.0);
411 assert_eq!(max, 1.0);
412 }
413
414 #[test]
415 fn test_preset_configurations() {
416 let standard = presets::standard();
418 let tight = presets::tight();
419 let loose = presets::loose();
420
421 assert_eq!(standard.target_tick_count, 5);
422 assert_eq!(tight.target_tick_count, 4);
423 assert_eq!(loose.target_tick_count, 6);
424
425 let (min1, max1) = calculate_nice_range(8.0, 35.0, tight);
427 let (min2, max2) = calculate_nice_range(8.0, 35.0, loose);
428
429 assert!((max2 - min2) >= (max1 - min1));
431 }
432}