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                ..Default::default()
80            };
81            let series = super::series_key(data, i);
82            let range = format!(
83                "[{}, {}]",
84                super::tip_value(&xmin_col[i]),
85                super::tip_value(&xmax_col[i])
86            );
87            let tip = Some(match &series {
88                Some(s) => format!("{s}: {}: {range}", super::tip_value(&y_col[i])),
89                None => format!("{}: {range}", super::tip_value(&y_col[i])),
90            });
91            super::set_mark(
92                backend,
93                tip,
94                Some(super::tip_value(&y_col[i])),
95                series,
96                super::raw_value(&xmin_col[i])
97                    .zip(super::raw_value(&xmax_col[i]))
98                    .map(|(a, b)| format!("{a} {b}")),
99            );
100            // The "value" here is an [xmin, xmax] range, not a single raw
101            // value `Scale::format_value` can take — reset only.
102            super::set_mark_formatted(backend, scales, None, &Aesthetic::X, None);
103            let h = self.cap_height;
104            let t = |p: (f64, f64)| coord.transform(p, &plot_area);
105            // One connected polyline per bar (cap, bar, cap) so each
106            // interval is a single hoverable mark.
107            backend.draw_line(
108                &[
109                    t((nx0, ny - h)),
110                    t((nx0, ny + h)),
111                    t((nx0, ny)),
112                    t((nx1, ny)),
113                    t((nx1, ny + h)),
114                    t((nx1, ny - h)),
115                ],
116                &style,
117            )?;
118        }
119        super::clear_mark(backend);
120        Ok(())
121    }
122
123    fn required_aes(&self) -> Vec<Aesthetic> {
124        vec![Aesthetic::Y, Aesthetic::Xmin, Aesthetic::Xmax]
125    }
126    fn default_stat(&self) -> Box<dyn Stat> {
127        Box::new(StatIdentity)
128    }
129    fn default_position(&self) -> Box<dyn Position> {
130        Box::new(PositionIdentity)
131    }
132    fn default_params(&self) -> GeomParams {
133        GeomParams::default()
134    }
135    fn name(&self) -> &str {
136        "errorbarh"
137    }
138    fn apply_element_geom(&mut self, el: &crate::theme::elements::ElementGeom) {
139        if let (true, Some(ink)) = (self.color == (0, 0, 0), el.ink) {
140            self.color = ink;
141        }
142    }
143    fn set_series_color(&mut self, color: (u8, u8, u8)) {
144        self.color = color;
145    }
146}