use uzor::types::Rect;
use crate::scale::{BandScale, Scale};
#[derive(Debug, Clone, Copy)]
pub struct PlotArea {
pub rect: Rect,
}
impl PlotArea {
pub fn new(rect: Rect) -> Self {
Self { rect }
}
pub fn x(&self, s: &dyn Scale, v: f64) -> f64 {
self.rect.x + s.map(v) * self.rect.width
}
pub fn y(&self, s: &dyn Scale, v: f64) -> f64 {
self.rect.y + (1.0 - s.map(v)) * self.rect.height
}
pub fn x_band(&self, s: &BandScale, i: usize) -> (f64, f64) {
let (t0, t1) = s.band_range(i);
(self.rect.x + t0 * self.rect.width, self.rect.x + t1 * self.rect.width)
}
pub fn y_band(&self, s: &BandScale, i: usize) -> (f64, f64) {
let (t0, t1) = s.band_range(i);
(self.rect.y + t0 * self.rect.height, self.rect.y + t1 * self.rect.height)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scale::LinearScale;
#[test]
fn x_maps_domain_min_max_to_rect_edges() {
let area = PlotArea::new(Rect::new(10.0, 20.0, 200.0, 100.0));
let scale = LinearScale::new(0.0, 100.0);
assert!((area.x(&scale, 0.0) - 10.0).abs() < 1e-9);
assert!((area.x(&scale, 100.0) - 210.0).abs() < 1e-9);
}
#[test]
fn y_is_inverted_domain_max_at_top() {
let area = PlotArea::new(Rect::new(0.0, 0.0, 100.0, 200.0));
let scale = LinearScale::new(0.0, 100.0);
assert!((area.y(&scale, 100.0) - 0.0).abs() < 1e-9);
assert!((area.y(&scale, 0.0) - 200.0).abs() < 1e-9);
}
#[test]
fn x_band_matches_scale_band_range_scaled_into_the_rect() {
let area = PlotArea::new(Rect::new(0.0, 0.0, 400.0, 100.0));
let band = BandScale::new(vec!["a".to_owned(), "b".to_owned()], 0.0);
let (x0, x1) = area.x_band(&band, 0);
assert!((x0 - 0.0).abs() < 1e-9);
assert!((x1 - 200.0).abs() < 1e-9);
let (x0, x1) = area.x_band(&band, 1);
assert!((x0 - 200.0).abs() < 1e-9);
assert!((x1 - 400.0).abs() < 1e-9);
}
#[test]
fn y_band_reads_top_down_in_natural_row_order() {
let area = PlotArea::new(Rect::new(0.0, 0.0, 100.0, 400.0));
let band = BandScale::new(vec!["row-0".to_owned(), "row-1".to_owned()], 0.0);
let (top, bottom) = area.y_band(&band, 0);
assert!((top - 0.0).abs() < 1e-9);
assert!((bottom - 200.0).abs() < 1e-9);
let (top, bottom) = area.y_band(&band, 1);
assert!((top - 200.0).abs() < 1e-9);
assert!((bottom - 400.0).abs() < 1e-9);
}
}