1use crate::error::ChartError;
4use crate::{
5 DEFAULT_COLOR, DEFAULT_HEIGHT, DEFAULT_PADDING_FRACTION, DEFAULT_TITLE_FONT_SIZE,
6 DEFAULT_WIDTH, ScaleType, TITLE_AREA_HEIGHT, extent_padded, validate_data_array,
7 validate_data_length, validate_dimensions, validate_positive,
8};
9use d3rs::color::D3Color;
10use d3rs::scale::{LinearScale, LogScale, Scale};
11use d3rs::shape::{Area, Curve};
12use d3rs::text::{VectorFontConfig, render_vector_text};
13use gpui::prelude::*;
14use gpui::*;
15use std::sync::Arc;
16
17#[derive(Clone)]
19pub struct AreaChart {
20 x: Vec<f64>,
21 y: Vec<f64>,
22 y0: Option<Vec<f64>>,
23 title: Option<String>,
24 color: u32,
25 opacity: f32,
26 curve: Curve,
27 width: f32,
28 height: f32,
29 x_scale_type: ScaleType,
30 y_scale_type: ScaleType,
31}
32
33impl AreaChart {
34 pub fn title(mut self, title: impl Into<String>) -> Self {
36 self.title = Some(title.into());
37 self
38 }
39
40 pub fn color(mut self, hex: u32) -> Self {
42 self.color = hex;
43 self
44 }
45
46 pub fn opacity(mut self, opacity: f32) -> Self {
48 self.opacity = opacity.clamp(0.0, 1.0);
49 self
50 }
51
52 pub fn curve(mut self, curve: Curve) -> Self {
54 self.curve = curve;
55 self
56 }
57
58 pub fn size(mut self, width: f32, height: f32) -> Self {
60 self.width = width;
61 self.height = height;
62 self
63 }
64
65 pub fn x_scale(mut self, scale: ScaleType) -> Self {
67 self.x_scale_type = scale;
68 self
69 }
70
71 pub fn y_scale(mut self, scale: ScaleType) -> Self {
73 self.y_scale_type = scale;
74 self
75 }
76
77 pub fn y0(mut self, y0: &[f64]) -> Self {
79 self.y0 = Some(y0.to_vec());
80 self
81 }
82
83 pub fn build(self) -> Result<impl IntoElement, ChartError> {
85 validate_data_array(&self.x, "x")?;
87 validate_data_array(&self.y, "y")?;
88 validate_data_length(self.x.len(), self.y.len(), "x", "y")?;
89 validate_dimensions(self.width, self.height)?;
90
91 if let Some(ref y0) = self.y0 {
92 validate_data_array(y0, "y0")?;
93 validate_data_length(self.x.len(), y0.len(), "x", "y0")?;
94 }
95
96 if self.x_scale_type == ScaleType::Log {
98 validate_positive(&self.x, "x")?;
99 }
100 if self.y_scale_type == ScaleType::Log {
101 validate_positive(&self.y, "y")?;
102 if let Some(ref y0) = self.y0 {
103 validate_positive(y0, "y0")?;
104 }
105 }
106
107 let title_height = if self.title.is_some() {
109 TITLE_AREA_HEIGHT
110 } else {
111 0.0
112 };
113 let plot_height = self.height - title_height;
114
115 let (x_min, x_max) = extent_padded(&self.x, DEFAULT_PADDING_FRACTION);
117
118 let y_iter = self.y.iter();
120 let (y_min, y_max) = if let Some(ref y0) = self.y0 {
121 let all_y: Vec<f64> = y_iter.chain(y0.iter()).copied().collect();
122 extent_padded(&all_y, DEFAULT_PADDING_FRACTION)
123 } else {
124 let mut all_y: Vec<f64> = y_iter.copied().collect();
125 all_y.push(0.0); extent_padded(&all_y, DEFAULT_PADDING_FRACTION)
127 };
128
129 struct AreaDatum {
131 x: f64,
132 y0: f64,
133 y1: f64,
134 }
135
136 let data: Vec<AreaDatum> = match &self.y0 {
137 Some(y0) => self
138 .x
139 .iter()
140 .zip(self.y.iter())
141 .zip(y0.iter())
142 .map(|((&x, &y1), &y0)| AreaDatum { x, y0, y1 })
143 .collect(),
144 None => self
145 .x
146 .iter()
147 .zip(self.y.iter())
148 .map(|(&x, &y1)| AreaDatum { x, y0: 0.0, y1 })
149 .collect(),
150 };
151
152 let color = D3Color::from_hex(self.color);
153 let fill_color = color.to_rgba();
154 let opacity = self.opacity;
155 let curve = self.curve;
156
157 let render_element = move |x_scale: Arc<dyn Scale<f64, f64>>,
159 y_scale: Arc<dyn Scale<f64, f64>>| {
160 let x_scale_prepaint = x_scale.clone();
161 let y_scale_prepaint = y_scale.clone();
162
163 canvas(
164 move |bounds, _, _| (x_scale_prepaint.clone(), y_scale_prepaint.clone(), bounds),
165 move |_, (x_scale, y_scale, bounds), window, _| {
166 let x_scale_x = x_scale.clone();
167 let y_scale_y0 = y_scale.clone();
168 let y_scale_y1 = y_scale.clone();
169
170 let area = Area::new()
171 .x(move |d: &AreaDatum| x_scale_x.scale(d.x))
172 .y0(move |d: &AreaDatum| y_scale_y0.scale(d.y0))
173 .y1(move |d: &AreaDatum| y_scale_y1.scale(d.y1))
174 .curve(curve);
175
176 let path = area.generate(&data);
177 let points = path.flatten(0.5);
178
179 let origin_x: f32 = bounds.origin.x.into();
180 let origin_y: f32 = bounds.origin.y.into();
181
182 if points.is_empty() {
183 return;
184 }
185
186 let mut path_builder = PathBuilder::fill();
187
188 let first = points[0];
189 path_builder.move_to(gpui::point(
190 px(origin_x + first.x as f32),
191 px(origin_y + first.y as f32),
192 ));
193
194 for p in points.iter().skip(1) {
195 path_builder.line_to(gpui::point(
196 px(origin_x + p.x as f32),
197 px(origin_y + p.y as f32),
198 ));
199 }
200
201 path_builder.close();
202
203 if let Ok(gpui_path) = path_builder.build() {
204 window.paint_path(
205 gpui_path,
206 Rgba {
207 r: fill_color.r,
208 g: fill_color.g,
209 b: fill_color.b,
210 a: fill_color.a * opacity,
211 },
212 );
213 }
214 },
215 )
216 };
217
218 let area_element: AnyElement = match (self.x_scale_type, self.y_scale_type) {
220 (ScaleType::Linear, ScaleType::Linear) => {
221 let x_scale = LinearScale::new()
222 .domain(x_min, x_max)
223 .range(0.0, self.width as f64);
224 let y_scale = LinearScale::new()
225 .domain(y_min, y_max)
226 .range(plot_height as f64, 0.0);
227 render_element(Arc::new(x_scale), Arc::new(y_scale)).into_any_element()
228 }
229 (ScaleType::Log, ScaleType::Linear) => {
230 let x_scale = LogScale::new()
231 .domain(x_min.max(1e-10), x_max)
232 .range(0.0, self.width as f64);
233 let y_scale = LinearScale::new()
234 .domain(y_min, y_max)
235 .range(plot_height as f64, 0.0);
236 render_element(Arc::new(x_scale), Arc::new(y_scale)).into_any_element()
237 }
238 (ScaleType::Linear, ScaleType::Log) => {
239 let x_scale = LinearScale::new()
240 .domain(x_min, x_max)
241 .range(0.0, self.width as f64);
242 let y_scale = LogScale::new()
243 .domain(y_min.max(1e-10), y_max)
244 .range(plot_height as f64, 0.0);
245 render_element(Arc::new(x_scale), Arc::new(y_scale)).into_any_element()
246 }
247 (ScaleType::Log, ScaleType::Log) => {
248 let x_scale = LogScale::new()
249 .domain(x_min.max(1e-10), x_max)
250 .range(0.0, self.width as f64);
251 let y_scale = LogScale::new()
252 .domain(y_min.max(1e-10), y_max)
253 .range(plot_height as f64, 0.0);
254 render_element(Arc::new(x_scale), Arc::new(y_scale)).into_any_element()
255 }
256 };
257
258 let mut container = div()
260 .w(px(self.width))
261 .h(px(self.height))
262 .relative()
263 .flex()
264 .flex_col();
265
266 if let Some(title) = &self.title {
268 let font_config =
269 VectorFontConfig::horizontal(DEFAULT_TITLE_FONT_SIZE, hsla(0.0, 0.0, 0.2, 1.0));
270 container = container.child(
271 div()
272 .w_full()
273 .h(px(title_height))
274 .flex()
275 .justify_center()
276 .items_center()
277 .child(render_vector_text(title, &font_config)),
278 );
279 }
280
281 container = container.child(
283 div()
284 .w(px(self.width))
285 .h(px(plot_height))
286 .relative()
287 .child(area_element),
288 );
289
290 Ok(container)
291 }
292}
293
294pub fn area(x: &[f64], y: &[f64]) -> AreaChart {
312 AreaChart {
313 x: x.to_vec(),
314 y: y.to_vec(),
315 y0: None,
316 title: None,
317 color: DEFAULT_COLOR,
318 opacity: 0.6,
319 curve: Curve::Linear,
320 width: DEFAULT_WIDTH,
321 height: DEFAULT_HEIGHT,
322 x_scale_type: ScaleType::Linear,
323 y_scale_type: ScaleType::Linear,
324 }
325}