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, Size};
17
18use super::{bar_heights, bar_slot, series_color};
19
20#[derive(IntoElement)]
24pub struct BarChart {
25 values: Vec<f32>,
26 labels: Vec<SharedString>,
27 colors: Vec<ColorValue>,
28 gap: f32,
29 width: Option<f32>,
30 height: f32,
31}
32
33impl BarChart {
34 pub fn new(values: impl IntoIterator<Item = f32>) -> Self {
35 BarChart {
36 values: values.into_iter().collect(),
37 labels: Vec::new(),
38 colors: Vec::new(),
39 gap: 0.2,
40 width: None,
41 height: 140.0,
42 }
43 }
44
45 pub fn entries(entries: impl IntoIterator<Item = (impl Into<SharedString>, f32)>) -> Self {
47 let (labels, values): (Vec<SharedString>, Vec<f32>) = entries
48 .into_iter()
49 .map(|(label, value)| (label.into(), value))
50 .unzip();
51 BarChart {
52 labels,
53 ..BarChart::new(values)
54 }
55 }
56
57 pub fn color(mut self, color: impl Into<ColorValue>) -> Self {
59 self.colors = vec![color.into()];
60 self
61 }
62
63 pub fn colors(mut self, colors: impl IntoIterator<Item = impl Into<ColorValue>>) -> Self {
65 self.colors = colors.into_iter().map(Into::into).collect();
66 self
67 }
68
69 pub fn gap(mut self, gap: f32) -> Self {
71 self.gap = gap.clamp(0.0, 0.9);
72 self
73 }
74
75 pub fn width(mut self, width: f32) -> Self {
77 self.width = Some(width);
78 self
79 }
80
81 pub fn height(mut self, height: f32) -> Self {
83 self.height = height;
84 self
85 }
86}
87
88impl RenderOnce for BarChart {
89 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
90 let t = theme(cx);
91 let n = self.values.len();
92 let bar_colors: Vec<Hsla> = (0..n).map(|i| series_color(t, &self.colors, i)).collect();
93 let dimmed = t.dimmed().hsla();
94 let font_xs = t.font_size(Size::Xs);
95 let fracs = bar_heights(&self.values);
96 let gap = self.gap;
97
98 let plot = canvas(
99 |_, _, _| (),
100 move |bounds, _, window, _cx| {
101 let w = f32::from(bounds.size.width);
102 let h = f32::from(bounds.size.height);
103 if w <= 0.0 || h <= 0.0 {
104 return;
105 }
106 for (i, frac) in fracs.iter().enumerate() {
107 let bh = h * frac;
108 if bh <= 0.0 {
109 continue;
110 }
111 let (x, bw) = bar_slot(i, fracs.len(), w, gap);
112 window.paint_quad(fill(
113 Bounds::new(
114 bounds.origin + point(px(x), px(h - bh)),
115 size(px(bw), px(bh)),
116 ),
117 bar_colors[i],
118 ));
119 }
120 },
121 )
122 .w_full()
123 .h(px(self.height));
124
125 let mut root = div().flex().flex_col().gap(px(4.0));
126 root = match self.width {
127 Some(w) => root.w(px(w)),
128 None => root.w_full(),
129 };
130 root = root.child(plot);
131
132 if !self.labels.is_empty() && n > 0 {
135 let cells = (0..n).map(|i| {
136 div()
137 .flex_1()
138 .flex()
139 .justify_center()
140 .overflow_hidden()
141 .text_size(px(font_xs))
142 .text_color(dimmed)
143 .child(self.labels.get(i).cloned().unwrap_or_default())
144 });
145 root = root.child(div().flex().flex_row().w_full().children(cells));
146 }
147 root
148 }
149}