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:
parent
d05379902c
commit
26885c20d0
2 changed files with 67 additions and 8 deletions
|
@ -7,7 +7,7 @@ use epaint::Mesh;
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use super::{PlotBounds, ScreenTransform};
|
use super::{CustomLabelFuncRef, PlotBounds, ScreenTransform};
|
||||||
use rect_elem::*;
|
use rect_elem::*;
|
||||||
use values::*;
|
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() {
|
let points = match self.geometry() {
|
||||||
PlotGeometry::Points(points) => points,
|
PlotGeometry::Points(points) => points,
|
||||||
PlotGeometry::None => {
|
PlotGeometry::None => {
|
||||||
|
@ -83,7 +89,7 @@ pub(super) trait PlotItem {
|
||||||
let pointer = plot.transform.position_from_value(&value);
|
let pointer = plot.transform.position_from_value(&value);
|
||||||
shapes.push(Shape::circle_filled(pointer, 3.0, line_color));
|
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)
|
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];
|
let bar = &self.bars[elem.index];
|
||||||
|
|
||||||
bar.add_shapes(plot.transform, true, shapes);
|
bar.add_shapes(plot.transform, true, shapes);
|
||||||
|
@ -1501,7 +1513,13 @@ impl PlotItem for BoxPlot {
|
||||||
find_closest_rect(&self.boxes, point, transform)
|
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];
|
let box_plot = &self.boxes[elem.index];
|
||||||
|
|
||||||
box_plot.add_shapes(plot.transform, true, shapes);
|
box_plot.add_shapes(plot.transform, true, shapes);
|
||||||
|
@ -1619,6 +1637,7 @@ pub(super) fn rulers_at_value(
|
||||||
name: &str,
|
name: &str,
|
||||||
plot: &PlotConfig<'_>,
|
plot: &PlotConfig<'_>,
|
||||||
shapes: &mut Vec<Shape>,
|
shapes: &mut Vec<Shape>,
|
||||||
|
custom_label_func: &CustomLabelFuncRef,
|
||||||
) {
|
) {
|
||||||
let line_color = rulers_color(plot.ui);
|
let line_color = rulers_color(plot.ui);
|
||||||
if plot.show_x {
|
if plot.show_x {
|
||||||
|
@ -1638,7 +1657,9 @@ pub(super) fn rulers_at_value(
|
||||||
let scale = plot.transform.dvalue_dpos();
|
let scale = plot.transform.dvalue_dpos();
|
||||||
let x_decimals = ((-scale[0].abs().log10()).ceil().at_least(0.0) as usize).at_most(6);
|
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);
|
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!(
|
format!(
|
||||||
"{}x = {:.*}\ny = {:.*}",
|
"{}x = {:.*}\ny = {:.*}",
|
||||||
prefix, x_decimals, value.x, y_decimals, value.y
|
prefix, x_decimals, value.x, y_decimals, value.y
|
||||||
|
|
|
@ -18,6 +18,9 @@ mod items;
|
||||||
mod legend;
|
mod legend;
|
||||||
mod transform;
|
mod transform;
|
||||||
|
|
||||||
|
type CustomLabelFunc = dyn Fn(&str, &Value) -> String;
|
||||||
|
type CustomLabelFuncRef = Option<Box<CustomLabelFunc>>;
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Information about the plot that has to persist between frames.
|
/// Information about the plot that has to persist between frames.
|
||||||
|
@ -76,6 +79,7 @@ pub struct Plot {
|
||||||
|
|
||||||
show_x: bool,
|
show_x: bool,
|
||||||
show_y: bool,
|
show_y: bool,
|
||||||
|
custom_label_func: CustomLabelFuncRef,
|
||||||
legend_config: Option<Legend>,
|
legend_config: Option<Legend>,
|
||||||
show_background: bool,
|
show_background: bool,
|
||||||
show_axes: [bool; 2],
|
show_axes: [bool; 2],
|
||||||
|
@ -102,6 +106,7 @@ impl Plot {
|
||||||
|
|
||||||
show_x: true,
|
show_x: true,
|
||||||
show_y: true,
|
show_y: true,
|
||||||
|
custom_label_func: None,
|
||||||
legend_config: None,
|
legend_config: None,
|
||||||
show_background: true,
|
show_background: true,
|
||||||
show_axes: [true; 2],
|
show_axes: [true; 2],
|
||||||
|
@ -182,6 +187,35 @@ impl Plot {
|
||||||
self
|
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.
|
/// Expand bounds to include the given x value.
|
||||||
/// For instance, to always show the y axis, call `plot.include_x(0.0)`.
|
/// 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 {
|
pub fn include_x(mut self, x: impl Into<f64>) -> Self {
|
||||||
|
@ -235,6 +269,7 @@ impl Plot {
|
||||||
view_aspect,
|
view_aspect,
|
||||||
mut show_x,
|
mut show_x,
|
||||||
mut show_y,
|
mut show_y,
|
||||||
|
custom_label_func,
|
||||||
legend_config,
|
legend_config,
|
||||||
show_background,
|
show_background,
|
||||||
show_axes,
|
show_axes,
|
||||||
|
@ -406,6 +441,7 @@ impl Plot {
|
||||||
items,
|
items,
|
||||||
show_x,
|
show_x,
|
||||||
show_y,
|
show_y,
|
||||||
|
custom_label_func,
|
||||||
show_axes,
|
show_axes,
|
||||||
transform: transform.clone(),
|
transform: transform.clone(),
|
||||||
};
|
};
|
||||||
|
@ -613,6 +649,7 @@ struct PreparedPlot {
|
||||||
items: Vec<Box<dyn PlotItem>>,
|
items: Vec<Box<dyn PlotItem>>,
|
||||||
show_x: bool,
|
show_x: bool,
|
||||||
show_y: bool,
|
show_y: bool,
|
||||||
|
custom_label_func: CustomLabelFuncRef,
|
||||||
show_axes: [bool; 2],
|
show_axes: [bool; 2],
|
||||||
transform: ScreenTransform,
|
transform: ScreenTransform,
|
||||||
}
|
}
|
||||||
|
@ -731,6 +768,7 @@ impl PreparedPlot {
|
||||||
transform,
|
transform,
|
||||||
show_x,
|
show_x,
|
||||||
show_y,
|
show_y,
|
||||||
|
custom_label_func,
|
||||||
items,
|
items,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
@ -760,10 +798,10 @@ impl PreparedPlot {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((item, elem)) = closest {
|
if let Some((item, elem)) = closest {
|
||||||
item.on_hover(elem, shapes, &plot);
|
item.on_hover(elem, shapes, &plot, custom_label_func);
|
||||||
} else {
|
} else {
|
||||||
let value = transform.value_from_position(pointer);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue