24
24
import six
25
25
from six .moves import xrange , zip
26
26
27
+ import logging
27
28
import warnings
28
29
29
30
import numpy as np
44
45
import matplotlib ._constrained_layout as constrained_layout
45
46
from matplotlib import docstring
46
47
48
+ _log = logging .getLogger (__name__ )
49
+
47
50
make_axes_kw_doc = '''
48
51
49
52
============= ====================================================
@@ -217,6 +220,65 @@ def _set_ticks_on_axis_warn(*args, **kw):
217
220
warnings .warn ("Use the colorbar set_ticks() method instead." )
218
221
219
222
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
+
220
282
class ColorbarBase (cm .ScalarMappable ):
221
283
'''
222
284
Draw a colorbar in an existing axes.
@@ -346,8 +408,15 @@ def draw_all(self):
346
408
and do all the drawing.
347
409
'''
348
410
411
+ # sets self._boundaries and self._values in real data units.
412
+ # takes into account extend values:
349
413
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:
350
417
self ._find_range ()
418
+ # returns the X and Y mesh, *but* this was/is in normalized
419
+ # units:
351
420
X , Y = self ._mesh ()
352
421
C = self ._values [:, np .newaxis ]
353
422
self ._config_axes (X , Y )
@@ -356,35 +425,103 @@ def draw_all(self):
356
425
357
426
def config_axis (self ):
358
427
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
+
359
435
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
364
437
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 )
369
444
370
445
self ._set_label ()
371
446
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
+
372
495
def update_ticks (self ):
373
496
"""
374
497
Force the update of the ticks and ticklabels. This must be
375
498
called whenever the tick locator and/or tick formatter changes.
376
499
"""
377
500
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 ()
383
504
505
+ if self .orientation == 'vertical' :
506
+ long_axis , short_axis = ax .yaxis , ax .xaxis
384
507
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 )
388
525
389
526
def set_ticks (self , ticks , update_ticks = True ):
390
527
"""
@@ -520,6 +657,7 @@ def _add_solids(self, X, Y, C):
520
657
# since the axes object should already have hold set.
521
658
_hold = self .ax ._hold
522
659
self .ax ._hold = True
660
+ _log .debug ('Setting pcolormesh' )
523
661
col = self .ax .pcolormesh (* args , ** kw )
524
662
self .ax ._hold = _hold
525
663
#self.add_observer(col) # We should observe, not be observed...
@@ -573,39 +711,11 @@ def add_lines(self, levels, colors, linewidths, erase=True):
573
711
self .ax .add_collection (col )
574
712
self .stale = True
575
713
576
- def _ticker (self ):
714
+ def _ticker (self , locator , formatter ):
577
715
'''
578
716
Return the sequence of ticks (colorbar data locations),
579
717
ticklabels (strings), and the corresponding offset string.
580
718
'''
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 )
609
719
if isinstance (self .norm , colors .NoNorm ) and self .boundaries is None :
610
720
intv = self ._values [0 ], self ._values [- 1 ]
611
721
else :
@@ -845,17 +955,29 @@ def _mesh(self):
845
955
transposition for a horizontal colorbar are done outside
846
956
this function.
847
957
'''
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
+
848
963
x = np .array ([0.0 , 1.0 ])
849
964
if self .spacing == 'uniform' :
850
965
y = self ._uniform_y (self ._central_N ())
851
966
else :
852
967
y = self ._proportional_y ()
968
+ if self ._use_auto_colorbar_locator ():
969
+ y = self .norm .inverse (y )
970
+ x = self .norm .inverse (x )
853
971
self ._y = y
854
972
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
855
977
if self ._extend_lower () and not self .extendrect :
856
- X [0 , :] = 0.5
978
+ X [0 , :] = xmid
857
979
if self ._extend_upper () and not self .extendrect :
858
- X [- 1 , :] = 0.5
980
+ X [- 1 , :] = xmid
859
981
return X , Y
860
982
861
983
def _locate (self , x ):
0 commit comments