2424import six
2525from six .moves import xrange , zip
2626
27+ import logging
2728import warnings
2829
2930import numpy as np
4445import matplotlib ._constrained_layout as constrained_layout
4546from matplotlib import docstring
4647
48+ _log = logging .getLogger (__name__ )
49+
4750make_axes_kw_doc = '''
4851
4952 ============= ====================================================
@@ -217,6 +220,65 @@ def _set_ticks_on_axis_warn(*args, **kw):
217220 warnings .warn ("Use the colorbar set_ticks() method instead." )
218221
219222
223+ class _ColorbarAutoLocator (ticker .MaxNLocator ):
224+ """
225+ AutoLocator for Colorbar
226+
227+ This locator is just a `.MaxNLocator` except the min and max are
228+ clipped by the norm's min and max (i.e. vmin/vmax from the
229+ image/pcolor/contour object). This is necessary so ticks don't
230+ extrude into the "extend regions".
231+ """
232+
233+ def __init__ (self , colorbar ):
234+ """
235+ _ColorbarAutoLocator(colorbar)
236+
237+ This ticker needs to know the *colorbar* so that it can access
238+ its *vmin* and *vmax*. Otherwise it is the same as
239+ `~.ticker.AutoLocator`.
240+ """
241+
242+ self ._colorbar = colorbar
243+ nbins = 'auto'
244+ steps = [1 , 2 , 2.5 , 5 , 10 ]
245+ ticker .MaxNLocator .__init__ (self , nbins = nbins , steps = steps )
246+
247+ def tick_values (self , vmin , vmax ):
248+ vmin = max (vmin , self ._colorbar .norm .vmin )
249+ vmax = min (vmax , self ._colorbar .norm .vmax )
250+ return ticker .MaxNLocator .tick_values (self , vmin , vmax )
251+
252+
253+ class _ColorbarLogLocator (ticker .LogLocator ):
254+ """
255+ LogLocator for Colorbarbar
256+
257+ This locator is just a `.LogLocator` except the min and max are
258+ clipped by the norm's min and max (i.e. vmin/vmax from the
259+ image/pcolor/contour object). This is necessary so ticks don't
260+ extrude into the "extend regions".
261+
262+ """
263+ def __init__ (self , colorbar , * args , ** kwargs ):
264+ """
265+ _ColorbarLogLocator(colorbar, *args, **kwargs)
266+
267+ This ticker needs to know the *colorbar* so that it can access
268+ its *vmin* and *vmax*. Otherwise it is the same as
269+ `~.ticker.LogLocator`. The ``*args`` and ``**kwargs`` are the
270+ same as `~.ticker.LogLocator`.
271+ """
272+ self ._colorbar = colorbar
273+ ticker .LogLocator .__init__ (self , * args , ** kwargs )
274+
275+ def tick_values (self , vmin , vmax ):
276+ vmin = self ._colorbar .norm .vmin
277+ vmax = self ._colorbar .norm .vmax
278+ ticks = ticker .LogLocator .tick_values (self , vmin , vmax )
279+ return ticks [(ticks >= vmin ) & (ticks <= vmax )]
280+
281+
220282class ColorbarBase (cm .ScalarMappable ):
221283 '''
222284 Draw a colorbar in an existing axes.
@@ -346,8 +408,15 @@ def draw_all(self):
346408 and do all the drawing.
347409 '''
348410
411+ # sets self._boundaries and self._values in real data units.
412+ # takes into account extend values:
349413 self ._process_values ()
414+ # sets self.vmin and vmax in data units, but just for
415+ # the part of the colorbar that is not part of the extend
416+ # patch:
350417 self ._find_range ()
418+ # returns the X and Y mesh, *but* this was/is in normalized
419+ # units:
351420 X , Y = self ._mesh ()
352421 C = self ._values [:, np .newaxis ]
353422 self ._config_axes (X , Y )
@@ -356,35 +425,103 @@ def draw_all(self):
356425
357426 def config_axis (self ):
358427 ax = self .ax
428+ if (isinstance (self .norm , colors .LogNorm )
429+ and self ._use_auto_colorbar_locator ()):
430+ # *both* axes are made log so that determining the
431+ # mid point is easier.
432+ ax .set_xscale ('log' )
433+ ax .set_yscale ('log' )
434+
359435 if self .orientation == 'vertical' :
360- ax .xaxis .set_ticks ([])
361- # location is either one of 'bottom' or 'top'
362- ax .yaxis .set_label_position (self .ticklocation )
363- ax .yaxis .set_ticks_position (self .ticklocation )
436+ long_axis , short_axis = ax .yaxis , ax .xaxis
364437 else :
365- ax .yaxis .set_ticks ([])
366- # location is either one of 'left' or 'right'
367- ax .xaxis .set_label_position (self .ticklocation )
368- ax .xaxis .set_ticks_position (self .ticklocation )
438+ long_axis , short_axis = ax .xaxis , ax .yaxis
439+
440+ long_axis .set_label_position (self .ticklocation )
441+ long_axis .set_ticks_position (self .ticklocation )
442+ short_axis .set_ticks ([])
443+ short_axis .set_ticks ([], minor = True )
369444
370445 self ._set_label ()
371446
447+ def _get_ticker_locator_formatter (self ):
448+ """
449+ This code looks at the norm being used by the colorbar
450+ and decides what locator and formatter to use. If ``locator`` has
451+ already been set by hand, it just returns
452+ ``self.locator, self.formatter``.
453+ """
454+ locator = self .locator
455+ formatter = self .formatter
456+ if locator is None :
457+ if self .boundaries is None :
458+ if isinstance (self .norm , colors .NoNorm ):
459+ nv = len (self ._values )
460+ base = 1 + int (nv / 10 )
461+ locator = ticker .IndexLocator (base = base , offset = 0 )
462+ elif isinstance (self .norm , colors .BoundaryNorm ):
463+ b = self .norm .boundaries
464+ locator = ticker .FixedLocator (b , nbins = 10 )
465+ elif isinstance (self .norm , colors .LogNorm ):
466+ locator = _ColorbarLogLocator (self )
467+ elif isinstance (self .norm , colors .SymLogNorm ):
468+ # The subs setting here should be replaced
469+ # by logic in the locator.
470+ locator = ticker .SymmetricalLogLocator (
471+ subs = np .arange (1 , 10 ),
472+ linthresh = self .norm .linthresh ,
473+ base = 10 )
474+ else :
475+ if mpl .rcParams ['_internal.classic_mode' ]:
476+ locator = ticker .MaxNLocator ()
477+ else :
478+ locator = _ColorbarAutoLocator (self )
479+ else :
480+ b = self ._boundaries [self ._inside ]
481+ locator = ticker .FixedLocator (b , nbins = 10 )
482+ _log .debug ('locator: %r' , locator )
483+ return locator , formatter
484+
485+ def _use_auto_colorbar_locator (self ):
486+ """
487+ Return if we should use an adjustable tick locator or a fixed
488+ one. (check is used twice so factored out here...)
489+ """
490+ return (self .boundaries is None
491+ and self .values is None
492+ and ((type (self .norm ) == colors .Normalize )
493+ or (type (self .norm ) == colors .LogNorm )))
494+
372495 def update_ticks (self ):
373496 """
374497 Force the update of the ticks and ticklabels. This must be
375498 called whenever the tick locator and/or tick formatter changes.
376499 """
377500 ax = self .ax
378- ticks , ticklabels , offset_string = self ._ticker ()
379- if self .orientation == 'vertical' :
380- ax .yaxis .set_ticks (ticks )
381- ax .set_yticklabels (ticklabels )
382- ax .yaxis .get_major_formatter ().set_offset_string (offset_string )
501+ # get the locator and formatter. Defaults to
502+ # self.locator if not None..
503+ locator , formatter = self ._get_ticker_locator_formatter ()
383504
505+ if self .orientation == 'vertical' :
506+ long_axis , short_axis = ax .yaxis , ax .xaxis
384507 else :
385- ax .xaxis .set_ticks (ticks )
386- ax .set_xticklabels (ticklabels )
387- ax .xaxis .get_major_formatter ().set_offset_string (offset_string )
508+ long_axis , short_axis = ax .xaxis , ax .yaxis
509+
510+ if self ._use_auto_colorbar_locator ():
511+ _log .debug ('Using auto colorbar locator on colorbar' )
512+ _log .debug ('locator: %r' , locator )
513+ long_axis .set_major_locator (locator )
514+ long_axis .set_major_formatter (formatter )
515+ if type (self .norm ) == colors .LogNorm :
516+ long_axis .set_minor_locator (_ColorbarLogLocator (self ,
517+ base = 10. , subs = 'auto' ))
518+ long_axis .set_minor_formatter (ticker .NullFormatter ())
519+ else :
520+ _log .debug ('Using fixed locator on colorbar' )
521+ ticks , ticklabels , offset_string = self ._ticker (locator , formatter )
522+ long_axis .set_ticks (ticks )
523+ long_axis .set_ticklabels (ticklabels )
524+ long_axis .get_major_formatter ().set_offset_string (offset_string )
388525
389526 def set_ticks (self , ticks , update_ticks = True ):
390527 """
@@ -520,6 +657,7 @@ def _add_solids(self, X, Y, C):
520657 # since the axes object should already have hold set.
521658 _hold = self .ax ._hold
522659 self .ax ._hold = True
660+ _log .debug ('Setting pcolormesh' )
523661 col = self .ax .pcolormesh (* args , ** kw )
524662 self .ax ._hold = _hold
525663 #self.add_observer(col) # We should observe, not be observed...
@@ -573,39 +711,11 @@ def add_lines(self, levels, colors, linewidths, erase=True):
573711 self .ax .add_collection (col )
574712 self .stale = True
575713
576- def _ticker (self ):
714+ def _ticker (self , locator , formatter ):
577715 '''
578716 Return the sequence of ticks (colorbar data locations),
579717 ticklabels (strings), and the corresponding offset string.
580718 '''
581- locator = self .locator
582- formatter = self .formatter
583- if locator is None :
584- if self .boundaries is None :
585- if isinstance (self .norm , colors .NoNorm ):
586- nv = len (self ._values )
587- base = 1 + int (nv / 10 )
588- locator = ticker .IndexLocator (base = base , offset = 0 )
589- elif isinstance (self .norm , colors .BoundaryNorm ):
590- b = self .norm .boundaries
591- locator = ticker .FixedLocator (b , nbins = 10 )
592- elif isinstance (self .norm , colors .LogNorm ):
593- locator = ticker .LogLocator (subs = 'all' )
594- elif isinstance (self .norm , colors .SymLogNorm ):
595- # The subs setting here should be replaced
596- # by logic in the locator.
597- locator = ticker .SymmetricalLogLocator (
598- subs = np .arange (1 , 10 ),
599- linthresh = self .norm .linthresh ,
600- base = 10 )
601- else :
602- if mpl .rcParams ['_internal.classic_mode' ]:
603- locator = ticker .MaxNLocator ()
604- else :
605- locator = ticker .AutoLocator ()
606- else :
607- b = self ._boundaries [self ._inside ]
608- locator = ticker .FixedLocator (b , nbins = 10 )
609719 if isinstance (self .norm , colors .NoNorm ) and self .boundaries is None :
610720 intv = self ._values [0 ], self ._values [- 1 ]
611721 else :
@@ -845,17 +955,29 @@ def _mesh(self):
845955 transposition for a horizontal colorbar are done outside
846956 this function.
847957 '''
958+ # if boundaries and values are None, then we can go ahead and
959+ # scale this up for Auto tick location. Otherwise we
960+ # want to keep normalized between 0 and 1 and use manual tick
961+ # locations.
962+
848963 x = np .array ([0.0 , 1.0 ])
849964 if self .spacing == 'uniform' :
850965 y = self ._uniform_y (self ._central_N ())
851966 else :
852967 y = self ._proportional_y ()
968+ if self ._use_auto_colorbar_locator ():
969+ y = self .norm .inverse (y )
970+ x = self .norm .inverse (x )
853971 self ._y = y
854972 X , Y = np .meshgrid (x , y )
973+ if self ._use_auto_colorbar_locator ():
974+ xmid = self .norm .inverse (0.5 )
975+ else :
976+ xmid = 0.5
855977 if self ._extend_lower () and not self .extendrect :
856- X [0 , :] = 0.5
978+ X [0 , :] = xmid
857979 if self ._extend_upper () and not self .extendrect :
858- X [- 1 , :] = 0.5
980+ X [- 1 , :] = xmid
859981 return X , Y
860982
861983 def _locate (self , x ):
0 commit comments