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 <ivgeni.segal@tovutiteam.com>
This commit is contained in:
Ivgeni "Iv" Segal 2021-12-25 07:29:29 -08:00 committed by GitHub
parent d05379902c
commit 26885c20d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 67 additions and 8 deletions

View file

@ -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<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 => {
@ -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<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);
@ -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);
@ -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 {
@ -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

View file

@ -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.
@ -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],
@ -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<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 {
@ -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<Box<dyn PlotItem>>,
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);
}
}
}