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 pub expand: bool,
26}
27
28impl CoordPolar {
29 pub fn new() -> Self {
30 CoordPolar {
31 theta: "x".to_string(),
32 start: 0.0,
33 direction: 1.0,
34 inner_radius: 0.0,
35 span: std::f64::consts::TAU,
36 expand: true,
37 }
38 }
39
40 pub fn with_span(mut self, start: f64, end: f64) -> Self {
46 if start.is_finite() && end.is_finite() && (end - start).abs() > 1e-9 {
47 self.start = start;
48 self.span = (end - start).abs().min(std::f64::consts::TAU);
49 self.direction = if end >= start { 1.0 } else { -1.0 };
50 }
51 self
52 }
53
54 fn unit_bbox(&self) -> (f64, f64, f64, f64) {
58 use std::f64::consts::{FRAC_PI_2, TAU};
59 let (a0, sweep) = (self.start, self.direction * self.span);
60 let pt = |a: f64, r: f64| (r * a.sin(), -r * a.cos());
61 let mut pts = vec![
62 pt(a0, 1.0),
63 pt(a0 + sweep, 1.0),
64 pt(a0, self.inner_radius),
65 pt(a0 + sweep, self.inner_radius),
66 ];
67 let (lo, hi) = if sweep >= 0.0 {
69 (a0, a0 + sweep)
70 } else {
71 (a0 + sweep, a0)
72 };
73 let mut k = (lo / FRAC_PI_2).ceil();
74 while k * FRAC_PI_2 <= hi + 1e-12 && (k * FRAC_PI_2 - lo) <= TAU {
75 pts.push(pt(k * FRAC_PI_2, 1.0));
76 k += 1.0;
77 }
78 let mut b = (
79 f64::INFINITY,
80 f64::NEG_INFINITY,
81 f64::INFINITY,
82 f64::NEG_INFINITY,
83 );
84 for (x, y) in pts {
85 b = (b.0.min(x), b.1.max(x), b.2.min(y), b.3.max(y));
86 }
87 b
88 }
89
90 pub fn expand(mut self, expand: bool) -> Self {
94 self.expand = expand;
95 self
96 }
97
98 pub fn inner_radius(mut self, frac: f64) -> Self {
100 self.inner_radius = frac.clamp(0.0, 0.95);
101 self
102 }
103
104 pub fn theta(mut self, theta: &str) -> Self {
105 self.theta = theta.to_string();
106 self
107 }
108
109 pub fn start(mut self, start: f64) -> Self {
110 self.start = start;
111 self
112 }
113
114 pub fn direction(mut self, dir: f64) -> Self {
115 self.direction = dir;
116 self
117 }
118}
119
120impl Default for CoordPolar {
121 fn default() -> Self {
122 Self::new()
123 }
124}
125
126impl Coord for CoordPolar {
127 fn transform(&self, point: (f64, f64), plot_area: &Rect) -> (f64, f64) {
128 let (nx, ny) = point;
129
130 let (angle_norm, radius_norm) = if self.theta == "x" {
132 (nx, ny)
133 } else {
134 (ny, nx)
135 };
136
137 let angle = self.start + self.direction * angle_norm * self.span;
139
140 let (bx0, bx1, by0, by1) = self.unit_bbox();
144 let (bw, bh) = ((bx1 - bx0).max(1e-9), (by1 - by0).max(1e-9));
145 let max_radius = (plot_area.width / bw).min(plot_area.height / bh);
146 let radius = (self.inner_radius + radius_norm * (1.0 - self.inner_radius)) * max_radius;
148
149 let cx = plot_area.x + plot_area.width / 2.0 - max_radius * (bx0 + bx1) / 2.0;
151 let cy = plot_area.y + plot_area.height / 2.0 - max_radius * (by0 + by1) / 2.0;
152
153 let px = cx + radius * angle.sin();
156 let py = cy - radius * angle.cos();
157
158 (px, py)
159 }
160
161 fn gridlines(&self) -> bool {
162 false
163 }
164
165 fn discrete_expand(&self) -> bool {
166 self.expand
167 }
168
169 fn is_flipped(&self) -> bool {
170 false
171 }
172
173 fn is_polar(&self) -> bool {
174 true
175 }
176
177 fn polar_theta_is_x(&self) -> bool {
178 self.theta == "x"
179 }
180}