ggplot_rs/scale/
scale_set.rs1use crate::aes::Aesthetic;
2use crate::data::{DataFrame, Value};
3use crate::render::backend::{Linetype, PointShape};
4use crate::scale::alpha::ScaleAlphaContinuous;
5use crate::scale::color::{ScaleColorContinuous, ScaleColorDiscrete};
6use crate::scale::continuous::ScaleContinuous;
7use crate::scale::datetime::ScaleDateTime;
8use crate::scale::discrete::ScaleDiscrete;
9use crate::scale::linetype::ScaleLinetypeDiscrete;
10use crate::scale::linewidth::ScaleLinewidthContinuous;
11use crate::scale::shape::ScaleShapeDiscrete;
12use crate::scale::size::ScaleSizeContinuous;
13
14use super::Scale;
15
16pub struct ScaleSet {
18 scales: Vec<Box<dyn Scale>>,
19}
20
21impl ScaleSet {
22 pub fn new() -> Self {
23 ScaleSet { scales: Vec::new() }
24 }
25
26 pub fn add(&mut self, scale: Box<dyn Scale>) {
28 let aes = scale.aesthetic();
30 self.scales.retain(|s| s.aesthetic() != aes);
31 self.scales.push(scale);
32 }
33
34 pub fn set_default_locale(&mut self, locale: &crate::locale::Locale) {
37 for s in &mut self.scales {
38 s.set_default_locale(locale);
39 }
40 }
41
42 pub fn get(&self, aes: &Aesthetic) -> Option<&dyn Scale> {
44 self.scales
45 .iter()
46 .find(|s| s.aesthetic() == *aes)
47 .map(|s| s.as_ref())
48 }
49
50 pub fn get_mut(&mut self, aes: &Aesthetic) -> Option<&mut Box<dyn Scale>> {
52 self.scales.iter_mut().find(|s| s.aesthetic() == *aes)
53 }
54
55 pub fn ensure_scale(&mut self, aes: &Aesthetic, data: &DataFrame) {
57 if self.get(aes).is_some() {
58 return;
59 }
60 if matches!(
63 aes,
64 Aesthetic::Open
65 | Aesthetic::High
66 | Aesthetic::Low
67 | Aesthetic::Close
68 | Aesthetic::Slope
69 | Aesthetic::Intercept
70 | Aesthetic::Xintercept
75 | Aesthetic::Yintercept
76 | Aesthetic::Tooltip
79 | Aesthetic::DataAttr(_)
80 | Aesthetic::Class
81 | Aesthetic::GroupWrap(_)
82 | Aesthetic::Fontface
85 | Aesthetic::Lower
90 | Aesthetic::Middle
91 | Aesthetic::Upper
92 | Aesthetic::Outliers
93 | Aesthetic::From
95 | Aesthetic::To
96 | Aesthetic::Subgroup
97 | Aesthetic::Sets
98 ) {
99 return;
100 }
101 let fallback: &[&str] = match aes {
105 Aesthetic::X => &["xmin", "xmax", "xend"],
106 Aesthetic::Y => &["ymin", "ymax", "yend"],
107 _ => &[],
108 };
109 let values = data
110 .column(aes.col_name())
111 .or_else(|| fallback.iter().find_map(|c| data.column(c)));
112
113 let is_discrete = match values {
114 Some(vals) => vals
115 .iter()
116 .any(|v| matches!(v, Value::Str(_) | Value::Bool(_))),
117 None => false,
118 };
119
120 let is_datetime = match values {
121 Some(vals) => vals.iter().any(|v| v.is_datetime()),
122 None => false,
123 };
124
125 match aes {
126 Aesthetic::Color | Aesthetic::Fill => {
127 if is_discrete {
128 let scale = ScaleColorDiscrete::new(aes.clone());
129 self.scales.push(Box::new(scale));
130 } else {
131 let scale = ScaleColorContinuous::new(aes.clone());
132 self.scales.push(Box::new(scale));
133 }
134 }
135 Aesthetic::Shape => {
136 let scale = ScaleShapeDiscrete::new();
137 self.scales.push(Box::new(scale));
138 }
139 Aesthetic::Linetype => {
140 let scale = ScaleLinetypeDiscrete::new();
141 self.scales.push(Box::new(scale));
142 }
143 Aesthetic::Size => {
144 let scale = ScaleSizeContinuous::new();
145 self.scales.push(Box::new(scale));
146 }
147 Aesthetic::Linewidth => {
148 let scale = ScaleLinewidthContinuous::new();
149 self.scales.push(Box::new(scale));
150 }
151 Aesthetic::Alpha => {
152 let scale = ScaleAlphaContinuous::new();
153 self.scales.push(Box::new(scale));
154 }
155 _ => {
156 if is_discrete {
157 let scale = ScaleDiscrete::new().for_aesthetic(aes.clone());
158 self.scales.push(Box::new(scale));
159 } else if is_datetime {
160 let scale = ScaleDateTime::new().for_aesthetic(aes.clone());
161 self.scales.push(Box::new(scale));
162 } else {
163 let scale = ScaleContinuous::new().for_aesthetic(aes.clone());
164 self.scales.push(Box::new(scale));
165 }
166 }
167 }
168 }
169
170 pub fn train_layer(&mut self, data: &DataFrame) {
172 for scale in &mut self.scales {
173 let col_name = scale.aesthetic().col_name().to_string();
174 if let Some(values) = data.column(&col_name) {
175 scale.train(values);
176 }
177 }
178 }
179
180 pub fn map_value(&self, aes: &Aesthetic, value: &Value) -> f64 {
182 self.get(aes).map(|s| s.map(value)).unwrap_or(0.0)
183 }
184
185 pub fn map_color(&self, aes: &Aesthetic, value: &Value) -> Option<(u8, u8, u8)> {
187 self.get(aes).and_then(|s| s.map_to_color(value))
188 }
189
190 pub fn na_fill_pattern(&self, aes: &Aesthetic) -> Option<crate::render::backend::FillPattern> {
195 self.get(aes).and_then(|s| s.na_fill_pattern())
196 }
197
198 pub fn map_shape(&self, value: &Value) -> Option<PointShape> {
200 self.get(&Aesthetic::Shape)
201 .and_then(|s| s.map_to_shape(value))
202 }
203
204 pub fn map_linetype(&self, value: &Value) -> Option<Linetype> {
206 self.get(&Aesthetic::Linetype)
207 .and_then(|s| s.map_to_linetype(value))
208 }
209
210 pub fn map_size(&self, value: &Value) -> Option<f64> {
212 self.get(&Aesthetic::Size)
213 .and_then(|s| s.map_to_size(value))
214 }
215
216 pub fn map_alpha(&self, value: &Value) -> Option<f64> {
218 self.get(&Aesthetic::Alpha)
219 .and_then(|s| s.map_to_alpha(value))
220 }
221
222 pub fn map_linewidth(&self, value: &Value) -> Option<f64> {
224 self.get(&Aesthetic::Linewidth)
225 .and_then(|s| s.map_to_size(value))
226 }
227
228 pub fn set_limits(&mut self, aes: &Aesthetic, min: f64, max: f64) {
230 if let Some(scale) = self.get_mut(aes) {
231 scale.set_limits(min, max);
232 }
233 }
234
235 pub fn sec_axis(&self, aes: &Aesthetic) -> Option<&crate::scale::sec_axis::SecAxis> {
237 self.get(aes).and_then(|s| s.sec_axis())
238 }
239
240 pub fn iter(&self) -> impl Iterator<Item = &dyn Scale> {
242 self.scales.iter().map(|s| s.as_ref())
243 }
244}
245
246impl Clone for ScaleSet {
247 fn clone(&self) -> Self {
248 ScaleSet {
249 scales: self.scales.iter().map(|s| s.clone_box()).collect(),
250 }
251 }
252}
253
254impl Default for ScaleSet {
255 fn default() -> Self {
256 Self::new()
257 }
258}