Skip to main content

chartml_core/scales/
mod.rs

1mod linear;
2mod band;
3mod time;
4mod ordinal;
5mod sqrt;
6
7pub use linear::ScaleLinear;
8pub use band::ScaleBand;
9pub use time::ScaleTime;
10pub use ordinal::ScaleOrdinal;
11pub use sqrt::ScaleSqrt;
12
13/// Common interface for continuous scales.
14/// Note: Each scale type also has its own specific methods beyond this trait.
15pub trait ContinuousScale {
16    /// Map a domain value to a range value.
17    fn map(&self, value: f64) -> f64;
18    /// Get the domain extent as (min, max).
19    fn domain(&self) -> (f64, f64);
20    /// Get the range extent as (min, max).
21    fn range(&self) -> (f64, f64);
22    /// Generate approximately `count` nice tick values within the domain.
23    fn ticks(&self, count: usize) -> Vec<f64>;
24    /// Clamp a value to domain bounds.
25    fn clamp(&self, value: f64) -> f64;
26}
27
28/// Calculate a nice step size for the given range and approximate tick count.
29/// Uses D3's tick step algorithm with sqrt(50)/sqrt(10)/sqrt(2) thresholds.
30pub(crate) fn tick_step(min: f64, max: f64, count: usize) -> f64 {
31    let raw_step = (max - min) / count as f64;
32    let magnitude = 10_f64.powf(raw_step.log10().floor());
33    let error = raw_step / magnitude;
34
35    if error >= 50_f64.sqrt() {
36        10.0 * magnitude
37    } else if error >= 10_f64.sqrt() {
38        5.0 * magnitude
39    } else if error >= 2_f64.sqrt() {
40        2.0 * magnitude
41    } else {
42        magnitude
43    }
44}
45
46/// Round a value to remove floating point artifacts based on step precision.
47pub(crate) fn round_to_precision(value: f64, step: f64) -> f64 {
48    if step == 0.0 {
49        return value;
50    }
51    let decimals = (-step.log10().floor()).max(0.0) as i32;
52    let factor = 10_f64.powi(decimals);
53    (value * factor).round() / factor
54}