22
22
import warnings
23
23
24
24
import numpy as np
25
+
25
26
import matplotlib as mpl
26
- import matplotlib .colors as colors
27
- import matplotlib .cm as cm
28
- from matplotlib import docstring
29
- import matplotlib .ticker as ticker
27
+ import matplotlib .artist as martist
30
28
import matplotlib .cbook as cbook
31
- import matplotlib .lines as lines
32
- import matplotlib .patches as patches
33
29
import matplotlib .collections as collections
30
+ import matplotlib .colors as colors
34
31
import matplotlib .contour as contour
35
- import matplotlib .artist as martist
36
-
32
+ import matplotlib .cm as cm
37
33
import matplotlib .gridspec as gridspec
34
+ import matplotlib .lines as lines
35
+ import matplotlib .patches as mpatches
36
+ import matplotlib .path as mpath
37
+ import matplotlib .ticker as ticker
38
38
39
+ from matplotlib import docstring
39
40
40
41
make_axes_kw_doc = '''
41
42
@@ -203,10 +204,11 @@ class ColorbarBase(cm.ScalarMappable):
203
204
Useful public methods are :meth:`set_label` and :meth:`add_lines`.
204
205
205
206
'''
206
- _slice_dict = {'neither' : slice (0 ,1000000 ),
207
- 'both' : slice (1 ,- 1 ),
208
- 'min' : slice (1 ,1000000 ),
209
- 'max' : slice (0 ,- 1 )}
207
+ # XXX Double check these, as I don't have an example where the change was necessary...
208
+ _slice_dict = {'neither' : slice (0 , None ),
209
+ 'both' : slice (1 , - 1 ),
210
+ 'min' : slice (1 , None ),
211
+ 'max' : slice (0 , - 1 )}
210
212
211
213
def __init__ (self , ax , cmap = None ,
212
214
norm = None ,
@@ -258,6 +260,14 @@ def __init__(self, ax, cmap=None,
258
260
self .config_axis ()
259
261
self .draw_all ()
260
262
263
+ def _extend_lower (self ):
264
+ """Returns whether the lower limit is open ended."""
265
+ return self .extend in ('both' , 'min' )
266
+
267
+ def _extend_upper (self ):
268
+ """Returns whether the uper limit is open ended."""
269
+ return self .extend in ('both' , 'max' )
270
+
261
271
def _patch_ax (self ):
262
272
def _warn (* args , ** kw ):
263
273
warnings .warn ("Use the colorbar set_ticks() method instead." )
@@ -273,7 +283,7 @@ def draw_all(self):
273
283
self ._process_values ()
274
284
self ._find_range ()
275
285
X , Y = self ._mesh ()
276
- C = self ._values [:,np .newaxis ]
286
+ C = self ._values [:, np .newaxis ]
277
287
self ._config_axes (X , Y )
278
288
if self .filled :
279
289
self ._add_solids (X , Y , C )
@@ -354,7 +364,7 @@ def _config_axes(self, X, Y):
354
364
c = mpl .rcParams ['axes.facecolor' ]
355
365
if self .patch is not None :
356
366
self .patch .remove ()
357
- self .patch = patches .Polygon (xy , edgecolor = c ,
367
+ self .patch = mpatches .Polygon (xy , edgecolor = c ,
358
368
facecolor = c ,
359
369
linewidth = 0.01 ,
360
370
zorder = - 1 )
@@ -401,13 +411,13 @@ def _edges(self, X, Y):
401
411
# Using the non-array form of these line segments is much
402
412
# simpler than making them into arrays.
403
413
if self .orientation == 'vertical' :
404
- return [zip (X [i ], Y [i ]) for i in range (1 , N - 1 )]
414
+ return [zip (X [i ], Y [i ]) for i in xrange (1 , N - 1 )]
405
415
else :
406
- return [zip (Y [i ], X [i ]) for i in range (1 , N - 1 )]
416
+ return [zip (Y [i ], X [i ]) for i in xrange (1 , N - 1 )]
407
417
408
418
def _add_solids (self , X , Y , C ):
409
419
'''
410
- Draw the colors using :meth:`~matplotlib.axes.Axes.pcolor `;
420
+ Draw the colors using :meth:`~matplotlib.axes.Axes.pcolormesh `;
411
421
optionally add separators.
412
422
'''
413
423
if self .orientation == 'vertical' :
@@ -449,9 +459,9 @@ def add_lines(self, levels, colors, linewidths):
449
459
x = np .array ([0.0 , 1.0 ])
450
460
X , Y = np .meshgrid (x ,y )
451
461
if self .orientation == 'vertical' :
452
- xy = [zip (X [i ], Y [i ]) for i in range (N )]
462
+ xy = [zip (X [i ], Y [i ]) for i in xrange (N )]
453
463
else :
454
- xy = [zip (Y [i ], X [i ]) for i in range (N )]
464
+ xy = [zip (Y [i ], X [i ]) for i in xrange (N )]
455
465
col = collections .LineCollection (xy , linewidths = linewidths )
456
466
457
467
if self .lines :
@@ -540,26 +550,26 @@ def _process_values(self, b=None):
540
550
b = self ._uniform_y (self .cmap .N + 1 ) * self .cmap .N - 0.5
541
551
v = np .zeros ((len (b )- 1 ,), dtype = np .int16 )
542
552
v [self ._inside ] = np .arange (self .cmap .N , dtype = np .int16 )
543
- if self .extend in ( 'both' , 'min' ):
553
+ if self ._extend_lower ( ):
544
554
v [0 ] = - 1
545
- if self .extend in ( 'both' , 'max' ):
555
+ if self ._extend_upper ( ):
546
556
v [- 1 ] = self .cmap .N
547
557
self ._boundaries = b
548
558
self ._values = v
549
559
return
550
560
elif isinstance (self .norm , colors .BoundaryNorm ):
551
561
b = list (self .norm .boundaries )
552
- if self .extend in ( 'both' , 'min' ):
562
+ if self ._extend_lower ( ):
553
563
b = [b [0 ]- 1 ] + b
554
- if self .extend in ( 'both' , 'max' ):
564
+ if self ._extend_upper ( ):
555
565
b = b + [b [- 1 ] + 1 ]
556
566
b = np .array (b )
557
567
v = np .zeros ((len (b )- 1 ,), dtype = float )
558
568
bi = self .norm .boundaries
559
569
v [self ._inside ] = 0.5 * (bi [:- 1 ] + bi [1 :])
560
- if self .extend in ( 'both' , 'min' ):
570
+ if self ._extend_lower ( ):
561
571
v [0 ] = b [0 ] - 1
562
- if self .extend in ( 'both' , 'max' ):
572
+ if self ._extend_upper ( ):
563
573
v [- 1 ] = b [- 1 ] + 1
564
574
self ._boundaries = b
565
575
self ._values = v
@@ -569,9 +579,9 @@ def _process_values(self, b=None):
569
579
self .norm .vmin = 0
570
580
self .norm .vmax = 1
571
581
b = self .norm .inverse (self ._uniform_y (self .cmap .N + 1 ))
572
- if self .extend in ( 'both' , 'min' ):
582
+ if self ._extend_lower ( ):
573
583
b [0 ] = b [0 ] - 1
574
- if self .extend in ( 'both' , 'max' ):
584
+ if self ._extend_upper ( ):
575
585
b [- 1 ] = b [- 1 ] + 1
576
586
self ._process_values (b )
577
587
@@ -589,7 +599,7 @@ def _central_N(self):
589
599
nb = len (self ._boundaries )
590
600
if self .extend == 'both' :
591
601
nb -= 2
592
- elif self .extend in ( 'min' , 'max' ):
602
+ elif self ._extend_lower ( ):
593
603
nb -= 1
594
604
return nb
595
605
@@ -637,9 +647,9 @@ def _proportional_y(self):
637
647
y = y / (self ._boundaries [- 1 ] - self ._boundaries [0 ])
638
648
else :
639
649
y = self .norm (self ._boundaries .copy ())
640
- if self .extend in ( 'both' , 'min' ):
650
+ if self ._extend_lower ( ):
641
651
y [0 ] = - 0.05
642
- if self .extend in ( 'both' , 'max' ):
652
+ if self ._extend_upper ( ):
643
653
y [- 1 ] = 1.05
644
654
yi = y [self ._inside ]
645
655
norm = colors .Normalize (yi [0 ], yi [- 1 ])
@@ -660,10 +670,10 @@ def _mesh(self):
660
670
y = self ._proportional_y ()
661
671
self ._y = y
662
672
X , Y = np .meshgrid (x ,y )
663
- if self .extend in ( 'min' , 'both' ):
664
- X [0 ,:] = 0.5
665
- if self .extend in ( 'max' , 'both' ):
666
- X [- 1 ,:] = 0.5
673
+ if self ._extend_lower ( ):
674
+ X [0 , :] = 0.5
675
+ if self ._extend_upper ( ):
676
+ X [- 1 , :] = 0.5
667
677
return X , Y
668
678
669
679
def _locate (self , x ):
@@ -703,6 +713,7 @@ def _locate(self, x):
703
713
def set_alpha (self , alpha ):
704
714
self .alpha = alpha
705
715
716
+
706
717
class Colorbar (ColorbarBase ):
707
718
"""
708
719
This class connects a :class:`ColorbarBase` to a
@@ -743,6 +754,17 @@ def __init__(self, ax, mappable, **kw):
743
754
744
755
ColorbarBase .__init__ (self , ax , ** kw )
745
756
757
+ def on_mappable_changed (self , mappable ):
758
+ """
759
+ Updates this colorbar to match the mappable's properties.
760
+
761
+ Typically this is automatically registered as an event handler
762
+ by :func:`colorbar_factory` and should not be called manually.
763
+
764
+ """
765
+ self .set_cmap (mappable .get_cmap ())
766
+ self .set_clim (mappable .get_clim ())
767
+ self .update_normal (mappable )
746
768
747
769
def add_lines (self , CS ):
748
770
'''
@@ -952,3 +974,102 @@ def make_axes_gridspec(parent, **kw):
952
974
cax = fig .add_subplot (gs2 [1 ])
953
975
cax .set_aspect (aspect , anchor = anchor , adjustable = 'box' )
954
976
return cax , kw
977
+
978
+
979
+ class ColorbarPatch (Colorbar ):
980
+ """
981
+ A Colorbar which is created using :class:`~matplotlib.patches.Patch`
982
+ rather than the default :func:`~matplotlib.axes.pcolor`.
983
+
984
+ """
985
+ def __init__ (self , ax , mappable , ** kw ):
986
+ # we do not want to override the behaviour of solids
987
+ # so add a new attribute which will be a list of the
988
+ # colored patches in the colorbar
989
+ self .solids_patches = []
990
+ Colorbar .__init__ (self , ax , mappable , ** kw )
991
+
992
+ def _add_solids (self , X , Y , C ):
993
+ '''
994
+ Draw the colors using :class:`~matplotlib.patches.Patch`;
995
+ optionally add separators.
996
+ '''
997
+ # Save, set, and restore hold state to keep pcolor from
998
+ # clearing the axes. Ordinarily this will not be needed,
999
+ # since the axes object should already have hold set.
1000
+ _hold = self .ax .ishold ()
1001
+ self .ax .hold (True )
1002
+
1003
+ kw = {'alpha' :self .alpha ,}
1004
+
1005
+ n_segments = len (C )
1006
+
1007
+ # ensure there are sufficent hatches
1008
+ hatches = self .mappable .hatches * n_segments
1009
+
1010
+ patches = []
1011
+ for i in xrange (len (X )- 1 ):
1012
+ val = C [i ][0 ]
1013
+ hatch = hatches [i ]
1014
+
1015
+ xy = np .array ([[X [i ][0 ], Y [i ][0 ]], [X [i ][1 ], Y [i ][0 ]],
1016
+ [X [i + 1 ][1 ], Y [i + 1 ][0 ]], [X [i + 1 ][0 ], Y [i + 1 ][1 ]]])
1017
+
1018
+ if self .orientation == 'horizontal' :
1019
+ # if horizontal swap the xs and ys
1020
+ xy = xy [..., ::- 1 ]
1021
+
1022
+ patch = mpatches .PathPatch (mpath .Path (xy ),
1023
+ facecolor = self .cmap (self .norm (val )),
1024
+ hatch = hatch ,
1025
+ edgecolor = 'none' , linewidth = 0 ,
1026
+ antialiased = False , ** kw
1027
+ )
1028
+ c = self .mappable .collections [i ]
1029
+
1030
+ self .ax .add_patch (patch )
1031
+ patches .append (patch )
1032
+
1033
+ if self .solids_patches :
1034
+ for solid in self .solids_patches :
1035
+ solid .remove ()
1036
+
1037
+ self .solids_patches = patches
1038
+
1039
+ # for compatibility with Colorbar, we will implement edge drawing as a
1040
+ # seperate line collection, even though we could have put a line on
1041
+ # the patches in self.solids_patches.
1042
+ if self .dividers is not None :
1043
+ self .dividers .remove ()
1044
+ self .dividers = None
1045
+
1046
+ if self .drawedges :
1047
+ self .dividers = collections .LineCollection (self ._edges (X ,Y ),
1048
+ colors = (mpl .rcParams ['axes.edgecolor' ],),
1049
+ linewidths = (0.5 * mpl .rcParams ['axes.linewidth' ],)
1050
+ )
1051
+ self .ax .add_collection (self .dividers )
1052
+
1053
+ self .ax .hold (_hold )
1054
+
1055
+
1056
+ def colorbar_factory (cax , mappable , ** kwargs ):
1057
+ """
1058
+ Creates a colorbar on the given axes for the given mappable.
1059
+
1060
+ Typically, for automatic colorbar placement given only a mappable use
1061
+ :meth:`~matplotlib.figure.Figure.colorbar`.
1062
+
1063
+ """
1064
+ # if the given mappable is a contourset with any hatching, use
1065
+ # ColorbarPatch else use Colorbar
1066
+ if (isinstance (mappable , contour .ContourSet ) \
1067
+ and any ([hatch is not None for hatch in mappable .hatches ])):
1068
+ cb = ColorbarPatch (cax , mappable , ** kwargs )
1069
+ else :
1070
+ cb = Colorbar (cax , mappable , ** kwargs )
1071
+
1072
+ mappable .callbacksSM .connect ('changed' , cb .on_mappable_changed )
1073
+ mappable .set_colorbar (cb , cax )
1074
+
1075
+ return cb
0 commit comments