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
88            let stroke_color = color_col
89                .and_then(|cc| scales.map_color(&Aesthetic::Color, &cc[first_idx]))
90                .unwrap_or(self.color);
91
92            let mut mapped: Vec<(f64, f64)> = indices
93                .iter()
94                .map(|&i| {
95                    let nx = x_scale.map(|s| s.map(&x_col[i])).unwrap_or(0.0);
96                    let ny = y_scale.map(|s| s.map(&y_col[i])).unwrap_or(0.0);
97                    (nx, ny)
98                })
99                .collect();
100            // Radar series go around the spokes in axis order.
101            if coord.is_radar() {
102                mapped.sort_by(|a, b| a.0.total_cmp(&b.0));
103            }
104            let points: Vec<(f64, f64)> = mapped
105                .into_iter()
106                .map(|p| coord.transform(p, &plot_area))
107                .collect();
108            let series = super::series_key(data, first_idx);
109            super::set_mark(backend, series.clone(), None, series, None);
110            super::set_mark_formatted(backend, scales, None, &Aesthetic::Y, None);
111            super::set_row_extras(backend, data, first_idx);
112
113            backend.draw_polygon(
114                &points,
115                &RectStyle {
116                    fill: Some(fill_color),
117                    // No outline when line_width <= 0 (e.g. filled contour bands,
118                    // where per-triangle strokes would show the triangulation).
119                    stroke: (self.line_width > 0.0).then_some(stroke_color),
120                    stroke_width: self.line_width,
121                    alpha: self.alpha,
122                    clip: true,
123                    corner_radius: 0.0,
124                },
125            )?;
126        }
127        super::clear_mark(backend);
128        super::clear_row_extras(backend);
129
130        Ok(())
131    }
132
133    fn required_aes(&self) -> Vec<Aesthetic> {
134        vec![Aesthetic::X, Aesthetic::Y]
135    }
136
137    fn default_stat(&self) -> Box<dyn Stat> {
138        Box::new(StatIdentity)
139    }
140
141    fn default_position(&self) -> Box<dyn Position> {
142        Box::new(PositionIdentity)
143    }
144
145    fn default_params(&self) -> GeomParams {
146        GeomParams::default()
147    }
148
149    fn name(&self) -> &str {
150        "polygon"
151    }
152
153    fn apply_element_geom(&mut self, el: &crate::theme::elements::ElementGeom) {
154        if let (true, Some(ink)) = (self.color == (50, 50, 50), el.ink) {
155            self.color = ink;
156        }
157    }
158
159    fn consumes_tooltips(&self) -> bool {
160        true
161    }
162
163    fn set_series_color(&mut self, color: (u8, u8, u8)) {
164        self.fill = color;
165    }
166}