Skip to main content

ggplot_rs/geom/
qq.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, PointShape, PointStyle};
7use crate::render::RenderError;
8use crate::scale::ScaleSet;
9use crate::stat::qq::{StatQQ, StatQQLine};
10use crate::stat::Stat;
11use crate::theme::Theme;
12
13use super::{Geom, GeomParams};
14
15/// QQ plot geometry — draws points at theoretical vs sample quantiles.
16pub struct GeomQQ {
17    pub size: f64,
18    pub color: (u8, u8, u8),
19    pub alpha: f64,
20}
21
22impl Default for GeomQQ {
23    fn default() -> Self {
24        GeomQQ {
25            size: 3.0,
26            color: (0, 0, 0),
27            alpha: 1.0,
28        }
29    }
30}
31
32impl Geom for GeomQQ {
33    fn draw(
34        &self,
35        data: &DataFrame,
36        coord: &dyn Coord,
37        scales: &ScaleSet,
38        _theme: &Theme,
39        backend: &mut dyn DrawBackend,
40    ) -> Result<(), RenderError> {
41        let x_col = data
42            .column("x")
43            .ok_or(RenderError::MissingAesthetic("x".into()))?;
44        let y_col = data
45            .column("y")
46            .ok_or(RenderError::MissingAesthetic("y".into()))?;
47        let color_col = data.column("color");
48
49        let plot_area = backend.plot_area();
50        let x_scale = scales.get(&Aesthetic::X);
51        let y_scale = scales.get(&Aesthetic::Y);
52
53        for i in 0..data.nrows() {
54            let nx = x_scale.map(|s| s.map(&x_col[i])).unwrap_or(0.0);
55            let ny = y_scale.map(|s| s.map(&y_col[i])).unwrap_or(0.0);
56            let (px, py) = coord.transform((nx, ny), &plot_area);
57
58            let pt_color = color_col
59                .and_then(|cc| scales.map_color(&Aesthetic::Color, &cc[i]))
60                .unwrap_or(self.color);
61
62            backend.draw_shape(
63                (px, py),
64                self.size,
65                &PointStyle {
66                    color: pt_color,
67                    alpha: self.alpha,
68                    filled: true,
69                    shape: PointShape::Circle,
70                },
71            )?;
72        }
73
74        Ok(())
75    }
76
77    fn required_aes(&self) -> Vec<Aesthetic> {
78        vec![Aesthetic::Y]
79    }
80
81    fn default_stat(&self) -> Box<dyn Stat> {
82        Box::new(StatQQ)
83    }
84
85    fn default_position(&self) -> Box<dyn Position> {
86        Box::new(PositionIdentity)
87    }
88
89    fn default_params(&self) -> GeomParams {
90        GeomParams::default()
91    }
92
93    fn name(&self) -> &str {
94        "qq"
95    }
96
97    fn set_series_color(&mut self, color: (u8, u8, u8)) {
98        self.color = color;
99    }
100}
101
102/// QQ line geometry — reference line through Q1/Q3 on the QQ plot.
103pub struct GeomQQLine {
104    pub color: (u8, u8, u8),
105    pub width: f64,
106    pub alpha: f64,
107}
108
109impl Default for GeomQQLine {
110    fn default() -> Self {
111        GeomQQLine {
112            color: (255, 0, 0),
113            width: 1.0,
114            alpha: 1.0,
115        }
116    }
117}
118
119impl Geom for GeomQQLine {
120    fn draw(
121        &self,
122        data: &DataFrame,
123        coord: &dyn Coord,
124        scales: &ScaleSet,
125        _theme: &Theme,
126        backend: &mut dyn DrawBackend,
127    ) -> Result<(), RenderError> {
128        let x_col = data
129            .column("x")
130            .ok_or(RenderError::MissingAesthetic("x".into()))?;
131        let y_col = data
132            .column("y")
133            .ok_or(RenderError::MissingAesthetic("y".into()))?;
134
135        let plot_area = backend.plot_area();
136        let x_scale = scales.get(&Aesthetic::X);
137        let y_scale = scales.get(&Aesthetic::Y);
138
139        let points: Vec<(f64, f64)> = (0..data.nrows())
140            .map(|i| {
141                let nx = x_scale.map(|s| s.map(&x_col[i])).unwrap_or(0.0);
142                let ny = y_scale.map(|s| s.map(&y_col[i])).unwrap_or(0.0);
143                coord.transform((nx, ny), &plot_area)
144            })
145            .collect();
146
147        if points.len() >= 2 {
148            backend.draw_line(
149                &points,
150                &LineStyle {
151                    color: self.color,
152                    alpha: self.alpha,
153                    width: self.width,
154                    linetype: Linetype::Dashed,
155                },
156            )?;
157        }
158
159        Ok(())
160    }
161
162    fn required_aes(&self) -> Vec<Aesthetic> {
163        vec![Aesthetic::Y]
164    }
165
166    fn default_stat(&self) -> Box<dyn Stat> {
167        Box::new(StatQQLine)
168    }
169
170    fn default_position(&self) -> Box<dyn Position> {
171        Box::new(PositionIdentity)
172    }
173
174    fn default_params(&self) -> GeomParams {
175        GeomParams::default()
176    }
177
178    fn name(&self) -> &str {
179        "qq_line"
180    }
181
182    fn set_series_color(&mut self, color: (u8, u8, u8)) {
183        self.color = color;
184    }
185}