diff --git a/crates/egui/src/widgets/plot/axis.rs b/crates/egui/src/widgets/plot/axis.rs index fdf5f2514..84d150263 100644 --- a/crates/egui/src/widgets/plot/axis.rs +++ b/crates/egui/src/widgets/plot/axis.rs @@ -14,88 +14,88 @@ use super::{transform::PlotTransform, GridMark, MIN_LINE_SPACING_IN_POINTS}; pub(super) type AxisFormatterFn = fn(f64, usize, &RangeInclusive) -> String; -/// Axis specifier. -/// -/// Used to specify which kind of axis an [`AxisConfig`] refers to. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum Axis { - X = 0, - Y = 1, -} +/// Generic constant for x-Axis +pub(super) const X_AXIS: usize = 0; +/// Generic constant for y-Axis +pub(super) const Y_AXIS: usize = 1; -/// Placement configuration for an axis. +/// Placement of an Axis. /// -/// `Default` means bottom for x, left for y. -/// `Opposite` means top for x, right for y. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum AxisPlacement { +/// `Default` means bottom for x-axis and left for y-axis. +/// `Opposite` means top for x-axis and right for y-axis. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Placement { Default, Opposite, } +// shorthand types for AxisHints, public API +/// Configuration for x-axis +pub type XAxisHints = AxisHints; +/// Configuration for y-axis +pub type YAxisHints = AxisHints; + +// shorthand types for AxisWidget +pub(super) type XAxisWidget = AxisWidget; +pub(super) type YAxisWidget = AxisWidget; /// Axis configuration. /// /// Used to configure axis label and ticks. #[derive(Clone)] -pub struct AxisConfig { - pub(super) placement: AxisPlacement, - label: String, +pub struct AxisHints { + pub(super) label: String, pub(super) formatter: AxisFormatterFn, digits: usize, - pub(super) axis: Axis, + pub(super) placement: Placement, } -impl Debug for AxisConfig { +impl Debug for AxisHints { fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + let axis_str = match AXIS { + X_AXIS => "x-axis", + Y_AXIS => "y-axis", + _ => unreachable!(), + }; write!( fmt, - "AxisConfig ( placement: {:?}, label: {}, formatter: ???, axis: {:?} )", - self.placement, self.label, self.axis + "Axis ( placement: {:?}, label: {}, formatter: ???, axis: {} )", + self.placement, self.label, axis_str ) } } -// TODO: this just a guess. It might cease to work if a user changes font size. +// TODO: this just a guess. It might cease to work if a user changes font size. const LINE_HEIGHT: f32 = 12.0; -impl AxisConfig { +impl Default for AxisHints { /// Initializes a default axis configuration for the specified [`Axis`]. /// - /// `placement` is bottom for x-axes and left for y-axes - /// `label` is empty + /// `label` is 'x' or 'y' /// `formatter` is default float to string formatter - pub const fn default(axis: Axis) -> Self { + /// maximum `digits` on tick label is 5 + fn default() -> Self { + let label = match AXIS { + X_AXIS => "x".to_string(), + Y_AXIS => "y".to_string(), + _ => unreachable!(), + }; Self { - placement: AxisPlacement::Default, - label: String::new(), + label, formatter: Self::default_formatter, digits: 5, - axis, + placement: Placement::Default, } } +} - /// Specify axis label - pub fn label(mut self, label: String) -> Self { - self.label = label; - self - } - +impl AxisHints { /// Specify custom formatter for ticks. /// /// The first parameter of `formatter` is the raw tick value as `f64`. /// The second paramter is the maximum number of characters that fit into y-labels. /// The second paramter of `formatter` is the currently shown range on this axis. - pub fn tick_formatter( - mut self, - formatter: fn(f64, usize, &RangeInclusive) -> String, - ) -> Self { - self.formatter = formatter; - self - } - - /// Specify the placement for this axis. - pub fn placement(mut self, placement: AxisPlacement) -> Self { - self.placement = placement; + pub fn formatter(mut self, fmt: fn(f64, usize, &RangeInclusive) -> String) -> Self { + self.formatter = fmt; self } @@ -111,46 +111,65 @@ impl AxisConfig { format!("{}", tick_rounded) } + /// Specify axis label. + /// + /// The default is 'x' for x-axes and 'y' for y-axes. + pub fn label(mut self, label: String) -> Self { + self.label = label; + self + } + + /// Specify maximum number of digits for ticks. + /// + /// This is considered by the default tick formatter + /// and affects the width of the internal y-axis widget pub fn max_digits(mut self, digits: usize) -> Self { self.digits = digits; self } + /// Specify the placement of the axis. + pub fn placement(mut self, placement: Placement) -> Self { + self.placement = placement; + self + } + pub(super) fn thickness(&self) -> f32 { - match self.axis { - Axis::X => { + match AXIS { + X_AXIS => { if self.label.is_empty() { 1.0 * LINE_HEIGHT } else { 3.0 * LINE_HEIGHT } } - Axis::Y => { + Y_AXIS => { if self.label.is_empty() { (self.digits as f32) * LINE_HEIGHT } else { (self.digits as f32 + 1.0) * LINE_HEIGHT } } + _ => unreachable!(), } } } #[derive(Clone)] -pub(super) struct AxisWidget { +pub(super) struct AxisWidget { pub(super) range: RangeInclusive, - pub(super) config: AxisConfig, + pub(super) hints: AxisHints, pub(super) rect: Rect, pub(super) transform: Option, pub(super) steps: Vec, } -impl AxisWidget { +impl AxisWidget { /// if `rect` as width or height == 0, is will be automatically calculated from ticks and text. - pub(super) fn new(config: AxisConfig, rect: Rect) -> Self { + pub(super) fn new(hints: AxisHints, rect: Rect) -> Self { Self { range: (0.0..=0.0), - config, + hints, rect, transform: None, steps: Vec::new(), @@ -158,54 +177,57 @@ impl AxisWidget { } } -impl Widget for AxisWidget { +impl Widget for AxisWidget { fn ui(self, ui: &mut Ui) -> Response { // --- add label --- let response = ui.allocate_rect(self.rect, Sense::click_and_drag()); if ui.is_rect_visible(response.rect) { let visuals = ui.style().visuals.clone(); - let text: WidgetText = self.config.label.into(); + let text: WidgetText = self.hints.label.into(); let galley = text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Body); let text_color = visuals .override_text_color .unwrap_or(ui.visuals().text_color()); - let angle: f32 = match self.config.axis { - Axis::X => 0.0, - Axis::Y => -std::f32::consts::PI * 0.5, + let angle: f32 = match AXIS { + X_AXIS => 0.0, + Y_AXIS => -std::f32::consts::PI * 0.5, + _ => unreachable!(), }; // select text_pos and angle depending on placement and orientation of widget - let text_pos = match self.config.placement { - AxisPlacement::Default => match self.config.axis { - Axis::X => { + let text_pos = match self.hints.placement { + Placement::Default => match AXIS { + X_AXIS => { let pos = response.rect.center_bottom(); Pos2 { x: pos.x - galley.size().x / 2.0, y: pos.y - galley.size().y * 1.25, } } - Axis::Y => { + Y_AXIS => { let pos = response.rect.left_center(); Pos2 { x: pos.x, y: pos.y + galley.size().x / 2.0, } } + _ => unreachable!(), }, - AxisPlacement::Opposite => match self.config.axis { - Axis::X => { + Placement::Opposite => match AXIS { + X_AXIS => { let pos = response.rect.center_top(); Pos2 { x: pos.x - galley.size().x / 2.0, y: pos.y + galley.size().y * 0.25, } } - Axis::Y => { + Y_AXIS => { let pos = response.rect.right_center(); Pos2 { x: pos.x - galley.size().y * 1.5, y: pos.y + galley.size().x / 2.0, } } + _ => unreachable!(), }, }; let shape = TextShape { @@ -225,11 +247,10 @@ impl Widget for AxisWidget { }; for step in self.steps { - let text = (self.config.formatter)(step.value, self.config.digits, &self.range); + let text = (self.hints.formatter)(step.value, self.hints.digits, &self.range); if !text.is_empty() { - let spacing_in_points = (transform.dpos_dvalue()[self.config.axis as usize] - * step.step_size) - .abs() as f32; + let spacing_in_points = + (transform.dpos_dvalue()[AXIS] * step.step_size).abs() as f32; let line_alpha = remap_clamp( spacing_in_points, @@ -242,11 +263,11 @@ impl Widget for AxisWidget { let galley = ui .painter() .layout_no_wrap(text, font_id.clone(), line_color); - let text_pos = match self.config.axis { - Axis::X => { - let y = match self.config.placement { - AxisPlacement::Default => self.rect.min.y, - AxisPlacement::Opposite => self.rect.max.y - galley.size().y, + let text_pos = match AXIS { + X_AXIS => { + let y = match self.hints.placement { + Placement::Default => self.rect.min.y, + Placement::Opposite => self.rect.max.y - galley.size().y, }; let projected_point = super::PlotPoint::new(step.value, 0.0); Pos2 { @@ -255,10 +276,10 @@ impl Widget for AxisWidget { y, } } - Axis::Y => { - let x = match self.config.placement { - AxisPlacement::Default => self.rect.max.x - galley.size().x, - AxisPlacement::Opposite => self.rect.min.x, + Y_AXIS => { + let x = match self.hints.placement { + Placement::Default => self.rect.max.x - galley.size().x, + Placement::Opposite => self.rect.min.x, }; let projected_point = super::PlotPoint::new(0.0, step.value); Pos2 { @@ -267,6 +288,7 @@ impl Widget for AxisWidget { - galley.size().y / 2.0, } } + _ => unreachable!(), }; ui.painter().add(Shape::galley(text_pos, galley)); diff --git a/crates/egui/src/widgets/plot/memory.rs b/crates/egui/src/widgets/plot/memory.rs new file mode 100644 index 000000000..844dcb5f6 --- /dev/null +++ b/crates/egui/src/widgets/plot/memory.rs @@ -0,0 +1,29 @@ +use epaint::Pos2; + +use crate::{Id, Context}; + +use super::{AxisBools, transform::ScreenTransform}; + +/// Information about the plot that has to persist between frames. +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[derive(Clone)] +pub(super) struct PlotMemory { + /// Indicates if the user has modified the bounds, for example by moving or zooming, + /// or if the bounds should be calculated based by included point or auto bounds. + pub(super) bounds_modified: AxisBools, + pub(super) hovered_entry: Option, + pub(super) hidden_items: ahash::HashSet, + pub(super) last_screen_transform: ScreenTransform, + /// Allows to remember the first click position when performing a boxed zoom + pub(super) last_click_pos_for_zoom: Option, +} + +impl PlotMemory { + pub fn load(ctx: &Context, id: Id) -> Option { + ctx.data().get_persisted(id) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.data().insert_persisted(id, self); + } +} diff --git a/crates/egui/src/widgets/plot/mod.rs b/crates/egui/src/widgets/plot/mod.rs index ef482b3c5..f26808691 100644 --- a/crates/egui/src/widgets/plot/mod.rs +++ b/crates/egui/src/widgets/plot/mod.rs @@ -1,13 +1,13 @@ //! Simple plotting library. use ahash::HashMap; +use std::ops::RangeInclusive; use crate::*; use epaint::util::FloatOrd; use epaint::Hsva; -pub use axis::{AxisPlacement, Axis, AxisConfig}; -use axis::AxisWidget; +use axis::{XAxisWidget, YAxisWidget, X_AXIS, Y_AXIS}; use items::PlotItem; use legend::LegendWidget; @@ -20,7 +20,9 @@ pub use transform::{PlotBounds, PlotTransform}; use items::{horizontal_line, rulers_color, vertical_line}; -pub mod axis; +pub use axis::{Placement, XAxisHints, YAxisHints}; + +mod axis; mod items; mod legend; mod transform; @@ -180,8 +182,7 @@ pub struct PlotResponse { pub struct Plot { id_source: Id, - center_x_axis: bool, - center_y_axis: bool, + center_axis: AxisBools, allow_zoom: AxisBools, allow_drag: AxisBools, allow_scroll: bool, @@ -206,11 +207,12 @@ pub struct Plot { show_y: bool, label_formatter: LabelFormatter, coordinates_formatter: Option<(Corner, CoordinatesFormatter)>, - axis_config: Vec, + x_axes: Vec, // default x axes + y_axes: Vec, // default y axes legend_config: Option, show_background: bool, - show_axes: [bool; 2], - + show_axes: AxisBools, + show_grid: AxisBools, grid_spacers: [GridSpacer; 2], sharp_grid_lines: bool, clamp_grid: bool, @@ -222,8 +224,7 @@ impl Plot { Self { id_source: Id::new(id_source), - center_x_axis: false, - center_y_axis: false, + center_axis: false.into(), allow_zoom: true.into(), allow_drag: true.into(), allow_scroll: true, @@ -248,11 +249,12 @@ impl Plot { show_y: true, label_formatter: None, coordinates_formatter: None, - axis_config: vec![AxisConfig::default(Axis::X), AxisConfig::default(Axis::Y)], + x_axes: vec![XAxisHints::default()], + y_axes: vec![YAxisHints::default()], legend_config: None, show_background: true, - show_axes: [true; 2], - + show_axes: true.into(), + show_grid: true.into(), grid_spacers: [log_grid_spacer(10), log_grid_spacer(10)], sharp_grid_lines: true, clamp_grid: false, @@ -311,13 +313,13 @@ impl Plot { /// Always keep the x-axis centered. Default: `false`. pub fn center_x_axis(mut self, on: bool) -> Self { - self.center_x_axis = on; + self.center_axis.x = on; self } /// Always keep the y-axis centered. Default: `false`. pub fn center_y_axis(mut self, on: bool) -> Self { - self.center_y_axis = on; + self.center_axis.y = on; self } @@ -506,11 +508,21 @@ impl Plot { self } - /// Show the axes. - /// Can be useful to disable if the plot is overlaid over an existing grid or content. + /// Show axis labels. + /// /// Default: `[true; 2]`. pub fn show_axes(mut self, show: [bool; 2]) -> Self { - self.show_axes = show; + self.show_axes.x = show[0]; + self.show_axes.y = show[1]; + self + } + + /// Show the grid. + /// Can be useful to disable if the plot is overlaid over an existing grid or content. + /// Default: `[true; 2]`. + pub fn show_grid(mut self, show: [bool; 2]) -> Self { + self.show_grid.x = show[0]; + self.show_grid.y = show[1]; self } @@ -553,12 +565,74 @@ impl Plot { self } - /// Configure Axes. + /// Set the x axis label of the bottom x-axis + pub fn x_axis_label(mut self, label: String) -> Self { + if !self.x_axes.is_empty() { + self.x_axes[0].label = label; + } + self + } + /// Set the y axis label of the left y-axis + pub fn y_axis_label(mut self, label: String) -> Self { + if !self.y_axes.is_empty() { + self.y_axes[0].label = label; + } + self + } + + /// Set the x-axis position + pub fn x_axis_position(mut self, placement: axis::Placement) -> Self { + if !self.x_axes.is_empty() { + self.x_axes[0].placement = placement; + } + self + } + + /// Set the y-axis position + pub fn y_axis_position(mut self, placement: axis::Placement) -> Self { + if !self.y_axes.is_empty() { + self.y_axes[0].placement = placement; + } + self + } + + /// Specify custom formatter for ticks on x-axis /// - /// Takes a vector of [`AxisConfig`] objects as argument to configure the plot axes. - /// See [`AxisConfig`] for available options. - pub fn axes(mut self, axis_config: Vec) -> Self { - self.axis_config = axis_config; + /// The first parameter of `fmt` is the raw tick value as `f64`. + /// The second paramter is the maximum requested number of characters per tick label. + /// The second paramter of `fmt` is the currently shown range on this axis. + pub fn x_axis_formatter(mut self, fmt: fn(f64, usize, &RangeInclusive) -> String) -> Self { + if !self.x_axes.is_empty() { + self.x_axes[0].formatter = fmt; + } + self + } + + /// Specify custom formatter for ticks on y-axis + /// + /// The first parameter of `formatter` is the raw tick value as `f64`. + /// The second paramter is the maximum requested number of characters per tick label. + /// The second paramter of `formatter` is the currently shown range on this axis. + pub fn y_axis_formatter(mut self, fmt: fn(f64, usize, &RangeInclusive) -> String) -> Self { + if !self.y_axes.is_empty() { + self.y_axes[0].formatter = fmt; + } + self + } + + /// Set custom configuration for bottom x-axis + /// + /// More than one axis may be specified. + pub fn custom_x_axes(mut self, hints: Vec) -> Self { + self.x_axes = hints; + self + } + + /// Set custom configuration for left y-axis + /// + /// More than one axis may be specified. + pub fn custom_y_axes(mut self, hints: Vec) -> Self { + self.y_axes = hints; self } @@ -574,8 +648,7 @@ impl Plot { ) -> PlotResponse { let Self { id_source, - center_x_axis, - center_y_axis, + center_axis, allow_zoom, allow_drag, allow_scroll, @@ -594,11 +667,13 @@ impl Plot { mut show_y, label_formatter, coordinates_formatter, - axis_config, + x_axes, + y_axes, legend_config, reset, show_background, show_axes, + show_grid, linked_axes, linked_cursors, @@ -637,7 +712,6 @@ impl Plot { min: pos, max: pos + size, }; - // Next we want to create this layout. // Incides are only examples. // @@ -660,41 +734,47 @@ impl Plot { // + +--------------------+-d-+ // - let mut axis_widgets = Vec::::new(); - let plot_rect: Rect; - { + let mut plot_rect: Rect = { // find dimensions of axis labels // for a, b, c, d meanings see picture let mut a = 0.0; let mut b = 0.0; let mut c = 0.0; let mut d = 0.0; - for cfg in &axis_config { - match cfg.placement { - AxisPlacement::Default => match cfg.axis { - Axis::X => { + if show_axes.x { + for cfg in &x_axes { + match cfg.placement { + axis::Placement::Default => { a += cfg.thickness(); } - Axis::Y => { - b += cfg.thickness(); - } - }, - AxisPlacement::Opposite => match cfg.axis { - Axis::X => { + axis::Placement::Opposite => { c += cfg.thickness(); } - Axis::Y => { - d += cfg.thickness(); - } - }, + } } } + if show_axes.y { + for cfg in &y_axes { + match cfg.placement { + axis::Placement::Default => { + b += cfg.thickness(); + } + axis::Placement::Opposite => { + d += cfg.thickness(); + } + } + } + } + // determine plot rectangle - plot_rect = Rect { + Rect { min: complete_rect.min + Vec2::new(b, c), max: complete_rect.max - Vec2::new(d, a), - }; - + } + }; + let mut x_axis_widgets = Vec::::new(); + let mut y_axis_widgets = Vec::::new(); + { // determine absolute rectangle for each axis label widget // widget cnt per border of plot in order left, top, right, bottom struct WidgetCnt { @@ -709,18 +789,11 @@ impl Plot { right: 0, bottom: 0, }; - for cfg in &axis_config { - let size_x = Vec2 { - x: cfg.thickness(), - y: 0.0, - }; - let size_y = Vec2 { - x: 0.0, - y: cfg.thickness(), - }; - let rect: Rect = match cfg.placement { - AxisPlacement::Default => match cfg.axis { - Axis::X => { + if show_axes.x { + for cfg in &x_axes { + let size_y = Vec2::new(0.0, cfg.thickness()); + let rect = match cfg.placement { + axis::Placement::Default => { let off = widget_cnt.bottom as f32; widget_cnt.bottom += 1; Rect { @@ -728,17 +801,7 @@ impl Plot { max: plot_rect.right_bottom() + size_y * (off + 1.0), } } - Axis::Y => { - let off = widget_cnt.left as f32; - widget_cnt.left += 1; - Rect { - min: plot_rect.left_top() - size_x * (off + 1.0), - max: plot_rect.left_bottom() - size_x * off, - } - } - }, - AxisPlacement::Opposite => match cfg.axis { - Axis::X => { + axis::Placement::Opposite => { let off = widget_cnt.top as f32; widget_cnt.top += 1; Rect { @@ -746,7 +809,23 @@ impl Plot { max: plot_rect.right_top() - size_y * off, } } - Axis::Y => { + }; + x_axis_widgets.push(XAxisWidget::new(cfg.clone(), rect)); + } + } + if show_axes.y { + for cfg in &y_axes { + let size_x = Vec2::new(cfg.thickness(), 0.0); + let rect = match cfg.placement { + axis::Placement::Default => { + let off = widget_cnt.left as f32; + widget_cnt.left += 1; + Rect { + min: plot_rect.left_top() - size_x * (off + 1.0), + max: plot_rect.left_bottom() - size_x * off, + } + } + axis::Placement::Opposite => { let off = widget_cnt.right as f32; widget_cnt.right += 1; Rect { @@ -754,14 +833,20 @@ impl Plot { max: plot_rect.right_bottom() + size_x * (off + 1.0), } } - }, - }; - axis_widgets.push(AxisWidget::new(cfg.clone(), rect)); + }; + y_axis_widgets.push(YAxisWidget::new(cfg.clone(), rect)); + } } } + // if to little space, remove axis widgets + if plot_rect.width() <= 0.0 || plot_rect.height() <= 0.0 { + y_axis_widgets.clear(); + x_axis_widgets.clear(); + plot_rect = complete_rect; + } + // Allocate the plot window. - // let (rect, response) = ui.allocate_exact_size(size, Sense::drag()); let response = ui.allocate_rect(plot_rect, Sense::drag()); let rect = plot_rect; // Load or initialize the memory. @@ -786,8 +871,8 @@ impl Plot { last_plot_transform: PlotTransform::new( rect, min_auto_bounds, - center_x_axis, - center_y_axis, + center_axis.x, + center_axis.y, ), last_click_pos_for_zoom: None, }); @@ -948,7 +1033,7 @@ impl Plot { } } - let mut transform = PlotTransform::new(rect, bounds, center_x_axis, center_y_axis); + let mut transform = PlotTransform::new(rect, bounds, center_axis.x, center_axis.y); // Enforce aspect ratio if let Some(data_aspect) = data_aspect { @@ -1056,23 +1141,36 @@ impl Plot { } } - for mut widget in axis_widgets { - let axis = widget.config.axis; - let bounds = transform.bounds(); - let axis_range = match axis { - Axis::X => bounds.range_x(), - Axis::Y => bounds.range_y(), - }; - widget.range = axis_range; + // --- transform initialized + + // Add legend widgets to plot + let bounds = transform.bounds(); + let x_axis_range = bounds.range_x(); + let x_steps = { let input = GridInput { - bounds: (bounds.min[axis as usize], bounds.max[axis as usize]), - base_step_size: transform.dvalue_dpos()[axis as usize] - * MIN_LINE_SPACING_IN_POINTS - * 2.0, + bounds: (bounds.min[X_AXIS], bounds.max[X_AXIS]), + base_step_size: transform.dvalue_dpos()[X_AXIS] * MIN_LINE_SPACING_IN_POINTS * 2.0, }; - let steps = (grid_spacers[axis as usize])(input); + (grid_spacers[X_AXIS])(input) + }; + let y_axis_range = bounds.range_y(); + let y_steps = { + let input = GridInput { + bounds: (bounds.min[Y_AXIS], bounds.max[Y_AXIS]), + base_step_size: transform.dvalue_dpos()[Y_AXIS] * MIN_LINE_SPACING_IN_POINTS * 2.0, + }; + (grid_spacers[Y_AXIS])(input) + }; + for mut widget in x_axis_widgets { + widget.range = x_axis_range.clone(); widget.transform = Some(transform.clone()); - widget.steps = steps; + widget.steps = x_steps.clone(); + ui.add(widget); + } + for mut widget in y_axis_widgets { + widget.range = y_axis_range.clone(); + widget.transform = Some(transform.clone()); + widget.steps = y_steps.clone(); ui.add(widget); } @@ -1087,11 +1185,10 @@ impl Plot { show_y, label_formatter, coordinates_formatter, - // axis_config, - show_axes, - 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), + show_grid, + transform: transform.clone(), + draw_cursor_x: linked_cursors.as_ref().map_or(false, |group| group.1.x), + draw_cursor_y: linked_cursors.as_ref().map_or(false, |group| group.1.y), draw_cursors, grid_spacers, sharp_grid_lines, @@ -1451,13 +1548,13 @@ struct PreparedPlot { label_formatter: LabelFormatter, coordinates_formatter: Option<(Corner, CoordinatesFormatter)>, // axis_formatters: [AxisFormatter; 2], - show_axes: [bool; 2], transform: PlotTransform, + show_grid: AxisBools, + grid_spacers: [GridSpacer; 2], draw_cursor_x: bool, draw_cursor_y: bool, draw_cursors: Vec, - grid_spacers: [GridSpacer; 2], sharp_grid_lines: bool, clamp_grid: bool, } @@ -1466,11 +1563,11 @@ impl PreparedPlot { fn ui(self, ui: &mut Ui, response: &Response) -> Vec { let mut axes_shapes = Vec::new(); - if self.show_axes[Axis::X as usize] { - self.paint_axis(ui, Axis::X, &mut axes_shapes, self.sharp_grid_lines); + if self.show_grid.x { + self.paint_grid::<{ X_AXIS }>(ui, &mut axes_shapes); } - if self.show_axes[Axis::Y as usize] { - self.paint_axis(ui, Axis::Y, &mut axes_shapes, self.sharp_grid_lines); + if self.show_grid.y { + self.paint_grid::<{ Y_AXIS }>(ui, &mut axes_shapes); } // Sort the axes by strength so that those with higher strength are drawn in front. @@ -1547,14 +1644,7 @@ impl PreparedPlot { cursors } - // `axis`=0 means x-axis, `axis`=1 means y-axis. - fn paint_axis( - &self, - ui: &Ui, - axis: Axis, - shapes: &mut Vec<(Shape, f32)>, - sharp_grid_lines: bool, - ) { + fn paint_grid(&self, ui: &Ui, shapes: &mut Vec<(Shape, f32)>) { #![allow(clippy::collapsible_else_if)] let Self { transform, @@ -1566,14 +1656,13 @@ impl PreparedPlot { // Where on the cross-dimension to show the label values let bounds = transform.bounds(); - let value_cross = - 0.0_f64.clamp(bounds.min[1 - axis as usize], bounds.max[1 - axis as usize]); + let value_cross = 0.0_f64.clamp(bounds.min[1 - AXIS], bounds.max[1 - AXIS]); let input = GridInput { - bounds: (bounds.min[axis as usize], bounds.max[axis as usize]), - base_step_size: transform.dvalue_dpos()[axis as usize] * MIN_LINE_SPACING_IN_POINTS, + bounds: (bounds.min[AXIS], bounds.max[AXIS]), + base_step_size: transform.dvalue_dpos()[AXIS] * MIN_LINE_SPACING_IN_POINTS, }; - let steps = (grid_spacers[axis as usize])(input); + let steps = (grid_spacers[AXIS])(input); let clamp_range = clamp_grid.then(|| { let mut tight_bounds = PlotBounds::NOTHING; @@ -1589,7 +1678,7 @@ impl PreparedPlot { let value_main = step.value; if let Some(clamp_range) = clamp_range { - if axis == Axis::X { + if AXIS == X_AXIS { if !clamp_range.range_x().contains(&value_main) { continue; }; @@ -1600,14 +1689,14 @@ impl PreparedPlot { } } - let value = match axis { - Axis::X => PlotPoint::new(value_main, value_cross), - Axis::Y => PlotPoint::new(value_cross, value_main), + let value = match AXIS { + X_AXIS => PlotPoint::new(value_main, value_cross), + Y_AXIS => PlotPoint::new(value_cross, value_main), + _ => unreachable!(), }; let pos_in_gui = transform.position_from_point(&value); - let spacing_in_points = - (transform.dpos_dvalue()[axis as usize] * step.step_size).abs() as f32; + let spacing_in_points = (transform.dpos_dvalue()[AXIS] * step.step_size).abs() as f32; if spacing_in_points > MIN_LINE_SPACING_IN_POINTS as f32 { let line_strength = remap_clamp( @@ -1620,11 +1709,11 @@ impl PreparedPlot { let mut p0 = pos_in_gui; let mut p1 = pos_in_gui; - p0[1 - axis as usize] = transform.frame().min[1 - axis as usize]; - p1[1 - axis as usize] = transform.frame().max[1 - axis as usize]; + p0[1 - AXIS] = transform.frame().min[1 - AXIS]; + p1[1 - AXIS] = transform.frame().max[1 - AXIS]; if let Some(clamp_range) = clamp_range { - if axis == Axis::X { + if AXIS == X_AXIS { p0.y = transform.position_from_point_y(clamp_range.min[1]); p1.y = transform.position_from_point_y(clamp_range.max[1]); } else { @@ -1633,7 +1722,7 @@ impl PreparedPlot { } } - if sharp_grid_lines { + if self.sharp_grid_lines { // Round to avoid aliasing p0 = ui.ctx().round_pos_to_pixels(p0); p1 = ui.ctx().round_pos_to_pixels(p1); diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index eeb93389d..0fe6fa42e 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, AxisConfig, GridInput, GridMark, PlotResponse}; +use egui::plot::{AxisBools, GridInput, GridMark, PlotResponse}; use egui::*; use plot::{ Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, HLine, @@ -265,10 +265,8 @@ impl LineDemo { self.time += ui.input(|i| i.unstable_dt).at_most(1.0 / 30.0) as f64; }; let mut plot = Plot::new("lines_demo") - .axes(vec![ - AxisConfig::default(plot::Axis::X).label("x".to_string()), - AxisConfig::default(plot::Axis::Y).label("y".to_string()), - ]) + .x_axis_label("x".to_string()) + .y_axis_label("y".to_string()) .legend(Legend::default()); if self.square { plot = plot.view_aspect(1.0); @@ -554,19 +552,12 @@ impl CustomAxisDemo { ui.label("Zoom in on the X-axis to see hours and minutes"); - let axes = vec![ - AxisConfig::default(plot::Axis::X) - .tick_formatter(x_fmt) - .label("Percent".to_string()), - AxisConfig::default(plot::Axis::Y) - .tick_formatter(y_fmt) - .max_digits(4) - .label("Time".to_string()), - ]; - Plot::new("custom_axes") .data_aspect(2.0 * MINS_PER_DAY as f32) - .axes(axes) + .x_axis_label("Percent".to_string()) + .x_axis_formatter(x_fmt) + .y_axis_label("Time".to_string()) + .y_axis_formatter(y_fmt) .x_grid_spacer(CustomAxisDemo::x_grid) .label_formatter(label_fmt) .show(ui, |plot_ui| {