Skip to content

Commit

Permalink
Add ability to customize the display of hover plot labels (#934)
Browse files Browse the repository at this point in the history
* 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 296caeb.

Co-authored-by: Ivgeni Segal <ivgeni.segal@tovutiteam.com>
  • Loading branch information
isegal and Ivgeni Segal authored Dec 25, 2021
1 parent d053799 commit 26885c2
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 8 deletions.
33 changes: 27 additions & 6 deletions egui/src/widgets/plot/items/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use epaint::Mesh;

use crate::*;

use super::{PlotBounds, ScreenTransform};
use super::{CustomLabelFuncRef, PlotBounds, ScreenTransform};
use rect_elem::*;
use values::*;

Expand Down Expand Up @@ -61,7 +61,13 @@ pub(super) trait PlotItem {
}
}

fn on_hover(&self, elem: ClosestElem, shapes: &mut Vec<Shape>, plot: &PlotConfig<'_>) {
fn on_hover(
&self,
elem: ClosestElem,
shapes: &mut Vec<Shape>,
plot: &PlotConfig<'_>,
custom_label_func: &CustomLabelFuncRef,
) {
let points = match self.geometry() {
PlotGeometry::Points(points) => points,
PlotGeometry::None => {
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -1365,7 +1371,13 @@ impl PlotItem for BarChart {
find_closest_rect(&self.bars, point, transform)
}

fn on_hover(&self, elem: ClosestElem, shapes: &mut Vec<Shape>, plot: &PlotConfig<'_>) {
fn on_hover(
&self,
elem: ClosestElem,
shapes: &mut Vec<Shape>,
plot: &PlotConfig<'_>,
_: &CustomLabelFuncRef,
) {
let bar = &self.bars[elem.index];

bar.add_shapes(plot.transform, true, shapes);
Expand Down Expand Up @@ -1501,7 +1513,13 @@ impl PlotItem for BoxPlot {
find_closest_rect(&self.boxes, point, transform)
}

fn on_hover(&self, elem: ClosestElem, shapes: &mut Vec<Shape>, plot: &PlotConfig<'_>) {
fn on_hover(
&self,
elem: ClosestElem,
shapes: &mut Vec<Shape>,
plot: &PlotConfig<'_>,
_: &CustomLabelFuncRef,
) {
let box_plot = &self.boxes[elem.index];

box_plot.add_shapes(plot.transform, true, shapes);
Expand Down Expand Up @@ -1619,6 +1637,7 @@ pub(super) fn rulers_at_value(
name: &str,
plot: &PlotConfig<'_>,
shapes: &mut Vec<Shape>,
custom_label_func: &CustomLabelFuncRef,
) {
let line_color = rulers_color(plot.ui);
if plot.show_x {
Expand All @@ -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
Expand Down
42 changes: 40 additions & 2 deletions egui/src/widgets/plot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ mod items;
mod legend;
mod transform;

type CustomLabelFunc = dyn Fn(&str, &Value) -> String;
type CustomLabelFuncRef = Option<Box<CustomLabelFunc>>;

// ----------------------------------------------------------------------------

/// Information about the plot that has to persist between frames.
Expand Down Expand Up @@ -76,6 +79,7 @@ pub struct Plot {

show_x: bool,
show_y: bool,
custom_label_func: CustomLabelFuncRef,
legend_config: Option<Legend>,
show_background: bool,
show_axes: [bool; 2],
Expand All @@ -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],
Expand Down Expand Up @@ -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<F: 'static + Fn(&str, &Value) -> 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<f64>) -> Self {
Expand Down Expand Up @@ -235,6 +269,7 @@ impl Plot {
view_aspect,
mut show_x,
mut show_y,
custom_label_func,
legend_config,
show_background,
show_axes,
Expand Down Expand Up @@ -406,6 +441,7 @@ impl Plot {
items,
show_x,
show_y,
custom_label_func,
show_axes,
transform: transform.clone(),
};
Expand Down Expand Up @@ -613,6 +649,7 @@ struct PreparedPlot {
items: Vec<Box<dyn PlotItem>>,
show_x: bool,
show_y: bool,
custom_label_func: CustomLabelFuncRef,
show_axes: [bool; 2],
transform: ScreenTransform,
}
Expand Down Expand Up @@ -731,6 +768,7 @@ impl PreparedPlot {
transform,
show_x,
show_y,
custom_label_func,
items,
..
} = self;
Expand Down Expand Up @@ -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);
}
}
}

1 comment on commit 26885c2

@galop1n
Copy link

@galop1n galop1n commented on 26885c2 Jan 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make more sense to have two custom label functions, one for x and one for y, in order to use them also for the axis. Right now the axis cannot be customize. The typical example is to put dates on the X axis, and append an unit on the Y axis

Please sign in to comment.