Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
46 changes: 14 additions & 32 deletions galleries/examples/images_contours_and_fields/multi_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
value *x* in the image).

If we want one colorbar to be representative for multiple images, we have
to explicitly ensure consistent data coloring by using the same data
normalization for all the images. We ensure this by explicitly creating a
``norm`` object that we pass to all the image plotting methods.
to explicitly ensure consistent data coloring by using the same
data-to-color pipeline for all the images. We ensure this by explicitly
creating a `matplotlib.colorizer.Colorizer` object that we pass to all
the image plotting methods.
"""

import matplotlib.pyplot as plt
import numpy as np

from matplotlib import colors
import matplotlib as mpl
Comment thread
timhoffm marked this conversation as resolved.
Outdated

np.random.seed(19680801)

Expand All @@ -31,12 +32,13 @@
fig, axs = plt.subplots(2, 2)
fig.suptitle('Multiple images')

# create a single norm to be shared across all images
norm = colors.Normalize(vmin=np.min(datasets), vmax=np.max(datasets))
# create a single norm and colorizer to be shared across all images
Comment thread
timhoffm marked this conversation as resolved.
Outdated
norm = mpl.colors.Normalize(vmin=np.min(datasets), vmax=np.max(datasets))
colorizer = mpl.colorizer.Colorizer(norm=norm)

images = []
for ax, data in zip(axs.flat, datasets):
images.append(ax.imshow(data, norm=norm))
images.append(ax.imshow(data, colorizer=colorizer))

fig.colorbar(images[0], ax=axs, orientation='horizontal', fraction=.1)

Expand All @@ -45,30 +47,10 @@
# %%
# The colors are now kept consistent across all images when changing the
# scaling, e.g. through zooming in the colorbar or via the "edit axis,
# curves and images parameters" GUI of the Qt backend. This is sufficient
# for most practical use cases.
#
# Advanced: Additionally sync the colormap
# ----------------------------------------
#
# Sharing a common norm object guarantees synchronized scaling because scale
# changes modify the norm object in-place and thus propagate to all images
# that use this norm. This approach does not help with synchronizing colormaps
# because changing the colormap of an image (e.g. through the "edit axis,
# curves and images parameters" GUI of the Qt backend) results in the image
# referencing the new colormap object. Thus, the other images are not updated.
#
# To update the other images, sync the
# colormaps using the following code::
#
# def sync_cmaps(changed_image):
# for im in images:
# if changed_image.get_cmap() != im.get_cmap():
# im.set_cmap(changed_image.get_cmap())
#
# for im in images:
# im.callbacks.connect('changed', sync_cmaps)
#
# curves and images parameters" GUI of the Qt backend. Additionally,
# if the colormap of the colorizer is changed, (e.g. through the "edit
# axis, curves and images parameters" GUI of the Qt backend) this change
# propagates to the other plots and the colorbar.
#
# .. admonition:: References
#
Expand All @@ -77,6 +59,6 @@
#
# - `matplotlib.axes.Axes.imshow` / `matplotlib.pyplot.imshow`
# - `matplotlib.figure.Figure.colorbar` / `matplotlib.pyplot.colorbar`
# - `matplotlib.colorizer.Colorizer`
# - `matplotlib.colors.Normalize`
# - `matplotlib.cm.ScalarMappable.set_cmap`
# - `matplotlib.cbook.CallbackRegistry.connect`
Comment thread
timhoffm marked this conversation as resolved.
Outdated
37 changes: 25 additions & 12 deletions galleries/users_explain/colors/colorbar_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
This tutorial shows how to build and customize standalone colorbars, i.e.
without an attached plot.

A `~.Figure.colorbar` needs a "mappable" (`matplotlib.cm.ScalarMappable`)
object (typically, an image) which indicates the colormap and the norm to be
used. In order to create a colorbar without an attached image, one can instead
use a `.ScalarMappable` with no associated data.
A `~.Figure.colorbar` needs a "mappable" (`matplotlib.colorizer.ColorizingArtist`)
object (typically, an image) which contains a colorizer
(`matplotlib.colorizer.Colorizer`) that holds the data-to-color pipeline (norm and
colormap). In order to create a colorbar without an attached image, one can instead
use a `.ColorizingArtist` with no associated data.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think you can be more straightforward. ColorizingArtist is the base class for everything that "can be colormapped". Directly instruct to create the base class and pass it to colorbar. This the detour "typically subclasses with data, like image, but you can create without data" is not needed for the context of creating a standalone colorbar.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the feedback, does the following read more easily:

"""
A `~.Figure.colorbar` requires a `matplotlib.colorizer.ColorizingArtist` which
contains a `matplotlib.colorizer.Colorizer` that holds the data-to-color pipeline
(norm and colormap). To create a colorbar without an attached plot one can 
use a `.ColorizingArtist` with no associated data.
"""

(and should we remove the last sentence?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is okish. Maybe an improvement would be to switch the last sentence for

To create a colorbar without an attached plot one can directly instantiate the
base class .ColorizingArtist, which has no associated data.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done :)

"""

import matplotlib.pyplot as plt
Expand All @@ -23,17 +24,21 @@
# -------------------------
# Here, we create a basic continuous colorbar with ticks and labels.
#
# The arguments to the `~.Figure.colorbar` call are the `.ScalarMappable`
# (constructed using the *norm* and *cmap* arguments), the axes where the
# colorbar should be drawn, and the colorbar's orientation.
# The arguments to the `~.Figure.colorbar` call are a `.ColorizingArtist`,
# the axes where the colorbar should be drawn, and the colorbar's orientation.
# To crate a `.ColorizingArtist` one must first make `.Colorizer` that holds the
# desired *norm* and *cmap*.
#
#
# For more information see the `~matplotlib.colorbar` API.

