Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Ticket 4376: Implemented functionality to let the user set an offset or
a scaling factor for a specific axis using ScalarFormatter.

- Scaling and offset have become exclusive: either scaling or offset or
  nothing.
- This also resolves ambiguity as (x-offset)/scalar != (x/scalar-offset)
- Added a new method set_useScalingFactor(True|False|value)
- Wrote documentation for both set_useOffset(True|False|value) and
  set_useScalingFactor()
- Fixed an (unreported) bug in HEAD that ignored the offset when scaling
  was performed automatically due to scientific notation
- rcParams['axes.formatter.useoffset'] still controls whether to use
  offset when guessing best format.
- set_powerlimits() still controls whether scaling is automatically
  performed
  • Loading branch information
VincentVandalon committed Mar 1, 2016
commit 137c7aa4f76e55f826e1891dd027422dac619e41
134 changes: 97 additions & 37 deletions lib/matplotlib/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
from matplotlib.externals import six

import decimal
import numbers
import locale
import math
import numpy as np
Expand Down Expand Up @@ -422,9 +423,12 @@ def __init__(self, useOffset=None, useMathText=None, useLocale=None):
# example: [1+1e-9,1+2e-9,1+3e-9] useMathText will render the offset
# and scientific notation in mathtext

if useOffset is None:
useOffset = rcParams['axes.formatter.useoffset']
self.set_useOffset(useOffset)
self.set_useOffset(False)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it no longer uses the rcparam?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still honors the rcparam, see line 671 and 672. I (probably) should have been clearer about this in the commit message.

_useoffset and get_useOffset() now indicates if the offset has been set by either the user or the algorithm choosing a good offset; It no longer return the same value as the rcParam.

axes.formatter.useoffset = False now disables the automatic algorithm. The user can still manually set an offset
axes.formatter.useoffset = True allows the automatic algorithm to set an offset. The user can still override this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The practice in the rest of the library is that the value of the rcparams are stashed when the artists are created. In part this is so that the rcparams context manager and in part so that we can be sure of when the rcprams are consulted (many things are deffered to draw time).

self.set_useScalingFactor(False)
if rcParams['axes.formatter.useoffset'] == True:
self._offsetScaleNone=0 #Basically an enum: 0=offset 1=scale 2=none
self._offsetScaleNone=2 #Basically an enum: 0=offset 1=scale 2=none

self._usetex = rcParams['text.usetex']
if useMathText is None:
useMathText = rcParams['axes.formatter.use_mathtext']
Expand All @@ -437,19 +441,52 @@ def __init__(self, useOffset=None, useMathText=None, useLocale=None):
useLocale = rcParams['axes.formatter.use_locale']
self._useLocale = useLocale

def get_useScalingFactor(self):
return self._offsetScaleNone == 1

def set_useScalingFactor(self,val):
"""
Enable or disable the use of scaling factor in the axis.
Note that either offset or scaling can be used. Therefore,
this automatically turns off offset
"""

if val in [True, False]:
self.orderOfMagnitude=math.log10(1)
self._offsetScaleNone=1 #0=offset 1=scale 2=none
if val == False:
self._offsetScaleNone=2 #0=offset 1=scale 2=none
elif isinstance(val,numbers.Number):
self._offsetScaleNone=1 #0=offset 1=scale 2=none
self.orderOfMagnitude=.5*math.log10(val*val)
else:
raise ValueError("'val' must be a number or a boolean")

useScalingFactor = property(fget=get_useScalingFactor, fset=set_useScalingFactor)

def get_useOffset(self):
return self._useOffset
return self._offsetScaleNone == 0

def set_useOffset(self, val):
"""
Enable or disable the use of offset in the axis or enable & set the offset
at the same time. Since offset cannot be used in conjunction with a scaling
factor, the scaling factor is turned off.
"""
if val in [True, False]:
self.offset = 0
self._useOffset = val
self.offsetval = 0
self._offsetScaleNone=0 #0=offset 1=scale 2=none
if val == False:
self._offsetScaleNone=2 #0=offset 1=scale 2=none
elif isinstance(val,numbers.Number):
self._offsetScaleNone=0 #0=offset 1=scale 2=none
self.offsetval = val
else:
self._useOffset = False
self.offset = val
raise ValueError("'val' must be a number or a boolean")

useOffset = property(fget=get_useOffset, fset=set_useOffset)


def get_useLocale(self):
return self._useLocale

Expand Down Expand Up @@ -511,53 +548,65 @@ def format_data(self, value):
s = self._formatSciNotation(s)
return self.fix_minus(s)


