#![allow(unused_imports)]
use crate::data::DataBounds;
#[derive(Debug, Clone, Copy)]
pub struct RangeCalculationConfig {
pub target_tick_count: usize,
pub zero_threshold: f32,
pub negative_margin: f32,
pub far_from_zero_margin: f32,
}
impl Default for RangeCalculationConfig {
fn default() -> Self {
Self {
target_tick_count: 5,
zero_threshold: 0.3,
negative_margin: 1.1,
far_from_zero_margin: 0.9,
}
}
}
pub fn calculate_nice_range(min: f32, max: f32, config: RangeCalculationConfig) -> (f32, f32) {
if max <= min {
if min == max {
if min == 0.0 {
return (0.0, 1.0);
} else if min > 0.0 {
return (0.0, min * 1.2);
} else {
return (min * 1.2, 0.0);
}
} else {
return (max, min); }
}
let nice_min = if min >= 0.0 && max > 0.0 {
if min <= max * config.zero_threshold {
0.0
} else {
#[cfg(feature = "std")]
let magnitude = 10.0_f32.powf((min * config.far_from_zero_margin).log10().floor());
#[cfg(all(
not(feature = "std"),
any(feature = "floating-point", feature = "libm-math")
))]
let magnitude = {
#[cfg(feature = "floating-point")]
{
#[cfg(all(not(feature = "std"), not(test), not(doctest)))]
{
use micromath::F32Ext;
10.0_f32.powf((min * config.far_from_zero_margin).log10().floor())
}
#[cfg(any(feature = "std", test, doctest))]
{
10.0_f32.powf((min * config.far_from_zero_margin).log10().floor())
}
}
#[cfg(all(feature = "libm-math", not(feature = "floating-point")))]
{
libm::powf(
10.0_f32,
libm::floorf(libm::log10f(min * config.far_from_zero_margin)),
)
}
};
#[cfg(not(any(feature = "std", feature = "floating-point", feature = "libm-math")))]
let _magnitude = 1.0;
#[cfg(feature = "std")]
let result = (min * config.far_from_zero_margin / magnitude).floor() * magnitude;
#[cfg(all(
not(feature = "std"),
any(feature = "floating-point", feature = "libm-math")
))]
let result = {
#[cfg(feature = "floating-point")]
{
#[cfg(all(not(feature = "std"), not(test), not(doctest)))]
{
use micromath::F32Ext;
(min * config.far_from_zero_margin / magnitude).floor() * magnitude
}
#[cfg(any(feature = "std", test, doctest))]
{
(min * config.far_from_zero_margin / magnitude).floor() * magnitude
}
}
#[cfg(all(feature = "libm-math", not(feature = "floating-point")))]
{
libm::floorf(min * config.far_from_zero_margin / magnitude) * magnitude
}
};
#[cfg(not(any(feature = "std", feature = "floating-point", feature = "libm-math")))]
let result = min * config.far_from_zero_margin; result
}
} else {
min * config.negative_margin
};
let data_range = max - nice_min;
let rough_step = data_range / config.target_tick_count as f32;
#[cfg(feature = "std")]
let magnitude = 10.0_f32.powf(rough_step.log10().floor());
#[cfg(all(
not(feature = "std"),
any(feature = "floating-point", feature = "libm-math")
))]
let magnitude = {
#[cfg(feature = "floating-point")]
{
#[cfg(all(not(feature = "std"), not(test), not(doctest)))]
{
use micromath::F32Ext;
10.0_f32.powf(rough_step.log10().floor())
}
#[cfg(any(feature = "std", test, doctest))]
{
10.0_f32.powf(rough_step.log10().floor())
}
}
#[cfg(all(feature = "libm-math", not(feature = "floating-point")))]
{
libm::powf(10.0_f32, libm::floorf(libm::log10f(rough_step)))
}
};
#[cfg(not(any(feature = "std", feature = "floating-point", feature = "libm-math")))]
let magnitude = 1.0;
let normalized_step = rough_step / magnitude;
let nice_step = if normalized_step <= 1.0 {
magnitude
} else if normalized_step <= 2.0 {
2.0 * magnitude
} else if normalized_step <= 5.0 {
5.0 * magnitude
} else {
10.0 * magnitude
};
#[cfg(feature = "std")]
let ticks_from_min = ((max - nice_min) / nice_step).ceil();
#[cfg(all(
not(feature = "std"),
any(feature = "floating-point", feature = "libm-math")
))]
let ticks_from_min = {
#[cfg(feature = "floating-point")]
{
#[cfg(all(not(feature = "std"), not(test), not(doctest)))]
{
use micromath::F32Ext;
((max - nice_min) / nice_step).ceil()
}
#[cfg(any(feature = "std", test, doctest))]
{
((max - nice_min) / nice_step).ceil()
}
}
#[cfg(all(feature = "libm-math", not(feature = "floating-point")))]
{
libm::ceilf((max - nice_min) / nice_step)
}
};
#[cfg(not(any(feature = "std", feature = "floating-point", feature = "libm-math")))]
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);
(nice_min, nice_max)
}
pub fn calculate_nice_ranges_from_bounds<X, Y>(
bounds: &DataBounds<X, Y>,
config: RangeCalculationConfig,
) -> ((f32, f32), (f32, f32))
where
X: Into<f32> + Copy + PartialOrd,
Y: Into<f32> + Copy + PartialOrd,
{
let x_range = calculate_nice_range(bounds.min_x.into(), bounds.max_x.into(), config);
let y_range = calculate_nice_range(bounds.min_y.into(), bounds.max_y.into(), config);
(x_range, y_range)
}
pub fn calculate_nice_ranges_separate_config<X, Y>(
bounds: &DataBounds<X, Y>,
x_config: RangeCalculationConfig,
y_config: RangeCalculationConfig,
) -> ((f32, f32), (f32, f32))
where
X: Into<f32> + Copy + PartialOrd,
Y: Into<f32> + Copy + PartialOrd,
{
let x_range = calculate_nice_range(bounds.min_x.into(), bounds.max_x.into(), x_config);
let y_range = calculate_nice_range(bounds.min_y.into(), bounds.max_y.into(), y_config);
(x_range, y_range)
}
pub mod presets {
use super::RangeCalculationConfig;
pub fn standard() -> RangeCalculationConfig {
RangeCalculationConfig::default()
}
pub fn tight() -> RangeCalculationConfig {
RangeCalculationConfig {
target_tick_count: 4,
zero_threshold: 0.1,
negative_margin: 1.05,
far_from_zero_margin: 0.95,
}
}
pub fn loose() -> RangeCalculationConfig {
RangeCalculationConfig {
target_tick_count: 6,
zero_threshold: 0.5,
negative_margin: 1.2,
far_from_zero_margin: 0.8,
}
}
pub fn time_series() -> RangeCalculationConfig {
RangeCalculationConfig {
target_tick_count: 6,
zero_threshold: 0.0, negative_margin: 1.1,
far_from_zero_margin: 0.9,
}
}
pub fn percentage() -> RangeCalculationConfig {
RangeCalculationConfig {
target_tick_count: 5,
zero_threshold: 1.0, negative_margin: 1.0, far_from_zero_margin: 1.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_nice_range_positive_data() {
let config = RangeCalculationConfig::default();
let (min, max) = calculate_nice_range(8.0, 35.0, config);
assert_eq!(min, 0.0);
assert_eq!(max, 40.0);
let (min, max) = calculate_nice_range(0.0, 9.0, config);
assert_eq!(min, 0.0);
assert_eq!(max, 10.0);
}
#[test]
fn test_calculate_nice_range_large_positive_data() {
let config = RangeCalculationConfig::default();
let (min, max) = calculate_nice_range(100.0, 150.0, config);
assert!(min > 0.0);
assert!(min < 100.0);
assert!(max >= 150.0);
}
#[test]
fn test_calculate_nice_range_negative_data() {
let config = RangeCalculationConfig::default();
let (min, max) = calculate_nice_range(-50.0, -10.0, config);
assert!(min < -50.0);
assert!(max >= -10.0);
}
#[test]
fn test_calculate_nice_range_edge_cases() {
let config = RangeCalculationConfig::default();
let (min, max) = calculate_nice_range(5.0, 5.0, config);
assert!(min <= 5.0);
assert!(max >= 5.0);
assert!(max > min);
let (min, max) = calculate_nice_range(0.0, 0.0, config);
assert_eq!(min, 0.0);
assert_eq!(max, 1.0);
}
#[test]
fn test_preset_configurations() {
let standard = presets::standard();
let tight = presets::tight();
let loose = presets::loose();
assert_eq!(standard.target_tick_count, 5);
assert_eq!(tight.target_tick_count, 4);
assert_eq!(loose.target_tick_count, 6);
let (min1, max1) = calculate_nice_range(8.0, 35.0, tight);
let (min2, max2) = calculate_nice_range(8.0, 35.0, loose);
assert!((max2 - min2) >= (max1 - min1));
}
}