Skip to main content

gpui_component/plot/shape/
bar.rs

1use gpui::{App, Bounds, Hsla, PaintQuad, Pixels, Point, Window, fill, point, px};
2
3use crate::plot::{
4    label::{PlotLabel, TEXT_GAP, TEXT_HEIGHT, Text},
5    origin_point,
6};
7
8#[allow(clippy::type_complexity)]
9pub struct Bar<T> {
10    data: Vec<T>,
11    x: Box<dyn Fn(&T) -> Option<f32>>,
12    band_width: f32,
13    y0: Box<dyn Fn(&T) -> f32>,
14    y1: Box<dyn Fn(&T) -> Option<f32>>,
15    fill: Box<dyn Fn(&T) -> Hsla>,
16    label: Option<Box<dyn Fn(&T, Point<Pixels>) -> Vec<Text>>>,
17}
18
19impl<T> Default for Bar<T> {
20    fn default() -> Self {
21        Self {
22            data: Vec::new(),
23            x: Box::new(|_| None),
24            band_width: 0.,
25            y0: Box::new(|_| 0.),
26            y1: Box::new(|_| None),
27            fill: Box::new(|_| gpui::black()),
28            label: None,
29        }
30    }
31}
32
33impl<T> Bar<T> {
34    pub fn new() -> Self {
35        Self::default()
36    }
37
38    /// Set the data of the Bar.
39    pub fn data<I>(mut self, data: I) -> Self
40    where
41        I: IntoIterator<Item = T>,
42    {
43        self.data = data.into_iter().collect();
44        self
45    }
46
47    /// Set the x of the Bar.
48    pub fn x<F>(mut self, x: F) -> Self
49    where
50        F: Fn(&T) -> Option<f32> + 'static,
51    {
52        self.x = Box::new(x);
53        self
54    }
55
56    /// Set the band width of the Bar.
57    pub fn band_width(mut self, band_width: f32) -> Self {
58        self.band_width = band_width;
59        self
60    }
61
62    /// Set the y0 of the Bar.
63    pub fn y0<F>(mut self, y: F) -> Self
64    where
65        F: Fn(&T) -> f32 + 'static,
66    {
67        self.y0 = Box::new(y);
68        self
69    }
70
71    /// Set the y1 of the Bar.
72    pub fn y1<F>(mut self, y: F) -> Self
73    where
74        F: Fn(&T) -> Option<f32> + 'static,
75    {
76        self.y1 = Box::new(y);
77        self
78    }
79
80    /// Set the fill color of the Bar.
81    pub fn fill<F, C>(mut self, fill: F) -> Self
82    where
83        F: Fn(&T) -> C + 'static,
84        C: Into<Hsla>,
85    {
86        self.fill = Box::new(move |v| fill(v).into());
87        self
88    }
89
90    /// Set the label of the Bar.
91    pub fn label<F>(mut self, label: F) -> Self
92    where
93        F: Fn(&T, Point<Pixels>) -> Vec<Text> + 'static,
94    {
95        self.label = Some(Box::new(label));
96        self
97    }
98
99    fn path(&self, bounds: &Bounds<Pixels>) -> (Vec<PaintQuad>, PlotLabel) {
100        let origin = bounds.origin;
101        let mut graph = vec![];
102        let mut labels = vec![];
103
104        for v in &self.data {
105            let x_tick = (self.x)(v);
106            let y_tick = (self.y1)(v);
107            let y0 = (self.y0)(v);
108
109            if let (Some(x_tick), Some(y_tick)) = (x_tick, y_tick) {
110                let is_negative = y_tick > y0;
111                let (p1, p2) = if is_negative {
112                    (
113                        origin_point(px(x_tick), px(y0), origin),
114                        origin_point(px(x_tick + self.band_width), px(y_tick), origin),
115                    )
116                } else {
117                    (
118                        origin_point(px(x_tick), px(y_tick), origin),
119                        origin_point(px(x_tick + self.band_width), px(y0), origin),
120                    )
121                };
122
123                let color = (self.fill)(v);
124
125                graph.push(fill(Bounds::from_corners(p1, p2), color));
126
127                if let Some(label) = &self.label {
128                    labels.extend(label(
129                        v,
130                        point(
131                            px(x_tick + self.band_width / 2.),
132                            if is_negative {
133                                px(y_tick + TEXT_GAP)
134                            } else {
135                                px(y_tick - TEXT_HEIGHT)
136                            },
137                        ),
138                    ));
139                }
140            }
141        }
142
143        (graph, PlotLabel::new(labels))
144    }
145
146    /// Paint the Bar.
147    pub fn paint(&self, bounds: &Bounds<Pixels>, window: &mut Window, cx: &mut App) {
148        let (graph, labels) = self.path(bounds);
149        for quad in graph {
150            window.paint_quad(quad);
151        }
152        labels.paint(bounds, window, cx);
153    }
154}