gpui_component/plot/shape/
area.rs1use gpui::{px, Background, Bounds, Path, PathBuilder, Pixels, Point, Window};
4
5use crate::plot::{origin_point, StrokeStyle};
6
7#[allow(clippy::type_complexity)]
8pub struct Area<T> {
9 data: Vec<T>,
10 x: Box<dyn Fn(&T) -> Option<f32>>,
11 y0: Option<f32>,
12 y1: Box<dyn Fn(&T) -> Option<f32>>,
13 fill: Background,
14 stroke: Background,
15 stroke_style: StrokeStyle,
16}
17
18impl<T> Default for Area<T> {
19 fn default() -> Self {
20 Self {
21 data: Vec::new(),
22 x: Box::new(|_| None),
23 y0: None,
24 y1: Box::new(|_| None),
25 fill: Default::default(),
26 stroke: Default::default(),
27 stroke_style: Default::default(),
28 }
29 }
30}
31
32impl<T> Area<T> {
33 pub fn new() -> Self {
34 Self::default()
35 }
36
37 pub fn data<I>(mut self, data: I) -> Self
39 where
40 I: IntoIterator<Item = T>,
41 {
42 self.data = data.into_iter().collect();
43 self
44 }
45
46 pub fn x<F>(mut self, x: F) -> Self
48 where
49 F: Fn(&T) -> Option<f32> + 'static,
50 {
51 self.x = Box::new(x);
52 self
53 }
54
55 pub fn y0(mut self, y0: f32) -> Self {
57 self.y0 = Some(y0);
58 self
59 }
60
61 pub fn y1<F>(mut self, y1: F) -> Self
63 where
64 F: Fn(&T) -> Option<f32> + 'static,
65 {
66 self.y1 = Box::new(y1);
67 self
68 }
69
70 pub fn fill(mut self, fill: impl Into<Background>) -> Self {
72 self.fill = fill.into();
73 self
74 }
75
76 pub fn stroke(mut self, stroke: impl Into<Background>) -> Self {
78 self.stroke = stroke.into();
79 self
80 }
81
82 pub fn stroke_style(mut self, stroke_style: StrokeStyle) -> Self {
84 self.stroke_style = stroke_style;
85 self
86 }
87
88 fn path(&self, bounds: &Bounds<Pixels>) -> (Option<Path<Pixels>>, Option<Path<Pixels>>) {
89 let origin = bounds.origin;
90 let mut area_builder = PathBuilder::fill();
91 let mut line_builder = PathBuilder::stroke(px(1.));
92
93 let mut points = vec![];
94
95 for v in self.data.iter() {
96 let x_tick = (self.x)(v);
97 let y_tick = (self.y1)(v);
98
99 if let (Some(x), Some(y)) = (x_tick, y_tick) {
100 let pos = origin_point(px(x), px(y), origin);
101
102 points.push(pos);
103 }
104 }
105
106 if points.is_empty() {
107 return (None, None);
108 }
109
110 if points.len() == 1 {
111 area_builder.move_to(points[0]);
112 line_builder.move_to(points[0]);
113 return (area_builder.build().ok(), line_builder.build().ok());
114 }
115
116 match self.stroke_style {
117 StrokeStyle::Natural => {
118 area_builder.move_to(points[0]);
119 line_builder.move_to(points[0]);
120 let n = points.len();
121 for i in 0..n - 1 {
122 let p0 = if i == 0 { points[0] } else { points[i - 1] };
123 let p1 = points[i];
124 let p2 = points[i + 1];
125 let p3 = if i + 2 < n {
126 points[i + 2]
127 } else {
128 points[n - 1]
129 };
130
131 let c1 = Point::new(p1.x + (p2.x - p0.x) / 6.0, p1.y + (p2.y - p0.y) / 6.0);
133 let c2 = Point::new(p2.x - (p3.x - p1.x) / 6.0, p2.y - (p3.y - p1.y) / 6.0);
134
135 area_builder.cubic_bezier_to(p2, c1, c2);
136 line_builder.cubic_bezier_to(p2, c1, c2);
137 }
138 }
139 StrokeStyle::Linear => {
140 area_builder.move_to(points[0]);
141 line_builder.move_to(points[0]);
142 for p in &points[1..] {
143 area_builder.line_to(*p);
144 line_builder.line_to(*p);
145 }
146 }
147 StrokeStyle::StepAfter => {
148 area_builder.move_to(points[0]);
149 line_builder.move_to(points[0]);
150 for p in points.windows(2) {
151 area_builder.line_to(Point::new(p[1].x, p[0].y));
152 area_builder.line_to(Point::new(p[1].x, p[1].y));
153 line_builder.line_to(Point::new(p[1].x, p[0].y));
154 line_builder.line_to(Point::new(p[1].x, p[1].y));
155 }
156 }
157 }
158
159 if let Some(last) = self.data.last() {
161 let x_tick = (self.x)(last);
162 if let (Some(x), Some(y)) = (x_tick, self.y0) {
163 area_builder.line_to(origin_point(px(x), px(y), bounds.origin));
164 area_builder.line_to(origin_point(px(0.), px(y), bounds.origin));
165 area_builder.close();
166 }
167 }
168
169 (area_builder.build().ok(), line_builder.build().ok())
170 }
171
172 pub fn paint(&self, bounds: &Bounds<Pixels>, window: &mut Window) {
174 let (area, line) = self.path(bounds);
175
176 if let Some(area) = area {
177 window.paint_path(area, self.fill);
178 }
179 if let Some(line) = line {
180 window.paint_path(line, self.stroke);
181 }
182 }
183}