Skip to main content

ggplot_rs/geom/
mod.rs

1pub mod area;
2pub mod bar;
3pub mod bin2d;
4pub mod blank;
5pub mod boxplot;
6pub mod bracket;
7pub mod candlestick;
8pub mod censor;
9pub mod col;
10pub mod contour;
11pub mod cooks;
12pub mod count;
13pub mod crossbar;
14pub mod curve;
15pub mod density;
16pub mod density2d;
17pub mod dotplot;
18pub mod errorbar;
19pub mod errorbarh;
20pub mod freqpoly;
21pub mod hex;
22pub mod histogram;
23pub mod jitter;
24pub mod line;
25pub mod linerange;
26pub mod path;
27pub mod point;
28pub mod pointrange;
29pub mod polygon;
30pub mod qq;
31pub mod raster;
32pub mod rect;
33pub mod refline;
34pub mod repel;
35pub mod ribbon;
36pub mod rug;
37pub mod segment;
38#[cfg(feature = "sf")]
39pub mod sf;
40pub mod smooth;
41pub mod spoke;
42pub mod step;
43pub(crate) mod support;
44pub mod text;
45pub mod tile;
46pub mod violin;
47
48use std::collections::HashMap;
49
50use crate::aes::Aesthetic;
51use crate::coord::Coord;
52use crate::data::DataFrame;
53use crate::position::Position;
54use crate::render::backend::DrawBackend;
55use crate::render::RenderError;
56use crate::scale::ScaleSet;
57use crate::stat::Stat;
58use crate::theme::Theme;
59
60/// Fixed (non-mapped) visual parameters for a geom.
61#[derive(Clone, Debug, Default)]
62pub struct GeomParams {
63    pub values: HashMap<String, f64>,
64    pub color: Option<(u8, u8, u8)>,
65    pub fill: Option<(u8, u8, u8)>,
66    pub alpha: Option<f64>,
67}
68
69/// Trait for geometric objects that draw data on the plot.
70pub trait Geom: Send + Sync {
71    /// Draw this geometry.
72    fn draw(
73        &self,
74        data: &DataFrame,
75        coord: &dyn Coord,
76        scales: &ScaleSet,
77        theme: &Theme,
78        backend: &mut dyn DrawBackend,
79    ) -> Result<(), RenderError>;
80
81    /// Required aesthetics.
82    fn required_aes(&self) -> Vec<Aesthetic>;
83
84    /// Default stat for this geom.
85    fn default_stat(&self) -> Box<dyn Stat>;
86
87    /// Default position adjustment.
88    fn default_position(&self) -> Box<dyn Position>;
89
90    /// Non-mapped visual defaults.
91    fn default_params(&self) -> GeomParams;
92
93    /// Name for debug/display.
94    fn name(&self) -> &str;
95
96    /// Apply a brand/primary color to this geom's single-series default (its
97    /// `color` or `fill`). The build pipeline calls this only when the layer has
98    /// no color/fill aesthetic mapped, so an explicit mapping always wins. The
99    /// default is a no-op; series geoms override it.
100    fn set_series_color(&mut self, _color: (u8, u8, u8)) {}
101
102    /// Whether this geom draws from a 0 baseline, so the Y scale should include
103    /// 0 even when `y` is explicitly mapped (bars/columns/area/histograms — as
104    /// in ggplot2). Default false.
105    fn include_zero_baseline(&self) -> bool {
106        false
107    }
108
109    /// Whether `-Inf`/`Inf` position values are meaningful for this geom
110    /// (ggplot2: "extend to the panel edge"). Rows with infinite positions are
111    /// otherwise dropped with a warning before stats run. `NaN` is always
112    /// dropped. Default false; `geom_rect` returns true.
113    fn allows_infinite(&self) -> bool {
114        false
115    }
116
117    /// Geom-specific data preparation after the stat and position steps but
118    /// before scale training (ggplot2's `GeomX$setup_data`) — e.g. a tile adds
119    /// its `xmin`/`xmax`/`ymin`/`ymax` extents so continuous scales train on
120    /// them. Default: no-op.
121    fn setup_data(&self, _data: &mut DataFrame) {}
122
123    /// Whether the layer inherits the plot-level aesthetic mapping (ggplot2's
124    /// `inherit.aes`). Reference-line geoms return false, as in ggplot2, so a
125    /// plot-level `color`/`x`/`y` mapping does not leak into a `geom_hline`.
126    /// Default true.
127    fn inherit_aes(&self) -> bool {
128        true
129    }
130}
131
132/// Format a value for a hover tooltip — strings verbatim, numbers rounded short,
133/// `Na` empty.
134pub(crate) fn tip_value(v: &crate::data::Value) -> String {
135    crate::format::format_value(v)
136}
137
138/// Half-width (normalized panel units) for bars on a continuous / date x axis:
139/// `width` × the smallest gap between distinct mapped x positions, like
140/// ggplot2's `resolution(x)`. A fixed fraction would make 60 daily bars overlap
141/// and 3 bars look like slivers. Falls back to `fallback` with < 2 distinct xs.
142pub(crate) fn continuous_bar_half_width(
143    mapped_xs: impl Iterator<Item = f64>,
144    width: f64,
145    fallback: f64,
146) -> f64 {
147    let mut xs: Vec<f64> = mapped_xs.filter(|x| x.is_finite()).collect();
148    xs.sort_by(|a, b| a.total_cmp(b));
149    xs.dedup_by(|a, b| (*a - *b).abs() < 1e-12);
150    let gap = xs
151        .windows(2)
152        .map(|w| w[1] - w[0])
153        .fold(f64::INFINITY, f64::min);
154    if gap.is_finite() && gap > 0.0 {
155        gap * width / 2.0
156    } else {
157        fallback
158    }
159}
160
161/// Raw (unformatted) value for a `data-value` attribute: numbers in shortest
162/// round-trip form, date-times as epoch seconds, strings verbatim. `None` for
163/// missing / non-finite values.
164pub(crate) fn raw_value(v: &crate::data::Value) -> Option<String> {
165    use crate::data::Value;
166    match v {
167        Value::Float(f) if f.is_finite() => Some(format!("{f}")),
168        Value::Float(_) | Value::Na => None,
169        Value::Integer(i) => Some(i.to_string()),
170        Value::DateTime(s) => Some(s.to_string()),
171        Value::Str(s) => Some(s.clone()),
172        Value::Bool(b) => Some(b.to_string()),
173    }
174}
175
176/// The series key of row `i` — its colour, else fill, else group level — for
177/// a `data-series` attribute.
178pub(crate) fn series_key(data: &DataFrame, i: usize) -> Option<String> {
179    ["color", "fill", "group"].iter().find_map(|c| {
180        data.column(c)
181            .and_then(|col| col.get(i))
182            .filter(|v| !v.is_na())
183            .map(tip_value)
184            .filter(|s| !s.is_empty())
185    })
186}
187
188/// The raw measured y of row `i`. Stacked/filled positions overwrite `y` with
189/// the cumulative top, so prefer the pre-position value they preserve.
190pub(crate) fn measured_value(data: &DataFrame, i: usize) -> Option<String> {
191    data.column(crate::position::RAW_Y_COL)
192        .or_else(|| data.column("y"))
193        .and_then(|c| c.get(i))
194        .and_then(raw_value)
195}
196
197/// Set all per-mark metadata (tooltip, `data-x`, `data-series`, `data-value`)
198/// for the next drawn mark(s).
199pub(crate) fn set_mark(
200    backend: &mut dyn DrawBackend,
201    tooltip: Option<String>,
202    x: Option<String>,
203    series: Option<String>,
204    value: Option<String>,
205) {
206    backend.set_tooltip(tooltip);
207    backend.set_mark_axis(x);
208    backend.set_mark_series(series);
209    backend.set_mark_value(value);
210}
211
212/// Clear all per-mark metadata after a geom has drawn its marks.
213pub(crate) fn clear_mark(backend: &mut dyn DrawBackend) {
214    set_mark(backend, None, None, None, None);
215}