1use gpui::{
4 Background, BorderStyle, Bounds, Hsla, PaintQuad, Path, PathBuilder, Pixels, Point, Window, px,
5 quad, size,
6};
7
8use crate::plot::{Curve, PathCache, ShapeKey, origin_point};
9
10#[allow(clippy::type_complexity)]
11pub struct Line<T> {
12 data: Vec<T>,
13 x: Box<dyn Fn(&T) -> Option<f32>>,
14 y: Box<dyn Fn(&T) -> Option<f32>>,
15 stroke: Background,
16 stroke_width: Pixels,
17 curve: Curve,
18 dot: bool,
19 dot_size: Pixels,
20 dot_fill: Background,
21 dot_stroke: Option<Hsla>,
22}
23
24impl<T> Default for Line<T> {
25 fn default() -> Self {
26 Self {
27 data: Vec::new(),
28 x: Box::new(|_| None),
29 y: Box::new(|_| None),
30 stroke: Default::default(),
31 stroke_width: px(1.),
32 curve: Curve::default(),
33 dot: false,
34 dot_size: px(4.),
35 dot_fill: gpui::transparent_black().into(),
36 dot_stroke: None,
37 }
38 }
39}
40
41impl<T> Line<T> {
42 pub fn new() -> Self {
43 Self::default()
44 }
45
46 pub fn data<I>(mut self, data: I) -> Self
48 where
49 I: IntoIterator<Item = T>,
50 {
51 self.data = data.into_iter().collect();
52 self
53 }
54
55 pub fn x<F>(mut self, x: F) -> Self
57 where
58 F: Fn(&T) -> Option<f32> + 'static,
59 {
60 self.x = Box::new(x);
61 self
62 }
63
64 pub fn y<F>(mut self, y: F) -> Self
66 where
67 F: Fn(&T) -> Option<f32> + 'static,
68 {
69 self.y = Box::new(y);
70 self
71 }
72
73 pub fn stroke(mut self, stroke: impl Into<Background>) -> Self {
75 self.stroke = stroke.into();
76 self
77 }
78
79 pub fn stroke_width(mut self, stroke_width: impl Into<Pixels>) -> Self {
81 self.stroke_width = stroke_width.into();
82 self
83 }
84
85 pub fn curve(mut self, curve: Curve) -> Self {
87 self.curve = curve;
88 self
89 }
90
91 pub fn dot(mut self) -> Self {
93 self.dot = true;
94 self
95 }
96
97 pub fn dot_size(mut self, dot_size: impl Into<Pixels>) -> Self {
99 self.dot_size = dot_size.into();
100 self
101 }
102
103 pub fn dot_fill(mut self, fill: impl Into<Background>) -> Self {
105 self.dot_fill = fill.into();
106 self
107 }
108
109 pub fn dot_stroke(mut self, stroke: impl Into<Hsla>) -> Self {
112 self.dot_stroke = Some(stroke.into());
113 self
114 }
115
116 #[deprecated(since = "0.7.0", note = "use `dot_fill`")]
117 pub fn dot_fill_color(self, color: impl Into<Hsla>) -> Self {
118 self.dot_fill(color.into())
119 }
120
121 #[deprecated(since = "0.7.0", note = "use `dot_stroke`")]
122 pub fn dot_stroke_color(self, color: impl Into<Hsla>) -> Self {
123 self.dot_stroke(color)
124 }
125
126 fn paint_dot(&self, dot: Point<Pixels>) -> PaintQuad {
128 quad(
129 gpui::bounds(dot, size(self.dot_size, self.dot_size)),
130 self.dot_size / 2.,
131 self.dot_fill,
132 px(1.),
133 self.dot_stroke
134 .or_else(|| self.dot_fill.as_solid())
135 .unwrap_or_default(),
136 BorderStyle::default(),
137 )
138 }
139
140 fn dots(&self, origin: Point<Pixels>) -> (Vec<Point<Pixels>>, Vec<PaintQuad>) {
142 let mut dots = vec![];
143 let mut paint_dots = vec![];
144
145 for v in self.data.iter() {
146 let x_tick = (self.x)(v);
147 let y_tick = (self.y)(v);
148
149 if let (Some(x), Some(y)) = (x_tick, y_tick) {
150 let pos = origin_point(px(x), px(y), origin);
151
152 if self.dot {
153 let dot_radius = self.dot_size.as_f32() / 2.;
154 let dot_pos = origin_point(px(x - dot_radius), px(y - dot_radius), origin);
155 paint_dots.push(self.paint_dot(dot_pos));
156 }
157
158 dots.push(pos);
159 }
160 }
161
162 (dots, paint_dots)
163 }
164
165 fn build_path(&self, dots: &[Point<Pixels>]) -> Option<Path<Pixels>> {
167 let mut builder = PathBuilder::stroke(self.stroke_width);
168
169 if dots.is_empty() {
170 return None;
171 }
172
173 if dots.len() == 1 {
174 builder.move_to(dots[0]);
175 return builder.build().ok();
176 }
177
178 match self.curve {
179 Curve::Natural => {
180 builder.move_to(dots[0]);
181 let n = dots.len();
182 for i in 0..n - 1 {
183 let p0 = if i == 0 { dots[0] } else { dots[i - 1] };
184 let p1 = dots[i];
185 let p2 = dots[i + 1];
186 let p3 = if i + 2 < n { dots[i + 2] } else { dots[n - 1] };
187
188 let c1 = Point::new(p1.x + (p2.x - p0.x) / 6.0, p1.y + (p2.y - p0.y) / 6.0);
190 let c2 = Point::new(p2.x - (p3.x - p1.x) / 6.0, p2.y - (p3.y - p1.y) / 6.0);
191
192 builder.cubic_bezier_to(p2, c1, c2);
193 }
194 }
195 Curve::Linear => {
196 builder.move_to(dots[0]);
197 for p in &dots[1..] {
198 builder.line_to(*p);
199 }
200 }
201 Curve::StepAfter => {
202 builder.move_to(dots[0]);
203 for (i, p) in dots.windows(2).enumerate() {
204 builder.line_to(Point::new(p[1].x, p[0].y));
205 if i < dots.len() - 2 {
207 builder.line_to(p[1]);
208 }
209 }
210 }
211 }
212
213 builder.build().ok()
214 }
215
216 fn path(&self, bounds: &Bounds<Pixels>) -> (Option<Path<Pixels>>, Vec<PaintQuad>) {
217 let (dots, paint_dots) = self.dots(bounds.origin);
218 (self.build_path(&dots), paint_dots)
219 }
220
221 pub fn paint_cached(
228 &self,
229 bounds: &Bounds<Pixels>,
230 cache: &mut PathCache,
231 window: &mut Window,
232 ) {
233 let (dots, paint_dots) = self.dots(Point::default());
234 let mut key = ShapeKey::new((self.curve, self.stroke_width.as_f32().to_bits()));
235 for dot in &dots {
236 key.point(*dot);
237 }
238 if let Some(path) = cache.get(key.finish(), bounds.origin, || self.build_path(&dots)) {
239 window.paint_path(path, self.stroke);
240 }
241 for dot in paint_dots {
243 let mut dot = dot;
244 dot.bounds.origin = dot.bounds.origin + bounds.origin;
245 window.paint_quad(dot);
246 }
247 }
248
249 pub fn paint(&self, bounds: &Bounds<Pixels>, window: &mut Window) {
251 let (path, dots) = self.path(bounds);
252 if let Some(path) = path {
253 window.paint_path(path, self.stroke);
254 }
255 for dot in dots {
256 window.paint_quad(dot);
257 }
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 use gpui::{Bounds, point, px};
266
267 #[test]
268 fn test_line_path() {
269 let data = vec![1., 2., 3.];
270 let line = Line::new()
271 .data(data.clone())
272 .x(|v| Some(*v))
273 .y(|v| Some(*v * 2.));
274
275 let bounds = Bounds::new(point(px(0.), px(0.)), size(px(100.), px(100.)));
276 let (path, dots) = line.path(&bounds);
277
278 assert!(path.is_some());
279 assert!(dots.is_empty());
280
281 let line_with_dots = Line::new()
282 .data(data)
283 .x(|v| Some(*v))
284 .y(|v| Some(*v * 2.))
285 .dot();
286
287 let (_, dots) = line_with_dots.path(&bounds);
288 assert_eq!(dots.len(), 3);
289 }
290}