From 41a0e2c9fe627881cb494a9791ce77135bf9e5ee Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Sun, 28 Mar 2021 12:59:25 -0700 Subject: [PATCH] add ImPlotAxisFlags_RangeFit and ImPlotAxisFlags_Foreground (#200) --- implot.cpp | 117 +++++++++++++++++++++------------------------- implot.h | 20 ++++---- implot_demo.cpp | 34 +++++++++++++- implot_internal.h | 33 +++++++++++-- implot_items.cpp | 8 ++++ 5 files changed, 132 insertions(+), 80 deletions(-) diff --git a/implot.cpp b/implot.cpp index b791fd7..cf0f61b 100644 --- a/implot.cpp +++ b/implot.cpp @@ -487,32 +487,6 @@ void BustPlotCache() { GImPlot->Plots.Clear(); } -void FitPoint(const ImPlotPoint& p) { - FitPointX(p.x); - FitPointY(p.y); -} - -void FitPointX(double x) { - ImPlotContext& gp = *GImPlot; - ImPlotRange& ex_x = gp.ExtentsX; - const bool log_x = ImHasFlag(gp.CurrentPlot->XAxis.Flags, ImPlotAxisFlags_LogScale); - if (!ImNanOrInf(x) && !(log_x && x <= 0)) { - ex_x.Min = x < ex_x.Min ? x : ex_x.Min; - ex_x.Max = x > ex_x.Max ? x : ex_x.Max; - } -} - -void FitPointY(double y) { - ImPlotContext& gp = *GImPlot; - const ImPlotYAxis y_axis = gp.CurrentPlot->CurrentYAxis; - ImPlotRange& ex_y = gp.ExtentsY[y_axis]; - const bool log_y = ImHasFlag(gp.CurrentPlot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale); - if (!ImNanOrInf(y) && !(log_y && y <= 0)) { - ex_y.Min = y < ex_y.Min ? y : ex_y.Min; - ex_y.Max = y > ex_y.Max ? y : ex_y.Max; - } -} - void PushLinkedAxis(ImPlotAxis& axis) { if (axis.LinkedMin) { *axis.LinkedMin = axis.Range.Min; } if (axis.LinkedMax) { *axis.LinkedMax = axis.Range.Max; } @@ -1301,6 +1275,36 @@ void UpdateAxisColors(int axis_flag, ImPlotAxis* axis) { // RENDERING //----------------------------------------------------------------------------- +static inline void RenderGridLinesX(ImDrawList& DrawList, const ImPlotTickCollection& ticks, const ImRect& rect, ImU32 col_maj, ImU32 col_min, float size_maj, float size_min) { + const float density = ticks.Size / rect.GetWidth(); + ImVec4 col_min4 = ImGui::ColorConvertU32ToFloat4(col_min); + col_min4.w *= ImClamp(ImRemap(density, 0.1f, 0.2f, 1.0f, 0.0f), 0.0f, 1.0f); + col_min = ImGui::ColorConvertFloat4ToU32(col_min4); + for (int t = 0; t < ticks.Size; t++) { + const ImPlotTick& xt = ticks.Ticks[t]; + if (xt.Level == 0) { + if (xt.Major) + DrawList.AddLine(ImVec2(xt.PixelPos, rect.Min.y), ImVec2(xt.PixelPos, rect.Max.y), col_maj, size_maj); + else if (density < 0.2f) + DrawList.AddLine(ImVec2(xt.PixelPos, rect.Min.y), ImVec2(xt.PixelPos, rect.Max.y), col_min, size_min); + } + } +} + +static inline void RenderGridLinesY(ImDrawList& DrawList, const ImPlotTickCollection& ticks, const ImRect& rect, ImU32 col_maj, ImU32 col_min, float size_maj, float size_min) { + const float density = ticks.Size / rect.GetHeight(); + ImVec4 col_min4 = ImGui::ColorConvertU32ToFloat4(col_min); + col_min4.w *= ImClamp(ImRemap(density, 0.1f, 0.2f, 1.0f, 0.0f), 0.0f, 1.0f); + col_min = ImGui::ColorConvertFloat4ToU32(col_min4); + for (int t = 0; t < ticks.Size; t++) { + const ImPlotTick& yt = ticks.Ticks[t]; + if (yt.Major) + DrawList.AddLine(ImVec2(rect.Min.x, yt.PixelPos), ImVec2(rect.Max.x, yt.PixelPos), col_maj, size_maj); + else if (density < 0.2f) + DrawList.AddLine(ImVec2(rect.Min.x, yt.PixelPos), ImVec2(rect.Max.x, yt.PixelPos), col_min, size_min); + } +} + static inline void RenderSelectionRect(ImDrawList& DrawList, const ImVec2& p_min, const ImVec2& p_max, const ImVec4& col) { const ImU32 col_bg = ImGui::GetColorU32(col * ImVec4(1,1,1,0.25f)); const ImU32 col_bd = ImGui::GetColorU32(col); @@ -1909,9 +1913,6 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con // grid bg DrawList.AddRectFilled(plot.PlotRect.Min, plot.PlotRect.Max, GetStyleColorU32(ImPlotCol_PlotBg)); - // render axes - PushPlotClipRect(); - // transform ticks (TODO: Move this into ImPlotTickCollection) if (gp.RenderX) { for (int t = 0; t < gp.XTicks.Size; t++) { @@ -1928,39 +1929,14 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con } } - // render grid - if (!ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_NoGridLines)) { - float density = gp.XTicks.Size / plot.PlotRect.GetWidth(); - ImVec4 col_min = ImGui::ColorConvertU32ToFloat4(plot.XAxis.ColorMin); - col_min.w *= ImClamp(ImRemap(density, 0.1f, 0.2f, 1.0f, 0.0f), 0.0f, 1.0f); - ImU32 col_min32 = ImGui::ColorConvertFloat4ToU32(col_min); - for (int t = 0; t < gp.XTicks.Size; t++) { - ImPlotTick& xt = gp.XTicks.Ticks[t]; - if (xt.Level == 0) { - if (xt.Major) - DrawList.AddLine(ImVec2(xt.PixelPos, plot.PlotRect.Min.y), ImVec2(xt.PixelPos, plot.PlotRect.Max.y), plot.XAxis.ColorMaj, gp.Style.MajorGridSize.x); - else if (density < 0.2f) - DrawList.AddLine(ImVec2(xt.PixelPos, plot.PlotRect.Min.y), ImVec2(xt.PixelPos, plot.PlotRect.Max.y), col_min32, gp.Style.MinorGridSize.x); - } - } - } - + // render grid (background) + PushPlotClipRect(gp.Style.PlotBorderSize == 0 ? 1.0f : 0.0f); + if (!ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_NoGridLines) && !ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_Foreground)) + RenderGridLinesX(DrawList, gp.XTicks, plot.PlotRect, plot.XAxis.ColorMaj, plot.XAxis.ColorMin, gp.Style.MajorGridSize.x, gp.Style.MinorGridSize.x); for (int i = 0; i < IMPLOT_Y_AXES; i++) { - if (plot.YAxis[i].Present && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_NoGridLines)) { - float density = gp.YTicks[i].Size / plot.PlotRect.GetHeight(); - ImVec4 col_min = ImGui::ColorConvertU32ToFloat4(plot.YAxis[i].ColorMin); - col_min.w *= ImClamp(ImRemap(density, 0.1f, 0.2f, 1.0f, 0.0f), 0.0f, 1.0f); - ImU32 col_min32 = ImGui::ColorConvertFloat4ToU32(col_min); - for (int t = 0; t < gp.YTicks[i].Size; t++) { - ImPlotTick& yt = gp.YTicks[i].Ticks[t]; - if (yt.Major) - DrawList.AddLine(ImVec2(plot.PlotRect.Min.x, yt.PixelPos), ImVec2(plot.PlotRect.Max.x, yt.PixelPos), plot.YAxis[i].ColorMaj, gp.Style.MajorGridSize.y); - else if (density < 0.2f) - DrawList.AddLine(ImVec2(plot.PlotRect.Min.x, yt.PixelPos), ImVec2(plot.PlotRect.Max.x, yt.PixelPos), col_min32, gp.Style.MinorGridSize.y); - } - } + if (plot.YAxis[i].Present && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_NoGridLines) && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_Foreground)) + RenderGridLinesY(DrawList, gp.YTicks[i], plot.PlotRect, plot.YAxis[i].ColorMaj, plot.YAxis[i].ColorMin, gp.Style.MajorGridSize.y, gp.Style.MinorGridSize.y); } - PopPlotClipRect(); // render title @@ -2262,7 +2238,17 @@ void EndPlot() { // FINAL RENDER ----------------------------------------------------------- - // render ticks + // render grid (foreground) + PushPlotClipRect(gp.Style.PlotBorderSize == 0 ? 1.0f : 0.0f); + if (!ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_NoGridLines) && ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_Foreground)) + RenderGridLinesX(DrawList, gp.XTicks, plot.PlotRect, plot.XAxis.ColorMaj, plot.XAxis.ColorMaj, gp.Style.MajorGridSize.x, gp.Style.MinorGridSize.x); + for (int i = 0; i < IMPLOT_Y_AXES; i++) { + if (plot.YAxis[i].Present && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_NoGridLines) && ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_Foreground)) + RenderGridLinesY(DrawList, gp.YTicks[i], plot.PlotRect, plot.YAxis[i].ColorMaj, plot.YAxis[i].ColorMin, gp.Style.MajorGridSize.y, gp.Style.MinorGridSize.y); + } + PopPlotClipRect(); + + // render x-ticks PushPlotClipRect(); if (!ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_NoTickMarks)) { for (int t = 0; t < gp.XTicks.Size; t++) { @@ -2276,12 +2262,12 @@ void EndPlot() { } PopPlotClipRect(); + // render y-ticks ImGui::PushClipRect(plot.PlotRect.Min, ImVec2(plot.FrameRect.Max.x, plot.PlotRect.Max.y), true); int axis_count = 0; for (int i = 0; i < IMPLOT_Y_AXES; i++) { if (!plot.YAxis[i].Present) { continue; } axis_count++; - float x_start = gp.YAxisReference[i]; if (!ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_NoTickMarks)) { float direction = (i == 0) ? 1.0f : -1.0f; @@ -2295,7 +2281,6 @@ void EndPlot() { (!no_major && yt->Major) ? gp.Style.MajorTickSize.y : gp.Style.MinorTickSize.y); } } - if (axis_count >= 3) { // Draw a bar next to the ticks to act as a visual separator. DrawList.AddLine(ImVec2(x_start, plot.PlotRect.Min.y), ImVec2(x_start, plot.PlotRect.Max.y), GetStyleColorU32(ImPlotCol_YAxisGrid3), 1); @@ -2674,10 +2659,12 @@ ImDrawList* GetPlotDrawList() { return ImGui::GetWindowDrawList(); } -void PushPlotClipRect() { +void PushPlotClipRect(float expand) { ImPlotContext& gp = *GImPlot; + ImRect rect = gp.CurrentPlot->PlotRect; + rect.Expand(expand); IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PushPlotClipRect() needs to be called between BeginPlot() and EndPlot()!"); - ImGui::PushClipRect(gp.CurrentPlot->PlotRect.Min, gp.CurrentPlot->PlotRect.Max, true); + ImGui::PushClipRect(rect.Min, rect.Max, true); } void PopPlotClipRect() { diff --git a/implot.h b/implot.h index aad5ea3..e486748 100644 --- a/implot.h +++ b/implot.h @@ -89,13 +89,15 @@ enum ImPlotAxisFlags_ { ImPlotAxisFlags_NoGridLines = 1 << 1, // no grid lines will be displayed ImPlotAxisFlags_NoTickMarks = 1 << 2, // no tick marks will be displayed ImPlotAxisFlags_NoTickLabels = 1 << 3, // no text labels will be displayed - ImPlotAxisFlags_LogScale = 1 << 4, // a logartithmic (base 10) axis scale will be used (mutually exclusive with ImPlotAxisFlags_Time) - ImPlotAxisFlags_Time = 1 << 5, // axis will display date/time formatted labels (mutually exclusive with ImPlotAxisFlags_LogScale) - ImPlotAxisFlags_Invert = 1 << 6, // the axis will be inverted - ImPlotAxisFlags_NoInitialFit = 1 << 7, // axis will not be initially fit to data extents on the first rendered frame (also the case if SetNextPlotLimits explicitly called) - ImPlotAxisFlags_AutoFit = 1 << 8, // axis will be auto-fitting to data extents - ImPlotAxisFlags_LockMin = 1 << 9, // the axis minimum value will be locked when panning/zooming - ImPlotAxisFlags_LockMax = 1 << 10, // the axis maximum value will be locked when panning/zooming + ImPlotAxisFlags_Foreground = 1 << 4, // grid lines will be displayed in the foreground (i.e. on top of data) in stead of the background + ImPlotAxisFlags_LogScale = 1 << 5, // a logartithmic (base 10) axis scale will be used (mutually exclusive with ImPlotAxisFlags_Time) + ImPlotAxisFlags_Time = 1 << 6, // axis will display date/time formatted labels (mutually exclusive with ImPlotAxisFlags_LogScale) + ImPlotAxisFlags_Invert = 1 << 7, // the axis will be inverted + ImPlotAxisFlags_NoInitialFit = 1 << 8, // axis will not be initially fit to data extents on the first rendered frame (also the case if SetNextPlotLimits explicitly called) + ImPlotAxisFlags_AutoFit = 1 << 9, // axis will be auto-fitting to data extents + ImPlotAxisFlags_RangeFit = 1 << 10, // axis will only fit points if the point is in the visible range of the **orthoganol** axis + ImPlotAxisFlags_LockMin = 1 << 11, // the axis minimum value will be locked when panning/zooming + ImPlotAxisFlags_LockMax = 1 << 12, // the axis maximum value will be locked when panning/zooming ImPlotAxisFlags_Lock = ImPlotAxisFlags_LockMin | ImPlotAxisFlags_LockMax, ImPlotAxisFlags_NoDecorations = ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks | ImPlotAxisFlags_NoTickLabels }; @@ -808,8 +810,8 @@ IMPLOT_API void ColormapIcon(ImPlotColormap cmap); // Get the plot draw list for custom rendering to the current plot area. Call between Begin/EndPlot. IMPLOT_API ImDrawList* GetPlotDrawList(); -// Push clip rect for rendering to current plot area. Call between Begin/EndPlot. -IMPLOT_API void PushPlotClipRect(); +// Push clip rect for rendering to current plot area. The rect can be expanded or contracted by #expand pixels. Call between Begin/EndPlot. +IMPLOT_API void PushPlotClipRect(float expand=0); // Pop plot clip rect. Call between Begin/EndPlot. IMPLOT_API void PopPlotClipRect(); diff --git a/implot_demo.cpp b/implot_demo.cpp index 6ec6ca4..bb49a4d 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -654,8 +654,9 @@ void ShowDemoWindow(bool* p_open) { static NormalDistribution<500000> dist1(1, 2); static NormalDistribution<500000> dist2(1, 1); double max_count = 0; - ImPlot::PushColormap("Twilight"); - if (ImPlot::BeginPlot("##Hist2D",0,0,ImVec2(ImGui::GetContentRegionAvail().x-100-ImGui::GetStyle().ItemSpacing.x,0),0,ImPlotAxisFlags_AutoFit,ImPlotAxisFlags_AutoFit)) { + ImPlotAxisFlags flags = ImPlotAxisFlags_AutoFit|ImPlotAxisFlags_Foreground; + ImPlot::PushColormap("Hot"); + if (ImPlot::BeginPlot("##Hist2D",0,0,ImVec2(ImGui::GetContentRegionAvail().x-100-ImGui::GetStyle().ItemSpacing.x,0),0,flags,flags)) { max_count = ImPlot::PlotHistogram2D("Hist2D",dist1.Data,dist2.Data,count,xybins[0],xybins[1],density2,ImPlotLimits(-6,6,-6,6)); ImPlot::EndPlot(); } @@ -952,6 +953,35 @@ void ShowDemoWindow(bool* p_open) { } } //------------------------------------------------------------------------- + if (ImGui::CollapsingHeader("Auto-Fitting Data")) { + + ImGui::BulletText("The Y-axis has been configured to auto-fit to only the data visible in X-axis range."); + ImGui::BulletText("Zoom and pan the X-axis. Disable Stems to see a difference in fit."); + ImGui::BulletText("If ImPlotAxisFlags_RangeFit is disabled, the axis will fit ALL data."); + + static ImPlotAxisFlags xflags = ImPlotAxisFlags_None; + static ImPlotAxisFlags yflags = ImPlotAxisFlags_AutoFit|ImPlotAxisFlags_RangeFit; + + ImGui::TextUnformatted("X: "); ImGui::SameLine(); + ImGui::CheckboxFlags("ImPlotAxisFlags_AutoFit##X", (unsigned int*)&xflags, ImPlotAxisFlags_AutoFit); ImGui::SameLine(); + ImGui::CheckboxFlags("ImPlotAxisFlags_RangeFit##X", (unsigned int*)&xflags, ImPlotAxisFlags_RangeFit); + + ImGui::TextUnformatted("Y: "); ImGui::SameLine(); + ImGui::CheckboxFlags("ImPlotAxisFlags_AutoFit##Y", (unsigned int*)&yflags, ImPlotAxisFlags_AutoFit); ImGui::SameLine(); + ImGui::CheckboxFlags("ImPlotAxisFlags_RangeFit##Y", (unsigned int*)&yflags, ImPlotAxisFlags_RangeFit); + + static double data[101]; + srand(0); + for (int i = 0; i < 101; ++i) + data[i] = 1 + sin(i/10.0f); + + if (ImPlot::BeginPlot("##DataFitting","X","Y",ImVec2(-1,0),0,xflags,yflags)) { + ImPlot::PlotLine("Line",data,101); + ImPlot::PlotStems("Stems",data,101); + ImPlot::EndPlot(); + }; + } + //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Querying")) { static ImVector data; static ImPlotLimits range, query, select; diff --git a/implot_internal.h b/implot_internal.h index b0a967e..3c0d1d5 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -987,12 +987,37 @@ static inline ImPlotScale GetCurrentScale() { return GImPlot->Scales[GetCurrentY // Returns true if the user has requested data to be fit. static inline bool FitThisFrame() { return GImPlot->FitThisFrame; } -// Extends the current plot's axes so that it encompasses point p -IMPLOT_API void FitPoint(const ImPlotPoint& p); +// Extend the the extents of an axis on current plot so that it encompes v +static inline void FitPointAxis(ImPlotAxis& axis, ImPlotRange& ext, double v) { + if (!ImNanOrInf(v) && !(ImHasFlag(axis.Flags, ImPlotAxisFlags_LogScale) && v <= 0)) { + ext.Min = v < ext.Min ? v : ext.Min; + ext.Max = v > ext.Max ? v : ext.Max; + } +} +// Extend the the extents of an axis on current plot so that it encompes v +static inline void FitPointMultiAxis(ImPlotAxis& axis, ImPlotAxis& alt, ImPlotRange& ext, double v, double v_alt) { + if (ImHasFlag(axis.Flags, ImPlotAxisFlags_RangeFit) && !alt.Range.Contains(v_alt)) + return; + if (!ImNanOrInf(v) && !(ImHasFlag(axis.Flags, ImPlotAxisFlags_LogScale) && v <= 0)) { + ext.Min = v < ext.Min ? v : ext.Min; + ext.Max = v > ext.Max ? v : ext.Max; + } +} // Extends the current plot's axes so that it encompasses a vertical line at x -IMPLOT_API void FitPointX(double x); +static inline void FitPointX(double x) { + FitPointAxis(GImPlot->CurrentPlot->XAxis, GImPlot->ExtentsX, x); +} // Extends the current plot's axes so that it encompasses a horizontal line at y -IMPLOT_API void FitPointY(double y); +static inline void FitPointY(double y) { + const ImPlotYAxis y_axis = GImPlot->CurrentPlot->CurrentYAxis; + FitPointAxis(GImPlot->CurrentPlot->YAxis[y_axis], GImPlot->ExtentsY[y_axis], y); +} +// Extends the current plot's axes so that it encompasses point p +static inline void FitPoint(const ImPlotPoint& p) { + const ImPlotYAxis y_axis = GImPlot->CurrentPlot->CurrentYAxis; + FitPointMultiAxis(GImPlot->CurrentPlot->XAxis, GImPlot->CurrentPlot->YAxis[y_axis], GImPlot->ExtentsX, p.x, p.y); + FitPointMultiAxis(GImPlot->CurrentPlot->YAxis[y_axis], GImPlot->CurrentPlot->XAxis, GImPlot->ExtentsY[y_axis], p.y, p.x); +} // Returns true if two ranges overlap static inline bool RangesOverlap(const ImPlotRange& r1, const ImPlotRange& r2) diff --git a/implot_items.cpp b/implot_items.cpp index b9322cf..9f94492 100644 --- a/implot_items.cpp +++ b/implot_items.cpp @@ -918,6 +918,8 @@ inline void PlotLineEx(const char* label_id, const Getter& getter) { } // render markers if (s.Marker != ImPlotMarker_None) { + PopPlotClipRect(); + PushPlotClipRect(s.MarkerSize); const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerOutline]); const ImU32 col_fill = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerFill]); switch (GetCurrentScale()) { @@ -989,6 +991,8 @@ inline void PlotScatterEx(const char* label_id, const Getter& getter) { // render markers ImPlotMarker marker = s.Marker == ImPlotMarker_None ? ImPlotMarker_Circle : s.Marker; if (marker != ImPlotMarker_None) { + PopPlotClipRect(); + PushPlotClipRect(s.MarkerSize); const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerOutline]); const ImU32 col_fill = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerFill]); switch (GetCurrentScale()) { @@ -1068,6 +1072,8 @@ inline void PlotStairsEx(const char* label_id, const Getter& getter) { } // render markers if (s.Marker != ImPlotMarker_None) { + PopPlotClipRect(); + PushPlotClipRect(s.MarkerSize); const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerOutline]); const ImU32 col_fill = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerFill]); switch (GetCurrentScale()) { @@ -1550,6 +1556,8 @@ inline void PlotStemsEx(const char* label_id, const GetterM& get_mark, const Get // render markers ImPlotMarker marker = s.Marker == ImPlotMarker_None ? ImPlotMarker_Circle : s.Marker; if (marker != ImPlotMarker_None) { + PopPlotClipRect(); + PushPlotClipRect(s.MarkerSize); const ImU32 col_line = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerOutline]); const ImU32 col_fill = ImGui::GetColorU32(s.Colors[ImPlotCol_MarkerFill]); switch (GetCurrentScale()) {