19
19
20
20
'''
21
21
22
+ import logging
22
23
import warnings
23
24
24
25
import numpy as np
39
40
import matplotlib ._constrained_layout as constrained_layout
40
41
from matplotlib import docstring
41
42
43
+ _log = logging .getLogger (__name__ )
44
+
42
45
make_axes_kw_doc = '''
43
46
44
47
============= ====================================================
@@ -212,6 +215,63 @@ def _set_ticks_on_axis_warn(*args, **kw):
212
215
warnings .warn ("Use the colorbar set_ticks() method instead." )
213
216
214
217
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
+
215
275
class ColorbarBase (cm .ScalarMappable ):
216
276
'''
217
277
Draw a colorbar in an existing axes.
@@ -341,8 +401,15 @@ def draw_all(self):
341
401
and do all the drawing.
342
402
'''
343
403
404
+ # sets self._boundaries and self._values in real data units.
405
+ # takes into account extend values:
344
406
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:
345
410
self ._find_range ()
411
+ # returns the X and Y mesh, *but* this was/is in normalized
412
+ # units:
346
413
X , Y = self ._mesh ()
347
414
C = self ._values [:, np .newaxis ]
348
415
self ._config_axes (X , Y )
@@ -351,35 +418,105 @@ def draw_all(self):
351
418
352
419
def config_axis (self ):
353
420
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
+
354
428
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
359
430
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 )
364
437
365
438
self ._set_label ()
366
439
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
+
367
488
def update_ticks (self ):
368
489
"""
369
490
Force the update of the ticks and ticklabels. This must be
370
491
called whenever the tick locator and/or tick formatter changes.
371
492
"""
372
493
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 ()
378
497
498
+ if self .orientation == 'vertical' :
499
+ long_axis , short_axis = ax .yaxis , ax .xaxis
379
500
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 )
383
520
384
521
def set_ticks (self , ticks , update_ticks = True ):
385
522
"""
@@ -515,6 +652,7 @@ def _add_solids(self, X, Y, C):
515
652
# since the axes object should already have hold set.
516
653
_hold = self .ax ._hold
517
654
self .ax ._hold = True
655
+ _log .debug ('Setting pcolormesh' )
518
656
col = self .ax .pcolormesh (* args , ** kw )
519
657
self .ax ._hold = _hold
520
658
#self.add_observer(col) # We should observe, not be observed...
@@ -568,39 +706,11 @@ def add_lines(self, levels, colors, linewidths, erase=True):
568
706
self .ax .add_collection (col )
569
707
self .stale = True
570
708
571
- def _ticker (self ):
709
+ def _ticker (self , locator , formatter ):
572
710
'''
573
711
Return the sequence of ticks (colorbar data locations),
574
712
ticklabels (strings), and the corresponding offset string.
575
713
'''
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 )
604
714
if isinstance (self .norm , colors .NoNorm ) and self .boundaries is None :
605
715
intv = self ._values [0 ], self ._values [- 1 ]
606
716
else :
@@ -840,17 +950,29 @@ def _mesh(self):
840
950
transposition for a horizontal colorbar are done outside
841
951
this function.
842
952
'''
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
+
843
958
x = np .array ([0.0 , 1.0 ])
844
959
if self .spacing == 'uniform' :
845
960
y = self ._uniform_y (self ._central_N ())
846
961
else :
847
962
y = self ._proportional_y ()
963
+ if self ._use_auto_colorbar_locator ():
964
+ y = self .norm .inverse (y )
965
+ x = self .norm .inverse (x )
848
966
self ._y = y
849
967
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
850
972
if self ._extend_lower () and not self .extendrect :
851
- X [0 , :] = 0.5
973
+ X [0 , :] = xmid
852
974
if self ._extend_upper () and not self .extendrect :
853
- X [- 1 , :] = 0.5
975
+ X [- 1 , :] = xmid
854
976
return X , Y
855
977
856
978
def _locate (self , x ):
0 commit comments