Вопрос

So I'm trying to make a graphing application, and I'm using Desmos as a base for that.

The thing I'm struggling with is the way Desmos handles the subdivisions of the axes. When you zoom in or out the scales are always on "easy" simple numbers like 5, 100, 1000 etc. So my question is: how does one go about simplifying their scale with any level of zoom?

BTW: Using C++

Это было полезно?

Решение

I was going to write a description of how to do this in general, but then I realize that the code may be easier than explaining.


Most important step: define precisely what you mean by "easy simple" numbers.


Example #1: 1, 2, 4, 8, 16, 32, 64, 128, ... , 1073741824, ...

These are powers of two. So, a straightforward ceil(log(x)/log(2.0)) will solve it.


Example #2: 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, ...

There is a mixture of powers of two, and some multiples of it. Let's take a closer look.

  • A subset of these can be described as powers of ten.
    • Changing the formula to ceil(log(x)/log(10.0)) will solve it.
  • For each power-of-ten, its multiples by 2.0 and 5.0 are also "easy simple numbers".
    • Inside each iteration, after checking the power-of-ten value, also check the two multiples. If it fits inside one of the multiple, that value can be returned as result.

Code

The following code is only meant to explain the concept. It is not efficient - an efficient version should have used logarithm to get the result in O(1) time.


#include <iostream>
#include <vector>
#include <limits>
#include <stdexcept>
#include <algorithm>

using namespace std;

double getNiceAxisLength(double value, double baseLength, double step, const std::vector<double>& subSteps)
{
    typedef std::vector<double>::const_iterator VecDoubleIter;
    if (value < 0.0)
    {
        throw std::invalid_argument("Error: value must be non-negative. Take absolute value if necessary.");
    }
    if (baseLength <= 0.0)
    {
        throw std::invalid_argument("Error: baseLength must be positive.");
    }
    if (step <= 1.0)
    {
        throw std::invalid_argument("Error: step must be strictly greater than 1.");
    }
    for (VecDoubleIter iter = subSteps.begin(); iter != subSteps.end(); ++iter)
    {
        double subStep = *iter;
        if (subStep <= 1.0 || subStep >= step)
        {
            throw std::invalid_argument("Error: each subStep must be strictly greater than 1, and strictly smaller than step.");
        }
    }
    // make ascending.
    std::vector<double> sortedSubSteps(subSteps.begin(), subSteps.end());
    std::sort(sortedSubSteps.begin(), sortedSubSteps.end());
    if (value <= baseLength)
    {
        return baseLength;
    }
    double length = baseLength;
    double terminateLength = numeric_limits<double>::max() / step;
    while (length < terminateLength)
    {
        for (VecDoubleIter iter = sortedSubSteps.begin(); iter != sortedSubSteps.end(); ++iter)
        {
            double subStep = *iter;
            if (value <= length * subStep)
            {
                return (length * subStep);
            }
        }
        double nextLength = length * step;
        if (value <= nextLength)
        {
            return nextLength;
        }
        length = nextLength;
    }
    return baseLength;
}

int main()
{
    double baseLength = 1.0;
    double step = 10.0;
    std::vector<double> subSteps;
    subSteps.push_back(2.5);
    subSteps.push_back(5);
    for (int k = 0; k < 1000; k += ((k >> 2) + 1))
    {
        double value = k;
        double result = getNiceAxisLength(value, baseLength, step, subSteps);
        cout << "k: " << value << " result: " << result << endl;
    }

    cout << "Hello world!" << endl;
    return 0;
}

Output

k: 0 result: 1
k: 1 result: 1
k: 2 result: 2.5
k: 3 result: 5
k: 4 result: 5
k: 6 result: 10
k: 8 result: 10
k: 11 result: 25
k: 14 result: 25
k: 18 result: 25
k: 23 result: 25
k: 29 result: 50
k: 37 result: 50
k: 47 result: 50
k: 59 result: 100
k: 74 result: 100
k: 93 result: 100
k: 117 result: 250
k: 147 result: 250
k: 184 result: 250
k: 231 result: 250
k: 289 result: 500
k: 362 result: 500
k: 453 result: 500
k: 567 result: 1000
k: 709 result: 1000
k: 887 result: 1000
Hello world!

Hello world!

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top