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