1use std::f32::consts::PI;
2use std::ops::RangeInclusive;
3
4use egui::Align2;
5use egui::Color32;
6use egui::Pos2;
7use egui::Rect;
8use egui::Shape;
9use egui::Stroke;
10use egui::TextStyle;
11use egui::Ui;
12use egui::Vec2;
13use egui::epaint::PathStroke;
14use egui::epaint::TextShape;
15use egui::pos2;
16use emath::TSTransform;
17
18use crate::aesthetics::LineStyle;
19use crate::axis::Axis;
20use crate::axis::PlotTransform;
21use crate::bounds::PlotBounds;
22use crate::bounds::PlotPoint;
23use crate::colors::highlighted_color;
24use crate::items::PlotGeometry;
25use crate::items::PlotItem;
26use crate::items::PlotItemBase;
27use crate::utils::find_name_candidate;
28
29const LABEL_PADDING: f32 = 4.0;
36
37#[derive(Clone, Debug, PartialEq)]
39pub struct Span {
40 base: PlotItemBase,
41 axis: Axis,
42 range: RangeInclusive<f64>,
43 fill: Color32,
44 border_stroke: Stroke,
45 border_style: LineStyle,
46 label_align: Align2,
47}
48
49impl Span {
50 pub fn new(name: impl Into<String>, range: impl Into<RangeInclusive<f64>>) -> Self {
52 Self {
53 base: PlotItemBase::new(name.into()),
54 axis: Axis::X,
55 range: range.into(),
56 fill: Color32::TRANSPARENT,
57 border_stroke: Stroke::new(1.0, Color32::TRANSPARENT),
58 border_style: LineStyle::Solid,
59 label_align: Align2::CENTER_TOP,
60 }
61 }
62
63 #[inline]
67 pub fn axis(mut self, axis: Axis) -> Self {
68 self.axis = axis;
69 match axis {
70 Axis::X => self.label_align = Align2::CENTER_TOP,
71 Axis::Y => self.label_align = Align2::LEFT_CENTER,
72 }
73 self
74 }
75
76 #[inline]
78 pub fn range(mut self, range: impl Into<RangeInclusive<f64>>) -> Self {
79 self.range = range.into();
80 self
81 }
82
83 #[inline]
85 pub fn fill(mut self, color: impl Into<Color32>) -> Self {
86 self.fill = color.into();
87 self
88 }
89
90 #[inline]
92 pub fn border(mut self, stroke: impl Into<Stroke>) -> Self {
93 self.border_stroke = stroke.into();
94 self
95 }
96
97 #[inline]
99 pub fn border_width(mut self, width: impl Into<f32>) -> Self {
100 self.border_stroke.width = width.into();
101 self
102 }
103
104 #[inline]
106 pub fn border_color(mut self, color: impl Into<Color32>) -> Self {
107 self.border_stroke.color = color.into();
108 self
109 }
110
111 #[inline]
113 pub fn border_style(mut self, style: LineStyle) -> Self {
114 self.border_style = style;
115 self
116 }
117
118 #[inline]
122 pub fn label_align(mut self, align: Align2) -> Self {
123 self.label_align = align;
124 self
125 }
126
127 #[inline]
128 pub(crate) fn fill_color(&self) -> Color32 {
129 self.fill
130 }
131
132 #[inline]
133 pub(crate) fn border_color_value(&self) -> Color32 {
134 self.border_stroke.color
135 }
136
137 fn range_sorted(&self) -> (f64, f64) {
138 let start = *self.range.start();
139 let end = *self.range.end();
140 if start <= end { (start, end) } else { (end, start) }
141 }
142
143 fn hline_points(value: f64, transform: &PlotTransform) -> Vec<Pos2> {
144 vec![
145 transform.position_from_point(&PlotPoint::new(transform.bounds().min[0], value)),
146 transform.position_from_point(&PlotPoint::new(transform.bounds().max[0], value)),
147 ]
148 }
149
150 fn vline_points(value: f64, transform: &PlotTransform) -> Vec<Pos2> {
151 vec![
152 transform.position_from_point(&PlotPoint::new(value, transform.bounds().min[1])),
153 transform.position_from_point(&PlotPoint::new(value, transform.bounds().max[1])),
154 ]
155 }
156
157 fn draw_border(&self, value: f64, stroke: Stroke, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
158 if stroke.color == Color32::TRANSPARENT || stroke.width <= 0.0 || !value.is_finite() {
159 return;
160 }
161
162 let line = match self.axis {
163 Axis::X => Self::vline_points(value, transform),
164 Axis::Y => Self::hline_points(value, transform),
165 };
166
167 self.border_style
168 .style_line(line, PathStroke::new(stroke.width, stroke.color), false, shapes);
169 }
170
171 fn available_width_for_name(&self, rect: &Rect) -> f32 {
172 match self.axis {
173 Axis::X => (rect.width() - 2.0 * LABEL_PADDING).max(0.0),
174 Axis::Y => (rect.height() - 2.0 * LABEL_PADDING).max(0.0),
175 }
176 }
177
178 fn draw_name(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>, span_rect: &Rect) {
179 let frame = *transform.frame();
180 let visible_rect = span_rect.intersect(frame);
181
182 let available_width = self.available_width_for_name(&visible_rect);
183 if available_width <= 0.0 {
184 return;
185 }
186
187 let font_id = TextStyle::Body.resolve(ui.style());
188 let text_color = ui.visuals().text_color();
189 let painter = ui.painter();
190
191 let name = find_name_candidate(&self.base.name, available_width, painter, &font_id);
192
193 let galley = painter.layout_no_wrap(name, font_id, text_color);
194
195 if galley.is_empty() {
196 return;
197 }
198
199 let mut text_shape = match self.axis {
201 Axis::X => TextShape::new(pos2(-galley.size().x / 2.0, -galley.size().y / 2.0), galley, text_color),
202
203 Axis::Y => TextShape::new(pos2(-galley.size().x / 2.0, -galley.size().y / 2.0), galley, text_color)
205 .with_angle_and_anchor(-PI / 2.0, Align2::CENTER_CENTER),
206 };
207
208 let text_rect = text_shape.visual_bounding_rect();
210 let (width, height) = (text_rect.width(), text_rect.height());
211
212 let text_pos_x = match self.label_align {
214 Align2::LEFT_CENTER | Align2::LEFT_TOP | Align2::LEFT_BOTTOM => visible_rect.left() + LABEL_PADDING,
215 Align2::CENTER_CENTER | Align2::CENTER_TOP | Align2::CENTER_BOTTOM => visible_rect.center().x - width / 2.0,
216 Align2::RIGHT_CENTER | Align2::RIGHT_TOP | Align2::RIGHT_BOTTOM => {
217 visible_rect.right() - LABEL_PADDING - width
218 }
219 };
220
221 let text_pos_y = match self.label_align {
222 Align2::LEFT_TOP | Align2::CENTER_TOP | Align2::RIGHT_TOP => visible_rect.top() + LABEL_PADDING,
223 Align2::LEFT_CENTER | Align2::CENTER_CENTER | Align2::RIGHT_CENTER => {
224 visible_rect.center().y - height / 2.0
225 }
226 Align2::LEFT_BOTTOM | Align2::CENTER_BOTTOM | Align2::RIGHT_BOTTOM => {
227 visible_rect.bottom() - LABEL_PADDING - height
228 }
229 };
230
231 let text_pos = pos2(text_pos_x + width / 2.0, text_pos_y + height / 2.0);
234
235 text_shape.transform(TSTransform::from_translation(Vec2::new(text_pos.x, text_pos.y)));
236
237 shapes.push(text_shape.into());
238 }
239}
240
241impl PlotItem for Span {
242 fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
243 let plot_bounds = match self.axis {
244 Axis::X => transform.bounds().range_x(),
245 Axis::Y => transform.bounds().range_y(),
246 };
247
248 let (range_min, range_max) = self.range_sorted();
249
250 if range_max < *plot_bounds.start() || range_min > *plot_bounds.end() {
252 return;
253 }
254
255 let mut stroke = self.border_stroke;
256 let mut fill = self.fill;
257 if self.base.highlight {
258 (stroke, fill) = highlighted_color(stroke, fill);
259 }
260
261 let range_min_clamped = range_min.max(*plot_bounds.start());
263 let range_max_clamped = range_max.min(*plot_bounds.end());
264
265 let span_rect = match self.axis {
267 Axis::X => transform.rect_from_values(
268 &PlotPoint::new(range_min_clamped, transform.bounds().min[1]),
269 &PlotPoint::new(range_max_clamped, transform.bounds().max[1]),
270 ),
271 Axis::Y => transform.rect_from_values(
272 &PlotPoint::new(transform.bounds().min[0], range_min_clamped),
273 &PlotPoint::new(transform.bounds().max[0], range_max_clamped),
274 ),
275 };
276
277 if fill != Color32::TRANSPARENT && span_rect.is_positive() {
278 shapes.push(Shape::rect_filled(span_rect, 0.0, fill));
279 }
280
281 if plot_bounds.contains(&range_min) {
283 self.draw_border(range_min, stroke, transform, shapes);
284 }
285
286 if plot_bounds.contains(&range_max) {
288 self.draw_border(range_max, stroke, transform, shapes);
289 }
290
291 self.draw_name(ui, transform, shapes, &span_rect);
292 }
293
294 fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
295
296 fn color(&self) -> Color32 {
297 if self.fill != Color32::TRANSPARENT {
298 self.fill
299 } else {
300 self.border_stroke.color
301 }
302 }
303
304 fn geometry(&self) -> PlotGeometry<'_> {
305 PlotGeometry::None
306 }
307
308 fn bounds(&self) -> PlotBounds {
309 PlotBounds::NOTHING
310 }
311
312 fn base(&self) -> &PlotItemBase {
313 &self.base
314 }
315
316 fn base_mut(&mut self) -> &mut PlotItemBase {
317 &mut self.base
318 }
319}