From 36679d12b1996005334960e39941e0904c1fbdb2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 2 Feb 2024 14:38:35 +0100 Subject: [PATCH] Make `egui_plot::PlotItem` a public trait (#3943) --- crates/egui_plot/src/items/mod.rs | 28 +-- crates/egui_plot/src/items/values.rs | 4 +- crates/egui_plot/src/lib.rs | 276 +++------------------------ crates/egui_plot/src/plot_ui.rs | 230 ++++++++++++++++++++++ crates/egui_plot/src/transform.rs | 21 +- 5 files changed, 283 insertions(+), 276 deletions(-) create mode 100644 crates/egui_plot/src/plot_ui.rs diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index fb176438e3c0..122189d2264c 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -23,7 +23,7 @@ mod values; const DEFAULT_FILL_ALPHA: f32 = 0.05; /// Container to pass-through several parameters related to plot visualization -pub(super) struct PlotConfig<'a> { +pub struct PlotConfig<'a> { pub ui: &'a Ui, pub transform: &'a PlotTransform, pub show_x: bool, @@ -31,8 +31,8 @@ pub(super) struct PlotConfig<'a> { } /// Trait shared by things that can be drawn in the plot. -pub(super) trait PlotItem { - fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec); +pub trait PlotItem { + fn shapes(&self, ui: &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); @@ -194,7 +194,7 @@ impl HLine { } impl PlotItem for HLine { - fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { + fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec) { let Self { y, stroke, @@ -329,7 +329,7 @@ impl VLine { } impl PlotItem for VLine { - fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { + fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec) { let Self { x, stroke, @@ -479,7 +479,7 @@ fn y_intersection(p1: &Pos2, p2: &Pos2, y: f32) -> Option { } impl PlotItem for Line { - fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { + fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec) { let Self { series, stroke, @@ -653,7 +653,7 @@ impl Polygon { } impl PlotItem for Polygon { - fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { + fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec) { let Self { series, stroke, @@ -778,7 +778,7 @@ impl Text { } impl PlotItem for Text { - fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { + fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec) { let color = if self.color == Color32::TRANSPARENT { ui.style().visuals.text_color() } else { @@ -910,7 +910,7 @@ impl Points { self } - /// Set the maximum extent of the marker around its position. + /// Set the maximum extent of the marker around its position, in ui points. #[inline] pub fn radius(mut self, radius: impl Into) -> Self { self.radius = radius.into(); @@ -939,7 +939,7 @@ impl Points { } impl PlotItem for Points { - fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { + fn shapes(&self, _ui: &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(); @@ -1167,7 +1167,7 @@ impl Arrows { } impl PlotItem for Arrows { - fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { + fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec) { use crate::emath::*; let Self { origins, @@ -1331,7 +1331,7 @@ impl PlotImage { } impl PlotItem for PlotImage { - fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { + fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec) { let Self { position, rotation, @@ -1565,7 +1565,7 @@ impl BarChart { } impl PlotItem for BarChart { - fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { + fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec) { for b in &self.bars { b.add_shapes(transform, self.highlight, shapes); } @@ -1726,7 +1726,7 @@ impl BoxPlot { } impl PlotItem for BoxPlot { - fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec) { + fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec) { for b in &self.boxes { b.add_shapes(transform, self.highlight, shapes); } diff --git a/crates/egui_plot/src/items/values.rs b/crates/egui_plot/src/items/values.rs index d05fdab36e78..8ed6d38f3337 100644 --- a/crates/egui_plot/src/items/values.rs +++ b/crates/egui_plot/src/items/values.rs @@ -357,7 +357,7 @@ impl MarkerShape { // ---------------------------------------------------------------------------- /// Query the points of the plot, for geometric relations like closest checks -pub(crate) enum PlotGeometry<'a> { +pub enum PlotGeometry<'a> { /// No geometry based on single elements (examples: text, image, horizontal/vertical line) None, @@ -425,7 +425,7 @@ impl ExplicitGenerator { // ---------------------------------------------------------------------------- /// Result of [`super::PlotItem::find_closest()`] search, identifies an element inside the item for immediate use -pub(crate) struct ClosestElem { +pub struct ClosestElem { /// Position of hovered-over value (or bar/box-plot/...) in PlotItem pub index: usize, diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index dc1be39f9601..f304c613797f 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -10,34 +10,33 @@ mod axis; mod items; mod legend; mod memory; +mod plot_ui; mod transform; use std::{ops::RangeInclusive, sync::Arc}; use egui::ahash::HashMap; -use epaint::util::FloatOrd; -use epaint::Hsva; - -use axis::AxisWidget; -use items::PlotItem; -use legend::LegendWidget; - use egui::*; - -pub use items::{ - Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape, - Orientation, PlotImage, PlotPoint, PlotPoints, Points, Polygon, Text, VLine, +use epaint::{util::FloatOrd, Hsva}; + +pub use crate::{ + axis::{Axis, AxisHints, HPlacement, Placement, VPlacement}, + items::{ + Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape, + Orientation, PlotImage, PlotItem, PlotPoint, PlotPoints, Points, Polygon, Text, VLine, + }, + legend::{Corner, Legend}, + memory::PlotMemory, + plot_ui::PlotUi, + transform::{PlotBounds, PlotTransform}, }; -pub use legend::{Corner, Legend}; -pub use transform::{PlotBounds, PlotTransform}; +use axis::AxisWidget; use items::{horizontal_line, rulers_color, vertical_line}; - -pub use axis::{Axis, AxisHints, HPlacement, Placement, VPlacement}; -pub use memory::PlotMemory; +use legend::LegendWidget; type LabelFormatterFn = dyn Fn(&str, &PlotPoint) -> String; -type LabelFormatter = Option>; +pub type LabelFormatter = Option>; type GridSpacerFn = dyn Fn(GridInput) -> Vec; type GridSpacer = Box; @@ -81,7 +80,7 @@ impl Default for CoordinatesFormatter { /// Indicates a vertical or horizontal cursor line in plot coordinates. #[derive(Copy, Clone, PartialEq)] -enum Cursor { +pub enum Cursor { Horizontal { y: f64 }, Vertical { x: f64 }, } @@ -820,13 +819,13 @@ impl Plot { // Call the plot build function. let mut plot_ui = PlotUi { + ctx: ui.ctx().clone(), items: Vec::new(), next_auto_color_idx: 0, last_plot_transform, last_auto_bounds: mem.auto_bounds, response, bounds_modifications: Vec::new(), - ctx: ui.ctx().clone(), }; let inner = build_fn(&mut plot_ui); let PlotUi { @@ -981,13 +980,15 @@ impl Plot { if let Some(data_aspect) = data_aspect { if let Some((_, linked_axes)) = &linked_axes { let change_x = linked_axes.y && !linked_axes.x; - mem.transform - .set_aspect_by_changing_axis(data_aspect as f64, change_x); + mem.transform.set_aspect_by_changing_axis( + data_aspect as f64, + if change_x { Axis::X } else { Axis::Y }, + ); } else if default_auto_bounds.any() { mem.transform.set_aspect_by_expanding(data_aspect as f64); } else { mem.transform - .set_aspect_by_changing_axis(data_aspect as f64, false); + .set_aspect_by_changing_axis(data_aspect as f64, Axis::Y); } } @@ -1325,235 +1326,6 @@ enum BoundsModification { Zoom(Vec2, PlotPoint), } -/// Provides methods to interact with a plot while building it. It is the single argument of the closure -/// provided to [`Plot::show`]. See [`Plot`] for an example of how to use it. -pub struct PlotUi { - items: Vec>, - next_auto_color_idx: usize, - last_plot_transform: PlotTransform, - last_auto_bounds: Vec2b, - response: Response, - bounds_modifications: Vec, - ctx: Context, -} - -impl PlotUi { - fn auto_color(&mut self) -> Color32 { - let i = self.next_auto_color_idx; - self.next_auto_color_idx += 1; - let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; // 0.61803398875 - let h = i as f32 * golden_ratio; - Hsva::new(h, 0.85, 0.5, 1.0).into() // TODO(emilk): OkLab or some other perspective color space - } - - pub fn ctx(&self) -> &Context { - &self.ctx - } - - /// The plot bounds as they were in the last frame. If called on the first frame and the bounds were not - /// 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_plot_transform.bounds() - } - - /// Set the plot bounds. Can be useful for implementing alternative plot navigation methods. - pub fn set_plot_bounds(&mut self, plot_bounds: PlotBounds) { - self.bounds_modifications - .push(BoundsModification::Set(plot_bounds)); - } - - /// Move the plot bounds. Can be useful for implementing alternative plot navigation methods. - pub fn translate_bounds(&mut self, delta_pos: Vec2) { - self.bounds_modifications - .push(BoundsModification::Translate(delta_pos)); - } - - /// Whether the plot axes were in auto-bounds mode in the last frame. If called on the first - /// frame, this is the [`Plot`]'s default auto-bounds mode. - pub fn auto_bounds(&self) -> Vec2b { - self.last_auto_bounds - } - - /// Set the auto-bounds mode for the plot axes. - pub fn set_auto_bounds(&mut self, auto_bounds: Vec2b) { - self.bounds_modifications - .push(BoundsModification::AutoBounds(auto_bounds)); - } - - /// Can be used to check if the plot was hovered or clicked. - pub fn response(&self) -> &Response { - &self.response - } - - /// Scale the plot bounds around a position in screen coordinates. - /// - /// Can be useful for implementing alternative plot navigation methods. - /// - /// The plot bounds are divided by `zoom_factor`, therefore: - /// - `zoom_factor < 1.0` zooms out, i.e., increases the visible range to show more data. - /// - `zoom_factor > 1.0` zooms in, i.e., reduces the visible range to show more detail. - pub fn zoom_bounds(&mut self, zoom_factor: Vec2, center: PlotPoint) { - self.bounds_modifications - .push(BoundsModification::Zoom(zoom_factor, center)); - } - - /// Scale the plot bounds around the hovered position, if any. - /// - /// Can be useful for implementing alternative plot navigation methods. - /// - /// The plot bounds are divided by `zoom_factor`, therefore: - /// - `zoom_factor < 1.0` zooms out, i.e., increases the visible range to show more data. - /// - `zoom_factor > 1.0` zooms in, i.e., reduces the visible range to show more detail. - pub fn zoom_bounds_around_hovered(&mut self, zoom_factor: Vec2) { - if let Some(hover_pos) = self.pointer_coordinate() { - self.zoom_bounds(zoom_factor, hover_pos); - } - } - - /// The pointer position in plot coordinates. Independent of whether the pointer is in the plot area. - pub fn pointer_coordinate(&self) -> Option { - // We need to subtract the drag delta to keep in sync with the frame-delayed screen transform: - let last_pos = self.ctx().input(|i| i.pointer.latest_pos())? - self.response.drag_delta(); - let value = self.plot_from_screen(last_pos); - Some(value) - } - - /// 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_plot_transform.dpos_dvalue(); - Vec2::new(delta.x / dp_dv[0] as f32, delta.y / dp_dv[1] as f32) - } - - /// Read the transform between 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_plot_transform.position_from_point(&position) - } - - /// Transform the screen coordinates to plot coordinates. - pub fn plot_from_screen(&self, position: Pos2) -> PlotPoint { - self.last_plot_transform.value_from_position(position) - } - - /// Add a data line. - pub fn line(&mut self, mut line: Line) { - if line.series.is_empty() { - return; - }; - - // Give the stroke an automatic color if no color has been assigned. - if line.stroke.color == Color32::TRANSPARENT { - line.stroke.color = self.auto_color(); - } - self.items.push(Box::new(line)); - } - - /// Add a polygon. The polygon has to be convex. - pub fn polygon(&mut self, mut polygon: Polygon) { - if polygon.series.is_empty() { - return; - }; - - // Give the stroke an automatic color if no color has been assigned. - if polygon.stroke.color == Color32::TRANSPARENT { - polygon.stroke.color = self.auto_color(); - } - self.items.push(Box::new(polygon)); - } - - /// Add a text. - pub fn text(&mut self, text: Text) { - if text.text.is_empty() { - return; - }; - - self.items.push(Box::new(text)); - } - - /// Add data points. - pub fn points(&mut self, mut points: Points) { - if points.series.is_empty() { - return; - }; - - // Give the points an automatic color if no color has been assigned. - if points.color == Color32::TRANSPARENT { - points.color = self.auto_color(); - } - self.items.push(Box::new(points)); - } - - /// Add arrows. - pub fn arrows(&mut self, mut arrows: Arrows) { - if arrows.origins.is_empty() || arrows.tips.is_empty() { - return; - }; - - // Give the arrows an automatic color if no color has been assigned. - if arrows.color == Color32::TRANSPARENT { - arrows.color = self.auto_color(); - } - self.items.push(Box::new(arrows)); - } - - /// Add an image. - pub fn image(&mut self, image: PlotImage) { - self.items.push(Box::new(image)); - } - - /// Add a horizontal line. - /// Can be useful e.g. to show min/max bounds or similar. - /// Always fills the full width of the plot. - pub fn hline(&mut self, mut hline: HLine) { - if hline.stroke.color == Color32::TRANSPARENT { - hline.stroke.color = self.auto_color(); - } - self.items.push(Box::new(hline)); - } - - /// Add a vertical line. - /// Can be useful e.g. to show min/max bounds or similar. - /// Always fills the full height of the plot. - pub fn vline(&mut self, mut vline: VLine) { - if vline.stroke.color == Color32::TRANSPARENT { - vline.stroke.color = self.auto_color(); - } - self.items.push(Box::new(vline)); - } - - /// Add a box plot diagram. - pub fn box_plot(&mut self, mut box_plot: BoxPlot) { - if box_plot.boxes.is_empty() { - return; - } - - // Give the elements an automatic color if no color has been assigned. - if box_plot.default_color == Color32::TRANSPARENT { - box_plot = box_plot.color(self.auto_color()); - } - self.items.push(Box::new(box_plot)); - } - - /// Add a bar chart. - pub fn bar_chart(&mut self, mut chart: BarChart) { - if chart.bars.is_empty() { - return; - } - - // Give the elements an automatic color if no color has been assigned. - if chart.default_color == Color32::TRANSPARENT { - chart = chart.color(self.auto_color()); - } - self.items.push(Box::new(chart)); - } -} - // ---------------------------------------------------------------------------- // Grid @@ -1671,7 +1443,7 @@ impl PreparedPlot { let mut plot_ui = ui.child_ui(*transform.frame(), Layout::default()); plot_ui.set_clip_rect(*transform.frame()); for item in &self.items { - item.shapes(&mut plot_ui, transform, &mut shapes); + item.shapes(&plot_ui, transform, &mut shapes); } let hover_pos = response.hover_pos(); diff --git a/crates/egui_plot/src/plot_ui.rs b/crates/egui_plot/src/plot_ui.rs new file mode 100644 index 000000000000..fb126298df50 --- /dev/null +++ b/crates/egui_plot/src/plot_ui.rs @@ -0,0 +1,230 @@ +use crate::*; + +/// Provides methods to interact with a plot while building it. It is the single argument of the closure +/// provided to [`Plot::show`]. See [`Plot`] for an example of how to use it. +pub struct PlotUi { + pub(crate) ctx: Context, + pub(crate) items: Vec>, + pub(crate) next_auto_color_idx: usize, + pub(crate) last_plot_transform: PlotTransform, + pub(crate) last_auto_bounds: Vec2b, + pub(crate) response: Response, + pub(crate) bounds_modifications: Vec, +} + +impl PlotUi { + fn auto_color(&mut self) -> Color32 { + let i = self.next_auto_color_idx; + self.next_auto_color_idx += 1; + let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; // 0.61803398875 + let h = i as f32 * golden_ratio; + Hsva::new(h, 0.85, 0.5, 1.0).into() // TODO(emilk): OkLab or some other perspective color space + } + + pub fn ctx(&self) -> &Context { + &self.ctx + } + + /// The plot bounds as they were in the last frame. If called on the first frame and the bounds were not + /// 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_plot_transform.bounds() + } + + /// Set the plot bounds. Can be useful for implementing alternative plot navigation methods. + pub fn set_plot_bounds(&mut self, plot_bounds: PlotBounds) { + self.bounds_modifications + .push(BoundsModification::Set(plot_bounds)); + } + + /// Move the plot bounds. Can be useful for implementing alternative plot navigation methods. + pub fn translate_bounds(&mut self, delta_pos: Vec2) { + self.bounds_modifications + .push(BoundsModification::Translate(delta_pos)); + } + + /// Whether the plot axes were in auto-bounds mode in the last frame. If called on the first + /// frame, this is the [`Plot`]'s default auto-bounds mode. + pub fn auto_bounds(&self) -> Vec2b { + self.last_auto_bounds + } + + /// Set the auto-bounds mode for the plot axes. + pub fn set_auto_bounds(&mut self, auto_bounds: Vec2b) { + self.bounds_modifications + .push(BoundsModification::AutoBounds(auto_bounds)); + } + + /// Can be used to check if the plot was hovered or clicked. + pub fn response(&self) -> &Response { + &self.response + } + + /// Scale the plot bounds around a position in screen coordinates. + /// + /// Can be useful for implementing alternative plot navigation methods. + /// + /// The plot bounds are divided by `zoom_factor`, therefore: + /// - `zoom_factor < 1.0` zooms out, i.e., increases the visible range to show more data. + /// - `zoom_factor > 1.0` zooms in, i.e., reduces the visible range to show more detail. + pub fn zoom_bounds(&mut self, zoom_factor: Vec2, center: PlotPoint) { + self.bounds_modifications + .push(BoundsModification::Zoom(zoom_factor, center)); + } + + /// Scale the plot bounds around the hovered position, if any. + /// + /// Can be useful for implementing alternative plot navigation methods. + /// + /// The plot bounds are divided by `zoom_factor`, therefore: + /// - `zoom_factor < 1.0` zooms out, i.e., increases the visible range to show more data. + /// - `zoom_factor > 1.0` zooms in, i.e., reduces the visible range to show more detail. + pub fn zoom_bounds_around_hovered(&mut self, zoom_factor: Vec2) { + if let Some(hover_pos) = self.pointer_coordinate() { + self.zoom_bounds(zoom_factor, hover_pos); + } + } + + /// The pointer position in plot coordinates. Independent of whether the pointer is in the plot area. + pub fn pointer_coordinate(&self) -> Option { + // We need to subtract the drag delta to keep in sync with the frame-delayed screen transform: + let last_pos = self.ctx().input(|i| i.pointer.latest_pos())? - self.response.drag_delta(); + let value = self.plot_from_screen(last_pos); + Some(value) + } + + /// 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_plot_transform.dpos_dvalue(); + Vec2::new(delta.x / dp_dv[0] as f32, delta.y / dp_dv[1] as f32) + } + + /// Read the transform between 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_plot_transform.position_from_point(&position) + } + + /// Transform the screen coordinates to plot coordinates. + pub fn plot_from_screen(&self, position: Pos2) -> PlotPoint { + self.last_plot_transform.value_from_position(position) + } + + /// Add a data line. + pub fn line(&mut self, mut line: Line) { + if line.series.is_empty() { + return; + }; + + // Give the stroke an automatic color if no color has been assigned. + if line.stroke.color == Color32::TRANSPARENT { + line.stroke.color = self.auto_color(); + } + self.items.push(Box::new(line)); + } + + /// Add a polygon. The polygon has to be convex. + pub fn polygon(&mut self, mut polygon: Polygon) { + if polygon.series.is_empty() { + return; + }; + + // Give the stroke an automatic color if no color has been assigned. + if polygon.stroke.color == Color32::TRANSPARENT { + polygon.stroke.color = self.auto_color(); + } + self.items.push(Box::new(polygon)); + } + + /// Add a text. + pub fn text(&mut self, text: Text) { + if text.text.is_empty() { + return; + }; + + self.items.push(Box::new(text)); + } + + /// Add data points. + pub fn points(&mut self, mut points: Points) { + if points.series.is_empty() { + return; + }; + + // Give the points an automatic color if no color has been assigned. + if points.color == Color32::TRANSPARENT { + points.color = self.auto_color(); + } + self.items.push(Box::new(points)); + } + + /// Add arrows. + pub fn arrows(&mut self, mut arrows: Arrows) { + if arrows.origins.is_empty() || arrows.tips.is_empty() { + return; + }; + + // Give the arrows an automatic color if no color has been assigned. + if arrows.color == Color32::TRANSPARENT { + arrows.color = self.auto_color(); + } + self.items.push(Box::new(arrows)); + } + + /// Add an image. + pub fn image(&mut self, image: PlotImage) { + self.items.push(Box::new(image)); + } + + /// Add a horizontal line. + /// Can be useful e.g. to show min/max bounds or similar. + /// Always fills the full width of the plot. + pub fn hline(&mut self, mut hline: HLine) { + if hline.stroke.color == Color32::TRANSPARENT { + hline.stroke.color = self.auto_color(); + } + self.items.push(Box::new(hline)); + } + + /// Add a vertical line. + /// Can be useful e.g. to show min/max bounds or similar. + /// Always fills the full height of the plot. + pub fn vline(&mut self, mut vline: VLine) { + if vline.stroke.color == Color32::TRANSPARENT { + vline.stroke.color = self.auto_color(); + } + self.items.push(Box::new(vline)); + } + + /// Add a box plot diagram. + pub fn box_plot(&mut self, mut box_plot: BoxPlot) { + if box_plot.boxes.is_empty() { + return; + } + + // Give the elements an automatic color if no color has been assigned. + if box_plot.default_color == Color32::TRANSPARENT { + box_plot = box_plot.color(self.auto_color()); + } + self.items.push(Box::new(box_plot)); + } + + /// Add a bar chart. + pub fn bar_chart(&mut self, mut chart: BarChart) { + if chart.bars.is_empty() { + return; + } + + // Give the elements an automatic color if no color has been assigned. + if chart.default_color == Color32::TRANSPARENT { + chart = chart.color(self.auto_color()); + } + self.items.push(Box::new(chart)); + } +} diff --git a/crates/egui_plot/src/transform.rs b/crates/egui_plot/src/transform.rs index 42edef0b627c..665132519fa1 100644 --- a/crates/egui_plot/src/transform.rs +++ b/crates/egui_plot/src/transform.rs @@ -379,7 +379,9 @@ impl PlotTransform { [1.0 / self.dpos_dvalue_x(), 1.0 / self.dpos_dvalue_y()] } - /// width / height aspect ratio + /// scale.x/scale.y ratio. + /// + /// If 1.0, it means the scale factor is the same in both axes. fn aspect(&self) -> f64 { let rw = self.frame.width() as f64; let rh = self.frame.height() as f64; @@ -408,7 +410,7 @@ impl PlotTransform { } /// Sets the aspect ratio by changing either the X or Y axis (callers choice). - pub(crate) 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, axis: Axis) { let current_aspect = self.aspect(); let epsilon = 1e-5; @@ -417,12 +419,15 @@ impl PlotTransform { return; } - if change_x { - self.bounds - .expand_x((aspect / current_aspect - 1.0) * self.bounds.width() * 0.5); - } else { - self.bounds - .expand_y((current_aspect / aspect - 1.0) * self.bounds.height() * 0.5); + match axis { + Axis::X => { + self.bounds + .expand_x((aspect / current_aspect - 1.0) * self.bounds.width() * 0.5); + } + Axis::Y => { + self.bounds + .expand_y((current_aspect / aspect - 1.0) * self.bounds.height() * 0.5); + } } } }