Plot: Legend improvements (#410)
* initial work on markers * clippy fix * simplify marker * use option for color * prepare for more demo plots * more improvements for markers * some small adjustments * better highlighting * don't draw transparent lines * use transparent color instead of option * don't brighten curves when highlighting * Initial changes to lengend: * Font options * Position options * Internal cleanup * draw legend on top of curves * update changelog * fix legend checkboxes * simplify legend * remove unnecessary derives * remove config from legend entries * avoid allocations and use line_segment * compare against transparent color * create new Points primitive * fix doctest * some cleanup and fix hover * common interface for lines and points * clippy fixes * reduce visibilities * update legend * clippy fix * change instances of "curve" to "item" * change visibility * Update egui/src/widgets/plot/mod.rs Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * Update egui/src/widgets/plot/mod.rs Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * Update egui_demo_lib/src/apps/demo/plot_demo.rs Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * Update egui_demo_lib/src/apps/demo/plot_demo.rs Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * changes based on review * add legend to demo * fix test * move highlighted items to front * dynamic plot size * add legend again * remove height * clippy fix * update changelog * minor changes * Update egui/src/widgets/plot/legend.rs Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * Update egui/src/widgets/plot/legend.rs Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * Update egui/src/widgets/plot/legend.rs Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * changes based on review * add functions to mutate legend config * use horizontal_align Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
ece25ee7f3
commit
02db9ee583
|
|
@ -8,6 +8,7 @@ NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md) and [
|
|||
## Unreleased
|
||||
|
||||
### Added ⭐
|
||||
* [Plot legend improvements](https://github.com/emilk/egui/pull/410).
|
||||
* [Line markers for plots](https://github.com/emilk/egui/pull/363).
|
||||
* Add right and bottom panels (`SidePanel::right` and `Panel::bottom`).
|
||||
* Add resizable panels.
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ pub(super) trait PlotItem {
|
|||
fn name(&self) -> &str;
|
||||
fn color(&self) -> Color32;
|
||||
fn highlight(&mut self);
|
||||
fn highlighted(&self) -> bool;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -273,7 +274,8 @@ impl Line {
|
|||
|
||||
/// Name of this line.
|
||||
///
|
||||
/// This name will show up in the plot legend, if legends are turned on.
|
||||
/// This name will show up in the plot legend, if legends are turned on. Multiple lines may
|
||||
/// share the same name, in which case they will also share an entry in the legend.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn name(mut self, name: impl ToString) -> Self {
|
||||
self.name = name.to_string();
|
||||
|
|
@ -327,6 +329,10 @@ impl PlotItem for Line {
|
|||
fn highlight(&mut self) {
|
||||
self.highlight = true;
|
||||
}
|
||||
|
||||
fn highlighted(&self) -> bool {
|
||||
self.highlight
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of points.
|
||||
|
|
@ -386,9 +392,10 @@ impl Points {
|
|||
self
|
||||
}
|
||||
|
||||
/// Name of this series of markers.
|
||||
/// Name of this set of points.
|
||||
///
|
||||
/// This name will show up in the plot legend, if legends are turned on.
|
||||
/// This name will show up in the plot legend, if legends are turned on. Multiple sets of points
|
||||
/// may share the same name, in which case they will also share an entry in the legend.
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn name(mut self, name: impl ToString) -> Self {
|
||||
self.name = name.to_string();
|
||||
|
|
@ -556,4 +563,8 @@ impl PlotItem for Points {
|
|||
fn highlight(&mut self) {
|
||||
self.highlight = true;
|
||||
}
|
||||
|
||||
fn highlighted(&self) -> bool {
|
||||
self.highlight
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,81 +1,237 @@
|
|||
use std::string::String;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
string::String,
|
||||
};
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub(crate) struct LegendEntry {
|
||||
pub text: String,
|
||||
pub color: Color32,
|
||||
pub checked: bool,
|
||||
pub hovered: bool,
|
||||
use super::items::PlotItem;
|
||||
|
||||
/// Where to place the plot legend.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Corner {
|
||||
LeftTop,
|
||||
RightTop,
|
||||
LeftBottom,
|
||||
RightBottom,
|
||||
}
|
||||
|
||||
impl Corner {
|
||||
pub fn all() -> impl Iterator<Item = Corner> {
|
||||
[
|
||||
Corner::LeftTop,
|
||||
Corner::RightTop,
|
||||
Corner::LeftBottom,
|
||||
Corner::RightBottom,
|
||||
]
|
||||
.iter()
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
||||
/// The configuration for a plot legend.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub struct Legend {
|
||||
pub text_style: TextStyle,
|
||||
pub position: Corner,
|
||||
}
|
||||
|
||||
impl Default for Legend {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
text_style: TextStyle::Body,
|
||||
position: Corner::RightTop,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Legend {
|
||||
pub fn text_style(mut self, style: TextStyle) -> Self {
|
||||
self.text_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn position(mut self, corner: Corner) -> Self {
|
||||
self.position = corner;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct LegendEntry {
|
||||
color: Color32,
|
||||
checked: bool,
|
||||
hovered: bool,
|
||||
}
|
||||
|
||||
impl LegendEntry {
|
||||
pub fn new(text: String, color: Color32, checked: bool) -> Self {
|
||||
fn new(color: Color32, checked: bool) -> Self {
|
||||
Self {
|
||||
text,
|
||||
color,
|
||||
checked,
|
||||
hovered: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &mut LegendEntry {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let LegendEntry {
|
||||
checked,
|
||||
text,
|
||||
fn ui(&mut self, ui: &mut Ui, text: String) -> Response {
|
||||
let Self {
|
||||
color,
|
||||
..
|
||||
checked,
|
||||
hovered,
|
||||
} = self;
|
||||
let icon_width = ui.spacing().icon_width;
|
||||
let icon_spacing = ui.spacing().icon_spacing;
|
||||
let padding = vec2(2.0, 2.0);
|
||||
let total_extra = padding + vec2(icon_width + icon_spacing, 0.0) + padding;
|
||||
|
||||
let text_style = TextStyle::Button;
|
||||
let galley = ui.fonts().layout_no_wrap(text_style, text.clone());
|
||||
let galley = ui.fonts().layout_no_wrap(ui.style().body_text_style, text);
|
||||
|
||||
let mut desired_size = total_extra + galley.size;
|
||||
desired_size = desired_size.at_least(ui.spacing().interact_size);
|
||||
desired_size.y = desired_size.y.at_least(icon_width);
|
||||
let icon_size = galley.size.y;
|
||||
let icon_spacing = icon_size / 5.0;
|
||||
let total_extra = vec2(icon_size + icon_spacing, 0.0);
|
||||
|
||||
let desired_size = total_extra + galley.size;
|
||||
let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click());
|
||||
let rect = rect.shrink2(padding);
|
||||
|
||||
response.widget_info(|| WidgetInfo::selected(WidgetType::Checkbox, *checked, &galley.text));
|
||||
|
||||
let visuals = ui.style().interact(&response);
|
||||
let label_on_the_left = ui.layout().horizontal_align() == Align::RIGHT;
|
||||
|
||||
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
|
||||
let icon_position_x = if label_on_the_left {
|
||||
rect.right() - icon_size / 2.0
|
||||
} else {
|
||||
rect.left() + icon_size / 2.0
|
||||
};
|
||||
let icon_position = pos2(icon_position_x, rect.center().y);
|
||||
let icon_rect = Rect::from_center_size(icon_position, vec2(icon_size, icon_size));
|
||||
|
||||
let painter = ui.painter();
|
||||
|
||||
painter.add(Shape::Circle {
|
||||
center: big_icon_rect.center(),
|
||||
radius: big_icon_rect.width() / 2.0 + visuals.expansion,
|
||||
center: icon_rect.center(),
|
||||
radius: icon_size * 0.5,
|
||||
fill: visuals.bg_fill,
|
||||
stroke: visuals.bg_stroke,
|
||||
});
|
||||
|
||||
if *checked {
|
||||
let fill = if *color == Color32::TRANSPARENT {
|
||||
ui.visuals().noninteractive().fg_stroke.color
|
||||
} else {
|
||||
*color
|
||||
};
|
||||
painter.add(Shape::Circle {
|
||||
center: small_icon_rect.center(),
|
||||
radius: small_icon_rect.width() * 0.8,
|
||||
fill: *color,
|
||||
center: icon_rect.center(),
|
||||
radius: icon_size * 0.4,
|
||||
fill,
|
||||
stroke: Default::default(),
|
||||
});
|
||||
}
|
||||
|
||||
let text_position = pos2(
|
||||
rect.left() + padding.x + icon_width + icon_spacing,
|
||||
rect.center().y - 0.5 * galley.size.y,
|
||||
);
|
||||
let text_position_x = if label_on_the_left {
|
||||
rect.right() - icon_size - icon_spacing - galley.size.x
|
||||
} else {
|
||||
rect.left() + icon_size + icon_spacing
|
||||
};
|
||||
|
||||
let text_position = pos2(text_position_x, rect.center().y - 0.5 * galley.size.y);
|
||||
painter.galley(text_position, galley, visuals.text_color());
|
||||
|
||||
self.checked ^= response.clicked_by(PointerButton::Primary);
|
||||
self.hovered = response.hovered();
|
||||
*checked ^= response.clicked_by(PointerButton::Primary);
|
||||
*hovered = response.hovered();
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct LegendWidget {
|
||||
rect: Rect,
|
||||
entries: BTreeMap<String, LegendEntry>,
|
||||
config: Legend,
|
||||
}
|
||||
|
||||
impl LegendWidget {
|
||||
/// Create a new legend from items, the names of items that are hidden and the style of the
|
||||
/// text. Returns `None` if the legend has no entries.
|
||||
pub(super) fn try_new(
|
||||
rect: Rect,
|
||||
config: Legend,
|
||||
items: &[Box<dyn PlotItem>],
|
||||
hidden_items: &HashSet<String>,
|
||||
) -> Option<Self> {
|
||||
// Collect the legend entries. If multiple items have the same name, they share a
|
||||
// checkbox. If their colors don't match, we pick a neutral color for the checkbox.
|
||||
let mut entries: BTreeMap<String, LegendEntry> = BTreeMap::new();
|
||||
items
|
||||
.iter()
|
||||
.filter(|item| !item.name().is_empty())
|
||||
.for_each(|item| {
|
||||
entries
|
||||
.entry(item.name().to_string())
|
||||
.and_modify(|entry| {
|
||||
if entry.color != item.color() {
|
||||
// Multiple items with different colors
|
||||
entry.color = Color32::TRANSPARENT;
|
||||
}
|
||||
})
|
||||
.or_insert_with(|| {
|
||||
let color = item.color();
|
||||
let checked = !hidden_items.contains(item.name());
|
||||
LegendEntry::new(color, checked)
|
||||
});
|
||||
});
|
||||
(!entries.is_empty()).then(|| Self {
|
||||
rect,
|
||||
entries,
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
// Get the names of the hidden items.
|
||||
pub fn get_hidden_items(&self) -> HashSet<String> {
|
||||
self.entries
|
||||
.iter()
|
||||
.filter(|(_, entry)| !entry.checked)
|
||||
.map(|(name, _)| name.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Get the name of the hovered items.
|
||||
pub fn get_hovered_entry_name(&self) -> Option<String> {
|
||||
self.entries
|
||||
.iter()
|
||||
.find(|(_, entry)| entry.hovered)
|
||||
.map(|(name, _)| name.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &mut LegendWidget {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let LegendWidget {
|
||||
rect,
|
||||
entries,
|
||||
config,
|
||||
} = self;
|
||||
|
||||
let main_dir = match config.position {
|
||||
Corner::LeftTop | Corner::RightTop => Direction::TopDown,
|
||||
Corner::LeftBottom | Corner::RightBottom => Direction::BottomUp,
|
||||
};
|
||||
let cross_align = match config.position {
|
||||
Corner::LeftTop | Corner::LeftBottom => Align::LEFT,
|
||||
Corner::RightTop | Corner::RightBottom => Align::RIGHT,
|
||||
};
|
||||
let layout = Layout::from_main_dir_and_cross_align(main_dir, cross_align);
|
||||
let legend_pad = 2.0;
|
||||
let legend_rect = rect.shrink(legend_pad);
|
||||
let mut legend_ui = ui.child_ui(legend_rect, layout);
|
||||
legend_ui
|
||||
.scope(|ui| {
|
||||
ui.style_mut().body_text_style = config.text_style;
|
||||
entries
|
||||
.iter_mut()
|
||||
.map(|(name, entry)| entry.ui(ui, name.clone()))
|
||||
.reduce(|r1, r2| r1.union(r2))
|
||||
.unwrap()
|
||||
})
|
||||
.inner
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@ mod items;
|
|||
mod legend;
|
||||
mod transform;
|
||||
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use items::PlotItem;
|
||||
pub use items::{HLine, VLine};
|
||||
pub use items::{Line, MarkerShape, Points, Value, Values};
|
||||
use legend::LegendEntry;
|
||||
use legend::LegendWidget;
|
||||
pub use legend::{Corner, Legend};
|
||||
use transform::{Bounds, ScreenTransform};
|
||||
|
||||
use crate::*;
|
||||
|
|
@ -23,6 +24,7 @@ use color::Hsva;
|
|||
struct PlotMemory {
|
||||
bounds: Bounds,
|
||||
auto_bounds: bool,
|
||||
hovered_entry: Option<String>,
|
||||
hidden_items: HashSet<String>,
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +69,7 @@ pub struct Plot {
|
|||
|
||||
show_x: bool,
|
||||
show_y: bool,
|
||||
show_legend: bool,
|
||||
legend_config: Option<Legend>,
|
||||
}
|
||||
|
||||
impl Plot {
|
||||
|
|
@ -96,7 +98,7 @@ impl Plot {
|
|||
|
||||
show_x: true,
|
||||
show_y: true,
|
||||
show_legend: true,
|
||||
legend_config: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -262,9 +264,16 @@ impl Plot {
|
|||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Use `Plot::legend` instead"]
|
||||
/// Whether to show a legend including all named items. Default: `true`.
|
||||
pub fn show_legend(mut self, show: bool) -> Self {
|
||||
self.show_legend = show;
|
||||
self.legend_config = show.then(Legend::default);
|
||||
self
|
||||
}
|
||||
|
||||
/// Show a legend including all named items.
|
||||
pub fn legend(mut self, legend: Legend) -> Self {
|
||||
self.legend_config = Some(legend);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -290,7 +299,7 @@ impl Widget for Plot {
|
|||
view_aspect,
|
||||
mut show_x,
|
||||
mut show_y,
|
||||
show_legend,
|
||||
legend_config,
|
||||
} = self;
|
||||
|
||||
let plot_id = ui.make_persistent_id(name);
|
||||
|
|
@ -300,6 +309,7 @@ impl Widget for Plot {
|
|||
.get_mut_or_insert_with(plot_id, || PlotMemory {
|
||||
bounds: min_auto_bounds,
|
||||
auto_bounds: !min_auto_bounds.is_valid(),
|
||||
hovered_entry: None,
|
||||
hidden_items: HashSet::new(),
|
||||
})
|
||||
.clone();
|
||||
|
|
@ -307,6 +317,7 @@ impl Widget for Plot {
|
|||
let PlotMemory {
|
||||
mut bounds,
|
||||
mut auto_bounds,
|
||||
mut hovered_entry,
|
||||
mut hidden_items,
|
||||
} = memory;
|
||||
|
||||
|
|
@ -345,65 +356,25 @@ impl Widget for Plot {
|
|||
stroke: ui.visuals().window_stroke(),
|
||||
});
|
||||
|
||||
// --- Legend ---
|
||||
|
||||
if show_legend {
|
||||
// Collect the legend entries. If multiple items have the same name, they share a
|
||||
// checkbox. If their colors don't match, we pick a neutral color for the checkbox.
|
||||
let mut legend_entries: BTreeMap<String, LegendEntry> = BTreeMap::new();
|
||||
let neutral_color = ui.visuals().noninteractive().fg_stroke.color;
|
||||
items
|
||||
.iter()
|
||||
.filter(|item| !item.name().is_empty())
|
||||
.for_each(|item| {
|
||||
let checked = !hidden_items.contains(item.name());
|
||||
let text = item.name();
|
||||
legend_entries
|
||||
.entry(item.name().to_string())
|
||||
.and_modify(|entry| {
|
||||
if entry.color != item.color() {
|
||||
entry.color = neutral_color
|
||||
}
|
||||
})
|
||||
.or_insert_with(|| {
|
||||
LegendEntry::new(text.to_string(), item.color(), checked)
|
||||
});
|
||||
});
|
||||
|
||||
// Show the legend.
|
||||
let mut legend_ui = ui.child_ui(rect, Layout::top_down(Align::LEFT));
|
||||
legend_entries.values_mut().for_each(|entry| {
|
||||
let response = legend_ui.add(entry);
|
||||
if response.hovered() {
|
||||
show_x = false;
|
||||
show_y = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Get the names of the hidden items.
|
||||
hidden_items = legend_entries
|
||||
.values()
|
||||
.filter(|entry| !entry.checked)
|
||||
.map(|entry| entry.text.clone())
|
||||
.collect();
|
||||
|
||||
// Highlight the hovered items.
|
||||
legend_entries
|
||||
.values()
|
||||
.filter(|entry| entry.hovered)
|
||||
.for_each(|entry| {
|
||||
items.iter_mut().for_each(|item| {
|
||||
if item.name() == entry.text {
|
||||
item.highlight();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Remove deselected items.
|
||||
items.retain(|item| !hidden_items.contains(item.name()));
|
||||
// Legend
|
||||
let legend = legend_config
|
||||
.and_then(|config| LegendWidget::try_new(rect, config, &items, &hidden_items));
|
||||
// Don't show hover cursor when hovering over legend.
|
||||
if hovered_entry.is_some() {
|
||||
show_x = false;
|
||||
show_y = false;
|
||||
}
|
||||
|
||||
// ---
|
||||
// Remove the deselected items.
|
||||
items.retain(|item| !hidden_items.contains(item.name()));
|
||||
// Highlight the hovered items.
|
||||
if let Some(hovered_name) = &hovered_entry {
|
||||
items
|
||||
.iter_mut()
|
||||
.filter(|entry| entry.name() == hovered_name)
|
||||
.for_each(|entry| entry.highlight());
|
||||
}
|
||||
// Move highlighted items to front.
|
||||
items.sort_by_key(|item| item.highlighted());
|
||||
|
||||
auto_bounds |= response.double_clicked_by(PointerButton::Primary);
|
||||
|
||||
|
|
@ -482,11 +453,18 @@ impl Widget for Plot {
|
|||
};
|
||||
prepared.ui(ui, &response);
|
||||
|
||||
if let Some(mut legend) = legend {
|
||||
ui.add(&mut legend);
|
||||
hidden_items = legend.get_hidden_items();
|
||||
hovered_entry = legend.get_hovered_entry_name();
|
||||
}
|
||||
|
||||
ui.memory().id_data.insert(
|
||||
plot_id,
|
||||
PlotMemory {
|
||||
bounds,
|
||||
auto_bounds,
|
||||
hovered_entry,
|
||||
hidden_items,
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use egui::plot::{Line, MarkerShape, Plot, Points, Value, Values};
|
||||
use egui::*;
|
||||
use plot::{Corner, Legend, Line, MarkerShape, Plot, Points, Value, Values};
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
|
|
@ -9,7 +9,6 @@ struct LineDemo {
|
|||
circle_radius: f64,
|
||||
circle_center: Pos2,
|
||||
square: bool,
|
||||
legend: bool,
|
||||
proportional: bool,
|
||||
}
|
||||
|
||||
|
|
@ -21,7 +20,6 @@ impl Default for LineDemo {
|
|||
circle_radius: 1.5,
|
||||
circle_center: Pos2::new(0.0, 0.0),
|
||||
square: false,
|
||||
legend: true,
|
||||
proportional: true,
|
||||
}
|
||||
}
|
||||
|
|
@ -35,7 +33,6 @@ impl LineDemo {
|
|||
circle_radius,
|
||||
circle_center,
|
||||
square,
|
||||
legend,
|
||||
proportional,
|
||||
..
|
||||
} = self;
|
||||
|
|
@ -69,7 +66,6 @@ impl LineDemo {
|
|||
ui.style_mut().wrap = Some(false);
|
||||
ui.checkbox(animate, "animate");
|
||||
ui.checkbox(square, "square view");
|
||||
ui.checkbox(legend, "legend");
|
||||
ui.checkbox(proportional, "proportional data axes");
|
||||
});
|
||||
});
|
||||
|
|
@ -124,7 +120,7 @@ impl Widget for &mut LineDemo {
|
|||
.line(self.circle())
|
||||
.line(self.sin())
|
||||
.line(self.thingy())
|
||||
.show_legend(self.legend);
|
||||
.legend(Legend::default());
|
||||
if self.square {
|
||||
plot = plot.view_aspect(1.0);
|
||||
}
|
||||
|
|
@ -200,7 +196,9 @@ impl Widget for &mut MarkerDemo {
|
|||
}
|
||||
});
|
||||
|
||||
let mut markers_plot = Plot::new("Markers Demo").data_aspect(1.0);
|
||||
let mut markers_plot = Plot::new("Markers Demo")
|
||||
.data_aspect(1.0)
|
||||
.legend(Legend::default());
|
||||
for marker in self.markers() {
|
||||
markers_plot = markers_plot.points(marker);
|
||||
}
|
||||
|
|
@ -208,10 +206,75 @@ impl Widget for &mut MarkerDemo {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct LegendDemo {
|
||||
config: Legend,
|
||||
}
|
||||
|
||||
impl Default for LegendDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
config: Legend::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LegendDemo {
|
||||
fn line_with_slope(slope: f64) -> Line {
|
||||
Line::new(Values::from_explicit_callback(
|
||||
move |x| slope * x,
|
||||
f64::NEG_INFINITY..=f64::INFINITY,
|
||||
100,
|
||||
))
|
||||
}
|
||||
fn sin() -> Line {
|
||||
Line::new(Values::from_explicit_callback(
|
||||
move |x| x.sin(),
|
||||
f64::NEG_INFINITY..=f64::INFINITY,
|
||||
100,
|
||||
))
|
||||
}
|
||||
fn cos() -> Line {
|
||||
Line::new(Values::from_explicit_callback(
|
||||
move |x| x.cos(),
|
||||
f64::NEG_INFINITY..=f64::INFINITY,
|
||||
100,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &mut LegendDemo {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let LegendDemo { config } = self;
|
||||
|
||||
ui.label("Text Style:");
|
||||
ui.horizontal(|ui| {
|
||||
TextStyle::all().for_each(|style| {
|
||||
ui.selectable_value(&mut config.text_style, style, format!("{:?}", style));
|
||||
});
|
||||
});
|
||||
ui.label("Position:");
|
||||
ui.horizontal(|ui| {
|
||||
Corner::all().for_each(|position| {
|
||||
ui.selectable_value(&mut config.position, position, format!("{:?}", position));
|
||||
});
|
||||
});
|
||||
let legend_plot = Plot::new("Legend Demo")
|
||||
.line(LegendDemo::line_with_slope(0.5).name("lines"))
|
||||
.line(LegendDemo::line_with_slope(1.0).name("lines"))
|
||||
.line(LegendDemo::line_with_slope(2.0).name("lines"))
|
||||
.line(LegendDemo::sin().name("sin(x)"))
|
||||
.line(LegendDemo::cos().name("cos(x)"))
|
||||
.legend(*config)
|
||||
.data_aspect(1.0);
|
||||
ui.add(legend_plot)
|
||||
}
|
||||
}
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum Panel {
|
||||
Lines,
|
||||
Markers,
|
||||
Legend,
|
||||
}
|
||||
|
||||
impl Default for Panel {
|
||||
|
|
@ -224,6 +287,7 @@ impl Default for Panel {
|
|||
pub struct PlotDemo {
|
||||
line_demo: LineDemo,
|
||||
marker_demo: MarkerDemo,
|
||||
legend_demo: LegendDemo,
|
||||
open_panel: Panel,
|
||||
}
|
||||
|
||||
|
|
@ -261,6 +325,7 @@ impl super::View for PlotDemo {
|
|||
ui.horizontal(|ui| {
|
||||
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::Legend, "Legend");
|
||||
});
|
||||
ui.separator();
|
||||
|
||||
|
|
@ -271,6 +336,9 @@ impl super::View for PlotDemo {
|
|||
Panel::Markers => {
|
||||
ui.add(&mut self.marker_demo);
|
||||
}
|
||||
Panel::Legend => {
|
||||
ui.add(&mut self.legend_demo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue