8000 contourpy algorithm kwarg, tests and docs · matplotlib/matplotlib@3fcf814 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3fcf814

Browse files
committed
contourpy algorithm kwarg, tests and docs
1 parent ad7ab66 commit 3fcf814

File tree

8 files changed

+118
-7
lines changed

8 files changed

+118
-7
lines changed

doc/api/next_api_changes/behavior/22229-TAC.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
AritistList proxies copy contents on iteration
2-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1+
ArtistList proxies copy contents on iteration
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33

44
When iterating over the contents of the the dynamically generated proxy lists
55
for the Artist-type accessors (see :ref:`Behavioural API Changes 3.5 - Axes
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
New algorithm keyword argument to contour and contourf
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
The contouring functions `~matplotlib.axes.Axes.contour` and
5+
`~matplotlib.axes.Axes.contourf` have a new keyword argument ``algorithm`` to
6+
control which algorithm is used to calculate the contours. There is a choice
7+
of four algorithms to use, and the default is to use ``algorithm='mpl2014'``
8+
which is the same algorithm that Matplotlib has been using since 2014.
9+
10+
Other possible values of the ``algorithm`` keyword argument are ``'mpl2005'``,
11+
``'serial'`` and ``'threaded'``; see the
12+
`ContourPy documentation <https://contourpy.readthedocs.io>`_ for further
13+
details.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
New external dependency ContourPy used for quad contour calculations
2+
--------------------------------------------------------------------
3+
4+
Previously Matplotlib shipped its own C++ code for calculating the contours of
5+
quad grids . Now the external library
6+
`ContourPy <https://github.com/contourpy/contourpy>`_ is used instead. There
7+
is a choice of four algorithms to use, controlled by the ``algorithm`` keyword
8+
argument to the functions `~matplotlib.axes.Axes.contour` and
9+
`~matplotlib.axes.Axes.contourf`. The default behaviour is to use
10+
``algorithm='mpl2014'`` which is the same algorithm that Matplotlib has been
11+
using since 2014.
12+
13+
See the `ContourPy documentation <https://contourpy.readthedocs.io>`_ for
14+
further details of the different algorithms.
15+
16+
.. note::
17+
18+
Contour lines and polygons produced by ``algorithm='mpl2014'`` will be the
19+
same as those produced before this change to within floating-point
20+
tolerance. The exception is for duplicate points, i.e. contours containing
21+
adjacent (x, y) points that are identical; previously the duplicate points
22+
were removed, now they are kept. Contours affected by this will produce the
23+
same visual output, but there will be a greater number of points in the
24+
contours.

lib/matplotlib/contour.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,7 +1420,7 @@ class QuadContourSet(ContourSet):
14201420
%(contour_set_attributes)s
14211421
"""
14221422

1423-
def _process_args(self, *args, corner_mask=None, **kwargs):
1423+
def _process_args(self, *args, corner_mask=None, algorithm=None, **kwargs):
14241424
"""
14251425
Process args and kwargs.
14261426
"""
@@ -1433,17 +1433,28 @@ def _process_args(self, *args, corner_mask=None, **kwargs):
14331433
contour_generator = args[0]._contour_generator
14341434
self._mins = args[0]._mins
14351435
self._maxs = args[0]._maxs
1436+
self._algorithm = args[0]._algorithm
14361437
else:
14371438
import contourpy
14381439

1440+
if algorithm is None:
1441+
algorithm = mpl.rcParams['contour.algorithm']
1442+
mpl.rcParams.validate["contour.algorithm"](algorithm)
1443+
self._algorithm = algorithm
1444+
14391445
if corner_mask is None:
1440-
corner_mask = mpl.rcParams['contour.corner_mask']
1446+
if self._algorithm == "mpl2005":
1447+
# mpl2005 does not support corner_mask=True so if not
1448+
# specifically requested then disable it.
1449+
corner_mask = False
1450+
else:
1451+
corner_mask = mpl.rcParams['contour.corner_mask']
14411452
self._corner_mask = corner_mask
14421453

14431454
x, y, z = self._contour_args(args, kwargs)
14441455

14451456
contour_generator = contourpy.contour_generator(
1446-
x, y, z, name="mpl2014", corner_mask=self._corner_mask,
1457+
x, y, z, name=self._algorithm, corner_mask=self._corner_mask,
14471458
line_type=contourpy.LineType.SeparateCode,
14481459
fill_type=contourpy.FillType.OuterCode,
14491460
chunk_size=self.nchunk)
@@ -1771,6 +1782,15 @@ def _initialize_x_y(self, z):
17711782
Hatching is supported in the PostScript, PDF, SVG and Agg
17721783
backends only.
17731784
1785+
algorithm : {'mpl2005', 'mpl2014', 'serial', 'threaded'}, optional
1786+
Which contouring algorithm to use to calculate the contour lines and
1787+
polygons. The algorithms are implemented in
1788+
`ContourPy <https://github.com/contourpy/contourpy>`_, consult the
1789+
`ContourPy documentation <https://contourpy.readthedocs.io>`_ for
1790+
further information.
1791+
1792+
The default is taken from :rc:`contour.algorithm`.
1793+
17741794
data : indexable object, optional
17751795
DATA_PARAMETER_PLACEHOLDER
17761796
@@ -1791,5 +1811,5 @@ def _initialize_x_y(self, z):
17911811
3. `.contour` and `.contourf` use a `marching squares
17921812
<https://en.wikipedia.org/wiki/Marching_squares>`_ algorithm to
17931813
compute contour locations. More information can be found in
1794-
the source ``src/_contour.h``.
1814+
`ContourPy documentation <https://contourpy.readthedocs.io>`_.
17951815
""")

