1use crate::math::linear::range;
2use crate::{Scale, ScaleKind};
3use itertools::Itertools;
4use std::collections::HashMap;
5
6const DEFAULT_PADDING: f32 = 0.1_f32;
7const DEFAULT_ALIGN: f32 = 0.5_f32;
8const DEFAULT_STEP: f32 = 1_f32;
9const DEFAULT_BANDWIDTH: f32 = 1_f32;
10
11#[derive(Clone)]
13pub struct BandScale {
14 domain: Vec<String>,
16
17 range_start: i32,
19
20 range_end: i32,
22
23 step: f32,
25
26 bandwidth: f32,
28
29 padding_inner: f32,
31
32 padding_outer: f32,
34
35 align: f32,
37
38 index: HashMap<String, usize>,
40
41 offsets: Vec<f32>,
43
44 no_boundaries_offset: bool,
47}
48
49impl BandScale {
50 pub fn new(domain: Vec<String>, range_start: i32, range_end: i32) -> Self {
52 let mut band = BandScale {
53 domain: domain.iter().unique().map(|s| s.to_string()).collect(),
54 range_start,
55 range_end,
56 step: DEFAULT_STEP,
57 bandwidth: DEFAULT_BANDWIDTH,
58 padding_inner: DEFAULT_PADDING,
59 padding_outer: DEFAULT_PADDING,
60 align: DEFAULT_ALIGN,
61 index: HashMap::new(),
62 offsets: Vec::new(),
63 no_boundaries_offset: false,
64 };
65 band.rescale();
66 band
67 }
68
69 pub fn range_start(&self) -> i32 {
71 self.range_start
72 }
73
74 pub fn range_end(&self) -> i32 {
76 self.range_end
77 }
78
79 pub fn set_inner_padding(mut self, padding: f32) -> Self {
81 self.padding_inner = padding;
82 self.rescale();
83 self
84 }
85
86 pub fn set_outer_padding(mut self, padding: f32) -> Self {
88 self.padding_outer = padding;
89 self.rescale();
90 self
91 }
92
93 pub fn set_no_boundaries_offset(mut self, no_boundaries_offset: bool) -> Self {
95 self.no_boundaries_offset = no_boundaries_offset;
96 self
97 }
98
99 fn rescale(&mut self) {
101 let (domain_len, offsets_count) = if self.no_boundaries_offset {
103 (self.domain.len() - 1, self.domain.len())
104 } else {
105 (self.domain.len(), self.domain.len() - 1)
106 };
107
108 let padded_domain_len = domain_len as f32 - self.padding_inner;
109 let mut start = self.range_start as f32;
110 let mut end = self.range_end as f32;
111 let reverse = end < start;
112 if reverse {
113 std::mem::swap(&mut start, &mut end)
114 }
115
116 let computed_step = padded_domain_len + self.padding_outer * 2_f32;
118 let mut step_denominator = 1_f32;
119 if computed_step > 1_f32 {
120 step_denominator = computed_step;
121 }
122 let range = range(start, end);
123 self.step = range / step_denominator;
124
125 start += (range - self.step * padded_domain_len) * self.align;
127 self.bandwidth = self.step * (1_f32 - self.padding_inner);
128
129 self.offsets.clear();
131 for i in 0..=offsets_count {
132 self.offsets.push(start + self.step * i as f32);
133 }
134 if reverse {
135 self.offsets.reverse();
136 }
137
138 self.index.clear();
140 let mut processed_domains = Vec::new();
141 for domain in self.domain.iter() {
142 if !self.index.contains_key(domain) {
143 self.index.insert(domain.clone(), processed_domains.len());
144 processed_domains.push(domain.clone());
145 }
146 }
147
148 self.domain.clear();
150 self.domain = processed_domains;
151 }
152}
153
154impl Scale<String> for BandScale {
155 fn scale(&self, domain: &String) -> f32 {
156 return match self.index.get(domain) {
157 Some(offset_idx) => self.offsets[*offset_idx],
158 None => 0_f32,
159 };
160 }
161
162 fn ticks(&self) -> Vec<String> {
163 self.domain.clone()
164 }
165
166 fn kind(&self) -> ScaleKind {
167 ScaleKind::Band
168 }
169
170 fn bandwidth(&self) -> f32 {
171 self.bandwidth
172 }
173
174 fn is_range_reversed(&self) -> bool {
175 self.range_start > self.range_end
176 }
177
178 fn tick_offset(&self) -> f32 {
179 if self.no_boundaries_offset {
180 return 0_f32;
181 }
182
183 self.bandwidth() / 2_f32
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn band_scale_with_boundaries_offset() {
194 let band_scale = BandScale::new(
195 vec![
196 "A".to_string(),
197 "B".to_string(),
198 "C".to_string(),
199 "C".to_string(),
200 "D".to_string(),
201 "A".to_string(),
202 ],
203 10,
204 200,
205 );
206
207 assert_eq!(band_scale.range_start(), 10);
208 assert_eq!(band_scale.range_end(), 200);
209 assert_eq!(
210 *band_scale.ticks(),
211 vec![
212 "A".to_string(),
213 "B".to_string(),
214 "C".to_string(),
215 "D".to_string()
216 ]
217 );
218 assert!((band_scale.scale(&"D".to_string()) - 153.65854).abs() < f32::EPSILON);
219 assert_eq!(band_scale.kind(), ScaleKind::Band);
220 assert!((band_scale.bandwidth() - 41.707317_f32).abs() < f32::EPSILON);
221 assert!(!band_scale.is_range_reversed());
222 assert!((band_scale.tick_offset() - 20.853659_f32).abs() < f32::EPSILON);
223 }
224
225 #[test]
226 fn band_scale_without_boundaries_offset() {
227 let band_scale = BandScale::new(
228 vec![
229 "a1".to_string(),
230 "a1".to_string(),
231 "a2".to_string(),
232 "a3".to_string(),
233 "a4".to_string(),
234 "a2".to_string(),
235 ],
236 0,
237 100,
238 )
239 .set_no_boundaries_offset(true)
240 .set_inner_padding(0_f32)
241 .set_outer_padding(0_f32);
242
243 assert_eq!(band_scale.range_start(), 0);
244 assert_eq!(band_scale.range_end(), 100);
245 assert_eq!(
246 *band_scale.ticks(),
247 vec![
248 "a1".to_string(),
249 "a2".to_string(),
250 "a3".to_string(),
251 "a4".to_string()
252 ]
253 );
254 assert!((band_scale.scale(&"a2".to_string()) - 33.333332_f32).abs() < f32::EPSILON);
255 assert_eq!(band_scale.kind(), ScaleKind::Band);
256 assert!((band_scale.bandwidth() - 33.333332_f32).abs() < f32::EPSILON);
257 assert!(!band_scale.is_range_reversed());
258 assert!((band_scale.tick_offset() - 0_f32).abs() < f32::EPSILON);
259 }
260}