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 )
@@ -357,34 +426,119 @@ def draw_all(self):
357
426
def config_axis (self ):
358
427
ax = self .ax
359
428
if self .orientation == 'vertical' :
360
- ax .xaxis .set_ticks ([])
361
429
# location is either one of 'bottom' or 'top'
362
430
ax .yaxis .set_label_position (self .ticklocation )
363
431
ax .yaxis .set_ticks_position (self .ticklocation )
432
+ if (isinstance (self .norm , colors .LogNorm )
433
+ and self ._use_adjustable ()):
434
+ ax .set_xscale ('log' )
435
+ ax .set_yscale ('log' )
436
+ ax .xaxis .set_ticks ([])
437
+ ax .xaxis .set_ticks ([], minor = True )
438
+
364
439
else :
365
- ax .yaxis .set_ticks ([])
366
440
# location is either one of 'left' or 'right'
367
441
ax .xaxis .set_label_position (self .ticklocation )
368
442
ax .xaxis .set_ticks_position (self .ticklocation )
443
+ if (isinstance (self .norm , colors .LogNorm )
444
+ and self ._use_adjustable ()):
445
+ ax .set_xscale ('log' )
446
+ ax .set_yscale ('log' )
447
+ ax .yaxis .set_ticks ([])
448
+ ax .yaxis .set_ticks ([], minor = True )
369
449
370
450
self ._set_label ()
371
451
452
+ def _get_ticker_locator_formatter (self ):
453
+ """
454
+ This code looks at the norm being used by the colorbar
455
+ and decides what locator and formatter to use. If ``locator`` has
456
+ already been set by hand, it just returns
457
+ ``self.locator, self.formatter``.
458
+ """
459
+ locator = self .locator
460
+ formatter = self .formatter
461
+ if locator is None :
462
+ if self .boundaries is None :
463
+ if isinstance (self .norm , colors .NoNorm ):
464
+ nv = len (self ._values )
465
+ base = 1 + int (nv / 10 )
466
+ locator = ticker .IndexLocator (base = base , offset = 0 )
467
+ elif isinstance (self .norm , colors .BoundaryNorm ):
468
+ b = self .norm .boundaries
469
+ locator = ticker .FixedLocator (b , nbins = 10 )
470
+ elif isinstance (self .norm , colors .LogNorm ):
471
+ locator = ColorbarLogLocator (self )
472
+ elif isinstance (self .norm , colors .SymLogNorm ):
473
+ # The subs setting here should be replaced
474
+ # by logic in the locator.
475
+ locator = ticker .SymmetricalLogLocator (
476
+ subs = np .arange (1 , 10 ),
477
+ linthresh = self .norm .linthresh ,
478
+ base = 10 )
479
+ else :
480
+ if mpl .rcParams ['_internal.classic_mode' ]:
481
+ locator = ticker .MaxNLocator ()
482
+ else :
483
+ locator = ColorbarAutoLocator (self )
484
+ else :
485
+ b = self ._boundaries [self ._inside ]
486
+ locator = ticker .FixedLocator (b , nbins = 10 )
487
+ _log .debug ('locator: %r' , locator )
488
+ return locator , formatter
489
+
490
+ def _use_adjustable (self ):
491
+ """
492
+ Return if we should use an adjustable tick locator or a fixed
493
+ one. (check is used twice so factored out here...)
494
+ """
495
+ return (self .boundaries is None
496
+ and self .values is None
497
+ and ((type (self .norm ) == colors .Normalize )
498
+ or (type (self .norm ) == colors .LogNorm )))
499
+
372
500
def update_ticks (self ):
373
501
"""
374
502
Force the update of the ticks and ticklabels. This must be
375
503
called whenever the tick locator and/or tick formatter changes.
376
504
"""
377
505
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 )
506
+ # get the locator and formatter. Defaults to
507
+ # self.locator if not None..
508
+ locator , formatter = self ._get_ticker_locator_formatter ()
509
+
510
+ if self ._use_adjustable ():
511
+ _log .debug ('Using adjustable locator on colorbar' )
512
+ _log .debug ('locator: %r' , locator )
513
+ #self._find_range()
514
+ if self .orientation == 'vertical' :
515
+ ax .yaxis .set_major_locator (locator )
516
+ ax .yaxis .set_major_formatter (formatter )
517
+ if type (self .norm ) == colors .LogNorm :
518
+ ax .yaxis .set_minor_locator (ColorbarLogLocator (self ,
519
+ base = 10. , subs = 'auto' ))
520
+ ax .yaxis .set_minor_formatter (ticker .NullFormatter ())
521
+
522
+ else :
523
+ ax .xaxis .set_major_locator (locator )
524
+ ax .xaxis .set_major_formatter (formatter )
525
+ if type (self .norm ) == colors .LogNorm :
526
+ ax .xaxis .set_minor_locator (ColorbarLogLocator (self ,
527
+ base = 10. , subs = 'auto' ))
528
+ ax .xaxis .set_minor_formatter (ticker .NullFormatter ())
383
529
384
530
else :
385
- ax .xaxis .set_ticks (ticks )
386
- ax .set_xticklabels (ticklabels )
387
- ax .xaxis .get_major_formatter ().set_offset_string (offset_string )
531
+ _log .debug ('Using fixed locator on colorbar' )
532
+ ticks , ticklabels , offset_string = self ._ticker (locator , formatter )
533
+ if self .orientation == 'vertical' :
534
+ ax .yaxis .set_ticks (ticks )
535
+ ax .set_yticklabels (ticklabels )
536
+ ax .yaxis .get_major_formatter ().set_offset_string (offset_string )
537
+
538
+ else :
539
+ ax .xaxis .set_ticks (ticks )
540
+ ax .set_xticklabels (ticklabels )
541
+ ax .xaxis .get_major_formatter ().set_offset_string (offset_string )
388
542
389
543
def set_ticks (self , ticks , update_ticks = True ):
390
544
"""
@@ -520,6 +674,7 @@ def _add_solids(self, X, Y, C):
520
674
# since the axes object should already have hold set.
521
675
_hold = self .ax ._hold
522
676
self .ax ._hold = True
677
+ _log .debug ('Setting pcolormesh' )
523
678
col = self .ax .pcolormesh (* args , ** kw )
524
679
self .ax ._hold = _hold
525
680
#self.add_observer(col) # We should observe, not be observed...
@@ -575,39 +730,11 @@ def add_lines(self, levels, colors, linewidths, erase=True):
575
730
self .ax .add_collection (col )
576
731
self .stale = True
577
732
578
- def _ticker (self ):
733
+ def _ticker (self , locator , formatter ):
579
734
'''
580
735
Return the sequence of ticks (colorbar data locations),
581
736
ticklabels (strings), and the corresponding offset string.
582
737
'''
583
- locator = self .locator
584
- formatter = self .formatter
585
- if locator is None :
586
- if self .boundaries is None :
587
- if isinstance (self .norm , colors .NoNorm ):
588
- nv = len (self ._values )
589
- base = 1 + int (nv / 10 )
590
- locator = ticker .IndexLocator (base = base , offset = 0 )
591
- elif isinstance (self .norm , colors .BoundaryNorm ):
592
- b = self .norm .boundaries
593
- locator = ticker .FixedLocator (b , nbins = 10 )
594
- elif isinstance (self .norm , colors .LogNorm ):
595
- locator = ticker .LogLocator (subs = 'all' )
596
- elif isinstance (self .norm , colors .SymLogNorm ):
597
- # The subs setting here should be replaced
598
- # by logic in the locator.
599
- locator = ticker .SymmetricalLogLocator (
600
- subs = np .arange (1 , 10 ),
601
- linthresh = self .norm .linthresh ,
602
- base = 10 )
603
- else :
604
- if mpl .rcParams ['_internal.classic_mode' ]:
605
- locator = ticker .MaxNLocator ()
606
- else :
607
- locator = ticker .AutoLocator ()
608
- else :
609
- b = self ._boundaries [self ._inside ]
610
- locator = ticker .FixedLocator (b , nbins = 10 )
611
738
if isinstance (self .norm , colors .NoNorm ) and self .boundaries is None :
612
739
intv = self ._values [0 ], self ._values [- 1 ]
613
740
else :
@@ -852,12 +979,28 @@ def _mesh(self):
852
979
y = self ._uniform_y (self ._central_N ())
853
980
else :
854
981
y = self ._proportional_y ()
982
+ # if boundaries and values are None, then we can go ahead and
983
+ # scale this up for Auto tick location. Otherwise we
984
+ # want to keep normalized between 0 and 1 and use manual tick
985
+ # locations.
986
+ if self ._use_adjustable ():
987
+ y = self .norm .inverse (y )
988
+ x = self .norm .inverse (x )
989
+ else :
990
+ dy = 1.0
855
991
self ._y = y
992
+
856
993
X , Y = np .meshgrid (x , y )
857
994
if self ._extend_lower () and not self .extendrect :
858
- X [0 , :] = 0.5
995
+ if self ._use_adjustable ():
996
+ X [0 , :] = self .norm .inverse (0.5 )
997
+ else :
998
+ X [0 , :] = 0.5
859
999
if self ._extend_upper () and not self .extendrect :
860
- X [- 1 , :] = 0.5
1000
+ if self ._use_adjustable ():
1001
+ X [- 1 , :] = self .norm .inverse (0.5 )
1002
+ else :
1003
+ X [- 1 , :] = 0.5
861
1004
return X , Y
862
1005
863
1006
def _locate (self , x ):
0 commit comments