diff --git a/implot.cpp b/implot.cpp index e33613b7..fd9966e1 100644 --- a/implot.cpp +++ b/implot.cpp @@ -1388,6 +1388,7 @@ void ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool /*time_a bool grid = axis.HasGridLines(); bool ticks = axis.HasTickMarks(); bool labels = axis.HasTickLabels(); + bool labelsInside = axis.HasTickLabelsInside(); double drag_speed = (axis.Range.Size() <= DBL_EPSILON) ? DBL_EPSILON * 1.0e+13 : 0.01 * axis.Range.Size(); // recover from almost equal axis limits. if (axis.Scale == ImPlotScale_Time) { @@ -1492,7 +1493,8 @@ void ShowAxisContextMenu(ImPlotAxis& axis, ImPlotAxis* equal_axis, bool /*time_a ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoTickMarks); if (ImGui::Checkbox("Tick Labels", &labels)) ImFlipFlag(axis.Flags, ImPlotAxisFlags_NoTickLabels); - + if (ImGui::Checkbox("Tick Labels Inside", &labelsInside)) + ImFlipFlag(axis.Flags, ImPlotAxisFlags_TickLabelsInside); } bool ShowLegendContextMenu(ImPlotLegend& legend, bool visible) { @@ -1662,7 +1664,42 @@ void UpdateAxisColors(ImPlotAxis& axis) { // axis.ColorHiLi = IM_COL32_BLACK_TRANS; } -void PadAndDatumAxesX(ImPlotPlot& plot, float& pad_T, float& pad_B, ImPlotAlignmentData* align) { +static void FindInnermostAxesX(ImPlotPlot& plot, int& innermost_T, int& innermost_B) { + innermost_T = innermost_B = -1; + for (int i = 0; i < IMPLOT_NUM_X_AXES; i++) { + ImPlotAxis& axis = plot.XAxis(i); + if (!axis.Enabled) + continue; + const bool opp = axis.IsOpposite(); + if (opp && innermost_T == -1) + innermost_T = i; + if (!opp && innermost_B == -1) + innermost_B = i; + } +} + +static void FindInnermostAxesY(ImPlotPlot& plot, int& innermost_L, int& innermost_R) { + innermost_L = innermost_R = -1; + for (int i = 0; i < IMPLOT_NUM_Y_AXES; i++) { + ImPlotAxis& axis = plot.YAxis(i); + if (!axis.Enabled) + continue; + const bool opp = axis.IsOpposite(); + if (opp && innermost_R == -1) + innermost_R = i; + if (!opp && innermost_L == -1) + innermost_L = i; + } +} + +static bool IsInnermostAxis(int i, bool opp, int innermost, int innermost_opp) { + if (opp) + return i == innermost_opp; + else + return i == innermost; +} + +void PadAndDatumAxesX(ImPlotPlot& plot, float& pad_T, float& pad_B, int innermost_T, int innermost_B, ImPlotAlignmentData* align) { ImPlotContext& gp = *GImPlot; @@ -1682,6 +1719,7 @@ void PadAndDatumAxesX(ImPlotPlot& plot, float& pad_T, float& pad_B, ImPlotAlignm const bool label = axis.HasLabel(); const bool ticks = axis.HasTickLabels(); const bool opp = axis.IsOpposite(); + const bool ins = axis.HasTickLabelsInside() && IsInnermostAxis(i, opp, innermost_B, innermost_T); const bool time = axis.Scale == ImPlotScale_Time; if (opp) { if (count_T++ > 0) @@ -1690,8 +1728,11 @@ void PadAndDatumAxesX(ImPlotPlot& plot, float& pad_T, float& pad_B, ImPlotAlignm ImVec2 label_size = ImGui::CalcTextSize(plot.GetAxisLabel(axis)); pad_T += label_size.y + P; } - if (ticks) + if (ticks && !ins) pad_T += ImMax(T, axis.Ticker.MaxSize.y) + P + (time ? T + P : 0); + // fix ins axis rect overlapping plot title if no label is present and there are no other axes above + if (ins && !label && count_T == 1 && plot.HasTitle()) + pad_T += K + P; axis.Datum1 = plot.CanvasRect.Min.y + pad_T; axis.Datum2 = last_T; last_T = axis.Datum1; @@ -1703,7 +1744,7 @@ void PadAndDatumAxesX(ImPlotPlot& plot, float& pad_T, float& pad_B, ImPlotAlignm ImVec2 label_size = ImGui::CalcTextSize(plot.GetAxisLabel(axis)); pad_B += label_size.y + P; } - if (ticks) + if (ticks && !ins) pad_B += ImMax(T, axis.Ticker.MaxSize.y) + P + (time ? T + P : 0); axis.Datum1 = plot.CanvasRect.Max.y - pad_B; axis.Datum2 = last_B; @@ -1731,7 +1772,7 @@ void PadAndDatumAxesX(ImPlotPlot& plot, float& pad_T, float& pad_B, ImPlotAlignm } } -void PadAndDatumAxesY(ImPlotPlot& plot, float& pad_L, float& pad_R, ImPlotAlignmentData* align) { +void PadAndDatumAxesY(ImPlotPlot& plot, float& pad_L, float& pad_R, int innermost_L, int innermost_R, ImPlotAlignmentData* align) { // [ pad_L ] [ pad_R ] // .................CanvasRect................ @@ -1766,12 +1807,13 @@ void PadAndDatumAxesY(ImPlotPlot& plot, float& pad_L, float& pad_R, ImPlotAlignm const bool label = axis.HasLabel(); const bool ticks = axis.HasTickLabels(); const bool opp = axis.IsOpposite(); + const bool ins = axis.HasTickLabelsInside() && IsInnermostAxis(i, opp, innermost_L, innermost_R); if (opp) { if (count_R++ > 0) pad_R += K + P; if (label) pad_R += T + P; - if (ticks) + if (ticks && !ins) pad_R += axis.Ticker.MaxSize.x + P; axis.Datum1 = plot.CanvasRect.Max.x - pad_R; axis.Datum2 = last_R; @@ -1782,7 +1824,7 @@ void PadAndDatumAxesY(ImPlotPlot& plot, float& pad_L, float& pad_R, ImPlotAlignm pad_L += K + P; if (label) pad_L += T + P; - if (ticks) + if (ticks && !ins) pad_L += axis.Ticker.MaxSize.x + P; axis.Datum1 = plot.CanvasRect.Min.x + pad_L; axis.Datum2 = last_L; @@ -2600,6 +2642,7 @@ void SetupFinish() { // plot bb float pad_top = 0, pad_bot = 0, pad_left = 0, pad_right = 0; + int innermost_top = -1, innermost_bot = -1, innermost_left = -1, innermost_right = -1; // (0) calc top padding form title ImVec2 title_size(0.0f, 0.0f); @@ -2611,7 +2654,8 @@ void SetupFinish() { } // (1) calc addition top padding and bot padding - PadAndDatumAxesX(plot,pad_top,pad_bot,gp.CurrentAlignmentH); + FindInnermostAxesX(plot, innermost_top, innermost_bot); + PadAndDatumAxesX(plot,pad_top,pad_bot,innermost_top,innermost_bot,gp.CurrentAlignmentH); const float plot_height = plot.CanvasRect.GetHeight() - pad_top - pad_bot; @@ -2624,7 +2668,8 @@ void SetupFinish() { } // (3) calc left/right pad - PadAndDatumAxesY(plot,pad_left,pad_right,gp.CurrentAlignmentV); + FindInnermostAxesY(plot, innermost_left, innermost_right); + PadAndDatumAxesY(plot,pad_left,pad_right,innermost_left,innermost_right,gp.CurrentAlignmentV); const float plot_width = plot.CanvasRect.GetWidth() - pad_left - pad_right; @@ -2641,7 +2686,7 @@ void SetupFinish() { const float title_pad = (title_size.x > 0) ? (title_size.y + gp.Style.LabelPadding.y) : 0.0f; pad_top = title_pad; pad_bot = 0; - PadAndDatumAxesX(plot,pad_top,pad_bot,gp.CurrentAlignmentH); + PadAndDatumAxesX(plot,pad_top,pad_bot,innermost_top,innermost_bot,gp.CurrentAlignmentH); // Update AxesRect to account for title padding (was done in step 0) if (title_size.x > 0) { plot.AxesRect.Min.y = plot.FrameRect.Min.y + gp.Style.PlotPadding.y + title_pad; @@ -2751,10 +2796,11 @@ void SetupFinish() { } const ImPlotTicker& tkr = ax.Ticker; const bool opp = ax.IsOpposite(); + const bool ins = ax.HasTickLabelsInside() && IsInnermostAxis(i, opp, innermost_bot, innermost_top); if (ax.HasLabel()) { const char* label = plot.GetAxisLabel(ax); const ImVec2 label_size = ImGui::CalcTextSize(label); - const float label_offset = (ax.HasTickLabels() ? tkr.MaxSize.y + gp.Style.LabelPadding.y : 0.0f) + const float label_offset = (ax.HasTickLabels() && !ins ? tkr.MaxSize.y + gp.Style.LabelPadding.y : 0.0f) + (tkr.Levels - 1) * (txt_height + gp.Style.LabelPadding.y) + gp.Style.LabelPadding.y; const ImVec2 label_pos(plot.PlotRect.GetCenter().x - label_size.x * 0.5f, @@ -2762,10 +2808,13 @@ void SetupFinish() { DrawList.AddText(label_pos, ax.ColorTxt, label); } if (ax.HasTickLabels()) { + const bool labels_opp = opp ^ ins; + const float label_pad = ins ? gp.Style.MajorTickLen.y : gp.Style.LabelPadding.y; + const float level_pad = txt_height + gp.Style.LabelPadding.y; for (int j = 0; j < tkr.TickCount(); ++j) { const ImPlotTick& tk = tkr.Ticks[j]; - const float datum = ax.Datum1 + (opp ? (-gp.Style.LabelPadding.y -txt_height -tk.Level * (txt_height + gp.Style.LabelPadding.y)) - : gp.Style.LabelPadding.y + tk.Level * (txt_height + gp.Style.LabelPadding.y)); + const float pad = label_pad + (labels_opp ? tk.LabelSize.y : 0) + (tk.Level * level_pad); + const float datum = ax.Datum1 + pad * (labels_opp ? -1.0f : 1.0f); if (tk.ShowLabel && tk.PixelPos >= plot.PlotRect.Min.x - 1 && tk.PixelPos <= plot.PlotRect.Max.x + 1) { ImVec2 start(tk.PixelPos - 0.5f * tk.LabelSize.x, datum); DrawList.AddText(start, ax.ColorTxt, tkr.GetText(j)); @@ -2790,19 +2839,23 @@ void SetupFinish() { } const ImPlotTicker& tkr = ax.Ticker; const bool opp = ax.IsOpposite(); + const bool ins = ax.HasTickLabelsInside() && IsInnermostAxis(i, opp, innermost_left, innermost_right); if (ax.HasLabel()) { const char* label = plot.GetAxisLabel(ax); const ImVec2 label_size = CalcTextSizeVertical(label); - const float label_offset = (ax.HasTickLabels() ? tkr.MaxSize.x + gp.Style.LabelPadding.x : 0.0f) + const float label_offset = (ax.HasTickLabels() && !ins ? tkr.MaxSize.x + gp.Style.LabelPadding.x : 0.0f) + gp.Style.LabelPadding.x; const ImVec2 label_pos(opp ? ax.Datum1 + label_offset : ax.Datum1 - label_offset - label_size.x, plot.PlotRect.GetCenter().y + label_size.y * 0.5f); AddTextVertical(&DrawList, label_pos, ax.ColorTxt, label); } if (ax.HasTickLabels()) { + const bool labels_opp = opp ^ ins; + const float label_pad = ins ? gp.Style.MajorTickLen.x : gp.Style.LabelPadding.x; for (int j = 0; j < tkr.TickCount(); ++j) { const ImPlotTick& tk = tkr.Ticks[j]; - const float datum = ax.Datum1 + (opp ? gp.Style.LabelPadding.x : (-gp.Style.LabelPadding.x - tk.LabelSize.x)); + const float pad = label_pad + (labels_opp ? 0 : tk.LabelSize.x); + const float datum = ax.Datum1 + pad * (labels_opp ? 1.0f : -1.0f); if (tk.ShowLabel && tk.PixelPos >= plot.PlotRect.Min.y - 1 && tk.PixelPos <= plot.PlotRect.Max.y + 1) { ImVec2 start(datum, tk.PixelPos - 0.5f * tk.LabelSize.y); DrawList.AddText(start, ax.ColorTxt, tkr.GetText(j)); diff --git a/implot.h b/implot.h index 2c995a80..0d36331a 100644 --- a/implot.h +++ b/implot.h @@ -168,26 +168,27 @@ enum ImPlotFlags_ { // Options for plot axes (see SetupAxis). enum ImPlotAxisFlags_ { - ImPlotAxisFlags_None = 0, // default - ImPlotAxisFlags_NoLabel = 1 << 0, // the axis label will not be displayed (axis labels are also hidden if the supplied string name is nullptr) - ImPlotAxisFlags_NoGridLines = 1 << 1, // no grid lines will be displayed - ImPlotAxisFlags_NoTickMarks = 1 << 2, // no tick marks will be displayed - ImPlotAxisFlags_NoTickLabels = 1 << 3, // no text labels will be displayed - ImPlotAxisFlags_NoInitialFit = 1 << 4, // axis will not be initially fit to data extents on the first rendered frame - ImPlotAxisFlags_NoMenus = 1 << 5, // the user will not be able to open context menus with right-click - ImPlotAxisFlags_NoSideSwitch = 1 << 6, // the user will not be able to switch the axis side by dragging it - ImPlotAxisFlags_NoHighlight = 1 << 7, // the axis will not have its background highlighted when hovered or held - ImPlotAxisFlags_Opposite = 1 << 8, // axis ticks and labels will be rendered on the conventionally opposite side (i.e, right or top) - ImPlotAxisFlags_Foreground = 1 << 9, // grid lines will be displayed in the foreground (i.e. on top of data) instead of the background - ImPlotAxisFlags_Invert = 1 << 10, // the axis will be inverted - ImPlotAxisFlags_AutoFit = 1 << 11, // axis will be auto-fitting to data extents - ImPlotAxisFlags_RangeFit = 1 << 12, // axis will only fit points if the point is in the visible range of the **orthogonal** axis - ImPlotAxisFlags_PanStretch = 1 << 13, // panning in a locked or constrained state will cause the axis to stretch if possible - ImPlotAxisFlags_LockMin = 1 << 14, // the axis minimum value will be locked when panning/zooming - ImPlotAxisFlags_LockMax = 1 << 15, // the axis maximum value will be locked when panning/zooming - ImPlotAxisFlags_Lock = ImPlotAxisFlags_LockMin | ImPlotAxisFlags_LockMax, - ImPlotAxisFlags_NoDecorations = ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks | ImPlotAxisFlags_NoTickLabels, - ImPlotAxisFlags_AuxDefault = ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_Opposite + ImPlotAxisFlags_None = 0, // default + ImPlotAxisFlags_NoLabel = 1 << 0, // the axis label will not be displayed (axis labels are also hidden if the supplied string name is nullptr) + ImPlotAxisFlags_NoGridLines = 1 << 1, // no grid lines will be displayed + ImPlotAxisFlags_NoTickMarks = 1 << 2, // no tick marks will be displayed + ImPlotAxisFlags_NoTickLabels = 1 << 3, // no text labels will be displayed + ImPlotAxisFlags_NoInitialFit = 1 << 4, // axis will not be initially fit to data extents on the first rendered frame + ImPlotAxisFlags_NoMenus = 1 << 5, // the user will not be able to open context menus with right-click + ImPlotAxisFlags_NoSideSwitch = 1 << 6, // the user will not be able to switch the axis side by dragging it + ImPlotAxisFlags_NoHighlight = 1 << 7, // the axis will not have its background highlighted when hovered or held + ImPlotAxisFlags_Opposite = 1 << 8, // axis ticks and labels will be rendered on the conventionally opposite side (i.e, right or top) + ImPlotAxisFlags_Foreground = 1 << 9, // grid lines will be displayed in the foreground (i.e. on top of data) instead of the background + ImPlotAxisFlags_Invert = 1 << 10, // the axis will be inverted + ImPlotAxisFlags_AutoFit = 1 << 11, // axis will be auto-fitting to data extents + ImPlotAxisFlags_RangeFit = 1 << 12, // axis will only fit points if the point is in the visible range of the **orthogonal** axis + ImPlotAxisFlags_PanStretch = 1 << 13, // panning in a locked or constrained state will cause the axis to stretch if possible + ImPlotAxisFlags_LockMin = 1 << 14, // the axis minimum value will be locked when panning/zooming + ImPlotAxisFlags_LockMax = 1 << 15, // the axis maximum value will be locked when panning/zooming + ImPlotAxisFlags_TickLabelsInside = 1 << 16, // axis tick labels will be drawn inside the plot area (applies only to the innermost axes) + ImPlotAxisFlags_Lock = ImPlotAxisFlags_LockMin | ImPlotAxisFlags_LockMax, + ImPlotAxisFlags_NoDecorations = ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoTickMarks | ImPlotAxisFlags_NoTickLabels, + ImPlotAxisFlags_AuxDefault = ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_Opposite }; // Options for subplots (see BeginSubplot) diff --git a/implot_internal.h b/implot_internal.h index da49cedb..b15b1d29 100644 --- a/implot_internal.h +++ b/implot_internal.h @@ -899,24 +899,25 @@ struct ImPlotAxis UpdateTransformCache(); } - inline bool HasLabel() const { return LabelOffset != -1 && !ImHasFlag(Flags, ImPlotAxisFlags_NoLabel); } - inline bool HasGridLines() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoGridLines); } - inline bool HasTickLabels() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoTickLabels); } - inline bool HasTickMarks() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoTickMarks); } - inline bool WillRender() const { return Enabled && (HasGridLines() || HasTickLabels() || HasTickMarks()); } - inline bool IsOpposite() const { return ImHasFlag(Flags, ImPlotAxisFlags_Opposite); } - inline bool IsInverted() const { return ImHasFlag(Flags, ImPlotAxisFlags_Invert); } - inline bool IsForeground() const { return ImHasFlag(Flags, ImPlotAxisFlags_Foreground); } - inline bool IsAutoFitting() const { return ImHasFlag(Flags, ImPlotAxisFlags_AutoFit); } - inline bool CanInitFit() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoInitialFit) && !HasRange && !LinkedMin && !LinkedMax; } - inline bool IsRangeLocked() const { return HasRange && RangeCond == ImPlotCond_Always; } - inline bool IsLockedMin() const { return !Enabled || IsRangeLocked() || ImHasFlag(Flags, ImPlotAxisFlags_LockMin); } - inline bool IsLockedMax() const { return !Enabled || IsRangeLocked() || ImHasFlag(Flags, ImPlotAxisFlags_LockMax); } - inline bool IsLocked() const { return IsLockedMin() && IsLockedMax(); } - inline bool IsInputLockedMin() const { return IsLockedMin() || IsAutoFitting(); } - inline bool IsInputLockedMax() const { return IsLockedMax() || IsAutoFitting(); } - inline bool IsInputLocked() const { return IsLocked() || IsAutoFitting(); } - inline bool HasMenus() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoMenus); } + inline bool HasLabel() const { return LabelOffset != -1 && !ImHasFlag(Flags, ImPlotAxisFlags_NoLabel); } + inline bool HasGridLines() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoGridLines); } + inline bool HasTickLabels() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoTickLabels); } + inline bool HasTickLabelsInside() const { return ImHasFlag(Flags, ImPlotAxisFlags_TickLabelsInside); } + inline bool HasTickMarks() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoTickMarks); } + inline bool WillRender() const { return Enabled && (HasGridLines() || HasTickLabels() || HasTickMarks()); } + inline bool IsOpposite() const { return ImHasFlag(Flags, ImPlotAxisFlags_Opposite); } + inline bool IsInverted() const { return ImHasFlag(Flags, ImPlotAxisFlags_Invert); } + inline bool IsForeground() const { return ImHasFlag(Flags, ImPlotAxisFlags_Foreground); } + inline bool IsAutoFitting() const { return ImHasFlag(Flags, ImPlotAxisFlags_AutoFit); } + inline bool CanInitFit() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoInitialFit) && !HasRange && !LinkedMin && !LinkedMax; } + inline bool IsRangeLocked() const { return HasRange && RangeCond == ImPlotCond_Always; } + inline bool IsLockedMin() const { return !Enabled || IsRangeLocked() || ImHasFlag(Flags, ImPlotAxisFlags_LockMin); } + inline bool IsLockedMax() const { return !Enabled || IsRangeLocked() || ImHasFlag(Flags, ImPlotAxisFlags_LockMax); } + inline bool IsLocked() const { return IsLockedMin() && IsLockedMax(); } + inline bool IsInputLockedMin() const { return IsLockedMin() || IsAutoFitting(); } + inline bool IsInputLockedMax() const { return IsLockedMax() || IsAutoFitting(); } + inline bool IsInputLocked() const { return IsLocked() || IsAutoFitting(); } + inline bool HasMenus() const { return !ImHasFlag(Flags, ImPlotAxisFlags_NoMenus); } inline bool IsPanLocked(bool increasing) { if (ImHasFlag(Flags, ImPlotAxisFlags_PanStretch)) {