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,108 @@ 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 )
383
-
501
+ # get the locator and formatter. Defaults to
502
+ # self.locator if not None..
503
+ locator , formatter = self ._get_ticker_locator_formatter ()
504
+
505
+ if self ._use_auto_colorbar_locator ():
506
+ _log .debug ('Using auto colorbar locator on colorbar' )
507
+ _log .debug ('locator: %r' , locator )
508
+ if self .orientation == 'vertical' :
509
+ long_axis , short_axis = ax .yaxis , ax .xaxis
510
+ else :
511
+ long_axis , short_axis = ax .xaxis , ax .yaxis
512
+ long_axis .set_major_locator (locator )
513
+ long_axis .set_major_formatter (formatter )
514
+ if type (self .norm ) == colors .LogNorm :
515
+ long_axis .set_minor_locator (_ColorbarLogLocator (self ,
516
+ base = 10. , subs = 'auto' ))
517
+ long_axis .set_minor_formatter (ticker .NullFormatter ())
384
518
else :
385
- ax .xaxis .set_ticks (ticks )
386
- ax .set_xticklabels (ticklabels )
387
- ax .xaxis .get_major_formatter ().set_offset_string (offset_string )
519
+ _log .debug ('Using fixed locator on colorbar' )
520
+ ticks , ticklabels , offset_string = self ._ticker (locator , formatter )
521
+ if self .orientation == 'vertical' :
522
+ ax .yaxis .set_ticks (ticks )
523
+ ax .set_yticklabels (ticklabels )
524
+ ax .yaxis .get_major_formatter ().set_offset_string (offset_string )
525
+
526
+ else :
527
+ ax .xaxis .set_ticks (ticks )
528
+ ax .set_xticklabels (ticklabels )
529
+ ax .xaxis .get_major_formatter ().set_offset_string (offset_string )
388
530
389
531
def set_ticks (self , ticks , update_ticks = True ):
390
532
"""
@@ -520,6 +662,7 @@ def _add_solids(self, X, Y, C):
520
662
# since the axes object should already have hold set.
521
663
_hold = self .ax ._hold
522
664
self .ax ._hold = True
665
+ _log .debug ('Setting pcolormesh' )
523
666
col = self .ax .pcolormesh (* args , ** kw )
524
667
self .ax ._hold = _hold
525
668
#self.add_observer(col) # We should observe, not be observed...
@@ -573,39 +716,11 @@ def add_lines(self, levels, colors, linewidths, erase=True):
573
716
self .ax .add_collection (col )
574
717
self .stale = True
575
718
576
- def _ticker (self ):
719
+ def _ticker (self , locator , formatter ):
577
720
'''
578
721
Return the sequence of ticks (colorbar data locations),
579
722
ticklabels (strings), and the corresponding offset string.
580
723
'''
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
724
if isinstance (self .norm , colors .NoNorm ) and self .boundaries is None :
610
725
intv = self ._values [0 ], self ._values [- 1 ]
611
726
else :
@@ -845,17 +960,29 @@ def _mesh(self):
845
960
transposition for a horizontal colorbar are done outside
846
961
this function.
847
962
'''
963
+ # if boundaries and values are None, then we can go ahead and
964
+ # scale this up for Auto tick location. Otherwise we
965
+ # want to keep normalized between 0 and 1 and use manual tick
966
+ # locations.
967
+
848
968
x = np .array ([0.0 , 1.0 ])
849
969
if self .spacing == 'uniform' :
850
970
y = self ._uniform_y (self ._central_N ())
851
971
else :
852
972
y = self ._proportional_y ()
973
+ if self ._use_auto_colorbar_locator ():
974
+ y = self .norm .inverse (y )
975
+ x = self .norm .inverse (x )
853
976
self ._y = y
854
977
X , Y = np .meshgrid (x , y )
978
+ if self ._use_auto_colorbar_locator ():
979
+ xmid = self .norm .inverse (0.5 )
980
+ else :
981
+ xmid = 0.5
855
982
if self ._extend_lower () and not self .extendrect :
856
- X [0 , :] = 0.5
983
+ X [0 , :] = xmid
857
984
if self ._extend_upper () and not self .extendrect :
858
- X [- 1 , :] = 0.5
985
+ X [- 1 , :] = xmid
859
986
return X , Y
860
987
861
988
def _locate (self , x ):
0 commit comments