1use crate::render::Rect;
2use crate::scale::transform::ScaleTransform;
3
4use super::{AxisSpan, Coord};
5
6pub struct CoordTrans {
18 x: Option<ScaleTransform>,
19 y: Option<ScaleTransform>,
20 x_span: Option<AxisSpan>,
21 y_span: Option<AxisSpan>,
22}
23
24impl CoordTrans {
25 pub fn new(x: Option<ScaleTransform>, y: Option<ScaleTransform>) -> Self {
26 CoordTrans {
27 x,
28 y,
29 x_span: None,
30 y_span: None,
31 }
32 }
33}
34
35fn warp(n: f64, trans: &Option<ScaleTransform>, span: Option<AxisSpan>) -> f64 {
42 let (trans, s) = match (trans, span) {
43 (Some(t), Some(s)) if s.max > s.min && (s.pmax - s.pmin).abs() > 1e-12 => (t, s),
44 _ => return n,
45 };
46 let v = s.min + (n - s.pmin) / (s.pmax - s.pmin) * (s.max - s.min);
47 if v < s.min || v > s.max {
48 return n; }
50 let (fmin, fmax, fv) = (trans.apply(s.min), trans.apply(s.max), trans.apply(v));
51 if fmin.is_finite() && fmax.is_finite() && fv.is_finite() && (fmax - fmin).abs() > 1e-12 {
52 let tf = (fv - fmin) / (fmax - fmin);
53 s.pmin + tf * (s.pmax - s.pmin)
54 } else {
55 n
56 }
57}
58
59impl Coord for CoordTrans {
60 fn transform(&self, point: (f64, f64), plot_area: &Rect) -> (f64, f64) {
61 let wx = warp(point.0, &self.x, self.x_span);
62 let wy = warp(point.1, &self.y, self.y_span);
63 let px = plot_area.x + wx * plot_area.width;
64 let py = plot_area.y + (1.0 - wy) * plot_area.height;
65 (px, py)
66 }
67
68 fn set_domains(&mut self, x: Option<AxisSpan>, y: Option<AxisSpan>) {
69 self.x_span = x;
70 self.y_span = y;
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 fn area() -> Rect {
79 Rect {
80 x: 0.0,
81 y: 0.0,
82 width: 100.0,
83 height: 100.0,
84 }
85 }
86
87 fn span(min: f64, max: f64) -> AxisSpan {
88 AxisSpan {
89 min,
90 max,
91 pmin: 0.0,
92 pmax: 1.0,
93 }
94 }
95
96 #[test]
97 fn log_warps_axis_nonlinearly() {
98 let mut c = CoordTrans::new(None, Some(ScaleTransform::Log10));
99 c.set_domains(None, Some(span(1.0, 100.0)));
100 let n10 = (10.0 - 1.0) / 99.0;
103 let (_, py) = c.transform((0.0, n10), &area());
104 assert!((py - 50.0).abs() < 1.0, "py = {py}, expected ~50");
105 }
106
107 #[test]
108 fn falls_back_to_linear_without_span_or_invalid() {
109 let c = CoordTrans::new(None, Some(ScaleTransform::Log10));
111 let (_, py) = c.transform((0.0, 0.25), &area());
112 assert!((py - 75.0).abs() < 1e-9); let mut c2 = CoordTrans::new(Some(ScaleTransform::Log10), None);
116 c2.set_domains(Some(span(-5.0, 5.0)), None);
117 let (px, _) = c2.transform((0.5, 0.0), &area());
118 assert!(px.is_finite());
119 }
120}