gpui_component/chart/
pie_chart.rs

1use std::rc::Rc;
2
3use gpui::{App, Bounds, Hsla, Pixels, Window};
4use gpui_component_macros::IntoPlot;
5use num_traits::Zero;
6
7use crate::{
8    plot::{
9        shape::{Arc, ArcData, Pie},
10        Plot,
11    },
12    ActiveTheme, PixelsExt,
13};
14
15#[derive(IntoPlot)]
16pub struct PieChart<T: 'static> {
17    data: Vec<T>,
18    inner_radius: f32,
19    inner_radius_fn: Option<Rc<dyn Fn(&ArcData<T>) -> f32 + 'static>>,
20    outer_radius: f32,
21    outer_radius_fn: Option<Rc<dyn Fn(&ArcData<T>) -> f32 + 'static>>,
22    pad_angle: f32,
23    value: Option<Rc<dyn Fn(&T) -> f32>>,
24    color: Option<Rc<dyn Fn(&T) -> Hsla>>,
25}
26
27impl<T> PieChart<T> {
28    pub fn new<I>(data: I) -> Self
29    where
30        I: IntoIterator<Item = T>,
31    {
32        Self {
33            data: data.into_iter().collect(),
34            inner_radius: 0.,
35            inner_radius_fn: None,
36            outer_radius: 0.,
37            outer_radius_fn: None,
38            pad_angle: 0.,
39            value: None,
40            color: None,
41        }
42    }
43
44    /// Set the inner radius of the pie chart.
45    pub fn inner_radius(mut self, inner_radius: f32) -> Self {
46        self.inner_radius = inner_radius;
47        self
48    }
49
50    /// Set the inner radius of the pie chart based on the arc data.
51    pub fn inner_radius_fn(
52        mut self,
53        inner_radius_fn: impl Fn(&ArcData<T>) -> f32 + 'static,
54    ) -> Self {
55        self.inner_radius_fn = Some(Rc::new(inner_radius_fn));
56        self
57    }
58
59    fn get_inner_radius(&self, arc: &ArcData<T>) -> f32 {
60        if let Some(inner_radius_fn) = self.inner_radius_fn.as_ref() {
61            inner_radius_fn(arc)
62        } else {
63            self.inner_radius
64        }
65    }
66
67    /// Set the outer radius of the pie chart.
68    pub fn outer_radius(mut self, outer_radius: f32) -> Self {
69        self.outer_radius = outer_radius;
70        self
71    }
72
73    /// Set the outer radius of the pie chart based on the arc data.
74    pub fn outer_radius_fn(
75        mut self,
76        outer_radius_fn: impl Fn(&ArcData<T>) -> f32 + 'static,
77    ) -> Self {
78        self.outer_radius_fn = Some(Rc::new(outer_radius_fn));
79        self
80    }
81
82    fn get_outer_radius(&self, arc: &ArcData<T>) -> f32 {
83        if let Some(outer_radius_fn) = self.outer_radius_fn.as_ref() {
84            outer_radius_fn(arc)
85        } else {
86            self.outer_radius
87        }
88    }
89
90    /// Set the pad angle of the pie chart.
91    pub fn pad_angle(mut self, pad_angle: f32) -> Self {
92        self.pad_angle = pad_angle;
93        self
94    }
95
96    pub fn value(mut self, value: impl Fn(&T) -> f32 + 'static) -> Self {
97        self.value = Some(Rc::new(value));
98        self
99    }
100
101    /// Set the color of the pie chart.
102    pub fn color<H>(mut self, color: impl Fn(&T) -> H + 'static) -> Self
103    where
104        H: Into<Hsla> + 'static,
105    {
106        self.color = Some(Rc::new(move |t| color(t).into()));
107        self
108    }
109}
110
111impl<T> Plot for PieChart<T> {
112    fn paint(&mut self, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App) {
113        let Some(value_fn) = self.value.as_ref() else {
114            return;
115        };
116
117        let outer_radius = if self.outer_radius.is_zero() {
118            bounds.size.height.as_f32() * 0.4
119        } else {
120            self.outer_radius
121        };
122
123        let arc = Arc::new()
124            .inner_radius(self.inner_radius)
125            .outer_radius(outer_radius);
126        let value_fn = value_fn.clone();
127        let mut pie = Pie::<T>::new().value(move |d| Some(value_fn(d)));
128        pie = pie.pad_angle(self.pad_angle);
129        let arcs = pie.arcs(&self.data);
130
131        for a in &arcs {
132            let inner_radius = self.get_inner_radius(a);
133            let outer_radius = self.get_outer_radius(a);
134            arc.paint(
135                a,
136                if let Some(color_fn) = self.color.as_ref() {
137                    color_fn(a.data)
138                } else {
139                    cx.theme().chart_2
140                },
141                Some(inner_radius),
142                Some(outer_radius),
143                &bounds,
144                window,
145            );
146        }
147    }
148}