Skip to content

Commit 7967c82

Browse files
committed
Merge pull request #3874 from ianthomas23/contour
New C++ contour code with corner_mask kwarg
2 parents 532eda4 + 27154d3 commit 7967c82

19 files changed

+12589
-1215
lines changed

CHANGELOG

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1+
2015-02-19 Rewrite of C++ code that calculates contours to add support for
2+
corner masking. This is controlled by the 'corner_mask' keyword
3+
in plotting commands 'contour' and 'contourf'. - IMT
4+
15
2015-01-23 Text bounding boxes are now computed with advance width rather than
2-
ink area. This may result in slightly different placement of text.
6+
ink area. This may result in slightly different placement of text.
37

48
2014-10-27 Allowed selection of the backend using the `MPLBACKEND` environment
59
variable. Added documentation on backend selection methods.
610

711
2014-09-27 Overhauled `colors.LightSource`. Added `LightSource.hillshade` to
8-
allow the independent generation of illumination maps. Added new
9-
types of blending for creating more visually appealing shaded relief
10-
plots (e.g. `blend_mode="overlay"`, etc, in addition to the legacy
11-
"hsv" mode).
12+
allow the independent generation of illumination maps. Added new
13+
types of blending for creating more visually appealing shaded relief
14+
plots (e.g. `blend_mode="overlay"`, etc, in addition to the legacy
15+
"hsv" mode).
1216

1317
2014-06-10 Added Colorbar.remove()
1418

@@ -64,7 +68,7 @@
6468
of a generator at line 263.
6569

6670
2014-04-02 Added `clipon=False` to patch creation of wedges and shadows
67-
in `pie`.
71+
in `pie`.
6872

6973
2014-02-25 In backend_qt4agg changed from using update -> repaint under
7074
windows. See comment in source near `self._priv_update` for
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Changed behaviour of contour plots
2+
``````````````````````````````````
3+
4+
The default behaviour of :func:`~matplotlib.pyplot.contour` and
5+
:func:`~matplotlib.pyplot.contourf` when using a masked array is now determined
6+
by the new keyword argument `corner_mask`, or if this is not specified then
7+
the new rcParam `contour.corner_mask` instead. The new default behaviour is
8+
equivalent to using `corner_mask=True`; the previous behaviour can be obtained
9+
using `corner_mask=False` or by changing the rcParam. The example
10+
http://matplotlib.org/examples/pylab_examples/contour_corner_mask.py
11+
demonstrates the difference. Use of the old contouring algorithm, which is
12+
obtained with `corner_mask='legacy'`, is now deprecated.
13+
14+
Contour labels may now appear in different places than in earlier versions of
15+
matplotlib.
16+
17+
In addition, the keyword argument `nchunk` now applies to
18+
:func:`~matplotlib.pyplot.contour` as well as
19+
:func:`~matplotlib.pyplot.contourf`, and it subdivides the domain into
20+
subdomains of exactly `nchunk` by `nchunk` quads, whereas previously it was
21+
only roughly `nchunk` by `nchunk` quads.
22+
23+
The C/C++ object that performs contour calculations used to be stored in the
24+
public attribute QuadContourSet.Cntr, but is now stored in a private attribute
25+
and should not be accessed by end users.

doc/users/whats_new/plotting.rst

+16
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,19 @@ Added center and frame kwargs to pie
33

