Plot items now have optional id which is returned in the plot's response when hovered (#3920)
This allows users to check which item the user interacts with in the plot. https://github.com/emilk/egui/assets/1220815/1a174b38-8414-49be-a802-d187cd93d154 --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
945a69d2f2
commit
ca513ce241
|
|
@ -791,8 +791,28 @@ 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,
|
||||
))
|
||||
.color(Color32::RED)
|
||||
.id(egui::Id::new("sin")),
|
||||
);
|
||||
plot_ui.line(
|
||||
Line::new(PlotPoints::from_explicit_callback(
|
||||
move |x| x.cos(),
|
||||
..,
|
||||
100,
|
||||
))
|
||||
.color(Color32::BLUE)
|
||||
.id(egui::Id::new("cos")),
|
||||
);
|
||||
|
||||
(
|
||||
plot_ui.screen_from_plot(PlotPoint::new(0.0, 0.0)),
|
||||
plot_ui.pointer_coordinate(),
|
||||
|
|
@ -824,6 +844,15 @@ impl InteractionDemo {
|
|||
);
|
||||
ui.label(format!("pointer coordinate drag delta: {coordinate_text}"));
|
||||
|
||||
let hovered_item = if hovered_plot_item == Some(egui::Id::new("sin")) {
|
||||
"red sin"
|
||||
} else if hovered_plot_item == Some(egui::Id::new("cos")) {
|
||||
"blue cos"
|
||||
} else {
|
||||
"none"
|
||||
};
|
||||
ui.label(format!("hovered plot item: {hovered_item}"));
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ pub(super) trait PlotItem {
|
|||
|
||||
fn bounds(&self) -> PlotBounds;
|
||||
|
||||
fn id(&self) -> Option<Id>;
|
||||
|
||||
fn find_closest(&self, point: Pos2, transform: &PlotTransform) -> Option<ClosestElem> {
|
||||
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<Id>,
|
||||
}
|
||||
|
||||
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<Id> {
|
||||
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<Id>,
|
||||
}
|
||||
|
||||
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<Id> {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
/// A series of values forming a path.
|
||||
|
|
@ -364,6 +392,7 @@ pub struct Line {
|
|||
pub(super) highlight: bool,
|
||||
pub(super) fill: Option<f32>,
|
||||
pub(super) style: LineStyle,
|
||||
id: Option<Id>,
|
||||
}
|
||||
|
||||
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<Id> {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
/// A convex polygon.
|
||||
|
|
@ -538,6 +579,7 @@ pub struct Polygon {
|
|||
pub(super) highlight: bool,
|
||||
pub(super) fill_color: Option<Color32>,
|
||||
pub(super) style: LineStyle,
|
||||
id: Option<Id>,
|
||||
}
|
||||
|
||||
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<Id> {
|
||||
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<Id>,
|
||||
}
|
||||
|
||||
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<Id> {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of points.
|
||||
|
|
@ -790,6 +857,7 @@ pub struct Points {
|
|||
pub(super) highlight: bool,
|
||||
|
||||
pub(super) stems: Option<f32>,
|
||||
id: Option<Id>,
|
||||
}
|
||||
|
||||
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<Id> {
|
||||
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<Id>,
|
||||
}
|
||||
|
||||
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<Id> {
|
||||
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<Id>,
|
||||
}
|
||||
|
||||
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<Id> {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -1344,6 +1443,7 @@ pub struct BarChart {
|
|||
pub(super) element_formatter: Option<Box<dyn Fn(&Bar, &BarChart) -> String>>,
|
||||
|
||||
highlight: bool,
|
||||
id: Option<Id>,
|
||||
}
|
||||
|
||||
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<Id> {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
/// A diagram containing a series of [`BoxElem`] elements.
|
||||
|
|
@ -1524,6 +1636,7 @@ pub struct BoxPlot {
|
|||
pub(super) element_formatter: Option<Box<dyn Fn(&BoxElem, &BoxPlot) -> String>>,
|
||||
|
||||
highlight: bool,
|
||||
id: Option<Id>,
|
||||
}
|
||||
|
||||
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<Id> {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -117,6 +117,11 @@ pub struct PlotResponse<R> {
|
|||
|
||||
/// 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<Id>,
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -803,7 +808,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(plot_rect, min_auto_bounds, center_axis.x, center_axis.y),
|
||||
last_click_pos_for_zoom: None,
|
||||
|
|
@ -848,14 +853,14 @@ impl Plot {
|
|||
let legend = legend_config
|
||||
.and_then(|config| LegendWidget::try_new(plot_rect, config, &items, &mem.hidden_items));
|
||||
// Don't show hover cursor when hovering over legend.
|
||||
if mem.hovered_item.is_some() {
|
||||
if mem.hovered_legend_item.is_some() {
|
||||
show_x = false;
|
||||
show_y = false;
|
||||
}
|
||||
// Remove the deselected items.
|
||||
items.retain(|item| !mem.hidden_items.contains(item.name()));
|
||||
// Highlight the hovered items.
|
||||
if let Some(hovered_name) = &mem.hovered_item {
|
||||
if let Some(hovered_name) = &mem.hovered_legend_item {
|
||||
items
|
||||
.iter_mut()
|
||||
.filter(|entry| entry.name() == hovered_name)
|
||||
|
|
@ -1137,7 +1142,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()
|
||||
|
|
@ -1151,7 +1156,7 @@ impl Plot {
|
|||
if let Some(mut legend) = legend {
|
||||
ui.add(&mut legend);
|
||||
mem.hidden_items = legend.hidden_items();
|
||||
mem.hovered_item = legend.hovered_item_name();
|
||||
mem.hovered_legend_item = legend.hovered_item_name();
|
||||
}
|
||||
|
||||
if let Some((id, _)) = linked_cursors.as_ref() {
|
||||
|
|
@ -1195,6 +1200,7 @@ impl Plot {
|
|||
inner,
|
||||
response,
|
||||
transform,
|
||||
hovered_plot_item,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1645,7 +1651,7 @@ struct PreparedPlot {
|
|||
}
|
||||
|
||||
impl PreparedPlot {
|
||||
fn ui(self, ui: &mut Ui, response: &Response) -> Vec<Cursor> {
|
||||
fn ui(self, ui: &mut Ui, response: &Response) -> (Vec<Cursor>, Option<Id>) {
|
||||
let mut axes_shapes = Vec::new();
|
||||
|
||||
if self.show_grid.x {
|
||||
|
|
@ -1669,10 +1675,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
|
||||
|
|
@ -1726,7 +1732,7 @@ impl PreparedPlot {
|
|||
}
|
||||
}
|
||||
|
||||
cursors
|
||||
(cursors, hovered_item_id)
|
||||
}
|
||||
|
||||
fn paint_grid(&self, ui: &Ui, shapes: &mut Vec<(Shape, f32)>, axis: Axis, fade_range: Rangef) {
|
||||
|
|
@ -1826,7 +1832,7 @@ impl PreparedPlot {
|
|||
}
|
||||
}
|
||||
|
||||
fn hover(&self, ui: &Ui, pointer: Pos2, shapes: &mut Vec<Shape>) -> Vec<Cursor> {
|
||||
fn hover(&self, ui: &Ui, pointer: Pos2, shapes: &mut Vec<Shape>) -> (Vec<Cursor>, Option<Id>) {
|
||||
let Self {
|
||||
transform,
|
||||
show_x,
|
||||
|
|
@ -1837,7 +1843,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);
|
||||
|
|
@ -1853,8 +1859,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,
|
||||
|
|
@ -1862,8 +1866,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(
|
||||
|
|
@ -1875,9 +1882,10 @@ impl PreparedPlot {
|
|||
&mut cursors,
|
||||
label_formatter,
|
||||
);
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
cursors
|
||||
(cursors, hovered_plot_item_id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ pub struct PlotMemory {
|
|||
/// the bounds, for example by moving or zooming.
|
||||
pub auto_bounds: Vec2b,
|
||||
|
||||
/// Which item is hovered?
|
||||
pub hovered_item: Option<String>,
|
||||
/// Display string of the hovered legend item if any.
|
||||
pub hovered_legend_item: Option<String>,
|
||||
|
||||
/// Which items _not_ to show?
|
||||
pub hidden_items: ahash::HashSet<String>,
|
||||
|
|
|
|||
Loading…
Reference in New Issue