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

Another Plotting Widget #3067

Closed
epezent opened this issue Mar 22, 2020 · 44 comments
Closed

Another Plotting Widget #3067

epezent opened this issue Mar 22, 2020 · 44 comments

Comments

@epezent
Copy link

epezent commented Mar 22, 2020

This widget is DEPRECATED! Please see the new library ImPlot and #3173

Original Post:

Thought I would share an enhanced plotting widget I created. This can be considered an alternative to soulthread's implementation, which was missing a few features I think are useful. The source code has been made a part of my organization's GUI toolkit, but is completely self contained to two files:

imgui_plot.hpp
imgui_plot.cpp

Several examples can be found here (see plots_*.cpp files). I do make use of std::vector and std::string for convenience, but I can't imagine it would be very difficult to strip out. I may eventually move this to its own repository, or submit a pull request if there is interest (though this probably too obtuse to belong in the core ImGui library).

Features

  • four plot types: Line, Scatter, X-bar, and Y-bar
  • mix/match multiple plot items on a single plot
  • optional titles, axis labels, and grid labels
  • legend with toggle buttons to show/hide items
  • reversible and lockable axes
  • size-aware, auto subdividing grid, or manually specified subdivisions
  • auto styling based on current theme, but most elements can be overridden
  • nice grid labels that are always power-of-ten multiples of 1, 2, and 5
  • mouse cursor location display and optional crosshairs mode
  • two utility functions for real-time data visualization (see below)
  • relatively good performance for large numbers and/or high density lines (see below)

Controls

  • scroll wheel zoom (both axes if plot area hovered, individual axes if axis labels hovered)
  • panning/dragging (both axes if plot area dragged, individual axes if axis labels dragged)
  • auto fit data (double-left-click plot area)
  • selection box (right-drag in plot area)
  • context menu (double-right-click plot area)

Usage

Configuration

// create and configure persistent PlotInterface and PlotItems upfront
ImGui::PlotInterface plot;
std::vector<ImGui::PlotItem> items(num_items);

plot.title = "My Plot Title";
plot.x_axis.minimum = 0;
plot.x_axis.maximum = 10;
plot.x_axis.label = "My X-Axis";

items[0].label = "My Line Plot";
items[0].type = ImGui::PlotItem::Line;
items[0].color = ImVec4(1,1,0,1);
items[0].data = std::vector<ImVec2>(...);

Rendering

// later in your update loop
ImGui::Begin("My Window");
ImGui::Plot("My Plot", plot, items);
ImGui::End();

Special Notes

  • By default, no anti-aliasing is done on Line plots for performance reasons. My apps use 4X MSAA, so I didn't see any reason to waste cycles on software AA. However, you can enable AA by defining IMGUI_PLOT_LINE_USE_AA at the top of imgui_plot.cpp.
  • If you plan to render several thousands lines or points, then you should consider enabling 32-bit indices by uncommenting #define ImDrawIdx unsigned int in your imconfig.h file, OR handling the ImGuiBackendFlags_RendererHasVtxOffset flag in your renderer (the official OpenGL3 renderer supports this). If you fail to do this, then you will at some point hit the maximum number of indices that can be rendered.

Examples

Zooming/Panning
1

Misc. Controls
2

Set Labels and Show/Hide Elements
3

Real Time Plotting
4

Adapts to Current Theme
6

Thousands of Points!
5

In Practice
Screenshot 2020-03-21 21 44 55

@sonoro1234
Copy link

Great!!
For interfacing with C, I find std::vector<ImVec2> data; member of PlotItem is difficult to handle.

@ocornut
Copy link
Owner

ocornut commented Mar 22, 2020

Looking very nice, thanks for sharing!

I agree that if the public interface was changed to use raw pointer + size and raw char* it would make the system more easily reusable from various languages and from codebase allergic to std:: containers. It also feels odd that PlotItem, by owning data would more likely require a copy every frame. Either way I am sure people could find their way to adapt it to their need!

@epezent
Copy link
Author

epezent commented Mar 22, 2020

Thanks for the feedback fellas.

@ocornut, my original usage example (since updated) did a poor job of illustrating this, but ImGui::PlotInterface and ImGui::PlotItem are both meant to be used as persistent objects, created upfront. They are passed by reference to ImGui::Plot(...). Unless I'm missing something, I don't think there would be any copying of data in that case.

I'll noodle on making this more C like. In general, I think the interface could certainly be made more ImGui-esque, e.g, something similar to the way tabs work:

// hypothetical usage
if (ImGui::BeginPlot("MyPlot", &plot) {
    ImGui::PlotLine("My Line Plot", xs, ys, color);
    ImGui::PlotScatter("My Scatter Plot", xs, ys, color);
    ImGui::PlotBar("My Bar Plot", xs,ys,width,color);
    // potentially other plot types, e.g. error bars, box plot, etc.
    ...
    ImGui::EndPlot();
}

Some state keeping would need to be done behind the scenes, but I think it's doable.

@astroesteban
Copy link

This is awesome! I was looking around for some real-time plotting widgets I could use in my ImGui app and this fits. Thanks for sharing! I'll report any feedback as I start to incorporate it into my project.

@epezent
Copy link
Author

epezent commented Mar 24, 2020

Thanks! I hope it works well for you.

I've add two functions IsPlotHovered and GetPlotMousePos, the latter of which returns the mouse cursor position in plot coordinates. These should be called after a call to Plot. Here it is in action as a really simple drawing context (source):

7

I can also confirm plots will work as a drag-and-drop target. Here's a simple proof-of-concept (source):

8

@abrigante
Copy link

This is awesome! I may try using this for one of my audio analysis projects :)

The one thing seems to be missing that would be a great addition is the ability to have a "stride" much like the vanilla ImGui Plots. I often have audio data in which their L/R channels are interleaved in a single array so its very handy to be able to pass the plot a reference to the raw data and a stride to plot a single channel, rather then having un-interleave the array whenever it gets updated.

@epezent
Copy link
Author

epezent commented Mar 26, 2020

Thanks @abrigante! I also have considered a stride based interface. I'll put this on the TODO list. PRs are always welcome as well!

@epezent
Copy link
Author

epezent commented Mar 26, 2020

Also, since you mentioned audio, note that there is currently no option for logarithmic axes, but it's something I want (and need) to incorporate soon.

@abrigante
Copy link

Also, since you mentioned audio, note that there is currently no option for logarithmic axes, but it's something I want (and need) to incorporate soon.

Noted! I may have some free-time soon and I'll try to get around to implementing those two features and submitting a PR for it.

@Shookk69
Copy link

Shookk69 commented Apr 3, 2020

plz add tooltip PlotItem

@epezent
Copy link
Author

epezent commented Apr 3, 2020

plz add tooltip PlotItem

ImGui tooltips work just fine, e.g.:

ImGui::Plot("My Rolling Plot", plot, items);
if (ImGui::IsItemHovered()) {
    ImGui::BeginTooltip();
    ImGui::Text("My Plot");
    ImGui::EndTooltip();
}

Did you mean something else? Please be more specific.

@Shookk69
Copy link

Shookk69 commented Apr 3, 2020

No, add Tootip for PlotItem. When hover mouseover the PlotItem, appeared tooltip with name or data PlotItem.
Example:
изображение

@epezent
Copy link
Author

epezent commented Apr 3, 2020

