diff --git a/CHANGELOG b/CHANGELOG
index 9bc96a63385a..6eff3d3e6e50 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,9 +1,17 @@
+2012-08-05 When a norm is passed to contourf, either or both of the
+ vmin, vmax attributes of that norm are now respected.
+ Formerly they were respected only if both were
+ specified. In addition, vmin and/or vmax can now
+ be passed to contourf directly as kwargs. - EF
+
2012-07-24 Contourf handles the extend kwarg by mapping the extended
ranges outside the normed 0-1 range so that they are
handled by colormap colors determined by the set_under
and set_over methods. Previously the extended ranges
were mapped to 0 or 1 so that the "under" and "over"
- colormap colors were ignored. - EF
+ colormap colors were ignored. This change also increases
+ slightly the color contrast for a given set of contour
+ levels. - EF
2012-06-24 Make use of mathtext in tick labels configurable - DSD
diff --git a/lib/matplotlib/axes.py b/lib/matplotlib/axes.py
index f631543b7fe2..3ccf7cdacd11 100644
--- a/lib/matplotlib/axes.py
+++ b/lib/matplotlib/axes.py
@@ -3754,7 +3754,7 @@ def vlines(self, x, ymin, ymax, colors='k', linestyles='solid',
linestyles=linestyles, label=label)
self.add_collection(coll)
coll.update(kwargs)
-
+
if len(x) > 0:
minx = min( x )
maxx = max( x )
@@ -6955,16 +6955,18 @@ def pcolor(self, *args, **kwargs):
A :class:`matplotlib.colors.Colormap` instance. If *None*, use
rc settings.
- norm: [ *None* | Normalize ]
+ *norm*: [ *None* | Normalize ]
An :class:`matplotlib.colors.Normalize` instance is used
to scale luminance data to 0,1. If *None*, defaults to
:func:`normalize`.
*vmin*/*vmax*: [ *None* | scalar ]
*vmin* and *vmax* are used in conjunction with *norm* to
- normalize luminance data. If either are *None*, the min
- and max of the color array *C* is used. If you pass a
- *norm* instance, *vmin* and *vmax* will be ignored.
+ normalize luminance data. If either is *None*, it
+ is autoscaled to the respective min or max
+ of the color array *C*. If not *None*, *vmin* or
+ *vmax* passed in here override any pre-existing values
+ supplied in the *norm* instance.
*shading*: [ 'flat' | 'faceted' ]
If 'faceted', a black grid is drawn around each rectangle; if
@@ -7121,10 +7123,8 @@ def pcolor(self, *args, **kwargs):
if norm is not None: assert(isinstance(norm, mcolors.Normalize))
collection.set_cmap(cmap)
collection.set_norm(norm)
- if vmin is not None or vmax is not None:
- collection.set_clim(vmin, vmax)
- else:
- collection.autoscale_None()
+ collection.set_clim(vmin, vmax)
+ collection.autoscale_None()
self.grid(False)
x = X.compressed()
@@ -7167,9 +7167,11 @@ def pcolormesh(self, *args, **kwargs):
*vmin*/*vmax*: [ *None* | scalar ]
*vmin* and *vmax* are used in conjunction with *norm* to
- normalize luminance data. If either are *None*, the min
- and max of the color array *C* is used. If you pass a
- *norm* instance, *vmin* and *vmax* will be ignored.
+ normalize luminance data. If either is *None*, it
+ is autoscaled to the respective min or max
+ of the color array *C*. If not *None*, *vmin* or
+ *vmax* passed in here override any pre-existing values
+ supplied in the *norm* instance.
*shading*: [ 'flat' | 'gouraud' ]
'flat' indicates a solid color for each quad. When
@@ -7235,10 +7237,8 @@ def pcolormesh(self, *args, **kwargs):
if norm is not None: assert(isinstance(norm, mcolors.Normalize))
collection.set_cmap(cmap)
collection.set_norm(norm)
- if vmin is not None or vmax is not None:
- collection.set_clim(vmin, vmax)
- else:
- collection.autoscale_None()
+ collection.set_clim(vmin, vmax)
+ collection.autoscale_None()
self.grid(False)
diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py
index 2f8a72092300..149d536d742a 100644
--- a/lib/matplotlib/colorbar.py
+++ b/lib/matplotlib/colorbar.py
@@ -255,7 +255,7 @@ def __init__(self, ax, cmap=None,
self.filled = filled
self.extendfrac = extendfrac
self.solids = None
- self.lines = None
+ self.lines = list()
self.outline = None
self.patch = None
self.dividers = None
@@ -467,14 +467,25 @@ def _add_solids(self, X, Y, C):
)
self.ax.add_collection(self.dividers)
- def add_lines(self, levels, colors, linewidths):
+ def add_lines(self, levels, colors, linewidths, erase=True):
'''
Draw lines on the colorbar.
+
+ *colors* and *linewidths* must be scalars or
+ sequences the same length as *levels*.
+
+ Set *erase* to False to add lines without first
+ removing any previously added lines.
'''
- N = len(levels)
- dummy, y = self._locate(levels)
- if len(y) != N:
- raise ValueError("levels are outside colorbar range")
+ y = self._locate(levels)
+ nlevs = len(levels)
+ igood = (y < 1.001) & (y > -0.001)
+ y = y[igood]
+ if cbook.iterable(colors):
+ colors = np.asarray(colors)[igood]
+ if cbook.iterable(linewidths):
+ linewidths = np.asarray(linewidths)[igood]
+ N = len(y)
x = np.array([0.0, 1.0])
X, Y = np.meshgrid(x,y)
if self.orientation == 'vertical':
@@ -483,9 +494,10 @@ def add_lines(self, levels, colors, linewidths):
xy = [zip(Y[i], X[i]) for i in xrange(N)]
col = collections.LineCollection(xy, linewidths=linewidths)
- if self.lines:
- self.lines.remove()
- self.lines = col
+ if erase and self.lines:
+ for lc in self.lines.pop():
+ lc.remove()
+ self.lines.append(col)
col.set_color(colors)
self.ax.add_collection(col)
@@ -528,7 +540,10 @@ def _ticker(self):
locator.axis.get_minpos = lambda : intv[0]
formatter.axis.get_minpos = lambda : intv[0]
b = np.array(locator())
- b, ticks = self._locate(b)
+ ticks = self._locate(b)
+ inrange = (ticks > -0.001) & (ticks < 1.001)
+ ticks = ticks[inrange]
+ b = b[inrange]
formatter.set_locs(b)
ticklabels = [formatter(t, i) for i, t in enumerate(b)]
offset_string = formatter.get_offset()
@@ -746,37 +761,36 @@ def _mesh(self):
def _locate(self, x):
'''
- Given a possible set of color data values, return the ones
- within range, together with their corresponding colorbar
- data coordinates.
+ Given a set of color data values, return their
+ corresponding colorbar data coordinates.
'''
if isinstance(self.norm, (colors.NoNorm, colors.BoundaryNorm)):
b = self._boundaries
xn = x
- xout = x
else:
# Do calculations using normalized coordinates so
# as to make the interpolation more accurate.
b = self.norm(self._boundaries, clip=False).filled()
- # We do our own clipping so that we can allow a tiny
- # bit of slop in the end point ticks to allow for
- # floating point errors.
xn = self.norm(x, clip=False).filled()
- in_cond = (xn > -0.001) & (xn < 1.001)
- xn = np.compress(in_cond, xn)
- xout = np.compress(in_cond, x)
- # The rest is linear interpolation with clipping.
+ # The rest is linear interpolation with extrapolation at ends.
y = self._y
N = len(b)
- ii = np.minimum(np.searchsorted(b, xn), N-1)
- i0 = np.maximum(ii - 1, 0)
+ ii = np.searchsorted(b, xn)
+ i0 = ii - 1
+ itop = (ii == N)
+ ibot = (ii == 0)
+ i0[itop] -= 1
+ ii[itop] -= 1
+ i0[ibot] += 1
+ ii[ibot] += 1
+
#db = b[ii] - b[i0]
db = np.take(b, ii) - np.take(b, i0)
- db = np.where(i0==ii, 1.0, db)
#dy = y[ii] - y[i0]
dy = np.take(y, ii) - np.take(y, i0)
z = np.take(y, i0) + (xn-np.take(b,i0))*dy/db
- return xout, z
+
+ return z
def set_alpha(self, alpha):
self.alpha = alpha
@@ -834,10 +848,13 @@ def on_mappable_changed(self, mappable):
self.set_clim(mappable.get_clim())
self.update_normal(mappable)
- def add_lines(self, CS):
+ def add_lines(self, CS, erase=True):
'''
Add the lines from a non-filled
:class:`~matplotlib.contour.ContourSet` to the colorbar.
+
+ Set *erase* to False if these lines should be added to
+ any pre-existing lines.
'''
if not isinstance(CS, contour.ContourSet) or CS.filled:
raise ValueError('add_lines is only for a ContourSet of lines')
@@ -851,7 +868,8 @@ def add_lines(self, CS):
#tcolors = [col.get_colors()[0] for col in CS.collections]
#tlinewidths = [col.get_linewidth()[0] for lw in CS.collections]
#print 'tlinewidths:', tlinewidths
- ColorbarBase.add_lines(self, CS.levels, tcolors, tlinewidths)
+ ColorbarBase.add_lines(self, CS.levels, tcolors, tlinewidths,
+ erase=erase)
def update_normal(self, mappable):
'''
@@ -884,7 +902,7 @@ def update_bruteforce(self, mappable):
self.outline = None
self.patch = None
self.solids = None
- self.lines = None
+ self.lines = list()
self.dividers = None
self.set_alpha(mappable.get_alpha())
self.cmap = mappable.cmap
diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py
index 5e80abcf6675..7bd62e90051f 100644
--- a/lib/matplotlib/colors.py
+++ b/lib/matplotlib/colors.py
@@ -510,11 +510,10 @@ def __call__(self, X, alpha=None, bytes=False):
xa = np.array([X])
else:
vtype = 'array'
- xma = ma.array(X, copy=False)
- mask_bad = xma.mask
- xa = xma.data.copy() # Copy here to avoid side effects.
+ xma = ma.array(X, copy=True) # Copy here to avoid side effects.
+ mask_bad = xma.mask # Mask will be used below.
+ xa = xma.filled() # Fill to avoid infs, etc.
del xma
- # masked values are substituted below; no need to fill them here
if xa.dtype.char in np.typecodes['Float']:
# Treat 1.0 as slightly less than 1.
diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py
index b018c8086089..5cfd7a074d07 100644
--- a/lib/matplotlib/contour.py
+++ b/lib/matplotlib/contour.py
@@ -682,7 +682,7 @@ class ContourSet(cm.ScalarMappable, ContourLabeler):
layers:
same as levels for line contours; half-way between
- levels for filled contours. See _process_colors method.
+ levels for filled contours. See :meth:`_process_colors`.
"""
def __init__(self, ax, *args, **kwargs):
"""
@@ -742,6 +742,8 @@ def __init__(self, ax, *args, **kwargs):
cmap = kwargs.get('cmap', None)
self.colors = kwargs.get('colors', None)
norm = kwargs.get('norm', None)
+ vmin = kwargs.get('vmin', None)
+ vmax = kwargs.get('vmax', None)
self.extend = kwargs.get('extend', 'neither')
self.antialiased = kwargs.get('antialiased', None)
if self.antialiased is None and self.filled:
@@ -789,7 +791,11 @@ def __init__(self, ax, *args, **kwargs):
kw = {'cmap': cmap}
if norm is not None:
kw['norm'] = norm
- cm.ScalarMappable.__init__(self, **kw) # sets self.cmap;
+ cm.ScalarMappable.__init__(self, **kw) # sets self.cmap, norm if needed;
+ if vmin is not None:
+ self.norm.vmin = vmin
+ if vmax is not None:
+ self.norm.vmax = vmax
self._process_colors()
self.allsegs, self.allkinds = self._get_allsegs_and_allkinds()
@@ -1029,40 +1035,65 @@ def _contour_level_args(self, z, args):
raise ValueError("Filled contours require at least 2 levels.")
def _process_levels(self):
- # Color mapping range (norm vmin, vmax) is based on levels.
+ """
+ Assign values to :attr:`layers` based on :attr:`levels`,
+ adding extended layers as needed if contours are filled.
+
+ For line contours, layers simply coincide with levels;
+ a line is a thin layer. No extended levels are needed
+ with line contours.
+ """
+ # The following attributes are no longer needed, and
+ # should be deprecated and removed to reduce confusion.
self.vmin = np.amin(self.levels)
self.vmax = np.amax(self.levels)
- # Make a private _levels to include extended regions.
+
+ # Make a private _levels to include extended regions; we
+ # want to leave the original levels attribute unchanged.
+ # (Colorbar needs this even for line contours.)
self._levels = list(self.levels)
+
+ if not self.filled:
+ self.layers = self.levels
+ return
+
if self.extend in ('both', 'min'):
self._levels.insert(0, min(self.levels[0],self.zmin) - 1)
if self.extend in ('both', 'max'):
self._levels.append(max(self.levels[-1],self.zmax) + 1)
self._levels = np.asarray(self._levels)
- if self.filled:
- # layer values are mid-way between levels
- self.layers = 0.5 * (self._levels[:-1] + self._levels[1:])
- # ...except that extended layers must be outside the
- # normed range:
- if self.extend in ('both', 'min'):
- self.layers[0] = -np.inf
- if self.extend in ('both', 'max'):
- self.layers[-1] = np.inf
- else:
- self.layers = self.levels # contour: a line is a thin layer
- # Use only original levels--no extended levels
+
+ # layer values are mid-way between levels
+ self.layers = 0.5 * (self._levels[:-1] + self._levels[1:])
+ # ...except that extended layers must be outside the
+ # normed range:
+ if self.extend in ('both', 'min'):
+ self.layers[0] = -np.inf
+ if self.extend in ('both', 'max'):
+ self.layers[-1] = np.inf
def _process_colors(self):
"""
Color argument processing for contouring.
- Note that we base the color mapping on the contour levels,
- not on the actual range of the Z values. This means we
- don't have to worry about bad values in Z, and we always have
- the full dynamic range available for the selected levels.
+ Note that we base the color mapping on the contour levels
+ and layers, not on the actual range of the Z values. This
+ means we don't have to worry about bad values in Z, and we
+ always have the full dynamic range available for the selected
+ levels.
The color is based on the midpoint of the layer, except for
- an extended end layers.
+ extended end layers. By default, the norm vmin and vmax
+ are the extreme values of the non-extended levels. Hence,
+ the layer color extremes are not the extreme values of
+ the colormap itself, but approach those values as the number
+ of levels increases. An advantage of this scheme is that
+ line contours, when added to filled contours, take on
+ colors that are consistent with those of the filled regions;
+ for example, a contour line on the boundary between two
+ regions will have a color intermediate between those
+ of the regions.
+
"""
self.monochrome = self.cmap.monochrome
if self.colors is not None:
@@ -1079,12 +1110,11 @@ def _process_colors(self):
self.set_norm(colors.NoNorm())
else:
self.cvalues = self.layers
- if not self.norm.scaled():
- self.set_clim(self.vmin, self.vmax)
+ self.set_array(self.levels)
+ self.autoscale_None()
if self.extend in ('both', 'max', 'min'):
self.norm.clip = False
- self.set_array(self.layers) # Required by colorbar, but not
- # actually used.
+
# self.tcolors are set by the "changed" method
def _process_linewidths(self):
@@ -1464,6 +1494,12 @@ def _initialize_x_y(self, z):
scaling data values to colors. If *norm* is *None* and
*colors* is *None*, the default linear scaling is used.
+ *vmin*, *vmax*: [ *None* | scalar ]
+ If not *None*, either or both of these values will be
+ supplied to the :class:`matplotlib.colors.Normalize`
+ instance, overriding the default color scaling based on
+ *levels*.
+
*levels*: [level0, level1, ..., leveln]
A list of floating point numbers indicating the level
curves to draw; eg to draw just the zero contour pass
@@ -1525,18 +1561,16 @@ def _initialize_x_y(self, z):
linewidths in the order specified
*linestyles*: [ *None* | 'solid' | 'dashed' | 'dashdot' | 'dotted' ]
- If *linestyles* is *None*, the 'solid' is used.
+ If *linestyles* is *None*, the default is 'solid' unless
+ the lines are monochrome. In that case, negative
+ contours will take their linestyle from the ``matplotlibrc``
+ ``contour.negative_linestyle`` setting.
*linestyles* can also be an iterable of the above strings
specifying a set of linestyles to be used. If this
iterable is shorter than the number of contour levels
it will be repeated as necessary.
- If contour is using a monochrome colormap and the contour
- level is less than 0, then the linestyle specified
- in ``contour.negative_linestyle`` in ``matplotlibrc``
- will be used.
-
contourf-only keyword arguments:
*nchunk*: [ 0 | integer ]
diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf
new file mode 100644
index 000000000000..35c75cc90bd5
Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.pdf differ
diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png
new file mode 100644
index 000000000000..43fe5ccad470
Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.png differ
diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg
new file mode 100644
index 000000000000..012e400e201f
--- /dev/null
+++ b/lib/matplotlib/tests/baseline_images/test_axes/contour_colorbar.svg
@@ -0,0 +1,20114 @@
+
+
+
+
diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf
index a944f701fab3..0554b3d9e4f6 100644
Binary files a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.pdf differ
diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.png b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.png
index 17d0307d81a7..1ad6c6a02263 100644
Binary files a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.png and b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.png differ
diff --git a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg
index 9169421d54eb..98ccabe28e56 100644
--- a/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg
+++ b/lib/matplotlib/tests/baseline_images/test_axes/contour_hatching.svg
@@ -470,16 +470,16 @@ L397.232 -333.62
L398.561 -333.246" id="C1_3_7b940d139a"/>
-
+
-
+
-
+
-
+
@@ -1346,16 +1346,16 @@ L398.251 -310.387
L398.561 -310.312" id="C2_3_a62ce94003"/>
-
+
-
+
-
+
-
+
@@ -2433,16 +2433,16 @@ L402.159 -287.153
L404.553 -286.708" id="C3_3_34a084a7cd"/>
-
+
-
+
-
+
-
+
@@ -3727,19 +3727,19 @@ L72 -252.303
L72 -249.398" id="C4_4_d8aa00cdc8"/>
-
+
-
+
-
+
-
+
-
+
@@ -4860,10 +4860,10 @@ L512.408 -44.8733
L510.255 -43.2" id="C5_1_464c506fbc"/>
-
+
-
+
@@ -5575,13 +5575,13 @@ L517.903 -182.602
L518.4 -182.223" id="C6_2_6c8c8dec59"/>
-
+
-
+
-
+
@@ -6003,10 +6003,10 @@ L517.078 -208.739
L518.4 -207.322" id="C7_1_e3e6f9ba7a"/>
-
+
-
+
@@ -6155,7 +6155,7 @@ L224.87 -197.123
L227.791 -196.513" id="C8_0_8de4362d0f"/>
-
+
@@ -6693,38 +6693,8 @@ L72 43.2" style="fill:none;stroke:#000000;"/>
-
-
-
-
-
-
+
+
-
-
+
+
-
-
-
-
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py
index cd1769095e07..378a4c9e3436 100644
--- a/lib/matplotlib/tests/test_axes.py
+++ b/lib/matplotlib/tests/test_axes.py
@@ -666,14 +666,15 @@ def test_hist_log():
ax = fig.add_subplot(111)
ax.hist(data, fill=False, log=True)
+def contour_dat():
+ x = np.linspace(-3, 5, 150)
+ y = np.linspace(-3, 5, 120)
+ z = np.cos(x) + np.sin(y[:, np.newaxis])
+ return x, y, z
+
@image_comparison(baseline_images=['contour_hatching'])
def test_contour_hatching():
- x = np.linspace(-3, 5, 150).reshape(1, -1)
- y = np.linspace(-3, 5, 120).reshape(-1, 1)
- z = np.cos(x) + np.sin(y)
-
- # we no longer need x and y to be 2 dimensional, so flatten them.
- x, y = x.flatten(), y.flatten()
+ x, y, z = contour_dat()
fig = plt.figure()
ax = fig.add_subplot(111)
@@ -681,6 +682,29 @@ def test_contour_hatching():
cmap=plt.get_cmap('gray'),
extend='both', alpha=0.5)
+@image_comparison(baseline_images=['contour_colorbar'])
+def test_contour_colorbar():
+ x, y, z = contour_dat()
+
+ fig = plt.figure()
+ ax = fig.add_subplot(111)
+ cs = ax.contourf(x, y, z, levels=np.arange(-1.8, 1.801, 0.2),
+ cmap=plt.get_cmap('RdBu'),
+ vmin=-0.6,
+ vmax=0.6,
+ extend='both')
+ cs1 = ax.contour(x, y, z, levels=np.arange(-2.2, -0.599, 0.2),
+ colors=['y'],
+ linestyles='solid',
+ linewidths=2)
+ cs2 = ax.contour(x, y, z, levels=np.arange(0.6, 2.2, 0.2),
+ colors=['c'],
+ linewidths=2)
+ cbar = fig.colorbar(cs, ax=ax)
+ cbar.add_lines(cs1)
+ cbar.add_lines(cs2, erase=False)
+
+
@image_comparison(baseline_images=['hist2d'])
def test_hist2d():
np.random.seed(0)
@@ -765,7 +789,7 @@ def test_log_scales():
plt.plot(np.log(np.linspace(0.1, 100)))
ax.set_yscale('log', basey=5.5)
ax.set_xscale('log', basex=9.0)
-
+
@image_comparison(baseline_images=['stackplot_test_image'])
def test_stackplot():