8000 ENH: improve contourf color autoscaling · matplotlib/matplotlib@e67f71e · GitHub
[go: up one dir, main page]

Skip to content

Commit e67f71e

Browse files
committed
ENH: improve contourf color autoscaling
Contour level auto-selection: clip unused Locator output: Locator.tick_values() returns levels beyond the data limits. In the case of LogLocator with a small data range, the overrun can be large because it is expanding to the decade points. In addition, no account was being taken of the "extend" kwarg. With this changeset, the outermost levels will be the miminum required to include the data interval in the default case, and will be reduced when "extend" is used so that some of the data range will fall in the extended sections. This is expected to be rare, however; normally the "extend" kwarg would be used only when levels are explicitly set, not auto-selected with a Locator. The contour_hatching test results were modified. The difference was reduced by putting the hatches in the same order as they were in the reference figure, but the images still don't match because the gray shades have changed, as expected. (Note that in the original and new reference figures the pdf and svg backends handle the alpha differently than the agg backend. This is unrelated to the PR.) For logscale contour colors, interpolate in screen space by taking the geometric mean of adjacent levels. The test_contour.py::test_contourf_log_extension code and image have been modified for this change in colors, and to make the behavior more clear in the case where levels are not specified. Add a note to next_api_changes. Treat contour levels (almost) the same as contourf levels Update additional test images affected by the contour color changes. mplot3d: don't assume every contour level has a line.
1 parent 0f05cf4 commit e67f71e

31 files changed

+42695
-43473
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Contour color autoscaling improvements
2+
--------------------------------------
3+
4+
Selection of contour levels is now the same for contour and
5+
contourf; previously, for contour, levels outside the data range were
6+
deleted. (Exception: if no contour levels are found within the
7+
data range, the `levels` attribute is replaced with a list holding
8+
only the minimum of the data range.)
9+
10+
When contour is called with levels specified as a target number rather
11+
than a list, and the 'extend' kwarg is used, the levels are now chosen
12+
such that some data typically will fall in the extended range.
13+
14+
When contour is called with a `LogNorm` or a `LogLocator`, it will now
15+
select colors using the geometric mean rather than the arithmetic mean
16+
of the contour levels.

lib/matplotlib/contour.py

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,22 +1174,45 @@ def _autolev(self, N):
11741174
"""
11751175
Select contour levels to span the data.
11761176
1177+
The target number of levels, *N*, is used only when the
1178+
scale is not log and default locator is used.
1179+
11771180
We need two more levels for filled contours than for
11781181
line contours, because for the latter we need to specify
11791182
the lower and upper boundary of each range. For example,
11801183
a single contour boundary, say at z = 0, requires only
11811184
one contour line, but two filled regions, and therefore
11821185
three levels to provide boundaries for both regions.
11831186
"""
1187+
self._auto = True
11841188
if self.locator is None:
11851189
if self.logscale:
11861190
self.locator = ticker.LogLocator()
11871191
else:
11881192
self.locator = ticker.MaxNLocator(N + 1, min_n_ticks=1)
11891193

11901194
lev = self.locator.tick_values(self.zmin, self.zmax)
1191-
self._auto = True
1192-
return lev
1195+
1196+
try:
1197+
if self.locator._symmetric:
1198+
return lev
1199+
except AttributeError:
1200+
pass
1201+
1202+
# Trim excess levels the locator may have supplied.
1203+
under = np.nonzero(lev < self.zmin)[0]
1204+
i0 = under[-1] if len(under) else 0
1205+
over = np.nonzero(lev > self.zmax)[0]
1206+
i1 = over[0] + 1 if len(over) else len(lev)
1207+
if self.extend in ('min', 'both'):
1208+
i0 += 1
1209+
if self.extend in ('max', 'both'):
1210+
i1 -= 1
1211+
1212+
if i1 - i0 < 3:
1213+
i0, i1 = 0, len(lev)
1214+
< F438 code>1215+
return lev[i0:i1]
11931216

11941217
def _contour_level_args(self, z, args):
11951218
"""
@@ -1220,8 +1243,8 @@ def _contour_level_args(self, z, args):
12201243

12211244
if not self.filled:
12221245
inside = (self.levels > self.zmin) & (self.levels < self.zmax)
1223-
self.levels = self.levels[inside]
1224-
if len(self.levels) == 0:
1246+
levels_in = self.levels[inside]
1247+
if len(levels_in) == 0:
12251248
self.levels = [self.zmin]
12261249
warnings.warn("No contour levels were found"
12271250
" within the data range.")
@@ -1246,27 +1269,28 @@ def _process_levels(self):
12461269
# (Colorbar needs this even for line contours.)
12471270
self._levels = list(self.levels)
12481271

1272+
if self.logscale:
1273+
lower, upper = 1e-250, 1e250
1274+
else:
1275+
lower, upper = -1e250, 1e250
1276+
12491277
if self.extend in ('both', 'min'):
1250-
self._levels.insert(0, min(self.levels[0], self.zmin) - 1)
1278+
self._levels.insert(0, lower)
12511279
if self.extend in ('both', 'max'):
1252-
self._levels.append(max(self.levels[-1], self.zmax) + 1)
1280+
self._levels.append(upper)
12531281
self._levels = np.asarray(self._levels)
12541282

12551283
if not self.filled:
12561284
self.layers = self.levels
12571285
return
12581286

1259-
# layer values are mid-way between levels
1260-
self.layers = 0.5 * (self._levels[:-1] + self._levels[1:])
1261-
# ...except that extended layers must be outside the
1262-
# normed range:
1263-
if self.extend in ('both', 'min'):
1264-
if self.logscale:
1265-
self.layers[0] = 1e-150
1266-
else:
1267-
self.layers[0] = -1e150
1268-
if self.extend in ('both', 'max'):
1269-
self.layers[-1] = 1e150
1287+
# Layer values are mid-way between levels in screen space.
1288+
if self.logscale:
1289+
# Avoid overflow by taking sqrt before multiplying.
1290+
self.layers = (np.sqrt(self._levels[:-1])
1291+
* np.sqrt(self._levels[1:]))
1292+
else:
1293+
self.layers = 0.5 * (self._levels[:-1] + self._levels[1:])
12701294

12711295
def _process_colors(self):
12721296
"""
Binary file not shown.
Loading

0 commit comments

Comments
 (0)
0