8000 Added a new ScalarFormatter called NewScalarFormatter. It will eventu… · matplotlib/matplotlib@2a83c96 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2a83c96

Browse files
committed
Added a new ScalarFormatter called NewScalarFormatter. It will eventually
replace the old one. svn path=/trunk/matplotlib/; revision=1250
1 parent a84809b commit 2a83c96

File tree

3 files changed

+180
-29
lines changed

3 files changed

+180
-29
lines changed

lib/matplotlib/axes.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from artist import Artist
1212
from axis import XAxis, YAxis
1313
from cbook import iterable, is_string_like, flatten, enumerate, \
14-
allequal, dict_delall, strip_math, popd, popall, silent_list
14+
allequal, dict_delall, popd, popall, silent_list
1515
from collections import RegularPolyCollection, PolyCollection, LineCollection
1616
from colors import colorConverter, normalize, Colormap, LinearSegmentedColormap, looks_like_color
1717
import cm
@@ -582,15 +582,8 @@ def format_xdata(self, x):
582582
"""
583583
try: return self.fmt_xdata(x)
584584
except TypeError:
585-
func = self.xaxis.get_major_formatter()
586-
# log formatters label only the decades which is not what
587-
# we want for coord formatting. hackish, yes
588-
if isinstance(func, LogFormatter) and func.labelOnlyBase:
589-
func.labelOnlyBase = False
590-
val = strip_math(func(x))
591-
func.labelOnlyBase = True
592-
else:
593-
val = func(x)
585+
func = self.xaxis.get_major_formatter().format_data
586+
val = func(x)
594587
return val
595588

596589
def format_ydata(self, y):
@@ -601,15 +594,8 @@ def format_ydata(self, y):
601594
"""
602595
try: return self.fmt_ydata(y)
603596
except TypeError:
604-
func = self.yaxis.get_major_formatter()
605-
# log formatters label only the decades which is not what
606-
# we want for coord formatting. hackish, yes
607-
if isinstance(func, LogFormatter) and func.labelOnlyBase:
608-
func.labelOnlyBase = False
609-
val = strip_math(func(y))
610-
func.labelOnlyBase = True
611-
else:
612-
val = func(y)
597+
func = self.yaxis.get_major_formatter().format_data
598+
val = func(y)
613599
return val
614600

615601
def format_coord(self, x, y):

lib/matplotlib/axis.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,22 @@ def __init__(self, axes):
459459
self.majorTicks = []
460460
self.minorTicks = []
461461

462+
if self.__name__=='xaxis':
463+
ox,oy = 1, -0.06
464+
v,h = 'top','right'
465+
else:
466+
ox,oy = 0, 1.01
467+
v,h='bottom','left'
468+
self.offsetText = Text(x=ox, y=oy,
469+
fontproperties = FontProperties(size=rcParams['tick.labelsize']),
470+
color = rcParams['axes.labelcolor'],
471+
verticalalignment=v,
472+
horizontalalignment=h,
473+
)
474+
self.offsetText.set_transform(self.axes.transAxes)
475+
## self.offsetText.set_transform( blend_xy_sep_transform( self.axes.transAxes,
476+
## identity_transform() ))
477+
self._set_artist_props(self.offsetText)
462478

463479
self.cla()
464480

@@ -548,6 +564,9 @@ def draw(self, renderer, *args, **kwargs):
548564
if tick.label2On:
549565
extent = tick.label2.get_window_extent(renderer)
550566
ticklabelBoxes2.append(extent)
567+
568+
self.offsetText.set_text( self.major.formatter.get_offset() )
569+
self.offsetText.draw(renderer)
551570

552571
# scale up the axis label box to also find the neighbors, not
553572
# just the tick labels that actually overlap note we need a
@@ -660,6 +679,7 @@ def get_minor_ticks(self):
660679
ticks = self.minorTicks[:numticks]
661680

662681
return ticks
682+
663683

664684
def grid(self, b=None, which='major'):
665685
"""
@@ -719,6 +739,7 @@ def set_minor_locator(self, locator):
719739
self.minor.locator = locator
720740
self.minor.locator.set_view_interval( self.get_view_interval() )
721741
self.minor.locator.set_data_interval( self.get_data_interval() )
742+
722743

723744
def set_ticklabels(self, ticklabels, *args, **kwargs):
724745
"""
@@ -859,7 +880,6 @@ def get_data_interval(self):
859880
return self.axes.dataLim.intervalx()
860881

861882

862-
863883
class YAxis(Axis):
864884
__name__ = 'yaxis'
865885

