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) {