Skip to content

Make contour and contourf color assignments consistent. #11412

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions doc/api/next_api_changes/2018-06-09-EF-contour_levels.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Contour color autoscaling improvements
--------------------------------------

Selection of contour levels is now the same for contour and
contourf; previously, for contour, levels outside the data range were
deleted. (Exception: if no contour levels are found within the
data range, the `levels` attribute is replaced with a list holding
only the minimum of the data range.)

When contour is called with levels specified as a target number rather
than a list, and the 'extend' kwarg is used, the levels are now chosen
such that some data typically will fall in the extended range.

When contour is called with a `LogNorm` or a `LogLocator`, it will now
select colors using the geometric mean rather than the arithmetic mean
of the contour levels.
58 changes: 41 additions & 17 deletions lib/matplotlib/contour.py
Original file line number Diff line number Diff line change
Expand Up @@ -1174,22 +1174,45 @@ def _autolev(self, N):
"""
Select contour levels to span the data.

The target number of levels, *N*, is used only when the
scale is not log and default locator is used.

We need two more levels for filled contours than for
line contours, because for the latter we need to specify
the lower and upper boundary of each range. For example,
a single contour boundary, say at z = 0, requires only
one contour line, but two filled regions, and therefore
three levels to provide boundaries for both regions.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suspect at least this docstring needs more info....

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what info you are looking for, but I am adding some now. This is a private method, so the docstring requirements are not as stringent as they would be for a public method.

"""
self._auto = True
if self.locator is None:
if self.logscale:
self.locator = ticker.LogLocator()
else:
self.locator = ticker.MaxNLocator(N + 1, min_n_ticks=1)

lev = self.locator.tick_values(self.zmin, self.zmax)
self._auto = True
return lev

try:
if self.locator._symmetric:
return lev
except AttributeError:
pass

# Trim excess levels the locator may have supplied.
under = np.nonzero(lev < self.zmin)[0]
i0 = under[-1] if len(under) else 0
over = np.nonzero(lev > self.zmax)[0]
i1 = over[0] + 1 if len(over) else len(lev)
if self.extend in ('min', 'both'):
i0 += 1
if self.extend in ('max', 'both'):
i1 -= 1

if i1 - i0 < 3:
i0, i1 = 0, len(lev)

return lev[i0:i1]

def _contour_level_args(self, z, args):
"""
Expand Down Expand Up @@ -1220,8 +1243,8 @@ def _contour_level_args(self, z, args):

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

if self.logscale:
lower, upper = 1e-250, 1e250
else:
lower, upper = -1e250, 1e250

if self.extend in ('both', 'min'):
self._levels.insert(0, min(self.levels[0], self.zmin) - 1)
self._levels.insert(0, lower)
if self.extend in ('both', 'max'):
self._levels.append(max(self.levels[-1], self.zmax) + 1)
self._levels.append(upper)
self._levels = np.asarray(self._levels)

if not self.filled:
self.layers = self.levels
return

# layer values are mid-way between levels
self.layers = 0.5 * (self._levels[:-1] + self._levels[1:])
# ...except that extended layers must be outside the
# normed range:
if self.extend in ('both', 'min'):
if self.logscale:
self.layers[0] = 1e-150
else:
self.layers[0] = -1e150
if self.extend in ('both', 'max'):
self.layers[-1] = 1e150
# Layer values are mid-way between levels in screen space.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"... in data space"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it is screen space. That's why the geometric mean is needed for the log scale.

if self.logscale:
# Avoid overflow by taking sqrt before multiplying.
self.layers = (np.sqrt(self._levels[:-1])
* np.sqrt(self._levels[1:]))
else:
self.layers = 0.5 * (self._levels[:-1] + self._levels[1:])

def _process_colors(self):
"""
Expand Down
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading