Skip to main content

ggplot_rs/geom/
ribbon.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, RectStyle};
7use crate::render::RenderError;
8use crate::scale::ScaleSet;
9use crate::stat::identity::StatIdentity;
10use crate::stat::Stat;
11use crate::theme::Theme;
12
13use super::step::StepDirection;
14use super::{Geom, GeomParams};
15
16/// How a band's edges connect consecutive `(x, ymin, ymax)` rows.
17pub(crate) enum BandShape<'a> {
18    /// Straight segments (`geom_ribbon`).
19    Linear,
20    /// A step function (`geom_stepribbon`), like `geom_step`'s direction.
21    Step(&'a StepDirection),
22}
23
24/// Insert step corners into a run of normalized `(x, y)` points.
25pub(crate) fn stepped(raw: &[(f64, f64)], direction: &StepDirection) -> Vec<(f64, f64)> {
26    let mut out = Vec::with_capacity(raw.len() * 2);
27    for (j, &(x, y)) in raw.iter().enumerate() {
28        if j > 0 {
29            let (px, py) = raw[j - 1];
30            match direction {
31                StepDirection::Hv => out.push((x, py)),
32                StepDirection::Vh => out.push((px, y)),
33                StepDirection::Mid => {
34                    let m = 0.5 * (px + x);
35                    out.push((m, py));
36                    out.push((m, y));
37                }
38            }
39        }
40        out.push((x, y));
41    }
42    out
43}
44
45/// Draw one filled band per group (group / colour / fill level) between
46/// `ymin` and `ymax`, rows ordered by x. Fill comes from a mapped `fill`,
47/// else `fill`. Shared by `geom_ribbon`, `geom_stepribbon`, `geom_qq_band`.
48pub(crate) fn draw_bands(
49    data: &DataFrame,
50    coord: &dyn Coord,
51    scales: &ScaleSet,
52    backend: &mut dyn DrawBackend,
53    fill: (u8, u8, u8),
54    alpha: f64,
55    shape: BandShape<'_>,
56) -> Result<(), RenderError> {
57    let x_col = data
58        .column("x")
59        .ok_or(RenderError::MissingAesthetic("x".into()))?;
60    let ymin_col = data
61        .column("ymin")
62        .ok_or(RenderError::MissingAesthetic("ymin".into()))?;
63    let ymax_col = data
64        .column("ymax")
65        .ok_or(RenderError::MissingAesthetic("ymax".into()))?;
66    let plot_area = backend.plot_area();
67    let y_scale = scales.get(&Aesthetic::Y);
68    let xm = super::support::XMapper::new(data, scales);
69
70    for rows in super::support::row_groups(data) {
71        let mut pts: Vec<(f64, f64, f64)> = rows
72            .iter()
73            .map(|&i| {
74                (
75                    xm.map(&x_col[i], i),
76                    y_scale.map(|s| s.map(&ymin_col[i])).unwrap_or(0.0),
77                    y_scale.map(|s| s.map(&ymax_col[i])).unwrap_or(0.0),
78                )
79            })
80            .filter(|(x, a, b)| x.is_finite() && a.is_finite() && b.is_finite())
81            .collect();
82        if pts.len() < 2 {
83            continue;
84        }
85        pts.sort_by(|a, b| a.0.total_cmp(&b.0));
86        let upper: Vec<(f64, f64)> = pts.iter().map(|p| (p.0, p.2)).collect();
87        let lower: Vec<(f64, f64)> = pts.iter().map(|p| (p.0, p.1)).collect();
88        let (upper, mut lower) = match shape {
89            BandShape::Linear => (upper, lower),
90            BandShape::Step(dir) => (stepped(&upper, dir), stepped(&lower, dir)),
91        };
92        lower.reverse();
93        let polygon: Vec<(f64, f64)> = upper
94            .into_iter()
95            .chain(lower)
96            .map(|p| coord.transform(p, &plot_area))
97            .collect();
98
99        let first = rows[0];
100        let band_fill = data
101            .column("fill")
102            .and_then(|c| scales.map_color(&Aesthetic::Fill, &c[first]))
103            .unwrap_or(fill);
104        let series = super::series_key(data, first);
105        super::set_mark(backend, series.clone(), None, series, None);
106        super::set_mark_formatted(backend, scales, None, &Aesthetic::Y, None);
107        backend.draw_polygon(
108            &polygon,
109            &RectStyle {
110                fill: Some(band_fill),
111                stroke: None,
112                stroke_width: 0.0,
113                alpha,
114                clip: true,
115                corner_radius: 0.0,
116            },
117        )?;
118    }
119    super::clear_mark(backend);
120    Ok(())
121}
122
123/// Ribbon geometry — filled band between ymin and ymax (one per group).
124pub struct GeomRibbon {
125    pub fill: (u8, u8, u8),
126    pub alpha: f64,
127}
128
129impl Default for GeomRibbon {
130    fn default() -> Self {
131        GeomRibbon {
132            fill: (97, 156, 255),
133            alpha: 0.3,
134        }
135    }
136}
137
138impl Geom for GeomRibbon {
139    fn draw(
140        &self,
141        data: &DataFrame,
142        coord: &dyn Coord,
143        scales: &ScaleSet,
144        _theme: &Theme,
145        backend: &mut dyn DrawBackend,
146    ) -> Result<(), RenderError> {
147        draw_bands(
148            data,
149            coord,
150            scales,
151            backend,
152            self.fill,
153            self.alpha,
154            BandShape::Linear,
155        )
156    }
157
158    fn required_aes(&self) -> Vec<Aesthetic> {
159        vec![Aesthetic::X, Aesthetic::Ymin, Aesthetic::Ymax]
160    }
161
162    fn default_stat(&self) -> Box<dyn Stat> {
163        Box::new(StatIdentity)
164    }
165    fn default_position(&self) -> Box<dyn Position> {
166        Box::new(PositionIdentity)
167    }
168    fn default_params(&self) -> GeomParams {
169        GeomParams::default()
170    }
171    fn name(&self) -> &str {
172        "ribbon"
173    }
174
175    fn set_series_color(&mut self, color: (u8, u8, u8)) {
176        self.fill = color;
177    }
178}
179
180/// Step ribbon (pammtools' `geom_stepribbon`): a band whose edges are step
181/// functions — the confidence band of a Kaplan–Meier curve or an ECDF.
182/// `direction` matches `geom_step` (`Hv` default: the value holds until the
183/// next x). `±Inf` x (e.g. `stat_ecdf`'s padding) extends to the panel edge.
184pub struct GeomStepribbon {
185    pub fill: (u8, u8, u8),
186    pub alpha: f64,
187    pub direction: StepDirection,
188}
189
190impl Default for GeomStepribbon {
191    fn default() -> Self {
192        GeomStepribbon {
193            fill: (97, 156, 255),
194            alpha: 0.3,
195            direction: StepDirection::Hv,
196        }
197    }
198}
199
200impl GeomStepribbon {
201    /// A step ribbon with the given direction.
202    pub fn new(direction: StepDirection) -> Self {
203        GeomStepribbon {
204            direction,
205            ..Default::default()
206        }
207    }
208}
209
210impl Geom for GeomStepribbon {
211    fn draw(
212        &self,
213        data: &DataFrame,
214        coord: &dyn Coord,
215        scales: &ScaleSet,
216        _theme: &Theme,
217        backend: &mut dyn DrawBackend,
218    ) -> Result<(), RenderError> {
219        draw_bands(
220            data,
221            coord,
222            scales,
223            backend,
224            self.fill,
225            self.alpha,
226            BandShape::Step(&self.direction),
227        )
228    }
229
230    fn required_aes(&self) -> Vec<Aesthetic> {
231        vec![Aesthetic::X, Aesthetic::Ymin, Aesthetic::Ymax]
232    }
233
234    fn default_stat(&self) -> Box<dyn Stat> {
235        Box::new(StatIdentity)
236    }
237    fn default_position(&self) -> Box<dyn Position> {
238        Box::new(PositionIdentity)
239    }
240    fn default_params(&self) -> GeomParams {
241        GeomParams::default()
242    }
243    fn name(&self) -> &str {
244        "stepribbon"
245    }
246
247    fn allows_infinite(&self) -> bool {
248        true
249    }
250
251    fn set_series_color(&mut self, color: (u8, u8, u8)) {
252        self.fill = color;
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259
260    #[test]
261    fn step_corners() {
262        let raw = [(0.0, 1.0), (1.0, 2.0), (2.0, 3.0)];
263        assert_eq!(
264            stepped(&raw, &StepDirection::Hv),
265            vec![(0.0, 1.0), (1.0, 1.0), (1.0, 2.0), (2.0, 2.0), (2.0, 3.0)]
266        );
267        assert_eq!(
268            stepped(&raw, &StepDirection::Vh),
269            vec![(0.0, 1.0), (0.0, 2.0), (1.0, 2.0), (1.0, 3.0), (2.0, 3.0)]
270        );
271        assert_eq!(
272            stepped(&raw[..2], &StepDirection::Mid),
273            vec![(0.0, 1.0), (0.5, 1.0), (0.5, 2.0), (1.0, 2.0)]
274        );
275    }
276}