use crate::render::Rect;
use super::Coord;
pub struct CoordPolar {
pub theta: String,
pub start: f64,
pub direction: f64,
pub inner_radius: f64,
pub span: f64,
}
impl CoordPolar {
pub fn new() -> Self {
CoordPolar {
theta: "x".to_string(),
start: 0.0,
direction: 1.0,
inner_radius: 0.0,
span: std::f64::consts::TAU,
}
}
pub fn with_span(mut self, start: f64, end: f64) -> Self {
if start.is_finite() && end.is_finite() && (end - start).abs() > 1e-9 {
self.start = start;
self.span = (end - start).abs().min(std::f64::consts::TAU);
self.direction = if end >= start { 1.0 } else { -1.0 };
}
self
}
fn unit_bbox(&self) -> (f64, f64, f64, f64) {
use std::f64::consts::{FRAC_PI_2, TAU};
let (a0, sweep) = (self.start, self.direction * self.span);
let pt = |a: f64, r: f64| (r * a.sin(), -r * a.cos());
let mut pts = vec![
pt(a0, 1.0),
pt(a0 + sweep, 1.0),
pt(a0, self.inner_radius),
pt(a0 + sweep, self.inner_radius),
];
let (lo, hi) = if sweep >= 0.0 {
(a0, a0 + sweep)
} else {
(a0 + sweep, a0)
};
let mut k = (lo / FRAC_PI_2).ceil();
while k * FRAC_PI_2 <= hi + 1e-12 && (k * FRAC_PI_2 - lo) <= TAU {
pts.push(pt(k * FRAC_PI_2, 1.0));
k += 1.0;
}
let mut b = (
f64::INFINITY,
f64::NEG_INFINITY,
f64::INFINITY,
f64::NEG_INFINITY,
);
for (x, y) in pts {
b = (b.0.min(x), b.1.max(x), b.2.min(y), b.3.max(y));
}
b
}
pub fn inner_radius(mut self, frac: f64) -> Self {
self.inner_radius = frac.clamp(0.0, 0.95);
self
}
pub fn theta(mut self, theta: &str) -> Self {
self.theta = theta.to_string();
self
}
pub fn start(mut self, start: f64) -> Self {
self.start = start;
self
}
pub fn direction(mut self, dir: f64) -> Self {
self.direction = dir;
self
}
}
impl Default for CoordPolar {
fn default() -> Self {
Self::new()
}
}
impl Coord for CoordPolar {
fn transform(&self, point: (f64, f64), plot_area: &Rect) -> (f64, f64) {
let (nx, ny) = point;
let (angle_norm, radius_norm) = if self.theta == "x" {
(nx, ny)
} else {
(ny, nx)
};
let angle = self.start + self.direction * angle_norm * self.span;
let (bx0, bx1, by0, by1) = self.unit_bbox();
let (bw, bh) = ((bx1 - bx0).max(1e-9), (by1 - by0).max(1e-9));
let max_radius = (plot_area.width / bw).min(plot_area.height / bh);
let radius = (self.inner_radius + radius_norm * (1.0 - self.inner_radius)) * max_radius;
let cx = plot_area.x + plot_area.width / 2.0 - max_radius * (bx0 + bx1) / 2.0;
let cy = plot_area.y + plot_area.height / 2.0 - max_radius * (by0 + by1) / 2.0;
let px = cx + radius * angle.sin();
let py = cy - radius * angle.cos();
(px, py)
}
fn gridlines(&self) -> bool {
false
}
fn is_flipped(&self) -> bool {
false
}
fn is_polar(&self) -> bool {
true
}
fn polar_theta_is_x(&self) -> bool {
self.theta == "x"
}
}