Skip to main content

gpui_base/plot/shape/
area.rs

1// @reference: https://d3js.org/d3-shape/area
2
3use gpui::{Background, Bounds, Path, PathBuilder, Pixels, Point, Window, px};
4
5use crate::plot::{Curve, PathCache, ShapeKey, 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    curve: Curve,
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            curve: Curve::default(),
28        }
29    }
30}
31
32impl<T> Area<T> {
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Set the data of the Area.
38    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    /// Set the x of the Area.
47    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    /// Set the y0 of the Area.
56    pub fn y0(mut self, y0: f32) -> Self {
57        self.y0 = Some(y0);
58        self
59    }
60
61    /// Set the y1 of the Area.
62    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    /// Set the fill color of the Area.
71    pub fn fill(mut self, fill: impl Into<Background>) -> Self {
72        self.fill = fill.into();
73        self
74    }
75
76    /// Set the stroke color of the Area.
77    pub fn stroke(mut self, stroke: impl Into<Background>) -> Self {
78        self.stroke = stroke.into();
79        self
80    }
81
82    /// Set how the Area's top line connects its points. Defaults to
83    /// [`Curve::Natural`].
84    pub fn curve(mut self, curve: Curve) -> Self {
85        self.curve = curve;
86        self
87    }
88
89    fn path(&self, bounds: &Bounds<Pixels>) -> (Option<Path<Pixels>>, Option<Path<Pixels>>) {
90        let origin = bounds.origin;
91        let mut area_builder = PathBuilder::fill();
92        let mut line_builder = PathBuilder::stroke(px(1.));
93
94        let mut points = vec![];
95
96        let mut first_x_tick = None;
97        let mut last_x_tick = None;
98        for (index, v) in self.data.iter().enumerate() {
99            if index == 0 {
100                first_x_tick = (self.x)(v);
101            }
102            if index == self.data.len() - 1 {
103                last_x_tick = (self.x)(v);
104            }
105            let x_tick = (self.x)(v);
106            let y_tick = (self.y1)(v);
107
108            if let (Some(x), Some(y)) = (x_tick, y_tick) {
109                let pos = origin_point(px(x), px(y), origin);
110
111                points.push(pos);
112            }
113        }
114
115        if points.is_empty() {
116            return (None, None);
117        }
118
119        if points.len() == 1 {
120            area_builder.move_to(points[0]);
121            line_builder.move_to(points[0]);
122            return (area_builder.build().ok(), line_builder.build().ok());
123        }
124
125        match self.curve {
126            Curve::Natural => {
127                area_builder.move_to(points[0]);
128                line_builder.move_to(points[0]);
129                let n = points.len();
130                for i in 0..n - 1 {
131                    let p0 = if i == 0 { points[0] } else { points[i - 1] };
132                    let p1 = points[i];
133                    let p2 = points[i + 1];
134                    let p3 = if i + 2 < n {
135                        points[i + 2]
136                    } else {
137                        points[n - 1]
138                    };
139
140                    // Catmull-Rom to Bezier
141                    let c1 = Point::new(p1.x + (p2.x - p0.x) / 6.0, p1.y + (p2.y - p0.y) / 6.0);
142                    let c2 = Point::new(p2.x - (p3.x - p1.x) / 6.0, p2.y - (p3.y - p1.y) / 6.0);
143
144                    area_builder.cubic_bezier_to(p2, c1, c2);
145                    line_builder.cubic_bezier_to(p2, c1, c2);
146                }
147            }
148            Curve::Linear => {
149                area_builder.move_to(points[0]);
150                line_builder.move_to(points[0]);
151                for p in &points[1..] {
152                    area_builder.line_to(*p);
153                    line_builder.line_to(*p);
154                }
155            }
156            Curve::StepAfter => {
157                area_builder.move_to(points[0]);
158                line_builder.move_to(points[0]);
159                for (i, p) in points.windows(2).enumerate() {
160                    area_builder.line_to(Point::new(p[1].x, p[0].y));
161                    line_builder.line_to(Point::new(p[1].x, p[0].y));
162                    // Don't draw the vertical line for the last point
163                    if i < points.len() - 2 {
164                        area_builder.line_to(p[1]);
165                        line_builder.line_to(p[1]);
166                    }
167                }
168            }
169        }
170
171        // Close path
172        if let (Some(first), Some(last), Some(y)) = (first_x_tick, last_x_tick, self.y0) {
173            area_builder.line_to(origin_point(px(last), px(y), bounds.origin));
174            area_builder.line_to(origin_point(px(first), px(y), bounds.origin));
175            area_builder.close();
176        }
177
178        (area_builder.build().ok(), line_builder.build().ok())
179    }
180
181    /// Paint the Area, reusing the fill and stroke tessellated by an earlier
182    /// paint while the projected points, baseline and curve style are
183    /// unchanged. `fill` and `line` are the two caches this area keeps
184    /// together; see [`Line::paint_cached`](super::Line::paint_cached).
185    pub fn paint_cached(
186        &self,
187        bounds: &Bounds<Pixels>,
188        fill: &mut PathCache,
189        line: &mut PathCache,
190        window: &mut Window,
191    ) {
192        let mut key = ShapeKey::new((self.curve, self.y0.map(f32::to_bits)));
193        for v in self.data.iter() {
194            if let (Some(x), Some(y)) = ((self.x)(v), (self.y1)(v)) {
195                key.f32(x).f32(y);
196            }
197        }
198        let key = key.finish();
199        let local = Bounds::new(Point::default(), bounds.size);
200        // One miss builds both paths; the second cache takes the stroke from
201        // the stash instead of building again.
202        let mut stroke_path = None;
203        let fill_path = fill.get(key, bounds.origin, || {
204            let (area, stroke) = self.path(&local);
205            stroke_path = Some(stroke);
206            area
207        });
208        let line_path = line.get(key, bounds.origin, || {
209            stroke_path.take().unwrap_or_else(|| self.path(&local).1)
210        });
211        if let Some(area) = fill_path {
212            window.paint_path(area, self.fill);
213        }
214        if let Some(line) = line_path {
215            window.paint_path(line, self.stroke);
216        }
217    }
218
219    /// Paint the Area.
220    pub fn paint(&self, bounds: &Bounds<Pixels>, window: &mut Window) {
221        let (area, line) = self.path(bounds);
222
223        if let Some(area) = area {
224            window.paint_path(area, self.fill);
225        }
226        if let Some(line) = line {
227            window.paint_path(line, self.stroke);
228        }
229    }
230}