Skip to content

Implement extend color bar for contourf #8806

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

Closed
wants to merge 4 commits into from
Closed
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
17 changes: 11 additions & 6 deletions lib/matplotlib/contour.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import matplotlib.texmanager as texmanager
import matplotlib.transforms as mtransforms
from matplotlib.cbook import mplDeprecation
from matplotlib.ticker import Locator, LogLocator

# Import needed for adding manual selection capability to clabel
from matplotlib.blocking_input import BlockingContourLabeler
Expand Down Expand Up @@ -831,9 +832,6 @@ def __init__(self, ax, *args, **kwargs):
self.logscale = True
if norm is None:
norm = colors.LogNorm()
if self.extend is not 'neither':
raise ValueError('extend kwarg does not work yet with log '
' scale')
else:
self.logscale = False

Expand Down Expand Up @@ -1213,10 +1211,17 @@ def _process_levels(self):
# (Colorbar needs this even for line contours.)
self._levels = list(self.levels)

tempLocator = Locator()
if self.logscale:
tempLocator = LogLocator()
if self.locator is not None:
tempLocator = self.locator
Copy link
Member

Choose a reason for hiding this comment

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

Instead of using a temporary Locator and then requiring that all Locators have 3 new private methods, I think it would be better to keep everything within the ContourBase class. This will be more readable than using Locator private methods that modify one of their arguments. It will likely require fewer LOC as well.

Copy link
Author

@ghost ghost Jun 28, 2017

Choose a reason for hiding this comment

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

@efiring Thank you for the input. I took sometimes to think it over more carefully, I will fix it your way.

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

if not self.filled:
Expand All @@ -1228,7 +1233,7 @@ def _process_levels(self):
# ...except that extended layers must be outside the
# normed range:
if self.extend in ('both', 'min'):
self.layers[0] = -1e150
self.layers[0] = tempLocator._get_lower_extend_layer_value()
if self.extend in ('both', 'max'):
self.layers[-1] = 1e150

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions lib/matplotlib/tests/test_colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from matplotlib.colors import BoundaryNorm, LogNorm
from matplotlib.cm import get_cmap
from matplotlib.colorbar import ColorbarBase
from matplotlib import ticker, cm
from matplotlib.mlab import bivariate_normal


def _get_cmap_norms():
Expand Down Expand Up @@ -308,3 +310,34 @@ def test_colorbar_lognorm_extension():
cb = ColorbarBase(ax, norm=LogNorm(vmin=0.1, vmax=1000.0),
orientation='vertical', extend='both')
assert cb._values[0] >= 0.0


@image_comparison(baseline_images=['extended_cbar_with_contourf_min',
Copy link
Member

Choose a reason for hiding this comment

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

Please condense the test down to a single image with 3 subplots, saving time and space.

'extended_cbar_with_contourf_max',
'extended_cbar_with_contourf_both'],
extensions=['png'])
def test_extended_colorbar_on_contourf():
np.random.seed(1)
Copy link
Member

Choose a reason for hiding this comment

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

Not used.

N = 100
x = np.linspace(-3.0, 3.0, N)
y = np.linspace(-2.0, 2.0, N)
X, Y = np.meshgrid(x, y)
z = (bivariate_normal(X, Y, 0.1, 0.2, 1.0, 1.0)
Copy link
Member

Choose a reason for hiding this comment

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

For the purpose of a test you can use a simpler pattern with far fewer points. For example, you could use

z = np.logspace(0.1, 10, 24).reshape((4, 6))

and omit the X and Y entirely.

+ 0.1 * bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0))
plt.figure()
plt.subplot(111)
plt.contourf(X, Y, z, cmap=cm.PuBu_r, locator=ticker.LogLocator(),
extend='min')
plt.colorbar()
Copy link
Member

Choose a reason for hiding this comment

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

For condensing the example you can use fig, axs = plt.subplots(ncols=3) and then use Axes and Figure method calls.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks. Will fix this test up.


plt.figure()
plt.subplot(111)
plt.contourf(X, Y, z, cmap=cm.PuBu_r, locator=ticker.LogLocator(),
extend='max')
plt.colorbar()

plt.figure()
plt.subplot(111)
plt.contourf(X, Y, z, cmap=cm.PuBu_r, locator=ticker.LogLocator(),
extend='both')
plt.colorbar()
18 changes: 18 additions & 0 deletions lib/matplotlib/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,15 @@ def zoom(self, direction):
step = 0.1 * interval * direction
self.axis.set_view_interval(vmin + step, vmax - step, ignore=True)

def _decrease_lower_bound(self, levels, lower_bound):
levels.insert(0, lower_bound - 1.0)

def _increase_upper_bound(self, levels, upper_bound):
levels.append(upper_bound + 1.0)

def _get_lower_extend_layer_value(self):
return -1e150

def refresh(self):
"""refresh internal information based on current lim"""
pass
Expand Down Expand Up @@ -2192,6 +2201,15 @@ def nonsingular(self, vmin, vmax):
vmax = decade_up(vmax, self._base)
return vmin, vmax

def _decrease_lower_bound(self, levels, lower_bound):
levels.insert(0, lower_bound / self._base)

def _increase_upper_bound(self, levels, upper_bound):
levels.append(upper_bound * self._base)

def _get_lower_extend_layer_value(self):
return np.nextafter(0, 1)


class SymmetricalLogLocator(Locator):
"""
Expand Down