From 901f0558b16f53609d854f0b21ffb177fcd7b495 Mon Sep 17 00:00:00 2001 From: epezent Date: Sun, 6 Sep 2020 14:48:16 -0500 Subject: [PATCH] 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.