1919
2020'''
2121
22+ import logging
2223import warnings
2324
2425import numpy as np
3940import matplotlib ._constrained_layout as constrained_layout
4041from matplotlib import docstring
4142
43+ _log = logging .getLogger (__name__ )
44+
4245make_axes_kw_doc = '''
4346
4447 ============= ====================================================
@@ -212,6 +215,63 @@ def _set_ticks_on_axis_warn(*args, **kw):
212215 warnings .warn ("Use the colorbar set_ticks() method instead." )
213216
214217
218+ class _ColorbarAutoLocator (ticker .MaxNLocator ):
219+ """
220+ AutoLocator for Colorbar
221+
222+ This locator is just a `.MaxNLocator` except the min and max are
223+ clipped by the norm's min and max (i.e. vmin/vmax from the
224+ image/pcolor/contour object). This is necessary so ticks don't
225+ extrude into the "extend regions".
226+ """
227+
228+ def __init__ (self , colorbar ):
229+ """
230+ This ticker needs to know the *colorbar* so that it can access
231+ its *vmin* and *vmax*. Otherwise it is the same as
232+ `~.ticker.AutoLocator`.
233+ """
234+
235+ self ._colorbar = colorbar
236+ nbins = 'auto'
237+ steps = [1 , 2 , 2.5 , 5 , 10 ]
238+ ticker .MaxNLocator .__init__ (self , nbins = nbins , steps = steps )
239+
240+ def tick_values (self , vmin , vmax ):
241+ vmin = max (vmin , self ._colorbar .norm .vmin )
242+ vmax = min (vmax , self ._colorbar .norm .vmax )
243+ return ticker .MaxNLocator .tick_values (self , vmin , vmax )
244+
245+
246+ class _ColorbarLogLocator (ticker .LogLocator ):
247+ """
248+ LogLocator for Colorbarbar
249+
250+ This locator is just a `.LogLocator` except the min and max are
251+ clipped by the norm's min and max (i.e. vmin/vmax from the
252+ image/pcolor/contour object). This is necessary so ticks don't
253+ extrude into the "extend regions".
254+
255+ """
256+ def __init__ (self , colorbar , * args , ** kwargs ):
257+ """
258+ _ColorbarLogLocator(colorbar, *args, **kwargs)
259+
260+ This ticker needs to know the *colorbar* so that it can access
261+ its *vmin* and *vmax*. Otherwise it is the same as
262+ `~.ticker.LogLocator`. The ``*args`` and ``**kwargs`` are the
263+ same as `~.ticker.LogLocator`.
264+ """
265+ self ._colorbar = colorbar
266+ ticker .LogLocator .__init__ (self , * args , ** kwargs )
267+
268+ def tick_values (self , vmin , vmax ):
269+ vmin = self ._colorbar .norm .vmin
270+ vmax = self ._colorbar .norm .vmax
271+ ticks = ticker .LogLocator .tick_values (self , vmin , vmax )
272+ return ticks [(ticks >= vmin ) & (ticks <= vmax )]
273+
274+
215275class ColorbarBase (cm .ScalarMappable ):
216276 '''
217277 Draw a colorbar in an existing axes.
@@ -341,8 +401,15 @@ def draw_all(self):
341401 and do all the drawing.
342402 '''
343403
404+ # sets self._boundaries and self._values in real data units.
405+ # takes into account extend values:
344406 self ._process_values ()
407+ # sets self.vmin and vmax in data units, but just for
408+ # the part of the colorbar that is not part of the extend
409+ # patch:
345410 self ._find_range ()
411+ # returns the X and Y mesh, *but* this was/is in normalized
412+ # units:
346413 X , Y = self ._mesh ()
347414 C = self ._values [:, np .newaxis ]
348415 self ._config_axes (X , Y )
@@ -351,35 +418,105 @@ def draw_all(self):
351418
352419 def config_axis (self ):
353420 ax = self .ax
421+ if (isinstance (self .norm , colors .LogNorm )
422+ and self ._use_auto_colorbar_locator ()):
423+ # *both* axes are made log so that determining the
424+ # mid point is easier.
425+ ax .set_xscale ('log' )
426+ ax .set_yscale ('log' )
427+
354428 if self .orientation == 'vertical' :
355- ax .xaxis .set_ticks ([])
356- # location is either one of 'bottom' or 'top'
357- ax .yaxis .set_label_position (self .ticklocation )
358- ax .yaxis .set_ticks_position (self .ticklocation )
429+ long_axis , short_axis = ax .yaxis , ax .xaxis
359430 else :
360- ax .yaxis .set_ticks ([])
361- # location is either one of 'left' or 'right'
362- ax .xaxis .set_label_position (self .ticklocation )
363- ax .xaxis .set_ticks_position (self .ticklocation )
431+ long_axis , short_axis = ax .xaxis , ax .yaxis
432+
433+ long_axis .set_label_position (self .ticklocation )
434+ long_axis .set_ticks_position (self .ticklocation )
435+ short_axis .set_ticks ([])
436+ short_axis .set_ticks ([], minor = True )
364437
365438 self ._set_label ()
366439
440+ def _get_ticker_locator_formatter (self ):
441+ """
442+ This code looks at the norm being used by the colorbar
443+ and decides what locator and formatter to use. If ``locator`` has
444+ already been set by hand, it just returns
445+ ``self.locator, self.formatter``.
446+ """
447+ locator = self .locator
448+ formatter = self .formatter
449+ if locator is None :
450+ if self .boundaries is None :
451+ if isinstance (self .norm , colors .NoNorm ):
452+ nv = len (self ._values )
453+ base = 1 + int (nv / 10 )
454+ locator = ticker .IndexLocator (base = base , offset = 0 )
455+ elif isinstance (self .norm , colors .BoundaryNorm ):
456+ b = self .norm .boundaries
457+ locator = ticker .FixedLocator (b , nbins = 10 )
458+ elif isinstance (self .norm , colors .LogNorm ):
459+ locator = _ColorbarLogLocator (self )
460+ elif isinstance (self .norm , colors .SymLogNorm ):
461+ # The subs setting here should be replaced
462+ # by logic in the locator.
463+ locator = ticker .SymmetricalLogLocator (
464+ subs = np .arange (1 , 10 ),
465+ linthresh = self .norm .linthresh ,
466+ base = 10 )
467+ else :
468+ if mpl .rcParams ['_internal.classic_mode' ]:
469+ locator = ticker .MaxNLocator ()
470+ else :
471+ locator = _ColorbarAutoLocator (self )
472+ else :
473+ b = self ._boundaries [self ._inside ]
474+ locator = ticker .FixedLocator (b , nbins = 10 )
475+ _log .debug ('locator: %r' , locator )
476+ return locator , formatter
477+
478+ def _use_auto_colorbar_locator (self ):
479+ """
480+ Return if we should use an adjustable tick locator or a fixed
481+ one. (check is used twice so factored out here...)
482+ """
483+ return (self .boundaries is None
484+ and self .values is None
485+ and ((type (self .norm ) == colors .Normalize )
486+ or (type (self .norm ) == colors .LogNorm )))
487+
367488 def update_ticks (self ):
368489 """
369490 Force the update of the ticks and ticklabels. This must be
370491 called whenever the tick locator and/or tick formatter changes.
371492 """
372493 ax = self .ax
373- ticks , ticklabels , offset_string = self ._ticker ()
374- if self .orientation == 'vertical' :
375- ax .yaxis .set_ticks (ticks )
376- ax .set_yticklabels (ticklabels )
377- ax .yaxis .get_major_formatter ().set_offset_string (offset_string )
494+ # get the locator and formatter. Defaults to
495+ # self.locator if not None..
496+ locator , formatter = self ._get_ticker_locator_formatter ()
378497
498+ if self .orientation == 'vertical' :
499+ long_axis , short_axis = ax .yaxis , ax .xaxis
379500 else :
380- ax .xaxis .set_ticks (ticks )
381- ax .set_xticklabels (ticklabels )
382- ax .xaxis .get_major_formatter ().set_offset_string (offset_string )
501+ long_axis , short_axis = ax .xaxis , ax .yaxis
502+
503+ if self ._use_auto_colorbar_locator ():
504+ _log .debug ('Using auto colorbar locator on colorbar' )
505+ _log .debug ('locator: %r' , locator )
506+ long_axis .set_major_locator (locator )
507+ long_axis .set_major_formatter (formatter )
508+ if type (self .norm ) == colors .LogNorm :
509+ long_axis .set_minor_locator (_ColorbarLogLocator (self ,
510+ base = 10. , subs = 'auto' ))
511+ long_axis .set_minor_formatter (
512+ ticker .LogFormatter ()
513+ )
514+ else :
515+ _log .debug ('Using fixed locator on colorbar' )
516+ ticks , ticklabels , offset_string = self ._ticker (locator , formatter )
517+ long_axis .set_ticks (ticks )
518+ long_axis .set_ticklabels (ticklabels )
519+ long_axis .get_major_formatter ().set_offset_string (offset_string )
383520
384521 def set_ticks (self , ticks , update_ticks = True ):
385522 """
@@ -515,6 +652,7 @@ def _add_solids(self, X, Y, C):
515652 # since the axes object should already have hold set.
516653 _hold = self .ax ._hold
517654 self .ax ._hold = True
655+ _log .debug ('Setting pcolormesh' )
518656 col = self .ax .pcolormesh (* args , ** kw )
519657 self .ax ._hold = _hold
520658 #self.add_observer(col) # We should observe, not be observed...
@@ -568,39 +706,11 @@ def add_lines(self, levels, colors, linewidths, erase=True):
568706 self .ax .add_collection (col )
569707 self .stale = True
570708
571- def _ticker (self ):
709+ def _ticker (self , locator , formatter ):
572710 '''
573711 Return the sequence of ticks (colorbar data locations),
574712 ticklabels (strings), and the corresponding offset string.
575713 '''
576- locator = self .locator
577- formatter = self .formatter
578- if locator is None :
579- if self .boundaries is None :
580- if isinstance (self .norm , colors .NoNorm ):
581- nv = len (self ._values )
582- base = 1 + int (nv / 10 )
583- locator = ticker .IndexLocator (base = base , offset = 0 )
584- elif isinstance (self .norm , colors .BoundaryNorm ):
585- b = self .norm .boundaries
586- locator = ticker .FixedLocator (b , nbins = 10 )
587- elif isinstance (self .norm , colors .LogNorm ):
588- locator = ticker .LogLocator (subs = 'all' )
589- elif isinstance (self .norm , colors .SymLogNorm ):
590- # The subs setting here should be replaced
591- # by logic in the locator.
592- locator = ticker .SymmetricalLogLocator (
593- subs = np .arange (1 , 10 ),
594- linthresh = self .norm .linthresh ,
595- base = 10 )
596- else :
597- if mpl .rcParams ['_internal.classic_mode' ]:
598- locator = ticker .MaxNLocator ()
599- else :
600- locator = ticker .AutoLocator ()
601- else :
602- b = self ._boundaries [self ._inside ]
603- locator = ticker .FixedLocator (b , nbins = 10 )
604714 if isinstance (self .norm , colors .NoNorm ) and self .boundaries is None :
605715 intv = self ._values [0 ], self ._values [- 1 ]
606716 else :
@@ -840,17 +950,29 @@ def _mesh(self):
840950 transposition for a horizontal colorbar are done outside
841951 this function.
842952 '''
953+ # if boundaries and values are None, then we can go ahead and
954+ # scale this up for Auto tick location. Otherwise we
955+ # want to keep normalized between 0 and 1 and use manual tick
956+ # locations.
957+
843958 x = np .array ([0.0 , 1.0 ])
844959 if self .spacing == 'uniform' :
845960 y = self ._uniform_y (self ._central_N ())
846961 else :
847962 y = self ._proportional_y ()
963+ if self ._use_auto_colorbar_locator ():
964+ y = self .norm .inverse (y )
965+ x = self .norm .inverse (x )
848966 self ._y = y
849967 X , Y = np .meshgrid (x , y )
968+ if self ._use_auto_colorbar_locator ():
969+ xmid = self .norm .inverse (0.5 )
970+ else :
971+ xmid = 0.5
850972 if self ._extend_lower () and not self .extendrect :
851- X [0 , :] = 0.5
973+ X [0 , :] = xmid
852974 if self ._extend_upper () and not self .extendrect :
853- X [- 1 , :] = 0.5
975+ X [- 1 , :] = xmid
854976 return X , Y
855977
856978 def _locate (self , x ):
0 commit comments