diff --git a/crates/egui/src/widgets/plot/items/bar.rs b/crates/egui/src/widgets/plot/items/bar.rs index a94a7d44..4ab6ebc7 100644 --- a/crates/egui/src/widgets/plot/items/bar.rs +++ b/crates/egui/src/widgets/plot/items/bar.rs @@ -2,7 +2,7 @@ use crate::emath::NumExt; use crate::epaint::{Color32, RectShape, Rounding, Shape, Stroke}; use super::{add_rulers_and_text, highlighted_color, Orientation, PlotConfig, RectElement}; -use crate::plot::{BarChart, Cursor, PlotPoint, ScreenTransform}; +use crate::plot::{BarChart, Cursor, PlotPoint, PlotTransform}; /// One bar in a [`BarChart`]. Potentially floating, allowing stacked bar charts. /// Width can be changed to allow variable-width histograms. @@ -116,7 +116,7 @@ impl Bar { pub(super) fn add_shapes( &self, - transform: &ScreenTransform, + transform: &PlotTransform, highlighted: bool, shapes: &mut Vec, ) { @@ -183,7 +183,7 @@ impl RectElement for Bar { self.orientation } - fn default_values_format(&self, transform: &ScreenTransform) -> String { + fn default_values_format(&self, transform: &PlotTransform) -> String { let scale = transform.dvalue_dpos(); let scale = match self.orientation { Orientation::Horizontal => scale[0], diff --git a/crates/egui/src/widgets/plot/items/box_elem.rs b/crates/egui/src/widgets/plot/items/box_elem.rs index 81174afc..3f915108 100644 --- a/crates/egui/src/widgets/plot/items/box_elem.rs +++ b/crates/egui/src/widgets/plot/items/box_elem.rs @@ -2,7 +2,7 @@ use crate::emath::NumExt; use crate::epaint::{Color32, RectShape, Rounding, Shape, Stroke}; use super::{add_rulers_and_text, highlighted_color, Orientation, PlotConfig, RectElement}; -use crate::plot::{BoxPlot, Cursor, PlotPoint, ScreenTransform}; +use crate::plot::{BoxPlot, Cursor, PlotPoint, PlotTransform}; /// Contains the values of a single box in a box plot. #[derive(Clone, Debug, PartialEq)] @@ -136,7 +136,7 @@ impl BoxElem { pub(super) fn add_shapes( &self, - transform: &ScreenTransform, + transform: &PlotTransform, highlighted: bool, shapes: &mut Vec, ) { @@ -267,7 +267,7 @@ impl RectElement for BoxElem { self.point_at(self.argument, self.spread.upper_whisker) } - fn default_values_format(&self, transform: &ScreenTransform) -> String { + fn default_values_format(&self, transform: &PlotTransform) -> String { let scale = transform.dvalue_dpos(); let scale = match self.orientation { Orientation::Horizontal => scale[0], diff --git a/crates/egui/src/widgets/plot/items/mod.rs b/crates/egui/src/widgets/plot/items/mod.rs index a4d430c7..e96d8b3b 100644 --- a/crates/egui/src/widgets/plot/items/mod.rs +++ b/crates/egui/src/widgets/plot/items/mod.rs @@ -7,7 +7,7 @@ use epaint::Mesh; use crate::*; -use super::{Cursor, LabelFormatter, PlotBounds, ScreenTransform}; +use super::{Cursor, LabelFormatter, PlotBounds, PlotTransform}; use rect_elem::*; use values::{ClosestElem, PlotGeometry}; @@ -25,14 +25,14 @@ const DEFAULT_FILL_ALPHA: f32 = 0.05; /// Container to pass-through several parameters related to plot visualization pub(super) struct PlotConfig<'a> { pub ui: &'a Ui, - pub transform: &'a ScreenTransform, + pub transform: &'a PlotTransform, pub show_x: bool, pub show_y: bool, } /// Trait shared by things that can be drawn in the plot. pub(super) trait PlotItem { - fn shapes(&self, ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec); + fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec); /// For plot-items which are generated based on x values (plotting functions). fn initialize(&mut self, x_range: RangeInclusive); @@ -49,7 +49,7 @@ pub(super) trait PlotItem { fn bounds(&self) -> PlotBounds; - fn find_closest(&self, point: Pos2, transform: &ScreenTransform) -> Option { + fn find_closest(&self, point: Pos2, transform: &PlotTransform) -> Option { match self.geometry() { PlotGeometry::None => None, @@ -177,7 +177,7 @@ impl HLine { } impl PlotItem for HLine { - fn shapes(&self, ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec) { + fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { let HLine { y, stroke, @@ -293,7 +293,7 @@ impl VLine { } impl PlotItem for VLine { - fn shapes(&self, ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec) { + fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { let VLine { x, stroke, @@ -423,7 +423,7 @@ fn y_intersection(p1: &Pos2, p2: &Pos2, y: f32) -> Option { } impl PlotItem for Line { - fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec) { + fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { let Self { series, stroke, @@ -584,7 +584,7 @@ impl Polygon { } impl PlotItem for Polygon { - fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec) { + fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { let Self { series, stroke, @@ -696,7 +696,7 @@ impl Text { } impl PlotItem for Text { - fn shapes(&self, ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec) { + fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { let color = if self.color == Color32::TRANSPARENT { ui.style().visuals.text_color() } else { @@ -836,7 +836,7 @@ impl Points { } impl PlotItem for Points { - fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec) { + fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { let sqrt_3 = 3_f32.sqrt(); let frac_sqrt_3_2 = 3_f32.sqrt() / 2.0; let frac_1_sqrt_2 = 1.0 / 2_f32.sqrt(); @@ -1039,7 +1039,7 @@ impl Arrows { } impl PlotItem for Arrows { - fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec) { + fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { use crate::emath::*; let Self { origins, @@ -1178,7 +1178,7 @@ impl PlotImage { } impl PlotItem for PlotImage { - fn shapes(&self, ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec) { + fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { let Self { position, texture_id, @@ -1369,7 +1369,7 @@ impl BarChart { } impl PlotItem for BarChart { - fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec) { + fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { for b in &self.bars { b.add_shapes(transform, self.highlight, shapes); } @@ -1407,7 +1407,7 @@ impl PlotItem for BarChart { bounds } - fn find_closest(&self, point: Pos2, transform: &ScreenTransform) -> Option { + fn find_closest(&self, point: Pos2, transform: &PlotTransform) -> Option { find_closest_rect(&self.bars, point, transform) } @@ -1512,7 +1512,7 @@ impl BoxPlot { } impl PlotItem for BoxPlot { - fn shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec) { + fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { for b in &self.boxes { b.add_shapes(transform, self.highlight, shapes); } @@ -1550,7 +1550,7 @@ impl PlotItem for BoxPlot { bounds } - fn find_closest(&self, point: Pos2, transform: &ScreenTransform) -> Option { + fn find_closest(&self, point: Pos2, transform: &PlotTransform) -> Option { find_closest_rect(&self.boxes, point, transform) } @@ -1582,7 +1582,7 @@ pub(crate) fn rulers_color(ui: &Ui) -> Color32 { pub(crate) fn vertical_line( pointer: Pos2, - transform: &ScreenTransform, + transform: &PlotTransform, line_color: Color32, ) -> Shape { let frame = transform.frame(); @@ -1597,7 +1597,7 @@ pub(crate) fn vertical_line( pub(crate) fn horizontal_line( pointer: Pos2, - transform: &ScreenTransform, + transform: &PlotTransform, line_color: Color32, ) -> Shape { let frame = transform.frame(); @@ -1731,7 +1731,7 @@ pub(super) fn rulers_at_value( fn find_closest_rect<'a, T>( rects: impl IntoIterator, point: Pos2, - transform: &ScreenTransform, + transform: &PlotTransform, ) -> Option where T: 'a + RectElement, diff --git a/crates/egui/src/widgets/plot/items/rect_elem.rs b/crates/egui/src/widgets/plot/items/rect_elem.rs index 0379a416..681d4fe8 100644 --- a/crates/egui/src/widgets/plot/items/rect_elem.rs +++ b/crates/egui/src/widgets/plot/items/rect_elem.rs @@ -1,5 +1,5 @@ use super::{Orientation, PlotPoint}; -use crate::plot::transform::{PlotBounds, ScreenTransform}; +use crate::plot::transform::{PlotBounds, PlotTransform}; use epaint::emath::NumExt; use epaint::{Color32, Rgba, Stroke}; @@ -48,7 +48,7 @@ pub(super) trait RectElement { } /// Debug formatting for hovered-over value, if none is specified by the user - fn default_values_format(&self, transform: &ScreenTransform) -> String; + fn default_values_format(&self, transform: &PlotTransform) -> String; } // ---------------------------------------------------------------------------- diff --git a/crates/egui/src/widgets/plot/mod.rs b/crates/egui/src/widgets/plot/mod.rs index 280baf22..c5db9199 100644 --- a/crates/egui/src/widgets/plot/mod.rs +++ b/crates/egui/src/widgets/plot/mod.rs @@ -9,14 +9,13 @@ use epaint::Hsva; use items::PlotItem; use legend::LegendWidget; -use transform::ScreenTransform; pub use items::{ Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape, Orientation, PlotImage, PlotPoint, PlotPoints, Points, Polygon, Text, VLine, }; pub use legend::{Corner, Legend}; -pub use transform::PlotBounds; +pub use transform::{PlotBounds, PlotTransform}; use self::items::{horizontal_line, rulers_color, vertical_line}; @@ -104,7 +103,7 @@ struct PlotMemory { bounds_modified: AxisBools, hovered_entry: Option, hidden_items: ahash::HashSet, - last_screen_transform: ScreenTransform, + last_plot_transform: PlotTransform, /// Allows to remember the first click position when performing a boxed zoom last_click_pos_for_zoom: Option, } @@ -149,6 +148,20 @@ struct BoundsLinkGroups(HashMap); // ---------------------------------------------------------------------------- +/// What [`Plot::show`] returns. +pub struct PlotResponse { + /// What the user closure returned. + pub inner: R, + + /// The response of the plot. + pub response: Response, + + /// The transform between screen coordinates and plot coordinates. + pub transform: PlotTransform, +} + +// ---------------------------------------------------------------------------- + /// A 2D plot, e.g. a graph of a function. /// /// [`Plot`] supports multiple lines and points. @@ -571,7 +584,7 @@ impl Plot { } /// Interact with and add items to the plot and finally draw it. - pub fn show(self, ui: &mut Ui, build_fn: impl FnOnce(&mut PlotUi) -> R) -> InnerResponse { + pub fn show(self, ui: &mut Ui, build_fn: impl FnOnce(&mut PlotUi) -> R) -> PlotResponse { self.show_dyn(ui, Box::new(build_fn)) } @@ -579,7 +592,7 @@ impl Plot { self, ui: &mut Ui, build_fn: Box R + 'a>, - ) -> InnerResponse { + ) -> PlotResponse { let Self { id_source, center_x_axis, @@ -661,7 +674,7 @@ impl Plot { bounds_modified: false.into(), hovered_entry: None, hidden_items: Default::default(), - last_screen_transform: ScreenTransform::new( + last_plot_transform: PlotTransform::new( rect, min_auto_bounds, center_x_axis, @@ -674,7 +687,7 @@ impl Plot { mut bounds_modified, mut hovered_entry, mut hidden_items, - last_screen_transform, + last_plot_transform, mut last_click_pos_for_zoom, } = memory; @@ -682,7 +695,7 @@ impl Plot { let mut plot_ui = PlotUi { items: Vec::new(), next_auto_color_idx: 0, - last_screen_transform, + last_plot_transform, response, bounds_modifications: Vec::new(), ctx: ui.ctx().clone(), @@ -691,7 +704,7 @@ impl Plot { let PlotUi { mut items, mut response, - last_screen_transform, + last_plot_transform, bounds_modifications, .. } = plot_ui; @@ -727,7 +740,7 @@ impl Plot { items.sort_by_key(|item| item.highlighted()); // --- Bound computation --- - let mut bounds = *last_screen_transform.bounds(); + let mut bounds = *last_plot_transform.bounds(); // Find the cursors from other plots we need to draw let draw_cursors: Vec = if let Some((id, _)) = linked_cursors.as_ref() { @@ -826,7 +839,7 @@ impl Plot { } } - let mut transform = ScreenTransform::new(rect, bounds, center_x_axis, center_y_axis); + let mut transform = PlotTransform::new(rect, bounds, center_x_axis, center_y_axis); // Enforce aspect ratio if let Some(data_aspect) = data_aspect { @@ -947,7 +960,7 @@ impl Plot { coordinates_formatter, axis_formatters, show_axes, - transform: transform.clone(), + transform, draw_cursor_x: linked_cursors.as_ref().map_or(false, |(_, group)| group.x), draw_cursor_y: linked_cursors.as_ref().map_or(false, |(_, group)| group.y), draw_cursors, @@ -999,7 +1012,7 @@ impl Plot { bounds_modified, hovered_entry, hidden_items, - last_screen_transform: transform, + last_plot_transform: transform, last_click_pos_for_zoom, }; memory.store(ui.ctx(), plot_id); @@ -1010,7 +1023,11 @@ impl Plot { response }; - InnerResponse { inner, response } + PlotResponse { + inner, + response, + transform, + } } } @@ -1026,7 +1043,7 @@ enum BoundsModification { pub struct PlotUi { items: Vec>, next_auto_color_idx: usize, - last_screen_transform: ScreenTransform, + last_plot_transform: PlotTransform, response: Response, bounds_modifications: Vec, ctx: Context, @@ -1049,7 +1066,7 @@ impl PlotUi { /// further specified in the plot builder, this will return bounds centered on the origin. The bounds do /// not change until the plot is drawn. pub fn plot_bounds(&self) -> PlotBounds { - *self.last_screen_transform.bounds() + *self.last_plot_transform.bounds() } /// Set the plot bounds. Can be useful for implementing alternative plot navigation methods. @@ -1090,18 +1107,23 @@ impl PlotUi { /// The pointer drag delta in plot coordinates. pub fn pointer_coordinate_drag_delta(&self) -> Vec2 { let delta = self.response.drag_delta(); - let dp_dv = self.last_screen_transform.dpos_dvalue(); + let dp_dv = self.last_plot_transform.dpos_dvalue(); Vec2::new(delta.x / dp_dv[0] as f32, delta.y / dp_dv[1] as f32) } + /// Read the transform netween plot coordinates and screen coordinates. + pub fn transform(&self) -> &PlotTransform { + &self.last_plot_transform + } + /// Transform the plot coordinates to screen coordinates. pub fn screen_from_plot(&self, position: PlotPoint) -> Pos2 { - self.last_screen_transform.position_from_point(&position) + self.last_plot_transform.position_from_point(&position) } /// Transform the screen coordinates to plot coordinates. pub fn plot_from_screen(&self, position: Pos2) -> PlotPoint { - self.last_screen_transform.value_from_position(position) + self.last_plot_transform.value_from_position(position) } /// Add a data line. @@ -1299,7 +1321,7 @@ struct PreparedPlot { coordinates_formatter: Option<(Corner, CoordinatesFormatter)>, axis_formatters: [AxisFormatter; 2], show_axes: [bool; 2], - transform: ScreenTransform, + transform: PlotTransform, draw_cursor_x: bool, draw_cursor_y: bool, draw_cursors: Vec, diff --git a/crates/egui/src/widgets/plot/transform.rs b/crates/egui/src/widgets/plot/transform.rs index ceff3a9a..cad4e694 100644 --- a/crates/egui/src/widgets/plot/transform.rs +++ b/crates/egui/src/widgets/plot/transform.rs @@ -181,10 +181,10 @@ impl PlotBounds { } } -/// Contains the screen rectangle and the plot bounds and provides methods to transform them. +/// Contains the screen rectangle and the plot bounds and provides methods to transform between them. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[derive(Clone)] -pub(crate) struct ScreenTransform { +#[derive(Clone, Copy, Debug)] +pub struct PlotTransform { /// The screen rectangle. frame: Rect, @@ -198,7 +198,7 @@ pub(crate) struct ScreenTransform { y_centered: bool, } -impl ScreenTransform { +impl PlotTransform { pub fn new(frame: Rect, mut bounds: PlotBounds, x_centered: bool, y_centered: bool) -> Self { // Make sure they are not empty. if !bounds.is_valid_x() { @@ -229,15 +229,16 @@ impl ScreenTransform { &self.frame } + /// Plot-space bounds. pub fn bounds(&self) -> &PlotBounds { &self.bounds } - pub fn set_bounds(&mut self, bounds: PlotBounds) { + pub(crate) fn set_bounds(&mut self, bounds: PlotBounds) { self.bounds = bounds; } - pub fn translate_bounds(&mut self, mut delta_pos: Vec2) { + pub(crate) fn translate_bounds(&mut self, mut delta_pos: Vec2) { if self.x_centered { delta_pos.x = 0.; } @@ -250,7 +251,7 @@ impl ScreenTransform { } /// Zoom by a relative factor with the given screen position as center. - pub fn zoom(&mut self, zoom_factor: Vec2, center: Pos2) { + pub(crate) fn zoom(&mut self, zoom_factor: Vec2, center: Pos2) { let center = self.value_from_position(center); let mut new_bounds = self.bounds; @@ -280,6 +281,7 @@ impl ScreenTransform { ) as f32 } + /// Screen/ui position from point on plot. pub fn position_from_point(&self, value: &PlotPoint) -> Pos2 { pos2( self.position_from_point_x(value.x), @@ -287,6 +289,7 @@ impl ScreenTransform { ) } + /// Plot point from screen/ui position. pub fn value_from_position(&self, pos: Pos2) -> PlotPoint { let x = remap( pos.x as f64, @@ -335,6 +338,7 @@ impl ScreenTransform { [1.0 / self.dpos_dvalue_x(), 1.0 / self.dpos_dvalue_y()] } + /// width / height aspect ratio fn aspect(&self) -> f64 { let rw = self.frame.width() as f64; let rh = self.frame.height() as f64; @@ -344,7 +348,7 @@ impl ScreenTransform { /// Sets the aspect ratio by expanding the x- or y-axis. /// /// This never contracts, so we don't miss out on any data. - pub fn set_aspect_by_expanding(&mut self, aspect: f64) { + pub(crate) fn set_aspect_by_expanding(&mut self, aspect: f64) { let current_aspect = self.aspect(); let epsilon = 1e-5; @@ -363,7 +367,7 @@ impl ScreenTransform { } /// Sets the aspect ratio by changing either the X or Y axis (callers choice). - pub fn set_aspect_by_changing_axis(&mut self, aspect: f64, change_x: bool) { + pub(crate) fn set_aspect_by_changing_axis(&mut self, aspect: f64, change_x: bool) { let current_aspect = self.aspect(); let epsilon = 1e-5; diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index ef15a66b..12d79ce0 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -1,7 +1,7 @@ use std::f64::consts::TAU; use std::ops::RangeInclusive; -use egui::plot::{AxisBools, GridInput, GridMark}; +use egui::plot::{AxisBools, GridInput, GridMark, PlotResponse}; use egui::*; use plot::{ Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, HLine, @@ -751,9 +751,10 @@ impl InteractionDemo { fn ui(&mut self, ui: &mut Ui) -> Response { let plot = Plot::new("interaction_demo").height(300.0); - let InnerResponse { + let PlotResponse { response, inner: (screen_pos, pointer_coordinate, pointer_coordinate_drag_delta, bounds, hovered), + .. } = plot.show(ui, |plot_ui| { ( plot_ui.screen_from_plot(PlotPoint::new(0.0, 0.0)),