1
0
Fork 0
mirror of https://github.com/gwm17/implot.git synced 2024-11-26 20:28:50 -05:00

time-axes complete

This commit is contained in:
epezent 2020-09-06 00:06:51 -05:00
parent d3ea373cc7
commit defc529219
3 changed files with 164 additions and 126 deletions

View File

@ -671,23 +671,30 @@ inline tm* GetGmTime(const time_t* time, tm* tm)
} }
ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count) { ImPlotTime AddTime(const ImPlotTime& t, ImPlotTimeUnit unit, int count) {
tm& Tm = GImPlot->Tm;
ImPlotTime t_out = t; ImPlotTime t_out = t;
switch(unit) { switch(unit) {
case ImPlotTimeUnit_Us: return ImPlotTime(t.S, t.Us + count); case ImPlotTimeUnit_Us: t_out.Us += count; break;
case ImPlotTimeUnit_Ms: return ImPlotTime(t.S, t.Us + count * 1000); case ImPlotTimeUnit_Ms: t_out.Us += count * 1000; break;
case ImPlotTimeUnit_S: t_out.S += count; break; case ImPlotTimeUnit_S: t_out.S += count; break;
case ImPlotTimeUnit_Min: t_out.S += count * 60; break; case ImPlotTimeUnit_Min: t_out.S += count * 60; break;
case ImPlotTimeUnit_Hr: t_out.S += count * 3600; break; case ImPlotTimeUnit_Hr: t_out.S += count * 3600; break;
case ImPlotTimeUnit_Day: t_out.S += count * 86400; break; case ImPlotTimeUnit_Day: t_out.S += count * 86400; break;
case ImPlotTimeUnit_Yr: count *= 12; // fall-through case ImPlotTimeUnit_Mo: for (int i = 0; i < count; ++i) { // this might have a bug
case ImPlotTimeUnit_Mo: for (int i = 0; i < count; ++i) { GetGmTime(&t.S, &Tm);
GetGmTime(&t.S, &GImPlot->Tm); t_out.S += 86400 * GetDaysInMonth(Tm.tm_year + 1900, Tm.tm_mon);
int days = GetDaysInMonth(GImPlot->Tm.tm_year, GImPlot->Tm.tm_mon); }
t_out = AddTime(t_out, ImPlotTimeUnit_Day, days); 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; break;
default: break; default: break;
} }
t_out.RollOver();
return t_out; return t_out;
} }
@ -730,13 +737,17 @@ ImPlotTime MakeYear(int year) {
int yr = year - 1900; int yr = year - 1900;
if (yr < 0) if (yr < 0)
yr = 0; yr = 0;
GImPlot->Tm = tm(); tm& Tm = GImPlot->Tm;
GImPlot->Tm.tm_year = yr; Tm.tm_sec = 0;
GImPlot->Tm.tm_sec = 1; Tm.tm_min = 0;
time_t s = MakeGmTime(&GImPlot->Tm); Tm.tm_hour = 0;
if (s < 0) Tm.tm_mday = 1;
s = 0; Tm.tm_mon = 0;
return (double)s; Tm.tm_year = yr;
Tm.tm_sec = 0;
Tm.tm_isdst = -1;
time_t s = MakeGmTime(&Tm);
return ImPlotTime(s);
} }
int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt) { int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt) {
@ -763,12 +774,13 @@ int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt) {
case ImPlotTimeFmt_S: return snprintf(buffer, size, ":%02d", sec); case ImPlotTimeFmt_S: return snprintf(buffer, size, ":%02d", sec);
case ImPlotTimeFmt_HrMinS: return snprintf(buffer, size, "%d:%02d:%02d%s", hr, min, sec, ap); 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_HrMin: return snprintf(buffer, size, "%d:%02d%s", hr, min, ap);
case ImPlotTimeFmt_Hr: return snprintf(buffer, size, "%u%s", hr, 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_DayMo: return snprintf(buffer, size, "%d/%d", mon, day);
case ImPlotTimeFmt_DayMoHrMin: return snprintf(buffer, size, "%d/%d %u:%02d%s", mon, day, hr, min, ap); case ImPlotTimeFmt_DayMoHr: return snprintf(buffer, size, "%d/%d %d%s", mon, day, hr, ap);
case ImPlotTimeFmt_DayMoYr: return snprintf(buffer, size, "%d/%d/%d", mon, day, yr); case ImPlotTimeFmt_DayMoHrMin: return snprintf(buffer, size, "%d/%d %d:%02d%s", mon, day, hr, min, ap);
case ImPlotTimeFmt_DayMoYrHrMin: return snprintf(buffer, size, "%d/%d/%d %u:%02d%s", mon, day, yr, hr, min, ap); case ImPlotTimeFmt_DayMoYr: return snprintf(buffer, size, "%d/%d/%02d", mon, day, yr);
case ImPlotTimeFmt_DayMoYrHrMinS: return snprintf(buffer, size, "%d/%d/%d %u:%02d:%02d%s", mon, day, yr, hr, min, sec, ap); 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_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_Mo: return snprintf(buffer, size, "%s", mnames[Tm.tm_mon]);
case ImPlotTimeFmt_Yr: return snprintf(buffer, size, "%d", year); case ImPlotTimeFmt_Yr: return snprintf(buffer, size, "%d", year);
@ -776,7 +788,7 @@ int FormatTime(const ImPlotTime& t, char* buffer, int size, ImPlotTimeFmt fmt) {
} }
} }
void PrintTime(double t, ImPlotTimeFmt fmt) { void PrintTime(const ImPlotTime& t, ImPlotTimeFmt fmt) {
static char buff[32]; static char buff[32];
FormatTime(t, buff, 32, fmt); FormatTime(t, buff, 32, fmt);
printf("%s\n",buff); printf("%s\n",buff);
@ -791,8 +803,9 @@ inline float GetTimeLabelWidth(ImPlotTimeFmt fmt) {
case ImPlotTimeFmt_S: return ImGui::CalcTextSize(":88").x; // :29 case ImPlotTimeFmt_S: return ImGui::CalcTextSize(":88").x; // :29
case ImPlotTimeFmt_HrMinS: return ImGui::CalcTextSize("88:88:88pm").x; // 7:21:29pm 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_HrMin: return ImGui::CalcTextSize("88:88pm").x; // 7:21pm
case ImPlotTimeFmt_Hr: return ImGui::CalcTextSize("8pm").x; // 7pm case ImPlotTimeFmt_Hr: return ImGui::CalcTextSize("88pm").x; // 7pm
case ImPlotTimeFmt_DayMo: return ImGui::CalcTextSize("88/88").x; // 10/3 case ImPlotTimeFmt_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_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_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_DayMoYrHrMin: return ImGui::CalcTextSize("88/88/88 88:88pm").x; // 10/3/91 7:21pm
@ -804,16 +817,23 @@ inline float GetTimeLabelWidth(ImPlotTimeFmt fmt) {
} }
} }
inline void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeFmt fmt) { inline void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, const ImPlotTime& t, ImPlotTimeFmt fmt) {
char temp[32]; char temp[32];
if (tick.ShowLabel) { if (tick.ShowLabel) {
tick.BufferOffset = buffer.size(); tick.BufferOffset = buffer.size();
FormatTime(tick.PlotPos, temp, 32, fmt); FormatTime(t, temp, 32, fmt);
buffer.append(temp, temp + strlen(temp) + 1); buffer.append(temp, temp + strlen(temp) + 1);
tick.LabelSize = ImGui::CalcTextSize(buffer.Buf.Data + tick.BufferOffset); 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] = { static const ImPlotTimeFmt TimeFormatLevel0[ImPlotTimeUnit_COUNT] = {
ImPlotTimeFmt_Us, ImPlotTimeFmt_Us,
ImPlotTimeFmt_SMs, ImPlotTimeFmt_SMs,
@ -830,9 +850,9 @@ static const ImPlotTimeFmt TimeFormatLevel1[ImPlotTimeUnit_COUNT] = {
ImPlotTimeFmt_HrMinS, ImPlotTimeFmt_HrMinS,
ImPlotTimeFmt_HrMin, ImPlotTimeFmt_HrMin,
ImPlotTimeFmt_HrMin, ImPlotTimeFmt_HrMin,
ImPlotTimeFmt_DayMo, ImPlotTimeFmt_DayMoYr,
ImPlotTimeFmt_DayMo, ImPlotTimeFmt_DayMoYr,
ImPlotTimeFmt_MoYr, ImPlotTimeFmt_Yr,
ImPlotTimeFmt_Yr ImPlotTimeFmt_Yr
}; };
@ -843,82 +863,86 @@ static const ImPlotTimeFmt TimeFormatLevel1First[ImPlotTimeUnit_COUNT] = {
ImPlotTimeFmt_DayMoYrHrMin, ImPlotTimeFmt_DayMoYrHrMin,
ImPlotTimeFmt_DayMoYr, ImPlotTimeFmt_DayMoYr,
ImPlotTimeFmt_DayMoYr, ImPlotTimeFmt_DayMoYr,
ImPlotTimeFmt_MoYr, ImPlotTimeFmt_Yr,
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) { void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollection& ticks) {
// get units for level 0 and level 1 labels // get units for level 0 and level 1 labels
const ImPlotTimeUnit unit0 = GetUnitForRange(range.Size() / (plot_width / 100)); // level = 0 (top) const ImPlotTimeUnit unit0 = GetUnitForRange(range.Size() / (plot_width / 100)); // level = 0 (top)
const ImPlotTimeUnit unit1 = unit0 + 1; // level = 1 (bottom) const ImPlotTimeUnit unit1 = unit0 + 1; // level = 1 (bottom)
// get time format specs // get time format specs
const ImPlotTimeFmt fmt0 = TimeFormatLevel0[unit0]; const ImPlotTimeFmt fmt0 = TimeFormatLevel0[unit0];
const ImPlotTimeFmt fmt1 = TimeFormatLevel1[unit1]; const ImPlotTimeFmt fmt1 = TimeFormatLevel1[unit1];
const ImPlotTimeFmt fmtf = TimeFormatLevel1First[unit1]; const ImPlotTimeFmt fmtf = TimeFormatLevel1First[unit1];
// min max times
const ImPlotTime t_min(range.Min); const ImPlotTime t_min = ImPlotTime::FromDouble(range.Min);
const ImPlotTime t_max(range.Max); const ImPlotTime t_max = ImPlotTime::FromDouble(range.Max);
// maximum allowable density of labels // maximum allowable density of labels
const float max_density = 0.5f; const float max_density = 0.5f;
// book keeping // book keeping
bool first = true; const char* last_major = NULL;
const char* last_major = NULL;
if (unit0 != ImPlotTimeUnit_Yr) { if (unit0 != ImPlotTimeUnit_Yr) {
// pixels per major (level 1) division // pixels per major (level 1) division
const float pix_per_major_div = plot_width / (float)(range.Size() / TimeUnitSpans[unit1]); const float pix_per_major_div = plot_width / (float)(range.Size() / TimeUnitSpans[unit1]);
// nominal pixels taken up by minor (level 0) label // nominal pixels taken up by labels
const float minor_label_width = GetTimeLabelWidth(fmt0); 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 // the maximum number of minor (level 0) labels that can fit between major (level 1) divisions
const int minor_per_major = (int)(max_density * pix_per_major_div / minor_label_width); const int minor_per_major = (int)(max_density * pix_per_major_div / fmt0_width);
// the minor step size (level 0) // the minor step size (level 0)
const int step = GetTimeStep(minor_per_major, unit0); const int step = GetTimeStep(minor_per_major, unit0);
// generate ticks // generate ticks
ImPlotTime t1 = FloorTime(ImPlotTime(range.Min), unit1); ImPlotTime t1 = FloorTime(ImPlotTime::FromDouble(range.Min), unit1);
t1 = t1;
while (t1 < t_max) { while (t1 < t_max) {
// get next major
const ImPlotTime t2 = AddTime(t1, unit1, 1);
// add major tick
if (t1 >= t_min && t1 <= t_max) { if (t1 >= t_min && t1 <= t_max) {
// minor level 0 tick // minor level 0 tick
ImPlotTick tick_min(t1.ToDouble(),true,true); ImPlotTick tick_min(t1.ToDouble(),true,true);
tick_min.Level = 0; tick_min.Level = 0;
LabelTickTime(tick_min,ticks.Labels,fmt0); LabelTickTime(tick_min,ticks.Labels,t1,fmt0);
ticks.AddTick(tick_min); ticks.AddTick(tick_min);
// major level 1 tick // major level 1 tick
ImPlotTick tick_maj(t1.ToDouble(),true,true); ImPlotTick tick_maj(t1.ToDouble(),true,true);
tick_maj.Level = 1; tick_maj.Level = 1;
LabelTickTime(tick_maj,ticks.Labels,first ? fmtf : fmt1); LabelTickTime(tick_maj,ticks.Labels,t1, last_major == NULL ? fmtf : fmt1);
const char* this_major = ticks.Labels.Buf.Data + tick_maj.BufferOffset; const char* this_major = ticks.Labels.Buf.Data + tick_maj.BufferOffset;
if (last_major == NULL) if (last_major && TimeLabelSame(last_major,this_major))
last_major = this_major; tick_maj.ShowLabel = false;
else if (strcmp(last_major,this_major) == 0) last_major = this_major;
tick_maj.ShowLabel = false; ticks.AddTick(tick_maj);
else
last_major = this_major;
ticks.AddTick(tick_maj);
if (first) first = false;
} }
// add minor ticks up until next major // add minor ticks up until next major
const ImPlotTime t2 = AddTime(t1, unit1, 1);
if (minor_per_major > 1 && (t_min <= t2 && t1 <= t_max)) { if (minor_per_major > 1 && (t_min <= t2 && t1 <= t_max)) {
ImPlotTime t12 = AddTime(t1, unit0, step); ImPlotTime t12 = AddTime(t1, unit0, step);
while (t12 < t2) { while (t12 < t2) {
float px_to_t2 = (float)((t2 - t12).ToDouble()/range.Size()) * plot_width; float px_to_t2 = (float)((t2 - t12).ToDouble()/range.Size()) * plot_width;
if (t12 >= t_min && t12 <= t_max) { if (t12 >= t_min && t12 <= t_max) {
ImPlotTick tick(t12.ToDouble(),false,px_to_t2 >= minor_label_width); ImPlotTick tick(t12.ToDouble(),false,px_to_t2 >= fmt0_width);
tick.Level = 0; tick.Level = 0;
LabelTickTime(tick,ticks.Labels,fmt0); LabelTickTime(tick,ticks.Labels,t12,fmt0);
ticks.AddTick(tick); ticks.AddTick(tick);
// if (first) { if (last_major == NULL && px_to_t2 >= fmt0_width && px_to_t2 >= (fmt1_width + fmtf_width) / 2) {
// ImPlotTick tick_maj(t12,true,true); ImPlotTick tick_maj(t12.ToDouble(),true,true);
// tick_maj.Level = 1; tick_maj.Level = 1;
// LabelTickTime(tick_maj,ticks.Labels,TimeFormatLevel1[unit1]); LabelTickTime(tick_maj,ticks.Labels,t12,fmtf);
// ticks.AddTick(tick_maj); last_major = ticks.Labels.Buf.Data + tick_maj.BufferOffset;
// first = false; ticks.AddTick(tick_maj);
// } }
} }
t12 = AddTime(t12, unit0, step); t12 = AddTime(t12, unit0, step);
} }
@ -929,22 +953,23 @@ void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollecti
else { else {
const float label_width = GetTimeLabelWidth(TimeFormatLevel0[ImPlotTimeUnit_Yr]); const float label_width = GetTimeLabelWidth(TimeFormatLevel0[ImPlotTimeUnit_Yr]);
const int max_labels = (int)(max_density * plot_width / label_width); const int max_labels = (int)(max_density * plot_width / label_width);
const int year_min = GetYear(range.Min); const int year_min = GetYear(t_min);
const int year_max = GetYear(CeilTime(range.Max, ImPlotTimeUnit_Yr)); const int year_max = GetYear(CeilTime(t_max, ImPlotTimeUnit_Yr));
const double nice_range = NiceNum((year_max - year_min)*0.99,false); const double nice_range = NiceNum((year_max - year_min)*0.99,false);
const double interval = NiceNum(nice_range / (max_labels - 1), true); const double interval = NiceNum(nice_range / (max_labels - 1), true);
const int graphmin = (int)(floor(year_min / interval) * interval); 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; const int step = (int)interval <= 0 ? 1 : (int)interval;
ImPlotTime t1 = MakeYear(graphmin);
while (t1 < range.Max) { for (int y = graphmin; y < graphmax; y += step) {
if (t1 >= t_min && t1 <= t_max) { ImPlotTime t = MakeYear(y);
ImPlotTick tick(t1.ToDouble(), true, true); if (t >= t_min && t <= t_max) {
ImPlotTick tick(t.ToDouble(), true, true);
tick.Level = 0; tick.Level = 0;
LabelTickTime(tick, ticks.Labels, TimeFormatLevel0[ImPlotTimeUnit_Yr]); LabelTickTime(tick, ticks.Labels, t, TimeFormatLevel0[ImPlotTimeUnit_Yr]);
ticks.AddTick(tick); ticks.AddTick(tick);
} }
t1 = AddTime(t1, ImPlotTimeUnit_Yr, step); }
}
} }
} }
@ -966,10 +991,14 @@ void UpdateAxisColors(int axis_flag, ImPlotAxisColor* col) {
// BeginPlot() // 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()?"); IM_ASSERT_USER_ERROR(GImPlot != NULL, "No current context. Did you call ImPlot::CreateContext() or ImPlot::SetCurrentContext()?");
ImPlotContext& gp = *GImPlot; ImPlotContext& gp = *GImPlot;
IM_ASSERT_USER_ERROR(gp.CurrentPlot == NULL, "Mismatched BeginPlot()/EndPlot()!"); 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 ----------------------------------------------------------- // FRONT MATTER -----------------------------------------------------------
@ -1002,6 +1031,8 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons
// There's probably an easy bit mask trick I'm not aware of. // There's probably an easy bit mask trick I'm not aware of.
if (flags != plot.PreviousFlags) if (flags != plot.PreviousFlags)
plot.Flags = flags; plot.Flags = flags;
if (x_flags != plot.XAxis.PreviousFlags)
plot.XAxis.Flags = x_flags;
if (y_flags != plot.YAxis[0].PreviousFlags) if (y_flags != plot.YAxis[0].PreviousFlags)
plot.YAxis[0].Flags = y_flags; plot.YAxis[0].Flags = y_flags;
if (y2_flags != plot.YAxis[1].PreviousFlags) if (y2_flags != plot.YAxis[1].PreviousFlags)
@ -1106,9 +1137,8 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons
const float txt_height = ImGui::GetTextLineHeight(); const float txt_height = ImGui::GetTextLineHeight();
const float pad_top = title_size.x > 0.0f ? txt_height + gp.Style.LabelPadding.y : 0; const float pad_top = title_size.x > 0.0f ? txt_height + gp.Style.LabelPadding.y : 0;
const float pad_bot = (gp.X.HasLabels ? txt_height + gp.Style.LabelPadding.y : 0) 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) + (x_label ? txt_height + gp.Style.LabelPadding.y : 0);
+ (gp.X.IsTime ? txt_height + gp.Style.LabelPadding.y : 0);
const float plot_height = gp.BB_Canvas.GetHeight() - pad_top - pad_bot; const float plot_height = gp.BB_Canvas.GetHeight() - pad_top - pad_bot;
@ -1573,14 +1603,14 @@ inline void EndDisabledControls(bool cond) {
} }
} }
inline void ShowAxisContextMenu(ImPlotAxisState& state) { inline void ShowAxisContextMenu(ImPlotAxisState& state, bool time_allowed) {
ImGui::PushItemWidth(75); ImGui::PushItemWidth(75);
bool total_lock = state.HasRange && state.RangeCond == ImGuiCond_Always; bool total_lock = state.HasRange && state.RangeCond == ImGuiCond_Always;
bool logscale = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_LogScale); bool logscale = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_LogScale);
// bool timesale = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_Time); bool timescale = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_Time);
bool grid = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines); bool grid = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines);
bool ticks = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_TickMarks); bool ticks = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_TickMarks);
bool labels = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_TickLabels); bool labels = ImHasFlag(state.Axis->Flags, ImPlotAxisFlags_TickLabels);
double drag_speed = (state.Axis->Range.Size() <= DBL_EPSILON) ? DBL_EPSILON * 1.0e+13 : 0.01 * state.Axis->Range.Size(); // recover from almost equal axis limits. 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); BeginDisabledControls(total_lock);
@ -1611,10 +1641,18 @@ inline void ShowAxisContextMenu(ImPlotAxisState& state) {
if (ImGui::Checkbox("Invert", &state.Invert)) if (ImGui::Checkbox("Invert", &state.Invert))
ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_Invert); ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_Invert);
BeginDisabledControls(timescale && time_allowed);
if (ImGui::Checkbox("Log Scale", &logscale)) if (ImGui::Checkbox("Log Scale", &logscale))
ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_LogScale); ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_LogScale);
// if (ImGui::Checkbox("Time", &timesale)) EndDisabledControls(timescale && time_allowed);
// ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_Time);
if (time_allowed) {
BeginDisabledControls(logscale);
if (ImGui::Checkbox("Time", &timescale))
ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_Time);
EndDisabledControls(logscale);
}
ImGui::Separator(); ImGui::Separator();
if (ImGui::Checkbox("Grid Lines", &grid)) if (ImGui::Checkbox("Grid Lines", &grid))
ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines); ImFlipFlag(state.Axis->Flags, ImPlotAxisFlags_GridLines);
@ -1629,7 +1667,7 @@ void ShowPlotContextMenu(ImPlotState& plot) {
ImPlotContext& gp = *GImPlot; ImPlotContext& gp = *GImPlot;
if (ImGui::BeginMenu("X-Axis")) { if (ImGui::BeginMenu("X-Axis")) {
ImGui::PushID("X"); ImGui::PushID("X");
ShowAxisContextMenu(gp.X); ShowAxisContextMenu(gp.X, true);
ImGui::PopID(); ImGui::PopID();
ImGui::EndMenu(); ImGui::EndMenu();
} }
@ -1648,7 +1686,7 @@ void ShowPlotContextMenu(ImPlotState& plot) {
} }
if (ImGui::BeginMenu(buf)) { if (ImGui::BeginMenu(buf)) {
ImGui::PushID(i); ImGui::PushID(i);
ShowAxisContextMenu(gp.Y[i]); ShowAxisContextMenu(gp.Y[i], false);
ImGui::PopID(); ImGui::PopID();
ImGui::EndMenu(); ImGui::EndMenu();
} }
@ -1721,10 +1759,11 @@ void EndPlot() {
if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickMarks)) { if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_TickMarks)) {
for (int t = 0; t < gp.XTicks.Size; t++) { for (int t = 0; t < gp.XTicks.Size; t++) {
ImPlotTick *xt = &gp.XTicks.Ticks[t]; ImPlotTick *xt = &gp.XTicks.Ticks[t];
DrawList.AddLine(ImVec2(xt->PixelPos, gp.BB_Plot.Max.y), if (xt->Level == 0)
ImVec2(xt->PixelPos, gp.BB_Plot.Max.y - (xt->Major ? gp.Style.MajorTickLen.x : gp.Style.MinorTickLen.x)), DrawList.AddLine(ImVec2(xt->PixelPos, gp.BB_Plot.Max.y),
gp.Col_X.Major, ImVec2(xt->PixelPos, gp.BB_Plot.Max.y - (xt->Major ? gp.Style.MajorTickLen.x : gp.Style.MinorTickLen.x)),
xt->Major ? gp.Style.MajorTickSize.x : gp.Style.MinorTickSize.x); gp.Col_X.Major,
xt->Major ? gp.Style.MajorTickSize.x : gp.Style.MinorTickSize.x);
} }
} }
PopPlotClipRect(); PopPlotClipRect();
@ -1916,6 +1955,12 @@ void EndPlot() {
if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) { if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) {
writer.Write("%.3E", gp.MousePos[0].x); 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 { else {
double range_x = gp.XTicks.Size > 1 ? (gp.XTicks.Ticks[1].PlotPos - gp.XTicks.Ticks[0].PlotPos) : plot.XAxis.Range.Size(); 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); writer.Write("%.*f", Precision(range_x), gp.MousePos[0].x);
@ -2001,7 +2046,7 @@ void EndPlot() {
ImGui::OpenPopup("##XContext"); ImGui::OpenPopup("##XContext");
if (ImGui::BeginPopup("##XContext")) { if (ImGui::BeginPopup("##XContext")) {
ImGui::Text("X-Axis"); ImGui::Separator(); ImGui::Text("X-Axis"); ImGui::Separator();
ShowAxisContextMenu(gp.X); ShowAxisContextMenu(gp.X, true);
ImGui::EndPopup(); ImGui::EndPopup();
} }
@ -2016,7 +2061,7 @@ void EndPlot() {
else { else {
ImGui::Text("Y-Axis %d", i + 1); ImGui::Separator(); ImGui::Text("Y-Axis %d", i + 1); ImGui::Separator();
} }
ShowAxisContextMenu(gp.Y[i]); ShowAxisContextMenu(gp.Y[i], false);
ImGui::EndPopup(); ImGui::EndPopup();
} }
ImGui::PopID(); ImGui::PopID();

View File

@ -152,7 +152,7 @@ void ShowDemoWindow(bool* p_open) {
return; return;
} }
ImGui::SetNextWindowPos(ImVec2(50, 50), ImGuiCond_FirstUseEver); 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); ImGui::Begin("ImPlot Demo", p_open, ImGuiWindowFlags_MenuBar);
if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenuBar()) {
if (ImGui::BeginMenu("Tools")) { if (ImGui::BeginMenu("Tools")) {
@ -591,23 +591,10 @@ void ShowDemoWindow(bool* p_open) {
ImPlot::EndPlot(); ImPlot::EndPlot();
} }
} }
if (ImGui::CollapsingHeader("Time Formatting")) { if (ImGui::CollapsingHeader("Time Formatted Axes")) {
static double min = 1599242863*0.5; static double min = 1577836800; // 01/01/2020 @ 12:00:00am (UTC)
static double max = 1599242863*1.5; static double max = 1609459200; // 01/01/2021 @ 12:00:00am (UTC)
static bool zooming = false; ImPlot::SetNextPlotLimits(min,max,0,1);
ImGuiCond cond = ImGuiCond_Once;
if (ImGui::Button("Zoom")) {
zooming = true;
}
if (zooming) {
cond = ImGuiCond_Always;
double range = max - min;
min += range * 0.005;
max -= range * 0.005;
if (range < 0.005)
zooming = false;
}
ImPlot::SetNextPlotLimits(min,max,0,1,cond);
if (ImPlot::BeginPlot("##Time", "UTC Time", "Y-Axis", ImVec2(-1,0), ImPlotFlags_Default, ImPlotAxisFlags_Default | ImPlotAxisFlags_Time)) { if (ImPlot::BeginPlot("##Time", "UTC Time", "Y-Axis", ImVec2(-1,0), ImPlotFlags_Default, ImPlotAxisFlags_Default | ImPlotAxisFlags_Time)) {
ImPlot::EndPlot(); ImPlot::EndPlot();
@ -615,8 +602,6 @@ void ShowDemoWindow(bool* p_open) {
} }
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
if (ImGui::CollapsingHeader("Multiple Y-Axes")) { if (ImGui::CollapsingHeader("Multiple Y-Axes")) {
static t_float xs[1001], xs2[1001], ys1[1001], ys2[1001], ys3[1001]; static t_float xs[1001], xs2[1001], ys1[1001], ys2[1001], ys3[1001];
for (int i = 0; i < 1001; ++i) { for (int i = 0; i < 1001; ++i) {
xs[i] = (i*0.1f); xs[i] = (i*0.1f);
@ -1294,7 +1279,7 @@ void PlotCandlestick(const char* label_id, const double* xs, const double* opens
// custom tool // custom tool
if (ImPlot::IsPlotHovered() && tooltip) { if (ImPlot::IsPlotHovered() && tooltip) {
ImPlotPoint mouse = ImPlot::GetPlotMousePos(); ImPlotPoint mouse = ImPlot::GetPlotMousePos();
mouse.x = ImPlot::RoundTime(ImPlotTime(mouse.x), ImPlotTimeUnit_Day).ToDouble(); mouse.x = ImPlot::RoundTime(ImPlotTime::FromDouble(mouse.x), ImPlotTimeUnit_Day).ToDouble();
float tool_l = ImPlot::PlotToPixels(mouse.x - half_width * 1.5, mouse.y).x; float tool_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_r = ImPlot::PlotToPixels(mouse.x + half_width * 1.5, mouse.y).x;
float tool_t = ImPlot::GetPlotPos().y; float tool_t = ImPlot::GetPlotPos().y;
@ -1308,7 +1293,7 @@ void PlotCandlestick(const char* label_id, const double* xs, const double* opens
if (idx != -1) { if (idx != -1) {
ImGui::BeginTooltip(); ImGui::BeginTooltip();
char buff[32]; char buff[32];
ImPlot::FormatTime(xs[idx],buff,32,ImPlotTimeFmt_DayMoYr); ImPlot::FormatTime(ImPlotTime::FromDouble(xs[idx]),buff,32,ImPlotTimeFmt_DayMoYr);
ImGui::Text("Day: %s", buff); ImGui::Text("Day: %s", buff);
ImGui::Text("Open: $%.2f", opens[idx]); ImGui::Text("Open: $%.2f", opens[idx]);
ImGui::Text("Close: $%.2f", closes[idx]); ImGui::Text("Close: $%.2f", closes[idx]);

View File

@ -126,10 +126,10 @@ struct ImOffsetCalculator {
struct ImBufferWriter struct ImBufferWriter
{ {
char* Buffer; char* Buffer;
size_t Size; int Size;
size_t Pos; int Pos;
ImBufferWriter(char* buffer, size_t size) { ImBufferWriter(char* buffer, int size) {
Buffer = buffer; Buffer = buffer;
Size = size; Size = size;
Pos = 0; Pos = 0;
@ -140,7 +140,7 @@ struct ImBufferWriter
va_start(argp, fmt); va_start(argp, fmt);
const int written = ::vsnprintf(&Buffer[Pos], Size - Pos - 1, fmt, argp); const int written = ::vsnprintf(&Buffer[Pos], Size - Pos - 1, fmt, argp);
if (written > 0) if (written > 0)
Pos += ImMin(size_t(written), Size-Pos-1); Pos += ImMin(written, Size-Pos-1);
va_end(argp); va_end(argp);
} }
}; };
@ -191,6 +191,7 @@ enum ImPlotTimeFmt_ {
ImPlotTimeFmt_HrMin, // 7:21pm ImPlotTimeFmt_HrMin, // 7:21pm
ImPlotTimeFmt_Hr, // 7pm ImPlotTimeFmt_Hr, // 7pm
ImPlotTimeFmt_DayMo, // 10/3 ImPlotTimeFmt_DayMo, // 10/3
ImPlotTimeFmt_DayMoHr, // 10/3 7pm
ImPlotTimeFmt_DayMoHrMin, // 10/3 7:21pm ImPlotTimeFmt_DayMoHrMin, // 10/3 7:21pm
ImPlotTimeFmt_DayMoYr, // 10/3/91 ImPlotTimeFmt_DayMoYr, // 10/3/91
ImPlotTimeFmt_DayMoYrHrMin, // 10/3/91 7:21pm ImPlotTimeFmt_DayMoYrHrMin, // 10/3/91 7:21pm
@ -580,13 +581,17 @@ struct ImPlotAxisScale
struct ImPlotTime { struct ImPlotTime {
time_t S; time_t S;
int Us; int Us;
ImPlotTime(time_t s, int us) { ImPlotTime() { S = 0; Us = 0; }
ImPlotTime(time_t s, int us = 0) {
S = s + us / 1000000; S = s + us / 1000000;
Us = us % 1000000; Us = us % 1000000;
} }
ImPlotTime(double t) { void RollOver() {
S = (time_t)t; S = S + Us / 1000000;
Us = (int)(t * 1000000 - floor(t) * 1000000); Us = Us % 1000000;
}
static ImPlotTime FromDouble(double t) {
return ImPlotTime((time_t)t, (int)(t * 1000000 - floor(t) * 1000000));
} }
double ToDouble() const { return (double)S + (double)Us / 1000000.0; } double ToDouble() const { return (double)S + (double)Us / 1000000.0; }
}; };
@ -696,7 +701,7 @@ void LabelTickDefault(ImPlotTick& tick, ImGuiTextBuffer& buffer);
// Label a tick with scientific formating. // Label a tick with scientific formating.
void LabelTickScientific(ImPlotTick& tick, ImGuiTextBuffer& buffer); void LabelTickScientific(ImPlotTick& tick, ImGuiTextBuffer& buffer);
// Label a tick with time formatting. // Label a tick with time formatting.
void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeFmt fmt); void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, const ImPlotTime& t, ImPlotTimeFmt fmt);
// Populates a list of ImPlotTicks with normal spaced and formatted ticks // Populates a list of ImPlotTicks with normal spaced and formatted ticks
void AddTicksDefault(const ImPlotRange& range, int nMajor, int nMinor, ImPlotTickCollection& ticks); void AddTicksDefault(const ImPlotRange& range, int nMajor, int nMinor, ImPlotTickCollection& ticks);
@ -780,6 +785,9 @@ inline T OffsetAndStride(const T* data, int idx, int count, int offset, int stri
// Time Utils // Time Utils
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// NB: These functions only work if there is a currnet context because the
// internal tm struct is owned by the context!
// Returns true if year is leap year (366 days long) // Returns true if year is leap year (366 days long)
inline bool IsLeapYear(int year) { inline bool IsLeapYear(int year) {
if (year % 4 != 0) return false; if (year % 4 != 0) return false;
@ -787,7 +795,7 @@ inline bool IsLeapYear(int year) {
if (year % 100 == 0) return false; if (year % 100 == 0) return false;
return true; return true;
} }
// Returns the number of days in a month, accounting for Feb. leap years. // Returns the number of days in a month, accounting for Feb. leap years. #month is zero indexed.
inline int GetDaysInMonth(int year, int month) { inline int GetDaysInMonth(int year, int month) {
static const int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; static const int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
return days[month] + (int)(month == 1 && IsLeapYear(year)); return days[month] + (int)(month == 1 && IsLeapYear(year));