1use gpui::prelude::*;
11use gpui::{
12 canvas, div, fill, point, px, size, App, Bounds, Hsla, IntoElement, SharedString, Window,
13};
14
15use crate::style::ColorValue;
16use crate::theme::theme;
17
18use super::axis::nice_ticks;
19use super::frame::{hover_slots, x_label_row, y_axis_column};
20use super::{bar_heights, bar_slot, series_color, tick_label};
21use crate::devtools::Probed;
22
23#[derive(IntoElement)]
27pub struct BarChart {
28 values: Vec<f32>,
29 labels: Vec<SharedString>,
30 colors: Vec<ColorValue>,
31 gap: f32,
32 axis: bool,
33 hover: bool,
34 width: Option<f32>,
35 height: f32,
36}
37
38impl BarChart {
39 pub fn new(values: impl IntoIterator<Item = f32>) -> Self {
40 BarChart {
41 values: values.into_iter().collect(),
42 labels: Vec::new(),
43 colors: Vec::new(),
44 gap: 0.2,
45 axis: false,
46 hover: false,
47 width: None,
48 height: 140.0,
49 }
50 }
51
52 pub fn entries(entries: impl IntoIterator<Item = (impl Into<SharedString>, f32)>) -> Self {
54 let (labels, values): (Vec<SharedString>, Vec<f32>) = entries
55 .into_iter()
56 .map(|(label, value)| (label.into(), value))
57 .unzip();
58 BarChart {
59 labels,
60 ..BarChart::new(values)
61 }
62 }
63
64 pub fn color(mut self, color: impl Into<ColorValue>) -> Self {
66 self.colors = vec![color.into()];
67 self
68 }
69
70 pub fn colors(mut self, colors: impl IntoIterator<Item = impl Into<ColorValue>>) -> Self {
72 self.colors = colors.into_iter().map(Into::into).collect();
73 self
74 }
75
76 pub fn gap(mut self, gap: f32) -> Self {
78 self.gap = gap.clamp(0.0, 0.9);
79 self
80 }
81
82 pub fn axis(mut self) -> Self {
85 self.axis = true;
86 self
87 }
88
89 pub fn hover(mut self) -> Self {
91 self.hover = true;
92 self
93 }
94
95 pub fn width(mut self, width: f32) -> Self {
97 self.width = Some(width);
98 self
99 }
100
101 pub fn height(mut self, height: f32) -> Self {
103 self.height = height;
104 self
105 }
106}
107
108impl RenderOnce for BarChart {
109 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
110 let t = theme(cx);
111 let n = self.values.len();
112 let bar_colors: Vec<Hsla> = (0..n).map(|i| series_color(t, &self.colors, i)).collect();
113 let grid = t.border().alpha(0.5);
114 let gap = self.gap;
115
116 let ticks = if self.axis {
119 let max = self.values.iter().copied().fold(0.0_f32, f32::max);
120 nice_ticks(0.0, max.max(1.0), 4)
121 } else {
122 Vec::new()
123 };
124 let fracs: Vec<f32> = match ticks.last() {
125 Some(&top) if top > 0.0 => self
126 .values
127 .iter()
128 .map(|&v| {
129 if v.is_finite() {
130 (v.max(0.0) / top).min(1.0)
131 } else {
132 0.0
133 }
134 })
135 .collect(),
136 _ => bar_heights(&self.values),
137 };
138 let gridline_count = ticks.len();
139
140 let plot = canvas(
141 |_, _, _| (),
142 move |bounds, _, window, _cx| {
143 let w = f32::from(bounds.size.width);
144 let h = f32::from(bounds.size.height);
145 if w <= 0.0 || h <= 0.0 {
146 return;
147 }
148 for i in 0..gridline_count {
149 let y = (h - 1.0) * (i as f32 / (gridline_count - 1).max(1) as f32);
150 window.paint_quad(fill(
151 Bounds::new(bounds.origin + point(px(0.0), px(y)), size(px(w), px(1.0))),
152 grid,
153 ));
154 }
155 for (i, frac) in fracs.iter().enumerate() {
156 let bh = h * frac;
157 if bh <= 0.0 {
158 continue;
159 }
160 let (x, bw) = bar_slot(i, fracs.len(), w, gap);
161 window.paint_quad(fill(
162 Bounds::new(
163 bounds.origin + point(px(x), px(h - bh)),
164 size(px(bw), px(bh)),
165 ),
166 bar_colors[i],
167 ));
168 }
169 },
170 )
171 .w_full()
172 .h(px(self.height));
173
174 let mut plot_wrap = div().relative().w_full().child(plot);
176 if self.hover && n > 0 {
177 let texts: Vec<SharedString> = (0..n)
178 .map(|i| {
179 let value = tick_label(self.values[i]);
180 match self.labels.get(i) {
181 Some(label) => format!("{label}: {value}").into(),
182 None => value.into(),
183 }
184 })
185 .collect();
186 plot_wrap = plot_wrap.child(hover_slots("guise-barchart-hover".into(), texts));
187 }
188
189 let mut plot_column = div()
190 .flex_1()
191 .flex()
192 .flex_col()
193 .gap(px(4.0))
194 .child(plot_wrap);
195 if !self.labels.is_empty() && n > 0 {
198 plot_column = plot_column.child(x_label_row(t, &self.labels));
199 }
200
201 let mut body = div().flex().flex_row();
202 body = match self.width {
203 Some(w) => body.w(px(w)),
204 None => body.w_full(),
205 };
206 if self.axis {
207 body = body.child(y_axis_column(t, &ticks, self.height));
208 }
209 body.child(plot_column).probe("BarChart")
210 }
211}