egui_plot/items/
arrows.rs1use 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 #[inline]
30 pub fn tip_length(mut self, tip_length: f32) -> Self {
31 self.tip_length = Some(tip_length);
32 self
33 }
34
35 #[inline]
37 pub fn color(mut self, color: impl Into<Color32>) -> Self {
38 self.color = color.into();
39 self
40 }
41
42 #[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 #[inline]
62 pub fn highlight(mut self, highlight: bool) -> Self {
63 self.base_mut().highlight = highlight;
64 self
65 }
66
67 #[inline]
69 pub fn allow_hover(mut self, hovering: bool) -> Self {
70 self.base_mut().allow_hover = hovering;
71 self
72 }
73
74 #[inline]
79 pub fn id(mut self, id: impl Into<Id>) -> Self {
80 self.base_mut().id = id.into();
81 self
82 }
83}
84
85pub 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}