gpui_component/plot/shape/
bar.rs1use 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 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 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 pub fn band_width(mut self, band_width: f32) -> Self {
58 self.band_width = band_width;
59 self
60 }
61
62 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 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 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 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 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}