Skip to content

Commit ed3d2c2

Browse files
authored
Spit crosshair and labels API (#221)
Now you can have one without the other. Addresses #217 # Public API changes The PlotItem label formatter is now optional: ``` -pub fn egui_plot::PlotItem::on_hover(&self, plot_area_response: &egui::response::Response, elem: egui_plot::ClosestElem, shapes: &mut alloc::vec::Vec<epaint::shapes::shape::Shape>, cursors: &mut alloc::vec::Vec<egui_plot::Cursor>, plot: &egui_plot::PlotConfig<'_>, label_formatter: &egui_plot::LabelFormatter<'_>) +pub fn egui_plot::PlotItem::on_hover(&self, plot_area_response: &egui::response::Response, elem: egui_plot::ClosestElem, shapes: &mut alloc::vec::Vec<epaint::shapes::shape::Shape>, cursors: &mut alloc::vec::Vec<egui_plot::Cursor>, plot: &egui_plot::PlotConfig<'_>, label_formatter: &core::option::Option<egui_plot::LabelFormatter<'_>>) ``` Allow to specify crosshair: ``` +pub fn egui_plot::Plot<'a>::show_crosshair(self, show: bool) -> Self +pub egui_plot::PlotConfig::show_crosshair: bool ``` Add default formatter: ``` +pub fn egui_plot::default_label_formatter(name: &str, value: &egui_plot::PlotPoint) -> alloc::string::String ``` And by default, labels are not shown. You have to enable it by passing `label_formatter(you_label_formatter)` You can use the `default_label_formatter`.
1 parent af87f3a commit ed3d2c2

File tree

8 files changed

+66
-59
lines changed

8 files changed

+66
-59
lines changed

egui_plot/src/items/bar_chart.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ impl PlotItem for BarChart {
210210
shapes: &mut Vec<Shape>,
211211
cursors: &mut Vec<Cursor>,
212212
plot: &PlotConfig<'_>,
213-
_: &LabelFormatter<'_>,
213+
_: &Option<LabelFormatter<'_>>,
214214
) {
215215
let bar = &self.bars[elem.index];
216216

egui_plot/src/items/box_plot.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ impl PlotItem for BoxPlot {
176176
shapes: &mut Vec<Shape>,
177177
cursors: &mut Vec<Cursor>,
178178
plot: &PlotConfig<'_>,
179-
_: &LabelFormatter<'_>,
179+
_: &Option<LabelFormatter<'_>>,
180180
) {
181181
let box_plot = &self.boxes[elem.index];
182182

egui_plot/src/items/heatmap.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ impl PlotItem for Heatmap {
460460
shapes: &mut Vec<Shape>,
461461
_cursors: &mut Vec<Cursor>,
462462
plot: &PlotConfig<'_>,
463-
_: &LabelFormatter<'_>,
463+
_: &Option<LabelFormatter<'_>>,
464464
) {
465465
let (rect, color, text) = self.tile_view_info(plot.ui, plot.transform, elem.index);
466466
let mut mesh = Mesh::default();

egui_plot/src/items/mod.rs

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use std::ops::RangeInclusive;
99
use egui::Align2;
1010
use egui::Color32;
1111
use egui::Id;
12-
use egui::NumExt as _;
1312
use egui::PopupAnchor;
1413
use egui::Pos2;
1514
use egui::Shape;
@@ -92,6 +91,9 @@ pub struct PlotConfig<'a> {
9291

9392
/// Whether to show the y-axis value.
9493
pub show_y: bool,
94+
95+
/// Whether to show the crosshair rulers.
96+
pub show_crosshair: bool,
9597
}
9698

9799
/// Trait shared by things that can be drawn in the plot.
@@ -172,7 +174,7 @@ pub trait PlotItem {
172174
shapes: &mut Vec<Shape>,
173175
cursors: &mut Vec<Cursor>,
174176
plot: &PlotConfig<'_>,
175-
label_formatter: &LabelFormatter<'_>,
177+
label_formatter: &Option<LabelFormatter<'_>>,
176178
) {
177179
let points = match self.geometry() {
178180
PlotGeometry::Points(points) => points,
@@ -273,44 +275,28 @@ pub(super) fn rulers_and_tooltip_at_value(
273275
name: &str,
274276
plot: &PlotConfig<'_>,
275277
cursors: &mut Vec<Cursor>,
276-
label_formatter: &LabelFormatter<'_>,
278+
label_formatter: &Option<LabelFormatter<'_>>,
277279
) {
278-
if plot.show_x {
279-
cursors.push(Cursor::Vertical { x: value.x });
280-
}
281-
if plot.show_y {
282-
cursors.push(Cursor::Horizontal { y: value.y });
283-
}
284-
285-
let text = if let Some(custom_label) = label_formatter {
286-
let label = custom_label(name, &value);
287-
if label.is_empty() {
288-
return;
280+
// Add crosshair rulers if enabled
281+
if plot.show_crosshair {
282+
if plot.show_x {
283+
cursors.push(Cursor::Vertical { x: value.x });
289284
}
290-
label
291-
} else {
292-
let prefix = if name.is_empty() {
293-
String::new()
294-
} else {
295-
format!("{name}\n")
296-
};
297-
let scale = plot.transform.dvalue_dpos();
298-
let x_decimals = ((-scale[0].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6);
299-
let y_decimals = ((-scale[1].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6);
300-
if plot.show_x && plot.show_y {
301-
format!(
302-
"{}x = {:.*}\ny = {:.*}",
303-
prefix, x_decimals, value.x, y_decimals, value.y
304-
)
305-
} else if plot.show_x {
306-
format!("{}x = {:.*}", prefix, x_decimals, value.x)
307-
} else if plot.show_y {
308-
format!("{}y = {:.*}", prefix, y_decimals, value.y)
309-
} else {
310-
unreachable!()
285+
if plot.show_y {
286+
cursors.push(Cursor::Horizontal { y: value.y });
311287
}
288+
}
289+
290+
// Only show tooltip if label_formatter is provided
291+
let Some(custom_label) = label_formatter else {
292+
return;
312293
};
313294

295+
let text = custom_label(name, &value);
296+
if text.is_empty() {
297+
return;
298+
}
299+
314300
// We show the tooltip as soon as we're hovering the plot area:
315301
let mut tooltip = egui::Tooltip::always_open(
316302
plot_area_response.ctx.clone(),

egui_plot/src/label.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,15 @@ pub fn format_number(number: f64, num_decimals: usize) -> String {
1919
type LabelFormatterFn<'a> = dyn Fn(&str, &PlotPoint) -> String + 'a;
2020

2121
/// Optional label formatter function for customizing hover labels.
22-
pub type LabelFormatter<'a> = Option<Box<LabelFormatterFn<'a>>>;
22+
pub type LabelFormatter<'a> = Box<LabelFormatterFn<'a>>;
23+
24+
/// Default label formatter that shows the x and y coordinates with 3 decimal
25+
/// 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)
33+
}

egui_plot/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub use crate::items::Span;
6262
pub use crate::items::Text;
6363
pub use crate::items::VLine;
6464
pub use crate::label::LabelFormatter;
65+
pub use crate::label::default_label_formatter;
6566
pub use crate::label::format_number;
6667
pub use crate::memory::PlotMemory;
6768
pub use crate::overlays::ColorConflictHandling;

egui_plot/src/plot.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ pub struct Plot<'a> {
115115

116116
show_x: bool,
117117
show_y: bool,
118-
label_formatter: LabelFormatter<'a>,
118+
show_crosshair: bool,
119+
label_formatter: Option<LabelFormatter<'a>>,
119120
coordinates_formatter: Option<(Corner, CoordinatesFormatter<'a>)>,
120121
x_axes: Vec<AxisHints<'a>>, // default x axes
121122
y_axes: Vec<AxisHints<'a>>, // default y axes
@@ -166,6 +167,7 @@ impl<'a> Plot<'a> {
166167

167168
show_x: true,
168169
show_y: true,
170+
show_crosshair: true,
169171
label_formatter: None,
170172
coordinates_formatter: None,
171173
x_axes: vec![AxisHints::new(Axis::X)],
@@ -272,6 +274,13 @@ impl<'a> Plot<'a> {
272274
self
273275
}
274276

277+
/// Show the crosshair when hovering. Default: `true`.
278+
#[inline]
279+
pub fn show_crosshair(mut self, show: bool) -> Self {
280+
self.show_crosshair = show;
281+
self
282+
}
283+
275284
/// Always keep the X-axis centered. Default: `false`.
276285
#[inline]
277286
pub fn center_x_axis(mut self, on: bool) -> Self {
@@ -1543,6 +1552,7 @@ impl<'a> Plot<'a> {
15431552
transform,
15441553
show_x: show_xy.x,
15451554
show_y: show_xy.y,
1555+
show_crosshair: self.show_crosshair,
15461556
};
15471557

15481558
let mut cursors = Vec::new();

examples/lines/src/app.rs

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use egui_plot::Line;
1515
use egui_plot::LineStyle;
1616
use egui_plot::Plot;
1717
use egui_plot::PlotPoints;
18+
use egui_plot::default_label_formatter;
1819

1920
#[derive(Clone, Copy, PartialEq)]
2021
pub struct LineExample {
@@ -27,6 +28,8 @@ pub struct LineExample {
2728
coordinates: bool,
2829
show_axes: bool,
2930
show_grid: bool,
31+
show_crosshair: bool,
32+
show_labels: bool,
3033
line_style: LineStyle,
3134
gradient: bool,
3235
gradient_fill: bool,
@@ -46,6 +49,8 @@ impl Default for LineExample {
4649
coordinates: true,
4750
show_axes: true,
4851
show_grid: true,
52+
show_crosshair: true,
53+
show_labels: true,
4954
line_style: LineStyle::Solid,
5055
gradient: false,
5156
gradient_fill: false,
@@ -61,31 +66,25 @@ impl LineExample {
6166
ui.group(|ui| {
6267
ui.vertical(|ui| {
6368
ui.label("Circle:");
64-
ui.add(
65-
egui::DragValue::new(&mut self.circle_radius)
66-
.speed(0.1)
67-
.range(0.0..=f64::INFINITY)
68-
.prefix("r: "),
69-
);
69+
ui.add(egui::DragValue::new(&mut self.circle_radius).speed(0.1).prefix("r: "));
7070
ui.horizontal(|ui| {
7171
ui.add(egui::DragValue::new(&mut self.circle_center.x).speed(0.1).prefix("x: "));
7272
ui.add(egui::DragValue::new(&mut self.circle_center.y).speed(1.0).prefix("y: "));
7373
});
74-
})
74+
});
7575
});
7676
ui.vertical(|ui| {
7777
ui.checkbox(&mut self.show_axes, "Show axes");
7878
ui.checkbox(&mut self.show_grid, "Show grid");
79-
ui.checkbox(&mut self.coordinates, "Show coordinates on hover")
80-
.on_hover_text("Can take a custom formatting function.");
79+
ui.checkbox(&mut self.show_crosshair, "Show crosshair");
80+
ui.checkbox(&mut self.coordinates, "Show coordinates");
81+
ui.checkbox(&mut self.show_labels, "Show hover labels");
8182
});
8283
ui.vertical(|ui| {
8384
ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
8485
ui.checkbox(&mut self.animate, "Animate");
85-
ui.checkbox(&mut self.square, "Square view")
86-
.on_hover_text("Always keep the viewport square.");
87-
ui.checkbox(&mut self.proportional, "Proportional data axes")
88-
.on_hover_text("Tick are the same size on both axes.");
86+
ui.checkbox(&mut self.square, "Square view");
87+
ui.checkbox(&mut self.proportional, "Proportional data axes");
8988
ComboBox::from_label("Line style")
9089
.selected_text(self.line_style.to_string())
9190
.show_ui(ui, |ui| {
@@ -103,8 +102,6 @@ impl LineExample {
103102
ui.vertical(|ui| {
104103
ui.checkbox(&mut self.gradient, "Gradient line");
105104
ui.add_enabled(self.gradient, Checkbox::new(&mut self.gradient_fill, "Gradient fill"));
106-
});
107-
ui.vertical(|ui| {
108105
ui.checkbox(&mut self.invert_x, "Invert X axis");
109106
ui.checkbox(&mut self.invert_y, "Invert Y axis");
110107
});
@@ -113,10 +110,9 @@ impl LineExample {
113110
}
114111

115112
fn circle(&self) -> Line<'_> {
116-
let n = 512;
117-
let points: PlotPoints<'_> = (0..=n)
113+
let points: PlotPoints<'_> = (0..=512)
118114
.map(|i| {
119-
let t = egui::remap(i as f64, 0.0..=(n as f64), 0.0..=TAU);
115+
let t = egui::remap(i as f64, 0.0..=512.0, 0.0..=TAU);
120116
[
121117
self.circle_radius * t.cos() + self.circle_center.x as f64,
122118
self.circle_radius * t.sin() + self.circle_center.y as f64,
@@ -166,11 +162,11 @@ impl LineExample {
166162
ui.ctx().request_repaint();
167163
self.time += ui.input(|i| i.unstable_dt).at_most(1.0 / 30.0) as f64;
168164
}
169-
170165
let mut plot = Plot::new("lines_demo")
171166
.legend(Legend::default().title("Lines"))
172167
.show_axes(self.show_axes)
173168
.show_grid(self.show_grid)
169+
.show_crosshair(self.show_crosshair)
174170
.invert_x(self.invert_x)
175171
.invert_y(self.invert_y);
176172
if self.square {
@@ -182,6 +178,9 @@ impl LineExample {
182178
if self.coordinates {
183179
plot = plot.coordinates_formatter(Corner::LeftBottom, CoordinatesFormatter::default());
184180
}
181+
if self.show_labels {
182+
plot = plot.label_formatter(default_label_formatter);
183+
}
185184
plot.show(ui, |plot_ui| {
186185
plot_ui.line(self.circle());
187186
plot_ui.line(self.sin());

0 commit comments

Comments
 (0)