Skip to content

Commit 9ebfc9c

Browse files
authored
Plot items now require an id (#82)
1 parent 99303b4 commit 9ebfc9c

File tree

9 files changed

+413
-823
lines changed

9 files changed

+413
-823
lines changed

demo/src/plot_demo.rs

+157-139
Large diffs are not rendered by default.

egui_plot/src/items/mod.rs

+198-606
Large diffs are not rendered by default.

egui_plot/src/legend.rs

+30-41
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub struct Legend {
5151
color_conflict_handling: ColorConflictHandling,
5252

5353
/// Used for overriding the `hidden_items` set in [`LegendWidget`].
54-
hidden_items: Option<ahash::HashSet<String>>,
54+
hidden_items: Option<ahash::HashSet<Id>>,
5555
}
5656

5757
impl Default for Legend {
@@ -94,7 +94,7 @@ impl Legend {
9494
#[inline]
9595
pub fn hidden_items<I>(mut self, hidden_items: I) -> Self
9696
where
97-
I: IntoIterator<Item = String>,
97+
I: IntoIterator<Item = Id>,
9898
{
9999
self.hidden_items = Some(hidden_items.into_iter().collect());
100100
self
@@ -123,33 +123,36 @@ impl Legend {
123123

124124
#[derive(Clone)]
125125
struct LegendEntry {
126-
item_id: Option<Id>,
126+
id: Id,
127+
name: String,
127128
color: Color32,
128129
checked: bool,
129130
hovered: bool,
130131
}
131132

132133
impl LegendEntry {
133-
fn new(item_id: Option<Id>, color: Color32, checked: bool) -> Self {
134+
fn new(id: Id, name: String, color: Color32, checked: bool) -> Self {
134135
Self {
135-
item_id,
136+
id,
137+
name,
136138
color,
137139
checked,
138140
hovered: false,
139141
}
140142
}
141143

142-
fn ui(&self, ui: &mut Ui, text: String, text_style: &TextStyle) -> Response {
144+
fn ui(&self, ui: &mut Ui, text_style: &TextStyle) -> Response {
143145
let Self {
144-
item_id: _,
146+
id: _,
147+
name,
145148
color,
146149
checked,
147150
hovered: _,
148151
} = self;
149152

150153
let font_id = text_style.resolve(ui.style());
151154

152-
let galley = ui.fonts(|f| f.layout_delayed_color(text, font_id, f32::INFINITY));
155+
let galley = ui.fonts(|f| f.layout_delayed_color(name.clone(), font_id, f32::INFINITY));
153156

154157
let icon_size = galley.size().y;
155158
let icon_spacing = icon_size / 5.0;
@@ -216,36 +219,26 @@ impl LegendEntry {
216219
#[derive(Clone)]
217220
pub(super) struct LegendWidget {
218221
rect: Rect,
219-
entries: Vec<(String, LegendEntry)>,
222+
entries: Vec<LegendEntry>,
220223
config: Legend,
221224
}
222225

223-
/// A reference to a legend item.
224-
///
225-
/// Since item ids are optional, we need to keep the name as well, using it for identification if needed.
226-
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
227-
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
228-
pub struct LegendItemReference {
229-
pub name: String,
230-
pub item_id: Option<Id>,
231-
}
232-
233226
impl LegendWidget {
234227
/// Create a new legend from items, the names of items that are hidden and the style of the
235228
/// text. Returns `None` if the legend has no entries.
236229
pub(super) fn try_new<'a>(
237230
rect: Rect,
238231
config: Legend,
239232
items: &[Box<dyn PlotItem + 'a>],
240-
hidden_items: &ahash::HashSet<String>, // Existing hidden items in the plot memory.
233+
hidden_items: &ahash::HashSet<Id>, // Existing hidden items in the plot memory.
241234
) -> Option<Self> {
242235
// If `config.hidden_items` is not `None`, it is used.
243236
let hidden_items = config.hidden_items.as_ref().unwrap_or(hidden_items);
244237

245238
// Collect the legend entries. If multiple items have the same name, they share a
246239
// checkbox. If their colors don't match, we pick a neutral color for the checkbox.
247240
let mut keys: BTreeMap<String, usize> = BTreeMap::new();
248-
let mut entries: BTreeMap<(usize, String), LegendEntry> = BTreeMap::new();
241+
let mut entries: BTreeMap<(usize, &str), LegendEntry> = BTreeMap::new();
249242
items
250243
.iter()
251244
.filter(|item| !item.name().is_empty())
@@ -257,8 +250,9 @@ impl LegendWidget {
257250
// Use the same key if we don't want insertion order
258251
0
259252
};
253+
260254
entries
261-
.entry((key, item.name().to_owned()))
255+
.entry((key, item.name()))
262256
.and_modify(|entry| {
263257
if entry.color != item.color() {
264258
match config.color_conflict_handling {
@@ -273,35 +267,30 @@ impl LegendWidget {
273267
})
274268
.or_insert_with(|| {
275269
let color = item.color();
276-
let checked = !hidden_items.contains(item.name());
277-
LegendEntry::new(item.id(), color, checked)
270+
let checked = !hidden_items.contains(&item.id());
271+
LegendEntry::new(item.id(), item.name().to_owned(), color, checked)
278272
});
279273
});
280274
(!entries.is_empty()).then_some(Self {
281275
rect,
282-
entries: entries.into_iter().map(|((_, k), v)| (k, v)).collect(),
276+
entries: entries.into_values().collect(),
283277
config,
284278
})
285279
}
286280

287281
// Get the names of the hidden items.
288-
pub fn hidden_items(&self) -> ahash::HashSet<String> {
282+
pub fn hidden_items(&self) -> ahash::HashSet<Id> {
289283
self.entries
290284
.iter()
291-
.filter(|(_, entry)| !entry.checked)
292-
.map(|(name, _)| name.clone())
285+
.filter_map(|entry| (!entry.checked).then_some(entry.id))
293286
.collect()
294287
}
295288

296289
// Get the name of the hovered items.
297-
pub fn hovered_item(&self) -> Option<LegendItemReference> {
290+
pub fn hovered_item(&self) -> Option<Id> {
298291
self.entries
299292
.iter()
300-
.find(|(_, entry)| entry.hovered)
301-
.map(|(name, entry)| LegendItemReference {
302-
name: name.to_string(),
303-
item_id: entry.item_id,
304-
})
293+
.find_map(|entry| entry.hovered.then_some(entry.id))
305294
}
306295
}
307296

@@ -343,14 +332,14 @@ impl Widget for &mut LegendWidget {
343332

344333
let response_union = entries
345334
.iter_mut()
346-
.map(|(name, entry)| {
347-
let response = entry.ui(ui, name.clone(), &config.text_style);
335+
.map(|entry| {
336+
let response = entry.ui(ui, &config.text_style);
348337

349338
// Handle interactions. Alt-clicking must be deferred to end of loop
350339
// since it may affect all entries.
351340
handle_interaction_on_legend_item(&response, entry);
352341
if response.clicked() && ui.input(|r| r.modifiers.alt) {
353-
focus_on_item = Some(name.clone());
342+
focus_on_item = Some(entry.id);
354343
}
355344

356345
response
@@ -377,14 +366,14 @@ fn handle_interaction_on_legend_item(response: &Response, entry: &mut LegendEntr
377366
}
378367

379368
/// Handle alt-click interaction (which may affect all entries).
380-
fn handle_focus_on_legend_item(clicked_entry_name: &str, entries: &mut [(String, LegendEntry)]) {
369+
fn handle_focus_on_legend_item(clicked_entry: &Id, entries: &mut [LegendEntry]) {
381370
// if all other items are already hidden, we show everything
382371
let is_focus_item_only_visible = entries
383372
.iter()
384-
.all(|(name, entry)| !entry.checked || (clicked_entry_name == name));
373+
.all(|entry| !entry.checked || (clicked_entry == &entry.id));
385374

386375
// either show everything or show only the focus item
387-
for (name, entry) in entries.iter_mut() {
388-
entry.checked = is_focus_item_only_visible || clicked_entry_name == name;
376+
for entry in entries.iter_mut() {
377+
entry.checked = is_focus_item_only_visible || clicked_entry == &entry.id;
389378
}
390379
}

egui_plot/src/lib.rs

+12-24
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub use crate::{
3939

4040
use axis::AxisWidget;
4141
use items::{horizontal_line, rulers_color, vertical_line};
42-
use legend::{LegendItemReference, LegendWidget};
42+
use legend::LegendWidget;
4343

4444
type LabelFormatterFn<'a> = dyn Fn(&str, &PlotPoint) -> String + 'a;
4545
pub type LabelFormatter<'a> = Option<Box<LabelFormatterFn<'a>>>;
@@ -125,7 +125,7 @@ pub struct PlotResponse<R> {
125125

126126
/// The id of a currently hovered item if any.
127127
///
128-
/// This is `None` if either no item was hovered, or the hovered item didn't provide an id.
128+
/// This is `None` if either no item was hovered.
129129
/// A plot item can be hovered either by hovering its representation in the plot (line, marker, etc.)
130130
/// or by hovering the item in the legend.
131131
pub hovered_plot_item: Option<Id>,
@@ -145,7 +145,7 @@ pub struct PlotResponse<R> {
145145
/// let x = i as f64 * 0.01;
146146
/// [x, x.sin()]
147147
/// }).collect();
148-
/// let line = Line::new(sin);
148+
/// let line = Line::new("sin", sin);
149149
/// Plot::new("my_plot").view_aspect(2.0).show(ui, |plot_ui| plot_ui.line(line));
150150
/// # });
151151
/// ```
@@ -397,7 +397,7 @@ impl<'a> Plot<'a> {
397397
/// let x = i as f64 * 0.01;
398398
/// [x, x.sin()]
399399
/// }).collect();
400-
/// let line = Line::new(sin);
400+
/// let line = Line::new("sin", sin);
401401
/// Plot::new("my_plot").view_aspect(2.0)
402402
/// .label_formatter(|name, value| {
403403
/// if !name.is_empty() {
@@ -898,21 +898,13 @@ impl<'a> Plot<'a> {
898898
show_y = false;
899899
}
900900
// Remove the deselected items.
901-
items.retain(|item| !mem.hidden_items.contains(item.name()));
901+
items.retain(|item| !mem.hidden_items.contains(&item.id()));
902902
// Highlight the hovered items.
903-
if let Some(LegendItemReference { name, item_id }) = &mem.hovered_legend_item {
904-
// If available, identify by id, not name.
905-
if let Some(item_id) = item_id {
906-
items
907-
.iter_mut()
908-
.filter(|entry| entry.id() == Some(*item_id))
909-
.for_each(|entry| entry.highlight());
910-
} else {
911-
items
912-
.iter_mut()
913-
.filter(|entry| entry.name() == name)
914-
.for_each(|entry| entry.highlight());
915-
}
903+
if let Some(item_id) = &mem.hovered_legend_item {
904+
items
905+
.iter_mut()
906+
.filter(|entry| &entry.id() == item_id)
907+
.for_each(|entry| entry.highlight());
916908
}
917909
// Move highlighted items to front.
918910
items.sort_by_key(|item| item.highlighted());
@@ -1224,11 +1216,7 @@ impl<'a> Plot<'a> {
12241216
mem.hidden_items = legend.hidden_items();
12251217
mem.hovered_legend_item = legend.hovered_item();
12261218

1227-
if let Some(LegendItemReference {
1228-
name: _,
1229-
item_id: Some(item_id),
1230-
}) = &mem.hovered_legend_item
1231-
{
1219+
if let Some(item_id) = &mem.hovered_legend_item {
12321220
hovered_plot_item.get_or_insert(*item_id);
12331221
}
12341222
}
@@ -1728,7 +1716,7 @@ impl<'a> PreparedPlot<'a> {
17281716

17291717
let hovered_plot_item_id = if let Some((item, elem)) = closest {
17301718
item.on_hover(elem, shapes, &mut cursors, &plot, label_formatter);
1731-
item.id()
1719+
Some(item.id())
17321720
} else {
17331721
let value = transform.value_from_position(pointer);
17341722
items::rulers_at_value(

egui_plot/src/memory.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
22

33
use egui::{Context, Id, Pos2, Vec2b};
44

5-
use crate::{legend::LegendItemReference, PlotBounds, PlotTransform};
5+
use crate::{PlotBounds, PlotTransform};
66

77
/// Information about the plot that has to persist between frames.
88
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
@@ -15,12 +15,10 @@ pub struct PlotMemory {
1515
pub auto_bounds: Vec2b,
1616

1717
/// Hovered legend item if any.
18-
///
19-
/// Display string plus, if available, the id of the hovered item.
20-
pub hovered_legend_item: Option<LegendItemReference>,
18+
pub hovered_legend_item: Option<Id>,
2119

2220
/// Which items _not_ to show?
23-
pub hidden_items: ahash::HashSet<String>,
21+
pub hidden_items: ahash::HashSet<Id>,
2422

2523
/// The transform from last frame.
2624
pub(crate) transform: PlotTransform,

examples/borrow_points/src/main.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ impl eframe::App for MyApp {
3333
Plot::new("My Plot")
3434
.legend(Legend::default())
3535
.show(ui, |plot_ui| {
36-
plot_ui.line(Line::new(PlotPoints::Borrowed(&self.points)).name("curve"));
36+
plot_ui
37+
.line(Line::new("curve", PlotPoints::Borrowed(&self.points)).name("curve"));
3738
});
3839
});
3940
}

examples/custom_plot_manipulation/src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ impl eframe::App for PlotExample {
122122
}
123123

124124
let sine_points = PlotPoints::from_explicit_callback(|x| x.sin(), .., 5000);
125-
plot_ui.line(Line::new(sine_points).name("Sine"));
125+
plot_ui.line(Line::new("Sine", sine_points));
126126
});
127127
});
128128
}

examples/legend_sort/src/main.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,15 @@ impl eframe::App for MyApp {
4646
Plot::new("My Plot")
4747
.legend(Legend::default().follow_insertion_order(self.insert_order))
4848
.show(ui, |plot_ui| {
49-
plot_ui
50-
.line(Line::new(PlotPoints::from(self.graph3.clone())).name("3rd Curve"));
51-
plot_ui.line(Line::new(PlotPoints::from(self.graph.clone())).name("1st Curve"));
52-
plot_ui
53-
.line(Line::new(PlotPoints::from(self.graph2.clone())).name("2nd Curve"));
49+
plot_ui.line(Line::new(
50+
"3rd Curve",
51+
PlotPoints::from(self.graph3.clone()),
52+
));
53+
plot_ui.line(Line::new("1st Curve", PlotPoints::from(self.graph.clone())));
54+
plot_ui.line(Line::new(
55+
"2nd Curve",
56+
PlotPoints::from(self.graph2.clone()),
57+
));
5458
});
5559
// Remember the position of the plot
5660
});

examples/save_plot/src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ impl eframe::App for MyApp {
3434
// let's create a dummy line in the plot
3535
let graph: Vec<[f64; 2]> = vec![[0.0, 1.0], [2.0, 3.0], [3.0, 2.0]];
3636
let inner = my_plot.show(ui, |plot_ui| {
37-
plot_ui.line(Line::new(PlotPoints::from(graph)).name("curve"));
37+
plot_ui.line(Line::new("curve", PlotPoints::from(graph)));
3838
});
3939
// Remember the position of the plot
4040
plot_rect = Some(inner.response.rect);

0 commit comments

Comments
 (0)