1use gpui::prelude::*;
17use gpui::{
18 canvas, div, fill, point, px, size, App, Bounds, Hsla, IntoElement, SharedString, Window,
19};
20
21use crate::style::ColorValue;
22use crate::theme::theme;
23
24use super::axis::nice_ticks;
25use super::frame::{legend_row, x_label_row, y_axis_column};
26use super::{
27 min_max, normalize_between, paint_band, paint_polyline_ys, series_color, stack_layers,
28};
29use crate::devtools::Probed;
30
31#[derive(IntoElement)]
33pub struct AreaChart {
34 series: Vec<(Option<SharedString>, Vec<f32>)>,
35 colors: Vec<ColorValue>,
36 stacked: bool,
37 axis: bool,
38 labels: Vec<SharedString>,
39 stroke: f32,
40 width: Option<f32>,
41 height: f32,
42}
43
44impl AreaChart {
45 pub fn new(values: impl IntoIterator<Item = f32>) -> Self {
46 AreaChart {
47 series: vec![(None, values.into_iter().collect())],
48 colors: Vec::new(),
49 stacked: true,
50 axis: false,
51 labels: Vec::new(),
52 stroke: 1.5,
53 width: None,
54 height: 140.0,
55 }
56 }
57
58 pub fn series(label: impl Into<SharedString>, values: impl IntoIterator<Item = f32>) -> Self {
60 let mut chart = AreaChart::new(values);
61 chart.series[0].0 = Some(label.into());
62 chart
63 }
64
65 pub fn add_series(
67 mut self,
68 label: impl Into<SharedString>,
69 values: impl IntoIterator<Item = f32>,
70 ) -> Self {
71 self
72 .series
73 .push((Some(label.into()), values.into_iter().collect()));
74 self
75 }
76
77 pub fn overlaid(mut self) -> Self {
79 self.stacked = false;
80 self
81 }
82
83 pub fn colors(mut self, colors: impl IntoIterator<Item = impl Into<ColorValue>>) -> Self {
85 self.colors = colors.into_iter().map(Into::into).collect();
86 self
87 }
88
89 pub fn axis(mut self) -> Self {
91 self.axis = true;
92 self
93 }
94
95 pub fn labels(mut self, labels: impl IntoIterator<Item = impl Into<SharedString>>) -> Self {
97 self.labels = labels.into_iter().map(Into::into).collect();
98 self
99 }
100
101 pub fn width(mut self, width: f32) -> Self {
103 self.width = Some(width);
104 self
105 }
106
107 pub fn height(mut self, height: f32) -> Self {
109 self.height = height;
110 self
111 }
112}
113
114impl RenderOnce for AreaChart {
115 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
116 let t = theme(cx);
117 let colors: Vec<Hsla> = (0..self.series.len())
118 .map(|i| series_color(t, &self.colors, i))
119 .collect();
120 let grid = t.border().alpha(0.5);
121 let stroke = self.stroke;
122 let stacked = self.stacked;
123
124 let raw: Vec<Vec<f32>> = self.series.iter().map(|(_, v)| v.clone()).collect();
125 let layers = if stacked {
126 stack_layers(&raw)
127 } else {
128 raw.clone()
129 };
130
131 let flat: Vec<f32> = layers.iter().flatten().copied().collect();
134 let (lo, hi) = if stacked {
135 (0.0, min_max(&flat).map(|(_, hi)| hi).unwrap_or(1.0))
136 } else {
137 min_max(&flat).unwrap_or((0.0, 1.0))
138 };
139 let ticks = nice_ticks(lo, hi, 4);
140 let (lo, hi) = (*ticks.first().unwrap(), *ticks.last().unwrap());
141 let gridline_count = ticks.len().max(2);
142
143 let paint_colors = colors.clone();
144 let plot = canvas(
145 |_, _, _| (),
146 move |bounds, _, window, _cx| {
147 let w = f32::from(bounds.size.width);
148 let h = f32::from(bounds.size.height);
149 if w <= 0.0 || h <= 0.0 {
150 return;
151 }
152 for i in 0..gridline_count {
153 let y = (h - 1.0) * (i as f32 / (gridline_count - 1) as f32);
154 window.paint_quad(fill(
155 Bounds::new(bounds.origin + point(px(0.0), px(y)), size(px(w), px(1.0))),
156 grid,
157 ));
158 }
159 let zero = normalize_between(&[0.0; 2], lo, hi)[0];
160 let mut below: Option<Vec<f32>> = None;
161 for (i, layer) in layers.iter().enumerate() {
162 let ys = normalize_between(layer, lo, hi);
163 let color = paint_colors[i];
164 let band = Hsla { a: 0.25, ..color };
165 if stacked {
166 let lower = below.clone().unwrap_or_else(|| vec![zero; ys.len()]);
167 paint_band(window, bounds, &ys, &lower, band);
168 below = Some(ys.clone());
169 }
170 paint_polyline_ys(
171 window,
172 bounds,
173 &ys,
174 stroke,
175 color,
176 (!stacked).then_some(band),
177 );
178 }
179 },
180 )
181 .w_full()
182 .h(px(self.height));
183
184 let mut body = div().flex().flex_row().w_full();
185 if self.axis {
186 body = body.child(y_axis_column(t, &ticks, self.height));
187 }
188 let mut plot_column = div().flex_1().flex().flex_col().gap(px(4.0)).child(plot);
189 if !self.labels.is_empty() {
190 plot_column = plot_column.child(x_label_row(t, &self.labels));
191 }
192 body = body.child(plot_column);
193
194 let legend: Vec<(SharedString, Hsla)> = self
195 .series
196 .iter()
197 .enumerate()
198 .filter_map(|(i, (label, _))| label.clone().map(|l| (l, colors[i])))
199 .collect();
200
201 let mut root = div().flex().flex_col();
202 root = match self.width {
203 Some(w) => root.w(px(w)),
204 None => root.w_full(),
205 };
206 root = root.child(body);
207 if !legend.is_empty() {
208 root = root.child(legend_row(t, &legend));
209 }
210 root.probe("AreaChart")
211 }
212}