1use crate::aes::Aesthetic;
4use crate::data::Value;
5use crate::render::Rect;
6use crate::scale::ScaleSet;
7
8use super::Coord;
9
10pub struct CoordRadar {
20 pub start: f64,
22 pub radius_frac: f64,
25 n_spokes: Option<usize>,
27 r0: f64,
29}
30
31impl CoordRadar {
32 pub fn new() -> Self {
33 CoordRadar {
34 start: 0.0,
35 radius_frac: 0.78,
36 n_spokes: None,
37 r0: 0.0,
38 }
39 }
40
41 pub fn start(mut self, start: f64) -> Self {
43 self.start = start;
44 self
45 }
46
47 pub fn radius_frac(mut self, frac: f64) -> Self {
49 self.radius_frac = frac.clamp(0.1, 1.0);
50 self
51 }
52
53 fn angle(&self, nx: f64) -> f64 {
56 let offset = match self.n_spokes {
57 Some(n) if n > 0 => -std::f64::consts::PI / n as f64,
58 _ => 0.0,
59 };
60 self.start + offset + nx * std::f64::consts::TAU
61 }
62}
63
64impl Default for CoordRadar {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70impl Coord for CoordRadar {
71 fn transform(&self, (nx, ny): (f64, f64), plot_area: &Rect) -> (f64, f64) {
72 let max_r = plot_area.width.min(plot_area.height) / 2.0 * self.radius_frac;
73 let span = (1.0 - self.r0).max(1e-9);
74 let frac = ((ny - self.r0) / span).max(0.0);
75 let r = if frac.is_finite() { frac * max_r } else { 0.0 };
76 let a = self.angle(nx);
77 let cx = plot_area.x + plot_area.width / 2.0;
78 let cy = plot_area.y + plot_area.height / 2.0;
79 (cx + r * a.sin(), cy - r * a.cos())
80 }
81
82 fn gridlines(&self) -> bool {
83 false
84 }
85
86 fn is_polar(&self) -> bool {
87 true
88 }
89
90 fn is_radar(&self) -> bool {
91 true
92 }
93
94 fn train_scales(&mut self, scales: &mut ScaleSet) {
95 if let Some(y) = scales.get_mut(&Aesthetic::Y) {
96 if !y.is_discrete() {
97 y.train(&[Value::Float(0.0)]);
98 self.r0 = y.map(&Value::Float(0.0));
99 }
100 }
101 self.n_spokes = scales
102 .get(&Aesthetic::X)
103 .filter(|s| s.is_discrete())
104 .map(|s| s.breaks().len());
105 }
106}