[Matrix-SIG] algorithm for intelligent axis scaling?
David Ascher
da@ski.org
Thu, 18 Mar 1999 13:13:35 -0800 (Pacific Standard Time)
> This is not directly a NumPy question, but I figured that the best answer
> might come from this group. :) I'm looking for a simple algorithm for
> generating intelligent axis scalings for a graph. Basically what I need is
> the axis minimum, axis maximum, and step length between ticks given the
> minimum data value, maximum data value, and suggested number of ticks.
> By intelligent, I mean reasonably intuitive rounding for the axis labels!
I've found it surprisingly difficult. The routine I use in Snow
(http://starship.python.net:9673/crew/da/Snow) is:
def format(v):
if int(v) == v:
return `int(v)`
else:
return `v`
def myticks(self, Min, Max, log, fitMin=1, fitMax=1):
r = [] ; l = []
# what's the tick seperation: if min is .2, then it's .1
# if it's .002, then it's 0.001.
if log:
startscale = floor(log10(Min))
endscale = floor(log10(Max))
candidates = []
for x in arange(startscale, endscale+1):
candidates = candidates + (arange(1,10)*power(10,x)).tolist()
valid = nonzero(logical_and(greater(candidates, Min),
less(candidates, Max)))
doable = candidates[valid[0]-1:valid[-1]+2]
for v in doable:
if v/power(10,floor(log10(v))) == 1.0:
r.append((v,0))
elif v/power(10,floor(log10(v))) == 5.0:
r.append((v,1))
else:
r.append((v,2))
if (v/power(10, floor(log10(v))) in (1.0, 5.0)):
l.append((v, None, format(v)))
# enable following for ticks at startat and endat
if 0 and (doable[0], doable[-1]):
l.append((v, None, format(v)))
return doable[0], r, l, doable[-1]
else: # linear case
d = abs(Max - Min)
epsilon = 1e-10
s = power(10,floor(log10(d))-1)
if fitMin:
startat = Min - (Min % (s * 10))
else:
startat = Min
overmax = Max + (s * 10)
if fitMax:
endat = overmax - (overmax % (s * 10))
else:
endat = Max
if d / (10*s) > 5.2: # magic!
# small tickmarks at unit factors
s = s * 10 # we need to upshift the scale
maj_mod = 10 # major tick marks at factors of 10
int_mod = 2 # intermediate tick marks at factors of 2
else:
maj_mod = 10 # major tick marks at factors of 10
int_mod = 5 # minors at factors of 5
# smalls at factors of 1
r = [] ; l = []
nummajors = 0 # for debugging
numints = 0
for x in arange(startat, endat+epsilon, s):
if (abs((x / s) % maj_mod) < epsilon):
x = round(x, 4)
r.append((x,0))
l.append((x, None, format(x)))
nummajors = nummajors + 1
elif (abs((x / s) % int_mod) < epsilon):
x = round(x, 4)
r.append((x,1))
l.append((x, None, format(x)))
numints = numints + 1
else:
x = round(x, 4)
r.append((x,2))
return startat, r, l, endat
It's a hack, but it's neater than the code in gist. =) It's also highly
undocumented. Playing with it however should make it relatively clear
what the return values are.
--david