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

first pass at time formatted axes

This commit is contained in:
epezent 2020-09-02 23:30:32 -05:00
parent 90693cca1b
commit 5f77a9bb58
4 changed files with 214 additions and 6 deletions

View File

@ -501,7 +501,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 +511,17 @@ 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);
}
}
void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, ImPlotTimeFmt fmt) {
char temp[16];
if (tick.ShowLabel) {
tick.BufferOffset = buffer.size();
FormatTime(tick.PlotPos, temp, 16, fmt);
buffer.append(temp, temp + strlen(temp) + 1);
tick.LabelSize = ImGui::CalcTextSize(buffer.Buf.Data + tick.BufferOffset);
}
@ -565,6 +575,17 @@ void AddTicksLogarithmic(const ImPlotRange& range, int nMajor, ImPlotTickCollect
}
}
void AddTicksTime(const ImPlotRange& range, ImPlotTickCollection& ticks) {
ImPlotTimeUnit unit_range = GetUnitForRange(range.Min, range.Max);
ImPlotTimeUnit unit_ticks = unit_range == 0 ? ImPlotTimeUnit_Us : unit_range - 1;
double t = FloorTime(range.Min, unit_range);
while (t < range.Max) {
t = AddTime(t, unit_ticks, 1);
ImPlotTick tick(t,false,true);
LabelTickTime(tick,ticks.Labels,unit_ticks);
ticks.AddTick(tick);
}
}
void AddTicksCustom(const double* values, const char** labels, int n, ImPlotTickCollection& ticks) {
for (int i = 0; i < n; ++i) {
@ -755,6 +776,8 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons
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 if (gp.X.IsTime)
AddTicksTime(plot.XAxis.Range, gp.XTicks);
else
AddTicksDefault(plot.XAxis.Range, ImMax(2, (int)IM_ROUND(0.003 * gp.BB_Canvas.GetWidth())), IMPLOT_SUB_DIV, gp.XTicks);
}
@ -768,10 +791,14 @@ bool BeginPlot(const char* title, const char* x_label, const char* y_label, cons
}
// plot bb
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_bot = (gp.X.HasLabels ? txt_height + gp.Style.LabelPadding.y : 0)
+ (x_label ? txt_height + gp.Style.LabelPadding.y : 0)
+ (gp.X.IsTime ? txt_height + gp.Style.LabelPadding.y : 0);
const float pad_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)

View File

@ -76,6 +76,7 @@ enum ImPlotAxisFlags_ {
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_Time = 1 << 7, // axis will display data/time formatted labels
ImPlotAxisFlags_Default = ImPlotAxisFlags_GridLines | ImPlotAxisFlags_TickMarks | ImPlotAxisFlags_TickLabels,
ImPlotAxisFlags_Auxiliary = ImPlotAxisFlags_TickMarks | ImPlotAxisFlags_TickLabels,
};

View File

