mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 13:20:05 -04:00
Improve plot item UX (#1816)
* initial work * changelog entry * fix CI * Update egui/src/widgets/plot/items/values.rs Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * Update egui/src/widgets/plot/items/values.rs Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * derive 'FromIterator' * remove `bytemuck` dependency again and remove borrowing plot points for now * update doctest * update documentation * remove unnecessary numeric cast * cargo fmt * Update egui/src/widgets/plot/items/values.rs Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
committed by
GitHub
parent
cf591da1a0
commit
0bf9fc9428
@@ -2,7 +2,7 @@ use crate::emath::NumExt;
|
||||
use crate::epaint::{Color32, RectShape, Rounding, Shape, Stroke};
|
||||
|
||||
use super::{add_rulers_and_text, highlighted_color, Orientation, PlotConfig, RectElement};
|
||||
use crate::plot::{BarChart, ScreenTransform, Value};
|
||||
use crate::plot::{BarChart, PlotPoint, ScreenTransform};
|
||||
|
||||
/// One bar in a [`BarChart`]. Potentially floating, allowing stacked bar charts.
|
||||
/// Width can be changed to allow variable-width histograms.
|
||||
@@ -157,15 +157,15 @@ impl RectElement for Bar {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn bounds_min(&self) -> Value {
|
||||
fn bounds_min(&self) -> PlotPoint {
|
||||
self.point_at(self.argument - self.bar_width / 2.0, self.lower())
|
||||
}
|
||||
|
||||
fn bounds_max(&self) -> Value {
|
||||
fn bounds_max(&self) -> PlotPoint {
|
||||
self.point_at(self.argument + self.bar_width / 2.0, self.upper())
|
||||
}
|
||||
|
||||
fn values_with_ruler(&self) -> Vec<Value> {
|
||||
fn values_with_ruler(&self) -> Vec<PlotPoint> {
|
||||
let base = self.base_offset.unwrap_or(0.0);
|
||||
let value_center = self.point_at(self.argument, base + self.value);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::emath::NumExt;
|
||||
use crate::epaint::{Color32, RectShape, Rounding, Shape, Stroke};
|
||||
|
||||
use super::{add_rulers_and_text, highlighted_color, Orientation, PlotConfig, RectElement};
|
||||
use crate::plot::{BoxPlot, ScreenTransform, Value};
|
||||
use crate::plot::{BoxPlot, PlotPoint, ScreenTransform};
|
||||
|
||||
/// Contains the values of a single box in a box plot.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@@ -161,8 +161,8 @@ impl BoxElem {
|
||||
let line_between = |v1, v2| {
|
||||
Shape::line_segment(
|
||||
[
|
||||
transform.position_from_value(&v1),
|
||||
transform.position_from_value(&v2),
|
||||
transform.position_from_point(&v1),
|
||||
transform.position_from_point(&v2),
|
||||
],
|
||||
stroke,
|
||||
)
|
||||
@@ -236,19 +236,19 @@ impl RectElement for BoxElem {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn bounds_min(&self) -> Value {
|
||||
fn bounds_min(&self) -> PlotPoint {
|
||||
let argument = self.argument - self.box_width.max(self.whisker_width) / 2.0;
|
||||
let value = self.spread.lower_whisker;
|
||||
self.point_at(argument, value)
|
||||
}
|
||||
|
||||
fn bounds_max(&self) -> Value {
|
||||
fn bounds_max(&self) -> PlotPoint {
|
||||
let argument = self.argument + self.box_width.max(self.whisker_width) / 2.0;
|
||||
let value = self.spread.upper_whisker;
|
||||
self.point_at(argument, value)
|
||||
}
|
||||
|
||||
fn values_with_ruler(&self) -> Vec<Value> {
|
||||
fn values_with_ruler(&self) -> Vec<PlotPoint> {
|
||||
let median = self.point_at(self.argument, self.spread.median);
|
||||
let q1 = self.point_at(self.argument, self.spread.quartile1);
|
||||
let q3 = self.point_at(self.argument, self.spread.quartile3);
|
||||
@@ -262,7 +262,7 @@ impl RectElement for BoxElem {
|
||||
self.orientation
|
||||
}
|
||||
|
||||
fn corner_value(&self) -> Value {
|
||||
fn corner_value(&self) -> PlotPoint {
|
||||
self.point_at(self.argument, self.spread.upper_whisker)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ use values::{ClosestElem, PlotGeometry};
|
||||
|
||||
pub use bar::Bar;
|
||||
pub use box_elem::{BoxElem, BoxSpread};
|
||||
pub use values::{LineStyle, MarkerShape, Orientation, Value, Values};
|
||||
pub use values::{LineStyle, MarkerShape, Orientation, PlotPoint, PlotPoints};
|
||||
|
||||
mod bar;
|
||||
mod box_elem;
|
||||
@@ -49,7 +49,7 @@ pub(super) trait PlotItem {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, value)| {
|
||||
let pos = transform.position_from_value(value);
|
||||
let pos = transform.position_from_point(value);
|
||||
let dist_sq = point.distance_sq(pos);
|
||||
ClosestElem { index, dist_sq }
|
||||
})
|
||||
@@ -86,7 +86,7 @@ pub(super) trait PlotItem {
|
||||
|
||||
// this method is only called, if the value is in the result set of find_closest()
|
||||
let value = points[elem.index];
|
||||
let pointer = plot.transform.position_from_value(&value);
|
||||
let pointer = plot.transform.position_from_point(&value);
|
||||
shapes.push(Shape::circle_filled(pointer, 3.0, line_color));
|
||||
|
||||
rulers_at_value(pointer, value, self.name(), plot, shapes, label_formatter);
|
||||
@@ -169,8 +169,8 @@ impl PlotItem for HLine {
|
||||
..
|
||||
} = self;
|
||||
let points = vec![
|
||||
transform.position_from_value(&Value::new(transform.bounds().min[0], *y)),
|
||||
transform.position_from_value(&Value::new(transform.bounds().max[0], *y)),
|
||||
transform.position_from_point(&PlotPoint::new(transform.bounds().min[0], *y)),
|
||||
transform.position_from_point(&PlotPoint::new(transform.bounds().max[0], *y)),
|
||||
];
|
||||
style.style_line(points, *stroke, *highlight, shapes);
|
||||
}
|
||||
@@ -279,8 +279,8 @@ impl PlotItem for VLine {
|
||||
..
|
||||
} = self;
|
||||
let points = vec![
|
||||
transform.position_from_value(&Value::new(*x, transform.bounds().min[1])),
|
||||
transform.position_from_value(&Value::new(*x, transform.bounds().max[1])),
|
||||
transform.position_from_point(&PlotPoint::new(*x, transform.bounds().min[1])),
|
||||
transform.position_from_point(&PlotPoint::new(*x, transform.bounds().max[1])),
|
||||
];
|
||||
style.style_line(points, *stroke, *highlight, shapes);
|
||||
}
|
||||
@@ -317,7 +317,7 @@ impl PlotItem for VLine {
|
||||
|
||||
/// A series of values forming a path.
|
||||
pub struct Line {
|
||||
pub(super) series: Values,
|
||||
pub(super) series: PlotPoints,
|
||||
pub(super) stroke: Stroke,
|
||||
pub(super) name: String,
|
||||
pub(super) highlight: bool,
|
||||
@@ -326,9 +326,9 @@ pub struct Line {
|
||||
}
|
||||
|
||||
impl Line {
|
||||
pub fn new(series: Values) -> Self {
|
||||
pub fn new(series: impl Into<PlotPoints>) -> Self {
|
||||
Self {
|
||||
series,
|
||||
series: series.into(),
|
||||
stroke: Stroke::new(1.0, Color32::TRANSPARENT),
|
||||
name: Default::default(),
|
||||
highlight: false,
|
||||
@@ -405,9 +405,9 @@ impl PlotItem for Line {
|
||||
} = self;
|
||||
|
||||
let values_tf: Vec<_> = series
|
||||
.values
|
||||
.points()
|
||||
.iter()
|
||||
.map(|v| transform.position_from_value(v))
|
||||
.map(|v| transform.position_from_point(v))
|
||||
.collect();
|
||||
let n_values = values_tf.len();
|
||||
|
||||
@@ -421,7 +421,7 @@ impl PlotItem for Line {
|
||||
fill_alpha = (2.0 * fill_alpha).at_most(1.0);
|
||||
}
|
||||
let y = transform
|
||||
.position_from_value(&Value::new(0.0, y_reference))
|
||||
.position_from_point(&PlotPoint::new(0.0, y_reference))
|
||||
.y;
|
||||
let fill_color = Rgba::from(stroke.color)
|
||||
.to_opaque()
|
||||
@@ -474,7 +474,7 @@ impl PlotItem for Line {
|
||||
}
|
||||
|
||||
fn geometry(&self) -> PlotGeometry<'_> {
|
||||
PlotGeometry::Points(&self.series.values)
|
||||
PlotGeometry::Points(self.series.points())
|
||||
}
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
@@ -484,7 +484,7 @@ impl PlotItem for Line {
|
||||
|
||||
/// A convex polygon.
|
||||
pub struct Polygon {
|
||||
pub(super) series: Values,
|
||||
pub(super) series: PlotPoints,
|
||||
pub(super) stroke: Stroke,
|
||||
pub(super) name: String,
|
||||
pub(super) highlight: bool,
|
||||
@@ -493,9 +493,9 @@ pub struct Polygon {
|
||||
}
|
||||
|
||||
impl Polygon {
|
||||
pub fn new(series: Values) -> Self {
|
||||
pub fn new(series: impl Into<PlotPoints>) -> Self {
|
||||
Self {
|
||||
series,
|
||||
series: series.into(),
|
||||
stroke: Stroke::new(1.0, Color32::TRANSPARENT),
|
||||
name: Default::default(),
|
||||
highlight: false,
|
||||
@@ -570,9 +570,9 @@ impl PlotItem for Polygon {
|
||||
}
|
||||
|
||||
let mut values_tf: Vec<_> = series
|
||||
.values
|
||||
.points()
|
||||
.iter()
|
||||
.map(|v| transform.position_from_value(v))
|
||||
.map(|v| transform.position_from_point(v))
|
||||
.collect();
|
||||
|
||||
let fill = Rgba::from(stroke.color).to_opaque().multiply(fill_alpha);
|
||||
@@ -604,7 +604,7 @@ impl PlotItem for Polygon {
|
||||
}
|
||||
|
||||
fn geometry(&self) -> PlotGeometry<'_> {
|
||||
PlotGeometry::Points(&self.series.values)
|
||||
PlotGeometry::Points(self.series.points())
|
||||
}
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
@@ -616,7 +616,7 @@ impl PlotItem for Polygon {
|
||||
#[derive(Clone)]
|
||||
pub struct Text {
|
||||
pub(super) text: WidgetText,
|
||||
pub(super) position: Value,
|
||||
pub(super) position: PlotPoint,
|
||||
pub(super) name: String,
|
||||
pub(super) highlight: bool,
|
||||
pub(super) color: Color32,
|
||||
@@ -624,7 +624,7 @@ pub struct Text {
|
||||
}
|
||||
|
||||
impl Text {
|
||||
pub fn new(position: Value, text: impl Into<WidgetText>) -> Self {
|
||||
pub fn new(position: PlotPoint, text: impl Into<WidgetText>) -> Self {
|
||||
Self {
|
||||
text: text.into(),
|
||||
position,
|
||||
@@ -679,7 +679,7 @@ impl PlotItem for Text {
|
||||
.clone()
|
||||
.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Small);
|
||||
|
||||
let pos = transform.position_from_value(&self.position);
|
||||
let pos = transform.position_from_point(&self.position);
|
||||
let rect = self
|
||||
.anchor
|
||||
.anchor_rect(Rect::from_min_size(pos, galley.size()));
|
||||
@@ -730,7 +730,7 @@ impl PlotItem for Text {
|
||||
|
||||
/// A set of points.
|
||||
pub struct Points {
|
||||
pub(super) series: Values,
|
||||
pub(super) series: PlotPoints,
|
||||
pub(super) shape: MarkerShape,
|
||||
/// Color of the marker. `Color32::TRANSPARENT` means that it will be picked automatically.
|
||||
pub(super) color: Color32,
|
||||
@@ -744,9 +744,9 @@ pub struct Points {
|
||||
}
|
||||
|
||||
impl Points {
|
||||
pub fn new(series: Values) -> Self {
|
||||
pub fn new(series: impl Into<PlotPoints>) -> Self {
|
||||
Self {
|
||||
series,
|
||||
series: series.into(),
|
||||
shape: MarkerShape::Circle,
|
||||
color: Color32::TRANSPARENT,
|
||||
filled: true,
|
||||
@@ -837,12 +837,12 @@ impl PlotItem for Points {
|
||||
stem_stroke.width *= 2.0;
|
||||
}
|
||||
|
||||
let y_reference = stems.map(|y| transform.position_from_value(&Value::new(0.0, y)).y);
|
||||
let y_reference = stems.map(|y| transform.position_from_point(&PlotPoint::new(0.0, y)).y);
|
||||
|
||||
series
|
||||
.values
|
||||
.points()
|
||||
.iter()
|
||||
.map(|value| transform.position_from_value(value))
|
||||
.map(|value| transform.position_from_point(value))
|
||||
.for_each(|center| {
|
||||
let tf = |dx: f32, dy: f32| -> Pos2 { center + radius * vec2(dx, dy) };
|
||||
|
||||
@@ -955,7 +955,7 @@ impl PlotItem for Points {
|
||||
}
|
||||
|
||||
fn geometry(&self) -> PlotGeometry<'_> {
|
||||
PlotGeometry::Points(&self.series.values)
|
||||
PlotGeometry::Points(self.series.points())
|
||||
}
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
@@ -965,18 +965,18 @@ impl PlotItem for Points {
|
||||
|
||||
/// A set of arrows.
|
||||
pub struct Arrows {
|
||||
pub(super) origins: Values,
|
||||
pub(super) tips: Values,
|
||||
pub(super) origins: PlotPoints,
|
||||
pub(super) tips: PlotPoints,
|
||||
pub(super) color: Color32,
|
||||
pub(super) name: String,
|
||||
pub(super) highlight: bool,
|
||||
}
|
||||
|
||||
impl Arrows {
|
||||
pub fn new(origins: Values, tips: Values) -> Self {
|
||||
pub fn new(origins: impl Into<PlotPoints>, tips: impl Into<PlotPoints>) -> Self {
|
||||
Self {
|
||||
origins,
|
||||
tips,
|
||||
origins: origins.into(),
|
||||
tips: tips.into(),
|
||||
color: Color32::TRANSPARENT,
|
||||
name: Default::default(),
|
||||
highlight: false,
|
||||
@@ -1020,13 +1020,13 @@ impl PlotItem for Arrows {
|
||||
} = self;
|
||||
let stroke = Stroke::new(if *highlight { 2.0 } else { 1.0 }, *color);
|
||||
origins
|
||||
.values
|
||||
.points()
|
||||
.iter()
|
||||
.zip(tips.values.iter())
|
||||
.zip(tips.points().iter())
|
||||
.map(|(origin, tip)| {
|
||||
(
|
||||
transform.position_from_value(origin),
|
||||
transform.position_from_value(tip),
|
||||
transform.position_from_point(origin),
|
||||
transform.position_from_point(tip),
|
||||
)
|
||||
})
|
||||
.for_each(|(origin, tip)| {
|
||||
@@ -1070,7 +1070,7 @@ impl PlotItem for Arrows {
|
||||
}
|
||||
|
||||
fn geometry(&self) -> PlotGeometry<'_> {
|
||||
PlotGeometry::Points(&self.origins.values)
|
||||
PlotGeometry::Points(self.origins.points())
|
||||
}
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
@@ -1081,7 +1081,7 @@ impl PlotItem for Arrows {
|
||||
/// An image in the plot.
|
||||
#[derive(Clone)]
|
||||
pub struct PlotImage {
|
||||
pub(super) position: Value,
|
||||
pub(super) position: PlotPoint,
|
||||
pub(super) texture_id: TextureId,
|
||||
pub(super) uv: Rect,
|
||||
pub(super) size: Vec2,
|
||||
@@ -1095,7 +1095,7 @@ impl PlotImage {
|
||||
/// Create a new image with position and size in plot coordinates.
|
||||
pub fn new(
|
||||
texture_id: impl Into<TextureId>,
|
||||
center_position: Value,
|
||||
center_position: PlotPoint,
|
||||
size: impl Into<Vec2>,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -1160,16 +1160,16 @@ impl PlotItem for PlotImage {
|
||||
..
|
||||
} = self;
|
||||
let rect = {
|
||||
let left_top = Value::new(
|
||||
let left_top = PlotPoint::new(
|
||||
position.x as f32 - size.x / 2.0,
|
||||
position.y as f32 - size.y / 2.0,
|
||||
);
|
||||
let right_bottom = Value::new(
|
||||
let right_bottom = PlotPoint::new(
|
||||
position.x as f32 + size.x / 2.0,
|
||||
position.y as f32 + size.y / 2.0,
|
||||
);
|
||||
let left_top_tf = transform.position_from_value(&left_top);
|
||||
let right_bottom_tf = transform.position_from_value(&right_bottom);
|
||||
let left_top_tf = transform.position_from_point(&left_top);
|
||||
let right_bottom_tf = transform.position_from_point(&right_bottom);
|
||||
Rect::from_two_pos(left_top_tf, right_bottom_tf)
|
||||
};
|
||||
Image::new(*texture_id, *size)
|
||||
@@ -1210,11 +1210,11 @@ impl PlotItem for PlotImage {
|
||||
|
||||
fn get_bounds(&self) -> PlotBounds {
|
||||
let mut bounds = PlotBounds::NOTHING;
|
||||
let left_top = Value::new(
|
||||
let left_top = PlotPoint::new(
|
||||
self.position.x as f32 - self.size.x / 2.0,
|
||||
self.position.y as f32 - self.size.y / 2.0,
|
||||
);
|
||||
let right_bottom = Value::new(
|
||||
let right_bottom = PlotPoint::new(
|
||||
self.position.x as f32 + self.size.x / 2.0,
|
||||
self.position.y as f32 + self.size.y / 2.0,
|
||||
);
|
||||
@@ -1586,8 +1586,8 @@ fn add_rulers_and_text(
|
||||
|
||||
// Rulers for argument (usually vertical)
|
||||
if show_argument {
|
||||
let push_argument_ruler = |argument: Value, shapes: &mut Vec<Shape>| {
|
||||
let position = plot.transform.position_from_value(&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),
|
||||
@@ -1602,8 +1602,8 @@ fn add_rulers_and_text(
|
||||
|
||||
// Rulers for values (usually horizontal)
|
||||
if show_values {
|
||||
let push_value_ruler = |value: Value, shapes: &mut Vec<Shape>| {
|
||||
let position = plot.transform.position_from_value(&value);
|
||||
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),
|
||||
@@ -1632,7 +1632,7 @@ fn add_rulers_and_text(
|
||||
let corner_value = elem.corner_value();
|
||||
shapes.push(Shape::text(
|
||||
&*plot.ui.fonts(),
|
||||
plot.transform.position_from_value(&corner_value) + vec2(3.0, -2.0),
|
||||
plot.transform.position_from_point(&corner_value) + vec2(3.0, -2.0),
|
||||
Align2::LEFT_BOTTOM,
|
||||
text,
|
||||
font_id,
|
||||
@@ -1645,7 +1645,7 @@ fn add_rulers_and_text(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn rulers_at_value(
|
||||
pointer: Pos2,
|
||||
value: Value,
|
||||
value: PlotPoint,
|
||||
name: &str,
|
||||
plot: &PlotConfig<'_>,
|
||||
shapes: &mut Vec<Shape>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{Orientation, Value};
|
||||
use super::{Orientation, PlotPoint};
|
||||
use crate::plot::transform::{PlotBounds, ScreenTransform};
|
||||
use epaint::emath::NumExt;
|
||||
use epaint::{Color32, Rgba, Stroke};
|
||||
@@ -6,8 +6,8 @@ use epaint::{Color32, Rgba, Stroke};
|
||||
/// Trait that abstracts from rectangular 'Value'-like elements, such as bars or boxes
|
||||
pub(super) trait RectElement {
|
||||
fn name(&self) -> &str;
|
||||
fn bounds_min(&self) -> Value;
|
||||
fn bounds_max(&self) -> Value;
|
||||
fn bounds_min(&self) -> PlotPoint;
|
||||
fn bounds_max(&self) -> PlotPoint;
|
||||
|
||||
fn bounds(&self) -> PlotBounds {
|
||||
let mut bounds = PlotBounds::NOTHING;
|
||||
@@ -17,29 +17,29 @@ pub(super) trait RectElement {
|
||||
}
|
||||
|
||||
/// At which argument (input; usually X) there is a ruler (usually vertical)
|
||||
fn arguments_with_ruler(&self) -> Vec<Value> {
|
||||
fn arguments_with_ruler(&self) -> Vec<PlotPoint> {
|
||||
// Default: one at center
|
||||
vec![self.bounds().center()]
|
||||
}
|
||||
|
||||
/// At which value (output; usually Y) there is a ruler (usually horizontal)
|
||||
fn values_with_ruler(&self) -> Vec<Value>;
|
||||
fn values_with_ruler(&self) -> Vec<PlotPoint>;
|
||||
|
||||
/// The diagram's orientation (vertical/horizontal)
|
||||
fn orientation(&self) -> Orientation;
|
||||
|
||||
/// Get X/Y-value for (argument, value) pair, taking into account orientation
|
||||
fn point_at(&self, argument: f64, value: f64) -> Value {
|
||||
fn point_at(&self, argument: f64, value: f64) -> PlotPoint {
|
||||
match self.orientation() {
|
||||
Orientation::Horizontal => Value::new(value, argument),
|
||||
Orientation::Vertical => Value::new(argument, value),
|
||||
Orientation::Horizontal => PlotPoint::new(value, argument),
|
||||
Orientation::Vertical => PlotPoint::new(argument, value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Right top of the rectangle (position of text)
|
||||
fn corner_value(&self) -> Value {
|
||||
fn corner_value(&self) -> PlotPoint {
|
||||
//self.point_at(self.position + self.width / 2.0, value)
|
||||
Value {
|
||||
PlotPoint {
|
||||
x: self.bounds_max().x,
|
||||
y: self.bounds_max().y,
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ use std::ops::{Bound, RangeBounds, RangeInclusive};
|
||||
|
||||
use crate::plot::transform::PlotBounds;
|
||||
|
||||
/// A value in the value-space of the plot.
|
||||
/// A point coordinate in the plot.
|
||||
///
|
||||
/// Uses f64 for improved accuracy to enable plotting
|
||||
/// large values (e.g. unix time on x axis).
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Value {
|
||||
pub struct PlotPoint {
|
||||
/// This is often something monotonically increasing, such as time, but doesn't have to be.
|
||||
/// Goes from left to right.
|
||||
pub x: f64,
|
||||
@@ -16,7 +16,14 @@ pub struct Value {
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
impl Value {
|
||||
impl From<[f64; 2]> for PlotPoint {
|
||||
#[inline]
|
||||
fn from([x, y]: [f64; 2]) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
impl PlotPoint {
|
||||
#[inline(always)]
|
||||
pub fn new(x: impl Into<f64>, y: impl Into<f64>) -> Self {
|
||||
Self {
|
||||
@@ -140,22 +147,49 @@ impl Default for Orientation {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Values {
|
||||
pub(super) values: Vec<Value>,
|
||||
generator: Option<ExplicitGenerator>,
|
||||
/// Represents many [`PlotPoint`]s.
|
||||
///
|
||||
/// These can be an owned `Vec` or generated with a function.
|
||||
pub enum PlotPoints {
|
||||
Owned(Vec<PlotPoint>),
|
||||
Generator(ExplicitGenerator),
|
||||
// Borrowed(&[PlotPoint]), // TODO: Lifetimes are tricky in this case.
|
||||
}
|
||||
|
||||
impl Values {
|
||||
pub fn from_values(values: Vec<Value>) -> Self {
|
||||
Self {
|
||||
values,
|
||||
generator: None,
|
||||
}
|
||||
impl Default for PlotPoints {
|
||||
fn default() -> Self {
|
||||
Self::Owned(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[f64; 2]> for PlotPoints {
|
||||
fn from(coordinate: [f64; 2]) -> Self {
|
||||
Self::new(vec![coordinate])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<[f64; 2]>> for PlotPoints {
|
||||
fn from(coordinates: Vec<[f64; 2]>) -> Self {
|
||||
Self::new(coordinates)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<[f64; 2]> for PlotPoints {
|
||||
fn from_iter<T: IntoIterator<Item = [f64; 2]>>(iter: T) -> Self {
|
||||
Self::Owned(iter.into_iter().map(|point| point.into()).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl PlotPoints {
|
||||
pub fn new(points: Vec<[f64; 2]>) -> Self {
|
||||
Self::from_iter(points)
|
||||
}
|
||||
|
||||
pub fn from_values_iter(iter: impl Iterator<Item = Value>) -> Self {
|
||||
Self::from_values(iter.collect())
|
||||
pub fn points(&self) -> &[PlotPoint] {
|
||||
match self {
|
||||
PlotPoints::Owned(points) => points.as_slice(),
|
||||
PlotPoints::Generator(_) => &[],
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a line based on a function `y=f(x)`, a range (which can be infinite) for x and the number of points.
|
||||
@@ -180,10 +214,7 @@ impl Values {
|
||||
points,
|
||||
};
|
||||
|
||||
Self {
|
||||
values: Vec::new(),
|
||||
generator: Some(generator),
|
||||
}
|
||||
Self::Generator(generator)
|
||||
}
|
||||
|
||||
/// Draw a line based on a function `(x,y)=f(t)`, a range for t and the number of points.
|
||||
@@ -208,48 +239,55 @@ impl Values {
|
||||
} else {
|
||||
(end - start) / points as f64
|
||||
};
|
||||
let values = (0..points).map(|i| {
|
||||
let t = start + i as f64 * increment;
|
||||
let (x, y) = function(t);
|
||||
Value { x, y }
|
||||
});
|
||||
Self::from_values_iter(values)
|
||||
(0..points)
|
||||
.map(|i| {
|
||||
let t = start + i as f64 * increment;
|
||||
let (x, y) = function(t);
|
||||
[x, y]
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// From a series of y-values.
|
||||
/// The x-values will be the indices of these values
|
||||
pub fn from_ys_f32(ys: &[f32]) -> Self {
|
||||
let values: Vec<Value> = ys
|
||||
.iter()
|
||||
ys.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &y)| Value {
|
||||
x: i as f64,
|
||||
y: y as f64,
|
||||
})
|
||||
.collect();
|
||||
Self::from_values(values)
|
||||
.map(|(i, &y)| [i as f64, y as f64])
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// From a series of y-values.
|
||||
/// The x-values will be the indices of these values
|
||||
pub fn from_ys_f64(ys: &[f64]) -> Self {
|
||||
ys.iter().enumerate().map(|(i, &y)| [i as f64, y]).collect()
|
||||
}
|
||||
|
||||
/// Returns true if there are no data points available and there is no function to generate any.
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.generator.is_none() && self.values.is_empty()
|
||||
match self {
|
||||
PlotPoints::Owned(points) => points.is_empty(),
|
||||
PlotPoints::Generator(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// If initialized with a generator function, this will generate `n` evenly spaced points in the
|
||||
/// given range.
|
||||
pub(super) fn generate_points(&mut self, x_range: RangeInclusive<f64>) {
|
||||
if let Some(generator) = self.generator.take() {
|
||||
if let Some(intersection) = Self::range_intersection(&x_range, &generator.x_range) {
|
||||
let increment =
|
||||
(intersection.end() - intersection.start()) / (generator.points - 1) as f64;
|
||||
self.values = (0..generator.points)
|
||||
.map(|i| {
|
||||
let x = intersection.start() + i as f64 * increment;
|
||||
let y = (generator.function)(x);
|
||||
Value { x, y }
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
if let Self::Generator(generator) = self {
|
||||
*self = Self::range_intersection(&x_range, &generator.x_range)
|
||||
.map(|intersection| {
|
||||
let increment =
|
||||
(intersection.end() - intersection.start()) / (generator.points - 1) as f64;
|
||||
(0..generator.points)
|
||||
.map(|i| {
|
||||
let x = intersection.start() + i as f64 * increment;
|
||||
let y = (generator.function)(x);
|
||||
[x, y]
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,18 +302,15 @@ impl Values {
|
||||
}
|
||||
|
||||
pub(super) fn get_bounds(&self) -> PlotBounds {
|
||||
if self.values.is_empty() {
|
||||
if let Some(generator) = &self.generator {
|
||||
generator.estimate_bounds()
|
||||
} else {
|
||||
PlotBounds::NOTHING
|
||||
match self {
|
||||
PlotPoints::Owned(points) => {
|
||||
let mut bounds = PlotBounds::NOTHING;
|
||||
for point in points {
|
||||
bounds.extend_with(point);
|
||||
}
|
||||
bounds
|
||||
}
|
||||
} else {
|
||||
let mut bounds = PlotBounds::NOTHING;
|
||||
for value in &self.values {
|
||||
bounds.extend_with(value);
|
||||
}
|
||||
bounds
|
||||
PlotPoints::Generator(generator) => generator.estimate_bounds(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,13 +353,13 @@ impl MarkerShape {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Query the values 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> {
|
||||
/// No geometry based on single elements (examples: text, image, horizontal/vertical line)
|
||||
None,
|
||||
|
||||
/// Point values (X-Y graphs)
|
||||
Points(&'a [Value]),
|
||||
Points(&'a [PlotPoint]),
|
||||
|
||||
/// Rectangles (examples: boxes or bars)
|
||||
// Has currently no data, as it would require copying rects or iterating a list of pointers.
|
||||
@@ -335,7 +370,7 @@ pub(crate) enum PlotGeometry<'a> {
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Describes a function y = f(x) with an optional range for x and a number of points.
|
||||
struct ExplicitGenerator {
|
||||
pub struct ExplicitGenerator {
|
||||
function: Box<dyn Fn(f64) -> f64>,
|
||||
x_range: RangeInclusive<f64>,
|
||||
points: usize,
|
||||
|
||||
@@ -13,7 +13,7 @@ use transform::ScreenTransform;
|
||||
|
||||
pub use items::{
|
||||
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape,
|
||||
Orientation, PlotImage, Points, Polygon, Text, VLine, Value, Values,
|
||||
Orientation, PlotImage, PlotPoint, PlotPoints, Points, Polygon, Text, VLine,
|
||||
};
|
||||
pub use legend::{Corner, Legend};
|
||||
pub use transform::PlotBounds;
|
||||
@@ -22,7 +22,7 @@ mod items;
|
||||
mod legend;
|
||||
mod transform;
|
||||
|
||||
type LabelFormatterFn = dyn Fn(&str, &Value) -> String;
|
||||
type LabelFormatterFn = dyn Fn(&str, &PlotPoint) -> String;
|
||||
type LabelFormatter = Option<Box<LabelFormatterFn>>;
|
||||
type AxisFormatterFn = dyn Fn(f64, &RangeInclusive<f64>) -> String;
|
||||
type AxisFormatter = Option<Box<AxisFormatterFn>>;
|
||||
@@ -32,12 +32,12 @@ type GridSpacer = Box<GridSpacerFn>;
|
||||
|
||||
/// Specifies the coordinates formatting when passed to [`Plot::coordinates_formatter`].
|
||||
pub struct CoordinatesFormatter {
|
||||
function: Box<dyn Fn(&Value, &PlotBounds) -> String>,
|
||||
function: Box<dyn Fn(&PlotPoint, &PlotBounds) -> String>,
|
||||
}
|
||||
|
||||
impl CoordinatesFormatter {
|
||||
/// Create a new formatter based on the pointer coordinate and the plot bounds.
|
||||
pub fn new(function: impl Fn(&Value, &PlotBounds) -> String + 'static) -> Self {
|
||||
pub fn new(function: impl Fn(&PlotPoint, &PlotBounds) -> String + 'static) -> Self {
|
||||
Self {
|
||||
function: Box::new(function),
|
||||
}
|
||||
@@ -52,7 +52,7 @@ impl CoordinatesFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
fn format(&self, value: &Value, bounds: &PlotBounds) -> String {
|
||||
fn format(&self, value: &PlotPoint, bounds: &PlotBounds) -> String {
|
||||
(self.function)(value, bounds)
|
||||
}
|
||||
}
|
||||
@@ -178,12 +178,12 @@ impl LinkedAxisGroup {
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// use egui::plot::{Line, Plot, Value, Values};
|
||||
/// let sin = (0..1000).map(|i| {
|
||||
/// use egui::plot::{Line, Plot, PlotPoints};
|
||||
/// let sin: PlotPoints = (0..1000).map(|i| {
|
||||
/// let x = i as f64 * 0.01;
|
||||
/// Value::new(x, x.sin())
|
||||
/// });
|
||||
/// let line = Line::new(Values::from_values_iter(sin));
|
||||
/// [x, x.sin()]
|
||||
/// }).collect();
|
||||
/// let line = Line::new(sin);
|
||||
/// Plot::new("my_plot").view_aspect(2.0).show(ui, |plot_ui| plot_ui.line(line));
|
||||
/// # });
|
||||
/// ```
|
||||
@@ -359,12 +359,12 @@ impl Plot {
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// use egui::plot::{Line, Plot, Value, Values};
|
||||
/// let sin = (0..1000).map(|i| {
|
||||
/// use egui::plot::{Line, Plot, PlotPoints};
|
||||
/// let sin: PlotPoints = (0..1000).map(|i| {
|
||||
/// let x = i as f64 * 0.01;
|
||||
/// Value::new(x, x.sin())
|
||||
/// });
|
||||
/// let line = Line::new(Values::from_values_iter(sin));
|
||||
/// [x, x.sin()]
|
||||
/// }).collect();
|
||||
/// let line = Line::new(sin);
|
||||
/// Plot::new("my_plot").view_aspect(2.0)
|
||||
/// .label_formatter(|name, value| {
|
||||
/// if !name.is_empty() {
|
||||
@@ -378,7 +378,7 @@ impl Plot {
|
||||
/// ```
|
||||
pub fn label_formatter(
|
||||
mut self,
|
||||
label_formatter: impl Fn(&str, &Value) -> String + 'static,
|
||||
label_formatter: impl Fn(&str, &PlotPoint) -> String + 'static,
|
||||
) -> Self {
|
||||
self.label_formatter = Some(Box::new(label_formatter));
|
||||
self
|
||||
@@ -892,7 +892,7 @@ impl PlotUi {
|
||||
}
|
||||
|
||||
/// The pointer position in plot coordinates. Independent of whether the pointer is in the plot area.
|
||||
pub fn pointer_coordinate(&self) -> Option<Value> {
|
||||
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().pointer.latest_pos()? - self.response.drag_delta();
|
||||
let value = self.plot_from_screen(last_pos);
|
||||
@@ -907,12 +907,12 @@ impl PlotUi {
|
||||
}
|
||||
|
||||
/// Transform the plot coordinates to screen coordinates.
|
||||
pub fn screen_from_plot(&self, position: Value) -> Pos2 {
|
||||
self.last_screen_transform.position_from_value(&position)
|
||||
pub fn screen_from_plot(&self, position: PlotPoint) -> Pos2 {
|
||||
self.last_screen_transform.position_from_point(&position)
|
||||
}
|
||||
|
||||
/// Transform the screen coordinates to plot coordinates.
|
||||
pub fn plot_from_screen(&self, position: Pos2) -> Value {
|
||||
pub fn plot_from_screen(&self, position: Pos2) -> PlotPoint {
|
||||
self.last_screen_transform.value_from_position(position)
|
||||
}
|
||||
|
||||
@@ -1188,12 +1188,12 @@ impl PreparedPlot {
|
||||
let value_main = step.value;
|
||||
|
||||
let value = if axis == 0 {
|
||||
Value::new(value_main, value_cross)
|
||||
PlotPoint::new(value_main, value_cross)
|
||||
} else {
|
||||
Value::new(value_cross, value_main)
|
||||
PlotPoint::new(value_cross, value_main)
|
||||
};
|
||||
|
||||
let pos_in_gui = transform.position_from_value(&value);
|
||||
let pos_in_gui = transform.position_from_point(&value);
|
||||
let spacing_in_points = (transform.dpos_dvalue()[axis] * step.step_size).abs() as f32;
|
||||
|
||||
let line_alpha = remap_clamp(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use super::items::Value;
|
||||
use super::PlotPoint;
|
||||
use crate::*;
|
||||
|
||||
/// 2D bounding box of f64 precision.
|
||||
@@ -52,15 +52,16 @@ impl PlotBounds {
|
||||
self.max[1] - self.min[1]
|
||||
}
|
||||
|
||||
pub fn center(&self) -> Value {
|
||||
Value {
|
||||
x: (self.min[0] + self.max[0]) / 2.0,
|
||||
y: (self.min[1] + self.max[1]) / 2.0,
|
||||
}
|
||||
pub fn center(&self) -> PlotPoint {
|
||||
[
|
||||
(self.min[0] + self.max[0]) / 2.0,
|
||||
(self.min[1] + self.max[1]) / 2.0,
|
||||
]
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Expand to include the given (x,y) value
|
||||
pub(crate) fn extend_with(&mut self, value: &Value) {
|
||||
pub(crate) fn extend_with(&mut self, value: &PlotPoint) {
|
||||
self.extend_with_x(value.x);
|
||||
self.extend_with_y(value.y);
|
||||
}
|
||||
@@ -236,7 +237,7 @@ impl ScreenTransform {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position_from_value(&self, value: &Value) -> Pos2 {
|
||||
pub fn position_from_point(&self, value: &PlotPoint) -> Pos2 {
|
||||
let x = remap(
|
||||
value.x,
|
||||
self.bounds.min[0]..=self.bounds.max[0],
|
||||
@@ -250,7 +251,7 @@ impl ScreenTransform {
|
||||
pos2(x as f32, y as f32)
|
||||
}
|
||||
|
||||
pub fn value_from_position(&self, pos: Pos2) -> Value {
|
||||
pub fn value_from_position(&self, pos: Pos2) -> PlotPoint {
|
||||
let x = remap(
|
||||
pos.x as f64,
|
||||
(self.frame.left() as f64)..=(self.frame.right() as f64),
|
||||
@@ -261,16 +262,16 @@ impl ScreenTransform {
|
||||
(self.frame.bottom() as f64)..=(self.frame.top() as f64), // negated y axis!
|
||||
self.bounds.min[1]..=self.bounds.max[1],
|
||||
);
|
||||
Value::new(x, y)
|
||||
PlotPoint::new(x, y)
|
||||
}
|
||||
|
||||
/// Transform a rectangle of plot values to a screen-coordinate rectangle.
|
||||
///
|
||||
/// This typically means that the rect is mirrored vertically (top becomes bottom and vice versa),
|
||||
/// since the plot's coordinate system has +Y up, while egui has +Y down.
|
||||
pub fn rect_from_values(&self, value1: &Value, value2: &Value) -> Rect {
|
||||
let pos1 = self.position_from_value(value1);
|
||||
let pos2 = self.position_from_value(value2);
|
||||
pub fn rect_from_values(&self, value1: &PlotPoint, value2: &PlotPoint) -> Rect {
|
||||
let pos1 = self.position_from_point(value1);
|
||||
let pos2 = self.position_from_point(value2);
|
||||
|
||||
let mut rect = Rect::NOTHING;
|
||||
rect.extend_with(pos1);
|
||||
|
||||
Reference in New Issue
Block a user