1use crate::render::Rect;
2
3use super::Coord;
4
5pub struct CoordPolar {
11 pub theta: String,
13 pub start: f64,
15 pub direction: f64,
17 pub inner_radius: f64,
19 pub span: f64,
22}
23
24impl CoordPolar {
25 pub fn new() -> Self {
26 CoordPolar {
27 theta: "x".to_string(),
28 start: 0.0,
29 direction: 1.0,
30 inner_radius: 0.0,
31 span: std::f64::consts::TAU,
32 }
33 }
34
35 pub fn with_span(mut self, start: f64, end: f64) -> Self {
41 if start.is_finite() && end.is_finite() && (end - start).abs() > 1e-9 {
42 self.start = start;
43 self.span = (end - start).abs().min(std::f64::consts::TAU);
44 self.direction = if end >= start { 1.0 } else { -1.0 };
45 }
46 self
47 }
48
49 fn unit_bbox(&self) -> (f64, f64, f64, f64) {
53 use std::f64::consts::{FRAC_PI_2, TAU};
54 let (a0, sweep) = (self.start, self.direction * self.span);
55 let pt = |a: f64, r: f64| (r * a.sin(), -r * a.cos());
56 let mut pts = vec![
57 pt(a0, 1.0),
58 pt(a0 + sweep, 1.0),
59 pt(a0, self.inner_radius),
60 pt(a0 + sweep, self.inner_radius),
61 ];
62 let (lo, hi) = if sweep >= 0.0 {
64 (a0, a0 + sweep)
65 } else {
66 (a0 + sweep, a0)
67 };
68 let mut k = (lo / FRAC_PI_2).ceil();
69 while k * FRAC_PI_2 <= hi + 1e-12 && (k * FRAC_PI_2 - lo) <= TAU {
70 pts.push(pt(k * FRAC_PI_2, 1.0));
71 k += 1.0;
72 }
73 let mut b = (
74 f64::INFINITY,
75 f64::NEG_INFINITY,
76 f64::INFINITY,
77 f64::NEG_INFINITY,
78 );
79 for (x, y) in pts {
80 b = (b.0.min(x), b.1.max(x), b.2.min(y), b.3.max(y));
81 }
82 b
83 }
84
85 pub fn inner_radius(mut self, frac: f64) -> Self {
87 self.inner_radius = frac.clamp(0.0, 0.95);
88 self
89 }
90
91 pub fn theta(mut self, theta: &str) -> Self {
92 self.theta = theta.to_string();
93 self
94 }
95
96 pub fn start(mut self, start: f64) -> Self {
97 self.start = start;
98 self
99 }
100
101 pub fn direction(mut self, dir: f64) -> Self {
102 self.direction = dir;
103 self
104 }
105}
106
107impl Default for CoordPolar {
108 fn default() -> Self {
109 Self::new()
110 }
111}
112
113impl Coord for CoordPolar {
114 fn transform(&self, point: (f64, f64), plot_area: &Rect) -> (f64, f64) {
115 let (nx, ny) = point;
116
117 let (angle_norm, radius_norm) = if self.theta == "x" {
119 (nx, ny)
120 } else {
121 (ny, nx)
122 };
123
124 let angle = self.start + self.direction * angle_norm * self.span;
126
127 let (bx0, bx1, by0, by1) = self.unit_bbox();
131 let (bw, bh) = ((bx1 - bx0).max(1e-9), (by1 - by0).max(1e-9));
132 let max_radius = (plot_area.width / bw).min(plot_area.height / bh);
133 let radius = (self.inner_radius + radius_norm * (1.0 - self.inner_radius)) * max_radius;
135
136 let cx = plot_area.x + plot_area.width / 2.0 - max_radius * (bx0 + bx1) / 2.0;
138 let cy = plot_area.y + plot_area.height / 2.0 - max_radius * (by0 + by1) / 2.0;
139
140 let px = cx + radius * angle.sin();
143 let py = cy - radius * angle.cos();
144
145 (px, py)
146 }
147
148 fn gridlines(&self) -> bool {
149 false
150 }
151
152 fn is_flipped(&self) -> bool {
153 false
154 }
155
156 fn is_polar(&self) -> bool {
157 true
158 }
159
160 fn polar_theta_is_x(&self) -> bool {
161 self.theta == "x"
162 }
163}