Logarithmic scales for UI

Logarithmic controls are common in games, especially in situations where high precision is required in low values and low precision in high values. Another use case is when we want to increase a value proportionally to its current value.

One such example is speed of motion, as used in Unreal's navigation system. When the mouse wheel is rolled, motion speed is increased proportionally to its current value. That is necessary for the user to "feel" the increase in speed. If, say, speed is increased by constant increments, then there will be a time when the increase in speed won't be significant enough for the user to notice any difference. If we choose to increase speed by a constant increment of 0.1m/s, then if the user starts from a speed of 0m/s, then the first increment would be very abrupt (what if we want to move very slowly instead?) but if we want to move very fast, a potentially uncomfortably large number of rolls would be required to change between fast and slow values.

To solve this problem we can always increase the speed in proportion to the current value. Say after one roll iteration, the new value will be 1.1 times the initial value. After a second roll iteration, the new value will be 1.1 times the current value, or 1.1 * 1.1 times the initial value. We can easily generalize this to calculate the value after N rolls to 1.1N, In this scheme, 1.1 is just the base of a power function so to write this in a more mathematically sound way, we can use:

1.1     f(base, N) = initial * baseN

for arbitrary bases and numbers of rolls

Decreasing is easy, just multiply by 1/base. Due to the way floating point errors accumulate, it might pay to keep track of the current power and use the power function directly to calculate the current value instead of accumulating multiplication errors from the previous values, though I must note that I haven't tested the veracity of this statement. It may be an interesting afternoon project.

Note how any base to the power of zero equals one, so we can obtain the initial value and get meaningful numbers by using both positive zero and negative exponents.

Using logarithmic scale for a range

In user interfaces, we usually have a range of permitted values that we want to allow, say {min, max}. It is often a good idea to get some information about the range, for example how many iterations it would take for min to reach max using a certain base. We can set f to the max and initial to the min value like this:

2.1       max = min * baseN

Solving for N we get:
 
2.2       N = logbase (max/min)

I will add a tidbit of information that will be useful for those who do those calculations on the computer, where a log function with an arbitrary base is missing. Calculating a log with an arbitrary base can be done like this:

2.3       logbase (x) = log (x) / log (base)

where log is the logarithm with base equal to e, the natural logarithm, and is the default log function in most programming languages. Proof should be easy, but if you want to see it let me know in the comments.

We usually don't want the user to roll too much between min and max, so we might want to choose a suitable value of N iterations, say 10. In that case, we need to calculate the base that would give us this amount of iterations.

2.4        base = (max/min)1/N

Problems with arithmetic

While everything so far has been very straightforward, there are issues with this simplistic approach, and those are the issues that sparked authoring this blog post in the first place. Otherwise this is just high school math material.

First, for those equations to work properly, both min and max need to be positive and, ideally greater than one. Let's see a few non-trivial problematic cases:

min = 0 is problematic because it leads to division by zero and it is impossible to reach as it needs an infinite number of power iterations or "rolls".

For 0 < min < 1, max > 1. the math is well behaved, but the number of iterations can be potentially huge.

Generally, this system does not seem to like minimum values less than one. What if we want to start from a negative value and iterate through the difference between min and max in an exponential fashion?

Restating the problem

Let's restate what we want. Starting from an arbitrary value min, we want to reach max by increasing in an exponential way. A bad first attempt is:

3.1          max = min + baseN.

To get intermediate values between min and max, we just use x < N as an exponent.

This is bad because values near min exhibit the same unwanted behavior for x: Very negative numbers are needed to represent values in the range {min, min + 1}. Also it is impossible to represent min by using any finite number x. To get around this, we will instead use the following hack:

3.2         max = min + D(N)

where D(x) has the following properties:

D(0) = 0
D(N) = max - min

Is there a way to represent D(x) with an exponential function? It turns out that there is:

3.3           D(x) = basex - 1

Using this function the first requirement is fullfilled automatically, since any base to the power of zero is one:

D(0) = base0 - 1 = 0

We can then choose base or N to satisfy:

D(N) = baseN - 1 = max - min

as needed. If we require a certain number of steps N, we solve for the base variable:

3.4         base = (max - min + 1)1/N

If we require a certain base, we solve for the number of steps N.

3.5         N = logbase (max - min + 1)

Using the method for sliders

If one wants to use a slider for incrementing between min and max in a logarithmic manner they can apply the above method. We need to solve for the base, since the number of steps N is dictated by setting it to the number of pixels between the slider corners.

Therefore, by substituting the value of the base that we get from eq. 3.4 to 3.3 we have

4.1          D(x) = (max - min + 1)x/N - 1

where x is the pixel where the slider handle is at, relative to the left corner of the slider.

To get the actual value of our variable, we need to use equation 3.2 for an arbitrary x.

4.2          value = min + (max - min + 1)x/N - 1

When we position the handle of the slider to a certain pixel that represents that value in a logarithmic manner, we have to solve the above equation for X:

value - min + 1 = (max - min + 1)x/N 

We can use a log with any base function to turn x from an exponent into a simple factor.

log(value - min + 1) = x/N * log(max - min + 1)

and finally, we get:

4.3           x = N * log(value - min + 1) / log(max - min + 1)

And there you have it.

I hope the above will help you implementing logarithmic controls for your UI.

Thanks for reading!

Comments

Popular posts from this blog

Improving the performance of UI list updates

Contexted drop in Rust