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::Key
82 | Aesthetic::GroupWrap(_)
83 | Aesthetic::Fontface
86 | Aesthetic::Hjust
88 | Aesthetic::Vjust
89 | Aesthetic::Lower
94 | Aesthetic::Middle
95 | Aesthetic::Upper
96 | Aesthetic::Outliers
97 | Aesthetic::From
99 | Aesthetic::To
100 | Aesthetic::Subgroup
101 | Aesthetic::Sets
102 ) {
103 return;
104 }
105 let fallback: &[&str] = match aes {
109 Aesthetic::X => &["xmin", "xmax", "xend"],
110 Aesthetic::Y => &["ymin", "ymax", "yend"],
111 _ => &[],
112 };
113 let values = data
114 .column(aes.col_name())
115 .or_else(|| fallback.iter().find_map(|c| data.column(c)));
116
117 let is_discrete = match values {
118 Some(vals) => vals
119 .iter()
120 .any(|v| matches!(v, Value::Str(_) | Value::Bool(_))),
121 None => false,
122 };
123
124 let is_datetime = match values {
125 Some(vals) => vals.iter().any(|v| v.is_datetime()),
126 None => false,
127 };
128
129 match aes {
130 Aesthetic::Color | Aesthetic::Fill => {
131 if is_discrete {
132 let scale = ScaleColorDiscrete::new(aes.clone());
133 self.scales.push(Box::new(scale));
134 } else {
135 let scale = ScaleColorContinuous::new(aes.clone());
136 self.scales.push(Box::new(scale));
137 }
138 }
139 Aesthetic::Shape => {
140 let scale = ScaleShapeDiscrete::new();
141 self.scales.push(Box::new(scale));
142 }
143 Aesthetic::Linetype => {
144 let scale = ScaleLinetypeDiscrete::new();
145 self.scales.push(Box::new(scale));
146 }
147 Aesthetic::Size => {
148 let scale = ScaleSizeContinuous::new();
149 self.scales.push(Box::new(scale));
150 }
151 Aesthetic::Linewidth => {
152 let scale = ScaleLinewidthContinuous::new();
153 self.scales.push(Box::new(scale));
154 }
155 Aesthetic::Alpha => {
156 let scale = ScaleAlphaContinuous::new();
157 self.scales.push(Box::new(scale));
158 }
159 _ => {
160 if is_discrete {
161 let scale = ScaleDiscrete::new().for_aesthetic(aes.clone());
162 self.scales.push(Box::new(scale));
163 } else if is_datetime {
164 let scale = ScaleDateTime::new().for_aesthetic(aes.clone());
165 self.scales.push(Box::new(scale));
166 } else {
167 let scale = ScaleContinuous::new().for_aesthetic(aes.clone());
168 self.scales.push(Box::new(scale));
169 }
170 }
171 }
172 }
173
174 pub fn train_layer(&mut self, data: &DataFrame) {
176 for scale in &mut self.scales {
177 let col_name = scale.aesthetic().col_name().to_string();
178 if let Some(values) = data.column(&col_name) {
179 scale.train(values);
180 }
181 }
182 }
183
184 pub fn map_value(&self, aes: &Aesthetic, value: &Value) -> f64 {
186 self.get(aes).map(|s| s.map(value)).unwrap_or(0.0)
187 }
188
189 pub fn map_color(&self, aes: &Aesthetic, value: &Value) -> Option<(u8, u8, u8)> {
191 self.get(aes).and_then(|s| s.map_to_color(value))
192 }
193
194 pub fn na_fill_pattern(&self, aes: &Aesthetic) -> Option<crate::render::backend::FillPattern> {
199 self.get(aes).and_then(|s| s.na_fill_pattern())
200 }
201
202 pub fn map_shape(&self, value: &Value) -> Option<PointShape> {
204 self.get(&Aesthetic::Shape)
205 .and_then(|s| s.map_to_shape(value))
206 }
207
208 pub fn map_linetype(&self, value: &Value) -> Option<Linetype> {
210 self.get(&Aesthetic::Linetype)
211 .and_then(|s| s.map_to_linetype(value))
212 }
213
214 pub fn map_size(&self, value: &Value) -> Option<f64> {
216 self.get(&Aesthetic::Size)
217 .and_then(|s| s.map_to_size(value))
218 }
219
220 pub fn map_alpha(&self, value: &Value) -> Option<f64> {
222 self.get(&Aesthetic::Alpha)
223 .and_then(|s| s.map_to_alpha(value))
224 }
225
226 pub fn map_linewidth(&self, value: &Value) -> Option<f64> {
228 self.get(&Aesthetic::Linewidth)
229 .and_then(|s| s.map_to_size(value))
230 }
231
232 pub fn set_limits(&mut self, aes: &Aesthetic, min: f64, max: f64) {
234 if let Some(scale) = self.get_mut(aes) {
235 scale.set_limits(min, max);
236 }
237 }
238
239 pub fn sec_axis(&self, aes: &Aesthetic) -> Option<&crate::scale::sec_axis::SecAxis> {
241 self.get(aes).and_then(|s| s.sec_axis())
242 }
243
244 pub fn iter(&self) -> impl Iterator<Item = &dyn Scale> {
246 self.scales.iter().map(|s| s.as_ref())
247 }
248}
249
250impl Clone for ScaleSet {
251 fn clone(&self) -> Self {
252 ScaleSet {
253 scales: self.scales.iter().map(|s| s.clone_box()).collect(),
254 }
255 }
256}
257
258impl Default for ScaleSet {
259 fn default() -> Self {
260 Self::new()
261 }
262}