rgpui_component/plot/shape/
area.rs1use rgpui::{Background, Bounds, Path, PathBuilder, Pixels, Point, Window, px};
4
5use crate::plot::{StrokeStyle, origin_point};
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 let mut first_x_tick = None;
96 let mut last_x_tick = None;
97 for (index, v) in self.data.iter().enumerate() {
98 if index == 0 {
99 first_x_tick = (self.x)(v);
100 }
101 if index == self.data.len() - 1 {
102 last_x_tick = (self.x)(v);
103 }
104 let x_tick = (self.x)(v);
105 let y_tick = (self.y1)(v);
106
107 if let (Some(x), Some(y)) = (x_tick, y_tick) {
108 let pos = origin_point(px(x), px(y), origin);
109
110 points.push(pos);
111 }
112 }
113
114 if points.is_empty() {
115 return (None, None);
116 }
117
118 if points.len() == 1 {
119 area_builder.move_to(points[0]);
120 line_builder.move_to(points[0]);
121 return (area_builder.build().ok(), line_builder.build().ok());
122 }
123
124 match self.stroke_style {
125 StrokeStyle::Natural => {
126 area_builder.move_to(points[0]);
127 line_builder.move_to(points[0]);
128 let n = points.len();
129 for i in 0..n - 1 {
130 let p0 = if i == 0 { points[0] } else { points[i - 1] };
131 let p1 = points[i];
132 let p2 = points[i + 1];
133 let p3 = if i + 2 < n {
134 points[i + 2]
135 } else {
136 points[n - 1]
137 };
138
139 let c1 = Point::new(p1.x + (p2.x - p0.x) / 6.0, p1.y + (p2.y - p0.y) / 6.0);
141 let c2 = Point::new(p2.x - (p3.x - p1.x) / 6.0, p2.y - (p3.y - p1.y) / 6.0);
142
143 area_builder.cubic_bezier_to(p2, c1, c2);
144 line_builder.cubic_bezier_to(p2, c1, c2);
145 }
146 }
147 StrokeStyle::Linear => {
148 area_builder.move_to(points[0]);
149 line_builder.move_to(points[0]);
150 for p in &points[1..] {
151 area_builder.line_to(*p);
152 line_builder.line_to(*p);
153 }
154 }
155 StrokeStyle::StepAfter => {
156 area_builder.move_to(points[0]);
157 line_builder.move_to(points[0]);
158 for (i, p) in points.windows(2).enumerate() {
159 area_builder.line_to(Point::new(p[1].x, p[0].y));
160 line_builder.line_to(Point::new(p[1].x, p[0].y));
161 if i < points.len() - 2 {
163 area_builder.line_to(p[1]);
164 line_builder.line_to(p[1]);
165 }
166 }
167 }
168 }
169
170 if let (Some(first), Some(last), Some(y)) = (first_x_tick, last_x_tick, self.y0) {
172 area_builder.line_to(origin_point(px(last), px(y), bounds.origin));
173 area_builder.line_to(origin_point(px(first), px(y), bounds.origin));
174 area_builder.close();
175 }
176
177 (area_builder.build().ok(), line_builder.build().ok())
178 }
179
180 pub fn paint(&self, bounds: &Bounds<Pixels>, window: &mut Window) {
182 let (area, line) = self.path(bounds);
183
184 if let Some(area) = area {
185 window.paint_path(area, self.fill);
186 }
187 if let Some(line) = line {
188 window.paint_path(line, self.stroke);
189 }
190 }
191}