Skip to main content

ggplot_rs/geom/
polygon.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::{Geom, GeomParams};
14
15/// Polygon geometry — arbitrary filled polygon from (x, y) grouped by group column.
16pub struct GeomPolygon {
17    pub fill: (u8, u8, u8),
18    pub color: (u8, u8, u8),
19    pub alpha: f64,
20    pub line_width: f64,
21}
22
23impl Default for GeomPolygon {
24    fn default() -> Self {
25        GeomPolygon {
26            fill: (97, 156, 255),
27            color: (50, 50, 50),
28            alpha: 0.5,
29            line_width: 0.5,
30        }
31    }
32}
33
34impl Geom for GeomPolygon {
35    fn draw(
36        &self,
37        data: &DataFrame,
38        coord: &dyn Coord,
39        scales: &ScaleSet,
40        _theme: &Theme,
41        backend: &mut dyn DrawBackend,
42    ) -> Result<(), RenderError> {
43        let x_col = data
44            .column("x")
45            .ok_or(RenderError::MissingAesthetic("x".into()))?;
46        let y_col = data
47            .column("y")
48            .ok_or(RenderError::MissingAesthetic("y".into()))?;
49        // Group by `group`, else by a mapped colour / fill (ggplot2 groups by
50        // discrete aesthetics), else one polygon.
51        let group_col = data
52            .column("group")
53            .or_else(|| data.column("color"))
54            .or_else(|| data.column("fill"));
55        let fill_col = data.column("fill");
56        let color_col = data.column("color");
57
58        let plot_area = backend.plot_area();
59        let x_scale = scales.get(&Aesthetic::X);
60        let y_scale = scales.get(&Aesthetic::Y);
61
62        // Group indices
63        let groups: Vec<(String, Vec<usize>)> = if let Some(gc) = group_col {
64            let mut groups: Vec<(String, Vec<usize>)> = Vec::new();
65            for (i, v) in gc.iter().enumerate() {
66                let key = v.to_group_key();
67                if let Some(entry) = groups.iter_mut().find(|(k, _)| k == &key) {
68                    entry.1.push(i);
69                } else {
70                    groups.push((key, vec![i]));
71                }
72            }
73            groups
74        } else {
75            vec![("".to_string(), (0..data.nrows()).collect())]
76        };
77
78        for (_, indices) in &groups {
79            if indices.len() < 3 {
80                continue;
81            }
82            let first_idx = indices[0];
83
84            let fill_color = fill_col
85                .and_then(|fc| scales.map_color(&Aesthetic::Fill, &fc[first_idx]))
86                .unwrap_or(self.fill);
87            // #206: a no-data group draws its scale's na_pattern instead
88            // of a flat na colour, when set.
89            let pattern = fill_col
90                .filter(|fc| matches!(fc[first_idx], crate::data::Value::Na))
91                .and_then(|_| scales.na_fill_pattern(&Aesthetic::Fill));
92
93            let stroke_color = color_col
94                .and_then(|cc| scales.map_color(&Aesthetic::Color, &cc[first_idx]))
95                .unwrap_or(self.color);
96
97            let mut mapped: Vec<(f64, f64)> = indices
98                .iter()
99                .map(|&i| {
100                    let nx = x_scale.map(|s| s.map(&x_col[i])).unwrap_or(0.0);
101                    let ny = y_scale.map(|s| s.map(&y_col[i])).unwrap_or(0.0);
102                    (nx, ny)
103                })
104                .collect();
105            // Radar series go around the spokes in axis order.
106            if coord.is_radar() {
107                mapped.sort_by(|a, b| a.0.total_cmp(&b.0));
108            }
109            let points: Vec<(f64, f64)> = mapped
110                .into_iter()
111                .map(|p| coord.transform(p, &plot_area))
112                .collect();
113            let series = super::series_key(data, first_idx);
114            super::set_mark(backend, series.clone(), None, series, None);
115            super::set_mark_formatted(backend, scales, None, &Aesthetic::Y, None);
116            super::set_row_extras(backend, data, first_idx);
117
118            let style = RectStyle {
119                fill: Some(fill_color),
120                // No outline when line_width <= 0 (e.g. filled contour bands,
121                // where per-triangle strokes would show the triangulation).
122                stroke: (self.line_width > 0.0).then_some(stroke_color),
123                stroke_width: self.line_width,
124                alpha: self.alpha,
125                clip: true,
126                corner_radius: 0.0,
127            };
128            match &pattern {
129                Some(p) => backend.draw_polygon_pattern(&points, p, &style)?,
130                None => backend.draw_polygon(&points, &style)?,
131            }
132        }
133        super::clear_mark(backend);
134        super::clear_row_extras(backend);
135
136        Ok(())
137    }
138
139    fn required_aes(&self) -> Vec<Aesthetic> {
140        vec![Aesthetic::X, Aesthetic::Y]
141    }
142
143    fn default_stat(&self) -> Box<dyn Stat> {
144        Box::new(StatIdentity)
145    }
146
147    fn default_position(&self) -> Box<dyn Position> {
148        Box::new(PositionIdentity)
149    }
150
151    fn default_params(&self) -> GeomParams {
152        GeomParams::default()
153    }
154
155    fn name(&self) -> &str {
156        "polygon"
157    }
158
159    fn apply_element_geom(&mut self, el: &crate::theme::elements::ElementGeom) {
160        if let (true, Some(ink)) = (self.color == (50, 50, 50), el.ink) {
161            self.color = ink;
162        }
163    }
164
165    fn consumes_tooltips(&self) -> bool {
166        true
167    }
168
169    fn set_series_color(&mut self, color: (u8, u8, u8)) {
170        self.fill = color;
171    }
172}