fig, ax = plt.subplots(figsize=(6, 1), layout='constrained')

norm = mpl.colors.Normalize(vmin=5, vmax=10)

fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap="cool"),
colorizer = mpl.colorizer.Colorizer(norm=norm, cmap="cool")

fig.colorbar(mpl.colorizer.ColorizingArtist(colorizer),
cax=ax, orientation='horizontal', label='Some Units')

# %%
Expand All @@ -47,7 +52,9 @@

fig, ax = plt.subplots(layout='constrained')

fig.colorbar(mpl.cm.ScalarMappable(norm=mpl.colors.Normalize(0, 1), cmap='magma'),
colorizer = mpl.colorizer.Colorizer(norm=mpl.colors.Normalize(0, 1), cmap='magma')

fig.colorbar(mpl.colorizer.ColorizingArtist(colorizer),
ax=ax, orientation='vertical', label='a colorbar label')

# %%
Expand All @@ -65,7 +72,9 @@
bounds = [-1, 2, 5, 7, 12, 15]
norm = mpl.colors.BoundaryNorm(bounds, cmap.N, extend='both')

fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap="viridis"),
colorizer = mpl.colorizer.Colorizer(norm=norm, cmap='viridis')

fig.colorbar(mpl.colorizer.ColorizingArtist(colorizer),
cax=ax, orientation='horizontal',
label="Discrete intervals with extend='both' keyword")

Expand Down Expand Up @@ -94,8 +103,10 @@
bounds = [1, 2, 4, 7, 8]
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)

colorizer = mpl.colorizer.Colorizer(norm=norm, cmap=cmap)

fig.colorbar(
mpl.cm.ScalarMappable(cmap=cmap, norm=norm),
mpl.colorizer.ColorizingArtist(colorizer),
cax=ax, orientation='horizontal',
extend='both',
spacing='proportional',
Expand All @@ -116,8 +127,10 @@
bounds = [-1.0, -0.5, 0.0, 0.5, 1.0]
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)

colorizer = mpl.colorizer.Colorizer(norm=norm, cmap=cmap)

fig.colorbar(
mpl.cm.ScalarMappable(cmap=cmap, norm=norm),
mpl.colorizer.ColorizingArtist(colorizer),
cax=ax, orientation='horizontal',
extend='both', extendfrac='auto',
spacing='uniform',
Expand Down
12 changes: 7 additions & 5 deletions lib/matplotlib/colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
import numpy as np

import matplotlib as mpl
from matplotlib import _api, cbook, collections, cm, colors, contour, ticker
from matplotlib import _api, cbook, collections, colors, contour, ticker
import matplotlib.artist as martist
import matplotlib.colorizer as mcolorizer
import matplotlib.patches as mpatches
import matplotlib.path as mpath
import matplotlib.spines as mspines
Expand Down Expand Up @@ -199,12 +200,12 @@ class Colorbar:
Draw a colorbar in an existing Axes.

Typically, colorbars are created using `.Figure.colorbar` or
`.pyplot.colorbar` and associated with `.ScalarMappable`\s (such as an
`.pyplot.colorbar` and associated with `.ColorizingArtist`\s (such as an
`.AxesImage` generated via `~.axes.Axes.imshow`).

In order to draw a colorbar not associated with other elements in the
figure, e.g. when showing a colormap by itself, one can create an empty
`.ScalarMappable`, or directly pass *cmap* and *norm* instead of *mappable*
`.ColorizingArtist`, or directly pass *cmap* and *norm* instead of *mappable*
to `Colorbar`.

Useful public methods are :meth:`set_label` and :meth:`add_lines`.
Expand Down Expand Up @@ -244,7 +245,7 @@ def __init__(
ax : `~matplotlib.axes.Axes`
The `~.axes.Axes` instance in which the colorbar is drawn.

mappable : `.ScalarMappable`
mappable : `.ColorizingArtist`
The mappable whose colormap and norm will be used.

To show the colors versus index instead of on a 0-1 scale, set the
Expand Down Expand Up @@ -288,7 +289,8 @@ def __init__(
colorbar and at the right for a vertical.
"""
if mappable is None:
mappable = cm.ScalarMappable(norm=norm, cmap=cmap)
colorizer = mcolorizer.Colorizer(norm=norm, cmap=cmap)
mappable = mcolorizer.ColorizingArtist(colorizer)

self.mappable = mappable
cmap = mappable.cmap
Expand Down
9 changes: 5 additions & 4 deletions lib/matplotlib/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1200,17 +1200,18 @@ def colorbar(
Parameters
----------
mappable
The `matplotlib.cm.ScalarMappable` (i.e., `.AxesImage`,
The `matplotlib.colorizer.ColorizingArtist` (i.e., `.AxesImage`,
`.ContourSet`, etc.) described by this colorbar. This argument is
mandatory for the `.Figure.colorbar` method but optional for the
`.pyplot.colorbar` function, which sets the default to the current
image.

Note that one can create a `.ScalarMappable` "on-the-fly" to
generate colorbars not attached to a previously drawn artist, e.g.
Note that one can create a `.colorizer.ColorizingArtist` "on-the-fly"
to generate colorbars not attached to a previously drawn artist, e.g.
::

fig.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax)
cr = colorizer.Colorizer(norm=norm, cmap=cmap)
fig.colorbar(colorizer.ColorizingArtist(cr), ax=ax)

cax : `~matplotlib.axes.Axes`, optional
Axes into which the colorbar will be drawn. If `None`, then a new
Expand Down