diff --git a/implot.cpp b/implot.cpp index bb46994..d07a3fc 100644 --- a/implot.cpp +++ b/implot.cpp @@ -401,11 +401,11 @@ void FitPoint(const ImPlotPoint& p) { ImPlotRange& ex_y = gp.ExtentsY[y_axis]; const bool log_x = ImHasFlag(gp.CurrentPlot->XAxis.Flags, ImPlotAxisFlags_LogScale); const bool log_y = ImHasFlag(gp.CurrentPlot->YAxis[y_axis].Flags, ImPlotAxisFlags_LogScale); - if (!NanOrInf(p.x) && !(log_x && p.x <= 0)) { + if (!ImNanOrInf(p.x) && !(log_x && p.x <= 0)) { ex_x.Min = p.x < ex_x.Min ? p.x : ex_x.Min; ex_x.Max = p.x > ex_x.Max ? p.x : ex_x.Max; } - if (!NanOrInf(p.y) && !(log_y && p.y <= 0)) { + if (!ImNanOrInf(p.y) && !(log_y && p.y <= 0)) { ex_y.Min = p.y < ex_y.Min ? p.y : ex_y.Min; ex_y.Max = p.y > ex_y.Max ? p.y : ex_y.Max; } @@ -711,23 +711,6 @@ void AddTicksCustom(const double* values, const char** labels, int n, ImPlotTick // Axis Utils //----------------------------------------------------------------------------- -void ConstrainAxis(ImPlotAxis& axis) { - axis.Range.Min = ConstrainNan(ConstrainInf(axis.Range.Min)); - axis.Range.Max = ConstrainNan(ConstrainInf(axis.Range.Max)); - if (ImHasFlag(axis.Flags, ImPlotAxisFlags_LogScale)) { - axis.Range.Min = ConstrainLog(axis.Range.Min); - axis.Range.Max = ConstrainLog(axis.Range.Max); - } - if (ImHasFlag(axis.Flags, ImPlotAxisFlags_Time)) { - axis.Range.Min = ConstrainTime(axis.Range.Min); - axis.Range.Max = ConstrainTime(axis.Range.Max); - // if (axis.Range.Size() < 0.0001) - // axis.Range.Max = axis.Range.Min + 0.0001; // TBD - } - if (axis.Range.Max <= axis.Range.Min) - axis.Range.Max = axis.Range.Min + DBL_EPSILON; -} - void UpdateAxisColors(int axis_flag, ImPlotAxisColor* col) { const ImVec4 col_label = GetStyleColorVec4(axis_flag); const ImVec4 col_grid = GetStyleColorVec4(axis_flag + 1); @@ -810,6 +793,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons if (gp.NextPlotData.HasXRange) { if (just_created || gp.NextPlotData.XRangeCond == ImGuiCond_Always) { plot.XAxis.Range = gp.NextPlotData.X; + plot.XAxis.Constrain(); } } @@ -817,6 +801,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons if (gp.NextPlotData.HasYRange[i]) { if (just_created || gp.NextPlotData.YRangeCond[i] == ImGuiCond_Always) { plot.YAxis[i].Range = gp.NextPlotData.Y[i]; + plot.YAxis[i].Constrain(); } } } @@ -840,12 +825,6 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons gp.Scales[i] = ImPlotScale_LogLog; } - // CONSTRAINTS ------------------------------------------------------------ - - ConstrainAxis(plot.XAxis); - for (int i = 0; i < IMPLOT_Y_AXES; i++) - ConstrainAxis(plot.YAxis[i]); - // AXIS COLORS ----------------------------------------------------------------- UpdateAxisColors(ImPlotCol_XAxis, &gp.Col_X); @@ -1017,19 +996,20 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons ImPlotPoint plot_tl = PixelsToPlot(gp.BB_Plot.Min - IO.MouseDelta, 0); ImPlotPoint plot_br = PixelsToPlot(gp.BB_Plot.Max - IO.MouseDelta, 0); if (!gp.X.LockMin) - plot.XAxis.Range.Min = gp.X.Invert ? plot_br.x : plot_tl.x; + plot.XAxis.SetMin(gp.X.Invert ? plot_br.x : plot_tl.x); if (!gp.X.LockMax) - plot.XAxis.Range.Max = gp.X.Invert ? plot_tl.x : plot_br.x; + plot.XAxis.SetMax(gp.X.Invert ? plot_tl.x : plot_br.x); + // ConstrainAxis(plot.XAxis); } for (int i = 0; i < IMPLOT_Y_AXES; i++) { if (!gp.Y[i].Lock && plot.YAxis[i].Dragging) { ImPlotPoint plot_tl = PixelsToPlot(gp.BB_Plot.Min - IO.MouseDelta, i); ImPlotPoint plot_br = PixelsToPlot(gp.BB_Plot.Max - IO.MouseDelta, i); - if (!gp.Y[i].LockMin) - plot.YAxis[i].Range.Min = gp.Y[i].Invert ? plot_tl.y : plot_br.y; + plot.YAxis[i].SetMin(gp.Y[i].Invert ? plot_tl.y : plot_br.y); if (!gp.Y[i].LockMax) - plot.YAxis[i].Range.Max = gp.Y[i].Invert ? plot_br.y : plot_tl.y; + plot.YAxis[i].SetMax(gp.Y[i].Invert ? plot_br.y : plot_tl.y); + // ConstrainAxis(plot.YAxis[i]); } } // Set the mouse cursor based on which axes are moving. @@ -1080,11 +1060,11 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons ImPlotAxisScale axis_scale(0, tx, ty, zoom_rate); const ImPlotPoint& plot_tl = axis_scale.Min; const ImPlotPoint& plot_br = axis_scale.Max; - if (!gp.X.LockMin) - plot.XAxis.Range.Min = gp.X.Invert ? plot_br.x : plot_tl.x; + plot.XAxis.SetMin(gp.X.Invert ? plot_br.x : plot_tl.x); if (!gp.X.LockMax) - plot.XAxis.Range.Max = gp.X.Invert ? plot_tl.x : plot_br.x; + plot.XAxis.SetMax(gp.X.Invert ? plot_tl.x : plot_br.x); + // ConstrainAxis(plot.XAxis); } for (int i = 0; i < IMPLOT_Y_AXES; i++) { if (plot.YAxis[i].HoveredTot && !gp.Y[i].Lock) { @@ -1092,9 +1072,10 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons const ImPlotPoint& plot_tl = axis_scale.Min; const ImPlotPoint& plot_br = axis_scale.Max; if (!gp.Y[i].LockMin) - plot.YAxis[i].Range.Min = gp.Y[i].Invert ? plot_tl.y : plot_br.y; + plot.YAxis[i].SetMin(gp.Y[i].Invert ? plot_tl.y : plot_br.y); if (!gp.Y[i].LockMax) - plot.YAxis[i].Range.Max = gp.Y[i].Invert ? plot_br.y : plot_tl.y; + plot.YAxis[i].SetMax(gp.Y[i].Invert ? plot_br.y : plot_tl.y); + // ConstrainAxis(plot.YAxis[i]); } } } @@ -1111,16 +1092,16 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons const bool x_can_change = !ImHasFlag(IO.KeyMods,gp.InputMap.HorizontalMod) && ImFabs(select_size.x) > 2; const bool y_can_change = !ImHasFlag(IO.KeyMods,gp.InputMap.VerticalMod) && ImFabs(select_size.y) > 2; if (!gp.X.LockMin && x_can_change) - plot.XAxis.Range.Min = ImMin(p1.x, p2.x); + plot.XAxis.SetMin(ImMin(p1.x, p2.x)); if (!gp.X.LockMax && x_can_change) - plot.XAxis.Range.Max = ImMax(p1.x, p2.x); + plot.XAxis.SetMax(ImMax(p1.x, p2.x)); for (int i = 0; i < IMPLOT_Y_AXES; i++) { p1 = PixelsToPlot(plot.SelectStart, i); p2 = PixelsToPlot(IO.MousePos, i); if (!gp.Y[i].LockMin && y_can_change) - plot.YAxis[i].Range.Min = ImMin(p1.y, p2.y); + plot.YAxis[i].SetMin(ImMin(p1.y, p2.y)); if (!gp.Y[i].LockMax && y_can_change) - plot.YAxis[i].Range.Max = ImMax(p1.y, p2.y); + plot.YAxis[i].SetMax(ImMax(p1.y, p2.y)); } } plot.Selecting = false; @@ -1363,7 +1344,7 @@ inline void ShowAxisContextMenu(ImPlotAxisState& state) { ImGui::PushItemWidth(75); bool total_lock = state.HasRange && state.RangeCond == ImGuiCond_Always; bool logscale = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_LogScale); - bool timesale = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_Time); + // bool timesale = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_Time); bool grid = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines); bool ticks = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_TickMarks); bool labels = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_TickLabels); @@ -1376,7 +1357,9 @@ inline void ShowAxisContextMenu(ImPlotAxisState& state) { ImGui::SameLine(); BeginDisabledControls(state.LockMin); - DragFloat("Min", &state.Axis->Range.Min, (float)drag_speed, -HUGE_VAL, state.Axis->Range.Max - DBL_EPSILON); + double temp_min = state.Axis->Range.Min; + if (DragFloat("Min", &temp_min, (float)drag_speed, -HUGE_VAL, state.Axis->Range.Max - DBL_EPSILON)) + state.Axis->SetMin(temp_min); EndDisabledControls(state.LockMin); BeginDisabledControls(total_lock); @@ -1386,7 +1369,9 @@ inline void ShowAxisContextMenu(ImPlotAxisState& state) { ImGui::SameLine(); BeginDisabledControls(state.LockMax); - DragFloat("Max", &state.Axis->Range.Max, (float)drag_speed, state.Axis->Range.Min + DBL_EPSILON, HUGE_VAL); + double temp_max = state.Axis->Range.Max; + if (DragFloat("Max", &temp_max, (float)drag_speed, state.Axis->Range.Min + DBL_EPSILON, HUGE_VAL)) + state.Axis->SetMax(temp_max); EndDisabledControls(state.LockMax); ImGui::Separator(); @@ -1395,8 +1380,8 @@ inline void ShowAxisContextMenu(ImPlotAxisState& state) { ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_Invert); if (ImGui::Checkbox("Log Scale", &logscale)) ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_LogScale); - if (ImGui::Checkbox("Time", ×ale)) - ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_Time); + // if (ImGui::Checkbox("Time", ×ale)) + // ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_Time); ImGui::Separator(); if (ImGui::Checkbox("Grid Lines", &grid)) ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines); @@ -1744,27 +1729,29 @@ void EndPlot() { // FIT DATA -------------------------------------------------------------- if (gp.FitThisFrame && (gp.VisibleItemCount > 0 || plot.Queried)) { - if (gp.FitX && !ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LockMin) && !NanOrInf(gp.ExtentsX.Min)) { - plot.XAxis.Range.Min = gp.ExtentsX.Min; + if (gp.FitX && !ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LockMin) && !ImNanOrInf(gp.ExtentsX.Min)) { + plot.XAxis.SetMin(gp.ExtentsX.Min); } - if (gp.FitX && !ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LockMax) && !NanOrInf(gp.ExtentsX.Max)) { - plot.XAxis.Range.Max = gp.ExtentsX.Max; + if (gp.FitX && !ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LockMax) && !ImNanOrInf(gp.ExtentsX.Max)) { + plot.XAxis.SetMax(gp.ExtentsX.Max); } if ((plot.XAxis.Range.Max - plot.XAxis.Range.Min) <= (2.0 * FLT_EPSILON)) { plot.XAxis.Range.Max += FLT_EPSILON; plot.XAxis.Range.Min -= FLT_EPSILON; } + // ConstrainAxis(plot.XAxis); for (int i = 0; i < IMPLOT_Y_AXES; i++) { - if (gp.FitY[i] && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LockMin) && !NanOrInf(gp.ExtentsY[i].Min)) { - plot.YAxis[i].Range.Min = gp.ExtentsY[i].Min; + if (gp.FitY[i] && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LockMin) && !ImNanOrInf(gp.ExtentsY[i].Min)) { + plot.YAxis[i].SetMin(gp.ExtentsY[i].Min); } - if (gp.FitY[i] && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LockMax) && !NanOrInf(gp.ExtentsY[i].Max)) { - plot.YAxis[i].Range.Max = gp.ExtentsY[i].Max; + if (gp.FitY[i] && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LockMax) && !ImNanOrInf(gp.ExtentsY[i].Max)) { + plot.YAxis[i].SetMax(gp.ExtentsY[i].Max); } if ((plot.YAxis[i].Range.Max - plot.YAxis[i].Range.Min) <= (2.0 * FLT_EPSILON)) { plot.YAxis[i].Range.Max += FLT_EPSILON; plot.YAxis[i].Range.Min -= FLT_EPSILON; } + // ConstrainAxis(plot.YAxis[i]); } } diff --git a/implot_internal.h b/implot_internal.h index cd6d80a..2907ae0 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -78,6 +78,8 @@ extern ImPlotContext* GImPlot; // Current implicit context pointer // Zoom rate for scroll (e.g. 0.1f = 10% plot range every scroll click) #define IMPLOT_ZOOM_RATE 0.1f // Maximum allowable timestamp value 01/01/3000 @ 12:00am (UTC) +#define IMPLOT_MIN_TIME 0 +// Maximum allowable timestamp value 01/01/3000 @ 12:00am (UTC) #define IMPLOT_MAX_TIME 32503680000 //----------------------------------------------------------------------------- @@ -87,21 +89,27 @@ extern ImPlotContext* GImPlot; // Current implicit context pointer // Computes the common (base-10) logarithm static inline float ImLog10(float x) { return log10f(x); } static inline double ImLog10(double x) { return log10(x); } - // Returns true if a flag is set template inline bool ImHasFlag(TSet set, TFlag flag) { return (set & flag) == flag; } - // Flips a flag in a flagset template inline void ImFlipFlag(TSet& set, TFlag flag) { ImHasFlag(set, flag) ? set &= ~flag : set |= flag; } - // Linearly remaps x from [x0 x1] to [y0 y1]. template inline T ImRemap(T x, T x0, T x1, T y0, T y1) { return y0 + (x - x0) * (y1 - y0) / (x1 - x0); } - // Returns always positive modulo (assumes r != 0) inline int ImPosMod(int l, int r) { return (l % r + r) % r; } +// Returns true if val is NAN or INFINITY +inline bool ImNanOrInf(double val) { return val == HUGE_VAL || val == -HUGE_VAL || isnan(val); } +// Turns NANs to 0s +inline double ImConstrainNan(double val) { return isnan(val) ? 0 : val; } +// Turns infinity to floating point maximums +inline double ImConstrainInf(double val) { return val == HUGE_VAL ? DBL_MAX : val == -HUGE_VAL ? - DBL_MAX : val; } +// Turns numbers less than or equal to 0 to 0.001 (sort of arbitrary, is there a better way?) +inline double ImConstrainLog(double val) { return val <= 0 ? 0.001f : val; } +// Turns numbers less than 0 to zero +inline double ImConstrainTime(double val) { return val < IMPLOT_MIN_TIME ? IMPLOT_MIN_TIME : (val > IMPLOT_MAX_TIME ? IMPLOT_MAX_TIME : val); } // Offset calculator helper template @@ -265,6 +273,53 @@ struct ImPlotAxis HoveredExt = false; HoveredTot = false; } + + bool SetMin(double _min) { + _min = ImConstrainNan(ImConstrainInf(_min)); + if (ImHasFlag(Flags, ImPlotAxisFlags_LogScale)) + _min = ImConstrainLog(_min); + if (ImHasFlag(Flags, ImPlotAxisFlags_Time)) { + _min = ImConstrainTime(_min); + if ((Range.Max - _min) < 0.0001) + return false; + } + if (_min >= Range.Max) + return false; + Range.Min = _min; + return true; + }; + + bool SetMax(double _max) { + _max = ImConstrainNan(ImConstrainInf(_max)); + if (ImHasFlag(Flags, ImPlotAxisFlags_LogScale)) + _max = ImConstrainLog(_max); + if (ImHasFlag(Flags, ImPlotAxisFlags_Time)) { + _max = ImConstrainTime(_max); + if ((_max - Range.Min) < 0.0001) + return false; + } + if (_max <= Range.Min) + return false; + Range.Max = _max; + return true; + }; + + void Constrain() { + Range.Min = ImConstrainNan(ImConstrainInf(Range.Min)); + Range.Max = ImConstrainNan(ImConstrainInf(Range.Max)); + if (ImHasFlag(Flags, ImPlotAxisFlags_LogScale)) { + Range.Min = ImConstrainLog(Range.Min); + Range.Max = ImConstrainLog(Range.Max); + } + if (ImHasFlag(Flags, ImPlotAxisFlags_Time)) { + Range.Min = ImConstrainTime(Range.Min); + Range.Max = ImConstrainTime(Range.Max); + if (Range.Size() < 0.0001) + Range.Max = Range.Min + 0.0001; // TBD + } + if (Range.Max <= Range.Min) + Range.Max = Range.Min + DBL_EPSILON; + } }; // Axis state information only needed between BeginPlot/EndPlot @@ -544,8 +599,6 @@ void BustItemCache(); // Gets the current y-axis for the current plot inline int GetCurrentYAxis() { return GImPlot->CurrentPlot->CurrentYAxis; } -// Constrains an axis range -void ConstrainAxis(ImPlotAxis& axis); // Updates axis ticks, lins, and label colors void UpdateAxisColors(int axis_flag, ImPlotAxisColor* col); @@ -626,16 +679,6 @@ inline ImU32 CalcTextColor(const ImVec4& bg) { return (bg.x * 0.299 + bg.y * 0.5 // Rounds x to powers of 2,5 and 10 for generating axis labels (from Graphics Gems 1 Chapter 11.2) double NiceNum(double x, bool round); -// Returns true if val is NAN or INFINITY -inline bool NanOrInf(double val) { return val == HUGE_VAL || val == -HUGE_VAL || isnan(val); } -// Turns NANs to 0s -inline double ConstrainNan(double val) { return isnan(val) ? 0 : val; } -// Turns infinity to floating point maximums -inline double ConstrainInf(double val) { return val == HUGE_VAL ? DBL_MAX : val == -HUGE_VAL ? - DBL_MAX : val; } -// Turns numbers less than or equal to 0 to 0.001 (sort of arbitrary, is there a better way?) -inline double ConstrainLog(double val) { return val <= 0 ? 0.001f : val; } -// Turns numbers less than 0 to zero -inline double ConstrainTime(double val) { return val < 0 ? 0 : (val > IMPLOT_MAX_TIME ? IMPLOT_MAX_TIME : val); } // Computes order of magnitude of double. inline int OrderOfMagnitude(double val) { return val == 0 ? 0 : (int)(floor(log10(fabs(val)))); } // Returns the precision required for a order of magnitude. diff --git a/implot_items.cpp b/implot_items.cpp index 24206a5..0138b0a 100644 --- a/implot_items.cpp +++ b/implot_items.cpp @@ -1512,11 +1512,11 @@ inline void PlotDigitalEx(const char* label_id, Getter getter) { ImPlotPoint itemData1 = getter(0); for (int i = 0; i < getter.Count; ++i) { ImPlotPoint itemData2 = getter(i); - if (NanOrInf(itemData1.y)) { + if (ImNanOrInf(itemData1.y)) { itemData1 = itemData2; continue; } - if (NanOrInf(itemData2.y)) itemData2.y = ConstrainNan(ConstrainInf(itemData2.y)); + if (ImNanOrInf(itemData2.y)) itemData2.y = ImConstrainNan(ImConstrainInf(itemData2.y)); int pixY_0 = (int)(s.LineWeight); itemData1.y = ImMax(0.0, itemData1.y); float pixY_1_float = s.DigitalBitHeight * (float)itemData1.y; @@ -1532,7 +1532,7 @@ inline void PlotDigitalEx(const char* label_id, Getter getter) { while (((i+2) < getter.Count) && (itemData1.y == itemData2.y)) { const int in = (i + 1); itemData2 = getter(in); - if (NanOrInf(itemData2.y)) break; + if (ImNanOrInf(itemData2.y)) break; pMax.x = PlotToPixels(itemData2).x; i++; }