Skip to main content

egui_plot/items/
arrows.rs

1use std::ops::RangeInclusive;
2
3use egui::Color32;
4use egui::Id;
5use egui::Shape;
6use egui::Stroke;
7use egui::Ui;
8use emath::Rot2;
9
10use crate::axis::PlotTransform;
11use crate::bounds::PlotBounds;
12use crate::data::PlotPoints;
13use crate::items::PlotGeometry;
14use crate::items::PlotItem;
15use crate::items::PlotItemBase;
16
17impl<'a> Arrows<'a> {
18    pub fn new(name: impl Into<String>, origins: impl Into<PlotPoints<'a>>, tips: impl Into<PlotPoints<'a>>) -> Self {
19        Self {
20            base: PlotItemBase::new(name.into()),
21            origins: origins.into(),
22            tips: tips.into(),
23            tip_length: None,
24            color: Color32::TRANSPARENT,
25        }
26    }
27
28    /// Set the length of the arrow tips
29    #[inline]
30    pub fn tip_length(mut self, tip_length: f32) -> Self {
31        self.tip_length = Some(tip_length);
32        self
33    }
34
35    /// Set the arrows' color.
36    #[inline]
37    pub fn color(mut self, color: impl Into<Color32>) -> Self {
38        self.color = color.into();
39        self
40    }
41
42    /// Name of this plot item.
43    ///
44    /// This name will show up in the plot legend, if legends are turned on.
45    ///
46    /// Setting the name via this method does not change the item's id, so you
47    /// can use it to change the name dynamically between frames without
48    /// losing the item's state. You should make sure the name passed to
49    /// [`Self::new`] is unique and stable for each item, or set unique and
50    /// stable ids explicitly via [`Self::id`].
51    #[expect(clippy::needless_pass_by_value, reason = "to allow various string types")]
52    #[inline]
53    pub fn name(mut self, name: impl ToString) -> Self {
54        self.base_mut().name = name.to_string();
55        self
56    }
57
58    /// Highlight this plot item, typically by scaling it up.
59    ///
60    /// If false, the item may still be highlighted via user interaction.
61    #[inline]
62    pub fn highlight(mut self, highlight: bool) -> Self {
63        self.base_mut().highlight = highlight;
64        self
65    }
66
67    /// Allowed hovering this item in the plot. Default: `true`.
68    #[inline]
69    pub fn allow_hover(mut self, hovering: bool) -> Self {
70        self.base_mut().allow_hover = hovering;
71        self
72    }
73
74    /// Sets the id of this plot item.
75    ///
76    /// By default the id is determined from the name passed to [`Self::new`],
77    /// but it can be explicitly set to a different value.
78    #[inline]
79    pub fn id(mut self, id: impl Into<Id>) -> Self {
80        self.base_mut().id = id.into();
81        self
82    }
83}
84
85/// A set of arrows.
86pub struct Arrows<'a> {
87    base: PlotItemBase,
88    pub(crate) origins: PlotPoints<'a>,
89    pub(crate) tips: PlotPoints<'a>,
90    pub(crate) tip_length: Option<f32>,
91    pub(crate) color: Color32,
92}
93
94impl PlotItem for Arrows<'_> {
95    fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
96        let Self {
97            origins,
98            tips,
99            tip_length,
100            color,
101            base,
102            ..
103        } = self;
104        let stroke = Stroke::new(if base.highlight { 2.0 } else { 1.0 }, *color);
105        origins
106            .points()
107            .iter()
108            .zip(tips.points().iter())
109            .map(|(origin, tip)| {
110                (
111                    transform.position_from_point(origin),
112                    transform.position_from_point(tip),
113                )
114            })
115            .for_each(|(origin, tip)| {
116                let vector = tip - origin;
117                let rot = Rot2::from_angle(std::f32::consts::TAU / 10.0);
118                let tip_length = if let Some(tip_length) = tip_length {
119                    *tip_length
120                } else {
121                    vector.length() / 4.0
122                };
123                let tip = origin + vector;
124                let dir = vector.normalized();
125                shapes.push(Shape::line_segment([origin, tip], stroke));
126                shapes.push(Shape::line(
127                    vec![
128                        tip - tip_length * (rot.inverse() * dir),
129                        tip,
130                        tip - tip_length * (rot * dir),
131                    ],
132                    stroke,
133                ));
134            });
135    }
136
137    fn initialize(&mut self, _x_range: RangeInclusive<f64>) {
138        self.origins.generate_points(f64::NEG_INFINITY..=f64::INFINITY);
139        self.tips.generate_points(f64::NEG_INFINITY..=f64::INFINITY);
140    }
141
142    fn color(&self) -> Color32 {
143        self.color
144    }
145
146    fn geometry(&self) -> PlotGeometry<'_> {
147        PlotGeometry::Points(self.origins.points())
148    }
149
150    fn bounds(&self) -> PlotBounds {
151        self.origins.bounds()
152    }
153
154    fn base(&self) -> &PlotItemBase {
155        &self.base
156    }
157
158    fn base_mut(&mut self) -> &mut PlotItemBase {
159        &mut self.base
160    }
161}