Imgui: How to draw multiple datasets in the same PlotLines? + Grid

Created on 5 May 2016  Â·  16Comments  Â·  Source: ocornut/imgui

PlotLines() works great for us, showing data in one array in real time. We have multiple windows up, each with one array. But we have a lot of data! We'd like to show two arrays of data, using different colors, in each window. Is this possible?

Would it be much trouble to draw horizontal and vertical grid lines in plots? Just enough to be visible but pale enough to not distract from the data.
Of course no one wants to see PlotLines get complicated, but still, grid lines might be nice.

plograph tricks & tips useful widgets

Most helpful comment

I clean code of @JaapSuter to support latest ImGui and supply for out-of-box usage.

#include "imgui_plot.h"

#define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui_internal.h"

namespace ImGui {

static ImU32 InvertColorU32(ImU32 in)
{
    ImVec4 in4 = ColorConvertU32ToFloat4(in);
    in4.x = 1.f - in4.x;
    in4.y = 1.f - in4.y;
    in4.z = 1.f - in4.z;
    return GetColorU32(in4);
}

static void PlotMultiEx(
    ImGuiPlotType plot_type,
    const char* label,
    int num_datas,
    const char** names,
    const ImColor* colors,
    float(*getter)(const void* data, int idx),
    const void * const * datas,
    int values_count,
    float scale_min,
    float scale_max,
    ImVec2 graph_size)
{
    const int values_offset = 0;

    ImGuiWindow* window = GetCurrentWindow();
    if (window->SkipItems)
        return;

    ImGuiContext& g = *GImGui;
    const ImGuiStyle& style = g.Style;

    const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
    if (graph_size.x == 0.0f)
        graph_size.x = CalcItemWidth();
    if (graph_size.y == 0.0f)
        graph_size.y = label_size.y + (style.FramePadding.y * 2);

    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y));
    const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
    const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));
    ItemSize(total_bb, style.FramePadding.y);
    if (!ItemAdd(total_bb, NULL))
        return;

    // Determine scale from values if not specified
    if (scale_min == FLT_MAX || scale_max == FLT_MAX)
    {
        float v_min = FLT_MAX;
        float v_max = -FLT_MAX;
        for (int data_idx = 0; data_idx < num_datas; ++data_idx)
        {
            for (int i = 0; i < values_count; i++)
            {
                const float v = getter(datas[data_idx], i);
                v_min = ImMin(v_min, v);
                v_max = ImMax(v_max, v);
            }
        }
        if (scale_min == FLT_MAX)
            scale_min = v_min;
        if (scale_max == FLT_MAX)
            scale_max = v_max;
    }

    RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);

    int res_w = ImMin((int) graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
    int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);

    // Tooltip on hover
    int v_hovered = -1;
    if (IsHovered(inner_bb, 0))
    {
        const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
        const int v_idx = (int) (t * item_count);
        IM_ASSERT(v_idx >= 0 && v_idx < values_count);

        // std::string toolTip;
        ImGui::BeginTooltip();
        const int idx0 = (v_idx + values_offset) % values_count;
        if (plot_type == ImGuiPlotType_Lines)
        {
            const int idx1 = (v_idx + 1 + values_offset) % values_count;
            Text("%8d %8d | Name", v_idx, v_idx+1);
            for (int dataIdx = 0; dataIdx < num_datas; ++dataIdx)
            {
                const float v0 = getter(datas[dataIdx], idx0);
                const float v1 = getter(datas[dataIdx], idx1);
                TextColored(colors[dataIdx], "%8.4g %8.4g | %s", v0, v1, names[dataIdx]);
            }
        }
        else if (plot_type == ImGuiPlotType_Histogram)
        {
            for (int dataIdx = 0; dataIdx < num_datas; ++dataIdx)
            {
                const float v0 = getter(datas[dataIdx], idx0);
                TextColored(colors[dataIdx], "%d: %8.4g | %s", v_idx, v0, names[dataIdx]);
            }
        }
        ImGui::EndTooltip();
        v_hovered = v_idx;
    }

    for (int data_idx = 0; data_idx < num_datas; ++data_idx)
    {
        const float t_step = 1.0f / (float) res_w;

        float v0 = getter(datas[data_idx], (0 + values_offset) % values_count);
        float t0 = 0.0f;
        ImVec2 tp0 = ImVec2(t0, 1.0f - ImSaturate((v0 - scale_min) / (scale_max - scale_min)));    // Point in the normalized space of our target rectangle

        const ImU32 col_base = colors[data_idx];
        const ImU32 col_hovered = InvertColorU32(colors[data_idx]);

        //const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
        //const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);

        for (int n = 0; n < res_w; n++)
        {
            const float t1 = t0 + t_step;
            const int v1_idx = (int) (t0 * item_count + 0.5f);
            IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
            const float v1 = getter(datas[data_idx], (v1_idx + values_offset + 1) % values_count);
            const ImVec2 tp1 = ImVec2(t1, 1.0f - ImSaturate((v1 - scale_min) / (scale_max - scale_min)));

            // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.
            ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
            ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, 1.0f));
            if (plot_type == ImGuiPlotType_Lines)
            {
                window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
            }
            else if (plot_type == ImGuiPlotType_Histogram)
            {
                if (pos1.x >= pos0.x + 2.0f)
                    pos1.x -= 1.0f;
                window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
            }

            t0 = t1;
            tp0 = tp1;
        }
    }

    RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
}