lib/matplotlib/ticker.py

Lines changed: 154 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,11 @@
111111
import sys, os, re, time, math, warnings
112112
from mlab import linspace
113113
from matplotlib import verbose
114-
from numerix import arange, array, asarray, ones, zeros, \
115-
nonzero, take, Float, log, logical_and
114+
from numerix import absolute, arange, array, asarray, average, Float, floor, log, \
115+
logical_and, nonzero, ones, take, zeros
116+
from matplotlib.numerix.mlab import amin, amax, std
117+
from matplotlib.mlab import frange
118+
from cbook import strip_math
116119

117120
class TickHelper:
118121

@@ -144,6 +147,12 @@ class Formatter(TickHelper):
144147
def __call__(self, x, pos=0):
145148
'Return the format for tick val x at position pos'
146149
raise NotImplementedError('Derived must overide')
150+
151+
def format_data(self,value):
152+
return self.__call__(value)
153+
154+
def get_offset(self):
155+
return ''
147156

148157
def set_locs(self, locs):
149158
self.locs = locs
@@ -190,10 +199,8 @@ def __init__(self, fmt):
190199
def __call__(self, x, pos=0):
191200
'Return the format for tick val x at position pos'
192201
return self.fmt % x
193-
194-
195-
196-
202+
203+
197204
class ScalarFormatter(Formatter):
198205
"""
199206
Tick location is a plain old number. If viewInterval is set, the
@@ -231,7 +238,116 @@ def pprint_val(self, x, d):
231238
s = s.rstrip('0').rstrip('.')
232239
return s
233240

234-
class LogFormatter(ScalarFormatter):
241+
242+
class NewScalarFormatter(Formatter):
243+
"""
244+
Tick location is a plain old number. If useOffset==True and the data range
245+
<1e-4* the data average, then an offset will be determined such that the
246+
tick labels are meaningful. Scientific notation is used for data < 1e-4 or
247+
data >= 1e4. Scientific notation is presented once for each axis, in the
248+
last ticklabel.
249+
"""
250+
def __init__(self, useOffset=True):
251+
"""
252+
useOffset allows plotting small data ranges with large offsets:
253+
for example: [1+1e-9,1+2e-9,1+3e-9]
254+
"""
255+
self._useOffset = useOffset
256+
self.offset = 0
257+
self.orderOfMagnitude = 0
258+
self.format = ''
259+
260+
def __call__(self, x, pos=0):
261+
'Return the format for tick val x at position pos'
262+
self.verify_intervals()
263+
d = abs(self.viewInterval.span())
264+
if self._useOffset: self._set_offset(d)
265+
self._set_orderOfMagnitude(d)
266+
self._set_format()
267+
return self.pprint_val(x)
268+
269+
def format_data(self,value):
270+
'return a formatted string representation of a number'
271+
s = '%1.5e'% value
272+
return self._formatSciNotation(s)
273+
274+
def get_offset(self):
275+
"""Return scientific notation, plus offset"""
276+
if self.orderOfMagnitude or self.offset:
277+
offsetStr = ''
278+
sciNotStr = ''
279+
## if self.offset:
280+
## p = ('+%1.10e'% self.offset).replace('+-','-')
281+
## offsetStr = self._formatSciNotation(p).replace('e',r'\times 10^{') + '}'
282+
## if self.orderOfMagnitude:
283+
## p = '%1.e'% 10**self.orderOfMagnitude
284+
## sciNotStr = self._formatSciNotation(p).replace('1e',r'\times 10^{') + '}'
285+
## return ''.join(('$',sciNotStr,offsetStr,'$'))
286+
if self.offset:
287+
p = ('+%1.10e'% self.offset).replace('+-','-')
288+
offsetStr = self._formatSciNotation(p)
289+
if self.orderOfMagnitude:
290+
p = '%1.e'% 10**self.orderOfMagnitude
291+
sciNotStr = self._formatSciNotation(p)
292+
return ''.join((sciNotStr,offsetStr))
293+
else: return ''
294+
295+
def set_locs(self, locs):
296+
self.locs = locs
297+
298+
def _set_offset(self, range):
299+
# offset of 20,001 is 20,000, for example
300+
locs = self.locs
301+
ave_loc = average(locs)
302+
if ave_loc: # dont want to take log10(0)
303+
ave_oom = math.floor(math.log10(absolute(ave_loc)))
304+
range_oom = math.ceil(math.log10(range))
305+
if absolute(ave_oom-range_oom) >= 4: # four sig-figs
306+
if ave_loc < 0: self.offset = math.floor(amax(locs)/10**range_oom)*10**range_oom
307+
else: self.offset = math.floor(amin(locs)/10**range_oom)*10**range_oom
308+
else: self.offset = 0
309+
310+
def _set_orderOfMagnitude(self,range):
311+
# if scientific notation is to be used, find the appropriate exponent
312+
# if using an numerical offset, find the exponent after applying the offset
313+
locs = absolute(self.locs)
314+
if self._useOffset: oom = math.floor(math.log10(range))
315+
else:
316+
if locs[0] > locs[-1]: oom = math.floor(math.log10(locs[0]))
317+
else: oom = math.floor(math.log10(locs[-1]))
318+
if oom <= -3:
319+
self.orderOfMagnitude = oom
320+
elif oom >= 4:
321+
self.orderOfMagnitude = oom
322+
else:
323+
self.orderOfMagnitude = 0
324+
325+
def _set_format(self):
326+
# set the format string to format all the ticklabels
327+
locs = (array(self.locs)-self.offset) / 10**self.orderOfMagnitude+1e-15
328+
sigfigs = [len(str('%1.3f'% loc).split('.')[1].rstrip('0')) \
329+
for loc in locs]
330+
sigfigs.sort()
331+
self.format = '%1.' + str(sigfigs[-1]) + 'f'
332+
333+
def pprint_val(self, x):
334+
xp = (x-self.offset)/10**self.orderOfMagnitude
335+
if closeto(xp,0): return '0'
336+
else: return self.format % xp
337+
338+
def _formatSciNotation(self,s):
339+
# transform 1e+004 into 1e4, for example
340+
tup = s.split('e')
341+
try:
342+
mantissa = tup[0].rstrip('0').rstrip('.')
343+
sign = tup[1][0].replace('+', '')
344+
exponent = tup[1][1:].lstrip('0')
345+
return ('%se%s%s' %(mantissa, sign, exponent)).rstrip('e')
346+
except IndexError,msg:
347+
return s
348+
349+
350+
class LogFormatter(Formatter):
235351
"""
236352
Format values for log axis;
237353
@@ -270,6 +386,12 @@ def __call__(self, x, pos=0):
270386
else : s = self.pprint_val(x,d)
271387
return s
272388

