I worked on a new formatter yesterday and today. It includes the indicator
that John described above, right now in the last ticklabel at the top of the
axis. This custom formatter also includes scientific notation in the last
ticklabel only. The ultimate goal is to have scientific notation be formatted
like in the logplots, but I havent gotten that far yet.
Using the offset makes a large ticklabel at the moment. You can pass
useOffset=False to ScalarFormatterScientific to turn this feature off (see end
of script below). Interested parties, please give this script a whirl and send
me your comments.
(John, I have now subclassed ScalarFormatter, I didnt realize I had altered
a method that other formatters were inheriting.)
Darren
from matplotlib import *
rc('font',size='smaller')
rc('tick',labelsize='smaller')
from matplotlib.ticker import ScalarFormatter, LinearLocator
import math
from matplotlib.numerix import absolute, average
from pylab import *
class ScalarFormatterScientific(ScalarFormatter):
"""
Tick location is a plain old number. If viewInterval is set, the
formatter will use %d, %1.#f or %1.ef as appropriate. If it is
not set, the formatter will do str conversion
"""
def __init__(self, useOffset=True):
"""
useOffset allows plotting small data ranges with large offsets:
for example: [1+1e-9,1+2e-9,1+3e-9]
"""
self._useOffset = useOffset
def set_locs(self, locs):
self.locs = locs
self._set_offset()
self._set_orderOfMagnitude()
self._set_format()
def _set_offset(self):
# offset of 20,001 is 20,000, for example
if self._useOffset:
ave_loc = average(self.locs)
std_loc = std(self.locs)
if ave_loc: # dont want to take log10(0)
ave_oom = math.floor(math.log10(absolute(ave_loc)))
if std_loc/math.fabs(ave_loc) < 1e-4: # four sig-figs
# add 1e-15 because of floating point precision, fixes conversion
self.offset = int(ave_loc/10**ave_oom+1e-15)*10**ave_oom
else: self.offset = 0
def _set_orderOfMagnitude(self):
# if using an offset, oom applies after applying the offset
locs = array(self.locs)-self.offset
ave_loc_abs = average(absolute(locs))
oom = math.floor(math.log10(ave_loc_abs))
# need to special-case for range of 0-1e-5
if oom <= 0 and std(locs) < 1e-4:#10**(2*oom):
self.orderOfMagnitude = oom
elif oom <=0 and oom >= -5:
pass
elif math.fabs(oom) >= 4:
self.orderOfMagnitude = oom
def _set_format(self):
locs = (array(self.locs,'d')-self.offset) / \
10**self.orderOfMagnitude+1e-15
sigfigs = [len(str('%1.4f'% loc).split('.')[1].rstrip('0')) \
for loc in locs]
sigfigs.sort()
self.format = '%1.' + str(sigfigs[-1]) + 'f'
def pprint_val(self, x, d):
xp = (x-self.offset)/10**self.orderOfMagnitude
if x==self.locs[-1] and (self.orderOfMagnitude or self.offset):
offsetStr = ''
sciNotStr = ''
xp = self.format % xp
if self.offset:
p = '%1.e+'% self.offset
offsetStr = self._formatSciNotation(p)
if self.orderOfMagnitude:
p = 'x%1.e'% 10**self.orderOfMagnitude
sciNotStr = self._formatSciNotation(p)
return ''.join((offsetStr,xp,sciNotStr))
elif xp==0: return '%d'% xp
else: return self.format % xp
def _formatSciNotation(self,s):
tup = s.split('e')
mantissa = tup[0]
sign = tup[1][0].replace('+', '')
exponent = tup[1][1:].lstrip('0')
return '%se%s%s' %(mantissa, sign, exponent)
figure(1,figsize=(6,6))
ax1 = axes([.2,.74,.75,.2])
ax1.plot(arange(11)*5e2)
ax1.yaxis.set_major_formatter(ScalarFormatterScientific())
ax1.xaxis.set_visible(False)
ax1.set_title('BIG NUMBERS',fontsize=14)
ax2 = axes([.2,.51,.75,.2])
ax2.plot(arange(11)*1e4)
ax2.yaxis.set_major_formatter(ScalarFormatterScientific())
ax2.text(1,6e4,'6e4')
ax2.xaxis.set_visible(False)
ax3 = axes([.2,.28,.75,.2])
ax3.plot(arange(11)*1e4+1e10)
ax3.yaxis.set_major_formatter(ScalarFormatterScientific())
ax3.text(1,6e4+1e10,'1e10+6e4')
ax3.xaxis.set_visible(False)
ax4 = axes([.2,.05,.75,.2])
ax4.plot(arange(11)*1e4+1e10)
ax4.yaxis.set_major_formatter(ScalarFormatterScientific(useOffset=False))
ax4.text(1,1e10+6e4,'same as above, no offset')
figure(2,figsize=(6,6))
ax1 = axes([.225,.74,.75,.2])
ax1.plot(arange(11)*5e-5)
ax1.yaxis.set_major_formatter(ScalarFormatterScientific())
ax1.xaxis.set_visible(False)
ax1.set_title('small numbers',fontsize=8)
ax2 = axes([.225,.51,.75,.2])
ax2.plot(arange(11)*1e-5)
ax2.yaxis.set_major_formatter(ScalarFormatterScientific())
ax2.text(1,6e-5,'6e-5')
ax2.xaxis.set_visible(False)
ax3 = axes([.225,.28,.75,.2])
ax3.plot(arange(11)*1e-10+1e-5)
ax3.yaxis.set_major_formatter(ScalarFormatterScientific())
ax3.text(1,1e-5+6e-10,'6e-10+1e-5')
ax3.xaxis.set_visible(False)
ax4 = axes([.225,.05,.75,.2])
ax4.plot(arange(11)*1e-10+1e-5)
ax4.yaxis.set_major_formatter(ScalarFormatterScientific(useOffset=False))
ax4.text(1,1e-5+6e-10,'same as above, no offset')
show()
···
On Friday 25 February 2005 08:45 am, Darren Dale wrote:
On Thursday 24 February 2005 11:02 pm, John Hunter wrote:
>
> > x=pylab.arange(0,1e-8,1e-9)+1.0 pylab.plot(x) pylab.show()
>
> > All works fine when I subtract the mean of x but there seems
> > to be a problem with labelling axes for plotted data which
> > is not close to zero but shows only small variations.
>
> I agree it's a bug. It's not immediately clear to me what the labels
> should be though
>
> 1.0000000002
> 1.0000000004
> 1.0000000006
>
> and so on? That takes up a lot of room. Granted, correct but ugly
> is better than incorrect but pretty, but I'm curious if there is a
> better way to format these cases. Perhaps ideal would be an indicator
> at the bottom or top of the y axis that read '1+' and then use 2e-9,
> 4e-9, etc as the actual tick labels. Do you agree this is ideal?