1
0
Fork 0
mirror of https://github.com/gwm17/implot.git synced 2024-11-23 02:38:53 -05:00

Adds SetNextPlotFormatX/Y for custom tick label formatting (#198)

* add SetNextPlotFormat

* work on pruning

* finish up fmt
This commit is contained in:
Evan Pezent 2021-03-25 09:19:00 -06:00 committed by GitHub
parent 4b0f9c9495
commit ed1baf471a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 149 additions and 112 deletions

View File

@ -689,45 +689,55 @@ void ShowLegendEntries(ImPlotPlot& plot, const ImRect& legend_bb, bool interacta
// Tick Utils // Tick Utils
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void LabelTickDefault(ImPlotTick& tick, ImGuiTextBuffer& buffer) { void AddTicksDefault(const ImPlotRange& range, float pix, ImPlotOrientation orn, ImPlotTickCollection& ticks, const char* fmt) {
char temp[32]; const int idx0 = ticks.Size;
if (tick.ShowLabel) { const int nMinor = 10;
tick.TextOffset = buffer.size(); const int nMajor = ImMax(2, (int)IM_ROUND(pix / (orn == ImPlotOrientation_Horizontal ? 400.0f : 300.0f)));
snprintf(temp, 32, "%.10g", tick.PlotPos);
buffer.append(temp, temp + strlen(temp) + 1);
tick.LabelSize = ImGui::CalcTextSize(buffer.Buf.Data + tick.TextOffset);
}
}
void LabelTickScientific(ImPlotTick& tick, ImGuiTextBuffer& buffer) {
char temp[32];
if (tick.ShowLabel) {
tick.TextOffset = buffer.size();
snprintf(temp, 32, "%.0E", tick.PlotPos);
buffer.append(temp, temp + strlen(temp) + 1);
tick.LabelSize = ImGui::CalcTextSize(buffer.Buf.Data + tick.TextOffset);
}
}
void AddTicksDefault(const ImPlotRange& range, int nMajor, int nMinor, ImPlotTickCollection& ticks) {
const double nice_range = NiceNum(range.Size() * 0.99, false); const double nice_range = NiceNum(range.Size() * 0.99, false);
const double interval = NiceNum(nice_range / (nMajor - 1), true); const double interval = NiceNum(nice_range / (nMajor - 1), true);
const double graphmin = floor(range.Min / interval) * interval; const double graphmin = floor(range.Min / interval) * interval;
const double graphmax = ceil(range.Max / interval) * interval; const double graphmax = ceil(range.Max / interval) * interval;
bool first_major_set = false;
int first_major_idx = 0;
char dummy[32];
sprintf(dummy,fmt,-ImAbs(interval / nMinor));
ImVec2 dummy_size = ImGui::CalcTextSize(dummy);
ImVec2 total_size(0,0);
for (double major = graphmin; major < graphmax + 0.5 * interval; major += interval) { for (double major = graphmin; major < graphmax + 0.5 * interval; major += interval) {
if (range.Contains(major)) // is this zero? combat zero formatting issues
ticks.Append(major, true, true, LabelTickDefault); if (major-interval < 0 && major+interval > 0)
major = 0;
if (range.Contains(major)) {
if (!first_major_set) {
first_major_idx = ticks.Size;
first_major_set = true;
}
ticks.Append(major, true, true, fmt);
total_size += dummy_size;
}
for (int i = 1; i < nMinor; ++i) { for (int i = 1; i < nMinor; ++i) {
double minor = major + i * interval / nMinor; double minor = major + i * interval / nMinor;
if (range.Contains(minor)) if (range.Contains(minor)) {
ticks.Append(minor, false, true, LabelTickDefault); ticks.Append(minor, false, true, fmt);
total_size += dummy_size;
}
} }
} }
// prune if necessary
if ((orn == ImPlotOrientation_Horizontal && total_size.x > pix) || (orn == ImPlotOrientation_Vertical && total_size.y > pix)) {
for (int i = first_major_idx-1; i >= idx0; i -= 2)
ticks.Ticks[i].ShowLabel = false;
for (int i = first_major_idx+1; i < ticks.Size; i += 2)
ticks.Ticks[i].ShowLabel = false;
}
} }
void AddTicksLogarithmic(const ImPlotRange& range, int nMajor, ImPlotTickCollection& ticks) { void AddTicksLogarithmic(const ImPlotRange& range, float pix, ImPlotOrientation orn, ImPlotTickCollection& ticks, const char* fmt) {
if (range.Min <= 0 || range.Max <= 0) if (range.Min <= 0 || range.Max <= 0)
return; return;
const int nMajor = orn == ImPlotOrientation_Horizontal ? ImMax(2, (int)IM_ROUND(pix * 0.01f)) : ImMax(2, (int)IM_ROUND(pix * 0.02f));
double log_min = ImLog10(range.Min); double log_min = ImLog10(range.Min);
double log_max = ImLog10(range.Max); double log_max = ImLog10(range.Max);
int exp_step = ImMax(1,(int)(log_max - log_min) / nMajor); int exp_step = ImMax(1,(int)(log_max - log_min) / nMajor);
@ -742,7 +752,7 @@ void AddTicksLogarithmic(const ImPlotRange& range, int nMajor, ImPlotTickCollect
double major2 = ImPow(10, (double)(e + 1)); double major2 = ImPow(10, (double)(e + 1));
double interval = (major2 - major1) / 9; double interval = (major2 - major1) / 9;
if (major1 >= (range.Min - DBL_EPSILON) && major1 <= (range.Max + DBL_EPSILON)) if (major1 >= (range.Min - DBL_EPSILON) && major1 <= (range.Max + DBL_EPSILON))
ticks.Append(major1, true, true, LabelTickScientific); ticks.Append(major1, true, true, fmt);
for (int j = 0; j < exp_step; ++j) { for (int j = 0; j < exp_step; ++j) {
major1 = ImPow(10, (double)(e+j)); major1 = ImPow(10, (double)(e+j));
major2 = ImPow(10, (double)(e+j+1)); major2 = ImPow(10, (double)(e+j+1));
@ -750,25 +760,25 @@ void AddTicksLogarithmic(const ImPlotRange& range, int nMajor, ImPlotTickCollect
for (int i = 1; i < (9 + (int)(j < (exp_step - 1))); ++i) { for (int i = 1; i < (9 + (int)(j < (exp_step - 1))); ++i) {
double minor = major1 + i * interval; double minor = major1 + i * interval;
if (minor >= (range.Min - DBL_EPSILON) && minor <= (range.Max + DBL_EPSILON)) if (minor >= (range.Min - DBL_EPSILON) && minor <= (range.Max + DBL_EPSILON))
ticks.Append(minor, false, false, LabelTickScientific); ticks.Append(minor, false, false, fmt);
} }
} }
} }
} }
void AddTicksCustom(const double* values, const char* const labels[], int n, ImPlotTickCollection& ticks) { void AddTicksCustom(const double* values, const char* const labels[], int n, ImPlotTickCollection& ticks, const char* fmt) {
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
ImPlotTick tick(values[i], false, true);
if (labels != NULL) { if (labels != NULL) {
ImPlotTick tick(values[i], false, true);
tick.TextOffset = ticks.TextBuffer.size(); tick.TextOffset = ticks.TextBuffer.size();
ticks.TextBuffer.append(labels[i], labels[i] + strlen(labels[i]) + 1); ticks.TextBuffer.append(labels[i], labels[i] + strlen(labels[i]) + 1);
tick.LabelSize = ImGui::CalcTextSize(labels[i]); tick.LabelSize = ImGui::CalcTextSize(labels[i]);
ticks.Append(tick);
} }
else { else {
LabelTickDefault(tick, ticks.TextBuffer); ticks.Append(values[i], false, true, fmt);
} }
ticks.Append(tick);
} }
} }
@ -1256,12 +1266,18 @@ void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollecti
// Axis Utils // Axis Utils
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
static inline int AxisPrecision(const ImPlotAxis& axis, const ImPlotTickCollection& ticks) {
const double range = ticks.Size > 1 ? (ticks.Ticks[1].PlotPos - ticks.Ticks[0].PlotPos) : axis.Range.Size();
return Precision(range);
}
static inline double RoundAxisValue(const ImPlotAxis& axis, const ImPlotTickCollection& ticks, double value) {
return RoundTo(value, AxisPrecision(axis,ticks));
}
int LabelAxisValue(const ImPlotAxis& axis, const ImPlotTickCollection& ticks, double value, char* buff, int size) { int LabelAxisValue(const ImPlotAxis& axis, const ImPlotTickCollection& ticks, double value, char* buff, int size) {
ImPlotContext& gp = *GImPlot; ImPlotContext& gp = *GImPlot;
if (ImHasFlag(axis.Flags, ImPlotAxisFlags_LogScale)) { if (ImHasFlag(axis.Flags, ImPlotAxisFlags_Time)) {
return snprintf(buff, size, "%.3E", value);
}
else if (ImHasFlag(axis.Flags, ImPlotAxisFlags_Time)) {
ImPlotTimeUnit unit = (axis.Orientation == ImPlotOrientation_Horizontal) ImPlotTimeUnit unit = (axis.Orientation == ImPlotOrientation_Horizontal)
? GetUnitForRange(axis.Range.Size() / (gp.CurrentPlot->PlotRect.GetWidth() / 100)) ? GetUnitForRange(axis.Range.Size() / (gp.CurrentPlot->PlotRect.GetWidth() / 100))
: GetUnitForRange(axis.Range.Size() / (gp.CurrentPlot->PlotRect.GetHeight() / 100)); : GetUnitForRange(axis.Range.Size() / (gp.CurrentPlot->PlotRect.GetHeight() / 100));
@ -1521,9 +1537,9 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con
for (int i = 0; i < IMPLOT_Y_AXES; i++) { for (int i = 0; i < IMPLOT_Y_AXES; i++) {
if (gp.RenderY[i] && gp.NextPlotData.ShowDefaultTicksY[i]) { if (gp.RenderY[i] && gp.NextPlotData.ShowDefaultTicksY[i]) {
if (ImHasFlag(plot.YAxis[i].Flags, ImPlotAxisFlags_LogScale)) 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]); AddTicksLogarithmic(plot.YAxis[i].Range, plot_height, ImPlotOrientation_Vertical, gp.YTicks[i], GetFormatY(i));
else else
AddTicksDefault(plot.YAxis[i].Range, ImMax(2, (int)IM_ROUND(0.0025 * plot_height)), IMPLOT_SUB_DIV, gp.YTicks[i]); AddTicksDefault(plot.YAxis[i].Range, plot_height, ImPlotOrientation_Vertical, gp.YTicks[i], GetFormatY(i));
} }
} }
@ -1547,9 +1563,9 @@ bool BeginPlot(const char* title, const char* x_label, const char* y1_label, con
if (plot.XAxis.IsTime()) if (plot.XAxis.IsTime())
AddTicksTime(plot.XAxis.Range, plot_width, gp.XTicks); AddTicksTime(plot.XAxis.Range, plot_width, gp.XTicks);
else if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) else if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale))
AddTicksLogarithmic(plot.XAxis.Range, (int)IM_ROUND(plot_width * 0.01f), gp.XTicks); AddTicksLogarithmic(plot.XAxis.Range, plot_width, ImPlotOrientation_Horizontal, gp.XTicks, GetFormatX());
else else
AddTicksDefault(plot.XAxis.Range, ImMax(2, (int)IM_ROUND(0.0025 * plot_width)), IMPLOT_SUB_DIV, gp.XTicks); AddTicksDefault(plot.XAxis.Range, plot_width, ImPlotOrientation_Horizontal, gp.XTicks, GetFormatX());
} }
// (5) calc plot bb // (5) calc plot bb
@ -2242,10 +2258,8 @@ void EndPlot() {
// AXIS STATES ------------------------------------------------------------ // AXIS STATES ------------------------------------------------------------
const bool any_y_locked = plot.AnyYInputLocked();
const bool any_y_dragging = plot.YAxis[0].Dragging || plot.YAxis[1].Dragging || plot.YAxis[2].Dragging; const bool any_y_dragging = plot.YAxis[0].Dragging || plot.YAxis[1].Dragging || plot.YAxis[2].Dragging;
// FINAL RENDER ----------------------------------------------------------- // FINAL RENDER -----------------------------------------------------------
// render ticks // render ticks
@ -2284,10 +2298,7 @@ void EndPlot() {
if (axis_count >= 3) { if (axis_count >= 3) {
// Draw a bar next to the ticks to act as a visual separator. // Draw a bar next to the ticks to act as a visual separator.
DrawList.AddLine( DrawList.AddLine(ImVec2(x_start, plot.PlotRect.Min.y), ImVec2(x_start, plot.PlotRect.Max.y), GetStyleColorU32(ImPlotCol_YAxisGrid3), 1);
ImVec2(x_start, plot.PlotRect.Min.y),
ImVec2(x_start, plot.PlotRect.Max.y),
GetStyleColorU32(ImPlotCol_YAxisGrid3), 1);
} }
} }
ImGui::PopClipRect(); ImGui::PopClipRect();
@ -2358,52 +2369,34 @@ void EndPlot() {
DrawList.AddLine(v3, v4, col); DrawList.AddLine(v3, v4, col);
} }
// render mouse pos (TODO: use LabelAxisValue) // render mouse pos
if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMousePos) && plot.PlotHovered) { if (!ImHasFlag(plot.Flags, ImPlotFlags_NoMousePos) && plot.PlotHovered) {
char buffer[128] = {}; char buffer[128] = {};
ImBufferWriter writer(buffer, sizeof(buffer)); ImBufferWriter writer(buffer, sizeof(buffer));
// x // x
if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_LogScale)) { if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_Time)) {
writer.Write("%.3E", gp.MousePos[0].x);
}
else if (ImHasFlag(plot.XAxis.Flags, ImPlotAxisFlags_Time)) {
ImPlotTimeUnit unit = GetUnitForRange(plot.XAxis.Range.Size() / (plot.PlotRect.GetWidth() / 100)); ImPlotTimeUnit unit = GetUnitForRange(plot.XAxis.Range.Size() / (plot.PlotRect.GetWidth() / 100));
const int written = FormatDateTime(ImPlotTime::FromDouble(gp.MousePos[0].x), &writer.Buffer[writer.Pos], writer.Size - writer.Pos - 1, GetDateTimeFmt(TimeFormatMouseCursor, unit)); const int written = FormatDateTime(ImPlotTime::FromDouble(gp.MousePos[0].x), &writer.Buffer[writer.Pos], writer.Size - writer.Pos - 1, GetDateTimeFmt(TimeFormatMouseCursor, unit));
if (written > 0) if (written > 0)
writer.Pos += ImMin(written, writer.Size - writer.Pos - 1); 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(); writer.Write(GetFormatX(), RoundAxisValue(plot.XAxis, gp.XTicks, gp.MousePos[0].x));
writer.Write("%.*f", Precision(range_x), gp.MousePos[0].x);
} }
// y1 // y1
if (ImHasFlag(plot.YAxis[0].Flags, ImPlotAxisFlags_LogScale)) { writer.Write(", ");
writer.Write(",%.3E", gp.MousePos[0].y); writer.Write(GetFormatY(0), RoundAxisValue(plot.YAxis[0], gp.YTicks[0], gp.MousePos[0].y));
}
else {
double range_y = gp.YTicks[0].Size > 1 ? (gp.YTicks[0].Ticks[1].PlotPos - gp.YTicks[0].Ticks[0].PlotPos) : plot.YAxis[0].Range.Size();
writer.Write(",%.*f", Precision(range_y), gp.MousePos[0].y);
}
// y2 // y2
if (ImHasFlag(plot.Flags, ImPlotFlags_YAxis2)) { if (ImHasFlag(plot.Flags, ImPlotFlags_YAxis2)) {
if (ImHasFlag(plot.YAxis[1].Flags, ImPlotAxisFlags_LogScale)) { writer.Write(", (");
writer.Write(",(%.3E)", gp.MousePos[1].y); writer.Write(GetFormatY(1), RoundAxisValue(plot.YAxis[1], gp.YTicks[1], gp.MousePos[1].y));
} writer.Write(")");
else {
double range_y = gp.YTicks[1].Size > 1 ? (gp.YTicks[1].Ticks[1].PlotPos - gp.YTicks[1].Ticks[0].PlotPos) : plot.YAxis[1].Range.Size();
writer.Write(",(%.*f)", Precision(range_y), gp.MousePos[1].y);
}
} }
// y3 // y3
if (ImHasFlag(plot.Flags, ImPlotFlags_YAxis3)) { if (ImHasFlag(plot.Flags, ImPlotFlags_YAxis3)) {
if (ImHasFlag(plot.YAxis[2].Flags, ImPlotAxisFlags_LogScale)) { writer.Write(", (");
writer.Write(",(%.3E)", gp.MousePos[2].y); writer.Write(GetFormatY(2), RoundAxisValue(plot.YAxis[2], gp.YTicks[2], gp.MousePos[2].y));
} writer.Write(")");
else {
double range_y = gp.YTicks[2].Size > 1 ? (gp.YTicks[2].Ticks[1].PlotPos - gp.YTicks[2].Ticks[0].PlotPos) : plot.YAxis[2].Range.Size();
writer.Write(",(%.*f)", Precision(range_y), gp.MousePos[2].y);
}
} }
const ImVec2 size = ImGui::CalcTextSize(buffer); const ImVec2 size = ImGui::CalcTextSize(buffer);
const ImVec2 pos = GetLocationPos(plot.PlotRect, size, plot.MousePosLocation, gp.Style.MousePosPadding); const ImVec2 pos = GetLocationPos(plot.PlotRect, size, plot.MousePosLocation, gp.Style.MousePosPadding);
@ -2618,7 +2611,7 @@ void SetNextPlotTicksX(const double* values, int n_ticks, const char* const labe
ImPlotContext& gp = *GImPlot; ImPlotContext& gp = *GImPlot;
IM_ASSERT_USER_ERROR(gp.CurrentPlot == NULL, "SetNextPlotTicksX() needs to be called before BeginPlot()!"); IM_ASSERT_USER_ERROR(gp.CurrentPlot == NULL, "SetNextPlotTicksX() needs to be called before BeginPlot()!");
gp.NextPlotData.ShowDefaultTicksX = show_default; gp.NextPlotData.ShowDefaultTicksX = show_default;
AddTicksCustom(values, labels, n_ticks, gp.XTicks); AddTicksCustom(values, labels, n_ticks, gp.XTicks, GetFormatX());
} }
void SetNextPlotTicksX(double x_min, double x_max, int n_ticks, const char* const labels[], bool show_default) { void SetNextPlotTicksX(double x_min, double x_max, int n_ticks, const char* const labels[], bool show_default) {
@ -2633,7 +2626,7 @@ void SetNextPlotTicksY(const double* values, int n_ticks, const char* const labe
IM_ASSERT_USER_ERROR(gp.CurrentPlot == NULL, "SetNextPlotTicksY() needs to be called before BeginPlot()!"); IM_ASSERT_USER_ERROR(gp.CurrentPlot == NULL, "SetNextPlotTicksY() needs to be called before BeginPlot()!");
IM_ASSERT_USER_ERROR(y_axis >= 0 && y_axis < IMPLOT_Y_AXES, "y_axis needs to be between 0 and IMPLOT_Y_AXES"); IM_ASSERT_USER_ERROR(y_axis >= 0 && y_axis < IMPLOT_Y_AXES, "y_axis needs to be between 0 and IMPLOT_Y_AXES");
gp.NextPlotData.ShowDefaultTicksY[y_axis] = show_default; gp.NextPlotData.ShowDefaultTicksY[y_axis] = show_default;
AddTicksCustom(values, labels, n_ticks, gp.YTicks[y_axis]); AddTicksCustom(values, labels, n_ticks, gp.YTicks[y_axis], GetFormatY(y_axis));
} }
void SetNextPlotTicksY(double y_min, double y_max, int n_ticks, const char* const labels[], bool show_default, ImPlotYAxis y_axis) { void SetNextPlotTicksY(double y_min, double y_max, int n_ticks, const char* const labels[], bool show_default, ImPlotYAxis y_axis) {
@ -2643,6 +2636,21 @@ void SetNextPlotTicksY(double y_min, double y_max, int n_ticks, const char* cons
SetNextPlotTicksY(&buffer[0], n_ticks, labels, show_default,y_axis); SetNextPlotTicksY(&buffer[0], n_ticks, labels, show_default,y_axis);
} }
void SetNextPlotFormatX(const char* fmt){
ImPlotContext& gp = *GImPlot;
IM_ASSERT_USER_ERROR(gp.CurrentPlot == NULL, "SetNextPlotFormatX() needs to be called before BeginPlot()!");
gp.NextPlotData.HasFmtX = true;
ImStrncpy(gp.NextPlotData.FmtX, fmt, 16);
}
void SetNextPlotFormatY(const char* fmt, ImPlotYAxis y_axis) {
ImPlotContext& gp = *GImPlot;
IM_ASSERT_USER_ERROR(gp.CurrentPlot == NULL, "SetNextPlotFormatY() needs to be called before BeginPlot()!");
IM_ASSERT_USER_ERROR(y_axis >= 0 && y_axis < IMPLOT_Y_AXES, "y_axis needs to be between 0 and IMPLOT_Y_AXES");
gp.NextPlotData.HasFmtY[y_axis] = true;
ImStrncpy(gp.NextPlotData.FmtY[y_axis], fmt, 16);
}
void SetPlotYAxis(ImPlotYAxis y_axis) { void SetPlotYAxis(ImPlotYAxis y_axis) {
ImPlotContext& gp = *GImPlot; ImPlotContext& gp = *GImPlot;
IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "SetPlotYAxis() needs to be called between BeginPlot() and EndPlot()!"); IM_ASSERT_USER_ERROR(gp.CurrentPlot != NULL, "SetPlotYAxis() needs to be called between BeginPlot() and EndPlot()!");
@ -3453,7 +3461,7 @@ void RenderColorBar(const ImU32* colors, int size, ImDrawList& DrawList, const I
} }
} }
void ColormapScale(const char* label, double scale_min, double scale_max, const ImVec2& size, ImPlotColormap cmap) { void ColormapScale(const char* label, double scale_min, double scale_max, const ImVec2& size, ImPlotColormap cmap, const char* fmt) {
ImGuiContext &G = *GImGui; ImGuiContext &G = *GImGui;
ImGuiWindow * Window = G.CurrentWindow; ImGuiWindow * Window = G.CurrentWindow;
if (Window->SkipItems) if (Window->SkipItems)
@ -3473,7 +3481,7 @@ void ColormapScale(const char* label, double scale_min, double scale_max, const
ImPlotRange range(scale_min,scale_max); ImPlotRange range(scale_min,scale_max);
gp.CTicks.Reset(); gp.CTicks.Reset();
AddTicksDefault(range, ImMax(2, (int)IM_ROUND(0.0025 * frame_size.y)), IMPLOT_SUB_DIV, gp.CTicks); AddTicksDefault(range, frame_size.y, ImPlotOrientation_Vertical, gp.CTicks, fmt);
const float txt_off = gp.Style.LabelPadding.x; const float txt_off = gp.Style.LabelPadding.x;
const float pad_right = txt_off + gp.CTicks.MaxWidth + (label_size.x > 0 ? txt_off + label_size.y : 0); const float pad_right = txt_off + gp.CTicks.MaxWidth + (label_size.x > 0 ? txt_off + label_size.y : 0);

View File

@ -513,19 +513,23 @@ IMPLOT_API void SetNextPlotLimits(double xmin, double xmax, double ymin, double
// Set the X axis range limits of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the X axis limits will be locked. // Set the X axis range limits of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the X axis limits will be locked.
IMPLOT_API void SetNextPlotLimitsX(double xmin, double xmax, ImGuiCond cond = ImGuiCond_Once); IMPLOT_API void SetNextPlotLimitsX(double xmin, double xmax, ImGuiCond cond = ImGuiCond_Once);
// Set the Y axis range limits of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the Y axis limits will be locked. // Set the Y axis range limits of the next plot. Call right before BeginPlot(). If ImGuiCond_Always is used, the Y axis limits will be locked.
IMPLOT_API void SetNextPlotLimitsY(double ymin, double ymax, ImGuiCond cond = ImGuiCond_Once, ImPlotYAxis y_axis = 0); IMPLOT_API void SetNextPlotLimitsY(double ymin, double ymax, ImGuiCond cond = ImGuiCond_Once, ImPlotYAxis y_axis = ImPlotYAxis_1);
// Links the next plot limits to external values. Set to NULL for no linkage. The pointer data must remain valid until the matching call to EndPlot. // Links the next plot limits to external values. Set to NULL for no linkage. The pointer data must remain valid until the matching call to EndPlot.
IMPLOT_API void LinkNextPlotLimits(double* xmin, double* xmax, double* ymin, double* ymax, double* ymin2 = NULL, double* ymax2 = NULL, double* ymin3 = NULL, double* ymax3 = NULL); IMPLOT_API void LinkNextPlotLimits(double* xmin, double* xmax, double* ymin, double* ymax, double* ymin2 = NULL, double* ymax2 = NULL, double* ymin3 = NULL, double* ymax3 = NULL);
// Fits the next plot axes to all plotted data if they are unlocked (equivalent to double-clicks). // Fits the next plot axes to all plotted data if they are unlocked (equivalent to double-clicks).
IMPLOT_API void FitNextPlotAxes(bool x = true, bool y = true, bool y2 = true, bool y3 = true); IMPLOT_API void FitNextPlotAxes(bool x = true, bool y = true, bool y2 = true, bool y3 = true);
// Set the X axis ticks and optionally the labels for the next plot. To keep the default ticks, set #show_default=true. // Set the X axis ticks and optionally the labels for the next plot. To keep the default ticks, set #keep_default=true.
IMPLOT_API void SetNextPlotTicksX(const double* values, int n_ticks, const char* const labels[] = NULL, bool show_default = false); IMPLOT_API void SetNextPlotTicksX(const double* values, int n_ticks, const char* const labels[] = NULL, bool keep_default = false);
IMPLOT_API void SetNextPlotTicksX(double x_min, double x_max, int n_ticks, const char* const labels[] = NULL, bool show_default = false); IMPLOT_API void SetNextPlotTicksX(double x_min, double x_max, int n_ticks, const char* const labels[] = NULL, bool keep_default = false);
// Set the Y axis ticks and optionally the labels for the next plot. To keep the default ticks, set #keep_default=true.
IMPLOT_API void SetNextPlotTicksY(const double* values, int n_ticks, const char* const labels[] = NULL, bool keep_default = false, ImPlotYAxis y_axis = ImPlotYAxis_1);
IMPLOT_API void SetNextPlotTicksY(double y_min, double y_max, int n_ticks, const char* const labels[] = NULL, bool keep_default = false, ImPlotYAxis y_axis = ImPlotYAxis_1);
// Set the Y axis ticks and optionally the labels for the next plot. To keep the default ticks, set #show_default=true. // Set the format for numeric X axis labels (default="%g"). Formated values will be doubles (i.e. don't supply %d, %i, etc.). Not applicable if ImPlotAxisFlags_Time enabled.
IMPLOT_API void SetNextPlotTicksY(const double* values, int n_ticks, const char* const labels[] = NULL, bool show_default = false, ImPlotYAxis y_axis = 0); IMPLOT_API void SetNextPlotFormatX(const char* fmt);
IMPLOT_API void SetNextPlotTicksY(double y_min, double y_max, int n_ticks, const char* const labels[] = NULL, bool show_default = false, ImPlotYAxis y_axis = 0); // Set the format for numeric Y axis labels (default="%g"). Formated values will be doubles (i.e. don't supply %d, %i, etc.).
IMPLOT_API void SetNextPlotFormatY(const char* fmt, ImPlotYAxis y_axis=ImPlotYAxis_1);
// The following functions MUST be called BETWEEN Begin/EndPlot! // The following functions MUST be called BETWEEN Begin/EndPlot!
@ -778,7 +782,7 @@ IMPLOT_API ImVec4 GetColormapColor(int idx, ImPlotColormap cmap = IMPLOT_AUTO);
IMPLOT_API ImVec4 SampleColormap(float t, ImPlotColormap cmap = IMPLOT_AUTO); IMPLOT_API ImVec4 SampleColormap(float t, ImPlotColormap cmap = IMPLOT_AUTO);
// Shows a vertical color scale with linear spaced ticks using the specified color map. Use double hashes to hide label (e.g. "##NoLabel"). // Shows a vertical color scale with linear spaced ticks using the specified color map. Use double hashes to hide label (e.g. "##NoLabel").
IMPLOT_API void ColormapScale(const char* label, double scale_min, double scale_max, const ImVec2& size = ImVec2(0,0), ImPlotColormap cmap = IMPLOT_AUTO); IMPLOT_API void ColormapScale(const char* label, double scale_min, double scale_max, const ImVec2& size = ImVec2(0,0), ImPlotColormap cmap = IMPLOT_AUTO, const char* fmt = "%g");
// Shows a horizontal slider with a colormap gradient background. Optionally returns the color sampled at t in [0 1]. // Shows a horizontal slider with a colormap gradient background. Optionally returns the color sampled at t in [0 1].
IMPLOT_API bool ColormapSlider(const char* label, float* t, ImVec4* out = NULL, const char* format = "", ImPlotColormap cmap = IMPLOT_AUTO); IMPLOT_API bool ColormapSlider(const char* label, float* t, ImVec4* out = NULL, const char* format = "", ImPlotColormap cmap = IMPLOT_AUTO);
// Shows a button with a colormap gradient brackground. // Shows a button with a colormap gradient brackground.

View File

@ -1420,8 +1420,11 @@ void ShowDemoWindow(bool* p_open) {
} }
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
if (ImGui::CollapsingHeader("Custom Ticks##")) { if (ImGui::CollapsingHeader("Custom Ticks##")) {
static bool custom_ticks = true; static bool custom_fmt = true;
static bool custom_ticks = false;
static bool custom_labels = true; static bool custom_labels = true;
ImGui::Checkbox("Show Custom Format", &custom_fmt);
ImGui::SameLine();
ImGui::Checkbox("Show Custom Ticks", &custom_ticks); ImGui::Checkbox("Show Custom Ticks", &custom_ticks);
if (custom_ticks) { if (custom_ticks) {
ImGui::SameLine(); ImGui::SameLine();
@ -1433,11 +1436,17 @@ void ShowDemoWindow(bool* p_open) {
static const char* ylabels[] = {"One","Three","Seven","Nine"}; static const char* ylabels[] = {"One","Three","Seven","Nine"};
static double yticks_aux[] = {0.2,0.4,0.6}; static double yticks_aux[] = {0.2,0.4,0.6};
static const char* ylabels_aux[] = {"A","B","C","D","E","F"}; static const char* ylabels_aux[] = {"A","B","C","D","E","F"};
if (custom_fmt) {
ImPlot::SetNextPlotFormatX("%g ms");
ImPlot::SetNextPlotFormatY("%g Hz", ImPlotYAxis_1);
ImPlot::SetNextPlotFormatY("%g dB", ImPlotYAxis_2);
ImPlot::SetNextPlotFormatY("%g km", ImPlotYAxis_3);
}
if (custom_ticks) { if (custom_ticks) {
ImPlot::SetNextPlotTicksX(&pi,1,custom_labels ? pi_str : NULL, true); ImPlot::SetNextPlotTicksX(&pi,1,custom_labels ? pi_str : NULL, true);
ImPlot::SetNextPlotTicksY(yticks, 4, custom_labels ? ylabels : NULL); ImPlot::SetNextPlotTicksY(yticks, 4, custom_labels ? ylabels : NULL, ImPlotYAxis_1);
ImPlot::SetNextPlotTicksY(yticks_aux, 3, custom_labels ? ylabels_aux : NULL, false, 1); ImPlot::SetNextPlotTicksY(yticks_aux, 3, custom_labels ? ylabels_aux : NULL, false, ImPlotYAxis_2);
ImPlot::SetNextPlotTicksY(0, 1, 6, custom_labels ? ylabels_aux : NULL, false, 2); ImPlot::SetNextPlotTicksY(0, 1, 6, custom_labels ? ylabels_aux : NULL, false, ImPlotYAxis_3);
} }
ImPlot::SetNextPlotLimits(2.5,5,0,10); ImPlot::SetNextPlotLimits(2.5,5,0,10);
if (ImPlot::BeginPlot("Custom Ticks", NULL, NULL, ImVec2(-1,0), ImPlotFlags_YAxis2 | ImPlotFlags_YAxis3)) { if (ImPlot::BeginPlot("Custom Ticks", NULL, NULL, ImVec2(-1,0), ImPlotFlags_YAxis2 | ImPlotFlags_YAxis3)) {
@ -1545,8 +1554,9 @@ void ShowDemoWindow(bool* p_open) {
ImGui::SameLine(); ImGui::ColorEdit4("##Bull", &bullCol.x, ImGuiColorEditFlags_NoInputs); ImGui::SameLine(); ImGui::ColorEdit4("##Bull", &bullCol.x, ImGuiColorEditFlags_NoInputs);
ImGui::SameLine(); ImGui::ColorEdit4("##Bear", &bearCol.x, ImGuiColorEditFlags_NoInputs); ImGui::SameLine(); ImGui::ColorEdit4("##Bear", &bearCol.x, ImGuiColorEditFlags_NoInputs);
ImPlot::GetStyle().UseLocalTime = false; ImPlot::GetStyle().UseLocalTime = false;
ImPlot::SetNextPlotFormatY("$%.0f");
ImPlot::SetNextPlotLimits(1546300800, 1571961600, 1250, 1600); ImPlot::SetNextPlotLimits(1546300800, 1571961600, 1250, 1600);
if (ImPlot::BeginPlot("Candlestick Chart","Day","USD",ImVec2(-1,0),0,ImPlotAxisFlags_Time)) { if (ImPlot::BeginPlot("Candlestick Chart",NULL,NULL,ImVec2(-1,0),0,ImPlotAxisFlags_Time)) {
MyImPlot::PlotCandlestick("GOOGL",dates, opens, closes, lows, highs, 218, tooltip, 0.25f, bullCol, bearCol); MyImPlot::PlotCandlestick("GOOGL",dates, opens, closes, lows, highs, 218, tooltip, 0.25f, bullCol, bearCol);
ImPlot::EndPlot(); ImPlot::EndPlot();
} }

View File

@ -51,14 +51,14 @@
// The maximum number of supported y-axes (DO NOT CHANGE THIS) // The maximum number of supported y-axes (DO NOT CHANGE THIS)
#define IMPLOT_Y_AXES 3 #define IMPLOT_Y_AXES 3
// The number of times to subdivided grid divisions (best if a multiple of 1, 2, and 5)
#define IMPLOT_SUB_DIV 10
// Zoom rate for scroll (e.g. 0.1f = 10% plot range every scroll click) // Zoom rate for scroll (e.g. 0.1f = 10% plot range every scroll click)
#define IMPLOT_ZOOM_RATE 0.1f #define IMPLOT_ZOOM_RATE 0.1f
// Mimimum allowable timestamp value 01/01/1970 @ 12:00am (UTC) (DO NOT DECREASE THIS) // Mimimum allowable timestamp value 01/01/1970 @ 12:00am (UTC) (DO NOT DECREASE THIS)
#define IMPLOT_MIN_TIME 0 #define IMPLOT_MIN_TIME 0
// Maximum allowable timestamp value 01/01/3000 @ 12:00am (UTC) (DO NOT INCREASE THIS) // Maximum allowable timestamp value 01/01/3000 @ 12:00am (UTC) (DO NOT INCREASE THIS)
#define IMPLOT_MAX_TIME 32503680000 #define IMPLOT_MAX_TIME 32503680000
// Default label format for axis labels
#define IMPLOT_LABEL_FMT "%g"
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] Macros // [SECTION] Macros
@ -528,6 +528,7 @@ struct ImPlotTick
struct ImPlotTickCollection { struct ImPlotTickCollection {
ImVector<ImPlotTick> Ticks; ImVector<ImPlotTick> Ticks;
ImGuiTextBuffer TextBuffer; ImGuiTextBuffer TextBuffer;
float TotalWidthMax;
float TotalWidth; float TotalWidth;
float TotalHeight; float TotalHeight;
float MaxWidth; float MaxWidth;
@ -536,22 +537,28 @@ struct ImPlotTickCollection {
ImPlotTickCollection() { Reset(); } ImPlotTickCollection() { Reset(); }
void Append(const ImPlotTick& tick) { const ImPlotTick& Append(const ImPlotTick& tick) {
if (tick.ShowLabel) { if (tick.ShowLabel) {
TotalWidth += tick.ShowLabel ? tick.LabelSize.x : 0; TotalWidth += tick.ShowLabel ? tick.LabelSize.x : 0;
TotalHeight += tick.ShowLabel ? tick.LabelSize.y : 0; TotalHeight += tick.ShowLabel ? tick.LabelSize.y : 0;
MaxWidth = tick.LabelSize.x > MaxWidth ? tick.LabelSize.x : MaxWidth; MaxWidth = tick.LabelSize.x > MaxWidth ? tick.LabelSize.x : MaxWidth;
MaxHeight = tick.LabelSize.y > MaxHeight ? tick.LabelSize.y : MaxHeight; MaxHeight = tick.LabelSize.y > MaxHeight ? tick.LabelSize.y : MaxHeight;
} }
Ticks.push_back(tick); Ticks.push_back(tick);
Size++; Size++;
return Ticks.back();
} }
void Append(double value, bool major, bool show_label, void (*labeler)(ImPlotTick& tick, ImGuiTextBuffer& buf)) { const ImPlotTick& Append(double value, bool major, bool show_label, const char* fmt) {
ImPlotTick tick(value, major, show_label); ImPlotTick tick(value, major, show_label);
if (labeler) if (show_label && fmt != NULL) {
labeler(tick, TextBuffer); char temp[32];
Append(tick); tick.TextOffset = TextBuffer.size();
snprintf(temp, 32, fmt, tick.PlotPos);
TextBuffer.append(temp, temp + strlen(temp) + 1);
tick.LabelSize = ImGui::CalcTextSize(TextBuffer.Buf.Data + tick.TextOffset);
}
return Append(tick);
} }
const char* GetText(int idx) const { const char* GetText(int idx) const {
@ -788,6 +795,10 @@ struct ImPlotNextPlotData
bool HasYRange[IMPLOT_Y_AXES]; bool HasYRange[IMPLOT_Y_AXES];
bool ShowDefaultTicksX; bool ShowDefaultTicksX;
bool ShowDefaultTicksY[IMPLOT_Y_AXES]; bool ShowDefaultTicksY[IMPLOT_Y_AXES];
char FmtX[16];
char FmtY[IMPLOT_Y_AXES][16];
bool HasFmtX;
bool HasFmtY[IMPLOT_Y_AXES];
bool FitX; bool FitX;
bool FitY[IMPLOT_Y_AXES]; bool FitY[IMPLOT_Y_AXES];
double* LinkedXmin; double* LinkedXmin;
@ -800,11 +811,13 @@ struct ImPlotNextPlotData
void Reset() { void Reset() {
HasXRange = false; HasXRange = false;
ShowDefaultTicksX = true; ShowDefaultTicksX = true;
HasFmtX = false;
FitX = false; FitX = false;
LinkedXmin = LinkedXmax = NULL; LinkedXmin = LinkedXmax = NULL;
for (int i = 0; i < IMPLOT_Y_AXES; ++i) { for (int i = 0; i < IMPLOT_Y_AXES; ++i) {
HasYRange[i] = false; HasYRange[i] = false;
ShowDefaultTicksY[i] = true; ShowDefaultTicksY[i] = true;
HasFmtY[i] = false;
FitY[i] = false; FitY[i] = false;
LinkedYmin[i] = LinkedYmax[i] = NULL; LinkedYmin[i] = LinkedYmax[i] = NULL;
} }
@ -993,6 +1006,10 @@ IMPLOT_API void PullLinkedAxis(ImPlotAxis& axis);
// Shows an axis's context menu. // Shows an axis's context menu.
IMPLOT_API void ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool time_allowed = false); IMPLOT_API void ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool time_allowed = false);
// Get format spec for axis
static inline const char* GetFormatX() { return GImPlot->NextPlotData.HasFmtX ? GImPlot->NextPlotData.FmtX : IMPLOT_LABEL_FMT; }
static inline const char* GetFormatY(ImPlotYAxis y) { return GImPlot->NextPlotData.HasFmtY[y] ? GImPlot->NextPlotData.FmtY[y] : IMPLOT_LABEL_FMT; }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] Legend Utils // [SECTION] Legend Utils
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -1010,21 +1027,17 @@ IMPLOT_API void ShowAltLegend(const char* title_id, ImPlotOrientation orientatio
// [SECTION] Tick Utils // [SECTION] Tick Utils
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Label a tick with default formatting.
IMPLOT_API void LabelTickDefault(ImPlotTick& tick, ImGuiTextBuffer& buffer);
// Label a tick with scientific formating.
IMPLOT_API void LabelTickScientific(ImPlotTick& tick, ImGuiTextBuffer& buffer);
// Label a tick with time formatting. // Label a tick with time formatting.
IMPLOT_API void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, const ImPlotTime& t, ImPlotDateTimeFmt fmt); IMPLOT_API void LabelTickTime(ImPlotTick& tick, ImGuiTextBuffer& buffer, const ImPlotTime& t, ImPlotDateTimeFmt fmt);
// Populates a list of ImPlotTicks with normal spaced and formatted ticks // Populates a list of ImPlotTicks with normal spaced and formatted ticks
IMPLOT_API void AddTicksDefault(const ImPlotRange& range, int nMajor, int nMinor, ImPlotTickCollection& ticks); IMPLOT_API void AddTicksDefault(const ImPlotRange& range, float pix, ImPlotOrientation orn, ImPlotTickCollection& ticks, const char* fmt);
// Populates a list of ImPlotTicks with logarithmic space and formatted ticks // Populates a list of ImPlotTicks with logarithmic space and formatted ticks
IMPLOT_API void AddTicksLogarithmic(const ImPlotRange& range, int nMajor, ImPlotTickCollection& ticks); IMPLOT_API void AddTicksLogarithmic(const ImPlotRange& range, float pix, ImPlotOrientation orn, ImPlotTickCollection& ticks, const char* fmt);
// Populates a list of ImPlotTicks with time formatted ticks. // Populates a list of ImPlotTicks with time formatted ticks.
IMPLOT_API void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollection& ticks); IMPLOT_API void AddTicksTime(const ImPlotRange& range, float plot_width, ImPlotTickCollection& ticks);
// Populates a list of ImPlotTicks with custom spaced and labeled ticks // Populates a list of ImPlotTicks with custom spaced and labeled ticks
IMPLOT_API void AddTicksCustom(const double* values, const char* const labels[], int n, ImPlotTickCollection& ticks); IMPLOT_API void AddTicksCustom(const double* values, const char* const labels[], int n, ImPlotTickCollection& ticks, const char* fmt);
// Create a a string label for a an axis value // Create a a string label for a an axis value
IMPLOT_API int LabelAxisValue(const ImPlotAxis& axis, const ImPlotTickCollection& ticks, double value, char* buff, int size); IMPLOT_API int LabelAxisValue(const ImPlotAxis& axis, const ImPlotTickCollection& ticks, double value, char* buff, int size);
@ -1057,7 +1070,7 @@ static inline ImVec2 CalcTextSizeVertical(const char *text) {
// Returns white or black text given background color // Returns white or black text given background color
static inline ImU32 CalcTextColor(const ImVec4& bg) { return (bg.x * 0.299 + bg.y * 0.587 + bg.z * 0.114) > 0.5 ? IM_COL32_BLACK : IM_COL32_WHITE; } static inline ImU32 CalcTextColor(const ImVec4& bg) { return (bg.x * 0.299 + bg.y * 0.587 + bg.z * 0.114) > 0.5 ? IM_COL32_BLACK : IM_COL32_WHITE; }
static inline ImU32 CalcTextColor(ImU32 bg) { return CalcTextColor(ImGui::ColorConvertU32ToFloat4(bg)); } static inline ImU32 CalcTextColor(ImU32 bg) { return CalcTextColor(ImGui::ColorConvertU32ToFloat4(bg)); }
// Lights or darkens a color for hover // Lightens or darkens a color for hover
static inline ImU32 CalcHoverColor(ImU32 col) { return ImMixU32(col, CalcTextColor(col), 32); } static inline ImU32 CalcHoverColor(ImU32 col) { return ImMixU32(col, CalcTextColor(col), 32); }
// Clamps a label position so that it fits a rect defined by Min/Max // Clamps a label position so that it fits a rect defined by Min/Max
@ -1091,6 +1104,8 @@ static inline int OrderOfMagnitude(double val) { return val == 0 ? 0 : (int)(flo
static inline int OrderToPrecision(int order) { return order > 0 ? 0 : 1 - order; } static inline int OrderToPrecision(int order) { return order > 0 ? 0 : 1 - order; }
// Returns a floating point precision to use given a value // Returns a floating point precision to use given a value
static inline int Precision(double val) { return OrderToPrecision(OrderOfMagnitude(val)); } static inline int Precision(double val) { return OrderToPrecision(OrderOfMagnitude(val)); }
// Round a value to a given precision
static inline double RoundTo(double val, int prec) { double p = pow(10,(double)prec); return floor(val*p+0.5)/p; }
// Returns the intersection point of two lines A and B (assumes they are not parallel!) // Returns the intersection point of two lines A and B (assumes they are not parallel!)
static inline ImVec2 Intersection(const ImVec2& a1, const ImVec2& a2, const ImVec2& b1, const ImVec2& b2) { static inline ImVec2 Intersection(const ImVec2& a1, const ImVec2& a2, const ImVec2& b1, const ImVec2& b2) {
@ -1118,7 +1133,7 @@ static inline T OffsetAndStride(const T* data, int idx, int count, int offset, i
// Calculate histogram bin counts and widths // Calculate histogram bin counts and widths
template <typename T> template <typename T>
void CalculateBins(const T* values, int count, ImPlotBin meth, const ImPlotRange& range, int& bins_out, double& width_out) { static inline void CalculateBins(const T* values, int count, ImPlotBin meth, const ImPlotRange& range, int& bins_out, double& width_out) {
switch (meth) { switch (meth) {
case ImPlotBin_Sqrt: case ImPlotBin_Sqrt:
bins_out = (int)ceil(sqrt(count)); bins_out = (int)ceil(sqrt(count));