1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-01 14:20:04 -04:00

Formatter for plot axis labels (#1130)

* Fix Orientation not exposed, although there are public fields with its type

* Implement formatters for X/Y axis labels

* Use array instead of separate X/Y formatters

* Swap axis formatters if charts are horizontal

* Review suggestions
This commit is contained in:
Jan Haller
2022-01-24 22:34:00 +01:00
committed by GitHub
parent 3418eb5d35
commit 366f544655
5 changed files with 97 additions and 17 deletions

View File

@@ -9,11 +9,11 @@ use crate::*;
use super::{CustomLabelFuncRef, PlotBounds, ScreenTransform};
use rect_elem::*;
use values::*;
use values::{ClosestElem, PlotGeometry};
pub use bar::Bar;
pub use box_elem::{BoxElem, BoxSpread};
pub use values::{LineStyle, MarkerShape, Value, Values};
pub use values::{LineStyle, MarkerShape, Orientation, Value, Values};
mod bar;
mod box_elem;

View File

@@ -125,6 +125,7 @@ impl ToString for LineStyle {
// ----------------------------------------------------------------------------
/// Determines whether a plot element is vertically or horizontally oriented.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Orientation {
Horizontal,

View File

@@ -10,7 +10,7 @@ use transform::{PlotBounds, ScreenTransform};
pub use items::{
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape,
PlotImage, Points, Polygon, Text, VLine, Value, Values,
Orientation, PlotImage, Points, Polygon, Text, VLine, Value, Values,
};
pub use legend::{Corner, Legend};
@@ -21,6 +21,9 @@ mod transform;
type CustomLabelFunc = dyn Fn(&str, &Value) -> String;
type CustomLabelFuncRef = Option<Box<CustomLabelFunc>>;
type AxisFormatterFn = dyn Fn(f64) -> String;
type AxisFormatter = Option<Box<AxisFormatterFn>>;
// ----------------------------------------------------------------------------
/// Information about the plot that has to persist between frames.
@@ -80,6 +83,7 @@ pub struct Plot {
show_x: bool,
show_y: bool,
custom_label_func: CustomLabelFuncRef,
axis_formatters: [AxisFormatter; 2],
legend_config: Option<Legend>,
show_background: bool,
show_axes: [bool; 2],
@@ -107,6 +111,7 @@ impl Plot {
show_x: true,
show_y: true,
custom_label_func: None,
axis_formatters: [None, None], // [None; 2] requires Copy
legend_config: None,
show_background: true,
show_axes: [true; 2],
@@ -208,11 +213,35 @@ impl Plot {
/// .show(ui, |plot_ui| plot_ui.line(line));
/// # });
/// ```
pub fn custom_label_func<F: 'static + Fn(&str, &Value) -> String>(
pub fn custom_label_func(
mut self,
custom_lebel_func: F,
custom_label_func: impl Fn(&str, &Value) -> String + 'static,
) -> Self {
self.custom_label_func = Some(Box::new(custom_lebel_func));
self.custom_label_func = Some(Box::new(custom_label_func));
self
}
/// Provide a function to customize the labels for the X axis.
///
/// This is useful for custom input domains, e.g. date/time.
///
/// If axis labels should not appear for certain values or beyond a certain zoom/resolution,
/// the formatter function can return empty strings. This is also useful if your domain is
/// discrete (e.g. only full days in a calendar).
pub fn x_axis_formatter(mut self, func: impl Fn(f64) -> String + 'static) -> Self {
self.axis_formatters[0] = Some(Box::new(func));
self
}
/// Provide a function to customize the labels for the Y axis.
///
/// This is useful for custom value representation, e.g. percentage or units.
///
/// If axis labels should not appear for certain values or beyond a certain zoom/resolution,
/// the formatter function can return empty strings. This is also useful if your Y values are
/// discrete (e.g. only integers).
pub fn y_axis_formatter(mut self, func: impl Fn(f64) -> String + 'static) -> Self {
self.axis_formatters[1] = Some(Box::new(func));
self
}
@@ -270,6 +299,7 @@ impl Plot {
mut show_x,
mut show_y,
custom_label_func,
axis_formatters,
legend_config,
show_background,
show_axes,
@@ -442,6 +472,7 @@ impl Plot {
show_x,
show_y,
custom_label_func,
axis_formatters,
show_axes,
transform: transform.clone(),
};
@@ -650,6 +681,7 @@ struct PreparedPlot {
show_x: bool,
show_y: bool,
custom_label_func: CustomLabelFuncRef,
axis_formatters: [AxisFormatter; 2],
show_axes: [bool; 2],
transform: ScreenTransform,
}
@@ -680,7 +712,11 @@ impl PreparedPlot {
}
fn paint_axis(&self, ui: &Ui, axis: usize, shapes: &mut Vec<Shape>) {
let Self { transform, .. } = self;
let Self {
transform,
axis_formatters,
..
} = self;
let bounds = transform.bounds();
@@ -740,18 +776,26 @@ impl PreparedPlot {
if text_alpha > 0.0 {
let color = color_from_alpha(ui, text_alpha);
let text = emath::round_to_decimals(value_main, 5).to_string(); // hack
let galley = ui.painter().layout_no_wrap(text, font_id.clone(), color);
let text: String = if let Some(formatter) = axis_formatters[axis].as_deref() {
formatter(value_main)
} else {
emath::round_to_decimals(value_main, 5).to_string() // hack
};
let mut text_pos = pos_in_gui + vec2(1.0, -galley.size().y);
// Custom formatters can return empty string to signal "no label at this resolution"
if !text.is_empty() {
let galley = ui.painter().layout_no_wrap(text, font_id.clone(), color);
// Make sure we see the labels, even if the axis is off-screen:
text_pos[1 - axis] = text_pos[1 - axis]
.at_most(transform.frame().max[1 - axis] - galley.size()[1 - axis] - 2.0)
.at_least(transform.frame().min[1 - axis] + 1.0);
let mut text_pos = pos_in_gui + vec2(1.0, -galley.size().y);
shapes.push(Shape::galley(text_pos, galley));
// Make sure we see the labels, even if the axis is off-screen:
text_pos[1 - axis] = text_pos[1 - axis]
.at_most(transform.frame().max[1 - axis] - galley.size()[1 - axis] - 2.0)
.at_least(transform.frame().min[1 - axis] + 1.0);
shapes.push(Shape::galley(text_pos, galley));
}
}
}