-
Notifications
You must be signed in to change notification settings - Fork 602
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<double> compute_log_labels(double min_val, double max_val, int n)
{
std::vector<double> 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<int>(ImPlot::GetCurrentPlot()->FrameRect.GetHeight() / (ImGui::GetFrameHeight() * 2.1));
auto ticks = compute_log_labels(axisMin, axisMax, numLabels);
ImPlot::SetupAxisTicks(ImAxis_Y1, ticks.data(), static_cast<int>(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.