Skip to main content

ggplot_rs/coord/
polar.rs

1use crate::render::Rect;
2
3use super::Coord;
4
5/// Polar coordinate system.
6///
7/// Maps one aesthetic to angle and the other to radius.
8/// - `theta = "x"` (default): x maps to angle, y to radius (pie charts, wind roses)
9/// - `theta = "y"`: y maps to angle, x to radius (Coxcomb charts)
10pub struct CoordPolar {
11    /// Which variable maps to angle: "x" or "y".
12    pub theta: String,
13    /// Start angle in radians (0 = 12 o'clock position).
14    pub start: f64,
15    /// Direction: 1 = clockwise, -1 = counterclockwise.
16    pub direction: f64,
17}
18
19impl CoordPolar {
20    pub fn new() -> Self {
21        CoordPolar {
22            theta: "x".to_string(),
23            start: 0.0,
24            direction: 1.0,
25        }
26    }
27
28    pub fn theta(mut self, theta: &str) -> Self {
29        self.theta = theta.to_string();
30        self
31    }
32
33    pub fn start(mut self, start: f64) -> Self {
34        self.start = start;
35        self
36    }
37
38    pub fn direction(mut self, dir: f64) -> Self {
39        self.direction = dir;
40        self
41    }
42}
43
44impl Default for CoordPolar {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl Coord for CoordPolar {
51    fn transform(&self, point: (f64, f64), plot_area: &Rect) -> (f64, f64) {
52        let (nx, ny) = point;
53
54        // Determine which normalized value maps to angle vs radius
55        let (angle_norm, radius_norm) = if self.theta == "x" {
56            (nx, ny)
57        } else {
58            (ny, nx)
59        };
60
61        // Convert to angle (full circle = 2π)
62        let angle = self.start + self.direction * angle_norm * std::f64::consts::TAU;
63
64        // Radius: fraction of the available radius (half the smaller dimension)
65        let max_radius = plot_area.width.min(plot_area.height) / 2.0;
66        let radius = radius_norm * max_radius;
67
68        // Center of the polar plot
69        let cx = plot_area.x + plot_area.width / 2.0;
70        let cy = plot_area.y + plot_area.height / 2.0;
71
72        // Convert polar to Cartesian pixel coordinates
73        // angle=0 points up (12 o'clock), increases clockwise
74        let px = cx + radius * angle.sin();
75        let py = cy - radius * angle.cos();
76
77        (px, py)
78    }
79
80    fn gridlines(&self) -> bool {
81        false
82    }
83
84    fn is_flipped(&self) -> bool {
85        false
86    }
87
88    fn is_polar(&self) -> bool {
89        true
90    }
91}