Skip to main content

ggplot_rs/geom/
area.rs

1use crate::aes::Aesthetic;
2use crate::coord::Coord;
3use crate::data::{DataFrame, Value};
4use crate::position::identity::PositionIdentity;
5use crate::position::Position;
6use crate::render::backend::{DrawBackend, LineStyle, Linetype, 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/// Area geometry — filled polygon from line to x-axis baseline.
16pub struct GeomArea {
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 GeomArea {
24    fn default() -> Self {
25        GeomArea {
26            fill: (97, 156, 255),
27            color: (50, 50, 50),
28            alpha: 0.4,
29            line_width: 1.0,
30        }
31    }
32}
33
34impl Geom for GeomArea {
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
50        // A fill/color aesthetic (or an explicit group) splits the data into one
51        // filled band per series — as in a grouped or stacked area chart.
52        let group_col = data
53            .column("fill")
54            .or_else(|| data.column("color"))
55            .or_else(|| data.column("group"));
56        // `ymin` is set by position="stack"/"fill" (the bottom of each segment);
57        // without it the band runs from the y=0 baseline.
58        let ymin_col = data.column("ymin");
59
60        let plot_area = backend.plot_area();
61        let x_scale = scales.get(&Aesthetic::X);
62        let y_scale = scales.get(&Aesthetic::Y);
63        let base_ny = y_scale.map(|s| s.map(&Value::Float(0.0))).unwrap_or(0.0);
64
65        // Group row indices by the series key, preserving first-seen order (which
66        // is the stacking order the position adjustment produced).
67        let mut groups: Vec<(String, Vec<usize>)> = Vec::new();
68        match group_col {
69            Some(gc) => {
70                for (i, v) in gc.iter().enumerate() {
71                    let key = v.to_group_key();
72                    match groups.iter_mut().find(|(k, _)| k == &key) {
73                        Some((_, idx)) => idx.push(i),
74                        None => groups.push((key, vec![i])),
75                    }
76                }
77            }
78            None => groups.push((String::new(), (0..data.nrows()).collect())),
79        }
80
81        for (_, indices) in &groups {
82            // Order this series left-to-right so the band is a clean polygon.
83            let mut idx = indices.clone();
84            idx.sort_by(|&a, &b| {
85                let xa = x_scale.map(|s| s.map(&x_col[a])).unwrap_or(0.0);
86                let xb = x_scale.map(|s| s.map(&x_col[b])).unwrap_or(0.0);
87                xa.total_cmp(&xb)
88            });
89
90            let fill = group_col
91                .and_then(|gc| scales.map_color(&Aesthetic::Fill, &gc[idx[0]]))
92                .or_else(|| {
93                    group_col.and_then(|gc| scales.map_color(&Aesthetic::Color, &gc[idx[0]]))
94                })
95                .unwrap_or(self.fill);
96
97            let mut upper: Vec<(f64, f64)> = Vec::with_capacity(idx.len());
98            let mut lower: Vec<(f64, f64)> = Vec::with_capacity(idx.len());
99            for &i in &idx {
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                let nb = ymin_col
103                    .and_then(|c| c[i].as_f64())
104                    .and_then(|v| y_scale.map(|s| s.map(&Value::Float(v))))
105                    .unwrap_or(base_ny);
106                upper.push(coord.transform((nx, ny), &plot_area));
107                lower.push(coord.transform((nx, nb), &plot_area));
108            }
109
110            let mut polygon = upper.clone();
111            lower.reverse();
112            polygon.extend(lower);
113            if polygon.len() >= 3 {
114                backend.draw_polygon(
115                    &polygon,
116                    &RectStyle {
117                        fill: Some(fill),
118                        stroke: None,
119                        stroke_width: 0.0,
120                        alpha: self.alpha,
121                        clip: true,
122                    },
123                )?;
124            }
125            if upper.len() >= 2 {
126                // A single-series area keeps its dark outline; multi-series bands
127                // outline in their own fill so stacked bands stay legible.
128                let line_color = if groups.len() > 1 { fill } else { self.color };
129                backend.draw_line(
130                    &upper,
131                    &LineStyle {
132                        color: line_color,
133                        alpha: 1.0,
134                        width: self.line_width,
135                        linetype: Linetype::Solid,
136                    },
137                )?;
138            }
139        }
140
141        Ok(())
142    }
143
144    fn required_aes(&self) -> Vec<Aesthetic> {
145        vec![Aesthetic::X, Aesthetic::Y]
146    }
147
148    fn default_stat(&self) -> Box<dyn Stat> {
149        Box::new(StatIdentity)
150    }
151    fn default_position(&self) -> Box<dyn Position> {
152        Box::new(PositionIdentity)
153    }
154    fn default_params(&self) -> GeomParams {
155        GeomParams::default()
156    }
157    fn name(&self) -> &str {
158        "area"
159    }
160
161    fn include_zero_baseline(&self) -> bool {
162        true
163    }
164
165    fn set_series_color(&mut self, color: (u8, u8, u8)) {
166        self.fill = color;
167    }
168}