Skip to content

Commit db52cb0

Browse files
upsjlucasmerlin
andauthored
Provide element index with label_formatter (#138)
<!-- Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/emilk/egui_plot/blob/master/CONTRIBUTING.md) before opening a Pull Request! * Keep your PR:s small and focused. * The PR title is what ends up in the changelog, so make it descriptive! * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo f or a new example. * Do NOT open PR:s from your `master` or `main` branch, as that makes it hard for maintainers to test and add commits to your PR. * Remember to run `cargo fmt` and `cargo clippy`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. Please be patient! We will review your PR, but our time is limited! --> label_formatter only provides the name of the plot and the position of the point, but that makes identifying the exact data point unnecessarily complicated, when the index of the data point is available internally. To change that, I wanted to add some more generic functionality, which either requires an interface break or adding a new function - I decided for the ~~latter~~ former. I changed the label_formatter interface to introduce three improvements: 1. Instead of passing an empty string "" for the plot name when no data point is selected, we pass an enum 2. Instead of returning a String, we return an Option<String>, which allows hiding the label when we don't need it, e.g. when not hovering over a data point. 3. Instead of taking just the position and plot name, we take the position, data point index and plot name. * [x] I have followed the instructions in the PR template --------- Co-authored-by: lucasmerlin <hi@lucasmerlin.me>
1 parent 1cb457d commit db52cb0

5 files changed

Lines changed: 72 additions & 27 deletions

File tree

egui_plot/src/items/mod.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub use crate::items::polygon::Polygon;
4040
pub use crate::items::series::Line;
4141
pub use crate::items::span::Span;
4242
pub use crate::items::text::Text;
43+
use crate::label::HoverPosition;
4344
use crate::label::LabelFormatter;
4445
use crate::rect_elem::RectElement;
4546

@@ -198,7 +199,14 @@ pub trait PlotItem {
198199
let pointer = plot.transform.position_from_point(&value);
199200
shapes.push(Shape::circle_filled(pointer, 3.0, line_color));
200201

201-
rulers_and_tooltip_at_value(plot_area_response, value, self.name(), plot, cursors, label_formatter);
202+
rulers_and_tooltip_at_value(
203+
plot_area_response,
204+
value,
205+
Some((self.name(), elem.index)),
206+
plot,
207+
cursors,
208+
label_formatter,
209+
);
202210
}
203211
}
204212

