Skip to main content

ggplot_rs/geom/
curve.rs

1use crate::aes::Aesthetic;
2use crate::coord::Coord;
3use crate::data::DataFrame;
4use crate::position::identity::PositionIdentity;
5use crate::position::Position;
6use crate::render::backend::{DrawBackend, LineStyle, Linetype};
7use crate::render::RenderError;
8use crate::scale::ScaleSet;
9use crate::stat::identity::StatIdentity;
10use crate::stat::Stat;
11use crate::theme::Theme;
12
13use super::{Geom, GeomParams};
14
15/// Curve geometry — curved segment via quadratic Bezier approximation.
16pub struct GeomCurve {
17    pub color: (u8, u8, u8),
18    pub width: f64,
19    pub alpha: f64,
20    pub curvature: f64,
21    pub ncp: usize,
22}
23
24impl Default for GeomCurve {
25    fn default() -> Self {
26        GeomCurve {
27            color: (0, 0, 0),
28            width: 1.0,
29            alpha: 1.0,
30            curvature: 0.5,
31            ncp: 5,
32        }
33    }
34}
35
36impl GeomCurve {
37    /// Generate points along a quadratic Bezier from p0 to p2 with control point p1.
38    fn bezier_points(
39        p0: (f64, f64),
40        p2: (f64, f64),
41        curvature: f64,
42        ncp: usize,
43    ) -> Vec<(f64, f64)> {
44        // Control point perpendicular to midpoint
45        let mx = (p0.0 + p2.0) / 2.0;
46        let my = (p0.1 + p2.1) / 2.0;
47        let dx = p2.0 - p0.0;
48        let dy = p2.1 - p0.1;
49        // Perpendicular direction
50        let p1x = mx - dy * curvature;
51        let p1y = my + dx * curvature;
52
53        let n = ncp + 2; // include start and end
54        (0..n)
55            .map(|i| {
56                let t = i as f64 / (n - 1) as f64;
57                let u = 1.0 - t;
58                let x = u * u * p0.0 + 2.0 * u * t * p1x + t * t * p2.0;
59                let y = u * u * p0.1 + 2.0 * u * t * p1y + t * t * p2.1;
60                (x, y)
61            })
62            .collect()
63    }
64}
65
66impl Geom for GeomCurve {
67    fn draw(
68        &self,
69        data: &DataFrame,
70        coord: &dyn Coord,
71        scales: &ScaleSet,
72        _theme: &Theme,
73        backend: &mut dyn DrawBackend,
74    ) -> Result<(), RenderError> {
75        let x_col = data
76            .column("x")
77            .ok_or(RenderError::MissingAesthetic("x".into()))?;
78        let y_col = data
79            .column("y")
80            .ok_or(RenderError::MissingAesthetic("y".into()))?;
81        let xend_col = data
82            .column("xend")
83            .ok_or(RenderError::MissingAesthetic("xend".into()))?;
84        let yend_col = data
85            .column("yend")
86            .ok_or(RenderError::MissingAesthetic("yend".into()))?;
87        let color_col = data.column("color");
88
89        let plot_area = backend.plot_area();
90        let x_scale = scales.get(&Aesthetic::X);
91        let y_scale = scales.get(&Aesthetic::Y);
92
93        for i in 0..data.nrows() {
94            let nx1 = x_scale.map(|s| s.map(&x_col[i])).unwrap_or(0.0);
95            let ny1 = y_scale.map(|s| s.map(&y_col[i])).unwrap_or(0.0);
96            let nx2 = x_scale.map(|s| s.map(&xend_col[i])).unwrap_or(0.0);
97            let ny2 = y_scale.map(|s| s.map(&yend_col[i])).unwrap_or(0.0);
98
99            let p0 = coord.transform((nx1, ny1), &plot_area);
100            let p2 = coord.transform((nx2, ny2), &plot_area);
101
102            let curve_points = Self::bezier_points(p0, p2, self.curvature, self.ncp);
103
104            let line_color = color_col
105                .and_then(|cc| scales.map_color(&Aesthetic::Color, &cc[i]))
106                .unwrap_or(self.color);
107
108            if curve_points.len() >= 2 {
109                backend.draw_line(
110                    &curve_points,
111                    &LineStyle {
112                        color: line_color,
113                        alpha: self.alpha,
114                        width: self.width,
115                        linetype: Linetype::Solid,
116                    },
117                )?;
118            }
119        }
120
121        Ok(())
122    }
123
124    fn required_aes(&self) -> Vec<Aesthetic> {
125        vec![Aesthetic::X, Aesthetic::Y, Aesthetic::Xend, Aesthetic::Yend]
126    }
127
128    fn default_stat(&self) -> Box<dyn Stat> {
129        Box::new(StatIdentity)
130    }
131
132    fn default_position(&self) -> Box<dyn Position> {
133        Box::new(PositionIdentity)
134    }
135
136    fn default_params(&self) -> GeomParams {
137        GeomParams::default()
138    }
139
140    fn name(&self) -> &str {
141        "curve"
142    }
143
144    fn set_series_color(&mut self, color: (u8, u8, u8)) {
145        self.color = color;
146    }
147}