From: Darren Dale <dd55@co...>  20050226 20:12:42

On Friday 25 February 2005 08:45 am, Darren Dale wrote: > On Thursday 24 February 2005 11:02 pm, John Hunter wrote: > > >>>>> "Hans" == Hans Fangohr <H.FANGOHR@...> writes: > > > > Hans> x=pylab.arange(0,1e8,1e9)+1.0 pylab.plot(x) pylab.show() > > > > Hans> All works fine when I subtract the mean of x but there seems > > Hans> to be a problem with labelling axes for plotted data which > > Hans> 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 2e9, > > 4e9, etc as the actual tick labels. Do you agree this is ideal? 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+1e9,1+2e9,1+3e9] """ 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) < 1e4: # four sigfigs # add 1e15 because of floating point precision, fixes conversion self.offset = int(ave_loc/10**ave_oom+1e15)*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 specialcase for range of 01e5 if oom <= 0 and std(locs) < 1e4:#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+1e15 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 = (xself.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)*5e5) 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)*1e5) ax2.yaxis.set_major_formatter(ScalarFormatterScientific()) ax2.text(1,6e5,'6e5') ax2.xaxis.set_visible(False) ax3 = axes([.225,.28,.75,.2]) ax3.plot(arange(11)*1e10+1e5) ax3.yaxis.set_major_formatter(ScalarFormatterScientific()) ax3.text(1,1e5+6e10,'6e10+1e5') ax3.xaxis.set_visible(False) ax4 = axes([.225,.05,.75,.2]) ax4.plot(arange(11)*1e10+1e5) ax4.yaxis.set_major_formatter(ScalarFormatterScientific(useOffset=False)) ax4.text(1,1e5+6e10,'same as above, no offset') show() 