From defc52921924a45d0525be1e5c514c659bcdf0ba Mon Sep 17 00:00:00 2001 From: epezent Date: Sun, 6 Sep 2020 00:06:51 -0500 Subject: [PATCH] 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));