use crate::render::Rect;
use crate::scale::transform::ScaleTransform;
use super::{AxisSpan, Coord};
pub struct CoordTrans {
x: Option<ScaleTransform>,
y: Option<ScaleTransform>,
x_span: Option<AxisSpan>,
y_span: Option<AxisSpan>,
}
impl CoordTrans {
pub fn new(x: Option<ScaleTransform>, y: Option<ScaleTransform>) -> Self {
CoordTrans {
x,
y,
x_span: None,
y_span: None,
}
}
}
fn warp(n: f64, trans: &Option<ScaleTransform>, span: Option<AxisSpan>) -> f64 {
let (trans, s) = match (trans, span) {
(Some(t), Some(s)) if s.max > s.min && (s.pmax - s.pmin).abs() > 1e-12 => (t, s),
_ => return n,
};
let v = s.min + (n - s.pmin) / (s.pmax - s.pmin) * (s.max - s.min);
if v < s.min || v > s.max {
return n; }
let (fmin, fmax, fv) = (trans.apply(s.min), trans.apply(s.max), trans.apply(v));
if fmin.is_finite() && fmax.is_finite() && fv.is_finite() && (fmax - fmin).abs() > 1e-12 {
let tf = (fv - fmin) / (fmax - fmin);
s.pmin + tf * (s.pmax - s.pmin)
} else {
n
}
}
impl Coord for CoordTrans {
fn transform(&self, point: (f64, f64), plot_area: &Rect) -> (f64, f64) {
let wx = warp(point.0, &self.x, self.x_span);
let wy = warp(point.1, &self.y, self.y_span);
let px = plot_area.x + wx * plot_area.width;
let py = plot_area.y + (1.0 - wy) * plot_area.height;
(px, py)
}
fn set_domains(&mut self, x: Option<AxisSpan>, y: Option<AxisSpan>) {
self.x_span = x;
self.y_span = y;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn area() -> Rect {
Rect {
x: 0.0,
y: 0.0,
width: 100.0,
height: 100.0,
}
}
fn span(min: f64, max: f64) -> AxisSpan {
AxisSpan {
min,
max,
pmin: 0.0,
pmax: 1.0,
}
}
#[test]
fn log_warps_axis_nonlinearly() {
let mut c = CoordTrans::new(None, Some(ScaleTransform::Log10));
c.set_domains(None, Some(span(1.0, 100.0)));
let n10 = (10.0 - 1.0) / 99.0;
let (_, py) = c.transform((0.0, n10), &area());
assert!((py - 50.0).abs() < 1.0, "py = {py}, expected ~50");
}
#[test]
fn falls_back_to_linear_without_span_or_invalid() {
let c = CoordTrans::new(None, Some(ScaleTransform::Log10));
let (_, py) = c.transform((0.0, 0.25), &area());
assert!((py - 75.0).abs() < 1e-9);
let mut c2 = CoordTrans::new(Some(ScaleTransform::Log10), None);
c2.set_domains(Some(span(-5.0, 5.0)), None);
let (px, _) = c2.transform((0.5, 0.0), &area());
assert!(px.is_finite());
}
}