Paulo Scardine
Paulo Scardine

Reputation: 77251

Linear X Logarithmic scale

Given a line X pixels long like:

0-------|---V---|-------|-------|-------max

If 0 <= V <= max, in linear scale V position would be X/max*V pixels.

How can I calculate the pixel position for a logarithmic scale, and starting from the pixel position how can I get back the value of V?

  1. It is not homework
  2. I want to know the math (no "use FOO-plotlib" comments, please)
  3. I like Python

A logarithmic scale has the effect of "zooming" the left side of the scale. Is it possible to do the same thing for the right side instead?

[UPDATE]

Thanks for the math lessons!

I ended up not using logarithms. I've simply used the average value (in a set of values) as the center of the scale. This control is used to select group boundary percentiles for a set of values that will be used to draw a choropleth chart.

If the user chooses a symmetric scale (red mark=average, green mark=center, darkness represents the number of occurrences of a value): enter image description here

An asymmetric scale makes fine-grained adjustments easier: enter image description here

Upvotes: 8

Views: 5720

Answers (3)

Lauritz V. Thaulow
Lauritz V. Thaulow

Reputation: 50985

So you've got some arbitrary value V, and you know that 0 <= V <= Vmax. You want to calculate the x-coordinate of a pixel, call it X, where your "screen" has x-coordinates from 0 to Xmax. As you say, to do this the "normal" way, you'd do

X = Xmax * V / Vmax
V = Vmax * X / Xmax

I like to think of it like I'm first normalizing the value to lie between 0 and 1 by calculating V / Vmax, and then I multiply this value by the maximum to get a value between 0 and that maximum.

To do the same logaritmically you need a different lower limit for the V value. If V is ever <= 0, you get a ValueError. So let's say 0 < Vmin <= V <= Vmax. Then you need to find out what logarithm to use, as there are infinitely many of them. Three are commonly encountered, those with base 2, e and 10, which results in x-axis that look like this:

------|------|------|------|----      ------|------|------|------|----
    2^-1    2^0    2^1    2^2     ==       0.5     1      2      4

------|------|------|------|----      ------|------|------|------|----
    e^-1    e^0    e^1    e^2     ==       0.4     1     2.7    7.4

------|------|------|------|----      ------|------|------|------|----
    10^-1  10^0   10^1   10^2     ==       0.1     1     10     100

So in principle, if we can get at the exponents from the expressions to the left, we can use the same principle as above to get a value between 0 and Xmax, and this is of course where log comes in. Assuming you use base b, you can use these expressions to convert back and forth:

from math import log
logmax = log(Vmax / Vmin, b)
X = Xmax * log(V / Vmin, b) / logmax
V = Vmin * b ** (logmax * X / Xmax)

It's almost the same way of thinking, except you need to first make sure that log(somevalue, b) will give you a non-negative value. You do this by dividing by Vmin inside the log function. Now you may divide by the maximum value the expression can yield, which is of course log(Vmax / Vmin, b), and you will get a value between 0 and 1, same as before.

The other way we need to first normalize (X / Xmax), then scale up again (* logmax) to the maximum expected by the inverse funciton. The inverse is to raise b to some value, by the way. Now if X is 0, b ** (logmax * X / Xmax) will equal 1, so to get the correct lower limit we multiply by Vmin. Or to put it another way, since the first thing we did going the other way was to divide by Vmin, we need to multiply with Vmin as the last thing we do now.

To "zoom" the "right side" of the equation, all you need to do is switch the equations, so you exponentiate going from V to X and take the logarithm going the other way. In principle, that is. Because you've also got to do something with the fact that X can be 0:

logmax = log(Xmax + 1, b)
X = b ** (logmax * (V - Vmin) / (Vmax - Vmin)) - 1
V = (Vmax - Vmin) * log(X + 1, b) / logmax + Vmin

Upvotes: 14

joaquin
joaquin

Reputation: 85615

This can be easily extended for other functions. My measure of space is given in chars instead of pixels (thats why max==chars(or pixels)).
Only for positive values.

import math

def scale(myval, mode='lin'):
    steps = 7
    chars = max = 10 * steps

    if mode=='log':
        val = 10 * math.log10(myval)
    else:
        val = myval

    coord = []
    count = 0
    not_yet = True
    for i in range(steps):
        for j in range(10):
            count += 1
            if val <= count and not_yet:
                coord.append('V')
                not_yet = False
                pos = count
            elif j==9:
                coord.append('|')
            else:
                coord.append('*')

    graph = ''.join(coord)
    text = 'graph %s\n\n%s\nvalue = %5.1f   rel.pos. = %5.2f\n'
    print  text % (mode, graph, myval, chars * pos/max) 


scale(50, 'lin')
scale(50, 'log')

enter image description here

Hope the above is not considered FOO-plotlib. But damn it ! this is SO ! :-)

Upvotes: 2

hamstergene
hamstergene

Reputation: 24429

           Linear               Logarithmic
Forward    pos = V * X/max      pos = log(V) * X/log(max)
Reverse    V = pos * max/X      V = B^( pos * log(max)/X )

(B is logarithm's base)

Obviously you must ensure that V >= 1 (V=1 will correspond to pos=0, V=0..1 corresponds to -inf..0, and for V<0 logarithm is not defined).

Upvotes: 3

Related Questions