egui_plot/items/
mod.rs

1//! Contains items that can be added to a plot.
2#![expect(clippy::type_complexity)] // TODO(emilk): simplify some of the callback types with type aliases
3
4use std::{ops::RangeInclusive, sync::Arc};
5
6use egui::{
7    Align2, Color32, CornerRadius, Id, ImageOptions, Mesh, NumExt as _, PopupAnchor, Pos2, Rect,
8    Rgba, Shape, Stroke, TextStyle, TextureId, Ui, Vec2, WidgetText,
9    emath::Rot2,
10    epaint::{CircleShape, PathStroke, TextShape},
11    pos2, vec2,
12};
13
14use emath::Float as _;
15use rect_elem::{RectElement, highlighted_color};
16
17use super::{Cursor, LabelFormatter, PlotBounds, PlotTransform};
18
19pub use bar::Bar;
20pub use box_elem::{BoxElem, BoxSpread};
21pub use values::{
22    ClosestElem, LineStyle, MarkerShape, Orientation, PlotGeometry, PlotPoint, PlotPoints,
23};
24
25mod bar;
26mod box_elem;
27mod rect_elem;
28mod values;
29
30const DEFAULT_FILL_ALPHA: f32 = 0.05;
31
32#[derive(Clone, Debug, PartialEq, Eq)]
33pub struct PlotItemBase {
34    name: String,
35    id: Id,
36    highlight: bool,
37    allow_hover: bool,
38}
39
40impl PlotItemBase {
41    pub fn new(name: String) -> Self {
42        let id = Id::new(&name);
43        Self {
44            name,
45            id,
46            highlight: false,
47            allow_hover: true,
48        }
49    }
50}
51
52macro_rules! builder_methods_for_base {
53    () => {
54        /// Name of this plot item.
55        ///
56        /// This name will show up in the plot legend, if legends are turned on.
57        ///
58        /// Setting the name via this method does not change the item's id, so you can use it to
59        /// change the name dynamically between frames without losing the item's state. You should
60        /// make sure the name passed to [`Self::new`] is unique and stable for each item, or
61        /// set unique and stable ids explicitly via [`Self::id`].
62        #[inline]
63        pub fn name(mut self, name: impl ToString) -> Self {
64            self.base_mut().name = name.to_string();
65            self
66        }
67
68        /// Highlight this plot item, typically by scaling it up.
69        ///
70        /// If false, the item may still be highlighted via user interaction.
71        #[inline]
72        pub fn highlight(mut self, highlight: bool) -> Self {
73            self.base_mut().highlight = highlight;
74            self
75        }
76
77        /// Allowed hovering this item in the plot. Default: `true`.
78        #[inline]
79        pub fn allow_hover(mut self, hovering: bool) -> Self {
80            self.base_mut().allow_hover = hovering;
81            self
82        }
83
84        /// Sets the id of this plot item.
85        ///
86        /// By default the id is determined from the name passed to [`Self::new`], but it can be
87        /// explicitly set to a different value.
88        #[inline]
89        pub fn id(mut self, id: impl Into<Id>) -> Self {
90            self.base_mut().id = id.into();
91            self
92        }
93    };
94}
95
96/// Container to pass-through several parameters related to plot visualization
97pub struct PlotConfig<'a> {
98    pub ui: &'a Ui,
99    pub transform: &'a PlotTransform,
100    pub show_x: bool,
101    pub show_y: bool,
102}
103
104/// Trait shared by things that can be drawn in the plot.
105pub trait PlotItem {
106    fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>);
107
108    /// For plot-items which are generated based on x values (plotting functions).
109    fn initialize(&mut self, x_range: RangeInclusive<f64>);
110
111    fn name(&self) -> &str {
112        &self.base().name
113    }
114
115    fn color(&self) -> Color32;
116
117    fn highlight(&mut self) {
118        self.base_mut().highlight = true;
119    }
120
121    fn highlighted(&self) -> bool {
122        self.base().highlight
123    }
124
125    /// Can the user hover this item?
126    fn allow_hover(&self) -> bool {
127        self.base().allow_hover
128    }
129
130    fn geometry(&self) -> PlotGeometry<'_>;
131
132    fn bounds(&self) -> PlotBounds;
133
134    fn base(&self) -> &PlotItemBase;
135
136    fn base_mut(&mut self) -> &mut PlotItemBase;
137
138    fn id(&self) -> Id {
139        self.base().id
140    }
141
142    fn find_closest(&self, point: Pos2, transform: &PlotTransform) -> Option<ClosestElem> {
143        match self.geometry() {
144            PlotGeometry::None => None,
145
146            PlotGeometry::Points(points) => points
147                .iter()
148                .enumerate()
149                .map(|(index, value)| {
150                    let pos = transform.position_from_point(value);
151                    let dist_sq = point.distance_sq(pos);
152                    ClosestElem { index, dist_sq }
153                })
154                .min_by_key(|e| e.dist_sq.ord()),
155
156            PlotGeometry::Rects => {
157                panic!("If the PlotItem is made of rects, it should implement find_closest()")
158            }
159        }
160    }
161
162    fn on_hover(
163        &self,
164        plot_area_response: &egui::Response,
165        elem: ClosestElem,
166        shapes: &mut Vec<Shape>,
167        cursors: &mut Vec<Cursor>,
168        plot: &PlotConfig<'_>,
169        label_formatter: &LabelFormatter<'_>,
170    ) {
171        let points = match self.geometry() {
172            PlotGeometry::Points(points) => points,
173            PlotGeometry::None => {
174                panic!("If the PlotItem has no geometry, on_hover() must not be called")
175            }
176            PlotGeometry::Rects => {
177                panic!("If the PlotItem is made of rects, it should implement on_hover()")
178            }
179        };
180
181        let line_color = if plot.ui.visuals().dark_mode {
182            Color32::from_gray(100).additive()
183        } else {
184            Color32::from_black_alpha(180)
185        };
186
187        // this method is only called, if the value is in the result set of find_closest()
188        let value = points[elem.index];
189        let pointer = plot.transform.position_from_point(&value);
190        shapes.push(Shape::circle_filled(pointer, 3.0, line_color));
191
192        rulers_and_tooltip_at_value(
193            plot_area_response,
194            value,
195            self.name(),
196            plot,
197            cursors,
198            label_formatter,
199        );
200    }
201}
202
203// ----------------------------------------------------------------------------
204
205/// A horizontal line in a plot, filling the full width
206#[derive(Clone, Debug, PartialEq)]
207pub struct HLine {
208    base: PlotItemBase,
209    pub(super) y: f64,
210    pub(super) stroke: Stroke,
211    pub(super) style: LineStyle,
212}
213
214impl HLine {
215    pub fn new(name: impl Into<String>, y: impl Into<f64>) -> Self {
216        Self {
217            base: PlotItemBase::new(name.into()),
218            y: y.into(),
219            stroke: Stroke::new(1.0, Color32::TRANSPARENT),
220            style: LineStyle::Solid,
221        }
222    }
223
224    /// Add a stroke.
225    #[inline]
226    pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
227        self.stroke = stroke.into();
228        self
229    }
230
231    /// Stroke width. A high value means the plot thickens.
232    #[inline]
233    pub fn width(mut self, width: impl Into<f32>) -> Self {
234        self.stroke.width = width.into();
235        self
236    }
237
238    /// Stroke color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
239    #[inline]
240    pub fn color(mut self, color: impl Into<Color32>) -> Self {
241        self.stroke.color = color.into();
242        self
243    }
244
245    /// Set the line's style. Default is `LineStyle::Solid`.
246    #[inline]
247    pub fn style(mut self, style: LineStyle) -> Self {
248        self.style = style;
249        self
250    }
251
252    builder_methods_for_base!();
253}
254
255impl PlotItem for HLine {
256    fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
257        let Self {
258            base,
259            y,
260            stroke,
261            style,
262            ..
263        } = self;
264
265        let points = vec![
266            transform.position_from_point(&PlotPoint::new(transform.bounds().min[0], *y)),
267            transform.position_from_point(&PlotPoint::new(transform.bounds().max[0], *y)),
268        ];
269        style.style_line(
270            points,
271            PathStroke::new(stroke.width, stroke.color),
272            base.highlight,
273            shapes,
274        );
275    }
276
277    fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
278
279    fn color(&self) -> Color32 {
280        self.stroke.color
281    }
282
283    fn base(&self) -> &PlotItemBase {
284        &self.base
285    }
286
287    fn base_mut(&mut self) -> &mut PlotItemBase {
288        &mut self.base
289    }
290
291    fn geometry(&self) -> PlotGeometry<'_> {
292        PlotGeometry::None
293    }
294
295    fn bounds(&self) -> PlotBounds {
296        let mut bounds = PlotBounds::NOTHING;
297        bounds.min[1] = self.y;
298        bounds.max[1] = self.y;
299        bounds
300    }
301}
302
303/// A vertical line in a plot, filling the full width
304#[derive(Clone, Debug, PartialEq)]
305pub struct VLine {
306    base: PlotItemBase,
307    pub(super) x: f64,
308    pub(super) stroke: Stroke,
309    pub(super) style: LineStyle,
310}
311
312impl VLine {
313    pub fn new(name: impl Into<String>, x: impl Into<f64>) -> Self {
314        Self {
315            base: PlotItemBase::new(name.into()),
316            x: x.into(),
317            stroke: Stroke::new(1.0, Color32::TRANSPARENT),
318            style: LineStyle::Solid,
319        }
320    }
321
322    /// Add a stroke.
323    #[inline]
324    pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
325        self.stroke = stroke.into();
326        self
327    }
328
329    /// Stroke width. A high value means the plot thickens.
330    #[inline]
331    pub fn width(mut self, width: impl Into<f32>) -> Self {
332        self.stroke.width = width.into();
333        self
334    }
335
336    /// Stroke color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
337    #[inline]
338    pub fn color(mut self, color: impl Into<Color32>) -> Self {
339        self.stroke.color = color.into();
340        self
341    }
342
343    /// Set the line's style. Default is `LineStyle::Solid`.
344    #[inline]
345    pub fn style(mut self, style: LineStyle) -> Self {
346        self.style = style;
347        self
348    }
349
350    builder_methods_for_base!();
351}
352
353impl PlotItem for VLine {
354    fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
355        let Self {
356            base,
357            x,
358            stroke,
359            style,
360            ..
361        } = self;
362
363        let points = vec![
364            transform.position_from_point(&PlotPoint::new(*x, transform.bounds().min[1])),
365            transform.position_from_point(&PlotPoint::new(*x, transform.bounds().max[1])),
366        ];
367        style.style_line(
368            points,
369            PathStroke::new(stroke.width, stroke.color),
370            base.highlight,
371            shapes,
372        );
373    }
374
375    fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
376
377    fn color(&self) -> Color32 {
378        self.stroke.color
379    }
380
381    fn base(&self) -> &PlotItemBase {
382        &self.base
383    }
384
385    fn base_mut(&mut self) -> &mut PlotItemBase {
386        &mut self.base
387    }
388
389    fn geometry(&self) -> PlotGeometry<'_> {
390        PlotGeometry::None
391    }
392
393    fn bounds(&self) -> PlotBounds {
394        let mut bounds = PlotBounds::NOTHING;
395        bounds.min[0] = self.x;
396        bounds.max[0] = self.x;
397        bounds
398    }
399}
400
401/// A series of values forming a path.
402pub struct Line<'a> {
403    base: PlotItemBase,
404    pub(super) series: PlotPoints<'a>,
405    pub(super) stroke: Stroke,
406    pub(super) fill: Option<f32>,
407    pub(super) fill_alpha: f32,
408    pub(super) gradient_color: Option<Arc<dyn Fn(PlotPoint) -> Color32 + Send + Sync>>,
409    pub(super) gradient_fill: bool,
410    pub(super) style: LineStyle,
411}
412
413impl<'a> Line<'a> {
414    pub fn new(name: impl Into<String>, series: impl Into<PlotPoints<'a>>) -> Self {
415        Self {
416            base: PlotItemBase::new(name.into()),
417            series: series.into(),
418            stroke: Stroke::new(1.5, Color32::TRANSPARENT), // Note: a stroke of 1.0 (or less) can look bad on low-dpi-screens
419            fill: None,
420            fill_alpha: DEFAULT_FILL_ALPHA,
421            gradient_color: None,
422            gradient_fill: false,
423            style: LineStyle::Solid,
424        }
425    }
426
427    /// Add a stroke.
428    #[inline]
429    pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
430        self.stroke = stroke.into();
431        self
432    }
433
434    /// Add an optional gradient color to the stroke using a callback. The callback
435    /// receives a `PlotPoint` as input with the current X and Y values and should
436    /// return a `Color32` to be used as the stroke color for that point.
437    ///
438    /// Setting the `gradient_fill` parameter to `true` will use the gradient
439    /// color callback for the fill area as well when `fill()` is set.
440    #[inline]
441    pub fn gradient_color(
442        mut self,
443        callback: Arc<dyn Fn(PlotPoint) -> Color32 + Send + Sync>,
444        gradient_fill: bool,
445    ) -> Self {
446        self.gradient_color = Some(callback);
447        self.gradient_fill = gradient_fill;
448        self
449    }
450
451    /// Stroke width. A high value means the plot thickens.
452    #[inline]
453    pub fn width(mut self, width: impl Into<f32>) -> Self {
454        self.stroke.width = width.into();
455        self
456    }
457
458    /// Stroke color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
459    #[inline]
460    pub fn color(mut self, color: impl Into<Color32>) -> Self {
461        self.stroke.color = color.into();
462        self
463    }
464
465    /// Fill the area between this line and a given horizontal reference line.
466    #[inline]
467    pub fn fill(mut self, y_reference: impl Into<f32>) -> Self {
468        self.fill = Some(y_reference.into());
469        self
470    }
471
472    /// Set the fill area's alpha channel. Default is `0.05`.
473    #[inline]
474    pub fn fill_alpha(mut self, alpha: impl Into<f32>) -> Self {
475        self.fill_alpha = alpha.into();
476        self
477    }
478
479    /// Set the line's style. Default is `LineStyle::Solid`.
480    #[inline]
481    pub fn style(mut self, style: LineStyle) -> Self {
482        self.style = style;
483        self
484    }
485
486    builder_methods_for_base!();
487}
488
489/// Returns the x-coordinate of a possible intersection between a line segment from `p1` to `p2` and
490/// a horizontal line at the given y-coordinate.
491fn y_intersection(p1: &Pos2, p2: &Pos2, y: f32) -> Option<f32> {
492    ((p1.y > y && p2.y < y) || (p1.y < y && p2.y > y))
493        .then_some(((y * (p1.x - p2.x)) - (p1.x * p2.y - p1.y * p2.x)) / (p1.y - p2.y))
494}
495
496impl PlotItem for Line<'_> {
497    fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
498        let Self {
499            base,
500            series,
501            stroke,
502            fill,
503            gradient_fill,
504            style,
505            ..
506        } = self;
507        let mut fill = *fill;
508
509        let mut final_stroke: PathStroke = (*stroke).into();
510        // if we have a gradient color, we need to wrap the stroke callback to transpose the position to a value
511        // the caller can reason about
512        if let Some(gradient_callback) = self.gradient_color.clone() {
513            let local_transform = *transform;
514            let wrapped_callback = move |_rec: Rect, pos: Pos2| -> Color32 {
515                let point = local_transform.value_from_position(pos);
516                gradient_callback(point)
517            };
518            final_stroke = PathStroke::new_uv(stroke.width, wrapped_callback.clone());
519        }
520
521        let values_tf: Vec<_> = series
522            .points()
523            .iter()
524            .map(|v| transform.position_from_point(v))
525            .collect();
526        let n_values = values_tf.len();
527
528        // Fill the area between the line and a reference line, if required.
529        if n_values < 2 {
530            fill = None;
531        }
532        if let Some(y_reference) = fill {
533            let mut fill_alpha = self.fill_alpha;
534            if base.highlight {
535                fill_alpha = (2.0 * fill_alpha).at_most(1.0);
536            }
537            let y = transform
538                .position_from_point(&PlotPoint::new(0.0, y_reference))
539                .y;
540            let default_fill_color = Rgba::from(stroke.color)
541                .to_opaque()
542                .multiply(fill_alpha)
543                .into();
544
545            let fill_color_for_point = |pos| {
546                if *gradient_fill && self.gradient_color.is_some() {
547                    Rgba::from(self
548                        .gradient_color
549                        .clone()
550                        .expect("Could not find gradient color callback")(
551                        transform.value_from_position(pos),
552                    ))
553                    .to_opaque()
554                    .multiply(fill_alpha)
555                    .into()
556                } else {
557                    default_fill_color
558                }
559            };
560
561            let mut mesh = Mesh::default();
562            let expected_intersections = 20;
563            mesh.reserve_triangles((n_values - 1) * 2);
564            mesh.reserve_vertices(n_values * 2 + expected_intersections);
565            values_tf.windows(2).for_each(|w| {
566                let fill_color = fill_color_for_point(w[0]);
567                let i = mesh.vertices.len() as u32;
568                mesh.colored_vertex(w[0], fill_color);
569                mesh.colored_vertex(pos2(w[0].x, y), fill_color);
570                if let Some(x) = y_intersection(&w[0], &w[1], y) {
571                    let point = pos2(x, y);
572                    mesh.colored_vertex(point, fill_color_for_point(point));
573                    mesh.add_triangle(i, i + 1, i + 2);
574                    mesh.add_triangle(i + 2, i + 3, i + 4);
575                } else {
576                    mesh.add_triangle(i, i + 1, i + 2);
577                    mesh.add_triangle(i + 1, i + 2, i + 3);
578                }
579            });
580            let last = values_tf[n_values - 1];
581            let fill_color = fill_color_for_point(last);
582            mesh.colored_vertex(last, fill_color);
583            mesh.colored_vertex(pos2(last.x, y), fill_color);
584            shapes.push(Shape::Mesh(std::sync::Arc::new(mesh)));
585        }
586        style.style_line(values_tf, final_stroke, base.highlight, shapes);
587    }
588
589    fn initialize(&mut self, x_range: RangeInclusive<f64>) {
590        self.series.generate_points(x_range);
591    }
592
593    fn color(&self) -> Color32 {
594        self.stroke.color
595    }
596
597    fn base(&self) -> &PlotItemBase {
598        &self.base
599    }
600
601    fn base_mut(&mut self) -> &mut PlotItemBase {
602        &mut self.base
603    }
604
605    fn geometry(&self) -> PlotGeometry<'_> {
606        PlotGeometry::Points(self.series.points())
607    }
608
609    fn bounds(&self) -> PlotBounds {
610        self.series.bounds()
611    }
612}
613
614/// A convex polygon.
615pub struct Polygon<'a> {
616    base: PlotItemBase,
617    pub(super) series: PlotPoints<'a>,
618    pub(super) stroke: Stroke,
619    pub(super) fill_color: Option<Color32>,
620    pub(super) style: LineStyle,
621}
622
623impl<'a> Polygon<'a> {
624    pub fn new(name: impl Into<String>, series: impl Into<PlotPoints<'a>>) -> Self {
625        Self {
626            base: PlotItemBase::new(name.into()),
627            series: series.into(),
628            stroke: Stroke::new(1.0, Color32::TRANSPARENT),
629            fill_color: None,
630            style: LineStyle::Solid,
631        }
632    }
633
634    /// Add a custom stroke.
635    #[inline]
636    pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
637        self.stroke = stroke.into();
638        self
639    }
640
641    /// Set the stroke width.
642    #[inline]
643    pub fn width(mut self, width: impl Into<f32>) -> Self {
644        self.stroke.width = width.into();
645        self
646    }
647
648    /// Fill color. Defaults to the stroke color with added transparency.
649    #[inline]
650    pub fn fill_color(mut self, color: impl Into<Color32>) -> Self {
651        self.fill_color = Some(color.into());
652        self
653    }
654
655    /// Set the outline's style. Default is `LineStyle::Solid`.
656    #[inline]
657    pub fn style(mut self, style: LineStyle) -> Self {
658        self.style = style;
659        self
660    }
661
662    builder_methods_for_base!();
663}
664
665impl PlotItem for Polygon<'_> {
666    fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
667        let Self {
668            base,
669            series,
670            stroke,
671            fill_color,
672            style,
673            ..
674        } = self;
675
676        let mut values_tf: Vec<_> = series
677            .points()
678            .iter()
679            .map(|v| transform.position_from_point(v))
680            .collect();
681
682        let fill_color = fill_color.unwrap_or(stroke.color.linear_multiply(DEFAULT_FILL_ALPHA));
683
684        let shape = Shape::convex_polygon(values_tf.clone(), fill_color, Stroke::NONE);
685        shapes.push(shape);
686
687        if let Some(first) = values_tf.first() {
688            values_tf.push(*first); // close the polygon
689        }
690
691        style.style_line(
692            values_tf,
693            PathStroke::new(stroke.width, stroke.color),
694            base.highlight,
695            shapes,
696        );
697    }
698
699    fn initialize(&mut self, x_range: RangeInclusive<f64>) {
700        self.series.generate_points(x_range);
701    }
702
703    fn color(&self) -> Color32 {
704        self.stroke.color
705    }
706
707    fn geometry(&self) -> PlotGeometry<'_> {
708        PlotGeometry::Points(self.series.points())
709    }
710
711    fn bounds(&self) -> PlotBounds {
712        self.series.bounds()
713    }
714
715    fn base(&self) -> &PlotItemBase {
716        &self.base
717    }
718
719    fn base_mut(&mut self) -> &mut PlotItemBase {
720        &mut self.base
721    }
722}
723
724/// Text inside the plot.
725#[derive(Clone)]
726pub struct Text {
727    base: PlotItemBase,
728    pub(super) text: WidgetText,
729    pub(super) position: PlotPoint,
730    pub(super) color: Color32,
731    pub(super) anchor: Align2,
732}
733
734impl Text {
735    pub fn new(name: impl Into<String>, position: PlotPoint, text: impl Into<WidgetText>) -> Self {
736        Self {
737            base: PlotItemBase::new(name.into()),
738            text: text.into(),
739            position,
740            color: Color32::TRANSPARENT,
741            anchor: Align2::CENTER_CENTER,
742        }
743    }
744
745    /// Text color.
746    #[inline]
747    pub fn color(mut self, color: impl Into<Color32>) -> Self {
748        self.color = color.into();
749        self
750    }
751
752    /// Anchor position of the text. Default is `Align2::CENTER_CENTER`.
753    #[inline]
754    pub fn anchor(mut self, anchor: Align2) -> Self {
755        self.anchor = anchor;
756        self
757    }
758
759    builder_methods_for_base!();
760}
761
762impl PlotItem for Text {
763    fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
764        let color = if self.color == Color32::TRANSPARENT {
765            ui.style().visuals.text_color()
766        } else {
767            self.color
768        };
769
770        let galley = self.text.clone().into_galley(
771            ui,
772            Some(egui::TextWrapMode::Extend),
773            f32::INFINITY,
774            TextStyle::Small,
775        );
776
777        let pos = transform.position_from_point(&self.position);
778        let rect = self.anchor.anchor_size(pos, galley.size());
779
780        shapes.push(TextShape::new(rect.min, galley, color).into());
781
782        if self.base.highlight {
783            shapes.push(Shape::rect_stroke(
784                rect.expand(1.0),
785                1.0,
786                Stroke::new(0.5, color),
787                egui::StrokeKind::Outside,
788            ));
789        }
790    }
791
792    fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
793
794    fn color(&self) -> Color32 {
795        self.color
796    }
797
798    fn geometry(&self) -> PlotGeometry<'_> {
799        PlotGeometry::None
800    }
801
802    fn bounds(&self) -> PlotBounds {
803        let mut bounds = PlotBounds::NOTHING;
804        bounds.extend_with(&self.position);
805        bounds
806    }
807
808    fn base(&self) -> &PlotItemBase {
809        &self.base
810    }
811
812    fn base_mut(&mut self) -> &mut PlotItemBase {
813        &mut self.base
814    }
815}
816
817/// A set of points.
818pub struct Points<'a> {
819    base: PlotItemBase,
820
821    pub(super) series: PlotPoints<'a>,
822
823    pub(super) shape: MarkerShape,
824
825    /// Color of the marker. `Color32::TRANSPARENT` means that it will be picked automatically.
826    pub(super) color: Color32,
827
828    /// Whether to fill the marker. Does not apply to all types.
829    pub(super) filled: bool,
830
831    /// The maximum extent of the marker from its center.
832    pub(super) radius: f32,
833
834    pub(super) stems: Option<f32>,
835}
836
837impl<'a> Points<'a> {
838    pub fn new(name: impl Into<String>, series: impl Into<PlotPoints<'a>>) -> Self {
839        Self {
840            base: PlotItemBase::new(name.into()),
841            series: series.into(),
842            shape: MarkerShape::Circle,
843            color: Color32::TRANSPARENT,
844            filled: true,
845            radius: 1.0,
846            stems: None,
847        }
848    }
849
850    /// Set the shape of the markers.
851    #[inline]
852    pub fn shape(mut self, shape: MarkerShape) -> Self {
853        self.shape = shape;
854        self
855    }
856
857    /// Set the marker's color.
858    #[inline]
859    pub fn color(mut self, color: impl Into<Color32>) -> Self {
860        self.color = color.into();
861        self
862    }
863
864    /// Whether to fill the marker.
865    #[inline]
866    pub fn filled(mut self, filled: bool) -> Self {
867        self.filled = filled;
868        self
869    }
870
871    /// Whether to add stems between the markers and a horizontal reference line.
872    #[inline]
873    pub fn stems(mut self, y_reference: impl Into<f32>) -> Self {
874        self.stems = Some(y_reference.into());
875        self
876    }
877
878    /// Set the maximum extent of the marker around its position, in ui points.
879    #[inline]
880    pub fn radius(mut self, radius: impl Into<f32>) -> Self {
881        self.radius = radius.into();
882        self
883    }
884
885    builder_methods_for_base!();
886}
887
888impl PlotItem for Points<'_> {
889    fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
890        let sqrt_3 = 3_f32.sqrt();
891        let frac_sqrt_3_2 = 3_f32.sqrt() / 2.0;
892        let frac_1_sqrt_2 = 1.0 / 2_f32.sqrt();
893
894        let Self {
895            base,
896            series,
897            shape,
898            color,
899            filled,
900            radius,
901            stems,
902            ..
903        } = self;
904
905        let mut radius = *radius;
906
907        let stroke_size = radius / 5.0;
908
909        let default_stroke = Stroke::new(stroke_size, *color);
910        let mut stem_stroke = default_stroke;
911        let (fill, stroke) = if *filled {
912            (*color, Stroke::NONE)
913        } else {
914            (Color32::TRANSPARENT, default_stroke)
915        };
916
917        if base.highlight {
918            radius *= 2f32.sqrt();
919            stem_stroke.width *= 2.0;
920        }
921
922        let y_reference = stems.map(|y| transform.position_from_point(&PlotPoint::new(0.0, y)).y);
923
924        series
925            .points()
926            .iter()
927            .map(|value| transform.position_from_point(value))
928            .for_each(|center| {
929                let tf = |dx: f32, dy: f32| -> Pos2 { center + radius * vec2(dx, dy) };
930
931                if let Some(y) = y_reference {
932                    let stem = Shape::line_segment([center, pos2(center.x, y)], stem_stroke);
933                    shapes.push(stem);
934                }
935
936                match shape {
937                    MarkerShape::Circle => {
938                        shapes.push(Shape::Circle(CircleShape {
939                            center,
940                            radius,
941                            fill,
942                            stroke,
943                        }));
944                    }
945                    MarkerShape::Diamond => {
946                        let points = vec![
947                            tf(0.0, 1.0),  // bottom
948                            tf(-1.0, 0.0), // left
949                            tf(0.0, -1.0), // top
950                            tf(1.0, 0.0),  // right
951                        ];
952                        shapes.push(Shape::convex_polygon(points, fill, stroke));
953                    }
954                    MarkerShape::Square => {
955                        let points = vec![
956                            tf(-frac_1_sqrt_2, frac_1_sqrt_2),
957                            tf(-frac_1_sqrt_2, -frac_1_sqrt_2),
958                            tf(frac_1_sqrt_2, -frac_1_sqrt_2),
959                            tf(frac_1_sqrt_2, frac_1_sqrt_2),
960                        ];
961                        shapes.push(Shape::convex_polygon(points, fill, stroke));
962                    }
963                    MarkerShape::Cross => {
964                        let diagonal1 = [
965                            tf(-frac_1_sqrt_2, -frac_1_sqrt_2),
966                            tf(frac_1_sqrt_2, frac_1_sqrt_2),
967                        ];
968                        let diagonal2 = [
969                            tf(frac_1_sqrt_2, -frac_1_sqrt_2),
970                            tf(-frac_1_sqrt_2, frac_1_sqrt_2),
971                        ];
972                        shapes.push(Shape::line_segment(diagonal1, default_stroke));
973                        shapes.push(Shape::line_segment(diagonal2, default_stroke));
974                    }
975                    MarkerShape::Plus => {
976                        let horizontal = [tf(-1.0, 0.0), tf(1.0, 0.0)];
977                        let vertical = [tf(0.0, -1.0), tf(0.0, 1.0)];
978                        shapes.push(Shape::line_segment(horizontal, default_stroke));
979                        shapes.push(Shape::line_segment(vertical, default_stroke));
980                    }
981                    MarkerShape::Up => {
982                        let points =
983                            vec![tf(0.0, -1.0), tf(0.5 * sqrt_3, 0.5), tf(-0.5 * sqrt_3, 0.5)];
984                        shapes.push(Shape::convex_polygon(points, fill, stroke));
985                    }
986                    MarkerShape::Down => {
987                        let points = vec![
988                            tf(0.0, 1.0),
989                            tf(-0.5 * sqrt_3, -0.5),
990                            tf(0.5 * sqrt_3, -0.5),
991                        ];
992                        shapes.push(Shape::convex_polygon(points, fill, stroke));
993                    }
994                    MarkerShape::Left => {
995                        let points =
996                            vec![tf(-1.0, 0.0), tf(0.5, -0.5 * sqrt_3), tf(0.5, 0.5 * sqrt_3)];
997                        shapes.push(Shape::convex_polygon(points, fill, stroke));
998                    }
999                    MarkerShape::Right => {
1000                        let points = vec![
1001                            tf(1.0, 0.0),
1002                            tf(-0.5, 0.5 * sqrt_3),
1003                            tf(-0.5, -0.5 * sqrt_3),
1004                        ];
1005                        shapes.push(Shape::convex_polygon(points, fill, stroke));
1006                    }
1007                    MarkerShape::Asterisk => {
1008                        let vertical = [tf(0.0, -1.0), tf(0.0, 1.0)];
1009                        let diagonal1 = [tf(-frac_sqrt_3_2, 0.5), tf(frac_sqrt_3_2, -0.5)];
1010                        let diagonal2 = [tf(-frac_sqrt_3_2, -0.5), tf(frac_sqrt_3_2, 0.5)];
1011                        shapes.push(Shape::line_segment(vertical, default_stroke));
1012                        shapes.push(Shape::line_segment(diagonal1, default_stroke));
1013                        shapes.push(Shape::line_segment(diagonal2, default_stroke));
1014                    }
1015                }
1016            });
1017    }
1018
1019    fn initialize(&mut self, x_range: RangeInclusive<f64>) {
1020        self.series.generate_points(x_range);
1021    }
1022
1023    fn color(&self) -> Color32 {
1024        self.color
1025    }
1026
1027    fn geometry(&self) -> PlotGeometry<'_> {
1028        PlotGeometry::Points(self.series.points())
1029    }
1030
1031    fn bounds(&self) -> PlotBounds {
1032        self.series.bounds()
1033    }
1034
1035    fn base(&self) -> &PlotItemBase {
1036        &self.base
1037    }
1038
1039    fn base_mut(&mut self) -> &mut PlotItemBase {
1040        &mut self.base
1041    }
1042}
1043
1044/// A set of arrows.
1045pub struct Arrows<'a> {
1046    base: PlotItemBase,
1047    pub(super) origins: PlotPoints<'a>,
1048    pub(super) tips: PlotPoints<'a>,
1049    pub(super) tip_length: Option<f32>,
1050    pub(super) color: Color32,
1051}
1052
1053impl<'a> Arrows<'a> {
1054    pub fn new(
1055        name: impl Into<String>,
1056        origins: impl Into<PlotPoints<'a>>,
1057        tips: impl Into<PlotPoints<'a>>,
1058    ) -> Self {
1059        Self {
1060            base: PlotItemBase::new(name.into()),
1061            origins: origins.into(),
1062            tips: tips.into(),
1063            tip_length: None,
1064            color: Color32::TRANSPARENT,
1065        }
1066    }
1067
1068    /// Set the length of the arrow tips
1069    #[inline]
1070    pub fn tip_length(mut self, tip_length: f32) -> Self {
1071        self.tip_length = Some(tip_length);
1072        self
1073    }
1074
1075    /// Set the arrows' color.
1076    #[inline]
1077    pub fn color(mut self, color: impl Into<Color32>) -> Self {
1078        self.color = color.into();
1079        self
1080    }
1081
1082    builder_methods_for_base!();
1083}
1084
1085impl PlotItem for Arrows<'_> {
1086    fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
1087        let Self {
1088            origins,
1089            tips,
1090            tip_length,
1091            color,
1092            base,
1093            ..
1094        } = self;
1095        let stroke = Stroke::new(if base.highlight { 2.0 } else { 1.0 }, *color);
1096        origins
1097            .points()
1098            .iter()
1099            .zip(tips.points().iter())
1100            .map(|(origin, tip)| {
1101                (
1102                    transform.position_from_point(origin),
1103                    transform.position_from_point(tip),
1104                )
1105            })
1106            .for_each(|(origin, tip)| {
1107                let vector = tip - origin;
1108                let rot = Rot2::from_angle(std::f32::consts::TAU / 10.0);
1109                let tip_length = if let Some(tip_length) = tip_length {
1110                    *tip_length
1111                } else {
1112                    vector.length() / 4.0
1113                };
1114                let tip = origin + vector;
1115                let dir = vector.normalized();
1116                shapes.push(Shape::line_segment([origin, tip], stroke));
1117                shapes.push(Shape::line(
1118                    vec![
1119                        tip - tip_length * (rot.inverse() * dir),
1120                        tip,
1121                        tip - tip_length * (rot * dir),
1122                    ],
1123                    stroke,
1124                ));
1125            });
1126    }
1127
1128    fn initialize(&mut self, _x_range: RangeInclusive<f64>) {
1129        self.origins
1130            .generate_points(f64::NEG_INFINITY..=f64::INFINITY);
1131        self.tips.generate_points(f64::NEG_INFINITY..=f64::INFINITY);
1132    }
1133
1134    fn color(&self) -> Color32 {
1135        self.color
1136    }
1137
1138    fn geometry(&self) -> PlotGeometry<'_> {
1139        PlotGeometry::Points(self.origins.points())
1140    }
1141
1142    fn bounds(&self) -> PlotBounds {
1143        self.origins.bounds()
1144    }
1145
1146    fn base(&self) -> &PlotItemBase {
1147        &self.base
1148    }
1149
1150    fn base_mut(&mut self) -> &mut PlotItemBase {
1151        &mut self.base
1152    }
1153}
1154
1155/// An image in the plot.
1156#[derive(Clone)]
1157pub struct PlotImage {
1158    base: PlotItemBase,
1159    pub(super) position: PlotPoint,
1160    pub(super) texture_id: TextureId,
1161    pub(super) uv: Rect,
1162    pub(super) size: Vec2,
1163    pub(crate) rotation: f64,
1164    pub(super) bg_fill: Color32,
1165    pub(super) tint: Color32,
1166}
1167
1168impl PlotImage {
1169    /// Create a new image with position and size in plot coordinates.
1170    pub fn new(
1171        name: impl Into<String>,
1172        texture_id: impl Into<TextureId>,
1173        center_position: PlotPoint,
1174        size: impl Into<Vec2>,
1175    ) -> Self {
1176        Self {
1177            base: PlotItemBase::new(name.into()),
1178            position: center_position,
1179            texture_id: texture_id.into(),
1180            uv: Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)),
1181            size: size.into(),
1182            rotation: 0.0,
1183            bg_fill: Default::default(),
1184            tint: Color32::WHITE,
1185        }
1186    }
1187
1188    /// Select UV range. Default is (0,0) in top-left, (1,1) bottom right.
1189    #[inline]
1190    pub fn uv(mut self, uv: impl Into<Rect>) -> Self {
1191        self.uv = uv.into();
1192        self
1193    }
1194
1195    /// A solid color to put behind the image. Useful for transparent images.
1196    #[inline]
1197    pub fn bg_fill(mut self, bg_fill: impl Into<Color32>) -> Self {
1198        self.bg_fill = bg_fill.into();
1199        self
1200    }
1201
1202    /// Multiply image color with this. Default is WHITE (no tint).
1203    #[inline]
1204    pub fn tint(mut self, tint: impl Into<Color32>) -> Self {
1205        self.tint = tint.into();
1206        self
1207    }
1208
1209    /// Rotate the image counter-clockwise around its center by an angle in radians.
1210    #[inline]
1211    pub fn rotate(mut self, angle: f64) -> Self {
1212        self.rotation = angle;
1213        self
1214    }
1215
1216    builder_methods_for_base!();
1217}
1218
1219impl PlotItem for PlotImage {
1220    fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
1221        let Self {
1222            position,
1223            rotation,
1224            texture_id,
1225            uv,
1226            size,
1227            bg_fill,
1228            tint,
1229            base,
1230            ..
1231        } = self;
1232        let image_screen_rect = {
1233            let left_top = PlotPoint::new(
1234                position.x - 0.5 * size.x as f64,
1235                position.y - 0.5 * size.y as f64,
1236            );
1237            let right_bottom = PlotPoint::new(
1238                position.x + 0.5 * size.x as f64,
1239                position.y + 0.5 * size.y as f64,
1240            );
1241            let left_top_screen = transform.position_from_point(&left_top);
1242            let right_bottom_screen = transform.position_from_point(&right_bottom);
1243            Rect::from_two_pos(left_top_screen, right_bottom_screen)
1244        };
1245        let screen_rotation = -*rotation as f32;
1246
1247        egui::paint_texture_at(
1248            ui.painter(),
1249            image_screen_rect,
1250            &ImageOptions {
1251                uv: *uv,
1252                bg_fill: *bg_fill,
1253                tint: *tint,
1254                rotation: Some((Rot2::from_angle(screen_rotation), Vec2::splat(0.5))),
1255                corner_radius: CornerRadius::ZERO,
1256            },
1257            &(*texture_id, image_screen_rect.size()).into(),
1258        );
1259        if base.highlight {
1260            let center = image_screen_rect.center();
1261            let rotation = Rot2::from_angle(screen_rotation);
1262            let outline = [
1263                image_screen_rect.right_bottom(),
1264                image_screen_rect.right_top(),
1265                image_screen_rect.left_top(),
1266                image_screen_rect.left_bottom(),
1267            ]
1268            .iter()
1269            .map(|point| center + rotation * (*point - center))
1270            .collect();
1271            shapes.push(Shape::closed_line(
1272                outline,
1273                Stroke::new(1.0, ui.visuals().strong_text_color()),
1274            ));
1275        }
1276    }
1277
1278    fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
1279
1280    fn color(&self) -> Color32 {
1281        Color32::TRANSPARENT
1282    }
1283
1284    fn geometry(&self) -> PlotGeometry<'_> {
1285        PlotGeometry::None
1286    }
1287
1288    fn bounds(&self) -> PlotBounds {
1289        let mut bounds = PlotBounds::NOTHING;
1290        let left_top = PlotPoint::new(
1291            self.position.x as f32 - self.size.x / 2.0,
1292            self.position.y as f32 - self.size.y / 2.0,
1293        );
1294        let right_bottom = PlotPoint::new(
1295            self.position.x as f32 + self.size.x / 2.0,
1296            self.position.y as f32 + self.size.y / 2.0,
1297        );
1298        bounds.extend_with(&left_top);
1299        bounds.extend_with(&right_bottom);
1300        bounds
1301    }
1302
1303    fn base(&self) -> &PlotItemBase {
1304        &self.base
1305    }
1306
1307    fn base_mut(&mut self) -> &mut PlotItemBase {
1308        &mut self.base
1309    }
1310}
1311
1312// ----------------------------------------------------------------------------
1313
1314/// A bar chart.
1315pub struct BarChart {
1316    base: PlotItemBase,
1317
1318    pub(super) bars: Vec<Bar>,
1319    default_color: Color32,
1320
1321    /// A custom element formatter
1322    pub(super) element_formatter: Option<Box<dyn Fn(&Bar, &BarChart) -> String>>,
1323}
1324
1325impl BarChart {
1326    /// Create a bar chart. It defaults to vertically oriented elements.
1327    pub fn new(name: impl Into<String>, bars: Vec<Bar>) -> Self {
1328        Self {
1329            base: PlotItemBase::new(name.into()),
1330            bars,
1331            default_color: Color32::TRANSPARENT,
1332            element_formatter: None,
1333        }
1334    }
1335
1336    /// Set the default color. It is set on all elements that do not already have a specific color.
1337    /// This is the color that shows up in the legend.
1338    /// It can be overridden at the bar level (see [[`Bar`]]).
1339    /// Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
1340    #[inline]
1341    pub fn color(mut self, color: impl Into<Color32>) -> Self {
1342        let plot_color = color.into();
1343        self.default_color = plot_color;
1344        for b in &mut self.bars {
1345            if b.fill == Color32::TRANSPARENT && b.stroke.color == Color32::TRANSPARENT {
1346                b.fill = plot_color.linear_multiply(0.2);
1347                b.stroke.color = plot_color;
1348            }
1349        }
1350        self
1351    }
1352
1353    /// Set all elements to be in a vertical orientation.
1354    /// Argument axis will be X and bar values will be on the Y axis.
1355    #[inline]
1356    pub fn vertical(mut self) -> Self {
1357        for b in &mut self.bars {
1358            b.orientation = Orientation::Vertical;
1359        }
1360        self
1361    }
1362
1363    /// Set all elements to be in a horizontal orientation.
1364    /// Argument axis will be Y and bar values will be on the X axis.
1365    #[inline]
1366    pub fn horizontal(mut self) -> Self {
1367        for b in &mut self.bars {
1368            b.orientation = Orientation::Horizontal;
1369        }
1370        self
1371    }
1372
1373    /// Set the width (thickness) of all its elements.
1374    #[inline]
1375    pub fn width(mut self, width: f64) -> Self {
1376        for b in &mut self.bars {
1377            b.bar_width = width;
1378        }
1379        self
1380    }
1381
1382    /// Add a custom way to format an element.
1383    /// Can be used to display a set number of decimals or custom labels.
1384    #[inline]
1385    pub fn element_formatter(mut self, formatter: Box<dyn Fn(&Bar, &Self) -> String>) -> Self {
1386        self.element_formatter = Some(formatter);
1387        self
1388    }
1389
1390    /// Stacks the bars on top of another chart.
1391    /// Positive values are stacked on top of other positive values.
1392    /// Negative values are stacked below other negative values.
1393    #[inline]
1394    pub fn stack_on(mut self, others: &[&Self]) -> Self {
1395        for (index, bar) in self.bars.iter_mut().enumerate() {
1396            let new_base_offset = if bar.value.is_sign_positive() {
1397                others
1398                    .iter()
1399                    .filter_map(|other_chart| other_chart.bars.get(index).map(|bar| bar.upper()))
1400                    .max_by_key(|value| value.ord())
1401            } else {
1402                others
1403                    .iter()
1404                    .filter_map(|other_chart| other_chart.bars.get(index).map(|bar| bar.lower()))
1405                    .min_by_key(|value| value.ord())
1406            };
1407
1408            if let Some(value) = new_base_offset {
1409                bar.base_offset = Some(value);
1410            }
1411        }
1412        self
1413    }
1414
1415    builder_methods_for_base!();
1416}
1417
1418impl PlotItem for BarChart {
1419    fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
1420        for b in &self.bars {
1421            b.add_shapes(transform, self.base.highlight, shapes);
1422        }
1423    }
1424
1425    fn initialize(&mut self, _x_range: RangeInclusive<f64>) {
1426        // nothing to do
1427    }
1428
1429    fn color(&self) -> Color32 {
1430        self.default_color
1431    }
1432
1433    fn geometry(&self) -> PlotGeometry<'_> {
1434        PlotGeometry::Rects
1435    }
1436
1437    fn bounds(&self) -> PlotBounds {
1438        let mut bounds = PlotBounds::NOTHING;
1439        for b in &self.bars {
1440            bounds.merge(&b.bounds());
1441        }
1442        bounds
1443    }
1444
1445    fn find_closest(&self, point: Pos2, transform: &PlotTransform) -> Option<ClosestElem> {
1446        find_closest_rect(&self.bars, point, transform)
1447    }
1448
1449    fn on_hover(
1450        &self,
1451        _plot_area_response: &egui::Response,
1452        elem: ClosestElem,
1453        shapes: &mut Vec<Shape>,
1454        cursors: &mut Vec<Cursor>,
1455        plot: &PlotConfig<'_>,
1456        _: &LabelFormatter<'_>,
1457    ) {
1458        let bar = &self.bars[elem.index];
1459
1460        bar.add_shapes(plot.transform, true, shapes);
1461        bar.add_rulers_and_text(self, plot, shapes, cursors);
1462    }
1463
1464    fn base(&self) -> &PlotItemBase {
1465        &self.base
1466    }
1467
1468    fn base_mut(&mut self) -> &mut PlotItemBase {
1469        &mut self.base
1470    }
1471}
1472
1473/// A diagram containing a series of [`BoxElem`] elements.
1474pub struct BoxPlot {
1475    base: PlotItemBase,
1476
1477    pub(super) boxes: Vec<BoxElem>,
1478    default_color: Color32,
1479
1480    /// A custom element formatter
1481    pub(super) element_formatter: Option<Box<dyn Fn(&BoxElem, &BoxPlot) -> String>>,
1482}
1483
1484impl BoxPlot {
1485    /// Create a plot containing multiple `boxes`. It defaults to vertically oriented elements.
1486    pub fn new(name: impl Into<String>, boxes: Vec<BoxElem>) -> Self {
1487        Self {
1488            base: PlotItemBase::new(name.into()),
1489            boxes,
1490            default_color: Color32::TRANSPARENT,
1491            element_formatter: None,
1492        }
1493    }
1494
1495    /// Set the default color. It is set on all elements that do not already have a specific color.
1496    /// This is the color that shows up in the legend.
1497    /// It can be overridden at the element level (see [`BoxElem`]).
1498    /// Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
1499    #[inline]
1500    pub fn color(mut self, color: impl Into<Color32>) -> Self {
1501        let plot_color = color.into();
1502        self.default_color = plot_color;
1503        for box_elem in &mut self.boxes {
1504            if box_elem.fill == Color32::TRANSPARENT
1505                && box_elem.stroke.color == Color32::TRANSPARENT
1506            {
1507                box_elem.fill = plot_color.linear_multiply(0.2);
1508                box_elem.stroke.color = plot_color;
1509            }
1510        }
1511        self
1512    }
1513
1514    /// Set all elements to be in a vertical orientation.
1515    /// Argument axis will be X and values will be on the Y axis.
1516    #[inline]
1517    pub fn vertical(mut self) -> Self {
1518        for box_elem in &mut self.boxes {
1519            box_elem.orientation = Orientation::Vertical;
1520        }
1521        self
1522    }
1523
1524    /// Set all elements to be in a horizontal orientation.
1525    /// Argument axis will be Y and values will be on the X axis.
1526    #[inline]
1527    pub fn horizontal(mut self) -> Self {
1528        for box_elem in &mut self.boxes {
1529            box_elem.orientation = Orientation::Horizontal;
1530        }
1531        self
1532    }
1533
1534    /// Add a custom way to format an element.
1535    /// Can be used to display a set number of decimals or custom labels.
1536    #[inline]
1537    pub fn element_formatter(mut self, formatter: Box<dyn Fn(&BoxElem, &Self) -> String>) -> Self {
1538        self.element_formatter = Some(formatter);
1539        self
1540    }
1541
1542    builder_methods_for_base!();
1543}
1544
1545impl PlotItem for BoxPlot {
1546    fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
1547        for b in &self.boxes {
1548            b.add_shapes(transform, self.base.highlight, shapes);
1549        }
1550    }
1551
1552    fn initialize(&mut self, _x_range: RangeInclusive<f64>) {
1553        // nothing to do
1554    }
1555
1556    fn color(&self) -> Color32 {
1557        self.default_color
1558    }
1559
1560    fn geometry(&self) -> PlotGeometry<'_> {
1561        PlotGeometry::Rects
1562    }
1563
1564    fn bounds(&self) -> PlotBounds {
1565        let mut bounds = PlotBounds::NOTHING;
1566        for b in &self.boxes {
1567            bounds.merge(&b.bounds());
1568        }
1569        bounds
1570    }
1571
1572    fn find_closest(&self, point: Pos2, transform: &PlotTransform) -> Option<ClosestElem> {
1573        find_closest_rect(&self.boxes, point, transform)
1574    }
1575
1576    fn on_hover(
1577        &self,
1578        _plot_area_response: &egui::Response,
1579        elem: ClosestElem,
1580        shapes: &mut Vec<Shape>,
1581        cursors: &mut Vec<Cursor>,
1582        plot: &PlotConfig<'_>,
1583        _: &LabelFormatter<'_>,
1584    ) {
1585        let box_plot = &self.boxes[elem.index];
1586
1587        box_plot.add_shapes(plot.transform, true, shapes);
1588        box_plot.add_rulers_and_text(self, plot, shapes, cursors);
1589    }
1590
1591    fn base(&self) -> &PlotItemBase {
1592        &self.base
1593    }
1594
1595    fn base_mut(&mut self) -> &mut PlotItemBase {
1596        &mut self.base
1597    }
1598}
1599
1600// ----------------------------------------------------------------------------
1601// Helper functions
1602
1603pub(crate) fn rulers_color(ui: &Ui) -> Color32 {
1604    if ui.visuals().dark_mode {
1605        Color32::from_gray(100).additive()
1606    } else {
1607        Color32::from_black_alpha(180)
1608    }
1609}
1610
1611pub(crate) fn vertical_line(
1612    pointer: Pos2,
1613    transform: &PlotTransform,
1614    line_color: Color32,
1615) -> Shape {
1616    let frame = transform.frame();
1617    Shape::line_segment(
1618        [
1619            pos2(pointer.x, frame.top()),
1620            pos2(pointer.x, frame.bottom()),
1621        ],
1622        (1.0, line_color),
1623    )
1624}
1625
1626pub(crate) fn horizontal_line(
1627    pointer: Pos2,
1628    transform: &PlotTransform,
1629    line_color: Color32,
1630) -> Shape {
1631    let frame = transform.frame();
1632    Shape::line_segment(
1633        [
1634            pos2(frame.left(), pointer.y),
1635            pos2(frame.right(), pointer.y),
1636        ],
1637        (1.0, line_color),
1638    )
1639}
1640
1641fn add_rulers_and_text(
1642    elem: &dyn RectElement,
1643    plot: &PlotConfig<'_>,
1644    text: Option<String>,
1645    shapes: &mut Vec<Shape>,
1646    cursors: &mut Vec<Cursor>,
1647) {
1648    let orientation = elem.orientation();
1649    let show_argument = plot.show_x && orientation == Orientation::Vertical
1650        || plot.show_y && orientation == Orientation::Horizontal;
1651    let show_values = plot.show_y && orientation == Orientation::Vertical
1652        || plot.show_x && orientation == Orientation::Horizontal;
1653
1654    // Rulers for argument (usually vertical)
1655    if show_argument {
1656        for pos in elem.arguments_with_ruler() {
1657            cursors.push(match orientation {
1658                Orientation::Horizontal => Cursor::Horizontal { y: pos.y },
1659                Orientation::Vertical => Cursor::Vertical { x: pos.x },
1660            });
1661        }
1662    }
1663
1664    // Rulers for values (usually horizontal)
1665    if show_values {
1666        for pos in elem.values_with_ruler() {
1667            cursors.push(match orientation {
1668                Orientation::Horizontal => Cursor::Vertical { x: pos.x },
1669                Orientation::Vertical => Cursor::Horizontal { y: pos.y },
1670            });
1671        }
1672    }
1673
1674    // Text
1675    let text = text.unwrap_or({
1676        let mut text = elem.name().to_owned(); // could be empty
1677
1678        if show_values {
1679            text.push('\n');
1680            text.push_str(&elem.default_values_format(plot.transform));
1681        }
1682
1683        text
1684    });
1685
1686    let font_id = TextStyle::Body.resolve(plot.ui.style());
1687
1688    let corner_value = elem.corner_value();
1689    plot.ui.fonts_mut(|f| {
1690        shapes.push(Shape::text(
1691            f,
1692            plot.transform.position_from_point(&corner_value) + vec2(3.0, -2.0),
1693            Align2::LEFT_BOTTOM,
1694            text,
1695            font_id,
1696            plot.ui.visuals().text_color(),
1697        ));
1698    });
1699}
1700
1701/// Draws a cross of horizontal and vertical ruler at the `pointer` position,
1702/// and a label describing the coordinate.
1703///
1704/// `value` is used to for text displaying X/Y coordinates.
1705pub(super) fn rulers_and_tooltip_at_value(
1706    plot_area_response: &egui::Response,
1707    value: PlotPoint,
1708    name: &str,
1709    plot: &PlotConfig<'_>,
1710    cursors: &mut Vec<Cursor>,
1711    label_formatter: &LabelFormatter<'_>,
1712) {
1713    if plot.show_x {
1714        cursors.push(Cursor::Vertical { x: value.x });
1715    }
1716    if plot.show_y {
1717        cursors.push(Cursor::Horizontal { y: value.y });
1718    }
1719
1720    let text = if let Some(custom_label) = label_formatter {
1721        custom_label(name, &value)
1722    } else {
1723        let prefix = if name.is_empty() {
1724            String::new()
1725        } else {
1726            format!("{name}\n")
1727        };
1728        let scale = plot.transform.dvalue_dpos();
1729        let x_decimals = ((-scale[0].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6);
1730        let y_decimals = ((-scale[1].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6);
1731        if plot.show_x && plot.show_y {
1732            format!(
1733                "{}x = {:.*}\ny = {:.*}",
1734                prefix, x_decimals, value.x, y_decimals, value.y
1735            )
1736        } else if plot.show_x {
1737            format!("{}x = {:.*}", prefix, x_decimals, value.x)
1738        } else if plot.show_y {
1739            format!("{}y = {:.*}", prefix, y_decimals, value.y)
1740        } else {
1741            unreachable!()
1742        }
1743    };
1744
1745    // We show the tooltip as soon as we're hovering the plot area:
1746    let mut tooltip = egui::Tooltip::always_open(
1747        plot_area_response.ctx.clone(),
1748        plot_area_response.layer_id,
1749        plot_area_response.id,
1750        PopupAnchor::Pointer,
1751    );
1752
1753    let tooltip_width = plot_area_response.ctx.style().spacing.tooltip_width;
1754
1755    tooltip.popup = tooltip.popup.width(tooltip_width);
1756
1757    tooltip.gap(12.0).show(|ui| {
1758        ui.set_max_width(tooltip_width);
1759        ui.label(text);
1760    });
1761}
1762
1763fn find_closest_rect<'a, T>(
1764    rects: impl IntoIterator<Item = &'a T>,
1765    point: Pos2,
1766    transform: &PlotTransform,
1767) -> Option<ClosestElem>
1768where
1769    T: 'a + RectElement,
1770{
1771    rects
1772        .into_iter()
1773        .enumerate()
1774        .map(|(index, bar)| {
1775            let bar_rect = transform.rect_from_values(&bar.bounds_min(), &bar.bounds_max());
1776            let dist_sq = bar_rect.distance_sq_to_pos(point);
1777
1778            ClosestElem { index, dist_sq }
1779        })
1780        .min_by_key(|e| e.dist_sq.ord())
1781}