@@ -2502,64 +2502,116 @@ def view_limits(self, vmin, vmax):
25022502 return result
25032503
25042504
2505- class LogitLocator (Locator ):
2505+ class LogitLocator (MaxNLocator ):
25062506 """
25072507 Determine the tick locations for logit axes
25082508 """
25092509
2510- def __init__ (self , minor = False ):
2511- """Place ticks on the logit locations."""
2512- self .minor = minor
2510+ def __init__ (self , minor = False , * , nbins = "auto" ):
2511+ """
2512+ Place ticks on the logit locations
2513+
2514+ Parameters
2515+ ----------
2516+ nbins : int or 'auto', optional
2517+ Number of ticks. Only used if minor is False.
2518+ minor : bool, default: False
2519+ Indicate if this locator is for minor ticks or not.
2520+ """
2521+
2522+ self ._minor = minor
2523+ MaxNLocator .__init__ (self , nbins = nbins , steps = [1 , 2 , 5 , 10 ])
25132524
2514- def set_params (self , minor = None ):
2525+ def set_params (self , minor = None , ** kwargs ):
25152526 """Set parameters within this locator."""
25162527 if minor is not None :
2517- self .minor = minor
2528+ self ._minor = minor
2529+ MaxNLocator .set_params (self , ** kwargs )
25182530
2519- def __call__ (self ):
2520- """Return the locations of the ticks."""
2521- vmin , vmax = self .axis .get_view_interval ()
2522- return self .tick_values (vmin , vmax )
2531+ @property
2532+ def minor (self ):
2533+ return self ._minor
2534+
2535+ @minor .setter
2536+ def minor (self , value ):
2537+ self .set_params (minor = value )
25232538
25242539 def tick_values (self , vmin , vmax ):
25252540 # dummy axis has no axes attribute
2526- if hasattr (self .axis , ' axes' ) and self .axis .axes .name == ' polar' :
2527- raise NotImplementedError (' Polar axis cannot be logit scaled yet' )
2541+ if hasattr (self .axis , " axes" ) and self .axis .axes .name == " polar" :
2542+ raise NotImplementedError (" Polar axis cannot be logit scaled yet" )
25282543
2529- vmin , vmax = self .nonsingular (vmin , vmax )
2530- vmin = np .log10 (vmin / (1 - vmin ))
2531- vmax = np .log10 (vmax / (1 - vmax ))
2532-
2533- decade_min = np .floor (vmin )
2534- decade_max = np .ceil (vmax )
2535-
2536- # major ticks
2537- if not self .minor :
2538- ticklocs = []
2539- if decade_min <= - 1 :
2540- expo = np .arange (decade_min , min (0 , decade_max + 1 ))
2541- ticklocs .extend (10 ** expo )
2542- if decade_min <= 0 <= decade_max :
2543- ticklocs .append (0.5 )
2544- if decade_max >= 1 :
2545- expo = - np .arange (max (1 , decade_min ), decade_max + 1 )
2546- ticklocs .extend (1 - 10 ** expo )
2547-
2548- # minor ticks
2544+ if self ._nbins == "auto" :
2545+ if self .axis is not None :
2546+ nbins = self .axis .get_tick_space ()
2547+ if nbins < 2 :
2548+ nbins = 2
2549+ else :
2550+ nbins = 9
25492551 else :
2550- ticklocs = []
2551- if decade_min <= - 2 :
2552- expo = np .arange (decade_min , min (- 1 , decade_max ))
2553- newticks = np .outer (np .arange (2 , 10 ), 10 ** expo ).ravel ()
2554- ticklocs .extend (newticks )
2555- if decade_min <= 0 <= decade_max :
2556- ticklocs .extend ([0.2 , 0.3 , 0.4 , 0.6 , 0.7 , 0.8 ])
2557- if decade_max >= 2 :
2558- expo = - np .arange (max (2 , decade_min ), decade_max + 1 )
2559- newticks = 1 - np .outer (np .arange (2 , 10 ), 10 ** expo ).ravel ()
2560- ticklocs .extend (newticks )
2552+ nbins = self ._nbins
25612553
2562- return self .raise_if_exceeds (np .array (ticklocs ))
2554+ # We define ideal ticks with their index:
2555+ # linscale: ... 1e-3 1e-2 1e-1 1/2 1-1e-1 1-1e-2 1-1e-3 ...
2556+ # b-scale : ... -3 -2 -1 0 1 2 3 ...
2557+ def ideal_ticks (x ):
2558+ return 10 ** x if x < 0 else 1 - (10 ** (- x )) if x > 0 else 1 / 2
2559+
2560+ vmin , vmax = self .nonsingular (vmin , vmax )
2561+ binf = int (
2562+ np .floor (np .log10 (vmin ))
2563+ if vmin < 0.5
2564+ else 0
2565+ if vmin < 0.9
2566+ else - np .ceil (np .log10 (1 - vmin ))
2567+ )
2568+ bsup = int (
2569+ np .ceil (np .log10 (vmax ))
2570+ if vmax <= 0.5
2571+ else 1
2572+ if vmax <= 0.9
2573+ else - np .floor (np .log10 (1 - vmax ))
2574+ )
2575+ numideal = bsup - binf - 1
2576+ if numideal >= 2 :
2577+ # have 2 or more wanted ideal ticks, so use them as major ticks
2578+ if numideal > nbins :
2579+ # to many ideal ticks, subsampling ideals for major ticks, and
2580+ # take others for minor ticks
2581+ subsampling_factor = math .ceil (numideal / nbins )
2582+ if self ._minor :
2583+ ticklocs = [
2584+ ideal_ticks (b )
2585+ for b in range (binf , bsup + 1 )
2586+ if (b % subsampling_factor ) != 0
2587+ ]
2588+ else :
2589+ ticklocs = [
2590+ ideal_ticks (b )
2591+ for b in range (binf , bsup + 1 )
2592+ if (b % subsampling_factor ) == 0
2593+ ]
2594+ return self .raise_if_exceeds (np .array (ticklocs ))
2595+ if self ._minor :
2596+ ticklocs = []
2597+ for b in range (binf , bsup ):
2598+ if b < - 1 :
2599+ ticklocs .extend (np .arange (2 , 10 ) * 10 ** b )
2600+ elif b == - 1 :
2601+ ticklocs .extend (np .arange (2 , 5 ) / 10 )
2602+ elif b == 0 :
2603+ ticklocs .extend (np .arange (6 , 9 ) / 10 )
2604+ else :
2605+ ticklocs .extend (
2606+ 1 - np .arange (2 , 10 )[::- 1 ] * 10 ** (- b - 1 )
2607+ )
2608+ return self .raise_if_exceeds (np .array (ticklocs ))
2609+ ticklocs = [ideal_ticks (b ) for b in range (binf , bsup + 1 )]
2610+ return self .raise_if_exceeds (np .array (ticklocs ))
2611+ # the scale is zoomed so same ticks as linear scale can be used
2612+ if self ._minor :
2613+ return []
2614+ return MaxNLocator .tick_values (self , vmin , vmax )
25632615
25642616 def nonsingular (self , vmin , vmax ):
25652617 initial_range = (1e-7 , 1 - 1e-7 )
0 commit comments