lib/matplotlib/mpl-data/matplotlibrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,10 +607,11 @@
607607
## * CONTOUR PLOTS *
608608
## ***************************************************************************
609609
#contour.negative_linestyle: dashed # string or on-off ink sequence
610-
#contour.corner_mask: True # {True, False, legacy}
610+
#contour.corner_mask: True # {True, False}
611611
#contour.linewidth: None # {float, None} Size of the contour line
612612
# widths. If set to None, it falls back to
613613
# `line.linewidth`.
614+
#contour.algorithm: mpl2014 # {mpl2005, mpl2014, serial, threaded}
614615

615616

616617
## ***************************************************************************

lib/matplotlib/rcsetup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,7 @@ def _convert_validator_spec(key, conv):
960960
"contour.negative_linestyle": _validate_linestyle,
961961
"contour.corner_mask": validate_bool,
962962
"contour.linewidth": validate_float_or_None,
963+
"contour.algorithm": ["mpl2005", "mpl2014", "serial", "threaded"],
963964

964965
# errorbar props
965966
"errorbar.capsize": validate_float,
Loading

lib/matplotlib/tests/test_contour.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import platform
33
import re
44

5+
import contourpy
56
import numpy as np
67
from numpy.testing import assert_array_almost_equal
78
import matplotlib as mpl
@@ -532,3 +533,54 @@ def test_contour_legend_elements():
532533
assert all(isinstance(a, LineCollection) for a in artists)
533534
assert all(same_color(a.get_color(), c)
534535
for a, c in zip(artists, colors))
536+
537+
538+
@pytest.mark.parametrize("algorithm, klass",
539+
[('mpl2005', contourpy.Mpl2005ContourGenerator),
540+
('mpl2014', contourpy.Mpl2014ContourGenerator),
541+
('serial', contourpy.SerialContourGenerator),
542+
('threaded', contourpy.ThreadedContourGenerator),
543+
('invalid', None)])
544+
def test_algorithm_name(algorithm, klass):
545+
z = np.array([[1.0, 2.0], [3.0, 4.0]])
546+
if klass is not None:
547+
cs = plt.contourf(z, algorithm=algorithm)
548+
assert isinstance(cs._contour_generator, klass)
549+
else:
550+
with pytest.raises(ValueError):
551+
plt.contourf(z, algorithm=algorithm)
552+
553+
554+
@pytest.mark.parametrize("algorithm",
555+
['mpl2005', 'mpl2014', 'serial', 'threaded'])
556+
def test_algorithm_supports_corner_mask(algorithm):
557+
z = np.array([[1.0, 2.0], [3.0, 4.0]])
558+
559+
# All algorithms support corner_mask=False
560+
plt.contourf(z, algorithm=algorithm, corner_mask=False)
561+
562+
# Only some algorithms support corner_mask=True
563+
if algorithm != 'mpl2005':
564+
plt.contourf(z, algorithm=algorithm, corner_mask=True)
565+
else:
566+
with pytest.raises(ValueError):
567+
plt.contourf(z, algorithm=algorithm, corner_mask=True)
568+
569+
570+
@image_comparison(baseline_images=['contour_all_algorithms'],
571+
extensions=['png'], remove_text=True)
572+
def test_all_algorithms():
573+
algorithms = ['mpl2005', 'mpl2014', 'serial', 'threaded']
574+
575+
rng = np.random.default_rng(2981)
576+
x, y = np.meshgrid(np.linspace(0.0, 1.0, 10), np.linspace(0.0, 1.0, 6))
577+
z = np.sin(15*x)*np.cos(10*y) + rng.normal(scale=0.5, size=(6, 10))
578+
mask = np.zeros_like(z, dtype=bool)
579+
mask[3, 7] = True
580+
z = np.ma.array(z, mask=mask)
581+
582+
_, axs = plt.subplots(2, 2)
583+
for ax, algorithm in zip(axs.ravel(), algorithms):
584+
ax.contourf(x, y, z, algorithm=algorithm)
585+
ax.contour(x, y, z, algorithm=algorithm, colors='k')
586+
ax.set_title(algorithm)

0 commit comments

Comments
 (0)
0