@@ -272,7 +280,7 @@ fn add_rulers_and_text(
272280
pub(super) fn rulers_and_tooltip_at_value(
273281
plot_area_response: &egui::Response,
274282
value: PlotPoint,
275-
name: &str,
283+
nearest_point: Option<(&str, usize)>,
276284
plot: &PlotConfig<'_>,
277285
cursors: &mut Vec<Cursor>,
278286
label_formatter: &Option<LabelFormatter<'_>>,
@@ -292,10 +300,18 @@ pub(super) fn rulers_and_tooltip_at_value(
292300
return;
293301
};
294302

295-
let text = custom_label(name, &value);
296-
if text.is_empty() {
303+
let hover_position = match nearest_point {
304+
Some((name, index)) => HoverPosition::NearDataPoint {
305+
plot_name: name,
306+
position: value,
307+
index,
308+
},
309+
None => HoverPosition::Elsewhere { position: value },
310+
};
311+
312+
let Some(text) = custom_label(&hover_position) else {
297313
return;
298-
}
314+
};
299315

300316
// We show the tooltip as soon as we're hovering the plot area:
301317
let mut tooltip = egui::Tooltip::always_open(

egui_plot/src/label.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,40 @@ pub fn format_number(number: f64, num_decimals: usize) -> String {
1616
}
1717
}
1818

19-
type LabelFormatterFn<'a> = dyn Fn(&str, &PlotPoint) -> String + 'a;
19+
type LabelFormatterFn<'a> = dyn Fn(&HoverPosition<'_>) -> Option<String> + 'a;
2020

2121
/// Optional label formatter function for customizing hover labels.
2222
pub type LabelFormatter<'a> = Box<LabelFormatterFn<'a>>;
2323

24+
/// Indicates the position of the cursor in a plot for hover purposes.
25+
#[derive(Copy, Clone, PartialEq)]
26+
pub enum HoverPosition<'a> {
27+
NearDataPoint {
28+
/// The name of the plot whose data point is nearest to the cursor
29+
plot_name: &'a str,
30+
31+
/// The position of the nearest data point
32+
position: PlotPoint,
33+
34+
/// The index of the nearest data point in its plot
35+
index: usize,
36+
},
37+
Elsewhere {
38+
/// The position in the plot over which the cursor hovers
39+
position: PlotPoint,
40+
},
41+
}
42+
2443
/// Default label formatter that shows the x and y coordinates with 3 decimal
2544
/// places.
26-
pub fn default_label_formatter(name: &str, value: &PlotPoint) -> String {
27-
let prefix = if name.is_empty() {
28-
String::new()
29-
} else {
30-
format!("{name}\n")
31-
};
32-
format!("{}x = {:.3}\ny = {:.3}", prefix, value.x, value.y)
45+
#[expect(clippy::unnecessary_wraps)]
46+
pub fn default_label_formatter(pos: &HoverPosition<'_>) -> Option<String> {
47+
Some(match pos {
48+
HoverPosition::NearDataPoint {
49+
plot_name,
50+
position,
51+
index: _,
52+
} => format!("{}\nx = {:.3}\ny = {:.3}", plot_name, position.x, position.y),
53+
HoverPosition::Elsewhere { position } => format!("x = {:.3}\ny = {:.3}", position.x, position.y),
54+
})
3355
}

egui_plot/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ pub use crate::items::Polygon;
6161
pub use crate::items::Span;
6262
pub use crate::items::Text;
6363
pub use crate::items::VLine;
64+
pub use crate::label::HoverPosition;
6465
pub use crate::label::LabelFormatter;
6566
pub use crate::label::default_label_formatter;
6667
pub use crate::label::format_number;

egui_plot/src/plot.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ use crate::items::PlotItem;
4747
use crate::items::Span;
4848
use crate::items::horizontal_line;
4949
use crate::items::vertical_line;
50+
use crate::label::HoverPosition;
5051
use crate::label::LabelFormatter;
5152
use crate::memory::PlotMemory;
5253
use crate::overlays::CoordinatesFormatter;
@@ -396,9 +397,7 @@ impl<'a> Plot<'a> {
396397
///
397398
/// ```
398399
/// # egui::__run_test_ui(|ui| {
399-
/// use egui_plot::Line;
400-
/// use egui_plot::Plot;
401-
/// use egui_plot::PlotPoints;
400+
/// use egui_plot::{HoverPosition, Line, Plot, PlotPoints};
402401
/// let sin: PlotPoints = (0..1000)
403402
/// .map(|i| {
404403
/// let x = i as f64 * 0.01;
@@ -408,18 +407,17 @@ impl<'a> Plot<'a> {
408407
/// let line = Line::new("sin", sin);
409408
/// Plot::new("my_plot")
410409
/// .view_aspect(2.0)
411-
/// .label_formatter(|name, value| {
412-
/// if !name.is_empty() {
413-
/// format!("{}: {:.*}%", name, 1, value.y)
414-
/// } else {
415-
/// "".to_owned()
410+
/// .label_formatter(|pos| match pos {
411+
/// HoverPosition::NearDataPoint { plot_name, position, .. } if !plot_name.is_empty() => {
412+
/// Some(format!("{}: {:.*}%", plot_name, 1, position.y))
416413
/// }
414+
/// _ => None,
417415
/// })
418416
/// .show(ui, |plot_ui| plot_ui.line(line));
419417
/// # });
420418
/// ```
421419
#[inline]
422-
pub fn label_formatter(mut self, label_formatter: impl Fn(&str, &PlotPoint) -> String + 'a) -> Self {
420+
pub fn label_formatter(mut self, label_formatter: impl Fn(&HoverPosition<'_>) -> Option<String> + 'a) -> Self {
423421
self.label_formatter = Some(Box::new(label_formatter));
424422
self
425423
}
@@ -1601,7 +1599,7 @@ impl<'a> Plot<'a> {
16011599
items::rulers_and_tooltip_at_value(
16021600
&plot_ui.response,
16031601
value,
1604-
"",
1602+
None,
16051603
&plot,
16061604
&mut cursors,
16071605
&self.label_formatter,

examples/custom_axes/src/app.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ use eframe::egui::Response;
55
use egui_plot::AxisHints;
66
use egui_plot::GridInput;
77
use egui_plot::GridMark;
8+
use egui_plot::HoverPosition;
89
use egui_plot::Line;
910
use egui_plot::Plot;
10-
use egui_plot::PlotPoint;
1111
use egui_plot::PlotPoints;
1212

1313
#[derive(Default)]
@@ -101,14 +101,22 @@ impl CustomAxesExample {
101101
}
102102
};
103103

104-
let label_fmt = |_s: &str, val: &PlotPoint| {
105-
format!(
104+
let label_fmt = |pos: &HoverPosition<'_>| {
105+
let val = match pos {
106+
HoverPosition::NearDataPoint {
107+
plot_name: _,
108+
position,
109+
index: _,
110+
}
111+
| HoverPosition::Elsewhere { position } => position,
112+
};
113+
Some(format!(
106114
"Day {d}, {h}:{m:02}\n{p:.2}%",
107115
d = day(val.x),
108116
h = hour(val.x),
109117
m = minute(val.x),
110118
p = percent(val.y)
111-
)
119+
))
112120
};
113121

114122
let x_axes = vec![

0 commit comments

Comments
 (0)