8000 Merge pull request #27576 from dstansby/log-contour-levels · matplotlib/matplotlib@7fd4c67 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7fd4c67

Browse files
authored
Merge pull request #27576 from dstansby/log-contour-levels
Fix specifying number of levels with log contour
2 parents 07b3032 + 8c0775e commit 7fd4c67

File tree

3 files changed

+50
-21
lines changed

3 files changed

+50
-21
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Maximum levels on log-scaled contour plots are now respected
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
When plotting contours with a log norm, passing an integer value to the ``levels``
4+
argument to cap the maximum number of contour levels now works as intended.

lib/matplotlib/contour.py

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -964,12 +964,29 @@ def changed(self):
964964
label.set_color(self.labelMappable.to_rgba(cv))
965965
super().changed()
966966

967-
def _autolev(self, N):
967+
def _ensure_locator_exists(self, N):
968968
"""
969-
Select contour levels to span the data.
969+
Set a locator on this ContourSet if it's not already set.
970970
971-
The target number of levels, *N*, is used only when the
972-
scale is not log and default locator is used.
971+
Parameters
972+
----------
973+
N : int or None
974+
If *N* is an int, it is used as the target number of levels.
975+
Otherwise when *N* is None, a reasonable default is chosen;
976+
for logscales the LogLocator chooses, N=7 is the default
977+
otherwise.
978+
"""
979+
if self.locator is None:
980+
if self.logscale:
981+
self.locator = ticker.LogLocator(numticks=N)
982+
else:
983+
if N is None:
984+
N = 7 # Hard coded default
985+
self.locator = ticker.MaxNLocator(N + 1, min_n_ticks=1)
986+
987+
def _autolev(self):
988+
"""
989+
Select contour levels to span the data.
973990
974991
We need two more levels for filled contours than for
975992
line contours, because for the latter we need to specify
@@ -978,12 +995,6 @@ def _autolev(self, N):
978995
one contour line, but two filled regions, and therefore
979996
three levels to provide boundaries for both regions.
980997
"""
981-
if self.locator is None:
982-
if self.logscale:
983-
self.locator = ticker.LogLocator()
984-
else:
985-
self.locator = ticker.MaxNLocator(N + 1, min_n_ticks=1)
986-
987998
lev = self.locator.tick_values(self.zmin, self.zmax)
988999

9891000
try:
@@ -1011,22 +1022,21 @@ def _process_contour_level_args(self, args, z_dtype):
10111022
"""
10121023
Determine the contour levels and store in self.levels.
10131024
"""
1014-
if self.levels is None:
1025+
levels_arg = self.levels
1026+
if levels_arg is None:
10151027
if args:
1028+
# Set if levels manually provided
10161029
levels_arg = args[0]
10171030
elif np.issubdtype(z_dtype, bool):
1018-
if self.filled:
1019-
levels_arg = [0, .5, 1]
1020-
else:
1021-
levels_arg = [.5]
1022-
else:
1023-
levels_arg = 7 # Default, hard-wired.
1024-
else:
1025-
levels_arg = self.levels
1026-
if isinstance(levels_arg, Integral):
1027-
self.levels = self._autolev(levels_arg)
1031+
# Set default values for bool data types
1032+
levels_arg = [0, .5, 1] if self.filled else [.5]
1033+
1034+
if isinstance(levels_arg, Integral) or levels_arg is None:
1035+
self._ensure_locator_exists(levels_arg)
1036+
self.levels = self._autolev()
10281037
else:
10291038
self.levels = np.asarray(levels_arg, np.float64)
1039+
10301040
if self.filled and len(self.levels) < 2:
10311041
raise ValueError("Filled contours require at least 2 levels.")
10321042
if len(self.levels) > 1 and np.min(np.diff(self.levels)) <= 0.0:

lib/matplotlib/tests/test_contour.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,21 @@ def test_log_locator_levels():
214214
assert_array_almost_equal(cb.ax.get_yticks(), c.levels)
215215

216216

217+
@pytest.mark.parametrize("n_levels", [2, 3, 4, 5, 6])
218+
def test_lognorm_levels(n_levels):
219+
x, y = np.mgrid[1:10:0.1, 1:10:0.1]
220+
data = np.abs(np.sin(x)* 2364 np.exp(y))
221+
222+
fig, ax = plt.subplots()
223+
im = ax.contour(x, y, data, norm=LogNorm(), levels=n_levels)
224+
fig.colorbar(im, ax=ax)
225+
226+
levels = im.levels
227+
visible_levels = levels[(levels <= data.max()) & (levels >= data.min())]
228+
# levels parameter promises "no more than n+1 "nice" contour levels "
229+
assert len(visible_levels) <= n_levels + 1
230+
231+
217232
@image_comparison(['contour_datetime_axis.png'], style='mpl20')
218233
def test_contour_datetime_axis():
219234
fig = plt.figure()

0 commit comments

Comments
 (0)
0