44
These control where the center of the pie graph are and if
55
the Axes frame is shown.
6+
7+
Contour plot corner masking
8+
```````````````````````````
9+
10+
Ian Thomas rewrote the C++ code that calculates contours to add support for
11+
corner masking. This is controlled by a new keyword argument
12+
``corner_mask`` in the functions :func:`~matplotlib.pyplot.contour` and
13+
:func:`~matplotlib.pyplot.contourf`. The previous behaviour, which is now
14+
obtained using ``corner_mask=False``, was for a single masked point to
15+
completely mask out all four quads touching that point. The new behaviour,
16+
obtained using ``corner_mask=True``, only masks the corners of those
17+
quads touching the point; any triangular corners comprising three unmasked
18+
points are contoured as usual. If the ``corner_mask`` keyword argument is not
19+
specified, the default value is taken from rcParams.
20+
21+
.. plot:: mpl_examples/pylab_examples/contour_corner_mask.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env python
2+
"""
3+
Illustrate the difference between corner_mask=False and corner_mask=True
4+
for masked contour plots.
5+
"""
6+
import matplotlib.pyplot as plt
7+
import numpy as np
8+
9+
# Data to plot.
10+
x, y = np.meshgrid(np.arange(7), np.arange(10))
11+
z = np.sin(0.5*x)*np.cos(0.52*y)
12+
13+
# Mask various z values.
14+
mask = np.zeros_like(z, dtype=np.bool)
15+
mask[2, 3:5] = True
16+
mask[3:5, 4] = True
17+
mask[7, 2] = True
18+
mask[5, 0] = True
19+
mask[0, 6] = True
20+
z = np.ma.array(z, mask=mask)
21+
22+
corner_masks = [False, True]
23+
for i, corner_mask in enumerate(corner_masks):
24+
plt.subplot(1, 2, i+1)
25+
cs = plt.contourf(x, y, z, corner_mask=corner_mask)
26+
plt.contour(cs, colors='k')
27+
plt.title('corner_mask = {}'.format(corner_mask))
28+
29+
# Plot grid.
30+
plt.grid(c='k', ls='-', alpha=0.3)
31+
32+
# Indicate masked points with red circles.
33+
plt.plot(np.ma.array(x, mask=~mask), y, 'ro')
34+
35+
plt.show()

lib/matplotlib/contour.py

+71-24
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import numpy as np
1414
from numpy import ma
1515
import matplotlib._cntr as _cntr
16+
import matplotlib._contour as _contour
1617
import matplotlib.path as mpath
1718
import matplotlib.ticker as ticker
1819
import matplotlib.cm as cm
@@ -989,9 +990,10 @@ def get_transform(self):
989990

990991
def __getstate__(self):
991992
state = self.__dict__.copy()
992-
# the C object Cntr cannot currently be pickled. This isn't a big issue
993-
# as it is not actually used once the contour has been calculated
994-
state['Cntr'] = None
993+
# the C object _contour_generator cannot currently be pickled. This
994+
# isn't a big issue as it is not actually used once the contour has
995+
# been calculated.
996+
state['_contour_generator'] = None
995997
return state
996998

997999
def legend_elements(self, variable_name='x', str_format=str):
@@ -1433,18 +1435,34 @@ def _process_args(self, *args, **kwargs):
14331435
Process args and kwargs.
14341436
"""
14351437
if isinstance(args[0], QuadContourSet):
1436-
C = args[0].Cntr
14371438
if self.levels is None:
14381439
self.levels = args[0].levels
14391440
self.zmin = args[0].zmin
14401441
self.zmax = args[0].zmax
1442+
self._corner_mask = args[0]._corner_mask
1443+
if self._corner_mask == 'legacy':
1444+
contour_generator = args[0].Cntr
1445+
else:
1446+
contour_generator = args[0]._contour_generator
14411447
else:
14421448
x, y, z = self._contour_args(args, kwargs)
14431449

14441450
_mask = ma.getmask(z)
1445-
if _mask is ma.nomask:
1451+
if _mask is ma.nomask or not _mask.any():
14461452
_mask = None
1447-
C = _cntr.Cntr(x, y, z.filled(), _mask)
1453+
1454+
self._corner_mask = kwargs.get('corner_mask', None)
1455+
if self._corner_mask is None:
1456+
self._corner_mask = mpl.rcParams['contour.corner_mask']
1457+
1458+
if self._corner_mask == 'legacy':
1459+
cbook.warn_deprecated('1.5',
1460+
name="corner_mask='legacy'",
1461+
alternative='corner_mask=False or True')
1462+
contour_generator = _cntr.Cntr(x, y, z.filled(), _mask)
1463+
else:
1464+
contour_generator = _contour.QuadContourGenerator(
1465+
x, y, z.filled(), _mask, self._corner_mask, self.nchunk)
14481466

14491467
t = self.get_transform()
14501468

@@ -1465,7 +1483,10 @@ def _process_args(self, *args, **kwargs):
14651483
self.ax.update_datalim([(x0, y0), (x1, y1)])
14661484
self.ax.autoscale_view(tight=True)
14671485

1468-
self.Cntr = C
1486+
if self._corner_mask == 'legacy':
1487+
self.Cntr = contour_generator
1488+
else:
1489+
self._contour_generator = contour_generator
14691490

14701491
def _get_allsegs_and_allkinds(self):
14711492
"""
@@ -1476,20 +1497,28 @@ def _get_allsegs_and_allkinds(self):
14761497
lowers, uppers = self._get_lowers_and_uppers()
14771498
allkinds = []
14781499
for level, level_upper in zip(lowers, uppers):
1479-
nlist = self.Cntr.trace(level, level_upper,
1480-
nchunk=self.nchunk)
1481-
nseg = len(nlist) // 2
1482-
segs = nlist[:nseg]
1483-
kinds = nlist[nseg:]
1484-
allsegs.append(segs)
1500+
if self._corner_mask == 'legacy':
1501+
nlist = self.Cntr.trace(level, level_upper,
1502+
nchunk=self.nchunk)
1503+
nseg = len(nlist) // 2
1504+
vertices = nlist[:nseg]
1505+
kinds = nlist[nseg:]
1506+
else:
1507+
vertices, kinds = \
1508+
self._contour_generator.create_filled_contour(
1509+
level, level_upper)
1510+
allsegs.append(vertices)
14851511
allkinds.append(kinds)
14861512
else:
14871513
allkinds = None
14881514
for level in self.levels:
1489-
nlist = self.Cntr.trace(level)
1490-
nseg = len(nlist) // 2
1491-
segs = nlist[:nseg]
1492-
allsegs.append(segs)
1515+
if self._corner_mask == 'legacy':
1516+
nlist = self.Cntr.trace(level)
1517+
nseg = len(nlist) // 2
1518+
vertices = nlist[:nseg]
1519+
else:
1520+
vertices = self._contour_generator.create_contour(level)
1521+
allsegs.append(vertices)
14931522
return allsegs, allkinds
14941523

14951524
def _contour_args(self, args, kwargs):
@@ -1672,6 +1701,20 @@ def _initialize_x_y(self, z):
16721701
16731702
Optional keyword arguments:
16741703
1704+
*corner_mask*: [ *True* | *False* | 'legacy' ]
1705+
Enable/disable corner masking, which only has an effect if *Z* is
1706+
a masked array. If *False*, any quad touching a masked point is
1707+
masked out. If *True*, only the triangular corners of quads
1708+
nearest those points are always masked out, other triangular
1709+
corners comprising three unmasked points are contoured as usual.
1710+
If 'legacy', the old contouring algorithm is used, which is
1711+
equivalent to *False* and is deprecated, only remaining whilst the
1712+
new algorithm is tested fully.
1713+
1714+
If not specified, the default is taken from
1715+
rcParams['contour.corner_mask'], which is True unless it has
1716+
been modified.
1717+
16751718
*colors*: [ *None* | string | (mpl_colors) ]
16761719
If *None*, the colormap specified by cmap will be used.
16771720
@@ -1750,6 +1793,15 @@ def _initialize_x_y(self, z):
17501793
filled contours, the default is *True*. For line contours,
17511794
it is taken from rcParams['lines.antialiased'].
17521795
1796+
*nchunk*: [ 0 | integer ]
1797+
If 0, no subdivision of the domain. Specify a positive integer to
1798+
divide the domain into subdomains of *nchunk* by *nchunk* quads.
1799+
Chunking reduces the maximum length of polygons generated by the
1800+
contouring algorithm which reduces the rendering workload passed
1801+
on to the backend and also requires slightly less RAM. It can
1802+
however introduce rendering artifacts at chunk boundaries depending
1803+
on the backend, the *antialiased* flag and value of *alpha*.
1804+
17531805
contour-only keyword arguments:
17541806
17551807
*linewidths*: [ *None* | number | tuple of numbers ]
@@ -1774,13 +1826,6 @@ def _initialize_x_y(self, z):
17741826
17751827
contourf-only keyword arguments:
17761828
1777-
*nchunk*: [ 0 | integer ]
1778-
If 0, no subdivision of the domain. Specify a positive integer to
1779-
divide the domain into subdomains of roughly *nchunk* by *nchunk*
1780-
points. This may never actually be advantageous, so this option may
1781-
be removed. Chunking introduces artifacts at the chunk boundaries
1782-
unless *antialiased* is *False*.
1783-
17841829
*hatches*:
17851830
A list of cross hatch patterns to use on the filled areas.
17861831
If None, no hatching will be added to the contour.
@@ -1802,4 +1847,6 @@ def _initialize_x_y(self, z):
18021847
.. plot:: mpl_examples/pylab_examples/contour_demo.py
18031848
18041849
.. plot:: mpl_examples/pylab_examples/contourf_demo.py
1850+
1851+
.. plot:: mpl_examples/pylab_examples/contour_corner_mask.py
18051852
"""

lib/matplotlib/rcsetup.py

+9
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,13 @@ def validate_negative_linestyle_legacy(s):
389389
return (0, dashes) # (offset, (solid, blank))
390390

391391

392+
def validate_corner_mask(s):
393+
if s == 'legacy':
394+
return s
395+
else:
396+
return validate_bool(s)
397+
398+
392399
def validate_tkpythoninspect(s):
393400
# Introduced 2010/07/05
394401
warnings.warn("tk.pythoninspect is obsolete, and has no effect")
@@ -589,8 +596,10 @@ def __call__(self, s):
589596
'image.origin': ['upper', six.text_type], # lookup table
590597
'image.resample': [False, validate_bool],
591598

599+
# contour props
592600
'contour.negative_linestyle': ['dashed',
593601
validate_negative_linestyle_legacy],
602+
'contour.corner_mask': [True, validate_corner_mask],
594603

595604
# axes props
596605
'axes.axisbelow': [False, validate_bool],

0 commit comments

Comments
 (0)