Skip to main content

ggplot_rs/scale/
mod.rs

1pub mod alpha;
2pub mod color;
3pub mod continuous;
4pub mod datetime;
5pub mod discrete;
6pub mod format;
7pub mod gradient;
8pub mod gradient_n;
9pub mod grey;
10pub mod identity;
11pub mod linetype;
12pub mod linetype_manual;
13pub mod linewidth;
14pub mod manual;
15pub mod modified;
16pub mod palettes;
17pub mod scale_set;
18pub mod sec_axis;
19pub mod shape;
20pub mod shape_manual;
21pub mod size;
22pub mod steps;
23pub mod transform;
24pub mod util;
25
26pub use scale_set::ScaleSet;
27
28use crate::aes::Aesthetic;
29use crate::data::Value;
30use crate::render::backend::{Linetype, PointShape};
31
32/// Trait for scales that map data values to visual properties.
33pub trait Scale: Send + Sync {
34    /// Which aesthetic this scale is for.
35    fn aesthetic(&self) -> Aesthetic;
36
37    /// Incorporate data values to determine domain.
38    fn train(&mut self, values: &[Value]);
39
40    /// Map a data value to a [0, 1] normalized position (position scales)
41    /// or to a concrete visual value index (color/size scales).
42    fn map(&self, value: &Value) -> f64;
43
44    /// Generate break positions and labels for axis/legend.
45    fn breaks(&self) -> Vec<(f64, String)>;
46
47    /// Human-readable name (axis title).
48    fn name(&self) -> &str;
49
50    /// Set the scale name.
51    fn set_name(&mut self, name: &str);
52
53    /// Apply transformation to raw data (e.g., log10).
54    fn transform(&self, value: &Value) -> Value {
55        value.clone()
56    }
57
58    /// Whether this is a discrete scale.
59    fn is_discrete(&self) -> bool {
60        false
61    }
62
63    /// Map a data value to an RGB color. Default returns None.
64    fn map_to_color(&self, _value: &Value) -> Option<(u8, u8, u8)> {
65        None
66    }
67
68    /// Map a data value to a point shape. Default returns None.
69    fn map_to_shape(&self, _value: &Value) -> Option<PointShape> {
70        None
71    }
72
73    /// Map a data value to a linetype. Default returns None.
74    fn map_to_linetype(&self, _value: &Value) -> Option<Linetype> {
75        None
76    }
77
78    /// Map a data value to a point size (radius in pixels). Default returns None.
79    fn map_to_size(&self, _value: &Value) -> Option<f64> {
80        None
81    }
82
83    /// Map a data value to an alpha (opacity) value. Default returns None.
84    fn map_to_alpha(&self, _value: &Value) -> Option<f64> {
85        None
86    }
87
88    /// Get the secondary axis specification, if any.
89    fn sec_axis(&self) -> Option<&sec_axis::SecAxis> {
90        None
91    }
92
93    /// Adopt a plot-level default [`Locale`](crate::locale::Locale) for the
94    /// default break labels (number marks, month names, date formats), unless
95    /// the scale already has its own. Called by the plot builder when
96    /// [`GGPlot::locale`](crate::GGPlot::locale) is set. Default: ignored
97    /// (scales without numeric/date labels).
98    fn set_default_locale(&mut self, _locale: &crate::locale::Locale) {}
99
100    /// The locale this scale's default labels are rendered with, if any.
101    fn locale(&self) -> Option<&crate::locale::Locale> {
102        None
103    }
104
105    /// Override the trained domain limits (used by coord_cartesian zoom).
106    fn set_limits(&mut self, _min: f64, _max: f64) {
107        // Default no-op. Continuous scales override this.
108    }
109
110    /// Return OOB filter limits if this scale was created with explicit limits
111    /// (e.g., via xlim/ylim). Data outside these limits should be removed before stats.
112    fn filter_limits(&self) -> Option<(f64, f64)> {
113        None
114    }
115
116    /// Return the trained data domain (min, max) for continuous scales.
117    /// Used by the colorbar legend to pass data-space values to map_to_color().
118    fn domain(&self) -> Option<(f64, f64)> {
119        None
120    }
121
122    /// The data values at the panel edges (normalized 0 and 1) for a trained
123    /// continuous position scale — the limits after expansion, in the scale's
124    /// transformed space. `None` for discrete / untrained / non-position scales.
125    fn expanded_domain(&self) -> Option<(f64, f64)> {
126        None
127    }
128
129    /// Whether this axis is drawn on the opposite side (x → top, y → right).
130    fn axis_position_opposite(&self) -> bool {
131        false
132    }
133
134    /// Set the expansion multiplier/additive constant (R's `expand =
135    /// c(mult, add)`), applied symmetrically to both ends. Used by
136    /// `CoordSf::train_scales` (`expand(false)` passes `(0.0, 0.0)`) to pin
137    /// an explicit `xlim`/`ylim` to the panel exactly. Default: no-op
138    /// (discrete / non-position scales have no expansion to set).
139    fn set_expand(&mut self, _mult: f64, _add: f64) {}
140
141    /// Map an already-computed `[0, 1]` scale fraction (as returned
142    /// alongside each label by [`breaks`](Self::breaks)) directly to an
143    /// output size, bypassing the round trip through [`map`](Self::map) — a
144    /// discrete-style legend key only has the break's rendered *label*, not
145    /// its original data value, so a continuous scale can't recover the
146    /// fraction by re-mapping the label text. Default: `None`.
147    fn map_fraction_to_size(&self, _fraction: f64) -> Option<f64> {
148        None
149    }
150
151    /// Clone this scale into a boxed trait object.
152    fn clone_box(&self) -> Box<dyn Scale>;
153
154    /// Reset training state so the scale can be retrained on new data.
155    fn reset_training(&mut self) {}
156
157    /// Validate this scale's configuration once training is complete (e.g.
158    /// a transform undefined at the domain's own limits) and push a
159    /// human-readable warning for each problem found. Called once per scale
160    /// after the final training pass, with the plot's warnings sink —
161    /// `map`/`map_to_size` run per mark at draw time and have no such
162    /// access, so a scale-level problem must be reported here instead.
163    /// Default: no-op.
164    fn validate(&self, _warnings: &mut Vec<String>) {}
165}
166
167#[cfg(test)]
168mod default_trait_method_tests {
169    use super::Scale;
170    use crate::aes::Aesthetic;
171    use crate::data::Value;
172
173    /// Scales that don't override `set_expand`/`map_fraction_to_size`
174    /// (e.g. an identity scale, which has no expansion or numeric output
175    /// at all) get the documented no-op/`None` defaults rather than a
176    /// missing-method compile error.
177    #[test]
178    fn set_expand_and_map_fraction_to_size_defaults_are_harmless() {
179        let mut scale = crate::scale::identity::ScaleColorIdentity::new(Aesthetic::Color);
180        // No panic, no effect — there's no expansion concept for a
181        // colour-identity scale.
182        scale.set_expand(0.1, 5.0);
183        assert_eq!(scale.map_fraction_to_size(0.5), None);
184        // validate() is also a no-op default; an identity scale never has
185        // anything to warn about.
186        let mut warnings = Vec::new();
187        scale.validate(&mut warnings);
188        assert!(warnings.is_empty());
189        // The scale is otherwise unaffected.
190        assert_eq!(
191            scale.map_to_color(&Value::Str("#FFFFFF".into())),
192            Some((255, 255, 255))
193        );
194    }
195}