From 4f17c3f61a45fec7cbcb8bd298cd32caa721ff73 Mon Sep 17 00:00:00 2001 From: Ivgeni Segal Date: Sun, 5 Dec 2021 22:17:23 -0800 Subject: [PATCH] Add ability to customize the display of hover plot labels --- egui/src/widgets/plot/items/mod.rs | 33 +++++++++++++++++++++----- egui/src/widgets/plot/mod.rs | 38 ++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/egui/src/widgets/plot/items/mod.rs b/egui/src/widgets/plot/items/mod.rs index 9f935ca7ed89..a5b62e976f21 100644 --- a/egui/src/widgets/plot/items/mod.rs +++ b/egui/src/widgets/plot/items/mod.rs @@ -7,7 +7,7 @@ use epaint::Mesh; use crate::util::float_ord::FloatOrd; use crate::*; -use super::{PlotBounds, ScreenTransform}; +use super::{CustomLabelFunc, PlotBounds, ScreenTransform}; use rect_elem::*; use values::*; @@ -61,7 +61,13 @@ pub(super) trait PlotItem { } } - fn on_hover(&self, elem: ClosestElem, shapes: &mut Vec, plot: &PlotConfig<'_>) { + fn on_hover( + &self, + elem: ClosestElem, + shapes: &mut Vec, + plot: &PlotConfig<'_>, + custom_label_func: &CustomLabelFunc, + ) { let points = match self.geometry() { PlotGeometry::Points(points) => points, PlotGeometry::None => { @@ -83,7 +89,7 @@ pub(super) trait PlotItem { let pointer = plot.transform.position_from_value(&value); shapes.push(Shape::circle_filled(pointer, 3.0, line_color)); - rulers_at_value(pointer, value, self.name(), plot, shapes); + rulers_at_value(pointer, value, self.name(), plot, shapes, custom_label_func); } } @@ -1365,7 +1371,13 @@ impl PlotItem for BarChart { find_closest_rect(&self.bars, point, transform) } - fn on_hover(&self, elem: ClosestElem, shapes: &mut Vec, plot: &PlotConfig<'_>) { + fn on_hover( + &self, + elem: ClosestElem, + shapes: &mut Vec, + plot: &PlotConfig<'_>, + _: &CustomLabelFunc, + ) { let bar = &self.bars[elem.index]; bar.add_shapes(plot.transform, true, shapes); @@ -1501,7 +1513,13 @@ impl PlotItem for BoxPlot { find_closest_rect(&self.boxes, point, transform) } - fn on_hover(&self, elem: ClosestElem, shapes: &mut Vec, plot: &PlotConfig<'_>) { + fn on_hover( + &self, + elem: ClosestElem, + shapes: &mut Vec, + plot: &PlotConfig<'_>, + _: &CustomLabelFunc, + ) { let box_plot = &self.boxes[elem.index]; box_plot.add_shapes(plot.transform, true, shapes); @@ -1619,6 +1637,7 @@ pub(super) fn rulers_at_value( name: &str, plot: &PlotConfig<'_>, shapes: &mut Vec, + custom_label_func: &CustomLabelFunc, ) { let line_color = rulers_color(plot.ui); if plot.show_x { @@ -1638,7 +1657,9 @@ pub(super) fn rulers_at_value( let scale = plot.transform.dvalue_dpos(); let x_decimals = ((-scale[0].abs().log10()).ceil().at_least(0.0) as usize).at_most(6); let y_decimals = ((-scale[1].abs().log10()).ceil().at_least(0.0) as usize).at_most(6); - if plot.show_x && plot.show_y { + if let Some(custom_label) = custom_label_func { + custom_label(name, &value) + } else if plot.show_x && plot.show_y { format!( "{}x = {:.*}\ny = {:.*}", prefix, x_decimals, value.x, y_decimals, value.y diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index 768c9f18b8f8..47eca2ea1f65 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -18,6 +18,8 @@ mod items; mod legend; mod transform; +type CustomLabelFunc = Option String>>; + // ---------------------------------------------------------------------------- /// Information about the plot that has to persist between frames. @@ -76,6 +78,7 @@ pub struct Plot { show_x: bool, show_y: bool, + custom_label_func: CustomLabelFunc, legend_config: Option, show_background: bool, show_axes: [bool; 2], @@ -102,6 +105,7 @@ impl Plot { show_x: true, show_y: true, + custom_label_func: None, legend_config: None, show_background: true, show_axes: [true; 2], @@ -182,6 +186,32 @@ impl Plot { self } + /// Provide a function to customize the on-hovel label for the x and y axis + /// + /// ``` + /// # egui::__run_test_ui(|ui| { + /// use egui::plot::{Line, Plot, Value, Values}; + /// let sin = (0..1000).map(|i| { + /// let x = i as f64 * 0.01; + /// Value::new(x, x.sin()) + /// }); + /// let line = Line::new(Values::from_values_iter(sin)); + /// Plot::new("my_plot").view_aspect(2.0) + /// .custom_label_func(Some(Box::new(|name, value| { + /// if !name.is_empty() { + /// format!("{}: {:.*}%", name, 1, value.y).to_string() + /// } else { + /// "".to_string() + /// } + /// }))) + /// .show(ui, |plot_ui| plot_ui.line(line)); + /// # }); + /// ``` + pub fn custom_label_func(mut self, custom_lebel_func: CustomLabelFunc) -> Self { + self.custom_label_func = custom_lebel_func; + self + } + /// Expand bounds to include the given x value. /// For instance, to always show the y axis, call `plot.include_x(0.0)`. pub fn include_x(mut self, x: impl Into) -> Self { @@ -235,6 +265,7 @@ impl Plot { view_aspect, mut show_x, mut show_y, + custom_label_func, legend_config, show_background, show_axes, @@ -406,6 +437,7 @@ impl Plot { items, show_x, show_y, + custom_label_func, show_axes, transform: transform.clone(), }; @@ -613,6 +645,7 @@ struct PreparedPlot { items: Vec>, show_x: bool, show_y: bool, + custom_label_func: CustomLabelFunc, show_axes: [bool; 2], transform: ScreenTransform, } @@ -731,6 +764,7 @@ impl PreparedPlot { transform, show_x, show_y, + custom_label_func, items, .. } = self; @@ -760,10 +794,10 @@ impl PreparedPlot { }; if let Some((item, elem)) = closest { - item.on_hover(elem, shapes, &plot); + item.on_hover(elem, shapes, &plot, custom_label_func); } else { let value = transform.value_from_position(pointer); - items::rulers_at_value(pointer, value, "", &plot, shapes); + items::rulers_at_value(pointer, value, "", &plot, shapes, custom_label_func); } } }