1use gpui::prelude::*;
11use gpui::{canvas, fill, point, px, size, App, Bounds, Hsla, IntoElement, Window};
12
13use crate::style::ColorValue;
14use crate::theme::theme;
15
16use super::{paint_polyline, resolve_color};
17
18const GRIDLINES: usize = 4;
20
21#[derive(IntoElement)]
24pub struct LineChart {
25 values: Vec<f32>,
26 color: Option<ColorValue>,
27 stroke: f32,
28 fill: bool,
29 width: Option<f32>,
30 height: f32,
31}
32
33impl LineChart {
34 pub fn new(values: impl IntoIterator<Item = f32>) -> Self {
35 LineChart {
36 values: values.into_iter().collect(),
37 color: None,
38 stroke: 2.0,
39 fill: false,
40 width: None,
41 height: 140.0,
42 }
43 }
44
45 pub fn color(mut self, color: impl Into<ColorValue>) -> Self {
47 self.color = Some(color.into());
48 self
49 }
50
51 pub fn stroke(mut self, width: f32) -> Self {
53 self.stroke = width.max(0.5);
54 self
55 }
56
57 pub fn fill(mut self) -> Self {
59 self.fill = true;
60 self
61 }
62
63 pub fn width(mut self, width: f32) -> Self {
65 self.width = Some(width);
66 self
67 }
68
69 pub fn height(mut self, height: f32) -> Self {
71 self.height = height;
72 self
73 }
74}
75
76impl RenderOnce for LineChart {
77 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
78 let t = theme(cx);
79 let line = self
80 .color
81 .map(|c| resolve_color(t, c))
82 .unwrap_or_else(|| t.primary().hsla());
83 let area = self.fill.then_some(Hsla { a: 0.15, ..line });
84 let grid = t.border().alpha(0.5);
85 let stroke = self.stroke;
86 let values = self.values;
87
88 let plot = canvas(
89 |_, _, _| (),
90 move |bounds, _, window, _cx| {
91 let w = f32::from(bounds.size.width);
92 let h = f32::from(bounds.size.height);
93 if w <= 0.0 || h <= 0.0 {
94 return;
95 }
96 for i in 0..GRIDLINES {
98 let y = (h - 1.0) * (i as f32 / (GRIDLINES - 1) as f32);
99 window.paint_quad(fill(
100 Bounds::new(bounds.origin + point(px(0.0), px(y)), size(px(w), px(1.0))),
101 grid,
102 ));
103 }
104 paint_polyline(window, bounds, &values, stroke, line, area);
105 },
106 )
107 .h(px(self.height));
108
109 match self.width {
110 Some(w) => plot.w(px(w)),
111 None => plot.w_full(),
112 }
113 }
114}