void PlotMultiLines(
    const char* label,
    int num_datas,
    const char** names,
    const ImColor* colors,
    float(*getter)(const void* data, int idx),
    const void * const * datas,
    int values_count,
    float scale_min,
    float scale_max,
    ImVec2 graph_size)
{
    PlotMultiEx(ImGuiPlotType_Lines, label, num_datas, names, colors, getter, datas, values_count, scale_min, scale_max, graph_size);
}

void PlotMultiHistograms(
    const char* label,
    int num_hists,
    const char** names,
    const ImColor* colors,
    float(*getter)(const void* data, int idx),
    const void * const * datas,
    int values_count,
    float scale_min,
    float scale_max,
    ImVec2 graph_size)
{
    PlotMultiEx(ImGuiPlotType_Histogram, label, num_hists, names, colors, getter, datas, values_count, scale_min, scale_max, graph_size);
}

} // namespace ImGui

All 16 comments

Hello Daren,

I have taken the liberty to merge your other post into the message here, because the issues are really similar.

None of those features are hard to develop per se, they are pretty trivial to do inside custom code. The difficulty is to design an API that would work for many people and to be frank I haven't looked at that in details yet. Those 2 functions haven't really evolved since 1.0 and are probably due for a re-overhaul.

However it is also easy to create your own customized graph. If you dig inside PlotLines() you'll find that it is a rather simple function and you can replicate it using the ImDrawList API and public the fairly API. You can call GetWindowDrawList() to retrieve the current draw list and add content yourself. Or you can include imgui_internal.h and copy and adjust the code as it.

One user for example was doing this:
74f788b6-c51b-11e5-8836-7886a6bdee87

It would however be nice if we dismantled the PlotXX functions, to provide more options and to provide helpers that the user could combine themselves. Some ideas:

  • Making it easy to draw several graphs
  • Helpers to compute range from a data set
  • Helpers to project positions for custom drawing
  • Helpers to detect hovered points (which on "Lines" sort of plotting could imply calculating distance on the Y coordinates to allow distinguishing multiple overlapped graphs)
  • Helpers to draw grids
  • Helpers to draw data in different ways (not just lines and histograms)
  • Allow the user to provide custom labelling on hover (#201), etc.
  • Allow the user to interactively pan, zoom, etc.

Edited my post above to clarify some of the ideas.
While I don't expect this new API to appear soon, may I encourage you to start playing with drawing your own plot and from there you may have a clearer set of ideas of what we could retrofit in a new API, or perhaps you would want to contribute to it.

Omar, did you see the email I sent you (about a grid plot component) yesterday?
I'm thinking maybe it got stuck in the spam filter.

@leino I haven't, and can't see it in my spams (but I could have accidentally deleted it). My e-mail is omar at miracleworld dot net tho it is preferable to keep discussions here.

It seems I screwed up the address. I re-sent it to the address instructed
on your homepage (miracleworld.net).

On Fri, May 6, 2016 at 12:03 PM, omar [email protected] wrote:

@leino https://github.com/leino I haven't, and can't see it in my spams
(but I could have accidentally deleted it). My e-mail is omar at
miracleworld dot net tho it is preferable to keep discussions here.

—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
https://github.com/ocornut/imgui/issues/632#issuecomment-217390379

So I emailed Omar a "tech demo" of how I think a good plot grid should work.

Don't want to go trough the due diligence of releasing the code publically yet, but I'm planning on making it public domain at some point.

For now, I'll just add my own thoughts on nice features here, as I promised Omar earlier today.

First thing I'll say is I think it would be nice if grid and curve plotting could be available separately, so that the user can fill in their own curve plotting as needed. The only thing needed for that (as far as I can tell without actually writing any code...) would be to just have a way of getting the viewport of the plot component. Both in screen coordinates and in "world coordinates" i.e. the coordinate system in which the data lives.

I've found that curve plotting can be done in many different ways depending on the particular circumstances, so I don't think a fully general purpose solution is feasible. Here are a few examples of circumstances:

1)
Is the data in a buffer with a fixed number of samples, or can more samples be generated?
If more samples can be generated, it is nice to provide "infinite zoom". All you would need is the min and max x coordinates of the plot window in world space.

2)
Is the sample spacing uniform? Is it random? Not exploiting uniform sample spacing has performance implications of course.

3)
Does the curve have "special points"? For example: curves may have extra data points (of special format) at points of discontinuity so that nicer rendering may be implemented. I also sometimes want to add in "critical sample points", for instance if the curve is coming from a model I know a lot about, I can choose to include extra samples at sharply peaked minima and maxima, besides the evenly spaced ones.

Here are some thoughts specifically about rendering a plot grid (nothing about curves here).
This stuff I have already implemented in a little tech demo thing which I sent to Omar.
The basic interface (for the rendering part, anyway) is pretty simple: just a function taking a rectangle in screen space and a rectangle in world space and draws the grid according to some settings.

Those settings are probably the most tricky part of the API design, so I'll share some thoughts on those as well. There are some fundamental limitations on what transforms can be supported. Clearly translation-and-zoom-only (no rotation/shearing) is probably an acceptable restriction, but there are further restrictions. For instanace, the user probably wants the widget to take up a fixed amount of screen space. That means that zooms/translations cannot be arbitrary, since that would take an arbitrary number of decimals to represent on the screen. So basically the limitations on the particular transform would come from a limitation on the margin size (where grid numbers are printed) in screen space. The user could select these margin sizes, and then the program could limit the zoom/translation appropriately.

I also like the grid lines fading in gradually as the user zooms: kind of like zooming in on an infinite-detail ruler with millimeter marks, centimeter marks, etc... Of course, a ruler would be a particular case where the base is 10, but generalising on the base is very easy. The grid numbers would then also need to be printed in this base, so that a controlled and predictable number of decimals is used.

This brings up another user-facing parameter to tweak: smallest on-screen distance between grid lines. Certainly the user would want this minimum distance to be larger than the height of the font used to print the grid numbers (so that the numbers would not overlap), but a bit more padding may be desired depending on what the usecase of the plot is (too many grid lines may look jumbled).

Finally, being able to individually select horizontal and vertical grid line visibility is nice.
In fact, most of the things I've mentioned should probably be settable indivudually on the x and y axes.

Apologies for the long post. I'll get back to finishing off my little tech demo of the grid rendering stuff, and then I'll bump this thread. Maybe it can be integrated, or else I'll at least make sure it's easy to plug in as an external thing.

Oh and btw, I figured out a way to draw it such that I only need 1 draw call per grid direction (horizontal/vertical). Works nicely and always draws exactly the visible grid lines.

The font could be drawn in n draw calls per grid direction, where n is the number of significant digits used to print the numbers, so typically fixed at something like 4 or 5, perhaps, though I'm not doing that at the moment: currently printing the grid numbers in the straight-forward way with one draw call per visible grid line for each direction.

Actual gpu draw calls are already merged by the ImDrawList api. Unless you change clipping rectangle or texture, everything is already one draw call.

Here's a quick and dirty version of PlotMultiLines and PlotMultiHistograms that works 'good enough' for me, for the time being. Comes with no warranties, nor guarantueed compile-ability.

Note, this code was written in "_have a deadline, so get 'r done whichever way mode_". Per Ocornut's comment below, there are certainly much better ways to write this (and when I get around to improving our version, I'll be sure to share it.

image

static void plotMultiEx(ImGuiPlotType plotType, const char* label, int numDatas, const char** names, const ImColor* colors, float(*getter)(void* data, int idx), void** datas, int count, float scaleMin, float scaleMax, ImVec2 graphSize) {;

    const int values_offset = 0;

    ImGuiWindow* window = GetCurrentWindow();
    if (window->SkipItems)
        return;

    ImGuiState& g = *GImGui;
    const ImGuiStyle& style = g.Style;

    const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
    if (graphSize.x == 0.0f)
        graphSize.x = CalcItemWidth() + (style.FramePadding.x * 2);
    if (graphSize.y == 0.0f)
        graphSize.y = label_size.y + (style.FramePadding.y * 2);

    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graphSize.x, graphSize.y));
    const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
    const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));
    ItemSize(total_bb, style.FramePadding.y);
    if (!ItemAdd(total_bb, NULL))
        return;

    // Determine scale from values if not specified
    if (scaleMin == FLT_MAX || scaleMax == FLT_MAX)
    {
        float v_min = FLT_MAX;
        float v_max = -FLT_MAX;
        for (int dataIdx = 0; dataIdx < numDatas; ++dataIdx) {
            for (int i = 0; i < count; i++)
            {
                const float v = getter(datas[dataIdx], i);
                v_min = ImMin(v_min, v);
                v_max = ImMax(v_max, v);
            }
        }
        if (scaleMin == FLT_MAX)
            scaleMin = v_min;
        if (scaleMax == FLT_MAX)
            scaleMax = v_max;
    }

    RenderFrame(frame_bb.Min, frame_bb.Max, window->Color(ImGuiCol_FrameBg), true, style.FrameRounding);

    int res_w = ImMin((int)graphSize.x, count) + ((plotType == ImGuiPlotType_Lines) ? -1 : 0);
    int item_count = count + ((plotType == ImGuiPlotType_Lines) ? -1 : 0);

    // Tooltip on hover
    int v_hovered = -1;
    if (IsHovered(inner_bb, 0))
    {
        const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
        const int v_idx = (int)(t * item_count);
        IM_ASSERT(v_idx >= 0 && v_idx < count);

        // std::string toolTip;
        imgui::BeginTooltip();
        for (int dataIdx = 0; dataIdx < numDatas; ++dataIdx) {

            const float v0 = getter(datas[dataIdx], (v_idx + values_offset) % count);
            TextColored(colors[dataIdx], fmt::format("{:4}, {:7.2f}, {}", v_idx, v0, names[dataIdx]).c_str());
        }
        imgui::EndTooltip();

        v_hovered = v_idx;
    }

    for (int dataIdx = 0; dataIdx < numDatas; ++dataIdx)
    {
        const float t_step = 1.0f / (float)res_w;

        float v0 = getter(datas[dataIdx], (0 + values_offset) % count);
        float t0 = 0.0f;
        ImVec2 tp0 = ImVec2(t0, 1.0f - ImSaturate((v0 - scaleMin) / (scaleMax - scaleMin)));    // Point in the normalized space of our target rectangle

        const ImU32 col_base = colors[dataIdx];
        const ImU32 col_hovered = invertColor(colors[dataIdx]);

        for (int n = 0; n < res_w; n++)
        {
            const float t1 = t0 + t_step;
            const int v1_idx = (int)(t0 * item_count + 0.5f);
            IM_ASSERT(v1_idx >= 0 && v1_idx < count);
            const float v1 = getter(datas[dataIdx], (v1_idx + values_offset + 1) % count);
            const ImVec2 tp1 = ImVec2(t1, 1.0f - ImSaturate((v1 - scaleMin) / (scaleMax - scaleMin)));

            // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.
            ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
            ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plotType == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, 1.0f));
            if (plotType == ImGuiPlotType_Lines)
            {
                window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
            }
            else if (plotType == ImGuiPlotType_Histogram)
            {
                if (pos1.x >= pos0.x + 2.0f)
                    pos1.x -= 1.0f;
                window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
            }

            t0 = t1;
            tp0 = tp1;
        }
    }

    RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
}

void plotMultiLines(const char* label, int numDatas, const char** names, const ImColor* colors, float(*getter)(void* data, int idx), void** datas, int count, float scaleMin, float scaleMax, ImVec2 graphSize) {
    plotMultiEx(ImGuiPlotType_Lines, label, numDatas, names, colors, getter, datas, count, scaleMin, scaleMax, graphSize);
}

void plotMultiHistograms(const char* label, int numHists, const char** names, const ImColor* colors, float(*getter)(void* data, int idx), void** datas, int count, float scaleMin, float scaleMax, ImVec2 graphSize) {
    plotMultiEx(ImGuiPlotType_Histogram, label, numHists, names, colors, getter, datas, count, scaleMin, scaleMax, graphSize);
}

Thanks for posting this as a reference! When posting code of that sort it's nice to include a picture to help people see how it looks.

I personally think the right approach would be to have multiple helper functions that can be called in sequence.

Thanks for reminding me about including a screenshot, I've updated the original comment. And you're bang-on about there being better ways to do this. I went for the _quickest fastest and dirtiest_ way possible, with there being a looming deadline and all :). When I get around to updating the code, I'll be sure to share the (what I hope will be) improved code.

Thanks for a great library Ocornut!

I clean code of @JaapSuter to support latest ImGui and supply for out-of-box usage.

#include "imgui_plot.h"

#define IMGUI_DEFINE_MATH_OPERATORS
#include "imgui_internal.h"

namespace ImGui {

static ImU32 InvertColorU32(ImU32 in)
{
    ImVec4 in4 = ColorConvertU32ToFloat4(in);
    in4.x = 1.f - in4.x;
    in4.y = 1.f - in4.y;
    in4.z = 1.f - in4.z;
    return GetColorU32(in4);
}

static void PlotMultiEx(
    ImGuiPlotType plot_type,
    const char* label,
    int num_datas,
    const char** names,
    const ImColor* colors,
    float(*getter)(const void* data, int idx),
    const void * const * datas,
    int values_count,
    float scale_min,
    float scale_max,
    ImVec2 graph_size)
{
    const int values_offset = 0;

    ImGuiWindow* window = GetCurrentWindow();
    if (window->SkipItems)
        return;

    ImGuiContext& g = *GImGui;
    const ImGuiStyle& style = g.Style;

    const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
    if (graph_size.x == 0.0f)
        graph_size.x = CalcItemWidth();
    if (graph_size.y == 0.0f)
        graph_size.y = label_size.y + (style.FramePadding.y * 2);

    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y));
    const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
    const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));
    ItemSize(total_bb, style.FramePadding.y);
    if (!ItemAdd(total_bb, NULL))
        return;

    // Determine scale from values if not specified
    if (scale_min == FLT_MAX || scale_max == FLT_MAX)
    {
        float v_min = FLT_MAX;
        float v_max = -FLT_MAX;
        for (int data_idx = 0; data_idx < num_datas; ++data_idx)
        {
            for (int i = 0; i < values_count; i++)
            {
                const float v = getter(datas[data_idx], i);
                v_min = ImMin(v_min, v);
                v_max = ImMax(v_max, v);
            }
        }
        if (scale_min == FLT_MAX)
            scale_min = v_min;
        if (scale_max == FLT_MAX)
            scale_max = v_max;
    }

    RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);

    int res_w = ImMin((int) graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
    int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);

    // Tooltip on hover
    int v_hovered = -1;
    if (IsHovered(inner_bb, 0))
    {
        const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
        const int v_idx = (int) (t * item_count);
        IM_ASSERT(v_idx >= 0 && v_idx < values_count);

        // std::string toolTip;
        ImGui::BeginTooltip();
        const int idx0 = (v_idx + values_offset) % values_count;
        if (plot_type == ImGuiPlotType_Lines)
        {
            const int idx1 = (v_idx + 1 + values_offset) % values_count;
            Text("%8d %8d | Name", v_idx, v_idx+1);
            for (int dataIdx = 0; dataIdx < num_datas; ++dataIdx)
            {
                const float v0 = getter(datas[dataIdx], idx0);
                const float v1 = getter(datas[dataIdx], idx1);
                TextColored(colors[dataIdx], "%8.4g %8.4g | %s", v0, v1, names[dataIdx]);
            }
        }
        else if (plot_type == ImGuiPlotType_Histogram)
        {
            for (int dataIdx = 0; dataIdx < num_datas; ++dataIdx)
            {
                const float v0 = getter(datas[dataIdx], idx0);
                TextColored(colors[dataIdx], "%d: %8.4g | %s", v_idx, v0, names[dataIdx]);
            }
        }
        ImGui::EndTooltip();
        v_hovered = v_idx;
    }

    for (int data_idx = 0; data_idx < num_datas; ++data_idx)
    {
        const float t_step = 1.0f / (float) res_w;

        float v0 = getter(datas[data_idx], (0 + values_offset) % values_count);
        float t0 = 0.0f;
        ImVec2 tp0 = ImVec2(t0, 1.0f - ImSaturate((v0 - scale_min) / (scale_max - scale_min)));    // Point in the normalized space of our target rectangle

        const ImU32 col_base = colors[data_idx];
        const ImU32 col_hovered = InvertColorU32(colors[data_idx]);

        //const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
        //const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);

        for (int n = 0; n < res_w; n++)
        {
            const float t1 = t0 + t_step;
            const int v1_idx = (int) (t0 * item_count + 0.5f);
            IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
            const float v1 = getter(datas[data_idx], (v1_idx + values_offset + 1) % values_count);
            const ImVec2 tp1 = ImVec2(t1, 1.0f - ImSaturate((v1 - scale_min) / (scale_max - scale_min)));

            // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.
            ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
            ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, 1.0f));
            if (plot_type == ImGuiPlotType_Lines)
            {
                window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
            }
            else if (plot_type == ImGuiPlotType_Histogram)
            {
                if (pos1.x >= pos0.x + 2.0f)
                    pos1.x -= 1.0f;
                window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
            }

            t0 = t1;
            tp0 = tp1;
        }
    }

    RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
}

