Make `egui_plot::PlotItem` a public trait (#3943)
This commit is contained in:
parent
8f2c8664e7
commit
60e272f192
|
|
@ -23,7 +23,7 @@ mod values;
|
||||||
const DEFAULT_FILL_ALPHA: f32 = 0.05;
|
const DEFAULT_FILL_ALPHA: f32 = 0.05;
|
||||||
|
|
||||||
/// Container to pass-through several parameters related to plot visualization
|
/// Container to pass-through several parameters related to plot visualization
|
||||||
pub(super) struct PlotConfig<'a> {
|
pub struct PlotConfig<'a> {
|
||||||
pub ui: &'a Ui,
|
pub ui: &'a Ui,
|
||||||
pub transform: &'a PlotTransform,
|
pub transform: &'a PlotTransform,
|
||||||
pub show_x: bool,
|
pub show_x: bool,
|
||||||
|
|
@ -31,8 +31,8 @@ pub(super) struct PlotConfig<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait shared by things that can be drawn in the plot.
|
/// Trait shared by things that can be drawn in the plot.
|
||||||
pub(super) trait PlotItem {
|
pub trait PlotItem {
|
||||||
fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>);
|
fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>);
|
||||||
|
|
||||||
/// For plot-items which are generated based on x values (plotting functions).
|
/// For plot-items which are generated based on x values (plotting functions).
|
||||||
fn initialize(&mut self, x_range: RangeInclusive<f64>);
|
fn initialize(&mut self, x_range: RangeInclusive<f64>);
|
||||||
|
|
@ -194,7 +194,7 @@ impl HLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for HLine {
|
impl PlotItem for HLine {
|
||||||
fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
||||||
let Self {
|
let Self {
|
||||||
y,
|
y,
|
||||||
stroke,
|
stroke,
|
||||||
|
|
@ -329,7 +329,7 @@ impl VLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for VLine {
|
impl PlotItem for VLine {
|
||||||
fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
||||||
let Self {
|
let Self {
|
||||||
x,
|
x,
|
||||||
stroke,
|
stroke,
|
||||||
|
|
@ -479,7 +479,7 @@ fn y_intersection(p1: &Pos2, p2: &Pos2, y: f32) -> Option<f32> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for Line {
|
impl PlotItem for Line {
|
||||||
fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
||||||
let Self {
|
let Self {
|
||||||
series,
|
series,
|
||||||
stroke,
|
stroke,
|
||||||
|
|
@ -653,7 +653,7 @@ impl Polygon {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for Polygon {
|
impl PlotItem for Polygon {
|
||||||
fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
||||||
let Self {
|
let Self {
|
||||||
series,
|
series,
|
||||||
stroke,
|
stroke,
|
||||||
|
|
@ -778,7 +778,7 @@ impl Text {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for Text {
|
impl PlotItem for Text {
|
||||||
fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
||||||
let color = if self.color == Color32::TRANSPARENT {
|
let color = if self.color == Color32::TRANSPARENT {
|
||||||
ui.style().visuals.text_color()
|
ui.style().visuals.text_color()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -910,7 +910,7 @@ impl Points {
|
||||||
self
|
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]
|
#[inline]
|
||||||
pub fn radius(mut self, radius: impl Into<f32>) -> Self {
|
pub fn radius(mut self, radius: impl Into<f32>) -> Self {
|
||||||
self.radius = radius.into();
|
self.radius = radius.into();
|
||||||
|
|
@ -939,7 +939,7 @@ impl Points {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for Points {
|
impl PlotItem for Points {
|
||||||
fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
||||||
let sqrt_3 = 3_f32.sqrt();
|
let sqrt_3 = 3_f32.sqrt();
|
||||||
let frac_sqrt_3_2 = 3_f32.sqrt() / 2.0;
|
let frac_sqrt_3_2 = 3_f32.sqrt() / 2.0;
|
||||||
let frac_1_sqrt_2 = 1.0 / 2_f32.sqrt();
|
let frac_1_sqrt_2 = 1.0 / 2_f32.sqrt();
|
||||||
|
|
@ -1167,7 +1167,7 @@ impl Arrows {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for Arrows {
|
impl PlotItem for Arrows {
|
||||||
fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
||||||
use crate::emath::*;
|
use crate::emath::*;
|
||||||
let Self {
|
let Self {
|
||||||
origins,
|
origins,
|
||||||
|
|
@ -1331,7 +1331,7 @@ impl PlotImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for PlotImage {
|
impl PlotItem for PlotImage {
|
||||||
fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
||||||
let Self {
|
let Self {
|
||||||
position,
|
position,
|
||||||
rotation,
|
rotation,
|
||||||
|
|
@ -1565,7 +1565,7 @@ impl BarChart {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for BarChart {
|
impl PlotItem for BarChart {
|
||||||
fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
||||||
for b in &self.bars {
|
for b in &self.bars {
|
||||||
b.add_shapes(transform, self.highlight, shapes);
|
b.add_shapes(transform, self.highlight, shapes);
|
||||||
}
|
}
|
||||||
|
|
@ -1726,7 +1726,7 @@ impl BoxPlot {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlotItem for BoxPlot {
|
impl PlotItem for BoxPlot {
|
||||||
fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
|
||||||
for b in &self.boxes {
|
for b in &self.boxes {
|
||||||
b.add_shapes(transform, self.highlight, shapes);
|
b.add_shapes(transform, self.highlight, shapes);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -357,7 +357,7 @@ impl MarkerShape {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Query the points of the plot, for geometric relations like closest checks
|
/// 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)
|
/// No geometry based on single elements (examples: text, image, horizontal/vertical line)
|
||||||
None,
|
None,
|
||||||
|
|
||||||
|
|
@ -425,7 +425,7 @@ impl ExplicitGenerator {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Result of [`super::PlotItem::find_closest()`] search, identifies an element inside the item for immediate use
|
/// 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
|
/// Position of hovered-over value (or bar/box-plot/...) in PlotItem
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,34 +10,33 @@ mod axis;
|
||||||
mod items;
|
mod items;
|
||||||
mod legend;
|
mod legend;
|
||||||
mod memory;
|
mod memory;
|
||||||
|
mod plot_ui;
|
||||||
mod transform;
|
mod transform;
|
||||||
|
|
||||||
use std::{ops::RangeInclusive, sync::Arc};
|
use std::{ops::RangeInclusive, sync::Arc};
|
||||||
|
|
||||||
use egui::ahash::HashMap;
|
use egui::ahash::HashMap;
|
||||||
use epaint::util::FloatOrd;
|
use egui::*;
|
||||||
use epaint::Hsva;
|
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 axis::AxisWidget;
|
||||||
use items::PlotItem;
|
use items::{horizontal_line, rulers_color, vertical_line};
|
||||||
use legend::LegendWidget;
|
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 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 GridSpacerFn = dyn Fn(GridInput) -> Vec<GridMark>;
|
||||||
type GridSpacer = Box<GridSpacerFn>;
|
type GridSpacer = Box<GridSpacerFn>;
|
||||||
|
|
@ -81,7 +80,7 @@ impl Default for CoordinatesFormatter {
|
||||||
|
|
||||||
/// Indicates a vertical or horizontal cursor line in plot coordinates.
|
/// Indicates a vertical or horizontal cursor line in plot coordinates.
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
enum Cursor {
|
pub enum Cursor {
|
||||||
Horizontal { y: f64 },
|
Horizontal { y: f64 },
|
||||||
Vertical { x: f64 },
|
Vertical { x: f64 },
|
||||||
}
|
}
|
||||||
|
|
@ -820,13 +819,13 @@ impl Plot {
|
||||||
|
|
||||||
// Call the plot build function.
|
// Call the plot build function.
|
||||||
let mut plot_ui = PlotUi {
|
let mut plot_ui = PlotUi {
|
||||||
|
ctx: ui.ctx().clone(),
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
next_auto_color_idx: 0,
|
next_auto_color_idx: 0,
|
||||||
last_plot_transform,
|
last_plot_transform,
|
||||||
last_auto_bounds: mem.auto_bounds,
|
last_auto_bounds: mem.auto_bounds,
|
||||||
response,
|
response,
|
||||||
bounds_modifications: Vec::new(),
|
bounds_modifications: Vec::new(),
|
||||||
ctx: ui.ctx().clone(),
|
|
||||||
};
|
};
|
||||||
let inner = build_fn(&mut plot_ui);
|
let inner = build_fn(&mut plot_ui);
|
||||||
let PlotUi {
|
let PlotUi {
|
||||||
|
|
@ -981,13 +980,15 @@ impl Plot {
|
||||||
if let Some(data_aspect) = data_aspect {
|
if let Some(data_aspect) = data_aspect {
|
||||||
if let Some((_, linked_axes)) = &linked_axes {
|
if let Some((_, linked_axes)) = &linked_axes {
|
||||||
let change_x = linked_axes.y && !linked_axes.x;
|
let change_x = linked_axes.y && !linked_axes.x;
|
||||||
mem.transform
|
mem.transform.set_aspect_by_changing_axis(
|
||||||
.set_aspect_by_changing_axis(data_aspect as f64, change_x);
|
data_aspect as f64,
|
||||||
|
if change_x { Axis::X } else { Axis::Y },
|
||||||
|
);
|
||||||
} else if default_auto_bounds.any() {
|
} else if default_auto_bounds.any() {
|
||||||
mem.transform.set_aspect_by_expanding(data_aspect as f64);
|
mem.transform.set_aspect_by_expanding(data_aspect as f64);
|
||||||
} else {
|
} else {
|
||||||
mem.transform
|
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),
|
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
|
// Grid
|
||||||
|
|
||||||
|
|
@ -1671,7 +1443,7 @@ impl PreparedPlot {
|
||||||
let mut plot_ui = ui.child_ui(*transform.frame(), Layout::default());
|
let mut plot_ui = ui.child_ui(*transform.frame(), Layout::default());
|
||||||
plot_ui.set_clip_rect(*transform.frame());
|
plot_ui.set_clip_rect(*transform.frame());
|
||||||
for item in &self.items {
|
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();
|
let hover_pos = response.hover_pos();
|
||||||
|
|
|
||||||
|
|
@ -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<Box<dyn PlotItem>>,
|
||||||
|
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<BoundsModification>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -379,7 +379,9 @@ impl PlotTransform {
|
||||||
[1.0 / self.dpos_dvalue_x(), 1.0 / self.dpos_dvalue_y()]
|
[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 {
|
fn aspect(&self) -> f64 {
|
||||||
let rw = self.frame.width() as f64;
|
let rw = self.frame.width() as f64;
|
||||||
let rh = self.frame.height() 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).
|
/// 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 current_aspect = self.aspect();
|
||||||
|
|
||||||
let epsilon = 1e-5;
|
let epsilon = 1e-5;
|
||||||
|
|
@ -417,12 +419,15 @@ impl PlotTransform {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if change_x {
|
match axis {
|
||||||
self.bounds
|
Axis::X => {
|
||||||
.expand_x((aspect / current_aspect - 1.0) * self.bounds.width() * 0.5);
|
self.bounds
|
||||||
} else {
|
.expand_x((aspect / current_aspect - 1.0) * self.bounds.width() * 0.5);
|
||||||
self.bounds
|
}
|
||||||
.expand_y((current_aspect / aspect - 1.0) * self.bounds.height() * 0.5);
|
Axis::Y => {
|
||||||
|
self.bounds
|
||||||
|
.expand_y((current_aspect / aspect - 1.0) * self.bounds.height() * 0.5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue