Skip to main content

ggplot_rs/position/
mod.rs

1pub mod dodge;
2pub mod dodge2;
3pub mod fill;
4pub mod identity;
5pub mod jitter;
6pub mod jitterdodge;
7pub mod nudge;
8pub mod stack;
9
10use crate::data::DataFrame;
11
12/// Parameters for position adjustments.
13#[derive(Clone, Debug)]
14pub struct PositionParams {
15    pub width: f64,
16    pub height: f64,
17    /// The whole layer's group rank order (review #167 finding 2), set by
18    /// `build::compute_position_per_panel` from [`Position::group_order`]
19    /// *before* splitting the layer's rows into panels. A per-panel
20    /// position (`PositionDodge`/`PositionDodge2`) ranks groups from this
21    /// — the whole layer's first-appearance order — rather than
22    /// recomputing it from whatever order each panel's own rows happen to
23    /// list groups in, so every panel draws a given group on the same
24    /// side (matching 0.22.2 and ggplot2 4.0.3's global `group` id).
25    /// `None` outside the per-panel path (or for positions that don't
26    /// override `group_order`), in which case each position falls back
27    /// to its previous, locally-computed order.
28    pub group_order: Option<Vec<String>>,
29}
30
31impl Default for PositionParams {
32    fn default() -> Self {
33        PositionParams {
34            width: 0.9,
35            height: 0.0,
36            group_order: None,
37        }
38    }
39}
40
41/// Trait for position adjustments.
42pub trait Position: Send + Sync {
43    /// Adjust positions for data.
44    fn compute(&self, data: &mut DataFrame, params: &PositionParams);
45
46    fn name(&self) -> &str;
47
48    /// Whether this position must run separately per facet panel rather
49    /// than once on the pooled rows before facets are split (B3-3, D8).
50    /// Default `false`. `PositionStack`/`PositionFill` override this to
51    /// `true` — a shared x category's stack height must not mix rows from
52    /// different panels. `PositionDodge`/`PositionDodge2`/`PositionJitter`
53    /// also override this to `true` (sipemu/ggplot-rs#153): ggplot2
54    /// computes a dodge's group count/offset via `compute_panel` (per
55    /// panel), and `position_jitter`'s default width/height — 40% of
56    /// `resolution()` — is likewise measured from each panel's own x/y,
57    /// not the rows pooled across panels (verified against ggplot2 4.0.3
58    /// with `layer_data()` on faceted plots; see `tests/position_panels_r.rs`).
59    fn per_panel(&self) -> bool {
60        false
61    }
62
63    /// The whole layer's group rank order (first-appearance, pre-panel-
64    /// split), for a `per_panel` position whose group *ranking* must come
65    /// from the whole layer even though its per-x *counting* stays
66    /// per-panel (review #167 finding 2; sipemu/ggplot-rs#153).
67    /// `build::compute_position_per_panel` calls this on the pooled data
68    /// before splitting and threads the result through
69    /// [`PositionParams::group_order`] to every panel's `compute` call.
70    /// Default `None` — positions that don't need this (stack/fill/
71    /// jitter) are unaffected.
72    fn group_order(&self, _data: &DataFrame) -> Option<Vec<String>> {
73        None
74    }
75
76    /// Validate this position's own parameters once per layer (D2);
77    /// default no-op. Implementations push a warning and fall back to a
78    /// sane default rather than panicking or silently miscomputing.
79    fn validate(&self, _warnings: &mut Vec<String>) {}
80}
81
82/// Column in which stacking positions keep each row's original (pre-stack)
83/// `y`, so hover metadata (`data-value`, tooltips) can report the measured
84/// value rather than the cumulative stack top.
85pub const RAW_Y_COL: &str = ".y_raw";
86
87/// Remember the pre-position `y` in [`RAW_Y_COL`] (first adjustment wins).
88pub(crate) fn preserve_raw_y(data: &mut DataFrame, y: &[crate::data::Value]) {
89    if !data.has_column(RAW_Y_COL) {
90        data.add_column(RAW_Y_COL.to_string(), y.to_vec());
91    }
92}
93
94/// Column in which dodging positions store each row's offset (in category
95/// units) when x is *discrete*: the category itself can't move, so geoms add
96/// `offset / n_levels` to the mapped x (see `geom::support::XMapper`).
97pub const DODGE_OFFSET_COL: &str = ".x_dodge_offset";
98/// Number of dodged groups sharing a row's discrete x position (stored next to
99/// [`DODGE_OFFSET_COL`]); bar-like geoms divide their width by it.
100pub const DODGE_N_COL: &str = ".x_dodge_n";