void PlotMultiLines(
    const char* label,
    int num_datas,
    const char** names,
    const ImColor* colors,
    float(*getter)(const void* data, int idx),
    const void * const * datas,
    int values_count,
    float scale_min,
    float scale_max,
    ImVec2 graph_size)
{
    PlotMultiEx(ImGuiPlotType_Lines, label, num_datas, names, colors, getter, datas, values_count, scale_min, scale_max, graph_size);
}

void PlotMultiHistograms(
    const char* label,
    int num_hists,
    const char** names,
    const ImColor* colors,
    float(*getter)(const void* data, int idx),
    const void * const * datas,
    int values_count,
    float scale_min,
    float scale_max,
    ImVec2 graph_size)
{
    PlotMultiEx(ImGuiPlotType_Histogram, label, num_hists, names, colors, getter, datas, values_count, scale_min, scale_max, graph_size);
}

} // namespace ImGui

this should be tip and tricks or useful widgets?

Love the widget, two comments on the code in this thread as of Dec 2020:

  1. IsHovered() is now called IsItemHovered(0)

  2. datas is a void ** and you cannot index a void as it has no size. This means datas[data_idx] will compile but not work. You need to cast datas to a type with a size before indexing it.

Other than that, works great!

const char* names[2] = { "table1", "table2" };
const ImColor colors[2] = { {1.0f, 1.0f, 0.5f, 1.0f}, {0.7f, 1.0f, 1.0f, 1.0f} };
PlotArrayGetterData table_list[2] = { {table1, sizeof(float)}, {table2, sizeof(float)} };
ImGui::PlotMultiHistograms( labelstring,  // label
                            2,            // num_hists,
                            names,        // names,
                            colors,       // colors,
                            &Plot_ArrayGetter, // getter
                            (const void* const*)&table_list, // datas,
                            63,           // values_count,
                            FLT_MAX,      // scale_min,
                            FLT_MAX,      // scale_max,
                            ImVec2 (256.0, 17.0f)  // graph_size
                          );

@fatlimey
This is a very old thread / piece of code.
Nowadays I suggest you use https://github.com/epezent/implot

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ILoveImgui picture ILoveImgui  Â·  3Comments

spaderthomas picture spaderthomas  Â·  3Comments

GrammarLord picture GrammarLord  Â·  3Comments

Folling picture Folling  Â·  3Comments

mkanakis picture mkanakis  Â·  3Comments