From 20aef48e437c8687ac61c78c9ab0c1cdff2f300b Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Wed, 4 Mar 2026 15:22:04 -0800 Subject: [PATCH 1/4] ENH: allow non-monotonic contour --- lib/matplotlib/contour.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 77dc58740971..089986ddc956 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -1029,8 +1029,12 @@ def _process_contour_level_args(self, args, z_dtype): if self.filled and len(self.levels) < 2: raise ValueError("Filled contours require at least 2 levels.") - if len(self.levels) > 1 and np.min(np.diff(self.levels)) <= 0.0: - raise ValueError("Contour levels must be increasing") + + # check if filled contours are monotonically increasing: + if (self.filled and len(self.levels) > 1 and + np.any(np.diff(self.levels) <= 0)): + raise ValueError( + "Filled contour levels must be monotonically increasing") def _process_levels(self): """ @@ -1496,7 +1500,7 @@ def _initialize_x_y(self, z): *n*=7 is the default. If array-like, draw contour lines at the specified levels. - The values must be in increasing order. + For filled contours, the values must be in increasing order. If not specified, a reasonable default is automatically chosen. For linear scales, this corresponds to *levels=7*. For logarithmic From ff05e352d7fdb0fa7d758dc246d25322fe0300e1 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Wed, 4 Mar 2026 15:38:53 -0800 Subject: [PATCH 2/4] TST: test unsorted contour levels --- lib/matplotlib/tests/test_contour.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index e242c219df10..d86c6f1f244a 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -335,6 +335,24 @@ def test_contourf_decreasing_levels(): plt.contourf(z, [1.0, 0.0]) +@check_figures_equal() +def test_contour_random_levels(fig_ref, fig_test): + # test that it doesn't matter if the levels are given in a random order and + # that their colors are matched to the correct levels. + Z = np.arange(100).reshape(10, 10) + ax_ref = fig_ref.subplots() + ax_test = fig_test.subplots() + + levels0 = np.array([6, 30, 10, 20, 33, 80, 53]) + colors0 = ['red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'black'] + ind = np.argsort(levels0) + + # sorted: + ax_ref.contour(Z, levels=levels0[ind], colors=np.array(colors0)[ind]) + # unsorted: + ax_test.contour(Z, levels=levels0, colors=colors0) + + def test_contourf_symmetric_locator(): # github issue 7271 z = np.arange(12).reshape((3, 4)) From c3b5b58640cc278f510b62790676fb024f046b28 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Fri, 6 Mar 2026 08:02:04 -0800 Subject: [PATCH 3/4] DOC: clarify color, line mapping --- lib/matplotlib/contour.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 089986ddc956..1bb09df44685 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -1523,8 +1523,8 @@ def _initialize_x_y(self, z): The colors of the levels, i.e. the lines for `.contour` and the areas for `.contourf`. - The sequence is cycled for the levels in ascending order. If the - sequence is shorter than the number of levels, it's repeated. + The colors are mapped to the levels in the order specified, repeating as + necessary if there are fewer colors than levels. As a shortcut, a single color may be used in place of one-element lists, i.e. ``'red'`` instead of ``['red']`` to color all levels with the same color. @@ -1645,8 +1645,8 @@ def _initialize_x_y(self, z): If a number, all levels will be plotted with this linewidth. - If a sequence, the levels in ascending order will be plotted with - the linewidths in the order specified. + If a sequence, the linewidths are mapped to the levels in order, + repeating as necessary if there are fewer linewidths than levels. If None, this falls back to :rc:`lines.linewidth`. From 7e2e5b5f46b984c2a357fa14c035c2db3417424b Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 17 Mar 2026 12:26:40 -0700 Subject: [PATCH 4/4] TST: test reversed, but not random levels --- lib/matplotlib/tests/test_contour.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index d86c6f1f244a..54f821e94823 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -336,21 +336,19 @@ def test_contourf_decreasing_levels(): @check_figures_equal() -def test_contour_random_levels(fig_ref, fig_test): - # test that it doesn't matter if the levels are given in a random order and - # that their colors are matched to the correct levels. +def test_contour_decreasing_levels(fig_ref, fig_test): + # Check that contour works with decreasing levels: Z = np.arange(100).reshape(10, 10) ax_ref = fig_ref.subplots() ax_test = fig_test.subplots() - levels0 = np.array([6, 30, 10, 20, 33, 80, 53]) + levels0 = np.array([6, 10, 20, 30, 33, 53, 80]) colors0 = ['red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'black'] - ind = np.argsort(levels0) # sorted: - ax_ref.contour(Z, levels=levels0[ind], colors=np.array(colors0)[ind]) - # unsorted: - ax_test.contour(Z, levels=levels0, colors=colors0) + ax_ref.contour(Z, levels=levels0, colors=np.array(colors0)) + # reversed: + ax_test.contour(Z, levels=levels0[::-1], colors=colors0[::-1]) def test_contourf_symmetric_locator():