@@ -807,6 +807,26 @@ def __call__(self, x, pos=None):
807807 nearest_long (fx ))
808808
809809
810+ class LogitFormatter (Formatter ):
811+ '''Probability formatter (using Math text)'''
812+ def __call__ (self , x , pos = None ):
813+ s = ''
814+ if 0.01 <= x <= 0.99 :
815+ if x in [.01 , 0.1 , 0.5 , 0.9 , 0.99 ]:
816+ s = '{:.2f}' .format (x )
817+ elif x < 0.01 :
818+ if is_decade (x ):
819+ s = '$10^{%.0f}$' % np .log10 (x )
820+ elif x > 0.99 :
821+ if is_decade (1 - x ):
822+ s = '$1-10^{%.0f}$' % np .log10 (1 - x )
823+ return s
824+
825+ def format_data_short (self , value ):
826+ 'return a short formatted string representation of a number'
827+ return '%-12g' % value
828+
829+
810830class EngFormatter (Formatter ):
811831 """
812832 Formats axis values using engineering prefixes to represent powers of 1000,
@@ -1694,6 +1714,88 @@ def view_limits(self, vmin, vmax):
16941714 return result
16951715
16961716
1717+ class LogitLocator (Locator ):
1718+ """
1719+ Determine the tick locations for logit axes
1720+ """
1721+
1722+ def __init__ (self , minor = False ):
1723+ """
1724+ place ticks on the logit locations
1725+ """
1726+ self .minor = minor
1727+
1728+ def __call__ (self ):
1729+ 'Return the locations of the ticks'
1730+ vmin , vmax = self .axis .get_view_interval ()
1731+ return self .tick_values (vmin , vmax )
1732+
1733+ def tick_values (self , vmin , vmax ):
1734+ # dummy axis has no axes attribute
1735+ if hasattr (self .axis , 'axes' ) and self .axis .axes .name == 'polar' :
1736+ raise NotImplementedError ('Polar axis cannot be logit scaled yet' )
1737+
1738+ # what to do if a window beyond ]0, 1[ is chosen
1739+ if vmin <= 0.0 :
1740+ if self .axis is not None :
1741+ vmin = self .axis .get_minpos ()
1742+
1743+ if (vmin <= 0.0 ) or (not np .isfinite (vmin )):
1744+ raise ValueError (
1745+ "Data has no values in ]0, 1[ and therefore can not be "
1746+ "logit-scaled." )
1747+
1748+ # NOTE: for vmax, we should query a property similar to get_minpos, but
1749+ # related to the maximal, less-than-one data point. Unfortunately,
1750+ # get_minpos is defined very deep in the BBox and updated with data,
1751+ # so for now we use the trick below.
1752+ if vmax >= 1.0 :
1753+ if self .axis is not None :
1754+ vmax = 1 - self .axis .get_minpos ()
1755+
1756+ if (vmax >= 1.0 ) or (not np .isfinite (vmax )):
1757+ raise ValueError (
1758+ "Data has no values in ]0, 1[ and therefore can not be "
1759+ "logit-scaled." )
1760+
1761+ if vmax < vmin :
1762+ vmin , vmax = vmax , vmin
1763+
1764+ vmin = np .log10 (vmin / (1 - vmin ))
1765+ vmax = np .log10 (vmax / (1 - vmax ))
1766+
1767+ decade_min = np .floor (vmin )
1768+ decade_max = np .ceil (vmax )
1769+
1770+ # major ticks
1771+ if not self .minor :
1772+ ticklocs = []
1773+ if (decade_min <= - 1 ):
1774+ expo = np .arange (decade_min , min (0 , decade_max + 1 ))
1775+ ticklocs .extend (list (10 ** expo ))
1776+ if (decade_min <= 0 ) and (decade_max >= 0 ):
1777+ ticklocs .append (0.5 )
1778+ if (decade_max >= 1 ):
1779+ expo = - np .arange (max (1 , decade_min ), decade_max + 1 )
1780+ ticklocs .extend (list (1 - 10 ** expo ))
1781+
1782+ # minor ticks
1783+ else :
1784+ ticklocs = []
1785+ if (decade_min <= - 2 ):
1786+ expo = np .arange (decade_min , min (- 1 , decade_max ))
1787+ newticks = np .outer (np .arange (2 , 10 ), 10 ** expo ).ravel ()
1788+ ticklocs .extend (list (newticks ))
1789+ if (decade_min <= 0 ) and (decade_max >= 0 ):
1790+ ticklocs .extend ([0.2 , 0.3 , 0.4 , 0.6 , 0.7 , 0.8 ])
1791+ if (decade_max >= 2 ):
1792+ expo = - np .arange (max (2 , decade_min ), decade_max + 1 )
1793+ newticks = 1 - np .outer (np .arange (2 , 10 ), 10 ** expo ).ravel ()
1794+ ticklocs .extend (list (newticks ))
1795+
1796+ return self .raise_if_exceeds (np .array (ticklocs ))
1797+
1798+
16971799class AutoLocator (MaxNLocator ):
16981800 def __init__ (self ):
16991801 MaxNLocator .__init__ (self , nbins = 9 , steps = [1 , 2 , 5 , 10 ])
0 commit comments