ravenrs_sheet/chart/
axis.rs1use crate::base::{DateSystem, Value, number_to_text};
4use crate::numfmt::{NumberFormat, format_value};
5
6pub(crate) const MAX_ABS: f64 = 1e300;
8
9#[derive(Clone, Copy, Debug, PartialEq)]
11pub struct Scale {
12 pub min: f64,
13 pub max: f64,
14 pub step: f64,
15}
16
17impl Scale {
18 pub fn ticks(&self) -> Vec<f64> {
20 let span = self.max - self.min;
21 if !(span.is_finite() && self.step.is_finite() && self.step > 0.0 && span > 0.0) {
22 return vec![self.min];
23 }
24 let n = ((span / self.step).round() as i64).clamp(1, 50);
25 (0..=n)
26 .map(|i| {
27 let v = self.min + i as f64 * self.step;
28 let snapped = (v / self.step).round() * self.step;
31 let v = if (snapped - v).abs() < self.step * 1e-9 { snapped } else { v };
32 if v.abs() < self.step * 1e-9 { 0.0 } else { v }
33 })
34 .collect()
35 }
36 pub fn frac(&self, v: f64) -> f32 {
38 let span = self.max - self.min;
39 if !(span.is_finite() && span > 0.0 && v.is_finite()) {
40 return 0.0;
41 }
42 (((v - self.min) / span).clamp(-0.5, 1.5)) as f32
43 }
44}
45
46pub(crate) fn clean(v: f64) -> f64 {
47 if v.is_finite() { v.clamp(-MAX_ABS, MAX_ABS) } else { 0.0 }
48}
49
50pub fn nice_scale(lo: f64, hi: f64, force_zero: bool) -> Scale {
56 let (mut lo, mut hi) = (clean(lo), clean(hi));
57 if lo > hi {
58 std::mem::swap(&mut lo, &mut hi);
59 }
60 if force_zero || (lo > 0.0 && lo < hi * 5.0 / 6.0) || lo == hi {
61 lo = lo.min(0.0);
62 }
63 if force_zero || (hi < 0.0 && hi > lo * 5.0 / 6.0) || lo == hi {
64 hi = hi.max(0.0);
65 }
66 if lo == hi {
67 return Scale { min: 0.0, max: 1.0, step: 0.2 };
69 }
70 let range = hi - lo;
71 if hi > 0.0 {
72 hi += range * 0.05;
73 }
74 if lo < 0.0 {
75 lo -= range * 0.05;
76 }
77 let range = hi - lo;
78 if !range.is_finite() || range <= 0.0 {
79 return Scale { min: 0.0, max: 1.0, step: 0.2 };
80 }
81 let e = (range / 8.0).log10().floor();
82 let mut chosen = None;
83 'outer: for k in [e - 1.0, e, e + 1.0, e + 2.0] {
84 let p = 10f64.powf(k);
85 for m in [1.0, 2.0, 5.0] {
86 let s = m * p;
87 if !(s.is_finite() && s > 0.0) {
88 continue;
89 }
90 let intervals = (hi / s - 1e-9).ceil() - (lo / s + 1e-9).floor();
91 if intervals <= 8.0 {
92 chosen = Some(s);
93 break 'outer;
94 }
95 }
96 }
97 let step = chosen.unwrap_or(range);
98 let mut min = (lo / step + 1e-9).floor() * step;
99 let mut max = (hi / step - 1e-9).ceil() * step;
100 if min.abs() < step * 1e-9 {
101 min = 0.0;
102 }
103 if max.abs() < step * 1e-9 {
104 max = 0.0;
105 }
106 if !(min.is_finite() && max.is_finite()) || max <= min {
107 return Scale { min: lo, max: hi, step: range };
108 }
109 Scale { min, max, step }
110}
111
112pub(crate) struct Fmt {
114 general: bool,
115 fmt: NumberFormat,
116}
117
118impl Fmt {
119 pub fn new(code: &str) -> Fmt {
120 let t = code.trim();
121 let general = t.is_empty() || t.eq_ignore_ascii_case("general");
122 Fmt { general, fmt: NumberFormat::parse_cached(if general { "General" } else { t }) }
123 }
124 pub fn format(&self, v: f64, step: Option<f64>) -> String {
126 let mut v = clean(v);
127 if let Some(s) = step
128 && s > 0.0
129 && (v / s).abs() < 1e-9
130 {
131 v = 0.0;
132 }
133 if self.general {
134 return general_text(v, step);
135 }
136 let mut t = format_value(&Value::Number(v), &self.fmt, DateSystem::D1900).text;
137 if t.chars().count() > 40 {
138 t = t.chars().take(40).collect();
139 }
140 t
141 }
142}
143
144fn general_text(v: f64, step: Option<f64>) -> String {
145 if v == 0.0 {
146 return "0".into();
147 }
148 match step {
149 Some(s) if s.is_finite() && s > 0.0 && v.abs() < 1e11 && v.abs() >= 1e-9 => {
150 let d = (-s.log10().floor()).clamp(0.0, 10.0) as usize;
151 let t = format!("{:.*}", d, v);
152 if t.trim_start_matches('-').chars().all(|c| c == '0' || c == '.') { "0".into() } else { t }
153 }
154 _ => number_to_text(v),
155 }
156}
157
158pub fn format_number(v: f64, code: &str, step: Option<f64>) -> String {
160 Fmt::new(code).format(v, step)
161}