8000 Change colorbar for contour to have the proper axes limits... by jklymak · Pull Request #13506 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Change colorbar for contour to have the proper axes limits... #13506

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 7 commits into from
May 19, 2019
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/2019-02-24-JMK.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
`matplotlib.colorbar.Colorbar` uses un-normalized axes for all mappables
------------------------------------------------------------------------

Before 3.0, `matplotlib.colorbar.Colorbar` (`~.Figure.colorbar`) normalized
all axes limits between 0 and 1 and had custom tickers to handle the
labelling of the colorbar ticks. After 3.0, colorbars constructed from
mappables that were *not* contours were constructed with axes that had
limits between ``vmin`` and ``vmax`` of the mappable's norm, and the tickers
were made children of the normal axes tickers.

This version of Matplotlib extends that to mappables made by contours, and
allows the axes to run between the lowest boundary in the contour and the
highest.

Code that worked around the normalization between 0 and 1 will need to be
modified.
49 changes: 29 additions & 20 deletions lib/matplotlib/colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
is a thin wrapper over :meth:`~matplotlib.figure.Figure.colorbar`.

'''

import copy
import logging

import numpy as np
Expand Down Expand Up @@ -489,6 +489,7 @@ def draw_all(self):
# units:
X, Y = self._mesh()
C = self._values[:, np.newaxis]
# decide minor/major axis
self.config_axis()
self._config_axes(X, Y)
if self.filled:
Expand Down Expand Up @@ -565,10 +566,11 @@ def _use_auto_colorbar_locator(self):
Return if we should use an adjustable tick locator or a fixed
one. (check is used twice so factored out here...)
"""
return (self.boundaries is None
and self.values is None
and ((type(self.norm) == colors.Normalize)
or (type(self.norm) == colors.LogNorm)))
contouring = ((self.boundaries is not None) and
(self.spacing == 'uniform'))
return (((type(self.norm) == colors.Normalize)
or (type(self.norm) == colors.LogNorm))
and not contouring)

def _reset_locator_formatter_scale(self):
"""
Expand All @@ -578,13 +580,11 @@ def _reset_locator_formatter_scale(self):
"""
self.locator = None
self.formatter = None
if (isinstance(self.norm, colors.LogNorm)
and self._use_auto_colorbar_locator()):
Copy link
Member

Choose a reason for hiding this comment

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

Why can that be left out?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think we want the axes to be logarithmic in this case, but I agree its open to interpretation:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors

z = np.arange(24).reshape(4, 6) + 1
z = np.power(2, z)
fig, axs = plt.subplots(1, 2, constrained_layout=True)
ax = axs[0]
cs = ax.contourf(z, levels=np.power(10., [1, 2, 6.5, 10] ), norm=mcolors.LogNorm())
fig.colorbar(cs, ax=ax, spacing='proportional')
ax = axs[1]
cs = ax.contourf(z, levels=np.power(10, [1, 2, 6.5, 10]), norm=mcolors.LogNorm())
fig.colorbar(cs, ax=ax, spacing='uniform')

plt.show()

With this change:

Figure_1

Without this change:

Figure_1Wrong

Before this PR:

Figure_1Old

Copy link
Member

Choose a reason for hiding this comment

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

👍 Looks reasonable.

if (isinstance(self.norm, colors.LogNorm)):
# *both* axes are made log so that determining the
# mid point is easier.
self.ax.set_xscale('log')
self.ax.set_yscale('log')

self.minorticks_on()
else:
self.ax.set_xscale('linear')
Expand Down Expand Up @@ -1073,26 +1073,35 @@ def _mesh(self):
These are suitable for a vertical colorbar; swapping and
transposition for a horizontal colorbar are done outside
this function.
'''
# if boundaries and values are None, then we can go ahead and
# scale this up for Auto tick location. Otherwise we
# want to keep normalized between 0 and 1 and use manual tick
# locations.

These are scaled between vmin and vmax
'''
# copy the norm and change the vmin and vmax to the vmin and
# vmax of the colorbar, not the norm. This allows the situation
# where the colormap has a narrower range than the colorbar, to
# accomodate extra contours:
norm = copy.copy(self.norm)
norm.vmin = self.vmin
norm.vmax = self.vmax
x = np.array([0.0, 1.0])
if self.spacing == 'uniform':
y = self._uniform_y(self._central_N())
else:
y = self._proportional_y()
if self._use_auto_colorbar_locator():
y = self.norm.inverse(y)
x = self.norm.inverse(x)
xmid = np.array([0.5])
try:
y = norm.inverse(y)
x = norm.inverse(x)
xmid = norm.inverse(xmid)
except ValueError:
# occurs for norms that don't have an inverse, in
# which case manually scale:
dv = self.vmax - self.vmin
x = x * dv + self.vmin
y = y * dv + self.vmin
xmid = xmid * dv + self.vmin
self._y = y
X, Y = np.meshgrid(x, y)
if self._use_auto_colorbar_locator():
xmid = self.norm.inverse(0.5)
else:
xmid = 0.5
if self._extend_lower() and not self.extendrect:
X[0, :] = xmid
if self._extend_upper() and not self.extendrect:
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -1334,7 +1334,7 @@ def inverse(self, value):
BoundaryNorm is not invertible, so calling this method will always
raise an error
"""
return ValueError("BoundaryNorm is not invertible")
raise ValueError("BoundaryNorm is not invertible")


class NoNorm(Normalize):
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.
24 changes: 21 additions & 3 deletions lib/matplotlib/tests/test_contour.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,9 +398,13 @@ def test_contourf_log_extension():
c3 = ax3.contourf(data, levels=levels,
norm=LogNorm(vmin=levels.min(), vmax=levels.max()),
extend='both')
plt.colorbar(c1, ax=ax1)
plt.colorbar(c2, ax=ax2)
plt.colorbar(c3, ax=ax3)
cb = plt.colorbar(c1, ax=ax1)
assert cb.ax.get_ylim() == (1e-8, 1e10)
cb = plt.colorbar(c2, ax=ax2)
assert cb.ax.get_ylim() == (1e-4, 1e6)
cb = plt.colorbar(c3, ax=ax3)
assert_array_almost_equal(cb.ax.get_ylim(),
[3.162277660168379e-05, 3162277.660168383], 2)


@image_comparison(baseline_images=['contour_addlines'],
Expand All @@ -417,3 +421,17 @@ def test_contour_addlines():
cont = ax.contour(X+1000)
cb = fig.colorbar(pcm)
cb.add_lines(cont)
assert_array_almost_equal(cb.ax.get_ylim(), [114.3091, 9972.30735], 3)


@image_comparison(baseline_images=['contour_uneven'],
extensions=['png'], remove_text=True, style='mpl20')
def test_contour_uneven():
z = np.arange(24).reshape(4, 6)
fig, axs = plt.subplots(1, 2)
ax = axs[0]
cs = ax.contourf(z, levels=[2, 4, 6, 10, 20])
fig.colorbar(cs, ax=ax, spacing='proportional')
ax = axs[1]
cs = ax.contourf(z, levels=[2, 4, 6, 10, 20])
fig.colorbar(cs, ax=ax, spacing='uniform')
0