gpui_component/plot/scale/
band.rs1use std::{collections::HashMap, hash::Hash};
4
5use itertools::Itertools;
6use num_traits::Zero;
7
8use super::Scale;
9
10#[derive(Clone)]
11pub struct ScaleBand<T> {
12 indices: HashMap<T, usize>,
18 range_diff: f32,
19 avg_width: f32,
20 padding_inner: f32,
21 padding_outer: f32,
22}
23
24impl<T> ScaleBand<T> {
25 pub fn new(domain: Vec<T>, range: Vec<f32>) -> Self
26 where
27 T: Eq + Hash,
28 {
29 let mut indices = HashMap::with_capacity(domain.len());
30 for value in domain {
31 let next = indices.len();
32 indices.entry(value).or_insert(next);
33 }
34
35 let len = indices.len() as f32;
36 let range_diff = range
37 .iter()
38 .minmax()
39 .into_option()
40 .map_or(0., |(min, max)| max - min);
41
42 Self {
43 indices,
44 range_diff,
45 avg_width: if len.is_zero() { 0. } else { range_diff / len },
46 padding_inner: 0.,
47 padding_outer: 0.,
48 }
49 }
50
51 pub fn band_width(&self) -> f32 {
53 (self.avg_width * (1. - self.padding_inner)).min(30.)
54 }
55
56 pub fn padding_inner(mut self, padding_inner: f32) -> Self {
58 self.padding_inner = padding_inner;
59 self
60 }
61
62 pub fn padding_outer(mut self, padding_outer: f32) -> Self {
64 self.padding_outer = padding_outer;
65 self
66 }
67
68 fn len(&self) -> usize {
70 self.indices.len()
71 }
72
73 fn ratio(&self) -> f32 {
75 1. + self.padding_inner / (self.len() - 1) as f32
76 }
77
78 fn display_avg_width(&self) -> f32 {
80 let padding_outer_width = self.avg_width * self.padding_outer;
81 (self.range_diff - padding_outer_width * 2.) / self.len() as f32
82 }
83}
84
85impl<T> Scale<T> for ScaleBand<T>
86where
87 T: Eq + Hash,
88{
89 fn tick(&self, value: &T) -> Option<f32> {
90 let index = *self.indices.get(value)?;
91 let domain_len = self.len();
92
93 if domain_len == 1 {
95 return Some((self.range_diff - self.band_width()) / 2.);
96 }
97
98 let avg_width = self.display_avg_width();
99 let padding_outer_width = self.avg_width * self.padding_outer;
100 Some(index as f32 * avg_width * self.ratio() + padding_outer_width)
101 }
102
103 fn least_index(&self, tick: f32) -> usize {
104 let domain_len = self.len();
105 if domain_len == 0 {
106 return 0;
107 }
108
109 if domain_len == 1 {
111 return 0;
112 }
113
114 let avg_width = self.display_avg_width();
115 let padding_outer_width = self.avg_width * self.padding_outer;
116 let adjusted_tick = tick - padding_outer_width;
117 let index = (adjusted_tick / (avg_width * self.ratio())).round() as i32;
118
119 (index.max(0) as usize).min(domain_len.saturating_sub(1))
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn test_scale_band() {
129 let scale = ScaleBand::new(vec![1, 2, 3], vec![0., 90.]);
130 assert_eq!(scale.tick(&1), Some(0.));
131 assert_eq!(scale.tick(&2), Some(30.));
132 assert_eq!(scale.tick(&3), Some(60.));
133 assert_eq!(scale.band_width(), 30.);
134 }
135
136 #[test]
137 fn test_scale_band_dedup() {
138 let scale = ScaleBand::new(vec![1, 2, 3, 1, 2, 3], vec![0., 90.]);
140 assert_eq!(scale.len(), 3);
141 assert_eq!(scale.tick(&1), Some(0.));
142 assert_eq!(scale.tick(&2), Some(30.));
143 assert_eq!(scale.tick(&3), Some(60.));
144 assert_eq!(scale.band_width(), 30.);
145 }
146
147 #[test]
148 fn test_scale_band_zero() {
149 let scale = ScaleBand::new(vec![], vec![0., 90.]);
150 assert_eq!(scale.tick(&1), None);
151 assert_eq!(scale.tick(&2), None);
152 assert_eq!(scale.tick(&3), None);
153 assert_eq!(scale.band_width(), 0.);
154
155 let scale = ScaleBand::new(vec![1, 2, 3], vec![]);
156 assert_eq!(scale.tick(&1), Some(0.));
157 assert_eq!(scale.tick(&2), Some(0.));
158 assert_eq!(scale.tick(&3), Some(0.));
159 assert_eq!(scale.band_width(), 0.);
160 }
161}