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

Add bar charts and boxplots #499

Closed
wants to merge 10 commits into from
Closed

Conversation

niladic
Copy link
Contributor

@niladic niladic commented Jun 19, 2021

Following up on #467, this PR adds bar charts and boxplots. Both work and can be tested in the demo app (I am sorry for the amount of code...). I open this PR as a draft because it conflicts with #471, and there might be some debatable choices:

  1. I think it would be better to cut items.rs into submodules. I don't do it for ease of review here, but can do it before merging.
  2. I tried to stay consistent with the existing API, only changing PlotItem::series() by get_bounds() and closest() (because it is only used in the hover behavior). My idea here it to make PlotItem sufficiently abstract to allow for more complex drawings like bar charts and boxplots here.
  3. I created a struct HoverElement which can cause some concerns about performance. I choose this solution because it is simpler, however I am totally open to change it for some kind of reference (I did not see a simple implementation, suggestions are welcome).
  4. There are fields named dummy_values, it is possible to remove them, but this will conflict with More plot items #471 (I can cleanup at some point)
  5. I purposefully avoided statistics here: no mention of histogram (I called it bar chart, like many JS libraries do) and no quartiles or whiskers calculation. I guess it can always be added as an additional layer at one point. Note that plotters took the opposite approach.
  6. In the plot demo, I put some values that were generated using rand, but did not know if it was appropriate to add the crate as a dependency. (fixed)

Screen Shot 2021-06-20 at 11 44 14

Screen Shot 2021-06-20 at 11 44 31

Screen Shot 2021-06-19 at 17 26 17

Screen Shot 2021-06-19 at 17 26 24

Screen Shot 2021-06-19 at 17 26 32

Screen Shot 2021-06-19 at 17 26 40

Replace PlotItem::series() by get_bounds() and closest()
Copy link
Contributor

@EmbersArc EmbersArc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! I like most of the new design decisions. I gave it a quick look and wrote down some suggestions.

egui/src/widgets/plot/items.rs Outdated Show resolved Hide resolved
egui_demo_lib/src/apps/demo/plot_demo.rs Outdated Show resolved Hide resolved
egui_demo_lib/src/apps/demo/plot_demo.rs Show resolved Hide resolved
])
.width(0.7)
.name("Set 2")
.stack_on(&[&chart1]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this from a user perspective. I haven't thought about it too much but maybe it would make sense to have a StackedBarChart type or something like BarChart::new_stacked(<vector or something>).

egui/src/widgets/plot/items.rs Show resolved Hide resolved
fn series_mut(&mut self) -> &mut Values;
fn get_bounds(&self) -> Bounds;
fn closest(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Give this function a more descriptive name.

closest_dist_sq = dist_sq;
closest_value = Some(value);
closest_item = Some(item.name());
if let Some(closest) = item.closest(ui, pointer, transform, *show_x, *show_y) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to create a bunch of shapes for all items and then throws all of them away except for maybe one set right? That is very inefficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just had an idea about that (commit 912b2f3)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks better, I don't know enough about about the language to estimate the performance impact of creating potentially thousands of closures though. Did you consider splitting this into two functions, one to get the distance to the item and one to then get the hover shapes? Then you could do some min_by and then call the trait method to get the shapes just for that object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered the split with a simple distance_square and hover_shapes on PlotItem, but decided against because it required to calculate the distance twice. I think I can salvage it by putting an index instead of the closure, and having fn hover_shapes(index: usize) on PlotItem. I think at this point, I need to do some benchmark of the different ideas. I am not sure how to bench a gui app though, I saw some code in the repo. I will try to have something concrete later this week.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay, it took me some time to get the benchmarks somewhat clean. I implemented an alternative here niladic@24d6547. Also, after trying to implement a non boxed closure here, I found out that I was mistaken on the feasibility. It is not possible without existential types (unstable feature).

I benched (code here niladic@fd8e344 and niladic@ecf9074)

Results are on this branch feature/add-barchart-bench-results. I tried to do it as clean as possible on my machine, but the benches are still a bit noisy. I would say they are really close to each other and there are no regressions in terms of perf. The most interesting bits are the boxed closure vs before this PR as baseline, the alternative vs before this PR as baseline and the alternative vs the boxed closure as baseline.

I also added some flamegraphs on what I think is high but somewhat realistic load:

To sum up, since there are no regressions and both implementations are comparable in terms of perf, I have no strong preference. The alternative with indexes might be simpler and easier to work with down the line. Tell me if you have any preference or other idea, otherwise I will go with the alternative one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey thanks a lot for doing those benchmarks! I agree that we should go with the alternative using indices.

egui/src/widgets/plot/items.rs Outdated Show resolved Hide resolved
egui/src/widgets/plot/items.rs Outdated Show resolved Hide resolved
@emilk
Copy link
Owner

emilk commented Nov 29, 2021

Merged in #863

@emilk emilk closed this Nov 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants