amdguid/items/
line.rs

1use std::ops::RangeInclusive;
2
3use egui::{pos2, Color32, Mesh, NumExt, Rgba, Shape, Stroke, Ui};
4
5use crate::items;
6use crate::items::plot_item::PlotItem;
7use crate::items::value::Value;
8use crate::items::values::Values;
9use crate::items::{LineStyle, DEFAULT_FILL_ALPHA};
10use crate::transform::{Bounds, ScreenTransform};
11
12impl PlotItem for Line {
13    fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
14        let Self {
15            series,
16            stroke,
17            highlight,
18            mut fill,
19            style,
20            ..
21        } = self;
22
23        let values_tf: Vec<_> = series
24            .values
25            .iter()
26            .map(|v| transform.position_from_value(v))
27            .collect();
28        let n_values = values_tf.len();
29
30        // Fill the area between the line and a reference line, if required.
31        if n_values < 2 {
32            fill = None;
33        }
34        if let Some(y_reference) = fill {
35            let mut fill_alpha = DEFAULT_FILL_ALPHA;
36            if *highlight {
37                fill_alpha = (2.0 * fill_alpha).at_most(1.0);
38            }
39            let y = transform
40                .position_from_value(&Value::new(0.0, y_reference))
41                .y;
42            let fill_color = Rgba::from(stroke.color)
43                .to_opaque()
44                .multiply(fill_alpha)
45                .into();
46            let mut mesh = Mesh::default();
47            let expected_intersections = 20;
48            mesh.reserve_triangles((n_values - 1) * 2);
49            mesh.reserve_vertices(n_values * 2 + expected_intersections);
50            values_tf[0..n_values - 1].windows(2).for_each(|w| {
51                let i = mesh.vertices.len() as u32;
52                mesh.colored_vertex(w[0], fill_color);
53                mesh.colored_vertex(pos2(w[0].x, y), fill_color);
54                if let Some(x) = items::y_intersection(&w[0], &w[1], y) {
55                    let point = pos2(x, y);
56                    mesh.colored_vertex(point, fill_color);
57                    mesh.add_triangle(i, i + 1, i + 2);
58                    mesh.add_triangle(i + 2, i + 3, i + 4);
59                } else {
60                    mesh.add_triangle(i, i + 1, i + 2);
61                    mesh.add_triangle(i + 1, i + 2, i + 3);
62                }
63            });
64            let last = values_tf[n_values - 1];
65            mesh.colored_vertex(last, fill_color);
66            mesh.colored_vertex(pos2(last.x, y), fill_color);
67            shapes.push(Shape::Mesh(mesh));
68        }
69
70        style.style_line(values_tf, *stroke, *highlight, shapes);
71    }
72
73    fn initialize(&mut self, x_range: RangeInclusive<f64>) {
74        self.series.generate_points(x_range);
75    }
76
77    fn name(&self) -> &str {
78        self.name.as_str()
79    }
80
81    fn color(&self) -> Color32 {
82        self.stroke.color
83    }
84
85    fn highlight(&mut self) {
86        self.highlight = true;
87    }
88
89    fn highlighted(&self) -> bool {
90        self.highlight
91    }
92
93    fn values(&self) -> Option<&Values> {
94        Some(&self.series)
95    }
96
97    fn bounds(&self) -> Bounds {
98        self.series.get_bounds()
99    }
100}
101
102/// A series of values forming a path.
103pub struct Line {
104    pub series: Values,
105    pub stroke: Stroke,
106    pub name: String,
107    pub highlight: bool,
108    pub fill: Option<f32>,
109    pub style: LineStyle,
110}
111
112impl Line {
113    pub fn new(series: Values) -> Self {
114        Self {
115            series,
116            stroke: Stroke::new(1.0, Color32::TRANSPARENT),
117            name: Default::default(),
118            highlight: false,
119            fill: None,
120            style: LineStyle::Solid,
121        }
122    }
123
124    /// Stroke color. Default is `Color32::TRANSPARENT` which means a color will
125    /// be auto-assigned.
126    #[must_use]
127    pub fn color(mut self, color: impl Into<Color32>) -> Self {
128        self.stroke.color = color.into();
129        self
130    }
131}