Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plot: Legend improvements #410

Merged
merged 63 commits into from
Jun 7, 2021
Merged
Changes from 1 commit
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
2ced76e
initial work on markers
EmbersArc May 9, 2021
0d09622
clippy fix
EmbersArc May 9, 2021
10ffbfc
simplify marker
EmbersArc May 11, 2021
edd7e17
Merge branch 'master' into plot-markers
EmbersArc May 11, 2021
4ad97f3
use option for color
EmbersArc May 11, 2021
5fb8820
prepare for more demo plots
EmbersArc May 11, 2021
ee5fd49
more improvements for markers
EmbersArc May 11, 2021
99cba8a
some small adjustments
EmbersArc May 11, 2021
10e56e0
better highlighting
EmbersArc May 14, 2021
75c5c1a
don't draw transparent lines
EmbersArc May 14, 2021
90622a5
use transparent color instead of option
EmbersArc May 14, 2021
729ce17
don't brighten curves when highlighting
EmbersArc May 14, 2021
c5945f2
Merge branch 'master' into plot-markers
EmbersArc May 16, 2021
7ccc02c
Initial changes to lengend:
EmbersArc May 16, 2021
15820d7
draw legend on top of curves
EmbersArc May 16, 2021
1d7b252
update changelog
EmbersArc May 16, 2021
c5a545b
Merge branch 'plot-markers' into legend-improvements
EmbersArc May 16, 2021
eafff6a
fix legend checkboxes
EmbersArc May 16, 2021
3cb3a14
simplify legend
EmbersArc May 16, 2021
4d4f579
remove unnecessary derives
EmbersArc May 17, 2021
ef6dc6e
remove config from legend entries
EmbersArc May 17, 2021
e62a816
avoid allocations and use line_segment
EmbersArc May 21, 2021
a4a3793
compare against transparent color
EmbersArc May 21, 2021
e4a7e56
Merge remote-tracking branch 'upstream/master' into plot-markers
EmbersArc May 21, 2021
ae00424
create new Points primitive
EmbersArc May 24, 2021
773be05
fix doctest
EmbersArc May 24, 2021
6831302
some cleanup and fix hover
EmbersArc May 24, 2021
4ee57fd
common interface for lines and points
EmbersArc May 24, 2021
4c375f0
clippy fixes
EmbersArc May 24, 2021
95bfbbf
reduce visibilities
EmbersArc May 24, 2021
0bc8abc
Merge branch 'plot-markers' into legend-improvements
EmbersArc May 25, 2021
7867c47
update legend
EmbersArc May 25, 2021
bffaa70
clippy fix
EmbersArc May 25, 2021
9f99826
change instances of "curve" to "item"
EmbersArc May 25, 2021
30b5073
change visibility
EmbersArc May 25, 2021
88fae19
Update egui/src/widgets/plot/mod.rs
EmbersArc May 26, 2021
8bc576b
Update egui/src/widgets/plot/mod.rs
EmbersArc May 26, 2021
c6423be
Update egui_demo_lib/src/apps/demo/plot_demo.rs
EmbersArc May 26, 2021
3aeb354
Update egui_demo_lib/src/apps/demo/plot_demo.rs
EmbersArc May 26, 2021
e76a679
changes based on review
EmbersArc May 26, 2021
8576e2a
Merge remote-tracking branch 'upstream/master' into plot-markers
EmbersArc May 26, 2021
887190e
Merge branch 'plot-markers' into legend-improvements
EmbersArc May 26, 2021
af47250
add legend to demo
EmbersArc May 26, 2021
43b4539
fix test
EmbersArc May 26, 2021
f033653
Merge branch 'plot-markers' into legend-improvements
EmbersArc May 26, 2021
a40efbf
move highlighted items to front
EmbersArc May 26, 2021
c541952
dynamic plot size
EmbersArc May 26, 2021
36ae1af
Merge branch 'plot-markers' into legend-improvements
EmbersArc May 26, 2021
5f29404
add legend again
EmbersArc May 27, 2021
4197bd1
remove height
EmbersArc May 27, 2021
8c9d1a0
clippy fix
EmbersArc May 27, 2021
090bbeb
Merge branch 'plot-markers' into legend-improvements
EmbersArc May 27, 2021
04ea585
Merge remote-tracking branch 'upstream/master' into legend-improvements
EmbersArc May 27, 2021
7653add
update changelog
EmbersArc May 27, 2021
96f175a
minor changes
EmbersArc May 28, 2021
eac5bf5
Update egui/src/widgets/plot/legend.rs
EmbersArc Jun 3, 2021
109a9b2
Update egui/src/widgets/plot/legend.rs
EmbersArc Jun 3, 2021
834f784
Update egui/src/widgets/plot/legend.rs
EmbersArc Jun 3, 2021
3845020
changes based on review
EmbersArc Jun 3, 2021
25bc76f
add functions to mutate legend config
EmbersArc Jun 5, 2021
490ba0a
Merge remote-tracking branch 'upstream/master' into legend-improvements
EmbersArc Jun 5, 2021
d27e0f4
Merge remote-tracking branch 'upstream/master' into legend-improvements
EmbersArc Jun 7, 2021
1fcf5c0
use horizontal_align
EmbersArc Jun 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Initial changes to lengend:
* Font options
* Position options
* Internal cleanup
EmbersArc committed May 16, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 7ccc02cd2f1cc63a36fcdfa6e2fcb8904379a9ba
2 changes: 1 addition & 1 deletion egui/src/widgets/plot/items.rs
Original file line number Diff line number Diff line change
@@ -538,7 +538,7 @@ impl Curve {
/// Name of this curve.
///
/// If a curve is given a name it will show up in the plot legend
/// (if legends are turned on).
/// (if legends are turned on). Multiple curves may share the same name.
#[allow(clippy::needless_pass_by_value)]
pub fn name(mut self, name: impl ToString) -> Self {
self.name = name.to_string();
207 changes: 171 additions & 36 deletions egui/src/widgets/plot/legend.rs
Original file line number Diff line number Diff line change
@@ -1,81 +1,216 @@
use std::string::String;
use std::{
collections::{BTreeMap, HashSet},
string::String,
};

use super::Curve;
use crate::*;

pub(crate) struct LegendEntry {
pub text: String,
pub color: Color32,
pub checked: bool,
pub hovered: bool,
/// Where to place the plot legend.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LegendPosition {
TopLeft,
TopRight,
BottomLeft,
BottomRight,
}

impl LegendPosition {
pub fn all() -> impl Iterator<Item = LegendPosition> {
[
LegendPosition::TopLeft,
LegendPosition::TopRight,
LegendPosition::BottomLeft,
LegendPosition::BottomRight,
]
.iter()
.copied()
}
}

/// The configuration for a plot legend.
#[derive(Clone, Copy, PartialEq)]
pub struct Legend {
pub text_style: TextStyle,
pub position: LegendPosition,
}

impl Default for Legend {
fn default() -> Self {
Self {
text_style: TextStyle::Body,
position: LegendPosition::TopRight,
}
}
}

struct LegendEntry {
config: Legend,
color: Option<Color32>,
checked: bool,
hovered: bool,
}

impl LegendEntry {
pub fn new(text: String, color: Color32, checked: bool) -> Self {
fn new(config: Legend, color: Option<Color32>, checked: bool) -> Self {
Self {
text,
config,
color,
checked,
hovered: false,
}
}
}

impl Widget for &mut LegendEntry {
impl Widget for (&String, &mut LegendEntry) {
fn ui(self, ui: &mut Ui) -> Response {
let LegendEntry {
checked,
let (
text,
color,
..
} = 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;
LegendEntry {
config,
color,
checked,
hovered,
},
) = self;

let text_style = TextStyle::Button;
let galley = ui.fonts().layout_no_wrap(text_style, text.clone());
let galley = ui.fonts().layout_no_wrap(config.text_style, text.clone());

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 (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
let icon_position_x = match config.position {
LegendPosition::BottomLeft | LegendPosition::TopLeft => rect.left() + icon_size / 2.0,
LegendPosition::BottomRight | LegendPosition::TopRight => {
rect.right() - 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 neutral_color = ui.visuals().noninteractive().fg_stroke.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: color.unwrap_or(neutral_color),
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 = match config.position {
LegendPosition::BottomLeft | LegendPosition::TopLeft => {
rect.left() + icon_size + icon_spacing
}
LegendPosition::BottomRight | LegendPosition::TopRight => {
rect.right() - icon_size - icon_spacing - galley.size.x
}
};
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
}
}

pub(crate) struct LegendWidget {
rect: Rect,
entries: BTreeMap<String, LegendEntry>,
config: Legend,
}

impl LegendWidget {
/// Create a new legend from curves, the names of curves that are hidden and the style of the
/// text. Returns `None` if the legend has no entries.
pub fn try_new(
rect: Rect,
config: Legend,
curves: &[Curve],
hidden_curves: &HashSet<String>,
) -> Option<Self> {
// Collect the legend entries. If multiple curves 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();
curves
.iter()
.filter(|curve| !curve.name.is_empty())
.for_each(|curve| {
entries
.entry(curve.name.clone())
.and_modify(|entry| {
if entry.color != curve.get_color() {
entry.color = None
}
})
.or_insert_with(|| {
let color = curve.get_color();
let checked = !hidden_curves.contains(&curve.name);
LegendEntry::new(config, color, checked)
});
});
(!entries.is_empty()).then(|| Self {
rect,
entries,
config,
})
}

// Get the names of the hidden curves.
pub fn get_hidden_curves(&self) -> HashSet<String> {
self.entries
.iter()
.filter(|(_, entry)| !entry.checked)
.map(|(name, _)| name.clone())
.collect()
}

// Get the name of the hovered curve.
pub fn get_hovered_entry_name(&self) -> Option<&str> {
self.entries
.iter()
.find(|(_, entry)| entry.hovered)
.map(|(name, _)| name.as_str())
}
}

impl Widget for &mut LegendWidget {
fn ui(self, ui: &mut Ui) -> Response {
let main_dir = match self.config.position {
LegendPosition::TopLeft | LegendPosition::TopRight => Direction::TopDown,
LegendPosition::BottomLeft | LegendPosition::BottomRight => Direction::BottomUp,
};
let cross_align = match self.config.position {
LegendPosition::TopLeft | LegendPosition::BottomLeft => Align::LEFT,
LegendPosition::TopRight | LegendPosition::BottomRight => Align::RIGHT,
};
let layout = Layout::from_main_dir_and_cross_align(main_dir, cross_align);
let legend_pad = 2.0;
let legend_rect = self.rect.shrink(legend_pad);
let mut legend_ui = ui.child_ui(legend_rect, layout);
self.entries
.iter_mut()
.map(|entry| legend_ui.add(entry))
.reduce(|r1, r2| r1.union(r2))
.unwrap()
}
}
Loading