From 26885c20d0c40786b9bf8d24986c83fe8ca76e08 Mon Sep 17 00:00:00 2001 From: "Ivgeni \"Iv\" Segal" Date: Sat, 25 Dec 2021 07:29:29 -0800 Subject: [PATCH] Add ability to customize the display of hover plot labels (#934) * Add ability to customize the display of hover plot labels * Ergonomic enhancement to plot hover label function * Use Option instead of empty string for custom hover label name arg * Revert "Use Option instead of empty string for custom hover label name arg" This reverts commit 296caebb74b7ee993fbff97187791180d16708af. Co-authored-by: Ivgeni Segal --- egui/src/widgets/plot/items/mod.rs | 33 ++++++++++++++++++----- egui/src/widgets/plot/mod.rs | 42 ++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/egui/src/widgets/plot/items/mod.rs b/egui/src/widgets/plot/items/mod.rs index 30a2be70c297..87d27f38491a 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::*; -use super::{PlotBounds, ScreenTransform}; +use super::{CustomLabelFuncRef, 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: &CustomLabelFuncRef, + ) { 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<'_>, + _: &CustomLabelFuncRef, + ) { 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<'_>, + _: &CustomLabelFuncRef, + ) { 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: &CustomLabelFuncRef, ) { 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 b37b0447486b..3dbf71fda2d2 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -18,6 +18,9 @@ mod items; mod legend; mod transform; +type CustomLabelFunc = dyn Fn(&str, &Value) -> String; +type CustomLabelFuncRef = Option>; + // ---------------------------------------------------------------------------- /// Information about the plot that has to persist between frames. @@ -76,6 +79,7 @@ pub struct Plot { show_x: bool, show_y: bool, + custom_label_func: CustomLabelFuncRef, legend_config: Option, show_background: bool, show_axes: [bool; 2], @@ -102,6 +106,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 +187,35 @@ 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(|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 String>( + mut self, + custom_lebel_func: F, + ) -> Self { + self.custom_label_func = Some(Box::new(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 +269,7 @@ impl Plot { view_aspect, mut show_x, mut show_y, + custom_label_func, legend_config, show_background, show_axes, @@ -406,6 +441,7 @@ impl Plot { items, show_x, show_y, + custom_label_func, show_axes, transform: transform.clone(), }; @@ -613,6 +649,7 @@ struct PreparedPlot { items: Vec>, show_x: bool, show_y: bool, + custom_label_func: CustomLabelFuncRef, show_axes: [bool; 2], transform: ScreenTransform, } @@ -731,6 +768,7 @@ impl PreparedPlot { transform, show_x, show_y, + custom_label_func, items, .. } = self; @@ -760,10 +798,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); } } }