egui_plot/
lib.rs

1//! Simple plotting library for [`egui`](https://github.com/emilk/egui).
2//!
3//! Check out [`Plot`] for how to get started.
4//!
5//! [**Looking for maintainer!**](https://github.com/emilk/egui/issues/4705)
6//!
7//! ## Feature flags
8#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
9//!
10
11mod axis;
12mod items;
13mod legend;
14mod memory;
15mod plot_ui;
16mod transform;
17
18use std::{cmp::Ordering, ops::RangeInclusive, sync::Arc};
19
20use ahash::HashMap;
21use egui::{
22    epaint, remap_clamp, vec2, Align2, Color32, CursorIcon, Id, Layout, NumExt, PointerButton,
23    Pos2, Rangef, Rect, Response, Sense, Shape, Stroke, TextStyle, Ui, Vec2, Vec2b, WidgetText,
24};
25use emath::Float as _;
26
27pub use crate::{
28    axis::{Axis, AxisHints, HPlacement, Placement, VPlacement},
29    items::{
30        Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, ClosestElem, HLine, Line, LineStyle,
31        MarkerShape, Orientation, PlotConfig, PlotGeometry, PlotImage, PlotItem, PlotPoint,
32        PlotPoints, Points, Polygon, Text, VLine,
33    },
34    legend::{ColorConflictHandling, Corner, Legend},
35    memory::PlotMemory,
36    plot_ui::PlotUi,
37    transform::{PlotBounds, PlotTransform},
38};
39
40use axis::AxisWidget;
41use items::{horizontal_line, rulers_color, vertical_line};
42use legend::LegendWidget;
43
44type LabelFormatterFn<'a> = dyn Fn(&str, &PlotPoint) -> String + 'a;
45pub type LabelFormatter<'a> = Option<Box<LabelFormatterFn<'a>>>;
46
47type GridSpacerFn<'a> = dyn Fn(GridInput) -> Vec<GridMark> + 'a;
48type GridSpacer<'a> = Box<GridSpacerFn<'a>>;
49
50type CoordinatesFormatterFn<'a> = dyn Fn(&PlotPoint, &PlotBounds) -> String + 'a;
51
52/// Specifies the coordinates formatting when passed to [`Plot::coordinates_formatter`].
53pub struct CoordinatesFormatter<'a> {
54    function: Box<CoordinatesFormatterFn<'a>>,
55}
56
57impl<'a> CoordinatesFormatter<'a> {
58    /// Create a new formatter based on the pointer coordinate and the plot bounds.
59    pub fn new(function: impl Fn(&PlotPoint, &PlotBounds) -> String + 'a) -> Self {
60        Self {
61            function: Box::new(function),
62        }
63    }
64
65    /// Show a fixed number of decimal places.
66    pub fn with_decimals(num_decimals: usize) -> Self {
67        Self {
68            function: Box::new(move |value, _| {
69                format!("x: {:.d$}\ny: {:.d$}", value.x, value.y, d = num_decimals)
70            }),
71        }
72    }
73
74    fn format(&self, value: &PlotPoint, bounds: &PlotBounds) -> String {
75        (self.function)(value, bounds)
76    }
77}
78
79impl Default for CoordinatesFormatter<'_> {
80    fn default() -> Self {
81        Self::with_decimals(3)
82    }
83}
84
85// ----------------------------------------------------------------------------
86
87/// Indicates a vertical or horizontal cursor line in plot coordinates.
88#[derive(Copy, Clone, PartialEq)]
89pub enum Cursor {
90    Horizontal { y: f64 },
91    Vertical { x: f64 },
92}
93
94/// Contains the cursors drawn for a plot widget in a single frame.
95#[derive(PartialEq, Clone)]
96struct PlotFrameCursors {
97    id: Id,
98    cursors: Vec<Cursor>,
99}
100
101#[derive(Default, Clone)]
102struct CursorLinkGroups(HashMap<Id, Vec<PlotFrameCursors>>);
103
104#[derive(Clone)]
105struct LinkedBounds {
106    bounds: PlotBounds,
107    auto_bounds: Vec2b,
108}
109
110#[derive(Default, Clone)]
111struct BoundsLinkGroups(HashMap<Id, LinkedBounds>);
112
113// ----------------------------------------------------------------------------
114
115/// What [`Plot::show`] returns.
116pub struct PlotResponse<R> {
117    /// What the user closure returned.
118    pub inner: R,
119
120    /// The response of the plot.
121    pub response: Response,
122
123    /// The transform between screen coordinates and plot coordinates.
124    pub transform: PlotTransform,
125
126    /// The id of a currently hovered item if any.
127    ///
128    /// This is `None` if either no item was hovered.
129    /// A plot item can be hovered either by hovering its representation in the plot (line, marker, etc.)
130    /// or by hovering the item in the legend.
131    pub hovered_plot_item: Option<Id>,
132}
133
134// ----------------------------------------------------------------------------
135
136/// A 2D plot, e.g. a graph of a function.
137///
138/// [`Plot`] supports multiple lines and points.
139///
140/// ```
141/// # egui::__run_test_ui(|ui| {
142/// use egui_plot::{Line, Plot, PlotPoints};
143///
144/// let sin: PlotPoints = (0..1000).map(|i| {
145///     let x = i as f64 * 0.01;
146///     [x, x.sin()]
147/// }).collect();
148/// let line = Line::new("sin", sin);
149/// Plot::new("my_plot").view_aspect(2.0).show(ui, |plot_ui| plot_ui.line(line));
150/// # });
151/// ```
152pub struct Plot<'a> {
153    id_source: Id,
154    id: Option<Id>,
155
156    center_axis: Vec2b,
157    allow_zoom: Vec2b,
158    allow_drag: Vec2b,
159    allow_scroll: Vec2b,
160    allow_double_click_reset: bool,
161    allow_boxed_zoom: bool,
162    default_auto_bounds: Vec2b,
163    min_auto_bounds: PlotBounds,
164    margin_fraction: Vec2,
165    boxed_zoom_pointer_button: PointerButton,
166    linked_axes: Option<(Id, Vec2b)>,
167    linked_cursors: Option<(Id, Vec2b)>,
168
169    min_size: Vec2,
170    width: Option<f32>,
171    height: Option<f32>,
172    data_aspect: Option<f32>,
173    view_aspect: Option<f32>,
174
175    reset: bool,
176
177    show_x: bool,
178    show_y: bool,
179    label_formatter: LabelFormatter<'a>,
180    coordinates_formatter: Option<(Corner, CoordinatesFormatter<'a>)>,
181    x_axes: Vec<AxisHints<'a>>, // default x axes
182    y_axes: Vec<AxisHints<'a>>, // default y axes
183    legend_config: Option<Legend>,
184    cursor_color: Option<Color32>,
185    show_background: bool,
186    show_axes: Vec2b,
187
188    show_grid: Vec2b,
189    grid_spacing: Rangef,
190    grid_spacers: [GridSpacer<'a>; 2],
191    clamp_grid: bool,
192
193    sense: Sense,
194}
195
196impl<'a> Plot<'a> {
197    /// Give a unique id for each plot within the same [`Ui`].
198    pub fn new(id_source: impl std::hash::Hash) -> Self {
199        Self {
200            id_source: Id::new(id_source),
201            id: None,
202
203            center_axis: false.into(),
204            allow_zoom: true.into(),
205            allow_drag: true.into(),
206            allow_scroll: true.into(),
207            allow_double_click_reset: true,
208            allow_boxed_zoom: true,
209            default_auto_bounds: true.into(),
210            min_auto_bounds: PlotBounds::NOTHING,
211            margin_fraction: Vec2::splat(0.05),
212            boxed_zoom_pointer_button: PointerButton::Secondary,
213            linked_axes: None,
214            linked_cursors: None,
215
216            min_size: Vec2::splat(64.0),
217            width: None,
218            height: None,
219            data_aspect: None,
220            view_aspect: None,
221
222            reset: false,
223
224            show_x: true,
225            show_y: true,
226            label_formatter: None,
227            coordinates_formatter: None,
228            x_axes: vec![AxisHints::new(Axis::X)],
229            y_axes: vec![AxisHints::new(Axis::Y)],
230            legend_config: None,
231            cursor_color: None,
232            show_background: true,
233            show_axes: true.into(),
234
235            show_grid: true.into(),
236            grid_spacing: Rangef::new(8.0, 300.0),
237            grid_spacers: [log_grid_spacer(10), log_grid_spacer(10)],
238            clamp_grid: false,
239
240            sense: egui::Sense::click_and_drag(),
241        }
242    }
243
244    /// Set an explicit (global) id for the plot.
245    ///
246    /// This will override the id set by [`Self::new`].
247    ///
248    /// This is the same `Id` that can be used for [`PlotMemory::load`].
249    #[inline]
250    pub fn id(mut self, id: Id) -> Self {
251        self.id = Some(id);
252        self
253    }
254
255    /// width / height ratio of the data.
256    /// For instance, it can be useful to set this to `1.0` for when the two axes show the same
257    /// unit.
258    /// By default the plot window's aspect ratio is used.
259    #[inline]
260    pub fn data_aspect(mut self, data_aspect: f32) -> Self {
261        self.data_aspect = Some(data_aspect);
262        self
263    }
264
265    /// width / height ratio of the plot region.
266    /// By default no fixed aspect ratio is set (and width/height will fill the ui it is in).
267    #[inline]
268    pub fn view_aspect(mut self, view_aspect: f32) -> Self {
269        self.view_aspect = Some(view_aspect);
270        self
271    }
272
273    /// Width of plot. By default a plot will fill the ui it is in.
274    /// If you set [`Self::view_aspect`], the width can be calculated from the height.
275    #[inline]
276    pub fn width(mut self, width: f32) -> Self {
277        self.min_size.x = width;
278        self.width = Some(width);
279        self
280    }
281
282    /// Height of plot. By default a plot will fill the ui it is in.
283    /// If you set [`Self::view_aspect`], the height can be calculated from the width.
284    #[inline]
285    pub fn height(mut self, height: f32) -> Self {
286        self.min_size.y = height;
287        self.height = Some(height);
288        self
289    }
290
291    /// Minimum size of the plot view.
292    #[inline]
293    pub fn min_size(mut self, min_size: Vec2) -> Self {
294        self.min_size = min_size;
295        self
296    }
297
298    /// Show the x-value (e.g. when hovering). Default: `true`.
299    #[inline]
300    pub fn show_x(mut self, show_x: bool) -> Self {
301        self.show_x = show_x;
302        self
303    }
304
305    /// Show the y-value (e.g. when hovering). Default: `true`.
306    #[inline]
307    pub fn show_y(mut self, show_y: bool) -> Self {
308        self.show_y = show_y;
309        self
310    }
311
312    /// Always keep the X-axis centered. Default: `false`.
313    #[inline]
314    pub fn center_x_axis(mut self, on: bool) -> Self {
315        self.center_axis.x = on;
316        self
317    }
318
319    /// Always keep the Y-axis centered. Default: `false`.
320    #[inline]
321    pub fn center_y_axis(mut self, on: bool) -> Self {
322        self.center_axis.y = on;
323        self
324    }
325
326    /// Whether to allow zooming in the plot. Default: `true`.
327    ///
328    /// Note: Allowing zoom in one axis but not the other may lead to unexpected results if used in combination with `data_aspect`.
329    #[inline]
330    pub fn allow_zoom<T>(mut self, on: T) -> Self
331    where
332        T: Into<Vec2b>,
333    {
334        self.allow_zoom = on.into();
335        self
336    }
337
338    /// Whether to allow scrolling in the plot. Default: `true`.
339    #[inline]
340    pub fn allow_scroll<T>(mut self, on: T) -> Self
341    where
342        T: Into<Vec2b>,
343    {
344        self.allow_scroll = on.into();
345        self
346    }
347
348    /// Whether to allow double clicking to reset the view.
349    /// Default: `true`.
350    #[inline]
351    pub fn allow_double_click_reset(mut self, on: bool) -> Self {
352        self.allow_double_click_reset = on;
353        self
354    }
355
356    /// Set the side margin as a fraction of the plot size. Only used for auto bounds.
357    ///
358    /// For instance, a value of `0.1` will add 10% space on both sides.
359    #[inline]
360    pub fn set_margin_fraction(mut self, margin_fraction: Vec2) -> Self {
361        self.margin_fraction = margin_fraction;
362        self
363    }
364
365    /// Whether to allow zooming in the plot by dragging out a box with the secondary mouse button.
366    ///
367    /// Default: `true`.
368    #[inline]
369    pub fn allow_boxed_zoom(mut self, on: bool) -> Self {
370        self.allow_boxed_zoom = on;
371        self
372    }
373
374    /// Config the button pointer to use for boxed zooming. Default: [`Secondary`](PointerButton::Secondary)
375    #[inline]
376    pub fn boxed_zoom_pointer_button(mut self, boxed_zoom_pointer_button: PointerButton) -> Self {
377        self.boxed_zoom_pointer_button = boxed_zoom_pointer_button;
378        self
379    }
380
381    /// Whether to allow dragging in the plot to move the bounds. Default: `true`.
382    #[inline]
383    pub fn allow_drag<T>(mut self, on: T) -> Self
384    where
385        T: Into<Vec2b>,
386    {
387        self.allow_drag = on.into();
388        self
389    }
390
391    /// Provide a function to customize the on-hover label for the x and y axis
392    ///
393    /// ```
394    /// # egui::__run_test_ui(|ui| {
395    /// use egui_plot::{Line, Plot, PlotPoints};
396    /// let sin: PlotPoints = (0..1000).map(|i| {
397    ///     let x = i as f64 * 0.01;
398    ///     [x, x.sin()]
399    /// }).collect();
400    /// let line = Line::new("sin", sin);
401    /// Plot::new("my_plot").view_aspect(2.0)
402    /// .label_formatter(|name, value| {
403    ///     if !name.is_empty() {
404    ///         format!("{}: {:.*}%", name, 1, value.y)
405    ///     } else {
406    ///         "".to_owned()
407    ///     }
408    /// })
409    /// .show(ui, |plot_ui| plot_ui.line(line));
410    /// # });
411    /// ```
412    pub fn label_formatter(
413        mut self,
414        label_formatter: impl Fn(&str, &PlotPoint) -> String + 'a,
415    ) -> Self {
416        self.label_formatter = Some(Box::new(label_formatter));
417        self
418    }
419
420    /// Show the pointer coordinates in the plot.
421    pub fn coordinates_formatter(
422        mut self,
423        position: Corner,
424        formatter: CoordinatesFormatter<'a>,
425    ) -> Self {
426        self.coordinates_formatter = Some((position, formatter));
427        self
428    }
429
430    /// Configure how the grid in the background is spaced apart along the X axis.
431    ///
432    /// Default is a log-10 grid, i.e. every plot unit is divided into 10 other units.
433    ///
434    /// The function has this signature:
435    /// ```ignore
436    /// fn step_sizes(input: GridInput) -> Vec<GridMark>;
437    /// ```
438    ///
439    /// This function should return all marks along the visible range of the X axis.
440    /// `step_size` also determines how thick/faint each line is drawn.
441    /// For example, if x = 80..=230 is visible and you want big marks at steps of
442    /// 100 and small ones at 25, you can return:
443    /// ```no_run
444    /// # use egui_plot::GridMark;
445    /// vec![
446    ///    // 100s
447    ///    GridMark { value: 100.0, step_size: 100.0 },
448    ///    GridMark { value: 200.0, step_size: 100.0 },
449    ///
450    ///    // 25s
451    ///    GridMark { value: 125.0, step_size: 25.0 },
452    ///    GridMark { value: 150.0, step_size: 25.0 },
453    ///    GridMark { value: 175.0, step_size: 25.0 },
454    ///    GridMark { value: 225.0, step_size: 25.0 },
455    /// ];
456    /// # ()
457    /// ```
458    ///
459    /// There are helpers for common cases, see [`log_grid_spacer`] and [`uniform_grid_spacer`].
460    #[inline]
461    pub fn x_grid_spacer(mut self, spacer: impl Fn(GridInput) -> Vec<GridMark> + 'a) -> Self {
462        self.grid_spacers[0] = Box::new(spacer);
463        self
464    }
465
466    /// Default is a log-10 grid, i.e. every plot unit is divided into 10 other units.
467    ///
468    /// See [`Self::x_grid_spacer`] for explanation.
469    #[inline]
470    pub fn y_grid_spacer(mut self, spacer: impl Fn(GridInput) -> Vec<GridMark> + 'a) -> Self {
471        self.grid_spacers[1] = Box::new(spacer);
472        self
473    }
474
475    /// Set when the grid starts showing.
476    ///
477    /// When grid lines are closer than the given minimum, they will be hidden.
478    /// When they get further apart they will fade in, until the reaches the given maximum,
479    /// at which point they are fully opaque.
480    #[inline]
481    pub fn grid_spacing(mut self, grid_spacing: impl Into<Rangef>) -> Self {
482        self.grid_spacing = grid_spacing.into();
483        self
484    }
485
486    /// Clamp the grid to only be visible at the range of data where we have values.
487    ///
488    /// Default: `false`.
489    #[inline]
490    pub fn clamp_grid(mut self, clamp_grid: bool) -> Self {
491        self.clamp_grid = clamp_grid;
492        self
493    }
494
495    /// Set the sense for the plot rect.
496    ///
497    /// Default: `Sense::click_and_drag()`.
498    #[inline]
499    pub fn sense(mut self, sense: Sense) -> Self {
500        self.sense = sense;
501        self
502    }
503
504    /// Overwrite the starting and reset bounds used for the x axis.
505    /// Set the `default_auto_bounds` of the x axis to `false`.
506    ///
507    /// Panics in debug builds if `min >= max`.
508    #[inline]
509    pub fn default_x_bounds(mut self, min: f64, max: f64) -> Self {
510        debug_assert!(
511            min < max,
512            "`min` must be less than `max` in `default_x_bounds`"
513        );
514        self.default_auto_bounds.x = false;
515        self.min_auto_bounds.min[0] = min;
516        self.min_auto_bounds.max[0] = max;
517        self
518    }
519
520    /// Overwrite the starting and reset bounds used for the y axis.
521    /// Set the `default_auto_bounds` of the y axis to `false`.
522    ///
523    /// Panics in debug builds if `min >= max`.
524    #[inline]
525    pub fn default_y_bounds(mut self, min: f64, max: f64) -> Self {
526        debug_assert!(
527            min < max,
528            "`min` must be less than `max` in `default_y_bounds`"
529        );
530        self.default_auto_bounds.y = false;
531        self.min_auto_bounds.min[1] = min;
532        self.min_auto_bounds.max[1] = max;
533        self
534    }
535
536    /// Expand bounds to include the given x value.
537    /// For instance, to always show the y axis, call `plot.include_x(0.0)`.
538    #[inline]
539    pub fn include_x(mut self, x: impl Into<f64>) -> Self {
540        self.min_auto_bounds.extend_with_x(x.into());
541        self
542    }
543
544    /// Expand bounds to include the given y value.
545    /// For instance, to always show the x axis, call `plot.include_y(0.0)`.
546    #[inline]
547    pub fn include_y(mut self, y: impl Into<f64>) -> Self {
548        self.min_auto_bounds.extend_with_y(y.into());
549        self
550    }
551
552    /// Set whether the bounds should be automatically set based on data by default.
553    ///
554    /// This is enabled by default.
555    #[inline]
556    pub fn auto_bounds(mut self, auto_bounds: impl Into<Vec2b>) -> Self {
557        self.default_auto_bounds = auto_bounds.into();
558        self
559    }
560
561    /// Expand bounds to fit all items across the x axis, including values given by `include_x`.
562    #[deprecated = "Use `auto_bounds` instead"]
563    #[inline]
564    pub fn auto_bounds_x(mut self) -> Self {
565        self.default_auto_bounds.x = true;
566        self
567    }
568
569    /// Expand bounds to fit all items across the y axis, including values given by `include_y`.
570    #[deprecated = "Use `auto_bounds` instead"]
571    #[inline]
572    pub fn auto_bounds_y(mut self) -> Self {
573        self.default_auto_bounds.y = true;
574        self
575    }
576
577    /// Show a legend including all named items.
578    #[inline]
579    pub fn legend(mut self, legend: Legend) -> Self {
580        self.legend_config = Some(legend);
581        self
582    }
583
584    /// Whether or not to show the background [`Rect`].
585    ///
586    /// Can be useful to disable if the plot is overlaid over existing content.
587    /// Default: `true`.
588    #[inline]
589    pub fn show_background(mut self, show: bool) -> Self {
590        self.show_background = show;
591        self
592    }
593
594    /// Show axis labels and grid tick values on the side of the plot.
595    ///
596    /// Default: `true`.
597    #[inline]
598    pub fn show_axes(mut self, show: impl Into<Vec2b>) -> Self {
599        self.show_axes = show.into();
600        self
601    }
602
603    /// Show a grid overlay on the plot.
604    ///
605    /// Default: `true`.
606    #[inline]
607    pub fn show_grid(mut self, show: impl Into<Vec2b>) -> Self {
608        self.show_grid = show.into();
609        self
610    }
611
612    /// Add this plot to an axis link group so that this plot will share the bounds with other plots in the
613    /// same group. A plot cannot belong to more than one axis group.
614    #[inline]
615    pub fn link_axis(mut self, group_id: impl Into<Id>, link: impl Into<Vec2b>) -> Self {
616        self.linked_axes = Some((group_id.into(), link.into()));
617        self
618    }
619
620    /// Add this plot to a cursor link group so that this plot will share the cursor position with other plots
621    /// in the same group. A plot cannot belong to more than one cursor group.
622    #[inline]
623    pub fn link_cursor(mut self, group_id: impl Into<Id>, link: impl Into<Vec2b>) -> Self {
624        self.linked_cursors = Some((group_id.into(), link.into()));
625        self
626    }
627
628    /// Round grid positions to full pixels to avoid aliasing. Improves plot appearance but might have an
629    /// undesired effect when shifting the plot bounds. Enabled by default.
630    #[inline]
631    #[deprecated = "This no longer has any effect and is always enabled."]
632    pub fn sharp_grid_lines(self, _enabled: bool) -> Self {
633        self
634    }
635
636    /// Resets the plot.
637    #[inline]
638    pub fn reset(mut self) -> Self {
639        self.reset = true;
640        self
641    }
642
643    /// Set the x axis label of the main X-axis.
644    ///
645    /// Default: no label.
646    #[inline]
647    pub fn x_axis_label(mut self, label: impl Into<WidgetText>) -> Self {
648        if let Some(main) = self.x_axes.first_mut() {
649            main.label = label.into();
650        }
651        self
652    }
653
654    /// Set the y axis label of the main Y-axis.
655    ///
656    /// Default: no label.
657    #[inline]
658    pub fn y_axis_label(mut self, label: impl Into<WidgetText>) -> Self {
659        if let Some(main) = self.y_axes.first_mut() {
660            main.label = label.into();
661        }
662        self
663    }
664
665    /// Set the position of the main X-axis.
666    #[inline]
667    pub fn x_axis_position(mut self, placement: axis::VPlacement) -> Self {
668        if let Some(main) = self.x_axes.first_mut() {
669            main.placement = placement.into();
670        }
671        self
672    }
673
674    /// Set the position of the main Y-axis.
675    #[inline]
676    pub fn y_axis_position(mut self, placement: axis::HPlacement) -> Self {
677        if let Some(main) = self.y_axes.first_mut() {
678            main.placement = placement.into();
679        }
680        self
681    }
682
683    /// Specify custom formatter for ticks on the main X-axis.
684    ///
685    /// Arguments of `fmt`:
686    /// * the grid mark to format
687    /// * currently shown range on this axis.
688    pub fn x_axis_formatter(
689        mut self,
690        fmt: impl Fn(GridMark, &RangeInclusive<f64>) -> String + 'a,
691    ) -> Self {
692        if let Some(main) = self.x_axes.first_mut() {
693            main.formatter = Arc::new(fmt);
694        }
695        self
696    }
697
698    /// Specify custom formatter for ticks on the main Y-axis.
699    ///
700    /// Arguments of `fmt`:
701    /// * the grid mark to format
702    /// * currently shown range on this axis.
703    pub fn y_axis_formatter(
704        mut self,
705        fmt: impl Fn(GridMark, &RangeInclusive<f64>) -> String + 'a,
706    ) -> Self {
707        if let Some(main) = self.y_axes.first_mut() {
708            main.formatter = Arc::new(fmt);
709        }
710        self
711    }
712
713    /// Set the minimum width of the main y-axis, in ui points.
714    ///
715    /// The width will automatically expand if any tickmark text is wider than this.
716    #[inline]
717    pub fn y_axis_min_width(mut self, min_width: f32) -> Self {
718        if let Some(main) = self.y_axes.first_mut() {
719            main.min_thickness = min_width;
720        }
721        self
722    }
723
724    /// Set the main Y-axis-width by number of digits
725    #[inline]
726    #[deprecated = "Use `y_axis_min_width` instead"]
727    pub fn y_axis_width(self, digits: usize) -> Self {
728        self.y_axis_min_width(12.0 * digits as f32)
729    }
730
731    /// Set custom configuration for X-axis
732    ///
733    /// More than one axis may be specified. The first specified axis is considered the main axis.
734    #[inline]
735    pub fn custom_x_axes(mut self, hints: Vec<AxisHints<'a>>) -> Self {
736        self.x_axes = hints;
737        self
738    }
739
740    /// Set custom configuration for left Y-axis
741    ///
742    /// More than one axis may be specified. The first specified axis is considered the main axis.
743    #[inline]
744    pub fn custom_y_axes(mut self, hints: Vec<AxisHints<'a>>) -> Self {
745        self.y_axes = hints;
746        self
747    }
748
749    /// Set custom cursor color.
750    ///
751    /// You may set the color to [`Color32::TRANSPARENT`] to hide the cursors.
752    #[inline]
753    pub fn cursor_color(mut self, color: Color32) -> Self {
754        self.cursor_color = Some(color);
755        self
756    }
757
758    /// Interact with and add items to the plot and finally draw it.
759    pub fn show<'b, R>(
760        self,
761        ui: &mut Ui,
762        build_fn: impl FnOnce(&mut PlotUi<'b>) -> R + 'a,
763    ) -> PlotResponse<R> {
764        self.show_dyn(ui, Box::new(build_fn))
765    }
766
767    #[allow(clippy::too_many_lines)] // TODO(emilk): shorten this function
768    #[allow(clippy::type_complexity)] // build_fn
769    fn show_dyn<'b, R>(
770        self,
771        ui: &mut Ui,
772        build_fn: Box<dyn FnOnce(&mut PlotUi<'b>) -> R + 'a>,
773    ) -> PlotResponse<R> {
774        let Self {
775            id_source,
776            id,
777            center_axis,
778            allow_zoom,
779            allow_drag,
780            allow_scroll,
781            allow_double_click_reset,
782            allow_boxed_zoom,
783            boxed_zoom_pointer_button,
784            default_auto_bounds,
785            min_auto_bounds,
786            margin_fraction,
787            width,
788            height,
789            mut min_size,
790            data_aspect,
791            view_aspect,
792            mut show_x,
793            mut show_y,
794            label_formatter,
795            coordinates_formatter,
796            x_axes,
797            y_axes,
798            legend_config,
799            cursor_color,
800            reset,
801            show_background,
802            show_axes,
803            show_grid,
804            grid_spacing,
805            linked_axes,
806            linked_cursors,
807
808            clamp_grid,
809            grid_spacers,
810            sense,
811        } = self;
812
813        // Disable interaction if ui is disabled.
814        let allow_zoom = allow_zoom.and(ui.is_enabled());
815        let allow_drag = allow_drag.and(ui.is_enabled());
816        let allow_scroll = allow_scroll.and(ui.is_enabled());
817
818        // Determine position of widget.
819        let pos = ui.available_rect_before_wrap().min;
820        // Minimum values for screen protection
821        min_size.x = min_size.x.at_least(1.0);
822        min_size.y = min_size.y.at_least(1.0);
823
824        // Determine size of widget.
825        let size = {
826            let width = width
827                .unwrap_or_else(|| {
828                    if let (Some(height), Some(aspect)) = (height, view_aspect) {
829                        height * aspect
830                    } else {
831                        ui.available_size_before_wrap().x
832                    }
833                })
834                .at_least(min_size.x);
835
836            let height = height
837                .unwrap_or_else(|| {
838                    if let Some(aspect) = view_aspect {
839                        width / aspect
840                    } else {
841                        ui.available_size_before_wrap().y
842                    }
843                })
844                .at_least(min_size.y);
845            vec2(width, height)
846        };
847
848        // Determine complete rect of widget.
849        let complete_rect = Rect {
850            min: pos,
851            max: pos + size,
852        };
853
854        let plot_id = id.unwrap_or_else(|| ui.make_persistent_id(id_source));
855
856        let ([x_axis_widgets, y_axis_widgets], plot_rect) = axis_widgets(
857            PlotMemory::load(ui.ctx(), plot_id).as_ref(), // TODO(emilk): avoid loading plot memory twice
858            show_axes,
859            complete_rect,
860            [&x_axes, &y_axes],
861        );
862
863        // Allocate the plot window.
864        let response = ui.allocate_rect(plot_rect, sense);
865
866        // Load or initialize the memory.
867        ui.ctx().check_for_id_clash(plot_id, plot_rect, "Plot");
868
869        let mut mem = if reset {
870            if let Some((name, _)) = linked_axes.as_ref() {
871                ui.data_mut(|data| {
872                    let link_groups: &mut BoundsLinkGroups = data.get_temp_mut_or_default(Id::NULL);
873                    link_groups.0.remove(name);
874                });
875            };
876            None
877        } else {
878            PlotMemory::load(ui.ctx(), plot_id)
879        }
880        .unwrap_or_else(|| PlotMemory {
881            auto_bounds: default_auto_bounds,
882            hovered_legend_item: None,
883            hidden_items: Default::default(),
884            transform: PlotTransform::new(plot_rect, min_auto_bounds, center_axis),
885            last_click_pos_for_zoom: None,
886            x_axis_thickness: Default::default(),
887            y_axis_thickness: Default::default(),
888        });
889
890        let last_plot_transform = mem.transform;
891
892        // Call the plot build function.
893        let mut plot_ui = PlotUi {
894            ctx: ui.ctx().clone(),
895            items: Vec::new(),
896            next_auto_color_idx: 0,
897            last_plot_transform,
898            last_auto_bounds: mem.auto_bounds,
899            response,
900            bounds_modifications: Vec::new(),
901        };
902        let inner = build_fn(&mut plot_ui);
903        let PlotUi {
904            mut items,
905            mut response,
906            last_plot_transform,
907            bounds_modifications,
908            ..
909        } = plot_ui;
910
911        // Background
912        if show_background {
913            ui.painter()
914                .with_clip_rect(plot_rect)
915                .add(epaint::RectShape::new(
916                    plot_rect,
917                    2,
918                    ui.visuals().extreme_bg_color,
919                    ui.visuals().widgets.noninteractive.bg_stroke,
920                    egui::StrokeKind::Inside,
921                ));
922        }
923
924        // --- Legend ---
925        let legend = legend_config
926            .and_then(|config| LegendWidget::try_new(plot_rect, config, &items, &mem.hidden_items));
927        // Don't show hover cursor when hovering over legend.
928        if mem.hovered_legend_item.is_some() {
929            show_x = false;
930            show_y = false;
931        }
932        // Remove the deselected items.
933        items.retain(|item| !mem.hidden_items.contains(&item.id()));
934        // Highlight the hovered items.
935        if let Some(item_id) = &mem.hovered_legend_item {
936            items
937                .iter_mut()
938                .filter(|entry| &entry.id() == item_id)
939                .for_each(|entry| entry.highlight());
940        }
941        // Move highlighted items to front.
942        items.sort_by_key(|item| item.highlighted());
943
944        // --- Bound computation ---
945        let mut bounds = *last_plot_transform.bounds();
946
947        // Find the cursors from other plots we need to draw
948        let draw_cursors: Vec<Cursor> = if let Some((id, _)) = linked_cursors.as_ref() {
949            ui.data_mut(|data| {
950                let frames: &mut CursorLinkGroups = data.get_temp_mut_or_default(Id::NULL);
951                let cursors = frames.0.entry(*id).or_default();
952
953                // Look for our previous frame
954                let index = cursors
955                    .iter()
956                    .enumerate()
957                    .find(|(_, frame)| frame.id == plot_id)
958                    .map(|(i, _)| i);
959
960                // Remove our previous frame and all older frames as these are no longer displayed. This avoids
961                // unbounded growth, as we add an entry each time we draw a plot.
962                index.map(|index| cursors.drain(0..=index));
963
964                // Gather all cursors of the remaining frames. This will be all the cursors of the
965                // other plots in the group. We want to draw these in the current plot too.
966                cursors
967                    .iter()
968                    .flat_map(|frame| frame.cursors.iter().copied())
969                    .collect()
970            })
971        } else {
972            Vec::new()
973        };
974
975        // Transfer the bounds from a link group.
976        if let Some((id, axes)) = linked_axes.as_ref() {
977            ui.data_mut(|data| {
978                let link_groups: &mut BoundsLinkGroups = data.get_temp_mut_or_default(Id::NULL);
979                if let Some(linked_bounds) = link_groups.0.get(id) {
980                    if axes.x {
981                        bounds.set_x(&linked_bounds.bounds);
982                        mem.auto_bounds.x = linked_bounds.auto_bounds.x;
983                    }
984                    if axes.y {
985                        bounds.set_y(&linked_bounds.bounds);
986                        mem.auto_bounds.y = linked_bounds.auto_bounds.y;
987                    }
988                };
989            });
990        };
991
992        // Allow double-clicking to reset to the initial bounds.
993        if allow_double_click_reset && response.double_clicked() {
994            mem.auto_bounds = true.into();
995        }
996
997        let any_dynamic_modifications = !bounds_modifications.is_empty();
998        // Apply bounds modifications.
999        for modification in bounds_modifications {
1000            match modification {
1001                BoundsModification::Set(new_bounds) => {
1002                    bounds = new_bounds;
1003                    mem.auto_bounds = false.into();
1004                }
1005                BoundsModification::Translate(delta) => {
1006                    let delta = (delta.x as f64, delta.y as f64);
1007                    bounds.translate(delta);
1008                    mem.auto_bounds = false.into();
1009                }
1010                BoundsModification::AutoBounds(new_auto_bounds) => {
1011                    mem.auto_bounds = new_auto_bounds;
1012                }
1013                BoundsModification::Zoom(zoom_factor, center) => {
1014                    bounds.zoom(zoom_factor, center);
1015                    mem.auto_bounds = false.into();
1016                }
1017            }
1018        }
1019
1020        // Reset bounds to initial bounds if they haven't been modified.
1021        if (!default_auto_bounds.x && !any_dynamic_modifications) || mem.auto_bounds.x {
1022            bounds.set_x(&min_auto_bounds);
1023        }
1024        if (!default_auto_bounds.y && !any_dynamic_modifications) || mem.auto_bounds.y {
1025            bounds.set_y(&min_auto_bounds);
1026        }
1027
1028        let auto_x = mem.auto_bounds.x && (!min_auto_bounds.is_valid_x() || default_auto_bounds.x);
1029        let auto_y = mem.auto_bounds.y && (!min_auto_bounds.is_valid_y() || default_auto_bounds.y);
1030
1031        // Set bounds automatically based on content.
1032        if auto_x || auto_y {
1033            for item in &items {
1034                let item_bounds = item.bounds();
1035                if auto_x {
1036                    bounds.merge_x(&item_bounds);
1037                }
1038                if auto_y {
1039                    bounds.merge_y(&item_bounds);
1040                }
1041            }
1042
1043            if auto_x {
1044                bounds.add_relative_margin_x(margin_fraction);
1045            }
1046
1047            if auto_y {
1048                bounds.add_relative_margin_y(margin_fraction);
1049            }
1050        }
1051
1052        mem.transform = PlotTransform::new(plot_rect, bounds, center_axis);
1053
1054        // Enforce aspect ratio
1055        if let Some(data_aspect) = data_aspect {
1056            if let Some((_, linked_axes)) = &linked_axes {
1057                let change_x = linked_axes.y && !linked_axes.x;
1058                mem.transform.set_aspect_by_changing_axis(
1059                    data_aspect as f64,
1060                    if change_x { Axis::X } else { Axis::Y },
1061                );
1062            } else if default_auto_bounds.any() {
1063                mem.transform.set_aspect_by_expanding(data_aspect as f64);
1064            } else {
1065                mem.transform
1066                    .set_aspect_by_changing_axis(data_aspect as f64, Axis::Y);
1067            }
1068        }
1069
1070        // Dragging
1071        if allow_drag.any() && response.dragged_by(PointerButton::Primary) {
1072            response = response.on_hover_cursor(CursorIcon::Grabbing);
1073            let mut delta = -response.drag_delta();
1074            if !allow_drag.x {
1075                delta.x = 0.0;
1076            }
1077            if !allow_drag.y {
1078                delta.y = 0.0;
1079            }
1080            mem.transform
1081                .translate_bounds((delta.x as f64, delta.y as f64));
1082            mem.auto_bounds = mem.auto_bounds.and(!allow_drag);
1083        }
1084
1085        // Zooming
1086        let mut boxed_zoom_rect = None;
1087        if allow_boxed_zoom {
1088            // Save last click to allow boxed zooming
1089            if response.drag_started() && response.dragged_by(boxed_zoom_pointer_button) {
1090                // it would be best for egui that input has a memory of the last click pos because it's a common pattern
1091                mem.last_click_pos_for_zoom = response.hover_pos();
1092            }
1093            let box_start_pos = mem.last_click_pos_for_zoom;
1094            let box_end_pos = response.hover_pos();
1095            if let (Some(box_start_pos), Some(box_end_pos)) = (box_start_pos, box_end_pos) {
1096                // while dragging prepare a Shape and draw it later on top of the plot
1097                if response.dragged_by(boxed_zoom_pointer_button) {
1098                    response = response.on_hover_cursor(CursorIcon::ZoomIn);
1099                    let rect = epaint::Rect::from_two_pos(box_start_pos, box_end_pos);
1100                    boxed_zoom_rect = Some((
1101                        epaint::RectShape::stroke(
1102                            rect,
1103                            0.0,
1104                            epaint::Stroke::new(4., Color32::DARK_BLUE),
1105                            egui::StrokeKind::Middle,
1106                        ), // Outer stroke
1107                        epaint::RectShape::stroke(
1108                            rect,
1109                            0.0,
1110                            epaint::Stroke::new(2., Color32::WHITE),
1111                            egui::StrokeKind::Middle,
1112                        ), // Inner stroke
1113                    ));
1114                }
1115                // when the click is release perform the zoom
1116                if response.drag_stopped() {
1117                    let box_start_pos = mem.transform.value_from_position(box_start_pos);
1118                    let box_end_pos = mem.transform.value_from_position(box_end_pos);
1119                    let new_bounds = PlotBounds {
1120                        min: [
1121                            box_start_pos.x.min(box_end_pos.x),
1122                            box_start_pos.y.min(box_end_pos.y),
1123                        ],
1124                        max: [
1125                            box_start_pos.x.max(box_end_pos.x),
1126                            box_start_pos.y.max(box_end_pos.y),
1127                        ],
1128                    };
1129                    if new_bounds.is_valid() {
1130                        mem.transform.set_bounds(new_bounds);
1131                        mem.auto_bounds = false.into();
1132                    }
1133                    // reset the boxed zoom state
1134                    mem.last_click_pos_for_zoom = None;
1135                }
1136            }
1137        }
1138
1139        // Note: we catch zoom/pan if the response contains the pointer, even if it isn't hovered.
1140        // For instance: The user is painting another interactive widget on top of the plot
1141        // but they still want to be able to pan/zoom the plot.
1142        if let (true, Some(hover_pos)) = (
1143            response.contains_pointer(),
1144            ui.input(|i| i.pointer.hover_pos()),
1145        ) {
1146            if allow_zoom.any() {
1147                let mut zoom_factor = if data_aspect.is_some() {
1148                    Vec2::splat(ui.input(|i| i.zoom_delta()))
1149                } else {
1150                    ui.input(|i| i.zoom_delta_2d())
1151                };
1152                if !allow_zoom.x {
1153                    zoom_factor.x = 1.0;
1154                }
1155                if !allow_zoom.y {
1156                    zoom_factor.y = 1.0;
1157                }
1158                if zoom_factor != Vec2::splat(1.0) {
1159                    mem.transform.zoom(zoom_factor, hover_pos);
1160                    mem.auto_bounds = mem.auto_bounds.and(!allow_zoom);
1161                }
1162            }
1163            if allow_scroll.any() {
1164                let mut scroll_delta = ui.input(|i| i.smooth_scroll_delta);
1165                if !allow_scroll.x {
1166                    scroll_delta.x = 0.0;
1167                }
1168                if !allow_scroll.y {
1169                    scroll_delta.y = 0.0;
1170                }
1171                if scroll_delta != Vec2::ZERO {
1172                    mem.transform
1173                        .translate_bounds((-scroll_delta.x as f64, -scroll_delta.y as f64));
1174                    mem.auto_bounds = false.into();
1175                }
1176            }
1177        }
1178
1179        // --- transform initialized
1180
1181        // Add legend widgets to plot
1182        let bounds = mem.transform.bounds();
1183        let x_axis_range = bounds.range_x();
1184        let x_steps = Arc::new({
1185            let input = GridInput {
1186                bounds: (bounds.min[0], bounds.max[0]),
1187                base_step_size: mem.transform.dvalue_dpos()[0].abs() * grid_spacing.min as f64,
1188            };
1189            (grid_spacers[0])(input)
1190        });
1191        let y_axis_range = bounds.range_y();
1192        let y_steps = Arc::new({
1193            let input = GridInput {
1194                bounds: (bounds.min[1], bounds.max[1]),
1195                base_step_size: mem.transform.dvalue_dpos()[1].abs() * grid_spacing.min as f64,
1196            };
1197            (grid_spacers[1])(input)
1198        });
1199        for (i, mut widget) in x_axis_widgets.into_iter().enumerate() {
1200            widget.range = x_axis_range.clone();
1201            widget.transform = Some(mem.transform);
1202            widget.steps = x_steps.clone();
1203            let (_response, thickness) = widget.ui(ui, Axis::X);
1204            mem.x_axis_thickness.insert(i, thickness);
1205        }
1206        for (i, mut widget) in y_axis_widgets.into_iter().enumerate() {
1207            widget.range = y_axis_range.clone();
1208            widget.transform = Some(mem.transform);
1209            widget.steps = y_steps.clone();
1210            let (_response, thickness) = widget.ui(ui, Axis::Y);
1211            mem.y_axis_thickness.insert(i, thickness);
1212        }
1213
1214        // Initialize values from functions.
1215        for item in &mut items {
1216            item.initialize(mem.transform.bounds().range_x());
1217        }
1218
1219        let prepared = PreparedPlot {
1220            items,
1221            show_x,
1222            show_y,
1223            label_formatter,
1224            coordinates_formatter,
1225            show_grid,
1226            grid_spacing,
1227            transform: mem.transform,
1228            draw_cursor_x: linked_cursors.as_ref().map_or(false, |group| group.1.x),
1229            draw_cursor_y: linked_cursors.as_ref().map_or(false, |group| group.1.y),
1230            draw_cursors,
1231            cursor_color,
1232            grid_spacers,
1233            clamp_grid,
1234        };
1235
1236        let (plot_cursors, mut hovered_plot_item) = prepared.ui(ui, &response);
1237
1238        if let Some(boxed_zoom_rect) = boxed_zoom_rect {
1239            ui.painter()
1240                .with_clip_rect(plot_rect)
1241                .add(boxed_zoom_rect.0);
1242            ui.painter()
1243                .with_clip_rect(plot_rect)
1244                .add(boxed_zoom_rect.1);
1245        }
1246
1247        if let Some(mut legend) = legend {
1248            ui.add(&mut legend);
1249            mem.hidden_items = legend.hidden_items();
1250            mem.hovered_legend_item = legend.hovered_item();
1251
1252            if let Some(item_id) = &mem.hovered_legend_item {
1253                hovered_plot_item.get_or_insert(*item_id);
1254            }
1255        }
1256
1257        if let Some((id, _)) = linked_cursors.as_ref() {
1258            // Push the frame we just drew to the list of frames
1259            ui.data_mut(|data| {
1260                let frames: &mut CursorLinkGroups = data.get_temp_mut_or_default(Id::NULL);
1261                let cursors = frames.0.entry(*id).or_default();
1262                cursors.push(PlotFrameCursors {
1263                    id: plot_id,
1264                    cursors: plot_cursors,
1265                });
1266            });
1267        }
1268
1269        if let Some((id, _)) = linked_axes.as_ref() {
1270            // Save the linked bounds.
1271            ui.data_mut(|data| {
1272                let link_groups: &mut BoundsLinkGroups = data.get_temp_mut_or_default(Id::NULL);
1273                link_groups.0.insert(
1274                    *id,
1275                    LinkedBounds {
1276                        bounds: *mem.transform.bounds(),
1277                        auto_bounds: mem.auto_bounds,
1278                    },
1279                );
1280            });
1281        }
1282
1283        let transform = mem.transform;
1284        mem.store(ui.ctx(), plot_id);
1285
1286        let response = if show_x || show_y {
1287            response.on_hover_cursor(CursorIcon::Crosshair)
1288        } else {
1289            response
1290        };
1291
1292        ui.advance_cursor_after_rect(complete_rect);
1293
1294        PlotResponse {
1295            inner,
1296            response,
1297            transform,
1298            hovered_plot_item,
1299        }
1300    }
1301}
1302
1303/// Returns the rect left after adding axes.
1304fn axis_widgets<'a>(
1305    mem: Option<&PlotMemory>,
1306    show_axes: impl Into<Vec2b>,
1307    complete_rect: Rect,
1308    [x_axes, y_axes]: [&'a [AxisHints<'a>]; 2],
1309) -> ([Vec<AxisWidget<'a>>; 2], Rect) {
1310    // Next we want to create this layout.
1311    // Indices are only examples.
1312    //
1313    //  left                     right
1314    //  +---+---------x----------+   +
1315    //  |   |      X-axis 3      |
1316    //  |   +--------------------+    top
1317    //  |   |      X-axis 2      |
1318    //  +-+-+--------------------+-+-+
1319    //  |y|y|                    |y|y|
1320    //  |-|-|                    |-|-|
1321    //  |A|A|                    |A|A|
1322    // y|x|x|    Plot Window     |x|x|
1323    //  |i|i|                    |i|i|
1324    //  |s|s|                    |s|s|
1325    //  |1|0|                    |2|3|
1326    //  +-+-+--------------------+-+-+
1327    //      |      X-axis 0      |   |
1328    //      +--------------------+   | bottom
1329    //      |      X-axis 1      |   |
1330    //  +   +--------------------+---+
1331    //
1332    let show_axes = show_axes.into();
1333
1334    let mut x_axis_widgets = Vec::<AxisWidget<'_>>::new();
1335    let mut y_axis_widgets = Vec::<AxisWidget<'_>>::new();
1336
1337    // Will shrink as we add more axes.
1338    let mut rect_left = complete_rect;
1339
1340    if show_axes.x {
1341        // We will fix this later, once we know how much space the y axes take up.
1342        let initial_x_range = complete_rect.x_range();
1343
1344        for (i, cfg) in x_axes.iter().enumerate().rev() {
1345            let mut height = cfg.thickness(Axis::X);
1346            if let Some(mem) = mem {
1347                // If the labels took up too much space the previous frame, give them more space now:
1348                height = height.max(mem.x_axis_thickness.get(&i).copied().unwrap_or_default());
1349            }
1350
1351            let rect = match VPlacement::from(cfg.placement) {
1352                VPlacement::Bottom => {
1353                    let bottom = rect_left.bottom();
1354                    *rect_left.bottom_mut() -= height;
1355                    let top = rect_left.bottom();
1356                    Rect::from_x_y_ranges(initial_x_range, top..=bottom)
1357                }
1358                VPlacement::Top => {
1359                    let top = rect_left.top();
1360                    *rect_left.top_mut() += height;
1361                    let bottom = rect_left.top();
1362                    Rect::from_x_y_ranges(initial_x_range, top..=bottom)
1363                }
1364            };
1365            x_axis_widgets.push(AxisWidget::new(cfg.clone(), rect));
1366        }
1367    }
1368    if show_axes.y {
1369        // We know this, since we've already allocated space for the x axes.
1370        let plot_y_range = rect_left.y_range();
1371
1372        for (i, cfg) in y_axes.iter().enumerate().rev() {
1373            let mut width = cfg.thickness(Axis::Y);
1374            if let Some(mem) = mem {
1375                // If the labels took up too much space the previous frame, give them more space now:
1376                width = width.max(mem.y_axis_thickness.get(&i).copied().unwrap_or_default());
1377            }
1378
1379            let rect = match HPlacement::from(cfg.placement) {
1380                HPlacement::Left => {
1381                    let left = rect_left.left();
1382                    *rect_left.left_mut() += width;
1383                    let right = rect_left.left();
1384                    Rect::from_x_y_ranges(left..=right, plot_y_range)
1385                }
1386                HPlacement::Right => {
1387                    let right = rect_left.right();
1388                    *rect_left.right_mut() -= width;
1389                    let left = rect_left.right();
1390                    Rect::from_x_y_ranges(left..=right, plot_y_range)
1391                }
1392            };
1393            y_axis_widgets.push(AxisWidget::new(cfg.clone(), rect));
1394        }
1395    }
1396
1397    // The loops iterated through {x,y}_axes in reverse order, so we have to reverse the
1398    // {x,y}_axis_widgets vec as well. Otherwise, the indices are messed up and the plot memory
1399    // (mem.{x,y}_axis_thickness) will access the wrong axis given an index.
1400    x_axis_widgets.reverse();
1401    y_axis_widgets.reverse();
1402
1403    let mut plot_rect = rect_left;
1404
1405    // If too little space, remove axis widgets
1406    if plot_rect.width() <= 0.0 || plot_rect.height() <= 0.0 {
1407        y_axis_widgets.clear();
1408        x_axis_widgets.clear();
1409        plot_rect = complete_rect;
1410    }
1411
1412    // Now that we know the final x_range of the plot_rect,
1413    // assign it to the x_axis_widgets (they are currently too wide):
1414    for widget in &mut x_axis_widgets {
1415        widget.rect = Rect::from_x_y_ranges(plot_rect.x_range(), widget.rect.y_range());
1416    }
1417
1418    ([x_axis_widgets, y_axis_widgets], plot_rect)
1419}
1420
1421/// User-requested modifications to the plot bounds. We collect them in the plot build function to later apply
1422/// them at the right time, as other modifications need to happen first.
1423enum BoundsModification {
1424    Set(PlotBounds),
1425    Translate(Vec2),
1426    AutoBounds(Vec2b),
1427    Zoom(Vec2, PlotPoint),
1428}
1429
1430// ----------------------------------------------------------------------------
1431// Grid
1432
1433/// Input for "grid spacer" functions.
1434///
1435/// See [`Plot::x_grid_spacer()`] and [`Plot::y_grid_spacer()`].
1436pub struct GridInput {
1437    /// Min/max of the visible data range (the values at the two edges of the plot,
1438    /// for the current axis).
1439    pub bounds: (f64, f64),
1440
1441    /// Recommended (but not required) lower-bound on the step size returned by custom grid spacers.
1442    ///
1443    /// Computed as the ratio between the diagram's bounds (in plot coordinates) and the viewport
1444    /// (in frame/window coordinates), scaled up to represent the minimal possible step.
1445    ///
1446    /// Always positive.
1447    pub base_step_size: f64,
1448}
1449
1450/// One mark (horizontal or vertical line) in the background grid of a plot.
1451#[derive(Debug, Clone, Copy, PartialEq)]
1452pub struct GridMark {
1453    /// X or Y value in the plot.
1454    pub value: f64,
1455
1456    /// The (approximate) distance to the next value of same thickness.
1457    ///
1458    /// Determines how thick the grid line is painted. It's not important that `step_size`
1459    /// matches the difference between two `value`s precisely, but rather that grid marks of
1460    /// same thickness have same `step_size`. For example, months can have a different number
1461    /// of days, but consistently using a `step_size` of 30 days is a valid approximation.
1462    pub step_size: f64,
1463}
1464
1465/// Recursively splits the grid into `base` subdivisions (e.g. 100, 10, 1).
1466///
1467/// The logarithmic base, expressing how many times each grid unit is subdivided.
1468/// 10 is a typical value, others are possible though.
1469pub fn log_grid_spacer(log_base: i64) -> GridSpacer<'static> {
1470    let log_base = log_base as f64;
1471    let step_sizes = move |input: GridInput| -> Vec<GridMark> {
1472        // handle degenerate cases
1473        if input.base_step_size.abs() < f64::EPSILON {
1474            return Vec::new();
1475        }
1476
1477        // The distance between two of the thinnest grid lines is "rounded" up
1478        // to the next-bigger power of base
1479        let smallest_visible_unit = next_power(input.base_step_size, log_base);
1480
1481        let step_sizes = [
1482            smallest_visible_unit,
1483            smallest_visible_unit * log_base,
1484            smallest_visible_unit * log_base * log_base,
1485        ];
1486
1487        generate_marks(step_sizes, input.bounds)
1488    };
1489
1490    Box::new(step_sizes)
1491}
1492
1493/// Splits the grid into uniform-sized spacings (e.g. 100, 25, 1).
1494///
1495/// This function should return 3 positive step sizes, designating where the lines in the grid are drawn.
1496/// Lines are thicker for larger step sizes. Ordering of returned value is irrelevant.
1497///
1498/// Why only 3 step sizes? Three is the number of different line thicknesses that egui typically uses in the grid.
1499/// Ideally, those 3 are not hardcoded values, but depend on the visible range (accessible through `GridInput`).
1500pub fn uniform_grid_spacer<'a>(spacer: impl Fn(GridInput) -> [f64; 3] + 'a) -> GridSpacer<'a> {
1501    let get_marks = move |input: GridInput| -> Vec<GridMark> {
1502        let bounds = input.bounds;
1503        let step_sizes = spacer(input);
1504        generate_marks(step_sizes, bounds)
1505    };
1506
1507    Box::new(get_marks)
1508}
1509
1510// ----------------------------------------------------------------------------
1511
1512struct PreparedPlot<'a> {
1513    items: Vec<Box<dyn PlotItem + 'a>>,
1514    show_x: bool,
1515    show_y: bool,
1516    label_formatter: LabelFormatter<'a>,
1517    coordinates_formatter: Option<(Corner, CoordinatesFormatter<'a>)>,
1518    // axis_formatters: [AxisFormatter; 2],
1519    transform: PlotTransform,
1520    show_grid: Vec2b,
1521    grid_spacing: Rangef,
1522    grid_spacers: [GridSpacer<'a>; 2],
1523    draw_cursor_x: bool,
1524    draw_cursor_y: bool,
1525    draw_cursors: Vec<Cursor>,
1526    cursor_color: Option<Color32>,
1527
1528    clamp_grid: bool,
1529}
1530
1531impl<'a> PreparedPlot<'a> {
1532    fn ui(self, ui: &mut Ui, response: &Response) -> (Vec<Cursor>, Option<Id>) {
1533        let mut axes_shapes = Vec::new();
1534
1535        if self.show_grid.x {
1536            self.paint_grid(ui, &mut axes_shapes, Axis::X, self.grid_spacing);
1537        }
1538        if self.show_grid.y {
1539            self.paint_grid(ui, &mut axes_shapes, Axis::Y, self.grid_spacing);
1540        }
1541
1542        // Sort the axes by strength so that those with higher strength are drawn in front.
1543        axes_shapes.sort_by(|(_, strength1), (_, strength2)| strength1.total_cmp(strength2));
1544
1545        let mut shapes = axes_shapes.into_iter().map(|(shape, _)| shape).collect();
1546
1547        let transform = &self.transform;
1548
1549        let mut plot_ui = ui.new_child(
1550            egui::UiBuilder::new()
1551                .max_rect(*transform.frame())
1552                .layout(Layout::default()),
1553        );
1554        plot_ui.set_clip_rect(transform.frame().intersect(ui.clip_rect()));
1555        for item in &self.items {
1556            item.shapes(&plot_ui, transform, &mut shapes);
1557        }
1558
1559        let hover_pos = response.hover_pos();
1560        let (cursors, hovered_item_id) = if let Some(pointer) = hover_pos {
1561            self.hover(ui, pointer, &mut shapes)
1562        } else {
1563            (Vec::new(), None)
1564        };
1565
1566        // Draw cursors
1567        let line_color = self.cursor_color.unwrap_or_else(|| rulers_color(ui));
1568
1569        let mut draw_cursor = |cursors: &Vec<Cursor>, always| {
1570            for &cursor in cursors {
1571                match cursor {
1572                    Cursor::Horizontal { y } => {
1573                        if self.draw_cursor_y || always {
1574                            shapes.push(horizontal_line(
1575                                transform.position_from_point(&PlotPoint::new(0.0, y)),
1576                                &self.transform,
1577                                line_color,
1578                            ));
1579                        }
1580                    }
1581                    Cursor::Vertical { x } => {
1582                        if self.draw_cursor_x || always {
1583                            shapes.push(vertical_line(
1584                                transform.position_from_point(&PlotPoint::new(x, 0.0)),
1585                                &self.transform,
1586                                line_color,
1587                            ));
1588                        }
1589                    }
1590                }
1591            }
1592        };
1593
1594        draw_cursor(&self.draw_cursors, false);
1595        draw_cursor(&cursors, true);
1596
1597        let painter = ui.painter().with_clip_rect(*transform.frame());
1598        painter.extend(shapes);
1599
1600        if let Some((corner, formatter)) = self.coordinates_formatter.as_ref() {
1601            let hover_pos = response.hover_pos();
1602            if let Some(pointer) = hover_pos {
1603                let font_id = TextStyle::Monospace.resolve(ui.style());
1604                let coordinate = transform.value_from_position(pointer);
1605                let text = formatter.format(&coordinate, transform.bounds());
1606                let padded_frame = transform.frame().shrink(4.0);
1607                let (anchor, position) = match corner {
1608                    Corner::LeftTop => (Align2::LEFT_TOP, padded_frame.left_top()),
1609                    Corner::RightTop => (Align2::RIGHT_TOP, padded_frame.right_top()),
1610                    Corner::LeftBottom => (Align2::LEFT_BOTTOM, padded_frame.left_bottom()),
1611                    Corner::RightBottom => (Align2::RIGHT_BOTTOM, padded_frame.right_bottom()),
1612                };
1613                painter.text(position, anchor, text, font_id, ui.visuals().text_color());
1614            }
1615        }
1616
1617        (cursors, hovered_item_id)
1618    }
1619
1620    fn paint_grid(&self, ui: &Ui, shapes: &mut Vec<(Shape, f32)>, axis: Axis, fade_range: Rangef) {
1621        #![allow(clippy::collapsible_else_if)]
1622        let Self {
1623            transform,
1624            // axis_formatters,
1625            grid_spacers,
1626            clamp_grid,
1627            ..
1628        } = self;
1629
1630        let iaxis = usize::from(axis);
1631
1632        // Where on the cross-dimension to show the label values
1633        let bounds = transform.bounds();
1634        let value_cross = 0.0_f64.clamp(bounds.min[1 - iaxis], bounds.max[1 - iaxis]);
1635
1636        let input = GridInput {
1637            bounds: (bounds.min[iaxis], bounds.max[iaxis]),
1638            base_step_size: transform.dvalue_dpos()[iaxis].abs() * fade_range.min as f64,
1639        };
1640        let steps = (grid_spacers[iaxis])(input);
1641
1642        let clamp_range = clamp_grid.then(|| {
1643            let mut tight_bounds = PlotBounds::NOTHING;
1644            for item in &self.items {
1645                let item_bounds = item.bounds();
1646                tight_bounds.merge_x(&item_bounds);
1647                tight_bounds.merge_y(&item_bounds);
1648            }
1649            tight_bounds
1650        });
1651
1652        for step in steps {
1653            let value_main = step.value;
1654
1655            if let Some(clamp_range) = clamp_range {
1656                match axis {
1657                    Axis::X => {
1658                        if !clamp_range.range_x().contains(&value_main) {
1659                            continue;
1660                        };
1661                    }
1662                    Axis::Y => {
1663                        if !clamp_range.range_y().contains(&value_main) {
1664                            continue;
1665                        };
1666                    }
1667                }
1668            }
1669
1670            let value = match axis {
1671                Axis::X => PlotPoint::new(value_main, value_cross),
1672                Axis::Y => PlotPoint::new(value_cross, value_main),
1673            };
1674
1675            let pos_in_gui = transform.position_from_point(&value);
1676            let spacing_in_points = (transform.dpos_dvalue()[iaxis] * step.step_size).abs() as f32;
1677
1678            if spacing_in_points <= fade_range.min {
1679                continue; // Too close together
1680            }
1681
1682            let line_strength = remap_clamp(spacing_in_points, fade_range, 0.0..=1.0);
1683
1684            let line_color = color_from_strength(ui, line_strength);
1685
1686            let mut p0 = pos_in_gui;
1687            let mut p1 = pos_in_gui;
1688            p0[1 - iaxis] = transform.frame().min[1 - iaxis];
1689            p1[1 - iaxis] = transform.frame().max[1 - iaxis];
1690
1691            if let Some(clamp_range) = clamp_range {
1692                match axis {
1693                    Axis::X => {
1694                        p0.y = transform.position_from_point_y(clamp_range.min[1]);
1695                        p1.y = transform.position_from_point_y(clamp_range.max[1]);
1696                    }
1697                    Axis::Y => {
1698                        p0.x = transform.position_from_point_x(clamp_range.min[0]);
1699                        p1.x = transform.position_from_point_x(clamp_range.max[0]);
1700                    }
1701                }
1702            }
1703
1704            shapes.push((
1705                Shape::line_segment([p0, p1], Stroke::new(1.0, line_color)),
1706                line_strength,
1707            ));
1708        }
1709    }
1710
1711    fn hover(&self, ui: &Ui, pointer: Pos2, shapes: &mut Vec<Shape>) -> (Vec<Cursor>, Option<Id>) {
1712        let Self {
1713            transform,
1714            show_x,
1715            show_y,
1716            label_formatter,
1717            items,
1718            ..
1719        } = self;
1720
1721        if !show_x && !show_y {
1722            return (Vec::new(), None);
1723        }
1724
1725        let interact_radius_sq = ui.style().interaction.interact_radius.powi(2);
1726
1727        let candidates = items
1728            .iter()
1729            .filter(|entry| entry.allow_hover())
1730            .filter_map(|item| {
1731                let item = &**item;
1732                let closest = item.find_closest(pointer, transform);
1733
1734                Some(item).zip(closest)
1735            });
1736
1737        let closest = candidates
1738            .min_by_key(|(_, elem)| elem.dist_sq.ord())
1739            .filter(|(_, elem)| elem.dist_sq <= interact_radius_sq);
1740
1741        let plot = items::PlotConfig {
1742            ui,
1743            transform,
1744            show_x: *show_x,
1745            show_y: *show_y,
1746        };
1747
1748        let mut cursors = Vec::new();
1749
1750        let hovered_plot_item_id = if let Some((item, elem)) = closest {
1751            item.on_hover(elem, shapes, &mut cursors, &plot, label_formatter);
1752            Some(item.id())
1753        } else {
1754            let value = transform.value_from_position(pointer);
1755            items::rulers_at_value(
1756                pointer,
1757                value,
1758                "",
1759                &plot,
1760                shapes,
1761                &mut cursors,
1762                label_formatter,
1763            );
1764            None
1765        };
1766
1767        (cursors, hovered_plot_item_id)
1768    }
1769}
1770
1771/// Returns next bigger power in given base
1772/// e.g.
1773/// ```ignore
1774/// use egui_plot::next_power;
1775/// assert_eq!(next_power(0.01, 10.0), 0.01);
1776/// assert_eq!(next_power(0.02, 10.0), 0.1);
1777/// assert_eq!(next_power(0.2,  10.0), 1);
1778/// ```
1779fn next_power(value: f64, base: f64) -> f64 {
1780    debug_assert_ne!(value, 0.0, "Bad input"); // can be negative (typical for Y axis)
1781    base.powi(value.abs().log(base).ceil() as i32)
1782}
1783
1784/// Fill in all values between [min, max] which are a multiple of `step_size`
1785fn generate_marks(step_sizes: [f64; 3], bounds: (f64, f64)) -> Vec<GridMark> {
1786    let mut steps = vec![];
1787    fill_marks_between(&mut steps, step_sizes[0], bounds);
1788    fill_marks_between(&mut steps, step_sizes[1], bounds);
1789    fill_marks_between(&mut steps, step_sizes[2], bounds);
1790
1791    // Remove duplicates:
1792    // This can happen because we have overlapping steps, e.g.:
1793    // step_size[0] =   10  =>  [-10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]
1794    // step_size[1] =  100  =>  [     0,                                     100          ]
1795    // step_size[2] = 1000  =>  [     0                                                   ]
1796
1797    steps.sort_by(|a, b| cmp_f64(a.value, b.value));
1798
1799    let min_step = step_sizes.iter().fold(f64::INFINITY, |a, &b| a.min(b));
1800    let eps = 0.1 * min_step; // avoid putting two ticks too closely together
1801
1802    let mut deduplicated: Vec<GridMark> = Vec::with_capacity(steps.len());
1803    for step in steps {
1804        if let Some(last) = deduplicated.last_mut() {
1805            if (last.value - step.value).abs() < eps {
1806                // Keep the one with the largest step size
1807                if last.step_size < step.step_size {
1808                    *last = step;
1809                }
1810                continue;
1811            }
1812        }
1813        deduplicated.push(step);
1814    }
1815
1816    deduplicated
1817}
1818
1819#[test]
1820fn test_generate_marks() {
1821    fn approx_eq(a: &GridMark, b: &GridMark) -> bool {
1822        (a.value - b.value).abs() < 1e-10 && a.step_size == b.step_size
1823    }
1824
1825    let gm = |value, step_size| GridMark { value, step_size };
1826
1827    let marks = generate_marks([0.01, 0.1, 1.0], (2.855, 3.015));
1828    let expected = vec![
1829        gm(2.86, 0.01),
1830        gm(2.87, 0.01),
1831        gm(2.88, 0.01),
1832        gm(2.89, 0.01),
1833        gm(2.90, 0.1),
1834        gm(2.91, 0.01),
1835        gm(2.92, 0.01),
1836        gm(2.93, 0.01),
1837        gm(2.94, 0.01),
1838        gm(2.95, 0.01),
1839        gm(2.96, 0.01),
1840        gm(2.97, 0.01),
1841        gm(2.98, 0.01),
1842        gm(2.99, 0.01),
1843        gm(3.00, 1.),
1844        gm(3.01, 0.01),
1845    ];
1846
1847    let mut problem = None;
1848    if marks.len() != expected.len() {
1849        problem = Some(format!(
1850            "Different lengths: got {}, expected {}",
1851            marks.len(),
1852            expected.len()
1853        ));
1854    }
1855
1856    for (i, (a, b)) in marks.iter().zip(&expected).enumerate() {
1857        if !approx_eq(a, b) {
1858            problem = Some(format!("Mismatch at index {i}: {a:?} != {b:?}"));
1859            break;
1860        }
1861    }
1862
1863    if let Some(problem) = problem {
1864        panic!("Test failed: {problem}. Got: {marks:#?}, expected: {expected:#?}");
1865    }
1866}
1867
1868fn cmp_f64(a: f64, b: f64) -> Ordering {
1869    match a.partial_cmp(&b) {
1870        Some(ord) => ord,
1871        None => a.is_nan().cmp(&b.is_nan()),
1872    }
1873}
1874
1875/// Fill in all values between [min, max] which are a multiple of `step_size`
1876fn fill_marks_between(out: &mut Vec<GridMark>, step_size: f64, (min, max): (f64, f64)) {
1877    debug_assert!(min <= max, "Bad plot bounds: min: {min}, max: {max}");
1878    let first = (min / step_size).ceil() as i64;
1879    let last = (max / step_size).ceil() as i64;
1880
1881    let marks_iter = (first..last).map(|i| {
1882        let value = (i as f64) * step_size;
1883        GridMark { value, step_size }
1884    });
1885    out.extend(marks_iter);
1886}
1887
1888/// Helper for formatting a number so that we always show at least a few decimals,
1889/// unless it is an integer, in which case we never show any decimals.
1890pub fn format_number(number: f64, num_decimals: usize) -> String {
1891    let is_integral = number as i64 as f64 == number;
1892    if is_integral {
1893        // perfect integer - show it as such:
1894        format!("{number:.0}")
1895    } else {
1896        // make sure we tell the user it is not an integer by always showing a decimal or two:
1897        format!("{:.*}", num_decimals.at_least(1), number)
1898    }
1899}
1900
1901/// Determine a color from a 0-1 strength value.
1902pub fn color_from_strength(ui: &Ui, strength: f32) -> Color32 {
1903    let base_color = ui.visuals().text_color();
1904    base_color.gamma_multiply(strength.sqrt())
1905}