From 5a2d5c15040eb3ac6624691d9e3070368a1a4f30 Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Thu, 30 Apr 2020 09:18:59 -0500 Subject: [PATCH 1/8] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8e67076..40ea3e6 100644 --- a/README.md +++ b/README.md @@ -83,3 +83,7 @@ A: Yes, you can use the C binding, [cimplot](https://github.com/cimgui/cimplot) ## Special Notes - By default, no anti-aliasing is done on line plots for performance reasons. If you use 4x MSAA, then you likely won't even notice. However, you can re-enable AA with the `ImPlotFlags_AntiAliased` flag. - 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 may at some point hit the maximum number of indices that can be rendered. + +## See Also +[ImGui Discussion Thread](https://github.com/ocornut/imgui/issues/3173) + From 99488136745fc7b2de329d4d61af56af01a3753d Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Thu, 30 Apr 2020 09:25:16 -0500 Subject: [PATCH 2/8] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 40ea3e6..8682f97 100644 --- a/README.md +++ b/README.md @@ -85,5 +85,5 @@ A: Yes, you can use the C binding, [cimplot](https://github.com/cimgui/cimplot) - 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 may at some point hit the maximum number of indices that can be rendered. ## See Also -[ImGui Discussion Thread](https://github.com/ocornut/imgui/issues/3173) - +[ImPlot discussion](https://github.com/ocornut/imgui/issues/3173) - ImPlot discussion issue at the official ImGui repository +[imgui-plot](https://github.com/soulthreads/imgui-plot) - an alternate plotting widget by soulthreads From 413639bce267b52b0dea6780e942485a5a710d4a Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Thu, 30 Apr 2020 09:25:28 -0500 Subject: [PATCH 3/8] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8682f97..885a315 100644 --- a/README.md +++ b/README.md @@ -86,4 +86,5 @@ A: Yes, you can use the C binding, [cimplot](https://github.com/cimgui/cimplot) ## See Also [ImPlot discussion](https://github.com/ocornut/imgui/issues/3173) - ImPlot discussion issue at the official ImGui repository + [imgui-plot](https://github.com/soulthreads/imgui-plot) - an alternate plotting widget by soulthreads From 8c622625cde441328b92854d77d75a1dc71e3af0 Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Fri, 1 May 2020 08:50:08 -0500 Subject: [PATCH 4/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 885a315..de8cfc3 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ A: Yes. Plot colors, palletes, and various styling variables can be pushed/poppe A: Yep! -**Q: Does ImPlot support X plot types?** +**Q: Does ImPlot support [insert plot type]?** A: Maybe. Check the gallery and demo to see if your desired plot type is shown. If not, consider submitting an issue or better yet, a PR! From ad01a01331ec5d35ccfb7f0675d379e3e7d3ffee Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Sun, 3 May 2020 00:24:10 -0500 Subject: [PATCH 5/8] optimizations, add pie plot --- implot.cpp | 1035 ++++++++++++++++++++++++++++------------------- implot.h | 4 + implot_demo.cpp | 61 ++- 3 files changed, 676 insertions(+), 424 deletions(-) diff --git a/implot.cpp b/implot.cpp index a6992c5..24e613b 100644 --- a/implot.cpp +++ b/implot.cpp @@ -76,9 +76,9 @@ namespace ImGui { namespace { -//============================================================================= -// General Utils -//============================================================================= +//----------------------------------------------------------------------------- +// Private Utils +//----------------------------------------------------------------------------- /// Returns true if a flag is set template @@ -97,20 +97,24 @@ inline float Remap(float x, float x0, float x1, float y0, float y1) { return y0 + (x - x0) * (y1 - y0) / (x1 - x0); } +/// Turns NANs to 0s inline float ConstrainNan(float val) { return val == NAN || val == -NAN ? 0 : val; } +/// Turns INFINITYs to FLT_MAXs inline float ConstrainInf(float val) { return val == INFINITY ? FLT_MAX : val == -INFINITY ? -FLT_MAX : val; } +/// Turns numbers less than or equal to 0 to 0.001 (sort of arbitrary, is there a better way?) inline float ConstrainLog(float val) { - return val <= 0 ? 0.001 : val; + return val <= 0 ? 0.001f : val; } +/// Returns true if val is NAN or INFINITY inline bool NanOrInf(float val) { - return val == INFINITY || val == -INFINITY || val == NAN || val == -NAN; + return val == INFINITY || val == NAN || val == -INFINITY || val == -NAN; } /// Utility function to that rounds x to powers of 2,5 and 10 for generating axis labels @@ -169,9 +173,18 @@ inline ImVec2 CalcTextSizeVertical(const char *text) { return ImVec2(sz.y, sz.x); } -//============================================================================= +} // private namespace + +//----------------------------------------------------------------------------- +// Forwards +//----------------------------------------------------------------------------- + +ImVec4 NextColor(); + +//----------------------------------------------------------------------------- // Structs -//============================================================================= +//----------------------------------------------------------------------------- + /// Tick mark info struct ImTick { @@ -189,13 +202,18 @@ struct ImTick { }; struct ImPlotItem { - ImPlotItem(); + ImPlotItem() { + Show = true; + Highlight = false; + Color = NextColor(); + NameOffset = -1; + ID = 0; + } ~ImPlotItem() { ID = 0; } bool Show; bool Highlight; ImVec4 Color; int NameOffset; - bool Active; ImGuiID ID; }; @@ -237,8 +255,11 @@ struct ImPlot { ImRect QueryRect; // relative to BB_grid!! bool DraggingQuery; ImPlotRange QueryRange; + ImPlotAxis XAxis; - ImPlotAxis YAxis; + ImPlotAxis YAxis; + inline ImPlotAxis& Axis(int idx) { return (&XAxis)[idx]; } + ImPlotFlags Flags; int ColorIdx; }; @@ -262,129 +283,35 @@ struct ImPlotContext { FitThisFrame = FitX = FitY = false; RestorePlotPalette(); } + /// ALl Plots ImPool Plots; /// Current Plot ImPlot* CurrentPlot; - // Legend - ImVector _LegendIndices; - ImGuiTextBuffer _LegendLabels; - - const char* GetLegendLabel(int i) const { - ImPlotItem* item = CurrentPlot->Items.GetByIndex(_LegendIndices[i]); - IM_ASSERT(item->NameOffset != -1 && item->NameOffset < _LegendLabels.Buf.Size); - return _LegendLabels.Buf.Data + item->NameOffset; - } - - ImPlotItem* GetLegendItem(int i) { - return CurrentPlot->Items.GetByIndex(_LegendIndices[i]); - } - - int GetLegendCount() const { - return _LegendIndices.size(); - } - - ImPlotItem* RegisterItem(const char* label_id) { - ImGuiID id = ImGui::GetID(label_id); - ImPlotItem* item = CurrentPlot->Items.GetOrAddByKey(id); - int idx = CurrentPlot->Items.GetIndex(item); - item->Active = true; - item->ID = id; - _LegendIndices.push_back(idx); - item->NameOffset = _LegendLabels.size(); - _LegendLabels.append(label_id, label_id + strlen(label_id) + 1); - if (item->Show) - VisibleItemCount++; - return item; - } - + ImVector LegendIndices; + ImGuiTextBuffer LegendLabels; // Bounding regions ImRect BB_Frame; ImRect BB_Canvas; ImRect BB_Grid; - // Hover states bool Hov_Frame; bool Hov_Grid; - - // Colors + // Cached Colors ImU32 Col_Frame, Col_Bg, Col_Border, Col_Txt, Col_TxtDis, Col_SlctBg, Col_SlctBd, Col_QryBg, Col_QryBd, Col_XMajor, Col_XMinor, Col_XTxt, - Col_YMajor, Col_YMinor, Col_YTxt; - - // Tick marks buffers + Col_YMajor, Col_YMinor, Col_YTxt; + // Tick marks ImVector XTicks, YTicks; ImGuiTextBuffer XTickLabels, YTickLabels; - - // Transformations + // Transformation cache ImRect PixelRange; - float Mx, My; - float LogDenX, LogDenY; - inline ImVec2 ToPixels(float x, float y) { - ImVec2 out; - if (HasFlag(CurrentPlot->XAxis.Flags, ImAxisFlags_LogScale)) { - float t = log10(x / CurrentPlot->XAxis.Min) / LogDenX; - x = ImLerp(CurrentPlot->XAxis.Min, CurrentPlot->XAxis.Max, t); - } - if (HasFlag(CurrentPlot->YAxis.Flags, ImAxisFlags_LogScale)) { - float t = log10(y / CurrentPlot->YAxis.Min) / LogDenY; - y = ImLerp(CurrentPlot->YAxis.Min, CurrentPlot->YAxis.Max, t); - } - out.x = PixelRange.Min.x + Mx * (x - CurrentPlot->XAxis.Min); - out.y = PixelRange.Min.y + My * (y - CurrentPlot->YAxis.Min); - return out; - } - inline ImVec2 ToPixels(const ImVec2& in) { - return ToPixels(in.x, in.y); - } - inline ImVec2 FromPixels(const ImVec2& in) { - ImVec2 out; - out.x = (in.x - PixelRange.Min.x) / Mx + CurrentPlot->XAxis.Min; - out.y = (in.y - PixelRange.Min.y) / My + CurrentPlot->YAxis.Min; - if (HasFlag(CurrentPlot->XAxis.Flags, ImAxisFlags_LogScale)) { - float t = (out.x - CurrentPlot->XAxis.Min) / (CurrentPlot->XAxis.Max - CurrentPlot->XAxis.Min); - out.x = pow(10, t * LogDenX) * CurrentPlot->XAxis.Min; - } - if (HasFlag(CurrentPlot->YAxis.Flags, ImAxisFlags_LogScale)) { - float t = (out.y - CurrentPlot->YAxis.Min) / (CurrentPlot->YAxis.Max - CurrentPlot->YAxis.Min); - out.y = pow(10, t * LogDenY) * CurrentPlot->YAxis.Min; - } - return out; - } - inline void UpdateTransforms() { - // get pixels for transforms - PixelRange = ImRect(HasFlag(CurrentPlot->XAxis.Flags, ImAxisFlags_Invert) ? BB_Grid.Max.x : BB_Grid.Min.x, - HasFlag(CurrentPlot->YAxis.Flags, ImAxisFlags_Invert) ? BB_Grid.Min.y : BB_Grid.Max.y, - HasFlag(CurrentPlot->XAxis.Flags, ImAxisFlags_Invert) ? BB_Grid.Min.x : BB_Grid.Max.x, - HasFlag(CurrentPlot->YAxis.Flags, ImAxisFlags_Invert) ? BB_Grid.Max.y : BB_Grid.Min.y); - - Mx = (PixelRange.Max.x - PixelRange.Min.x) / (CurrentPlot->XAxis.Max - CurrentPlot->XAxis.Min); - My = (PixelRange.Max.y - PixelRange.Min.y) / (CurrentPlot->YAxis.Max - CurrentPlot->YAxis.Min); - LogDenX = log10(CurrentPlot->XAxis.Max / CurrentPlot->XAxis.Min); - LogDenY = log10(CurrentPlot->YAxis.Max / CurrentPlot->YAxis.Min); - } - - /// Returns the next unused default plot color - ImVec4 NextColor() { - auto col = ColorMap[CurrentPlot->ColorIdx % ColorMap.size()]; - CurrentPlot->ColorIdx++; - return col; - } - - inline void FitPoint(const ImVec2& p) { - if (!NanOrInf(p.x)) { - Extents.Min.x = p.x < Extents.Min.x ? p.x : Extents.Min.x; - Extents.Max.x = p.x > Extents.Max.x ? p.x : Extents.Max.x; - } - if (!NanOrInf(p.y)) { - Extents.Min.y = p.y < Extents.Min.y ? p.y : Extents.Min.y; - Extents.Max.y = p.y > Extents.Max.y ? p.y : Extents.Max.y; - } - } + ImVec2 M; // linear scale (slope) + ImVec2 LogDen; // log scale denominator // Data extents ImRect Extents; @@ -399,26 +326,182 @@ struct ImPlotContext { ImPlotStyle Style; ImVector ColorModifiers; // Stack for PushStyleColor()/PopStyleColor() ImVector StyleModifiers; // Stack for PushStyleVar()/PopStyleVar() - ImNextPlotData NextPlotData; - - + ImNextPlotData NextPlotData; }; /// Global plot context static ImPlotContext gp; -ImPlotItem::ImPlotItem() { - Show = true; - Highlight = false; - Color = gp.NextColor(); - NameOffset = -1; - Active = true; - ID = 0; +//----------------------------------------------------------------------------- +// Utils +//----------------------------------------------------------------------------- + +/// Returns the next unused default plot color +ImVec4 NextColor() { + auto col = gp.ColorMap[gp.CurrentPlot->ColorIdx % gp.ColorMap.size()]; + gp.CurrentPlot->ColorIdx++; + return col; } -//============================================================================= +inline void FitPoint(const ImVec2& p) { + if (!NanOrInf(p.x)) { + gp.Extents.Min.x = p.x < gp.Extents.Min.x ? p.x : gp.Extents.Min.x; + gp.Extents.Max.x = p.x > gp.Extents.Max.x ? p.x : gp.Extents.Max.x; + } + if (!NanOrInf(p.y)) { + gp.Extents.Min.y = p.y < gp.Extents.Min.y ? p.y : gp.Extents.Min.y; + gp.Extents.Max.y = p.y > gp.Extents.Max.y ? p.y : gp.Extents.Max.y; + } +} + +//----------------------------------------------------------------------------- +// Coordinate Transforms +//----------------------------------------------------------------------------- + +inline void UpdateTransformCache() { + // get pixels for transforms + gp.PixelRange = ImRect(HasFlag(gp.CurrentPlot->XAxis.Flags, ImAxisFlags_Invert) ? gp.BB_Grid.Max.x : gp.BB_Grid.Min.x, + HasFlag(gp.CurrentPlot->YAxis.Flags, ImAxisFlags_Invert) ? gp.BB_Grid.Min.y : gp.BB_Grid.Max.y, + HasFlag(gp.CurrentPlot->XAxis.Flags, ImAxisFlags_Invert) ? gp.BB_Grid.Min.x : gp.BB_Grid.Max.x, + HasFlag(gp.CurrentPlot->YAxis.Flags, ImAxisFlags_Invert) ? gp.BB_Grid.Max.y : gp.BB_Grid.Min.y); + + gp.M.x = (gp.PixelRange.Max.x - gp.PixelRange.Min.x) / (gp.CurrentPlot->XAxis.Max - gp.CurrentPlot->XAxis.Min); + gp.M.y = (gp.PixelRange.Max.y - gp.PixelRange.Min.y) / (gp.CurrentPlot->YAxis.Max - gp.CurrentPlot->YAxis.Min); + gp.LogDen.x = log10(gp.CurrentPlot->XAxis.Max / gp.CurrentPlot->XAxis.Min); + gp.LogDen.y = log10(gp.CurrentPlot->YAxis.Max / gp.CurrentPlot->YAxis.Min); +} + +inline ImVec2 PixelsToPlot(float x, float y) { + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PixelsToPlot() Needs to be called between BeginPlot() and EndPlot()!"); + ImVec2 plt; + plt.x = (x - gp.PixelRange.Min.x) / gp.M.x + gp.CurrentPlot->XAxis.Min; + plt.y = (y - gp.PixelRange.Min.y) / gp.M.y + gp.CurrentPlot->YAxis.Min; + if (HasFlag(gp.CurrentPlot->XAxis.Flags, ImAxisFlags_LogScale)) { + float t = (plt.x - gp.CurrentPlot->XAxis.Min) / (gp.CurrentPlot->XAxis.Max - gp.CurrentPlot->XAxis.Min); + plt.x = pow(10.0f, t * gp.LogDen.x) * gp.CurrentPlot->XAxis.Min; + } + if (HasFlag(gp.CurrentPlot->YAxis.Flags, ImAxisFlags_LogScale)) { + float t = (plt.y - gp.CurrentPlot->YAxis.Min) / (gp.CurrentPlot->YAxis.Max - gp.CurrentPlot->YAxis.Min); + plt.y = pow(10.0f, t * gp.LogDen.y) * gp.CurrentPlot->YAxis.Min; + } + return plt; +} + +inline ImVec2 PlotToPixels(float x, float y) { + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotToPixels() Needs to be called between BeginPlot() and EndPlot()!"); + ImVec2 pix; + if (HasFlag(gp.CurrentPlot->XAxis.Flags, ImAxisFlags_LogScale)) { + float t = log10(x / gp.CurrentPlot->XAxis.Min) / gp.LogDen.x; + x = ImLerp(gp.CurrentPlot->XAxis.Min, gp.CurrentPlot->XAxis.Max, t); + } + if (HasFlag(gp.CurrentPlot->YAxis.Flags, ImAxisFlags_LogScale)) { + float t = log10(y / gp.CurrentPlot->YAxis.Min) / gp.LogDen.y; + y = ImLerp(gp.CurrentPlot->YAxis.Min, gp.CurrentPlot->YAxis.Max, t); + } + pix.x = gp.PixelRange.Min.x + gp.M.x * (x - gp.CurrentPlot->XAxis.Min); + pix.y = gp.PixelRange.Min.y + gp.M.y * (y - gp.CurrentPlot->YAxis.Min); + return pix; +} + +ImVec2 PixelsToPlot(const ImVec2& pix) { + return PixelsToPlot(pix.x, pix.y); +} + +ImVec2 PlotToPixels(const ImVec2& plt) { + return PlotToPixels(plt.x, plt.y); +} + +struct Plt2PixLinLin { + static inline ImVec2 Transform(const ImVec2& plt) { return Transform(plt.x, plt.y); } + static inline ImVec2 Transform(float x, float y) { + return { gp.PixelRange.Min.x + gp.M.x * (x - gp.CurrentPlot->XAxis.Min), + gp.PixelRange.Min.y + gp.M.y * (y - gp.CurrentPlot->YAxis.Min) }; + } +}; + +struct Plt2PixLinLinObj { + Plt2PixLinLinObj() { + a = gp.PixelRange.Min.x; + b = gp.M.x; + c = gp.CurrentPlot->XAxis.Min; + d = gp.PixelRange.Min.y; + e = gp.M.y; + f = gp.CurrentPlot->YAxis.Min; + } + inline ImVec2 Transform(const ImVec2& plt) { return Transform(plt.x, plt.y); } + inline ImVec2 Transform(float x, float y) { + return { a + b * (x - c), d + e * (y - f) }; + } + float a, b, c, d, e, f; +}; + + +struct Plt2PixLogLin { + static inline ImVec2 Transform(const ImVec2& plt) { return Transform(plt.x, plt.y); } + static inline ImVec2 Transform(float x, float y) { + float t = log10(x / gp.CurrentPlot->XAxis.Min) / gp.LogDen.x; + x = ImLerp(gp.CurrentPlot->XAxis.Min, gp.CurrentPlot->XAxis.Max, t); + return { gp.PixelRange.Min.x + gp.M.x * (x - gp.CurrentPlot->XAxis.Min), + gp.PixelRange.Min.y + gp.M.y * (y - gp.CurrentPlot->YAxis.Min) }; + } +}; + +struct Plt2PixLinLog { + static inline ImVec2 Transform(const ImVec2& plt) { return Transform(plt.x, plt.y); } + static inline ImVec2 Transform(float x, float y) { + float t = log10(y / gp.CurrentPlot->YAxis.Min) / gp.LogDen.y; + y = ImLerp(gp.CurrentPlot->YAxis.Min, gp.CurrentPlot->YAxis.Max, t); + return { gp.PixelRange.Min.x + gp.M.x * (x - gp.CurrentPlot->XAxis.Min), + gp.PixelRange.Min.y + gp.M.y * (y - gp.CurrentPlot->YAxis.Min) }; + } +}; + +struct Plt2PixLogLog { + static inline ImVec2 Transform(const ImVec2& plt) { return Transform(plt.x, plt.y); } + static inline ImVec2 Transform(float x, float y) { + float t = log10(x / gp.CurrentPlot->XAxis.Min) / gp.LogDen.x; + x = ImLerp(gp.CurrentPlot->XAxis.Min, gp.CurrentPlot->XAxis.Max, t); + t = log10(y / gp.CurrentPlot->YAxis.Min) / gp.LogDen.y; + y = ImLerp(gp.CurrentPlot->YAxis.Min, gp.CurrentPlot->YAxis.Max, t); + return { gp.PixelRange.Min.x + gp.M.x * (x - gp.CurrentPlot->XAxis.Min), + gp.PixelRange.Min.y + gp.M.y * (y - gp.CurrentPlot->YAxis.Min) }; + } +}; + +//----------------------------------------------------------------------------- +// Legend Utils +//----------------------------------------------------------------------------- + +ImPlotItem* RegisterItem(const char* label_id) { + ImGuiID id = ImGui::GetID(label_id); + ImPlotItem* item = gp.CurrentPlot->Items.GetOrAddByKey(id); + int idx = gp.CurrentPlot->Items.GetIndex(item); + item->ID = id; + gp.LegendIndices.push_back(idx); + item->NameOffset = gp.LegendLabels.size(); + gp.LegendLabels.append(label_id, label_id + strlen(label_id) + 1); + if (item->Show) + gp.VisibleItemCount++; + return item; +} + +int GetLegendCount() { + return gp.LegendIndices.size(); +} + +ImPlotItem* GetLegendItem(int i) { + return gp.CurrentPlot->Items.GetByIndex(gp.LegendIndices[i]); +} + +const char* GetLegendLabel(int i) { + ImPlotItem* item = gp.CurrentPlot->Items.GetByIndex(gp.LegendIndices[i]); + IM_ASSERT(item->NameOffset != -1 && item->NameOffset < gp.LegendLabels.Buf.Size); + return gp.LegendLabels.Buf.Data + item->NameOffset; +} + +//----------------------------------------------------------------------------- // Tick Utils -//============================================================================= +//----------------------------------------------------------------------------- inline void GetTicks(float tMin, float tMax, int nMajor, int nMinor, bool logscale, ImVector &out) { out.shrink(0); @@ -473,11 +556,9 @@ inline void LabelTicks(ImVector &ticks, bool scientific, ImGuiTextBuffer } } -} // private namespace - -//============================================================================= +//----------------------------------------------------------------------------- // BeginPlot() -//============================================================================= +//----------------------------------------------------------------------------- bool BeginPlot(const char* title, const char* x_label, const char* y_label, const ImVec2& size, ImPlotFlags flags, ImAxisFlags x_flags, ImAxisFlags y_flags) { @@ -492,13 +573,9 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons return false; } - ImGui::BeginChild(title, size); - Window = ImGui::GetCurrentWindow(); - Window->ScrollMax.y = 1.0f; const ImGuiID ID = Window->GetID(title); - ImDrawList & DrawList = *Window->DrawList; const ImGuiStyle &Style = G.Style; - const ImGuiIO & IO = GetIO(); + const ImGuiIO & IO = GetIO(); bool just_created = gp.Plots.GetByKey(ID) == NULL; gp.CurrentPlot = gp.Plots.GetOrAddByKey(ID); @@ -510,6 +587,15 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.YAxis.Flags = y_flags; } + // capture scroll with a child region + if (!HasFlag(plot.Flags, ImPlotFlags_NoChild)) { + ImGui::BeginChild(title, size); + Window = ImGui::GetCurrentWindow(); + Window->ScrollMax.y = 1.0f; + } + + ImDrawList &DrawList = *Window->DrawList; + // NextPlotData ----------------------------------------------------------- if (gp.NextPlotData.HasXRange) { @@ -607,7 +693,8 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons if (!ItemAdd(gp.BB_Frame, 0, &gp.BB_Frame)) { gp.NextPlotData = ImNextPlotData(); gp.CurrentPlot = NULL; - ImGui::EndChild(); + if (!HasFlag(plot.Flags, ImPlotFlags_NoChild)) + ImGui::EndChild(); return false; } gp.Hov_Frame = ItemHoverable(gp.BB_Frame, ID); @@ -668,9 +755,9 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons bb_query.Max += gp.BB_Grid.Min; } else { - gp.UpdateTransforms(); - ImVec2 p1 = gp.ToPixels(plot.QueryRange.XMin, plot.QueryRange.YMin); - ImVec2 p2 = gp.ToPixels(plot.QueryRange.XMax, plot.QueryRange.YMax); + UpdateTransformCache(); + ImVec2 p1 = PlotToPixels(plot.QueryRange.XMin, plot.QueryRange.YMin); + ImVec2 p2 = PlotToPixels(plot.QueryRange.XMax, plot.QueryRange.YMax); bb_query.Min = ImVec2(ImMin(p1.x,p2.x), ImMin(p1.y,p2.y)); bb_query.Max = ImVec2(ImMax(p1.x,p2.x), ImMax(p1.y,p2.y)); } @@ -684,12 +771,12 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons if (plot.DraggingQuery) { SetMouseCursor(ImGuiMouseCursor_ResizeAll); if (!HasFlag(plot.Flags, ImPlotFlags_PixelQuery)) { - ImVec2 p1 = gp.ToPixels(plot.QueryRange.XMin, plot.QueryRange.YMin); - ImVec2 p2 = gp.ToPixels(plot.QueryRange.XMax, plot.QueryRange.YMax); + ImVec2 p1 = PlotToPixels(plot.QueryRange.XMin, plot.QueryRange.YMin); + ImVec2 p2 = PlotToPixels(plot.QueryRange.XMax, plot.QueryRange.YMax); plot.QueryRect.Min = ImVec2(ImMin(p1.x,p2.x), ImMin(p1.y,p2.y)) + IO.MouseDelta; plot.QueryRect.Max = ImVec2(ImMax(p1.x,p2.x), ImMax(p1.y,p2.y)) + IO.MouseDelta; - p1 = gp.FromPixels(plot.QueryRect.Min); - p2 = gp.FromPixels(plot.QueryRect.Max); + p1 = PixelsToPlot(plot.QueryRect.Min); + p2 = PixelsToPlot(plot.QueryRect.Max); plot.QueryRect.Min -= gp.BB_Grid.Min; plot.QueryRect.Max -= gp.BB_Grid.Min; plot.QueryRange.XMin = ImMin(p1.x, p2.x); @@ -722,9 +809,9 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } // do drag if (plot.XAxis.Dragging || plot.YAxis.Dragging) { - gp.UpdateTransforms(); - ImVec2 plot_tl = gp.FromPixels(gp.BB_Grid.Min - IO.MouseDelta); - ImVec2 plot_br = gp.FromPixels(gp.BB_Grid.Max - IO.MouseDelta); + UpdateTransformCache(); + ImVec2 plot_tl = PixelsToPlot(gp.BB_Grid.Min - IO.MouseDelta); + ImVec2 plot_br = PixelsToPlot(gp.BB_Grid.Max - IO.MouseDelta); if (!lock_x && plot.XAxis.Dragging) { if (!lock_x_min) plot.XAxis.Min = flip_x ? plot_br.x : plot_tl.x; @@ -755,14 +842,14 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // SCROLL INPUT ----------------------------------------------------------- if (gp.Hov_Frame && (hov_x_axis_region || hov_y_axis_region) && IO.MouseWheel != 0) { - gp.UpdateTransforms(); + UpdateTransformCache(); float zoom_rate = 0.1f; if (IO.MouseWheel > 0) zoom_rate = (-zoom_rate) / (1.0f + (2.0f * zoom_rate)); float tx = Remap(IO.MousePos.x, gp.BB_Grid.Min.x, gp.BB_Grid.Max.x, 0, 1); float ty = Remap(IO.MousePos.y, gp.BB_Grid.Min.y, gp.BB_Grid.Max.y, 0, 1); - ImVec2 plot_tl = gp.FromPixels(gp.BB_Grid.Min - gp.BB_Grid.GetSize() * ImVec2(tx * zoom_rate, ty * zoom_rate)); - ImVec2 plot_br = gp.FromPixels(gp.BB_Grid.Max + gp.BB_Grid.GetSize() * ImVec2((1 - tx) * zoom_rate, (1 - ty) * zoom_rate)); + ImVec2 plot_tl = PixelsToPlot(gp.BB_Grid.Min - gp.BB_Grid.GetSize() * ImVec2(tx * zoom_rate, ty * zoom_rate)); + ImVec2 plot_br = PixelsToPlot(gp.BB_Grid.Max + gp.BB_Grid.GetSize() * ImVec2((1 - tx) * zoom_rate, (1 - ty) * zoom_rate)); if (hov_x_axis_region && !lock_x) { if (!lock_x_min) plot.XAxis.Min = flip_x ? plot_br.x : plot_tl.x; @@ -781,11 +868,11 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // confirm selection if (plot.Selecting && (IO.MouseReleased[1] || !IO.MouseDown[1])) { - gp.UpdateTransforms(); + UpdateTransformCache(); ImVec2 select_size = plot.SelectStart - IO.MousePos; if (HasFlag(plot.Flags, ImPlotFlags_Selection) && ImFabs(select_size.x) > 2 && ImFabs(select_size.y) > 2) { - ImVec2 p1 = gp.FromPixels(plot.SelectStart); - ImVec2 p2 = gp.FromPixels(IO.MousePos); + ImVec2 p1 = PixelsToPlot(plot.SelectStart); + ImVec2 p2 = PixelsToPlot(IO.MousePos); if (!lock_x_min && !IO.KeyAlt) plot.XAxis.Min = ImMin(p1.x, p2.x); if (!lock_x_max && !IO.KeyAlt) @@ -812,13 +899,13 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } // update query if (plot.Querying) { - gp.UpdateTransforms(); + UpdateTransformCache(); plot.QueryRect.Min.x = IO.KeyAlt ? gp.BB_Grid.Min.x : ImMin(plot.QueryStart.x, IO.MousePos.x); plot.QueryRect.Max.x = IO.KeyAlt ? gp.BB_Grid.Max.x : ImMax(plot.QueryStart.x, IO.MousePos.x); plot.QueryRect.Min.y = IO.KeyShift ? gp.BB_Grid.Min.y : ImMin(plot.QueryStart.y, IO.MousePos.y); plot.QueryRect.Max.y = IO.KeyShift ? gp.BB_Grid.Max.y : ImMax(plot.QueryStart.y, IO.MousePos.y); - ImVec2 p1 = gp.FromPixels(plot.QueryRect.Min); - ImVec2 p2 = gp.FromPixels(plot.QueryRect.Max); + ImVec2 p1 = PixelsToPlot(plot.QueryRect.Min); + ImVec2 p2 = PixelsToPlot(plot.QueryRect.Max); plot.QueryRect.Min -= gp.BB_Grid.Min; plot.QueryRect.Max -= gp.BB_Grid.Min; plot.QueryRange.XMin = ImMin(p1.x, p2.x); @@ -881,10 +968,10 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons if ((IO.MouseClicked[0] || IO.MouseClicked[1]) && gp.Hov_Frame) FocusWindow(GetCurrentWindow()); - gp.UpdateTransforms(); + UpdateTransformCache(); // set mouse position - gp.LastMousePos = gp.FromPixels(IO.MousePos); + gp.LastMousePos = PixelsToPlot(IO.MousePos); // RENDER ----------------------------------------------------------------- @@ -892,16 +979,16 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons DrawList.AddRectFilled(gp.BB_Grid.Min, gp.BB_Grid.Max, gp.Col_Bg); // render axes - ImGui::PushClipRect(gp.BB_Grid.Min, gp.BB_Grid.Max, true); + PushPlotClipRect(); // transform ticks if (gp.RenderX) { for (auto& xt : gp.XTicks) - xt.PixelPos = gp.ToPixels((float)xt.PlotPos, 0).x; + xt.PixelPos = PlotToPixels((float)xt.PlotPos, 0).x; } if (gp.RenderY) { for (auto& yt : gp.YTicks) - yt.PixelPos = gp.ToPixels(0, (float)yt.PlotPos).y; + yt.PixelPos = PlotToPixels(0, (float)yt.PlotPos).y; } // render grid @@ -915,7 +1002,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons DrawList.AddLine({gp.BB_Grid.Min.x, yt.PixelPos}, {gp.BB_Grid.Max.x, yt.PixelPos}, yt.Major ? gp.Col_YMajor : gp.Col_YMinor, 1); } - ImGui::PopClipRect(); + PopPlotClipRect(); // render title if (title_size.x > 0.0f) { @@ -924,7 +1011,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // render labels if (HasFlag(plot.XAxis.Flags, ImAxisFlags_TickLabels)) { - ImGui::PushClipRect(gp.BB_Frame.Min, gp.BB_Frame.Max, true); + PushClipRect(gp.BB_Frame.Min, gp.BB_Frame.Max, true); for (auto &xt : gp.XTicks) { if (xt.RenderLabel && xt.PixelPos >= gp.BB_Grid.Min.x - 1 && xt.PixelPos <= gp.BB_Grid.Max.x + 1) DrawList.AddText({xt.PixelPos - xt.Size.x * 0.5f, gp.BB_Grid.Max.y + txt_off}, gp.Col_XTxt, gp.XTickLabels.Buf.Data + xt.TextOffset); @@ -938,7 +1025,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons DrawList.AddText(xLabel_pos, gp.Col_XTxt, x_label); } if (HasFlag(plot.YAxis.Flags, ImAxisFlags_TickLabels)) { - ImGui::PushClipRect(gp.BB_Frame.Min, gp.BB_Frame.Max, true); + PushClipRect(gp.BB_Frame.Min, gp.BB_Frame.Max, true); for (auto &yt : gp.YTicks) { if (yt.RenderLabel && yt.PixelPos >= gp.BB_Grid.Min.y - 1 && yt.PixelPos <= gp.BB_Grid.Max.y + 1) DrawList.AddText({gp.BB_Grid.Min.x - txt_off - yt.Size.x, yt.PixelPos - 0.5f * yt.Size.y}, gp.Col_YTxt, gp.YTickLabels.Buf.Data + yt.TextOffset); @@ -955,9 +1042,6 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // push plot ID into stack PushID(ID); - // Deactivate all existing items - for (int i = 0; i < plot.Items.GetSize(); ++i) - plot.Items.GetByIndex(i)->Active = false; // reset items count gp.VisibleItemCount = 0; // reset extents @@ -966,13 +1050,13 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons gp.Extents.Max.x = -INFINITY; gp.Extents.Max.y = -INFINITY; // clear item names - gp._LegendLabels.Buf.resize(0); + gp.LegendLabels.Buf.resize(0); return true; } -//============================================================================= +//----------------------------------------------------------------------------- // Context Menu -//============================================================================= +//----------------------------------------------------------------------------- inline void AxisMenu(ImPlotAxis& Axis) { ImGui::PushItemWidth(75); @@ -1071,9 +1155,9 @@ void PlotContextMenu(ImPlot& plot) { } -//============================================================================= +//----------------------------------------------------------------------------- // EndPlot() -//============================================================================= +//----------------------------------------------------------------------------- void EndPlot() { @@ -1101,7 +1185,7 @@ void EndPlot() { // FINAL RENDER ----------------------------------------------------------- - PushClipRect(gp.BB_Grid.Min, gp.BB_Grid.Max, true); + PushPlotClipRect(); // render ticks if (HasFlag(plot.XAxis.Flags, ImAxisFlags_TickMarks)) { @@ -1143,8 +1227,8 @@ void EndPlot() { } } else if (plot.Queried) { - ImVec2 p1 = gp.ToPixels(plot.QueryRange.XMin, plot.QueryRange.YMin); - ImVec2 p2 = gp.ToPixels(plot.QueryRange.XMax, plot.QueryRange.YMax); + ImVec2 p1 = PlotToPixels(plot.QueryRange.XMin, plot.QueryRange.YMin); + ImVec2 p2 = PlotToPixels(plot.QueryRange.XMax, plot.QueryRange.YMax); ImVec2 Min(ImMin(p1.x,p2.x), ImMin(p1.y,p2.y)); ImVec2 Max(ImMax(p1.x,p2.x), ImMax(p1.y,p2.y)); DrawList.AddRectFilled(Min, Max, gp.Col_QryBg); @@ -1157,13 +1241,13 @@ void EndPlot() { const ImVec2 legend_padding(5, 5); const float legend_icon_size = txt_ht; ImRect legend_content_bb; - int nItems = gp.GetLegendCount(); + int nItems = GetLegendCount(); bool hov_legend = false; if (HasFlag(plot.Flags, ImPlotFlags_Legend) && nItems > 0) { // get max width float max_label_width = 0; for (int i = 0; i < nItems; ++i) { - const char* label = gp.GetLegendLabel(i); + const char* label = GetLegendLabel(i); auto labelWidth = CalcTextSize(label, NULL, true); max_label_width = labelWidth.x > max_label_width ? labelWidth.x : max_label_width; } @@ -1175,7 +1259,7 @@ void EndPlot() { DrawList.AddRect(plot.BB_Legend.Min, plot.BB_Legend.Max, gp.Col_Border); // render each legend item for (int i = 0; i < nItems; ++i) { - ImPlotItem* item = gp.GetLegendItem(i); + ImPlotItem* item = GetLegendItem(i); ImRect icon_bb; icon_bb.Min = legend_content_bb.Min + legend_padding + ImVec2(0, i * txt_ht) + ImVec2(2, 2); icon_bb.Max = legend_content_bb.Min + legend_padding + ImVec2(0, i * txt_ht) + ImVec2(legend_icon_size - 2, legend_icon_size - 2); @@ -1201,7 +1285,7 @@ void EndPlot() { iconColor = item->Show ? GetColorU32(item->Color) : gp.Col_TxtDis; } DrawList.AddRectFilled(icon_bb.Min, icon_bb.Max, iconColor, 1); - const char* label = gp.GetLegendLabel(i); + const char* label = GetLegendLabel(i); const char* text_display_end = FindRenderedTextEnd(label, NULL); if (label != text_display_end) DrawList.AddText(legend_content_bb.Min + legend_padding + ImVec2(legend_icon_size, i * txt_ht), @@ -1237,7 +1321,7 @@ void EndPlot() { DrawList.AddText(pos, gp.Col_Txt, buffer); } - ImGui::PopClipRect(); + PopPlotClipRect(); // render border DrawList.AddRect(gp.BB_Grid.Min, gp.BB_Grid.Max, gp.Col_Border); @@ -1266,7 +1350,7 @@ void EndPlot() { // CLEANUP ---------------------------------------------------------------- // Reset legend items - gp._LegendIndices.shrink(0); + gp.LegendIndices.shrink(0); // Null current plot/data gp.CurrentPlot = NULL; // Reset next plot data @@ -1274,12 +1358,13 @@ void EndPlot() { // Pop PushID at the end of BeginPlot PopID(); // End child window - ImGui::EndChild(); + if (!HasFlag(plot.Flags, ImPlotFlags_NoChild)) + ImGui::EndChild(); } -//============================================================================= +//----------------------------------------------------------------------------- // MISC API -//============================================================================= +//----------------------------------------------------------------------------- void SetNextPlotRange(float x_min, float x_max, float y_min, float y_max, ImGuiCond cond) { SetNextPlotRangeX(x_min, x_max, cond); @@ -1312,16 +1397,6 @@ ImVec2 GetPlotSize() { return gp.BB_Grid.GetSize(); } -ImVec2 PixelsToPlot(const ImVec2& pix) { - IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PixelsToPlot() Needs to be called between BeginPlot() and EndPlot()!"); - return gp.FromPixels(pix); -} - -ImVec2 PlotToPixels(const ImVec2& plt) { - IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotToPixels() Needs to be called between BeginPlot() and EndPlot()!"); - return gp.ToPixels(plt); -} - void PushPlotClipRect() { IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PushPlotClipRect() Needs to be called between BeginPlot() and EndPlot()!"); PushClipRect(gp.BB_Grid.Min, gp.BB_Grid.Max, true); @@ -1361,9 +1436,9 @@ ImPlotRange GetPlotQuery() { IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "GetPlotQuery() Needs to be called between BeginPlot() and EndPlot()!"); ImPlot& plot = *gp.CurrentPlot; if (HasFlag(plot.Flags, ImPlotFlags_PixelQuery)) { - gp.UpdateTransforms(); - ImVec2 p1 = gp.FromPixels(plot.QueryRect.Min + gp.BB_Grid.Min); - ImVec2 p2 = gp.FromPixels(plot.QueryRect.Max + gp.BB_Grid.Min); + UpdateTransformCache(); + ImVec2 p1 = PixelsToPlot(plot.QueryRect.Min + gp.BB_Grid.Min); + ImVec2 p2 = PixelsToPlot(plot.QueryRect.Max + gp.BB_Grid.Min); plot.QueryRange.XMin = ImMin(p1.x, p2.x); plot.QueryRange.XMax = ImMax(p1.x, p2.x); plot.QueryRange.YMin = ImMin(p1.y, p2.y); @@ -1372,9 +1447,9 @@ ImPlotRange GetPlotQuery() { return plot.QueryRange; } -//============================================================================= +//----------------------------------------------------------------------------- // STYLING -//============================================================================= +//----------------------------------------------------------------------------- struct ImPlotStyleVarInfo { ImGuiDataType Type; @@ -1503,9 +1578,9 @@ void PopPlotStyleVar(int count) { } } -//============================================================================= -// MARKERS -//============================================================================= +//----------------------------------------------------------------------------- +// RENDERING FUNCTIONS +//----------------------------------------------------------------------------- #define SQRT_1_2 0.70710678118f #define SQRT_3_2 0.86602540378f @@ -1593,61 +1668,169 @@ inline void MarkerCross(ImDrawList& DrawList, const ImVec2& c, float s, bool out DrawList.AddLine(marker[1], marker[3], col_outline, weight); } -//============================================================================= -// PLOTTERS -//============================================================================= +template +inline void RenderMarkers(ImDrawList& DrawList, Getter getter, int count, int offset, bool rend_mk_line, ImU32 col_mk_line, bool rend_mk_fill, ImU32 col_mk_fill, bool cull) { +int idx = offset; + for (int i = 0; i < count; ++i) { + ImVec2 c; + c = Transformer::Transform(getter(idx)); + idx = (idx + 1) % count; + if (!cull || gp.BB_Grid.Contains(c)) { + // TODO: Optimize the loop and if statements, this is atrocious + if (HasFlag(gp.Style.Marker, ImMarker_Circle)) + MakerCircle(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (HasFlag(gp.Style.Marker, ImMarker_Square)) + MarkerSquare(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (HasFlag(gp.Style.Marker, ImMarker_Diamond)) + MarkerDiamond(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (HasFlag(gp.Style.Marker, ImMarker_Up)) + MarkerUp(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (HasFlag(gp.Style.Marker, ImMarker_Down)) + MarkerDown(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (HasFlag(gp.Style.Marker, ImMarker_Left)) + MarkerLeft(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (HasFlag(gp.Style.Marker, ImMarker_Right)) + MarkerRight(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (HasFlag(gp.Style.Marker, ImMarker_Cross)) + MarkerCross(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (HasFlag(gp.Style.Marker, ImMarker_Plus)) + MarkerPlus(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + if (HasFlag(gp.Style.Marker, ImMarker_Asterisk)) + MarkerAsterisk(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); + } + } +} -struct ImPlotGetterData -{ - const float* Xs; - const float* Ys; - const float* ErrNeg; - const float* ErrPos; - int Stride; - float XShift; - float YShift; - ImPlotGetterData(const float* xs, const float* ys, int stride, - float x_shift = 0, float y_shift = 0, - const float* err_neg = NULL, const float* err_pos = NULL) { - Xs = xs; - Ys = ys; - Stride = stride; - XShift = x_shift; - YShift = y_shift; - ErrNeg = err_neg; - ErrPos = err_pos; +inline void RenderLine(ImDrawList& DrawList, const ImVec2& p1, const ImVec2& p2, float line_weight, ImU32 col_line, ImVec2 uv) { + // http://assemblyrequired.crashworks.org/timing-square-root/ + float dx = p2.x - p1.x; + float dy = p2.y - p1.y; + IM_NORMALIZE2F_OVER_ZERO(dx, dy); + dx *= (line_weight * 0.5f); + dy *= (line_weight * 0.5f); + DrawList._VtxWritePtr[0].pos.x = p1.x + dy; + DrawList._VtxWritePtr[0].pos.y = p1.y - dx; + DrawList._VtxWritePtr[0].uv = uv; + DrawList._VtxWritePtr[0].col = col_line; + DrawList._VtxWritePtr[1].pos.x = p2.x + dy; + DrawList._VtxWritePtr[1].pos.y = p2.y - dx; + DrawList._VtxWritePtr[1].uv = uv; + DrawList._VtxWritePtr[1].col = col_line; + DrawList._VtxWritePtr[2].pos.x = p2.x - dy; + DrawList._VtxWritePtr[2].pos.y = p2.y + dx; + DrawList._VtxWritePtr[2].uv = uv; + DrawList._VtxWritePtr[2].col = col_line; + DrawList._VtxWritePtr[3].pos.x = p1.x - dy; + DrawList._VtxWritePtr[3].pos.y = p1.y + dx; + DrawList._VtxWritePtr[3].uv = uv; + DrawList._VtxWritePtr[3].col = col_line; + DrawList._VtxWritePtr += 4; + DrawList._IdxWritePtr[0] = (ImDrawIdx)(DrawList._VtxCurrentIdx); + DrawList._IdxWritePtr[1] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 1); + DrawList._IdxWritePtr[2] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 2); + DrawList._IdxWritePtr[3] = (ImDrawIdx)(DrawList._VtxCurrentIdx); + DrawList._IdxWritePtr[4] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 2); + DrawList._IdxWritePtr[5] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 3); + DrawList._IdxWritePtr += 6; + DrawList._VtxCurrentIdx += 4; +} + +inline void RenderLineAA(ImDrawList& DrawList, const ImVec2& p1, const ImVec2& p2, float line_weight, ImU32 col_line) { + DrawList.AddLine(p1, p2, col_line, line_weight); +} + +template +inline void RenderLines(ImDrawList& DrawList, Getter getter, int count, int offset, float line_weight, ImU32 col_line, bool cull) { +// render line segments + const int segments = count - 1; + int i1 = offset; + ImVec2 p1, p2; + if (HasFlag(gp.CurrentPlot->Flags, ImPlotFlags_AntiAliased)) { + for (int s = 0; s < segments; ++s) { + const int i2 = (i1 + 1) % count; + p1 = Transformer::Transform(getter(i1)); + p2 = Transformer::Transform(getter(i2)); + i1 = i2; + if (!cull || gp.BB_Grid.Contains(p1) || gp.BB_Grid.Contains(p2)) + RenderLineAA(DrawList, p1, p2, line_weight, col_line); + } } -}; + else { + const ImVec2 uv = DrawList._Data->TexUvWhitePixel; + DrawList.PrimReserve(segments * 6, segments * 4); + int segments_culled = 0; + for (int s = 0; s < segments; ++s) { + const int i2 = (i1 + 1) % count; + p1 = Transformer::Transform(getter(i1)); + p2 = Transformer::Transform(getter(i2)); + i1 = i2; + if (!cull || gp.BB_Grid.Contains(p1) || gp.BB_Grid.Contains(p2)) + RenderLine(DrawList, p1, p2, line_weight, col_line, uv); + else + segments_culled++; + } + if (segments_culled > 0) + DrawList.PrimUnreserve(segments_culled * 6, segments_culled * 4); + } +} -inline float ImStrideIndex(const float* data, int idx, int stride) { +//----------------------------------------------------------------------------- +// DATA GETTERS +//----------------------------------------------------------------------------- + +inline float StrideIndex(const float* data, int idx, int stride) { return *(const float*)(const void*)((const unsigned char*)data + (size_t)idx * stride); } -static ImVec2 ImPlotGetter1D(void* data, int idx) { - ImPlotGetterData* data_1d = (ImPlotGetterData*)data; - return ImVec2((float)idx, ImStrideIndex(data_1d->Ys, idx, data_1d->Stride)); -} +struct GetterYs { + GetterYs(const float* ys, int stride) { Ys = ys; Stride = stride; } + const float* Ys; + int Stride; + inline ImVec2 operator()(int idx) { + return ImVec2((float)idx, StrideIndex(Ys, idx, Stride)); + } +}; -static ImVec2 ImPlotGetter2D(void* data, int idx) { - ImPlotGetterData* data_2d = (ImPlotGetterData*)data; - return ImVec2(ImStrideIndex(data_2d->Xs, idx, data_2d->Stride), ImStrideIndex(data_2d->Ys, idx, data_2d->Stride)); -} +struct Getter2D { + Getter2D(const float* xs, const float* ys, int stride) { Xs = xs; Ys = ys; Stride = stride; } + const float* Xs; + const float* Ys; + int Stride; + inline ImVec2 operator()(int idx) { + return ImVec2(StrideIndex(Xs, idx, Stride), StrideIndex(Ys, idx, Stride)); + } +}; -void Plot(const char* label_id, const float* values, int count, int offset, int stride) { - ImPlotGetterData data(nullptr, values, stride); - Plot(label_id, &ImPlotGetter1D, (void*)&data, count, offset); -} +struct GetterImVec2 { + GetterImVec2(const ImVec2* data) { Data = data; } + inline ImVec2 operator()(int idx) { return Data[idx]; } + const ImVec2* Data; +}; -void Plot(const char* label_id, const float* xs, const float* ys, int count, int offset, int stride) { - ImPlotGetterData data(xs,ys,stride); - Plot(label_id, &ImPlotGetter2D, (void*)&data, count, offset); -} +struct GetterFuncPtrImVec2 { + GetterFuncPtrImVec2(ImVec2 (*g)(void* data, int idx), void* d) { getter = g; data = d;} + ImVec2 operator()(int idx) { return getter(data, idx); } + ImVec2 (*getter)(void* data, int idx); + void* data; +}; -void Plot(const char* label_id, ImVec2 (*getter)(void* data, int idx), void* data, int count, int offset) +struct GetterFuncPtrImVec4 { + GetterFuncPtrImVec4(ImVec4 (*g)(void* data, int idx), void* d) { getter = g; data = d;} + ImVec4 operator()(int idx) { return getter(data, idx); } + ImVec4 (*getter)(void* data, int idx); + void* data; +}; + +//----------------------------------------------------------------------------- +// PLOT +//----------------------------------------------------------------------------- + +template +inline void PlotEx(const char* label_id, Getter getter, int count, int offset) { IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "Plot() Needs to be called between BeginPlot() and EndPlot()!"); - ImPlotItem* item = gp.RegisterItem(label_id); + ImPlotItem* item = RegisterItem(label_id); if (!item->Show) return; @@ -1661,144 +1844,88 @@ void Plot(const char* label_id, ImVec2 (*getter)(void* data, int idx), void* dat ImU32 col_mk_line = gp.Style.Colors[ImPlotCol_MarkerOutline].w == -1 ? col_line : GetColorU32(gp.Style.Colors[ImPlotCol_MarkerOutline]); ImU32 col_mk_fill = gp.Style.Colors[ImPlotCol_MarkerFill].w == -1 ? col_line : GetColorU32(gp.Style.Colors[ImPlotCol_MarkerFill]); + const float line_weight = item->Highlight ? gp.Style.LineWeight * 2 : gp.Style.LineWeight; + if (gp.Style.Colors[ImPlotCol_Line].w != -1) item->Color = gp.Style.Colors[ImPlotCol_Line]; + bool cull = HasFlag(gp.CurrentPlot->Flags, ImPlotFlags_CullData); + // find data extents if (gp.FitThisFrame) { for (int i = 0; i < count; ++i) { - ImVec2 p = getter(data, i); - gp.FitPoint(p); + ImVec2 p = getter(i); + FitPoint(p); } } - - ImGui::PushClipRect(gp.BB_Grid.Min, gp.BB_Grid.Max, true); - bool cull = HasFlag(gp.CurrentPlot->Flags, ImPlotFlags_CullData); - - const float line_weight = item->Highlight ? gp.Style.LineWeight * 2 : gp.Style.LineWeight; - - // render line segments + PushPlotClipRect(); if (count > 1 && rend_line) { - const int segments = count - 1; - int i1 = offset; - ImVec2 p1, p2; - if (HasFlag(gp.CurrentPlot->Flags, ImPlotFlags_AntiAliased)) { - for (int s = 0; s < segments; ++s) { - const int i2 = (i1 + 1) % count; - p1 = gp.ToPixels(getter(data, i1)); - p2 = gp.ToPixels(getter(data, i2)); - i1 = i2; - if (!cull || gp.BB_Grid.Contains(p1) || gp.BB_Grid.Contains(p2)) - DrawList.AddLine(p1, p2, col_line, line_weight); - } - } - else { - const int idx_count = segments * 6; - const int vtx_count = segments * 4; - const ImVec2 uv = DrawList._Data->TexUvWhitePixel; - DrawList.PrimReserve(idx_count, vtx_count); - int segments_culled = 0; - for (int s = 0; s < segments; ++s) { - const int i2 = (i1 + 1) % count; - p1 = gp.ToPixels(getter(data, i1)); - p2 = gp.ToPixels(getter(data, i2)); - i1 = i2; - if (!cull || gp.BB_Grid.Contains(p1) || gp.BB_Grid.Contains(p2)) { - float dx = p2.x - p1.x; - float dy = p2.y - p1.y; - IM_NORMALIZE2F_OVER_ZERO(dx, dy); - dx *= (line_weight * 0.5f); - dy *= (line_weight * 0.5f); - DrawList._VtxWritePtr[0].pos.x = p1.x + dy; - DrawList._VtxWritePtr[0].pos.y = p1.y - dx; - DrawList._VtxWritePtr[0].uv = uv; - DrawList._VtxWritePtr[0].col = col_line; - DrawList._VtxWritePtr[1].pos.x = p2.x + dy; - DrawList._VtxWritePtr[1].pos.y = p2.y - dx; - DrawList._VtxWritePtr[1].uv = uv; - DrawList._VtxWritePtr[1].col = col_line; - DrawList._VtxWritePtr[2].pos.x = p2.x - dy; - DrawList._VtxWritePtr[2].pos.y = p2.y + dx; - DrawList._VtxWritePtr[2].uv = uv; - DrawList._VtxWritePtr[2].col = col_line; - DrawList._VtxWritePtr[3].pos.x = p1.x - dy; - DrawList._VtxWritePtr[3].pos.y = p1.y + dx; - DrawList._VtxWritePtr[3].uv = uv; - DrawList._VtxWritePtr[3].col = col_line; - DrawList._VtxWritePtr += 4; - DrawList._IdxWritePtr[0] = (ImDrawIdx)(DrawList._VtxCurrentIdx); - DrawList._IdxWritePtr[1] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 1); - DrawList._IdxWritePtr[2] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 2); - DrawList._IdxWritePtr[3] = (ImDrawIdx)(DrawList._VtxCurrentIdx); - DrawList._IdxWritePtr[4] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 2); - DrawList._IdxWritePtr[5] = (ImDrawIdx)(DrawList._VtxCurrentIdx + 3); - DrawList._IdxWritePtr += 6; - DrawList._VtxCurrentIdx += 4; - } - else { - segments_culled++; - } - } - if (segments_culled > 0) - DrawList.PrimUnreserve(segments_culled * 6, segments_culled * 4); - } - } - + if (HasFlag(gp.CurrentPlot->XAxis.Flags, ImAxisFlags_LogScale) && HasFlag(gp.CurrentPlot->YAxis.Flags, ImAxisFlags_LogScale)) + RenderLines(DrawList, getter, count, offset, line_weight, col_line, cull); + else if (HasFlag(gp.CurrentPlot->XAxis.Flags, ImAxisFlags_LogScale)) + RenderLines(DrawList, getter, count, offset, line_weight, col_line, cull); + else if (HasFlag(gp.CurrentPlot->YAxis.Flags, ImAxisFlags_LogScale)) + RenderLines(DrawList, getter, count, offset, line_weight, col_line, cull); + else + RenderLines(DrawList, getter, count, offset, line_weight, col_line, cull); + } // render markers if (gp.Style.Marker != ImMarker_None) { - int idx = offset; - for (int i = 0; i < count; ++i) { - ImVec2 c; - c = gp.ToPixels(getter(data, idx)); - idx = (idx + 1) % count; - if (!cull || gp.BB_Grid.Contains(c)) { - // TODO: Optimize the loop and if statements, this is atrocious - if (HasFlag(gp.Style.Marker, ImMarker_Circle)) - MakerCircle(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImMarker_Square)) - MarkerSquare(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImMarker_Diamond)) - MarkerDiamond(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImMarker_Up)) - MarkerUp(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImMarker_Down)) - MarkerDown(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImMarker_Left)) - MarkerLeft(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImMarker_Right)) - MarkerRight(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImMarker_Cross)) - MarkerCross(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImMarker_Plus)) - MarkerPlus(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - if (HasFlag(gp.Style.Marker, ImMarker_Asterisk)) - MarkerAsterisk(DrawList, c, gp.Style.MarkerSize, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, gp.Style.MarkerWeight); - } - } + if (HasFlag(gp.CurrentPlot->XAxis.Flags, ImAxisFlags_LogScale) && HasFlag(gp.CurrentPlot->YAxis.Flags, ImAxisFlags_LogScale)) + RenderMarkers(DrawList, getter, count, offset, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, cull); + else if (HasFlag(gp.CurrentPlot->XAxis.Flags, ImAxisFlags_LogScale)) + RenderMarkers(DrawList, getter, count, offset, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, cull); + else if (HasFlag(gp.CurrentPlot->YAxis.Flags, ImAxisFlags_LogScale)) + RenderMarkers(DrawList, getter, count, offset, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, cull); + else + RenderMarkers(DrawList, getter, count, offset, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, cull); } - ImGui::PopClipRect(); + PopPlotClipRect(); } -static ImVec2 ImPlotGetterBarV(void* data, int idx) { - ImPlotGetterData* data_1d = (ImPlotGetterData*)data; - return ImVec2((float)idx + data_1d->XShift, ImStrideIndex(data_1d->Ys, idx, data_1d->Stride)); +void Plot(const char* label_id, const float* values, int count, int offset, int stride) { + GetterYs getter(values,stride); + PlotEx(label_id, getter, count, offset); } -void PlotBar(const char* label_id, const float* values, int count, float width, float shift, int offset, int stride) { - ImPlotGetterData data(NULL, values, stride, shift); - PlotBar(label_id, &ImPlotGetterBarV, (void*)&data, count, width, offset); +void Plot(const char* label_id, const float* xs, const float* ys, int count, int offset, int stride) { + Getter2D getter(xs,ys,stride); + return PlotEx(label_id, getter, count, offset); } -void PlotBar(const char* label_id, const float* xs, const float* ys, int count, float width, int offset, int stride) { - ImPlotGetterData data(xs,ys,stride); - PlotBar(label_id, &ImPlotGetter2D, (void*)&data, count, width, offset); +void Plot(const char* label_id, const ImVec2* data, int count, int offset) { + GetterImVec2 getter(data); + return PlotEx(label_id, getter, count, offset); } -void PlotBar(const char* label_id, ImVec2 (*getter)(void* data, int idx), void* data, int count, float width, int offset) { +void Plot(const char* label_id, ImVec2 (*getter_func)(void* data, int idx), void* data, int count, int offset) { + GetterFuncPtrImVec2 getter(getter_func,data); + return PlotEx(label_id, getter, count, offset); +} + +//----------------------------------------------------------------------------- +// PLOT BAR +//----------------------------------------------------------------------------- + +struct GetterBarV { + const float* Ys; float XShift; int Stride; + GetterBarV(const float* ys, float xshift, int stride) { Ys = ys; XShift = xshift; Stride = stride; } + inline ImVec2 operator()(int idx) { return ImVec2((float)idx + XShift, StrideIndex(Ys, idx, Stride)); } +}; + +struct GetterBarH { + const float* Xs; float YShift; int Stride; + GetterBarH(const float* xs, float yshift, int stride) { Xs = xs; YShift = yshift; Stride = stride; } + inline ImVec2 operator()(int idx) { return ImVec2(StrideIndex(Xs, idx, Stride), (float)idx + YShift); } +}; + + +template +void PlotBarEx(const char* label_id, Getter getter, int count, float width, int offset) { IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotBar() Needs to be called between BeginPlot() and EndPlot()!"); - ImPlotItem* item = gp.RegisterItem(label_id); + ImPlotItem* item = RegisterItem(label_id); if (!item->Show) return; @@ -1816,56 +1943,59 @@ void PlotBar(const char* label_id, ImVec2 (*getter)(void* data, int idx), void* if (gp.Style.Colors[ImPlotCol_Line].w != -1) item->Color = gp.Style.Colors[ImPlotCol_Line]; - PushClipRect(gp.BB_Grid.Min, gp.BB_Grid.Max, true); + PushPlotClipRect(); float half_width = width * 0.5f; // find data extents if (gp.FitThisFrame) { for (int i = 0; i < count; ++i) { - ImVec2 p = getter(data, i); - gp.FitPoint(ImVec2(p.x - half_width, p.y)); - gp.FitPoint(ImVec2(p.x + half_width, 0)); + ImVec2 p = getter(i); + FitPoint(ImVec2(p.x - half_width, p.y)); + FitPoint(ImVec2(p.x + half_width, 0)); } } int idx = offset; for (int i = 0; i < count; ++i) { ImVec2 p; - p = getter(data, idx); + p = getter(idx); idx = (idx + 1) % count; if (p.y == 0) continue; - ImVec2 a = gp.ToPixels(p.x - half_width, p.y); - ImVec2 b = gp.ToPixels(p.x + half_width, 0); + ImVec2 a = PlotToPixels(p.x - half_width, p.y); + ImVec2 b = PlotToPixels(p.x + half_width, 0); if (rend_fill) DrawList.AddRectFilled(a, b, col_fill); if (rend_line) DrawList.AddRect(a, b, col_line); } - PopClipRect(); + PopPlotClipRect(); } -static ImVec2 ImPlotGetterBarH(void* data, int idx) { - ImPlotGetterData* data_1d = (ImPlotGetterData*)data; - return ImVec2(ImStrideIndex(data_1d->Xs, idx, data_1d->Stride), (float)idx + data_1d->YShift); +void PlotBar(const char* label_id, const float* values, int count, float width, float shift, int offset, int stride) { + GetterBarV getter(values,shift,stride); + PlotBarEx(label_id, getter, count, width, offset); } -void PlotBarH(const char* label_id, const float* values, int count, float height, float shift, int offset, int stride) { - ImPlotGetterData data(values, NULL, stride, 0, shift); - PlotBarH(label_id, &ImPlotGetterBarH, (void*)&data, count, height, offset); +void PlotBar(const char* label_id, const float* xs, const float* ys, int count, float width, int offset, int stride) { + Getter2D getter(xs,ys,stride); + PlotBarEx(label_id, getter, count, width, offset); } -void PlotBarH(const char* label_id, const float* xs, const float* ys, int count, float height, int offset, int stride) { - ImPlotGetterData data(xs,ys,stride); - PlotBarH(label_id, &ImPlotGetter2D, (void*)&data, count, height, offset); +void PlotBar(const char* label_id, ImVec2 (*getter_func)(void* data, int idx), void* data, int count, float width, int offset) { + GetterFuncPtrImVec2 getter(getter_func, data); + PlotBarEx(label_id, getter, count, width, offset); } -void PlotBarH(const char* label_id, ImVec2 (*getter)(void* data, int idx), void* data, int count, float height, int offset) { +//----------------------------------------------------------------------------- + +template +void PlotBarHEx(const char* label_id, Getter getter, int count, float height, int offset) { IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotBarH() Needs to be called between BeginPlot() and EndPlot()!"); - ImPlotItem* item = gp.RegisterItem(label_id); + ImPlotItem* item = RegisterItem(label_id); if (!item->Show) return; @@ -1883,66 +2013,80 @@ void PlotBarH(const char* label_id, ImVec2 (*getter)(void* data, int idx), void* if (gp.Style.Colors[ImPlotCol_Line].w != -1) item->Color = gp.Style.Colors[ImPlotCol_Line]; - PushClipRect(gp.BB_Grid.Min, gp.BB_Grid.Max, true); + PushPlotClipRect(); float half_height = height * 0.5f; // find data extents if (gp.FitThisFrame) { for (int i = 0; i < count; ++i) { - ImVec2 p = getter(data, i); - gp.FitPoint(ImVec2(0, p.y - half_height)); - gp.FitPoint(ImVec2(p.x, p.y + half_height)); + ImVec2 p = getter(i); + FitPoint(ImVec2(0, p.y - half_height)); + FitPoint(ImVec2(p.x, p.y + half_height)); } } int idx = offset; for (int i = 0; i < count; ++i) { ImVec2 p; - p = getter(data, idx); + p = getter(idx); idx = (idx + 1) % count; if (p.x == 0) continue; - ImVec2 a = gp.ToPixels(0, p.y - half_height); - ImVec2 b = gp.ToPixels(p.x, p.y + half_height); + ImVec2 a = PlotToPixels(0, p.y - half_height); + ImVec2 b = PlotToPixels(p.x, p.y + half_height); if (rend_fill) DrawList.AddRectFilled(a, b, col_fill); if (rend_line) DrawList.AddRect(a, b, col_line); } - PopClipRect(); + PopPlotClipRect(); } -static ImVec4 ImPlotGetterError(void* data, int idx) { - ImPlotGetterData* data_4d = (ImPlotGetterData*)data; - return ImVec4(ImStrideIndex(data_4d->Xs, idx, data_4d->Stride), - ImStrideIndex(data_4d->Ys, idx, data_4d->Stride), - ImStrideIndex(data_4d->ErrNeg, idx, data_4d->Stride), - ImStrideIndex(data_4d->ErrPos, idx, data_4d->Stride)); +void PlotBarH(const char* label_id, const float* values, int count, float height, float shift, int offset, int stride) { + GetterBarH getter(values,shift,stride); + PlotBarHEx(label_id, getter, count, height, offset); } -void PlotErrorBars(const char* label_id, const float* xs, const float* ys, const float* err, int count, int offset, int stride) { - ImPlotGetterData data(xs,ys,stride,0,0,err,err); - PlotErrorBars(label_id, &ImPlotGetterError, (void*)&data, count, offset); +void PlotBarH(const char* label_id, const float* xs, const float* ys, int count, float height, int offset, int stride) { + Getter2D getter(xs,ys,stride); + PlotBarHEx(label_id, getter, count, height, offset); } -void PlotErrorBars(const char* label_id, const float* xs, const float* ys, const float* neg, const float* pos, int count, int offset, int stride) { - ImPlotGetterData data(xs,ys,stride,0,0,neg,pos); - PlotErrorBars(label_id, &ImPlotGetterError, (void*)&data, count, offset); +void PlotBarH(const char* label_id, ImVec2 (*getter_func)(void* data, int idx), void* data, int count, float height, int offset) { + GetterFuncPtrImVec2 getter(getter_func, data); + PlotBarHEx(label_id, getter, count, height, offset); } -void PlotErrorBars(const char* label_id, ImVec4 (*getter)(void* data, int idx), void* data, int count, int offset) { +//----------------------------------------------------------------------------- +// PLOT ERROR BARS +//----------------------------------------------------------------------------- + +struct GetterError { + const float* Xs; const float* Ys; const float* Neg; const float* Pos; int Stride; + GetterError(const float* xs, const float* ys, const float* neg, const float* pos, int stride) { + Xs = xs; Ys = ys; Neg = neg; Pos = pos; Stride = stride; + } + ImVec4 operator()(int idx) { + return ImVec4(StrideIndex(Xs, idx, Stride), + StrideIndex(Ys, idx, Stride), + StrideIndex(Neg, idx, Stride), + StrideIndex(Pos, idx, Stride)); + } +}; + +template +void PlotErrorBarsEx(const char* label_id, Getter getter, int count, int offset) { IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotErrorBars() Needs to be called between BeginPlot() and EndPlot()!"); ImGuiID id = GetID(label_id); - ImPlotItem* item = gp.CurrentPlot->Items.GetByKey(id); if (item != NULL && item->Show == false) return; ImDrawList & DrawList = *GetWindowDrawList(); - PushClipRect(gp.BB_Grid.Min, gp.BB_Grid.Max, true); + PushPlotClipRect(); const ImU32 col = gp.Style.Colors[ImPlotCol_ErrorBar].w == -1 ? GetColorU32(ImGuiCol_Text) : GetColorU32(gp.Style.Colors[ImPlotCol_ErrorBar]); const bool rend_whisker = gp.Style.ErrorBarSize > 0; @@ -1952,39 +2096,112 @@ void PlotErrorBars(const char* label_id, ImVec4 (*getter)(void* data, int idx), // find data extents if (gp.FitThisFrame) { for (int i = 0; i < count; ++i) { - ImVec4 e = getter(data, i); - gp.FitPoint(ImVec2(e.x , e.y - e.z)); - gp.FitPoint(ImVec2(e.x , e.y + e.w )); + ImVec4 e = getter(i); + FitPoint(ImVec2(e.x , e.y - e.z)); + FitPoint(ImVec2(e.x , e.y + e.w )); } } int idx = offset; for (int i = 0; i < count; ++i) { ImVec4 e; - e = getter(data, idx); + e = getter(idx); idx = (idx + 1) % count; - ImVec2 p1 = gp.ToPixels(e.x, e.y - e.z); - ImVec2 p2 = gp.ToPixels(e.x, e.y + e.w); + ImVec2 p1 = PlotToPixels(e.x, e.y - e.z); + ImVec2 p2 = PlotToPixels(e.x, e.y + e.w); DrawList.AddLine(p1,p2,col, gp.Style.ErrorBarWeight); if (rend_whisker) { DrawList.AddLine(p1 - ImVec2(half_whisker, 0), p1 + ImVec2(half_whisker, 0), col, gp.Style.ErrorBarWeight); DrawList.AddLine(p2 - ImVec2(half_whisker, 0), p2 + ImVec2(half_whisker, 0), col, gp.Style.ErrorBarWeight); } } + PopPlotClipRect(); +} - PopClipRect(); +void PlotErrorBars(const char* label_id, const float* xs, const float* ys, const float* err, int count, int offset, int stride) { + GetterError getter(xs, ys, err, err, stride); + PlotErrorBarsEx(label_id, getter, count, offset); +} + +void PlotErrorBars(const char* label_id, const float* xs, const float* ys, const float* neg, const float* pos, int count, int offset, int stride) { + GetterError getter(xs, ys, neg, pos, stride); + PlotErrorBarsEx(label_id, getter, count, offset); +} + +void PlotErrorBars(const char* label_id, ImVec4 (*getter_func)(void* data, int idx), void* data, int count, int offset) { + GetterFuncPtrImVec4 getter(getter_func, data); + PlotErrorBarsEx(label_id, getter, count, offset); +} + +//----------------------------------------------------------------------------- +// PLOT MISC +//----------------------------------------------------------------------------- + +inline void DrawPieSlice(ImDrawList& DrawList, const ImVec2& center, float radius, float a0, float a1, ImU32 col) { + static const float resolution = 50 / (2 * IM_PI); + static ImVec2 buffer[50]; + buffer[0] = PlotToPixels(center); + int n = ImMax(3, (int)((a1 - a0) * resolution)); + float da = (a1 - a0) / (n - 1); + for (int i = 0; i < n; ++i) { + float a = a0 + i * da; + buffer[i + 1] = PlotToPixels(center.x + radius * cos(a), center.y + radius * sin(a)); + } + DrawList.AddConvexPolyFilled(buffer, n + 1, col); +} + + +void PlotPieChart(char** label_ids, float* values, int count, const ImVec2& center, float radius, bool show_percents, float angle0) { + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotPieChart() Needs to be called between BeginPlot() and EndPlot()!"); + ImDrawList & DrawList = *GetWindowDrawList(); + + float sum = 0; + for (int i = 0; i < count; ++i) + sum += values[i]; + + const bool normalize = sum > 1.0f; + + PushPlotClipRect(); + float a0 = angle0 * 2 * IM_PI / 360.0f; + float a1 = angle0 * 2 * IM_PI / 360.0f; + for (int i = 0; i < count; ++i) { + ImPlotItem* item = RegisterItem(label_ids[i]); + ImU32 col = GetColorU32(item->Color); + float percent = normalize ? values[i] / sum : values[i]; + a1 = a0 + 2 * IM_PI * percent; + if (item->Show) { + if (percent < 0.5) { + DrawPieSlice(DrawList, center, radius, a0, a1, col); + } + else { + DrawPieSlice(DrawList, center, radius, a0, a0 + (a1 - a0) * 0.5f, col); + DrawPieSlice(DrawList, center, radius, a0 + (a1 - a0) * 0.5f, a1, col); + } + if (show_percents) { + static char buffer[8]; + sprintf(buffer, "%.0f%%", percent * 100); + ImVec2 size = CalcTextSize(buffer); + float angle = a0 + (a1 - a0) * 0.5f; + ImVec2 pos = PlotToPixels(center.x + 0.5f * radius * cos(angle), center.y + 0.5f * radius * sin(angle)); + DrawList.AddText(pos - size * 0.5f + ImVec2(1,1), gp.Col_Bg, buffer); + DrawList.AddText(pos - size * 0.5f, GetColorU32(ImGuiCol_Text), buffer); + } + } + a0 = a1; + } + PopPlotClipRect(); } void PlotLabel(const char* text, float x, float y, bool vertical, const ImVec2& pixel_offset) { IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotLabel() Needs to be called between BeginPlot() and EndPlot()!"); ImDrawList & DrawList = *ImGui::GetWindowDrawList(); - PushClipRect(gp.BB_Grid.Min, gp.BB_Grid.Max, true); - ImVec2 pos = gp.ToPixels({x,y}) + pixel_offset; + PushPlotClipRect(); + ImVec2 pos = PlotToPixels({x,y}) + pixel_offset; if (vertical) AddTextVertical(&DrawList, text, pos, gp.Col_Txt); else DrawList.AddText(pos, gp.Col_Txt, text); - PopClipRect(); + PopPlotClipRect(); } } // namespace ImGui \ No newline at end of file diff --git a/implot.h b/implot.h index a1d0be0..83af945 100644 --- a/implot.h +++ b/implot.h @@ -46,6 +46,7 @@ enum ImPlotFlags_ { ImPlotFlags_Crosshairs = 1 << 6, // the default mouse cursor will be replaced with a crosshair when hovered ImPlotFlags_CullData = 1 << 7, // plot data outside the plot area will be culled from rendering ImPlotFlags_AntiAliased = 1 << 8, // lines and fills will be anti-aliased (not recommended) + ImPlotFlags_NoChild = 1 << 9, // a child window region will not be used to capture mouse scroll (can boost performance for single ImGui window applications) ImPlotFlags_Default = ImPlotFlags_MousePos | ImPlotFlags_Legend | ImPlotFlags_Highlight | ImPlotFlags_Selection | ImPlotFlags_ContextMenu | ImPlotFlags_CullData }; @@ -154,6 +155,7 @@ void EndPlot(); // Plots a standard 2D line and/or scatter plot . void Plot(const char* label_id, const float* values, int count, int offset = 0, int stride = sizeof(float)); void Plot(const char* label_id, const float* xs, const float* ys, int count, int offset = 0, int stride = sizeof(float)); +void Plot(const char* label_id, const ImVec2* data, int count, int offset = 0); void Plot(const char* label_id, ImVec2 (*getter)(void* data, int idx), void* data, int count, int offset = 0); // Plots vertical bars. void PlotBar(const char* label_id, const float* values, int count, float width = 0.67f, float shift = 0, int offset = 0, int stride = sizeof(float)); @@ -167,6 +169,8 @@ void PlotBarH(const char* label_id, ImVec2 (*getter)(void* data, int idx), void* void PlotErrorBars(const char* label_id, const float* xs, const float* ys, const float* err, int count, int offset = 0, int stride = sizeof(float)); void PlotErrorBars(const char* label_id, const float* xs, const float* ys, const float* neg, const float* pos, int count, int offset = 0, int stride = sizeof(float)); void PlotErrorBars(const char* label_id, ImVec4 (*getter)(void* data, int idx), void* data, int count, int offset = 0); +// Plots a pie chart. If the sum of values > 1, each value will be normalized. Center and radius are in plot coordinates. +void PlotPieChart(char** label_ids, float* values, int count, const ImVec2& center, float radius, bool show_percents = true, float angle0 = 90); // Plots a text label at point x,y. void PlotLabel(const char* text, float x, float y, bool vertical = false, const ImVec2& pixel_offset = ImVec2(0,0)); diff --git a/implot_demo.cpp b/implot_demo.cpp index 2505bba..c200541 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -63,17 +63,15 @@ struct RollingData { struct BenchmarkItem { BenchmarkItem() { float y = RandomRange(0,1); - Xs = new float[1000]; - Ys = new float[1000]; + Data = new ImVec2[1000]; for (int i = 0; i < 1000; ++i) { - Xs[i] = i*0.001f; - Ys[i] = y + RandomRange(-0.01f,0.01f); + Data[i].x = i*0.001f; + Data[i].y = y + RandomRange(-0.01f,0.01f); } Col = ImVec4(RandomRange(0,1),RandomRange(0,1),RandomRange(0,1),1); } - ~BenchmarkItem() { delete Xs; delete Ys; } - float* Xs; - float* Ys; + ~BenchmarkItem() { delete Data; } + ImVec2* Data; ImVec4 Col; }; @@ -84,8 +82,8 @@ namespace ImGui { void ShowImPlotDemoWindow(bool* p_open) { ImVec2 main_viewport_pos = ImGui::GetMainViewport()->Pos; - ImGui::SetNextWindowPos(ImVec2(main_viewport_pos.x + 650, main_viewport_pos.y + 20), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSize(ImVec2(550, 680), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(50, 50), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(520, 750), ImGuiCond_FirstUseEver); ImGui::Begin("ImPlot Demo", p_open); ImGui::Text("ImPlot says hello. (0.1 WIP)"); if (ImGui::CollapsingHeader("Help")) { @@ -193,8 +191,8 @@ void ShowImPlotDemoWindow(bool* p_open) { float xs[5] = {1,2,3,4,5}; float lin[5] = {8,8,9,7,8}; float bar[5] = {1,2,5,3,4}; - float err1[5] = {0.2, 0.4, 0.2, 0.6, 0.4}; - float err2[5] = {0.4, 0.2, 0.4, 0.8, 0.6}; + float err1[5] = {0.2f, 0.4f, 0.2f, 0.6f, 0.4f}; + float err2[5] = {0.4f, 0.2f, 0.4f, 0.8f, 0.6f}; ImGui::SetNextPlotRange(0, 6, 0, 10); if (ImGui::BeginPlot("##ErrorBars",NULL,NULL,ImVec2(-1,300))) { @@ -212,6 +210,38 @@ void ShowImPlotDemoWindow(bool* p_open) { ImGui::EndPlot(); } } + //------------------------------------------------------------------------- + if (ImGui::CollapsingHeader("Pie Charts")) { + static char* labels1[] = {"Frogs","Hogs","Dogs","Logs"}; + static float pre_normalized[] = {0.15f, 0.30f, 0.45f, 0.10f}; + ImVec2 center(0.5f,0.5f); // in plot units, not pixels + float radius = 0.4f; // in plot units, not pixels + + SetNextPlotRange(0,1,0,1,ImGuiCond_Always); + if (ImGui::BeginPlot("##Pie1", NULL, NULL, ImVec2(250,250), ImPlotFlags_Legend, 0, 0)) { + ImGui::PlotPieChart(labels1, pre_normalized, 4, center, radius); + ImGui::EndPlot(); + } + ImGui::SameLine(); + + static ImVec4 YlOrRd[5] = { + {1.0000f, 1.0000f, 0.8000f, 1.0f}, + {0.9961f, 0.8510f, 0.4627f, 1.0f}, + {0.9961f, 0.6314f, 0.2627f, 1.0f}, + {0.9882f, 0.3059f, 0.1647f, 1.0f}, + {0.7412f, 0.0f, 0.1490f, 1.0f}, + }; + ImGui::SetPlotPalette(YlOrRd, 5); + SetNextPlotRange(0,1,0,1,ImGuiCond_Always); + static char* labels2[] = {"One","Two","Three","Four","Five"}; + static float not_normalized[] = {1,2,3,4,5}; + if (ImGui::BeginPlot("##Pie2", NULL, NULL, ImVec2(250,250), ImPlotFlags_Legend, 0, 0)) { + ImGui::PlotPieChart(labels2, not_normalized, 5, center, radius); + ImGui::EndPlot(); + } + ImGui::RestorePlotPalette(); + } + //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Realtime Plots")) { ImGui::BulletText("Move your mouse to change the data!"); @@ -532,12 +562,13 @@ void ShowImPlotDemoWindow(bool* p_open) { static BenchmarkItem items[n_items]; ImGui::BulletText("Make sure VSync is disabled."); ImGui::BulletText("%d lines with %d points each @ %.3f FPS.",n_items,1000,ImGui::GetIO().Framerate); - if (ImGui::BeginPlot("##Bench",NULL,NULL,{-1,300})) { - char buff[16]; + SetNextPlotRange(0,1,0,1, ImGuiCond_Always); + if (ImGui::BeginPlot("##Bench",NULL,NULL,{-1,300},ImPlotFlags_Default | ImPlotFlags_NoChild)) { + char buff[16]; for (int i = 0; i < 100; ++i) { sprintf(buff, "item_%d",i); ImGui::PushPlotColor(ImPlotCol_Line, items[i].Col); - ImGui::Plot(buff, items[i].Xs, items[i].Ys, 1000); + ImGui::Plot(buff, items[i].Data, 1000); ImGui::PopPlotColor(); } ImGui::EndPlot(); @@ -548,4 +579,4 @@ void ShowImPlotDemoWindow(bool* p_open) { } -} // namespace ImGui +} // namespace ImGui \ No newline at end of file From 2ce88f0da7db850c908d299f05ae6a9045614f3b Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Sun, 3 May 2020 00:31:45 -0500 Subject: [PATCH 6/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de8cfc3..44bbb24 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ImPlot is an immediate mode plotting widget for [Dear ImGui](https://github.com/ ## Features -- multiple plot types: line, scatter, vertical/horizontal bars, error bars, with more likely to come +- multiple plot types: line, scatter, vertical/horizontal bars, error bars, pie charts, with more likely to come - mix/match multiple plot items on a single plot - configurable axes ranges and scaling (linear/log) - reversible and lockable axes From a7303fd99431a417961268c6ebb4c01dcdf9f43c Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Sun, 3 May 2020 00:37:48 -0500 Subject: [PATCH 7/8] fix typos in readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 44bbb24..ff18a7e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # ImPlot -ImPlot is an immediate mode plotting widget for [Dear ImGui](https://github.com/ocornut/imgui). It aims to provide a first-class API that will make ImGui users feel right at home. ImPlot is well suited for visualizing program data in real-time and requires minimal code to integrate. Just like ImGui, it does not burden the end user with GUI state managment, avoids STL containers and C++ headers, and has no external dependencies except for ImGui itself. +ImPlot is an immediate mode plotting widget for [Dear ImGui](https://github.com/ocornut/imgui). It aims to provide a first-class API that will make ImGui users feel right at home. ImPlot is well suited for visualizing program data in real-time and requires minimal code to integrate. Just like ImGui, it does not burden the end user with GUI state management, avoids STL containers and C++ headers, and has no external dependencies except for ImGui itself. @@ -46,7 +46,7 @@ Just add `implot.h`, `implot.cpp`, and optionally `implot_demo.cpp` to your sour **Q: Why?** -A: ImGui is an incredibly powerful tool for rapid prototyping and development, but provides only limited mechanisms for data visualation. Two dimensional plots are ubiquitous and useful to almost any application. Being able to visualize your data in real-time will give you insight and better understanding of your application. +A: ImGui is an incredibly powerful tool for rapid prototyping and development, but provides only limited mechanisms for data visualization. Two dimensional plots are ubiquitous and useful to almost any application. Being able to visualize your data in real-time will give you insight and better understanding of your application. **Q: Is ImPlot suitable for real-time plotting?** @@ -54,7 +54,7 @@ A: Yes, within reason. You can plot tens to hundreds of thousands of points with **Q: Can plot styles be modified?** -A: Yes. Plot colors, palletes, and various styling variables can be pushed/popped or modified permantly on startup. +A: Yes. Plot colors, palettes, and various styling variables can be pushed/popped or modified permanently on startup. **Q: Does ImPlot support logarithmic scaling?** From f31ffaae9e4d3dbabb98940e4c8855db302bc732 Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Sun, 3 May 2020 00:46:20 -0500 Subject: [PATCH 8/8] Update README.md --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ff18a7e..b268dfa 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,13 @@ ImPlot is an immediate mode plotting widget for [Dear ImGui](https://github.com/ ## Features -- multiple plot types: line, scatter, vertical/horizontal bars, error bars, pie charts, with more likely to come +- multiple plot types: + - line + - scatter + - vertical/horizontal bars + - error bars + - pie charts + - and more likely to come - mix/match multiple plot items on a single plot - configurable axes ranges and scaling (linear/log) - reversible and lockable axes