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 label quadrant #3028

Closed
barbiefan opened this issue May 25, 2023 · 5 comments
Closed

Plot label quadrant #3028

barbiefan opened this issue May 25, 2023 · 5 comments

Comments

@barbiefan
Copy link

barbiefan commented May 25, 2023

First of all - thank you for such an amazing job with this project!

Is your feature request related to a problem? Please describe.
Plot label currently is stuck in top-right corner of the pointer, which is an issue if you want to read label at the edge of a plot (top edge, or right edge). Label isn't drawn in that case (screenshot below)
image_2023-05-25_23-09-53

Describe the solution you'd like
I don't know internals of the library well enough to produce a sensible solution (and very new to rust as a whole), but I made a simple hack for my own purposes, which is: compare value (which i assume just pointer coordinates in plot space) with plot bounds (screenshot with solution below). That way we can put label in the opposite quadrant of the cursor compared to cursor's position in the plot (it's in the egui/crates/egui/src/widgets/plot/items/mod.rs rulers_at_value())
image

Describe alternatives you've considered
I think a much better way to do this would be to provide some setting for user to decide how label is displayed

Additional context
again, much thanks for the work you all put into this project, it's extremely useful.

code of hacky solution in the screenshot:
// determine which quadrant cursor is placed in.
let bounds = plot.transform.bounds();
let mut quadrant: [bool; 2] = [true, true];
quadrant[0] = value.x > bounds.min()[0] + (bounds.max()[0] - bounds.min()[0]) / 2.0;
quadrant[1] = value.y > bounds.min()[1] + (bounds.max()[1] - bounds.min()[1]) / 2.0;

let label_quadrant = match quadrant {
    [true, true] => Align2::RIGHT_TOP,
    [false, true] => Align2::LEFT_TOP,
    [true, false] => Align2::RIGHT_BOTTOM,
    [false, false] => Align2::LEFT_BOTTOM,
};

let label_offset = quadrant.map(|b| match b {
    true => 1.0,
    false => -1.0,
});

let font_id = TextStyle::Body.resolve(plot.ui.style());
plot.ui.fonts(|f| {
    shapes.push(Shape::text(
        f,
        pointer + vec2(-3.0 * label_offset[0], 2.0 * label_offset[1]),
        label_quadrant,
        text,
        font_id,
        plot.ui.visuals().text_color(),
    ));
});
@apessino
Copy link

Having the label move around relative to the cursor does not seem great to me, and then you are still going to clip if the label is too big to fit into the graph area (happens a lot if you use multiple narrow plots next to each other, for example).

I find that using show_tooltip_at_pointer(..) works better and is a lot more flexible - it is consistent, deals with window edges as well, and then you have much more freedom to shape/format the output as you need.

For example:

bandicam.2023-05-25.14-40-49-494.mp4

@barbiefan
Copy link
Author

barbiefan commented May 25, 2023

Having the label move around relative to the cursor does not seem great to me, and then you are still going to clip if the label is too big to fit into the graph area (happens a lot if you use multiple narrow plots next to each other, for example).

I totally agree with all that. In fact small graphs is exactly the reason I did that modification.

I find that using show_tooltip_at_pointer(..) works better and is a lot more flexible - it is consistent, deals with window edges as well, and then you have much more freedom to shape/format the output as you need.

That is really helpful! I didn't know that functionality existed
However this method lacks "snapping" to the nearest point, unless I do the calculations myself, which is doable, but since native label does this already it would be nice to get the item to which label has "snapped to", instead of performing same calculations twice. Is there something like that cause I can't find it?...

But the resulting ui itself is miles better than what I had before. I could really use some snapping tho

@apessino
Copy link

apessino commented May 25, 2023

What you want to snap is not the location of the pointer, it's the location egui samples on the graph. All you have to do is cache the n that egui computes and gives to the formatter, then do your own lookup of the graph value at that point (which is preferable anyway, especially when you have more complex ways of looking at the data).

See the following in my code (ignore the Sym part, n is what you care about):

let pp = std::rc::Rc::new(std::cell::RefCell::<(Sym, f32, f32)>::new((
    Sym::EMPTY,
    0.0,
    0.0,
)));
let pp_ = pp.clone();
let formatter = move |name: &str, n: &egui::plot::PlotPoint| {
    *pp.borrow_mut() = (Sym::hash_str(name), n.x as f32, n.y as f32);
    "".into()
};

let mut sovs_plot = egui::plot::Plot::new("sovs_plot")
    .include_x(max_x as f64)
    .auto_bounds_x()
    .auto_bounds_y()
    .view_aspect(4.0)
    .label_formatter(formatter)
    .show_axes([false, true])
    .show_x(false)
    .set_margin_fraction(egui::Vec2::new(0.1, 0.15));

let egui::plot::PlotResponse { inner: hovered, .. } = sovs_plot.show(ui, |plot_ui| {
    // ..draw the plot here
});

if hovered {
    let (name, time, n) = *pp_.borrow();
    if let Some(tx) = self.graph_readout(name, time, n, time_stats) {
        egui::show_tooltip_at_pointer(context, egui::Id::new("graph_read"), |ui| {
            widget::text_style(
                ui,
                egui::TextStyle::Name(String::from(Self::MONO_SMALL).into()),
                |ui| {
                    tx.iter().for_each(|t| {
                        ui.label(t.clone());
                    })
                },
            );
        });
    }
}

This still leverages the snapping and calculations egui does, but caches the value that gets computed by accessing it from the formatter, and then uses it to make as complex and as detailed a "tooltip" as I desire at whatever the actual pointer location will be (close to the point but not snapped). Works very well.

EDIT: I left out the important plot definition code - added it now :)

@apessino
Copy link

BTW you can see in my video that you still get the plot location snapped and highlighted by egui, as desired.

@barbiefan
Copy link
Author

Thank you a lot, this works great!
Feels a bit hacky but still does the job :D

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

No branches or pull requests

2 participants