use super::scale::Scale;
use std::f64::consts::TAU;
type P = (f64, f64);
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Dir {
Column,
Row,
Radial,
}
pub struct Plot {
pub x0: f64,
pub y0: f64,
pub x1: f64,
pub y1: f64,
pub dir: Dir,
}
impl Plot {
pub fn w(&self) -> f64 {
self.x1 - self.x0
}
pub fn h(&self) -> f64 {
self.y1 - self.y0
}
pub fn project(&self, x: &Scale, xv: f64, value: &Scale, v: f64) -> P {
match self.dir {
Dir::Column => (self.x_at(x, xv), self.y_at(value, v)),
Dir::Row => (
self.x0 + value.frac(v) * self.w(),
self.y0 + x.frac(xv) * self.h(),
),
Dir::Radial => {
let (cx, cy) = self.center();
let theta = self.spoke_angle(x, xv);
let r = value.frac(v) * self.radius();
(cx + r * theta.sin(), cy - r * theta.cos())
}
}
}
pub fn is_radial(&self) -> bool {
self.dir == Dir::Radial
}
pub fn center(&self) -> P {
((self.x0 + self.x1) / 2.0, (self.y0 + self.y1) / 2.0)
}
pub fn radius(&self) -> f64 {
self.w().min(self.h()) / 2.0
}
pub fn spoke_angle(&self, x: &Scale, xv: f64) -> f64 {
let n = match x {
Scale::Band { n } => *n as f64,
_ => 1.0,
};
TAU * xv / n.max(1.0)
}
pub fn x_at(&self, x: &Scale, v: f64) -> f64 {
self.x0 + x.frac(v) * self.w()
}
pub fn y_at(&self, value: &Scale, v: f64) -> f64 {
self.y1 - value.frac(v) * self.h()
}
pub fn slot_px(&self, x: &Scale, i: usize) -> (f64, f64) {
let (f0, f1) = x.slot(i);
(self.x0 + f0 * self.w(), self.x0 + f1 * self.w())
}
pub fn clip(&self, points: &[P]) -> Vec<Vec<P>> {
let mut runs: Vec<Vec<P>> = Vec::new();
let mut cur: Vec<P> = Vec::new();
for w in points.windows(2) {
if let Some((a, b, b_in)) = self.clip_segment(w[0], w[1]) {
if cur.is_empty() {
cur.push(a);
} else if cur.last() != Some(&a) {
runs.push(std::mem::take(&mut cur));
cur.push(a);
}
cur.push(b);
if !b_in {
runs.push(std::mem::take(&mut cur));
}
}
}
if !cur.is_empty() {
runs.push(cur);
}
runs.retain(|r| r.len() >= 2);
runs
}
fn clip_segment(&self, p0: P, p1: P) -> Option<(P, P, bool)> {
let (mut t0, mut t1) = (0.0_f64, 1.0_f64);
let dx = p1.0 - p0.0;
let dy = p1.1 - p0.1;
let checks = [
(-dx, p0.0 - self.x0),
(dx, self.x1 - p0.0),
(-dy, p0.1 - self.y0),
(dy, self.y1 - p0.1),
];
for (p, q) in checks {
if p.abs() < 1e-12 {
if q < 0.0 {
return None; }
} else {
let r = q / p;
if p < 0.0 {
if r > t1 {
return None;
}
if r > t0 {
t0 = r;
}
} else {
if r < t0 {
return None;
}
if r < t1 {
t1 = r;
}
}
}
}
let a = (p0.0 + t0 * dx, p0.1 + t0 * dy);
let b = (p0.0 + t1 * dx, p0.1 + t1 * dy);
Some((a, b, t1 == 1.0))
}
}