def get_offset(self):
"""Return scientific notation, plus offset"""
if len(self.locs) == 0:
return ''
s = ''
if self.orderOfMagnitude or self.offset:
offsetStr = ''
sciNotStr = ''
if self.offset:
offsetStr = self.format_data(self.offset)
if self.offset > 0:
offsetStr = '+' + offsetStr
offsetStr = ''
sciNotStr = ''
if self._offsetScaleNone == 0: #OFFSET
if self._offsetScaleNone == 0:
offsetStr = self.format_data(self.offsetval)
if self.offsetval > 0:
offsetStr = ' +' + offsetStr
elif self._offsetScaleNone == 1: #SCALE
if self.orderOfMagnitude:
if self._usetex or self._useMathText:
sciNotStr = self.format_data(10 ** self.orderOfMagnitude)
else:
sciNotStr = '1e%d' % self.orderOfMagnitude
if self._useMathText:
if sciNotStr != '':
sciNotStr = r'\times%s' % _mathdefault(sciNotStr)
s = ''.join(('$', sciNotStr, _mathdefault(offsetStr), '$'))
elif self._usetex:
if sciNotStr != '':
sciNotStr = r'\times%s' % sciNotStr
s = ''.join(('$', sciNotStr, offsetStr, '$'))
else:
s = ''.join((sciNotStr, offsetStr))
#sciNotStr = '%g' % 10 **self.orderOfMagnitude
sciNotStr = '\u22C5 ' + self.format_data(10 ** self.orderOfMagnitude)

#Do final formatting
if self._useMathText:
if sciNotStr != '':
sciNotStr = r'\times%s' % _mathdefault(sciNotStr)
s = ''.join(('$', sciNotStr, _mathdefault(offsetStr), '$'))
elif self._usetex:
if sciNotStr != '':

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to grouping methods (user, inherited, local), the diff is somewhat hard to read.

sciNotStr = r'\times%s' % sciNotStr
s = ''.join(('$', sciNotStr, offsetStr, '$'))
else:
s = ''.join((sciNotStr, offsetStr))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lost word.


return self.fix_minus(s)

def set_locs(self, locs):

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Start reading here to follow the flow of the code (this is called at plot time).

'set the locations of the ticks'
self.locs = locs

if len(self.locs) > 0:
vmin, vmax = self.axis.get_view_interval()
d = abs(vmax - vmin)
if self._useOffset:
if self._offsetScaleNone == 0:
self._set_offset(d)

self._set_orderOfMagnitude(d)
self._set_format(vmin, vmax)

def _set_offset(self, range):
#Determine if an offset is needed and if so, set it.
#This is only needed when scaling/offset hasn't been set by the user
if self._offsetScaleNone != 2:
return

# offset of 20,001 is 20,000, for example
locs = self.locs

if locs is None or not len(locs) or range == 0:
self.offset = 0
self.set_useOffset(False)
return
vmin, vmax = sorted(self.axis.get_view_interval())
locs = np.asarray(locs)
Expand All @@ -570,21 +619,23 @@ def _set_offset(self, range):
if np.absolute(ave_oom - range_oom) >= 3: # four sig-figs
p10 = 10 ** range_oom
if ave_loc < 0:
self.offset = (math.ceil(np.max(locs) / p10) * p10)
self.set_useOffset((math.ceil(np.max(locs) / p10) * p10))
else:
self.offset = (math.floor(np.min(locs) / p10) * p10)
self.set_useOffset((math.floor(np.min(locs) / p10) * p10))
else:
self.offset = 0
self.set_useOffset(False)


def _set_orderOfMagnitude(self, range):
# If the user has set scale/offset, their input is used
# if scientific notation is to be used, find the appropriate exponent
# if using an numerical offset, find the exponent after applying the
# offset
if not self._scientific:
self.orderOfMagnitude = 0
if self._offsetScaleNone != 2 or self._scientific == False:
#User specified or unwanted
return

locs = np.absolute(self.locs)
if self.offset:
if self._offsetScaleNone == 0:
oom = math.floor(math.log10(range))
else:
if locs[0] > locs[-1]:
Expand All @@ -597,8 +648,10 @@ def _set_orderOfMagnitude(self, range):
oom = math.floor(math.log10(val))
if oom <= self._powerlimits[0]:
self.orderOfMagnitude = oom
self._offsetScaleNone=1 #Set to scale
elif oom >= self._powerlimits[1]:
self.orderOfMagnitude = oom
self._offsetScaleNone=1 #Set to scale
else:
self.orderOfMagnitude = 0

Expand All @@ -609,7 +662,7 @@ def _set_format(self, vmin, vmax):
_locs = list(self.locs) + [vmin, vmax]
else:
_locs = self.locs
locs = (np.asarray(_locs) - self.offset) / 10. ** self.orderOfMagnitude
locs = (np.asarray(_locs) - self.offsetval) / 10. ** self.orderOfMagnitude
loc_range = np.ptp(locs)
# Curvilinear coordinates can yield two identical points.
if loc_range == 0:
Expand Down Expand Up @@ -638,7 +691,14 @@ def _set_format(self, vmin, vmax):
self.format = '$%s$' % _mathdefault(self.format)

def pprint_val(self, x):
xp = (x - self.offset) / (10. ** self.orderOfMagnitude)
#Decide if we are doing offset, scale, or none
if self._offsetScaleNone == 0 :
xp = x - self.offsetval
elif self._offsetScaleNone == 1:
xp = x / (10. ** self.orderOfMagnitude)
else:
xp = x

if np.absolute(xp) < 1e-8:
xp = 0
if self._useLocale:
Expand Down