Skip to main content

ggplot_rs/geom/
refline.rs

1use crate::aes::Aesthetic;
2use crate::coord::Coord;
3use crate::data::{DataFrame, Value};
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/// Horizontal reference line spanning the entire plot width.
16pub struct GeomHline {
17    pub yintercept: f64,
18    pub color: (u8, u8, u8),
19    pub width: f64,
20    pub linetype: Linetype,
21    pub alpha: f64,
22}
23
24impl GeomHline {
25    pub fn new(yintercept: f64) -> Self {
26        GeomHline {
27            yintercept,
28            color: (0, 0, 0),
29            width: 1.0,
30            linetype: Linetype::Dashed,
31            alpha: 1.0,
32        }
33    }
34}
35
36impl Geom for GeomHline {
37    fn draw(
38        &self,
39        _data: &DataFrame,
40        coord: &dyn Coord,
41        scales: &ScaleSet,
42        _theme: &Theme,
43        backend: &mut dyn DrawBackend,
44    ) -> Result<(), RenderError> {
45        let plot_area = backend.plot_area();
46        let y_scale = scales.get(&Aesthetic::Y);
47
48        let ny = y_scale
49            .map(|s| s.map(&Value::Float(self.yintercept)))
50            .unwrap_or(0.5);
51
52        let (left, y_px) = coord.transform((0.0, ny), &plot_area);
53        let (right, _) = coord.transform((1.0, ny), &plot_area);
54
55        backend.draw_line(
56            &[(left, y_px), (right, y_px)],
57            &LineStyle {
58                color: self.color,
59                alpha: self.alpha,
60                width: self.width,
61                linetype: self.linetype,
62            },
63        )?;
64
65        Ok(())
66    }
67
68    fn required_aes(&self) -> Vec<Aesthetic> {
69        vec![]
70    }
71    fn default_stat(&self) -> Box<dyn Stat> {
72        Box::new(StatIdentity)
73    }
74    fn default_position(&self) -> Box<dyn Position> {
75        Box::new(PositionIdentity)
76    }
77    fn default_params(&self) -> GeomParams {
78        GeomParams::default()
79    }
80    fn name(&self) -> &str {
81        "hline"
82    }
83}
84
85/// Vertical reference line spanning the entire plot height.
86pub struct GeomVline {
87    pub xintercept: f64,
88    pub color: (u8, u8, u8),
89    pub width: f64,
90    pub linetype: Linetype,
91    pub alpha: f64,
92}
93
94impl GeomVline {
95    pub fn new(xintercept: f64) -> Self {
96        GeomVline {
97            xintercept,
98            color: (0, 0, 0),
99            width: 1.0,
100            linetype: Linetype::Dashed,
101            alpha: 1.0,
102        }
103    }
104}
105
106impl Geom for GeomVline {
107    fn draw(
108        &self,
109        _data: &DataFrame,
110        coord: &dyn Coord,
111        scales: &ScaleSet,
112        _theme: &Theme,
113        backend: &mut dyn DrawBackend,
114    ) -> Result<(), RenderError> {
115        let plot_area = backend.plot_area();
116        let x_scale = scales.get(&Aesthetic::X);
117
118        let nx = x_scale
119            .map(|s| s.map(&Value::Float(self.xintercept)))
120            .unwrap_or(0.5);
121
122        let (x_px, top) = coord.transform((nx, 1.0), &plot_area);
123        let (_, bottom) = coord.transform((nx, 0.0), &plot_area);
124
125        backend.draw_line(
126            &[(x_px, top), (x_px, bottom)],
127            &LineStyle {
128                color: self.color,
129                alpha: self.alpha,
130                width: self.width,
131                linetype: self.linetype,
132            },
133        )?;
134
135        Ok(())
136    }
137
138    fn required_aes(&self) -> Vec<Aesthetic> {
139        vec![]
140    }
141    fn default_stat(&self) -> Box<dyn Stat> {
142        Box::new(StatIdentity)
143    }
144    fn default_position(&self) -> Box<dyn Position> {
145        Box::new(PositionIdentity)
146    }
147    fn default_params(&self) -> GeomParams {
148        GeomParams::default()
149    }
150    fn name(&self) -> &str {
151        "vline"
152    }
153}
154
155/// Arbitrary line y = slope*x + intercept spanning the plot.
156pub struct GeomAbline {
157    pub slope: f64,
158    pub intercept: f64,
159    pub color: (u8, u8, u8),
160    pub width: f64,
161    pub linetype: Linetype,
162    pub alpha: f64,
163}
164
165impl GeomAbline {
166    pub fn new(slope: f64, intercept: f64) -> Self {
167        GeomAbline {
168            slope,
169            intercept,
170            color: (0, 0, 0),
171            width: 1.0,
172            linetype: Linetype::Dashed,
173            alpha: 1.0,
174        }
175    }
176}
177
178impl Geom for GeomAbline {
179    fn draw(
180        &self,
181        _data: &DataFrame,
182        coord: &dyn Coord,
183        _scales: &ScaleSet,
184        _theme: &Theme,
185        backend: &mut dyn DrawBackend,
186    ) -> Result<(), RenderError> {
187        let plot_area = backend.plot_area();
188
189        // Sample points across the x-range using normalized coords
190        let n_pts = 50;
191        let points: Vec<(f64, f64)> = (0..=n_pts)
192            .filter_map(|i| {
193                let nx = i as f64 / n_pts as f64;
194                let ny = self.slope * nx + self.intercept;
195                if (-0.1..=1.1).contains(&ny) {
196                    Some(coord.transform((nx, ny), &plot_area))
197                } else {
198                    None
199                }
200            })
201            .collect();
202
203        if points.len() >= 2 {
204            backend.draw_line(
205                &points,
206                &LineStyle {
207                    color: self.color,
208                    alpha: self.alpha,
209                    width: self.width,
210                    linetype: self.linetype,
211                },
212            )?;
213        }
214
215        Ok(())
216    }
217
218    fn required_aes(&self) -> Vec<Aesthetic> {
219        vec![]
220    }
221    fn default_stat(&self) -> Box<dyn Stat> {
222        Box::new(StatIdentity)
223    }
224    fn default_position(&self) -> Box<dyn Position> {
225        Box::new(PositionIdentity)
226    }
227    fn default_params(&self) -> GeomParams {
228        GeomParams::default()
229    }
230    fn name(&self) -> &str {
231        "abline"
232    }
233}