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
111            backend.draw_polygon(
112                &points,
113                &RectStyle {
114                    fill: Some(fill_color),
115                    // No outline when line_width <= 0 (e.g. filled contour bands,
116                    // where per-triangle strokes would show the triangulation).
117                    stroke: (self.line_width > 0.0).then_some(stroke_color),
118                    stroke_width: self.line_width,
119                    alpha: self.alpha,
120                    clip: true,
121                },
122            )?;
123        }
124        super::clear_mark(backend);
125
126        Ok(())
127    }
128
129    fn required_aes(&self) -> Vec<Aesthetic> {
130        vec![Aesthetic::X, Aesthetic::Y]
131    }
132
133    fn default_stat(&self) -> Box<dyn Stat> {
134        Box::new(StatIdentity)
135    }
136
137    fn default_position(&self) -> Box<dyn Position> {
138        Box::new(PositionIdentity)
139    }
140
141    fn default_params(&self) -> GeomParams {
142        GeomParams::default()
143    }
144
145    fn name(&self) -> &str {
146        "polygon"
147    }
148
149    fn set_series_color(&mut self, color: (u8, u8, u8)) {
150        self.fill = color;
151    }
152}