Skip to content

FIX: colorbar with boundary norm, proportional, extend #20987

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
Sep 5, 2021
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
5 changes: 3 additions & 2 deletions lib/matplotlib/colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -1198,8 +1198,9 @@ def _proportional_y(self):
a proportional colorbar, plus extension lengths if required:
"""
if isinstance(self.norm, colors.BoundaryNorm):
y = (self._boundaries - self._boundaries[0])
y = y / (self._boundaries[-1] - self._boundaries[0])
y = (self._boundaries - self._boundaries[self._inside][0])
y = y / (self._boundaries[self._inside][-1] -
self._boundaries[self._inside][0])
# need yscaled the same as the axes scale to get
# the extend lengths.
if self.spacing == 'uniform':
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions lib/matplotlib/tests/test_colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -827,3 +827,30 @@ def test_aspects():
np.testing.assert_almost_equal(
cb[1][0].ax.get_position(original=False).height * 2,
cb[1][2].ax.get_position(original=False).height, decimal=2)


@image_comparison(['proportional_colorbars.png'], remove_text=True,
style='mpl20')
def test_proportional_colorbars():

x = y = np.arange(-3.0, 3.01, 0.025)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
x = y = np.arange(-3.0, 3.01, 0.025)
x = y = np.linspace(-3, 3, 241)

np.arange with non-integer step is generally not recommended

https://numpy.org/doc/stable/reference/generated/numpy.arange.html:

When using a non-integer step, such as 0.1, the results will often not be consistent. It is better to use numpy.linspace for these cases.

While it doesn't matter for the test, let's set a good example.

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 guess I don't understand this advice. Very often one wants to specify dx instead of N. Sure you can compute one from the other, but I don't see why one would be more consistent than the other.

Copy link
Member

@timhoffm timhoffm Sep 4, 2021

Choose a reason for hiding this comment

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

Since you have limited numeric precision, the results are only approximately the same. For arange you often don't get expected "exact" numbers. e.g. the middle value is 0 with linspace but 1.06e-14 with arange. Additionally, the "endpoint not included" feature makes sense for indices, but is a somewhat awkard API for (float)numeric sequences. And in conjunction with the numeric jitter, this can have surprising effects; e.g. https://stackoverflow.com/questions/10011302/python-numpy-arange-unexpected-results.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, that's just floating point arithmetic. I'm not convinced that makes arange bad style or "inconsistent"

Copy link
Member

@timhoffm timhoffm Sep 5, 2021

Choose a reason for hiding this comment

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

Well, "inconsistent" is numpy's description. That may not be the best term.

However, that the endpoint and number of points depend on small numeric effects is highly problematic. Practically, you don't know what you will get if endpoint is start + N times interval.

Anyway, I'm not going to argue more about this within a test.

X, Y = np.meshgrid(x, y)
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
Z = (Z1 - Z2) * 2

levels = [-1.25, -0.5, -0.125, 0.125, 0.5, 1.25]
cmap = mcolors.ListedColormap(
['0.3', '0.5', 'white', 'lightblue', 'steelblue'])
cmap.set_under('darkred')
cmap.set_over('crimson')
norm = mcolors.BoundaryNorm(levels, cmap.N)

extends = ['neither', 'both']
spacings = ['uniform', 'proportional']
fig, axs = plt.subplots(2, 2)
for i in range(2):
for j in range(2):
CS3 = axs[i, j].contourf(X, Y, Z, levels, cmap=cmap, norm=norm,
extend=extends[i])
fig.colorbar(CS3, spacing=spacings[j], ax=axs[i, j])