Skip to content

Commit d317501

Browse files
Merge pull request #29 from EmmanuelDodoo/feature/numeric_scale
feat!: Scale improvements
2 parents 6dd1bf5 + 0641a1d commit d317501

File tree

5 files changed

+149
-47
lines changed

5 files changed

+149
-47
lines changed

src/models/bar.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ mod barchart_tests {
129129
let y_scale = {
130130
let values = vec!["one", "two", "three", "four", "five"];
131131

132-
Scale::new(values, ScaleKind::Text)
132+
Scale::new(values, ScaleKind::Categorical)
133133
};
134134

135135
match BarChart::new(bars, x_scale, y_scale) {

src/models/common.rs

Lines changed: 144 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ impl<X, Y> From<(X, Y)> for Point<X, Y> {
2424
///
2525
/// Points on a [`ScaleKind::Text`] are treated categorically with all duplicates removed and in an arbitary order. Points on other [`ScaleKind`] are treated numerically as a range
2626
#[derive(Debug, Clone, Copy, PartialEq)]
27-
pub enum ScaleKind {
27+
pub(crate) enum ScaleKind {
2828
Number,
2929
Integer,
3030
Float,
31-
Text,
31+
Categorical,
3232
}
3333

3434
impl From<ColumnType> for ScaleKind {
@@ -37,7 +37,7 @@ impl From<ColumnType> for ScaleKind {
3737
ColumnType::Number => ScaleKind::Number,
3838
ColumnType::Integer => ScaleKind::Integer,
3939
ColumnType::Float => ScaleKind::Float,
40-
_ => ScaleKind::Text,
40+
_ => ScaleKind::Categorical,
4141
}
4242
}
4343
}
@@ -59,13 +59,19 @@ enum ScaleValues {
5959
end: f32,
6060
step: f32,
6161
},
62-
Text(Vec<String>),
62+
Categorical(Vec<Data>),
6363
}
6464

6565
#[derive(Debug, Clone, PartialEq)]
6666
pub struct Scale {
67-
pub kind: ScaleKind,
67+
/// The type of scale
68+
pub(crate) kind: ScaleKind,
69+
/// The values within the scale
6870
values: ScaleValues,
71+
/// The number of points on the scale.
72+
///
73+
/// For non-categorical data this is at most one more than the number of
74+
/// points used to generate the scale
6975
pub length: usize,
7076
}
7177

@@ -76,13 +82,11 @@ impl Scale {
7682
pub(crate) fn new(points: impl IntoIterator<Item = impl Into<Data>>, kind: ScaleKind) -> Self {
7783
let points = points.into_iter().map(Into::into);
7884
match kind {
79-
ScaleKind::Text => {
80-
let values = points
81-
.map(|point| point.to_string())
82-
.collect::<HashSet<String>>();
83-
let values = values.into_iter().collect::<Vec<String>>();
85+
ScaleKind::Categorical => {
86+
let values = points.collect::<HashSet<Data>>();
87+
let values = values.into_iter().collect::<Vec<Data>>();
8488
let length = values.len();
85-
let values = ScaleValues::Text(values);
89+
let values = ScaleValues::Categorical(values);
8690

8791
Self {
8892
kind,
@@ -100,7 +104,7 @@ impl Scale {
100104
valid.insert(num);
101105
}
102106
other => {
103-
invalid.insert(other.to_string());
107+
invalid.insert(other);
104108
}
105109
};
106110
}
@@ -117,15 +121,15 @@ impl Scale {
117121
}
118122
} else if !invalid.is_empty() {
119123
for point in valid.into_iter() {
120-
invalid.insert(point.to_string());
124+
invalid.insert(point.into());
121125
}
122126

123-
let invalid = invalid.into_iter().collect::<Vec<String>>();
127+
let invalid = invalid.into_iter().collect::<Vec<Data>>();
124128
let length = invalid.len();
125129

126130
Self {
127-
kind: ScaleKind::Text,
128-
values: ScaleValues::Text(invalid),
131+
kind: ScaleKind::Categorical,
132+
values: ScaleValues::Categorical(invalid),
129133
length,
130134
}
131135
} else {
@@ -142,7 +146,7 @@ impl Scale {
142146
valid.insert(num);
143147
}
144148
other => {
145-
invalid.insert(other.to_string());
149+
invalid.insert(other);
146150
}
147151
}
148152
}
@@ -159,15 +163,15 @@ impl Scale {
159163
}
160164
} else if !invalid.is_empty() {
161165
for point in valid.into_iter() {
162-
invalid.insert(point.to_string());
166+
invalid.insert(point.into());
163167
}
164168

165-
let invalid = invalid.into_iter().collect::<Vec<String>>();
169+
let invalid = invalid.into_iter().collect::<Vec<Data>>();
166170
let length = invalid.len();
167171

168172
Self {
169-
kind: ScaleKind::Text,
170-
values: ScaleValues::Text(invalid),
173+
kind: ScaleKind::Categorical,
174+
values: ScaleValues::Categorical(invalid),
171175
length,
172176
}
173177
} else {
@@ -187,7 +191,7 @@ impl Scale {
187191
}
188192
}
189193
other => {
190-
invalid.insert(other.to_string());
194+
invalid.insert(other);
191195
}
192196
}
193197
}
@@ -204,15 +208,15 @@ impl Scale {
204208
}
205209
} else if !invalid.is_empty() {
206210
for point in valid.into_iter() {
207-
invalid.insert(point.to_string());
211+
invalid.insert(point.into());
208212
}
209213

210-
let invalid = invalid.into_iter().collect::<Vec<String>>();
214+
let invalid = invalid.into_iter().collect::<Vec<Data>>();
211215
let length = invalid.len();
212216

213217
Self {
214-
kind: ScaleKind::Text,
215-
values: ScaleValues::Text(invalid),
218+
kind: ScaleKind::Categorical,
219+
values: ScaleValues::Categorical(invalid),
216220
length,
217221
}
218222
} else {
@@ -222,9 +226,14 @@ impl Scale {
222226
}
223227
}
224228

