gpui_component/plot/shape/
pie.rs

1// @reference: https://d3js.org/d3-shape/pie
2
3use std::f32::consts::TAU;
4
5use super::arc::ArcData;
6
7#[allow(clippy::type_complexity)]
8pub struct Pie<T> {
9    value: Box<dyn Fn(&T) -> Option<f32>>,
10    start_angle: f32,
11    end_angle: f32,
12    pad_angle: f32,
13}
14
15impl<T> Default for Pie<T> {
16    fn default() -> Self {
17        Self {
18            value: Box::new(|_| None),
19            start_angle: 0.,
20            end_angle: TAU,
21            pad_angle: 0.,
22        }
23    }
24}
25
26impl<T> Pie<T> {
27    pub fn new() -> Self {
28        Self::default()
29    }
30
31    /// Set the value of the Pie.
32    pub fn value<F>(mut self, value: F) -> Self
33    where
34        F: 'static + Fn(&T) -> Option<f32>,
35    {
36        self.value = Box::new(value);
37        self
38    }
39
40    /// Set the start angle of the Pie.
41    pub fn start_angle(mut self, start_angle: f32) -> Self {
42        self.start_angle = start_angle;
43        self
44    }
45
46    /// Set the end angle of the Pie.
47    pub fn end_angle(mut self, end_angle: f32) -> Self {
48        self.end_angle = end_angle;
49        self
50    }
51
52    /// Set the pad angle of the Pie.
53    pub fn pad_angle(mut self, pad_angle: f32) -> Self {
54        self.pad_angle = pad_angle;
55        self
56    }
57
58    /// Get the arcs of the Pie.
59    pub fn arcs<'a>(&self, data: &'a [T]) -> Vec<ArcData<'a, T>> {
60        let mut values = Vec::new();
61        let mut sum = 0.;
62
63        for (idx, v) in data.iter().enumerate() {
64            if let Some(value) = (self.value)(v) {
65                if value > 0. {
66                    sum += value;
67                    values.push((idx, v, value));
68                }
69            }
70        }
71
72        let mut arcs = Vec::with_capacity(values.len());
73        let mut k = self.start_angle;
74
75        for (index, v, value) in values {
76            let start_angle = k;
77            let angle_delta = if sum > 0. {
78                (value / sum) * (self.end_angle - self.start_angle)
79            } else {
80                0.
81            };
82            k += angle_delta;
83            let end_angle = k;
84
85            arcs.push(ArcData {
86                data: v,
87                index,
88                value,
89                start_angle,
90                end_angle,
91                pad_angle: self.pad_angle,
92            });
93        }
94
95        arcs
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_pie() {
105        let pie = Pie::new().value(|v| Some(*v));
106
107        let data = vec![1., 1., 1.];
108        let arcs = pie.arcs(&data);
109
110        assert_eq!(arcs.len(), 3);
111
112        assert_eq!(arcs[0].value, 1.);
113        assert_eq!(arcs[1].value, 1.);
114        assert_eq!(arcs[2].value, 1.);
115
116        assert_eq!(arcs[0].start_angle, 0.);
117        assert_eq!(arcs[0].end_angle, arcs[1].start_angle);
118        assert_eq!(arcs[1].end_angle, arcs[2].start_angle);
119        assert_eq!(arcs[2].end_angle, TAU);
120    }
121
122    #[test]
123    fn test_pie_zero_values() {
124        let pie = Pie::new().value(|v| Some(*v));
125        let data = vec![0., 1., 0., 2.];
126        let arcs = pie.arcs(&data);
127
128        assert_eq!(arcs.len(), 2);
129        assert_eq!(arcs[0].value, 1.);
130        assert_eq!(arcs[1].value, 2.);
131        assert_eq!(arcs[0].index, 1);
132        assert_eq!(arcs[1].index, 3);
133    }
134}