Allow cursors to be linked for plots (#1722)
* Allow cursors to be linked * Link cursors in demo * Refactor cursor memory to deal with removal of plots * Refactor PlotItem::on_hover to produce a list of cursors * Use a separate `LinkedCursorsGroup` type to link cursors. * Refactor `Cursor`. * Inline `push_argument_ruler` and `push_value_ruler`. * Update documentation * Update doc reference
This commit is contained in:
parent
b978b06159
commit
9be060fe69
5 changed files with 242 additions and 46 deletions
|
@ -2,7 +2,7 @@ use crate::emath::NumExt;
|
||||||
use crate::epaint::{Color32, RectShape, Rounding, Shape, Stroke};
|
use crate::epaint::{Color32, RectShape, Rounding, Shape, Stroke};
|
||||||
|
|
||||||
use super::{add_rulers_and_text, highlighted_color, Orientation, PlotConfig, RectElement};
|
use super::{add_rulers_and_text, highlighted_color, Orientation, PlotConfig, RectElement};
|
||||||
use crate::plot::{BarChart, PlotPoint, ScreenTransform};
|
use crate::plot::{BarChart, Cursor, PlotPoint, ScreenTransform};
|
||||||
|
|
||||||
/// One bar in a [`BarChart`]. Potentially floating, allowing stacked bar charts.
|
/// One bar in a [`BarChart`]. Potentially floating, allowing stacked bar charts.
|
||||||
/// Width can be changed to allow variable-width histograms.
|
/// Width can be changed to allow variable-width histograms.
|
||||||
|
@ -142,13 +142,14 @@ impl Bar {
|
||||||
parent: &BarChart,
|
parent: &BarChart,
|
||||||
plot: &PlotConfig<'_>,
|
plot: &PlotConfig<'_>,
|
||||||
shapes: &mut Vec<Shape>,
|
shapes: &mut Vec<Shape>,
|
||||||
|
cursors: &mut Vec<Cursor>,
|
||||||
) {
|
) {
|
||||||
let text: Option<String> = parent
|
let text: Option<String> = parent
|
||||||
.element_formatter
|
.element_formatter
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|fmt| fmt(self, parent));
|
.map(|fmt| fmt(self, parent));
|
||||||
|
|
||||||
add_rulers_and_text(self, plot, text, shapes);
|
add_rulers_and_text(self, plot, text, shapes, cursors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::emath::NumExt;
|
||||||
use crate::epaint::{Color32, RectShape, Rounding, Shape, Stroke};
|
use crate::epaint::{Color32, RectShape, Rounding, Shape, Stroke};
|
||||||
|
|
||||||
use super::{add_rulers_and_text, highlighted_color, Orientation, PlotConfig, RectElement};
|
use super::{add_rulers_and_text, highlighted_color, Orientation, PlotConfig, RectElement};
|
||||||
use crate::plot::{BoxPlot, PlotPoint, ScreenTransform};
|
use crate::plot::{BoxPlot, Cursor, PlotPoint, ScreenTransform};
|
||||||
|
|
||||||
/// Contains the values of a single box in a box plot.
|
/// Contains the values of a single box in a box plot.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -221,13 +221,14 @@ impl BoxElem {
|
||||||
parent: &BoxPlot,
|
parent: &BoxPlot,
|
||||||
plot: &PlotConfig<'_>,
|
plot: &PlotConfig<'_>,
|
||||||
shapes: &mut Vec<Shape>,
|
shapes: &mut Vec<Shape>,
|
||||||
|
cursors: &mut Vec<Cursor>,
|
||||||
) {
|
) {
|
||||||
let text: Option<String> = parent
|
let text: Option<String> = parent
|
||||||
.element_formatter
|
.element_formatter
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|fmt| fmt(self, parent));
|
.map(|fmt| fmt(self, parent));
|
||||||
|
|
||||||
add_rulers_and_text(self, plot, text, shapes);
|
add_rulers_and_text(self, plot, text, shapes, cursors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use epaint::Mesh;
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use super::{LabelFormatter, PlotBounds, ScreenTransform};
|
use super::{Cursor, LabelFormatter, PlotBounds, ScreenTransform};
|
||||||
use rect_elem::*;
|
use rect_elem::*;
|
||||||
use values::{ClosestElem, PlotGeometry};
|
use values::{ClosestElem, PlotGeometry};
|
||||||
|
|
||||||
|
@ -72,6 +72,7 @@ pub(super) trait PlotItem {
|
||||||
&self,
|
&self,
|
||||||
elem: ClosestElem,
|
elem: ClosestElem,
|
||||||
shapes: &mut Vec<Shape>,
|
shapes: &mut Vec<Shape>,
|
||||||
|
cursors: &mut Vec<Cursor>,
|
||||||
plot: &PlotConfig<'_>,
|
plot: &PlotConfig<'_>,
|
||||||
label_formatter: &LabelFormatter,
|
label_formatter: &LabelFormatter,
|
||||||
) {
|
) {
|
||||||
|
@ -96,7 +97,15 @@ pub(super) trait PlotItem {
|
||||||
let pointer = plot.transform.position_from_point(&value);
|
let pointer = plot.transform.position_from_point(&value);
|
||||||
shapes.push(Shape::circle_filled(pointer, 3.0, line_color));
|
shapes.push(Shape::circle_filled(pointer, 3.0, line_color));
|
||||||
|
|
||||||
rulers_at_value(pointer, value, self.name(), plot, shapes, label_formatter);
|
rulers_at_value(
|
||||||
|
pointer,
|
||||||
|
value,
|
||||||
|
self.name(),
|
||||||
|
plot,
|
||||||
|
shapes,
|
||||||
|
cursors,
|
||||||
|
label_formatter,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1392,13 +1401,14 @@ impl PlotItem for BarChart {
|
||||||
&self,
|
&self,
|
||||||
elem: ClosestElem,
|
elem: ClosestElem,
|
||||||
shapes: &mut Vec<Shape>,
|
shapes: &mut Vec<Shape>,
|
||||||
|
cursors: &mut Vec<Cursor>,
|
||||||
plot: &PlotConfig<'_>,
|
plot: &PlotConfig<'_>,
|
||||||
_: &LabelFormatter,
|
_: &LabelFormatter,
|
||||||
) {
|
) {
|
||||||
let bar = &self.bars[elem.index];
|
let bar = &self.bars[elem.index];
|
||||||
|
|
||||||
bar.add_shapes(plot.transform, true, shapes);
|
bar.add_shapes(plot.transform, true, shapes);
|
||||||
bar.add_rulers_and_text(self, plot, shapes);
|
bar.add_rulers_and_text(self, plot, shapes, cursors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1534,20 +1544,21 @@ impl PlotItem for BoxPlot {
|
||||||
&self,
|
&self,
|
||||||
elem: ClosestElem,
|
elem: ClosestElem,
|
||||||
shapes: &mut Vec<Shape>,
|
shapes: &mut Vec<Shape>,
|
||||||
|
cursors: &mut Vec<Cursor>,
|
||||||
plot: &PlotConfig<'_>,
|
plot: &PlotConfig<'_>,
|
||||||
_: &LabelFormatter,
|
_: &LabelFormatter,
|
||||||
) {
|
) {
|
||||||
let box_plot = &self.boxes[elem.index];
|
let box_plot = &self.boxes[elem.index];
|
||||||
|
|
||||||
box_plot.add_shapes(plot.transform, true, shapes);
|
box_plot.add_shapes(plot.transform, true, shapes);
|
||||||
box_plot.add_rulers_and_text(self, plot, shapes);
|
box_plot.add_rulers_and_text(self, plot, shapes, cursors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Helper functions
|
// Helper functions
|
||||||
|
|
||||||
fn rulers_color(ui: &Ui) -> Color32 {
|
pub(crate) fn rulers_color(ui: &Ui) -> Color32 {
|
||||||
if ui.visuals().dark_mode {
|
if ui.visuals().dark_mode {
|
||||||
Color32::from_gray(100).additive()
|
Color32::from_gray(100).additive()
|
||||||
} else {
|
} else {
|
||||||
|
@ -1555,7 +1566,11 @@ fn rulers_color(ui: &Ui) -> Color32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vertical_line(pointer: Pos2, transform: &ScreenTransform, line_color: Color32) -> Shape {
|
pub(crate) fn vertical_line(
|
||||||
|
pointer: Pos2,
|
||||||
|
transform: &ScreenTransform,
|
||||||
|
line_color: Color32,
|
||||||
|
) -> Shape {
|
||||||
let frame = transform.frame();
|
let frame = transform.frame();
|
||||||
Shape::line_segment(
|
Shape::line_segment(
|
||||||
[
|
[
|
||||||
|
@ -1566,7 +1581,11 @@ fn vertical_line(pointer: Pos2, transform: &ScreenTransform, line_color: Color32
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn horizontal_line(pointer: Pos2, transform: &ScreenTransform, line_color: Color32) -> Shape {
|
pub(crate) fn horizontal_line(
|
||||||
|
pointer: Pos2,
|
||||||
|
transform: &ScreenTransform,
|
||||||
|
line_color: Color32,
|
||||||
|
) -> Shape {
|
||||||
let frame = transform.frame();
|
let frame = transform.frame();
|
||||||
Shape::line_segment(
|
Shape::line_segment(
|
||||||
[
|
[
|
||||||
|
@ -1582,6 +1601,7 @@ fn add_rulers_and_text(
|
||||||
plot: &PlotConfig<'_>,
|
plot: &PlotConfig<'_>,
|
||||||
text: Option<String>,
|
text: Option<String>,
|
||||||
shapes: &mut Vec<Shape>,
|
shapes: &mut Vec<Shape>,
|
||||||
|
cursors: &mut Vec<Cursor>,
|
||||||
) {
|
) {
|
||||||
let orientation = elem.orientation();
|
let orientation = elem.orientation();
|
||||||
let show_argument = plot.show_x && orientation == Orientation::Vertical
|
let show_argument = plot.show_x && orientation == Orientation::Vertical
|
||||||
|
@ -1589,37 +1609,23 @@ fn add_rulers_and_text(
|
||||||
let show_values = plot.show_y && orientation == Orientation::Vertical
|
let show_values = plot.show_y && orientation == Orientation::Vertical
|
||||||
|| plot.show_x && orientation == Orientation::Horizontal;
|
|| plot.show_x && orientation == Orientation::Horizontal;
|
||||||
|
|
||||||
let line_color = rulers_color(plot.ui);
|
|
||||||
|
|
||||||
// Rulers for argument (usually vertical)
|
// Rulers for argument (usually vertical)
|
||||||
if show_argument {
|
if show_argument {
|
||||||
let push_argument_ruler = |argument: PlotPoint, shapes: &mut Vec<Shape>| {
|
|
||||||
let position = plot.transform.position_from_point(&argument);
|
|
||||||
let line = match orientation {
|
|
||||||
Orientation::Horizontal => horizontal_line(position, plot.transform, line_color),
|
|
||||||
Orientation::Vertical => vertical_line(position, plot.transform, line_color),
|
|
||||||
};
|
|
||||||
shapes.push(line);
|
|
||||||
};
|
|
||||||
|
|
||||||
for pos in elem.arguments_with_ruler() {
|
for pos in elem.arguments_with_ruler() {
|
||||||
push_argument_ruler(pos, shapes);
|
cursors.push(match orientation {
|
||||||
|
Orientation::Horizontal => Cursor::Horizontal { y: pos.y },
|
||||||
|
Orientation::Vertical => Cursor::Vertical { x: pos.x },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rulers for values (usually horizontal)
|
// Rulers for values (usually horizontal)
|
||||||
if show_values {
|
if show_values {
|
||||||
let push_value_ruler = |value: PlotPoint, shapes: &mut Vec<Shape>| {
|
|
||||||
let position = plot.transform.position_from_point(&value);
|
|
||||||
let line = match orientation {
|
|
||||||
Orientation::Horizontal => vertical_line(position, plot.transform, line_color),
|
|
||||||
Orientation::Vertical => horizontal_line(position, plot.transform, line_color),
|
|
||||||
};
|
|
||||||
shapes.push(line);
|
|
||||||
};
|
|
||||||
|
|
||||||
for pos in elem.values_with_ruler() {
|
for pos in elem.values_with_ruler() {
|
||||||
push_value_ruler(pos, shapes);
|
cursors.push(match orientation {
|
||||||
|
Orientation::Horizontal => Cursor::Vertical { x: pos.x },
|
||||||
|
Orientation::Vertical => Cursor::Horizontal { y: pos.y },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1656,14 +1662,14 @@ pub(super) fn rulers_at_value(
|
||||||
name: &str,
|
name: &str,
|
||||||
plot: &PlotConfig<'_>,
|
plot: &PlotConfig<'_>,
|
||||||
shapes: &mut Vec<Shape>,
|
shapes: &mut Vec<Shape>,
|
||||||
|
cursors: &mut Vec<Cursor>,
|
||||||
label_formatter: &LabelFormatter,
|
label_formatter: &LabelFormatter,
|
||||||
) {
|
) {
|
||||||
let line_color = rulers_color(plot.ui);
|
|
||||||
if plot.show_x {
|
if plot.show_x {
|
||||||
shapes.push(vertical_line(pointer, plot.transform, line_color));
|
cursors.push(Cursor::Vertical { x: value.x });
|
||||||
}
|
}
|
||||||
if plot.show_y {
|
if plot.show_y {
|
||||||
shapes.push(horizontal_line(pointer, plot.transform, line_color));
|
cursors.push(Cursor::Horizontal { y: value.y });
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut prefix = String::new();
|
let mut prefix = String::new();
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
//! Simple plotting library.
|
//! Simple plotting library.
|
||||||
|
|
||||||
use std::{cell::Cell, ops::RangeInclusive, rc::Rc};
|
use std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
ops::RangeInclusive,
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use epaint::color::Hsva;
|
use epaint::color::Hsva;
|
||||||
|
@ -17,6 +21,8 @@ pub use items::{
|
||||||
pub use legend::{Corner, Legend};
|
pub use legend::{Corner, Legend};
|
||||||
pub use transform::PlotBounds;
|
pub use transform::PlotBounds;
|
||||||
|
|
||||||
|
use self::items::{horizontal_line, rulers_color, vertical_line};
|
||||||
|
|
||||||
mod items;
|
mod items;
|
||||||
mod legend;
|
mod legend;
|
||||||
mod transform;
|
mod transform;
|
||||||
|
@ -114,6 +120,74 @@ impl PlotMemory {
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Indicates a vertical or horizontal cursor line in plot coordinates.
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
enum Cursor {
|
||||||
|
Horizontal { y: f64 },
|
||||||
|
Vertical { x: f64 },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Contains the cursors drawn for a plot widget in a single frame.
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
struct PlotFrameCursors {
|
||||||
|
id: Id,
|
||||||
|
cursors: Vec<Cursor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines how multiple plots share the same cursor for one or both of their axes. Can be added while building
|
||||||
|
/// a plot with [`Plot::link_cursor`]. Contains an internal state, meaning that this object should be stored by
|
||||||
|
/// the user between frames.
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct LinkedCursorsGroup {
|
||||||
|
link_x: bool,
|
||||||
|
link_y: bool,
|
||||||
|
// We store the cursors drawn for each linked plot. Each time a plot in the group is drawn, the
|
||||||
|
// cursors due to hovering it drew are appended to `frames`, so lower indices are older.
|
||||||
|
// When a plot is redrawn all entries older than its previous entry are removed. This avoids
|
||||||
|
// unbounded growth and also ensures entries for plots which are not longer part of the group
|
||||||
|
// gets removed.
|
||||||
|
frames: Rc<RefCell<Vec<PlotFrameCursors>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LinkedCursorsGroup {
|
||||||
|
pub fn new(link_x: bool, link_y: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
link_x,
|
||||||
|
link_y,
|
||||||
|
frames: Rc::new(RefCell::new(Vec::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only link the cursor for the x-axis.
|
||||||
|
pub fn x() -> Self {
|
||||||
|
Self::new(true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only link the cursor for the y-axis.
|
||||||
|
pub fn y() -> Self {
|
||||||
|
Self::new(false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Link the cursors for both axes.
|
||||||
|
pub fn both() -> Self {
|
||||||
|
Self::new(true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change whether the cursor for the x-axis is linked for this group. Using this after plots in this group have been
|
||||||
|
/// drawn in this frame already may lead to unexpected results.
|
||||||
|
pub fn set_link_x(&mut self, link: bool) {
|
||||||
|
self.link_x = link;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change whether the cursor for the y-axis is linked for this group. Using this after plots in this group have been
|
||||||
|
/// drawn in this frame already may lead to unexpected results.
|
||||||
|
pub fn set_link_y(&mut self, link: bool) {
|
||||||
|
self.link_y = link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Defines how multiple plots share the same range for one or both of their axes. Can be added while building
|
/// Defines how multiple plots share the same range for one or both of their axes. Can be added while building
|
||||||
/// a plot with [`Plot::link_axis`]. Contains an internal state, meaning that this object should be stored by
|
/// a plot with [`Plot::link_axis`]. Contains an internal state, meaning that this object should be stored by
|
||||||
/// the user between frames.
|
/// the user between frames.
|
||||||
|
@ -199,6 +273,7 @@ pub struct Plot {
|
||||||
allow_boxed_zoom: bool,
|
allow_boxed_zoom: bool,
|
||||||
boxed_zoom_pointer_button: PointerButton,
|
boxed_zoom_pointer_button: PointerButton,
|
||||||
linked_axes: Option<LinkedAxisGroup>,
|
linked_axes: Option<LinkedAxisGroup>,
|
||||||
|
linked_cursors: Option<LinkedCursorsGroup>,
|
||||||
|
|
||||||
min_size: Vec2,
|
min_size: Vec2,
|
||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
|
@ -233,6 +308,7 @@ impl Plot {
|
||||||
allow_boxed_zoom: true,
|
allow_boxed_zoom: true,
|
||||||
boxed_zoom_pointer_button: PointerButton::Secondary,
|
boxed_zoom_pointer_button: PointerButton::Secondary,
|
||||||
linked_axes: None,
|
linked_axes: None,
|
||||||
|
linked_cursors: None,
|
||||||
|
|
||||||
min_size: Vec2::splat(64.0),
|
min_size: Vec2::splat(64.0),
|
||||||
width: None,
|
width: None,
|
||||||
|
@ -509,6 +585,13 @@ impl Plot {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a [`LinkedCursorsGroup`] so that this plot will share the bounds with other plots that have this
|
||||||
|
/// group assigned. A plot cannot belong to more than one group.
|
||||||
|
pub fn link_cursor(mut self, group: LinkedCursorsGroup) -> Self {
|
||||||
|
self.linked_cursors = Some(group);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Interact with and add items to the plot and finally draw it.
|
/// Interact with and add items to the plot and finally draw it.
|
||||||
pub fn show<R>(self, ui: &mut Ui, build_fn: impl FnOnce(&mut PlotUi) -> R) -> InnerResponse<R> {
|
pub fn show<R>(self, ui: &mut Ui, build_fn: impl FnOnce(&mut PlotUi) -> R) -> InnerResponse<R> {
|
||||||
self.show_dyn(ui, Box::new(build_fn))
|
self.show_dyn(ui, Box::new(build_fn))
|
||||||
|
@ -544,6 +627,7 @@ impl Plot {
|
||||||
show_background,
|
show_background,
|
||||||
show_axes,
|
show_axes,
|
||||||
linked_axes,
|
linked_axes,
|
||||||
|
linked_cursors,
|
||||||
grid_spacers,
|
grid_spacers,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
@ -660,6 +744,31 @@ impl Plot {
|
||||||
// --- Bound computation ---
|
// --- Bound computation ---
|
||||||
let mut bounds = *last_screen_transform.bounds();
|
let mut bounds = *last_screen_transform.bounds();
|
||||||
|
|
||||||
|
// Find the cursors from other plots we need to draw
|
||||||
|
let draw_cursors: Vec<Cursor> = if let Some(group) = linked_cursors.as_ref() {
|
||||||
|
let mut frames = group.frames.borrow_mut();
|
||||||
|
|
||||||
|
// Look for our previous frame
|
||||||
|
let index = frames
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, frame)| frame.id == plot_id)
|
||||||
|
.map(|(i, _)| i);
|
||||||
|
|
||||||
|
// Remove our previous frame and all older frames as these are no longer displayed. This avoids
|
||||||
|
// unbounded growth, as we add an entry each time we draw a plot.
|
||||||
|
index.map(|index| frames.drain(0..=index));
|
||||||
|
|
||||||
|
// Gather all cursors of the remaining frames. This will be all the cursors of the
|
||||||
|
// other plots in the group. We want to draw these in the current plot too.
|
||||||
|
frames
|
||||||
|
.iter()
|
||||||
|
.flat_map(|frame| frame.cursors.iter().copied())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
// Transfer the bounds from a link group.
|
// Transfer the bounds from a link group.
|
||||||
if let Some(axes) = linked_axes.as_ref() {
|
if let Some(axes) = linked_axes.as_ref() {
|
||||||
if let Some(linked_bounds) = axes.get() {
|
if let Some(linked_bounds) = axes.get() {
|
||||||
|
@ -818,8 +927,11 @@ impl Plot {
|
||||||
show_axes,
|
show_axes,
|
||||||
transform: transform.clone(),
|
transform: transform.clone(),
|
||||||
grid_spacers,
|
grid_spacers,
|
||||||
|
draw_cursor_x: linked_cursors.as_ref().map_or(false, |group| group.link_x),
|
||||||
|
draw_cursor_y: linked_cursors.as_ref().map_or(false, |group| group.link_y),
|
||||||
|
draw_cursors,
|
||||||
};
|
};
|
||||||
prepared.ui(ui, &response);
|
let plot_cursors = prepared.ui(ui, &response);
|
||||||
|
|
||||||
if let Some(boxed_zoom_rect) = boxed_zoom_rect {
|
if let Some(boxed_zoom_rect) = boxed_zoom_rect {
|
||||||
ui.painter().with_clip_rect(rect).add(boxed_zoom_rect.0);
|
ui.painter().with_clip_rect(rect).add(boxed_zoom_rect.0);
|
||||||
|
@ -832,6 +944,14 @@ impl Plot {
|
||||||
hovered_entry = legend.hovered_entry_name();
|
hovered_entry = legend.hovered_entry_name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(group) = linked_cursors.as_ref() {
|
||||||
|
// Push the frame we just drew to the list of frames
|
||||||
|
group.frames.borrow_mut().push(PlotFrameCursors {
|
||||||
|
id: plot_id,
|
||||||
|
cursors: plot_cursors,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(group) = linked_axes.as_ref() {
|
if let Some(group) = linked_axes.as_ref() {
|
||||||
group.set(*transform.bounds());
|
group.set(*transform.bounds());
|
||||||
}
|
}
|
||||||
|
@ -1118,10 +1238,13 @@ struct PreparedPlot {
|
||||||
show_axes: [bool; 2],
|
show_axes: [bool; 2],
|
||||||
transform: ScreenTransform,
|
transform: ScreenTransform,
|
||||||
grid_spacers: [GridSpacer; 2],
|
grid_spacers: [GridSpacer; 2],
|
||||||
|
draw_cursor_x: bool,
|
||||||
|
draw_cursor_y: bool,
|
||||||
|
draw_cursors: Vec<Cursor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PreparedPlot {
|
impl PreparedPlot {
|
||||||
fn ui(self, ui: &mut Ui, response: &Response) {
|
fn ui(self, ui: &mut Ui, response: &Response) -> Vec<Cursor> {
|
||||||
let mut shapes = Vec::new();
|
let mut shapes = Vec::new();
|
||||||
|
|
||||||
for d in 0..2 {
|
for d in 0..2 {
|
||||||
|
@ -1138,9 +1261,42 @@ impl PreparedPlot {
|
||||||
item.shapes(&mut plot_ui, transform, &mut shapes);
|
item.shapes(&mut plot_ui, transform, &mut shapes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(pointer) = response.hover_pos() {
|
let cursors = if let Some(pointer) = response.hover_pos() {
|
||||||
self.hover(ui, pointer, &mut shapes);
|
self.hover(ui, pointer, &mut shapes)
|
||||||
}
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Draw cursors
|
||||||
|
let line_color = rulers_color(ui);
|
||||||
|
|
||||||
|
let mut draw_cursor = |cursors: &Vec<Cursor>, always| {
|
||||||
|
for &cursor in cursors {
|
||||||
|
match cursor {
|
||||||
|
Cursor::Horizontal { y } => {
|
||||||
|
if self.draw_cursor_y || always {
|
||||||
|
shapes.push(horizontal_line(
|
||||||
|
transform.position_from_point(&PlotPoint::new(0.0, y)),
|
||||||
|
&self.transform,
|
||||||
|
line_color,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Cursor::Vertical { x } => {
|
||||||
|
if self.draw_cursor_x || always {
|
||||||
|
shapes.push(vertical_line(
|
||||||
|
transform.position_from_point(&PlotPoint::new(x, 0.0)),
|
||||||
|
&self.transform,
|
||||||
|
line_color,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
draw_cursor(&self.draw_cursors, false);
|
||||||
|
draw_cursor(&cursors, true);
|
||||||
|
|
||||||
let painter = ui.painter().with_clip_rect(*transform.frame());
|
let painter = ui.painter().with_clip_rect(*transform.frame());
|
||||||
painter.extend(shapes);
|
painter.extend(shapes);
|
||||||
|
@ -1160,6 +1316,8 @@ impl PreparedPlot {
|
||||||
painter.text(position, anchor, text, font_id, ui.visuals().text_color());
|
painter.text(position, anchor, text, font_id, ui.visuals().text_color());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cursors
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_axis(&self, ui: &Ui, axis: usize, shapes: &mut Vec<Shape>) {
|
fn paint_axis(&self, ui: &Ui, axis: usize, shapes: &mut Vec<Shape>) {
|
||||||
|
@ -1253,7 +1411,7 @@ impl PreparedPlot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hover(&self, ui: &Ui, pointer: Pos2, shapes: &mut Vec<Shape>) {
|
fn hover(&self, ui: &Ui, pointer: Pos2, shapes: &mut Vec<Shape>) -> Vec<Cursor> {
|
||||||
let Self {
|
let Self {
|
||||||
transform,
|
transform,
|
||||||
show_x,
|
show_x,
|
||||||
|
@ -1264,7 +1422,7 @@ impl PreparedPlot {
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
if !show_x && !show_y {
|
if !show_x && !show_y {
|
||||||
return;
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let interact_radius_sq: f32 = (16.0f32).powi(2);
|
let interact_radius_sq: f32 = (16.0f32).powi(2);
|
||||||
|
@ -1280,6 +1438,8 @@ impl PreparedPlot {
|
||||||
.min_by_key(|(_, elem)| elem.dist_sq.ord())
|
.min_by_key(|(_, elem)| elem.dist_sq.ord())
|
||||||
.filter(|(_, elem)| elem.dist_sq <= interact_radius_sq);
|
.filter(|(_, elem)| elem.dist_sq <= interact_radius_sq);
|
||||||
|
|
||||||
|
let mut cursors = Vec::new();
|
||||||
|
|
||||||
let plot = items::PlotConfig {
|
let plot = items::PlotConfig {
|
||||||
ui,
|
ui,
|
||||||
transform,
|
transform,
|
||||||
|
@ -1288,11 +1448,21 @@ impl PreparedPlot {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((item, elem)) = closest {
|
if let Some((item, elem)) = closest {
|
||||||
item.on_hover(elem, shapes, &plot, label_formatter);
|
item.on_hover(elem, shapes, &mut cursors, &plot, label_formatter);
|
||||||
} else {
|
} else {
|
||||||
let value = transform.value_from_position(pointer);
|
let value = transform.value_from_position(pointer);
|
||||||
items::rulers_at_value(pointer, value, "", &plot, shapes, label_formatter);
|
items::rulers_at_value(
|
||||||
|
pointer,
|
||||||
|
value,
|
||||||
|
"",
|
||||||
|
&plot,
|
||||||
|
shapes,
|
||||||
|
&mut cursors,
|
||||||
|
label_formatter,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cursors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -459,16 +459,24 @@ struct LinkedAxisDemo {
|
||||||
link_x: bool,
|
link_x: bool,
|
||||||
link_y: bool,
|
link_y: bool,
|
||||||
group: plot::LinkedAxisGroup,
|
group: plot::LinkedAxisGroup,
|
||||||
|
cursor_group: plot::LinkedCursorsGroup,
|
||||||
|
link_cursor_x: bool,
|
||||||
|
link_cursor_y: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LinkedAxisDemo {
|
impl Default for LinkedAxisDemo {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let link_x = true;
|
let link_x = true;
|
||||||
let link_y = false;
|
let link_y = false;
|
||||||
|
let link_cursor_x = true;
|
||||||
|
let link_cursor_y = false;
|
||||||
Self {
|
Self {
|
||||||
link_x,
|
link_x,
|
||||||
link_y,
|
link_y,
|
||||||
group: plot::LinkedAxisGroup::new(link_x, link_y),
|
group: plot::LinkedAxisGroup::new(link_x, link_y),
|
||||||
|
cursor_group: plot::LinkedCursorsGroup::new(link_cursor_x, link_cursor_y),
|
||||||
|
link_cursor_x,
|
||||||
|
link_cursor_y,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -514,18 +522,27 @@ impl LinkedAxisDemo {
|
||||||
});
|
});
|
||||||
self.group.set_link_x(self.link_x);
|
self.group.set_link_x(self.link_x);
|
||||||
self.group.set_link_y(self.link_y);
|
self.group.set_link_y(self.link_y);
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Linked cursors:");
|
||||||
|
ui.checkbox(&mut self.link_cursor_x, "X");
|
||||||
|
ui.checkbox(&mut self.link_cursor_y, "Y");
|
||||||
|
});
|
||||||
|
self.cursor_group.set_link_x(self.link_cursor_x);
|
||||||
|
self.cursor_group.set_link_y(self.link_cursor_y);
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
Plot::new("linked_axis_1")
|
Plot::new("linked_axis_1")
|
||||||
.data_aspect(1.0)
|
.data_aspect(1.0)
|
||||||
.width(250.0)
|
.width(250.0)
|
||||||
.height(250.0)
|
.height(250.0)
|
||||||
.link_axis(self.group.clone())
|
.link_axis(self.group.clone())
|
||||||
|
.link_cursor(self.cursor_group.clone())
|
||||||
.show(ui, LinkedAxisDemo::configure_plot);
|
.show(ui, LinkedAxisDemo::configure_plot);
|
||||||
Plot::new("linked_axis_2")
|
Plot::new("linked_axis_2")
|
||||||
.data_aspect(2.0)
|
.data_aspect(2.0)
|
||||||
.width(150.0)
|
.width(150.0)
|
||||||
.height(250.0)
|
.height(250.0)
|
||||||
.link_axis(self.group.clone())
|
.link_axis(self.group.clone())
|
||||||
|
.link_cursor(self.cursor_group.clone())
|
||||||
.show(ui, LinkedAxisDemo::configure_plot);
|
.show(ui, LinkedAxisDemo::configure_plot);
|
||||||
});
|
});
|
||||||
Plot::new("linked_axis_3")
|
Plot::new("linked_axis_3")
|
||||||
|
@ -533,6 +550,7 @@ impl LinkedAxisDemo {
|
||||||
.width(250.0)
|
.width(250.0)
|
||||||
.height(150.0)
|
.height(150.0)
|
||||||
.link_axis(self.group.clone())
|
.link_axis(self.group.clone())
|
||||||
|
.link_cursor(self.cursor_group.clone())
|
||||||
.show(ui, LinkedAxisDemo::configure_plot)
|
.show(ui, LinkedAxisDemo::configure_plot)
|
||||||
.response
|
.response
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue