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.series
72 .push((Some(label.into()), values.into_iter().collect()));
73 self
74 }
75
76 pub fn overlaid(mut self) -> Self {
78 self.stacked = false;
79 self
80 }
81
82 pub fn colors(mut self, colors: impl IntoIterator<Item = impl Into<ColorValue>>) -> Self {
84 self.colors = colors.into_iter().map(Into::into).collect();
85 self
86 }
87
88 pub fn axis(mut self) -> Self {
90 self.axis = true;
91 self
92 }
93
94 pub fn labels(mut self, labels: impl IntoIterator<Item = impl Into<SharedString>>) -> Self {
96 self.labels = labels.into_iter().map(Into::into).collect();
97 self
98 }
99
100 pub fn width(mut self, width: f32) -> Self {
102 self.width = Some(width);
103 self
104 }
105
106 pub fn height(mut self, height: f32) -> Self {
108 self.height = height;
109 self
110 }
111}
112
113impl RenderOnce for AreaChart {
114 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
115 let t = theme(cx);
116 let colors: Vec<Hsla> = (0..self.series.len())
117 .map(|i| series_color(t, &self.colors, i))
118 .collect();
119 let grid = t.border().alpha(0.5);
120 let stroke = self.stroke;
121 let stacked = self.stacked;
122
123 let raw: Vec<Vec<f32>> = self.series.iter().map(|(_, v)| v.clone()).collect();
124 let layers = if stacked {
125 stack_layers(&raw)
126 } else {
127 raw.clone()
128 };
129
130 let flat: Vec<f32> = layers.iter().flatten().copied().collect();
133 let (lo, hi) = if stacked {
134 (0.0, min_max(&flat).map(|(_, hi)| hi).unwrap_or(1.0))
135 } else {
136 min_max(&flat).unwrap_or((0.0, 1.0))
137 };
138 let ticks = nice_ticks(lo, hi, 4);
139 let (lo, hi) = (*ticks.first().unwrap(), *ticks.last().unwrap());
140 let gridline_count = ticks.len().max(2);
141
142 let paint_colors = colors.clone();
143 let plot = canvas(
144 |_, _, _| (),
145 move |bounds, _, window, _cx| {
146 let w = f32::from(bounds.size.width);
147 let h = f32::from(bounds.size.height);
148 if w <= 0.0 || h <= 0.0 {
149 return;
150 }
151 for i in 0..gridline_count {
152 let y = (h - 1.0) * (i as f32 / (gridline_count - 1) as f32);
153 window.paint_quad(fill(
154 Bounds::new(bounds.origin + point(px(0.0), px(y)), size(px(w), px(1.0))),
155 grid,
156 ));
157 }
158 let zero = normalize_between(&vec![0.0; 2], lo, hi)[0];
159 let mut below: Option<Vec<f32>> = None;
160 for (i, layer) in layers.iter().enumerate() {
161 let ys = normalize_between(layer, lo, hi);
162 let color = paint_colors[i];
163 let band = Hsla { a: 0.25, ..color };
164 if stacked {
165 let lower = below.clone().unwrap_or_else(|| vec![zero; ys.len()]);
166 paint_band(window, bounds, &ys, &lower, band);
167 below = Some(ys.clone());
168 }
169 paint_polyline_ys(
170 window,
171 bounds,
172 &ys,
173 stroke,
174 color,
175 (!stacked).then_some(band),
176 );
177 }
178 },
179 )
180 .w_full()
181 .h(px(self.height));
182
183 let mut body = div().flex().flex_row().w_full();
184 if self.axis {
185 body = body.child(y_axis_column(t, &ticks, self.height));
186 }
187 let mut plot_column = div().flex_1().flex().flex_col().gap(px(4.0)).child(plot);
188 if !self.labels.is_empty() {
189 plot_column = plot_column.child(x_label_row(t, &self.labels));
190 }
191 body = body.child(plot_column);
192
193 let legend: Vec<(SharedString, Hsla)> = self
194 .series
195 .iter()
196 .enumerate()
197 .filter_map(|(i, (label, _))| label.clone().map(|l| (l, colors[i])))
198 .collect();
199
200 let mut root = div().flex().flex_col();
201 root = match self.width {
202 Some(w) => root.w(px(w)),
203 None => root.w_full(),
204 };
205 root = root.child(body);
206 if !legend.is_empty() {
207 root = root.child(legend_row(t, &legend));
208 }
209 root.probe("AreaChart")
210 }
211}