From 0154c02a37b8e558dfa1757a55eb9d270048c645 Mon Sep 17 00:00:00 2001 From: epezent Date: Tue, 8 Sep 2020 00:56:00 -0500 Subject: [PATCH 1/4] prototyping date picker widget --- implot.cpp | 101 +++++++++++++++++++++++++++++++++++++++++++++- implot_internal.h | 5 ++- 2 files changed, 103 insertions(+), 3 deletions(-) diff --git a/implot.cpp b/implot.cpp index 66cf79e..395c8d2 100644 --- a/implot.cpp +++ b/implot.cpp @@ -725,9 +725,12 @@ ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count) { 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_Mo: for (int i = 0; i < count; ++i) { // this might have a bug + case ImPlotTimeUnit_Mo: for (int i = 0; i < abs(count); ++i) { GetTime(t_out, &Tm); - t_out.S += 86400 * GetDaysInMonth(Tm.tm_year + 1900, Tm.tm_mon); + if (count > 0) + t_out.S += 86400 * GetDaysInMonth(Tm.tm_year + 1900, Tm.tm_mon); + else if (count < 0) + t_out.S -= 86400 * GetDaysInMonth(Tm.tm_year + 1900 - Tm.tm_mon == 0 ? 1 : 0, Tm.tm_mon == 0 ? 11 : Tm.tm_mon - 1); // NOT WORKING } break; case ImPlotTimeUnit_Yr: for (int i = 0; i < count; ++i) { @@ -3109,6 +3112,100 @@ void ShowUserGuide() { ImGui::BulletText("Click legend label icons to show/hide plot items."); } +bool ShowDatePicker(ImPlotTime* t) { + + static const char* names_mo[] = {"January","February","March","April","May","June","July","August","September","October","November","December"}; + static const char* names_wd[] = {"Su","Mo","Tu","We","Th","Fr","Sa"}; + + ImGuiStyle& style = ImGui::GetStyle(); + + tm& Tm = GImPlot->Tm; + GetTime(*t, &Tm); + + const int this_year = Tm.tm_year + 1900; + const int last_year = this_year - 1; + const int this_mon = Tm.tm_mon; + const int last_mon = this_mon == 0 ? 11 : this_mon - 1; + const int days_this_mo = GetDaysInMonth(this_year, this_mon); + const int days_last_mo = GetDaysInMonth(this_mon == 0 ? last_year : this_year, last_mon); + + ImPlotTime t_first_mo = FloorTime(*t,ImPlotTimeUnit_Mo); + GetTime(*t,&Tm); + + const int first_wd = Tm.tm_wday; + + // sizing + float ht = ImGui::GetFrameHeight(); + const ImVec2 cell_size(ht*1.25f,ht); + + // begin group + ImGui::BeginGroup(); + + // month year + ImGui::Text("%s %d", names_mo[this_mon], this_year); + + // up/down arrows for month + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); + ImGui::SameLine(style.WindowPadding.x + 5*cell_size.x); + if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size)) { + *t = AddTime(*t, ImPlotTimeUnit_Mo, -1); + } + ImGui::SameLine(); + if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size)) { + *t = AddTime(*t, ImPlotTimeUnit_Mo, 1); + } + + // render weekday abbreviations + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + for (int i = 0; i < 7; ++i) { + ImGui::Button(names_wd[i],cell_size); + if (i != 6) ImGui::SameLine(); + } + ImGui::PopItemFlag(); + + // 0 = last mo, 1 = this mo, 2 = next mo + int mo = first_wd > 0 ? 0 : 1; + int day = mo == 1 ? 1 : days_last_mo - first_wd + 1; + + char buff[4]; + + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 7; ++j) { + + if (mo == 0 && day > days_last_mo) { + mo = 1; day = 1; + } + else if (mo == 1 && day > days_this_mo) { + mo = 2; day = 1; + } + + if (mo == 0 || mo == 2) + ImGui::PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); + else if (mo == 1 && day == Tm.tm_mday) + ImGui::PushStyleColor(ImGuiCol_Button, style.Colors[ImGuiCol_TextDisabled]); + + snprintf(buff,4,"%d",day); + ImGui::Button(buff,cell_size); + if (j != 6) + ImGui::SameLine(); + + if (mo == 0 || mo == 2) + ImGui::PopStyleColor(); + else if (mo == 1 && day == Tm.tm_mday) + ImGui::PopStyleColor(); + + day++; + } + } + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + ImGui::EndGroup(); + return true; +} + void StyleColorsAuto(ImPlotStyle* dst) { ImPlotStyle* style = dst ? dst : &ImPlot::GetStyle(); ImVec4* colors = style->Colors; diff --git a/implot_internal.h b/implot_internal.h index abcaf17..02da487 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -800,7 +800,7 @@ inline bool IsLeapYear(int year) { // 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)); + return days[month] + (int)(month == 1 && IsLeapYear(year)); } // Make a timestamp from a tm struct expressed as a UTC time (i.e. GMT timezone). @@ -835,6 +835,9 @@ IMPLOT_API int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTim // Prints a timestamp to console IMPLOT_API void PrintTime(const ImPlotTime& t, ImPlotTimeFmt fmt); +// Shows a date picker widget block. +IMPLOT_API bool ShowDatePicker(ImPlotTime* t); + //----------------------------------------------------------------------------- // [SECTION] Internal / Experimental Plotters // No guarantee of forward compatibility here! From 729bd762cc392df85709c000deae1349f752cb28 Mon Sep 17 00:00:00 2001 From: epezent Date: Tue, 8 Sep 2020 23:47:02 -0500 Subject: [PATCH 2/4] date picker finished, time picker working --- implot.cpp | 633 +++++++++++++++++++++++++++++++++------------- implot_demo.cpp | 2 +- implot_internal.h | 134 +++++----- 3 files changed, 526 insertions(+), 243 deletions(-) diff --git a/implot.cpp b/implot.cpp index 395c8d2..98a3a17 100644 --- a/implot.cpp +++ b/implot.cpp @@ -659,17 +659,13 @@ inline int GetTimeStep(int max_divs, ImPlotTimeUnit unit) { } ImPlotTime MkGmtTime(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 ImPlotTime(secs,0); + ImPlotTime t; +#ifdef _WIN32 + t.S = _mkgmtime(ptm); +#else + t.S = timegm(ptm); +#endif + return t; } tm* GetGmtTime(const ImPlotTime& t, tm* ptm) @@ -715,6 +711,49 @@ inline tm* GetTime(const ImPlotTime& t, tm* ptm) { return GetGmtTime(t,ptm); } +ImPlotTime MakeTime(int year, int month, int day, int hour, int min, int sec, int us) { + tm& Tm = GImPlot->Tm; + + int yr = year - 1900; + if (yr < 0) + yr = 0; + + sec = sec + us / 1000000; + us = us % 1000000; + + Tm.tm_sec = sec; + Tm.tm_min = min; + Tm.tm_hour = hour; + Tm.tm_mday = day; + Tm.tm_mon = month; + Tm.tm_year = yr; + + ImPlotTime t = MkTime(&Tm); + + t.Us = us; + return t; +} + +ImPlotTime MakeYear(int year) { + int yr = year - 1900; + if (yr < 0) + yr = 0; + 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; + return MkTime(&Tm); +} + +int GetYear(const ImPlotTime& t) { + tm& Tm = GImPlot->Tm; + GetTime(t, &Tm); + return Tm.tm_year + 1900; +} + ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count) { tm& Tm = GImPlot->Tm; ImPlotTime t_out = t; @@ -727,17 +766,18 @@ ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count) { case ImPlotTimeUnit_Day: t_out.S += count * 86400; break; case ImPlotTimeUnit_Mo: for (int i = 0; i < abs(count); ++i) { GetTime(t_out, &Tm); - if (count > 0) + if (count > 0) t_out.S += 86400 * GetDaysInMonth(Tm.tm_year + 1900, Tm.tm_mon); else if (count < 0) - t_out.S -= 86400 * GetDaysInMonth(Tm.tm_year + 1900 - Tm.tm_mon == 0 ? 1 : 0, Tm.tm_mon == 0 ? 11 : Tm.tm_mon - 1); // NOT WORKING + t_out.S -= 86400 * GetDaysInMonth(Tm.tm_year + 1900 - (Tm.tm_mon == 0 ? 1 : 0), Tm.tm_mon == 0 ? 11 : Tm.tm_mon - 1); // NOT WORKING } 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; + case ImPlotTimeUnit_Yr: for (int i = 0; i < abs(count); ++i) { + if (count > 0) + t_out.S += 86400 * (365 + (int)IsLeapYear(GetYear(t_out))); + else if (count < 0) + t_out.S -= 86400 * (365 + (int)IsLeapYear(GetYear(t_out) - 1)); + // this is incorrect if leap year and we are past Feb 28 } break; default: break; @@ -774,27 +814,6 @@ ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit) { return t.S - t1.S < t2.S - t.S ? t1 : t2; } -int GetYear(const ImPlotTime& t) { - tm& Tm = GImPlot->Tm; - GetTime(t, &Tm); - return Tm.tm_year + 1900; -} - -ImPlotTime MakeYear(int year) { - int yr = year - 1900; - if (yr < 0) - yr = 0; - 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; - return MkTime(&Tm); -} - int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt) { tm& Tm = GImPlot->Tm; GetTime(t, &Tm); @@ -813,23 +832,24 @@ int FormatTime(const ImPlotTime& 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, ".%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); - case ImPlotTimeFmt_HrMin: return snprintf(buffer, size, "%d:%02d%s", hr, min, 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); - default: return 0; + 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); + case ImPlotTimeFmt_HrMin: return snprintf(buffer, size, "%d:%02d%s", hr, min, 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_DayMoYrHrMinSUs: return snprintf(buffer, size, "%d/%d/%d %d:%02d:%02d.%03d%03d%s", mon, day, yr, hr, min, sec, ms, us, 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; } } @@ -842,23 +862,24 @@ void PrintTime(const ImPlotTime& 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(".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 - case ImPlotTimeFmt_HrMin: return ImGui::CalcTextSize("88:88pm").x; // 7:21pm - 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 - 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; + 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 + case ImPlotTimeFmt_HrMin: return ImGui::CalcTextSize("88:88pm").x; // 7:21pm + 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 + case ImPlotTimeFmt_DayMoYrHrMinS: return ImGui::CalcTextSize("88/88/88 88:88:88pm").x; // 10/3/91 7:21:29pm + case ImPlotTimeFmt_DayMoYrHrMinSUs: return ImGui::CalcTextSize("88/88/8888 88:88:88.888888pm").x; // 10/3/1991 7:21:29.123456pm + 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; } } @@ -1665,62 +1686,104 @@ inline void EndDisabledControls(bool cond) { } 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 timescale = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_Time); - 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. + ImPlotAxis& axis = *state.Axis; + bool total_lock = state.HasRange && state.RangeCond == ImGuiCond_Always; + bool logscale = ImHasFlag(axis.Flags, ImPlotAxisFlags_LogScale); + bool timescale = ImHasFlag(axis.Flags, ImPlotAxisFlags_Time); + bool grid = !ImHasFlag(axis.Flags, ImPlotAxisFlags_NoGridLines); + bool ticks = !ImHasFlag(axis.Flags, ImPlotAxisFlags_NoTickMarks); + bool labels = !ImHasFlag(axis.Flags, ImPlotAxisFlags_NoTickLabels); + double drag_speed = (axis.Range.Size() <= DBL_EPSILON) ? DBL_EPSILON * 1.0e+13 : 0.01 * axis.Range.Size(); // recover from almost equal axis limits. - BeginDisabledControls(total_lock); - if (ImGui::Checkbox("##LockMin", &state.LockMin)) - ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_LockMin); - EndDisabledControls(total_lock); + if (timescale) { + ImPlotTime tmin = ImPlotTime::FromDouble(axis.Range.Min); + ImPlotTime tmax = ImPlotTime::FromDouble(axis.Range.Max); - ImGui::SameLine(); - BeginDisabledControls(state.LockMin); - 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); + if (ImGui::Checkbox("##LockMin", &state.LockMin)) + ImFlipFlag(axis.Flags, ImPlotAxisFlags_LockMin); + EndDisabledControls(total_lock); + ImGui::SameLine(); + BeginDisabledControls(state.LockMin); + if (ImGui::BeginMenu("Min Time")) { + if (ShowDatePicker("minpick",&axis.PickerLevel,&axis.PickerTimeMin,&tmin,&tmax)) { + if (axis.PickerTimeMin >= tmax) { + tmax = AddTime(axis.PickerTimeMin, ImPlotTimeUnit_Day, 1); + axis.SetMax(tmax.ToDouble()); + } + axis.SetMin(axis.PickerTimeMin.ToDouble()); + } + ImGui::EndMenu(); + } + EndDisabledControls(state.LockMin); - BeginDisabledControls(total_lock); - if (ImGui::Checkbox("##LockMax", &state.LockMax)) - ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_LockMax); - EndDisabledControls(total_lock); + BeginDisabledControls(total_lock); + if (ImGui::Checkbox("##LockMax", &state.LockMax)) + ImFlipFlag(axis.Flags, ImPlotAxisFlags_LockMax); + EndDisabledControls(total_lock); + ImGui::SameLine(); + BeginDisabledControls(state.LockMax); + if (ImGui::BeginMenu("Max Time")) { + if (ShowDatePicker("macpick",&axis.PickerLevel,&axis.PickerTimeMax,&tmin,&tmax)) { + if (axis.PickerTimeMax <= tmin) { + tmin = AddTime(axis.PickerTimeMax, ImPlotTimeUnit_Day, -1); + axis.SetMin(tmin.ToDouble()); + } + axis.SetMax(axis.PickerTimeMax.ToDouble()); + } + ImGui::EndMenu(); + } + EndDisabledControls(state.LockMax); + } + else { + BeginDisabledControls(total_lock); + if (ImGui::Checkbox("##LockMin", &state.LockMin)) + ImFlipFlag(axis.Flags, ImPlotAxisFlags_LockMin); + EndDisabledControls(total_lock); + ImGui::SameLine(); + BeginDisabledControls(state.LockMin); + double temp_min = axis.Range.Min; + if (DragFloat("Min", &temp_min, (float)drag_speed, -HUGE_VAL, axis.Range.Max - DBL_EPSILON)) + axis.SetMin(temp_min); + EndDisabledControls(state.LockMin); - ImGui::SameLine(); - BeginDisabledControls(state.LockMax); - 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); + BeginDisabledControls(total_lock); + if (ImGui::Checkbox("##LockMax", &state.LockMax)) + ImFlipFlag(axis.Flags, ImPlotAxisFlags_LockMax); + EndDisabledControls(total_lock); + ImGui::SameLine(); + BeginDisabledControls(state.LockMax); + double temp_max = axis.Range.Max; + if (DragFloat("Max", &temp_max, (float)drag_speed, axis.Range.Min + DBL_EPSILON, HUGE_VAL)) + axis.SetMax(temp_max); + EndDisabledControls(state.LockMax); + } ImGui::Separator(); if (ImGui::Checkbox("Invert", &state.Invert)) - ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_Invert); + ImFlipFlag(axis.Flags, ImPlotAxisFlags_Invert); BeginDisabledControls(timescale && time_allowed); if (ImGui::Checkbox("Log Scale", &logscale)) - ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_LogScale); + ImFlipFlag(axis.Flags, ImPlotAxisFlags_LogScale); EndDisabledControls(timescale && time_allowed); if (time_allowed) { BeginDisabledControls(logscale); if (ImGui::Checkbox("Time", ×cale)) - ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_Time); + ImFlipFlag(axis.Flags, ImPlotAxisFlags_Time); EndDisabledControls(logscale); } ImGui::Separator(); if (ImGui::Checkbox("Grid Lines", &grid)) - ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_NoGridLines); + ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoGridLines); if (ImGui::Checkbox("Tick Marks", &ticks)) - ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_NoTickMarks); + ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoTickMarks); if (ImGui::Checkbox("Labels", &labels)) - ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_NoTickLabels); + ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoTickLabels); } @@ -3112,98 +3175,304 @@ void ShowUserGuide() { ImGui::BulletText("Click legend label icons to show/hide plot items."); } -bool ShowDatePicker(ImPlotTime* t) { +bool ShowDatePicker(const char* id, int* level, ImPlotTime* t, const ImPlotTime* t1, const ImPlotTime* t2) { - static const char* names_mo[] = {"January","February","March","April","May","June","July","August","September","October","November","December"}; - static const char* names_wd[] = {"Su","Mo","Tu","We","Th","Fr","Sa"}; - - ImGuiStyle& style = ImGui::GetStyle(); - - tm& Tm = GImPlot->Tm; - GetTime(*t, &Tm); - - const int this_year = Tm.tm_year + 1900; - const int last_year = this_year - 1; - const int this_mon = Tm.tm_mon; - const int last_mon = this_mon == 0 ? 11 : this_mon - 1; - const int days_this_mo = GetDaysInMonth(this_year, this_mon); - const int days_last_mo = GetDaysInMonth(this_mon == 0 ? last_year : this_year, last_mon); - - ImPlotTime t_first_mo = FloorTime(*t,ImPlotTimeUnit_Mo); - GetTime(*t,&Tm); - - const int first_wd = Tm.tm_wday; - - // sizing - float ht = ImGui::GetFrameHeight(); - const ImVec2 cell_size(ht*1.25f,ht); - - // begin group + ImGui::PushID(id); ImGui::BeginGroup(); - - // month year - ImGui::Text("%s %d", names_mo[this_mon], this_year); - - // up/down arrows for month ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); - ImGui::SameLine(style.WindowPadding.x + 5*cell_size.x); - if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size)) { - *t = AddTime(*t, ImPlotTimeUnit_Mo, -1); - } - ImGui::SameLine(); - if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size)) { - *t = AddTime(*t, ImPlotTimeUnit_Mo, 1); + + static const char* names_mo[] = {"January","February","March","April","May","June","July","August","September","October","November","December"}; + static const char* abrvs_mo[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; + static const char* abrvs_wd[] = {"Su","Mo","Tu","We","Th","Fr","Sa"}; + + ImGuiStyle& style = ImGui::GetStyle(); + ImVec4 col_txt = style.Colors[ImGuiCol_Text]; + ImVec4 col_dis = style.Colors[ImGuiCol_TextDisabled]; + const float ht = ImGui::GetFrameHeight(); + ImVec2 cell_size(ht*1.25f,ht); + char buff[32]; + bool clk = false; + tm& Tm = GImPlot->Tm; + + const int min_yr = 1970; + const int max_yr = 2999; + + // t1 parts + int t1_mo = 0; int t1_md = 0; int t1_yr = 0; + if (t1 != NULL) { + GetTime(*t1,&Tm); + t1_mo = Tm.tm_mon; + t1_md = Tm.tm_mday; + t1_yr = Tm.tm_year + 1900; } - // render weekday abbreviations - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); - for (int i = 0; i < 7; ++i) { - ImGui::Button(names_wd[i],cell_size); - if (i != 6) ImGui::SameLine(); + // t2 parts + int t2_mo = 0; int t2_md = 0; int t2_yr = 0; + if (t2 != NULL) { + GetTime(*t2,&Tm); + t2_mo = Tm.tm_mon; + t2_md = Tm.tm_mday; + t2_yr = Tm.tm_year + 1900; } - ImGui::PopItemFlag(); - // 0 = last mo, 1 = this mo, 2 = next mo - int mo = first_wd > 0 ? 0 : 1; - int day = mo == 1 ? 1 : days_last_mo - first_wd + 1; + // day widget + if (*level == 0) { + *t = FloorTime(*t, ImPlotTimeUnit_Day); + GetTime(*t, &Tm); + const int this_year = Tm.tm_year + 1900; + const int last_year = this_year - 1; + const int next_year = this_year + 1; + const int this_mon = Tm.tm_mon; + const int last_mon = this_mon == 0 ? 11 : this_mon - 1; + const int next_mon = this_mon == 11 ? 0 : this_mon + 1; + const int days_this_mo = GetDaysInMonth(this_year, this_mon); + const int days_last_mo = GetDaysInMonth(this_mon == 0 ? last_year : this_year, last_mon); + ImPlotTime t_first_mo = FloorTime(*t,ImPlotTimeUnit_Mo); + GetTime(t_first_mo,&Tm); + const int first_wd = Tm.tm_wday; + // month year + snprintf(buff, 32, "%s %d", names_mo[this_mon], this_year); + if (ImGui::Button(buff)) + *level = 1; + ImGui::SameLine(5*cell_size.x); + BeginDisabledControls(this_year <= min_yr && this_mon == 0); + if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size)) + *t = AddTime(*t, ImPlotTimeUnit_Mo, -1); + EndDisabledControls(this_year <= min_yr && this_mon == 0); + ImGui::SameLine(); + BeginDisabledControls(this_year >= max_yr && this_mon == 11); + if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size)) + *t = AddTime(*t, ImPlotTimeUnit_Mo, 1); + EndDisabledControls(this_year >= max_yr && this_mon == 11); + // render weekday abbreviations + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + for (int i = 0; i < 7; ++i) { + ImGui::Button(abrvs_wd[i],cell_size); + if (i != 6) { ImGui::SameLine(); } + } + ImGui::PopItemFlag(); + // 0 = last mo, 1 = this mo, 2 = next mo + int mo = first_wd > 0 ? 0 : 1; + int day = mo == 1 ? 1 : days_last_mo - first_wd + 1; + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 7; ++j) { + if (mo == 0 && day > days_last_mo) { + mo = 1; day = 1; + } + else if (mo == 1 && day > days_this_mo) { + mo = 2; day = 1; + } + const int now_yr = (mo == 0 && this_mon == 0) ? last_year : ((mo == 2 && this_mon == 11) ? next_year : this_year); + const int now_mo = mo == 0 ? last_mon : (mo == 1 ? this_mon : next_mon); + const int now_md = day; - char buff[4]; + const bool off_mo = mo == 0 || mo == 2; + const bool t1_or_t2 = (t1 != NULL && t1_mo == now_mo && t1_yr == now_yr && t1_md == now_md) || + (t2 != NULL && t2_mo == now_mo && t2_yr == now_yr && t2_md == now_md); - for (int i = 0; i < 6; ++i) { - for (int j = 0; j < 7; ++j) { - - if (mo == 0 && day > days_last_mo) { - mo = 1; day = 1; + if (off_mo) + ImGui::PushStyleColor(ImGuiCol_Text, col_dis); + if (t1_or_t2) { + ImGui::PushStyleColor(ImGuiCol_Button, col_dis); + ImGui::PushStyleColor(ImGuiCol_Text, col_txt); + } + ImGui::PushID(i*7+j); + snprintf(buff,32,"%d",day); + if (now_yr == min_yr-1 || now_yr == max_yr+1) { + ImGui::Dummy(cell_size); + } + else if (ImGui::Button(buff,cell_size) && !clk) { + *t = MakeTime(now_yr, now_mo, now_md); + clk = true; + } + ImGui::PopID(); + if (t1_or_t2) + ImGui::PopStyleColor(2); + if (off_mo) + ImGui::PopStyleColor(); + if (j != 6) + ImGui::SameLine(); + day++; + } + } + } + // month widget + else if (*level == 1) { + *t = FloorTime(*t, ImPlotTimeUnit_Mo); + GetTime(*t, &Tm); + int this_yr = Tm.tm_year + 1900; + snprintf(buff, 32, "%d", this_yr); + if (ImGui::Button(buff)) + *level = 2; + BeginDisabledControls(this_yr <= min_yr); + ImGui::SameLine(5*cell_size.x); + if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size)) + *t = AddTime(*t, ImPlotTimeUnit_Yr, -1); + EndDisabledControls(this_yr <= min_yr); + ImGui::SameLine(); + BeginDisabledControls(this_yr >= max_yr); + if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size)) + *t = AddTime(*t, ImPlotTimeUnit_Yr, 1); + EndDisabledControls(this_yr >= max_yr); + // ImGui::Dummy(cell_size); + cell_size.x *= 7.0f/4.0f; + cell_size.y *= 7.0f/3.0f; + int mo = 0; + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 4; ++j) { + const bool t1_or_t2 = (t1 != NULL && t1_yr == this_yr && t1_mo == mo) || + (t2 != NULL && t2_yr == this_yr && t2_mo == mo); + if (t1_or_t2) + ImGui::PushStyleColor(ImGuiCol_Button, col_dis); + if (ImGui::Button(abrvs_mo[mo],cell_size) && !clk) { + *t = MakeTime(this_yr, mo); + *level = 0; + } + if (t1_or_t2) + ImGui::PopStyleColor(); + if (j != 3) + ImGui::SameLine(); + mo++; + } + } + } + else if (*level == 2) { + *t = FloorTime(*t, ImPlotTimeUnit_Yr); + int this_yr = GetYear(*t); + int yr = this_yr - this_yr % 20; + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + snprintf(buff,32,"%d-%d",yr,yr+19); + ImGui::Button(buff); + ImGui::PopItemFlag(); + ImGui::SameLine(5*cell_size.x); + BeginDisabledControls(yr <= min_yr); + if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size)) + *t = MakeYear(yr-20); + EndDisabledControls(yr <= min_yr); + ImGui::SameLine(); + BeginDisabledControls(yr + 20 >= max_yr); + if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size)) + *t = MakeYear(yr+20); + EndDisabledControls(yr+ 20 >= max_yr); + // ImGui::Dummy(cell_size); + cell_size.x *= 7.0f/4.0f; + cell_size.y *= 7.0f/5.0f; + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 4; ++j) { + const bool t1_or_t2 = (t1 != NULL && t1_yr == yr) || (t2 != NULL && t2_yr == yr); + if (t1_or_t2) + ImGui::PushStyleColor(ImGuiCol_Button, col_dis); + snprintf(buff,32,"%d",yr); + if (yr<1970||yr>3000) { + ImGui::Dummy(cell_size); + } + else if (ImGui::Button(buff,cell_size)) { + *t = MakeYear(yr); + *level = 1; + } + if (t1_or_t2) + ImGui::PopStyleColor(); + if (j != 3) + ImGui::SameLine(); + yr++; } - else if (mo == 1 && day > days_this_mo) { - mo = 2; day = 1; - } - - if (mo == 0 || mo == 2) - ImGui::PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); - else if (mo == 1 && day == Tm.tm_mday) - ImGui::PushStyleColor(ImGuiCol_Button, style.Colors[ImGuiCol_TextDisabled]); - - snprintf(buff,4,"%d",day); - ImGui::Button(buff,cell_size); - if (j != 6) - ImGui::SameLine(); - - if (mo == 0 || mo == 2) - ImGui::PopStyleColor(); - else if (mo == 1 && day == Tm.tm_mday) - ImGui::PopStyleColor(); - - day++; } } - ImGui::PopStyleVar(); ImGui::PopStyleColor(); - ImGui::EndGroup(); - return true; + ImGui::PopID(); + return clk; +} + +bool ShowTimePicker(const char* id, ImPlotTime* t) { + ImGui::PushID(id); + tm& Tm = GImPlot->Tm; + GetTime(*t,&Tm); + + static const char* nums[] = { "00","01","02","03","04","05","06","07","08","09", + "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"}; + + static const char* am_pm[] = {"am","pm"}; + + int hr = (Tm.tm_hour == 0 || Tm.tm_hour == 12) ? 12 : Tm.tm_hour % 12; + int min = Tm.tm_min; + int sec = Tm.tm_sec; + int ap = Tm.tm_hour < 12 ? 0 : 1; + + bool changed = false; + + ImVec2 spacing = ImGui::GetStyle().ItemSpacing; + spacing.x = 0; + float width = ImGui::CalcTextSize("888").x; + float height = ImGui::GetFrameHeight(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, spacing); + ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize,2.0f); + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0,0,0,0)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered)); + + ImGui::SetNextItemWidth(width); + if (ImGui::BeginCombo("##hr",nums[hr],ImGuiComboFlags_NoArrowButton)) { + for (int i = 1; i < 13; ++i) { + if (ImGui::Selectable(nums[i],i==hr)) { + hr = i; + changed = true; + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + ImGui::Text(":"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(width); + if (ImGui::BeginCombo("##min",nums[min],ImGuiComboFlags_NoArrowButton)) { + for (int i = 0; i < 60; ++i) { + if (ImGui::Selectable(nums[i],i==min)) { + min = i; + changed = true; + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + ImGui::Text(":"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(width); + if (ImGui::BeginCombo("##sec",nums[sec],ImGuiComboFlags_NoArrowButton)) { + for (int i = 0; i < 60; ++i) { + if (ImGui::Selectable(nums[i],i==sec)) { + sec = i; + changed = true; + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + if (ImGui::Button(am_pm[ap],ImVec2(height,height))) { + ap = 1 - ap; + changed = true; + } + + ImGui::PopStyleColor(3); + ImGui::PopStyleVar(2); + ImGui::PopID(); + + if (changed) { + hr = hr % 12 + ap * 12; + Tm.tm_hour = hr; + Tm.tm_min = min; + Tm.tm_sec = sec; + *t = MkTime(&Tm); + } + + return changed; } void StyleColorsAuto(ImPlotStyle* dst) { diff --git a/implot_demo.cpp b/implot_demo.cpp index 59244cb..5237bce 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -1212,7 +1212,7 @@ void ShowDemoWindow(bool* p_open) { 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,0),0,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(); } diff --git a/implot_internal.h b/implot_internal.h index 02da487..6d7fbf7 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -183,28 +183,55 @@ enum ImPlotTimeUnit_ { }; enum ImPlotTimeFmt_ { - ImPlotTimeFmt_Us, // .428 552 - ImPlotTimeFmt_SUs, // :29.428 552 - ImPlotTimeFmt_SMs, // :29.428 - ImPlotTimeFmt_S, // :29 - ImPlotTimeFmt_HrMinS, // 7:21:29pm - 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 - ImPlotTimeFmt_DayMoYrHrMinS, // 10/3/91 7:21:29pm - ImPlotTimeFmt_MoYr, // Oct 1991 - ImPlotTimeFmt_Mo, // Oct - ImPlotTimeFmt_Yr // 1991 + ImPlotTimeFmt_Us, // .428 552 + ImPlotTimeFmt_SUs, // :29.428 552 + ImPlotTimeFmt_SMs, // :29.428 + ImPlotTimeFmt_S, // :29 + ImPlotTimeFmt_HrMinS, // 7:21:29pm + 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 + ImPlotTimeFmt_DayMoYrHrMinS, // 10/3/91 7:21:29pm + ImPlotTimeFmt_DayMoYrHrMinSUs, // 10/3/91 7:21:29.123456pm + ImPlotTimeFmt_MoYr, // Oct 1991 + ImPlotTimeFmt_Mo, // Oct + ImPlotTimeFmt_Yr // 1991 }; //----------------------------------------------------------------------------- // [SECTION] ImPlot Structs //----------------------------------------------------------------------------- +/// Two part timestamp struct. +struct ImPlotTime { + 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; } + 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) +{ 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; } + // Storage for colormap modifiers struct ImPlotColormapMod { ImPlotColormapMod(const ImVec4* colormap, int colormap_size) { @@ -297,6 +324,8 @@ struct ImPlotAxis bool HoveredTot; double* LinkedMin; double* LinkedMax; + ImPlotTime PickerTimeMin, PickerTimeMax; + int PickerLevel; ImPlotAxis() { Flags = PreviousFlags = ImPlotAxisFlags_None; @@ -306,6 +335,7 @@ struct ImPlotAxis HoveredExt = false; HoveredTot = false; LinkedMin = LinkedMax = NULL; + PickerLevel = 0; } bool SetMin(double _min) { @@ -317,6 +347,7 @@ struct ImPlotAxis if (_min >= Range.Max) return false; Range.Min = _min; + PickerTimeMin = ImPlotTime::FromDouble(Range.Min); return true; }; @@ -329,6 +360,7 @@ struct ImPlotAxis if (_max <= Range.Min) return false; Range.Max = _max; + PickerTimeMax = ImPlotTime::FromDouble(Range.Max); return true; }; @@ -336,6 +368,8 @@ struct ImPlotAxis Range.Min = _min; Range.Max = _max; Constrain(); + PickerTimeMin = ImPlotTime::FromDouble(Range.Min); + PickerTimeMax = ImPlotTime::FromDouble(Range.Max); } void SetRange(const ImPlotRange& range) { @@ -585,32 +619,6 @@ struct ImPlotAxisScale } }; -/// Two part timestamp struct. -struct ImPlotTime { - 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; } - 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) -{ 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! @@ -792,10 +800,7 @@ inline T OffsetAndStride(const T* data, int idx, int count, int offset, int stri // 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; + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); } // Returns the number of days in a month, accounting for Feb. leap years. #month is zero indexed. inline int GetDaysInMonth(int year, int month) { @@ -803,18 +808,26 @@ 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). +// Make a UNIX timestamp from a tm struct expressed in UTC time (i.e. GMT timezone). IMPLOT_API ImPlotTime MkGmtTime(struct tm *ptm); -// Make a tm struct from a timestamp expressed as a UTC time (i.e. GMT timezone). +// Make a tm struct expressed in UTC time (i.e. GMT timezone) from a UNIX timestamp. IMPLOT_API tm* GetGmtTime(const ImPlotTime& t, tm* ptm); -// Make a timestamp from a tm struct expressed as a local time. +// Make a UNIX timestamp from a tm struct expressed in local time. IMPLOT_API ImPlotTime MkLocTime(struct tm *ptm); -// Make a tm struct from a timestamp expressed as a local time. +// Make a tm struct expressed in local time from a UNIX timestamp. IMPLOT_API 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! +// NB: The following functions only work if there is a current ImPlotContext because the +// internal tm struct is owned by the context! They are aware of ImPlotStyle.UseLocalTime. + +// Make a timestamp from time components. +// year[1970-3000], month[0-11], day[1-31], hour[0-23], min[0-59], sec[0-59], us[0,999999] +IMPLOT_API ImPlotTime MakeTime(int year, int month = 0, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0); +// Make a timestamp starting at the first day of a year [1970-3000] +IMPLOT_API ImPlotTime MakeYear(int year); +// Get year component from timestamp [1970-3000] +IMPLOT_API int GetYear(const ImPlotTime& t); // Adds time to a timestamp. #count must be positive! IMPLOT_API ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count); @@ -825,18 +838,19 @@ IMPLOT_API ImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit); // Rounds a timestamp up or down to the nearest unit. IMPLOT_API ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit); -// Get year from timestamp -IMPLOT_API int GetYear(const ImPlotTime& t); -// Make a timestamp starting at the first day of a year -IMPLOT_API ImPlotTime MakeYear(int year); - // Formates a timestamp t into a buffer according to fmt. IMPLOT_API int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt); // Prints a timestamp to console -IMPLOT_API void PrintTime(const ImPlotTime& t, ImPlotTimeFmt fmt); +IMPLOT_API void PrintTime(const ImPlotTime& t, ImPlotTimeFmt fmt = ImPlotTimeFmt_DayMoYrHrMinSUs); -// Shows a date picker widget block. -IMPLOT_API bool ShowDatePicker(ImPlotTime* t); +// Shows a date picker widget block (year/month/day). +// #level = 0 for day, 1 for month, 2 for year. Modified by user interaction. +// #t will be set when a day is clicked and the function will return true. +// #t1 and #t2 are optional dates to highlight. +IMPLOT_API bool ShowDatePicker(const char* id, int* level, ImPlotTime* t, const ImPlotTime* t1 = NULL, const ImPlotTime* t2 = NULL); +// Shows a time picker widget block (hour/min/sec). +// #t will be set when a new hour, minute, or sec is selected and the function will return true. +IMPLOT_API bool ShowTimePicker(const char* id, ImPlotTime* t); //----------------------------------------------------------------------------- // [SECTION] Internal / Experimental Plotters From 3cdf7add046a3e55835e0df4d3814484279a609a Mon Sep 17 00:00:00 2001 From: epezent Date: Wed, 9 Sep 2020 09:00:50 -0500 Subject: [PATCH 3/4] finish date/time picker --- implot.cpp | 51 +++++++++++++++++++++++++++++++++++------------ implot_demo.cpp | 2 +- implot_internal.h | 16 +++++++++++---- 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/implot.cpp b/implot.cpp index 98a3a17..23de207 100644 --- a/implot.cpp +++ b/implot.cpp @@ -814,6 +814,21 @@ ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit) { return t.S - t1.S < t2.S - t.S ? t1 : t2; } +ImPlotTime CombineDateTime(const ImPlotTime& date_part, const ImPlotTime& tod_part) { + tm& Tm = GImPlot->Tm; + GetTime(date_part, &GImPlot->Tm); + int y = Tm.tm_year; + int m = Tm.tm_mon; + int d = Tm.tm_mday; + GetTime(tod_part, &GImPlot->Tm); + Tm.tm_year = y; + Tm.tm_mon = m; + Tm.tm_mday = d; + ImPlotTime t = MkTime(&Tm); + t.Us = tod_part.Us; + return t; +} + int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt) { tm& Tm = GImPlot->Tm; GetTime(t, &Tm); @@ -1685,7 +1700,7 @@ inline void EndDisabledControls(bool cond) { } } -inline void ShowAxisContextMenu(ImPlotAxisState& state, bool time_allowed) { +void ShowAxisContextMenu(ImPlotAxisState& state, bool time_allowed) { ImGui::PushItemWidth(75); ImPlotAxis& axis = *state.Axis; @@ -1708,12 +1723,17 @@ inline void ShowAxisContextMenu(ImPlotAxisState& state, bool time_allowed) { ImGui::SameLine(); BeginDisabledControls(state.LockMin); if (ImGui::BeginMenu("Min Time")) { - if (ShowDatePicker("minpick",&axis.PickerLevel,&axis.PickerTimeMin,&tmin,&tmax)) { - if (axis.PickerTimeMin >= tmax) { - tmax = AddTime(axis.PickerTimeMin, ImPlotTimeUnit_Day, 1); - axis.SetMax(tmax.ToDouble()); - } - axis.SetMin(axis.PickerTimeMin.ToDouble()); + if (ShowTimePicker("mintime", &tmin)) { + if (tmin >= tmax) + tmax = AddTime(tmin, ImPlotTimeUnit_S, 1); + axis.SetRange(tmin.ToDouble(),tmax.ToDouble()); + } + ImGui::Separator(); + if (ShowDatePicker("mindate",&axis.PickerLevel,&axis.PickerTimeMin,&tmin,&tmax)) { + tmin = CombineDateTime(axis.PickerTimeMin, tmin); + if (tmin >= tmax) + tmax = AddTime(tmin, ImPlotTimeUnit_S, 1); + axis.SetRange(tmin.ToDouble(), tmax.ToDouble()); } ImGui::EndMenu(); } @@ -1726,12 +1746,17 @@ inline void ShowAxisContextMenu(ImPlotAxisState& state, bool time_allowed) { ImGui::SameLine(); BeginDisabledControls(state.LockMax); if (ImGui::BeginMenu("Max Time")) { - if (ShowDatePicker("macpick",&axis.PickerLevel,&axis.PickerTimeMax,&tmin,&tmax)) { - if (axis.PickerTimeMax <= tmin) { - tmin = AddTime(axis.PickerTimeMax, ImPlotTimeUnit_Day, -1); - axis.SetMin(tmin.ToDouble()); - } - axis.SetMax(axis.PickerTimeMax.ToDouble()); + if (ShowTimePicker("maxtime", &tmax)) { + if (tmax <= tmin) + tmin = AddTime(tmax, ImPlotTimeUnit_S, -1); + axis.SetRange(tmin.ToDouble(),tmax.ToDouble()); + } + ImGui::Separator(); + if (ShowDatePicker("maxdate",&axis.PickerLevel,&axis.PickerTimeMax,&tmin,&tmax)) { + tmax = CombineDateTime(axis.PickerTimeMax, tmax); + if (tmax <= tmin) + tmin = AddTime(tmax, ImPlotTimeUnit_S, -1); + axis.SetRange(tmin.ToDouble(), tmax.ToDouble()); } ImGui::EndMenu(); } diff --git a/implot_demo.cpp b/implot_demo.cpp index 5237bce..064451b 100644 --- a/implot_demo.cpp +++ b/implot_demo.cpp @@ -623,7 +623,7 @@ void ShowDemoWindow(bool* p_open) { } ImPlot::SetNextPlotLimits(t_min,t_max,0,1); - if (ImPlot::BeginPlot("##Time", "Time", "Value", ImVec2(-1,0), 0, ImPlotAxisFlags_Time)) { + if (ImPlot::BeginPlot("##Time", NULL, NULL, ImVec2(-1,0), 0, ImPlotAxisFlags_Time)) { if (data != NULL) { // downsample our data int downsample = (int)ImPlot::GetPlotLimits().X.Size() / 1000 + 1; diff --git a/implot_internal.h b/implot_internal.h index 6d7fbf7..4341286 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -196,7 +196,7 @@ enum ImPlotTimeFmt_ { ImPlotTimeFmt_DayMoYr, // 10/3/91 ImPlotTimeFmt_DayMoYrHrMin, // 10/3/91 7:21pm ImPlotTimeFmt_DayMoYrHrMinS, // 10/3/91 7:21:29pm - ImPlotTimeFmt_DayMoYrHrMinSUs, // 10/3/91 7:21:29.123456pm + ImPlotTimeFmt_DayMoYrHrMinSUs, // 10/3/1991 7:21:29.123456pm ImPlotTimeFmt_MoYr, // Oct 1991 ImPlotTimeFmt_Mo, // Oct ImPlotTimeFmt_Yr // 1991 @@ -646,6 +646,9 @@ IMPLOT_API ImPlotState* GetCurrentPlot(); // Busts the cache for every plot in the current context IMPLOT_API void BustPlotCache(); +// Shows a plot's context menu. +IMPLOT_API void ShowPlotContextMenu(ImPlotState& plot); + //----------------------------------------------------------------------------- // [SECTION] Item Utils //----------------------------------------------------------------------------- @@ -696,6 +699,9 @@ IMPLOT_API void PushLinkedAxis(ImPlotAxis& axis); // Updates axis internal range from points for linked axes. IMPLOT_API void PullLinkedAxis(ImPlotAxis& axis); +// Shows an axis's context menu. +IMPLOT_API void ShowAxisContextMenu(ImPlotAxisState& state, bool time_allowed = false); + //----------------------------------------------------------------------------- // [SECTION] Legend Utils //----------------------------------------------------------------------------- @@ -829,16 +835,18 @@ IMPLOT_API ImPlotTime MakeYear(int year); // Get year component from timestamp [1970-3000] IMPLOT_API int GetYear(const ImPlotTime& t); -// Adds time to a timestamp. #count must be positive! +// Adds or subtracts time from a timestamp. #count > 0 to add, < 0 to subtract. IMPLOT_API ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count); -// Rounds a timestamp down to nearest. +// Rounds a timestamp down to nearest unit. IMPLOT_API ImPlotTime FloorTime(const ImPlotTime& t, ImPlotTimeUnit unit); // Rounds a timestamp up to the nearest unit. IMPLOT_API ImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit); // Rounds a timestamp up or down to the nearest unit. IMPLOT_API ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit); +// Combines the date of one timestamp with the time-of-day of another timestamp. +IMPLOT_API ImPlotTime CombineDateTime(const ImPlotTime& date_part, const ImPlotTime& tod_part); -// Formates a timestamp t into a buffer according to fmt. +// Formulates a timestamp t into a buffer according to fmt. IMPLOT_API int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt); // Prints a timestamp to console IMPLOT_API void PrintTime(const ImPlotTime& t, ImPlotTimeFmt fmt = ImPlotTimeFmt_DayMoYrHrMinSUs); From 04dc2c6be96b13f302a697e78c077ec895e91ab1 Mon Sep 17 00:00:00 2001 From: epezent Date: Wed, 9 Sep 2020 19:17:19 -0500 Subject: [PATCH 4/4] fix bugs in date picker --- implot.cpp | 28 +++++++++------------------- implot_internal.h | 6 ++---- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/implot.cpp b/implot.cpp index 23de207..345ffe1 100644 --- a/implot.cpp +++ b/implot.cpp @@ -665,6 +665,8 @@ ImPlotTime MkGmtTime(struct tm *ptm) { #else t.S = timegm(ptm); #endif + if (t.S < 0) + t.S = 0; return t; } @@ -683,6 +685,8 @@ tm* GetGmtTime(const ImPlotTime& t, tm* ptm) ImPlotTime MkLocTime(struct tm *ptm) { ImPlotTime t; t.S = mktime(ptm); + if (t.S < 0) + t.S = 0; return t; } @@ -734,20 +738,6 @@ ImPlotTime MakeTime(int year, int month, int day, int hour, int min, int sec, in return t; } -ImPlotTime MakeYear(int year) { - int yr = year - 1900; - if (yr < 0) - yr = 0; - 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; - return MkTime(&Tm); -} - int GetYear(const ImPlotTime& t) { tm& Tm = GImPlot->Tm; GetTime(t, &Tm); @@ -860,7 +850,7 @@ int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt) { 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_DayMoYrHrMinSUs: return snprintf(buffer, size, "%d/%d/%d %d:%02d:%02d.%03d%03d%s", mon, day, yr, hr, min, sec, ms, us, ap); + case ImPlotTimeFmt_DayMoYrHrMinSUs: return snprintf(buffer, size, "%d/%d/%d %d:%02d:%02d.%03d%03d%s", mon, day, year, hr, min, sec, ms, us, 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); @@ -1043,7 +1033,7 @@ void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollecti const int step = (int)interval <= 0 ? 1 : (int)interval; for (int y = graphmin; y < graphmax; y += step) { - ImPlotTime t = MakeYear(y); + ImPlotTime t = MakeTime(y); if (t >= t_min && t <= t_max) { ImPlotTick tick(t.ToDouble(), true, true); tick.Level = 0; @@ -3373,12 +3363,12 @@ bool ShowDatePicker(const char* id, int* level, ImPlotTime* t, const ImPlotTime* ImGui::SameLine(5*cell_size.x); BeginDisabledControls(yr <= min_yr); if (ImGui::ArrowButtonEx("##Up",ImGuiDir_Up,cell_size)) - *t = MakeYear(yr-20); + *t = MakeTime(yr-20); EndDisabledControls(yr <= min_yr); ImGui::SameLine(); BeginDisabledControls(yr + 20 >= max_yr); if (ImGui::ArrowButtonEx("##Down",ImGuiDir_Down,cell_size)) - *t = MakeYear(yr+20); + *t = MakeTime(yr+20); EndDisabledControls(yr+ 20 >= max_yr); // ImGui::Dummy(cell_size); cell_size.x *= 7.0f/4.0f; @@ -3393,7 +3383,7 @@ bool ShowDatePicker(const char* id, int* level, ImPlotTime* t, const ImPlotTime* ImGui::Dummy(cell_size); } else if (ImGui::Button(buff,cell_size)) { - *t = MakeYear(yr); + *t = MakeTime(yr); *level = 1; } if (t1_or_t2) diff --git a/implot_internal.h b/implot_internal.h index 4341286..29ac6be 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -830,8 +830,6 @@ IMPLOT_API tm* GetLocTime(const ImPlotTime& t, tm* ptm); // Make a timestamp from time components. // year[1970-3000], month[0-11], day[1-31], hour[0-23], min[0-59], sec[0-59], us[0,999999] IMPLOT_API ImPlotTime MakeTime(int year, int month = 0, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0); -// Make a timestamp starting at the first day of a year [1970-3000] -IMPLOT_API ImPlotTime MakeYear(int year); // Get year component from timestamp [1970-3000] IMPLOT_API int GetYear(const ImPlotTime& t); @@ -844,7 +842,7 @@ IMPLOT_API ImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit); // Rounds a timestamp up or down to the nearest unit. IMPLOT_API ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit); // Combines the date of one timestamp with the time-of-day of another timestamp. -IMPLOT_API ImPlotTime CombineDateTime(const ImPlotTime& date_part, const ImPlotTime& tod_part); +IMPLOT_API ImPlotTime CombineDateTime(const ImPlotTime& date_part, const ImPlotTime& time_part); // Formulates a timestamp t into a buffer according to fmt. IMPLOT_API int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt); @@ -857,7 +855,7 @@ IMPLOT_API void PrintTime(const ImPlotTime& t, ImPlotTimeFmt fmt = ImPlotTimeFmt // #t1 and #t2 are optional dates to highlight. IMPLOT_API bool ShowDatePicker(const char* id, int* level, ImPlotTime* t, const ImPlotTime* t1 = NULL, const ImPlotTime* t2 = NULL); // Shows a time picker widget block (hour/min/sec). -// #t will be set when a new hour, minute, or sec is selected and the function will return true. +// #t will be set when a new hour, minute, or sec is selected or am/pm is toggled, and the function will return true. IMPLOT_API bool ShowTimePicker(const char* id, ImPlotTime* t); //-----------------------------------------------------------------------------