1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
use crate::aes::Aesthetic;
use crate::data::{DataFrame, Value};
use crate::render::backend::{Linetype, PointShape};
use crate::scale::alpha::ScaleAlphaContinuous;
use crate::scale::color::{ScaleColorContinuous, ScaleColorDiscrete};
use crate::scale::continuous::ScaleContinuous;
use crate::scale::datetime::ScaleDateTime;
use crate::scale::discrete::ScaleDiscrete;
use crate::scale::linetype::ScaleLinetypeDiscrete;
use crate::scale::linewidth::ScaleLinewidthContinuous;
use crate::scale::shape::ScaleShapeDiscrete;
use crate::scale::size::ScaleSizeContinuous;
use super::Scale;
/// Registry of all scales for a plot. Handles auto-detection and training.
pub struct ScaleSet {
scales: Vec<Box<dyn Scale>>,
}
impl ScaleSet {
pub fn new() -> Self {
ScaleSet { scales: Vec::new() }
}
/// Add a user-specified scale.
pub fn add(&mut self, scale: Box<dyn Scale>) {
// Replace existing scale for same aesthetic
let aes = scale.aesthetic();
self.scales.retain(|s| s.aesthetic() != aes);
self.scales.push(scale);
}
/// Give every scale the plot-level default locale (scales with their own
/// locale keep it; scales without numeric/date labels ignore it).
pub fn set_default_locale(&mut self, locale: &crate::locale::Locale) {
for s in &mut self.scales {
s.set_default_locale(locale);
}
}
/// Get a scale for a specific aesthetic.
pub fn get(&self, aes: &Aesthetic) -> Option<&dyn Scale> {
self.scales
.iter()
.find(|s| s.aesthetic() == *aes)
.map(|s| s.as_ref())
}
/// Get mutable scale for a specific aesthetic.
pub fn get_mut(&mut self, aes: &Aesthetic) -> Option<&mut Box<dyn Scale>> {
self.scales.iter_mut().find(|s| s.aesthetic() == *aes)
}
/// Ensure a scale exists for a given aesthetic. Auto-detect type from data.
pub fn ensure_scale(&mut self, aes: &Aesthetic, data: &DataFrame) {
if self.get(aes).is_some() {
return;
}
// OHLC values live on the y scale (via the geom's ymin/ymax extents);
// they get no scale of their own.
if matches!(
aes,
Aesthetic::Open
| Aesthetic::High
| Aesthetic::Low
| Aesthetic::Close
| Aesthetic::Slope
| Aesthetic::Intercept
// Reference-line intercepts live on the x / y scale; the
// build creates that scale from them only if no layer did
// (so a `geom_vline(0)` added first can't force a
// continuous scale onto a discrete axis).
| Aesthetic::Xintercept
| Aesthetic::Yintercept
// Interactive aesthetics carry their value straight through
// to the mark's attributes — no visual mapping, no legend.
| Aesthetic::Tooltip
| Aesthetic::DataAttr(_)
| Aesthetic::Class
| Aesthetic::GroupWrap(_)
// Per-row fontface (#228) is parsed straight into
// `FontFace` — no visual mapping, no legend.
| Aesthetic::Fontface
// Boxplot stat="identity" hinges/outliers (B3-5) live on
// the y scale (via `column_aesthetic`/`Y_FAMILY`/the
// build's stat_aes training list), not a scale of their
// own.
| Aesthetic::Lower
| Aesthetic::Middle
| Aesthetic::Upper
| Aesthetic::Outliers
// Edge-list node names (flow charts) — no scale.
| Aesthetic::From
| Aesthetic::To
| Aesthetic::Subgroup
| Aesthetic::Sets
) {
return;
}
// A position scale created from extent columns alone (`xmin`/`xmax`
// with no `x`, e.g. a Gantt bar) detects its type from them, so a
// date range still gets a date scale.
let fallback: &[&str] = match aes {
Aesthetic::X => &["xmin", "xmax", "xend"],
Aesthetic::Y => &["ymin", "ymax", "yend"],
_ => &[],
};
let values = data
.column(aes.col_name())
.or_else(|| fallback.iter().find_map(|c| data.column(c)));
let is_discrete = match values {
Some(vals) => vals
.iter()
.any(|v| matches!(v, Value::Str(_) | Value::Bool(_))),
None => false,
};
let is_datetime = match values {
Some(vals) => vals.iter().any(|v| v.is_datetime()),
None => false,
};
match aes {
Aesthetic::Color | Aesthetic::Fill => {
if is_discrete {
let scale = ScaleColorDiscrete::new(aes.clone());
self.scales.push(Box::new(scale));
} else {
let scale = ScaleColorContinuous::new(aes.clone());
self.scales.push(Box::new(scale));
}
}
Aesthetic::Shape => {
let scale = ScaleShapeDiscrete::new();
self.scales.push(Box::new(scale));
}
Aesthetic::Linetype => {
let scale = ScaleLinetypeDiscrete::new();
self.scales.push(Box::new(scale));
}
Aesthetic::Size => {
let scale = ScaleSizeContinuous::new();
self.scales.push(Box::new(scale));
}
Aesthetic::Linewidth => {
let scale = ScaleLinewidthContinuous::new();
self.scales.push(Box::new(scale));
}
Aesthetic::Alpha => {
let scale = ScaleAlphaContinuous::new();
self.scales.push(Box::new(scale));
}
_ => {
if is_discrete {
let scale = ScaleDiscrete::new().for_aesthetic(aes.clone());
self.scales.push(Box::new(scale));
} else if is_datetime {
let scale = ScaleDateTime::new().for_aesthetic(aes.clone());
self.scales.push(Box::new(scale));
} else {
let scale = ScaleContinuous::new().for_aesthetic(aes.clone());
self.scales.push(Box::new(scale));
}
}
}
}
/// Train all scales on data from one layer.
pub fn train_layer(&mut self, data: &DataFrame) {
for scale in &mut self.scales {
let col_name = scale.aesthetic().col_name().to_string();
if let Some(values) = data.column(&col_name) {
scale.train(values);
}
}
}
/// Map a single value through the appropriate scale.
pub fn map_value(&self, aes: &Aesthetic, value: &Value) -> f64 {
self.get(aes).map(|s| s.map(value)).unwrap_or(0.0)
}
/// Map a value to an RGB color through the appropriate color/fill scale.
pub fn map_color(&self, aes: &Aesthetic, value: &Value) -> Option<(u8, u8, u8)> {
self.get(aes).and_then(|s| s.map_to_color(value))
}
/// The `aes` scale's configured na-pattern, if any (`ScaleFillBinned::
/// na_pattern`, #206) — a geom checks this for a row/key it would
/// otherwise draw with the scale's flat na colour, before falling
/// back to [`map_color`](Self::map_color)'s plain result.
pub fn na_fill_pattern(&self, aes: &Aesthetic) -> Option<crate::render::backend::FillPattern> {
self.get(aes).and_then(|s| s.na_fill_pattern())
}
/// Map a value to a point shape through the shape scale.
pub fn map_shape(&self, value: &Value) -> Option<PointShape> {
self.get(&Aesthetic::Shape)
.and_then(|s| s.map_to_shape(value))
}
/// Map a value to a linetype through the linetype scale.
pub fn map_linetype(&self, value: &Value) -> Option<Linetype> {
self.get(&Aesthetic::Linetype)
.and_then(|s| s.map_to_linetype(value))
}
/// Map a value to a point size through the size scale.
pub fn map_size(&self, value: &Value) -> Option<f64> {
self.get(&Aesthetic::Size)
.and_then(|s| s.map_to_size(value))
}
/// Map a value to an alpha (opacity) through the alpha scale.
pub fn map_alpha(&self, value: &Value) -> Option<f64> {
self.get(&Aesthetic::Alpha)
.and_then(|s| s.map_to_alpha(value))
}
/// Map a value to a stroke width through the linewidth scale.
pub fn map_linewidth(&self, value: &Value) -> Option<f64> {
self.get(&Aesthetic::Linewidth)
.and_then(|s| s.map_to_size(value))
}
/// Override the domain limits for a scale (used by coord_cartesian zoom).
pub fn set_limits(&mut self, aes: &Aesthetic, min: f64, max: f64) {
if let Some(scale) = self.get_mut(aes) {
scale.set_limits(min, max);
}
}
/// Get the secondary axis for an aesthetic, if one exists.
pub fn sec_axis(&self, aes: &Aesthetic) -> Option<&crate::scale::sec_axis::SecAxis> {
self.get(aes).and_then(|s| s.sec_axis())
}
/// Get all scales.
pub fn iter(&self) -> impl Iterator<Item = &dyn Scale> {
self.scales.iter().map(|s| s.as_ref())
}
}
impl Clone for ScaleSet {
fn clone(&self) -> Self {
ScaleSet {
scales: self.scales.iter().map(|s| s.clone_box()).collect(),
}
}
}
impl Default for ScaleSet {
fn default() -> Self {
Self::new()
}
}