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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy