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