Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b2f2ab9

Browse files
author
Phil Elson
committed
Added hatchable colorbar.
1 parent 7ef723e commit b2f2ab9

File tree

7 files changed

+192
-46
lines changed

7 files changed

+192
-46
lines changed

examples/pylab_examples/contourf_demo.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@
6565
CS3 = contourf(X, Y, Z, levels,
6666
colors = ('r', 'g', 'b'),
6767
origin=origin,
68-
extend='both',
69-
hatches=['/', '\\'])
68+
extend='both')
7069
# Our data range extends outside the range of levels; make
7170
# data below the lowest contour level yellow, and above the
7271
# highest level cyan:
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import matplotlib.pyplot as plt
2+
import numpy
3+
4+
x = numpy.linspace(-3, 5, 150).reshape(1, -1)
5+
y = numpy.linspace(-3, 5, 120).reshape(-1, 1)
6+
z = numpy.cos(x) + numpy.sin(y)
7+
8+
# we no longer need x and y to be 2 dimensional, so flatten them
9+
x = x.flatten()
10+
y = y.flatten()
11+
12+
# plot #1
13+
# the simplest hatched plot
14+
fig = plt.figure()
15+
cm = plt.contourf(x, y, z, hatches=['-', '/', '\\', '//'], cmap=plt.get_cmap('gray'),
16+
extend='both', alpha=0.5
17+
)
18+
19+
plt.colorbar()
20+
21+
22+
# plot #2
23+
# a plot of hatches without color
24+
plt.figure()
25+
plt.contour(x, y, z, colors='black', )
26+
plt.contourf(x, y, z, colors='none', hatches=['.', '/', '\\', None, '..', '\\\\'])
27+
28+
29+
plt.show()

lib/matplotlib/collections.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,7 @@ def update_from(self, other):
585585
self._linewidths = other._linewidths
586586
self._linestyles = other._linestyles
587587
self._pickradius = other._pickradius
588+
self._hatch = other._hatch
588589

589590
# update_from for scalarmappable
590591
self._A = other._A

lib/matplotlib/colorbar.py

Lines changed: 155 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,21 @@
2222
import warnings
2323

2424
import numpy as np
25+
2526
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
3028
import matplotlib.cbook as cbook
31-
import matplotlib.lines as lines
32-
import matplotlib.patches as patches
3329
import matplotlib.collections as collections
30+
import matplotlib.colors as colors
3431
import matplotlib.contour as contour
35-
import matplotlib.artist as martist
36-
32+
import matplotlib.cm as cm
3733
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
3838

39+
from matplotlib import docstring
3940

4041
make_axes_kw_doc = '''
4142
@@ -203,10 +204,11 @@ class ColorbarBase(cm.ScalarMappable):
203204
Useful public methods are :meth:`set_label` and :meth:`add_lines`.
204205
205206
'''
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)}
210212

211213
def __init__(self, ax, cmap=None,
212214
norm=None,
@@ -258,6 +260,14 @@ def __init__(self, ax, cmap=None,
258260
self.config_axis()
259261
self.draw_all()
260262

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+
261271
def _patch_ax(self):
262272
def _warn(*args, **kw):
263273
warnings.warn("Use the colorbar set_ticks() method instead.")
@@ -273,7 +283,7 @@ def draw_all(self):
273283
self._process_values()
274284
self._find_range()
275285
X, Y = self._mesh()
276-
C = self._values[:,np.newaxis]
286+
C = self._values[:, np.newaxis]
277287
self._config_axes(X, Y)
278288
if self.filled:
279289
self._add_solids(X, Y, C)
@@ -354,7 +364,7 @@ def _config_axes(self, X, Y):
354364
c = mpl.rcParams['axes.facecolor']
355365
if self.patch is not None:
356366
self.patch.remove()
357-
self.patch = patches.Polygon(xy, edgecolor=c,
367+
self.patch = mpatches.Polygon(xy, edgecolor=c,
358368
facecolor=c,
359369
linewidth=0.01,
360370
zorder=-1)
@@ -401,13 +411,13 @@ def _edges(self, X, Y):
401411
# Using the non-array form of these line segments is much
402412
# simpler than making them into arrays.
403413
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)]
405415
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)]
407417

408418
def _add_solids(self, X, Y, C):
409419
'''
410-
Draw the colors using :meth:`~matplotlib.axes.Axes.pcolor`;
420+
Draw the colors using :meth:`~matplotlib.axes.Axes.pcolormesh`;
411421
optionally add separators.
412422
'''
413423
if self.orientation == 'vertical':
@@ -449,9 +459,9 @@ def add_lines(self, levels, colors, linewidths):
449459
x = np.array([0.0, 1.0])
450460
X, Y = np.meshgrid(x,y)
451461
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)]
453463
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)]
455465
col = collections.LineCollection(xy, linewidths=linewidths)
456466

