Skip to main content

ggplot_rs/geom/
errorbarh.rs

1//! Horizontal error bars (`geom_errorbarh`): a line from `(xmin, y)` to
2//! `(xmax, y)` with vertical end caps — horizontal confidence intervals in a
3//! coefficient forest or a time-to-event plot.
4
5use crate::aes::Aesthetic;
6use crate::coord::Coord;
7use crate::data::DataFrame;
8use crate::position::identity::PositionIdentity;
9use crate::position::Position;
10use crate::render::backend::{DrawBackend, LineStyle, Linetype};
11use crate::render::RenderError;
12use crate::scale::ScaleSet;
13use crate::stat::identity::StatIdentity;
14use crate::stat::Stat;
15use crate::theme::Theme;
16
17use super::{Geom, GeomParams};
18
19/// Horizontal error bar geometry (requires `xmin`, `xmax`, `y`).
20pub struct GeomErrorbarh {
21    pub color: (u8, u8, u8),
22    /// Line width.
23    pub width: f64,
24    /// Half-height of the end caps, in normalized panel units (like
25    /// `GeomErrorbar::cap_width`).
26    pub cap_height: f64,
27    pub alpha: f64,
28}
29
30impl Default for GeomErrorbarh {
31    fn default() -> Self {
32        GeomErrorbarh {
33            color: (0, 0, 0),
34            width: 1.0,
35            cap_height: 0.02,
36            alpha: 1.0,
37        }
38    }
39}
40
41impl Geom for GeomErrorbarh {
42    fn draw(
43        &self,
44        data: &DataFrame,
45        coord: &dyn Coord,
46        scales: &ScaleSet,
47        _theme: &Theme,
48        backend: &mut dyn DrawBackend,
49    ) -> Result<(), RenderError> {
50        let y_col = data
51            .column("y")
52            .ok_or(RenderError::MissingAesthetic("y".into()))?;
53        let xmin_col = data
54            .column("xmin")
55            .ok_or(RenderError::MissingAesthetic("xmin".into()))?;
56        let xmax_col = data
57            .column("xmax")
58            .ok_or(RenderError::MissingAesthetic("xmax".into()))?;
59        let plot_area = backend.plot_area();
60        let x_scale = scales.get(&Aesthetic::X);
61        let y_scale = scales.get(&Aesthetic::Y);
62
63        for i in 0..data.nrows() {
64            let ny = y_scale.map(|s| s.map(&y_col[i])).unwrap_or(0.0);
65            let nx0 = x_scale.map(|s| s.map(&xmin_col[i])).unwrap_or(0.0);
66            let nx1 = x_scale.map(|s| s.map(&xmax_col[i])).unwrap_or(0.0);
67            if !(ny.is_finite() && nx0.is_finite() && nx1.is_finite()) {
68                continue;
69            }
70            let color = data
71                .column("color")
72                .and_then(|c| scales.map_color(&Aesthetic::Color, &c[i]))
73                .unwrap_or(self.color);
74            let style = LineStyle {
75                color,
76                alpha: self.alpha,
77                width: self.width,
78                linetype: Linetype::Solid,
79            };
80            let series = super::series_key(data, i);
81            let range = format!(
82                "[{}, {}]",
83                super::tip_value(&xmin_col[i]),
84                super::tip_value(&xmax_col[i])
85            );
86            let tip = Some(match &series {
87                Some(s) => format!("{s}: {}: {range}", super::tip_value(&y_col[i])),
88                None => format!("{}: {range}", super::tip_value(&y_col[i])),
89            });
90            super::set_mark(
91                backend,
92                tip,
93                Some(super::tip_value(&y_col[i])),
94                series,
95                super::raw_value(&xmin_col[i])
96                    .zip(super::raw_value(&xmax_col[i]))
97                    .map(|(a, b)| format!("{a} {b}")),
98            );
99            let h = self.cap_height;
100            let t = |p: (f64, f64)| coord.transform(p, &plot_area);
101            // One connected polyline per bar (cap, bar, cap) so each
102            // interval is a single hoverable mark.
103            backend.draw_line(
104                &[
105                    t((nx0, ny - h)),
106                    t((nx0, ny + h)),
107                    t((nx0, ny)),
108                    t((nx1, ny)),
109                    t((nx1, ny + h)),
110                    t((nx1, ny - h)),
111                ],
112                &style,
113            )?;
114        }
115        super::clear_mark(backend);
116        Ok(())
117    }
118
119    fn required_aes(&self) -> Vec<Aesthetic> {
120        vec![Aesthetic::Y, Aesthetic::Xmin, Aesthetic::Xmax]
121    }
122    fn default_stat(&self) -> Box<dyn Stat> {
123        Box::new(StatIdentity)
124    }
125    fn default_position(&self) -> Box<dyn Position> {
126        Box::new(PositionIdentity)
127    }
128    fn default_params(&self) -> GeomParams {
129        GeomParams::default()
130    }
131    fn name(&self) -> &str {
132        "errorbarh"
133    }
134    fn set_series_color(&mut self, color: (u8, u8, u8)) {
135        self.color = color;
136    }
137}