epezent / epezent/implot

Log-Scale Labels/Ticks are broken

Open
#617 3 comments 1 reaction 0 assignees View on GitHub
Dominant language
C++
Stars
6.2k
Forks
693
PR merge metrics
No merged PRs in 30d

Description

Hi,
i was using a log-scale in my project and when zooming in, the automatic labeling / automatic ticks are really unusable.
For example, having a log-axis zoomed into the range from 99k to 101k produces the single 100k label.
Zooming into the range from 101k to 102k generate no labels what so ever.
I consider this broken behavior.

I have no idea how to fix this in the code base, but ImPlot allows you to set your manual labels, therefore i want to publish an algorithm i wrote, with the help of NiceNum (that already exist in the code-base) that generates a sane list of labels for any set of valid log-axis-limits:

```
double niceNum(double range, bool round)
{
const double exponent = std::floor(std::log10(range));
const double fraction = range / std::pow(10.0, exponent);
double niceFraction;

if (round)
{
if (fraction < 1.5)
niceFraction = 1;
else if (fraction < 3)
niceFraction = 2;
else if (fraction < 7)
niceFraction = 5;
else
niceFraction = 10;
}
else
{
if (fraction <= 1)
niceFraction = 1;
else if (fraction <= 2)
niceFraction = 2;
else if (fraction <= 5)
niceFraction = 5;
else
niceFraction = 10;
}

return niceFraction * std::pow(10.0, exponent);
}

std::vector compute_log_labels(double min_val, double max_val, int n)
{
std::vector labels;
labels.reserve(n*2);
const double logStepSize = (std::log10(max_val) - std::log10(min_val)) / double(n);
const double firstNiceDiff = niceNum(std::pow(10.0, std::log10(min_val) + logStepSize) - min_val, true);
const double lastNiceDiff = niceNum(max_val - std::pow(10.0, std::log10(max_val) - logStepSize), true);

const double niceStart = std::max(std::floor(min_val / firstNiceDiff), 1.0) * firstNiceDiff;
const double niceEnd = std::ceil(max_val / lastNiceDiff) * lastNiceDiff;

for (double val = niceStart; val <= niceEnd; )
{
labels.push_back(val);
const double niceDiff = niceNum(std::pow(10.0, std::log10(val) + logStepSize) - val, true);
const auto newVal = val+niceDiff;
const auto valFloor = std::floor(newVal / niceDiff) * niceDiff;
val = valFloor > val ? valFloor : std::ceil(newVal / niceDiff) * niceDiff;
}

return labels;
}
```

This will generate a list of labels for the range [min_val, max_val] with approximately n entries. It can be more, it can be less.
I have used this successfully:
```
const auto numLabels = static_cast(ImPlot::GetCurrentPlot()->FrameRect.GetHeight() / (ImGui::GetFrameHeight() * 2.1));
auto ticks = compute_log_labels(axisMin, axisMax, numLabels);
ImPlot::SetupAxisTicks(ImAxis_Y1, ticks.data(), static_cast(ticks.size()), nullptr, false);
```

I hope this helps someone or it could even somehow merged into the project so this algorithm is the default behavior.
The existing ImPlot::NiceNum function does some shenanigans by casting the floor() result to an integer, all while that integer is never used as an integer but immediately casted back to double. I therefore included a corrected version that does not do useless overhead.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.