457467
if self.lines:
@@ -540,26 +550,26 @@ def _process_values(self, b=None):
540550
b = self._uniform_y(self.cmap.N+1) * self.cmap.N - 0.5
541551
v = np.zeros((len(b)-1,), dtype=np.int16)
542552
v[self._inside] = np.arange(self.cmap.N, dtype=np.int16)
543-
if self.extend in ('both', 'min'):
553+
if self._extend_lower():
544554
v[0] = -1
545-
if self.extend in ('both', 'max'):
555+
if self._extend_upper():
546556
v[-1] = self.cmap.N
547557
self._boundaries = b
548558
self._values = v
549559
return
550560
elif isinstance(self.norm, colors.BoundaryNorm):
551561
b = list(self.norm.boundaries)
552-
if self.extend in ('both', 'min'):
562+
if self._extend_lower():
553563
b = [b[0]-1] + b
554-
if self.extend in ('both', 'max'):
564+
if self._extend_upper():
555565
b = b + [b[-1] + 1]
556566
b = np.array(b)
557567
v = np.zeros((len(b)-1,), dtype=float)
558568
bi = self.norm.boundaries
559569
v[self._inside] = 0.5*(bi[:-1] + bi[1:])
560-
if self.extend in ('both', 'min'):
570+
if self._extend_lower():
561571
v[0] = b[0] - 1
562-
if self.extend in ('both', 'max'):
572+
if self._extend_upper():
563573
v[-1] = b[-1] + 1
564574
self._boundaries = b
565575
self._values = v
@@ -569,9 +579,9 @@ def _process_values(self, b=None):
569579
self.norm.vmin = 0
570580
self.norm.vmax = 1
571581
b = self.norm.inverse(self._uniform_y(self.cmap.N+1))
572-
if self.extend in ('both', 'min'):
582+
if self._extend_lower():
573583
b[0] = b[0] - 1
574-
if self.extend in ('both', 'max'):
584+
if self._extend_upper():
575585
b[-1] = b[-1] + 1
576586
self._process_values(b)
577587

@@ -589,7 +599,7 @@ def _central_N(self):
589599
nb = len(self._boundaries)
590600
if self.extend == 'both':
591601
nb -= 2
592-
elif self.extend in ('min', 'max'):
602+
elif self._extend_lower():
593603
nb -= 1
594604
return nb
595605

@@ -637,9 +647,9 @@ def _proportional_y(self):
637647
y = y / (self._boundaries[-1] - self._boundaries[0])
638648
else:
639649
y = self.norm(self._boundaries.copy())
640-
if self.extend in ('both', 'min'):
650+
if self._extend_lower():
641651
y[0] = -0.05
642-
if self.extend in ('both', 'max'):
652+
if self._extend_upper():
643653
y[-1] = 1.05
644654
yi = y[self._inside]
645655
norm = colors.Normalize(yi[0], yi[-1])
@@ -660,10 +670,10 @@ def _mesh(self):
660670
y = self._proportional_y()
661671
self._y = y
662672
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
667677
return X, Y
668678

669679
def _locate(self, x):
@@ -703,6 +713,7 @@ def _locate(self, x):
703713
def set_alpha(self, alpha):
704714
self.alpha = alpha
705715

716+
706717
class Colorbar(ColorbarBase):
707718
"""
708719
This class connects a :class:`ColorbarBase` to a
@@ -743,6 +754,17 @@ def __init__(self, ax, mappable, **kw):
743754

744755
ColorbarBase.__init__(self, ax, **kw)
745756

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)
746768

747769
def add_lines(self, CS):
748770
'''
@@ -952,3 +974,102 @@ def make_axes_gridspec(parent, **kw):
952974
cax = fig.add_subplot(gs2[1])
953975
cax.set_aspect(aspect, anchor=anchor, adjustable='box')
954976
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

lib/matplotlib/figure.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,16 +1194,8 @@ def colorbar(self, mappable, cax=None, ax=None, **kw):
11941194
else:
11951195
cax, kw = cbar.make_axes(ax, **kw)
11961196
cax.hold(True)
1197-
cb = cbar.Colorbar(cax, mappable, **kw)
1197+
cb = cbar.colorbar_factory(cax, mappable, **kw)
11981198

1199-
def on_changed(m):
1200-
#print 'calling on changed', m.get_cmap().name
1201-
cb.set_cmap(m.get_cmap())
1202-
cb.set_clim(m.get_clim())
1203-
cb.update_normal(m)
1204-
1205-
self.cbid = mappable.callbacksSM.connect('changed', on_changed)
1206-
mappable.set_colorbar(cb, cax)
12071199
self.sca(ax)
12081200
return cb
12091201

lib/matplotlib/patches.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,7 @@ def __str__(self):
505505
return self.__class__.__name__ \
506506
+ "(%g,%g;%gx%g)" % (self._x, self._y, self._width, self._height)
507507

508+
508509
@docstring.dedent_interpd
509510
def __init__(self, xy, width, height, **kwargs):
510511
"""

0 commit comments

Comments
 (0)