From d432920439bc99dece2d571253697a7e35ed5df8 Mon Sep 17 00:00:00 2001 From: Josh Pieper Date: Sat, 9 May 2020 09:13:37 -0400 Subject: [PATCH 01/10] Fix a trivial documentation copy-paste-o --- implot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implot.h b/implot.h index 0ffbeb7..90fa7f1 100644 --- a/implot.h +++ b/implot.h @@ -228,7 +228,7 @@ void PopPlotStyleVar(int count = 1); void SetNextPlotRange(float x_min, float x_max, float y_min, float y_max, ImGuiCond cond = ImGuiCond_Once); /// Set the X axis range of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the axis will be locked. void SetNextPlotRangeX(float x_min, float x_max, ImGuiCond cond = ImGuiCond_Once); -/// Set the X axis range of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the axis will be locked. +/// Set the Y axis range of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the axis will be locked. void SetNextPlotRangeY(float y_min, float y_max, ImGuiCond cond = ImGuiCond_Once); // Get the current Plot position (top-left) in pixels. From e7d0b182be2af404fe5dbdcfca2afda384067756 Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Sat, 9 May 2020 09:08:38 -0500 Subject: [PATCH 02/10] Update implot_demo.cpp --- implot_demo.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/implot_demo.cpp b/implot_demo.cpp index 758d5de..e43aa9d 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -21,6 +21,9 @@ // SOFTWARE. // ImPlot v0.1 WIP +#ifdef _MSC_VER +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#endif #include #include From 1db64ff20993b1b14bd1dad1d9e8ab71b5c803ba Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Sat, 9 May 2020 09:09:05 -0500 Subject: [PATCH 03/10] Update implot.cpp --- implot.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/implot.cpp b/implot.cpp index e6d24a2..fee94bf 100644 --- a/implot.cpp +++ b/implot.cpp @@ -22,6 +22,10 @@ // ImPlot v0.1 WIP +#ifdef _MSC_VER +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#endif + #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif From 9c19c20aae8495f83a496d226587b7643c26fb1e Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Sat, 9 May 2020 09:09:30 -0500 Subject: [PATCH 04/10] Update implot_demo.cpp --- implot_demo.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/implot_demo.cpp b/implot_demo.cpp index e43aa9d..2ea7b6e 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -21,6 +21,7 @@ // SOFTWARE. // ImPlot v0.1 WIP + #ifdef _MSC_VER #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen #endif From 8d15bb6c411f82c44e9c145f4c6a817eae5815ca Mon Sep 17 00:00:00 2001 From: Josh Pieper Date: Sun, 10 May 2020 08:38:23 -0400 Subject: [PATCH 05/10] Remove uses of the C++11 auto feature --- implot.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/implot.cpp b/implot.cpp index fee94bf..596945b 100644 --- a/implot.cpp +++ b/implot.cpp @@ -345,7 +345,7 @@ static ImPlotContext gp; /// Returns the next unused default plot color ImVec4 NextColor() { - auto col = gp.ColorMap[gp.CurrentPlot->ColorIdx % gp.ColorMap.size()]; + ImVec4 col = gp.ColorMap[gp.CurrentPlot->ColorIdx % gp.ColorMap.size()]; gp.CurrentPlot->ColorIdx++; return col; } @@ -550,7 +550,7 @@ inline void GetTicks(float tMin, float tMax, int nMajor, int nMinor, bool logsca inline void LabelTicks(ImVector &ticks, bool scientific, ImGuiTextBuffer& buffer) { buffer.Buf.resize(0); char temp[32]; - for (auto &tk : ticks) { + for (ImTick &tk : ticks) { if (tk.RenderLabel) { tk.TextOffset = buffer.size(); if (scientific) @@ -730,7 +730,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons float max_label_width = 0; if (HasFlag(plot.YAxis.Flags, ImAxisFlags_TickLabels)) { LabelTicks(gp.YTicks, HasFlag(plot.YAxis.Flags, ImAxisFlags_Scientific), gp.YTickLabels); - for (auto &yt : gp.YTicks) + for (ImTick &yt : gp.YTicks) max_label_width = yt.Size.x > max_label_width ? yt.Size.x : max_label_width; } @@ -990,22 +990,22 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // transform ticks if (gp.RenderX) { - for (auto& xt : gp.XTicks) + for (ImTick& xt : gp.XTicks) xt.PixelPos = PlotToPixels((float)xt.PlotPos, 0).x; } if (gp.RenderY) { - for (auto& yt : gp.YTicks) + for (ImTick& yt : gp.YTicks) yt.PixelPos = PlotToPixels(0, (float)yt.PlotPos).y; } // render grid if (HasFlag(plot.XAxis.Flags, ImAxisFlags_GridLines)) { - for (auto &xt : gp.XTicks) + for (ImTick &xt : gp.XTicks) DrawList.AddLine({xt.PixelPos, gp.BB_Grid.Min.y}, {xt.PixelPos, gp.BB_Grid.Max.y}, xt.Major ? gp.Col_XMajor : gp.Col_XMinor, 1); } if (HasFlag(plot.YAxis.Flags, ImAxisFlags_GridLines)) { - for (auto &yt : gp.YTicks) + for (ImTick &yt : gp.YTicks) 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); } @@ -1019,7 +1019,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // render labels if (HasFlag(plot.XAxis.Flags, ImAxisFlags_TickLabels)) { PushClipRect(gp.BB_Frame.Min, gp.BB_Frame.Max, true); - for (auto &xt : gp.XTicks) { + for (ImTick &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); } @@ -1033,7 +1033,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } if (HasFlag(plot.YAxis.Flags, ImAxisFlags_TickLabels)) { PushClipRect(gp.BB_Frame.Min, gp.BB_Frame.Max, true); - for (auto &yt : gp.YTicks) { + for (ImTick &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); } @@ -1196,11 +1196,11 @@ void EndPlot() { // render ticks if (HasFlag(plot.XAxis.Flags, ImAxisFlags_TickMarks)) { - for (auto &xt : gp.XTicks) + for (ImTick &xt : gp.XTicks) DrawList.AddLine({xt.PixelPos, gp.BB_Grid.Max.y},{xt.PixelPos, gp.BB_Grid.Max.y - (xt.Major ? 10.0f : 5.0f)}, gp.Col_Border, 1); } if (HasFlag(plot.YAxis.Flags, ImAxisFlags_TickMarks)) { - for (auto &yt : gp.YTicks) + for (ImTick &yt : gp.YTicks) DrawList.AddLine({gp.BB_Grid.Min.x, yt.PixelPos}, {gp.BB_Grid.Min.x + (yt.Major ? 10.0f : 5.0f), yt.PixelPos}, gp.Col_Border, 1); } @@ -1255,7 +1255,7 @@ void EndPlot() { float max_label_width = 0; for (int i = 0; i < nItems; ++i) { const char* label = GetLegendLabel(i); - auto labelWidth = CalcTextSize(label, NULL, true); + ImVec2 labelWidth = CalcTextSize(label, NULL, true); max_label_width = labelWidth.x > max_label_width ? labelWidth.x : max_label_width; } legend_content_bb = ImRect(gp.BB_Grid.Min + legend_offset, gp.BB_Grid.Min + legend_offset + ImVec2(max_label_width, nItems * txt_ht)); @@ -1282,7 +1282,7 @@ void EndPlot() { item->Highlight = false; ImU32 iconColor; if (hov_legend && icon_bb.Contains(IO.MousePos)) { - auto colAlpha = item->Color; + ImVec4 colAlpha = item->Color; colAlpha.w = 0.5f; iconColor = item->Show ? GetColorU32(colAlpha) : GetColorU32(ImGuiCol_TextDisabled, 0.5f); @@ -2278,7 +2278,7 @@ inline void PlotDigitalEx(const char* label_id, Getter getter, int count, int of if (pMax.x > gp.PixelRange.Max.x) pMax.x = gp.PixelRange.Max.x; //plot a rectangle that extends up to x2 with y1 height if ((pMax.x > pMin.x) && (!cull || gp.BB_Grid.Contains(pMin) || gp.BB_Grid.Contains(pMax))) { - auto colAlpha = item->Color; + ImVec4 colAlpha = item->Color; colAlpha.w = item->Highlight ? 1.0 : 0.9; DrawList.AddRectFilled(pMin, pMax, GetColorU32(colAlpha)); } From 61a3124ca9bbb6368ec6552a1253b7d1047df2d6 Mon Sep 17 00:00:00 2001 From: Andras Kucsma Date: Sun, 10 May 2020 18:19:54 +0200 Subject: [PATCH 06/10] Identify NANs with isnan() In C/C++ NAN == NAN is false, so isnan() must be used to identify NAN values. --- implot.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/implot.cpp b/implot.cpp index 596945b..ef79452 100644 --- a/implot.cpp +++ b/implot.cpp @@ -104,7 +104,7 @@ inline float Remap(float x, float x0, float x1, float y0, float y1) { /// Turns NANs to 0s inline float ConstrainNan(float val) { - return val == NAN || val == -NAN ? 0 : val; + return isnan(val) ? 0 : val; } /// Turns INFINITYs to FLT_MAXs @@ -119,7 +119,7 @@ inline float ConstrainLog(float val) { /// Returns true if val is NAN or INFINITY inline bool NanOrInf(float val) { - return val == INFINITY || val == NAN || val == -INFINITY || val == -NAN; + return val == INFINITY || val == -INFINITY || isnan(val); } /// Utility function to that rounds x to powers of 2,5 and 10 for generating axis labels From 5eb4b713849801ce675c14ebc825971da7b0e4e3 Mon Sep 17 00:00:00 2001 From: Josh Pieper Date: Sat, 9 May 2020 10:18:16 -0400 Subject: [PATCH 07/10] Support multiple Y axes simultaneously This allows up to 3 Y axes to be used, one on the left side, and up to 2 on the right. To use multiple axes, one of the `ImPlotFlags_Y2Axis` or `ImPlotFlags_Y3Axis` must be passed to `BeginPlot`. Then, before each Plot call, SetPlotYAxis may be used to set that plot on an alternate axis. An extra line and ticks are rendered off to the right if all 3 axes are configured (although those ticks are always only 5 long, regardless of major status). Each of the axes, when present, has a (possibly invisible if ticks and labels are disabled) hit target that can be used to scroll and scale that axis independently. Some other changes were required to make this happen: * Queries are now only in pixel coordinates * ImPlotRange has been renamed to ImPlotBounds (and correspondingly GetPlotRange is now GetPlotBounds) * All APIs which allow querying values in plot coordinates now have an optional "y_axis" argument, which can be -1 to use the currently selected axis, or non-negative to pick a specific axes. * BeginPlot used to lock all flags on the first call. Now it only locks the axis flags, and not the primary plot flags. * The mouseover text now renders the 2nd and 3rd axis in parenthesis. --- implot.cpp | 1099 +++++++++++++++++++++++++++++------------------ implot.h | 78 ++-- implot_demo.cpp | 83 +++- 3 files changed, 795 insertions(+), 465 deletions(-) diff --git a/implot.cpp b/implot.cpp index ef79452..725fe6f 100644 --- a/implot.cpp +++ b/implot.cpp @@ -65,22 +65,34 @@ ImPlotStyle::ImPlotStyle() { Colors[ImPlotCol_PlotBorder] = IM_COL_AUTO; Colors[ImPlotCol_XAxis] = IM_COL_AUTO; Colors[ImPlotCol_YAxis] = IM_COL_AUTO; + Colors[ImPlotCol_Y2Axis] = IM_COL_AUTO; + Colors[ImPlotCol_Y3Axis] = IM_COL_AUTO; Colors[ImPlotCol_Selection] = ImVec4(1,1,0,1); Colors[ImPlotCol_Query] = ImVec4(0,1,0,1); } -ImPlotRange::ImPlotRange() { - XMin = XMax = YMin = YMax = NAN; +ImPlotRange::ImPlotRange() : Min(NAN), Max(NAN) {} + +bool ImPlotRange::Contains(float v) const { + return v >= Min && v <= Max; } -bool ImPlotRange::Contains(const ImVec2& p) { - return p.x >= XMin && p.x <= XMax && p.y >= YMin && p.y <= YMax; +float ImPlotRange::Size() const { + return Max - Min; +} + +ImPlotBounds::ImPlotBounds() {} + +bool ImPlotBounds::Contains(const ImVec2& p) const { + return X.Contains(p.x) && Y.Contains(p.y); } namespace ImGui { namespace { +#define MAX_Y_AXES 3 + //----------------------------------------------------------------------------- // Private Utils //----------------------------------------------------------------------------- @@ -226,16 +238,14 @@ struct ImPlotItem { struct ImPlotAxis { ImPlotAxis() { Dragging = false; - Min = 0; - Max = 1; + Range.Min = 0; + Range.Max = 1; Divisions = 3; Subdivisions = 10; Flags = ImAxisFlags_Default; } - float Range() { return Max - Min; } bool Dragging; - float Min; - float Max; + ImPlotRange Range; int Divisions; int Subdivisions; ImAxisFlags Flags; @@ -248,6 +258,7 @@ struct ImPlot { SelectStart = QueryStart = ImVec2(0,0); Flags = ImPlotFlags_Default; ColorIdx = 0; + CurrentYAxis = 0; } ImPool Items; @@ -259,33 +270,30 @@ struct ImPlot { ImVec2 QueryStart; ImRect QueryRect; // relative to BB_grid!! bool DraggingQuery; - ImPlotRange QueryRange; ImPlotAxis XAxis; - ImPlotAxis YAxis; - inline ImPlotAxis& Axis(int idx) { return (&XAxis)[idx]; } + ImPlotAxis YAxis[MAX_Y_AXES]; ImPlotFlags Flags; int ColorIdx; + int CurrentYAxis; }; struct ImNextPlotData { - ImNextPlotData() { - HasXRange = false; - HasYRange = false; - } - ImGuiCond XRangeCond; - ImGuiCond YRangeCond; - bool HasXRange; - bool HasYRange; - float XMin, XMax, YMin, YMax; + ImNextPlotData() : HasXBounds{}, HasYBounds{} {} + ImGuiCond XBoundsCond; + ImGuiCond YBoundsCond[MAX_Y_AXES]; + bool HasXBounds; + bool HasYBounds[MAX_Y_AXES]; + ImPlotRange X; + ImPlotRange Y[MAX_Y_AXES]; }; /// Holds Plot state information that must persist only between calls to BeginPlot()/EndPlot() struct ImPlotContext { - ImPlotContext() { + ImPlotContext() : RenderX(), RenderY() { CurrentPlot = NULL; - FitThisFrame = FitX = FitY = false; + FitThisFrame = FitX = false; RestorePlotPalette(); } @@ -307,25 +315,37 @@ struct ImPlotContext { 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 - ImVector XTicks, YTicks; - ImGuiTextBuffer XTickLabels, YTickLabels; + Col_QryBg, Col_QryBd; + struct AxisColor { + AxisColor() : Major(), Minor(), Txt() {} + ImU32 Major, Minor, Txt; + }; + AxisColor Col_X; + AxisColor Col_Y[MAX_Y_AXES]; + // Tick marks + ImVector XTicks, YTicks[MAX_Y_AXES]; + ImGuiTextBuffer XTickLabels, YTickLabels[MAX_Y_AXES]; + float AxisLabelReference[MAX_Y_AXES]; // Transformation cache - ImRect PixelRange; - ImVec2 M; // linear scale (slope) - ImVec2 LogDen; // log scale denominator + ImRect PixelRange[MAX_Y_AXES]; + // linear scale (slope) + float Mx; + float My[MAX_Y_AXES]; + // log scale denominator + float LogDenX; + float LogDenY[MAX_Y_AXES]; // Data extents - ImRect Extents; - bool FitThisFrame; bool FitX; bool FitY; + ImPlotRange ExtentsX; + ImPlotRange ExtentsY[MAX_Y_AXES]; + + bool FitThisFrame; bool FitX; + bool FitY[MAX_Y_AXES] = {}; int VisibleItemCount; // Render flags - bool RenderX, RenderY; + bool RenderX, RenderY[MAX_Y_AXES]; // Mouse pos - ImVec2 LastMousePos; + ImVec2 LastMousePos[MAX_Y_AXES]; // Style ImVector ColorMap; ImPlotStyle Style; @@ -351,13 +371,15 @@ ImVec4 NextColor() { } inline void FitPoint(const ImVec2& p) { + ImPlotRange* extents_x = &gp.ExtentsX; + ImPlotRange* extents_y = &gp.ExtentsY[gp.CurrentPlot->CurrentYAxis]; 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; + extents_x->Min = p.x < extents_x->Min ? p.x : extents_x->Min; + extents_x->Max = p.x > extents_x->Max ? p.x : extents_x->Max; } 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; + extents_y->Min = p.y < extents_y->Min ? p.y : extents_y->Min; + extents_y->Max = p.y > extents_y->Max ? p.y : extents_y->Max; } } @@ -367,112 +389,118 @@ inline void FitPoint(const ImVec2& p) { 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); + for (int i = 0; i < MAX_Y_AXES; i++) { + gp.PixelRange[i] = ImRect(HasFlag(gp.CurrentPlot->XAxis.Flags, ImAxisFlags_Invert) ? gp.BB_Grid.Max.x : gp.BB_Grid.Min.x, + HasFlag(gp.CurrentPlot->YAxis[i].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[i].Flags, ImAxisFlags_Invert) ? gp.BB_Grid.Max.y : gp.BB_Grid.Min.y); + + gp.My[i] = (gp.PixelRange[i].Max.y - gp.PixelRange[i].Min.y) / gp.CurrentPlot->YAxis[i].Range.Size(); + } + gp.LogDenX = log10(gp.CurrentPlot->XAxis.Range.Max / gp.CurrentPlot->XAxis.Range.Min); + for (int i = 0; i < MAX_Y_AXES; i++) { + gp.LogDenY[i] = log10(gp.CurrentPlot->YAxis[i].Range.Max / gp.CurrentPlot->YAxis[i].Range.Min); + } + gp.Mx = (gp.PixelRange[0].Max.x - gp.PixelRange[0].Min.x) / gp.CurrentPlot->XAxis.Range.Size(); } -inline ImVec2 PixelsToPlot(float x, float y) { +inline ImVec2 PixelsToPlot(float x, float y, int y_axis_in = -1) { IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PixelsToPlot() Needs to be called between BeginPlot() and EndPlot()!"); + const int y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; 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; + plt.x = (x - gp.PixelRange[y_axis].Min.x) / gp.Mx + gp.CurrentPlot->XAxis.Range.Min; + plt.y = (y - gp.PixelRange[y_axis].Min.y) / gp.My[y_axis] + gp.CurrentPlot->YAxis[y_axis].Range.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; + float t = (plt.x - gp.CurrentPlot->XAxis.Range.Min) / gp.CurrentPlot->XAxis.Range.Size(); + plt.x = pow(10.0f, t * gp.LogDenX) * gp.CurrentPlot->XAxis.Range.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; + if (HasFlag(gp.CurrentPlot->YAxis[y_axis].Flags, ImAxisFlags_LogScale)) { + float t = (plt.y - gp.CurrentPlot->YAxis[y_axis].Range.Min) / gp.CurrentPlot->YAxis[y_axis].Range.Size(); + plt.y = pow(10.0f, t * gp.LogDenY[y_axis]) * gp.CurrentPlot->YAxis[y_axis].Range.Min; } return plt; } -inline ImVec2 PlotToPixels(float x, float y) { +inline ImVec2 PlotToPixels(float x, float y, int y_axis_in = -1) { IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "PlotToPixels() Needs to be called between BeginPlot() and EndPlot()!"); + const int y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; 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); + float t = log10(x / gp.CurrentPlot->XAxis.Range.Min) / gp.LogDenX; + x = ImLerp(gp.CurrentPlot->XAxis.Range.Min, gp.CurrentPlot->XAxis.Range.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); + if (HasFlag(gp.CurrentPlot->YAxis[y_axis].Flags, ImAxisFlags_LogScale)) { + float t = log10(y / gp.CurrentPlot->YAxis[y_axis].Range.Min) / gp.LogDenY[y_axis]; + y = ImLerp(gp.CurrentPlot->YAxis[y_axis].Range.Min, gp.CurrentPlot->YAxis[y_axis].Range.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); + pix.x = gp.PixelRange[y_axis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min); + pix.y = gp.PixelRange[y_axis].Min.y + gp.My[y_axis] * (y - gp.CurrentPlot->YAxis[y_axis].Range.Min); return pix; } -ImVec2 PixelsToPlot(const ImVec2& pix) { - return PixelsToPlot(pix.x, pix.y); +ImVec2 PixelsToPlot(const ImVec2& pix, int y_axis) { + return PixelsToPlot(pix.x, pix.y, y_axis); } -ImVec2 PlotToPixels(const ImVec2& plt) { - return PlotToPixels(plt.x, plt.y); +ImVec2 PlotToPixels(const ImVec2& plt, int y_axis) { + return PlotToPixels(plt.x, plt.y, y_axis); } 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) }; - } -}; + Plt2PixLinLin(int y_axis_in) : y_axis(y_axis_in) {} -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; + ImVec2 operator()(const ImVec2& plt) { return (*this)(plt.x, plt.y); } + ImVec2 operator()(float x, float y) { + return { gp.PixelRange[y_axis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min), + gp.PixelRange[y_axis].Min.y + gp.My[y_axis] * (y - gp.CurrentPlot->YAxis[y_axis].Range.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; -}; + int y_axis; +}; 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) }; + Plt2PixLogLin(int y_axis_in) : y_axis(y_axis_in) {} + + ImVec2 operator()(const ImVec2& plt) { return (*this)(plt.x, plt.y); } + ImVec2 operator()(float x, float y) { + float t = log10(x / gp.CurrentPlot->XAxis.Range.Min) / gp.LogDenX; + x = ImLerp(gp.CurrentPlot->XAxis.Range.Min, gp.CurrentPlot->XAxis.Range.Max, t); + return { gp.PixelRange[y_axis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min), + gp.PixelRange[y_axis].Min.y + gp.My[y_axis] * (y - gp.CurrentPlot->YAxis[y_axis].Range.Min) }; } + + int y_axis; }; 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) }; + Plt2PixLinLog(int y_axis_in) : y_axis(y_axis_in) {} + + ImVec2 operator()(const ImVec2& plt) { return (*this)(plt.x, plt.y); } + ImVec2 operator()(float x, float y) { + float t = log10(y / gp.CurrentPlot->YAxis[y_axis].Range.Min) / gp.LogDenY[y_axis]; + y = ImLerp(gp.CurrentPlot->YAxis[y_axis].Range.Min, gp.CurrentPlot->YAxis[y_axis].Range.Max, t); + return { gp.PixelRange[y_axis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min), + gp.PixelRange[y_axis].Min.y + gp.My[y_axis] * (y - gp.CurrentPlot->YAxis[y_axis].Range.Min) }; } + + int y_axis; }; 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) }; + Plt2PixLogLog(int y_axis_in) : y_axis(y_axis_in) {} + + ImVec2 operator()(const ImVec2& plt) { return (*this)(plt.x, plt.y); } + ImVec2 operator()(float x, float y) { + float t = log10(x / gp.CurrentPlot->XAxis.Range.Min) / gp.LogDenX; + x = ImLerp(gp.CurrentPlot->XAxis.Range.Min, gp.CurrentPlot->XAxis.Range.Max, t); + t = log10(y / gp.CurrentPlot->YAxis[y_axis].Range.Min) / gp.LogDenY[y_axis]; + y = ImLerp(gp.CurrentPlot->YAxis[y_axis].Range.Min, gp.CurrentPlot->YAxis[y_axis].Range.Max, t); + return { gp.PixelRange[y_axis].Min.x + gp.Mx * (x - gp.CurrentPlot->XAxis.Range.Min), + gp.PixelRange[y_axis].Min.y + gp.My[y_axis] * (y - gp.CurrentPlot->YAxis[y_axis].Range.Min) }; } + + int y_axis; }; //----------------------------------------------------------------------------- @@ -510,37 +538,37 @@ const char* GetLegendLabel(int i) { // Tick Utils //----------------------------------------------------------------------------- -inline void GetTicks(float tMin, float tMax, int nMajor, int nMinor, bool logscale, ImVector &out) { +inline void GetTicks(const ImPlotRange& scale, int nMajor, int nMinor, bool logscale, ImVector &out) { out.shrink(0); if (logscale) { - if (tMin <= 0 || tMax <= 0) + if (scale.Min <= 0 || scale.Max <= 0) return; - int exp_min = (int)(ImFloor(log10(tMin))); - int exp_max = (int)(ImCeil(log10(tMax))); + int exp_min = (int)(ImFloor(log10(scale.Min))); + int exp_max = (int)(ImCeil(log10(scale.Max))); for (int e = exp_min - 1; e < exp_max + 1; ++e) { double major1 = ImPow(10, (double)(e)); double major2 = ImPow(10, (double)(e + 1)); double interval = (major2 - major1) / 9; - if (major1 >= (tMin - FLT_EPSILON) && major1 <= (tMax + FLT_EPSILON)) + if (major1 >= (scale.Min - FLT_EPSILON) && major1 <= (scale.Max + FLT_EPSILON)) out.push_back(ImTick(major1, true)); for (int i = 1; i < 9; ++i) { double minor = major1 + i * interval; - if (minor >= (tMin - FLT_EPSILON) && minor <= (tMax + FLT_EPSILON)) + if (minor >= (scale.Min - FLT_EPSILON) && minor <= (scale.Max + FLT_EPSILON)) out.push_back(ImTick(minor, false, false)); } } } else { - const double range = NiceNum(tMax - tMin, 0); + const double range = NiceNum(scale.Max - scale.Min, 0); const double interval = NiceNum(range / (nMajor - 1), 1); - const double graphmin = floor(tMin / interval) * interval; - const double graphmax = ceil(tMax / interval) * interval; + const double graphmin = floor(scale.Min / interval) * interval; + const double graphmax = ceil(scale.Max / interval) * interval; for (double major = graphmin; major < graphmax + 0.5 * interval; major += interval) { - if (major >= tMin && major <= tMax) + if (major >= scale.Min && major <= scale.Max) out.push_back(ImTick(major, true)); for (int i = 1; i < nMinor; ++i) { double minor = major + i * interval / nMinor; - if (minor >= tMin && minor <= tMax) + if (minor >= scale.Min && minor <= scale.Max) out.push_back(ImTick(minor, false)); } } @@ -563,11 +591,88 @@ inline void LabelTicks(ImVector &ticks, bool scientific, ImGuiTextBuffer } } +namespace { +struct AxisState { + ImPlotAxis* axis; + bool has_range; + ImGuiCond range_cond; + bool present; + int present_so_far; + bool flip; + bool lock_min; + bool lock_max; + bool lock; + + AxisState(ImPlotAxis& axis_in, bool has_range_in, ImGuiCond range_cond_in, + bool present_in, int previous_present) + : axis(&axis_in), + has_range(has_range_in), + range_cond(range_cond_in), + present(present_in), + present_so_far(previous_present + (present ? 1 : 0)), + flip(HasFlag(axis->Flags, ImAxisFlags_Invert)), + lock_min(HasFlag(axis->Flags, ImAxisFlags_LockMin)), + lock_max(HasFlag(axis->Flags, ImAxisFlags_LockMax)), + lock(present && ((lock_min && lock_max) || (has_range && range_cond == ImGuiCond_Always))) {} + + AxisState() + : axis(), + has_range(), + range_cond(), + present(), + present_so_far(), + flip(), + lock_min(), + lock_max(), + lock() {} +}; + +void UpdateAxisColor(int axis_flag, ImPlotContext::AxisColor* col) { + const ImVec4 col_Axis = gp.Style.Colors[axis_flag].w == -1 ? ImGui::GetStyle().Colors[ImGuiCol_Text] * ImVec4(1, 1, 1, 0.25f) : gp.Style.Colors[axis_flag]; + col->Major = GetColorU32(col_Axis); + col->Minor = GetColorU32(col_Axis * ImVec4(1, 1, 1, 0.25f)); + col->Txt = GetColorU32({col_Axis.x, col_Axis.y, col_Axis.z, 1}); +} + +ImRect GetAxisScale(int y_axis, float tx, float ty, float zoom_rate) { + return ImRect( + PixelsToPlot(gp.BB_Grid.Min - gp.BB_Grid.GetSize() * ImVec2(tx * zoom_rate, ty * zoom_rate), y_axis), + PixelsToPlot(gp.BB_Grid.Max + gp.BB_Grid.GetSize() * ImVec2((1 - tx) * zoom_rate, (1 - ty) * zoom_rate), y_axis)); +} + +class YPadCalculator { + public: + YPadCalculator(const AxisState* axis_states, const float* max_label_widths, int txt_off) + : AxisStates(axis_states), MaxLabelWidths(max_label_widths), TxtOff(txt_off) {} + + float operator()(int y_axis) { + ImPlot& plot = *gp.CurrentPlot; + if (!AxisStates[y_axis].present) { return 0; } + // If we have more than 1 axis present before us, then we need + // extra space to account for our tick bar. + int pad_result = 0; + if (AxisStates[y_axis].present_so_far >= 3) { + pad_result += 6; + } + if (!HasFlag(plot.YAxis[y_axis].Flags, ImAxisFlags_TickLabels)) { + return pad_result; + } + pad_result += MaxLabelWidths[y_axis] + TxtOff; + return pad_result; + } + + private: + const AxisState* const AxisStates; + const float* const MaxLabelWidths; + const int TxtOff; +}; +} // 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) { +bool BeginPlot(const char* title, const char* x_label, const char* y_label, const ImVec2& size, ImPlotFlags flags, ImAxisFlags x_flags, ImAxisFlags y_flags, ImAxisFlags y2_flags, ImAxisFlags y3_flags) { IM_ASSERT_USER_ERROR(gp.CurrentPlot == NULL, "Mismatched BeginPlot()/EndPlot()!"); @@ -588,10 +693,14 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons gp.CurrentPlot = gp.Plots.GetOrAddByKey(ID); ImPlot &plot = *gp.CurrentPlot; + plot.CurrentYAxis = 0; + + plot.Flags = flags; if (just_created) { - plot.Flags = flags; plot.XAxis.Flags = x_flags; - plot.YAxis.Flags = y_flags; + plot.YAxis[0].Flags = y_flags; + plot.YAxis[1].Flags = y2_flags; + plot.YAxis[2].Flags = y3_flags; } // capture scroll with a child region @@ -605,56 +714,59 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // NextPlotData ----------------------------------------------------------- - if (gp.NextPlotData.HasXRange) { - if (just_created || gp.NextPlotData.XRangeCond == ImGuiCond_Always) + if (gp.NextPlotData.HasXBounds) { + if (just_created || gp.NextPlotData.XBoundsCond == ImGuiCond_Always) { - plot.XAxis.Min = gp.NextPlotData.XMin; - plot.XAxis.Max = gp.NextPlotData.XMax; + plot.XAxis.Range = gp.NextPlotData.X; } } - if (gp.NextPlotData.HasYRange) { - if (just_created || gp.NextPlotData.YRangeCond == ImGuiCond_Always) - { - plot.YAxis.Min = gp.NextPlotData.YMin; - plot.YAxis.Max = gp.NextPlotData.YMax; + for (int i = 0; i < MAX_Y_AXES; i++) { + if (gp.NextPlotData.HasYBounds[i]) { + if (just_created || gp.NextPlotData.YBoundsCond[i] == ImGuiCond_Always) + { + plot.YAxis[i].Range = gp.NextPlotData.Y[i]; + } } } // AXIS STATES ------------------------------------------------------------ + AxisState x(plot.XAxis, gp.NextPlotData.HasXBounds, gp.NextPlotData.XBoundsCond, true, 0); + AxisState y[MAX_Y_AXES]; + y[0] = AxisState(plot.YAxis[0], gp.NextPlotData.HasYBounds[0], gp.NextPlotData.YBoundsCond[0], true, 0); + y[1] = AxisState(plot.YAxis[1], gp.NextPlotData.HasYBounds[1], gp.NextPlotData.YBoundsCond[1], + HasFlag(plot.Flags, ImPlotFlags_Y2Axis), y[0].present_so_far); + y[2] = AxisState(plot.YAxis[2], gp.NextPlotData.HasYBounds[2], gp.NextPlotData.YBoundsCond[2], + HasFlag(plot.Flags, ImPlotFlags_Y3Axis), y[1].present_so_far); - const bool flip_x = HasFlag(plot.XAxis.Flags, ImAxisFlags_Invert); - const bool lock_x_min = HasFlag(plot.XAxis.Flags, ImAxisFlags_LockMin); - const bool lock_x_max = HasFlag(plot.XAxis.Flags, ImAxisFlags_LockMax); - const bool lock_x = (lock_x_min && lock_x_max) || (gp.NextPlotData.HasXRange && gp.NextPlotData.XRangeCond == ImGuiCond_Always); + const bool lock_plot = x.lock && y[0].lock && y[1].lock && y[2].lock; - const bool flip_y = HasFlag(plot.YAxis.Flags, ImAxisFlags_Invert); - const bool lock_y_min = HasFlag(plot.YAxis.Flags, ImAxisFlags_LockMin); - const bool lock_y_max = HasFlag(plot.YAxis.Flags, ImAxisFlags_LockMax); - const bool lock_y = (lock_y_min && lock_y_max) || (gp.NextPlotData.HasYRange && gp.NextPlotData.YRangeCond == ImGuiCond_Always); - - const bool lock_plot = lock_x && lock_y; - // CONSTRAINTS ------------------------------------------------------------ - plot.XAxis.Min = ConstrainNan(ConstrainInf(plot.XAxis.Min)); - plot.XAxis.Max = ConstrainNan(ConstrainInf(plot.XAxis.Max)); - plot.YAxis.Min = ConstrainNan(ConstrainInf(plot.YAxis.Min)); - plot.YAxis.Max = ConstrainNan(ConstrainInf(plot.YAxis.Max)); + plot.XAxis.Range.Min = ConstrainNan(ConstrainInf(plot.XAxis.Range.Min)); + plot.XAxis.Range.Max = ConstrainNan(ConstrainInf(plot.XAxis.Range.Max)); + for (int i = 0; i < MAX_Y_AXES; i++) { + plot.YAxis[i].Range.Min = ConstrainNan(ConstrainInf(plot.YAxis[i].Range.Min)); + plot.YAxis[i].Range.Max = ConstrainNan(ConstrainInf(plot.YAxis[i].Range.Max)); + } if (HasFlag(plot.XAxis.Flags, ImAxisFlags_LogScale)) - plot.XAxis.Min = ConstrainLog(plot.XAxis.Min); + plot.XAxis.Range.Min = ConstrainLog(plot.XAxis.Range.Min); if (HasFlag(plot.XAxis.Flags, ImAxisFlags_LogScale)) - plot.XAxis.Max = ConstrainLog(plot.XAxis.Max); - if (HasFlag(plot.YAxis.Flags, ImAxisFlags_LogScale)) - plot.YAxis.Min = ConstrainLog(plot.YAxis.Min); - if (HasFlag(plot.YAxis.Flags, ImAxisFlags_LogScale)) - plot.YAxis.Max = ConstrainLog(plot.YAxis.Max); + plot.XAxis.Range.Max = ConstrainLog(plot.XAxis.Range.Max); + for (int i = 0; i < MAX_Y_AXES; i++) { + if (HasFlag(plot.YAxis[i].Flags, ImAxisFlags_LogScale)) + plot.YAxis[i].Range.Min = ConstrainLog(plot.YAxis[i].Range.Min); + if (HasFlag(plot.YAxis[i].Flags, ImAxisFlags_LogScale)) + plot.YAxis[i].Range.Max = ConstrainLog(plot.YAxis[i].Range.Max); + } - if (plot.XAxis.Max <= plot.XAxis.Min) - plot.XAxis.Max = plot.XAxis.Min + FLT_EPSILON; - if (plot.YAxis.Max <= plot.YAxis.Min) - plot.YAxis.Max = plot.YAxis.Min + FLT_EPSILON; + if (plot.XAxis.Range.Max <= plot.XAxis.Range.Min) + plot.XAxis.Range.Max = plot.XAxis.Range.Min + FLT_EPSILON; + for (int i = 0; i < MAX_Y_AXES; i++) { + if (plot.YAxis[i].Range.Max <= plot.YAxis[i].Range.Min) + plot.YAxis[i].Range.Max = plot.YAxis[i].Range.Min + FLT_EPSILON; + } // adaptive divisions if (HasFlag(plot.XAxis.Flags, ImAxisFlags_Adaptive)) { @@ -662,10 +774,12 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons if (plot.XAxis.Divisions < 2) plot.XAxis.Divisions = 2; } - if (HasFlag(plot.YAxis.Flags, ImAxisFlags_Adaptive)) { - plot.YAxis.Divisions = (int)IM_ROUND(0.003 * gp.BB_Canvas.GetHeight()); - if (plot.YAxis.Divisions < 2) - plot.YAxis.Divisions = 2; + for (int i = 0; i < MAX_Y_AXES; i++) { + if (HasFlag(plot.YAxis[i].Flags, ImAxisFlags_Adaptive)) { + plot.YAxis[i].Divisions = (int)IM_ROUND(0.003 * gp.BB_Canvas.GetHeight()); + if (plot.YAxis[i].Divisions < 2) + plot.YAxis[i].Divisions = 2; + } } // COLORS ----------------------------------------------------------------- @@ -674,15 +788,10 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons gp.Col_Bg = gp.Style.Colors[ImPlotCol_PlotBg].w == -1 ? GetColorU32(ImGuiCol_WindowBg) : GetColorU32(gp.Style.Colors[ImPlotCol_PlotBg]); gp.Col_Border = gp.Style.Colors[ImPlotCol_PlotBorder].w == -1 ? GetColorU32(ImGuiCol_Text, 0.5f) : GetColorU32(gp.Style.Colors[ImPlotCol_PlotBorder]); - const ImVec4 col_xAxis = gp.Style.Colors[ImPlotCol_XAxis].w == -1 ? ImGui::GetStyle().Colors[ImGuiCol_Text] * ImVec4(1, 1, 1, 0.25f) : gp.Style.Colors[ImPlotCol_XAxis]; - gp.Col_XMajor = GetColorU32(col_xAxis); - gp.Col_XMinor = GetColorU32(col_xAxis * ImVec4(1, 1, 1, 0.25f)); - gp.Col_XTxt = GetColorU32({col_xAxis.x, col_xAxis.y, col_xAxis.z, 1}); - - const ImVec4 col_yAxis = gp.Style.Colors[ImPlotCol_YAxis].w == -1 ? ImGui::GetStyle().Colors[ImGuiCol_Text] * ImVec4(1, 1, 1, 0.25f) : gp.Style.Colors[ImPlotCol_YAxis]; - gp.Col_YMajor = GetColorU32(col_yAxis); - gp.Col_YMinor = GetColorU32(col_yAxis * ImVec4(1, 1, 1, 0.25f)); - gp.Col_YTxt = GetColorU32({col_yAxis.x, col_yAxis.y, col_yAxis.z, 1}); + UpdateAxisColor(ImPlotCol_XAxis, &gp.Col_X); + UpdateAxisColor(ImPlotCol_YAxis, &gp.Col_Y[0]); + UpdateAxisColor(ImPlotCol_Y2Axis, &gp.Col_Y[1]); + UpdateAxisColor(ImPlotCol_Y3Axis, &gp.Col_Y[2]); gp.Col_Txt = GetColorU32(ImGuiCol_Text); gp.Col_TxtDis = GetColorU32(ImGuiCol_TextDisabled); @@ -710,28 +819,38 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // canvas bb gp.BB_Canvas = ImRect(gp.BB_Frame.Min + Style.WindowPadding, gp.BB_Frame.Max - Style.WindowPadding); - gp.RenderX = (HasFlag(plot.XAxis.Flags, ImAxisFlags_GridLines) || - HasFlag(plot.XAxis.Flags, ImAxisFlags_TickMarks) || + gp.RenderX = (HasFlag(plot.XAxis.Flags, ImAxisFlags_GridLines) || + HasFlag(plot.XAxis.Flags, ImAxisFlags_TickMarks) || HasFlag(plot.XAxis.Flags, ImAxisFlags_TickLabels)) && plot.XAxis.Divisions > 1; - gp.RenderY = (HasFlag(plot.YAxis.Flags, ImAxisFlags_GridLines) || - HasFlag(plot.YAxis.Flags, ImAxisFlags_TickMarks) || - HasFlag(plot.YAxis.Flags, ImAxisFlags_TickLabels)) && plot.YAxis.Divisions > 1; + for (int i = 0; i < MAX_Y_AXES; i++) { + gp.RenderY[i] = + y[i].present && + (HasFlag(plot.YAxis[i].Flags, ImAxisFlags_GridLines) || + HasFlag(plot.YAxis[i].Flags, ImAxisFlags_TickMarks) || + HasFlag(plot.YAxis[i].Flags, ImAxisFlags_TickLabels)) && plot.YAxis[i].Divisions > 1; + } // get ticks if (gp.RenderX) - GetTicks(plot.XAxis.Min, plot.XAxis.Max, plot.XAxis.Divisions, plot.XAxis.Subdivisions, HasFlag(plot.XAxis.Flags, ImAxisFlags_LogScale), gp.XTicks); - if (gp.RenderY) - GetTicks(plot.YAxis.Min, plot.YAxis.Max, plot.YAxis.Divisions, plot.YAxis.Subdivisions, HasFlag(plot.YAxis.Flags, ImAxisFlags_LogScale), gp.YTicks); + GetTicks(plot.XAxis.Range, plot.XAxis.Divisions, plot.XAxis.Subdivisions, HasFlag(plot.XAxis.Flags, ImAxisFlags_LogScale), gp.XTicks); + for (int i = 0; i < MAX_Y_AXES; i++) { + if (gp.RenderY[i]) { + GetTicks(plot.YAxis[i].Range, plot.YAxis[i].Divisions, plot.YAxis[i].Subdivisions, HasFlag(plot.YAxis[i].Flags, ImAxisFlags_LogScale), gp.YTicks[i]); + } + } // label ticks if (HasFlag(plot.XAxis.Flags, ImAxisFlags_TickLabels)) LabelTicks(gp.XTicks, HasFlag(plot.XAxis.Flags, ImAxisFlags_Scientific), gp.XTickLabels); - float max_label_width = 0; - if (HasFlag(plot.YAxis.Flags, ImAxisFlags_TickLabels)) { - LabelTicks(gp.YTicks, HasFlag(plot.YAxis.Flags, ImAxisFlags_Scientific), gp.YTickLabels); - for (ImTick &yt : gp.YTicks) - max_label_width = yt.Size.x > max_label_width ? yt.Size.x : max_label_width; + float max_label_width[MAX_Y_AXES] = {}; + for (int i = 0; i < MAX_Y_AXES; i++) { + if (y[i].present && HasFlag(plot.YAxis[i].Flags, ImAxisFlags_TickLabels)) { + LabelTicks(gp.YTicks[i], HasFlag(plot.YAxis[i].Flags, ImAxisFlags_Scientific), gp.YTickLabels[i]); + for (ImTick &yt : gp.YTicks[i]) { + max_label_width[i] = yt.Size.x > max_label_width[i] ? yt.Size.x : max_label_width[i]; + } + } } // grid bb @@ -740,34 +859,57 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons const float txt_height = GetTextLineHeight(); const float pad_top = title_size.x > 0.0f ? txt_height + txt_off : 0; const float pad_bot = (HasFlag(plot.XAxis.Flags, ImAxisFlags_TickLabels) ? txt_height + txt_off : 0) + (x_label ? txt_height + txt_off : 0); - const float pad_left = (HasFlag(plot.YAxis.Flags, ImAxisFlags_TickLabels) ? max_label_width + txt_off : 0) + (y_label ? txt_height + txt_off : 0); - gp.BB_Grid = ImRect(gp.BB_Canvas.Min + ImVec2(pad_left, pad_top), gp.BB_Canvas.Max - ImVec2(0, pad_bot)); + YPadCalculator y_axis_pad(y, max_label_width, txt_off); + const float pad_left = y_axis_pad(0) + (y_label ? txt_height + txt_off : 0); + const float pad_right = y_axis_pad(1) + y_axis_pad(2); + gp.BB_Grid = ImRect(gp.BB_Canvas.Min + ImVec2(pad_left, pad_top), gp.BB_Canvas.Max - ImVec2(pad_right, pad_bot)); gp.Hov_Grid = gp.BB_Grid.Contains(IO.MousePos); // axis region bbs const ImRect xAxisRegion_bb(gp.BB_Grid.Min + ImVec2(10, 0), {gp.BB_Grid.Max.x, gp.BB_Frame.Max.y}); const bool hov_x_axis_region = xAxisRegion_bb.Contains(IO.MousePos); - const ImRect yAxisRegion_bb({gp.BB_Frame.Min.x, gp.BB_Grid.Min.y}, gp.BB_Grid.Max - ImVec2(0, 10)); - const bool hov_y_axis_region = yAxisRegion_bb.Contains(IO.MousePos); + + // The left labels are referenced to the left of the bounding box. + gp.AxisLabelReference[0] = gp.BB_Grid.Min.x; + // If Y axis 1 is present, its labels will be referenced to the + // right of the bounding box. + gp.AxisLabelReference[1] = gp.BB_Grid.Max.x; + // The third axis may be either referenced to the right of the + // bounding box, or 6 pixels further past the end of the 2nd axis. + gp.AxisLabelReference[2] = + !y[1].present ? + gp.BB_Grid.Max.x : + (gp.AxisLabelReference[1] + y_axis_pad(1) + 6); + + ImRect yAxisRegion_bb[MAX_Y_AXES]; + yAxisRegion_bb[0] = ImRect({gp.BB_Frame.Min.x, gp.BB_Grid.Min.y}, {gp.BB_Grid.Min.x + 6, gp.BB_Grid.Max.y - 10}); + // The auxiliary y axes are off to the right of the BB grid. + yAxisRegion_bb[1] = ImRect({gp.BB_Grid.Max.x - 6, gp.BB_Grid.Min.y}, + gp.BB_Grid.Max + ImVec2(y_axis_pad(1), 0)); + yAxisRegion_bb[2] = ImRect({gp.AxisLabelReference[2] - 6, gp.BB_Grid.Min.y}, + yAxisRegion_bb[1].Max + ImVec2(y_axis_pad(2), 0)); + + ImRect centralRegion({gp.BB_Grid.Min.x + 6, gp.BB_Grid.Min.y}, + {gp.BB_Grid.Max.x - 6, gp.BB_Grid.Max.y}); + + const bool hov_y_axis_region[MAX_Y_AXES] = { + y[0].present && (yAxisRegion_bb[0].Contains(IO.MousePos) || centralRegion.Contains(IO.MousePos)), + y[1].present && (yAxisRegion_bb[1].Contains(IO.MousePos) || centralRegion.Contains(IO.MousePos)), + y[2].present && (yAxisRegion_bb[2].Contains(IO.MousePos) || centralRegion.Contains(IO.MousePos)), + }; + const bool any_hov_y_axis_region = + hov_y_axis_region[0] || hov_y_axis_region[1] || hov_y_axis_region[2]; // legend hovered from last frame const bool hov_legend = HasFlag(plot.Flags, ImPlotFlags_Legend) ? gp.Hov_Frame && plot.BB_Legend.Contains(IO.MousePos) : false; bool hov_query = false; if (plot.Queried && !plot.Querying) { - ImRect bb_query; - if (HasFlag(plot.Flags, ImPlotFlags_PixelQuery)) { - bb_query = plot.QueryRect; - bb_query.Min += gp.BB_Grid.Min; - bb_query.Max += gp.BB_Grid.Min; - } - else { - 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)); - } + ImRect bb_query = plot.QueryRect; + + bb_query.Min += gp.BB_Grid.Min; + bb_query.Max += gp.BB_Grid.Min; + hov_query = bb_query.Contains(IO.MousePos); } @@ -777,28 +919,14 @@ 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 = 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 = 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); - plot.QueryRange.XMax = ImMax(p1.x, p2.x); - plot.QueryRange.YMin = ImMin(p1.y, p2.y); - plot.QueryRange.YMax = ImMax(p1.y, p2.y); - } - else { - plot.QueryRect.Min += IO.MouseDelta; - plot.QueryRect.Max += IO.MouseDelta; - } + plot.QueryRect.Min += IO.MouseDelta; + plot.QueryRect.Max += IO.MouseDelta; } if (gp.Hov_Frame && hov_query && !plot.DraggingQuery && !plot.Selecting && !hov_legend) { SetMouseCursor(ImGuiMouseCursor_ResizeAll); - if (IO.MouseDown[0] && !plot.XAxis.Dragging && !plot.YAxis.Dragging) { + const bool any_y_dragging = + plot.YAxis[0].Dragging || plot.YAxis[1].Dragging || plot.YAxis[2].Dragging; + if (IO.MouseDown[0] && !plot.XAxis.Dragging && !any_y_dragging) { plot.DraggingQuery = true; } } @@ -810,64 +938,102 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.XAxis.Dragging = false; G.IO.MouseDragMaxDistanceSqr[0] = 0; } - if (plot.YAxis.Dragging && (IO.MouseReleased[0] || !IO.MouseDown[0])) { - plot.YAxis.Dragging = false; - G.IO.MouseDragMaxDistanceSqr[0] = 0; + for (int i = 0; i < MAX_Y_AXES; i++) { + if (plot.YAxis[i].Dragging && (IO.MouseReleased[0] || !IO.MouseDown[0])) { + plot.YAxis[i].Dragging = false; + G.IO.MouseDragMaxDistanceSqr[0] = 0; + } } // do drag - if (plot.XAxis.Dragging || plot.YAxis.Dragging) { + const bool any_y_dragging = + plot.YAxis[0].Dragging || plot.YAxis[1].Dragging || plot.YAxis[2].Dragging; + + if (plot.XAxis.Dragging || any_y_dragging) { 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; - if (!lock_x_max) - plot.XAxis.Max = flip_x ? plot_tl.x : plot_br.x; + if (!x.lock && plot.XAxis.Dragging) { + ImVec2 plot_tl = PixelsToPlot(gp.BB_Grid.Min - IO.MouseDelta, 0); + ImVec2 plot_br = PixelsToPlot(gp.BB_Grid.Max - IO.MouseDelta, 0); + if (!x.lock_min) + plot.XAxis.Range.Min = x.flip ? plot_br.x : plot_tl.x; + if (!x.lock_max) + plot.XAxis.Range.Max = x.flip ? plot_tl.x : plot_br.x; } - if (!lock_y && plot.YAxis.Dragging) { - if (!lock_y_min) - plot.YAxis.Min = flip_y ? plot_tl.y : plot_br.y; - if (!lock_y_max) - plot.YAxis.Max = flip_y ? plot_br.y : plot_tl.y; + for (int i = 0; i < MAX_Y_AXES; i++) { + if (!y[i].lock && plot.YAxis[i].Dragging) { + ImVec2 plot_tl = PixelsToPlot(gp.BB_Grid.Min - IO.MouseDelta, i); + ImVec2 plot_br = PixelsToPlot(gp.BB_Grid.Max - IO.MouseDelta, i); + + if (!y[i].lock_min) + plot.YAxis[i].Range.Min = y[i].flip ? plot_tl.y : plot_br.y; + if (!y[i].lock_max) + plot.YAxis[i].Range.Max = y[i].flip ? plot_br.y : plot_tl.y; + } } - if ((lock_x && lock_y) || (lock_x && plot.XAxis.Dragging && !plot.YAxis.Dragging) || (lock_y && plot.YAxis.Dragging && !plot.XAxis.Dragging)) + // Set the mouse cursor based on which axes are moving. + int direction = 0; + if (!x.lock && plot.XAxis.Dragging) { + direction |= (1 << 1); + } + for (int i = 0; i < MAX_Y_AXES; i++) { + if (!y[i].present) { continue; } + if (!y[i].lock && plot.YAxis[i].Dragging) { + direction |= (1 << 2); + break; + } + } + + if (direction == 0) { ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); - else if (lock_x || (!plot.XAxis.Dragging && plot.YAxis.Dragging)) - ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); - else if (lock_y || (!plot.YAxis.Dragging && plot.XAxis.Dragging)) + } else if (direction == (1 << 1)) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); - else + } else if (direction == (1 << 2)) { + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); + } else { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeAll); + } } // start drag - if (gp.Hov_Frame && hov_x_axis_region && IO.MouseDragMaxDistanceSqr[0] > 5 && !plot.Selecting && !hov_legend && !hov_query && !plot.DraggingQuery) - plot.XAxis.Dragging = true; - if (gp.Hov_Frame && hov_y_axis_region && IO.MouseDragMaxDistanceSqr[0] > 5 && !plot.Selecting && !hov_legend && !hov_query && !plot.DraggingQuery) - plot.YAxis.Dragging = true; + if (gp.Hov_Frame && IO.MouseDragMaxDistanceSqr[0] > 5 && !plot.Selecting && !hov_legend && !hov_query && !plot.DraggingQuery) { + if (hov_x_axis_region) { + plot.XAxis.Dragging = true; + } + for (int i = 0; i < MAX_Y_AXES; i++) { + if (hov_y_axis_region[i]) { + plot.YAxis[i].Dragging = true; + } + } + } // SCROLL INPUT ----------------------------------------------------------- - if (gp.Hov_Frame && (hov_x_axis_region || hov_y_axis_region) && IO.MouseWheel != 0) { + if (gp.Hov_Frame && (hov_x_axis_region || any_hov_y_axis_region) && IO.MouseWheel != 0) { 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 = 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; - if (!lock_x_max) - plot.XAxis.Max = flip_x ? plot_tl.x : plot_br.x; - } - if (hov_y_axis_region && !lock_y) { - if (!lock_y_min) - plot.YAxis.Min = flip_y ? plot_tl.y : plot_br.y; - if (!lock_y_max) - plot.YAxis.Max = flip_y ? plot_br.y : plot_tl.y; + if (hov_x_axis_region && !x.lock) { + ImRect axis_scale = GetAxisScale(0, tx, ty, zoom_rate); + const ImVec2& plot_tl = axis_scale.Min; + const ImVec2& plot_br = axis_scale.Max; + + if (!x.lock_min) + plot.XAxis.Range.Min = x.flip ? plot_br.x : plot_tl.x; + if (!x.lock_max) + plot.XAxis.Range.Max = x.flip ? plot_tl.x : plot_br.x; + } + for (int i = 0; i < MAX_Y_AXES; i++) { + if (hov_y_axis_region[i] && !y[i].lock) { + ImRect axis_scale = GetAxisScale(i, tx, ty, zoom_rate); + const ImVec2& plot_tl = axis_scale.Min; + const ImVec2& plot_br = axis_scale.Max; + + if (!y[i].lock_min) + plot.YAxis[i].Range.Min = y[i].flip ? plot_tl.y : plot_br.y; + if (!y[i].lock_max) + plot.YAxis[i].Range.Max = y[i].flip ? plot_br.y : plot_tl.y; + } } } @@ -880,14 +1046,16 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons if (HasFlag(plot.Flags, ImPlotFlags_Selection) && ImFabs(select_size.x) > 2 && ImFabs(select_size.y) > 2) { 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) - plot.XAxis.Max = ImMax(p1.x, p2.x); - if (!lock_y_min && !IO.KeyShift) - plot.YAxis.Min = ImMin(p1.y, p2.y); - if (!lock_y_max && !IO.KeyShift) - plot.YAxis.Max = ImMax(p1.y, p2.y); + if (!x.lock_min && !IO.KeyAlt) + plot.XAxis.Range.Min = ImMin(p1.x, p2.x); + if (!x.lock_max && !IO.KeyAlt) + plot.XAxis.Range.Max = ImMax(p1.x, p2.x); + for (int i = 0; i < MAX_Y_AXES; i++) { + if (!y[i].lock_min && !IO.KeyShift) + plot.YAxis[i].Range.Min = ImMin(p1.y, p2.y); + if (!y[i].lock_max && !IO.KeyShift) + plot.YAxis[i].Range.Max = ImMax(p1.y, p2.y); + } } plot.Selecting = false; } @@ -911,14 +1079,9 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons 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 = 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); - plot.QueryRange.XMax = ImMax(p1.x, p2.x); - plot.QueryRange.YMin = ImMin(p1.y, p2.y); - plot.QueryRange.YMax = ImMax(p1.y, p2.y); } // end query if (plot.Querying && (IO.MouseReleased[2] || IO.MouseReleased[1])) { @@ -928,13 +1091,11 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } else { plot.Queried = false; - plot.QueryRange = ImPlotRange(); } } // begin query if ((gp.Hov_Frame && gp.Hov_Grid && IO.MouseClicked[2])) { plot.QueryRect = ImRect(0,0,0,0); - plot.QueryRange = ImPlotRange(); plot.Querying = true; plot.Queried = true; plot.QueryStart = IO.MousePos; @@ -943,7 +1104,6 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons if (plot.Selecting && IO.KeyCtrl) { plot.Selecting = false; plot.QueryRect = ImRect(0,0,0,0); - plot.QueryRange = ImPlotRange(); plot.Querying = true; plot.Queried = true; plot.QueryStart = plot.SelectStart; @@ -953,20 +1113,23 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.Querying = false; plot.Queried = false; plot.QueryRect = ImRect(0,0,0,0); - plot.QueryRange = ImPlotRange(); } // DOUBLE CLICK ----------------------------------------------------------- - if ( IO.MouseDoubleClicked[0] && gp.Hov_Frame && (hov_x_axis_region || hov_y_axis_region) && !hov_legend && !hov_query) { + if ( IO.MouseDoubleClicked[0] && gp.Hov_Frame && (hov_x_axis_region || any_hov_y_axis_region) && !hov_legend && !hov_query) { gp.FitThisFrame = true; gp.FitX = hov_x_axis_region; - gp.FitY = hov_y_axis_region; + for (int i = 0; i < MAX_Y_AXES; i++) { + gp.FitY[i] = hov_y_axis_region[i]; + } } else { gp.FitThisFrame = false; gp.FitX = false; - gp.FitY = false; + for (int i = 0; i < MAX_Y_AXES; i++) { + gp.FitY[i] = false; + } } // FOCUS ------------------------------------------------------------------ @@ -978,7 +1141,9 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons UpdateTransformCache(); // set mouse position - gp.LastMousePos = PixelsToPlot(IO.MousePos); + for (int i = 0; i < MAX_Y_AXES; i++) { + gp.LastMousePos[i] = PixelsToPlot(IO.MousePos, i); + } // RENDER ----------------------------------------------------------------- @@ -991,22 +1156,26 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // transform ticks if (gp.RenderX) { for (ImTick& xt : gp.XTicks) - xt.PixelPos = PlotToPixels((float)xt.PlotPos, 0).x; + xt.PixelPos = PlotToPixels((float)xt.PlotPos, 0, 0).x; } - if (gp.RenderY) { - for (ImTick& yt : gp.YTicks) - yt.PixelPos = PlotToPixels(0, (float)yt.PlotPos).y; + for (int i = 0; i < MAX_Y_AXES; i++) { + if (gp.RenderY[i]) { + for (ImTick& yt : gp.YTicks[i]) + yt.PixelPos = PlotToPixels(0, (float)yt.PlotPos, i).y; + } } // render grid if (HasFlag(plot.XAxis.Flags, ImAxisFlags_GridLines)) { for (ImTick &xt : gp.XTicks) - DrawList.AddLine({xt.PixelPos, gp.BB_Grid.Min.y}, {xt.PixelPos, gp.BB_Grid.Max.y}, xt.Major ? gp.Col_XMajor : gp.Col_XMinor, 1); + DrawList.AddLine({xt.PixelPos, gp.BB_Grid.Min.y}, {xt.PixelPos, gp.BB_Grid.Max.y}, xt.Major ? gp.Col_X.Major : gp.Col_X.Minor, 1); } - if (HasFlag(plot.YAxis.Flags, ImAxisFlags_GridLines)) { - for (ImTick &yt : gp.YTicks) - 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); + for (int i = 0; i < MAX_Y_AXES; i++) { + if (y[i].present && HasFlag(plot.YAxis[i].Flags, ImAxisFlags_GridLines)) { + for (ImTick &yt : gp.YTicks[i]) + DrawList.AddLine({gp.BB_Grid.Min.x, yt.PixelPos}, {gp.BB_Grid.Max.x, yt.PixelPos}, yt.Major ? gp.Col_Y[i].Major : gp.Col_Y[i].Minor, 1); + } } PopPlotClipRect(); @@ -1021,7 +1190,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons PushClipRect(gp.BB_Frame.Min, gp.BB_Frame.Max, true); for (ImTick &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); + DrawList.AddText({xt.PixelPos - xt.Size.x * 0.5f, gp.BB_Grid.Max.y + txt_off}, gp.Col_X.Txt, gp.XTickLabels.Buf.Data + xt.TextOffset); } ImGui::PopClipRect(); } @@ -1029,20 +1198,29 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons const ImVec2 xLabel_size = CalcTextSize(x_label); const ImVec2 xLabel_pos(gp.BB_Grid.GetCenter().x - xLabel_size.x * 0.5f, gp.BB_Canvas.Max.y - txt_height); - DrawList.AddText(xLabel_pos, gp.Col_XTxt, x_label); + DrawList.AddText(xLabel_pos, gp.Col_X.Txt, x_label); } - if (HasFlag(plot.YAxis.Flags, ImAxisFlags_TickLabels)) { - PushClipRect(gp.BB_Frame.Min, gp.BB_Frame.Max, true); - for (ImTick &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); + PushClipRect(gp.BB_Frame.Min, gp.BB_Frame.Max, true); + for (int i = 0; i < MAX_Y_AXES; i++) { + if (y[i].present && HasFlag(plot.YAxis[i].Flags, ImAxisFlags_TickLabels)) { + const int x_start = + gp.AxisLabelReference[i] + + ((i == 0) ? + (-txt_off - max_label_width[0]) : + txt_off); + for (ImTick &yt : gp.YTicks[i]) { + if (yt.RenderLabel && yt.PixelPos >= gp.BB_Grid.Min.y - 1 && yt.PixelPos <= gp.BB_Grid.Max.y + 1) { + ImVec2 start(x_start, yt.PixelPos - 0.5f * yt.Size.y); + DrawList.AddText(start, gp.Col_Y[i].Txt, gp.YTickLabels[i].Buf.Data + yt.TextOffset); + } + } } - ImGui::PopClipRect(); } + ImGui::PopClipRect(); if (y_label) { const ImVec2 yLabel_size = CalcTextSizeVertical(y_label); const ImVec2 yLabel_pos(gp.BB_Canvas.Min.x, gp.BB_Grid.GetCenter().y + yLabel_size.y * 0.5f); - AddTextVertical(&DrawList, y_label, yLabel_pos, gp.Col_YTxt); + AddTextVertical(&DrawList, y_label, yLabel_pos, gp.Col_Y[0].Txt); } // PREP ------------------------------------------------------------------- @@ -1052,10 +1230,12 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // reset items count gp.VisibleItemCount = 0; // reset extents - gp.Extents.Min.x = INFINITY; - gp.Extents.Min.y = INFINITY; - gp.Extents.Max.x = -INFINITY; - gp.Extents.Max.y = -INFINITY; + gp.ExtentsX.Min = INFINITY; + gp.ExtentsX.Max = -INFINITY; + for (int i = 0; i < MAX_Y_AXES; i++) { + gp.ExtentsY[i].Min = INFINITY; + gp.ExtentsY[i].Max = -INFINITY; + } // clear item names gp.LegendLabels.Buf.resize(0); // reset digital plot items count @@ -1083,7 +1263,7 @@ inline void AxisMenu(ImPlotAxis& Axis) { ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.25f); } - ImGui::DragFloat("Min", &Axis.Min, 0.01f + 0.01f * (Axis.Max - Axis.Min), -INFINITY, Axis.Max - FLT_EPSILON); + ImGui::DragFloat("Min", &Axis.Range.Min, 0.01f + 0.01f * (Axis.Range.Size()), -INFINITY, Axis.Range.Max - FLT_EPSILON); if (lock_min) { ImGui::PopItemFlag(); ImGui::PopStyleVar(); } @@ -1094,7 +1274,7 @@ inline void AxisMenu(ImPlotAxis& Axis) { if (lock_max) { ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.25f); } - ImGui::DragFloat("Max", &Axis.Max, 0.01f + 0.01f * (Axis.Max - Axis.Min), Axis.Min + FLT_EPSILON, INFINITY); + ImGui::DragFloat("Max", &Axis.Range.Max, 0.01f + 0.01f * (Axis.Range.Size()), Axis.Range.Min + FLT_EPSILON, INFINITY); if (lock_max) { ImGui::PopItemFlag(); ImGui::PopStyleVar(); @@ -1120,20 +1300,32 @@ void PlotContextMenu(ImPlot& plot) { PopID(); ImGui::EndMenu(); } - if (ImGui::BeginMenu("Y-Axis")) { - PushID("Y"); - AxisMenu(plot.YAxis); - PopID(); - ImGui::EndMenu(); + for (int i = 0; i < MAX_Y_AXES; i++) { + if (i == 1 && !HasFlag(plot.Flags, ImPlotFlags_Y2Axis)) { + continue; + } + if (i == 2 && !HasFlag(plot.Flags, ImPlotFlags_Y3Axis)) { + continue; + } + char buf[10] = {}; + if (i == 0) { + snprintf(buf, sizeof(buf) - 1, "Y-Axis"); + } else { + snprintf(buf, sizeof(buf) - 1, "Y-Axis %d", i + 1); + } + if (ImGui::BeginMenu(buf)) { + PushID(i); + AxisMenu(plot.YAxis[i]); + PopID(); + ImGui::EndMenu(); + } } + ImGui::Separator(); if ((ImGui::BeginMenu("Settings"))) { if (ImGui::MenuItem("Box Select",NULL,HasFlag(plot.Flags, ImPlotFlags_Selection))) { FlipFlag(plot.Flags, ImPlotFlags_Selection); } - if (ImGui::MenuItem("Pixel Query",NULL,HasFlag(plot.Flags, ImPlotFlags_PixelQuery))) { - FlipFlag(plot.Flags, ImPlotFlags_PixelQuery); - } if (ImGui::MenuItem("Crosshairs",NULL,HasFlag(plot.Flags, ImPlotFlags_Crosshairs))) { FlipFlag(plot.Flags, ImPlotFlags_Crosshairs); } @@ -1164,6 +1356,30 @@ void PlotContextMenu(ImPlot& plot) { } +namespace { +class BufferWriter { + public: + BufferWriter(char* buffer, size_t size) : Buffer(buffer), Pos(0), Size(size) {} + + void Write(const char* fmt, ...) IM_FMTARGS(2) { + va_list argp; + va_start(argp, fmt); + VWrite(fmt, argp); + va_end(argp); + } + + private: + void VWrite(const char* fmt, va_list argp) { + const int written = ::vsnprintf(&Buffer[Pos], Size - Pos - 1, fmt, argp); + Pos += written; + } + + char* const Buffer; + size_t Pos; + const size_t Size; +}; +} + //----------------------------------------------------------------------------- // EndPlot() //----------------------------------------------------------------------------- @@ -1180,28 +1396,54 @@ void EndPlot() { // AXIS STATES ------------------------------------------------------------ - const bool lock_x_min = HasFlag(plot.XAxis.Flags, ImAxisFlags_LockMin); - const bool lock_x_max = HasFlag(plot.XAxis.Flags, ImAxisFlags_LockMax); - const bool lock_x = (lock_x_min && lock_x_max) || (gp.NextPlotData.HasXRange && gp.NextPlotData.XRangeCond == ImGuiCond_Always); + AxisState x(plot.XAxis, gp.NextPlotData.HasXBounds, gp.NextPlotData.XBoundsCond, true, 0); + AxisState y[MAX_Y_AXES]; + y[0] = AxisState(plot.YAxis[0], gp.NextPlotData.HasYBounds[0], gp.NextPlotData.YBoundsCond[0], true, 0); + y[1] = AxisState(plot.YAxis[1], gp.NextPlotData.HasYBounds[1], gp.NextPlotData.YBoundsCond[1], + HasFlag(plot.Flags, ImPlotFlags_Y2Axis), y[0].present_so_far); + y[2] = AxisState(plot.YAxis[2], gp.NextPlotData.HasYBounds[2], gp.NextPlotData.YBoundsCond[2], + HasFlag(plot.Flags, ImPlotFlags_Y3Axis), y[1].present_so_far); - const bool lock_y_min = HasFlag(plot.YAxis.Flags, ImAxisFlags_LockMin); - const bool lock_y_max = HasFlag(plot.YAxis.Flags, ImAxisFlags_LockMax); - const bool lock_y = (lock_y_min && lock_y_max) || (gp.NextPlotData.HasYRange && gp.NextPlotData.YRangeCond == ImGuiCond_Always); - - const bool lock_plot = lock_x && lock_y; + const bool lock_plot = x.lock && y[0].lock && y[1].lock && y[2].lock; + const bool any_y_locked = y[0].lock || y[1].lock || y[2].lock; + const bool any_y_dragging = plot.YAxis[0].Dragging || plot.YAxis[1].Dragging || plot.YAxis[2].Dragging; // FINAL RENDER ----------------------------------------------------------- - PushPlotClipRect(); + PushClipRect(gp.BB_Grid.Min, gp.BB_Frame.Max, true); // render ticks if (HasFlag(plot.XAxis.Flags, ImAxisFlags_TickMarks)) { for (ImTick &xt : gp.XTicks) DrawList.AddLine({xt.PixelPos, gp.BB_Grid.Max.y},{xt.PixelPos, gp.BB_Grid.Max.y - (xt.Major ? 10.0f : 5.0f)}, gp.Col_Border, 1); } - if (HasFlag(plot.YAxis.Flags, ImAxisFlags_TickMarks)) { - for (ImTick &yt : gp.YTicks) - DrawList.AddLine({gp.BB_Grid.Min.x, yt.PixelPos}, {gp.BB_Grid.Min.x + (yt.Major ? 10.0f : 5.0f), yt.PixelPos}, gp.Col_Border, 1); + int axis_count = 0; + for (int i = 0; i < MAX_Y_AXES; i++) { + if (!y[i].present) { continue; } + axis_count++; + + if (!HasFlag(plot.YAxis[i].Flags, ImAxisFlags_TickMarks)) { continue; } + + float x_start = gp.AxisLabelReference[i]; + float direction = (i == 0) ? 1.0 : -1.0; + bool no_major = axis_count >= 3; + + for (ImTick &yt : gp.YTicks[i]) { + ImVec2 start = ImVec2(x_start, yt.PixelPos); + + DrawList.AddLine( + start, + start + ImVec2(direction * ((!no_major && yt.Major) ? 10.0f : 5.0f), 0), + gp.Col_Border, 1); + } + + if (axis_count >= 3) { + // Draw a bar next to the ticks to act as a visual separator. + DrawList.AddLine( + ImVec2(x_start, gp.BB_Grid.Min.y), + ImVec2(x_start, gp.BB_Grid.Max.y), + gp.Col_Border, 1); + } } // render selection/query @@ -1212,11 +1454,11 @@ void EndPlot() { DrawList.AddRectFilled(gp.BB_Grid.Min, gp.BB_Grid.Max, gp.Col_SlctBg); DrawList.AddRect( gp.BB_Grid.Min, gp.BB_Grid.Max, gp.Col_SlctBd); } - else if ((lock_x || IO.KeyAlt) && select_bb.GetHeight() > 2) { + else if ((x.lock || IO.KeyAlt) && select_bb.GetHeight() > 2) { DrawList.AddRectFilled(ImVec2(gp.BB_Grid.Min.x, select_bb.Min.y), ImVec2(gp.BB_Grid.Max.x, select_bb.Max.y), gp.Col_SlctBg); DrawList.AddRect( ImVec2(gp.BB_Grid.Min.x, select_bb.Min.y), ImVec2(gp.BB_Grid.Max.x, select_bb.Max.y), gp.Col_SlctBd); } - else if ((lock_y || IO.KeyShift) && select_bb.GetWidth() > 2) { + else if ((any_y_locked || IO.KeyShift) && select_bb.GetWidth() > 2) { DrawList.AddRectFilled(ImVec2(select_bb.Min.x, gp.BB_Grid.Min.y), ImVec2(select_bb.Max.x, gp.BB_Grid.Max.y), gp.Col_SlctBg); DrawList.AddRect( ImVec2(select_bb.Min.x, gp.BB_Grid.Min.y), ImVec2(select_bb.Max.x, gp.BB_Grid.Max.y), gp.Col_SlctBd); } @@ -1227,19 +1469,20 @@ void EndPlot() { } } - if (plot.Querying || (HasFlag(plot.Flags, ImPlotFlags_PixelQuery) && plot.Queried)) { + if (plot.Querying || plot.Queried) { if (plot.QueryRect.GetWidth() > 2 && plot.QueryRect.GetHeight() > 2) { DrawList.AddRectFilled(plot.QueryRect.Min + gp.BB_Grid.Min, plot.QueryRect.Max + gp.BB_Grid.Min, gp.Col_QryBg); DrawList.AddRect( plot.QueryRect.Min + gp.BB_Grid.Min, plot.QueryRect.Max + gp.BB_Grid.Min, gp.Col_QryBd); } } else if (plot.Queried) { - 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); - DrawList.AddRect( Min, Max, gp.Col_QryBd); + ImRect bb_query = plot.QueryRect; + + bb_query.Min += gp.BB_Grid.Min; + bb_query.Max += gp.BB_Grid.Min; + + DrawList.AddRectFilled(bb_query.Min, bb_query.Max, gp.Col_QryBg); + DrawList.AddRect( bb_query.Min, bb_query.Max, gp.Col_QryBd); } // render legend @@ -1302,7 +1545,7 @@ void EndPlot() { // render crosshairs if (HasFlag(plot.Flags, ImPlotFlags_Crosshairs) && gp.Hov_Grid && gp.Hov_Frame && - !(plot.XAxis.Dragging || plot.YAxis.Dragging) && !plot.Selecting && !plot.Querying && !hov_legend) { + !(plot.XAxis.Dragging || any_y_dragging) && !plot.Selecting && !plot.Querying && !hov_legend) { ImGui::SetMouseCursor(ImGuiMouseCursor_None); ImVec2 xy = IO.MousePos; ImVec2 h1(gp.BB_Grid.Min.x, xy.y); @@ -1321,14 +1564,22 @@ void EndPlot() { // render mouse pos if (HasFlag(plot.Flags, ImPlotFlags_MousePos) && gp.Hov_Grid) { - static char buffer[128]; - sprintf(buffer, "%.2f,%.2f", gp.LastMousePos.x, gp.LastMousePos.y); + static char buffer[128] = {}; + BufferWriter writer(buffer, sizeof(buffer)); + + writer.Write("%.2f,%.2f", gp.LastMousePos[0].x, gp.LastMousePos[0].y); + if (HasFlag(plot.Flags, ImPlotFlags_Y2Axis)) { + writer.Write(", (%.2f)", gp.LastMousePos[1].y); + } + if (HasFlag(plot.Flags, ImPlotFlags_Y3Axis)) { + writer.Write(", (%.2f)", gp.LastMousePos[2].y); + } ImVec2 size = CalcTextSize(buffer); ImVec2 pos = gp.BB_Grid.Max - size - ImVec2(5, 5); DrawList.AddText(pos, gp.Col_Txt, buffer); } - PopPlotClipRect(); + PopClipRect(); // render border DrawList.AddRect(gp.BB_Grid.Min, gp.BB_Grid.Max, gp.Col_Border); @@ -1336,14 +1587,20 @@ void EndPlot() { // FIT DATA -------------------------------------------------------------- if (gp.FitThisFrame && (gp.VisibleItemCount > 0 || plot.Queried)) { - if (gp.FitX && !HasFlag(plot.XAxis.Flags, ImAxisFlags_LockMin) && !NanOrInf(gp.Extents.Min.x)) - plot.XAxis.Min = gp.Extents.Min.x; - if (gp.FitX && !HasFlag(plot.XAxis.Flags, ImAxisFlags_LockMax) && !NanOrInf(gp.Extents.Max.x)) - plot.XAxis.Max = gp.Extents.Max.x; - if (gp.FitY && !HasFlag(plot.YAxis.Flags, ImAxisFlags_LockMin) && !NanOrInf(gp.Extents.Min.y)) - plot.YAxis.Min = gp.Extents.Min.y; - if (gp.FitY && !HasFlag(plot.YAxis.Flags, ImAxisFlags_LockMax) && !NanOrInf(gp.Extents.Max.y)) - plot.YAxis.Max = gp.Extents.Max.y; + if (gp.FitX && !HasFlag(plot.XAxis.Flags, ImAxisFlags_LockMin) && !NanOrInf(gp.ExtentsX.Min)) { + plot.XAxis.Range.Min = gp.ExtentsX.Min; + } + if (gp.FitX && !HasFlag(plot.XAxis.Flags, ImAxisFlags_LockMax) && !NanOrInf(gp.ExtentsX.Max)) { + plot.XAxis.Range.Max = gp.ExtentsX.Max; + } + for (int i = 0; i < MAX_Y_AXES; i++) { + if (gp.FitY[i] && !HasFlag(plot.YAxis[i].Flags, ImAxisFlags_LockMin) && !NanOrInf(gp.ExtentsY[i].Min)) { + plot.YAxis[i].Range.Min = gp.ExtentsY[i].Min; + } + if (gp.FitY[i] && !HasFlag(plot.YAxis[i].Flags, ImAxisFlags_LockMax) && !NanOrInf(gp.ExtentsY[i].Max)) { + plot.YAxis[i].Range.Max = gp.ExtentsY[i].Max; + } + } } // CONTEXT MENU ----------------------------------------------------------- @@ -1373,25 +1630,32 @@ void EndPlot() { // MISC API //----------------------------------------------------------------------------- -void SetNextPlotRange(float x_min, float x_max, float y_min, float y_max, ImGuiCond cond) { - SetNextPlotRangeX(x_min, x_max, cond); - SetNextPlotRangeY(y_min, y_max, cond); +void SetNextPlotBounds(float x_min, float x_max, float y_min, float y_max, ImGuiCond cond) { + SetNextPlotBoundsX(x_min, x_max, cond); + SetNextPlotBoundsY(y_min, y_max, cond); } -void SetNextPlotRangeX(float x_min, float x_max, ImGuiCond cond) { +void SetNextPlotBoundsX(float x_min, float x_max, ImGuiCond cond) { IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. - gp.NextPlotData.HasXRange = true; - gp.NextPlotData.XRangeCond = cond; - gp.NextPlotData.XMin = x_min; - gp.NextPlotData.XMax = x_max; + gp.NextPlotData.HasXBounds = true; + gp.NextPlotData.XBoundsCond = cond; + gp.NextPlotData.X.Min = x_min; + gp.NextPlotData.X.Max = x_max; } -void SetNextPlotRangeY(float y_min, float y_max, ImGuiCond cond) { +void SetNextPlotBoundsY(float y_min, float y_max, ImGuiCond cond, int y_axis) { + IM_ASSERT_USER_ERROR(y_axis >= 0 && y_axis < MAX_Y_AXES, "y_axis Needs to be between 0 and MAX_Y_AXES"); IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. - gp.NextPlotData.HasYRange = true; - gp.NextPlotData.YRangeCond = cond; - gp.NextPlotData.YMin = y_min; - gp.NextPlotData.YMax = y_max; + gp.NextPlotData.HasYBounds[y_axis] = true; + gp.NextPlotData.YBoundsCond[y_axis] = cond; + gp.NextPlotData.Y[y_axis].Min = y_min; + gp.NextPlotData.Y[y_axis].Max = y_max; +} + +void SetPlotYAxis(int y_axis) { + IM_ASSERT_USER_ERROR(y_axis >= 0 && y_axis < MAX_Y_AXES, "y_axis Needs to be between 0 and MAX_Y_AXES"); + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "SetPlotYAxis() Needs to be called between BeginPlot() and EndPlot()!"); + gp.CurrentPlot->CurrentYAxis = y_axis; } ImVec2 GetPlotPos() { @@ -1417,21 +1681,24 @@ bool IsPlotHovered() { IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "IsPlotHovered() Needs to be called between BeginPlot() and EndPlot()!"); return gp.Hov_Grid; } -ImVec2 GetPlotMousePos() { +ImVec2 GetPlotMousePos(int y_axis_in) { + IM_ASSERT_USER_ERROR(y_axis_in >= -1 && y_axis_in < MAX_Y_AXES, "y_axis needs to between -1 and MAX_Y_AXES"); IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "GetPlotMousePos() Needs to be called between BeginPlot() and EndPlot()!"); - return gp.LastMousePos; + const int y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; + return gp.LastMousePos[y_axis]; } -ImPlotRange GetPlotRange() { - IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "GetPlotRange() Needs to be called between BeginPlot() and EndPlot()!"); +ImPlotBounds GetPlotBounds(int y_axis_in) { + IM_ASSERT_USER_ERROR(y_axis_in >= -1 && y_axis_in < MAX_Y_AXES, "y_axis needs to between -1 and MAX_Y_AXES"); + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "GetPlotBounds() Needs to be called between BeginPlot() and EndPlot()!"); + const int y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; + ImPlot& plot = *gp.CurrentPlot; - ImPlotRange range; - range.XMin = plot.XAxis.Min; - range.XMax = plot.XAxis.Max; - range.YMin = plot.YAxis.Min; - range.YMax = plot.YAxis.Max; - return range; + ImPlotBounds bounds; + bounds.X = plot.XAxis.Range; + bounds.Y = plot.YAxis[y_axis].Range; + return bounds; } bool IsPlotQueried() { @@ -1439,19 +1706,23 @@ bool IsPlotQueried() { return gp.CurrentPlot->Queried; } -ImPlotRange GetPlotQuery() { +ImPlotBounds GetPlotQuery(int y_axis_in) { + IM_ASSERT_USER_ERROR(y_axis_in >= -1 && y_axis_in < MAX_Y_AXES, "y_axis needs to between -1 and MAX_Y_AXES"); 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)) { - 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); - plot.QueryRange.YMax = ImMax(p1.y, p2.y); - } - return plot.QueryRange; + const int y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; + + UpdateTransformCache(); + ImVec2 p1 = PixelsToPlot(plot.QueryRect.Min + gp.BB_Grid.Min, y_axis); + ImVec2 p2 = PixelsToPlot(plot.QueryRect.Max + gp.BB_Grid.Min, y_axis); + + ImPlotBounds result; + result.X.Min = ImMin(p1.x, p2.x); + result.X.Max = ImMax(p1.x, p2.x); + result.Y.Min = ImMin(p1.y, p2.y); + result.Y.Max = ImMax(p1.y, p2.y); + + return result; } //----------------------------------------------------------------------------- @@ -1610,7 +1881,7 @@ inline void MarkerGeneral(ImDrawList& DrawList, ImVec2* points, int n, const ImV } } -inline void MakerCircle(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { +inline void MarkerCircle(ImDrawList& DrawList, const ImVec2& c, float s, bool outline, ImU32 col_outline, bool fill, ImU32 col_fill, float weight) { ImVec2 marker[10] = {{1.0f, 0.0f}, {0.809017f, 0.58778524f}, {0.30901697f, 0.95105654f}, @@ -1677,16 +1948,16 @@ inline void MarkerCross(ImDrawList& DrawList, const ImVec2& c, float s, bool out } 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) { +inline void RenderMarkers(Transformer transformer, 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)); + c = transformer(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); + MarkerCircle(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)) @@ -1748,7 +2019,7 @@ inline void RenderLineAA(ImDrawList& DrawList, const ImVec2& p1, const ImVec2& p } template -inline void RenderLines(ImDrawList& DrawList, Getter getter, int count, int offset, float line_weight, ImU32 col_line, bool cull) { +inline void RenderLines(Transformer transformer, 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; @@ -1756,8 +2027,8 @@ inline void RenderLines(ImDrawList& DrawList, Getter getter, int count, int offs 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)); + p1 = transformer(getter(i1)); + p2 = transformer(getter(i2)); i1 = i2; if (!cull || gp.BB_Grid.Contains(p1) || gp.BB_Grid.Contains(p2)) RenderLineAA(DrawList, p1, p2, line_weight, col_line); @@ -1769,8 +2040,8 @@ inline void RenderLines(ImDrawList& DrawList, Getter getter, int count, int offs 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)); + p1 = transformer(getter(i1)); + p2 = transformer(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); @@ -1838,6 +2109,8 @@ 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()!"); + ImPlot* plot = gp.CurrentPlot; + const int y_axis = plot->CurrentYAxis; ImPlotItem* item = RegisterItem(label_id); if (!item->Show) return; @@ -1857,7 +2130,7 @@ inline void PlotEx(const char* label_id, Getter getter, int count, int offset) if (gp.Style.Colors[ImPlotCol_Line].w != -1) item->Color = gp.Style.Colors[ImPlotCol_Line]; - bool cull = HasFlag(gp.CurrentPlot->Flags, ImPlotFlags_CullData); + bool cull = HasFlag(plot->Flags, ImPlotFlags_CullData); // find data extents if (gp.FitThisFrame) { @@ -1868,25 +2141,25 @@ inline void PlotEx(const char* label_id, Getter getter, int count, int offset) } PushPlotClipRect(); if (count > 1 && rend_line) { - 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); + if (HasFlag(plot->XAxis.Flags, ImAxisFlags_LogScale) && HasFlag(plot->YAxis[y_axis].Flags, ImAxisFlags_LogScale)) + RenderLines(Plt2PixLogLog(y_axis), DrawList, getter, count, offset, line_weight, col_line, cull); + else if (HasFlag(plot->XAxis.Flags, ImAxisFlags_LogScale)) + RenderLines(Plt2PixLogLin(y_axis), DrawList, getter, count, offset, line_weight, col_line, cull); + else if (HasFlag(plot->YAxis[y_axis].Flags, ImAxisFlags_LogScale)) + RenderLines(Plt2PixLinLog(y_axis), DrawList, getter, count, offset, line_weight, col_line, cull); else - RenderLines(DrawList, getter, count, offset, line_weight, col_line, cull); + RenderLines(Plt2PixLinLin(y_axis), DrawList, getter, count, offset, line_weight, col_line, cull); } // render markers if (gp.Style.Marker != ImMarker_None) { - 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); + if (HasFlag(plot->XAxis.Flags, ImAxisFlags_LogScale) && HasFlag(plot->YAxis[y_axis].Flags, ImAxisFlags_LogScale)) + RenderMarkers(Plt2PixLogLog(y_axis), DrawList, getter, count, offset, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, cull); + else if (HasFlag(plot->XAxis.Flags, ImAxisFlags_LogScale)) + RenderMarkers(Plt2PixLogLin(y_axis), DrawList, getter, count, offset, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, cull); + else if (HasFlag(plot->YAxis[y_axis].Flags, ImAxisFlags_LogScale)) + RenderMarkers(Plt2PixLinLog(y_axis), 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); + RenderMarkers(Plt2PixLinLin(y_axis), DrawList, getter, count, offset, rend_mk_line, col_mk_line, rend_mk_fill, col_mk_fill, cull); } PopPlotClipRect(); } @@ -2241,17 +2514,19 @@ inline void PlotDigitalEx(const char* label_id, Getter getter, int count, int of const float line_weight = item->Highlight ? gp.Style.LineWeight * 2 : gp.Style.LineWeight; + const int ax = gp.CurrentPlot->CurrentYAxis; + // render digital signals as "pixel bases" rectangles if (count > 1 && rend_line) { // - const float mx = (gp.PixelRange.Max.x - gp.PixelRange.Min.x) / (gp.CurrentPlot->XAxis.Max - gp.CurrentPlot->XAxis.Min); + const float mx = (gp.PixelRange[ax].Max.x - gp.PixelRange[ax].Min.x) / gp.CurrentPlot->XAxis.Range.Size(); int pixY_0 = line_weight; int pixY_1 = gp.Style.DigitalBitHeight; int pixY_Offset = 20;//20 pixel from bottom due to mouse cursor label int pixY_chOffset = pixY_1 + 3; //3 pixels between channels ImVec2 pMin, pMax; - float y0 = (gp.PixelRange.Min.y) + ((-pixY_chOffset * gp.DigitalPlotItemCnt) - pixY_0 - pixY_Offset); - float y1 = (gp.PixelRange.Min.y) + ((-pixY_chOffset * gp.DigitalPlotItemCnt) - pixY_1 - pixY_Offset); + float y0 = (gp.PixelRange[ax].Min.y) + ((-pixY_chOffset * gp.DigitalPlotItemCnt) - pixY_0 - pixY_Offset); + float y1 = (gp.PixelRange[ax].Min.y) + ((-pixY_chOffset * gp.DigitalPlotItemCnt) - pixY_1 - pixY_Offset); const int segments = count - 1; int i1 = offset; for (int s = 0; s < segments; ++s) { @@ -2259,23 +2534,23 @@ inline void PlotDigitalEx(const char* label_id, Getter getter, int count, int of ImVec2 itemData1 = getter(i1); ImVec2 itemData2 = getter(i2); i1 = i2; - pMin.x = gp.PixelRange.Min.x + mx * (itemData1.x - gp.CurrentPlot->XAxis.Min); - pMin.y = (gp.PixelRange.Min.y) + ((-pixY_chOffset * gp.DigitalPlotItemCnt) - pixY_Offset); - pMax.x = gp.PixelRange.Min.x + mx * (itemData2.x - gp.CurrentPlot->XAxis.Min); + pMin.x = gp.PixelRange[ax].Min.x + mx * (itemData1.x - gp.CurrentPlot->XAxis.Range.Min); + pMin.y = (gp.PixelRange[ax].Min.y) + ((-pixY_chOffset * gp.DigitalPlotItemCnt) - pixY_Offset); + pMax.x = gp.PixelRange[ax].Min.x + mx * (itemData2.x - gp.CurrentPlot->XAxis.Range.Min); pMax.y = ((int) itemData1.y == 0) ? y0 : y1; //plot only one rectangle for same digital state while (((s+2) < segments) && ((int) itemData1.y == (int) itemData2.y)) { const int i2 = (i1 + 1) % count; itemData2 = getter(i2); - pMax.x = gp.PixelRange.Min.x + mx * (itemData2.x - gp.CurrentPlot->XAxis.Min); + pMax.x = gp.PixelRange[ax].Min.x + mx * (itemData2.x - gp.CurrentPlot->XAxis.Range.Min); i1 = i2; s++; } //do not extend plot outside plot range - if (pMin.x < gp.PixelRange.Min.x) pMin.x = gp.PixelRange.Min.x; - if (pMax.x < gp.PixelRange.Min.x) pMax.x = gp.PixelRange.Min.x; - if (pMin.x > gp.PixelRange.Max.x) pMin.x = gp.PixelRange.Max.x; - if (pMax.x > gp.PixelRange.Max.x) pMax.x = gp.PixelRange.Max.x; + if (pMin.x < gp.PixelRange[ax].Min.x) pMin.x = gp.PixelRange[ax].Min.x; + if (pMax.x < gp.PixelRange[ax].Min.x) pMax.x = gp.PixelRange[ax].Min.x; + if (pMin.x > gp.PixelRange[ax].Max.x) pMin.x = gp.PixelRange[ax].Max.x; + if (pMax.x > gp.PixelRange[ax].Max.x) pMax.x = gp.PixelRange[ax].Max.x; //plot a rectangle that extends up to x2 with y1 height if ((pMax.x > pMin.x) && (!cull || gp.BB_Grid.Contains(pMin) || gp.BB_Grid.Contains(pMax))) { ImVec4 colAlpha = item->Color; diff --git a/implot.h b/implot.h index 90fa7f1..bbe6c44 100644 --- a/implot.h +++ b/implot.h @@ -37,16 +37,17 @@ typedef int ImMarker; // Options for plots enum ImPlotFlags_ { - ImPlotFlags_MousePos = 1 << 0, // the mouse position, in plot coordinates, will be displayed in the bottom-right - ImPlotFlags_Legend = 1 << 1, // a legend will be displayed in the top-left - ImPlotFlags_Highlight = 1 << 2, // plot items will be highlighted when their legend entry is hovered - ImPlotFlags_Selection = 1 << 3, // the user will be able to box-select with right-mouse - ImPlotFlags_PixelQuery = 1 << 4, // query ranges will not change their pixel position if the plot is scrolled/zoomed - ImPlotFlags_ContextMenu = 1 << 5, // the user will be able to open a context menu with double-right click - 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_MousePos = 1 << 0, // the mouse position, in plot coordinates, will be displayed in the bottom-right + ImPlotFlags_Legend = 1 << 1, // a legend will be displayed in the top-left + ImPlotFlags_Highlight = 1 << 2, // plot items will be highlighted when their legend entry is hovered + ImPlotFlags_Selection = 1 << 3, // the user will be able to box-select with right-mouse + ImPlotFlags_ContextMenu = 1 << 4, // the user will be able to open a context menu with double-right click + ImPlotFlags_Crosshairs = 1 << 5, // the default mouse cursor will be replaced with a crosshair when hovered + ImPlotFlags_CullData = 1 << 6, // plot data outside the plot area will be culled from rendering + ImPlotFlags_AntiAliased = 1 << 7, // lines and fills will be anti-aliased (not recommended) + ImPlotFlags_NoChild = 1 << 8, // a child window region will not be used to capture mouse scroll (can boost performance for single ImGui window applications) + ImPlotFlags_Y2Axis = 1 << 9, // enable a second y axis + ImPlotFlags_Y3Axis = 1 << 10, // enable a third y axis ImPlotFlags_Default = ImPlotFlags_MousePos | ImPlotFlags_Legend | ImPlotFlags_Highlight | ImPlotFlags_Selection | ImPlotFlags_ContextMenu | ImPlotFlags_CullData }; @@ -61,7 +62,8 @@ enum ImAxisFlags_ { ImAxisFlags_Adaptive = 1 << 6, // grid divisions will adapt to the current pixel size the axis ImAxisFlags_LogScale = 1 << 7, // a logartithmic (base 10) axis scale will be used ImAxisFlags_Scientific = 1 << 8, // scientific notation will be used for tick labels if displayed (WIP, not very good yet) - ImAxisFlags_Default = ImAxisFlags_GridLines | ImAxisFlags_TickMarks | ImAxisFlags_TickLabels | ImAxisFlags_Adaptive + ImAxisFlags_Default = ImAxisFlags_GridLines | ImAxisFlags_TickMarks | ImAxisFlags_TickLabels | ImAxisFlags_Adaptive, + ImAxisFlags_Auxiliary_Default = ImAxisFlags_Default & ~ImAxisFlags_GridLines, }; // Plot styling colors @@ -75,7 +77,9 @@ enum ImPlotCol_ { ImPlotCol_PlotBg, // plot area background color (defaults to ImGuiCol_WindowBg) ImPlotCol_PlotBorder, // plot area border color (defaults to ImGuiCol_Text) ImPlotCol_XAxis, // x-axis grid/label color (defaults to ImGuiCol_Text) - ImPlotCol_YAxis, // x-axis grid/label color (defaults to ImGuiCol_Text) + ImPlotCol_YAxis, // y-axis grid/label color (defaults to ImGuiCol_Text) + ImPlotCol_Y2Axis, // y2-axis grid/label color (defaults to ImGuiCol_Text) + ImPlotCol_Y3Axis, // y3-axis grid/label color (defaults to ImGuiCol_Text) ImPlotCol_Selection, // box-selection color (defaults to yellow) ImPlotCol_Query, // box-query color (defaults to green) ImPlotCol_COUNT @@ -107,11 +111,18 @@ enum ImMarker_ { ImMarker_Asterisk = 1 << 10, // a asterisk marker will be rendered at each point (not filled) }; -/// Plot range utility struct struct ImPlotRange { - float XMin, XMax, YMin, YMax; + float Min, Max; ImPlotRange(); - bool Contains(const ImVec2& p); + bool Contains(float) const; + float Size() const; +}; + +/// Plot range utility struct +struct ImPlotBounds { + ImPlotRange X, Y; + ImPlotBounds(); + bool Contains(const ImVec2& p) const; }; // Plot style structure @@ -137,15 +148,17 @@ namespace ImGui { // be called, e.g. "if (BeginPlot(...)) { ... EndPlot(); }"". #title_id must // be unique. If you need to avoid ID collisions or don't want to display a // title in the plot, use double hashes (e.g. "MyPlot##Hidden"). If #x_label -// and/or #y_label are provided, axes labels will be displayed. Flags are only -// set ONCE during the first call to BeginPlot. +// and/or #y_label are provided, axes labels will be displayed. Axis flags are +// only set ONCE during the first call to BeginPlot. bool BeginPlot(const char* title_id, const char* x_label = NULL, const char* y_label = NULL, const ImVec2& size = ImVec2(-1,-1), ImPlotFlags flags = ImPlotFlags_Default, ImAxisFlags x_flags = ImAxisFlags_Default, - ImAxisFlags y_flags = ImAxisFlags_Default); + ImAxisFlags y_flags = ImAxisFlags_Default, + ImAxisFlags y2_flags = ImAxisFlags_Auxiliary_Default, + ImAxisFlags y3_flags = ImAxisFlags_Auxiliary_Default); // Only call EndPlot() if BeginPlot() returns true! Typically called at the end // of an if statement conditioned on BeginPlot(). void EndPlot(); @@ -185,14 +198,14 @@ void PlotDigital(const char* label_id, ImVec2 (*getter)(void* data, int idx), vo /// Returns true if the plot area in the current or most recent plot is hovered. bool IsPlotHovered(); -/// Returns the mouse position in x,y coordinates of the current or most recent plot. -ImVec2 GetPlotMousePos(); -/// Returns the current or most recent plot axis range. -ImPlotRange GetPlotRange(); +/// Returns the mouse position in x,y coordinates of the current or most recent plot. A negative y_axis uses the current value of SetPlotYAxis (0 initially). +ImVec2 GetPlotMousePos(int y_axis = -1); +/// Returns the current or most recent plot axis range. A negative y_axis uses the current value of SetPlotYAxis (0 initially). +ImPlotBounds GetPlotBounds(int y_axis = -1); /// Returns true if the current or most recent plot is being queried. bool IsPlotQueried(); -/// Returns the current or most recent plot querey range. -ImPlotRange GetPlotQuery(); +/// Returns the current or most recent plot query bounds. +ImPlotBounds GetPlotQuery(int y_axis = -1); //----------------------------------------------------------------------------- // Plot Styling @@ -225,21 +238,24 @@ void PopPlotStyleVar(int count = 1); //----------------------------------------------------------------------------- /// Set the axes ranges of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the axes will be locked. -void SetNextPlotRange(float x_min, float x_max, float y_min, float y_max, ImGuiCond cond = ImGuiCond_Once); +void SetNextPlotBounds(float x_min, float x_max, float y_min, float y_max, ImGuiCond cond = ImGuiCond_Once); /// Set the X axis range of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the axis will be locked. -void SetNextPlotRangeX(float x_min, float x_max, ImGuiCond cond = ImGuiCond_Once); +void SetNextPlotBoundsX(float x_min, float x_max, ImGuiCond cond = ImGuiCond_Once); /// Set the Y axis range of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the axis will be locked. -void SetNextPlotRangeY(float y_min, float y_max, ImGuiCond cond = ImGuiCond_Once); +void SetNextPlotBoundsY(float y_min, float y_max, ImGuiCond cond = ImGuiCond_Once, int y_axis = 0); + +/// Select which Y axis will be used for subsequent plot elements. The default is '0', or the first Y axis. +void SetPlotYAxis(int); // Get the current Plot position (top-left) in pixels. ImVec2 GetPlotPos(); // Get the curent Plot size in pixels. ImVec2 GetPlotSize(); -// Convert pixels to a position in the current plot's coordinate system. -ImVec2 PixelsToPlot(const ImVec2& pix); -// Convert a position in the current plot's coordinate system to pixels. -ImVec2 PlotToPixels(const ImVec2& plt); +// Convert pixels to a position in the current plot's coordinate system. A negative y_axis uses the current value of SetPlotYAxis (0 initially). +ImVec2 PixelsToPlot(const ImVec2& pix, int y_axis = -1); +// Convert a position in the current plot's coordinate system to pixels. A negative y_axis uses the current value of SetPlotYAxis (0 initially). +ImVec2 PlotToPixels(const ImVec2& plt, int y_axis = -1); // Push clip rect for rendering to current plot area void PushPlotClipRect(); diff --git a/implot_demo.cpp b/implot_demo.cpp index 2ea7b6e..948efdd 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -176,9 +176,9 @@ void ShowImPlotDemoWindow(bool* p_open) { static bool horz = false; ImGui::Checkbox("Horizontal",&horz); if (horz) - ImGui::SetNextPlotRange(0, 110, -0.5f, 9.5f, ImGuiCond_Always); + ImGui::SetNextPlotBounds(0, 110, -0.5f, 9.5f, ImGuiCond_Always); else - ImGui::SetNextPlotRange(-0.5f, 9.5f, 0, 110, ImGuiCond_Always); + ImGui::SetNextPlotBounds(-0.5f, 9.5f, 0, 110, ImGuiCond_Always); if (ImGui::BeginPlot("Bar Plot", horz ? "Score": "Student", horz ? "Student" : "Score", {-1, 300})) { static float midtm[10] = {83, 67, 23, 89, 83, 78, 91, 82, 85, 90}; static float final[10] = {80, 62, 56, 99, 55, 78, 88, 78, 90, 100}; @@ -203,7 +203,7 @@ void ShowImPlotDemoWindow(bool* p_open) { float bar[5] = {1,2,5,3,4}; 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); + ImGui::SetNextPlotBounds(0, 6, 0, 10); if (ImGui::BeginPlot("##ErrorBars",NULL,NULL,ImVec2(-1,300))) { ImGui::PlotBar("Bar", xs, bar, 5, 0.5f); @@ -227,7 +227,7 @@ void ShowImPlotDemoWindow(bool* p_open) { 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); + SetNextPlotBounds(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(); @@ -242,7 +242,7 @@ void ShowImPlotDemoWindow(bool* p_open) { {0.7412f, 0.0f, 0.1490f, 1.0f}, }; ImGui::SetPlotPalette(YlOrRd, 5); - SetNextPlotRange(0,1,0,1,ImGuiCond_Always); + SetNextPlotBounds(0,1,0,1,ImGuiCond_Always); static const 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)) { @@ -268,14 +268,14 @@ void ShowImPlotDemoWindow(bool* p_open) { sdata2.AddPoint(t, mouse.y * 0.0005f); rdata2.AddPoint(t, mouse.y * 0.0005f); } - ImGui::SetNextPlotRangeX(t - 10, t, paused ? ImGuiCond_Once : ImGuiCond_Always); + ImGui::SetNextPlotBoundsX(t - 10, t, paused ? ImGuiCond_Once : ImGuiCond_Always); static int rt_axis = ImAxisFlags_Default & ~ImAxisFlags_TickLabels; if (ImGui::BeginPlot("##Scrolling", NULL, NULL, {-1,150}, ImPlotFlags_Default, rt_axis, rt_axis)) { ImGui::Plot("Data 1", &sdata1.Data[0].x, &sdata1.Data[0].y, sdata1.Data.size(), sdata1.Offset, 2 * sizeof(float)); ImGui::Plot("Data 2", &sdata2.Data[0].x, &sdata2.Data[0].y, sdata2.Data.size(), sdata2.Offset, 2 * sizeof(float)); ImGui::EndPlot(); } - ImGui::SetNextPlotRangeX(0, 10, ImGuiCond_Always); + ImGui::SetNextPlotBoundsX(0, 10, ImGuiCond_Always); if (ImGui::BeginPlot("##Rolling", NULL, NULL, {-1,150}, ImPlotFlags_Default, rt_axis, rt_axis)) { ImGui::Plot("Data 1", &rdata1.Data[0].x, &rdata1.Data[0].y, rdata1.Data.size(), 0, 2 * sizeof(float)); ImGui::Plot("Data 2", &rdata2.Data[0].x, &rdata2.Data[0].y, rdata2.Data.size(), 0, 2 * sizeof(float)); @@ -285,7 +285,7 @@ void ShowImPlotDemoWindow(bool* p_open) { //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Markers and Labels")) { - ImGui::SetNextPlotRange(0, 10, 0, 12); + ImGui::SetNextPlotBounds(0, 10, 0, 12); if (ImGui::BeginPlot("##MarkerStyles", NULL, NULL, ImVec2(-1,300), 0, 0, 0)) { float xs[2] = {1,4}; float ys[2] = {10,11}; @@ -369,7 +369,7 @@ void ShowImPlotDemoWindow(bool* p_open) { ys2[i] = log(xs[i]); ys3[i] = pow(10.0f, xs[i]); } - ImGui::SetNextPlotRange(0.1f, 100, 0, 10); + ImGui::SetNextPlotBounds(0.1f, 100, 0, 10); if (ImGui::BeginPlot("Log Plot", NULL, NULL, ImVec2(-1,300), ImPlotFlags_Default, ImAxisFlags_Default | ImAxisFlags_LogScale )) { ImGui::Plot("f(x) = x", xs, xs, 1001); ImGui::Plot("f(x) = sin(x)+1", xs, ys1, 1001); @@ -379,12 +379,51 @@ void ShowImPlotDemoWindow(bool* p_open) { } } //------------------------------------------------------------------------- + if (ImGui::CollapsingHeader("Multiple Y Axes")) { + static float xs[1001], xs2[1001], ys1[1001], ys2[1001], ys3[1001]; + static bool y2_axis = true; + static bool y3_axis = false; + + for (int i = 0; i < 1001; ++i) { + xs[i] = (float)(i*0.1f); + ys1[i] = sin(xs[i]) * 3 + 1; + ys2[i] = cos(xs[i]) * 0.2 + 0.5; + ys3[i] = sin(xs[i]+.5) * 100 + 200; + xs2[i] = xs[i] + 10.0; + } + ImGui::SetNextPlotBounds(0.1f, 100, 0, 10); + ImGui::SetNextPlotBoundsY(0, 1, ImGuiCond_Once, 1); + ImGui::SetNextPlotBoundsY(0, 300, ImGuiCond_Once, 2); + if (ImGui::BeginPlot("Multi-Axis Plot", NULL, NULL, ImVec2(-1,300), + ImPlotFlags_Default | + (y2_axis ? ImPlotFlags_Y2Axis : 0) | + (y3_axis ? ImPlotFlags_Y3Axis : 0))) { + ImGui::Plot("f(x) = x", xs, xs, 1001); + ImGui::Plot("f(x) = sin(x)*3+1", xs, ys1, 1001); + + if (y2_axis) { + ImGui::SetPlotYAxis(1); + ImGui::Plot("f(x) = cos(x)*.2+.5 (Y2)", xs, ys2, 1001); + } + + if (y3_axis) { + ImGui::SetPlotYAxis(2); + ImGui::Plot("f(x) = sin(x+.5)*100+200 (Y3)", xs2, ys3, 1001); + } + + ImGui::EndPlot(); + } + ImGui::Checkbox("Y2 Axis", &y2_axis); + ImGui::SameLine(); + ImGui::Checkbox("Y3 Axis", &y3_axis); + } + //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Querying")) { ImGui::BulletText("Ctrl + click in the plot area to draw points."); ImGui::BulletText("Middle click (or Ctrl + right click) and drag to query points."); ImGui::BulletText("Hold the Alt and/or Shift keys to expand the query range."); static ImVector data; - ImPlotRange range, query; + ImPlotBounds range, query; if (ImGui::BeginPlot("##Drawing", NULL, NULL, ImVec2(-1,300), ImPlotFlags_Default, ImAxisFlags_GridLines, ImAxisFlags_GridLines)) { if (ImGui::IsPlotHovered() && ImGui::IsMouseClicked(0) && ImGui::GetIO().KeyCtrl) data.push_back(ImGui::GetPlotMousePos()); @@ -393,7 +432,7 @@ void ShowImPlotDemoWindow(bool* p_open) { if (data.size() > 0) ImGui::Plot("Points", &data[0].x, &data[0].y, data.size(), 0, 2 * sizeof(float)); if (ImGui::IsPlotQueried() && data.size() > 0) { - ImPlotRange range = ImGui::GetPlotQuery(); + ImPlotBounds range = ImGui::GetPlotQuery(); int cnt = 0; ImVec2 avg; for (int i = 0; i < data.size(); ++i) { @@ -410,12 +449,12 @@ void ShowImPlotDemoWindow(bool* p_open) { } } ImGui::PopPlotStyleVar(2); - range = ImGui::GetPlotRange(); + range = ImGui::GetPlotBounds(); query = ImGui::GetPlotQuery(); ImGui::EndPlot(); } - ImGui::Text("The current plot range is: [%g,%g,%g,%g]", range.XMin, range.XMax, range.YMin, range.YMax); - ImGui::Text("The current query range is: [%g,%g,%g,%g]", query.XMin, query.XMax, query.YMin, query.YMax); + ImGui::Text("The current plot range is: [%g,%g,%g,%g]", range.X.Min, range.X.Max, range.Y.Min, range.Y.Max); + ImGui::Text("The current query range is: [%g,%g,%g,%g]", query.X.Min, query.X.Max, query.Y.Min, query.Y.Max); } //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Views")) { @@ -436,9 +475,9 @@ void ShowImPlotDemoWindow(bool* p_open) { } ImGui::BulletText("Query the first plot to render a subview in the second plot."); ImGui::BulletText("Toggle \"Pixel Query\" in the context menu and then pan the plot."); - ImGui::SetNextPlotRange(0,0.01f,-1,1); + ImGui::SetNextPlotBounds(0,0.01f,-1,1); ImAxisFlags flgs = ImAxisFlags_Default & ~ImAxisFlags_TickLabels; - ImPlotRange query; + ImPlotBounds query; if (ImGui::BeginPlot("##View1",NULL,NULL,ImVec2(-1,150), ImPlotFlags_Default, flgs, flgs)) { ImGui::Plot("Signal 1", x_data, y_data1, 512); ImGui::Plot("Signal 2", x_data, y_data2, 512); @@ -446,7 +485,7 @@ void ShowImPlotDemoWindow(bool* p_open) { query = ImGui::GetPlotQuery(); ImGui::EndPlot(); } - ImGui::SetNextPlotRange(query.XMin, query.XMax, query.YMin, query.YMax, ImGuiCond_Always); + ImGui::SetNextPlotBounds(query.X.Min, query.X.Max, query.Y.Min, query.Y.Max, ImGuiCond_Always); if (ImGui::BeginPlot("##View2",NULL,NULL,ImVec2(-1,150), 0, 0, 0)) { ImGui::Plot("Signal 1", x_data, y_data1, 512); ImGui::Plot("Signal 2", x_data, y_data2, 512); @@ -504,7 +543,7 @@ void ShowImPlotDemoWindow(bool* p_open) { data[i].Data.back().y + (0.005f + 0.0002f * (float)rand() / float(RAND_MAX)) * (-1 + 2 * (float)rand() / float(RAND_MAX))); } } - ImGui::SetNextPlotRangeX(t - 10, t, paused ? ImGuiCond_Once : ImGuiCond_Always); + ImGui::SetNextPlotBoundsX(t - 10, t, paused ? ImGuiCond_Once : ImGuiCond_Always); if (ImGui::BeginPlot("##DND", NULL, NULL, ImVec2(-1,300), ImPlotFlags_Default)) { for (int i = 0; i < 10; ++i) { if (show[i]) { @@ -605,8 +644,8 @@ void ShowImPlotDemoWindow(bool* p_open) { if (showAnalog[i]) dataAnalog[i].AddPoint(t, sin(2*t) - cos(2*t)); } - ImGui::SetNextPlotRangeY(-1, 1); - ImGui::SetNextPlotRangeX(t - 10.0f, t, paused ? ImGuiCond_Once : ImGuiCond_Always); + ImGui::SetNextPlotBoundsY(-1, 1); + ImGui::SetNextPlotBoundsX(t - 10.0f, t, paused ? ImGuiCond_Once : ImGuiCond_Always); if (ImGui::BeginPlot("##Digital", NULL, NULL, ImVec2(-1,300), ImPlotFlags_Default)) { for (int i = 0; i < K_PLOT_DIGITAL_CH_COUNT; ++i) { if (showDigital[i]) { @@ -653,7 +692,7 @@ void ShowImPlotDemoWindow(bool* p_open) { ImGui::PushPlotColor(ImPlotCol_XAxis, IM_COL32(192, 192, 192, 192)); ImGui::PushPlotColor(ImPlotCol_YAxis, IM_COL32(192, 192, 192, 192)); ImGui::PushPlotStyleVar(ImPlotStyleVar_LineWeight, 2); - ImGui::SetNextPlotRange(-0.5f, 9.5f, -0.5f, 9.5f); + ImGui::SetNextPlotBounds(-0.5f, 9.5f, -0.5f, 9.5f); if (ImGui::BeginPlot("##Custom", NULL, NULL, {-1,300}, ImPlotFlags_Default & ~ImPlotFlags_Legend, 0)) { float lin[10] = {8,8,9,7,8,8,8,9,7,8}; float bar[10] = {1,2,5,3,4,1,2,5,3,4}; @@ -688,7 +727,7 @@ 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); - SetNextPlotRange(0,1,0,1, ImGuiCond_Always); + SetNextPlotBounds(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) { From 7c0121dae223da65dc19030dcb4310e8f4f141ba Mon Sep 17 00:00:00 2001 From: Sayantan Datta Date: Sun, 10 May 2020 23:35:34 -0400 Subject: [PATCH 08/10] Add stdlib.h - fixes rand(), srand() and RAND_MAX on VC --- implot_demo.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/implot_demo.cpp b/implot_demo.cpp index 2ea7b6e..1c337fe 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace { From 329ad9dd80c761b00a5bfe46533fdda626036692 Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Mon, 11 May 2020 00:45:46 -0500 Subject: [PATCH 09/10] tidy up multi y axis support --- implot.cpp | 218 +++++++++++++++++++++++++++++------------------- implot.h | 78 ++++++++--------- implot_demo.cpp | 121 +++++++++++++++------------ 3 files changed, 237 insertions(+), 180 deletions(-) diff --git a/implot.cpp b/implot.cpp index 725fe6f..c7bb446 100644 --- a/implot.cpp +++ b/implot.cpp @@ -20,7 +20,26 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// ImPlot v0.1 WIP +// ImPlot v0.2 WIP + +/* + +API BREAKING CHANGES +==================== +Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix. +Below is a change-log of API breaking changes only. If you are using one of the functions listed, expect to have to fix some code. +When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all implot files. +You can read releases logs https://github.com/epezent/implot/releases for more details. + +- 2020/05/10 (0.2) - The following function/struct names were changes: + - ImPlotRange -> ImPlotLimits + - GetPlotRange() -> GetPlotLimits() + - SetNextPlotRange -> SetNextPlotLimits + - SetNextPlotRangeX -> SetNextPlotLimitsX + - SetNextPlotRangeY -> SetNextPlotLimitsY +- 2020/05/10 (0.2) - Plot queries are pixel based by default. Query rects that maintain relative plot position have been removed. This was done to support multi-y-axis. + +*/ #ifdef _MSC_VER #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen @@ -30,6 +49,7 @@ #define IMGUI_DEFINE_MATH_OPERATORS #endif + #include #include @@ -45,6 +65,8 @@ // Special Color used to specific that a plot item color should set determined automatically. #define IM_COL_AUTO ImVec4(0,0,0,-1) +// The maximum number of support y-axes +#define MAX_Y_AXES 3 ImPlotStyle::ImPlotStyle() { LineWeight = 1; @@ -65,8 +87,8 @@ ImPlotStyle::ImPlotStyle() { Colors[ImPlotCol_PlotBorder] = IM_COL_AUTO; Colors[ImPlotCol_XAxis] = IM_COL_AUTO; Colors[ImPlotCol_YAxis] = IM_COL_AUTO; - Colors[ImPlotCol_Y2Axis] = IM_COL_AUTO; - Colors[ImPlotCol_Y3Axis] = IM_COL_AUTO; + Colors[ImPlotCol_YAxis2] = IM_COL_AUTO; + Colors[ImPlotCol_YAxis3] = IM_COL_AUTO; Colors[ImPlotCol_Selection] = ImVec4(1,1,0,1); Colors[ImPlotCol_Query] = ImVec4(0,1,0,1); } @@ -81,9 +103,9 @@ float ImPlotRange::Size() const { return Max - Min; } -ImPlotBounds::ImPlotBounds() {} +ImPlotLimits::ImPlotLimits() {} -bool ImPlotBounds::Contains(const ImVec2& p) const { +bool ImPlotLimits::Contains(const ImVec2& p) const { return X.Contains(p.x) && Y.Contains(p.y); } @@ -91,8 +113,6 @@ namespace ImGui { namespace { -#define MAX_Y_AXES 3 - //----------------------------------------------------------------------------- // Private Utils //----------------------------------------------------------------------------- @@ -242,13 +262,13 @@ struct ImPlotAxis { Range.Max = 1; Divisions = 3; Subdivisions = 10; - Flags = ImAxisFlags_Default; + Flags = PreviousFlags = ImAxisFlags_Default; } bool Dragging; ImPlotRange Range; int Divisions; int Subdivisions; - ImAxisFlags Flags; + ImAxisFlags Flags, PreviousFlags; }; /// Holds Plot state information that must persist between frames @@ -256,7 +276,7 @@ struct ImPlot { ImPlot() { Selecting = Querying = Queried = DraggingQuery = false; SelectStart = QueryStart = ImVec2(0,0); - Flags = ImPlotFlags_Default; + Flags = PreviousFlags = ImPlotFlags_Default; ColorIdx = 0; CurrentYAxis = 0; } @@ -274,17 +294,17 @@ struct ImPlot { ImPlotAxis XAxis; ImPlotAxis YAxis[MAX_Y_AXES]; - ImPlotFlags Flags; + ImPlotFlags Flags, PreviousFlags; int ColorIdx; int CurrentYAxis; }; struct ImNextPlotData { - ImNextPlotData() : HasXBounds{}, HasYBounds{} {} - ImGuiCond XBoundsCond; - ImGuiCond YBoundsCond[MAX_Y_AXES]; - bool HasXBounds; - bool HasYBounds[MAX_Y_AXES]; + ImNextPlotData() : HasXRange{}, HasYRange{} {} + ImGuiCond XRangeCond; + ImGuiCond YRangeCond[MAX_Y_AXES]; + bool HasXRange; + bool HasYRange[MAX_Y_AXES]; ImPlotRange X; ImPlotRange Y[MAX_Y_AXES]; }; @@ -642,7 +662,7 @@ ImRect GetAxisScale(int y_axis, float tx, float ty, float zoom_rate) { class YPadCalculator { public: - YPadCalculator(const AxisState* axis_states, const float* max_label_widths, int txt_off) + YPadCalculator(const AxisState* axis_states, const float* max_label_widths, float txt_off) : AxisStates(axis_states), MaxLabelWidths(max_label_widths), TxtOff(txt_off) {} float operator()(int y_axis) { @@ -650,9 +670,9 @@ class YPadCalculator { if (!AxisStates[y_axis].present) { return 0; } // If we have more than 1 axis present before us, then we need // extra space to account for our tick bar. - int pad_result = 0; + float pad_result = 0; if (AxisStates[y_axis].present_so_far >= 3) { - pad_result += 6; + pad_result += 6.0f; } if (!HasFlag(plot.YAxis[y_axis].Flags, ImAxisFlags_TickLabels)) { return pad_result; @@ -664,7 +684,7 @@ class YPadCalculator { private: const AxisState* const AxisStates; const float* const MaxLabelWidths; - const int TxtOff; + const float TxtOff; }; } // namespace @@ -695,13 +715,31 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.CurrentYAxis = 0; - plot.Flags = flags; if (just_created) { - plot.XAxis.Flags = x_flags; + plot.Flags = flags; + plot.XAxis.Flags = x_flags; plot.YAxis[0].Flags = y_flags; plot.YAxis[1].Flags = y2_flags; plot.YAxis[2].Flags = y3_flags; } + else { + // TODO: Check which individual flags changed, and only reset those! + // There's probably an easy bit mask trick I'm not aware of. + if (flags != plot.PreviousFlags) + plot.Flags = flags; + if (y_flags != plot.YAxis[0].PreviousFlags) + plot.YAxis[0].PreviousFlags = y_flags; + if (y2_flags != plot.YAxis[1].PreviousFlags) + plot.YAxis[1].PreviousFlags = y2_flags; + if (y3_flags != plot.YAxis[2].PreviousFlags) + plot.YAxis[2].PreviousFlags = y3_flags; + } + + plot.PreviousFlags = flags; + plot.XAxis.PreviousFlags = x_flags; + plot.YAxis[0].PreviousFlags = y_flags; + plot.YAxis[1].PreviousFlags = y2_flags; + plot.YAxis[2].PreviousFlags = y3_flags; // capture scroll with a child region if (!HasFlag(plot.Flags, ImPlotFlags_NoChild)) { @@ -714,16 +752,16 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // NextPlotData ----------------------------------------------------------- - if (gp.NextPlotData.HasXBounds) { - if (just_created || gp.NextPlotData.XBoundsCond == ImGuiCond_Always) + if (gp.NextPlotData.HasXRange) { + if (just_created || gp.NextPlotData.XRangeCond == ImGuiCond_Always) { plot.XAxis.Range = gp.NextPlotData.X; } } for (int i = 0; i < MAX_Y_AXES; i++) { - if (gp.NextPlotData.HasYBounds[i]) { - if (just_created || gp.NextPlotData.YBoundsCond[i] == ImGuiCond_Always) + if (gp.NextPlotData.HasYRange[i]) { + if (just_created || gp.NextPlotData.YRangeCond[i] == ImGuiCond_Always) { plot.YAxis[i].Range = gp.NextPlotData.Y[i]; } @@ -731,13 +769,13 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } // AXIS STATES ------------------------------------------------------------ - AxisState x(plot.XAxis, gp.NextPlotData.HasXBounds, gp.NextPlotData.XBoundsCond, true, 0); + AxisState x(plot.XAxis, gp.NextPlotData.HasXRange, gp.NextPlotData.XRangeCond, true, 0); AxisState y[MAX_Y_AXES]; - y[0] = AxisState(plot.YAxis[0], gp.NextPlotData.HasYBounds[0], gp.NextPlotData.YBoundsCond[0], true, 0); - y[1] = AxisState(plot.YAxis[1], gp.NextPlotData.HasYBounds[1], gp.NextPlotData.YBoundsCond[1], - HasFlag(plot.Flags, ImPlotFlags_Y2Axis), y[0].present_so_far); - y[2] = AxisState(plot.YAxis[2], gp.NextPlotData.HasYBounds[2], gp.NextPlotData.YBoundsCond[2], - HasFlag(plot.Flags, ImPlotFlags_Y3Axis), y[1].present_so_far); + y[0] = AxisState(plot.YAxis[0], gp.NextPlotData.HasYRange[0], gp.NextPlotData.YRangeCond[0], true, 0); + y[1] = AxisState(plot.YAxis[1], gp.NextPlotData.HasYRange[1], gp.NextPlotData.YRangeCond[1], + HasFlag(plot.Flags, ImPlotFlags_YAxis2), y[0].present_so_far); + y[2] = AxisState(plot.YAxis[2], gp.NextPlotData.HasYRange[2], gp.NextPlotData.YRangeCond[2], + HasFlag(plot.Flags, ImPlotFlags_YAxis3), y[1].present_so_far); const bool lock_plot = x.lock && y[0].lock && y[1].lock && y[2].lock; @@ -790,8 +828,8 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons UpdateAxisColor(ImPlotCol_XAxis, &gp.Col_X); UpdateAxisColor(ImPlotCol_YAxis, &gp.Col_Y[0]); - UpdateAxisColor(ImPlotCol_Y2Axis, &gp.Col_Y[1]); - UpdateAxisColor(ImPlotCol_Y3Axis, &gp.Col_Y[2]); + UpdateAxisColor(ImPlotCol_YAxis2, &gp.Col_Y[1]); + UpdateAxisColor(ImPlotCol_YAxis3, &gp.Col_Y[2]); gp.Col_Txt = GetColorU32(ImGuiCol_Text); gp.Col_TxtDis = GetColorU32(ImGuiCol_TextDisabled); @@ -866,7 +904,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons gp.Hov_Grid = gp.BB_Grid.Contains(IO.MousePos); // axis region bbs - const ImRect xAxisRegion_bb(gp.BB_Grid.Min + ImVec2(10, 0), {gp.BB_Grid.Max.x, gp.BB_Frame.Max.y}); + const ImRect xAxisRegion_bb(gp.BB_Grid.Min + ImVec2(10, 0), ImVec2(gp.BB_Grid.Max.x, gp.BB_Frame.Max.y) - ImVec2(10, 0)); const bool hov_x_axis_region = xAxisRegion_bb.Contains(IO.MousePos); // The left labels are referenced to the left of the bounding box. @@ -897,14 +935,13 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons y[1].present && (yAxisRegion_bb[1].Contains(IO.MousePos) || centralRegion.Contains(IO.MousePos)), y[2].present && (yAxisRegion_bb[2].Contains(IO.MousePos) || centralRegion.Contains(IO.MousePos)), }; - const bool any_hov_y_axis_region = - hov_y_axis_region[0] || hov_y_axis_region[1] || hov_y_axis_region[2]; + const bool any_hov_y_axis_region = hov_y_axis_region[0] || hov_y_axis_region[1] || hov_y_axis_region[2]; // legend hovered from last frame const bool hov_legend = HasFlag(plot.Flags, ImPlotFlags_Legend) ? gp.Hov_Frame && plot.BB_Legend.Contains(IO.MousePos) : false; bool hov_query = false; - if (plot.Queried && !plot.Querying) { + if (gp.Hov_Frame && gp.Hov_Grid && plot.Queried && !plot.Querying) { ImRect bb_query = plot.QueryRect; bb_query.Min += gp.BB_Grid.Min; @@ -922,10 +959,9 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.QueryRect.Min += IO.MouseDelta; plot.QueryRect.Max += IO.MouseDelta; } - if (gp.Hov_Frame && hov_query && !plot.DraggingQuery && !plot.Selecting && !hov_legend) { + if (gp.Hov_Frame && gp.Hov_Grid && hov_query && !plot.DraggingQuery && !plot.Selecting && !hov_legend) { SetMouseCursor(ImGuiMouseCursor_ResizeAll); - const bool any_y_dragging = - plot.YAxis[0].Dragging || plot.YAxis[1].Dragging || plot.YAxis[2].Dragging; + const bool any_y_dragging = plot.YAxis[0].Dragging || plot.YAxis[1].Dragging || plot.YAxis[2].Dragging; if (IO.MouseDown[0] && !plot.XAxis.Dragging && !any_y_dragging) { plot.DraggingQuery = true; } @@ -933,6 +969,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // DRAG INPUT ------------------------------------------------------------- + // end drags if (plot.XAxis.Dragging && (IO.MouseReleased[0] || !IO.MouseDown[0])) { plot.XAxis.Dragging = false; @@ -944,11 +981,10 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons G.IO.MouseDragMaxDistanceSqr[0] = 0; } } + const bool any_y_dragging = plot.YAxis[0].Dragging || plot.YAxis[1].Dragging || plot.YAxis[2].Dragging; + bool drag_in_progress = plot.XAxis.Dragging || any_y_dragging; // do drag - const bool any_y_dragging = - plot.YAxis[0].Dragging || plot.YAxis[1].Dragging || plot.YAxis[2].Dragging; - - if (plot.XAxis.Dragging || any_y_dragging) { + if (drag_in_progress) { UpdateTransformCache(); if (!x.lock && plot.XAxis.Dragging) { ImVec2 plot_tl = PixelsToPlot(gp.BB_Grid.Min - IO.MouseDelta, 0); @@ -993,7 +1029,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } } // start drag - if (gp.Hov_Frame && IO.MouseDragMaxDistanceSqr[0] > 5 && !plot.Selecting && !hov_legend && !hov_query && !plot.DraggingQuery) { + if (!drag_in_progress && gp.Hov_Frame && IO.MouseDragMaxDistanceSqr[0] > 5 && !plot.Selecting && !hov_legend && !hov_query && !plot.DraggingQuery) { if (hov_x_axis_region) { plot.XAxis.Dragging = true; } @@ -1051,6 +1087,8 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons if (!x.lock_max && !IO.KeyAlt) plot.XAxis.Range.Max = ImMax(p1.x, p2.x); for (int i = 0; i < MAX_Y_AXES; i++) { + p1 = PixelsToPlot(plot.SelectStart, i); + p2 = PixelsToPlot(IO.MousePos, i); if (!y[i].lock_min && !IO.KeyShift) plot.YAxis[i].Range.Min = ImMin(p1.y, p2.y); if (!y[i].lock_max && !IO.KeyShift) @@ -1094,21 +1132,21 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } } // begin query - if ((gp.Hov_Frame && gp.Hov_Grid && IO.MouseClicked[2])) { + if (HasFlag(plot.Flags, ImPlotFlags_Query) && (gp.Hov_Frame && gp.Hov_Grid && IO.MouseClicked[2])) { plot.QueryRect = ImRect(0,0,0,0); plot.Querying = true; plot.Queried = true; plot.QueryStart = IO.MousePos; } // toggle between select/query - if (plot.Selecting && IO.KeyCtrl) { + if (HasFlag(plot.Flags, ImPlotFlags_Query) && plot.Selecting && IO.KeyCtrl) { plot.Selecting = false; plot.QueryRect = ImRect(0,0,0,0); plot.Querying = true; plot.Queried = true; plot.QueryStart = plot.SelectStart; } - if (plot.Querying && !IO.KeyCtrl && !IO.MouseDown[2]) { + if (HasFlag(plot.Flags, ImPlotFlags_Selection) && plot.Querying && !IO.KeyCtrl && !IO.MouseDown[2]) { plot.Selecting = true; plot.Querying = false; plot.Queried = false; @@ -1203,7 +1241,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons PushClipRect(gp.BB_Frame.Min, gp.BB_Frame.Max, true); for (int i = 0; i < MAX_Y_AXES; i++) { if (y[i].present && HasFlag(plot.YAxis[i].Flags, ImAxisFlags_TickLabels)) { - const int x_start = + const float x_start = gp.AxisLabelReference[i] + ((i == 0) ? (-txt_off - max_label_width[0]) : @@ -1301,10 +1339,10 @@ void PlotContextMenu(ImPlot& plot) { ImGui::EndMenu(); } for (int i = 0; i < MAX_Y_AXES; i++) { - if (i == 1 && !HasFlag(plot.Flags, ImPlotFlags_Y2Axis)) { + if (i == 1 && !HasFlag(plot.Flags, ImPlotFlags_YAxis2)) { continue; } - if (i == 2 && !HasFlag(plot.Flags, ImPlotFlags_Y3Axis)) { + if (i == 2 && !HasFlag(plot.Flags, ImPlotFlags_YAxis3)) { continue; } char buf[10] = {}; @@ -1396,13 +1434,13 @@ void EndPlot() { // AXIS STATES ------------------------------------------------------------ - AxisState x(plot.XAxis, gp.NextPlotData.HasXBounds, gp.NextPlotData.XBoundsCond, true, 0); + AxisState x(plot.XAxis, gp.NextPlotData.HasXRange, gp.NextPlotData.XRangeCond, true, 0); AxisState y[MAX_Y_AXES]; - y[0] = AxisState(plot.YAxis[0], gp.NextPlotData.HasYBounds[0], gp.NextPlotData.YBoundsCond[0], true, 0); - y[1] = AxisState(plot.YAxis[1], gp.NextPlotData.HasYBounds[1], gp.NextPlotData.YBoundsCond[1], - HasFlag(plot.Flags, ImPlotFlags_Y2Axis), y[0].present_so_far); - y[2] = AxisState(plot.YAxis[2], gp.NextPlotData.HasYBounds[2], gp.NextPlotData.YBoundsCond[2], - HasFlag(plot.Flags, ImPlotFlags_Y3Axis), y[1].present_so_far); + y[0] = AxisState(plot.YAxis[0], gp.NextPlotData.HasYRange[0], gp.NextPlotData.YRangeCond[0], true, 0); + y[1] = AxisState(plot.YAxis[1], gp.NextPlotData.HasYRange[1], gp.NextPlotData.YRangeCond[1], + HasFlag(plot.Flags, ImPlotFlags_YAxis2), y[0].present_so_far); + y[2] = AxisState(plot.YAxis[2], gp.NextPlotData.HasYRange[2], gp.NextPlotData.YRangeCond[2], + HasFlag(plot.Flags, ImPlotFlags_YAxis3), y[1].present_so_far); const bool lock_plot = x.lock && y[0].lock && y[1].lock && y[2].lock; const bool any_y_locked = y[0].lock || y[1].lock || y[2].lock; @@ -1413,10 +1451,14 @@ void EndPlot() { PushClipRect(gp.BB_Grid.Min, gp.BB_Frame.Max, true); // render ticks + PushPlotClipRect(); if (HasFlag(plot.XAxis.Flags, ImAxisFlags_TickMarks)) { for (ImTick &xt : gp.XTicks) DrawList.AddLine({xt.PixelPos, gp.BB_Grid.Max.y},{xt.PixelPos, gp.BB_Grid.Max.y - (xt.Major ? 10.0f : 5.0f)}, gp.Col_Border, 1); } + PopPlotClipRect(); + + PushClipRect(gp.BB_Grid.Min, {gp.BB_Frame.Max.x, gp.BB_Grid.Max.y}, true); int axis_count = 0; for (int i = 0; i < MAX_Y_AXES; i++) { if (!y[i].present) { continue; } @@ -1425,7 +1467,7 @@ void EndPlot() { if (!HasFlag(plot.YAxis[i].Flags, ImAxisFlags_TickMarks)) { continue; } float x_start = gp.AxisLabelReference[i]; - float direction = (i == 0) ? 1.0 : -1.0; + float direction = (i == 0) ? 1.0f : -1.0f; bool no_major = axis_count >= 3; for (ImTick &yt : gp.YTicks[i]) { @@ -1446,6 +1488,9 @@ void EndPlot() { } } + PopClipRect(); + + PushPlotClipRect(); // render selection/query if (plot.Selecting) { ImRect select_bb(ImMin(IO.MousePos, plot.SelectStart), ImMax(IO.MousePos, plot.SelectStart)); @@ -1568,18 +1613,18 @@ void EndPlot() { BufferWriter writer(buffer, sizeof(buffer)); writer.Write("%.2f,%.2f", gp.LastMousePos[0].x, gp.LastMousePos[0].y); - if (HasFlag(plot.Flags, ImPlotFlags_Y2Axis)) { - writer.Write(", (%.2f)", gp.LastMousePos[1].y); + if (HasFlag(plot.Flags, ImPlotFlags_YAxis2)) { + writer.Write(",(%.2f)", gp.LastMousePos[1].y); } - if (HasFlag(plot.Flags, ImPlotFlags_Y3Axis)) { - writer.Write(", (%.2f)", gp.LastMousePos[2].y); + if (HasFlag(plot.Flags, ImPlotFlags_YAxis3)) { + writer.Write(",(%.2f)", gp.LastMousePos[2].y); } ImVec2 size = CalcTextSize(buffer); ImVec2 pos = gp.BB_Grid.Max - size - ImVec2(5, 5); DrawList.AddText(pos, gp.Col_Txt, buffer); } - PopClipRect(); + PopPlotClipRect(); // render border DrawList.AddRect(gp.BB_Grid.Min, gp.BB_Grid.Max, gp.Col_Border); @@ -1630,24 +1675,24 @@ void EndPlot() { // MISC API //----------------------------------------------------------------------------- -void SetNextPlotBounds(float x_min, float x_max, float y_min, float y_max, ImGuiCond cond) { - SetNextPlotBoundsX(x_min, x_max, cond); - SetNextPlotBoundsY(y_min, y_max, cond); +void SetNextPlotLimits(float x_min, float x_max, float y_min, float y_max, ImGuiCond cond) { + SetNextPlotLimitsX(x_min, x_max, cond); + SetNextPlotLimitsY(y_min, y_max, cond); } -void SetNextPlotBoundsX(float x_min, float x_max, ImGuiCond cond) { +void SetNextPlotLimitsX(float x_min, float x_max, ImGuiCond cond) { IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. - gp.NextPlotData.HasXBounds = true; - gp.NextPlotData.XBoundsCond = cond; + gp.NextPlotData.HasXRange = true; + gp.NextPlotData.XRangeCond = cond; gp.NextPlotData.X.Min = x_min; gp.NextPlotData.X.Max = x_max; } -void SetNextPlotBoundsY(float y_min, float y_max, ImGuiCond cond, int y_axis) { +void SetNextPlotLimitsY(float y_min, float y_max, ImGuiCond cond, int y_axis) { IM_ASSERT_USER_ERROR(y_axis >= 0 && y_axis < MAX_Y_AXES, "y_axis Needs to be between 0 and MAX_Y_AXES"); IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. - gp.NextPlotData.HasYBounds[y_axis] = true; - gp.NextPlotData.YBoundsCond[y_axis] = cond; + gp.NextPlotData.HasYRange[y_axis] = true; + gp.NextPlotData.YRangeCond[y_axis] = cond; gp.NextPlotData.Y[y_axis].Min = y_min; gp.NextPlotData.Y[y_axis].Max = y_max; } @@ -1689,16 +1734,16 @@ ImVec2 GetPlotMousePos(int y_axis_in) { } -ImPlotBounds GetPlotBounds(int y_axis_in) { +ImPlotLimits GetPlotLimits(int y_axis_in) { IM_ASSERT_USER_ERROR(y_axis_in >= -1 && y_axis_in < MAX_Y_AXES, "y_axis needs to between -1 and MAX_Y_AXES"); - IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "GetPlotBounds() Needs to be called between BeginPlot() and EndPlot()!"); + IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "GetPlotLimits() Needs to be called between BeginPlot() and EndPlot()!"); const int y_axis = y_axis_in >= 0 ? y_axis_in : gp.CurrentPlot->CurrentYAxis; ImPlot& plot = *gp.CurrentPlot; - ImPlotBounds bounds; - bounds.X = plot.XAxis.Range; - bounds.Y = plot.YAxis[y_axis].Range; - return bounds; + ImPlotLimits limits; + limits.X = plot.XAxis.Range; + limits.Y = plot.YAxis[y_axis].Range; + return limits; } bool IsPlotQueried() { @@ -1706,7 +1751,7 @@ bool IsPlotQueried() { return gp.CurrentPlot->Queried; } -ImPlotBounds GetPlotQuery(int y_axis_in) { +ImPlotLimits GetPlotQuery(int y_axis_in) { IM_ASSERT_USER_ERROR(y_axis_in >= -1 && y_axis_in < MAX_Y_AXES, "y_axis needs to between -1 and MAX_Y_AXES"); IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "GetPlotQuery() Needs to be called between BeginPlot() and EndPlot()!"); ImPlot& plot = *gp.CurrentPlot; @@ -1716,12 +1761,11 @@ ImPlotBounds GetPlotQuery(int y_axis_in) { ImVec2 p1 = PixelsToPlot(plot.QueryRect.Min + gp.BB_Grid.Min, y_axis); ImVec2 p2 = PixelsToPlot(plot.QueryRect.Max + gp.BB_Grid.Min, y_axis); - ImPlotBounds result; + ImPlotLimits result; result.X.Min = ImMin(p1.x, p2.x); result.X.Max = ImMax(p1.x, p2.x); result.Y.Min = ImMin(p1.y, p2.y); result.Y.Max = ImMax(p1.y, p2.y); - return result; } @@ -2520,10 +2564,10 @@ inline void PlotDigitalEx(const char* label_id, Getter getter, int count, int of if (count > 1 && rend_line) { // const float mx = (gp.PixelRange[ax].Max.x - gp.PixelRange[ax].Min.x) / gp.CurrentPlot->XAxis.Range.Size(); - int pixY_0 = line_weight; - int pixY_1 = gp.Style.DigitalBitHeight; - int pixY_Offset = 20;//20 pixel from bottom due to mouse cursor label - int pixY_chOffset = pixY_1 + 3; //3 pixels between channels + float pixY_0 = line_weight; + float pixY_1 = gp.Style.DigitalBitHeight; + float pixY_Offset = 20;//20 pixel from bottom due to mouse cursor label + float pixY_chOffset = pixY_1 + 3; //3 pixels between channels ImVec2 pMin, pMax; float y0 = (gp.PixelRange[ax].Min.y) + ((-pixY_chOffset * gp.DigitalPlotItemCnt) - pixY_0 - pixY_Offset); float y1 = (gp.PixelRange[ax].Min.y) + ((-pixY_chOffset * gp.DigitalPlotItemCnt) - pixY_1 - pixY_Offset); @@ -2554,7 +2598,7 @@ inline void PlotDigitalEx(const char* label_id, Getter getter, int count, int of //plot a rectangle that extends up to x2 with y1 height if ((pMax.x > pMin.x) && (!cull || gp.BB_Grid.Contains(pMin) || gp.BB_Grid.Contains(pMax))) { ImVec4 colAlpha = item->Color; - colAlpha.w = item->Highlight ? 1.0 : 0.9; + colAlpha.w = item->Highlight ? 1.0f : 0.9f; DrawList.AddRectFilled(pMin, pMax, GetColorU32(colAlpha)); } } diff --git a/implot.h b/implot.h index bbe6c44..4911094 100644 --- a/implot.h +++ b/implot.h @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// ImPlot v0.1 WIP +// ImPlot v0.2 WIP #pragma once #include @@ -41,13 +41,14 @@ enum ImPlotFlags_ { ImPlotFlags_Legend = 1 << 1, // a legend will be displayed in the top-left ImPlotFlags_Highlight = 1 << 2, // plot items will be highlighted when their legend entry is hovered ImPlotFlags_Selection = 1 << 3, // the user will be able to box-select with right-mouse - ImPlotFlags_ContextMenu = 1 << 4, // the user will be able to open a context menu with double-right click - ImPlotFlags_Crosshairs = 1 << 5, // the default mouse cursor will be replaced with a crosshair when hovered - ImPlotFlags_CullData = 1 << 6, // plot data outside the plot area will be culled from rendering - ImPlotFlags_AntiAliased = 1 << 7, // lines and fills will be anti-aliased (not recommended) - ImPlotFlags_NoChild = 1 << 8, // a child window region will not be used to capture mouse scroll (can boost performance for single ImGui window applications) - ImPlotFlags_Y2Axis = 1 << 9, // enable a second y axis - ImPlotFlags_Y3Axis = 1 << 10, // enable a third y axis + ImPlotFlags_Query = 1 << 4, // the user will be able to draw query rects with middle-mouse + ImPlotFlags_ContextMenu = 1 << 5, // the user will be able to open a context menu with double-right click + 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_YAxis2 = 1 << 10, // enable a 2nd y axis + ImPlotFlags_YAxis3 = 1 << 11, // enable a 3rd y axis ImPlotFlags_Default = ImPlotFlags_MousePos | ImPlotFlags_Legend | ImPlotFlags_Highlight | ImPlotFlags_Selection | ImPlotFlags_ContextMenu | ImPlotFlags_CullData }; @@ -63,7 +64,7 @@ enum ImAxisFlags_ { ImAxisFlags_LogScale = 1 << 7, // a logartithmic (base 10) axis scale will be used ImAxisFlags_Scientific = 1 << 8, // scientific notation will be used for tick labels if displayed (WIP, not very good yet) ImAxisFlags_Default = ImAxisFlags_GridLines | ImAxisFlags_TickMarks | ImAxisFlags_TickLabels | ImAxisFlags_Adaptive, - ImAxisFlags_Auxiliary_Default = ImAxisFlags_Default & ~ImAxisFlags_GridLines, + ImAxisFlags_Auxiliary = ImAxisFlags_Default & ~ImAxisFlags_GridLines, }; // Plot styling colors @@ -76,15 +77,16 @@ enum ImPlotCol_ { ImPlotCol_FrameBg, // plot frame background color (defaults to ImGuiCol_FrameBg) ImPlotCol_PlotBg, // plot area background color (defaults to ImGuiCol_WindowBg) ImPlotCol_PlotBorder, // plot area border color (defaults to ImGuiCol_Text) - ImPlotCol_XAxis, // x-axis grid/label color (defaults to ImGuiCol_Text) - ImPlotCol_YAxis, // y-axis grid/label color (defaults to ImGuiCol_Text) - ImPlotCol_Y2Axis, // y2-axis grid/label color (defaults to ImGuiCol_Text) - ImPlotCol_Y3Axis, // y3-axis grid/label color (defaults to ImGuiCol_Text) + ImPlotCol_XAxis, // x-axis grid/label color (defaults to 25% ImGuiCol_Text) + ImPlotCol_YAxis, // y-axis grid/label color (defaults to 25% ImGuiCol_Text) + ImPlotCol_YAxis2, // 2nd y-axis grid/label color (defaults to 25% ImGuiCol_Text) + ImPlotCol_YAxis3, // 3rd y-axis grid/label color (defaults to 25% ImGuiCol_Text) ImPlotCol_Selection, // box-selection color (defaults to yellow) ImPlotCol_Query, // box-query color (defaults to green) ImPlotCol_COUNT }; +// Plot styling variables enum ImPlotStyleVar_ { ImPlotStyleVar_LineWeight, // float, line weight in pixels ImPlotStyleVar_Marker, // int, marker specification @@ -111,17 +113,18 @@ enum ImMarker_ { ImMarker_Asterisk = 1 << 10, // a asterisk marker will be rendered at each point (not filled) }; +// A range defined by a min/max value. Used for plot axes ranges. struct ImPlotRange { float Min, Max; ImPlotRange(); - bool Contains(float) const; + bool Contains(float value) const; float Size() const; }; -/// Plot range utility struct -struct ImPlotBounds { +// Combination of two ranges for X and Y axes. +struct ImPlotLimits { ImPlotRange X, Y; - ImPlotBounds(); + ImPlotLimits(); bool Contains(const ImVec2& p) const; }; @@ -148,17 +151,16 @@ namespace ImGui { // be called, e.g. "if (BeginPlot(...)) { ... EndPlot(); }"". #title_id must // be unique. If you need to avoid ID collisions or don't want to display a // title in the plot, use double hashes (e.g. "MyPlot##Hidden"). If #x_label -// and/or #y_label are provided, axes labels will be displayed. Axis flags are -// only set ONCE during the first call to BeginPlot. +// and/or #y_label are provided, axes labels will be displayed. bool BeginPlot(const char* title_id, - const char* x_label = NULL, - const char* y_label = NULL, - const ImVec2& size = ImVec2(-1,-1), - ImPlotFlags flags = ImPlotFlags_Default, - ImAxisFlags x_flags = ImAxisFlags_Default, - ImAxisFlags y_flags = ImAxisFlags_Default, - ImAxisFlags y2_flags = ImAxisFlags_Auxiliary_Default, - ImAxisFlags y3_flags = ImAxisFlags_Auxiliary_Default); + const char* x_label = NULL, + const char* y_label = NULL, + const ImVec2& size = ImVec2(-1,-1), + ImPlotFlags flags = ImPlotFlags_Default, + ImAxisFlags x_flags = ImAxisFlags_Default, + ImAxisFlags y_flags = ImAxisFlags_Default, + ImAxisFlags y2_flags = ImAxisFlags_Auxiliary, + ImAxisFlags y3_flags = ImAxisFlags_Auxiliary); // Only call EndPlot() if BeginPlot() returns true! Typically called at the end // of an if statement conditioned on BeginPlot(). void EndPlot(); @@ -167,7 +169,7 @@ void EndPlot(); // Plot Items //----------------------------------------------------------------------------- -// Plots a standard 2D line and/or scatter plot . +// 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); @@ -201,11 +203,11 @@ bool IsPlotHovered(); /// Returns the mouse position in x,y coordinates of the current or most recent plot. A negative y_axis uses the current value of SetPlotYAxis (0 initially). ImVec2 GetPlotMousePos(int y_axis = -1); /// Returns the current or most recent plot axis range. A negative y_axis uses the current value of SetPlotYAxis (0 initially). -ImPlotBounds GetPlotBounds(int y_axis = -1); +ImPlotLimits GetPlotLimits(int y_axis = -1); /// Returns true if the current or most recent plot is being queried. bool IsPlotQueried(); /// Returns the current or most recent plot query bounds. -ImPlotBounds GetPlotQuery(int y_axis = -1); +ImPlotLimits GetPlotQuery(int y_axis = -1); //----------------------------------------------------------------------------- // Plot Styling @@ -237,15 +239,15 @@ void PopPlotStyleVar(int count = 1); // Plot Utils //----------------------------------------------------------------------------- -/// Set the axes ranges of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the axes will be locked. -void SetNextPlotBounds(float x_min, float x_max, float y_min, float y_max, ImGuiCond cond = ImGuiCond_Once); -/// Set the X axis range of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the axis will be locked. -void SetNextPlotBoundsX(float x_min, float x_max, ImGuiCond cond = ImGuiCond_Once); -/// Set the Y axis range of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the axis will be locked. -void SetNextPlotBoundsY(float y_min, float y_max, ImGuiCond cond = ImGuiCond_Once, int y_axis = 0); +/// Set the axes range limits of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the axes limits will be locked. +void SetNextPlotLimits(float x_min, float x_max, float y_min, float y_max, ImGuiCond cond = ImGuiCond_Once); +/// Set the X axis range limits of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the axis limits will be locked. +void SetNextPlotLimitsX(float x_min, float x_max, ImGuiCond cond = ImGuiCond_Once); +/// Set the Y axis range limits of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the axis limits will be locked. +void SetNextPlotLimitsY(float y_min, float y_max, ImGuiCond cond = ImGuiCond_Once, int y_axis = 0); -/// Select which Y axis will be used for subsequent plot elements. The default is '0', or the first Y axis. -void SetPlotYAxis(int); +/// Select which Y axis will be used for subsequent plot elements. The default is '0', or the first Y axis. +void SetPlotYAxis(int y_axis); // Get the current Plot position (top-left) in pixels. ImVec2 GetPlotPos(); diff --git a/implot_demo.cpp b/implot_demo.cpp index 948efdd..55baebb 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// ImPlot v0.1 WIP +// ImPlot v0.2 WIP #ifdef _MSC_VER #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen @@ -95,7 +95,7 @@ void ShowImPlotDemoWindow(bool* p_open) { 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)"); + ImGui::Text("ImPlot says hello. (0.2 WIP)"); if (ImGui::CollapsingHeader("Help")) { ImGui::Text("USER GUIDE:"); ImGui::BulletText("Left click and drag within the plot area to pan X and Y axes."); @@ -112,11 +112,6 @@ void ShowImPlotDemoWindow(bool* p_open) { ImGui::BulletText("Hold Shift to expand box selection vertically."); ImGui::BulletText("Left click while box selecting to cancel the selection."); ImGui::Unindent(); - ImGui::BulletText("Middle click (or Ctrl + right click) and drag to create a query range."); - ImGui::Indent(); - ImGui::BulletText("Hold Alt to expand query horizontally."); - ImGui::BulletText("Hold Shift to expand query vertically."); - ImGui::Unindent(); ImGui::BulletText("Double left click to fit all visible data."); ImGui::Indent(); ImGui::BulletText("Double left click on an axis to fit the individual axis."); @@ -176,9 +171,9 @@ void ShowImPlotDemoWindow(bool* p_open) { static bool horz = false; ImGui::Checkbox("Horizontal",&horz); if (horz) - ImGui::SetNextPlotBounds(0, 110, -0.5f, 9.5f, ImGuiCond_Always); + ImGui::SetNextPlotLimits(0, 110, -0.5f, 9.5f, ImGuiCond_Always); else - ImGui::SetNextPlotBounds(-0.5f, 9.5f, 0, 110, ImGuiCond_Always); + ImGui::SetNextPlotLimits(-0.5f, 9.5f, 0, 110, ImGuiCond_Always); if (ImGui::BeginPlot("Bar Plot", horz ? "Score": "Student", horz ? "Student" : "Score", {-1, 300})) { static float midtm[10] = {83, 67, 23, 89, 83, 78, 91, 82, 85, 90}; static float final[10] = {80, 62, 56, 99, 55, 78, 88, 78, 90, 100}; @@ -203,7 +198,7 @@ void ShowImPlotDemoWindow(bool* p_open) { float bar[5] = {1,2,5,3,4}; 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::SetNextPlotBounds(0, 6, 0, 10); + ImGui::SetNextPlotLimits(0, 6, 0, 10); if (ImGui::BeginPlot("##ErrorBars",NULL,NULL,ImVec2(-1,300))) { ImGui::PlotBar("Bar", xs, bar, 5, 0.5f); @@ -227,7 +222,7 @@ void ShowImPlotDemoWindow(bool* p_open) { ImVec2 center(0.5f,0.5f); // in plot units, not pixels float radius = 0.4f; // in plot units, not pixels - SetNextPlotBounds(0,1,0,1,ImGuiCond_Always); + SetNextPlotLimits(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(); @@ -242,7 +237,7 @@ void ShowImPlotDemoWindow(bool* p_open) { {0.7412f, 0.0f, 0.1490f, 1.0f}, }; ImGui::SetPlotPalette(YlOrRd, 5); - SetNextPlotBounds(0,1,0,1,ImGuiCond_Always); + SetNextPlotLimits(0,1,0,1,ImGuiCond_Always); static const 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)) { @@ -268,14 +263,14 @@ void ShowImPlotDemoWindow(bool* p_open) { sdata2.AddPoint(t, mouse.y * 0.0005f); rdata2.AddPoint(t, mouse.y * 0.0005f); } - ImGui::SetNextPlotBoundsX(t - 10, t, paused ? ImGuiCond_Once : ImGuiCond_Always); + ImGui::SetNextPlotLimitsX(t - 10, t, paused ? ImGuiCond_Once : ImGuiCond_Always); static int rt_axis = ImAxisFlags_Default & ~ImAxisFlags_TickLabels; if (ImGui::BeginPlot("##Scrolling", NULL, NULL, {-1,150}, ImPlotFlags_Default, rt_axis, rt_axis)) { ImGui::Plot("Data 1", &sdata1.Data[0].x, &sdata1.Data[0].y, sdata1.Data.size(), sdata1.Offset, 2 * sizeof(float)); ImGui::Plot("Data 2", &sdata2.Data[0].x, &sdata2.Data[0].y, sdata2.Data.size(), sdata2.Offset, 2 * sizeof(float)); ImGui::EndPlot(); } - ImGui::SetNextPlotBoundsX(0, 10, ImGuiCond_Always); + ImGui::SetNextPlotLimitsX(0, 10, ImGuiCond_Always); if (ImGui::BeginPlot("##Rolling", NULL, NULL, {-1,150}, ImPlotFlags_Default, rt_axis, rt_axis)) { ImGui::Plot("Data 1", &rdata1.Data[0].x, &rdata1.Data[0].y, rdata1.Data.size(), 0, 2 * sizeof(float)); ImGui::Plot("Data 2", &rdata2.Data[0].x, &rdata2.Data[0].y, rdata2.Data.size(), 0, 2 * sizeof(float)); @@ -285,7 +280,7 @@ void ShowImPlotDemoWindow(bool* p_open) { //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Markers and Labels")) { - ImGui::SetNextPlotBounds(0, 10, 0, 12); + ImGui::SetNextPlotLimits(0, 10, 0, 12); if (ImGui::BeginPlot("##MarkerStyles", NULL, NULL, ImVec2(-1,300), 0, 0, 0)) { float xs[2] = {1,4}; float ys[2] = {10,11}; @@ -369,7 +364,7 @@ void ShowImPlotDemoWindow(bool* p_open) { ys2[i] = log(xs[i]); ys3[i] = pow(10.0f, xs[i]); } - ImGui::SetNextPlotBounds(0.1f, 100, 0, 10); + ImGui::SetNextPlotLimits(0.1f, 100, 0, 10); if (ImGui::BeginPlot("Log Plot", NULL, NULL, ImVec2(-1,300), ImPlotFlags_Default, ImAxisFlags_Default | ImAxisFlags_LogScale )) { ImGui::Plot("f(x) = x", xs, xs, 1001); ImGui::Plot("f(x) = sin(x)+1", xs, ys1, 1001); @@ -379,25 +374,43 @@ void ShowImPlotDemoWindow(bool* p_open) { } } //------------------------------------------------------------------------- - if (ImGui::CollapsingHeader("Multiple Y Axes")) { + if (ImGui::CollapsingHeader("Multiple Y-Axes")) { + static ImVec4 txt_col = ImGui::GetStyle().Colors[ImGuiCol_Text]; + txt_col.w = 0.25f; + static ImVec4 y1_col = txt_col; + static ImVec4 y2_col = txt_col; + static ImVec4 y3_col = txt_col; + static float xs[1001], xs2[1001], ys1[1001], ys2[1001], ys3[1001]; static bool y2_axis = true; static bool y3_axis = false; - + ImGui::Checkbox("Y-Axis 2", &y2_axis); + ImGui::SameLine(); + ImGui::Checkbox("Y-Axis 3", &y3_axis); + ImGui::SameLine(); + ImGui::ColorEdit4("##Col1", &y1_col.x, ImGuiColorEditFlags_NoInputs); + ImGui::SameLine(); + ImGui::ColorEdit4("##Col2", &y2_col.x, ImGuiColorEditFlags_NoInputs); + ImGui::SameLine(); + ImGui::ColorEdit4("##Col3", &y3_col.x, ImGuiColorEditFlags_NoInputs); for (int i = 0; i < 1001; ++i) { xs[i] = (float)(i*0.1f); ys1[i] = sin(xs[i]) * 3 + 1; - ys2[i] = cos(xs[i]) * 0.2 + 0.5; - ys3[i] = sin(xs[i]+.5) * 100 + 200; - xs2[i] = xs[i] + 10.0; + ys2[i] = cos(xs[i]) * 0.2f + 0.5f; + ys3[i] = sin(xs[i]+0.5f) * 100 + 200; + xs2[i] = xs[i] + 10.0f; } - ImGui::SetNextPlotBounds(0.1f, 100, 0, 10); - ImGui::SetNextPlotBoundsY(0, 1, ImGuiCond_Once, 1); - ImGui::SetNextPlotBoundsY(0, 300, ImGuiCond_Once, 2); + ImGui::SetNextPlotLimits(0.1f, 100, 0, 10); + ImGui::SetNextPlotLimitsY(0, 1, ImGuiCond_Once, 1); + ImGui::SetNextPlotLimitsY(0, 300, ImGuiCond_Once, 2); + ImGui::PushPlotColor(ImPlotCol_YAxis, y1_col); + ImGui::PushPlotColor(ImPlotCol_YAxis2, y2_col); + ImGui::PushPlotColor(ImPlotCol_YAxis3, y3_col); + if (ImGui::BeginPlot("Multi-Axis Plot", NULL, NULL, ImVec2(-1,300), ImPlotFlags_Default | - (y2_axis ? ImPlotFlags_Y2Axis : 0) | - (y3_axis ? ImPlotFlags_Y3Axis : 0))) { + (y2_axis ? ImPlotFlags_YAxis2 : 0) | + (y3_axis ? ImPlotFlags_YAxis3 : 0))) { ImGui::Plot("f(x) = x", xs, xs, 1001); ImGui::Plot("f(x) = sin(x)*3+1", xs, ys1, 1001); @@ -413,18 +426,20 @@ void ShowImPlotDemoWindow(bool* p_open) { ImGui::EndPlot(); } - ImGui::Checkbox("Y2 Axis", &y2_axis); - ImGui::SameLine(); - ImGui::Checkbox("Y3 Axis", &y3_axis); + ImGui::PopPlotColor(3); } //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Querying")) { ImGui::BulletText("Ctrl + click in the plot area to draw points."); - ImGui::BulletText("Middle click (or Ctrl + right click) and drag to query points."); - ImGui::BulletText("Hold the Alt and/or Shift keys to expand the query range."); + ImGui::BulletText("Middle click (or Ctrl + right click) and drag to create a query rect."); + ImGui::Indent(); + ImGui::BulletText("Hold Alt to expand query horizontally."); + ImGui::BulletText("Hold Shift to expand query vertically."); + ImGui::BulletText("The query rect can be dragged after it's created."); + ImGui::Unindent(); static ImVector data; - ImPlotBounds range, query; - if (ImGui::BeginPlot("##Drawing", NULL, NULL, ImVec2(-1,300), ImPlotFlags_Default, ImAxisFlags_GridLines, ImAxisFlags_GridLines)) { + ImPlotLimits range, query; + if (ImGui::BeginPlot("##Drawing", NULL, NULL, ImVec2(-1,300), ImPlotFlags_Default | ImPlotFlags_Query, ImAxisFlags_GridLines, ImAxisFlags_GridLines)) { if (ImGui::IsPlotHovered() && ImGui::IsMouseClicked(0) && ImGui::GetIO().KeyCtrl) data.push_back(ImGui::GetPlotMousePos()); ImGui::PushPlotStyleVar(ImPlotStyleVar_LineWeight, 0); @@ -432,7 +447,7 @@ void ShowImPlotDemoWindow(bool* p_open) { if (data.size() > 0) ImGui::Plot("Points", &data[0].x, &data[0].y, data.size(), 0, 2 * sizeof(float)); if (ImGui::IsPlotQueried() && data.size() > 0) { - ImPlotBounds range = ImGui::GetPlotQuery(); + ImPlotLimits range = ImGui::GetPlotQuery(); int cnt = 0; ImVec2 avg; for (int i = 0; i < data.size(); ++i) { @@ -449,12 +464,12 @@ void ShowImPlotDemoWindow(bool* p_open) { } } ImGui::PopPlotStyleVar(2); - range = ImGui::GetPlotBounds(); + range = ImGui::GetPlotLimits(); query = ImGui::GetPlotQuery(); ImGui::EndPlot(); } - ImGui::Text("The current plot range is: [%g,%g,%g,%g]", range.X.Min, range.X.Max, range.Y.Min, range.Y.Max); - ImGui::Text("The current query range is: [%g,%g,%g,%g]", query.X.Min, query.X.Max, query.Y.Min, query.Y.Max); + ImGui::Text("The current plot limits are: [%g,%g,%g,%g]", range.X.Min, range.X.Max, range.Y.Min, range.Y.Max); + ImGui::Text("The current query limits are: [%g,%g,%g,%g]", query.X.Min, query.X.Max, query.Y.Min, query.Y.Max); } //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Views")) { @@ -468,24 +483,23 @@ void ShowImPlotDemoWindow(bool* p_open) { for (size_t i = 0; i < 512; ++i) { const float t = i / sampling_freq; x_data[i] = t; - const float arg = 2 * 3.14 * freq * t; + const float arg = 2 * 3.14f * freq * t; y_data1[i] = sin(arg); - y_data2[i] = y_data1[i] * -0.6 + sin(2 * arg) * 0.4; - y_data3[i] = y_data2[i] * -0.6 + sin(3 * arg) * 0.4; + y_data2[i] = y_data1[i] * -0.6f + sin(2 * arg) * 0.4f; + y_data3[i] = y_data2[i] * -0.6f + sin(3 * arg) * 0.4f; } - ImGui::BulletText("Query the first plot to render a subview in the second plot."); - ImGui::BulletText("Toggle \"Pixel Query\" in the context menu and then pan the plot."); - ImGui::SetNextPlotBounds(0,0.01f,-1,1); + ImGui::BulletText("Query the first plot to render a subview in the second plot (see above for controls)."); + ImGui::SetNextPlotLimits(0,0.01f,-1,1); ImAxisFlags flgs = ImAxisFlags_Default & ~ImAxisFlags_TickLabels; - ImPlotBounds query; - if (ImGui::BeginPlot("##View1",NULL,NULL,ImVec2(-1,150), ImPlotFlags_Default, flgs, flgs)) { + ImPlotLimits query; + if (ImGui::BeginPlot("##View1",NULL,NULL,ImVec2(-1,150), ImPlotFlags_Default | ImPlotFlags_Query, flgs, flgs)) { ImGui::Plot("Signal 1", x_data, y_data1, 512); ImGui::Plot("Signal 2", x_data, y_data2, 512); ImGui::Plot("Signal 3", x_data, y_data3, 512); query = ImGui::GetPlotQuery(); ImGui::EndPlot(); } - ImGui::SetNextPlotBounds(query.X.Min, query.X.Max, query.Y.Min, query.Y.Max, ImGuiCond_Always); + ImGui::SetNextPlotLimits(query.X.Min, query.X.Max, query.Y.Min, query.Y.Max, ImGuiCond_Always); if (ImGui::BeginPlot("##View2",NULL,NULL,ImVec2(-1,150), 0, 0, 0)) { ImGui::Plot("Signal 1", x_data, y_data1, 512); ImGui::Plot("Signal 2", x_data, y_data2, 512); @@ -493,12 +507,9 @@ void ShowImPlotDemoWindow(bool* p_open) { ImGui::EndPlot(); } } - - - //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Drag and Drop")) { - srand(10000000 * ImGui::GetTime()); + srand((int)(10000000 * ImGui::GetTime())); static bool paused = false; static bool init = true; static ScrollingData data[10]; @@ -543,7 +554,7 @@ void ShowImPlotDemoWindow(bool* p_open) { data[i].Data.back().y + (0.005f + 0.0002f * (float)rand() / float(RAND_MAX)) * (-1 + 2 * (float)rand() / float(RAND_MAX))); } } - ImGui::SetNextPlotBoundsX(t - 10, t, paused ? ImGuiCond_Once : ImGuiCond_Always); + ImGui::SetNextPlotLimitsX(t - 10, t, paused ? ImGuiCond_Once : ImGuiCond_Always); if (ImGui::BeginPlot("##DND", NULL, NULL, ImVec2(-1,300), ImPlotFlags_Default)) { for (int i = 0; i < 10; ++i) { if (show[i]) { @@ -644,8 +655,8 @@ void ShowImPlotDemoWindow(bool* p_open) { if (showAnalog[i]) dataAnalog[i].AddPoint(t, sin(2*t) - cos(2*t)); } - ImGui::SetNextPlotBoundsY(-1, 1); - ImGui::SetNextPlotBoundsX(t - 10.0f, t, paused ? ImGuiCond_Once : ImGuiCond_Always); + ImGui::SetNextPlotLimitsY(-1, 1); + ImGui::SetNextPlotLimitsX(t - 10.0f, t, paused ? ImGuiCond_Once : ImGuiCond_Always); if (ImGui::BeginPlot("##Digital", NULL, NULL, ImVec2(-1,300), ImPlotFlags_Default)) { for (int i = 0; i < K_PLOT_DIGITAL_CH_COUNT; ++i) { if (showDigital[i]) { @@ -692,7 +703,7 @@ void ShowImPlotDemoWindow(bool* p_open) { ImGui::PushPlotColor(ImPlotCol_XAxis, IM_COL32(192, 192, 192, 192)); ImGui::PushPlotColor(ImPlotCol_YAxis, IM_COL32(192, 192, 192, 192)); ImGui::PushPlotStyleVar(ImPlotStyleVar_LineWeight, 2); - ImGui::SetNextPlotBounds(-0.5f, 9.5f, -0.5f, 9.5f); + ImGui::SetNextPlotLimits(-0.5f, 9.5f, -0.5f, 9.5f); if (ImGui::BeginPlot("##Custom", NULL, NULL, {-1,300}, ImPlotFlags_Default & ~ImPlotFlags_Legend, 0)) { float lin[10] = {8,8,9,7,8,8,8,9,7,8}; float bar[10] = {1,2,5,3,4,1,2,5,3,4}; @@ -727,7 +738,7 @@ 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); - SetNextPlotBounds(0,1,0,1, ImGuiCond_Always); + SetNextPlotLimits(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) { From 023f96d067653835355329cf00a81e369d2f213a Mon Sep 17 00:00:00 2001 From: Evan Pezent Date: Mon, 11 May 2020 01:10:57 -0500 Subject: [PATCH 10/10] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index b268dfa..a8d4b45 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ ImPlot is an immediate mode plotting widget for [Dear ImGui](https://github.com/ - mix/match multiple plot items on a single plot - configurable axes ranges and scaling (linear/log) - reversible and lockable axes +- support for up to three independent y-axes - controls for zooming, panning, box selection, and auto-fitting data - controls for creating persistent query ranges (see demo) - several plot styling options: 10 marker types, adjustable marker sizes, line weights, outline colors, fill colors, etc. @@ -66,6 +67,10 @@ A: Yes. Plot colors, palettes, and various styling variables can be pushed/poppe A: Yep! +**Q: Does ImPlot support multiple y-axes? x-axes?** + +A: Yes to y-axes (up to three), "not yet" to x-axes. + **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!