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

Improve/Finish insertion_order #65

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1781,6 +1781,15 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"

[[package]]
name = "legend_sort"
version = "0.1.0"
dependencies = [
"eframe",
"egui_plot",
"env_logger",
]

[[package]]
name = "libc"
version = "0.2.167"
Expand Down
65 changes: 54 additions & 11 deletions egui_plot/src/legend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ impl Corner {
}
}

/// How to handle multiple conflicting color for a legend item.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ColorConflictHandling {
PickFirst,
PickLast,
RemoveColor,
}

/// The configuration for a plot legend.
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
Expand All @@ -38,6 +47,9 @@ pub struct Legend {
pub background_alpha: f32,
pub position: Corner,

follow_insertion_order: bool,
color_conflict_handling: ColorConflictHandling,

/// Used for overriding the `hidden_items` set in [`LegendWidget`].
hidden_items: Option<ahash::HashSet<String>>,
}
Expand All @@ -48,7 +60,8 @@ impl Default for Legend {
text_style: TextStyle::Body,
background_alpha: 0.75,
position: Corner::RightTop,

follow_insertion_order: false,
color_conflict_handling: ColorConflictHandling::RemoveColor,
hidden_items: None,
}
}
Expand Down Expand Up @@ -86,6 +99,25 @@ impl Legend {
self.hidden_items = Some(hidden_items.into_iter().collect());
self
}

/// Specifies if the legend item order should be the inserted order.
/// Default: `false`.
/// If `true`, the order of the legend items will be the same as the order as they were added.
#[inline]
pub fn follow_insertion_order(mut self, follow: bool) -> Self {
self.follow_insertion_order = follow;
self
}

/// Specifies how to handle conflicting colors for an item.
#[inline]
pub fn color_conflict_handling(
mut self,
color_conflict_handling: ColorConflictHandling,
) -> Self {
self.color_conflict_handling = color_conflict_handling;
self
}
}

#[derive(Clone)]
Expand Down Expand Up @@ -180,7 +212,7 @@ impl LegendEntry {
#[derive(Clone)]
pub(super) struct LegendWidget {
rect: Rect,
entries: BTreeMap<String, LegendEntry>,
entries: Vec<(String, LegendEntry)>,
config: Legend,
}

Expand All @@ -198,17 +230,31 @@ impl LegendWidget {

// 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();
let mut keys: BTreeMap<String, usize> = BTreeMap::new();
let mut entries: BTreeMap<(usize, String), LegendEntry> = BTreeMap::new();
items
.iter()
.filter(|item| !item.name().is_empty())
.for_each(|item| {
let next_entry = entries.len();
let key = if config.follow_insertion_order {
*keys.entry(item.name().to_owned()).or_insert(next_entry)
} else {
// Use the same key if we don't want insertion order
0
};
entries
.entry(item.name().to_owned())
.entry((key, item.name().to_owned()))
.and_modify(|entry| {
if entry.color != item.color() {
// Multiple items with different colors
entry.color = Color32::TRANSPARENT;
match config.color_conflict_handling {
ColorConflictHandling::PickFirst => (),
ColorConflictHandling::PickLast => entry.color = item.color(),
ColorConflictHandling::RemoveColor => {
// Multiple items with different colors
entry.color = Color32::TRANSPARENT;
}
}
}
})
.or_insert_with(|| {
Expand All @@ -219,7 +265,7 @@ impl LegendWidget {
});
(!entries.is_empty()).then_some(Self {
rect,
entries,
entries: entries.into_iter().map(|((_, k), v)| (k, v)).collect(),
config,
})
}
Expand Down Expand Up @@ -314,10 +360,7 @@ fn handle_interaction_on_legend_item(response: &Response, entry: &mut LegendEntr
}

/// Handle alt-click interaction (which may affect all entries).
fn handle_focus_on_legend_item(
clicked_entry_name: &str,
entries: &mut BTreeMap<String, LegendEntry>,
) {
fn handle_focus_on_legend_item(clicked_entry_name: &str, entries: &mut [(String, LegendEntry)]) {
// if all other items are already hidden, we show everything
let is_focus_item_only_visible = entries
.iter()
Expand Down
2 changes: 1 addition & 1 deletion egui_plot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub use crate::{
MarkerShape, Orientation, PlotConfig, PlotGeometry, PlotImage, PlotItem, PlotPoint,
PlotPoints, Points, Polygon, Text, VLine,
},
legend::{Corner, Legend},
legend::{ColorConflictHandling, Corner, Legend},
memory::PlotMemory,
plot_ui::PlotUi,
transform::{PlotBounds, PlotTransform},
Expand Down
19 changes: 19 additions & 0 deletions examples/legend_sort/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "legend_sort"
version = "0.1.0"
authors = ["hacknus <[email protected]>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.80"
publish = false

[lints]
workspace = true

[dependencies]
eframe = { workspace = true, features = ["default"] }
egui_plot.workspace = true
env_logger = { workspace = true, default-features = false, features = [
"auto-color",
"humantime",
] }
5 changes: 5 additions & 0 deletions examples/legend_sort/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This example shows how to sort the legend entries.

```sh
cargo run -p legend_sort
```
58 changes: 58 additions & 0 deletions examples/legend_sort/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
#![allow(rustdoc::missing_crate_level_docs)] // it's an example

use eframe::egui;
use egui_plot::{Legend, Line, Plot, PlotPoints};

fn main() -> eframe::Result {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).

let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([350.0, 200.0]),
..Default::default()
};
let graph: Vec<[f64; 2]> = vec![[0.0, 1.0], [2.0, 3.0], [3.0, 2.0]];
let graph2: Vec<[f64; 2]> = vec![[0.0, 2.0], [2.0, 4.0], [3.0, 3.0]];
let graph3: Vec<[f64; 2]> = vec![[0.0, 3.0], [2.0, 5.0], [3.0, 4.0]];

eframe::run_native(
"My egui App with a plot",
options,
Box::new(|_cc| {
Ok(Box::new(MyApp {
insert_order: false,
graph,
graph2,
graph3,
}))
}),
)
}

#[derive(Default)]
struct MyApp {
insert_order: bool,
graph: Vec<[f64; 2]>,
graph2: Vec<[f64; 2]>,
graph3: Vec<[f64; 2]>,
}

impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.label("If checked the legend will follow the order as the curves are inserted");
ui.checkbox(&mut self.insert_order, "Insert order");

Plot::new("My Plot")
.legend(Legend::default().follow_insertion_order(self.insert_order))
.show(ui, |plot_ui| {
plot_ui
.line(Line::new(PlotPoints::from(self.graph3.clone())).name("3rd Curve"));
plot_ui.line(Line::new(PlotPoints::from(self.graph.clone())).name("1st Curve"));
plot_ui
.line(Line::new(PlotPoints::from(self.graph2.clone())).name("2nd Curve"));
});
// Remember the position of the plot
});
}
}
Loading