@ -591,6 +591,13 @@ void ShowDemoWindow(bool* p_open) {
ImPlot::EndPlot();
}
}
if (ImGui::CollapsingHeader("Time Formatting")) {
ImPlot::SetNextPlotLimits(1599106881,1599106881+1000000,0,1);
if (ImPlot::BeginPlot("UTC Time", "Date-Time", "Y-Axis", ImVec2(-1,0), ImPlotFlags_Default, ImPlotAxisFlags_Default | ImPlotAxisFlags_Time)) {
ImPlot::EndPlot();
}
}
//-------------------------------------------------------------------------
if (ImGui::CollapsingHeader("Multiple Y-Axes")) {

View File

@ -35,6 +35,7 @@
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <time.h>
#include "imgui_internal.h"
#ifndef IMPLOT_VERSION
@ -148,6 +149,8 @@ struct ImPlotPointArray {
//-----------------------------------------------------------------------------
typedef int ImPlotScale; // -> enum ImPlotScale_
typedef int ImPlotTimeUnit; // -> enum ImPlotTimeUnit_
typedef int ImPlotTimeFmt; // -> enum ImPlotTimeFmt_
// XY axes scaling combinations
enum ImPlotScale_ {
@ -157,6 +160,29 @@ 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_SUs, // :29.428 552
ImPlotTimeFmt_SMs, // :29.428
ImPlotTimeFmt_S, // :29
ImPlotTimeFmt_HrMin, // 7:21pm
ImPlotTimeFmt_Hr, // 7pm
ImPlotTimeFmt_MoDay, // 10/3
ImPlotTimeFmt_Mo, // Oct
ImPlotTimeFmt_Yr // 1991
};
//-----------------------------------------------------------------------------
// [SECTION] ImPlot Structs
//-----------------------------------------------------------------------------
@ -272,6 +298,7 @@ struct ImPlotAxisState
bool LockMin;
bool LockMax;
bool Lock;
bool IsTime;
ImPlotAxisState(ImPlotAxis* axis, bool has_range, ImGuiCond range_cond, bool present) {
Axis = axis;
@ -283,6 +310,7 @@ struct ImPlotAxisState
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() { }
@ -456,6 +484,9 @@ struct ImPlotContext {
int ColormapSize;
ImVector<ImPlotColormapMod> ColormapModifiers;
// Time
tm Tm;
// Misc
int VisibleItemCount;
int DigitalPlotItemCnt;
@ -560,15 +591,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, 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);
@ -649,6 +684,144 @@ 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);
}
//-----------------------------------------------------------------------------
// [SECTION] Time Utils
//-----------------------------------------------------------------------------
static const int DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
static const double ImPlotTimeUnitSpans[ImPlotTimeUnit_COUNT] = {0.000001, 0.001, 1, 60, 3600, 86400, 2629800, 31557600};
inline ImPlotTimeUnit GetUnitForRange(double smin, double smax) {
double range = smax - smin;
for (int i = 0; i < ImPlotTimeUnit_COUNT; ++i) {
if (range <= ImPlotTimeUnitSpans[i])
return (ImPlotTimeUnit)i;
}
return ImPlotTimeUnit_Yr;
}
// 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.
inline int GetDaysInMonth(int year, int month) {
return DaysInMonth[month] + (int)(month == 1 && IsLeapYear(year));
}
inline time_t MkGmTime(const 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 += DaysInMonth[m] * 86400;
if (m == 1 && IsLeapYear(year)) secs += 86400;
}
secs += (ptm->tm_mday - 1) * 86400;
secs += ptm->tm_hour * 3600;
secs += ptm->tm_min * 60;
secs += ptm->tm_sec;
return secs;
}
inline tm* GmTime(const time_t* time, tm* tm)
{
#ifdef _MSC_VER
if (gmtime_s(tm, time) == 0)
return tm;
else
return NULL;
#else
return gmtime_r(time, tm);
#endif
}
inline double AddTime(double t, ImPlotTimeUnit unit, int count) {
switch(unit) {
case ImPlotTimeUnit_Us: return t + count * 0.000001;
case ImPlotTimeUnit_Ms: return t + count * 0.001;
case ImPlotTimeUnit_S: return t + count;
case ImPlotTimeUnit_Min: return t + count * 60;
case ImPlotTimeUnit_Hr: return t + count * 3600;
case ImPlotTimeUnit_Day: return t + count * 86400;
case ImPlotTimeUnit_Yr: count *= 12; // fall-through
case ImPlotTimeUnit_Mo: for (int i = 0; i < count; ++i) {
time_t s = (time_t)t;
GmTime(&s, &GImPlot->Tm);
int days = GetDaysInMonth(GImPlot->Tm.tm_year, GImPlot->Tm.tm_mon);
t = AddTime(t, ImPlotTimeUnit_Day, days);
}
return t;
default: return t;
}
}
inline double FloorTime(double t, ImPlotTimeUnit unit) {
time_t s = (time_t)t;
GmTime(&s, &GImPlot->Tm);
GImPlot->Tm.tm_isdst = -1;
switch (unit) {
case ImPlotTimeUnit_S: return (double)s;
case ImPlotTimeUnit_Ms: return floor(t * 1000) / 1000;
case ImPlotTimeUnit_Us: return floor(t * 1000000) / 1000000;
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;
}
s = MkGmTime(&GImPlot->Tm);
return (double)s;
}
inline double CeilTime(double t, ImPlotTimeUnit unit) {
return AddTime(FloorTime(t, unit), unit, 1);
}
inline void FormatTime(double t, char* buffer, int size, ImPlotTimeFmt fmt) {
time_t s = (time_t)t;
int ms = (int)(t * 1000 - floor(t) * 1000);
int us = (int)(t * 1000000 - floor(t) * 1000000);
tm& Tm = GImPlot->Tm;
GmTime(&s, &Tm);
switch(fmt) {
case ImPlotTimeFmt_Yr: strftime(buffer, size, "%Y", &Tm); break;
case ImPlotTimeFmt_Mo: strftime(buffer, size, "%b", &Tm); break;
case ImPlotTimeFmt_MoDay: strftime(buffer, size, "%m/%d", &Tm); break;
case ImPlotTimeFmt_Hr:
if (Tm.tm_hour == 0)
snprintf(buffer, size, "12am");
else if (Tm.tm_hour == 12)
snprintf(buffer, size, "12pm");
else if (Tm.tm_hour < 12)
snprintf(buffer, size, "%uam", Tm.tm_hour);
else if (Tm.tm_hour > 12)
snprintf(buffer, size, "%upm", Tm.tm_hour - 12);
break;
case ImPlotTimeFmt_HrMin:
if (Tm.tm_hour == 0)
snprintf(buffer, size, "12:%02dam", Tm.tm_min);
else if (Tm.tm_hour == 12)
snprintf(buffer, size, "12%02dpm", Tm.tm_min);
else if (Tm.tm_hour < 12)
snprintf(buffer, size, "%u:%02dam", Tm.tm_hour, Tm.tm_min);
else if (Tm.tm_hour > 12)
snprintf(buffer, size, "%u:%02dpm", Tm.tm_hour - 12, Tm.tm_min);
break;
case ImPlotTimeFmt_S: strftime(buffer, size, ":%S", &Tm);
case ImPlotTimeFmt_SMs: snprintf(buffer, size, ":%02d.%d", Tm.tm_sec, ms);
case ImPlotTimeFmt_SUs: snprintf(buffer, size, ":%02d.%d", Tm.tm_sec, us);
default: break;
}
}
//-----------------------------------------------------------------------------
// [SECTION] Internal / Experimental Plotters
// No guarantee of forward compatibility here!