diff --git a/implot.cpp b/implot.cpp index 242f152..ba213b8 100644 --- a/implot.cpp +++ b/implot.cpp @@ -31,6 +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 (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. @@ -105,7 +107,6 @@ ImPlotStyle::ImPlotStyle() { ErrorBarWeight = 1.5f; DigitalBitHeight = 8; DigitalBitGap = 4; - AntiAliasedLines = false; PlotBorderSize = 1; MinorAlpha = 0.25f; @@ -122,6 +123,9 @@ ImPlotStyle::ImPlotStyle() { PlotMinSize = ImVec2(300,225); ImPlot::StyleColorsAuto(this); + + AntiAliasedLines = false; + UseLocalTime = false; } namespace ImPlot { @@ -401,11 +405,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; } @@ -501,7 +505,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 +515,7 @@ 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); } @@ -565,7 +569,6 @@ 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); @@ -582,20 +585,430 @@ void AddTicksCustom(const double* values, const char** labels, int n, ImPlotTick } //----------------------------------------------------------------------------- -// Axis Utils +// Time Ticks and 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); +// this may not be thread safe? +static const double TimeUnitSpans[ImPlotTimeUnit_COUNT] = { + 0.000001, + 0.001, + 1, + 60, + 3600, + 86400, + 2629800, + 31557600 +}; + +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; } - if (axis.Range.Max <= axis.Range.Min) - axis.Range.Max = axis.Range.Min + DBL_EPSILON; + return ImPlotTimeUnit_Yr; } +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,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}; + 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; +} + +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); +} + +tm* GetGmtTime(const ImPlotTime& t, tm* ptm) +{ +#ifdef _WIN32 + if (gmtime_s(ptm, &t.S) == 0) + return ptm; + else + return NULL; +#else + 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; + switch(unit) { + 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_Mo: for (int i = 0; i < count; ++i) { // this might have a bug + GetTime(t_out, &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; +} + +ImPlotTime FloorTime(const ImPlotTime& t, ImPlotTimeUnit unit) { + 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); + 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; + } + return MkTime(&GImPlot->Tm); +} + +ImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit) { + return AddTime(FloorTime(t, unit), unit, 1); +} + +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(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); + + 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; + 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, ".%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; + } +} + +void PrintTime(const ImPlotTime& 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_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; + } +} + +inline void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, const ImPlotTime& t, ImPlotTimeFmt fmt) { + char temp[32]; + if (tick.ShowLabel) { + tick.BufferOffset = buffer.size(); + 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, + 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_DayMoYr, + ImPlotTimeFmt_DayMoYr, + 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 +}; + +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]; + // 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; + // book keeping + 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 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 / fmt0_width); + // the minor step size (level 0) + const int step = GetTimeStep(minor_per_major, unit0); + // generate ticks + 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,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,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 + 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 >= fmt0_width); + tick.Level = 0; + LabelTickTime(tick,ticks.Labels,t12,fmt0); + ticks.AddTick(tick); + 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); + } + } + t1 = t2; + } + } + 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(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; + + 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, t, TimeFormatLevel0[ImPlotTimeUnit_Yr]); + ticks.AddTick(tick); + } + } + } +} + +//----------------------------------------------------------------------------- +// Axis Utils +//----------------------------------------------------------------------------- + void UpdateAxisColors(int axis_flag, ImPlotAxisColor* col) { const ImVec4 col_label = GetStyleColorVec4(axis_flag); const ImVec4 col_grid = GetStyleColorVec4(axis_flag + 1); @@ -610,10 +1023,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 ----------------------------------------------------------- @@ -646,6 +1063,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) @@ -676,16 +1095,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; - } + 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]; - } + if (just_created || gp.NextPlotData.YRangeCond[i] == ImGuiCond_Always) + plot.YAxis[i].SetRange(gp.NextPlotData.Y[i]); } } @@ -708,12 +1125,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); @@ -721,7 +1132,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); @@ -741,46 +1152,60 @@ 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)); - } - // 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 - 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_NoGridLines) || + !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_NoTickMarks) || + !ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_NoTickLabels)); } // 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); + + 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 + (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; + + // (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 (gp.X.IsTime) + AddTicksTime(plot.XAxis.Range, plot_width, gp.XTicks); + 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); + } + + // (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)); @@ -819,7 +1244,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) { @@ -870,19 +1295,18 @@ 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); } 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); } } // Set the mouse cursor based on which axes are moving. @@ -933,11 +1357,10 @@ 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); } for (int i = 0; i < IMPLOT_Y_AXES; i++) { if (plot.YAxis[i].HoveredTot && !gp.Y[i].Lock) { @@ -945,9 +1368,9 @@ 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); } } } @@ -958,28 +1381,28 @@ 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; 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; } // 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 @@ -1026,7 +1449,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; @@ -1097,22 +1520,24 @@ 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); 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); + } } } 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); @@ -1150,15 +1575,16 @@ 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) - 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++) { - 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]; @@ -1209,13 +1635,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 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_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); @@ -1225,7 +1652,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); @@ -1235,22 +1664,34 @@ 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(); 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); + 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); + 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); } @@ -1258,7 +1699,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(); } @@ -1277,7 +1718,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(); } @@ -1285,8 +1726,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); @@ -1294,16 +1735,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")) { @@ -1347,13 +1788,14 @@ 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]; - 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(); @@ -1365,7 +1807,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++) { @@ -1407,7 +1849,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); @@ -1458,7 +1900,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) { @@ -1468,7 +1910,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); @@ -1537,7 +1979,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)); @@ -1545,6 +1987,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); @@ -1591,50 +2039,52 @@ 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.Range.Min = (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.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(); 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].Range.Min = (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].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(); } } // 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(); - ShowAxisContextMenu(gp.X); + ShowAxisContextMenu(gp.X, true); ImGui::EndPopup(); } 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) { @@ -1643,7 +2093,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.h b/implot.h index 634a22e..1c4a6d9 100644 --- a/implot.h +++ b/implot.h @@ -53,31 +53,34 @@ 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 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 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 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_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_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_NoDecorations = ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks | ImPlotAxisFlags_NoTickLabels }; // Plot styling colors. @@ -204,31 +207,34 @@ 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 + 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 + // 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(); }; @@ -281,11 +287,11 @@ 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, - ImPlotAxisFlags x_flags = ImPlotAxisFlags_Default, - ImPlotAxisFlags y_flags = ImPlotAxisFlags_Default, - ImPlotAxisFlags y2_flags = ImPlotAxisFlags_Auxiliary, - ImPlotAxisFlags y3_flags = ImPlotAxisFlags_Auxiliary); + ImPlotFlags flags = ImPlotFlags_None, + 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 36cbe6e..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; @@ -152,7 +172,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")) { @@ -346,8 +366,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, - 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 +445,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, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations)) { ImPlot::PlotPieChart(labels1, data1, 4, 0.5f, 0.5f, 0.4f, normalize, "%.2f"); ImPlot::EndPlot(); } @@ -438,7 +457,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, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations)) { ImPlot::PlotPieChart(labels2, data2, 5, 0.5f, 0.5f, 0.4f, true, "%.0f", 180); ImPlot::EndPlot(); } @@ -470,12 +489,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),0,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 +507,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,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(); @@ -514,15 +533,15 @@ 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), 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 +602,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_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); @@ -591,10 +610,48 @@ void ShowDemoWindow(bool* p_open) { ImPlot::EndPlot(); } } + if (ImGui::CollapsingHeader("Time Formatted Axes")) { + + 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(); + } + } //------------------------------------------------------------------------- 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); @@ -620,7 +677,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); @@ -649,7 +705,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)) { 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)); @@ -700,9 +756,9 @@ 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_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); @@ -767,7 +823,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]; @@ -1024,7 +1080,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(); } @@ -1116,7 +1172,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}; @@ -1128,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::SetNextPlotLimits(0, 218, 1250, 1600); - if (ImPlot::BeginPlot("Candlestick Chart","Day","USD")) { + ImPlot::GetStyle().UseLocalTime = false; + ImPlot::SetNextPlotLimits(1546300800, 1571961600, 1250, 1600); + 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(); } @@ -1169,7 +1226,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_NoDecorations,ImPlotAxisFlags_NoDecorations)) { ImPlot::PushStyleColor(ImPlotCol_Line, col); ImPlot::PlotLine(id, values, count, offset); ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f); @@ -1249,7 +1306,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) @@ -1262,10 +1320,43 @@ 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::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; + 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(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]); + 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 - 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) { @@ -1273,10 +1364,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]); @@ -1287,28 +1374,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 = round(mouse.x); - 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(); - ImGui::Text("Day: %.0f", xs[idx]); - 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(); } @@ -1397,7 +1463,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 1f4969d..d359308 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 @@ -76,6 +77,10 @@ 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_MIN_TIME 0 +// Maximum allowable timestamp value 01/01/3000 @ 12:00am (UTC) +#define IMPLOT_MAX_TIME 32503680000 //----------------------------------------------------------------------------- // [SECTION] Generic Helpers @@ -84,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 @@ -115,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; @@ -129,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); } }; @@ -147,7 +158,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 +170,37 @@ 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_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 +}; + //----------------------------------------------------------------------------- // [SECTION] ImPlot Structs //----------------------------------------------------------------------------- @@ -190,12 +234,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; } }; @@ -251,13 +297,62 @@ struct ImPlotAxis bool HoveredTot; ImPlotAxis() { - Flags = PreviousFlags = ImPlotAxisFlags_Default; + Flags = PreviousFlags = ImPlotAxisFlags_None; Range.Min = 0; Range.Max = 1; Dragging = false; 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 (_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) + 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)); + 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.Max <= Range.Min) + Range.Max = Range.Min + DBL_EPSILON; + } }; // Axis state information only needed between BeginPlot/EndPlot @@ -272,17 +367,19 @@ struct ImPlotAxisState bool LockMin; bool LockMax; bool Lock; + bool IsTime; ImPlotAxisState(ImPlotAxis* axis, bool has_range, ImGuiCond range_cond, bool present) { Axis = axis; 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); Lock = !Present || ((LockMin && LockMax) || (HasRange && RangeCond == ImGuiCond_Always)); + IsTime = ImHasFlag(Axis->Flags, ImPlotAxisFlags_Time); } ImPlotAxisState() { } @@ -336,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; @@ -456,6 +553,9 @@ struct ImPlotContext { int ColormapSize; ImVector ColormapModifiers; + // Time + tm Tm; + // Misc int VisibleItemCount; int DigitalPlotItemCnt; @@ -477,6 +577,32 @@ 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! @@ -532,8 +658,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); @@ -547,6 +671,10 @@ 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 //----------------------------------------------------------------------------- @@ -560,15 +688,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, 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); // 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); @@ -610,14 +742,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; } // 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. @@ -649,6 +773,57 @@ 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); } +//----------------------------------------------------------------------------- +// Time Utils +//----------------------------------------------------------------------------- + + + +// 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. #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)); +} + +// 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. +ImPlotTime FloorTime(const ImPlotTime& t, ImPlotTimeUnit unit); +// Rounds a timestamp up to the nearest unit. +ImPlotTime CeilTime(const ImPlotTime& t, ImPlotTimeUnit unit); +// Rounds a timestamp up or down to the nearest unit. +ImPlotTime RoundTime(const ImPlotTime& t, ImPlotTimeUnit unit); + +// Get year from timestamp +int GetYear(const ImPlotTime& t); +// Make a timestamp starting at the first day of a year +ImPlotTime MakeYear(int year); + +// Formates a timestamp t into a buffer according to fmt. +int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt); +// Prints a timestamp to console +void PrintTime(const ImPlotTime& t, ImPlotTimeFmt fmt); + //----------------------------------------------------------------------------- // [SECTION] Internal / Experimental Plotters // No guarantee of forward compatibility here! diff --git a/implot_items.cpp b/implot_items.cpp index 24206a5..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? @@ -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++; }