I see. I didn't add this for a few reasons:

  1. There is already a mouse XY position indicator in the bottom right of plot. This, for me, has been sufficient when wanting to query values in a curve (it's not exact, but close enough).
  2. This widget was intended for large data sets, unlike the example you posted which only has a few points. Iterating thousands of data point and doing a mouse check on each may be too expensive. Further, because this is a generic 2D plotting system, I'd have to do magnitude check for each point since you can't assume whether the independent axis is X or Y (in which case you'd just check one dimension, which is what ImGui PlotLines does I believe).

I guess it could be made an optional setting, perhaps for each axis, with a special magnitude checking case if both axes need to be queried. E.g.:

if (plot.x_axis.tooltip && plot.y_axis.toolip) {
    // check all x and y points using magnitude distance
}
else if (plot.x_axis.tooltip) {
    // check all x points using x distance only
}
else if (plot.y_axis.tooltip) {
    // check all y points using y distance only
}

If you want to implement this and submit a PR, I'd be happy to review it. For now though, I don't think this is something I have time to work on. Sorry.

@ozlb
Copy link

ozlb commented Apr 7, 2020

I can also confirm plots will work as a drag-and-drop target.

Great job!

It will be great to have also multiple y axis
image ZDGFI0

an additional feature can be by drag/drop to group measures under a specific y axis (red and blue under one y axis and green in another y axis)
image EV4MI0

Digital signals
image 96HJI0

X axis with time or date/time representation

@ozlb
Copy link

ozlb commented Apr 11, 2020

Plotting in real time mode 4 channels (items) with PlotItemBufferPoint max_points at 4096 will crash after some time.. (around 128[s])
void AddDrawListToDrawData(ImVector<ImDrawList*>, ImDrawList): Assertion `draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices.

In order to solve the issue, is there any other solution than uncomment '#define ImDrawIdx unsigned int' line in imconfig.h." ?

@epezent
Copy link
Author

epezent commented Apr 11, 2020

It’s not something I’ve investigated, as I have been working with 32 bit indices. I think we’d need to implement some sort of culling scheme. As it is, there is no culling...every point is sent to the DrawList, even if it is outside the plot area. Seems like low hanging fruit, so maybe I can take a look. This of course won’t solve your issue if all 4x4096 points are in the plot area. If that’s the case, then it might be possible to decimate the data.

@ocornut
Copy link
Owner

ocornut commented Apr 12, 2020

Since 1.71 (2019/06) it is possible for the back-ends to set ImGuiBackendFlags_RendererHasVtxOffset and support VtxOffset allowing for mesh larger than 64k vertices even with 16-bit indices.
Either way, you should coarse clip render item in your plotting system, ImDrawList doesn't do any clipping on CPU side.

@epezent
Copy link
Author

epezent commented Apr 13, 2020

I've added a simple culling solution for Line and Scatter plot types. For Scatter, I simply don't render a point if it is outside of the plot area. For Line, I don't render a line sub-segment if both its endpoints are outside of the plot area. This, however, is not robust to line segments that jump across the plot area (i.e. both endpoints are outside the plot area, but the line actually crosses the plot area). This typically won't happen unless you are very zoomed in on the data. The only other solution that comes to mind is to add a line intersection test between all segments and each of the 4 lines bounding the plot area. This seems unreasonable, so I'm open to other suggestions.

@ozlb
Copy link

ozlb commented Apr 13, 2020

Since 1.71 (2019/06) it is possible for the back-ends to set ImGuiBackendFlags_RendererHasVtxOffset and support VtxOffset allowing for mesh larger than 64k vertices even with 16-bit indices.
Either way, you should coarse clip render item in your plotting system, ImDrawList doesn't do any clipping on CPU side.

Enabling RederHasVtxOffset is passing assertion but not solving the issue.

mahilab/mahi-gui#8 (comment)

@ocornut
Copy link
Owner

ocornut commented Apr 13, 2020

@ozlb Did you actually handle the ImDrawCmd’s VtxOffset field correctly in the renderer? Its not just having enabling a flag stating the renderer can do it, it also needs to be done. Check the various renderers for an example of what they do.

@ozlb
Copy link

ozlb commented Apr 13, 2020

@ozlb Did you actually handle the ImDrawCmd’s VtxOffset field correctly in the renderer? Its not just having enabling a flag stating the renderer can do it, it also needs to be done. Check the various renderers for an example of what they do.

To be honest I simply enable it. I will try to study and understand what should be done in order to handle it properly. In this moment I don't know how to do it and I'm not skilled to be able to reply properly, but I'm sure that I start from OpenGL2 official renderer and probably it's not supported.

@Thanh-Binh
Copy link

Thanks for sharing very nice tool!
How can I insert a marker (cross line , circle etc) for some points of the plot? Or some vertical lines in a plot?

@epezent
Copy link
Author

epezent commented Apr 14, 2020

There is no built-in way to do what you are asking, but you could just simply make a separate PlotItem that only plots the points you want to distinguish, and uses a different color and/or maker size. I currently only support filled circles for scatter plots. Crosses are a good idea. I may look into supporting different maker styles.

@Thanh-Binh
Copy link

@epezent thanks for your smart response!
Can I display those points on my PlotLines?

@epezent
Copy link
Author

epezent commented Apr 14, 2020

Sure, shouldn't be a problem. The vector of PlotItems are rendered from front to back, so make sure the PlotlLine item comes before the subset PlotScatter item so that the line is under the circle makers.

@Thanh-Binh
Copy link

@ecraven any code for this task? Where can I insert this?
I am new with imGui and use it only for my visualization. Could you pls help me?

@epezent
Copy link
Author

epezent commented Apr 14, 2020

Pseudo-code (not tested):

// init
std::vector<ImGui::PlotItem> items(2);
items[0].type = ImGui::PlotItem::Line;
items[0].data = {{0,0},{1,1},{2,2},{3,3},{4,4}};
items[0].color = {1,0,0,1};
items[1].type = ImGui::PlotItem::Scatter;
items[1].data = {{0,0},{3,3}};
items[1].color = {0,1,0,1};

...

// update loop
ImGui::Plot("My Plot", my_plot, items);

@Thanh-Binh
Copy link

Thanks, but I do not find the function Plot() in the current version

@epezent
Copy link
Author

epezent commented Apr 14, 2020

This isn't built into ImGui, it is a separate library. I suggest you reread the first post.

@Thanh-Binh
Copy link

Understood! Can I use your imgui_plot* in the old version of imGui?

@epezent
Copy link
Author

epezent commented Apr 14, 2020

I suppose, though I'm not sure how far back you could go before certain functions start missing. I've only tested it with 1.75 (docking branch).

@Thanh-Binh
Copy link

Ok ! I will test it tomorrow , yhanks

@ozlb
Copy link

ozlb commented Apr 15, 2020

@ozlb Did you actually handle the ImDrawCmd’s VtxOffset field correctly in the renderer? Its not just having enabling a flag stating the renderer can do it, it also needs to be done. Check the various renderers for an example of what they do.

I managed to build using OpenGL3 renderer and i can confirm that with VtxOffset it's perfectly working with default is 16-bit vertex indices.

@Thanh-Binh
Copy link

@epezent I have tested your codes with my imGUI 1.0. It is not compatible and has many errors.

@Dan-Bird112
Copy link

Dan-Bird112 commented Apr 20, 2020

Had a memory issue when plotting data with very large values. Found the issue to be in the rendering of the mouse position if plot.show_mouse_pos was true. Upped the char buffer to size 100 (arbitrary choice) and seems to have fixed it.

// render mouse pos
if (plot.show_mouse_pos && grid_hovered) {
	//Old buffer size of 32 not large enough for plots with very large values (Change to scientific notation?)
	//static char buffer[32];
	static char buffer[100];
	sprintf(buffer, "%.2f,%.2f", g_plot_mouse_pos.x, g_plot_mouse_pos.y);
	ImVec2 size = CalcTextSize(buffer);
	ImVec2 pos = grid_bb.Max - size - ImVec2(textOffset, textOffset);
	DrawList.AddText(pos, color_txt, buffer);
} 

dephora added a commit to dephora/imgui that referenced this issue Apr 23, 2020
@Thanh-Binh
Copy link

@epezent How can I display 2 plots arranged vertically (one over another).
In my code follows, the 2nd plot can not be displayed well. Thanks.

    ImGui::PlotInterface plot;
    std::vector<ImGui::PlotItem> items(2);
    plot.title = "My Plot Title";
    plot.x_axis.minimum = 0;
    plot.x_axis.maximum = buf_size;
    plot.x_axis.label = "My X-Axis";
    plot.y_axis.minimum = -3;
    plot.y_axis.maximum = +3;
    items[0].label = "My Line Plot";
    items[0].type = ImGui::PlotItem::Line;
    items[0].color = ImVec4(1,1,0,1);
    items[0].data = data1;
    items[1].label = "My Line Plot 2";
    items[1].type = ImGui::PlotItem::Scatter; //ImGui::PlotItem::Line;
    items[1].color = ImVec4(1,0,0,1);
    items[1].data = data2;

    ImGui::Plot("My Plot", plot, items);

    ImGui::PlotInterface plot1;
    std::vector<ImGui::PlotItem> items1(1);
    plot1.title = "My Plot Title 1";
    plot1.x_axis.minimum = 0;
    plot1.x_axis.maximum = buf_size;
    plot1.x_axis.label = "My X-Axis";
    plot1.y_axis.minimum = -3;
    plot1.y_axis.maximum = +3;

    items1[0].label = "My Line Plot1";
    items1[0].type = ImGui::PlotItem::Line;
    items1[0].color = ImVec4(1,1,0,1);
    items1[0].data = data1;

    ImGui::Plot("My Plot", plot1, items1);

@epezent
Copy link
Author

epezent commented Apr 23, 2020

@Thanh-Binh, first, you shouldn't be putting all of that code in your update loop. Everything except for the two calls to ImGui::Plot should be done prior to entering the loop. Second, the reason you can't see the second plot is because the default behavior for plots is to fill the available XY space (i.e. size = ImVec2(-1,-1). So, you should provide an explicit size argument to the first call to ImGui::Plot, e.g. ImVec2(-1, 300).

@Thanh-Binh
Copy link

@epezent thanks for your quick response.
I am not sure if I understood your first point correctly. In my experiments, all setting for x-, y-axis and items are minimum required. Could you please explain me details? Thanks

@Thanh-Binh
Copy link

@epezent understood now your 1st point! Thanks

@Thanh-Binh
Copy link

@epezent how can I change the marker size in PlotItem::Scatter? Thanks

@jujujuhuakai
Copy link

Great!!!!
Hi @epezent , I've tested plotting with ~ 500,000 points. The fps reduces to around 20. I wonder if there is anyway I could boost the performance? Thanks a lot!

@epezent
Copy link
Author

epezent commented Apr 26, 2020

@jujujuhuakai I don't have any quick fixes at the moment. I'm already rendering lines as efficiently as I know how through the ImGui drawing API.

The obvious answer to your problem is to down sample the data (e.g. only render every Nth point would be the simplest method). This isn't built into my API (yet), so you'll need to handle it yourself.

@ocornut has hinted that changes are coming to ImGui that may give line rendering a performance boost (mahilab/mahi-gui#8 (comment)).

@epezent
Copy link
Author

epezent commented Apr 28, 2020

Hi everyone! After receiving your feedback, I have turned this widget into a more refined plotting API:

ImPlot

You can read about the new features in #3173

The TL/DR version is:

  • removed C++ containers ... no vectors, no strings!
  • removed burden of user-owned structs ... all state is now managed internally
  • plots now accept raw data instead of vectors, which can be used with a stride value, or optionally custom defined getter functions
  • logarithmic axes scaling
  • error bars
  • customizable data markers
  • customizable color palettes
  • improved plot context menu
  • text labels and callouts

Thank you all for the positive feedback. I hope you continue to find this useful!

Closing the issue to avoid future confusion.

@epezent epezent closed this as completed Apr 28, 2020
@Soufianeelkabil
Copy link

Hi, First I wanna to thank you for this fantastic library.
I am so beginning with the using of ImGui, and I need some help to add in my window a plot, but I don't know how I can do it, if there is any help, I will be grateful.
Thank u in Advance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests