More plot items (#471)
* Added plot items: * Arrows, also called "Quiver plots" in matplotlib etc. * Convex polygons * Text * Images Other changes: * Make HLine/VLine into PlotItems as well. * Add a "fill" property to Line so that we can fill/shade the area between a line and a horizontal reference line. * Add stems to Points, which are lines between the points and a horizontal reference line. * Allow using .. when specifying ranges for values generated by explicit callback functions, as an alias for f64::NEG_INFINITY..f64::INFINITY * Allow using ranges with exclusive end bounds for values generated by parametric callback functions to generate values where the first and last value are not the same. * update changelog * add legend background
This commit is contained in:
parent
e22c242d17
commit
147e7a47aa
5 changed files with 971 additions and 117 deletions
|
@ -8,6 +8,7 @@ NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md) and [
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### Added ⭐
|
### Added ⭐
|
||||||
|
* [More plot items: Arrows, Polygons, Text, Images](https://github.com/emilk/egui/pull/471).
|
||||||
* [Plot legend improvements](https://github.com/emilk/egui/pull/410).
|
* [Plot legend improvements](https://github.com/emilk/egui/pull/410).
|
||||||
* [Line markers for plots](https://github.com/emilk/egui/pull/363).
|
* [Line markers for plots](https://github.com/emilk/egui/pull/363).
|
||||||
* Add right and bottom panels (`SidePanel::right` and `Panel::bottom`).
|
* Add right and bottom panels (`SidePanel::right` and `Panel::bottom`).
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -33,6 +33,7 @@ impl Corner {
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub struct Legend {
|
pub struct Legend {
|
||||||
pub text_style: TextStyle,
|
pub text_style: TextStyle,
|
||||||
|
pub background_alpha: f32,
|
||||||
pub position: Corner,
|
pub position: Corner,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,17 +41,26 @@ impl Default for Legend {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
text_style: TextStyle::Body,
|
text_style: TextStyle::Body,
|
||||||
|
background_alpha: 0.75,
|
||||||
position: Corner::RightTop,
|
position: Corner::RightTop,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Legend {
|
impl Legend {
|
||||||
|
/// Which text style to use for the legend. Default: `TextStyle::Body`.
|
||||||
pub fn text_style(mut self, style: TextStyle) -> Self {
|
pub fn text_style(mut self, style: TextStyle) -> Self {
|
||||||
self.text_style = style;
|
self.text_style = style;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The alpha of the legend background. Default: `0.75`.
|
||||||
|
pub fn background_alpha(mut self, alpha: f32) -> Self {
|
||||||
|
self.background_alpha = alpha;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// In which corner to place the legend. Default: `Corner::RightTop`.
|
||||||
pub fn position(mut self, corner: Corner) -> Self {
|
pub fn position(mut self, corner: Corner) -> Self {
|
||||||
self.position = corner;
|
self.position = corner;
|
||||||
self
|
self
|
||||||
|
@ -220,17 +230,29 @@ impl Widget for &mut LegendWidget {
|
||||||
Corner::RightTop | Corner::RightBottom => Align::RIGHT,
|
Corner::RightTop | Corner::RightBottom => Align::RIGHT,
|
||||||
};
|
};
|
||||||
let layout = Layout::from_main_dir_and_cross_align(main_dir, cross_align);
|
let layout = Layout::from_main_dir_and_cross_align(main_dir, cross_align);
|
||||||
let legend_pad = 2.0;
|
let legend_pad = 4.0;
|
||||||
let legend_rect = rect.shrink(legend_pad);
|
let legend_rect = rect.shrink(legend_pad);
|
||||||
let mut legend_ui = ui.child_ui(legend_rect, layout);
|
let mut legend_ui = ui.child_ui(legend_rect, layout);
|
||||||
legend_ui
|
legend_ui
|
||||||
.scope(|ui| {
|
.scope(|ui| {
|
||||||
ui.style_mut().body_text_style = config.text_style;
|
ui.style_mut().body_text_style = config.text_style;
|
||||||
entries
|
let background_frame = Frame {
|
||||||
.iter_mut()
|
margin: vec2(8.0, 4.0),
|
||||||
.map(|(name, entry)| entry.ui(ui, name.clone()))
|
corner_radius: ui.style().visuals.window_corner_radius,
|
||||||
.reduce(|r1, r2| r1.union(r2))
|
shadow: epaint::Shadow::default(),
|
||||||
.unwrap()
|
fill: ui.style().visuals.extreme_bg_color,
|
||||||
|
stroke: ui.style().visuals.window_stroke(),
|
||||||
|
}
|
||||||
|
.multiply_with_opacity(config.background_alpha);
|
||||||
|
background_frame
|
||||||
|
.show(ui, |ui| {
|
||||||
|
entries
|
||||||
|
.iter_mut()
|
||||||
|
.map(|(name, entry)| entry.ui(ui, name.clone()))
|
||||||
|
.reduce(|r1, r2| r1.union(r2))
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.inner
|
||||||
})
|
})
|
||||||
.inner
|
.inner
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ mod transform;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use items::PlotItem;
|
use items::PlotItem;
|
||||||
|
pub use items::{Arrows, Line, MarkerShape, PlotImage, Points, Polygon, Text, Value, Values};
|
||||||
pub use items::{HLine, VLine};
|
pub use items::{HLine, VLine};
|
||||||
pub use items::{Line, MarkerShape, Points, Value, Values};
|
|
||||||
use legend::LegendWidget;
|
use legend::LegendWidget;
|
||||||
pub use legend::{Corner, Legend};
|
pub use legend::{Corner, Legend};
|
||||||
use transform::{Bounds, ScreenTransform};
|
use transform::{Bounds, ScreenTransform};
|
||||||
|
@ -51,8 +51,6 @@ pub struct Plot {
|
||||||
next_auto_color_idx: usize,
|
next_auto_color_idx: usize,
|
||||||
|
|
||||||
items: Vec<Box<dyn PlotItem>>,
|
items: Vec<Box<dyn PlotItem>>,
|
||||||
hlines: Vec<HLine>,
|
|
||||||
vlines: Vec<VLine>,
|
|
||||||
|
|
||||||
center_x_axis: bool,
|
center_x_axis: bool,
|
||||||
center_y_axis: bool,
|
center_y_axis: bool,
|
||||||
|
@ -80,8 +78,6 @@ impl Plot {
|
||||||
next_auto_color_idx: 0,
|
next_auto_color_idx: 0,
|
||||||
|
|
||||||
items: Default::default(),
|
items: Default::default(),
|
||||||
hlines: Default::default(),
|
|
||||||
vlines: Default::default(),
|
|
||||||
|
|
||||||
center_x_axis: false,
|
center_x_axis: false,
|
||||||
center_y_axis: false,
|
center_y_axis: false,
|
||||||
|
@ -111,7 +107,6 @@ impl Plot {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a data lines.
|
/// Add a data lines.
|
||||||
/// You can add multiple lines.
|
|
||||||
pub fn line(mut self, mut line: Line) -> Self {
|
pub fn line(mut self, mut line: Line) -> Self {
|
||||||
if line.series.is_empty() {
|
if line.series.is_empty() {
|
||||||
return self;
|
return self;
|
||||||
|
@ -122,12 +117,34 @@ impl Plot {
|
||||||
line.stroke.color = self.auto_color();
|
line.stroke.color = self.auto_color();
|
||||||
}
|
}
|
||||||
self.items.push(Box::new(line));
|
self.items.push(Box::new(line));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a polygon. The polygon has to be convex.
|
||||||
|
pub fn polygon(mut self, mut polygon: Polygon) -> Self {
|
||||||
|
if polygon.series.is_empty() {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a text.
|
||||||
|
pub fn text(mut self, text: Text) -> Self {
|
||||||
|
if text.text.is_empty() {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.items.push(Box::new(text));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add data points.
|
/// Add data points.
|
||||||
/// You can add multiple sets of points.
|
|
||||||
pub fn points(mut self, mut points: Points) -> Self {
|
pub fn points(mut self, mut points: Points) -> Self {
|
||||||
if points.series.is_empty() {
|
if points.series.is_empty() {
|
||||||
return self;
|
return self;
|
||||||
|
@ -138,7 +155,26 @@ impl Plot {
|
||||||
points.color = self.auto_color();
|
points.color = self.auto_color();
|
||||||
}
|
}
|
||||||
self.items.push(Box::new(points));
|
self.items.push(Box::new(points));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add arrows.
|
||||||
|
pub fn arrows(mut self, mut arrows: Arrows) -> Self {
|
||||||
|
if arrows.origins.is_empty() || arrows.tips.is_empty() {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an image.
|
||||||
|
pub fn image(mut self, image: PlotImage) -> Self {
|
||||||
|
self.items.push(Box::new(image));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +185,7 @@ impl Plot {
|
||||||
if hline.stroke.color == Color32::TRANSPARENT {
|
if hline.stroke.color == Color32::TRANSPARENT {
|
||||||
hline.stroke.color = self.auto_color();
|
hline.stroke.color = self.auto_color();
|
||||||
}
|
}
|
||||||
self.hlines.push(hline);
|
self.items.push(Box::new(hline));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +196,7 @@ impl Plot {
|
||||||
if vline.stroke.color == Color32::TRANSPARENT {
|
if vline.stroke.color == Color32::TRANSPARENT {
|
||||||
vline.stroke.color = self.auto_color();
|
vline.stroke.color = self.auto_color();
|
||||||
}
|
}
|
||||||
self.vlines.push(vline);
|
self.items.push(Box::new(vline));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,8 +320,6 @@ impl Widget for Plot {
|
||||||
name,
|
name,
|
||||||
next_auto_color_idx: _,
|
next_auto_color_idx: _,
|
||||||
mut items,
|
mut items,
|
||||||
hlines,
|
|
||||||
vlines,
|
|
||||||
center_x_axis,
|
center_x_axis,
|
||||||
center_y_axis,
|
center_y_axis,
|
||||||
allow_zoom,
|
allow_zoom,
|
||||||
|
@ -381,11 +415,9 @@ impl Widget for Plot {
|
||||||
// Set bounds automatically based on content.
|
// Set bounds automatically based on content.
|
||||||
if auto_bounds || !bounds.is_valid() {
|
if auto_bounds || !bounds.is_valid() {
|
||||||
bounds = min_auto_bounds;
|
bounds = min_auto_bounds;
|
||||||
hlines.iter().for_each(|line| bounds.extend_with_y(line.y));
|
|
||||||
vlines.iter().for_each(|line| bounds.extend_with_x(line.x));
|
|
||||||
items
|
items
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|item| bounds.merge(&item.series().get_bounds()));
|
.for_each(|item| bounds.merge(&item.get_bounds()));
|
||||||
bounds.add_relative_margin(margin_fraction);
|
bounds.add_relative_margin(margin_fraction);
|
||||||
}
|
}
|
||||||
// Make sure they are not empty.
|
// Make sure they are not empty.
|
||||||
|
@ -436,17 +468,14 @@ impl Widget for Plot {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize values from functions.
|
// Initialize values from functions.
|
||||||
items.iter_mut().for_each(|item| {
|
items
|
||||||
item.series_mut()
|
.iter_mut()
|
||||||
.generate_points(transform.bounds().range_x())
|
.for_each(|item| item.initialize(transform.bounds().range_x()));
|
||||||
});
|
|
||||||
|
|
||||||
let bounds = *transform.bounds();
|
let bounds = *transform.bounds();
|
||||||
|
|
||||||
let prepared = Prepared {
|
let prepared = Prepared {
|
||||||
items,
|
items,
|
||||||
hlines,
|
|
||||||
vlines,
|
|
||||||
show_x,
|
show_x,
|
||||||
show_y,
|
show_y,
|
||||||
transform,
|
transform,
|
||||||
|
@ -479,8 +508,6 @@ impl Widget for Plot {
|
||||||
|
|
||||||
struct Prepared {
|
struct Prepared {
|
||||||
items: Vec<Box<dyn PlotItem>>,
|
items: Vec<Box<dyn PlotItem>>,
|
||||||
hlines: Vec<HLine>,
|
|
||||||
vlines: Vec<VLine>,
|
|
||||||
show_x: bool,
|
show_x: bool,
|
||||||
show_y: bool,
|
show_y: bool,
|
||||||
transform: ScreenTransform,
|
transform: ScreenTransform,
|
||||||
|
@ -496,26 +523,10 @@ impl Prepared {
|
||||||
|
|
||||||
let transform = &self.transform;
|
let transform = &self.transform;
|
||||||
|
|
||||||
for &hline in &self.hlines {
|
let mut plot_ui = ui.child_ui(*transform.frame(), Layout::default());
|
||||||
let HLine { y, stroke } = hline;
|
plot_ui.set_clip_rect(*transform.frame());
|
||||||
let points = [
|
|
||||||
transform.position_from_value(&Value::new(transform.bounds().min[0], y)),
|
|
||||||
transform.position_from_value(&Value::new(transform.bounds().max[0], y)),
|
|
||||||
];
|
|
||||||
shapes.push(Shape::line_segment(points, stroke));
|
|
||||||
}
|
|
||||||
|
|
||||||
for &vline in &self.vlines {
|
|
||||||
let VLine { x, stroke } = vline;
|
|
||||||
let points = [
|
|
||||||
transform.position_from_value(&Value::new(x, transform.bounds().min[1])),
|
|
||||||
transform.position_from_value(&Value::new(x, transform.bounds().max[1])),
|
|
||||||
];
|
|
||||||
shapes.push(Shape::line_segment(points, stroke));
|
|
||||||
}
|
|
||||||
|
|
||||||
for item in &self.items {
|
for item in &self.items {
|
||||||
item.get_shapes(transform, &mut shapes);
|
item.get_shapes(&mut plot_ui, transform, &mut shapes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(pointer) = response.hover_pos() {
|
if let Some(pointer) = response.hover_pos() {
|
||||||
|
@ -632,13 +643,15 @@ impl Prepared {
|
||||||
let mut closest_item = None;
|
let mut closest_item = None;
|
||||||
let mut closest_dist_sq = interact_radius.powi(2);
|
let mut closest_dist_sq = interact_radius.powi(2);
|
||||||
for item in items {
|
for item in items {
|
||||||
for value in &item.series().values {
|
if let Some(values) = item.values() {
|
||||||
let pos = transform.position_from_value(value);
|
for value in &values.values {
|
||||||
let dist_sq = pointer.distance_sq(pos);
|
let pos = transform.position_from_value(value);
|
||||||
if dist_sq < closest_dist_sq {
|
let dist_sq = pointer.distance_sq(pos);
|
||||||
closest_dist_sq = dist_sq;
|
if dist_sq < closest_dist_sq {
|
||||||
closest_value = Some(value);
|
closest_dist_sq = dist_sq;
|
||||||
closest_item = Some(item.name());
|
closest_value = Some(value);
|
||||||
|
closest_item = Some(item.name());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use egui::*;
|
use egui::*;
|
||||||
use plot::{Corner, Legend, Line, MarkerShape, Plot, Points, Value, Values};
|
use plot::{
|
||||||
|
Arrows, Corner, HLine, Legend, Line, MarkerShape, Plot, PlotImage, Points, Polygon, Text,
|
||||||
|
VLine, Value, Values,
|
||||||
|
};
|
||||||
use std::f64::consts::TAU;
|
use std::f64::consts::TAU;
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
|
@ -44,7 +47,7 @@ impl LineDemo {
|
||||||
ui.add(
|
ui.add(
|
||||||
egui::DragValue::new(circle_radius)
|
egui::DragValue::new(circle_radius)
|
||||||
.speed(0.1)
|
.speed(0.1)
|
||||||
.clamp_range(0.0..=f32::INFINITY)
|
.clamp_range(0.0..=f64::INFINITY)
|
||||||
.prefix("r: "),
|
.prefix("r: "),
|
||||||
);
|
);
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
@ -90,7 +93,7 @@ impl LineDemo {
|
||||||
let time = self.time;
|
let time = self.time;
|
||||||
Line::new(Values::from_explicit_callback(
|
Line::new(Values::from_explicit_callback(
|
||||||
move |x| 0.5 * (2.0 * x).sin() * time.sin(),
|
move |x| 0.5 * (2.0 * x).sin() * time.sin(),
|
||||||
f64::NEG_INFINITY..=f64::INFINITY,
|
..,
|
||||||
512,
|
512,
|
||||||
))
|
))
|
||||||
.color(Color32::from_rgb(200, 100, 100))
|
.color(Color32::from_rgb(200, 100, 100))
|
||||||
|
@ -187,7 +190,7 @@ impl Widget for &mut MarkerDemo {
|
||||||
ui.add(
|
ui.add(
|
||||||
egui::DragValue::new(&mut self.marker_radius)
|
egui::DragValue::new(&mut self.marker_radius)
|
||||||
.speed(0.1)
|
.speed(0.1)
|
||||||
.clamp_range(0.0..=f32::INFINITY)
|
.clamp_range(0.0..=f64::INFINITY)
|
||||||
.prefix("marker radius: "),
|
.prefix("marker radius: "),
|
||||||
);
|
);
|
||||||
ui.checkbox(&mut self.custom_marker_color, "custom marker color");
|
ui.checkbox(&mut self.custom_marker_color, "custom marker color");
|
||||||
|
@ -221,25 +224,13 @@ impl Default for LegendDemo {
|
||||||
|
|
||||||
impl LegendDemo {
|
impl LegendDemo {
|
||||||
fn line_with_slope(slope: f64) -> Line {
|
fn line_with_slope(slope: f64) -> Line {
|
||||||
Line::new(Values::from_explicit_callback(
|
Line::new(Values::from_explicit_callback(move |x| slope * x, .., 100))
|
||||||
move |x| slope * x,
|
|
||||||
f64::NEG_INFINITY..=f64::INFINITY,
|
|
||||||
100,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
fn sin() -> Line {
|
fn sin() -> Line {
|
||||||
Line::new(Values::from_explicit_callback(
|
Line::new(Values::from_explicit_callback(move |x| x.sin(), .., 100))
|
||||||
move |x| x.sin(),
|
|
||||||
f64::NEG_INFINITY..=f64::INFINITY,
|
|
||||||
100,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
fn cos() -> Line {
|
fn cos() -> Line {
|
||||||
Line::new(Values::from_explicit_callback(
|
Line::new(Values::from_explicit_callback(move |x| x.cos(), .., 100))
|
||||||
move |x| x.cos(),
|
|
||||||
f64::NEG_INFINITY..=f64::INFINITY,
|
|
||||||
100,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,6 +250,12 @@ impl Widget for &mut LegendDemo {
|
||||||
ui.selectable_value(&mut config.position, position, format!("{:?}", position));
|
ui.selectable_value(&mut config.position, position, format!("{:?}", position));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
ui.label("Background alpha:");
|
||||||
|
ui.add(
|
||||||
|
egui::DragValue::new(&mut config.background_alpha)
|
||||||
|
.speed(0.02)
|
||||||
|
.clamp_range(0.0..=1.0),
|
||||||
|
);
|
||||||
let legend_plot = Plot::new("Legend Demo")
|
let legend_plot = Plot::new("Legend Demo")
|
||||||
.line(LegendDemo::line_with_slope(0.5).name("lines"))
|
.line(LegendDemo::line_with_slope(0.5).name("lines"))
|
||||||
.line(LegendDemo::line_with_slope(1.0).name("lines"))
|
.line(LegendDemo::line_with_slope(1.0).name("lines"))
|
||||||
|
@ -270,11 +267,82 @@ impl Widget for &mut LegendDemo {
|
||||||
ui.add(legend_plot)
|
ui.add(legend_plot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Default)]
|
||||||
|
struct ItemsDemo {}
|
||||||
|
|
||||||
|
impl ItemsDemo {}
|
||||||
|
|
||||||
|
impl Widget for &mut ItemsDemo {
|
||||||
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
|
let n = 100;
|
||||||
|
let mut sin_values: Vec<_> = (0..=n)
|
||||||
|
.map(|i| remap(i as f64, 0.0..=n as f64, -TAU..=TAU))
|
||||||
|
.map(|i| Value::new(i, i.sin()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let line = Line::new(Values::from_values(sin_values.split_off(n / 2))).fill(-1.5);
|
||||||
|
let polygon = Polygon::new(Values::from_parametric_callback(
|
||||||
|
|t| (4.0 * t.sin() + 2.0 * t.cos(), 4.0 * t.cos() + 2.0 * t.sin()),
|
||||||
|
0.0..TAU,
|
||||||
|
100,
|
||||||
|
));
|
||||||
|
let points = Points::new(Values::from_values(sin_values))
|
||||||
|
.stems(-1.5)
|
||||||
|
.radius(1.0);
|
||||||
|
|
||||||
|
let arrows = {
|
||||||
|
let pos_radius = 8.0;
|
||||||
|
let tip_radius = 7.0;
|
||||||
|
let arrow_origins = Values::from_parametric_callback(
|
||||||
|
|t| (pos_radius * t.sin(), pos_radius * t.cos()),
|
||||||
|
0.0..TAU,
|
||||||
|
36,
|
||||||
|
);
|
||||||
|
let arrow_tips = Values::from_parametric_callback(
|
||||||
|
|t| (tip_radius * t.sin(), tip_radius * t.cos()),
|
||||||
|
0.0..TAU,
|
||||||
|
36,
|
||||||
|
);
|
||||||
|
Arrows::new(arrow_origins, arrow_tips)
|
||||||
|
};
|
||||||
|
let image = PlotImage::new(
|
||||||
|
TextureId::Egui,
|
||||||
|
Value::new(0.0, 10.0),
|
||||||
|
[
|
||||||
|
ui.fonts().texture().width as f32 / 100.0,
|
||||||
|
ui.fonts().texture().height as f32 / 100.0,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let plot = Plot::new("Items Demo")
|
||||||
|
.hline(HLine::new(9.0).name("Lines horizontal"))
|
||||||
|
.hline(HLine::new(-9.0).name("Lines horizontal"))
|
||||||
|
.vline(VLine::new(9.0).name("Lines vertical"))
|
||||||
|
.vline(VLine::new(-9.0).name("Lines vertical"))
|
||||||
|
.line(line.name("Line with fill"))
|
||||||
|
.polygon(polygon.name("Convex polygon"))
|
||||||
|
.points(points.name("Points with stems"))
|
||||||
|
.text(Text::new(Value::new(-3.0, -3.0), "wow").name("Text"))
|
||||||
|
.text(Text::new(Value::new(-2.0, 2.5), "so graph").name("Text"))
|
||||||
|
.text(Text::new(Value::new(3.0, 3.0), "much color").name("Text"))
|
||||||
|
.text(Text::new(Value::new(2.5, -2.0), "such plot").name("Text"))
|
||||||
|
.image(image.name("Image"))
|
||||||
|
.arrows(arrows.name("Arrows"))
|
||||||
|
.legend(Legend::default().position(Corner::RightBottom))
|
||||||
|
.show_x(false)
|
||||||
|
.show_y(false)
|
||||||
|
.data_aspect(1.0);
|
||||||
|
ui.add(plot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
enum Panel {
|
enum Panel {
|
||||||
Lines,
|
Lines,
|
||||||
Markers,
|
Markers,
|
||||||
Legend,
|
Legend,
|
||||||
|
Items,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Panel {
|
impl Default for Panel {
|
||||||
|
@ -288,6 +356,7 @@ pub struct PlotDemo {
|
||||||
line_demo: LineDemo,
|
line_demo: LineDemo,
|
||||||
marker_demo: MarkerDemo,
|
marker_demo: MarkerDemo,
|
||||||
legend_demo: LegendDemo,
|
legend_demo: LegendDemo,
|
||||||
|
items_demo: ItemsDemo,
|
||||||
open_panel: Panel,
|
open_panel: Panel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,6 +395,7 @@ impl super::View for PlotDemo {
|
||||||
ui.selectable_value(&mut self.open_panel, Panel::Lines, "Lines");
|
ui.selectable_value(&mut self.open_panel, Panel::Lines, "Lines");
|
||||||
ui.selectable_value(&mut self.open_panel, Panel::Markers, "Markers");
|
ui.selectable_value(&mut self.open_panel, Panel::Markers, "Markers");
|
||||||
ui.selectable_value(&mut self.open_panel, Panel::Legend, "Legend");
|
ui.selectable_value(&mut self.open_panel, Panel::Legend, "Legend");
|
||||||
|
ui.selectable_value(&mut self.open_panel, Panel::Items, "Items");
|
||||||
});
|
});
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
|
@ -339,6 +409,9 @@ impl super::View for PlotDemo {
|
||||||
Panel::Legend => {
|
Panel::Legend => {
|
||||||
ui.add(&mut self.legend_demo);
|
ui.add(&mut self.legend_demo);
|
||||||
}
|
}
|
||||||
|
Panel::Items => {
|
||||||
|
ui.add(&mut self.items_demo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue