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 pub r_axis_inside: Option<bool>,
30 pub rotate_angle: bool,
32 pub reverse_r: bool,
34}
35
36impl CoordPolar {
37 pub fn new() -> Self {
38 CoordPolar {
39 theta: "x".to_string(),
40 start: 0.0,
41 direction: 1.0,
42 inner_radius: 0.0,
43 span: std::f64::consts::TAU,
44 expand: true,
45 r_axis_inside: None,
46 rotate_angle: false,
47 reverse_r: false,
48 }
49 }
50
51 pub fn r_axis_inside(mut self, inside: bool) -> Self {
53 self.r_axis_inside = Some(inside);
54 self
55 }
56
57 pub fn rotate_angle(mut self, rotate: bool) -> Self {
59 self.rotate_angle = rotate;
60 self
61 }
62
63 pub fn reverse(mut self, theta: bool, r: bool) -> Self {
66 if theta {
67 self.direction = -self.direction.signum();
68 }
69 self.reverse_r = r;
70 self
71 }
72
73 fn frame(&self, plot_area: &Rect) -> ((f64, f64), f64) {
75 let (bx0, bx1, by0, by1) = self.unit_bbox();
76 let (bw, bh) = ((bx1 - bx0).max(1e-9), (by1 - by0).max(1e-9));
77 let max_radius = (plot_area.width / bw).min(plot_area.height / bh);
78 let cx = plot_area.x + plot_area.width / 2.0 - max_radius * (bx0 + bx1) / 2.0;
79 let cy = plot_area.y + plot_area.height / 2.0 - max_radius * (by0 + by1) / 2.0;
80 ((cx, cy), max_radius)
81 }
82
83 pub fn with_span(mut self, start: f64, end: f64) -> Self {
89 if start.is_finite() && end.is_finite() && (end - start).abs() > 1e-9 {
90 self.start = start;
91 self.span = (end - start).abs().min(std::f64::consts::TAU);
92 self.direction = if end >= start { 1.0 } else { -1.0 };
93 }
94 self
95 }
96
97 fn unit_bbox(&self) -> (f64, f64, f64, f64) {
101 use std::f64::consts::{FRAC_PI_2, TAU};
102 let (a0, sweep) = (self.start, self.direction * self.span);
103 let pt = |a: f64, r: f64| (r * a.sin(), -r * a.cos());
104 let mut pts = vec![
105 pt(a0, 1.0),
106 pt(a0 + sweep, 1.0),
107 pt(a0, self.inner_radius),
108 pt(a0 + sweep, self.inner_radius),
109 ];
110 let (lo, hi) = if sweep >= 0.0 {
112 (a0, a0 + sweep)
113 } else {
114 (a0 + sweep, a0)
115 };
116 let mut k = (lo / FRAC_PI_2).ceil();
117 while k * FRAC_PI_2 <= hi + 1e-12 && (k * FRAC_PI_2 - lo) <= TAU {
118 pts.push(pt(k * FRAC_PI_2, 1.0));
119 k += 1.0;
120 }
121 let mut b = (
122 f64::INFINITY,
123 f64::NEG_INFINITY,
124 f64::INFINITY,
125 f64::NEG_INFINITY,
126 );
127 for (x, y) in pts {
128 b = (b.0.min(x), b.1.max(x), b.2.min(y), b.3.max(y));
129 }
130 b
131 }
132
133 pub fn expand(mut self, expand: bool) -> Self {
137 self.expand = expand;
138 self
139 }
140
141 pub fn inner_radius(mut self, frac: f64) -> Self {
143 self.inner_radius = frac.clamp(0.0, 0.95);
144 self
145 }
146
147 pub fn theta(mut self, theta: &str) -> Self {
148 self.theta = theta.to_string();
149 self
150 }
151
152 pub fn start(mut self, start: f64) -> Self {
153 self.start = start;
154 self
155 }
156
157 pub fn direction(mut self, dir: f64) -> Self {
158 self.direction = dir;
159 self
160 }
161}
162
163impl Default for CoordPolar {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169impl Coord for CoordPolar {
170 fn transform(&self, point: (f64, f64), plot_area: &Rect) -> (f64, f64) {
171 let (nx, ny) = point;
172
173 let (angle_norm, radius_norm) = if self.theta == "x" {
175 (nx, ny)
176 } else {
177 (ny, nx)
178 };
179
180 let angle = self.start + self.direction * angle_norm * self.span;
182
183 let ((cx, cy), max_radius) = self.frame(plot_area);
187 let radius_norm = if self.reverse_r {
188 1.0 - radius_norm
189 } else {
190 radius_norm
191 };
192 let radius = (self.inner_radius + radius_norm * (1.0 - self.inner_radius)) * max_radius;
194
195 let px = cx + radius * angle.sin();
198 let py = cy - radius * angle.cos();
199
200 (px, py)
201 }
202
203 fn gridlines(&self) -> bool {
204 false
205 }
206
207 fn discrete_expand(&self) -> bool {
208 self.expand
209 }
210
211 fn is_flipped(&self) -> bool {
212 false
213 }
214
215 fn is_polar(&self) -> bool {
216 true
217 }
218
219 fn polar_theta_is_x(&self) -> bool {
220 self.theta == "x"
221 }
222
223 fn train_scales(&mut self, scales: &mut crate::scale::ScaleSet) {
226 let aes = if self.theta == "x" {
227 crate::aes::Aesthetic::X
228 } else {
229 crate::aes::Aesthetic::Y
230 };
231 if let Some(s) = scales.get_mut(&aes).filter(|s| !s.is_discrete()) {
232 s.set_expand(0.0, 0.0);
233 }
234 }
235
236 fn polar_guide(&self, plot_area: &Rect) -> Option<super::PolarGuide> {
237 let full = self.span >= std::f64::consts::TAU - 1e-9;
238 Some(super::PolarGuide {
239 centre: self.frame(plot_area).0,
240 full_circle: full,
241 r_axis_inside: self.r_axis_inside.unwrap_or(!full),
242 rotate_labels: self.rotate_angle,
243 })
244 }
245}