From 5f77a9bb58001b5bac462381192ddcd79b3c6a82 Mon Sep 17 00:00:00 2001 From: epezent Date: Wed, 2 Sep 2020 23:30:32 -0500 Subject: [PATCH 01/16] first pass at time formatted axes --- implot.cpp | 33 ++++++++- implot.h | 1 + implot_demo.cpp | 7 ++ implot_internal.h | 179 +++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 214 insertions(+), 6 deletions(-) diff --git a/implot.cpp b/implot.cpp index 242f152..f1dff5c 100644 --- a/implot.cpp +++ b/implot.cpp @@ -501,7 +501,7 @@ void LabelTickDefault(ImPlotTick& tick, ImGuiTextBuffer& buffer) { char temp[32]; if (tick.ShowLabel) { tick.BufferOffset = buffer.size(); - sprintf(temp, "%.10g", tick.PlotPos); + snprintf(temp, 32, "%.10g", tick.PlotPos); buffer.append(temp, temp + strlen(temp) + 1); tick.LabelSize = ImGui::CalcTextSize(buffer.Buf.Data + tick.BufferOffset); } @@ -511,7 +511,17 @@ void LabelTickScientific(ImPlotTick& tick, ImGuiTextBuffer& buffer) { char temp[32]; if (tick.ShowLabel) { tick.BufferOffset = buffer.size(); - sprintf(temp, "%.0E", tick.PlotPos); + snprintf(temp, 32, "%.0E", tick.PlotPos); + buffer.append(temp, temp + strlen(temp) + 1); + tick.LabelSize = ImGui::CalcTextSize(buffer.Buf.Data + tick.BufferOffset); + } +} + +void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeFmt fmt) { + char temp[16]; + if (tick.ShowLabel) { + tick.BufferOffset = buffer.size(); + FormatTime(tick.PlotPos, temp, 16, fmt); buffer.append(temp, temp + strlen(temp) + 1); tick.LabelSize = ImGui::CalcTextSize(buffer.Buf.Data + tick.BufferOffset); } @@ -565,6 +575,17 @@ void AddTicksLogarithmic(const ImPlotRange& range, int nMajor, ImPlotTickCollect } } +void AddTicksTime(const ImPlotRange& range, ImPlotTickCollection& ticks) { + ImPlotTimeUnit unit_range = GetUnitForRange(range.Min, range.Max); + ImPlotTimeUnit unit_ticks = unit_range == 0 ? ImPlotTimeUnit_Us : unit_range - 1; + double t = FloorTime(range.Min, unit_range); + while (t < range.Max) { + t = AddTime(t, unit_ticks, 1); + ImPlotTick tick(t,false,true); + LabelTickTime(tick,ticks.Labels,unit_ticks); + ticks.AddTick(tick); + } +} void AddTicksCustom(const double* values, const char** labels, int n, ImPlotTickCollection& ticks) { for (int i = 0; i < n; ++i) { @@ -755,6 +776,8 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons if (gp.RenderX && gp.NextPlotData.ShowDefaultTicksX) { if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) AddTicksLogarithmic(plot.XAxis.Range, (int)(gp.BB_Canvas.GetWidth() * 0.01f), gp.XTicks); + else if (gp.X.IsTime) + AddTicksTime(plot.XAxis.Range, gp.XTicks); else AddTicksDefault(plot.XAxis.Range, ImMax(2, (int)IM_ROUND(0.003 * gp.BB_Canvas.GetWidth())), IMPLOT_SUB_DIV, gp.XTicks); } @@ -768,10 +791,14 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } // plot bb + const ImVec2 title_size = ImGui::CalcTextSize(title, NULL, true); const float txt_height = ImGui::GetTextLineHeight(); + const float pad_top = title_size.x > 0.0f ? txt_height + gp.Style.LabelPadding.y : 0; - const float pad_bot = (gp.X.HasLabels ? txt_height + gp.Style.LabelPadding.y : 0) + (x_label ? txt_height + gp.Style.LabelPadding.y : 0); + const float pad_bot = (gp.X.HasLabels ? txt_height + gp.Style.LabelPadding.y : 0) + + (x_label ? txt_height + gp.Style.LabelPadding.y : 0) + + (gp.X.IsTime ? txt_height + gp.Style.LabelPadding.y : 0); const float pad_left = (y_label ? txt_height + gp.Style.LabelPadding.x : 0) + (gp.Y[0].HasLabels ? gp.YTicks[0].MaxWidth + gp.Style.LabelPadding.x : 0); const float pad_right = ((gp.Y[1].Present && gp.Y[1].HasLabels) ? gp.YTicks[1].MaxWidth + gp.Style.LabelPadding.x : 0) diff --git a/implot.h b/implot.h index 634a22e..eda8d3d 100644 --- a/implot.h +++ b/implot.h @@ -76,6 +76,7 @@ enum ImPlotAxisFlags_ { ImPlotAxisFlags_LockMin = 1 << 4, // the axis minimum value will be locked when panning/zooming ImPlotAxisFlags_LockMax = 1 << 5, // the axis maximum value will be locked when panning/zooming ImPlotAxisFlags_LogScale = 1 << 6, // a logartithmic (base 10) axis scale will be used + ImPlotAxisFlags_Time = 1 << 7, // axis will display data/time formatted labels ImPlotAxisFlags_Default = ImPlotAxisFlags_GridLines | ImPlotAxisFlags_TickMarks | ImPlotAxisFlags_TickLabels, ImPlotAxisFlags_Auxiliary = ImPlotAxisFlags_TickMarks | ImPlotAxisFlags_TickLabels, }; diff --git a/implot_demo.cpp b/implot_demo.cpp index 36cbe6e..2ed3a81 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -591,6 +591,13 @@ void ShowDemoWindow(bool* p_open) { ImPlot::EndPlot(); } } + if (ImGui::CollapsingHeader("Time Formatting")) { + ImPlot::SetNextPlotLimits(1599106881,1599106881+1000000,0,1); + if (ImPlot::BeginPlot("UTC Time", "Date-Time", "Y-Axis", ImVec2(-1,0), ImPlotFlags_Default, ImPlotAxisFlags_Default | ImPlotAxisFlags_Time)) { + + ImPlot::EndPlot(); + } + } //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Multiple Y-Axes")) { diff --git a/implot_internal.h b/implot_internal.h index 1f4969d..934b546 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -35,6 +35,7 @@ #define IMGUI_DEFINE_MATH_OPERATORS #endif +#include #include "imgui_internal.h" #ifndef IMPLOT_VERSION @@ -147,7 +148,9 @@ struct ImPlotPointArray { // [SECTION] ImPlot Enums //----------------------------------------------------------------------------- -typedef int ImPlotScale; // -> enum ImPlotScale_ +typedef int ImPlotScale; // -> enum ImPlotScale_ +typedef int ImPlotTimeUnit; // -> enum ImPlotTimeUnit_ +typedef int ImPlotTimeFmt; // -> enum ImPlotTimeFmt_ // XY axes scaling combinations enum ImPlotScale_ { @@ -157,6 +160,29 @@ enum ImPlotScale_ { ImPlotScale_LogLog // log x, log y }; +enum ImPlotTimeUnit_ { + ImPlotTimeUnit_Us, // microsecond + ImPlotTimeUnit_Ms, // millisecond + ImPlotTimeUnit_S, // second + ImPlotTimeUnit_Min, // minute + ImPlotTimeUnit_Hr, // hour + ImPlotTimeUnit_Day, // day + ImPlotTimeUnit_Mo, // month + ImPlotTimeUnit_Yr, // year + ImPlotTimeUnit_COUNT +}; + +enum ImPlotTimeFmt_ { + ImPlotTimeFmt_SUs, // :29.428 552 + ImPlotTimeFmt_SMs, // :29.428 + ImPlotTimeFmt_S, // :29 + ImPlotTimeFmt_HrMin, // 7:21pm + ImPlotTimeFmt_Hr, // 7pm + ImPlotTimeFmt_MoDay, // 10/3 + ImPlotTimeFmt_Mo, // Oct + ImPlotTimeFmt_Yr // 1991 +}; + //----------------------------------------------------------------------------- // [SECTION] ImPlot Structs //----------------------------------------------------------------------------- @@ -272,6 +298,7 @@ struct ImPlotAxisState bool LockMin; bool LockMax; bool Lock; + bool IsTime; ImPlotAxisState(ImPlotAxis* axis, bool has_range, ImGuiCond range_cond, bool present) { Axis = axis; @@ -283,6 +310,7 @@ struct ImPlotAxisState LockMin = ImHasFlag(Axis->Flags, ImPlotAxisFlags_LockMin) || (HasRange && RangeCond == ImGuiCond_Always); LockMax = ImHasFlag(Axis->Flags, ImPlotAxisFlags_LockMax) || (HasRange && RangeCond == ImGuiCond_Always); Lock = !Present || ((LockMin && LockMax) || (HasRange && RangeCond == ImGuiCond_Always)); + IsTime = ImHasFlag(Axis->Flags, ImPlotAxisFlags_Time); } ImPlotAxisState() { } @@ -456,6 +484,9 @@ struct ImPlotContext { int ColormapSize; ImVector ColormapModifiers; + // Time + tm Tm; + // Misc int VisibleItemCount; int DigitalPlotItemCnt; @@ -560,15 +591,19 @@ const char* GetLegendLabel(int i); // [SECTION] Tick Utils //----------------------------------------------------------------------------- -// Label a tick with default formatting +// Label a tick with default formatting. void LabelTickDefault(ImPlotTick& tick, ImGuiTextBuffer& buffer); -// Label a tick with scientific formating +// Label a tick with scientific formating. void LabelTickScientific(ImPlotTick& tick, ImGuiTextBuffer& buffer); +// Label a tick with time formatting. +void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeFmt fmt); // Populates a list of ImPlotTicks with normal spaced and formatted ticks void AddTicksDefault(const ImPlotRange& range, int nMajor, int nMinor, ImPlotTickCollection& ticks); // Populates a list of ImPlotTicks with logarithmic space and formatted ticks void AddTicksLogarithmic(const ImPlotRange& range, int nMajor, ImPlotTickCollection& ticks); +// Populates a list of ImPlotTicks with time formatted ticks. +void AddTicksTime(const ImPlotRange& range, int nMajor, ImPlotTickCollection& ticks); // Populates a list of ImPlotTicks with custom spaced and labeled ticks void AddTicksCustom(const double* values, const char** labels, int n, ImPlotTickCollection& ticks); @@ -649,6 +684,144 @@ inline T OffsetAndStride(const T* data, int idx, int count, int offset, int stri return *(const T*)(const void*)((const unsigned char*)data + (size_t)idx * stride); } +//----------------------------------------------------------------------------- +// [SECTION] Time Utils +//----------------------------------------------------------------------------- + +static const int DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +static const double ImPlotTimeUnitSpans[ImPlotTimeUnit_COUNT] = {0.000001, 0.001, 1, 60, 3600, 86400, 2629800, 31557600}; + +inline ImPlotTimeUnit GetUnitForRange(double smin, double smax) { + double range = smax - smin; + for (int i = 0; i < ImPlotTimeUnit_COUNT; ++i) { + if (range <= ImPlotTimeUnitSpans[i]) + return (ImPlotTimeUnit)i; + } + return ImPlotTimeUnit_Yr; +} + +// Returns true if year is leap year (366 days long) +inline bool IsLeapYear(int year) { + if (year % 4 != 0) return false; + if (year % 400 == 0) return true; + if (year % 100 == 0) return false; + return true; +} + +// Returns the number of days in a month, accounting for Feb. leap years. +inline int GetDaysInMonth(int year, int month) { + return DaysInMonth[month] + (int)(month == 1 && IsLeapYear(year)); +} + +inline time_t MkGmTime(const struct tm *ptm) { + time_t secs = 0; + int year = ptm->tm_year + 1900; + for (int y = 1970; y < year; ++y) { + secs += (IsLeapYear(y)? 366: 365) * 86400; + } + for (int m = 0; m < ptm->tm_mon; ++m) { + secs += DaysInMonth[m] * 86400; + if (m == 1 && IsLeapYear(year)) secs += 86400; + } + secs += (ptm->tm_mday - 1) * 86400; + secs += ptm->tm_hour * 3600; + secs += ptm->tm_min * 60; + secs += ptm->tm_sec; + return secs; +} + +inline tm* GmTime(const time_t* time, tm* tm) +{ +#ifdef _MSC_VER + if (gmtime_s(tm, time) == 0) + return tm; + else + return NULL; +#else + return gmtime_r(time, tm); +#endif +} + +inline double AddTime(double t, ImPlotTimeUnit unit, int count) { + switch(unit) { + case ImPlotTimeUnit_Us: return t + count * 0.000001; + case ImPlotTimeUnit_Ms: return t + count * 0.001; + case ImPlotTimeUnit_S: return t + count; + case ImPlotTimeUnit_Min: return t + count * 60; + case ImPlotTimeUnit_Hr: return t + count * 3600; + case ImPlotTimeUnit_Day: return t + count * 86400; + case ImPlotTimeUnit_Yr: count *= 12; // fall-through + case ImPlotTimeUnit_Mo: for (int i = 0; i < count; ++i) { + time_t s = (time_t)t; + GmTime(&s, &GImPlot->Tm); + int days = GetDaysInMonth(GImPlot->Tm.tm_year, GImPlot->Tm.tm_mon); + t = AddTime(t, ImPlotTimeUnit_Day, days); + } + return t; + default: return t; + } +} + +inline double FloorTime(double t, ImPlotTimeUnit unit) { + time_t s = (time_t)t; + GmTime(&s, &GImPlot->Tm); + GImPlot->Tm.tm_isdst = -1; + switch (unit) { + case ImPlotTimeUnit_S: return (double)s; + case ImPlotTimeUnit_Ms: return floor(t * 1000) / 1000; + case ImPlotTimeUnit_Us: return floor(t * 1000000) / 1000000; + case ImPlotTimeUnit_Yr: GImPlot->Tm.tm_mon = 0; // fall-through + case ImPlotTimeUnit_Mo: GImPlot->Tm.tm_mday = 1; // fall-through + case ImPlotTimeUnit_Day: GImPlot->Tm.tm_hour = 0; // fall-through + case ImPlotTimeUnit_Hr: GImPlot->Tm.tm_min = 0; // fall-through + case ImPlotTimeUnit_Min: GImPlot->Tm.tm_sec = 0; break; + default: return t; + } + s = MkGmTime(&GImPlot->Tm); + return (double)s; +} + +inline double CeilTime(double t, ImPlotTimeUnit unit) { + return AddTime(FloorTime(t, unit), unit, 1); +} + +inline void FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) { + time_t s = (time_t)t; + int ms = (int)(t * 1000 - floor(t) * 1000); + int us = (int)(t * 1000000 - floor(t) * 1000000); + tm& Tm = GImPlot->Tm; + GmTime(&s, &Tm); + switch(fmt) { + case ImPlotTimeFmt_Yr: strftime(buffer, size, "%Y", &Tm); break; + case ImPlotTimeFmt_Mo: strftime(buffer, size, "%b", &Tm); break; + case ImPlotTimeFmt_MoDay: strftime(buffer, size, "%m/%d", &Tm); break; + case ImPlotTimeFmt_Hr: + if (Tm.tm_hour == 0) + snprintf(buffer, size, "12am"); + else if (Tm.tm_hour == 12) + snprintf(buffer, size, "12pm"); + else if (Tm.tm_hour < 12) + snprintf(buffer, size, "%uam", Tm.tm_hour); + else if (Tm.tm_hour > 12) + snprintf(buffer, size, "%upm", Tm.tm_hour - 12); + break; + case ImPlotTimeFmt_HrMin: + if (Tm.tm_hour == 0) + snprintf(buffer, size, "12:%02dam", Tm.tm_min); + else if (Tm.tm_hour == 12) + snprintf(buffer, size, "12%02dpm", Tm.tm_min); + else if (Tm.tm_hour < 12) + snprintf(buffer, size, "%u:%02dam", Tm.tm_hour, Tm.tm_min); + else if (Tm.tm_hour > 12) + snprintf(buffer, size, "%u:%02dpm", Tm.tm_hour - 12, Tm.tm_min); + break; + case ImPlotTimeFmt_S: strftime(buffer, size, ":%S", &Tm); + case ImPlotTimeFmt_SMs: snprintf(buffer, size, ":%02d.%d", Tm.tm_sec, ms); + case ImPlotTimeFmt_SUs: snprintf(buffer, size, ":%02d.%d", Tm.tm_sec, us); + default: break; + } +} + //----------------------------------------------------------------------------- // [SECTION] Internal / Experimental Plotters // No guarantee of forward compatibility here! From e5f1cf4bdf57e8fe4bdc2ff3d79712045236f7c9 Mon Sep 17 00:00:00 2001 From: epezent Date: Thu, 3 Sep 2020 08:00:36 -0500 Subject: [PATCH 02/16] fix bug in FormatTime --- implot.cpp | 5 +++-- implot_internal.h | 51 +++++++++++++++++++---------------------------- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/implot.cpp b/implot.cpp index f1dff5c..8e2bd70 100644 --- a/implot.cpp +++ b/implot.cpp @@ -517,11 +517,11 @@ void LabelTickScientific(ImPlotTick& tick, ImGuiTextBuffer& buffer) { } } -void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeFmt fmt) { +void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeUnit unit) { char temp[16]; if (tick.ShowLabel) { tick.BufferOffset = buffer.size(); - FormatTime(tick.PlotPos, temp, 16, fmt); + FormatTime(tick.PlotPos, temp, 16, unit); buffer.append(temp, temp + strlen(temp) + 1); tick.LabelSize = ImGui::CalcTextSize(buffer.Buf.Data + tick.BufferOffset); } @@ -578,6 +578,7 @@ void AddTicksLogarithmic(const ImPlotRange& range, int nMajor, ImPlotTickCollect void AddTicksTime(const ImPlotRange& range, ImPlotTickCollection& ticks) { ImPlotTimeUnit unit_range = GetUnitForRange(range.Min, range.Max); ImPlotTimeUnit unit_ticks = unit_range == 0 ? ImPlotTimeUnit_Us : unit_range - 1; + printf("%d\n",(int)unit_ticks); double t = FloorTime(range.Min, unit_range); while (t < range.Max) { t = AddTime(t, unit_ticks, 1); diff --git a/implot_internal.h b/implot_internal.h index 934b546..e6cdda5 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -150,7 +150,7 @@ struct ImPlotPointArray { typedef int ImPlotScale; // -> enum ImPlotScale_ typedef int ImPlotTimeUnit; // -> enum ImPlotTimeUnit_ -typedef int ImPlotTimeFmt; // -> enum ImPlotTimeFmt_ +typedef int ImPlotTimeUnit; // -> enum ImPlotTimeUnit_ // XY axes scaling combinations enum ImPlotScale_ { @@ -161,28 +161,17 @@ enum ImPlotScale_ { }; enum ImPlotTimeUnit_ { - ImPlotTimeUnit_Us, // microsecond - ImPlotTimeUnit_Ms, // millisecond - ImPlotTimeUnit_S, // second - ImPlotTimeUnit_Min, // minute - ImPlotTimeUnit_Hr, // hour - ImPlotTimeUnit_Day, // day - ImPlotTimeUnit_Mo, // month - ImPlotTimeUnit_Yr, // year + ImPlotTimeUnit_Us, // microsecond (:29.428 552) + ImPlotTimeUnit_Ms, // millisecond (:29.428) + ImPlotTimeUnit_S, // second (:29) + ImPlotTimeUnit_Min, // minute (7:21pm) + ImPlotTimeUnit_Hr, // hour (7pm) + ImPlotTimeUnit_Day, // day (10/3) + ImPlotTimeUnit_Mo, // month (Oct) + ImPlotTimeUnit_Yr, // year (1991) ImPlotTimeUnit_COUNT }; -enum ImPlotTimeFmt_ { - ImPlotTimeFmt_SUs, // :29.428 552 - ImPlotTimeFmt_SMs, // :29.428 - ImPlotTimeFmt_S, // :29 - ImPlotTimeFmt_HrMin, // 7:21pm - ImPlotTimeFmt_Hr, // 7pm - ImPlotTimeFmt_MoDay, // 10/3 - ImPlotTimeFmt_Mo, // Oct - ImPlotTimeFmt_Yr // 1991 -}; - //----------------------------------------------------------------------------- // [SECTION] ImPlot Structs //----------------------------------------------------------------------------- @@ -596,7 +585,7 @@ void LabelTickDefault(ImPlotTick& tick, ImGuiTextBuffer& buffer); // Label a tick with scientific formating. void LabelTickScientific(ImPlotTick& tick, ImGuiTextBuffer& buffer); // Label a tick with time formatting. -void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeFmt fmt); +void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeUnit fmt); // Populates a list of ImPlotTicks with normal spaced and formatted ticks void AddTicksDefault(const ImPlotRange& range, int nMajor, int nMinor, ImPlotTickCollection& ticks); @@ -785,17 +774,17 @@ inline double CeilTime(double t, ImPlotTimeUnit unit) { return AddTime(FloorTime(t, unit), unit, 1); } -inline void FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) { +inline void FormatTime(double t, char* buffer, int size, ImPlotTimeUnit unit) { time_t s = (time_t)t; int ms = (int)(t * 1000 - floor(t) * 1000); int us = (int)(t * 1000000 - floor(t) * 1000000); tm& Tm = GImPlot->Tm; GmTime(&s, &Tm); - switch(fmt) { - case ImPlotTimeFmt_Yr: strftime(buffer, size, "%Y", &Tm); break; - case ImPlotTimeFmt_Mo: strftime(buffer, size, "%b", &Tm); break; - case ImPlotTimeFmt_MoDay: strftime(buffer, size, "%m/%d", &Tm); break; - case ImPlotTimeFmt_Hr: + switch(unit) { + case ImPlotTimeUnit_Yr: strftime(buffer, size, "%Y", &Tm); break; + case ImPlotTimeUnit_Mo: strftime(buffer, size, "%b", &Tm); break; + case ImPlotTimeUnit_Day: strftime(buffer, size, "%m/%d", &Tm); break; + case ImPlotTimeUnit_Hr: if (Tm.tm_hour == 0) snprintf(buffer, size, "12am"); else if (Tm.tm_hour == 12) @@ -805,7 +794,7 @@ inline void FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) { else if (Tm.tm_hour > 12) snprintf(buffer, size, "%upm", Tm.tm_hour - 12); break; - case ImPlotTimeFmt_HrMin: + case ImPlotTimeUnit_Min: if (Tm.tm_hour == 0) snprintf(buffer, size, "12:%02dam", Tm.tm_min); else if (Tm.tm_hour == 12) @@ -815,9 +804,9 @@ inline void FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) { else if (Tm.tm_hour > 12) snprintf(buffer, size, "%u:%02dpm", Tm.tm_hour - 12, Tm.tm_min); break; - case ImPlotTimeFmt_S: strftime(buffer, size, ":%S", &Tm); - case ImPlotTimeFmt_SMs: snprintf(buffer, size, ":%02d.%d", Tm.tm_sec, ms); - case ImPlotTimeFmt_SUs: snprintf(buffer, size, ":%02d.%d", Tm.tm_sec, us); + case ImPlotTimeUnit_S: snprintf(buffer, size, ":%02d", Tm.tm_sec); break; + case ImPlotTimeUnit_Ms: snprintf(buffer, size, ":%02d.%03d", Tm.tm_sec, ms); break; + case ImPlotTimeUnit_Us: snprintf(buffer, size, ":%02d.%06d", Tm.tm_sec, us); break; default: break; } } From 0c76ffe81eb457b0ed424d87aee3a240f2fab3c4 Mon Sep 17 00:00:00 2001 From: epezent Date: Thu, 3 Sep 2020 23:27:56 -0500 Subject: [PATCH 03/16] time-axes almost working! --- implot.cpp | 182 ++++++++++++++++++++++++++++++++++------------ implot_internal.h | 172 ++++++++++++++++++++++++++++++++----------- 2 files changed, 263 insertions(+), 91 deletions(-) diff --git a/implot.cpp b/implot.cpp index 8e2bd70..61850c8 100644 --- a/implot.cpp +++ b/implot.cpp @@ -517,11 +517,11 @@ void LabelTickScientific(ImPlotTick& tick, ImGuiTextBuffer& buffer) { } } -void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeUnit unit) { - char temp[16]; +void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeFmt fmt) { + char temp[32]; if (tick.ShowLabel) { tick.BufferOffset = buffer.size(); - FormatTime(tick.PlotPos, temp, 16, unit); + FormatTime(tick.PlotPos, temp, 32, fmt); buffer.append(temp, temp + strlen(temp) + 1); tick.LabelSize = ImGui::CalcTextSize(buffer.Buf.Data + tick.BufferOffset); } @@ -575,17 +575,82 @@ void AddTicksLogarithmic(const ImPlotRange& range, int nMajor, ImPlotTickCollect } } -void AddTicksTime(const ImPlotRange& range, ImPlotTickCollection& ticks) { - ImPlotTimeUnit unit_range = GetUnitForRange(range.Min, range.Max); - ImPlotTimeUnit unit_ticks = unit_range == 0 ? ImPlotTimeUnit_Us : unit_range - 1; - printf("%d\n",(int)unit_ticks); - double t = FloorTime(range.Min, unit_range); - while (t < range.Max) { - t = AddTime(t, unit_ticks, 1); - ImPlotTick tick(t,false,true); - LabelTickTime(tick,ticks.Labels,unit_ticks); - ticks.AddTick(tick); +// splits +// mo: 6 3 2 +// day: +// hr: 12, 6, 3, 2, 1 +// min: 30: 15, 10, 5, 1 + +void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollection& ticks) { + const ImPlotTimeUnit unit0 = GetUnitForRange(range.Size() / (plot_width / 100)); // level = 0 (top) + const ImPlotTimeUnit unit1 = unit0 + 1; // level = 1 (bottom) + + // maximum allowable density of labels + const float max_density = 0.5f; + // pixels per major (level 1) division + const float pix_per_major_div = plot_width / (float)(range.Size() / TimeUnitSpans[unit1]); + // nominal pixels taken up by minor (level 0) label + const float minor_label_width = GetTimeLabelWidth(TimeFormatLevel0[unit0]); + // the maximum number of minor (level 0) labels that can fit between major (level 1) divisions + const int minor_per_major = (int)(max_density * pix_per_major_div / minor_label_width); + + if (unit1 != ImPlotTimeUnit_COUNT) { + double t = FloorTime(range.Min, unit1); + while (t < range.Max) { + if (range.Contains(t)) { + ImPlotTick tick_maj(t,true,true); + tick_maj.Level = 1; + LabelTickTime(tick_maj,ticks.Labels,TimeFormatLevel1[unit1]); + ticks.AddTick(tick_maj); + ImPlotTick tick_min(t,true,true); + tick_min.Level = 0; + LabelTickTime(tick_min,ticks.Labels,TimeFormatLevel0[unit0]); + ticks.AddTick(tick_min); + } + // add minor ticks up until next major + if (minor_per_major > 1) { + double t2 = AddTime(t, unit1, 1); + double inc = (t2 - t) / minor_per_major; + for (int i = 1; i < minor_per_major; ++i) { + double t3 = t + i * inc; + if (range.Contains(t3)) { + ImPlotTick tick(t3,false,true); + tick.Level = 0; + LabelTickTime(tick,ticks.Labels,TimeFormatLevel0[unit0]); + ticks.AddTick(tick); + } + } + + if (unit0 == ImPlotTimeUnit_Us) { + + } + else if (unit0 == ImPlotTimeUnit_Ms) { + + } + else if (unit0 == ImPlotTimeUnit_S) { + + } + else if (unit0 == ImPlotTimeUnit_Min) { + + } + else if (unit0 == ImPlotTimeUnit_Hr) { + + } + else if (unit0 == ImPlotTimeUnit_Day) { + + } + else if (unit0 == ImPlotTimeUnit_Mo) { + + } + } + t = AddTime(t, unit1, 1); + } } + else { + + } + + // printf("%d\n",minor_per_major); } void AddTicksCustom(const double* values, const char** labels, int n, ImPlotTickCollection& ticks) { @@ -614,6 +679,12 @@ void ConstrainAxis(ImPlotAxis& axis) { 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; } @@ -743,7 +814,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons UpdateAxisColors(ImPlotCol_YAxis2, &gp.Col_Y[1]); UpdateAxisColors(ImPlotCol_YAxis3, &gp.Col_Y[2]); - // BB AND HOVER ----------------------------------------------------------- + // BB, PADDING, HOVER ----------------------------------------------------------- // frame ImVec2 frame_size = ImGui::CalcItemSize(size, IMPLOT_DEFAULT_W, IMPLOT_DEFAULT_H); @@ -767,48 +838,57 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickMarks) || ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickLabels)); for (int i = 0; i < IMPLOT_Y_AXES; i++) { - gp.RenderY[i] = - gp.Y[i].Present && - (ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_GridLines) || - ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickMarks) || - ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickLabels)); - } - // get ticks - if (gp.RenderX && gp.NextPlotData.ShowDefaultTicksX) { - if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) - AddTicksLogarithmic(plot.XAxis.Range, (int)(gp.BB_Canvas.GetWidth() * 0.01f), gp.XTicks); - else if (gp.X.IsTime) - AddTicksTime(plot.XAxis.Range, gp.XTicks); - else - AddTicksDefault(plot.XAxis.Range, ImMax(2, (int)IM_ROUND(0.003 * gp.BB_Canvas.GetWidth())), IMPLOT_SUB_DIV, gp.XTicks); - } - for (int i = 0; i < IMPLOT_Y_AXES; i++) { - if (gp.RenderY[i] && gp.NextPlotData.ShowDefaultTicksY[i]) { - if (ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LogScale)) - AddTicksLogarithmic(plot.YAxis[i].Range, (int)(gp.BB_Canvas.GetHeight() * 0.02f) ,gp.YTicks[i]); - else - AddTicksDefault(plot.YAxis[i].Range, ImMax(2, (int)IM_ROUND(0.003 * gp.BB_Canvas.GetHeight())), IMPLOT_SUB_DIV, gp.YTicks[i]); - } + gp.RenderY[i] = gp.Y[i].Present && + (ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_GridLines) || + ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickMarks) || + ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickLabels)); } // plot bb - + + // (1) calc top/bot padding and plot height const ImVec2 title_size = ImGui::CalcTextSize(title, NULL, true); const float txt_height = ImGui::GetTextLineHeight(); - const float pad_top = title_size.x > 0.0f ? txt_height + gp.Style.LabelPadding.y : 0; - const float pad_bot = (gp.X.HasLabels ? txt_height + gp.Style.LabelPadding.y : 0) - + (x_label ? txt_height + gp.Style.LabelPadding.y : 0) - + (gp.X.IsTime ? txt_height + gp.Style.LabelPadding.y : 0); + const float pad_top = title_size.x > 0.0f ? txt_height + gp.Style.LabelPadding.y : 0; + const float pad_bot = (gp.X.HasLabels ? txt_height + gp.Style.LabelPadding.y : 0) + + (x_label ? txt_height + gp.Style.LabelPadding.y : 0) + + (gp.X.IsTime ? txt_height + gp.Style.LabelPadding.y : 0); + + const float plot_height = gp.BB_Canvas.GetHeight() - pad_top - pad_bot; + + // (2) get y tick labels (needed for left/right pad) + for (int i = 0; i < IMPLOT_Y_AXES; i++) { + if (gp.RenderY[i] && gp.NextPlotData.ShowDefaultTicksY[i]) { + if (ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LogScale)) + AddTicksLogarithmic(plot.YAxis[i].Range, ImMax(2, (int)IM_ROUND(plot_height * 0.02f)) ,gp.YTicks[i]); + else + AddTicksDefault(plot.YAxis[i].Range, ImMax(2, (int)IM_ROUND(0.0025 * plot_height)), IMPLOT_SUB_DIV, gp.YTicks[i]); + } + } + + // (3) calc left/right pad const float pad_left = (y_label ? txt_height + gp.Style.LabelPadding.x : 0) + (gp.Y[0].HasLabels ? gp.YTicks[0].MaxWidth + gp.Style.LabelPadding.x : 0); const float pad_right = ((gp.Y[1].Present && gp.Y[1].HasLabels) ? gp.YTicks[1].MaxWidth + gp.Style.LabelPadding.x : 0) + ((gp.Y[1].Present && gp.Y[2].Present) ? gp.Style.LabelPadding.x + gp.Style.MinorTickLen.y : 0) + ((gp.Y[2].Present && gp.Y[2].HasLabels) ? gp.YTicks[2].MaxWidth + gp.Style.LabelPadding.x : 0); + const float plot_width = gp.BB_Canvas.GetWidth() - pad_left - pad_right; - gp.BB_Plot = ImRect(gp.BB_Canvas.Min + ImVec2(pad_left, pad_top), gp.BB_Canvas.Max - ImVec2(pad_right, pad_bot)); - gp.Hov_Plot = gp.BB_Plot.Contains(IO.MousePos); + // (4) get x ticks + if (gp.RenderX && gp.NextPlotData.ShowDefaultTicksX) { + if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) + AddTicksLogarithmic(plot.XAxis.Range, (int)IM_ROUND(plot_width * 0.01f), gp.XTicks); + else if (gp.X.IsTime) + AddTicksTime(plot.XAxis.Range, plot_width, gp.XTicks); + else + AddTicksDefault(plot.XAxis.Range, ImMax(2, (int)IM_ROUND(0.0025 * plot_width)), IMPLOT_SUB_DIV, gp.XTicks); + } + + // (5) calc plot bb + gp.BB_Plot = ImRect(gp.BB_Canvas.Min + ImVec2(pad_left, pad_top), gp.BB_Canvas.Max - ImVec2(pad_right, pad_bot)); + gp.Hov_Plot = gp.BB_Plot.Contains(IO.MousePos); // x axis region bb and hover const ImRect xAxisRegion_bb(gp.BB_Plot.GetBL(), ImVec2(gp.BB_Plot.Max.x, gp.BB_Frame.Max.y)); @@ -1132,10 +1212,12 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons ImU32 col_min32 = ImGui::ColorConvertFloat4ToU32(col_min); for (int t = 0; t < gp.XTicks.Size; t++) { ImPlotTick& xt = gp.XTicks.Ticks[t]; - if (xt.Major) - DrawList.AddLine(ImVec2(xt.PixelPos, gp.BB_Plot.Min.y), ImVec2(xt.PixelPos, gp.BB_Plot.Max.y), gp.Col_X.Major, gp.Style.MajorGridSize.x); - else if (density < 0.2f) - DrawList.AddLine(ImVec2(xt.PixelPos, gp.BB_Plot.Min.y), ImVec2(xt.PixelPos, gp.BB_Plot.Max.y), col_min32, gp.Style.MinorGridSize.x); + if (xt.Level == 0) { + if (xt.Major) + DrawList.AddLine(ImVec2(xt.PixelPos, gp.BB_Plot.Min.y), ImVec2(xt.PixelPos, gp.BB_Plot.Max.y), gp.Col_X.Major, gp.Style.MajorGridSize.x); + else if (density < 0.2f) + DrawList.AddLine(ImVec2(xt.PixelPos, gp.BB_Plot.Min.y), ImVec2(xt.PixelPos, gp.BB_Plot.Max.y), col_min32, gp.Style.MinorGridSize.x); + } } } @@ -1182,7 +1264,8 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons for (int t = 0; t < gp.XTicks.Size; t++) { ImPlotTick *xt = &gp.XTicks.Ticks[t]; if (xt->ShowLabel && xt->PixelPos >= gp.BB_Plot.Min.x - 1 && xt->PixelPos <= gp.BB_Plot.Max.x + 1) - DrawList.AddText(ImVec2(xt->PixelPos - xt->LabelSize.x * 0.5f, gp.BB_Plot.Max.y + gp.Style.LabelPadding.y), xt->Major ? gp.Col_X.MajTxt : gp.Col_X.MinTxt, gp.XTicks.GetLabel(t)); + DrawList.AddText(ImVec2(xt->PixelPos - xt->LabelSize.x * 0.5f, gp.BB_Plot.Max.y + gp.Style.LabelPadding.y + xt->Level * (txt_height + gp.Style.LabelPadding.y)), + xt->Major ? gp.Col_X.MajTxt : gp.Col_X.MinTxt, gp.XTicks.GetLabel(t)); } } for (int i = 0; i < IMPLOT_Y_AXES; i++) { @@ -1241,6 +1324,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 grid = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines); bool ticks = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_TickMarks); bool labels = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_TickLabels); @@ -1272,6 +1356,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); ImGui::Separator(); if (ImGui::Checkbox("Grid Lines", &grid)) ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines); diff --git a/implot_internal.h b/implot_internal.h index e6cdda5..6bd32da 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -77,6 +77,8 @@ extern ImPlotContext* GImPlot; // Current implicit context pointer #define IMPLOT_SUB_DIV 10 // 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_MAX_TIME 32503680000 //----------------------------------------------------------------------------- // [SECTION] Generic Helpers @@ -150,7 +152,7 @@ struct ImPlotPointArray { typedef int ImPlotScale; // -> enum ImPlotScale_ typedef int ImPlotTimeUnit; // -> enum ImPlotTimeUnit_ -typedef int ImPlotTimeUnit; // -> enum ImPlotTimeUnit_ +typedef int ImPlotTimeFmt; // -> enum ImPlotTimeFmt_ // XY axes scaling combinations enum ImPlotScale_ { @@ -160,18 +162,6 @@ enum ImPlotScale_ { ImPlotScale_LogLog // log x, log y }; -enum ImPlotTimeUnit_ { - ImPlotTimeUnit_Us, // microsecond (:29.428 552) - ImPlotTimeUnit_Ms, // millisecond (:29.428) - ImPlotTimeUnit_S, // second (:29) - ImPlotTimeUnit_Min, // minute (7:21pm) - ImPlotTimeUnit_Hr, // hour (7pm) - ImPlotTimeUnit_Day, // day (10/3) - ImPlotTimeUnit_Mo, // month (Oct) - ImPlotTimeUnit_Yr, // year (1991) - ImPlotTimeUnit_COUNT -}; - //----------------------------------------------------------------------------- // [SECTION] ImPlot Structs //----------------------------------------------------------------------------- @@ -205,12 +195,14 @@ struct ImPlotTick int BufferOffset; bool Major; bool ShowLabel; + int Level; ImPlotTick(double value, bool major, bool show_label) { PlotPos = value; Major = major; ShowLabel = show_label; BufferOffset = -1; + Level = 0; } }; @@ -585,7 +577,7 @@ void LabelTickDefault(ImPlotTick& tick, ImGuiTextBuffer& buffer); // Label a tick with scientific formating. void LabelTickScientific(ImPlotTick& tick, ImGuiTextBuffer& buffer); // Label a tick with time formatting. -void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeUnit fmt); +void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeFmt fmt); // Populates a list of ImPlotTicks with normal spaced and formatted ticks void AddTicksDefault(const ImPlotRange& range, int nMajor, int nMinor, ImPlotTickCollection& ticks); @@ -642,6 +634,8 @@ inline double ConstrainNan(double val) { return isnan(val) ? 0 : val; } 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. @@ -677,18 +671,67 @@ inline T OffsetAndStride(const T* data, int idx, int count, int offset, int stri // [SECTION] Time Utils //----------------------------------------------------------------------------- -static const int DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; -static const double ImPlotTimeUnitSpans[ImPlotTimeUnit_COUNT] = {0.000001, 0.001, 1, 60, 3600, 86400, 2629800, 31557600}; +enum ImPlotTimeUnit_ { // primary + ImPlotTimeUnit_Us, // microsecond :29.428552 + ImPlotTimeUnit_Ms, // millisecond :29.428 + ImPlotTimeUnit_S, // second :29 + ImPlotTimeUnit_Min, // minute 7:21pm + ImPlotTimeUnit_Hr, // hour 7pm + ImPlotTimeUnit_Day, // day 10/3 + ImPlotTimeUnit_Mo, // month Oct + ImPlotTimeUnit_Yr, // year 1991 + ImPlotTimeUnit_COUNT +}; -inline ImPlotTimeUnit GetUnitForRange(double smin, double smax) { - double range = smax - smin; +enum ImPlotTimeFmt_ { + ImPlotTimeFmt_SUs, // :29.428552 + ImPlotTimeFmt_SMs, // :29.428 + ImPlotTimeFmt_S, // :29 + ImPlotTimeFmt_HrMin, // 7:21pm + ImPlotTimeFmt_Hr, // 7pm + ImPlotTimeFmt_DayMo, // 10/3 + ImPlotTimeFmt_DayMoHrMin, // 10/3 7:21pm + ImPlotTimeFmt_DayMoYrHrMin, // 10/3/1991 7:21pm + ImPlotTimeFmt_Mo, // Oct + ImPlotTimeFmt_Yr // 1991 +}; + +static const int DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +static const double TimeUnitSpans[ImPlotTimeUnit_COUNT] = {0.000001, 0.001, 1, 60, 3600, 86400, 2629800, 31557600}; +static const ImPlotTimeFmt TimeFormatLevel0[ImPlotTimeUnit_COUNT] = +{ + ImPlotTimeFmt_SUs, + ImPlotTimeFmt_SMs, + ImPlotTimeFmt_S, + ImPlotTimeFmt_HrMin, + ImPlotTimeFmt_Hr, + ImPlotTimeFmt_DayMo, + ImPlotTimeFmt_Mo, + ImPlotTimeFmt_Yr +}; +static const ImPlotTimeFmt TimeFormatLevel1[ImPlotTimeUnit_COUNT] = +{ + ImPlotTimeFmt_DayMoHrMin, + ImPlotTimeFmt_DayMoHrMin, + ImPlotTimeFmt_DayMoHrMin, + ImPlotTimeFmt_DayMoHrMin, + ImPlotTimeFmt_DayMo, + ImPlotTimeFmt_DayMo, + ImPlotTimeFmt_Yr, + ImPlotTimeFmt_Yr +}; + + +inline ImPlotTimeUnit GetUnitForRange(double range) { + static double cutoffs[ImPlotTimeUnit_COUNT] = {0.001, 1, 60, 3600, 86400, 2629800, 31557600, IMPLOT_MAX_TIME}; for (int i = 0; i < ImPlotTimeUnit_COUNT; ++i) { - if (range <= ImPlotTimeUnitSpans[i]) + if (range <= cutoffs[i]) return (ImPlotTimeUnit)i; } return ImPlotTimeUnit_Yr; } + // Returns true if year is leap year (366 days long) inline bool IsLeapYear(int year) { if (year % 4 != 0) return false; @@ -741,13 +784,13 @@ inline double AddTime(double t, ImPlotTimeUnit unit, int count) { case ImPlotTimeUnit_Day: return t + count * 86400; case ImPlotTimeUnit_Yr: count *= 12; // fall-through case ImPlotTimeUnit_Mo: for (int i = 0; i < count; ++i) { - time_t s = (time_t)t; - GmTime(&s, &GImPlot->Tm); - int days = GetDaysInMonth(GImPlot->Tm.tm_year, GImPlot->Tm.tm_mon); - t = AddTime(t, ImPlotTimeUnit_Day, days); - } - return t; - default: return t; + time_t s = (time_t)t; + GmTime(&s, &GImPlot->Tm); + int days = GetDaysInMonth(GImPlot->Tm.tm_year, GImPlot->Tm.tm_mon); + t = AddTime(t, ImPlotTimeUnit_Day, days); + } + return t; + default: return t; } } @@ -774,43 +817,86 @@ inline double CeilTime(double t, ImPlotTimeUnit unit) { return AddTime(FloorTime(t, unit), unit, 1); } -inline void FormatTime(double t, char* buffer, int size, ImPlotTimeUnit unit) { +inline void FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) { time_t s = (time_t)t; int ms = (int)(t * 1000 - floor(t) * 1000); int us = (int)(t * 1000000 - floor(t) * 1000000); tm& Tm = GImPlot->Tm; - GmTime(&s, &Tm); - switch(unit) { - case ImPlotTimeUnit_Yr: strftime(buffer, size, "%Y", &Tm); break; - case ImPlotTimeUnit_Mo: strftime(buffer, size, "%b", &Tm); break; - case ImPlotTimeUnit_Day: strftime(buffer, size, "%m/%d", &Tm); break; - case ImPlotTimeUnit_Hr: + IM_ASSERT(GmTime(&s, &Tm) != NULL); + switch(fmt) { + case ImPlotTimeFmt_Yr: strftime(buffer, size, "%Y", &Tm); break; + case ImPlotTimeFmt_Mo: strftime(buffer, size, "%b", &Tm); break; + case ImPlotTimeFmt_DayMo: snprintf(buffer, size, "%d/%d", Tm.tm_mon + 1, Tm.tm_mday); break; + case ImPlotTimeFmt_DayMoHrMin: + if (Tm.tm_hour == 0) + snprintf(buffer, size, "%d/%d 12:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_min); + else if (Tm.tm_hour == 12) + snprintf(buffer, size, "%d/%d 12%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_min); + else if (Tm.tm_hour < 12) + snprintf(buffer, size, "%d/%d %u:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_hour, Tm.tm_min); + else if (Tm.tm_hour > 12) + snprintf(buffer, size, "%d/%d %u:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_hour - 12, Tm.tm_min); + break; + case ImPlotTimeFmt_DayMoYrHrMin: + if (Tm.tm_hour == 0) + snprintf(buffer, size, "%d/%d/%d 12:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_min); + else if (Tm.tm_hour == 12) + snprintf(buffer, size, "%d/%d/%d 12:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_min); + else if (Tm.tm_hour < 12) + snprintf(buffer, size, "%d/%d/%d %u:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_hour, Tm.tm_min); + else if (Tm.tm_hour > 12) + snprintf(buffer, size, "%d/%d/%d %u:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_hour - 12, Tm.tm_min); + break; + case ImPlotTimeFmt_Hr: if (Tm.tm_hour == 0) snprintf(buffer, size, "12am"); else if (Tm.tm_hour == 12) snprintf(buffer, size, "12pm"); else if (Tm.tm_hour < 12) snprintf(buffer, size, "%uam", Tm.tm_hour); - else if (Tm.tm_hour > 12) - snprintf(buffer, size, "%upm", Tm.tm_hour - 12); + else if (Tm.tm_hour > 12) + snprintf(buffer, size, "%upm", Tm.tm_hour - 12); break; - case ImPlotTimeUnit_Min: + case ImPlotTimeFmt_HrMin: if (Tm.tm_hour == 0) snprintf(buffer, size, "12:%02dam", Tm.tm_min); else if (Tm.tm_hour == 12) - snprintf(buffer, size, "12%02dpm", Tm.tm_min); + snprintf(buffer, size, "12:%02dpm", Tm.tm_min); else if (Tm.tm_hour < 12) - snprintf(buffer, size, "%u:%02dam", Tm.tm_hour, Tm.tm_min); - else if (Tm.tm_hour > 12) - snprintf(buffer, size, "%u:%02dpm", Tm.tm_hour - 12, Tm.tm_min); + snprintf(buffer, size, "%d:%02dam", Tm.tm_hour, Tm.tm_min); + else if (Tm.tm_hour > 12) + snprintf(buffer, size, "%d:%02dpm", Tm.tm_hour - 12, Tm.tm_min); break; - case ImPlotTimeUnit_S: snprintf(buffer, size, ":%02d", Tm.tm_sec); break; - case ImPlotTimeUnit_Ms: snprintf(buffer, size, ":%02d.%03d", Tm.tm_sec, ms); break; - case ImPlotTimeUnit_Us: snprintf(buffer, size, ":%02d.%06d", Tm.tm_sec, us); break; + case ImPlotTimeFmt_S: snprintf(buffer, size, ":%02d", Tm.tm_sec); break; + case ImPlotTimeFmt_SMs: snprintf(buffer, size, ":%02d.%03d", Tm.tm_sec, ms); break; + case ImPlotTimeFmt_SUs: snprintf(buffer, size, ":%02d.%06d", Tm.tm_sec, us); break; default: break; } } +inline void PrintTime(double t, ImPlotTimeFmt fmt) { + char buff[32]; + FormatTime(t, buff, 32, fmt); + printf("%s\n",buff); +} + +// Returns the nominally largest possible width for a time format +inline float GetTimeLabelWidth(ImPlotTimeFmt fmt) { + switch (fmt) { + case ImPlotTimeFmt_SUs: return ImGui::CalcTextSize(":88.888888").x; // :29.428552 + case ImPlotTimeFmt_SMs: return ImGui::CalcTextSize(":88.888").x; // :29.428 + case ImPlotTimeFmt_S: return ImGui::CalcTextSize(":88").x; // :29 + case ImPlotTimeFmt_HrMin: return ImGui::CalcTextSize("88:88pm").x; // 7:21pm + case ImPlotTimeFmt_Hr: return ImGui::CalcTextSize("8pm").x; // 7pm + case ImPlotTimeFmt_DayMo: return ImGui::CalcTextSize("88/88").x; // 10/3 + case ImPlotTimeFmt_DayMoHrMin: return ImGui::CalcTextSize("88/88 88:88pm").x; // 10/3 7:21pm + case ImPlotTimeFmt_DayMoYrHrMin: return ImGui::CalcTextSize("88/88/8888 88:88pm").x; // 10/3/1991 7:21pm + case ImPlotTimeFmt_Mo: return ImGui::CalcTextSize("MMM").x; // Oct + case ImPlotTimeFmt_Yr: return ImGui::CalcTextSize("8888").x; // 1991 + default: return 0; + } +} + //----------------------------------------------------------------------------- // [SECTION] Internal / Experimental Plotters // No guarantee of forward compatibility here! From 8d7444076537596baf17a90d116d555d9e5f042f Mon Sep 17 00:00:00 2001 From: epezent Date: Fri, 4 Sep 2020 13:13:45 -0500 Subject: [PATCH 04/16] time axes nearly finished except for a few bugs and oddities --- implot.cpp | 135 +++++++++++++++++++++++++++++----------------- implot_demo.cpp | 19 ++++++- implot_internal.h | 36 ++++++++++--- 3 files changed, 133 insertions(+), 57 deletions(-) diff --git a/implot.cpp b/implot.cpp index 61850c8..91fb296 100644 --- a/implot.cpp +++ b/implot.cpp @@ -575,82 +575,121 @@ void AddTicksLogarithmic(const ImPlotRange& range, int nMajor, ImPlotTickCollect } } -// splits -// mo: 6 3 2 -// day: -// hr: 12, 6, 3, 2, 1 -// min: 30: 15, 10, 5, 1 +inline int LowerBoundStep(int max_divs, const int* divs, const int* step, int size) { + if (max_divs < divs[0]) + return 0; + for (int i = 1; i < size; ++i) { + if (max_divs < divs[i]) + return step[i-1]; + } + return step[size-1]; +} + +inline int GetTimeStep(int max_divs, ImPlotTimeUnit unit) { + if (unit == ImPlotTimeUnit_Ms || unit == ImPlotTimeUnit_Us) { + static const int step[] = {500,250,200,100,50,25,20,10,5}; + static const int divs[] = {2,4,5,10,20,40,50,100,200}; + return LowerBoundStep(max_divs, divs, step, 9); + } + if (unit == ImPlotTimeUnit_S || unit == ImPlotTimeUnit_Min) { + static const int step[] = {30,15,10,5,1}; + static const int divs[] = {2,4,6,12,60}; + return LowerBoundStep(max_divs, divs, step, 5); + } + else if (unit == ImPlotTimeUnit_Hr) { + static const int step[] = {12,6,3,2,1}; + static const int divs[] = {2,4,8,12,24}; + return LowerBoundStep(max_divs, divs, step, 5); + } + else if (unit == ImPlotTimeUnit_Day) { + static const int step[] = {14,7,2,1}; + static const int divs[] = {2,4,14,28}; + return LowerBoundStep(max_divs, divs, step, 4); + } + else if (unit == ImPlotTimeUnit_Mo) { + static const int step[] = {6,3,2,1}; + static const int divs[] = {2,4,6,12}; + return LowerBoundStep(max_divs, divs, step, 4); + } + return 0; +} + + void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollection& ticks) { + // get units for level 0 and level 1 labels const ImPlotTimeUnit unit0 = GetUnitForRange(range.Size() / (plot_width / 100)); // level = 0 (top) const ImPlotTimeUnit unit1 = unit0 + 1; // level = 1 (bottom) // maximum allowable density of labels const float max_density = 0.5f; - // pixels per major (level 1) division - const float pix_per_major_div = plot_width / (float)(range.Size() / TimeUnitSpans[unit1]); - // nominal pixels taken up by minor (level 0) label - const float minor_label_width = GetTimeLabelWidth(TimeFormatLevel0[unit0]); - // the maximum number of minor (level 0) labels that can fit between major (level 1) divisions - const int minor_per_major = (int)(max_density * pix_per_major_div / minor_label_width); - if (unit1 != ImPlotTimeUnit_COUNT) { - double t = FloorTime(range.Min, unit1); - while (t < range.Max) { - if (range.Contains(t)) { - ImPlotTick tick_maj(t,true,true); + if (unit0 != ImPlotTimeUnit_Yr) { + // pixels per major (level 1) division + const float pix_per_major_div = plot_width / (float)(range.Size() / TimeUnitSpans[unit1]); + // nominal pixels taken up by minor (level 0) label + const float minor_label_width = GetTimeLabelWidth(TimeFormatLevel0[unit0]); + // the maximum number of minor (level 0) labels that can fit between major (level 1) divisions + const int minor_per_major = (int)(max_density * pix_per_major_div / minor_label_width); + // the minor step size (level 0) + const int step = GetTimeStep(minor_per_major, unit0); + // generate ticks + double t1 = FloorTime(range.Min, unit1); + while (t1 < range.Max) { + if (range.Contains(t1)) { + ImPlotTick tick_maj(t1,true,true); tick_maj.Level = 1; LabelTickTime(tick_maj,ticks.Labels,TimeFormatLevel1[unit1]); ticks.AddTick(tick_maj); - ImPlotTick tick_min(t,true,true); + ImPlotTick tick_min(t1,true,true); tick_min.Level = 0; LabelTickTime(tick_min,ticks.Labels,TimeFormatLevel0[unit0]); ticks.AddTick(tick_min); } // add minor ticks up until next major if (minor_per_major > 1) { - double t2 = AddTime(t, unit1, 1); - double inc = (t2 - t) / minor_per_major; - for (int i = 1; i < minor_per_major; ++i) { - double t3 = t + i * inc; - if (range.Contains(t3)) { - ImPlotTick tick(t3,false,true); + double t2 = AddTime(t1, unit1, 1); + double t12 = AddTime(t1, unit0, step); + while (t12 < t2) { + float px_to_t2 = (float)((t2 - t12)/range.Size()) * plot_width; + if (range.Contains(t12)) { + ImPlotTick tick(t12,false,px_to_t2 >= minor_label_width); tick.Level = 0; LabelTickTime(tick,ticks.Labels,TimeFormatLevel0[unit0]); ticks.AddTick(tick); } - } - - if (unit0 == ImPlotTimeUnit_Us) { - - } - else if (unit0 == ImPlotTimeUnit_Ms) { - - } - else if (unit0 == ImPlotTimeUnit_S) { - - } - else if (unit0 == ImPlotTimeUnit_Min) { - - } - else if (unit0 == ImPlotTimeUnit_Hr) { - - } - else if (unit0 == ImPlotTimeUnit_Day) { - - } - else if (unit0 == ImPlotTimeUnit_Mo) { - - } + t12 = AddTime(t12, unit0, step); + } } - t = AddTime(t, unit1, 1); + t1 = AddTime(t1, unit1, 1); } } else { + // nominal pixels taken up by year label + const float label_width = GetTimeLabelWidth(TimeFormatLevel0[ImPlotTimeUnit_Yr]); + // maximum number of labels we can display + const int max_labels = (int)(max_density * plot_width / label_width); + + const int year_min = GetYear(range.Min); + const int year_max = GetYear(CeilTime(range.Max, ImPlotTimeUnit_Yr)); + const double nice_range = NiceNum((year_max - year_min)*0.99,false); + const double interval = NiceNum(nice_range / (max_labels - 1), true); + const int graphmin = (int)(floor(year_min / interval) * interval); + + double t1 = MakeYear(graphmin); + while (t1 < range.Max) { + if (range.Contains(t1)) { + ImPlotTick tick(t1, true, true); + tick.Level = 0; + LabelTickTime(tick, ticks.Labels, TimeFormatLevel0[ImPlotTimeUnit_Yr]); + ticks.AddTick(tick); + } + t1 = AddTime(t1, ImPlotTimeUnit_Yr, (int)interval); + } } - // printf("%d\n",minor_per_major); + // printf("%d , %d\n",minor_per_major,step); } void AddTicksCustom(const double* values, const char** labels, int n, ImPlotTickCollection& ticks) { diff --git a/implot_demo.cpp b/implot_demo.cpp index 2ed3a81..4eff8b8 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -592,8 +592,23 @@ void ShowDemoWindow(bool* p_open) { } } if (ImGui::CollapsingHeader("Time Formatting")) { - ImPlot::SetNextPlotLimits(1599106881,1599106881+1000000,0,1); - if (ImPlot::BeginPlot("UTC Time", "Date-Time", "Y-Axis", ImVec2(-1,0), ImPlotFlags_Default, ImPlotAxisFlags_Default | ImPlotAxisFlags_Time)) { + static double min = 1599242863*0.5; + static double max = 1599242863*1.5; + static bool zooming = false; + ImGuiCond cond = ImGuiCond_Once; + if (ImGui::Button("Zoom")) { + zooming = true; + } + if (zooming) { + cond = ImGuiCond_Always; + double range = max - min; + min += range * 0.005; + max -= range * 0.005; + if (range < 0.005) + zooming = false; + } + ImPlot::SetNextPlotLimits(min,max,0,1,cond); + if (ImPlot::BeginPlot("##Time", "UTC Time", "Y-Axis", ImVec2(-1,0), ImPlotFlags_Default, ImPlotAxisFlags_Default | ImPlotAxisFlags_Time)) { ImPlot::EndPlot(); } diff --git a/implot_internal.h b/implot_internal.h index 6bd32da..cd6d80a 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -745,7 +745,7 @@ inline int GetDaysInMonth(int year, int month) { return DaysInMonth[month] + (int)(month == 1 && IsLeapYear(year)); } -inline time_t MkGmTime(const struct tm *ptm) { +inline time_t MakeGmTime(const struct tm *ptm) { time_t secs = 0; int year = ptm->tm_year + 1900; for (int y = 1970; y < year; ++y) { @@ -762,7 +762,7 @@ inline time_t MkGmTime(const struct tm *ptm) { return secs; } -inline tm* GmTime(const time_t* time, tm* tm) +inline tm* GetGmTime(const time_t* time, tm* tm) { #ifdef _MSC_VER if (gmtime_s(tm, time) == 0) @@ -785,7 +785,7 @@ inline double AddTime(double t, ImPlotTimeUnit unit, int count) { case ImPlotTimeUnit_Yr: count *= 12; // fall-through case ImPlotTimeUnit_Mo: for (int i = 0; i < count; ++i) { time_t s = (time_t)t; - GmTime(&s, &GImPlot->Tm); + GetGmTime(&s, &GImPlot->Tm); int days = GetDaysInMonth(GImPlot->Tm.tm_year, GImPlot->Tm.tm_mon); t = AddTime(t, ImPlotTimeUnit_Day, days); } @@ -794,9 +794,29 @@ inline double AddTime(double t, ImPlotTimeUnit unit, int count) { } } +inline int GetYear(double t) { + time_t s = (time_t)t; + tm& Tm = GImPlot->Tm; + IM_ASSERT(GetGmTime(&s, &Tm) != NULL); + return Tm.tm_year + 1900; +} + +inline double MakeYear(int year) { + int yr = year - 1900; + if (yr < 0) + yr = 0; + GImPlot->Tm = tm(); + GImPlot->Tm.tm_year = yr; + GImPlot->Tm.tm_sec = 1; + time_t s = MakeGmTime(&GImPlot->Tm); + if (s < 0) + s = 0; + return (double)s; +} + inline double FloorTime(double t, ImPlotTimeUnit unit) { time_t s = (time_t)t; - GmTime(&s, &GImPlot->Tm); + GetGmTime(&s, &GImPlot->Tm); GImPlot->Tm.tm_isdst = -1; switch (unit) { case ImPlotTimeUnit_S: return (double)s; @@ -809,7 +829,7 @@ inline double FloorTime(double t, ImPlotTimeUnit unit) { case ImPlotTimeUnit_Min: GImPlot->Tm.tm_sec = 0; break; default: return t; } - s = MkGmTime(&GImPlot->Tm); + s = MakeGmTime(&GImPlot->Tm); return (double)s; } @@ -820,9 +840,11 @@ inline double CeilTime(double t, ImPlotTimeUnit unit) { inline void FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) { time_t s = (time_t)t; int ms = (int)(t * 1000 - floor(t) * 1000); + ms = ms % 10 == 9 ? ms + 1 : ms; int us = (int)(t * 1000000 - floor(t) * 1000000); + us = us % 10 == 9 ? us + 1 : us; tm& Tm = GImPlot->Tm; - IM_ASSERT(GmTime(&s, &Tm) != NULL); + IM_ASSERT(GetGmTime(&s, &Tm) != NULL); switch(fmt) { case ImPlotTimeFmt_Yr: strftime(buffer, size, "%Y", &Tm); break; case ImPlotTimeFmt_Mo: strftime(buffer, size, "%b", &Tm); break; @@ -831,7 +853,7 @@ inline void FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) { if (Tm.tm_hour == 0) snprintf(buffer, size, "%d/%d 12:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_min); else if (Tm.tm_hour == 12) - snprintf(buffer, size, "%d/%d 12%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_min); + snprintf(buffer, size, "%d/%d 12:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_min); else if (Tm.tm_hour < 12) snprintf(buffer, size, "%d/%d %u:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_hour, Tm.tm_min); else if (Tm.tm_hour > 12) From c70eee57019a0c2a02ad205026d9df44b00b1bfc Mon Sep 17 00:00:00 2001 From: epezent Date: Fri, 4 Sep 2020 13:28:55 -0500 Subject: [PATCH 05/16] make time override log --- implot.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/implot.cpp b/implot.cpp index 91fb296..123b17d 100644 --- a/implot.cpp +++ b/implot.cpp @@ -917,10 +917,10 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // (4) get x ticks if (gp.RenderX && gp.NextPlotData.ShowDefaultTicksX) { + if (gp.X.IsTime) + AddTicksTime(plot.XAxis.Range, plot_width, gp.XTicks); if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) AddTicksLogarithmic(plot.XAxis.Range, (int)IM_ROUND(plot_width * 0.01f), gp.XTicks); - else if (gp.X.IsTime) - AddTicksTime(plot.XAxis.Range, plot_width, gp.XTicks); else AddTicksDefault(plot.XAxis.Range, ImMax(2, (int)IM_ROUND(0.0025 * plot_width)), IMPLOT_SUB_DIV, gp.XTicks); } From fe552f25f0bf95d182177d3746ad2dc66d27ec26 Mon Sep 17 00:00:00 2001 From: epezent Date: Fri, 4 Sep 2020 13:36:56 -0500 Subject: [PATCH 06/16] make time override log --- implot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implot.cpp b/implot.cpp index 123b17d..bb46994 100644 --- a/implot.cpp +++ b/implot.cpp @@ -919,7 +919,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons if (gp.RenderX && gp.NextPlotData.ShowDefaultTicksX) { if (gp.X.IsTime) AddTicksTime(plot.XAxis.Range, plot_width, gp.XTicks); - if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) + else if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) AddTicksLogarithmic(plot.XAxis.Range, (int)IM_ROUND(plot_width * 0.01f), gp.XTicks); else AddTicksDefault(plot.XAxis.Range, ImMax(2, (int)IM_ROUND(0.0025 * plot_width)), IMPLOT_SUB_DIV, gp.XTicks); From e0450d00af0cad3ba73f973a6efb069bddd39da6 Mon Sep 17 00:00:00 2001 From: epezent Date: Fri, 4 Sep 2020 19:33:10 -0500 Subject: [PATCH 07/16] improve axis constraining --- implot.cpp | 91 ++++++++++++++++++++--------------------------- implot_internal.h | 75 +++++++++++++++++++++++++++++--------- implot_items.cpp | 6 ++-- 3 files changed, 101 insertions(+), 71 deletions(-) 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++; } From 61e6b5118eb5473d7f3732b7f254ead5e64674d6 Mon Sep 17 00:00:00 2001 From: epezent Date: Fri, 4 Sep 2020 23:30:45 -0500 Subject: [PATCH 08/16] tidy up time-axes so far --- implot.cpp | 308 +++++++++++++++++++++++++++++++++++++++------- implot_demo.cpp | 14 ++- implot_internal.h | 285 ++++++++---------------------------------- 3 files changed, 320 insertions(+), 287 deletions(-) diff --git a/implot.cpp b/implot.cpp index d07a3fc..065756d 100644 --- a/implot.cpp +++ b/implot.cpp @@ -517,16 +517,6 @@ void LabelTickScientific(ImPlotTick& tick, ImGuiTextBuffer& buffer) { } } -void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeFmt fmt) { - char temp[32]; - if (tick.ShowLabel) { - tick.BufferOffset = buffer.size(); - FormatTime(tick.PlotPos, temp, 32, fmt); - buffer.append(temp, temp + strlen(temp) + 1); - tick.LabelSize = ImGui::CalcTextSize(buffer.Buf.Data + tick.BufferOffset); - } -} - void AddTicksDefault(const ImPlotRange& range, int nMajor, int nMinor, ImPlotTickCollection& ticks) { const double nice_range = NiceNum(range.Size() * 0.99, false); const double interval = NiceNum(nice_range / (nMajor - 1), true); @@ -575,6 +565,68 @@ void AddTicksLogarithmic(const ImPlotRange& range, int nMajor, ImPlotTickCollect } } +void AddTicksCustom(const double* values, const char** labels, int n, ImPlotTickCollection& ticks) { + for (int i = 0; i < n; ++i) { + ImPlotTick tick(values[i], false, true); + if (labels != NULL) { + tick.BufferOffset = ticks.Labels.size(); + ticks.Labels.append(labels[i], labels[i] + strlen(labels[i]) + 1); + tick.LabelSize = ImGui::CalcTextSize(labels[i]); + } + else { + LabelTickDefault(tick, ticks.Labels); + } + ticks.AddTick(tick); + } +} + +//----------------------------------------------------------------------------- +// Time Ticks and Utils +//----------------------------------------------------------------------------- + +// this may not be thread safe? +static const double TimeUnitSpans[ImPlotTimeUnit_COUNT] = { + 0.000001, + 0.001, + 1, + 60, + 3600, + 86400, + 2629800, + 31557600 +}; + +static const ImPlotTimeFmt TimeFormatLevel0[ImPlotTimeUnit_COUNT] = { + ImPlotTimeFmt_SUs, + ImPlotTimeFmt_SMs, + ImPlotTimeFmt_S, + ImPlotTimeFmt_HrMin, + ImPlotTimeFmt_Hr, + ImPlotTimeFmt_DayMo, + ImPlotTimeFmt_Mo, + ImPlotTimeFmt_Yr +}; + +static const ImPlotTimeFmt TimeFormatLevel1[ImPlotTimeUnit_COUNT] = { + ImPlotTimeFmt_DayMoHrMin, + ImPlotTimeFmt_DayMoHrMin, + ImPlotTimeFmt_DayMoHrMin, + ImPlotTimeFmt_DayMoHrMin, + ImPlotTimeFmt_DayMo, + ImPlotTimeFmt_DayMo, + ImPlotTimeFmt_Yr, + ImPlotTimeFmt_Yr +}; + +inline ImPlotTimeUnit GetUnitForRange(double range) { + static double cutoffs[ImPlotTimeUnit_COUNT] = {0.001, 1, 60, 3600, 86400, 2629800, 31557600, IMPLOT_MAX_TIME}; + for (int i = 0; i < ImPlotTimeUnit_COUNT; ++i) { + if (range <= cutoffs[i]) + return (ImPlotTimeUnit)i; + } + return ImPlotTimeUnit_Yr; +} + inline int LowerBoundStep(int max_divs, const int* divs, const int* step, int size) { if (max_divs < divs[0]) return 0; @@ -614,16 +666,200 @@ inline int GetTimeStep(int max_divs, ImPlotTimeUnit unit) { return 0; } +inline time_t MakeGmTime(const struct tm *ptm) { + time_t secs = 0; + int year = ptm->tm_year + 1900; + for (int y = 1970; y < year; ++y) + secs += (IsLeapYear(y)? 366: 365) * 86400; + for (int m = 0; m < ptm->tm_mon; ++m) + secs += GetDaysInMonth(year, m) * 86400; + secs += (ptm->tm_mday - 1) * 86400; + secs += ptm->tm_hour * 3600; + secs += ptm->tm_min * 60; + secs += ptm->tm_sec; + return secs; +} +inline tm* GetGmTime(const time_t* time, tm* tm) +{ +#ifdef _WIN32 + if (gmtime_s(tm, time) == 0) + return tm; + else + return NULL; +#else + return gmtime_r(time, tm); +#endif +} + +double AddTime(double t, ImPlotTimeUnit unit, int count) { + switch(unit) { + case ImPlotTimeUnit_Us: return t + count * 0.000001; + case ImPlotTimeUnit_Ms: return t + count * 0.001; + case ImPlotTimeUnit_S: return t + count; + case ImPlotTimeUnit_Min: return t + count * 60; + case ImPlotTimeUnit_Hr: return t + count * 3600; + case ImPlotTimeUnit_Day: return t + count * 86400; + case ImPlotTimeUnit_Yr: count *= 12; // fall-through + case ImPlotTimeUnit_Mo: for (int i = 0; i < count; ++i) { + time_t s = (time_t)t; + GetGmTime(&s, &GImPlot->Tm); + int days = GetDaysInMonth(GImPlot->Tm.tm_year, GImPlot->Tm.tm_mon); + t = AddTime(t, ImPlotTimeUnit_Day, days); + } + return t; + default: return t; + } +} + +double FloorTime(double t, ImPlotTimeUnit unit) { + time_t s = (time_t)t; + GetGmTime(&s, &GImPlot->Tm); + GImPlot->Tm.tm_isdst = -1; + switch (unit) { + case ImPlotTimeUnit_S: return (double)s; + case ImPlotTimeUnit_Ms: return floor(t * 1000) / 1000; + case ImPlotTimeUnit_Us: return floor(t * 1000000) / 1000000; + case ImPlotTimeUnit_Yr: GImPlot->Tm.tm_mon = 0; // fall-through + case ImPlotTimeUnit_Mo: GImPlot->Tm.tm_mday = 1; // fall-through + case ImPlotTimeUnit_Day: GImPlot->Tm.tm_hour = 0; // fall-through + case ImPlotTimeUnit_Hr: GImPlot->Tm.tm_min = 0; // fall-through + case ImPlotTimeUnit_Min: GImPlot->Tm.tm_sec = 0; break; + default: return t; + } + s = MakeGmTime(&GImPlot->Tm); + return (double)s; +} + +double CeilTime(double t, ImPlotTimeUnit unit) { + return AddTime(FloorTime(t, unit), unit, 1); +} + +double RoundTime(double t, ImPlotTimeUnit unit) { + double t1 = FloorTime(t, unit); + double t2 = AddTime(t1,unit,1); + return t - t1 < t2 - t ? t1 : t2; +} + +int GetYear(double t) { + time_t s = (time_t)t; + tm& Tm = GImPlot->Tm; + GetGmTime(&s, &Tm); + return Tm.tm_year + 1900; +} + +double MakeYear(int year) { + int yr = year - 1900; + if (yr < 0) + yr = 0; + GImPlot->Tm = tm(); + GImPlot->Tm.tm_year = yr; + GImPlot->Tm.tm_sec = 1; + time_t s = MakeGmTime(&GImPlot->Tm); + if (s < 0) + s = 0; + return (double)s; +} + +void FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) { + time_t s = (time_t)t; + int ms = (int)(t * 1000 - floor(t) * 1000); + ms = ms % 10 == 9 ? ms + 1 : ms; + int us = (int)(t * 1000000 - floor(t) * 1000000); + us = us % 10 == 9 ? us + 1 : us; + tm& Tm = GImPlot->Tm; + GetGmTime(&s, &Tm); + switch(fmt) { + case ImPlotTimeFmt_Yr: strftime(buffer, size, "%Y", &Tm); break; + case ImPlotTimeFmt_Mo: strftime(buffer, size, "%b", &Tm); break; + case ImPlotTimeFmt_DayMo: snprintf(buffer, size, "%d/%d", Tm.tm_mon + 1, Tm.tm_mday); break; + case ImPlotTimeFmt_DayMoHrMin: + if (Tm.tm_hour == 0) + snprintf(buffer, size, "%d/%d 12:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_min); + else if (Tm.tm_hour == 12) + snprintf(buffer, size, "%d/%d 12:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_min); + else if (Tm.tm_hour < 12) + snprintf(buffer, size, "%d/%d %u:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_hour, Tm.tm_min); + else if (Tm.tm_hour > 12) + snprintf(buffer, size, "%d/%d %u:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_hour - 12, Tm.tm_min); + break; + case ImPlotTimeFmt_DayMoYr: snprintf(buffer, size, "%d/%d/%d", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900); break; + case ImPlotTimeFmt_DayMoYrHrMin: + if (Tm.tm_hour == 0) + snprintf(buffer, size, "%d/%d/%d 12:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_min); + else if (Tm.tm_hour == 12) + snprintf(buffer, size, "%d/%d/%d 12:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_min); + else if (Tm.tm_hour < 12) + snprintf(buffer, size, "%d/%d/%d %u:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_hour, Tm.tm_min); + else if (Tm.tm_hour > 12) + snprintf(buffer, size, "%d/%d/%d %u:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_hour - 12, Tm.tm_min); + break; + case ImPlotTimeFmt_Hr: + if (Tm.tm_hour == 0) + snprintf(buffer, size, "12am"); + else if (Tm.tm_hour == 12) + snprintf(buffer, size, "12pm"); + else if (Tm.tm_hour < 12) + snprintf(buffer, size, "%uam", Tm.tm_hour); + else if (Tm.tm_hour > 12) + snprintf(buffer, size, "%upm", Tm.tm_hour - 12); + break; + case ImPlotTimeFmt_HrMin: + if (Tm.tm_hour == 0) + snprintf(buffer, size, "12:%02dam", Tm.tm_min); + else if (Tm.tm_hour == 12) + snprintf(buffer, size, "12:%02dpm", Tm.tm_min); + else if (Tm.tm_hour < 12) + snprintf(buffer, size, "%d:%02dam", Tm.tm_hour, Tm.tm_min); + else if (Tm.tm_hour > 12) + snprintf(buffer, size, "%d:%02dpm", Tm.tm_hour - 12, Tm.tm_min); + break; + case ImPlotTimeFmt_S: snprintf(buffer, size, ":%02d", Tm.tm_sec); break; + case ImPlotTimeFmt_SMs: snprintf(buffer, size, ":%02d.%03d", Tm.tm_sec, ms); break; + case ImPlotTimeFmt_SUs: snprintf(buffer, size, ":%02d.%06d", Tm.tm_sec, us); break; + default: break; + } +} + +void PrintTime(double t, ImPlotTimeFmt fmt) { + static char buff[32]; + FormatTime(t, buff, 32, fmt); + printf("%s\n",buff); +} + +// Returns the nominally largest possible width for a time format +inline float GetTimeLabelWidth(ImPlotTimeFmt fmt) { + switch (fmt) { + case ImPlotTimeFmt_SUs: return ImGui::CalcTextSize(":88.888888").x; // :29.428552 + case ImPlotTimeFmt_SMs: return ImGui::CalcTextSize(":88.888").x; // :29.428 + case ImPlotTimeFmt_S: return ImGui::CalcTextSize(":88").x; // :29 + case ImPlotTimeFmt_HrMin: return ImGui::CalcTextSize("88:88pm").x; // 7:21pm + case ImPlotTimeFmt_Hr: return ImGui::CalcTextSize("8pm").x; // 7pm + case ImPlotTimeFmt_DayMo: return ImGui::CalcTextSize("88/88").x; // 10/3 + case ImPlotTimeFmt_DayMoHrMin: return ImGui::CalcTextSize("88/88 88:88pm").x; // 10/3 7:21pm + case ImPlotTimeFmt_DayMoYrHrMin: return ImGui::CalcTextSize("88/88/8888 88:88pm").x; // 10/3/1991 7:21pm + case ImPlotTimeFmt_Mo: return ImGui::CalcTextSize("MMM").x; // Oct + case ImPlotTimeFmt_Yr: return ImGui::CalcTextSize("8888").x; // 1991 + default: return 0; + } +} + +inline void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeFmt fmt) { + char temp[32]; + if (tick.ShowLabel) { + tick.BufferOffset = buffer.size(); + FormatTime(tick.PlotPos, temp, 32, fmt); + buffer.append(temp, temp + strlen(temp) + 1); + tick.LabelSize = ImGui::CalcTextSize(buffer.Buf.Data + tick.BufferOffset); + } +} void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollection& ticks) { // get units for level 0 and level 1 labels const ImPlotTimeUnit unit0 = GetUnitForRange(range.Size() / (plot_width / 100)); // level = 0 (top) const ImPlotTimeUnit unit1 = unit0 + 1; // level = 1 (bottom) - // maximum allowable density of labels - const float max_density = 0.5f; - + const float max_density = 0.66f; if (unit0 != ImPlotTimeUnit_Yr) { // pixels per major (level 1) division const float pix_per_major_div = plot_width / (float)(range.Size() / TimeUnitSpans[unit1]); @@ -647,8 +883,9 @@ void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollecti ticks.AddTick(tick_min); } // add minor ticks up until next major - if (minor_per_major > 1) { - double t2 = AddTime(t1, unit1, 1); + const double t2 = AddTime(t1, unit1, 1); + const ImPlotRange r12(t1,t2); + if (minor_per_major > 1 && RangesOverlap(range,r12)) { double t12 = AddTime(t1, unit0, step); while (t12 < t2) { float px_to_t2 = (float)((t2 - t12)/range.Size()) * plot_width; @@ -661,22 +898,18 @@ void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollecti t12 = AddTime(t12, unit0, step); } } - t1 = AddTime(t1, unit1, 1); + t1 = t2; } } else { - // nominal pixels taken up by year label const float label_width = GetTimeLabelWidth(TimeFormatLevel0[ImPlotTimeUnit_Yr]); - // maximum number of labels we can display const int max_labels = (int)(max_density * plot_width / label_width); - - - const int year_min = GetYear(range.Min); - const int year_max = GetYear(CeilTime(range.Max, ImPlotTimeUnit_Yr)); + const int year_min = GetYear(range.Min); + const int year_max = GetYear(CeilTime(range.Max, ImPlotTimeUnit_Yr)); const double nice_range = NiceNum((year_max - year_min)*0.99,false); const double interval = NiceNum(nice_range / (max_labels - 1), true); const int graphmin = (int)(floor(year_min / interval) * interval); - + const int step = (int)interval <= 0 ? 1 : (int)interval; double t1 = MakeYear(graphmin); while (t1 < range.Max) { if (range.Contains(t1)) { @@ -685,26 +918,9 @@ void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollecti LabelTickTime(tick, ticks.Labels, TimeFormatLevel0[ImPlotTimeUnit_Yr]); ticks.AddTick(tick); } - t1 = AddTime(t1, ImPlotTimeUnit_Yr, (int)interval); + t1 = AddTime(t1, ImPlotTimeUnit_Yr, step); } } - - // printf("%d , %d\n",minor_per_major,step); -} - -void AddTicksCustom(const double* values, const char** labels, int n, ImPlotTickCollection& ticks) { - for (int i = 0; i < n; ++i) { - ImPlotTick tick(values[i], false, true); - if (labels != NULL) { - tick.BufferOffset = ticks.Labels.size(); - ticks.Labels.append(labels[i], labels[i] + strlen(labels[i]) + 1); - tick.LabelSize = ImGui::CalcTextSize(labels[i]); - } - else { - LabelTickDefault(tick, ticks.Labels); - } - ticks.AddTick(tick); - } } //----------------------------------------------------------------------------- @@ -1730,27 +1946,29 @@ void EndPlot() { if (gp.FitThisFrame && (gp.VisibleItemCount > 0 || plot.Queried)) { if (gp.FitX && !ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LockMin) && !ImNanOrInf(gp.ExtentsX.Min)) { - plot.XAxis.SetMin(gp.ExtentsX.Min); + plot.XAxis.Range.Min = (gp.ExtentsX.Min); } if (gp.FitX && !ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LockMax) && !ImNanOrInf(gp.ExtentsX.Max)) { - plot.XAxis.SetMax(gp.ExtentsX.Max); + plot.XAxis.Range.Max = (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; } + plot.XAxis.Constrain(); // ConstrainAxis(plot.XAxis); for (int i = 0; i < IMPLOT_Y_AXES; i++) { if (gp.FitY[i] && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LockMin) && !ImNanOrInf(gp.ExtentsY[i].Min)) { - plot.YAxis[i].SetMin(gp.ExtentsY[i].Min); + plot.YAxis[i].Range.Min = (gp.ExtentsY[i].Min); } if (gp.FitY[i] && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LockMax) && !ImNanOrInf(gp.ExtentsY[i].Max)) { - plot.YAxis[i].SetMax(gp.ExtentsY[i].Max); + plot.YAxis[i].Range.Max = (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; } + plot.YAxis[i].Constrain(); // ConstrainAxis(plot.YAxis[i]); } } diff --git a/implot_demo.cpp b/implot_demo.cpp index 4eff8b8..c4308e4 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -1138,7 +1138,7 @@ void ShowDemoWindow(bool* p_open) { //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Custom Plotters and Tooltips")) { ImGui::BulletText("You can create custom plotters or extend ImPlot using implot_internal.h."); - double dates[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217}; + double dates[] = {1546300800,1546387200,1546473600,1546560000,1546819200,1546905600,1546992000,1547078400,1547164800,1547424000,1547510400,1547596800,1547683200,1547769600,1547942400,1548028800,1548115200,1548201600,1548288000,1548374400,1548633600,1548720000,1548806400,1548892800,1548979200,1549238400,1549324800,1549411200,1549497600,1549584000,1549843200,1549929600,1550016000,1550102400,1550188800,1550361600,1550448000,1550534400,1550620800,1550707200,1550793600,1551052800,1551139200,1551225600,1551312000,1551398400,1551657600,1551744000,1551830400,1551916800,1552003200,1552262400,1552348800,1552435200,1552521600,1552608000,1552867200,1552953600,1553040000,1553126400,1553212800,1553472000,1553558400,1553644800,1553731200,1553817600,1554076800,1554163200,1554249600,1554336000,1554422400,1554681600,1554768000,1554854400,1554940800,1555027200,1555286400,1555372800,1555459200,1555545600,1555632000,1555891200,1555977600,1556064000,1556150400,1556236800,1556496000,1556582400,1556668800,1556755200,1556841600,1557100800,1557187200,1557273600,1557360000,1557446400,1557705600,1557792000,1557878400,1557964800,1558051200,1558310400,1558396800,1558483200,1558569600,1558656000,1558828800,1558915200,1559001600,1559088000,1559174400,1559260800,1559520000,1559606400,1559692800,1559779200,1559865600,1560124800,1560211200,1560297600,1560384000,1560470400,1560729600,1560816000,1560902400,1560988800,1561075200,1561334400,1561420800,1561507200,1561593600,1561680000,1561939200,1562025600,1562112000,1562198400,1562284800,1562544000,1562630400,1562716800,1562803200,1562889600,1563148800,1563235200,1563321600,1563408000,1563494400,1563753600,1563840000,1563926400,1564012800,1564099200,1564358400,1564444800,1564531200,1564617600,1564704000,1564963200,1565049600,1565136000,1565222400,1565308800,1565568000,1565654400,1565740800,1565827200,1565913600,1566172800,1566259200,1566345600,1566432000,1566518400,1566777600,1566864000,1566950400,1567036800,1567123200,1567296000,1567382400,1567468800,1567555200,1567641600,1567728000,1567987200,1568073600,1568160000,1568246400,1568332800,1568592000,1568678400,1568764800,1568851200,1568937600,1569196800,1569283200,1569369600,1569456000,1569542400,1569801600,1569888000,1569974400,1570060800,1570147200,1570406400,1570492800,1570579200,1570665600,1570752000,1571011200,1571097600,1571184000,1571270400,1571356800,1571616000,1571702400,1571788800,1571875200,1571961600}; double opens[] = {1284.7,1319.9,1318.7,1328,1317.6,1321.6,1314.3,1325,1319.3,1323.1,1324.7,1321.3,1323.5,1322,1281.3,1281.95,1311.1,1315,1314,1313.1,1331.9,1334.2,1341.3,1350.6,1349.8,1346.4,1343.4,1344.9,1335.6,1337.9,1342.5,1337,1338.6,1337,1340.4,1324.65,1324.35,1349.5,1371.3,1367.9,1351.3,1357.8,1356.1,1356,1347.6,1339.1,1320.6,1311.8,1314,1312.4,1312.3,1323.5,1319.1,1327.2,1332.1,1320.3,1323.1,1328,1330.9,1338,1333,1335.3,1345.2,1341.1,1332.5,1314,1314.4,1310.7,1314,1313.1,1315,1313.7,1320,1326.5,1329.2,1314.2,1312.3,1309.5,1297.4,1293.7,1277.9,1295.8,1295.2,1290.3,1294.2,1298,1306.4,1299.8,1302.3,1297,1289.6,1302,1300.7,1303.5,1300.5,1303.2,1306,1318.7,1315,1314.5,1304.1,1294.7,1293.7,1291.2,1290.2,1300.4,1284.2,1284.25,1301.8,1295.9,1296.2,1304.4,1323.1,1340.9,1341,1348,1351.4,1351.4,1343.5,1342.3,1349,1357.6,1357.1,1354.7,1361.4,1375.2,1403.5,1414.7,1433.2,1438,1423.6,1424.4,1418,1399.5,1435.5,1421.25,1434.1,1412.4,1409.8,1412.2,1433.4,1418.4,1429,1428.8,1420.6,1441,1460.4,1441.7,1438.4,1431,1439.3,1427.4,1431.9,1439.5,1443.7,1425.6,1457.5,1451.2,1481.1,1486.7,1512.1,1515.9,1509.2,1522.3,1513,1526.6,1533.9,1523,1506.3,1518.4,1512.4,1508.8,1545.4,1537.3,1551.8,1549.4,1536.9,1535.25,1537.95,1535.2,1556,1561.4,1525.6,1516.4,1507,1493.9,1504.9,1506.5,1513.1,1506.5,1509.7,1502,1506.8,1521.5,1529.8,1539.8,1510.9,1511.8,1501.7,1478,1485.4,1505.6,1511.6,1518.6,1498.7,1510.9,1510.8,1498.3,1492,1497.7,1484.8,1494.2,1495.6,1495.6,1487.5,1491.1,1495.1,1506.4}; double highs[] = {1284.75,1320.6,1327,1330.8,1326.8,1321.6,1326,1328,1325.8,1327.1,1326,1326,1323.5,1322.1,1282.7,1282.95,1315.8,1316.3,1314,1333.2,1334.7,1341.7,1353.2,1354.6,1352.2,1346.4,1345.7,1344.9,1340.7,1344.2,1342.7,1342.1,1345.2,1342,1350,1324.95,1330.75,1369.6,1374.3,1368.4,1359.8,1359,1357,1356,1353.4,1340.6,1322.3,1314.1,1316.1,1312.9,1325.7,1323.5,1326.3,1336,1332.1,1330.1,1330.4,1334.7,1341.1,1344.2,1338.8,1348.4,1345.6,1342.8,1334.7,1322.3,1319.3,1314.7,1316.6,1316.4,1315,1325.4,1328.3,1332.2,1329.2,1316.9,1312.3,1309.5,1299.6,1296.9,1277.9,1299.5,1296.2,1298.4,1302.5,1308.7,1306.4,1305.9,1307,1297.2,1301.7,1305,1305.3,1310.2,1307,1308,1319.8,1321.7,1318.7,1316.2,1305.9,1295.8,1293.8,1293.7,1304.2,1302,1285.15,1286.85,1304,1302,1305.2,1323,1344.1,1345.2,1360.1,1355.3,1363.8,1353,1344.7,1353.6,1358,1373.6,1358.2,1369.6,1377.6,1408.9,1425.5,1435.9,1453.7,1438,1426,1439.1,1418,1435,1452.6,1426.65,1437.5,1421.5,1414.1,1433.3,1441.3,1431.4,1433.9,1432.4,1440.8,1462.3,1467,1443.5,1444,1442.9,1447,1437.6,1440.8,1445.7,1447.8,1458.2,1461.9,1481.8,1486.8,1522.7,1521.3,1521.1,1531.5,1546.1,1534.9,1537.7,1538.6,1523.6,1518.8,1518.4,1514.6,1540.3,1565,1554.5,1556.6,1559.8,1541.9,1542.9,1540.05,1558.9,1566.2,1561.9,1536.2,1523.8,1509.1,1506.2,1532.2,1516.6,1519.7,1515,1519.5,1512.1,1524.5,1534.4,1543.3,1543.3,1542.8,1519.5,1507.2,1493.5,1511.4,1525.8,1522.2,1518.8,1515.3,1518,1522.3,1508,1501.5,1503,1495.5,1501.1,1497.9,1498.7,1492.1,1499.4,1506.9,1520.9}; double lows[] = {1282.85,1315,1318.7,1309.6,1317.6,1312.9,1312.4,1319.1,1319,1321,1318.1,1321.3,1319.9,1312,1280.5,1276.15,1308,1309.9,1308.5,1312.3,1329.3,1333.1,1340.2,1347,1345.9,1338,1340.8,1335,1332,1337.9,1333,1336.8,1333.2,1329.9,1340.4,1323.85,1324.05,1349,1366.3,1351.2,1349.1,1352.4,1350.7,1344.3,1338.9,1316.3,1308.4,1306.9,1309.6,1306.7,1312.3,1315.4,1319,1327.2,1317.2,1320,1323,1328,1323,1327.8,1331.7,1335.3,1336.6,1331.8,1311.4,1310,1309.5,1308,1310.6,1302.8,1306.6,1313.7,1320,1322.8,1311,1312.1,1303.6,1293.9,1293.5,1291,1277.9,1294.1,1286,1289.1,1293.5,1296.9,1298,1299.6,1292.9,1285.1,1288.5,1296.3,1297.2,1298.4,1298.6,1302,1300.3,1312,1310.8,1301.9,1292,1291.1,1286.3,1289.2,1289.9,1297.4,1283.65,1283.25,1292.9,1295.9,1290.8,1304.2,1322.7,1336.1,1341,1343.5,1345.8,1340.3,1335.1,1341.5,1347.6,1352.8,1348.2,1353.7,1356.5,1373.3,1398,1414.7,1427,1416.4,1412.7,1420.1,1396.4,1398.8,1426.6,1412.85,1400.7,1406,1399.8,1404.4,1415.5,1417.2,1421.9,1415,1413.7,1428.1,1434,1435.7,1427.5,1429.4,1423.9,1425.6,1427.5,1434.8,1422.3,1412.1,1442.5,1448.8,1468.2,1484.3,1501.6,1506.2,1498.6,1488.9,1504.5,1518.3,1513.9,1503.3,1503,1506.5,1502.1,1503,1534.8,1535.3,1541.4,1528.6,1525.6,1535.25,1528.15,1528,1542.6,1514.3,1510.7,1505.5,1492.1,1492.9,1496.8,1493.1,1503.4,1500.9,1490.7,1496.3,1505.3,1505.3,1517.9,1507.4,1507.1,1493.3,1470.5,1465,1480.5,1501.7,1501.4,1493.3,1492.1,1505.1,1495.7,1478,1487.1,1480.8,1480.6,1487,1488.3,1484.8,1484,1490.7,1490.4,1503.1}; @@ -1150,8 +1150,8 @@ void ShowDemoWindow(bool* p_open) { static ImVec4 bearCol = ImVec4(0.853f, 0.050f, 0.310f, 1.000f); ImGui::SameLine(); ImGui::ColorEdit4("##Bull", &bullCol.x, ImGuiColorEditFlags_NoInputs); ImGui::SameLine(); ImGui::ColorEdit4("##Bear", &bearCol.x, ImGuiColorEditFlags_NoInputs); - ImPlot::SetNextPlotLimits(0, 218, 1250, 1600); - if (ImPlot::BeginPlot("Candlestick Chart","Day","USD")) { + ImPlot::SetNextPlotLimits(1546300800, 1571961600, 1250, 1600); + if (ImPlot::BeginPlot("Candlestick Chart","Day","USD",ImVec2(-1,-1),ImPlotFlags_Default, ImPlotAxisFlags_Default | ImPlotAxisFlags_Time)) { MyImPlot::PlotCandlestick("GOOGL",dates, opens, closes, lows, highs, 218, tooltip, 0.25f, bullCol, bearCol); ImPlot::EndPlot(); } @@ -1287,7 +1287,7 @@ void PlotCandlestick(const char* label_id, const double* xs, const double* opens // begin plot item if (ImPlot::BeginItem(label_id)) { // override legend icon color - ImPlot::GetCurrentItem()->Color = ImVec4(1,1,1,1); + ImPlot::GetCurrentItem()->Color = ImVec4(0.25f,0.25f,0.25f,1); // fit data if requested if (ImPlot::FitThisFrame()) { for (int i = 0; i < count; ++i) { @@ -1312,7 +1312,7 @@ void PlotCandlestick(const char* label_id, const double* xs, const double* opens // custom tool if (ImPlot::IsPlotHovered() && tooltip) { ImPlotPoint mouse = ImPlot::GetPlotMousePos(); - mouse.x = round(mouse.x); + mouse.x = ImPlot::RoundTime(mouse.x, ImPlotTimeUnit_Day); float tool_l = ImPlot::PlotToPixels(mouse.x - half_width * 1.5, mouse.y).x; float tool_r = ImPlot::PlotToPixels(mouse.x + half_width * 1.5, mouse.y).x; float tool_t = ImPlot::GetPlotPos().y; @@ -1323,7 +1323,9 @@ void PlotCandlestick(const char* label_id, const double* xs, const double* opens // render tool tip (won't be affected by plot clip rect) if (idx != -1) { ImGui::BeginTooltip(); - ImGui::Text("Day: %.0f", xs[idx]); + char buff[32]; + ImPlot::FormatTime(xs[idx],buff,32,ImPlotTimeFmt_DayMoYr); + ImGui::Text("Day: %s", buff); ImGui::Text("Open: $%.2f", opens[idx]); ImGui::Text("Close: $%.2f", closes[idx]); ImGui::Text("Low: $%.2f", lows[idx]); diff --git a/implot_internal.h b/implot_internal.h index 2907ae0..7198021 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -170,6 +170,32 @@ enum ImPlotScale_ { ImPlotScale_LogLog // log x, log y }; +enum ImPlotTimeUnit_ { // primary + ImPlotTimeUnit_Us, // microsecond :29.428552 + ImPlotTimeUnit_Ms, // millisecond :29.428 + ImPlotTimeUnit_S, // second :29 + ImPlotTimeUnit_Min, // minute 7:21pm + ImPlotTimeUnit_Hr, // hour 7pm + ImPlotTimeUnit_Day, // day 10/3 + ImPlotTimeUnit_Mo, // month Oct + ImPlotTimeUnit_Yr, // year 1991 + ImPlotTimeUnit_COUNT +}; + +enum ImPlotTimeFmt_ { + ImPlotTimeFmt_SUs, // :29.428552 + ImPlotTimeFmt_SMs, // :29.428 + ImPlotTimeFmt_S, // :29 + ImPlotTimeFmt_HrMin, // 7:21pm + ImPlotTimeFmt_Hr, // 7pm + ImPlotTimeFmt_DayMo, // 10/3 + ImPlotTimeFmt_DayMoHrMin, // 10/3 7:21pm + ImPlotTimeFmt_DayMoYr, // 10/3/1991 + ImPlotTimeFmt_DayMoYrHrMin, // 10/3/1991 7:21pm + ImPlotTimeFmt_Mo, // Oct + ImPlotTimeFmt_Yr // 1991 +}; + //----------------------------------------------------------------------------- // [SECTION] ImPlot Structs //----------------------------------------------------------------------------- @@ -612,6 +638,9 @@ inline bool FitThisFrame() { return GImPlot->FitThisFrame; } // Extends the current plots axes so that it encompasses point p void FitPoint(const ImPlotPoint& p); +// Returns true if two ranges overlap +inline bool RangesOverlap(const ImPlotRange& r1, const ImPlotRange& r2) { return r1.Min <= r2.Max && r2.Min <= r1.Max; } + //----------------------------------------------------------------------------- // [SECTION] Legend Utils //----------------------------------------------------------------------------- @@ -711,256 +740,40 @@ inline T OffsetAndStride(const T* data, int idx, int count, int offset, int stri } //----------------------------------------------------------------------------- -// [SECTION] Time Utils +// Time Utils //----------------------------------------------------------------------------- -enum ImPlotTimeUnit_ { // primary - ImPlotTimeUnit_Us, // microsecond :29.428552 - ImPlotTimeUnit_Ms, // millisecond :29.428 - ImPlotTimeUnit_S, // second :29 - ImPlotTimeUnit_Min, // minute 7:21pm - ImPlotTimeUnit_Hr, // hour 7pm - ImPlotTimeUnit_Day, // day 10/3 - ImPlotTimeUnit_Mo, // month Oct - ImPlotTimeUnit_Yr, // year 1991 - ImPlotTimeUnit_COUNT -}; - -enum ImPlotTimeFmt_ { - ImPlotTimeFmt_SUs, // :29.428552 - ImPlotTimeFmt_SMs, // :29.428 - ImPlotTimeFmt_S, // :29 - ImPlotTimeFmt_HrMin, // 7:21pm - ImPlotTimeFmt_Hr, // 7pm - ImPlotTimeFmt_DayMo, // 10/3 - ImPlotTimeFmt_DayMoHrMin, // 10/3 7:21pm - ImPlotTimeFmt_DayMoYrHrMin, // 10/3/1991 7:21pm - ImPlotTimeFmt_Mo, // Oct - ImPlotTimeFmt_Yr // 1991 -}; - -static const int DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; -static const double TimeUnitSpans[ImPlotTimeUnit_COUNT] = {0.000001, 0.001, 1, 60, 3600, 86400, 2629800, 31557600}; -static const ImPlotTimeFmt TimeFormatLevel0[ImPlotTimeUnit_COUNT] = -{ - ImPlotTimeFmt_SUs, - ImPlotTimeFmt_SMs, - ImPlotTimeFmt_S, - ImPlotTimeFmt_HrMin, - ImPlotTimeFmt_Hr, - ImPlotTimeFmt_DayMo, - ImPlotTimeFmt_Mo, - ImPlotTimeFmt_Yr -}; -static const ImPlotTimeFmt TimeFormatLevel1[ImPlotTimeUnit_COUNT] = -{ - ImPlotTimeFmt_DayMoHrMin, - ImPlotTimeFmt_DayMoHrMin, - ImPlotTimeFmt_DayMoHrMin, - ImPlotTimeFmt_DayMoHrMin, - ImPlotTimeFmt_DayMo, - ImPlotTimeFmt_DayMo, - ImPlotTimeFmt_Yr, - ImPlotTimeFmt_Yr -}; - - -inline ImPlotTimeUnit GetUnitForRange(double range) { - static double cutoffs[ImPlotTimeUnit_COUNT] = {0.001, 1, 60, 3600, 86400, 2629800, 31557600, IMPLOT_MAX_TIME}; - for (int i = 0; i < ImPlotTimeUnit_COUNT; ++i) { - if (range <= cutoffs[i]) - return (ImPlotTimeUnit)i; - } - return ImPlotTimeUnit_Yr; -} - - // Returns true if year is leap year (366 days long) inline bool IsLeapYear(int year) { - if (year % 4 != 0) return false; + if (year % 4 != 0) return false; if (year % 400 == 0) return true; if (year % 100 == 0) return false; return true; } - // Returns the number of days in a month, accounting for Feb. leap years. inline int GetDaysInMonth(int year, int month) { - return DaysInMonth[month] + (int)(month == 1 && IsLeapYear(year)); + static const int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + return days[month] + (int)(month == 1 && IsLeapYear(year)); } -inline time_t MakeGmTime(const struct tm *ptm) { - time_t secs = 0; - int year = ptm->tm_year + 1900; - for (int y = 1970; y < year; ++y) { - secs += (IsLeapYear(y)? 366: 365) * 86400; - } - for (int m = 0; m < ptm->tm_mon; ++m) { - secs += DaysInMonth[m] * 86400; - if (m == 1 && IsLeapYear(year)) secs += 86400; - } - secs += (ptm->tm_mday - 1) * 86400; - secs += ptm->tm_hour * 3600; - secs += ptm->tm_min * 60; - secs += ptm->tm_sec; - return secs; -} +// Adds time to a timestamp. #count must be positive! +double AddTime(double t, ImPlotTimeUnit unit, int count); +// Rounds a timestamp down to nearest. +double FloorTime(double t, ImPlotTimeUnit unit); +// Rounds a timestamp up to the nearest unit. +double CeilTime(double t, ImPlotTimeUnit unit); +// Rounds a timestamp up or down to the nearest unit. +double RoundTime(double t, ImPlotTimeUnit unit); -inline tm* GetGmTime(const time_t* time, tm* tm) -{ -#ifdef _MSC_VER - if (gmtime_s(tm, time) == 0) - return tm; - else - return NULL; -#else - return gmtime_r(time, tm); -#endif -} +// Get year from timestamp +int GetYear(double t); +// Make a timestamp starting at the first day of a year +double MakeYear(int year); -inline double AddTime(double t, ImPlotTimeUnit unit, int count) { - switch(unit) { - case ImPlotTimeUnit_Us: return t + count * 0.000001; - case ImPlotTimeUnit_Ms: return t + count * 0.001; - case ImPlotTimeUnit_S: return t + count; - case ImPlotTimeUnit_Min: return t + count * 60; - case ImPlotTimeUnit_Hr: return t + count * 3600; - case ImPlotTimeUnit_Day: return t + count * 86400; - case ImPlotTimeUnit_Yr: count *= 12; // fall-through - case ImPlotTimeUnit_Mo: for (int i = 0; i < count; ++i) { - time_t s = (time_t)t; - GetGmTime(&s, &GImPlot->Tm); - int days = GetDaysInMonth(GImPlot->Tm.tm_year, GImPlot->Tm.tm_mon); - t = AddTime(t, ImPlotTimeUnit_Day, days); - } - return t; - default: return t; - } -} - -inline int GetYear(double t) { - time_t s = (time_t)t; - tm& Tm = GImPlot->Tm; - IM_ASSERT(GetGmTime(&s, &Tm) != NULL); - return Tm.tm_year + 1900; -} - -inline double MakeYear(int year) { - int yr = year - 1900; - if (yr < 0) - yr = 0; - GImPlot->Tm = tm(); - GImPlot->Tm.tm_year = yr; - GImPlot->Tm.tm_sec = 1; - time_t s = MakeGmTime(&GImPlot->Tm); - if (s < 0) - s = 0; - return (double)s; -} - -inline double FloorTime(double t, ImPlotTimeUnit unit) { - time_t s = (time_t)t; - GetGmTime(&s, &GImPlot->Tm); - GImPlot->Tm.tm_isdst = -1; - switch (unit) { - case ImPlotTimeUnit_S: return (double)s; - case ImPlotTimeUnit_Ms: return floor(t * 1000) / 1000; - case ImPlotTimeUnit_Us: return floor(t * 1000000) / 1000000; - case ImPlotTimeUnit_Yr: GImPlot->Tm.tm_mon = 0; // fall-through - case ImPlotTimeUnit_Mo: GImPlot->Tm.tm_mday = 1; // fall-through - case ImPlotTimeUnit_Day: GImPlot->Tm.tm_hour = 0; // fall-through - case ImPlotTimeUnit_Hr: GImPlot->Tm.tm_min = 0; // fall-through - case ImPlotTimeUnit_Min: GImPlot->Tm.tm_sec = 0; break; - default: return t; - } - s = MakeGmTime(&GImPlot->Tm); - return (double)s; -} - -inline double CeilTime(double t, ImPlotTimeUnit unit) { - return AddTime(FloorTime(t, unit), unit, 1); -} - -inline void FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) { - time_t s = (time_t)t; - int ms = (int)(t * 1000 - floor(t) * 1000); - ms = ms % 10 == 9 ? ms + 1 : ms; - int us = (int)(t * 1000000 - floor(t) * 1000000); - us = us % 10 == 9 ? us + 1 : us; - tm& Tm = GImPlot->Tm; - IM_ASSERT(GetGmTime(&s, &Tm) != NULL); - switch(fmt) { - case ImPlotTimeFmt_Yr: strftime(buffer, size, "%Y", &Tm); break; - case ImPlotTimeFmt_Mo: strftime(buffer, size, "%b", &Tm); break; - case ImPlotTimeFmt_DayMo: snprintf(buffer, size, "%d/%d", Tm.tm_mon + 1, Tm.tm_mday); break; - case ImPlotTimeFmt_DayMoHrMin: - if (Tm.tm_hour == 0) - snprintf(buffer, size, "%d/%d 12:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_min); - else if (Tm.tm_hour == 12) - snprintf(buffer, size, "%d/%d 12:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_min); - else if (Tm.tm_hour < 12) - snprintf(buffer, size, "%d/%d %u:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_hour, Tm.tm_min); - else if (Tm.tm_hour > 12) - snprintf(buffer, size, "%d/%d %u:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_hour - 12, Tm.tm_min); - break; - case ImPlotTimeFmt_DayMoYrHrMin: - if (Tm.tm_hour == 0) - snprintf(buffer, size, "%d/%d/%d 12:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_min); - else if (Tm.tm_hour == 12) - snprintf(buffer, size, "%d/%d/%d 12:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_min); - else if (Tm.tm_hour < 12) - snprintf(buffer, size, "%d/%d/%d %u:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_hour, Tm.tm_min); - else if (Tm.tm_hour > 12) - snprintf(buffer, size, "%d/%d/%d %u:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_hour - 12, Tm.tm_min); - break; - case ImPlotTimeFmt_Hr: - if (Tm.tm_hour == 0) - snprintf(buffer, size, "12am"); - else if (Tm.tm_hour == 12) - snprintf(buffer, size, "12pm"); - else if (Tm.tm_hour < 12) - snprintf(buffer, size, "%uam", Tm.tm_hour); - else if (Tm.tm_hour > 12) - snprintf(buffer, size, "%upm", Tm.tm_hour - 12); - break; - case ImPlotTimeFmt_HrMin: - if (Tm.tm_hour == 0) - snprintf(buffer, size, "12:%02dam", Tm.tm_min); - else if (Tm.tm_hour == 12) - snprintf(buffer, size, "12:%02dpm", Tm.tm_min); - else if (Tm.tm_hour < 12) - snprintf(buffer, size, "%d:%02dam", Tm.tm_hour, Tm.tm_min); - else if (Tm.tm_hour > 12) - snprintf(buffer, size, "%d:%02dpm", Tm.tm_hour - 12, Tm.tm_min); - break; - case ImPlotTimeFmt_S: snprintf(buffer, size, ":%02d", Tm.tm_sec); break; - case ImPlotTimeFmt_SMs: snprintf(buffer, size, ":%02d.%03d", Tm.tm_sec, ms); break; - case ImPlotTimeFmt_SUs: snprintf(buffer, size, ":%02d.%06d", Tm.tm_sec, us); break; - default: break; - } -} - -inline void PrintTime(double t, ImPlotTimeFmt fmt) { - char buff[32]; - FormatTime(t, buff, 32, fmt); - printf("%s\n",buff); -} - -// Returns the nominally largest possible width for a time format -inline float GetTimeLabelWidth(ImPlotTimeFmt fmt) { - switch (fmt) { - case ImPlotTimeFmt_SUs: return ImGui::CalcTextSize(":88.888888").x; // :29.428552 - case ImPlotTimeFmt_SMs: return ImGui::CalcTextSize(":88.888").x; // :29.428 - case ImPlotTimeFmt_S: return ImGui::CalcTextSize(":88").x; // :29 - case ImPlotTimeFmt_HrMin: return ImGui::CalcTextSize("88:88pm").x; // 7:21pm - case ImPlotTimeFmt_Hr: return ImGui::CalcTextSize("8pm").x; // 7pm - case ImPlotTimeFmt_DayMo: return ImGui::CalcTextSize("88/88").x; // 10/3 - case ImPlotTimeFmt_DayMoHrMin: return ImGui::CalcTextSize("88/88 88:88pm").x; // 10/3 7:21pm - case ImPlotTimeFmt_DayMoYrHrMin: return ImGui::CalcTextSize("88/88/8888 88:88pm").x; // 10/3/1991 7:21pm - case ImPlotTimeFmt_Mo: return ImGui::CalcTextSize("MMM").x; // Oct - case ImPlotTimeFmt_Yr: return ImGui::CalcTextSize("8888").x; // 1991 - default: return 0; - } -} +// Formates a timestamp t into a buffer according to fmt. +void FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt); +// Prints a timestamp to console +void PrintTime(double t, ImPlotTimeFmt fmt); //----------------------------------------------------------------------------- // [SECTION] Internal / Experimental Plotters From f92625a462fbff6c5c9c0aa9de88111f11b09603 Mon Sep 17 00:00:00 2001 From: epezent Date: Sat, 5 Sep 2020 09:38:08 -0500 Subject: [PATCH 09/16] add ImPlotTime struct --- implot.cpp | 207 +++++++++++++++++++++++++--------------------- implot_internal.h | 56 ++++++++----- 2 files changed, 149 insertions(+), 114 deletions(-) diff --git a/implot.cpp b/implot.cpp index 065756d..83281fe 100644 --- a/implot.cpp +++ b/implot.cpp @@ -596,28 +596,6 @@ static const double TimeUnitSpans[ImPlotTimeUnit_COUNT] = { 31557600 }; -static const ImPlotTimeFmt TimeFormatLevel0[ImPlotTimeUnit_COUNT] = { - ImPlotTimeFmt_SUs, - ImPlotTimeFmt_SMs, - ImPlotTimeFmt_S, - ImPlotTimeFmt_HrMin, - ImPlotTimeFmt_Hr, - ImPlotTimeFmt_DayMo, - ImPlotTimeFmt_Mo, - ImPlotTimeFmt_Yr -}; - -static const ImPlotTimeFmt TimeFormatLevel1[ImPlotTimeUnit_COUNT] = { - ImPlotTimeFmt_DayMoHrMin, - ImPlotTimeFmt_DayMoHrMin, - ImPlotTimeFmt_DayMoHrMin, - ImPlotTimeFmt_DayMoHrMin, - ImPlotTimeFmt_DayMo, - ImPlotTimeFmt_DayMo, - ImPlotTimeFmt_Yr, - ImPlotTimeFmt_Yr -}; - inline ImPlotTimeUnit GetUnitForRange(double range) { static double cutoffs[ImPlotTimeUnit_COUNT] = {0.001, 1, 60, 3600, 86400, 2629800, 31557600, IMPLOT_MAX_TIME}; for (int i = 0; i < ImPlotTimeUnit_COUNT; ++i) { @@ -761,63 +739,43 @@ double MakeYear(int year) { return (double)s; } -void FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) { +int FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) { + printf("%.6f\n",t); time_t s = (time_t)t; int ms = (int)(t * 1000 - floor(t) * 1000); - ms = ms % 10 == 9 ? ms + 1 : ms; + ms = ms % 10 == 9 ? ms + 1 : ms; // don't like this int us = (int)(t * 1000000 - floor(t) * 1000000); - us = us % 10 == 9 ? us + 1 : us; + us = us % 10 == 9 ? us + 1 : us; // don't like this tm& Tm = GImPlot->Tm; GetGmTime(&s, &Tm); - switch(fmt) { - case ImPlotTimeFmt_Yr: strftime(buffer, size, "%Y", &Tm); break; - case ImPlotTimeFmt_Mo: strftime(buffer, size, "%b", &Tm); break; - case ImPlotTimeFmt_DayMo: snprintf(buffer, size, "%d/%d", Tm.tm_mon + 1, Tm.tm_mday); break; - case ImPlotTimeFmt_DayMoHrMin: - if (Tm.tm_hour == 0) - snprintf(buffer, size, "%d/%d 12:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_min); - else if (Tm.tm_hour == 12) - snprintf(buffer, size, "%d/%d 12:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_min); - else if (Tm.tm_hour < 12) - snprintf(buffer, size, "%d/%d %u:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_hour, Tm.tm_min); - else if (Tm.tm_hour > 12) - snprintf(buffer, size, "%d/%d %u:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_hour - 12, Tm.tm_min); - break; - case ImPlotTimeFmt_DayMoYr: snprintf(buffer, size, "%d/%d/%d", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900); break; - case ImPlotTimeFmt_DayMoYrHrMin: - if (Tm.tm_hour == 0) - snprintf(buffer, size, "%d/%d/%d 12:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_min); - else if (Tm.tm_hour == 12) - snprintf(buffer, size, "%d/%d/%d 12:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_min); - else if (Tm.tm_hour < 12) - snprintf(buffer, size, "%d/%d/%d %u:%02dam", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_hour, Tm.tm_min); - else if (Tm.tm_hour > 12) - snprintf(buffer, size, "%d/%d/%d %u:%02dpm", Tm.tm_mon + 1, Tm.tm_mday, Tm.tm_year + 1900, Tm.tm_hour - 12, Tm.tm_min); - break; - case ImPlotTimeFmt_Hr: - if (Tm.tm_hour == 0) - snprintf(buffer, size, "12am"); - else if (Tm.tm_hour == 12) - snprintf(buffer, size, "12pm"); - else if (Tm.tm_hour < 12) - snprintf(buffer, size, "%uam", Tm.tm_hour); - else if (Tm.tm_hour > 12) - snprintf(buffer, size, "%upm", Tm.tm_hour - 12); - break; - case ImPlotTimeFmt_HrMin: - if (Tm.tm_hour == 0) - snprintf(buffer, size, "12:%02dam", Tm.tm_min); - else if (Tm.tm_hour == 12) - snprintf(buffer, size, "12:%02dpm", Tm.tm_min); - else if (Tm.tm_hour < 12) - snprintf(buffer, size, "%d:%02dam", Tm.tm_hour, Tm.tm_min); - else if (Tm.tm_hour > 12) - snprintf(buffer, size, "%d:%02dpm", Tm.tm_hour - 12, Tm.tm_min); - break; - case ImPlotTimeFmt_S: snprintf(buffer, size, ":%02d", Tm.tm_sec); break; - case ImPlotTimeFmt_SMs: snprintf(buffer, size, ":%02d.%03d", Tm.tm_sec, ms); break; - case ImPlotTimeFmt_SUs: snprintf(buffer, size, ":%02d.%06d", Tm.tm_sec, us); break; - default: break; + + const char* ap = Tm.tm_hour < 12 ? "am" : "pm"; + const int sec = Tm.tm_sec; + const int min = Tm.tm_min; + const int hr = (Tm.tm_hour == 0 || Tm.tm_hour == 12) ? 12 : Tm.tm_hour % 12; + const int day = Tm.tm_mday; + const int mon = Tm.tm_mon + 1; + const int year = Tm.tm_year + 1900; + const int yr = year % 100; + + static const char mnames[12][4] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; + + switch(fmt) { + case ImPlotTimeFmt_Us: return snprintf(buffer, size, ".%06d", us); + case ImPlotTimeFmt_SUs: return snprintf(buffer, size, ":%02d.%06d", sec, us); + case ImPlotTimeFmt_SMs: return snprintf(buffer, size, ":%02d.%03d", sec, ms); + case ImPlotTimeFmt_S: return snprintf(buffer, size, ":%02d", sec); + case ImPlotTimeFmt_HrMinS: return snprintf(buffer, size, "%d:%02d:%02d%s", hr, min, sec, ap); + case ImPlotTimeFmt_HrMin: return snprintf(buffer, size, "%d:%02d%s", hr, min, ap); + case ImPlotTimeFmt_Hr: return snprintf(buffer, size, "%u%s", hr, ap); + case ImPlotTimeFmt_DayMo: return snprintf(buffer, size, "%d/%d", mon, day); + case ImPlotTimeFmt_DayMoHrMin: return snprintf(buffer, size, "%d/%d %u:%02d%s", mon, day, hr, min, ap); + case ImPlotTimeFmt_DayMoYr: return snprintf(buffer, size, "%d/%d/%d", mon, day, yr); + case ImPlotTimeFmt_DayMoYrHrMin: return snprintf(buffer, size, "%d/%d/%d %u:%02d%s", mon, day, yr, hr, min, ap); + case ImPlotTimeFmt_DayMoYrHrMinS: return snprintf(buffer, size, "%d/%d/%d %u:%02d:%02d%s", mon, day, yr, hr, min, sec, ap); + case ImPlotTimeFmt_Mo: return snprintf(buffer, size, "%s", mnames[Tm.tm_mon]); + case ImPlotTimeFmt_Yr: return snprintf(buffer, size, "%d", year); + default: return 0; } } @@ -830,17 +788,21 @@ void PrintTime(double t, ImPlotTimeFmt fmt) { // Returns the nominally largest possible width for a time format inline float GetTimeLabelWidth(ImPlotTimeFmt fmt) { switch (fmt) { - case ImPlotTimeFmt_SUs: return ImGui::CalcTextSize(":88.888888").x; // :29.428552 - case ImPlotTimeFmt_SMs: return ImGui::CalcTextSize(":88.888").x; // :29.428 - case ImPlotTimeFmt_S: return ImGui::CalcTextSize(":88").x; // :29 - case ImPlotTimeFmt_HrMin: return ImGui::CalcTextSize("88:88pm").x; // 7:21pm - case ImPlotTimeFmt_Hr: return ImGui::CalcTextSize("8pm").x; // 7pm - case ImPlotTimeFmt_DayMo: return ImGui::CalcTextSize("88/88").x; // 10/3 - case ImPlotTimeFmt_DayMoHrMin: return ImGui::CalcTextSize("88/88 88:88pm").x; // 10/3 7:21pm - case ImPlotTimeFmt_DayMoYrHrMin: return ImGui::CalcTextSize("88/88/8888 88:88pm").x; // 10/3/1991 7:21pm - case ImPlotTimeFmt_Mo: return ImGui::CalcTextSize("MMM").x; // Oct - case ImPlotTimeFmt_Yr: return ImGui::CalcTextSize("8888").x; // 1991 - default: return 0; + case ImPlotTimeFmt_Us: return ImGui::CalcTextSize(".888888").x; // .428552 + case ImPlotTimeFmt_SUs: return ImGui::CalcTextSize(":88.888888").x; // :29.428552 + case ImPlotTimeFmt_SMs: return ImGui::CalcTextSize(":88.888").x; // :29.428 + case ImPlotTimeFmt_S: return ImGui::CalcTextSize(":88").x; // :29 + case ImPlotTimeFmt_HrMinS: return ImGui::CalcTextSize("88:88:88pm").x; // 7:21:29pm + case ImPlotTimeFmt_HrMin: return ImGui::CalcTextSize("88:88pm").x; // 7:21pm + case ImPlotTimeFmt_Hr: return ImGui::CalcTextSize("8pm").x; // 7pm + case ImPlotTimeFmt_DayMo: return ImGui::CalcTextSize("88/88").x; // 10/3 + case ImPlotTimeFmt_DayMoHrMin: return ImGui::CalcTextSize("88/88 88:88pm").x; // 10/3 7:21pm + case ImPlotTimeFmt_DayMoYr: return ImGui::CalcTextSize("88/88/88").x; // 10/3/1991 + case ImPlotTimeFmt_DayMoYrHrMin: return ImGui::CalcTextSize("88/88/88 88:88pm").x; // 10/3/91 7:21pm + case ImPlotTimeFmt_DayMoYrHrMinS: return ImGui::CalcTextSize("88/88/88 88:88:88pm").x; // 10/3/91 7:21:29pm + case ImPlotTimeFmt_Mo: return ImGui::CalcTextSize("MMM").x; // Oct + case ImPlotTimeFmt_Yr: return ImGui::CalcTextSize("8888").x; // 1991 + default: return 0; } } @@ -854,17 +816,58 @@ inline void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeF } } +static const ImPlotTimeFmt TimeFormatLevel0[ImPlotTimeUnit_COUNT] = { + ImPlotTimeFmt_Us, + ImPlotTimeFmt_SMs, + ImPlotTimeFmt_S, + ImPlotTimeFmt_HrMin, + ImPlotTimeFmt_Hr, + ImPlotTimeFmt_DayMo, + ImPlotTimeFmt_Mo, + ImPlotTimeFmt_Yr +}; + +static const ImPlotTimeFmt TimeFormatLevel1[ImPlotTimeUnit_COUNT] = { + ImPlotTimeFmt_HrMin, + ImPlotTimeFmt_HrMinS, + ImPlotTimeFmt_HrMin, + ImPlotTimeFmt_HrMin, + ImPlotTimeFmt_DayMo, + ImPlotTimeFmt_DayMo, + ImPlotTimeFmt_Yr, + ImPlotTimeFmt_Yr +}; + +static const ImPlotTimeFmt TimeFormatLevel1First[ImPlotTimeUnit_COUNT] = { + ImPlotTimeFmt_DayMoYrHrMinS, + ImPlotTimeFmt_DayMoYrHrMinS, + ImPlotTimeFmt_DayMoYrHrMin, + ImPlotTimeFmt_DayMoYrHrMin, + ImPlotTimeFmt_DayMoYr, + ImPlotTimeFmt_DayMoYr, + ImPlotTimeFmt_Yr, + ImPlotTimeFmt_Yr +}; + void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollection& ticks) { // get units for level 0 and level 1 labels const ImPlotTimeUnit unit0 = GetUnitForRange(range.Size() / (plot_width / 100)); // level = 0 (top) const ImPlotTimeUnit unit1 = unit0 + 1; // level = 1 (bottom) + + // get time format specs + const ImPlotTimeFmt fmt0 = TimeFormatLevel0[unit0]; + const ImPlotTimeFmt fmt1 = TimeFormatLevel1[unit1]; + const ImPlotTimeFmt fmtf = TimeFormatLevel1First[unit1]; + // maximum allowable density of labels - const float max_density = 0.66f; + const float max_density = 0.5f; + bool first = true; + const char* last_major = NULL; if (unit0 != ImPlotTimeUnit_Yr) { // pixels per major (level 1) division const float pix_per_major_div = plot_width / (float)(range.Size() / TimeUnitSpans[unit1]); // nominal pixels taken up by minor (level 0) label - const float minor_label_width = GetTimeLabelWidth(TimeFormatLevel0[unit0]); + const float minor_label_width = GetTimeLabelWidth(fmt0); // the maximum number of minor (level 0) labels that can fit between major (level 1) divisions const int minor_per_major = (int)(max_density * pix_per_major_div / minor_label_width); // the minor step size (level 0) @@ -873,14 +876,25 @@ void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollecti double t1 = FloorTime(range.Min, unit1); while (t1 < range.Max) { if (range.Contains(t1)) { - ImPlotTick tick_maj(t1,true,true); - tick_maj.Level = 1; - LabelTickTime(tick_maj,ticks.Labels,TimeFormatLevel1[unit1]); - ticks.AddTick(tick_maj); + // minor level 0 tick ImPlotTick tick_min(t1,true,true); tick_min.Level = 0; - LabelTickTime(tick_min,ticks.Labels,TimeFormatLevel0[unit0]); + LabelTickTime(tick_min,ticks.Labels,fmt0); ticks.AddTick(tick_min); + // major level 1 tick + ImPlotTick tick_maj(t1,true,true); + tick_maj.Level = 1; + LabelTickTime(tick_maj,ticks.Labels,first ? fmtf : fmt1); + const char* this_major = ticks.Labels.Buf.Data + tick_maj.BufferOffset; + if (last_major == NULL) + last_major = this_major; + else if (strcmp(last_major,this_major) == 0) + tick_maj.ShowLabel = false; + else + last_major = this_major; + ticks.AddTick(tick_maj); + + if (first) first = false; } // add minor ticks up until next major const double t2 = AddTime(t1, unit1, 1); @@ -892,8 +906,15 @@ void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollecti if (range.Contains(t12)) { ImPlotTick tick(t12,false,px_to_t2 >= minor_label_width); tick.Level = 0; - LabelTickTime(tick,ticks.Labels,TimeFormatLevel0[unit0]); + LabelTickTime(tick,ticks.Labels,fmt0); ticks.AddTick(tick); + // if (first) { + // ImPlotTick tick_maj(t12,true,true); + // tick_maj.Level = 1; + // LabelTickTime(tick_maj,ticks.Labels,TimeFormatLevel1[unit1]); + // ticks.AddTick(tick_maj); + // first = false; + // } } t12 = AddTime(t12, unit0, step); } diff --git a/implot_internal.h b/implot_internal.h index 7198021..db486ee 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -170,30 +170,33 @@ enum ImPlotScale_ { ImPlotScale_LogLog // log x, log y }; -enum ImPlotTimeUnit_ { // primary - ImPlotTimeUnit_Us, // microsecond :29.428552 - ImPlotTimeUnit_Ms, // millisecond :29.428 - ImPlotTimeUnit_S, // second :29 - ImPlotTimeUnit_Min, // minute 7:21pm - ImPlotTimeUnit_Hr, // hour 7pm - ImPlotTimeUnit_Day, // day 10/3 - ImPlotTimeUnit_Mo, // month Oct - ImPlotTimeUnit_Yr, // year 1991 +enum ImPlotTimeUnit_ { + ImPlotTimeUnit_Us, // microsecond + ImPlotTimeUnit_Ms, // millisecond + ImPlotTimeUnit_S, // second + ImPlotTimeUnit_Min, // minute + ImPlotTimeUnit_Hr, // hour + ImPlotTimeUnit_Day, // day + ImPlotTimeUnit_Mo, // month + ImPlotTimeUnit_Yr, // year ImPlotTimeUnit_COUNT }; enum ImPlotTimeFmt_ { - ImPlotTimeFmt_SUs, // :29.428552 - ImPlotTimeFmt_SMs, // :29.428 - ImPlotTimeFmt_S, // :29 - ImPlotTimeFmt_HrMin, // 7:21pm - ImPlotTimeFmt_Hr, // 7pm - ImPlotTimeFmt_DayMo, // 10/3 - ImPlotTimeFmt_DayMoHrMin, // 10/3 7:21pm - ImPlotTimeFmt_DayMoYr, // 10/3/1991 - ImPlotTimeFmt_DayMoYrHrMin, // 10/3/1991 7:21pm - ImPlotTimeFmt_Mo, // Oct - ImPlotTimeFmt_Yr // 1991 + ImPlotTimeFmt_Us, // .428552 + ImPlotTimeFmt_SUs, // :29.428552 + ImPlotTimeFmt_SMs, // :29.428 + ImPlotTimeFmt_S, // :29 + ImPlotTimeFmt_HrMinS, // 7:21:29pm + ImPlotTimeFmt_HrMin, // 7:21pm + ImPlotTimeFmt_Hr, // 7pm + ImPlotTimeFmt_DayMo, // 10/3 + ImPlotTimeFmt_DayMoHrMin, // 10/3 7:21pm + ImPlotTimeFmt_DayMoYr, // 10/3/91 + ImPlotTimeFmt_DayMoYrHrMin, // 10/3/91 7:21pm + ImPlotTimeFmt_DayMoYrHrMinS, // 10/3/91 7:21:29pm + ImPlotTimeFmt_Mo, // Oct + ImPlotTimeFmt_Yr // 1991 }; //----------------------------------------------------------------------------- @@ -570,6 +573,17 @@ struct ImPlotAxisScale } }; +/// Two part time struct. +struct ImPlotTime { + time_t S; + time_t Us; + ImPlotTime(time_t s, time_t us) { S = s; Us = us;} + ImPlotTime(double t) { + S = (time_t)t; + Us = (int)(t * 1000000 - floor(t) * 1000000); + } +}; + //----------------------------------------------------------------------------- // [SECTION] Internal API // No guarantee of forward compatibility here! @@ -771,7 +785,7 @@ int GetYear(double t); double MakeYear(int year); // Formates a timestamp t into a buffer according to fmt. -void FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt); +int FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt); // Prints a timestamp to console void PrintTime(double t, ImPlotTimeFmt fmt); From d3ea373cc7d2eb90f47f4b0c0a44ac88c0418e10 Mon Sep 17 00:00:00 2001 From: epezent Date: Sat, 5 Sep 2020 12:25:44 -0500 Subject: [PATCH 10/16] convert to ImPlotTime usage --- implot.cpp | 146 ++++++++++++++++++++++------------------------ implot_demo.cpp | 65 ++++++++++++--------- implot_internal.h | 69 ++++++++++++++-------- 3 files changed, 152 insertions(+), 128 deletions(-) diff --git a/implot.cpp b/implot.cpp index 83281fe..200c5bd 100644 --- a/implot.cpp +++ b/implot.cpp @@ -617,9 +617,9 @@ inline int LowerBoundStep(int max_divs, const int* divs, const int* step, int si inline int GetTimeStep(int max_divs, ImPlotTimeUnit unit) { if (unit == ImPlotTimeUnit_Ms || unit == ImPlotTimeUnit_Us) { - static const int step[] = {500,250,200,100,50,25,20,10,5}; - static const int divs[] = {2,4,5,10,20,40,50,100,200}; - return LowerBoundStep(max_divs, divs, step, 9); + static const int step[] = {500,250,200,100,50,25,20,10,5,2,1}; + static const int divs[] = {2,4,5,10,20,40,50,100,200,500,1000}; + return LowerBoundStep(max_divs, divs, step, 11); } if (unit == ImPlotTimeUnit_S || unit == ImPlotTimeUnit_Min) { static const int step[] = {30,15,10,5,1}; @@ -670,63 +670,63 @@ inline tm* GetGmTime(const time_t* time, tm* tm) #endif } -double AddTime(double t, ImPlotTimeUnit unit, int count) { +ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count) { + ImPlotTime t_out = t; switch(unit) { - case ImPlotTimeUnit_Us: return t + count * 0.000001; - case ImPlotTimeUnit_Ms: return t + count * 0.001; - case ImPlotTimeUnit_S: return t + count; - case ImPlotTimeUnit_Min: return t + count * 60; - case ImPlotTimeUnit_Hr: return t + count * 3600; - case ImPlotTimeUnit_Day: return t + count * 86400; + case ImPlotTimeUnit_Us: return ImPlotTime(t.S, t.Us + count); + case ImPlotTimeUnit_Ms: return ImPlotTime(t.S, t.Us + count * 1000); + case ImPlotTimeUnit_S: t_out.S += count; break; + case ImPlotTimeUnit_Min: t_out.S += count * 60; break; + case ImPlotTimeUnit_Hr: t_out.S += count * 3600; break; + case ImPlotTimeUnit_Day: t_out.S += count * 86400; break; case ImPlotTimeUnit_Yr: count *= 12; // fall-through case ImPlotTimeUnit_Mo: for (int i = 0; i < count; ++i) { - time_t s = (time_t)t; - GetGmTime(&s, &GImPlot->Tm); + GetGmTime(&t.S, &GImPlot->Tm); int days = GetDaysInMonth(GImPlot->Tm.tm_year, GImPlot->Tm.tm_mon); - t = AddTime(t, ImPlotTimeUnit_Day, days); + t_out = AddTime(t_out, ImPlotTimeUnit_Day, days); } - return t; - default: return t; + break; + default: break; } + return t_out; } -double FloorTime(double t, ImPlotTimeUnit unit) { - time_t s = (time_t)t; - GetGmTime(&s, &GImPlot->Tm); +ImPlotTime FloorTime(const ImPlotTime& t, ImPlotTimeUnit unit) { + GetGmTime(&t.S, &GImPlot->Tm); GImPlot->Tm.tm_isdst = -1; switch (unit) { - case ImPlotTimeUnit_S: return (double)s; - case ImPlotTimeUnit_Ms: return floor(t * 1000) / 1000; - case ImPlotTimeUnit_Us: return floor(t * 1000000) / 1000000; + case ImPlotTimeUnit_S: return ImPlotTime(t.S, 0); + case ImPlotTimeUnit_Ms: return ImPlotTime(t.S, (t.Us / 1000) * 1000); + case ImPlotTimeUnit_Us: return t; case ImPlotTimeUnit_Yr: GImPlot->Tm.tm_mon = 0; // fall-through case ImPlotTimeUnit_Mo: GImPlot->Tm.tm_mday = 1; // fall-through case ImPlotTimeUnit_Day: GImPlot->Tm.tm_hour = 0; // fall-through case ImPlotTimeUnit_Hr: GImPlot->Tm.tm_min = 0; // fall-through case ImPlotTimeUnit_Min: GImPlot->Tm.tm_sec = 0; break; - default: return t; + default: return t; } - s = MakeGmTime(&GImPlot->Tm); - return (double)s; + return ImPlotTime(MakeGmTime(&GImPlot->Tm),0); } -double CeilTime(double t, ImPlotTimeUnit unit) { +ImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit) { return AddTime(FloorTime(t, unit), unit, 1); } -double RoundTime(double t, ImPlotTimeUnit unit) { - double t1 = FloorTime(t, unit); - double t2 = AddTime(t1,unit,1); - return t - t1 < t2 - t ? t1 : t2; +ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit) { + ImPlotTime t1 = FloorTime(t, unit); + ImPlotTime t2 = AddTime(t1,unit,1); + if (t1.S == t2.S) + return t.Us - t1.Us < t2.Us - t.Us ? t1 : t2; + return t.S - t1.S < t2.S - t.S ? t1 : t2; } -int GetYear(double t) { - time_t s = (time_t)t; +int GetYear(const ImPlotTime& t) { tm& Tm = GImPlot->Tm; - GetGmTime(&s, &Tm); + GetGmTime(&t.S, &Tm); return Tm.tm_year + 1900; } -double MakeYear(int year) { +ImPlotTime MakeYear(int year) { int yr = year - 1900; if (yr < 0) yr = 0; @@ -739,17 +739,13 @@ double MakeYear(int year) { return (double)s; } -int FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) { - printf("%.6f\n",t); - time_t s = (time_t)t; - int ms = (int)(t * 1000 - floor(t) * 1000); - ms = ms % 10 == 9 ? ms + 1 : ms; // don't like this - int us = (int)(t * 1000000 - floor(t) * 1000000); - us = us % 10 == 9 ? us + 1 : us; // don't like this +int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt) { tm& Tm = GImPlot->Tm; - GetGmTime(&s, &Tm); + GetGmTime(&t.S, &Tm); const char* ap = Tm.tm_hour < 12 ? "am" : "pm"; + const int us = t.Us % 1000; + const int ms = t.Us / 1000; const int sec = Tm.tm_sec; const int min = Tm.tm_min; const int hr = (Tm.tm_hour == 0 || Tm.tm_hour == 12) ? 12 : Tm.tm_hour % 12; @@ -761,8 +757,8 @@ int FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) { static const char mnames[12][4] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; switch(fmt) { - case ImPlotTimeFmt_Us: return snprintf(buffer, size, ".%06d", us); - case ImPlotTimeFmt_SUs: return snprintf(buffer, size, ":%02d.%06d", sec, us); + case ImPlotTimeFmt_Us: return snprintf(buffer, size, ".%03d %03d", ms, us); + case ImPlotTimeFmt_SUs: return snprintf(buffer, size, ":%02d.%03d %03d", sec, ms, us); case ImPlotTimeFmt_SMs: return snprintf(buffer, size, ":%02d.%03d", sec, ms); case ImPlotTimeFmt_S: return snprintf(buffer, size, ":%02d", sec); case ImPlotTimeFmt_HrMinS: return snprintf(buffer, size, "%d:%02d:%02d%s", hr, min, sec, ap); @@ -773,6 +769,7 @@ int FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) { case ImPlotTimeFmt_DayMoYr: return snprintf(buffer, size, "%d/%d/%d", mon, day, yr); case ImPlotTimeFmt_DayMoYrHrMin: return snprintf(buffer, size, "%d/%d/%d %u:%02d%s", mon, day, yr, hr, min, ap); case ImPlotTimeFmt_DayMoYrHrMinS: return snprintf(buffer, size, "%d/%d/%d %u:%02d:%02d%s", mon, day, yr, hr, min, sec, ap); + case ImPlotTimeFmt_MoYr: return snprintf(buffer, size, "%s %d", mnames[Tm.tm_mon], year); case ImPlotTimeFmt_Mo: return snprintf(buffer, size, "%s", mnames[Tm.tm_mon]); case ImPlotTimeFmt_Yr: return snprintf(buffer, size, "%d", year); default: return 0; @@ -788,8 +785,8 @@ void PrintTime(double t, ImPlotTimeFmt fmt) { // Returns the nominally largest possible width for a time format inline float GetTimeLabelWidth(ImPlotTimeFmt fmt) { switch (fmt) { - case ImPlotTimeFmt_Us: return ImGui::CalcTextSize(".888888").x; // .428552 - case ImPlotTimeFmt_SUs: return ImGui::CalcTextSize(":88.888888").x; // :29.428552 + case ImPlotTimeFmt_Us: return ImGui::CalcTextSize(".888 888").x; // .428 552 + case ImPlotTimeFmt_SUs: return ImGui::CalcTextSize(":88.888 888").x; // :29.428 552 case ImPlotTimeFmt_SMs: return ImGui::CalcTextSize(":88.888").x; // :29.428 case ImPlotTimeFmt_S: return ImGui::CalcTextSize(":88").x; // :29 case ImPlotTimeFmt_HrMinS: return ImGui::CalcTextSize("88:88:88pm").x; // 7:21:29pm @@ -800,6 +797,7 @@ inline float GetTimeLabelWidth(ImPlotTimeFmt fmt) { case ImPlotTimeFmt_DayMoYr: return ImGui::CalcTextSize("88/88/88").x; // 10/3/1991 case ImPlotTimeFmt_DayMoYrHrMin: return ImGui::CalcTextSize("88/88/88 88:88pm").x; // 10/3/91 7:21pm case ImPlotTimeFmt_DayMoYrHrMinS: return ImGui::CalcTextSize("88/88/88 88:88:88pm").x; // 10/3/91 7:21:29pm + case ImPlotTimeFmt_MoYr: return ImGui::CalcTextSize("MMM 8888").x; // Oct 1991 case ImPlotTimeFmt_Mo: return ImGui::CalcTextSize("MMM").x; // Oct case ImPlotTimeFmt_Yr: return ImGui::CalcTextSize("8888").x; // 1991 default: return 0; @@ -834,7 +832,7 @@ static const ImPlotTimeFmt TimeFormatLevel1[ImPlotTimeUnit_COUNT] = { ImPlotTimeFmt_HrMin, ImPlotTimeFmt_DayMo, ImPlotTimeFmt_DayMo, - ImPlotTimeFmt_Yr, + ImPlotTimeFmt_MoYr, ImPlotTimeFmt_Yr }; @@ -845,7 +843,7 @@ static const ImPlotTimeFmt TimeFormatLevel1First[ImPlotTimeUnit_COUNT] = { ImPlotTimeFmt_DayMoYrHrMin, ImPlotTimeFmt_DayMoYr, ImPlotTimeFmt_DayMoYr, - ImPlotTimeFmt_Yr, + ImPlotTimeFmt_MoYr, ImPlotTimeFmt_Yr }; @@ -859,10 +857,16 @@ void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollecti const ImPlotTimeFmt fmt1 = TimeFormatLevel1[unit1]; const ImPlotTimeFmt fmtf = TimeFormatLevel1First[unit1]; + const ImPlotTime t_min(range.Min); + const ImPlotTime t_max(range.Max); + // maximum allowable density of labels const float max_density = 0.5f; + + // book keeping bool first = true; const char* last_major = NULL; + if (unit0 != ImPlotTimeUnit_Yr) { // pixels per major (level 1) division const float pix_per_major_div = plot_width / (float)(range.Size() / TimeUnitSpans[unit1]); @@ -873,16 +877,17 @@ void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollecti // the minor step size (level 0) const int step = GetTimeStep(minor_per_major, unit0); // generate ticks - double t1 = FloorTime(range.Min, unit1); - while (t1 < range.Max) { - if (range.Contains(t1)) { + ImPlotTime t1 = FloorTime(ImPlotTime(range.Min), unit1); + t1 = t1; + while (t1 < t_max) { + if (t1 >= t_min && t1 <= t_max) { // minor level 0 tick - ImPlotTick tick_min(t1,true,true); + ImPlotTick tick_min(t1.ToDouble(),true,true); tick_min.Level = 0; LabelTickTime(tick_min,ticks.Labels,fmt0); ticks.AddTick(tick_min); // major level 1 tick - ImPlotTick tick_maj(t1,true,true); + ImPlotTick tick_maj(t1.ToDouble(),true,true); tick_maj.Level = 1; LabelTickTime(tick_maj,ticks.Labels,first ? fmtf : fmt1); const char* this_major = ticks.Labels.Buf.Data + tick_maj.BufferOffset; @@ -897,14 +902,13 @@ void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollecti if (first) first = false; } // add minor ticks up until next major - const double t2 = AddTime(t1, unit1, 1); - const ImPlotRange r12(t1,t2); - if (minor_per_major > 1 && RangesOverlap(range,r12)) { - double t12 = AddTime(t1, unit0, step); + const ImPlotTime t2 = AddTime(t1, unit1, 1); + if (minor_per_major > 1 && (t_min <= t2 && t1 <= t_max)) { + ImPlotTime t12 = AddTime(t1, unit0, step); while (t12 < t2) { - float px_to_t2 = (float)((t2 - t12)/range.Size()) * plot_width; - if (range.Contains(t12)) { - ImPlotTick tick(t12,false,px_to_t2 >= minor_label_width); + float px_to_t2 = (float)((t2 - t12).ToDouble()/range.Size()) * plot_width; + if (t12 >= t_min && t12 <= t_max) { + ImPlotTick tick(t12.ToDouble(),false,px_to_t2 >= minor_label_width); tick.Level = 0; LabelTickTime(tick,ticks.Labels,fmt0); ticks.AddTick(tick); @@ -931,10 +935,10 @@ void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollecti const double interval = NiceNum(nice_range / (max_labels - 1), true); const int graphmin = (int)(floor(year_min / interval) * interval); const int step = (int)interval <= 0 ? 1 : (int)interval; - double t1 = MakeYear(graphmin); + ImPlotTime t1 = MakeYear(graphmin); while (t1 < range.Max) { - if (range.Contains(t1)) { - ImPlotTick tick(t1, true, true); + if (t1 >= t_min && t1 <= t_max) { + ImPlotTick tick(t1.ToDouble(), true, true); tick.Level = 0; LabelTickTime(tick, ticks.Labels, TimeFormatLevel0[ImPlotTimeUnit_Yr]); ticks.AddTick(tick); @@ -1028,18 +1032,14 @@ 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) { - plot.XAxis.Range = gp.NextPlotData.X; - plot.XAxis.Constrain(); - } + if (just_created || gp.NextPlotData.XRangeCond == ImGuiCond_Always) + plot.XAxis.SetRange(gp.NextPlotData.X); } for (int i = 0; i < IMPLOT_Y_AXES; i++) { 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(); - } + if (just_created || gp.NextPlotData.YRangeCond[i] == ImGuiCond_Always) + plot.YAxis[i].SetRange(gp.NextPlotData.Y[i]); } } @@ -1236,7 +1236,6 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.XAxis.SetMin(gp.X.Invert ? plot_br.x : plot_tl.x); if (!gp.X.LockMax) 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) { @@ -1246,7 +1245,6 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.YAxis[i].SetMin(gp.Y[i].Invert ? plot_tl.y : plot_br.y); if (!gp.Y[i].LockMax) 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. @@ -1301,7 +1299,6 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.XAxis.SetMin(gp.X.Invert ? plot_br.x : plot_tl.x); if (!gp.X.LockMax) 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) { @@ -1312,7 +1309,6 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.YAxis[i].SetMin(gp.Y[i].Invert ? plot_tl.y : plot_br.y); if (!gp.Y[i].LockMax) plot.YAxis[i].SetMax(gp.Y[i].Invert ? plot_br.y : plot_tl.y); - // ConstrainAxis(plot.YAxis[i]); } } } @@ -1977,7 +1973,6 @@ void EndPlot() { plot.XAxis.Range.Min -= FLT_EPSILON; } plot.XAxis.Constrain(); - // ConstrainAxis(plot.XAxis); for (int i = 0; i < IMPLOT_Y_AXES; i++) { if (gp.FitY[i] && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LockMin) && !ImNanOrInf(gp.ExtentsY[i].Min)) { plot.YAxis[i].Range.Min = (gp.ExtentsY[i].Min); @@ -1990,7 +1985,6 @@ void EndPlot() { plot.YAxis[i].Range.Min -= FLT_EPSILON; } plot.YAxis[i].Constrain(); - // ConstrainAxis(plot.YAxis[i]); } } diff --git a/implot_demo.cpp b/implot_demo.cpp index c4308e4..5252aba 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -1271,7 +1271,8 @@ void StyleSeaborn() { namespace MyImPlot { -int BinarySearch(const double* arr, int l, int r, double x) { +template +int BinarySearch(const T* arr, int l, int r, T x) { if (r >= l) { int mid = l + (r - l) / 2; if (arr[mid] == x) @@ -1284,6 +1285,39 @@ int BinarySearch(const double* arr, int l, int r, double x) { } void PlotCandlestick(const char* label_id, const double* xs, const double* opens, const double* closes, const double* lows, const double* highs, int count, bool tooltip, float width_percent, ImVec4 bullCol, ImVec4 bearCol) { + + // get ImGui window DrawList + ImDrawList* draw_list = ImPlot::GetPlotDrawList(); + // calc real value width + double half_width = count > 1 ? (xs[1] - xs[0]) * width_percent : width_percent; + + // custom tool + if (ImPlot::IsPlotHovered() && tooltip) { + ImPlotPoint mouse = ImPlot::GetPlotMousePos(); + mouse.x = ImPlot::RoundTime(ImPlotTime(mouse.x), ImPlotTimeUnit_Day).ToDouble(); + float tool_l = ImPlot::PlotToPixels(mouse.x - half_width * 1.5, mouse.y).x; + float tool_r = ImPlot::PlotToPixels(mouse.x + half_width * 1.5, mouse.y).x; + float tool_t = ImPlot::GetPlotPos().y; + float tool_b = tool_t + ImPlot::GetPlotSize().y; + ImPlot::PushPlotClipRect(); + draw_list->AddRectFilled(ImVec2(tool_l, tool_t), ImVec2(tool_r, tool_b), IM_COL32(128,128,128,64)); + ImPlot::PopPlotClipRect(); + // find mouse location index + int idx = BinarySearch(xs, 0, count - 1, mouse.x); + // render tool tip (won't be affected by plot clip rect) + if (idx != -1) { + ImGui::BeginTooltip(); + char buff[32]; + ImPlot::FormatTime(xs[idx],buff,32,ImPlotTimeFmt_DayMoYr); + ImGui::Text("Day: %s", buff); + ImGui::Text("Open: $%.2f", opens[idx]); + ImGui::Text("Close: $%.2f", closes[idx]); + ImGui::Text("Low: $%.2f", lows[idx]); + ImGui::Text("High: $%.2f", highs[idx]); + ImGui::EndTooltip(); + } + } + // begin plot item if (ImPlot::BeginItem(label_id)) { // override legend icon color @@ -1295,10 +1329,6 @@ void PlotCandlestick(const char* label_id, const double* xs, const double* opens ImPlot::FitPoint(ImPlotPoint(xs[i], highs[i])); } } - // get ImGui window DrawList - ImDrawList* draw_list = ImPlot::GetPlotDrawList(); - // calc real value width - double half_width = count > 1 ? (xs[1] - xs[0]) * width_percent : width_percent; // render data for (int i = 0; i < count; ++i) { ImVec2 open_pos = ImPlot::PlotToPixels(xs[i] - half_width, opens[i]); @@ -1309,30 +1339,7 @@ void PlotCandlestick(const char* label_id, const double* xs, const double* opens draw_list->AddLine(low_pos, high_pos, color); draw_list->AddRectFilled(open_pos, close_pos, color); } - // custom tool - if (ImPlot::IsPlotHovered() && tooltip) { - ImPlotPoint mouse = ImPlot::GetPlotMousePos(); - mouse.x = ImPlot::RoundTime(mouse.x, ImPlotTimeUnit_Day); - float tool_l = ImPlot::PlotToPixels(mouse.x - half_width * 1.5, mouse.y).x; - float tool_r = ImPlot::PlotToPixels(mouse.x + half_width * 1.5, mouse.y).x; - float tool_t = ImPlot::GetPlotPos().y; - float tool_b = tool_t + ImPlot::GetPlotSize().y; - draw_list->AddRectFilled(ImVec2(tool_l, tool_t), ImVec2(tool_r, tool_b), IM_COL32(0,255,255,64)); - // find mouse location index - int idx = BinarySearch(xs, 0, count - 1, mouse.x); - // render tool tip (won't be affected by plot clip rect) - if (idx != -1) { - ImGui::BeginTooltip(); - char buff[32]; - ImPlot::FormatTime(xs[idx],buff,32,ImPlotTimeFmt_DayMoYr); - ImGui::Text("Day: %s", buff); - ImGui::Text("Open: $%.2f", opens[idx]); - ImGui::Text("Close: $%.2f", closes[idx]); - ImGui::Text("Low: $%.2f", lows[idx]); - ImGui::Text("High: $%.2f", highs[idx]); - ImGui::EndTooltip(); - } - } + // end plot item ImPlot::EndItem(); } diff --git a/implot_internal.h b/implot_internal.h index db486ee..e041209 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -195,6 +195,7 @@ enum ImPlotTimeFmt_ { ImPlotTimeFmt_DayMoYr, // 10/3/91 ImPlotTimeFmt_DayMoYrHrMin, // 10/3/91 7:21pm ImPlotTimeFmt_DayMoYrHrMinS, // 10/3/91 7:21:29pm + ImPlotTimeFmt_MoYr, // Oct 1991 ImPlotTimeFmt_Mo, // Oct ImPlotTimeFmt_Yr // 1991 }; @@ -307,11 +308,8 @@ struct ImPlotAxis _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 (ImHasFlag(Flags, ImPlotAxisFlags_Time)) + _min = ImConstrainTime(_min); if (_min >= Range.Max) return false; Range.Min = _min; @@ -322,17 +320,24 @@ struct ImPlotAxis _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 (ImHasFlag(Flags, ImPlotAxisFlags_Time)) + _max = ImConstrainTime(_max); if (_max <= Range.Min) return false; Range.Max = _max; return true; }; + void SetRange(double _min, double _max) { + Range.Min = _min; + Range.Max = _max; + Constrain(); + } + + void SetRange(const ImPlotRange& range) { + SetRange(range.Min, range.Max); + } + void Constrain() { Range.Min = ImConstrainNan(ImConstrainInf(Range.Min)); Range.Max = ImConstrainNan(ImConstrainInf(Range.Max)); @@ -343,8 +348,6 @@ struct ImPlotAxis 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; @@ -576,14 +579,33 @@ struct ImPlotAxisScale /// Two part time struct. struct ImPlotTime { time_t S; - time_t Us; - ImPlotTime(time_t s, time_t us) { S = s; Us = us;} + int Us; + ImPlotTime(time_t s, int us) { + S = s + us / 1000000; + Us = us % 1000000; + } ImPlotTime(double t) { S = (time_t)t; Us = (int)(t * 1000000 - floor(t) * 1000000); } + double ToDouble() const { return (double)S + (double)Us / 1000000.0; } }; +static inline ImPlotTime operator+(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return ImPlotTime(lhs.S + rhs.S, lhs.Us + rhs.Us); } +static inline ImPlotTime operator-(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return ImPlotTime(lhs.S - rhs.S, lhs.Us - rhs.Us); } +static inline bool operator==(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return lhs.S == rhs.S && lhs.Us == rhs.Us; } +static inline bool operator<(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return lhs.S == rhs.S ? lhs.Us < rhs.Us : lhs.S < rhs.S; } +static inline bool operator>(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return rhs < lhs; } +static inline bool operator<=(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return lhs < rhs || lhs == rhs; } +static inline bool operator>=(const ImPlotTime& lhs, const ImPlotTime& rhs) +{ return lhs > rhs || lhs == rhs; } + //----------------------------------------------------------------------------- // [SECTION] Internal API // No guarantee of forward compatibility here! @@ -653,7 +675,8 @@ inline bool FitThisFrame() { return GImPlot->FitThisFrame; } void FitPoint(const ImPlotPoint& p); // Returns true if two ranges overlap -inline bool RangesOverlap(const ImPlotRange& r1, const ImPlotRange& r2) { return r1.Min <= r2.Max && r2.Min <= r1.Max; } +inline bool RangesOverlap(const ImPlotRange& r1, const ImPlotRange& r2) +{ return r1.Min <= r2.Max && r2.Min <= r1.Max; } //----------------------------------------------------------------------------- // [SECTION] Legend Utils @@ -771,23 +794,23 @@ inline int GetDaysInMonth(int year, int month) { } // Adds time to a timestamp. #count must be positive! -double AddTime(double t, ImPlotTimeUnit unit, int count); +ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count); // Rounds a timestamp down to nearest. -double FloorTime(double t, ImPlotTimeUnit unit); +ImPlotTime FloorTime(const ImPlotTime& t, ImPlotTimeUnit unit); // Rounds a timestamp up to the nearest unit. -double CeilTime(double t, ImPlotTimeUnit unit); +ImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit); // Rounds a timestamp up or down to the nearest unit. -double RoundTime(double t, ImPlotTimeUnit unit); +ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit); // Get year from timestamp -int GetYear(double t); +int GetYear(const ImPlotTime& t); // Make a timestamp starting at the first day of a year -double MakeYear(int year); +ImPlotTime MakeYear(int year); // Formates a timestamp t into a buffer according to fmt. -int FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt); +int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt); // Prints a timestamp to console -void PrintTime(double t, ImPlotTimeFmt fmt); +void PrintTime(const ImPlotTime& t, ImPlotTimeFmt fmt); //----------------------------------------------------------------------------- // [SECTION] Internal / Experimental Plotters From defc52921924a45d0525be1e5c514c659bcdf0ba Mon Sep 17 00:00:00 2001 From: epezent Date: Sun, 6 Sep 2020 00:06:51 -0500 Subject: [PATCH 11/16] time-axes complete --- implot.cpp | 233 +++++++++++++++++++++++++++------------------- implot_demo.cpp | 29 ++---- implot_internal.h | 28 ++++-- 3 files changed, 164 insertions(+), 126 deletions(-) diff --git a/implot.cpp b/implot.cpp index 200c5bd..18121b9 100644 --- a/implot.cpp +++ b/implot.cpp @@ -671,23 +671,30 @@ inline tm* GetGmTime(const time_t* time, tm* tm) } ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count) { + tm& Tm = GImPlot->Tm; ImPlotTime t_out = t; switch(unit) { - case ImPlotTimeUnit_Us: return ImPlotTime(t.S, t.Us + count); - case ImPlotTimeUnit_Ms: return ImPlotTime(t.S, t.Us + count * 1000); + case ImPlotTimeUnit_Us: t_out.Us += count; break; + case ImPlotTimeUnit_Ms: t_out.Us += count * 1000; break; case ImPlotTimeUnit_S: t_out.S += count; break; case ImPlotTimeUnit_Min: t_out.S += count * 60; break; case ImPlotTimeUnit_Hr: t_out.S += count * 3600; break; case ImPlotTimeUnit_Day: t_out.S += count * 86400; break; - case ImPlotTimeUnit_Yr: count *= 12; // fall-through - case ImPlotTimeUnit_Mo: for (int i = 0; i < count; ++i) { - GetGmTime(&t.S, &GImPlot->Tm); - int days = GetDaysInMonth(GImPlot->Tm.tm_year, GImPlot->Tm.tm_mon); - t_out = AddTime(t_out, ImPlotTimeUnit_Day, days); + case ImPlotTimeUnit_Mo: for (int i = 0; i < count; ++i) { // this might have a bug + GetGmTime(&t.S, &Tm); + t_out.S += 86400 * GetDaysInMonth(Tm.tm_year + 1900, Tm.tm_mon); + } + break; + case ImPlotTimeUnit_Yr: for (int i = 0; i < count; ++i) { + if (IsLeapYear(GetYear(t_out))) + t_out.S += 366 * 86400; + else + t_out.S += 365 * 86400; } break; default: break; } + t_out.RollOver(); return t_out; } @@ -730,13 +737,17 @@ ImPlotTime MakeYear(int year) { int yr = year - 1900; if (yr < 0) yr = 0; - GImPlot->Tm = tm(); - GImPlot->Tm.tm_year = yr; - GImPlot->Tm.tm_sec = 1; - time_t s = MakeGmTime(&GImPlot->Tm); - if (s < 0) - s = 0; - return (double)s; + tm& Tm = GImPlot->Tm; + Tm.tm_sec = 0; + Tm.tm_min = 0; + Tm.tm_hour = 0; + Tm.tm_mday = 1; + Tm.tm_mon = 0; + Tm.tm_year = yr; + Tm.tm_sec = 0; + Tm.tm_isdst = -1; + time_t s = MakeGmTime(&Tm); + return ImPlotTime(s); } int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt) { @@ -763,12 +774,13 @@ int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt) { case ImPlotTimeFmt_S: return snprintf(buffer, size, ":%02d", sec); case ImPlotTimeFmt_HrMinS: return snprintf(buffer, size, "%d:%02d:%02d%s", hr, min, sec, ap); case ImPlotTimeFmt_HrMin: return snprintf(buffer, size, "%d:%02d%s", hr, min, ap); - case ImPlotTimeFmt_Hr: return snprintf(buffer, size, "%u%s", hr, ap); - case ImPlotTimeFmt_DayMo: return snprintf(buffer, size, "%d/%d", mon, day); - case ImPlotTimeFmt_DayMoHrMin: return snprintf(buffer, size, "%d/%d %u:%02d%s", mon, day, hr, min, ap); - case ImPlotTimeFmt_DayMoYr: return snprintf(buffer, size, "%d/%d/%d", mon, day, yr); - case ImPlotTimeFmt_DayMoYrHrMin: return snprintf(buffer, size, "%d/%d/%d %u:%02d%s", mon, day, yr, hr, min, ap); - case ImPlotTimeFmt_DayMoYrHrMinS: return snprintf(buffer, size, "%d/%d/%d %u:%02d:%02d%s", mon, day, yr, hr, min, sec, ap); + case ImPlotTimeFmt_Hr: return snprintf(buffer, size, "%d%s", hr, ap); + case ImPlotTimeFmt_DayMo: return snprintf(buffer, size, "%d/%d", mon, day); + case ImPlotTimeFmt_DayMoHr: return snprintf(buffer, size, "%d/%d %d%s", mon, day, hr, ap); + case ImPlotTimeFmt_DayMoHrMin: return snprintf(buffer, size, "%d/%d %d:%02d%s", mon, day, hr, min, ap); + case ImPlotTimeFmt_DayMoYr: return snprintf(buffer, size, "%d/%d/%02d", mon, day, yr); + case ImPlotTimeFmt_DayMoYrHrMin: return snprintf(buffer, size, "%d/%d/%02d %d:%02d%s", mon, day, yr, hr, min, ap); + case ImPlotTimeFmt_DayMoYrHrMinS: return snprintf(buffer, size, "%d/%d/%02d %d:%02d:%02d%s", mon, day, yr, hr, min, sec, ap); case ImPlotTimeFmt_MoYr: return snprintf(buffer, size, "%s %d", mnames[Tm.tm_mon], year); case ImPlotTimeFmt_Mo: return snprintf(buffer, size, "%s", mnames[Tm.tm_mon]); case ImPlotTimeFmt_Yr: return snprintf(buffer, size, "%d", year); @@ -776,7 +788,7 @@ int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt) { } } -void PrintTime(double t, ImPlotTimeFmt fmt) { +void PrintTime(const ImPlotTime& t, ImPlotTimeFmt fmt) { static char buff[32]; FormatTime(t, buff, 32, fmt); printf("%s\n",buff); @@ -791,8 +803,9 @@ inline float GetTimeLabelWidth(ImPlotTimeFmt fmt) { case ImPlotTimeFmt_S: return ImGui::CalcTextSize(":88").x; // :29 case ImPlotTimeFmt_HrMinS: return ImGui::CalcTextSize("88:88:88pm").x; // 7:21:29pm case ImPlotTimeFmt_HrMin: return ImGui::CalcTextSize("88:88pm").x; // 7:21pm - case ImPlotTimeFmt_Hr: return ImGui::CalcTextSize("8pm").x; // 7pm + case ImPlotTimeFmt_Hr: return ImGui::CalcTextSize("88pm").x; // 7pm case ImPlotTimeFmt_DayMo: return ImGui::CalcTextSize("88/88").x; // 10/3 + case ImPlotTimeFmt_DayMoHr: return ImGui::CalcTextSize("88/88 88pm").x; // 10/3 7:21pm case ImPlotTimeFmt_DayMoHrMin: return ImGui::CalcTextSize("88/88 88:88pm").x; // 10/3 7:21pm case ImPlotTimeFmt_DayMoYr: return ImGui::CalcTextSize("88/88/88").x; // 10/3/1991 case ImPlotTimeFmt_DayMoYrHrMin: return ImGui::CalcTextSize("88/88/88 88:88pm").x; // 10/3/91 7:21pm @@ -804,16 +817,23 @@ inline float GetTimeLabelWidth(ImPlotTimeFmt fmt) { } } -inline void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeFmt fmt) { +inline void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, const ImPlotTime& t, ImPlotTimeFmt fmt) { char temp[32]; if (tick.ShowLabel) { tick.BufferOffset = buffer.size(); - FormatTime(tick.PlotPos, temp, 32, fmt); + FormatTime(t, temp, 32, fmt); buffer.append(temp, temp + strlen(temp) + 1); tick.LabelSize = ImGui::CalcTextSize(buffer.Buf.Data + tick.BufferOffset); } } +inline bool TimeLabelSame(const char* l1, const char* l2) { + size_t len1 = strlen(l1); + size_t len2 = strlen(l2); + size_t n = len1 < len2 ? len1 : len2; + return strcmp(l1 + len1 - n, l2 + len2 - n) == 0; +} + static const ImPlotTimeFmt TimeFormatLevel0[ImPlotTimeUnit_COUNT] = { ImPlotTimeFmt_Us, ImPlotTimeFmt_SMs, @@ -830,9 +850,9 @@ static const ImPlotTimeFmt TimeFormatLevel1[ImPlotTimeUnit_COUNT] = { ImPlotTimeFmt_HrMinS, ImPlotTimeFmt_HrMin, ImPlotTimeFmt_HrMin, - ImPlotTimeFmt_DayMo, - ImPlotTimeFmt_DayMo, - ImPlotTimeFmt_MoYr, + ImPlotTimeFmt_DayMoYr, + ImPlotTimeFmt_DayMoYr, + ImPlotTimeFmt_Yr, ImPlotTimeFmt_Yr }; @@ -843,82 +863,86 @@ static const ImPlotTimeFmt TimeFormatLevel1First[ImPlotTimeUnit_COUNT] = { ImPlotTimeFmt_DayMoYrHrMin, ImPlotTimeFmt_DayMoYr, ImPlotTimeFmt_DayMoYr, - ImPlotTimeFmt_MoYr, + ImPlotTimeFmt_Yr, ImPlotTimeFmt_Yr }; +static const ImPlotTimeFmt TimeFormatMouseCursor[ImPlotTimeUnit_COUNT] = { + ImPlotTimeFmt_Us, + ImPlotTimeFmt_SUs, + ImPlotTimeFmt_SMs, + ImPlotTimeFmt_HrMinS, + ImPlotTimeFmt_HrMin, + ImPlotTimeFmt_DayMoHr, + ImPlotTimeFmt_DayMoYr, + ImPlotTimeFmt_MoYr +}; + void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollection& ticks) { // get units for level 0 and level 1 labels const ImPlotTimeUnit unit0 = GetUnitForRange(range.Size() / (plot_width / 100)); // level = 0 (top) const ImPlotTimeUnit unit1 = unit0 + 1; // level = 1 (bottom) - // get time format specs const ImPlotTimeFmt fmt0 = TimeFormatLevel0[unit0]; const ImPlotTimeFmt fmt1 = TimeFormatLevel1[unit1]; const ImPlotTimeFmt fmtf = TimeFormatLevel1First[unit1]; - - const ImPlotTime t_min(range.Min); - const ImPlotTime t_max(range.Max); - + // min max times + const ImPlotTime t_min = ImPlotTime::FromDouble(range.Min); + const ImPlotTime t_max = ImPlotTime::FromDouble(range.Max); // maximum allowable density of labels - const float max_density = 0.5f; - + const float max_density = 0.5f; // book keeping - bool first = true; - const char* last_major = NULL; - + const char* last_major = NULL; if (unit0 != ImPlotTimeUnit_Yr) { // pixels per major (level 1) division const float pix_per_major_div = plot_width / (float)(range.Size() / TimeUnitSpans[unit1]); - // nominal pixels taken up by minor (level 0) label - const float minor_label_width = GetTimeLabelWidth(fmt0); + // nominal pixels taken up by labels + const float fmt0_width = GetTimeLabelWidth(fmt0); + const float fmt1_width = GetTimeLabelWidth(fmt1); + const float fmtf_width = GetTimeLabelWidth(fmtf); // the maximum number of minor (level 0) labels that can fit between major (level 1) divisions - const int minor_per_major = (int)(max_density * pix_per_major_div / minor_label_width); + const int minor_per_major = (int)(max_density * pix_per_major_div / fmt0_width); // the minor step size (level 0) const int step = GetTimeStep(minor_per_major, unit0); // generate ticks - ImPlotTime t1 = FloorTime(ImPlotTime(range.Min), unit1); - t1 = t1; + ImPlotTime t1 = FloorTime(ImPlotTime::FromDouble(range.Min), unit1); while (t1 < t_max) { + // get next major + const ImPlotTime t2 = AddTime(t1, unit1, 1); + // add major tick if (t1 >= t_min && t1 <= t_max) { // minor level 0 tick ImPlotTick tick_min(t1.ToDouble(),true,true); tick_min.Level = 0; - LabelTickTime(tick_min,ticks.Labels,fmt0); + LabelTickTime(tick_min,ticks.Labels,t1,fmt0); ticks.AddTick(tick_min); // major level 1 tick ImPlotTick tick_maj(t1.ToDouble(),true,true); tick_maj.Level = 1; - LabelTickTime(tick_maj,ticks.Labels,first ? fmtf : fmt1); - const char* this_major = ticks.Labels.Buf.Data + tick_maj.BufferOffset; - if (last_major == NULL) - last_major = this_major; - else if (strcmp(last_major,this_major) == 0) - tick_maj.ShowLabel = false; - else - last_major = this_major; - ticks.AddTick(tick_maj); - - if (first) first = false; + LabelTickTime(tick_maj,ticks.Labels,t1, last_major == NULL ? fmtf : fmt1); + const char* this_major = ticks.Labels.Buf.Data + tick_maj.BufferOffset; + if (last_major && TimeLabelSame(last_major,this_major)) + tick_maj.ShowLabel = false; + last_major = this_major; + ticks.AddTick(tick_maj); } // add minor ticks up until next major - const ImPlotTime t2 = AddTime(t1, unit1, 1); if (minor_per_major > 1 && (t_min <= t2 && t1 <= t_max)) { ImPlotTime t12 = AddTime(t1, unit0, step); while (t12 < t2) { float px_to_t2 = (float)((t2 - t12).ToDouble()/range.Size()) * plot_width; if (t12 >= t_min && t12 <= t_max) { - ImPlotTick tick(t12.ToDouble(),false,px_to_t2 >= minor_label_width); + ImPlotTick tick(t12.ToDouble(),false,px_to_t2 >= fmt0_width); tick.Level = 0; - LabelTickTime(tick,ticks.Labels,fmt0); + LabelTickTime(tick,ticks.Labels,t12,fmt0); ticks.AddTick(tick); - // if (first) { - // ImPlotTick tick_maj(t12,true,true); - // tick_maj.Level = 1; - // LabelTickTime(tick_maj,ticks.Labels,TimeFormatLevel1[unit1]); - // ticks.AddTick(tick_maj); - // first = false; - // } + if (last_major == NULL && px_to_t2 >= fmt0_width && px_to_t2 >= (fmt1_width + fmtf_width) / 2) { + ImPlotTick tick_maj(t12.ToDouble(),true,true); + tick_maj.Level = 1; + LabelTickTime(tick_maj,ticks.Labels,t12,fmtf); + last_major = ticks.Labels.Buf.Data + tick_maj.BufferOffset; + ticks.AddTick(tick_maj); + } } t12 = AddTime(t12, unit0, step); } @@ -929,22 +953,23 @@ void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollecti else { const float label_width = GetTimeLabelWidth(TimeFormatLevel0[ImPlotTimeUnit_Yr]); const int max_labels = (int)(max_density * plot_width / label_width); - const int year_min = GetYear(range.Min); - const int year_max = GetYear(CeilTime(range.Max, ImPlotTimeUnit_Yr)); + const int year_min = GetYear(t_min); + const int year_max = GetYear(CeilTime(t_max, ImPlotTimeUnit_Yr)); const double nice_range = NiceNum((year_max - year_min)*0.99,false); const double interval = NiceNum(nice_range / (max_labels - 1), true); const int graphmin = (int)(floor(year_min / interval) * interval); + const int graphmax = (int)(ceil(year_max / interval) * interval); const int step = (int)interval <= 0 ? 1 : (int)interval; - ImPlotTime t1 = MakeYear(graphmin); - while (t1 < range.Max) { - if (t1 >= t_min && t1 <= t_max) { - ImPlotTick tick(t1.ToDouble(), true, true); + + for (int y = graphmin; y < graphmax; y += step) { + ImPlotTime t = MakeYear(y); + if (t >= t_min && t <= t_max) { + ImPlotTick tick(t.ToDouble(), true, true); tick.Level = 0; - LabelTickTime(tick, ticks.Labels, TimeFormatLevel0[ImPlotTimeUnit_Yr]); + LabelTickTime(tick, ticks.Labels, t, TimeFormatLevel0[ImPlotTimeUnit_Yr]); ticks.AddTick(tick); } - t1 = AddTime(t1, ImPlotTimeUnit_Yr, step); - } + } } } @@ -966,10 +991,14 @@ void UpdateAxisColors(int axis_flag, ImPlotAxisColor* col) { // BeginPlot() //----------------------------------------------------------------------------- -bool BeginPlot(const char* title, const char* x_label, const char* y_label, const ImVec2& size, ImPlotFlags flags, ImPlotAxisFlags x_flags, ImPlotAxisFlags y_flags, ImPlotAxisFlags y2_flags, ImPlotAxisFlags y3_flags) { +bool BeginPlot(const char* title, const char* x_label, const char* y_label, const ImVec2& size, + ImPlotFlags flags, ImPlotAxisFlags x_flags, ImPlotAxisFlags y_flags, ImPlotAxisFlags y2_flags, ImPlotAxisFlags y3_flags) +{ IM_ASSERT_USER_ERROR(GImPlot != NULL, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?"); ImPlotContext& gp = *GImPlot; IM_ASSERT_USER_ERROR(gp.CurrentPlot == NULL, "Mismatched BeginPlot()/EndPlot()!"); + IM_ASSERT_USER_ERROR(!(ImHasFlag(x_flags, ImPlotAxisFlags_Time) && ImHasFlag(x_flags, ImPlotAxisFlags_LogScale)), "ImPlotAxisFlags_Time and ImPlotAxisFlags_LogScale cannot be enabled at the same time!"); + IM_ASSERT_USER_ERROR(!ImHasFlag(y_flags, ImPlotAxisFlags_Time), "Y axes cannot display time formatted labels!"); // FRONT MATTER ----------------------------------------------------------- @@ -1002,6 +1031,8 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // There's probably an easy bit mask trick I'm not aware of. if (flags != plot.PreviousFlags) plot.Flags = flags; + if (x_flags != plot.XAxis.PreviousFlags) + plot.XAxis.Flags = x_flags; if (y_flags != plot.YAxis[0].PreviousFlags) plot.YAxis[0].Flags = y_flags; if (y2_flags != plot.YAxis[1].PreviousFlags) @@ -1106,9 +1137,8 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons const float txt_height = ImGui::GetTextLineHeight(); const float pad_top = title_size.x > 0.0f ? txt_height + gp.Style.LabelPadding.y : 0; - const float pad_bot = (gp.X.HasLabels ? txt_height + gp.Style.LabelPadding.y : 0) - + (x_label ? txt_height + gp.Style.LabelPadding.y : 0) - + (gp.X.IsTime ? txt_height + gp.Style.LabelPadding.y : 0); + const float pad_bot = (gp.X.HasLabels ? txt_height + gp.Style.LabelPadding.y + (gp.X.IsTime ? txt_height + gp.Style.LabelPadding.y : 0) : 0) + + (x_label ? txt_height + gp.Style.LabelPadding.y : 0); const float plot_height = gp.BB_Canvas.GetHeight() - pad_top - pad_bot; @@ -1573,14 +1603,14 @@ inline void EndDisabledControls(bool cond) { } } -inline void ShowAxisContextMenu(ImPlotAxisState& state) { +inline void ShowAxisContextMenu(ImPlotAxisState& state, bool time_allowed) { 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 grid = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines); - bool ticks = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_TickMarks); - bool labels = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_TickLabels); + bool logscale = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_LogScale); + bool timescale = 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); double drag_speed = (state.Axis->Range.Size() <= DBL_EPSILON) ? DBL_EPSILON * 1.0e+13 : 0.01 * state.Axis->Range.Size(); // recover from almost equal axis limits. BeginDisabledControls(total_lock); @@ -1611,10 +1641,18 @@ inline void ShowAxisContextMenu(ImPlotAxisState& state) { if (ImGui::Checkbox("Invert", &state.Invert)) ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_Invert); + BeginDisabledControls(timescale && time_allowed); if (ImGui::Checkbox("Log Scale", &logscale)) ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_LogScale); - // if (ImGui::Checkbox("Time", ×ale)) - // ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_Time); + EndDisabledControls(timescale && time_allowed); + + if (time_allowed) { + BeginDisabledControls(logscale); + if (ImGui::Checkbox("Time", ×cale)) + ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_Time); + EndDisabledControls(logscale); + } + ImGui::Separator(); if (ImGui::Checkbox("Grid Lines", &grid)) ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines); @@ -1629,7 +1667,7 @@ void ShowPlotContextMenu(ImPlotState& plot) { ImPlotContext& gp = *GImPlot; if (ImGui::BeginMenu("X-Axis")) { ImGui::PushID("X"); - ShowAxisContextMenu(gp.X); + ShowAxisContextMenu(gp.X, true); ImGui::PopID(); ImGui::EndMenu(); } @@ -1648,7 +1686,7 @@ void ShowPlotContextMenu(ImPlotState& plot) { } if (ImGui::BeginMenu(buf)) { ImGui::PushID(i); - ShowAxisContextMenu(gp.Y[i]); + ShowAxisContextMenu(gp.Y[i], false); ImGui::PopID(); ImGui::EndMenu(); } @@ -1721,10 +1759,11 @@ void EndPlot() { if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickMarks)) { for (int t = 0; t < gp.XTicks.Size; t++) { ImPlotTick *xt = &gp.XTicks.Ticks[t]; - DrawList.AddLine(ImVec2(xt->PixelPos, gp.BB_Plot.Max.y), - ImVec2(xt->PixelPos, gp.BB_Plot.Max.y - (xt->Major ? gp.Style.MajorTickLen.x : gp.Style.MinorTickLen.x)), - gp.Col_X.Major, - xt->Major ? gp.Style.MajorTickSize.x : gp.Style.MinorTickSize.x); + if (xt->Level == 0) + DrawList.AddLine(ImVec2(xt->PixelPos, gp.BB_Plot.Max.y), + ImVec2(xt->PixelPos, gp.BB_Plot.Max.y - (xt->Major ? gp.Style.MajorTickLen.x : gp.Style.MinorTickLen.x)), + gp.Col_X.Major, + xt->Major ? gp.Style.MajorTickSize.x : gp.Style.MinorTickSize.x); } } PopPlotClipRect(); @@ -1916,6 +1955,12 @@ void EndPlot() { if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) { writer.Write("%.3E", gp.MousePos[0].x); } + else if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_Time)) { + ImPlotTimeUnit unit = GetUnitForRange(plot.XAxis.Range.Size() / (gp.BB_Plot.GetWidth() / 100)); + const int written = FormatTime(ImPlotTime::FromDouble(gp.MousePos[0].x), &writer.Buffer[writer.Pos], writer.Size - writer.Pos - 1, TimeFormatMouseCursor[unit]); + if (written > 0) + writer.Pos += ImMin(written, writer.Size - writer.Pos - 1); + } else { double range_x = gp.XTicks.Size > 1 ? (gp.XTicks.Ticks[1].PlotPos - gp.XTicks.Ticks[0].PlotPos) : plot.XAxis.Range.Size(); writer.Write("%.*f", Precision(range_x), gp.MousePos[0].x); @@ -2001,7 +2046,7 @@ void EndPlot() { ImGui::OpenPopup("##XContext"); if (ImGui::BeginPopup("##XContext")) { ImGui::Text("X-Axis"); ImGui::Separator(); - ShowAxisContextMenu(gp.X); + ShowAxisContextMenu(gp.X, true); ImGui::EndPopup(); } @@ -2016,7 +2061,7 @@ void EndPlot() { else { ImGui::Text("Y-Axis %d", i + 1); ImGui::Separator(); } - ShowAxisContextMenu(gp.Y[i]); + ShowAxisContextMenu(gp.Y[i], false); ImGui::EndPopup(); } ImGui::PopID(); diff --git a/implot_demo.cpp b/implot_demo.cpp index 5252aba..8863731 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -152,7 +152,7 @@ void ShowDemoWindow(bool* p_open) { return; } ImGui::SetNextWindowPos(ImVec2(50, 50), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSize(ImVec2(530, 750), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(600, 750), ImGuiCond_FirstUseEver); ImGui::Begin("ImPlot Demo", p_open, ImGuiWindowFlags_MenuBar); if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("Tools")) { @@ -591,23 +591,10 @@ void ShowDemoWindow(bool* p_open) { ImPlot::EndPlot(); } } - if (ImGui::CollapsingHeader("Time Formatting")) { - static double min = 1599242863*0.5; - static double max = 1599242863*1.5; - static bool zooming = false; - ImGuiCond cond = ImGuiCond_Once; - if (ImGui::Button("Zoom")) { - zooming = true; - } - if (zooming) { - cond = ImGuiCond_Always; - double range = max - min; - min += range * 0.005; - max -= range * 0.005; - if (range < 0.005) - zooming = false; - } - ImPlot::SetNextPlotLimits(min,max,0,1,cond); + if (ImGui::CollapsingHeader("Time Formatted Axes")) { + static double min = 1577836800; // 01/01/2020 @ 12:00:00am (UTC) + static double max = 1609459200; // 01/01/2021 @ 12:00:00am (UTC) + ImPlot::SetNextPlotLimits(min,max,0,1); if (ImPlot::BeginPlot("##Time", "UTC Time", "Y-Axis", ImVec2(-1,0), ImPlotFlags_Default, ImPlotAxisFlags_Default | ImPlotAxisFlags_Time)) { ImPlot::EndPlot(); @@ -615,8 +602,6 @@ void ShowDemoWindow(bool* p_open) { } //------------------------------------------------------------------------- if (ImGui::CollapsingHeader("Multiple Y-Axes")) { - - static t_float xs[1001], xs2[1001], ys1[1001], ys2[1001], ys3[1001]; for (int i = 0; i < 1001; ++i) { xs[i] = (i*0.1f); @@ -1294,7 +1279,7 @@ void PlotCandlestick(const char* label_id, const double* xs, const double* opens // custom tool if (ImPlot::IsPlotHovered() && tooltip) { ImPlotPoint mouse = ImPlot::GetPlotMousePos(); - mouse.x = ImPlot::RoundTime(ImPlotTime(mouse.x), ImPlotTimeUnit_Day).ToDouble(); + mouse.x = ImPlot::RoundTime(ImPlotTime::FromDouble(mouse.x), ImPlotTimeUnit_Day).ToDouble(); float tool_l = ImPlot::PlotToPixels(mouse.x - half_width * 1.5, mouse.y).x; float tool_r = ImPlot::PlotToPixels(mouse.x + half_width * 1.5, mouse.y).x; float tool_t = ImPlot::GetPlotPos().y; @@ -1308,7 +1293,7 @@ void PlotCandlestick(const char* label_id, const double* xs, const double* opens if (idx != -1) { ImGui::BeginTooltip(); char buff[32]; - ImPlot::FormatTime(xs[idx],buff,32,ImPlotTimeFmt_DayMoYr); + ImPlot::FormatTime(ImPlotTime::FromDouble(xs[idx]),buff,32,ImPlotTimeFmt_DayMoYr); ImGui::Text("Day: %s", buff); ImGui::Text("Open: $%.2f", opens[idx]); ImGui::Text("Close: $%.2f", closes[idx]); diff --git a/implot_internal.h b/implot_internal.h index e041209..8272cc3 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -126,10 +126,10 @@ struct ImOffsetCalculator { struct ImBufferWriter { char* Buffer; - size_t Size; - size_t Pos; + int Size; + int Pos; - ImBufferWriter(char* buffer, size_t size) { + ImBufferWriter(char* buffer, int size) { Buffer = buffer; Size = size; Pos = 0; @@ -140,7 +140,7 @@ struct ImBufferWriter va_start(argp, fmt); const int written = ::vsnprintf(&Buffer[Pos], Size - Pos - 1, fmt, argp); if (written > 0) - Pos += ImMin(size_t(written), Size-Pos-1); + Pos += ImMin(written, Size-Pos-1); va_end(argp); } }; @@ -191,6 +191,7 @@ enum ImPlotTimeFmt_ { ImPlotTimeFmt_HrMin, // 7:21pm ImPlotTimeFmt_Hr, // 7pm ImPlotTimeFmt_DayMo, // 10/3 + ImPlotTimeFmt_DayMoHr, // 10/3 7pm ImPlotTimeFmt_DayMoHrMin, // 10/3 7:21pm ImPlotTimeFmt_DayMoYr, // 10/3/91 ImPlotTimeFmt_DayMoYrHrMin, // 10/3/91 7:21pm @@ -580,13 +581,17 @@ struct ImPlotAxisScale struct ImPlotTime { time_t S; int Us; - ImPlotTime(time_t s, int us) { + ImPlotTime() { S = 0; Us = 0; } + ImPlotTime(time_t s, int us = 0) { S = s + us / 1000000; Us = us % 1000000; } - ImPlotTime(double t) { - S = (time_t)t; - Us = (int)(t * 1000000 - floor(t) * 1000000); + void RollOver() { + S = S + Us / 1000000; + Us = Us % 1000000; + } + static ImPlotTime FromDouble(double t) { + return ImPlotTime((time_t)t, (int)(t * 1000000 - floor(t) * 1000000)); } double ToDouble() const { return (double)S + (double)Us / 1000000.0; } }; @@ -696,7 +701,7 @@ void LabelTickDefault(ImPlotTick& tick, ImGuiTextBuffer& buffer); // Label a tick with scientific formating. void LabelTickScientific(ImPlotTick& tick, ImGuiTextBuffer& buffer); // Label a tick with time formatting. -void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeFmt fmt); +void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, const ImPlotTime& t, ImPlotTimeFmt fmt); // Populates a list of ImPlotTicks with normal spaced and formatted ticks void AddTicksDefault(const ImPlotRange& range, int nMajor, int nMinor, ImPlotTickCollection& ticks); @@ -780,6 +785,9 @@ inline T OffsetAndStride(const T* data, int idx, int count, int offset, int stri // Time Utils //----------------------------------------------------------------------------- +// NB: These functions only work if there is a currnet context because the +// internal tm struct is owned by the context! + // Returns true if year is leap year (366 days long) inline bool IsLeapYear(int year) { if (year % 4 != 0) return false; @@ -787,7 +795,7 @@ inline bool IsLeapYear(int year) { if (year % 100 == 0) return false; return true; } -// Returns the number of days in a month, accounting for Feb. leap years. +// Returns the number of days in a month, accounting for Feb. leap years. #month is zero indexed. inline int GetDaysInMonth(int year, int month) { static const int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; return days[month] + (int)(month == 1 && IsLeapYear(year)); From 2dcdfc519a40a8210f965660b14f8118f34ffba8 Mon Sep 17 00:00:00 2001 From: epezent Date: Sun, 6 Sep 2020 00:42:03 -0500 Subject: [PATCH 12/16] invert ImPlotFlags --- implot.cpp | 34 +++++++++++++++++----------------- implot.h | 26 +++++++++++++------------- implot_demo.cpp | 31 +++++++++++++++---------------- implot_internal.h | 2 +- implot_items.cpp | 2 +- 5 files changed, 47 insertions(+), 48 deletions(-) diff --git a/implot.cpp b/implot.cpp index 18121b9..73ae917 100644 --- a/implot.cpp +++ b/implot.cpp @@ -1212,7 +1212,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons const bool any_hov_y_axis_region = plot.YAxis[0].HoveredTot || plot.YAxis[1].HoveredTot || plot.YAxis[2].HoveredTot; // legend hovered from last frame - const bool hov_legend = ImHasFlag(plot.Flags, ImPlotFlags_Legend) ? gp.Hov_Frame && plot.BB_Legend.Contains(IO.MousePos) : false; + const bool hov_legend = !ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) ? gp.Hov_Frame && plot.BB_Legend.Contains(IO.MousePos) : false; bool hov_query = false; if (gp.Hov_Frame && gp.Hov_Plot && plot.Queried && !plot.Querying) { @@ -1349,7 +1349,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons if (plot.Selecting && (IO.MouseReleased[gp.InputMap.BoxSelectButton] || !IO.MouseDown[gp.InputMap.BoxSelectButton])) { UpdateTransformCache(); ImVec2 select_size = plot.SelectStart - IO.MousePos; - if (ImHasFlag(plot.Flags, ImPlotFlags_BoxSelect)) { + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect)) { ImPlotPoint p1 = PixelsToPlot(plot.SelectStart); ImPlotPoint p2 = PixelsToPlot(IO.MousePos); const bool x_can_change = !ImHasFlag(IO.KeyMods,gp.InputMap.HorizontalMod) && ImFabs(select_size.x) > 2; @@ -1370,7 +1370,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.Selecting = false; } // bad selection - if (plot.Selecting && (!ImHasFlag(plot.Flags, ImPlotFlags_BoxSelect) || gp.LockPlot) && ImLengthSqr(plot.SelectStart - IO.MousePos) > 4) { + if (plot.Selecting && (ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect) || gp.LockPlot) && ImLengthSqr(plot.SelectStart - IO.MousePos) > 4) { ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); } // cancel selection @@ -1417,7 +1417,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons plot.Queried = true; plot.QueryStart = plot.SelectStart; } - if (ImHasFlag(plot.Flags, ImPlotFlags_BoxSelect) && plot.Querying && !ImHasFlag(IO.KeyMods, gp.InputMap.QueryToggleMod) && !IO.MouseDown[gp.InputMap.QueryButton]) { + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect) && plot.Querying && !ImHasFlag(IO.KeyMods, gp.InputMap.QueryToggleMod) && !IO.MouseDown[gp.InputMap.QueryButton]) { plot.Selecting = true; plot.Querying = false; plot.Queried = false; @@ -1694,8 +1694,8 @@ void ShowPlotContextMenu(ImPlotState& plot) { ImGui::Separator(); if ((ImGui::BeginMenu("Settings"))) { - if (ImGui::MenuItem("Box Select",NULL,ImHasFlag(plot.Flags, ImPlotFlags_BoxSelect))) { - ImFlipFlag(plot.Flags, ImPlotFlags_BoxSelect); + if (ImGui::MenuItem("Box Select",NULL,!ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect))) { + ImFlipFlag(plot.Flags, ImPlotFlags_NoBoxSelect); } if (ImGui::MenuItem("Query",NULL,ImHasFlag(plot.Flags, ImPlotFlags_Query))) { ImFlipFlag(plot.Flags, ImPlotFlags_Query); @@ -1703,16 +1703,16 @@ void ShowPlotContextMenu(ImPlotState& plot) { if (ImGui::MenuItem("Crosshairs",NULL,ImHasFlag(plot.Flags, ImPlotFlags_Crosshairs))) { ImFlipFlag(plot.Flags, ImPlotFlags_Crosshairs); } - if (ImGui::MenuItem("Mouse Position",NULL,ImHasFlag(plot.Flags, ImPlotFlags_MousePos))) { - ImFlipFlag(plot.Flags, ImPlotFlags_MousePos); + if (ImGui::MenuItem("Mouse Position",NULL,!ImHasFlag(plot.Flags, ImPlotFlags_NoMousePos))) { + ImFlipFlag(plot.Flags, ImPlotFlags_NoMousePos); } if (ImGui::MenuItem("Anti-Aliased Lines",NULL,ImHasFlag(plot.Flags, ImPlotFlags_AntiAliased))) { ImFlipFlag(plot.Flags, ImPlotFlags_AntiAliased); } ImGui::EndMenu(); } - if (ImGui::MenuItem("Legend",NULL,ImHasFlag(plot.Flags, ImPlotFlags_Legend))) { - ImFlipFlag(plot.Flags, ImPlotFlags_Legend); + if (ImGui::MenuItem("Legend",NULL,!ImHasFlag(plot.Flags, ImPlotFlags_NoLegend))) { + ImFlipFlag(plot.Flags, ImPlotFlags_NoLegend); } #ifdef IMPLOT_DEBUG if (ImGui::BeginMenu("Debug")) { @@ -1817,7 +1817,7 @@ void EndPlot() { const bool wide_enough = ImFabs(select_bb.GetWidth()) > 2; const bool tall_enough = ImFabs(select_bb.GetHeight()) > 2; const bool big_enough = wide_enough && tall_enough; - if (plot.Selecting && !gp.LockPlot && ImHasFlag(plot.Flags, ImPlotFlags_BoxSelect)) { + if (plot.Selecting && !gp.LockPlot && !ImHasFlag(plot.Flags, ImPlotFlags_NoBoxSelect)) { const ImVec4 col = GetStyleColorVec4(ImPlotCol_Selection); const ImU32 col_bg = ImGui::GetColorU32(col * ImVec4(1,1,1,0.25f)); const ImU32 col_bd = ImGui::GetColorU32(col); @@ -1868,7 +1868,7 @@ void EndPlot() { ImRect legend_content_bb; int nItems = GetLegendCount(); bool hov_legend = false; - if (ImHasFlag(plot.Flags, ImPlotFlags_Legend) && nItems > 0) { + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) && nItems > 0) { // get max width float max_label_width = 0; for (int i = 0; i < nItems; ++i) { @@ -1878,7 +1878,7 @@ void EndPlot() { } legend_content_bb = ImRect(gp.BB_Plot.Min + legend_offset, gp.BB_Plot.Min + legend_offset + ImVec2(max_label_width, nItems * txt_ht)); plot.BB_Legend = ImRect(legend_content_bb.Min, legend_content_bb.Max + legend_spacing * 2 + ImVec2(legend_icon_size, 0)); - hov_legend = ImHasFlag(plot.Flags, ImPlotFlags_Legend) ? gp.Hov_Frame && plot.BB_Legend.Contains(IO.MousePos) : false; + hov_legend = !ImHasFlag(plot.Flags, ImPlotFlags_NoLegend) ? gp.Hov_Frame && plot.BB_Legend.Contains(IO.MousePos) : false; // render legend box ImU32 col_bg = GetStyleColorU32(ImPlotCol_LegendBg); ImU32 col_bd = GetStyleColorU32(ImPlotCol_LegendBorder); @@ -1947,7 +1947,7 @@ void EndPlot() { } // render mouse pos - if (ImHasFlag(plot.Flags, ImPlotFlags_MousePos) && gp.Hov_Plot) { + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMousePos) && gp.Hov_Plot) { char buffer[128] = {}; ImBufferWriter writer(buffer, sizeof(buffer)); @@ -2035,14 +2035,14 @@ void EndPlot() { // CONTEXT MENUS ----------------------------------------------------------- - if (ImHasFlag(plot.Flags, ImPlotFlags_ContextMenu) && gp.Hov_Frame && gp.Hov_Plot && IO.MouseDoubleClicked[gp.InputMap.ContextMenuButton] && !hov_legend) + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMenus) && gp.Hov_Frame && gp.Hov_Plot && IO.MouseDoubleClicked[gp.InputMap.ContextMenuButton] && !hov_legend) ImGui::OpenPopup("##PlotContext"); if (ImGui::BeginPopup("##PlotContext")) { ShowPlotContextMenu(plot); ImGui::EndPopup(); } - if (ImHasFlag(plot.Flags, ImPlotFlags_ContextMenu) && gp.Hov_Frame && plot.XAxis.HoveredExt && IO.MouseDoubleClicked[gp.InputMap.ContextMenuButton] && !hov_legend) + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMenus) && gp.Hov_Frame && plot.XAxis.HoveredExt && IO.MouseDoubleClicked[gp.InputMap.ContextMenuButton] && !hov_legend) ImGui::OpenPopup("##XContext"); if (ImGui::BeginPopup("##XContext")) { ImGui::Text("X-Axis"); ImGui::Separator(); @@ -2052,7 +2052,7 @@ void EndPlot() { for (int i = 0; i < IMPLOT_Y_AXES; ++i) { ImGui::PushID(i); - if (ImHasFlag(plot.Flags, ImPlotFlags_ContextMenu) && gp.Hov_Frame && plot.YAxis[i].HoveredExt && IO.MouseDoubleClicked[gp.InputMap.ContextMenuButton] && !hov_legend) + if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMenus) && gp.Hov_Frame && plot.YAxis[i].HoveredExt && IO.MouseDoubleClicked[gp.InputMap.ContextMenuButton] && !hov_legend) ImGui::OpenPopup("##YContext"); if (ImGui::BeginPopup("##YContext")) { if (i == 0) { diff --git a/implot.h b/implot.h index eda8d3d..01e5ba7 100644 --- a/implot.h +++ b/implot.h @@ -53,18 +53,18 @@ typedef int ImPlotColormap; // -> enum ImPlotColormap_ // 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_BoxSelect = 1 << 3, // the user will be able to box-select with right-mouse - ImPlotFlags_Query = 1 << 4, // the user will be able to draw query rects with middle-mouse - ImPlotFlags_ContextMenu = 1 << 5, // the user will be able to open context menus with double-right click - ImPlotFlags_Crosshairs = 1 << 6, // the default mouse cursor will be replaced with a crosshair when hovered - ImPlotFlags_AntiAliased = 1 << 7, // plot lines will be software anti-aliased (not recommended for density plots, prefer MSAA) - 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_YAxis2 = 1 << 9, // enable a 2nd y-axis - ImPlotFlags_YAxis3 = 1 << 10, // enable a 3rd y-axis - ImPlotFlags_Default = ImPlotFlags_MousePos | ImPlotFlags_Legend | ImPlotFlags_Highlight | ImPlotFlags_BoxSelect | ImPlotFlags_ContextMenu + ImPlotFlags_None = 0, // default + ImPlotFlags_NoLegend = 1 << 0, // the top-left legend will not be displayed + ImPlotFlags_NoMenus = 1 << 1, // the user will be able to open context menus with double-right click + ImPlotFlags_NoBoxSelect = 1 << 2, // the user will be able to box-select with right-mouse + ImPlotFlags_NoMousePos = 1 << 3, // the mouse position, in plot coordinates, will not be displayed in the bottom-right + ImPlotFlags_NoHighlight = 1 << 4, // plot items will be highlighted when their legend entry is hovered + ImPlotFlags_NoChild = 1 << 5, // a child window region will not be used to capture mouse scroll (can boost performance for single ImGui window applications) + ImPlotFlags_YAxis2 = 1 << 6, // enable a 2nd y-axis + ImPlotFlags_YAxis3 = 1 << 7, // enable a 3rd y-axis + ImPlotFlags_Query = 1 << 8, // the user will be able to draw query rects with middle-mouse + ImPlotFlags_Crosshairs = 1 << 9, // the default mouse cursor will be replaced with a crosshair when hovered + ImPlotFlags_AntiAliased = 1 << 10 // plot lines will be software anti-aliased (not recommended for density plots, prefer MSAA) }; // Options for plot axes (X and Y). @@ -282,7 +282,7 @@ bool BeginPlot(const char* title_id, const char* x_label = NULL, const char* y_label = NULL, const ImVec2& size = ImVec2(-1,0), - ImPlotFlags flags = ImPlotFlags_Default, + ImPlotFlags flags = ImPlotFlags_None, ImPlotAxisFlags x_flags = ImPlotAxisFlags_Default, ImPlotAxisFlags y_flags = ImPlotAxisFlags_Default, ImPlotAxisFlags y2_flags = ImPlotAxisFlags_Auxiliary, diff --git a/implot_demo.cpp b/implot_demo.cpp index 8863731..065008c 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -346,7 +346,7 @@ void ShowDemoWindow(bool* p_open) { ImPlot::SetNextPlotTicksX(positions, 10, labels); } if (ImPlot::BeginPlot("Bar Plot", horz ? "Score" : "Student", horz ? "Student" : "Score", - ImVec2(-1,0), ImPlotFlags_Default, ImPlotAxisFlags_Default, + ImVec2(-1,0), 0, ImPlotAxisFlags_Default, horz ? ImPlotAxisFlags_Default | ImPlotAxisFlags_Invert : ImPlotAxisFlags_Default)) { if (horz) { @@ -426,7 +426,7 @@ void ShowDemoWindow(bool* p_open) { } ImPlot::SetNextPlotLimits(0,1,0,1,ImGuiCond_Always); - if (ImPlot::BeginPlot("##Pie1", NULL, NULL, ImVec2(250,250), ImPlotFlags_Legend, 0, 0)) { + if (ImPlot::BeginPlot("##Pie1", NULL, NULL, ImVec2(250,250), ImPlotFlags_NoMousePos | ImPlotFlags_NoMousePos, 0, 0)) { ImPlot::PlotPieChart(labels1, data1, 4, 0.5f, 0.5f, 0.4f, normalize, "%.2f"); ImPlot::EndPlot(); } @@ -438,7 +438,7 @@ void ShowDemoWindow(bool* p_open) { ImPlot::PushColormap(ImPlotColormap_Pastel); ImPlot::SetNextPlotLimits(0,1,0,1,ImGuiCond_Always); - if (ImPlot::BeginPlot("##Pie2", NULL, NULL, ImVec2(250,250), ImPlotFlags_Legend, 0, 0)) { + if (ImPlot::BeginPlot("##Pie2", NULL, NULL, ImVec2(250,250), ImPlotFlags_NoMousePos | ImPlotFlags_NoMousePos, 0, 0)) { ImPlot::PlotPieChart(labels2, data2, 5, 0.5f, 0.5f, 0.4f, true, "%.0f", 180); ImPlot::EndPlot(); } @@ -475,7 +475,7 @@ void ShowDemoWindow(bool* p_open) { ImPlot::PushColormap(map); SetNextPlotTicksX(0 + 1.0/14.0, 1 - 1.0/14.0, 7, xlabels); SetNextPlotTicksY(1 - 1.0/14.0, 0 + 1.0/14.0, 7, ylabels); - if (ImPlot::BeginPlot("##Heatmap1",NULL,NULL,ImVec2(225,225),0,axes_flags,axes_flags)) { + if (ImPlot::BeginPlot("##Heatmap1",NULL,NULL,ImVec2(225,225),ImPlotFlags_NoLegend,axes_flags,axes_flags)) { ImPlot::PlotHeatmap("heat",values1[0],7,7,scale_min,scale_max); ImPlot::EndPlot(); } @@ -488,7 +488,7 @@ void ShowDemoWindow(bool* p_open) { static ImVec4 gray[2] = {ImVec4(0,0,0,1), ImVec4(1,1,1,1)}; ImPlot::PushColormap(gray, 2); ImPlot::SetNextPlotLimits(-1,1,-1,1); - if (ImPlot::BeginPlot("##Heatmap2",NULL,NULL,ImVec2(225,225),ImPlotFlags_ContextMenu,0,0)) { + if (ImPlot::BeginPlot("##Heatmap2",NULL,NULL,ImVec2(225,225),0,0,0)) { ImPlot::PlotHeatmap("heat1",values2,100,100,0,1,NULL); ImPlot::PlotHeatmap("heat2",values2,100,100,0,1,NULL, ImPlotPoint(-1,-1), ImPlotPoint(0,0)); ImPlot::EndPlot(); @@ -516,13 +516,13 @@ void ShowDemoWindow(bool* p_open) { static ImPlotAxisFlags rt_axis = ImPlotAxisFlags_Default & ~ImPlotAxisFlags_TickLabels; ImPlot::SetNextPlotLimitsX(t - history, t, ImGuiCond_Always); - if (ImPlot::BeginPlot("##Scrolling", NULL, NULL, ImVec2(-1,150), ImPlotFlags_Default, rt_axis, rt_axis | ImPlotAxisFlags_LockMin)) { + if (ImPlot::BeginPlot("##Scrolling", NULL, NULL, ImVec2(-1,150), 0, rt_axis, rt_axis | ImPlotAxisFlags_LockMin)) { ImPlot::PlotShaded("Data 1", &sdata1.Data[0].x, &sdata1.Data[0].y, sdata1.Data.size(), 0, sdata1.Offset, 2 * sizeof(t_float)); ImPlot::PlotLine("Data 2", &sdata2.Data[0], sdata2.Data.size(), sdata2.Offset); ImPlot::EndPlot(); } ImPlot::SetNextPlotLimitsX(0, history, ImGuiCond_Always); - if (ImPlot::BeginPlot("##Rolling", NULL, NULL, ImVec2(-1,150), ImPlotFlags_Default, rt_axis, rt_axis)) { + if (ImPlot::BeginPlot("##Rolling", NULL, NULL, ImVec2(-1,150), 0, rt_axis, rt_axis)) { // two methods of plotting Data // as ImVec2* (or ImPlot*): ImPlot::PlotLine("Data 1", &rdata1.Data[0], rdata1.Data.size()); @@ -583,7 +583,7 @@ void ShowDemoWindow(bool* p_open) { ImGui::BulletText("Open the plot context menu (double right click) to change scales."); ImPlot::SetNextPlotLimits(0.1, 100, 0, 10); - if (ImPlot::BeginPlot("Log Plot", NULL, NULL, ImVec2(-1,0), ImPlotFlags_Default, ImPlotAxisFlags_Default | ImPlotAxisFlags_LogScale )) { + if (ImPlot::BeginPlot("Log Plot", NULL, NULL, ImVec2(-1,0), 0, ImPlotAxisFlags_Default | ImPlotAxisFlags_LogScale )) { ImPlot::PlotLine("f(x) = x", xs, xs, 1001); ImPlot::PlotLine("f(x) = sin(x)+1", xs, ys1, 1001); ImPlot::PlotLine("f(x) = log(x)", xs, ys2, 1001); @@ -595,7 +595,7 @@ void ShowDemoWindow(bool* p_open) { static double min = 1577836800; // 01/01/2020 @ 12:00:00am (UTC) static double max = 1609459200; // 01/01/2021 @ 12:00:00am (UTC) ImPlot::SetNextPlotLimits(min,max,0,1); - if (ImPlot::BeginPlot("##Time", "UTC Time", "Y-Axis", ImVec2(-1,0), ImPlotFlags_Default, ImPlotAxisFlags_Default | ImPlotAxisFlags_Time)) { + if (ImPlot::BeginPlot("##Time", "UTC Time", "Y-Axis", ImVec2(-1,0), 0, ImPlotAxisFlags_Default | ImPlotAxisFlags_Time)) { ImPlot::EndPlot(); } @@ -627,7 +627,6 @@ void ShowDemoWindow(bool* p_open) { ImPlot::SetNextPlotLimitsY(0, 1, ImGuiCond_Once, 1); ImPlot::SetNextPlotLimitsY(0, 300, ImGuiCond_Once, 2); if (ImPlot::BeginPlot("Multi-Axis Plot", NULL, NULL, ImVec2(-1,0), - ImPlotFlags_Default | (y2_axis ? ImPlotFlags_YAxis2 : 0) | (y3_axis ? ImPlotFlags_YAxis3 : 0))) { ImPlot::PlotLine("f(x) = x", xs, xs, 1001); @@ -656,7 +655,7 @@ void ShowDemoWindow(bool* p_open) { ImGui::BulletText("The query rect can be dragged after it's created."); ImGui::Unindent(); - if (ImPlot::BeginPlot("##Drawing", NULL, NULL, ImVec2(-1,0), ImPlotFlags_Default | ImPlotFlags_Query, ImPlotAxisFlags_GridLines, ImPlotAxisFlags_GridLines)) { + if (ImPlot::BeginPlot("##Drawing", NULL, NULL, ImVec2(-1,0), ImPlotFlags_Query, ImPlotAxisFlags_GridLines, ImPlotAxisFlags_GridLines)) { if (ImPlot::IsPlotHovered() && ImGui::IsMouseClicked(0) && ImGui::GetIO().KeyCtrl) { ImPlotPoint pt = ImPlot::GetPlotMousePos(); data.push_back(t_float2((t_float)pt.x, (t_float)pt.y)); @@ -709,7 +708,7 @@ void ShowDemoWindow(bool* p_open) { ImPlot::SetNextPlotLimits(0,0.01,-1,1); ImPlotAxisFlags flags = ImPlotAxisFlags_Default & ~ImPlotAxisFlags_TickLabels; ImPlotLimits query; - if (ImPlot::BeginPlot("##View1",NULL,NULL,ImVec2(-1,150), ImPlotFlags_Default | ImPlotFlags_Query, flags, flags)) { + if (ImPlot::BeginPlot("##View1",NULL,NULL,ImVec2(-1,150), ImPlotFlags_Query, flags, flags)) { ImPlot::PlotLine("Signal 1", x_data, y_data1, 512); ImPlot::PlotLine("Signal 2", x_data, y_data2, 512); ImPlot::PlotLine("Signal 3", x_data, y_data3, 512); @@ -774,7 +773,7 @@ void ShowDemoWindow(bool* p_open) { } } ImPlot::SetNextPlotLimitsX((double)t - 10, t, paused ? ImGuiCond_Once : ImGuiCond_Always); - if (ImPlot::BeginPlot("##DND", NULL, NULL, ImVec2(-1,0), ImPlotFlags_Legend | ImPlotFlags_Highlight | ImPlotFlags_BoxSelect | ImPlotFlags_ContextMenu | ImPlotFlags_YAxis2 | ImPlotFlags_YAxis3)) { + if (ImPlot::BeginPlot("##DND", NULL, NULL, ImVec2(-1,0), ImPlotFlags_YAxis2 | ImPlotFlags_YAxis3)) { for (int i = 0; i < K_CHANNELS; ++i) { if (show[i] && data[i].Data.size() > 0) { char label[K_CHANNELS]; @@ -1031,7 +1030,7 @@ void ShowDemoWindow(bool* p_open) { ImPlot::SetNextPlotTicksY(0, 1, 6, custom_labels ? ylabels_aux : NULL, false, 2); } ImPlot::SetNextPlotLimits(2.5,5,0,10); - if (ImPlot::BeginPlot("Custom Ticks", NULL, NULL, ImVec2(-1,0), ImPlotFlags_Default | ImPlotFlags_YAxis2 | ImPlotFlags_YAxis3)) { + if (ImPlot::BeginPlot("Custom Ticks", NULL, NULL, ImVec2(-1,0), ImPlotFlags_YAxis2 | ImPlotFlags_YAxis3)) { // nothing to see here, just the ticks ImPlot::EndPlot(); } @@ -1136,7 +1135,7 @@ void ShowDemoWindow(bool* p_open) { ImGui::SameLine(); ImGui::ColorEdit4("##Bull", &bullCol.x, ImGuiColorEditFlags_NoInputs); ImGui::SameLine(); ImGui::ColorEdit4("##Bear", &bearCol.x, ImGuiColorEditFlags_NoInputs); ImPlot::SetNextPlotLimits(1546300800, 1571961600, 1250, 1600); - if (ImPlot::BeginPlot("Candlestick Chart","Day","USD",ImVec2(-1,-1),ImPlotFlags_Default, ImPlotAxisFlags_Default | ImPlotAxisFlags_Time)) { + if (ImPlot::BeginPlot("Candlestick Chart","Day","USD",ImVec2(-1,-1),0,ImPlotAxisFlags_Default | ImPlotAxisFlags_Time)) { MyImPlot::PlotCandlestick("GOOGL",dates, opens, closes, lows, highs, 218, tooltip, 0.25f, bullCol, bearCol); ImPlot::EndPlot(); } @@ -1413,7 +1412,7 @@ void ShowBenchmarkTool() { ImPlot::SetNextPlotLimits(0,500,0,500,ImGuiCond_Always); static char buffer[8]; - if (ImPlot::BeginPlot("##Stats", "Lines (1,000 pts each)", "Framerate (Hz)", ImVec2(-1,0), ImPlotFlags_Default | ImPlotFlags_NoChild)) { + if (ImPlot::BeginPlot("##Stats", "Lines (1,000 pts each)", "Framerate (Hz)", ImVec2(-1,0), ImPlotFlags_NoChild)) { for (int run = 0; run < records.size(); ++run) { sprintf(buffer, "Run %d", run + 1); ImPlot::PlotLine(buffer, records[run].Data, records[run].Size); diff --git a/implot_internal.h b/implot_internal.h index 8272cc3..c1044b0 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -433,7 +433,7 @@ struct ImPlotState int CurrentYAxis; ImPlotState() { - Flags = PreviousFlags = ImPlotFlags_Default; + Flags = PreviousFlags = ImPlotFlags_None; SelectStart = QueryStart = ImVec2(0,0); Selecting = Querying = Queried = DraggingQuery = false; ColormapIdx = CurrentYAxis = 0; diff --git a/implot_items.cpp b/implot_items.cpp index 0138b0a..409db74 100644 --- a/implot_items.cpp +++ b/implot_items.cpp @@ -145,7 +145,7 @@ bool BeginItem(const char* label_id, ImPlotCol recolor_from) { s.Colors[ImPlotCol_Fill].w *= s.FillAlpha; // s.Colors[ImPlotCol_MarkerFill].w *= s.FillAlpha; // TODO: this should be separate, if it at all // apply highlight mods - if (item->LegendHovered && ImHasFlag(gp.CurrentPlot->Flags, ImPlotFlags_Highlight)) { + if (item->LegendHovered && !ImHasFlag(gp.CurrentPlot->Flags, ImPlotFlags_NoHighlight)) { s.LineWeight *= 2; s.MarkerWeight *= 2; // TODO: highlight fills? From be6e1c2d2ed73af683b4cb5bd2c6164b169b44a6 Mon Sep 17 00:00:00 2001 From: epezent Date: Sun, 6 Sep 2020 01:32:15 -0500 Subject: [PATCH 13/16] invert ImPlotAxisFlags --- implot.cpp | 37 +++++++++++++++++++------------------ implot.h | 42 ++++++++++++++++++++++-------------------- implot_demo.cpp | 27 +++++++++++++-------------- implot_internal.h | 4 ++-- 4 files changed, 56 insertions(+), 54 deletions(-) diff --git a/implot.cpp b/implot.cpp index 73ae917..0fec297 100644 --- a/implot.cpp +++ b/implot.cpp @@ -31,6 +31,7 @@ Below is a change-log of API breaking changes only. If you are using one of the When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all implot files. You can read releases logs https://github.com/epezent/implot/releases for more details. +- 2020/09/06 (0.7) - Several flags under ImPlotFlags and ImPlotAxisFlags were inverted so that the default flagset is simply 0. This more closely matches ImGui's style. - 2020/08/28 (0.5) - ImPlotMarker_ can no longer be combined with bitwise OR, |. This features caused unecessary slow-down, and almost no one used it. - 2020/08/25 (0.5) - ImPlotAxisFlags_Scientific was removed. Logarithmic axes automatically uses scientific notation. - 2020/08/17 (0.5) - PlotText was changed so that text is centered horizontally and vertically about the desired point. @@ -1120,14 +1121,14 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // canvas bb gp.BB_Canvas = ImRect(gp.BB_Frame.Min + gp.Style.PlotPadding, gp.BB_Frame.Max - gp.Style.PlotPadding); - gp.RenderX = (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_GridLines) || - ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickMarks) || - ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickLabels)); + gp.RenderX = (!ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_NoGridLines) || + !ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_NoTickMarks) || + !ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_NoTickLabels)); for (int i = 0; i < IMPLOT_Y_AXES; i++) { gp.RenderY[i] = gp.Y[i].Present && - (ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_GridLines) || - ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickMarks) || - ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickLabels)); + (!ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_NoGridLines) || + !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_NoTickMarks) || + !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_NoTickLabels)); } // plot bb @@ -1488,7 +1489,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } // render grid - if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_GridLines)) { + if (!ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_NoGridLines)) { float density = gp.XTicks.Size / gp.BB_Plot.GetWidth(); ImVec4 col_min = ImGui::ColorConvertU32ToFloat4(gp.Col_X.Minor); col_min.w *= ImClamp(ImRemap(density, 0.1f, 0.2f, 1.0f, 0.0f), 0.0f, 1.0f); @@ -1505,7 +1506,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } for (int i = 0; i < IMPLOT_Y_AXES; i++) { - if (gp.Y[i].Present && ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_GridLines)) { + if (gp.Y[i].Present && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_NoGridLines)) { float density = gp.YTicks[i].Size / gp.BB_Plot.GetHeight(); ImVec4 col_min = ImGui::ColorConvertU32ToFloat4(gp.Col_Y[i].Minor); col_min.w *= ImClamp(ImRemap(density, 0.1f, 0.2f, 1.0f, 0.0f), 0.0f, 1.0f); @@ -1543,7 +1544,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons // render tick labels ImGui::PushClipRect(gp.BB_Frame.Min, gp.BB_Frame.Max, true); - if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickLabels)) { + if (!ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_NoTickLabels)) { for (int t = 0; t < gp.XTicks.Size; t++) { ImPlotTick *xt = &gp.XTicks.Ticks[t]; if (xt->ShowLabel && xt->PixelPos >= gp.BB_Plot.Min.x - 1 && xt->PixelPos <= gp.BB_Plot.Max.x + 1) @@ -1552,7 +1553,7 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons } } for (int i = 0; i < IMPLOT_Y_AXES; i++) { - if (gp.Y[i].Present && ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickLabels)) { + if (gp.Y[i].Present && !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_NoTickLabels)) { for (int t = 0; t < gp.YTicks[i].Size; t++) { const float x_start = gp.YAxisReference[i] + (i == 0 ? (-gp.Style.LabelPadding.x - gp.YTicks[i].Ticks[t].LabelSize.x) : gp.Style.LabelPadding.x); ImPlotTick *yt = &gp.YTicks[i].Ticks[t]; @@ -1608,9 +1609,9 @@ inline void ShowAxisContextMenu(ImPlotAxisState& state, bool time_allowed) { bool total_lock = state.HasRange && state.RangeCond == ImGuiCond_Always; bool logscale = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_LogScale); bool timescale = 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); + bool grid = !ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_NoGridLines); + bool ticks = !ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_NoTickMarks); + bool labels = !ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_NoTickLabels); double drag_speed = (state.Axis->Range.Size() <= DBL_EPSILON) ? DBL_EPSILON * 1.0e+13 : 0.01 * state.Axis->Range.Size(); // recover from almost equal axis limits. BeginDisabledControls(total_lock); @@ -1655,11 +1656,11 @@ inline void ShowAxisContextMenu(ImPlotAxisState& state, bool time_allowed) { ImGui::Separator(); if (ImGui::Checkbox("Grid Lines", &grid)) - ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines); + ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_NoGridLines); if (ImGui::Checkbox("Tick Marks", &ticks)) - ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_TickMarks); + ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_NoTickMarks); if (ImGui::Checkbox("Labels", &labels)) - ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_TickLabels); + ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_NoTickLabels); } @@ -1756,7 +1757,7 @@ void EndPlot() { // render ticks PushPlotClipRect(); - if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickMarks)) { + if (!ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_NoTickMarks)) { for (int t = 0; t < gp.XTicks.Size; t++) { ImPlotTick *xt = &gp.XTicks.Ticks[t]; if (xt->Level == 0) @@ -1775,7 +1776,7 @@ void EndPlot() { axis_count++; float x_start = gp.YAxisReference[i]; - if (ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_TickMarks)) { + if (!ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_NoTickMarks)) { float direction = (i == 0) ? 1.0f : -1.0f; bool no_major = axis_count >= 3; for (int t = 0; t < gp.YTicks[i].Size; t++) { diff --git a/implot.h b/implot.h index 01e5ba7..4e87c5b 100644 --- a/implot.h +++ b/implot.h @@ -55,30 +55,32 @@ typedef int ImPlotColormap; // -> enum ImPlotColormap_ enum ImPlotFlags_ { ImPlotFlags_None = 0, // default ImPlotFlags_NoLegend = 1 << 0, // the top-left legend will not be displayed - ImPlotFlags_NoMenus = 1 << 1, // the user will be able to open context menus with double-right click - ImPlotFlags_NoBoxSelect = 1 << 2, // the user will be able to box-select with right-mouse + ImPlotFlags_NoMenus = 1 << 1, // the user will not be able to open context menus with double-right click + ImPlotFlags_NoBoxSelect = 1 << 2, // the user will not be able to box-select with right-mouse ImPlotFlags_NoMousePos = 1 << 3, // the mouse position, in plot coordinates, will not be displayed in the bottom-right - ImPlotFlags_NoHighlight = 1 << 4, // plot items will be highlighted when their legend entry is hovered + ImPlotFlags_NoHighlight = 1 << 4, // plot items will not be highlighted when their legend entry is hovered ImPlotFlags_NoChild = 1 << 5, // a child window region will not be used to capture mouse scroll (can boost performance for single ImGui window applications) - ImPlotFlags_YAxis2 = 1 << 6, // enable a 2nd y-axis - ImPlotFlags_YAxis3 = 1 << 7, // enable a 3rd y-axis + ImPlotFlags_YAxis2 = 1 << 6, // enable a 2nd y-axis on the right side + ImPlotFlags_YAxis3 = 1 << 7, // enable a 3rd y-axis on the right side ImPlotFlags_Query = 1 << 8, // the user will be able to draw query rects with middle-mouse ImPlotFlags_Crosshairs = 1 << 9, // the default mouse cursor will be replaced with a crosshair when hovered - ImPlotFlags_AntiAliased = 1 << 10 // plot lines will be software anti-aliased (not recommended for density plots, prefer MSAA) + ImPlotFlags_AntiAliased = 1 << 10, // plot lines will be software anti-aliased (not recommended for density plots, prefer MSAA) + ImPlotFlags_CanvasOnly = ImPlotFlags_NoLegend | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoMousePos }; // Options for plot axes (X and Y). enum ImPlotAxisFlags_ { - ImPlotAxisFlags_GridLines = 1 << 0, // grid lines will be displayed - ImPlotAxisFlags_TickMarks = 1 << 1, // tick marks will be displayed for each grid line - ImPlotAxisFlags_TickLabels = 1 << 2, // text labels will be displayed for each grid line - ImPlotAxisFlags_Invert = 1 << 3, // the axis will be inverted - ImPlotAxisFlags_LockMin = 1 << 4, // the axis minimum value will be locked when panning/zooming - ImPlotAxisFlags_LockMax = 1 << 5, // the axis maximum value will be locked when panning/zooming - ImPlotAxisFlags_LogScale = 1 << 6, // a logartithmic (base 10) axis scale will be used - ImPlotAxisFlags_Time = 1 << 7, // axis will display data/time formatted labels - ImPlotAxisFlags_Default = ImPlotAxisFlags_GridLines | ImPlotAxisFlags_TickMarks | ImPlotAxisFlags_TickLabels, - ImPlotAxisFlags_Auxiliary = ImPlotAxisFlags_TickMarks | ImPlotAxisFlags_TickLabels, + ImPlotAxisFlags_None = 0, // default + ImPlotAxisFlags_NoGridLines = 1 << 0, // no grid lines will be displayed + ImPlotAxisFlags_NoTickMarks = 1 << 1, // no tick marks will be displayed + ImPlotAxisFlags_NoTickLabels = 1 << 2, // no text labels will be displayed + ImPlotAxisFlags_LogScale = 1 << 3, // a logartithmic (base 10) axis scale will be used (mutually exclusive with ImPlotAxisFlags_Time) + ImPlotAxisFlags_Time = 1 << 4, // axis will display date/time formatted labels (mutually exclusive with ImPlotAxisFlags_LogScale) + ImPlotAxisFlags_Invert = 1 << 5, // the axis will be inverted + ImPlotAxisFlags_LockMin = 1 << 6, // the axis minimum value will be locked when panning/zooming + ImPlotAxisFlags_LockMax = 1 << 7, // the axis maximum value will be locked when panning/zooming + ImPlotAxisFlags_Lock = ImPlotAxisFlags_LockMin | ImPlotAxisFlags_LockMax, + ImPlotAxisFlags_NoTicks = ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks | ImPlotAxisFlags_NoTickLabels }; // Plot styling colors. @@ -283,10 +285,10 @@ bool BeginPlot(const char* title_id, const char* y_label = NULL, const ImVec2& size = ImVec2(-1,0), ImPlotFlags flags = ImPlotFlags_None, - ImPlotAxisFlags x_flags = ImPlotAxisFlags_Default, - ImPlotAxisFlags y_flags = ImPlotAxisFlags_Default, - ImPlotAxisFlags y2_flags = ImPlotAxisFlags_Auxiliary, - ImPlotAxisFlags y3_flags = ImPlotAxisFlags_Auxiliary); + ImPlotAxisFlags x_flags = ImPlotAxisFlags_None, + ImPlotAxisFlags y_flags = ImPlotAxisFlags_None, + ImPlotAxisFlags y2_flags = ImPlotAxisFlags_NoGridLines, + ImPlotAxisFlags y3_flags = ImPlotAxisFlags_NoGridLines); // Only call EndPlot() if BeginPlot() returns true! Typically called at the end // of an if statement conditioned on BeginPlot(). diff --git a/implot_demo.cpp b/implot_demo.cpp index 065008c..4d765f6 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -346,8 +346,7 @@ void ShowDemoWindow(bool* p_open) { ImPlot::SetNextPlotTicksX(positions, 10, labels); } if (ImPlot::BeginPlot("Bar Plot", horz ? "Score" : "Student", horz ? "Student" : "Score", - ImVec2(-1,0), 0, ImPlotAxisFlags_Default, - horz ? ImPlotAxisFlags_Default | ImPlotAxisFlags_Invert : ImPlotAxisFlags_Default)) + ImVec2(-1,0), 0, 0, horz ? ImPlotAxisFlags_Invert : 0)) { if (horz) { ImPlot::PlotBarsH("Midterm Exam", midtm, 10, 0.2f, -0.2f); @@ -426,7 +425,7 @@ void ShowDemoWindow(bool* p_open) { } ImPlot::SetNextPlotLimits(0,1,0,1,ImGuiCond_Always); - if (ImPlot::BeginPlot("##Pie1", NULL, NULL, ImVec2(250,250), ImPlotFlags_NoMousePos | ImPlotFlags_NoMousePos, 0, 0)) { + if (ImPlot::BeginPlot("##Pie1", NULL, NULL, ImVec2(250,250), ImPlotFlags_NoMousePos, ImPlotAxisFlags_NoTicks, ImPlotAxisFlags_NoTicks)) { ImPlot::PlotPieChart(labels1, data1, 4, 0.5f, 0.5f, 0.4f, normalize, "%.2f"); ImPlot::EndPlot(); } @@ -438,7 +437,7 @@ void ShowDemoWindow(bool* p_open) { ImPlot::PushColormap(ImPlotColormap_Pastel); ImPlot::SetNextPlotLimits(0,1,0,1,ImGuiCond_Always); - if (ImPlot::BeginPlot("##Pie2", NULL, NULL, ImVec2(250,250), ImPlotFlags_NoMousePos | ImPlotFlags_NoMousePos, 0, 0)) { + if (ImPlot::BeginPlot("##Pie2", NULL, NULL, ImVec2(250,250), ImPlotFlags_NoMousePos, ImPlotAxisFlags_NoTicks, ImPlotAxisFlags_NoTicks)) { ImPlot::PlotPieChart(labels2, data2, 5, 0.5f, 0.5f, 0.4f, true, "%.0f", 180); ImPlot::EndPlot(); } @@ -470,12 +469,12 @@ void ShowDemoWindow(bool* p_open) { ImGui::LabelText("##Colormap Index", "%s", ImPlot::GetColormapName(map)); ImGui::SetNextItemWidth(225); ImGui::DragFloatRange2("Min / Max",&scale_min, &scale_max, 0.01f, -20, 20); - static ImPlotAxisFlags axes_flags = ImPlotAxisFlags_LockMin | ImPlotAxisFlags_LockMax | ImPlotAxisFlags_TickLabels; + static ImPlotAxisFlags axes_flags = ImPlotAxisFlags_Lock | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks; ImPlot::PushColormap(map); SetNextPlotTicksX(0 + 1.0/14.0, 1 - 1.0/14.0, 7, xlabels); SetNextPlotTicksY(1 - 1.0/14.0, 0 + 1.0/14.0, 7, ylabels); - if (ImPlot::BeginPlot("##Heatmap1",NULL,NULL,ImVec2(225,225),ImPlotFlags_NoLegend,axes_flags,axes_flags)) { + if (ImPlot::BeginPlot("##Heatmap1",NULL,NULL,ImVec2(225,225),ImPlotFlags_NoLegend|ImPlotFlags_NoMousePos,axes_flags,axes_flags)) { ImPlot::PlotHeatmap("heat",values1[0],7,7,scale_min,scale_max); ImPlot::EndPlot(); } @@ -488,7 +487,7 @@ void ShowDemoWindow(bool* p_open) { static ImVec4 gray[2] = {ImVec4(0,0,0,1), ImVec4(1,1,1,1)}; ImPlot::PushColormap(gray, 2); ImPlot::SetNextPlotLimits(-1,1,-1,1); - if (ImPlot::BeginPlot("##Heatmap2",NULL,NULL,ImVec2(225,225),0,0,0)) { + if (ImPlot::BeginPlot("##Heatmap2",NULL,NULL,ImVec2(225,225),0,ImPlotAxisFlags_NoTicks,ImPlotAxisFlags_NoTicks)) { ImPlot::PlotHeatmap("heat1",values2,100,100,0,1,NULL); ImPlot::PlotHeatmap("heat2",values2,100,100,0,1,NULL, ImPlotPoint(-1,-1), ImPlotPoint(0,0)); ImPlot::EndPlot(); @@ -514,7 +513,7 @@ void ShowDemoWindow(bool* p_open) { rdata1.Span = history; rdata2.Span = history; - static ImPlotAxisFlags rt_axis = ImPlotAxisFlags_Default & ~ImPlotAxisFlags_TickLabels; + static ImPlotAxisFlags rt_axis = ImPlotAxisFlags_NoTickLabels; ImPlot::SetNextPlotLimitsX(t - history, t, ImGuiCond_Always); if (ImPlot::BeginPlot("##Scrolling", NULL, NULL, ImVec2(-1,150), 0, rt_axis, rt_axis | ImPlotAxisFlags_LockMin)) { ImPlot::PlotShaded("Data 1", &sdata1.Data[0].x, &sdata1.Data[0].y, sdata1.Data.size(), 0, sdata1.Offset, 2 * sizeof(t_float)); @@ -583,7 +582,7 @@ void ShowDemoWindow(bool* p_open) { ImGui::BulletText("Open the plot context menu (double right click) to change scales."); ImPlot::SetNextPlotLimits(0.1, 100, 0, 10); - if (ImPlot::BeginPlot("Log Plot", NULL, NULL, ImVec2(-1,0), 0, ImPlotAxisFlags_Default | ImPlotAxisFlags_LogScale )) { + if (ImPlot::BeginPlot("Log Plot", NULL, NULL, ImVec2(-1,0), 0, ImPlotAxisFlags_LogScale )) { ImPlot::PlotLine("f(x) = x", xs, xs, 1001); ImPlot::PlotLine("f(x) = sin(x)+1", xs, ys1, 1001); ImPlot::PlotLine("f(x) = log(x)", xs, ys2, 1001); @@ -595,7 +594,7 @@ void ShowDemoWindow(bool* p_open) { static double min = 1577836800; // 01/01/2020 @ 12:00:00am (UTC) static double max = 1609459200; // 01/01/2021 @ 12:00:00am (UTC) ImPlot::SetNextPlotLimits(min,max,0,1); - if (ImPlot::BeginPlot("##Time", "UTC Time", "Y-Axis", ImVec2(-1,0), 0, ImPlotAxisFlags_Default | ImPlotAxisFlags_Time)) { + if (ImPlot::BeginPlot("##Time", "UTC Time", "Y-Axis", ImVec2(-1,0), 0, ImPlotAxisFlags_Time)) { ImPlot::EndPlot(); } @@ -655,7 +654,7 @@ void ShowDemoWindow(bool* p_open) { ImGui::BulletText("The query rect can be dragged after it's created."); ImGui::Unindent(); - if (ImPlot::BeginPlot("##Drawing", NULL, NULL, ImVec2(-1,0), ImPlotFlags_Query, ImPlotAxisFlags_GridLines, ImPlotAxisFlags_GridLines)) { + if (ImPlot::BeginPlot("##Drawing", NULL, NULL, ImVec2(-1,0), ImPlotFlags_Query)) { if (ImPlot::IsPlotHovered() && ImGui::IsMouseClicked(0) && ImGui::GetIO().KeyCtrl) { ImPlotPoint pt = ImPlot::GetPlotMousePos(); data.push_back(t_float2((t_float)pt.x, (t_float)pt.y)); @@ -706,7 +705,7 @@ void ShowDemoWindow(bool* p_open) { } ImGui::BulletText("Query the first plot to render a subview in the second plot (see above for controls)."); ImPlot::SetNextPlotLimits(0,0.01,-1,1); - ImPlotAxisFlags flags = ImPlotAxisFlags_Default & ~ImPlotAxisFlags_TickLabels; + ImPlotAxisFlags flags = ImPlotAxisFlags_NoTickLabels; ImPlotLimits query; if (ImPlot::BeginPlot("##View1",NULL,NULL,ImVec2(-1,150), ImPlotFlags_Query, flags, flags)) { ImPlot::PlotLine("Signal 1", x_data, y_data1, 512); @@ -1135,7 +1134,7 @@ void ShowDemoWindow(bool* p_open) { ImGui::SameLine(); ImGui::ColorEdit4("##Bull", &bullCol.x, ImGuiColorEditFlags_NoInputs); ImGui::SameLine(); ImGui::ColorEdit4("##Bear", &bearCol.x, ImGuiColorEditFlags_NoInputs); ImPlot::SetNextPlotLimits(1546300800, 1571961600, 1250, 1600); - if (ImPlot::BeginPlot("Candlestick Chart","Day","USD",ImVec2(-1,-1),0,ImPlotAxisFlags_Default | ImPlotAxisFlags_Time)) { + if (ImPlot::BeginPlot("Candlestick Chart","Day","USD",ImVec2(-1,-1),0,ImPlotAxisFlags_Time)) { MyImPlot::PlotCandlestick("GOOGL",dates, opens, closes, lows, highs, 218, tooltip, 0.25f, bullCol, bearCol); ImPlot::EndPlot(); } @@ -1175,7 +1174,7 @@ ImPlotPoint Spiral(void*, int idx) { void Sparkline(const char* id, const float* values, int count, float min_v, float max_v, int offset, const ImVec4& col, const ImVec2& size) { ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, ImVec2(0,0)); ImPlot::SetNextPlotLimits(0, count - 1, min_v, max_v, ImGuiCond_Always); - if (ImPlot::BeginPlot(id,0,0,size,ImPlotFlags_NoChild,0,0,0,0)) { + if (ImPlot::BeginPlot(id,0,0,size,ImPlotFlags_CanvasOnly|ImPlotFlags_NoChild,ImPlotAxisFlags_NoTicks,ImPlotAxisFlags_NoTicks)) { ImPlot::PushStyleColor(ImPlotCol_Line, col); ImPlot::PlotLine(id, values, count, offset); ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f); diff --git a/implot_internal.h b/implot_internal.h index c1044b0..b01a6dc 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -297,7 +297,7 @@ struct ImPlotAxis bool HoveredTot; ImPlotAxis() { - Flags = PreviousFlags = ImPlotAxisFlags_Default; + Flags = PreviousFlags = ImPlotAxisFlags_None; Range.Min = 0; Range.Max = 1; Dragging = false; @@ -374,7 +374,7 @@ struct ImPlotAxisState HasRange = has_range; RangeCond = range_cond; Present = present; - HasLabels = ImHasFlag(Axis->Flags, ImPlotAxisFlags_TickLabels); + HasLabels = !ImHasFlag(Axis->Flags, ImPlotAxisFlags_NoTickLabels); Invert = ImHasFlag(Axis->Flags, ImPlotAxisFlags_Invert); LockMin = ImHasFlag(Axis->Flags, ImPlotAxisFlags_LockMin) || (HasRange && RangeCond == ImGuiCond_Always); LockMax = ImHasFlag(Axis->Flags, ImPlotAxisFlags_LockMax) || (HasRange && RangeCond == ImGuiCond_Always); From 6f3f43c815a1c997a25732397b9699ececd54938 Mon Sep 17 00:00:00 2001 From: epezent Date: Sun, 6 Sep 2020 01:38:18 -0500 Subject: [PATCH 14/16] ImPlotAxisFlags_NoDecorations --- implot.cpp | 3 ++- implot.h | 22 +++++++++++----------- implot_demo.cpp | 8 ++++---- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/implot.cpp b/implot.cpp index 0fec297..d238e35 100644 --- a/implot.cpp +++ b/implot.cpp @@ -31,7 +31,8 @@ Below is a change-log of API breaking changes only. If you are using one of the When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all implot files. You can read releases logs https://github.com/epezent/implot/releases for more details. -- 2020/09/06 (0.7) - Several flags under ImPlotFlags and ImPlotAxisFlags were inverted so that the default flagset is simply 0. This more closely matches ImGui's style. +- 2020/09/06 (0.7) - Several flags under ImPlotFlags and ImPlotAxisFlags were inverted (e.g. ImPlotFlags_Legend -> ImPlotFlags_NoLegend) so that the default flagset + is simply 0. This more closely matches ImGui's style and makes it easier to enable non-default but commonly used flags (e.g. ImPlotAxisFlags_Time). - 2020/08/28 (0.5) - ImPlotMarker_ can no longer be combined with bitwise OR, |. This features caused unecessary slow-down, and almost no one used it. - 2020/08/25 (0.5) - ImPlotAxisFlags_Scientific was removed. Logarithmic axes automatically uses scientific notation. - 2020/08/17 (0.5) - PlotText was changed so that text is centered horizontally and vertically about the desired point. diff --git a/implot.h b/implot.h index 4e87c5b..9b47235 100644 --- a/implot.h +++ b/implot.h @@ -70,17 +70,17 @@ enum ImPlotFlags_ { // Options for plot axes (X and Y). enum ImPlotAxisFlags_ { - ImPlotAxisFlags_None = 0, // default - ImPlotAxisFlags_NoGridLines = 1 << 0, // no grid lines will be displayed - ImPlotAxisFlags_NoTickMarks = 1 << 1, // no tick marks will be displayed - ImPlotAxisFlags_NoTickLabels = 1 << 2, // no text labels will be displayed - ImPlotAxisFlags_LogScale = 1 << 3, // a logartithmic (base 10) axis scale will be used (mutually exclusive with ImPlotAxisFlags_Time) - ImPlotAxisFlags_Time = 1 << 4, // axis will display date/time formatted labels (mutually exclusive with ImPlotAxisFlags_LogScale) - ImPlotAxisFlags_Invert = 1 << 5, // the axis will be inverted - ImPlotAxisFlags_LockMin = 1 << 6, // the axis minimum value will be locked when panning/zooming - ImPlotAxisFlags_LockMax = 1 << 7, // the axis maximum value will be locked when panning/zooming - ImPlotAxisFlags_Lock = ImPlotAxisFlags_LockMin | ImPlotAxisFlags_LockMax, - ImPlotAxisFlags_NoTicks = ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks | ImPlotAxisFlags_NoTickLabels + ImPlotAxisFlags_None = 0, // default + ImPlotAxisFlags_NoGridLines = 1 << 0, // no grid lines will be displayed + ImPlotAxisFlags_NoTickMarks = 1 << 1, // no tick marks will be displayed + ImPlotAxisFlags_NoTickLabels = 1 << 2, // no text labels will be displayed + ImPlotAxisFlags_LogScale = 1 << 3, // a logartithmic (base 10) axis scale will be used (mutually exclusive with ImPlotAxisFlags_Time) + ImPlotAxisFlags_Time = 1 << 4, // axis will display date/time formatted labels (mutually exclusive with ImPlotAxisFlags_LogScale) + ImPlotAxisFlags_Invert = 1 << 5, // the axis will be inverted + ImPlotAxisFlags_LockMin = 1 << 6, // the axis minimum value will be locked when panning/zooming + ImPlotAxisFlags_LockMax = 1 << 7, // the axis maximum value will be locked when panning/zooming + ImPlotAxisFlags_Lock = ImPlotAxisFlags_LockMin | ImPlotAxisFlags_LockMax, + ImPlotAxisFlags_NoDecorations = ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks | ImPlotAxisFlags_NoTickLabels }; // Plot styling colors. diff --git a/implot_demo.cpp b/implot_demo.cpp index 4d765f6..d13a891 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -425,7 +425,7 @@ void ShowDemoWindow(bool* p_open) { } ImPlot::SetNextPlotLimits(0,1,0,1,ImGuiCond_Always); - if (ImPlot::BeginPlot("##Pie1", NULL, NULL, ImVec2(250,250), ImPlotFlags_NoMousePos, ImPlotAxisFlags_NoTicks, ImPlotAxisFlags_NoTicks)) { + if (ImPlot::BeginPlot("##Pie1", NULL, NULL, ImVec2(250,250), ImPlotFlags_NoMousePos, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations)) { ImPlot::PlotPieChart(labels1, data1, 4, 0.5f, 0.5f, 0.4f, normalize, "%.2f"); ImPlot::EndPlot(); } @@ -437,7 +437,7 @@ void ShowDemoWindow(bool* p_open) { ImPlot::PushColormap(ImPlotColormap_Pastel); ImPlot::SetNextPlotLimits(0,1,0,1,ImGuiCond_Always); - if (ImPlot::BeginPlot("##Pie2", NULL, NULL, ImVec2(250,250), ImPlotFlags_NoMousePos, ImPlotAxisFlags_NoTicks, ImPlotAxisFlags_NoTicks)) { + if (ImPlot::BeginPlot("##Pie2", NULL, NULL, ImVec2(250,250), ImPlotFlags_NoMousePos, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations)) { ImPlot::PlotPieChart(labels2, data2, 5, 0.5f, 0.5f, 0.4f, true, "%.0f", 180); ImPlot::EndPlot(); } @@ -487,7 +487,7 @@ void ShowDemoWindow(bool* p_open) { static ImVec4 gray[2] = {ImVec4(0,0,0,1), ImVec4(1,1,1,1)}; ImPlot::PushColormap(gray, 2); ImPlot::SetNextPlotLimits(-1,1,-1,1); - if (ImPlot::BeginPlot("##Heatmap2",NULL,NULL,ImVec2(225,225),0,ImPlotAxisFlags_NoTicks,ImPlotAxisFlags_NoTicks)) { + if (ImPlot::BeginPlot("##Heatmap2",NULL,NULL,ImVec2(225,225),0,ImPlotAxisFlags_NoDecorations,ImPlotAxisFlags_NoDecorations)) { ImPlot::PlotHeatmap("heat1",values2,100,100,0,1,NULL); ImPlot::PlotHeatmap("heat2",values2,100,100,0,1,NULL, ImPlotPoint(-1,-1), ImPlotPoint(0,0)); ImPlot::EndPlot(); @@ -1174,7 +1174,7 @@ ImPlotPoint Spiral(void*, int idx) { void Sparkline(const char* id, const float* values, int count, float min_v, float max_v, int offset, const ImVec4& col, const ImVec2& size) { ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, ImVec2(0,0)); ImPlot::SetNextPlotLimits(0, count - 1, min_v, max_v, ImGuiCond_Always); - if (ImPlot::BeginPlot(id,0,0,size,ImPlotFlags_CanvasOnly|ImPlotFlags_NoChild,ImPlotAxisFlags_NoTicks,ImPlotAxisFlags_NoTicks)) { + if (ImPlot::BeginPlot(id,0,0,size,ImPlotFlags_CanvasOnly|ImPlotFlags_NoChild,ImPlotAxisFlags_NoDecorations,ImPlotAxisFlags_NoDecorations)) { ImPlot::PushStyleColor(ImPlotCol_Line, col); ImPlot::PlotLine(id, values, count, offset); ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f); From d23bd30a44ff0e6b2a7f36376d6785fa3ea56a7c Mon Sep 17 00:00:00 2001 From: epezent Date: Sun, 6 Sep 2020 01:48:47 -0500 Subject: [PATCH 15/16] header cleanup --- implot.h | 48 +++++++++++++++++++++++------------------------ implot_internal.h | 26 +++++++++---------------- 2 files changed, 33 insertions(+), 41 deletions(-) diff --git a/implot.h b/implot.h index 9b47235..444ce01 100644 --- a/implot.h +++ b/implot.h @@ -207,31 +207,31 @@ struct ImPlotLimits { // Plot style structure struct ImPlotStyle { // item styling variables - float LineWeight; // = 1, item line weight in pixels - ImPlotMarker Marker; // = ImPlotMarker_None, marker specification - float MarkerSize; // = 4, marker size in pixels (roughly the marker's "radius") - float MarkerWeight; // = 1, outline weight of markers in pixels - float FillAlpha; // = 1, alpha modifier applied to plot fills - float ErrorBarSize; // = 5, error bar whisker width in pixels - float ErrorBarWeight; // = 1.5, error bar whisker weight in pixels - float DigitalBitHeight; // = 8, digital channels bit height (at y = 1.0f) in pixels - float DigitalBitGap; // = 4, digital channels bit padding gap in pixels + float LineWeight; // = 1, item line weight in pixels + int Marker; // = ImPlotMarker_None, marker specification + float MarkerSize; // = 4, marker size in pixels (roughly the marker's "radius") + float MarkerWeight; // = 1, outline weight of markers in pixels + float FillAlpha; // = 1, alpha modifier applied to plot fills + float ErrorBarSize; // = 5, error bar whisker width in pixels + float ErrorBarWeight; // = 1.5, error bar whisker weight in pixels + float DigitalBitHeight; // = 8, digital channels bit height (at y = 1.0f) in pixels + float DigitalBitGap; // = 4, digital channels bit padding gap in pixels // plot styling variables - bool AntiAliasedLines; // = false, enable global anti-aliasing on plot lines (overrides ImPlotFlags_AntiAliased) - float PlotBorderSize; // = 1, line thickness of border around plot area - float MinorAlpha; // = 0.25 alpha multiplier applied to minor axis grid lines - ImVec2 MajorTickLen; // = 10,10 major tick lengths for X and Y axes - ImVec2 MinorTickLen; // = 5,5 minor tick lengths for X and Y axes - ImVec2 MajorTickSize; // = 1,1 line thickness of major ticks - ImVec2 MinorTickSize; // = 1,1 line thickness of minor ticks - ImVec2 MajorGridSize; // = 1,1 line thickness of major grid lines - ImVec2 MinorGridSize; // = 1,1 line thickness of minor grid lines - ImVec2 PlotPadding; // = 8,8 padding between widget frame and plot area and/or labels - ImVec2 LabelPadding; // = 5,5 padding between axes labels, tick labels, and plot edge - ImVec2 LegendPadding; // = 10,10 legend padding from top-left of plot - ImVec2 InfoPadding; // = 10,10 padding between plot edge and interior info text - ImVec2 PlotMinSize; // = 300,225 minimum size plot frame can be when shrunk - ImVec4 Colors[ImPlotCol_COUNT]; // array of plot specific colors + bool AntiAliasedLines; // = false, enable global anti-aliasing on plot lines (overrides ImPlotFlags_AntiAliased) + float PlotBorderSize; // = 1, line thickness of border around plot area + float MinorAlpha; // = 0.25 alpha multiplier applied to minor axis grid lines + ImVec2 MajorTickLen; // = 10,10 major tick lengths for X and Y axes + ImVec2 MinorTickLen; // = 5,5 minor tick lengths for X and Y axes + ImVec2 MajorTickSize; // = 1,1 line thickness of major ticks + ImVec2 MinorTickSize; // = 1,1 line thickness of minor ticks + ImVec2 MajorGridSize; // = 1,1 line thickness of major grid lines + ImVec2 MinorGridSize; // = 1,1 line thickness of minor grid lines + ImVec2 PlotPadding; // = 8,8 padding between widget frame and plot area and/or labels + ImVec2 LabelPadding; // = 5,5 padding between axes labels, tick labels, and plot edge + ImVec2 LegendPadding; // = 10,10 legend padding from top-left of plot + ImVec2 InfoPadding; // = 10,10 padding between plot edge and interior info text + ImVec2 PlotMinSize; // = 300,225 minimum size plot frame can be when shrunk + ImVec4 Colors[ImPlotCol_COUNT]; // array of plot specific colors ImPlotStyle(); }; diff --git a/implot_internal.h b/implot_internal.h index b01a6dc..de76130 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -183,8 +183,8 @@ enum ImPlotTimeUnit_ { }; enum ImPlotTimeFmt_ { - ImPlotTimeFmt_Us, // .428552 - ImPlotTimeFmt_SUs, // :29.428552 + ImPlotTimeFmt_Us, // .428 552 + ImPlotTimeFmt_SUs, // :29.428 552 ImPlotTimeFmt_SMs, // :29.428 ImPlotTimeFmt_S, // :29 ImPlotTimeFmt_HrMinS, // 7:21:29pm @@ -579,21 +579,13 @@ struct ImPlotAxisScale /// Two part time struct. struct ImPlotTime { - time_t S; - int Us; + time_t S; // second part + int Us; // microsecond part ImPlotTime() { S = 0; Us = 0; } - ImPlotTime(time_t s, int us = 0) { - S = s + us / 1000000; - Us = us % 1000000; - } - void RollOver() { - S = S + Us / 1000000; - Us = Us % 1000000; - } - static ImPlotTime FromDouble(double t) { - return ImPlotTime((time_t)t, (int)(t * 1000000 - floor(t) * 1000000)); - } - double ToDouble() const { return (double)S + (double)Us / 1000000.0; } + ImPlotTime(time_t s, int us = 0) { S = s + us / 1000000; Us = us % 1000000; } + void RollOver() { S = S + Us / 1000000; Us = Us % 1000000; } + double ToDouble() const { return (double)S + (double)Us / 1000000.0; } + static ImPlotTime FromDouble(double t) { return ImPlotTime((time_t)t, (int)(t * 1000000 - floor(t) * 1000000)); } }; static inline ImPlotTime operator+(const ImPlotTime& lhs, const ImPlotTime& rhs) @@ -785,7 +777,7 @@ inline T OffsetAndStride(const T* data, int idx, int count, int offset, int stri // Time Utils //----------------------------------------------------------------------------- -// NB: These functions only work if there is a currnet context because the +// NB: These functions only work if there is a current ImPlotContext because the // internal tm struct is owned by the context! // Returns true if year is leap year (366 days long) From 901f0558b16f53609d854f0b21ffb177fcd7b495 Mon Sep 17 00:00:00 2001 From: epezent Date: Sun, 6 Sep 2020 14:48:16 -0500 Subject: [PATCH 16/16] fix up time format demo --- implot.cpp | 62 +++++++++++++++++++++++++++++++++++------------ implot.h | 5 +++- implot_demo.cpp | 62 +++++++++++++++++++++++++++++++++++++++++++---- implot_internal.h | 18 +++++++++++--- 4 files changed, 122 insertions(+), 25 deletions(-) diff --git a/implot.cpp b/implot.cpp index d238e35..ba213b8 100644 --- a/implot.cpp +++ b/implot.cpp @@ -107,7 +107,6 @@ ImPlotStyle::ImPlotStyle() { ErrorBarWeight = 1.5f; DigitalBitHeight = 8; DigitalBitGap = 4; - AntiAliasedLines = false; PlotBorderSize = 1; MinorAlpha = 0.25f; @@ -124,6 +123,9 @@ ImPlotStyle::ImPlotStyle() { PlotMinSize = ImVec2(300,225); ImPlot::StyleColorsAuto(this); + + AntiAliasedLines = false; + UseLocalTime = false; } namespace ImPlot { @@ -646,7 +648,7 @@ inline int GetTimeStep(int max_divs, ImPlotTimeUnit unit) { return 0; } -inline time_t MakeGmTime(const struct tm *ptm) { +ImPlotTime MkGmtTime(struct tm *ptm) { time_t secs = 0; int year = ptm->tm_year + 1900; for (int y = 1970; y < year; ++y) @@ -657,21 +659,52 @@ inline time_t MakeGmTime(const struct tm *ptm) { secs += ptm->tm_hour * 3600; secs += ptm->tm_min * 60; secs += ptm->tm_sec; - return secs; + return ImPlotTime(secs,0); } -inline tm* GetGmTime(const time_t* time, tm* tm) +tm* GetGmtTime(const ImPlotTime& t, tm* ptm) { #ifdef _WIN32 - if (gmtime_s(tm, time) == 0) - return tm; + if (gmtime_s(ptm, &t.S) == 0) + return ptm; else return NULL; #else - return gmtime_r(time, tm); + return gmtime_r(&t.S, ptm); #endif } +ImPlotTime MkLocTime(struct tm *ptm) { + ImPlotTime t; + t.S = mktime(ptm); + return t; +} + +tm* GetLocTime(const ImPlotTime& t, tm* ptm) { +#ifdef _WIN32 + if (localtime_s(ptm, &t.S) == 0) + return ptm; + else + return NULL; +#else + return localtime_r(&t.S, ptm); +#endif +} + +inline ImPlotTime MkTime(struct tm *ptm) { + if (GetStyle().UseLocalTime) + return MkLocTime(ptm); + else + return MkGmtTime(ptm); +} + +inline tm* GetTime(const ImPlotTime& t, tm* ptm) { + if (GetStyle().UseLocalTime) + return GetLocTime(t,ptm); + else + return GetGmtTime(t,ptm); +} + ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count) { tm& Tm = GImPlot->Tm; ImPlotTime t_out = t; @@ -683,7 +716,7 @@ ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count) { case ImPlotTimeUnit_Hr: t_out.S += count * 3600; break; case ImPlotTimeUnit_Day: t_out.S += count * 86400; break; case ImPlotTimeUnit_Mo: for (int i = 0; i < count; ++i) { // this might have a bug - GetGmTime(&t.S, &Tm); + GetTime(t_out, &Tm); t_out.S += 86400 * GetDaysInMonth(Tm.tm_year + 1900, Tm.tm_mon); } break; @@ -701,8 +734,7 @@ ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count) { } ImPlotTime FloorTime(const ImPlotTime& t, ImPlotTimeUnit unit) { - GetGmTime(&t.S, &GImPlot->Tm); - GImPlot->Tm.tm_isdst = -1; + GetTime(t, &GImPlot->Tm); switch (unit) { case ImPlotTimeUnit_S: return ImPlotTime(t.S, 0); case ImPlotTimeUnit_Ms: return ImPlotTime(t.S, (t.Us / 1000) * 1000); @@ -714,7 +746,7 @@ ImPlotTime FloorTime(const ImPlotTime& t, ImPlotTimeUnit unit) { case ImPlotTimeUnit_Min: GImPlot->Tm.tm_sec = 0; break; default: return t; } - return ImPlotTime(MakeGmTime(&GImPlot->Tm),0); + return MkTime(&GImPlot->Tm); } ImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit) { @@ -731,7 +763,7 @@ ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit) { int GetYear(const ImPlotTime& t) { tm& Tm = GImPlot->Tm; - GetGmTime(&t.S, &Tm); + GetTime(t, &Tm); return Tm.tm_year + 1900; } @@ -747,14 +779,12 @@ ImPlotTime MakeYear(int year) { Tm.tm_mon = 0; Tm.tm_year = yr; Tm.tm_sec = 0; - Tm.tm_isdst = -1; - time_t s = MakeGmTime(&Tm); - return ImPlotTime(s); + return MkTime(&Tm); } int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt) { tm& Tm = GImPlot->Tm; - GetGmTime(&t.S, &Tm); + GetTime(t, &Tm); const char* ap = Tm.tm_hour < 12 ? "am" : "pm"; const int us = t.Us % 1000; diff --git a/implot.h b/implot.h index 444ce01..1c4a6d9 100644 --- a/implot.h +++ b/implot.h @@ -217,7 +217,6 @@ struct ImPlotStyle { float DigitalBitHeight; // = 8, digital channels bit height (at y = 1.0f) in pixels float DigitalBitGap; // = 4, digital channels bit padding gap in pixels // plot styling variables - bool AntiAliasedLines; // = false, enable global anti-aliasing on plot lines (overrides ImPlotFlags_AntiAliased) float PlotBorderSize; // = 1, line thickness of border around plot area float MinorAlpha; // = 0.25 alpha multiplier applied to minor axis grid lines ImVec2 MajorTickLen; // = 10,10 major tick lengths for X and Y axes @@ -231,7 +230,11 @@ struct ImPlotStyle { ImVec2 LegendPadding; // = 10,10 legend padding from top-left of plot ImVec2 InfoPadding; // = 10,10 padding between plot edge and interior info text ImVec2 PlotMinSize; // = 300,225 minimum size plot frame can be when shrunk + // colors ImVec4 Colors[ImPlotCol_COUNT]; // array of plot specific colors + // settings/flags + bool AntiAliasedLines; // = false, enable global anti-aliasing on plot lines (overrides ImPlotFlags_AntiAliased) + bool UseLocalTime; // = false, axis labels will be formatted for your timezone when ImPlotAxisFlag_Time is enabled ImPlotStyle(); }; diff --git a/implot_demo.cpp b/implot_demo.cpp index d13a891..f170e19 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #ifdef _MSC_VER #define sprintf sprintf_s @@ -124,6 +125,25 @@ struct RollingBuffer { } }; +// Huge data used by Time Formatting example (~500 MB allocation!) +struct HugeTimeData { + HugeTimeData(double min) { + Ts = new double[Size]; + Ys = new double[Size]; + for (int i = 0; i < Size; ++i) { + Ts[i] = min + i; + Ys[i] = GetY(Ts[i]); + } + } + ~HugeTimeData() { delete[] Ts; delete[] Ys; } + static double GetY(double t) { + return 0.5 + 0.25 * sin(t/86400/12) + 0.005 * sin(t/3600); + } + double* Ts; + double* Ys; + static const int Size = 60*60*24*366; +}; + void ShowDemoWindow(bool* p_open) { t_float DEMO_TIME = (t_float)ImGui::GetTime(); static bool show_imgui_metrics = false; @@ -591,11 +611,42 @@ void ShowDemoWindow(bool* p_open) { } } if (ImGui::CollapsingHeader("Time Formatted Axes")) { - static double min = 1577836800; // 01/01/2020 @ 12:00:00am (UTC) - static double max = 1609459200; // 01/01/2021 @ 12:00:00am (UTC) - ImPlot::SetNextPlotLimits(min,max,0,1); - if (ImPlot::BeginPlot("##Time", "UTC Time", "Y-Axis", ImVec2(-1,0), 0, ImPlotAxisFlags_Time)) { + static double t_min = 1577836800; // 01/01/2020 @ 12:00:00am (UTC) + static double t_max = 1609459200; // 01/01/2021 @ 12:00:00am (UTC) + + ImGui::BulletText("When ImPlotAxisFlags_Time is enabled on the X-Axis, values are interpreted as\n" + "UNIX timestamps in seconds and axis labels are formated as date/time."); + ImGui::BulletText("By default, labels are in UTC time but can be set to use local time instead."); + + ImGui::Checkbox("Use Local Time",&ImPlot::GetStyle().UseLocalTime); + + static HugeTimeData* data = NULL; + if (data == NULL) { + ImGui::SameLine(); + if (ImGui::Button("Generate Huge Data (~500MB!)")) { + static HugeTimeData sdata(t_min); + data = &sdata; + } + } + + ImPlot::SetNextPlotLimits(t_min,t_max,0,1); + if (ImPlot::BeginPlot("##Time", "Time", "Value", ImVec2(-1,0), 0, ImPlotAxisFlags_Time)) { + if (data != NULL) { + // downsample our data + int downsample = (int)ImPlot::GetPlotLimits().X.Size() / 1000 + 1; + int start = (int)(ImPlot::GetPlotLimits().X.Min - t_min); + start = start < 0 ? 0 : start > HugeTimeData::Size - 1 ? HugeTimeData::Size - 1 : start; + int end = (int)(ImPlot::GetPlotLimits().X.Max - t_min) + 1000; + end = end < 0 ? 0 : end > HugeTimeData::Size - 1 ? HugeTimeData::Size - 1 : end; + int size = (end - start)/downsample; + // plot it + ImPlot::PlotLine("Time Series", &data->Ts[start], &data->Ys[start], size, 0, sizeof(double)*downsample); + } + // plot time now + double t_now = (double)time(0); + double y_now = HugeTimeData::GetY(t_now); + ImPlot::PlotScatter("Now",&t_now,&y_now,1); ImPlot::EndPlot(); } } @@ -1133,8 +1184,9 @@ void ShowDemoWindow(bool* p_open) { static ImVec4 bearCol = ImVec4(0.853f, 0.050f, 0.310f, 1.000f); ImGui::SameLine(); ImGui::ColorEdit4("##Bull", &bullCol.x, ImGuiColorEditFlags_NoInputs); ImGui::SameLine(); ImGui::ColorEdit4("##Bear", &bearCol.x, ImGuiColorEditFlags_NoInputs); + ImPlot::GetStyle().UseLocalTime = false; ImPlot::SetNextPlotLimits(1546300800, 1571961600, 1250, 1600); - if (ImPlot::BeginPlot("Candlestick Chart","Day","USD",ImVec2(-1,-1),0,ImPlotAxisFlags_Time)) { + if (ImPlot::BeginPlot("Candlestick Chart","Day","USD",ImVec2(-1,0),0,ImPlotAxisFlags_Time)) { MyImPlot::PlotCandlestick("GOOGL",dates, opens, closes, lows, highs, 218, tooltip, 0.25f, bullCol, bearCol); ImPlot::EndPlot(); } diff --git a/implot_internal.h b/implot_internal.h index de76130..d359308 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -577,7 +577,7 @@ struct ImPlotAxisScale } }; -/// Two part time struct. +/// Two part timestamp struct. struct ImPlotTime { time_t S; // second part int Us; // microsecond part @@ -777,8 +777,7 @@ inline T OffsetAndStride(const T* data, int idx, int count, int offset, int stri // Time Utils //----------------------------------------------------------------------------- -// NB: These functions only work if there is a current ImPlotContext because the -// internal tm struct is owned by the context! + // Returns true if year is leap year (366 days long) inline bool IsLeapYear(int year) { @@ -793,6 +792,19 @@ inline int GetDaysInMonth(int year, int month) { return days[month] + (int)(month == 1 && IsLeapYear(year)); } +// Make a timestamp from a tm struct expressed as a UTC time (i.e. GMT timezone). +ImPlotTime MkGmtTime(struct tm *ptm); +// Make a tm struct from a timestamp expressed as a UTC time (i.e. GMT timezone). +tm* GetGmtTime(const ImPlotTime& t, tm* ptm); + +// Make a timestamp from a tm struct expressed as a local time. +ImPlotTime MkLocTime(struct tm *ptm); +// Make a tm struct from a timestamp expressed as a local time. +tm* GetLocTime(const ImPlotTime& t, tm* ptm); + +// NB: These functions only work if there is a current ImPlotContext because the +// internal tm struct is owned by the context! + // Adds time to a timestamp. #count must be positive! ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count); // Rounds a timestamp down to nearest.