From 16a5612f9a347371edab2f9f097bb7367fca7618 Mon Sep 17 00:00:00 2001 From: Emilio Date: Thu, 10 Aug 2023 10:23:28 +0200 Subject: [PATCH] Plotter: add control on how to resolve the axes origin label drawing and possibility to override axes text adaptive contrast behaviour (#2) * allow for selecting which axis has priority when having to resolve a label conflict (i.e.: not displaying the 0 label on both axes) * add stroke overrides for axes, nasty hack * comment * spelling mistakes * pr feedback * pr feedback, remove custom colors and use bool to drive adaptive contrast --- crates/egui_extras/src/table.rs | 2 +- crates/egui_plot/src/lib.rs | 139 ++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index bcc87cd6afd5..62fc85b4ac78 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -546,7 +546,7 @@ pub struct Table<'a> { /// Returned from [`Table::body`]. Contains information about the way the table has been built by the context. pub struct TableOutput { - /// The response recieved upon the creation of the table's header, if one was defined. + /// The response received upon the creation of the table's header, if one was defined. pub header_response: Option, /// The output of the [`ScrollArea`] containing the table body's contents. diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 111f5f65ce33..1e176a9781ce 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -236,8 +236,14 @@ pub struct Plot { show_y: bool, label_formatter: LabelFormatter, coordinates_formatter: Option<(Corner, CoordinatesFormatter)>, +<<<<<<< HEAD:crates/egui_plot/src/lib.rs x_axes: Vec, // default x axes y_axes: Vec, // default y axes +======= + axis_formatters: [AxisFormatter; 2], + x_axis_origin_label_has_priority: bool, + axis_text_use_adaptive_contrast: [bool; 2], +>>>>>>> e76d03b9 (Plotter: add control on how to resolve the axes origin label drawing and possibility to override axes text adaptive contrast behaviour (#2)):crates/egui/src/widgets/plot/mod.rs legend_config: Option, show_background: bool, show_axes: AxisBools, @@ -278,8 +284,14 @@ impl Plot { show_y: true, label_formatter: None, coordinates_formatter: None, +<<<<<<< HEAD:crates/egui_plot/src/lib.rs x_axes: vec![Default::default()], y_axes: vec![Default::default()], +======= + axis_formatters: [None, None], // [None; 2] requires Copy + x_axis_origin_label_has_priority: true, + axis_text_use_adaptive_contrast: [true, true], +>>>>>>> e76d03b9 (Plotter: add control on how to resolve the axes origin label drawing and possibility to override axes text adaptive contrast behaviour (#2)):crates/egui/src/widgets/plot/mod.rs legend_config: None, show_background: true, show_axes: true.into(), @@ -446,6 +458,62 @@ impl Plot { self } +<<<<<<< HEAD:crates/egui_plot/src/lib.rs +======= + /// Provide a function to customize the labels for the X axis based on the current visible value range. + /// + /// 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, &RangeInclusive) -> String + 'static, + ) -> Self { + self.axis_formatters[0] = Some(Box::new(func)); + self + } + + /// Provide a function to customize the labels for the Y axis based on the current value range. + /// + /// 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, &RangeInclusive) -> String + 'static, + ) -> Self { + self.axis_formatters[1] = Some(Box::new(func)); + self + } + + /// Set the priority of the X axis origin label. Default: `true`. + /// This defines which label has display priority when resolving the overlapping of the origin (0.0) labels. + pub fn x_axis_origin_label_has_priority(mut self, val: bool) -> Self { + self.x_axis_origin_label_has_priority = val; + self + } + + /// Overrides the default behaviour of the X axis' text rendering. + /// Always use the ui foreground color instead of calculating the color of the stroke using the `color_from_contrast` calculations, + /// which blends the background and foreground colors according to a defined contrast + pub fn x_axis_text_adaptive_contrast(mut self, val: bool) -> Self { + self.axis_text_use_adaptive_contrast[0] = val; + self + } + + /// Overrides the default behaviour of the Y axis' text rendering. + /// Always use the ui foreground color instead of calculating the color of the stroke using the `color_from_contrast` calculations, + /// which blends the background and foreground colors according to a defined contrast + pub fn y_axis_text_adaptive_contrast(mut self, val: bool) -> Self { + self.axis_text_use_adaptive_contrast[1] = val; + self + } + +>>>>>>> e76d03b9 (Plotter: add control on how to resolve the axes origin label drawing and possibility to override axes text adaptive contrast behaviour (#2)):crates/egui/src/widgets/plot/mod.rs /// Configure how the grid in the background is spaced apart along the X axis. /// /// Default is a log-10 grid, i.e. every plot unit is divided into 10 other units. @@ -719,8 +787,14 @@ impl Plot { mut show_y, label_formatter, coordinates_formatter, +<<<<<<< HEAD:crates/egui_plot/src/lib.rs x_axes, y_axes, +======= + axis_formatters, + x_axis_origin_label_has_priority, + axis_text_use_adaptive_contrast, +>>>>>>> e76d03b9 (Plotter: add control on how to resolve the axes origin label drawing and possibility to override axes text adaptive contrast behaviour (#2)):crates/egui/src/widgets/plot/mod.rs legend_config, reset, show_background, @@ -1179,6 +1253,9 @@ impl Plot { grid_spacers, sharp_grid_lines, clamp_grid, + + x_axis_origin_label_has_priority, + axis_text_use_adaptive_contrast, }; let plot_cursors = prepared.ui(ui, &response); @@ -1552,7 +1629,11 @@ pub struct GridInput { } /// One mark (horizontal or vertical line) in the background grid of a plot. +<<<<<<< HEAD:crates/egui_plot/src/lib.rs #[derive(Debug, Clone, Copy)] +======= +#[derive(Debug)] +>>>>>>> e76d03b9 (Plotter: add control on how to resolve the axes origin label drawing and possibility to override axes text adaptive contrast behaviour (#2)):crates/egui/src/widgets/plot/mod.rs pub struct GridMark { /// X or Y value in the plot. pub value: f64, @@ -1624,6 +1705,9 @@ struct PreparedPlot { sharp_grid_lines: bool, clamp_grid: bool, + + x_axis_origin_label_has_priority: bool, + axis_text_use_adaptive_contrast: [bool; 2], } impl PreparedPlot { @@ -1807,6 +1891,61 @@ impl PreparedPlot { line_strength, )); } +<<<<<<< HEAD:crates/egui_plot/src/lib.rs +======= + + const MIN_TEXT_SPACING: f32 = 40.0; + if spacing_in_points > MIN_TEXT_SPACING { + let text_strength = + remap_clamp(spacing_in_points, MIN_TEXT_SPACING..=150.0, 0.0..=1.0); + + let color = if self.axis_text_use_adaptive_contrast[axis] { + color_from_contrast(ui, text_strength) + } else { + ui.visuals().widgets.open.fg_stroke.color + }; + + let text: String = if let Some(formatter) = axis_formatters[axis].as_deref() { + formatter(value_main, &axis_range) + } else { + emath::round_to_decimals(value_main, 5).to_string() // hack + }; + + // Skip origin label for the axis that doesn't have priority, if the axis with priority is already showing it (otherwise it's displayed twice) + let priority_axis_idx = if self.x_axis_origin_label_has_priority { + 0 + } else { + 1 + }; + let skip_low_priority_origin = + axis != priority_axis_idx && other_axis_shown && value_main == 0.0; + + // Custom formatters can return empty string to signal "no label at this resolution" + if !text.is_empty() && !skip_low_priority_origin { + let galley = ui.painter().layout_no_wrap(text, font_id.clone(), color); + + let mut text_pos = pos_in_gui + vec2(1.0, -galley.size().y); + + // 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), text_strength)); + } + } + } + + fn color_from_contrast(ui: &Ui, contrast: f32) -> Color32 { + let bg = ui.visuals().extreme_bg_color; + let fg = ui.visuals().widgets.open.fg_stroke.color; + let mix = 0.5 * contrast.sqrt(); + Color32::from_rgb( + lerp((bg.r() as f32)..=(fg.r() as f32), mix) as u8, + lerp((bg.g() as f32)..=(fg.g() as f32), mix) as u8, + lerp((bg.b() as f32)..=(fg.b() as f32), mix) as u8, + ) +>>>>>>> e76d03b9 (Plotter: add control on how to resolve the axes origin label drawing and possibility to override axes text adaptive contrast behaviour (#2)):crates/egui/src/widgets/plot/mod.rs } }