diff --git a/implot.cpp b/implot.cpp index 242f152..2d85488 100644 --- a/implot.cpp +++ b/implot.cpp @@ -411,6 +411,16 @@ void FitPoint(const ImPlotPoint& p) { } } +void PushLinkedAxis(ImPlotAxis& axis) { + if (axis.LinkedMin) { *axis.LinkedMin = axis.Range.Min; } + if (axis.LinkedMax) { *axis.LinkedMax = axis.Range.Max; } +} + +void PullLinkedAxis(ImPlotAxis& axis) { + if (axis.LinkedMin) { axis.Range.Min = *axis.LinkedMin; } + if (axis.LinkedMax) { axis.Range.Max = *axis.LinkedMax; } +} + //----------------------------------------------------------------------------- // Coordinate Utils //----------------------------------------------------------------------------- @@ -675,6 +685,16 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // NextPlotData ----------------------------------------------------------- + // linked axes + plot.XAxis.LinkedMin = gp.NextPlotData.LinkedXmin; + plot.XAxis.LinkedMax = gp.NextPlotData.LinkedXmax; + PullLinkedAxis(plot.XAxis); + for (int i = 0; i < IMPLOT_Y_AXES; ++i) { + plot.YAxis[i].LinkedMin = gp.NextPlotData.LinkedYmin[i]; + plot.YAxis[i].LinkedMax = gp.NextPlotData.LinkedYmax[i]; + PullLinkedAxis(plot.YAxis[i]); + } + if (gp.NextPlotData.HasXRange) { if (just_created || gp.NextPlotData.XRangeCond == ImGuiCond_Always) { plot.XAxis.Range = gp.NextPlotData.X; @@ -1649,6 +1669,13 @@ void EndPlot() { ImGui::PopID(); } + + // LINKED AXES ------------------------------------------------------------ + + PushLinkedAxis(plot.XAxis); + for (int i = 0; i < IMPLOT_Y_AXES; ++i) + PushLinkedAxis(plot.YAxis[i]); + // CLEANUP ---------------------------------------------------------------- // reset the plot items for the next frame @@ -1697,6 +1724,18 @@ void SetNextPlotLimitsY(double y_min, double y_max, ImGuiCond cond, int y_axis) gp.NextPlotData.Y[y_axis].Max = y_max; } +void LinkNextPlotLimits(double* xmin, double* xmax, double* ymin, double* ymax, double* ymin2, double* ymax2, double* ymin3, double* ymax3) { + ImPlotContext& gp = *GImPlot; + gp.NextPlotData.LinkedXmin = xmin; + gp.NextPlotData.LinkedXmax = xmax; + gp.NextPlotData.LinkedYmin[0] = ymin; + gp.NextPlotData.LinkedYmax[0] = ymax; + gp.NextPlotData.LinkedYmin[1] = ymin2; + gp.NextPlotData.LinkedYmax[1] = ymax2; + gp.NextPlotData.LinkedYmin[2] = ymin3; + gp.NextPlotData.LinkedYmax[2] = ymax3; +} + void FitNextPlotAxes(bool x, bool y, bool y2, bool y3) { ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(gp.CurrentPlot == NULL, "FitNextPlotAxes() needs to be called before BeginPlot()!"); diff --git a/implot.h b/implot.h index 634a22e..863630a 100644 --- a/implot.h +++ b/implot.h @@ -376,14 +376,17 @@ void PlotText(const char* text, double x, double y, bool vertical = false, const //----------------------------------------------------------------------------- // Set the axes range limits of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the axes limits will be locked. -void SetNextPlotLimits(double x_min, double x_max, double y_min, double y_max, ImGuiCond cond = ImGuiCond_Once); +void SetNextPlotLimits(double xmin, double xmax, double ymin, double ymax, ImGuiCond cond = ImGuiCond_Once); // Set the X axis range limits of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the X axis limits will be locked. -void SetNextPlotLimitsX(double x_min, double x_max, ImGuiCond cond = ImGuiCond_Once); +void SetNextPlotLimitsX(double xmin, double xmax, ImGuiCond cond = ImGuiCond_Once); // Set the Y axis range limits of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the Y axis limits will be locked. -void SetNextPlotLimitsY(double y_min, double y_max, ImGuiCond cond = ImGuiCond_Once, int y_axis = 0); +void SetNextPlotLimitsY(double ymin, double ymax, ImGuiCond cond = ImGuiCond_Once, int y_axis = 0); +// Links the next plot axes to external range. Set to NULL for no linkage. The pointer data must remain valid until the matching call EndPlot. +void LinkNextPlotLimits(double* xmin, double* xmax, double* ymin, double* ymax, double* ymin2 = NULL, double* ymax2 = NULL, double* ymin3 = NULL, double* ymax3 = NULL); // Fits the next plot axes to all plotted data if they are unlocked (equivalent to double-clicks). void FitNextPlotAxes(bool x = true, bool y = true, bool y2 = true, bool y3 = true); + // Set the X axis ticks and optionally the labels for the next plot. void SetNextPlotTicksX(const double* values, int n_ticks, const char** labels = NULL, bool show_default = false); void SetNextPlotTicksX(double x_min, double x_max, int n_ticks, const char** labels = NULL, bool show_default = false); diff --git a/implot_demo.cpp b/implot_demo.cpp index 36cbe6e..a9e8ab2 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -637,6 +637,25 @@ void ShowDemoWindow(bool* p_open) { } } //------------------------------------------------------------------------- + if (ImGui::CollapsingHeader("Linked Axes")) { + static double xmin = 0, xmax = 1, ymin = 0, ymax = 1; + static bool linkx = true, linky = true; + t_float data[2] = {0,1}; + ImGui::Checkbox("Link X", &linkx); + ImGui::SameLine(); + ImGui::Checkbox("Link Y", &linky); + ImPlot::LinkNextPlotLimits(linkx ? &xmin : NULL , linkx ? &xmax : NULL, linky ? &ymin : NULL, linky ? &ymax : NULL); + if (ImPlot::BeginPlot("Plot A")) { + ImPlot::PlotLine("Line",data,2); + ImPlot::EndPlot(); + } + ImPlot::LinkNextPlotLimits(linkx ? &xmin : NULL , linkx ? &xmax : NULL, linky ? &ymin : NULL, linky ? &ymax : NULL); + if (ImPlot::BeginPlot("Plot B")) { + ImPlot::PlotLine("Line",data,2); + ImPlot::EndPlot(); + } + } + //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Querying")) { static ImVector data; static ImPlotLimits range, query; diff --git a/implot_internal.h b/implot_internal.h index 1f4969d..1f95e21 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -249,6 +249,8 @@ struct ImPlotAxis bool Dragging; bool HoveredExt; bool HoveredTot; + double* LinkedMin; + double* LinkedMax; ImPlotAxis() { Flags = PreviousFlags = ImPlotAxisFlags_Default; @@ -257,6 +259,7 @@ struct ImPlotAxis Dragging = false; HoveredExt = false; HoveredTot = false; + LinkedMin = LinkedMax = NULL; } }; @@ -356,19 +359,26 @@ struct ImPlotNextPlotData bool ShowDefaultTicksY[IMPLOT_Y_AXES]; bool FitX; bool FitY[IMPLOT_Y_AXES]; + double* LinkedXmin; + double* LinkedXmax; + double* LinkedYmin[IMPLOT_Y_AXES]; + double* LinkedYmax[IMPLOT_Y_AXES]; ImPlotNextPlotData() { HasXRange = false; ShowDefaultTicksX = true; FitX = false; + LinkedXmin = LinkedXmax = NULL; for (int i = 0; i < IMPLOT_Y_AXES; ++i) { HasYRange[i] = false; ShowDefaultTicksY[i] = true; FitY[i] = false; + LinkedYmin[i] = LinkedYmax[i] = NULL; } } }; + // Temporary data storage for upcoming item struct ImPlotItemStyle { ImVec4 Colors[5]; // ImPlotCol_Line, ImPlotCol_Fill, ImPlotCol_MarkerOutline, ImPlotCol_MarkerFill, ImPlotCol_ErrorBar @@ -547,6 +557,11 @@ inline bool FitThisFrame() { return GImPlot->FitThisFrame; } // Extends the current plots axes so that it encompasses point p void FitPoint(const ImPlotPoint& p); +// Updates pointers for linked axes from axis internal range +void PushLinkedAxis(ImPlotAxis& axis); +// Updates axis internal range from points for linked axes +void PullLinkedAxis(ImPlotAxis& axis); + //----------------------------------------------------------------------------- // [SECTION] Legend Utils //-----------------------------------------------------------------------------