229+
/// Returns the points on the scale.
230+
///
231+
/// Categorical scales return all points used to generate the scale.
232+
///
233+
/// Non-Categorical scales return a ordered generated range, guaranteed to contain all initial points.
225234
pub fn points(&self) -> Vec<Data> {
226235
match &self.values {
227-
ScaleValues::Text(values) => values.iter().cloned().map(Data::Text).collect(),
236+
ScaleValues::Categorical(values) => values.clone(),
228237
ScaleValues::Number { start, step, .. } => {
229238
let mut output = Vec::default();
230239
let n = self.length as isize;
@@ -261,9 +270,55 @@ impl Scale {
261270
}
262271
}
263272

273+
/// Returns the successive points on the scale. For categorical and floating
274+
/// point scales, this is the same as [`Scale::points`]
275+
///
276+
/// # Example
277+
///
278+
/// ```
279+
/// use modav_core::{repr::Data, models::Scale};
280+
///
281+
/// let scale = Scale::from(vec![1,2,9,10]);
282+
/// assert_eq!(scale.ranged(), (1..=10).map(From::from).collect::<Vec<Data>>())
283+
///
284+
/// ```
285+
pub fn ranged(&self) -> Vec<Data> {
286+
match &self.values {
287+
ScaleValues::Integer { start, end, .. } => {
288+
let range = *start..=*end;
289+
range.map(From::from).collect()
290+
}
291+
ScaleValues::Number { start, end, .. } => {
292+
let range = *start..=*end;
293+
range.map(From::from).collect()
294+
}
295+
_ => self.points(),
296+
}
297+
}
298+
299+
/// Returns true if the scale contains the given [`Data`].
300+
///
301+
/// For non-categorical scales, true is returned if a valid data value falls
302+
/// the range min(Scale::points), max(Scale::points).
303+
///
304+
/// # Example
305+
///
306+
/// ```
307+
/// use modav_core::{repr::Data, models::Scale};
308+
///
309+
/// let scale = Scale::from(vec![1,3,4,5]);
310+
/// assert!(scale.contains(&Data::Integer(3)));
311+
///
312+
/// /// Returns true, even though 2 was not in original scale points
313+
/// assert!(scale.contains(&Data::Integer(2)));
314+
///
315+
/// /// scale doesn't contain [`Data::Number`]
316+
/// assert!(!scale.contains(&Data::Number(3)));
317+
///
318+
/// ```
264319
pub fn contains(&self, value: &Data) -> bool {
265320
match (&self.values, value) {
266-
(ScaleValues::Text(values), Data::Text(val)) => values.contains(val),
321+
(ScaleValues::Categorical(values), data) => values.contains(data),
267322
(ScaleValues::Number { start, step, .. }, Data::Number(num)) => {
268323
let end = start + (*step * (self.length - 1) as isize);
269324
start <= num && num <= &end
@@ -280,6 +335,11 @@ impl Scale {
280335
}
281336
}
282337

338+
/// Returns true if the scale is categorical
339+
pub fn is_categorical(&self) -> bool {
340+
self.kind == ScaleKind::Categorical
341+
}
342+
283343
/// Assumes points is not empty
284344
fn from_i32(points: impl Iterator<Item = i32>) -> Self {
285345
let deduped = points.collect::<HashSet<i32>>();
@@ -470,7 +530,7 @@ impl Scale {
470530
}
471531

472532
pub fn sort(&mut self) {
473-
if let ScaleValues::Text(values) = &mut self.values {
533+
if let ScaleValues::Categorical(values) = &mut self.values {
474534
values.sort();
475535
}
476536
}
@@ -569,18 +629,18 @@ mod tests {
569629
assert!(!scale.contains(&Data::Float(0.99)));
570630

571631
let pnts: Vec<isize> = vec![1, 12, 12, 6, 4, 1, 25];
572-
let mut scale = Scale::new(pnts, ScaleKind::Text);
632+
let mut scale = Scale::new(pnts, ScaleKind::Categorical);
573633
scale.sort();
574634

575635
assert_eq!(scale.length, 5);
576636
assert_eq!(
577637
scale.points(),
578638
vec![
579-
Data::Text("1".into()),
580-
Data::Text("12".into()),
581-
Data::Text("25".into()),
582-
Data::Text("4".into()),
583-
Data::Text("6".into()),
639+
Data::Number(1),
640+
Data::Number(4),
641+
Data::Number(6),
642+
Data::Number(12),
643+
Data::Number(25),
584644
]
585645
);
586646

@@ -597,17 +657,16 @@ mod tests {
597657
assert_eq!(
598658
scale.points(),
599659
vec![
600-
Data::Text("4".into()),
601-
Data::Text("44".into()),
602-
Data::Text("<None>".into()),
660+
Data::None,
661+
Data::Integer(4),
662+
Data::Integer(44),
603663
Data::Text("Test".into()),
604664
]
605665
);
606-
assert!(scale.contains(&Data::Text("44".into())));
607-
assert!(!scale.contains(&Data::Integer(44)));
608-
assert!(!scale.contains(&Data::None));
666+
assert!(!scale.contains(&Data::Text("44".into())));
667+
assert!(scale.contains(&Data::Integer(44)));
668+
assert!(scale.contains(&Data::None));
609669
assert!(scale.contains(&Data::Text("Test".into())));
610-
assert!(scale.contains(&Data::Text("<None>".into())));
611670
}
612671

613672
#[test]
@@ -679,4 +738,47 @@ mod tests {
679738
assert_eq!(scale.points(), vec![Data::Integer(0)]);
680739
assert!(scale.contains(&Data::Integer(0)));
681740
}
741+
742+
#[test]
743+
fn test_scale_ranged() {
744+
let pnts = vec![1, 2, 9, 10];
745+
let scale = Scale::new(pnts, ScaleKind::Integer);
746+
let rng = scale.ranged();
747+
748+
assert_eq!(
749+
rng,
750+
vec![
751+
Data::Integer(1),
752+
Data::Integer(2),
753+
Data::Integer(3),
754+
Data::Integer(4),
755+
Data::Integer(5),
756+
Data::Integer(6),
757+
Data::Integer(7),
758+
Data::Integer(8),
759+
Data::Integer(9),
760+
Data::Integer(10),
761+
]
762+
);
763+
764+
let pnts: Vec<isize> = vec![1, 2, 9, 10];
765+
let scale = Scale::new(pnts, ScaleKind::Number);
766+
let rng = scale.ranged();
767+
768+
assert_eq!(
769+
rng,
770+
vec![
771+
Data::Number(1),
772+
Data::Number(2),
773+
Data::Number(3),
774+
Data::Number(4),
775+
Data::Number(5),
776+
Data::Number(6),
777+
Data::Number(7),
778+
Data::Number(8),
779+
Data::Number(9),
780+
Data::Number(10),
781+
]
782+
)
783+
}
682784
}

src/models/line.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ mod line_tests {
174174
let y_scale = {
175175
let values = vec!["one", "two", "three", "four", "five"];
176176

177-
Scale::new(values, ScaleKind::Text)
177+
Scale::new(values, ScaleKind::Categorical)
178178
};
179179

180180
match LineGraph::new(

src/models/stacked_bar.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ mod stacked_barchart_tests {
350350
let x_scale = {
351351
let values = vec!["One", "Two", "Three", "Four", "Five"];
352352

353-
Scale::new(values, ScaleKind::Text)
353+
Scale::new(values, ScaleKind::Categorical)
354354
};
355355

356356
let y_scale = vec![14, 16, 19].into();

src/repr/sheet.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,7 +1041,7 @@ impl Sheet {
10411041
None
10421042
}
10431043
});
1044-
Scale::new(values, ScaleKind::Text)
1044+
Scale::new(values, ScaleKind::Categorical)
10451045
}
10461046
_ => {
10471047
let values = x_values.into_iter().enumerate().filter_map(|(idx, lbl)| {
@@ -1051,7 +1051,7 @@ impl Sheet {
10511051
None
10521052
}
10531053
});
1054-
Scale::new(values, ScaleKind::Text)
1054+
Scale::new(values, ScaleKind::Categorical)
10551055
}
10561056
};
10571057

0 commit comments

Comments
 (0)