389+
def format_data(self,value):
390+
self.labelOnlyBase = False
391+
value = strip_math(self.__call__(value))
392+
self.labelOnlyBase = True
393+
return value
394+
273395
def is_decade(self, x):
274396
n = self.nearest_long(x)
275397
return abs(x-n)<1e-10
@@ -278,6 +400,29 @@ def nearest_long(self, x):
278400
if x==0: return 0L
279401
elif x>0: return long(x+0.5)
280402
else: return long(x-0.5)
403+
404+
def pprint_val(self, x, d):
405+
#if the number is not too big and it's an int, format it as an
406+
#int
407+
if abs(x)<1e4 and x==int(x): return '%d' % x
408+
409+
if d < 1e-2: fmt = '%1.3e'
410+
elif d < 1e-1: fmt = '%1.3f'
411+
elif d > 1e5: fmt = '%1.1e'
412+
elif d > 10 : fmt = '%1.1f'
413+
elif d > 1 : fmt = '%1.2f'
414+
else: fmt = '%1.3f'
415+
s = fmt % x
416+
#print d, x, fmt, s
417+
tup = s.split('e')
418+
if len(tup)==2:
419+
mantissa = tup[0].rstrip('0').rstrip('.')
420+
sign = tup[1][0].replace('+', '')
421+
exponent = tup[1][1:].lstrip('0')
422+
s = '%se%s%s' %(mantissa, sign, exponent)
423+
else:
424+
s = s.rstrip('0').rstrip('.')
425+
return s
281426

282427
class LogFormatterExponent(LogFormatter):
283428
"""
@@ -542,7 +687,8 @@ def __call__(self):
542687
if vmax<vmin:
543688
vmin, vmax = vmax, vmin
544689
vmin = self._base.ge(vmin)
545-
locs = arange(vmin, vmax+0.001*self._base.get_base(), self._base.get_base())
690+
691+
locs = frange(vmin, vmax+0.001*self._base.get_base(), self._base.get_base())
546692

547693
return locs
548694

@@ -560,7 +706,6 @@ def autoscale(self):
560706
if vmin==vmax:
561707
vmin -=1
562708
vmax +=1
563-
564709

565710
return self.nonsingular(vmin, vmax)
566711

0 commit comments

Comments
 (0)
0