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";