From a68c8910923d6957a0962d8a1e591681a459f270 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 20 Dec 2022 15:27:01 +0100 Subject: [PATCH] Improve choice of number of decimals to show when hovering in plot --- CHANGELOG.md | 1 + crates/egui/src/widgets/plot/items/bar.rs | 8 +- .../egui/src/widgets/plot/items/box_elem.rs | 10 +- crates/egui/src/widgets/plot/items/mod.rs | 5 +- crates/egui/src/widgets/plot/mod.rs | 13 + crates/egui_demo_lib/src/demo/plot_demo.rs | 237 +++++++++--------- 6 files changed, 149 insertions(+), 125 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e678135..6d66952a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG ### Changed 🔧 * Improved plot grid appearance ([#2412](https://github.com/emilk/egui/pull/2412)). +* Improved the algorithm for picking the number of decimals to show when hovering values in the `Plot`. ### Fixed 🐛 * Expose `TextEdit`'s multiline flag to AccessKit ([#2448](https://github.com/emilk/egui/pull/2448)). diff --git a/crates/egui/src/widgets/plot/items/bar.rs b/crates/egui/src/widgets/plot/items/bar.rs index 74602199..a94a7d44 100644 --- a/crates/egui/src/widgets/plot/items/bar.rs +++ b/crates/egui/src/widgets/plot/items/bar.rs @@ -185,7 +185,11 @@ impl RectElement for Bar { fn default_values_format(&self, transform: &ScreenTransform) -> String { let scale = transform.dvalue_dpos(); - let y_decimals = ((-scale[1].abs().log10()).ceil().at_least(0.0) as usize).at_most(6); - format!("\n{:.*}", y_decimals, self.value) + let scale = match self.orientation { + Orientation::Horizontal => scale[0], + Orientation::Vertical => scale[1], + }; + let decimals = ((-scale.abs().log10()).ceil().at_least(0.0) as usize).at_most(6); + crate::plot::format_number(self.value, decimals) } } diff --git a/crates/egui/src/widgets/plot/items/box_elem.rs b/crates/egui/src/widgets/plot/items/box_elem.rs index a865a9db..81174afc 100644 --- a/crates/egui/src/widgets/plot/items/box_elem.rs +++ b/crates/egui/src/widgets/plot/items/box_elem.rs @@ -269,9 +269,15 @@ impl RectElement for BoxElem { fn default_values_format(&self, transform: &ScreenTransform) -> String { let scale = transform.dvalue_dpos(); - let y_decimals = ((-scale[1].abs().log10()).ceil().at_least(0.0) as usize).at_most(6); + let scale = match self.orientation { + Orientation::Horizontal => scale[0], + Orientation::Vertical => scale[1], + }; + let y_decimals = ((-scale.abs().log10()).ceil().at_least(0.0) as usize) + .at_most(6) + .at_least(1); format!( - "\nMax = {max:.decimals$}\ + "Max = {max:.decimals$}\ \nQuartile 3 = {q3:.decimals$}\ \nMedian = {med:.decimals$}\ \nQuartile 1 = {q1:.decimals$}\ diff --git a/crates/egui/src/widgets/plot/items/mod.rs b/crates/egui/src/widgets/plot/items/mod.rs index dfbcbd44..378f27eb 100644 --- a/crates/egui/src/widgets/plot/items/mod.rs +++ b/crates/egui/src/widgets/plot/items/mod.rs @@ -1648,6 +1648,7 @@ fn add_rulers_and_text( let mut text = elem.name().to_owned(); // could be empty if show_values { + text.push('\n'); text.push_str(&elem.default_values_format(plot.transform)); } @@ -1694,8 +1695,8 @@ pub(super) fn rulers_at_value( let text = { 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); + let x_decimals = ((-scale[0].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6); + let y_decimals = ((-scale[1].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6); if let Some(custom_label) = label_formatter { custom_label(name, &value) } else if plot.show_x && plot.show_y { diff --git a/crates/egui/src/widgets/plot/mod.rs b/crates/egui/src/widgets/plot/mod.rs index d46f2056..bbefe4b4 100644 --- a/crates/egui/src/widgets/plot/mod.rs +++ b/crates/egui/src/widgets/plot/mod.rs @@ -1647,3 +1647,16 @@ fn fill_marks_between(out: &mut Vec, step_size: f64, (min, max): (f64, }); out.extend(marks_iter); } + +/// Helper for formatting a number so that we always show at least a few decimals, +/// unless it is an integer, in which case we never show any decimals. +pub fn format_number(number: f64, num_decimals: usize) -> String { + let is_integral = number as i64 as f64 == number; + if is_integral { + // perfect integer - show it as such: + format!("{:.0}", number) + } else { + // make sure we tell the user it is not an integer by always showing a decimal or two: + format!("{:.*}", num_decimals.at_least(1), number) + } +} diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index bb98a114..7e9d8833 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -11,6 +11,124 @@ use plot::{ // ---------------------------------------------------------------------------- +#[derive(PartialEq, Eq)] +enum Panel { + Lines, + Markers, + Legend, + Charts, + Items, + Interaction, + CustomAxes, + LinkedAxes, +} + +impl Default for Panel { + fn default() -> Self { + Self::Lines + } +} + +// ---------------------------------------------------------------------------- + +#[derive(PartialEq, Default)] +pub struct PlotDemo { + line_demo: LineDemo, + marker_demo: MarkerDemo, + legend_demo: LegendDemo, + charts_demo: ChartsDemo, + items_demo: ItemsDemo, + interaction_demo: InteractionDemo, + custom_axes_demo: CustomAxisDemo, + linked_axes_demo: LinkedAxisDemo, + open_panel: Panel, +} + +impl super::Demo for PlotDemo { + fn name(&self) -> &'static str { + "🗠 Plot" + } + + fn show(&mut self, ctx: &Context, open: &mut bool) { + use super::View as _; + Window::new(self.name()) + .open(open) + .default_size(vec2(400.0, 400.0)) + .vscroll(false) + .show(ctx, |ui| self.ui(ui)); + } +} + +impl super::View for PlotDemo { + fn ui(&mut self, ui: &mut Ui) { + ui.horizontal(|ui| { + egui::reset_button(ui, self); + ui.collapsing("Instructions", |ui| { + ui.label("Pan by dragging, or scroll (+ shift = horizontal)."); + ui.label("Box zooming: Right click to zoom in and zoom out using a selection."); + if cfg!(target_arch = "wasm32") { + ui.label("Zoom with ctrl / ⌘ + pointer wheel, or with pinch gesture."); + } else if cfg!(target_os = "macos") { + ui.label("Zoom with ctrl / ⌘ + scroll."); + } else { + ui.label("Zoom with ctrl + scroll."); + } + ui.label("Reset view with double-click."); + ui.add(crate::egui_github_link_file!()); + }); + }); + ui.separator(); + ui.horizontal(|ui| { + ui.selectable_value(&mut self.open_panel, Panel::Lines, "Lines"); + ui.selectable_value(&mut self.open_panel, Panel::Markers, "Markers"); + ui.selectable_value(&mut self.open_panel, Panel::Legend, "Legend"); + ui.selectable_value(&mut self.open_panel, Panel::Charts, "Charts"); + ui.selectable_value(&mut self.open_panel, Panel::Items, "Items"); + ui.selectable_value(&mut self.open_panel, Panel::Interaction, "Interaction"); + ui.selectable_value(&mut self.open_panel, Panel::CustomAxes, "Custom Axes"); + ui.selectable_value(&mut self.open_panel, Panel::LinkedAxes, "Linked Axes"); + }); + ui.separator(); + + match self.open_panel { + Panel::Lines => { + self.line_demo.ui(ui); + } + Panel::Markers => { + self.marker_demo.ui(ui); + } + Panel::Legend => { + self.legend_demo.ui(ui); + } + Panel::Charts => { + self.charts_demo.ui(ui); + } + Panel::Items => { + self.items_demo.ui(ui); + } + Panel::Interaction => { + self.interaction_demo.ui(ui); + } + Panel::CustomAxes => { + self.custom_axes_demo.ui(ui); + } + Panel::LinkedAxes => { + self.linked_axes_demo.ui(ui); + } + } + } +} + +fn is_approx_zero(val: f64) -> bool { + val.abs() < 1e-6 +} + +fn is_approx_integer(val: f64) -> bool { + val.fract().abs() < 1e-6 +} + +// ---------------------------------------------------------------------------- + #[derive(PartialEq)] struct LineDemo { animate: bool, @@ -754,7 +872,6 @@ impl ChartsDemo { Plot::new("Normal Distribution Demo") .legend(Legend::default()) - .data_aspect(1.0) .clamp_grid(true) .show(ui, |plot_ui| plot_ui.bar_chart(chart)) .response @@ -865,121 +982,3 @@ impl ChartsDemo { .response } } - -// ---------------------------------------------------------------------------- - -#[derive(PartialEq, Eq)] -enum Panel { - Lines, - Markers, - Legend, - Charts, - Items, - Interaction, - CustomAxes, - LinkedAxes, -} - -impl Default for Panel { - fn default() -> Self { - Self::Lines - } -} - -// ---------------------------------------------------------------------------- - -#[derive(PartialEq, Default)] -pub struct PlotDemo { - line_demo: LineDemo, - marker_demo: MarkerDemo, - legend_demo: LegendDemo, - charts_demo: ChartsDemo, - items_demo: ItemsDemo, - interaction_demo: InteractionDemo, - custom_axes_demo: CustomAxisDemo, - linked_axes_demo: LinkedAxisDemo, - open_panel: Panel, -} - -impl super::Demo for PlotDemo { - fn name(&self) -> &'static str { - "🗠 Plot" - } - - fn show(&mut self, ctx: &Context, open: &mut bool) { - use super::View as _; - Window::new(self.name()) - .open(open) - .default_size(vec2(400.0, 400.0)) - .vscroll(false) - .show(ctx, |ui| self.ui(ui)); - } -} - -impl super::View for PlotDemo { - fn ui(&mut self, ui: &mut Ui) { - ui.horizontal(|ui| { - egui::reset_button(ui, self); - ui.collapsing("Instructions", |ui| { - ui.label("Pan by dragging, or scroll (+ shift = horizontal)."); - ui.label("Box zooming: Right click to zoom in and zoom out using a selection."); - if cfg!(target_arch = "wasm32") { - ui.label("Zoom with ctrl / ⌘ + pointer wheel, or with pinch gesture."); - } else if cfg!(target_os = "macos") { - ui.label("Zoom with ctrl / ⌘ + scroll."); - } else { - ui.label("Zoom with ctrl + scroll."); - } - ui.label("Reset view with double-click."); - ui.add(crate::egui_github_link_file!()); - }); - }); - ui.separator(); - ui.horizontal(|ui| { - ui.selectable_value(&mut self.open_panel, Panel::Lines, "Lines"); - ui.selectable_value(&mut self.open_panel, Panel::Markers, "Markers"); - ui.selectable_value(&mut self.open_panel, Panel::Legend, "Legend"); - ui.selectable_value(&mut self.open_panel, Panel::Charts, "Charts"); - ui.selectable_value(&mut self.open_panel, Panel::Items, "Items"); - ui.selectable_value(&mut self.open_panel, Panel::Interaction, "Interaction"); - ui.selectable_value(&mut self.open_panel, Panel::CustomAxes, "Custom Axes"); - ui.selectable_value(&mut self.open_panel, Panel::LinkedAxes, "Linked Axes"); - }); - ui.separator(); - - match self.open_panel { - Panel::Lines => { - self.line_demo.ui(ui); - } - Panel::Markers => { - self.marker_demo.ui(ui); - } - Panel::Legend => { - self.legend_demo.ui(ui); - } - Panel::Charts => { - self.charts_demo.ui(ui); - } - Panel::Items => { - self.items_demo.ui(ui); - } - Panel::Interaction => { - self.interaction_demo.ui(ui); - } - Panel::CustomAxes => { - self.custom_axes_demo.ui(ui); - } - Panel::LinkedAxes => { - self.linked_axes_demo.ui(ui); - } - } - } -} - -fn is_approx_zero(val: f64) -> bool { - val.abs() < 1e-6 -} - -fn is_approx_integer(val: f64) -> bool { - val.fract().abs() < 1e-6 -}