1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 22:00:03 -04:00

Make egui_plot::PlotItem a public trait (#3943)

This commit is contained in:
Emil Ernerfeldt
2024-02-02 14:38:35 +01:00
committed by GitHub
parent 8f2c8664e7
commit 60e272f192
5 changed files with 284 additions and 277 deletions

View File

@@ -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 egui::*;
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},
};
use axis::AxisWidget;
use items::PlotItem;
use items::{horizontal_line, rulers_color, vertical_line};
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,
};
pub use legend::{Corner, Legend};
pub use transform::{PlotBounds, PlotTransform};
use items::{horizontal_line, rulers_color, vertical_line};
pub use axis::{Axis, AxisHints, HPlacement, Placement, VPlacement};
pub use memory::PlotMemory;
type LabelFormatterFn = dyn Fn(&str, &PlotPoint) -> String;
type LabelFormatter = Option<Box<LabelFormatterFn>>;
pub type LabelFormatter = Option<Box<LabelFormatterFn>>;
type GridSpacerFn = dyn Fn(GridInput) -> Vec<GridMark>;
type GridSpacer = Box<GridSpacerFn>;
@@ -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<Box<dyn PlotItem>>,
next_auto_color_idx: usize,
last_plot_transform: PlotTransform,
last_auto_bounds: Vec2b,
response: Response,
bounds_modifications: Vec<BoundsModification>,
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<PlotPoint> {
// 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();