epezent / implot

Immediate Mode Plotting
MIT License
4.55k stars 503 forks source link

axis title labels: limit to axis size and align right #480

Open RalphSteinhagen opened 1 year ago

RalphSteinhagen commented 1 year ago

First, thanks for your great library! :+1:

I may have found a bug/missing feature in ImPlot.

If the rendered axis title widths are larger than the plot axis widths they may overlap neighbouring widgets, e.g.:

image

My present workaround consists of estimating the axis title size and either suppressing (zero-sized string)

const auto axis        = is_horizontal ? ImAxis_X1 : ImAxis_Y1;
const auto estTextSize = is_horizontal ? ImGui::CalcTextSize(axisLabel.c_str()).x : ImGui::CalcTextSize(axisLabel.c_str()).y;
ImPlot::SetupAxis(axis, estTextSize > axis.width ? "" : axisLabel.c_str(), axisFlags);
axis.width = // presently derived from 'ImPlot::GetPlotSize()'

or to shorten it, e.g. "super long axis title [unit]" -> "super l...". The issue is that the plot pixel size is much larger than the actual axis and I'd like to avoid recomputing and/or adding fudge factors to compensate for this.

Some questions:

  1. How to obtain the axes' rect size information?
  2. Could the suppression/shortening of axis title labels become a default feature? If yes: Would it be possible to (optionally) align the axis title label to the right and add these options as flags to enum ImPlotAxisFlags_ {...}? This latter common for scientific plots and/or when only units (e.g. '[GeV/c]', '[UTC]', ...) are being displayed.

Any help/advice would be much appreciated!

EDIT: I found the following workaround for adjusting the axis title length:

const auto estTextSize = ImGui::CalcTextSize(axisLabel.c_str()).x;
if (estTextSize >= a.width) {
    static const auto estDotSize = ImGui::CalcTextSize("...").x;
    const std::size_t newSize    = a.width / std::max(1.f, estTextSize) * axisLabel.length();
    fmt::print("overlap {} > {}  text: {} -> {} \n", estTextSize, a.width, axisLabel.c_str(), fmt::format("{1:{0}}", newSize, axisLabel));
    ImPlot::SetupAxis(axis, fmt::format("...{}", axisLabel.substr(axisLabel.length() - newSize, axisLabel.length())).c_str(), axisFlags);
} else {
    ImPlot::SetupAxis(axis, axisLabel.c_str(), axisFlags);
}

and once the axis is setup the computation of the axis width/height in pixel (works after the second frame draw):

// compute axis pixel width for H and height for V
 [&plot] {
    const ImPlotRect axisLimits = ImPlot::GetPlotLimits(IMPLOT_AUTO, IMPLOT_AUTO);
    const auto       p0         = ImPlot::PlotToPixels(axisLimits.X.Min, axisLimits.Y.Min);
    const auto       p1         = ImPlot::PlotToPixels(axisLimits.X.Max, axisLimits.Y.Max);
    const int        xWidth     = static_cast<int>(std::abs(p1.x - p0.x));
    const int        yHeight    = static_cast<int>(std::abs(p1.y - p0.y));
    std::for_each(plot.axes.begin(), plot.axes.end(), [xWidth, yHeight](auto &a) { a.width = (a.axis == Dashboard::Plot::Axis::X) ? xWidth : yHeight; });
}();

image

If someone has an easier/cleaner/more integrated solution ...thanks in advance.