diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index edd5215bfb5..67d19109d13 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -791,8 +791,26 @@ impl InteractionDemo { let PlotResponse { response, inner: (screen_pos, pointer_coordinate, pointer_coordinate_drag_delta, bounds, hovered), + hovered_plot_item, .. } = plot.show(ui, |plot_ui| { + plot_ui.line( + Line::new(PlotPoints::from_explicit_callback( + move |x| x.sin(), + .., + 100, + )) + .id(egui::Id::new("sin")), + ); + plot_ui.line( + Line::new(PlotPoints::from_explicit_callback( + move |x| x.cos(), + .., + 100, + )) + .id(egui::Id::new("cos")), + ); + ( plot_ui.screen_from_plot(PlotPoint::new(0.0, 0.0)), plot_ui.pointer_coordinate(), @@ -823,6 +841,7 @@ impl InteractionDemo { pointer_coordinate_drag_delta.x, pointer_coordinate_drag_delta.y ); ui.label(format!("pointer coordinate drag delta: {coordinate_text}")); + ui.label(format!("hovered plot item: {hovered_plot_item:?}")); response } diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index a3b1ae8cbc9..fb176438e3c 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -49,6 +49,8 @@ pub(super) trait PlotItem { fn bounds(&self) -> PlotBounds; + fn id(&self) -> Option; + fn find_closest(&self, point: Pos2, transform: &PlotTransform) -> Option { match self.geometry() { PlotGeometry::None => None, @@ -120,6 +122,7 @@ pub struct HLine { pub(super) name: String, pub(super) highlight: bool, pub(super) style: LineStyle, + id: Option, } impl HLine { @@ -130,6 +133,7 @@ impl HLine { name: String::default(), highlight: false, style: LineStyle::Solid, + id: None, } } @@ -180,6 +184,13 @@ impl HLine { self.name = name.to_string(); self } + + /// Set the line's id which is used to identify it in the plot's response. + #[inline] + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } } impl PlotItem for HLine { @@ -232,6 +243,10 @@ impl PlotItem for HLine { bounds.max[1] = self.y; bounds } + + fn id(&self) -> Option { + self.id + } } /// A vertical line in a plot, filling the full width @@ -242,6 +257,7 @@ pub struct VLine { pub(super) name: String, pub(super) highlight: bool, pub(super) style: LineStyle, + id: Option, } impl VLine { @@ -252,6 +268,7 @@ impl VLine { name: String::default(), highlight: false, style: LineStyle::Solid, + id: None, } } @@ -302,6 +319,13 @@ impl VLine { self.name = name.to_string(); self } + + /// Set the line's id which is used to identify it in the plot's response. + #[inline] + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } } impl PlotItem for VLine { @@ -354,6 +378,10 @@ impl PlotItem for VLine { bounds.max[0] = self.x; bounds } + + fn id(&self) -> Option { + self.id + } } /// A series of values forming a path. @@ -364,6 +392,7 @@ pub struct Line { pub(super) highlight: bool, pub(super) fill: Option, pub(super) style: LineStyle, + id: Option, } impl Line { @@ -375,6 +404,7 @@ impl Line { highlight: false, fill: None, style: LineStyle::Solid, + id: None, } } @@ -432,6 +462,13 @@ impl Line { self.name = name.to_string(); self } + + /// Set the line's id which is used to identify it in the plot's response. + #[inline] + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } } /// Returns the x-coordinate of a possible intersection between a line segment from `p1` to `p2` and @@ -528,6 +565,10 @@ impl PlotItem for Line { fn bounds(&self) -> PlotBounds { self.series.bounds() } + + fn id(&self) -> Option { + self.id + } } /// A convex polygon. @@ -538,6 +579,7 @@ pub struct Polygon { pub(super) highlight: bool, pub(super) fill_color: Option, pub(super) style: LineStyle, + id: Option, } impl Polygon { @@ -549,6 +591,7 @@ impl Polygon { highlight: false, fill_color: None, style: LineStyle::Solid, + id: None, } } @@ -600,6 +643,13 @@ impl Polygon { self.name = name.to_string(); self } + + /// Set the polygon's id which is used to identify it in the plot's response. + #[inline] + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } } impl PlotItem for Polygon { @@ -654,6 +704,10 @@ impl PlotItem for Polygon { fn bounds(&self) -> PlotBounds { self.series.bounds() } + + fn id(&self) -> Option { + self.id + } } /// Text inside the plot. @@ -665,6 +719,7 @@ pub struct Text { pub(super) highlight: bool, pub(super) color: Color32, pub(super) anchor: Align2, + id: Option, } impl Text { @@ -676,6 +731,7 @@ impl Text { highlight: false, color: Color32::TRANSPARENT, anchor: Align2::CENTER_CENTER, + id: None, } } @@ -712,6 +768,13 @@ impl Text { self.name = name.to_string(); self } + + /// Set the text's id which is used to identify it in the plot's response. + #[inline] + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } } impl PlotItem for Text { @@ -768,6 +831,10 @@ impl PlotItem for Text { bounds.extend_with(&self.position); bounds } + + fn id(&self) -> Option { + self.id + } } /// A set of points. @@ -790,6 +857,7 @@ pub struct Points { pub(super) highlight: bool, pub(super) stems: Option, + id: Option, } impl Points { @@ -803,6 +871,7 @@ impl Points { name: Default::default(), highlight: false, stems: None, + id: None, } } @@ -860,6 +929,13 @@ impl Points { self.name = name.to_string(); self } + + /// Set the points' id which is used to identify them in the plot's response. + #[inline] + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } } impl PlotItem for Points { @@ -1018,6 +1094,10 @@ impl PlotItem for Points { fn bounds(&self) -> PlotBounds { self.series.bounds() } + + fn id(&self) -> Option { + self.id + } } /// A set of arrows. @@ -1028,6 +1108,7 @@ pub struct Arrows { pub(super) color: Color32, pub(super) name: String, pub(super) highlight: bool, + id: Option, } impl Arrows { @@ -1039,6 +1120,7 @@ impl Arrows { color: Color32::TRANSPARENT, name: Default::default(), highlight: false, + id: None, } } @@ -1075,6 +1157,13 @@ impl Arrows { self.name = name.to_string(); self } + + /// Set the arrows' id which is used to identify them in the plot's response. + #[inline] + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } } impl PlotItem for Arrows { @@ -1150,6 +1239,10 @@ impl PlotItem for Arrows { fn bounds(&self) -> PlotBounds { self.origins.bounds() } + + fn id(&self) -> Option { + self.id + } } /// An image in the plot. @@ -1164,6 +1257,7 @@ pub struct PlotImage { pub(super) tint: Color32, pub(super) highlight: bool, pub(super) name: String, + id: Option, } impl PlotImage { @@ -1183,6 +1277,7 @@ impl PlotImage { rotation: 0.0, bg_fill: Default::default(), tint: Color32::WHITE, + id: None, } } @@ -1330,6 +1425,10 @@ impl PlotItem for PlotImage { bounds.extend_with(&right_bottom); bounds } + + fn id(&self) -> Option { + self.id + } } // ---------------------------------------------------------------------------- @@ -1344,6 +1443,7 @@ pub struct BarChart { pub(super) element_formatter: Option String>>, highlight: bool, + id: Option, } impl BarChart { @@ -1355,6 +1455,7 @@ impl BarChart { name: String::new(), element_formatter: None, highlight: false, + id: None, } } @@ -1454,6 +1555,13 @@ impl BarChart { } self } + + /// Set the bar chart's id which is used to identify it in the plot's response. + #[inline] + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } } impl PlotItem for BarChart { @@ -1512,6 +1620,10 @@ impl PlotItem for BarChart { bar.add_shapes(plot.transform, true, shapes); bar.add_rulers_and_text(self, plot, shapes, cursors); } + + fn id(&self) -> Option { + self.id + } } /// A diagram containing a series of [`BoxElem`] elements. @@ -1524,6 +1636,7 @@ pub struct BoxPlot { pub(super) element_formatter: Option String>>, highlight: bool, + id: Option, } impl BoxPlot { @@ -1535,6 +1648,7 @@ impl BoxPlot { name: String::new(), element_formatter: None, highlight: false, + id: None, } } @@ -1602,6 +1716,13 @@ impl BoxPlot { self.element_formatter = Some(formatter); self } + + /// Set the box plot's id which is used to identify it in the plot's response. + #[inline] + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } } impl PlotItem for BoxPlot { @@ -1660,6 +1781,10 @@ impl PlotItem for BoxPlot { box_plot.add_shapes(plot.transform, true, shapes); box_plot.add_rulers_and_text(self, plot, shapes, cursors); } + + fn id(&self) -> Option { + self.id + } } // ---------------------------------------------------------------------------- diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 4fe89ff367d..461b282758d 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -117,6 +117,11 @@ pub struct PlotResponse { /// The transform between screen coordinates and plot coordinates. pub transform: PlotTransform, + + /// The id of a currently hovered item if any. + /// + /// This is `None` if either no item was hovered, or the hovered item didn't provide an id. + pub hovered_plot_item: Option, } // ---------------------------------------------------------------------------- @@ -858,7 +863,7 @@ impl Plot { } .unwrap_or_else(|| PlotMemory { auto_bounds: default_auto_bounds, - hovered_item: None, + hovered_legend_item: None, hidden_items: Default::default(), transform: PlotTransform::new(rect, min_auto_bounds, center_axis.x, center_axis.y), last_click_pos_for_zoom: None, @@ -866,7 +871,7 @@ impl Plot { let PlotMemory { mut auto_bounds, - mut hovered_item, + mut hovered_legend_item, mut hidden_items, transform: last_plot_transform, mut last_click_pos_for_zoom, @@ -907,14 +912,14 @@ impl Plot { let legend = legend_config .and_then(|config| LegendWidget::try_new(rect, config, &items, &hidden_items)); // Don't show hover cursor when hovering over legend. - if hovered_item.is_some() { + if hovered_legend_item.is_some() { show_x = false; show_y = false; } // Remove the deselected items. items.retain(|item| !hidden_items.contains(item.name())); // Highlight the hovered items. - if let Some(hovered_name) = &hovered_item { + if let Some(hovered_name) = &hovered_legend_item { items .iter_mut() .filter(|entry| entry.name() == hovered_name) @@ -1190,7 +1195,7 @@ impl Plot { clamp_grid, }; - let plot_cursors = prepared.ui(ui, &response); + let (plot_cursors, hovered_plot_item) = prepared.ui(ui, &response); if let Some(boxed_zoom_rect) = boxed_zoom_rect { ui.painter().with_clip_rect(rect).add(boxed_zoom_rect.0); @@ -1200,7 +1205,7 @@ impl Plot { if let Some(mut legend) = legend { ui.add(&mut legend); hidden_items = legend.hidden_items(); - hovered_item = legend.hovered_item_name(); + hovered_legend_item = legend.hovered_item_name(); } if let Some((id, _)) = linked_cursors.as_ref() { @@ -1231,7 +1236,7 @@ impl Plot { let memory = PlotMemory { auto_bounds, - hovered_item, + hovered_legend_item, hidden_items, transform, last_click_pos_for_zoom, @@ -1248,6 +1253,7 @@ impl Plot { inner, response, transform, + hovered_plot_item, } } } @@ -1660,7 +1666,7 @@ struct PreparedPlot { } impl PreparedPlot { - fn ui(self, ui: &mut Ui, response: &Response) -> Vec { + fn ui(self, ui: &mut Ui, response: &Response) -> (Vec, Option) { let mut axes_shapes = Vec::new(); if self.show_grid.x { @@ -1684,10 +1690,10 @@ impl PreparedPlot { } let hover_pos = response.hover_pos(); - let cursors = if let Some(pointer) = hover_pos { + let (cursors, hovered_item_id) = if let Some(pointer) = hover_pos { self.hover(ui, pointer, &mut shapes) } else { - Vec::new() + (Vec::new(), None) }; // Draw cursors @@ -1741,7 +1747,7 @@ impl PreparedPlot { } } - cursors + (cursors, hovered_item_id) } fn paint_grid(&self, ui: &Ui, shapes: &mut Vec<(Shape, f32)>, axis: Axis, fade_range: Rangef) { @@ -1841,7 +1847,7 @@ impl PreparedPlot { } } - fn hover(&self, ui: &Ui, pointer: Pos2, shapes: &mut Vec) -> Vec { + fn hover(&self, ui: &Ui, pointer: Pos2, shapes: &mut Vec) -> (Vec, Option) { let Self { transform, show_x, @@ -1852,7 +1858,7 @@ impl PreparedPlot { } = self; if !show_x && !show_y { - return Vec::new(); + return (Vec::new(), None); } let interact_radius_sq = (16.0_f32).powi(2); @@ -1868,8 +1874,6 @@ impl PreparedPlot { .min_by_key(|(_, elem)| elem.dist_sq.ord()) .filter(|(_, elem)| elem.dist_sq <= interact_radius_sq); - let mut cursors = Vec::new(); - let plot = items::PlotConfig { ui, transform, @@ -1877,8 +1881,11 @@ impl PreparedPlot { show_y: *show_y, }; - if let Some((item, elem)) = closest { + let mut cursors = Vec::new(); + + let hovered_plot_item_id = if let Some((item, elem)) = closest { item.on_hover(elem, shapes, &mut cursors, &plot, label_formatter); + item.id() } else { let value = transform.value_from_position(pointer); items::rulers_at_value( @@ -1890,9 +1897,10 @@ impl PreparedPlot { &mut cursors, label_formatter, ); - } + None + }; - cursors + (cursors, hovered_plot_item_id) } } diff --git a/crates/egui_plot/src/memory.rs b/crates/egui_plot/src/memory.rs index c334f115274..08056c89417 100644 --- a/crates/egui_plot/src/memory.rs +++ b/crates/egui_plot/src/memory.rs @@ -12,8 +12,8 @@ pub struct PlotMemory { /// the bounds, for example by moving or zooming. pub auto_bounds: Vec2b, - /// Which item is hovered? - pub hovered_item: Option, + /// Display string of the hovered legend item if any. + pub hovered_legend_item: Option, /// Which items _not_ to show? pub hidden_items: ahash::HashSet,