GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Multivar imshow by trygvrad · Pull Request #30597 · matplotlib/matplotlib

Conversation

@trygvrad

@trygvrad trygvrad commented Sep 24, 2025 •
edited by timhoffm
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Exposes the functionality of MultiNorm, BivarColormap and MultivarColormap to the top level plotting functions ax.imshow(), ax.pcolor() and ax.pcolormesh(). This closes #30526, see Bivariate and Multivariate Colormapping
As a side-effect of the pcolor/pcolormesh implementation, Collection also gets the new functionality.

In short, this PR allows you to plot multivariate data more easily, but it does not:

  • Create equivalents to fig.colorbar() for BivarColormap and MultivarColormap to work with ColorizingArtist
  • Select bivariate and multivariate colormaps to include in matplotlib
  • Examples demonstrating the new functionality

These will come in later PRs. See Bivariate and Multivariate Colormapping

Examples demonstrating new functionality:

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
cmap = mpl.bivar_colormaps['BiPeak']
x_0 = np.arange(25, dtype='float32').reshape(5, 5) % 5
x_1 = np.arange(25, dtype='float32').reshape(5, 5).T % 5
x_0, x_1 = x_0 + 0.3*x_1, x_0*-0.3 + x_1, 

fig, axes = plt.subplots(1, 3, figsize=(6, 2))
axes[0].imshow(x_0, cmap=cmap[0])
axes[1].imshow(x_1, cmap=cmap[1])
axes[2].imshow((x_0, x_1), cmap=cmap)
axes[0].set_title('data 0')
axes[1].set_title('data 1')
axes[2].set_title('data 0 and 1')
image
fig, axes = plt.subplots(1, 6, figsize=(10, 2.3))
axes[0].imshow((x_0, x_1), cmap='BiPeak', interpolation='nearest')
axes[1].matshow((x_0, x_1), cmap='BiPeak')
axes[2].pcolor((x_0, x_1), cmap='BiPeak')
axes[3].pcolormesh((x_0, x_1), cmap='BiPeak')

x = np.arange(5)
y = np.arange(5)
X, Y = np.meshgrid(x, y)
axes[4].pcolormesh(X, Y, (x_0, x_1), cmap='BiPeak')

patches = [
    mpl.patches.Wedge((.3, .7), .1, 0, 360),             # Full circle
    mpl.patches.Wedge((.7, .8), .2, 0, 360, width=0.05),  # Full ring
    mpl.patches.Wedge((.8, .3), .2, 0, 45),              # Full sector
    mpl.patches.Wedge((.8, .3), .2, 22.5, 90, width=0.10),  # Ring sector
]
colors_0 = np.arange(len(patches)) // 2
colors_1 = np.arange(len(patches)) % 2
p = mpl.collections.PatchCollection(patches, cmap='BiPeak', alpha=0.5)
p.set_array((colors_0, colors_1))
axes[5].add_collection(p)
axes[0].set_title('imshow')
axes[1].set_title('matshow')
axes[2].set_title('pcolor')
axes[3].set_title('pcolormesh (C)')
axes[4].set_title('pcolormesh (X, Y, C)')
axes[5].set_title('PatchCollection')
fig.tight_layout()
image
  • "closes #30526" is in the body of the PR description to link the related issue
  • new and changed code is tested
  • [N/A] Plotting related features are demonstrated in an example
  • [N/A] New Features and API Changes are noted with a directive and release note
  • [N/A] Documentation complies with general and docstring guidelines

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@github-actions github-actions Bot added topic: color/color & colormaps topic: images topic: collections and mappables labels Sep 24, 2025
@trygvrad
trygvrad force-pushed the multivar_imshow branch from be62c09 to 0f557d5 Compare September 24, 2025 21:00
@github-actions github-actions Bot added the topic: pyplot API label Sep 24, 2025
@trygvrad
trygvrad force-pushed the multivar_imshow branch from 0f557d5 to e2aab93 Compare September 24, 2025 21:10
@QuLogic QuLogic added this to Bivariate and Multivariate Colormapping Sep 25, 2025
@QuLogic QuLogic added this to the v3.12.0 milestone Sep 25, 2025
@QuLogic QuLogic added the status: waiting for other PR label Oct 30, 2025
@github-actions github-actions Bot added the status: needs rebase label Oct 30, 2025
@trygvrad trygvrad moved this to In Progress in Bivariate and Multivariate Colormapping Oct 30, 2025
@trygvrad
trygvrad force-pushed the multivar_imshow branch from e2aab93 to 23e6caf Compare October 30, 2025 20:29
@github-actions github-actions Bot removed topic: color/color & colormaps status: needs rebase labels Oct 30, 2025
@trygvrad trygvrad removed the status: waiting for other PR label Nov 2, 2025
@trygvrad
trygvrad force-pushed the multivar_imshow branch 2 times, most recently from 1efde4a to 35746f5 Compare November 2, 2025 22:12
@github-actions github-actions Bot added the Documentation: API files in lib/ and doc/api label Nov 2, 2025
@trygvrad
trygvrad force-pushed the multivar_imshow branch from 35746f5 to c334208 Compare November 2, 2025 22:30
@github-actions github-actions Bot added the topic: color/color & colormaps label Nov 2, 2025
@trygvrad

trygvrad commented Nov 2, 2025

Copy link
Copy Markdown
Contributor Author

I fixed the circleci doc error for this.
It would be great if someone could take a look :)
@QuLogic @story645 @ksunden @timhoffm

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

story645
story645 reviewed Nov 20, 2025
View reviewed changes
Comment thread lib/matplotlib/cbook.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/axes/_axes.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/axes/_axes.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/axes/_axes.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/axes/_axes.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/tests/test_axes.py
fig, axes = plt.subplots(2, 3)

# interpolation='nearest' to reduce size of baseline image
axes[0, 0].imshow(x_1, interpolation='nearest', alpha=0.5)

@story645 story645 Nov 20, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

are the other interpolations tested?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@trygvrad trygvrad Nov 30, 2025

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Nope!,
I'm changing one of tests so that it is :)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@story645 story645 Jan 6, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

feeling silly but can't find the test with this change

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@trygvrad trygvrad Jan 7, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

It's in the following test test_multivariate_visualizations()

line 10101 does imshow without specifying interpolation: axes[0].imshow((x_0, x_1, x_2), cmap='3VarAddA')
multivariate_visualizations

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@story645 story645 Feb 23, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

doesn't imshow usually default to nearest though? https://matplotlib.org/devdocs/api/_as_gen/matplotlib.axes.Axes.imshow.html#matplotlib-axes-axes-imshow

Like what happens if interpolation is set to none?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread lib/matplotlib/image.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/image.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/image.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/image.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

ksunden
ksunden previously requested changes Nov 20, 2025
View reviewed changes

@ksunden ksunden left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

General thoughts on return types:

Doing things like float | tuple[float, ...] as is done for several things here (vmin/vmax, clip, etc) is potentially problematic.

Humans may easily work with that, but type checkers will likely yell that they didn't check for all possible outcomes

None is a bit of a special case in being more acceptable (easier to check, etc)

Consider moving these in new code to always return a tuple (even if single element) This keeps the branching needed to a minimum and is not too cumbersome to work for in the single variable case.

Obviously, existing APIs need to maintain back-compat, so this is limited to new code.

Consider whether conceptually an empty tuple is what is truly meant by the None case, but if it is not, retain None

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread lib/matplotlib/axes/_axes.pyi Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/axes/_axes.pyi Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/axes/_axes.pyi Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/colorizer.pyi Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/pyplot.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/pyplot.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/pyplot.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@github-actions github-actions Bot added the status: needs rebase label Nov 24, 2025
@trygvrad

trygvrad commented Dec 1, 2025

Copy link
Copy Markdown
Contributor Author

General thoughts on return types:
Doing things like float | tuple[float, ...] as is done for several things here (vmin/vmax, clip, etc) is potentially problematic.
Humans may easily work with that, but type checkers will likely yell that they didn't check for all possible outcomes

We discussed change the behaviour of colorizer to always return tuples on the call last week.

The relevant moving parts here are:

  1. Norm (Normalize, MultiNorm): members: vmin, vmax, clip
  2. Colorizer: members: get/set_clim, get/set_clip, vmin, vmax, clip
  3. _ColorizingInterface: members: get/set_clim, get/set_clip

The Norm ABC must be typed as follows for backwards compatibility:
def vmin(self) -> float | tuple[float | None, ...] | None: ...


For the Colorizer, I think it makes sense to force tuples on the getter, but allow both on the setter:

    def get_clim(self) -> tuple[tuple[float | None, ...], tuple[float | None, ...]]: ...
    def set_clim(self, vmin: float | tuple[float, ...] | None = ..., vmax: float | tuple[float, ...] | None = ...) -> None: ...

For the _ColorizingInterface we have two options.
A: allow both
def set_clim(self, vmin: float | tuple[float, float] | tuple[float | None, ...] | None, vmax: float | tuple[float | None, ...] | None = ...) -> None: ...
B: Allow get/set_clim only when using scalar data, and otherwise encourage the user to use the colorizer interface:
def set_clim(self, vmin: float | tuple[float, float] | None = ..., vmax: float | None = ...) -> None: ...

    def get_clim(self):
        """
        Return the values (min, max) that are mapped to the colormap limits.

        This function is not available for multivariate data.
        """
        if self._colorizer.norm.n_components > 1:
            raise AttributeError("`.get_clim()` is unavailable when using a colormap "
                                 "with multiple components. Use "
                                 "`.colorizer.get_clim()` instead.")
        return self.colorizer.norm.vmin, self.colorizer.norm.vmax

One reason why I favor option B, is that set_clim is already sufficiently complicated, because for scalar data it allows both signatures:
.set_clim(vmin=vmin, vmax=vmax)
.set_clim((vmin, vmax))
and the 2nd option is ambiguous if there are two colors

@ksunden @story645 Could you let me know what you think?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@trygvrad
trygvrad force-pushed the multivar_imshow branch from d890452 to b570ecc Compare December 1, 2025 14:19
@github-actions github-actions Bot removed the status: needs rebase label Dec 1, 2025
@trygvrad
trygvrad force-pushed the multivar_imshow branch from b570ecc to 053e524 Compare December 1, 2025 14:28
@github-actions github-actions Bot added the status: needs rebase label Dec 8, 2025
story645
story645 approved these changes Jan 6, 2026
View reviewed changes

@story645 story645 left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Sorry for the very long delay in reviewing. Minor nits but I think this is fine otherwise.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread lib/matplotlib/axes/_axes.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/axes/_axes.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/tests/test_axes.py
fig, axes = plt.subplots(2, 3)

# interpolation='nearest' to reduce size of baseline image
axes[0, 0].imshow(x_1, interpolation='nearest', alpha=0.5)

@story645 story645 Jan 6, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

feeling silly but can't find the test with this change

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
trygvrad and others added 4 commits June 6, 2026 21:47
@trygvrad
implementing feedback from story645
fce7799
@trygvrad
doc fix for _ColorizerInterface.get_clim()
5393cba
@trygvrad
update based on feedback from @timhoffm
b8caed7
@trygvrad @timhoffm
Apply suggestions from code review
fdb3725
Co-authored-by: Tim Hoffmann <[email protected]>
@trygvrad
trygvrad force-pushed the multivar_imshow branch from 6626f26 to fdb3725 Compare June 6, 2026 21:08
@github-actions github-actions Bot removed the status: needs rebase label Jun 6, 2026
@trygvrad
multivar imshow additional test
d49ef95
@trygvrad
trygvrad force-pushed the multivar_imshow branch from fac9556 to d49ef95 Compare June 6, 2026 23:56
@trygvrad

trygvrad commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

@timhoffm @ksunden
As discussed at the meeting on Thursday, I have now rebased this PR.
Once this is merged, we can more easily review #31214

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

timhoffm
timhoffm reviewed Jun 9, 2026
View reviewed changes

@timhoffm timhoffm left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Mostly small changes that I noticed when re-reading the PR. Let's still get them in. Please add them as an additional commit for easier review. We'll squash-merge in the end.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread lib/matplotlib/axes/_axes.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/axes/_axes.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/axes/_axes.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/axes/_axes.py
Comment on lines +6736 to +6737
C = mcolorizer._ensure_multivariate_data(args[-1],
colorizer.cmap.n_variates)

@timhoffm timhoffm Jun 9, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I feel _ensure_multivariate_data is not a good name because:

  • "ensure" has more connotation of validation, not necessarily conversion
  • this does not necessarily output multivariate data.

Good naming is hard and I propose to do this as a follow-up as this is internal and has been here before the PR.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread lib/matplotlib/colorizer.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/colorizer.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread lib/matplotlib/image.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@trygvrad
updates based on feedback from @timhoffm
b701032
@trygvrad

trygvrad commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

@timhoffm
I am putting my replies here so that I can resolve the comments above without hiding the answers for future reference.

What about structured arrays, are they supported too? Do we possibly need one place to define "multivariate data" that can be referenced?

Yes we support structured data as well. On a related note the internal _ImageBase class requires structured data, and as requested I updated the docstring of this so that it reads:

            - a (M, N) array interpreted as scalar (greyscale) image,
              with one of the dtypes `~numpy.float32`, `~numpy.float64`,
              `~numpy.float128`, `~numpy.uint16` or `~numpy.uint8`.
            - a (M, N) structured array with K fields for multivariate colormapping.
              This must be used with a `.BivarColormap` (K=2) or generally with a
              K-component `.MultivarColormap`.
            - (M, N, 4) RGBA image with a dtype of `~numpy.float32`,
              `~numpy.float64`, `~numpy.float128`, or `~numpy.uint8`.

How should we add the option of structured data to the top level functions?
Should we include the two ways to include multivariate data on one line, i.e. something like this?:

            - a (K, M, N) scalar array or a structured (M, N) array with K fields:
              a K-component M*N mesh for multivariate colormapping. This must be 
              used with a `.BivarColormap` (K=2) or generally with a K-component 
              `.MultivarColormap`.

Also, did we discuss (K, M, N) vs. (M, N, K)? (Sorry in case I bring up topics that we may have discussed before)

Yes, this has been discussed, and it this keeps coming back up. I believe the primary discussions on this was at the weekly meeting around this time. I know I have made multiple posts on this before, but I have a difficult time finding them among all the different PRs.
If we want to discuss this again I suggest we bring it up at a weekly meeting and make sure that this time we write some things in the meeting notes :)

Also, it states "This parameter is ignored if X is RGB(A)."

I'm changing this to Scalar colormaps are ignored if *X* is RGB(A). which better reflects the current behaviour both on main and in this pr.

plt.imshow(np.random.random((6, 7, 3), cmap='not_a_colormap')

will cause an exception on main, while only if the cmap argument is a valid scalar colormap is the paramater ignored, i.e.:

plt.imshow(np.random.random((6, 7, 3), cmap='viridis')

How do we know RGB(A), i.e. shape (M, N, 3) or (M, N, 4) if there is (K, N, M) as multivariate data. Is this now a heuristic that the first or last dimension is low?

The multivariate pipeline is triggered by a valid multivariate colormap, thus we have:

i.e.:

plt.imshow(np.random.random((3, 3, 3))                        →   interpreted as rgb image
plt.imshow(np.random.random((3, 3, 3), cmap='viridis')        →   interpreted as rgb image
plt.imshow(np.random.random((3, 3, 3), cmap='not_a_cmap')     →   raises an error
plt.imshow(np.random.random((3, 3, 3), cmap='3VarAddA')       →   uses a multivariate colormap
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@trygvrad

trygvrad commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

@timhoffm A follow up on this would be much appreciated :)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@trygvrad

trygvrad commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Mostly small changes that I noticed when re-reading the PR. Let's still get them in. Please add them as an additional commit for easier review. We'll squash-merge in the end.

@timhoffm I really need you to finish this review/approve this so that we can get it merged and other people can start reviewing #31214

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@timhoffm

timhoffm commented Jul 24, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Also, did we discuss (K, M, N) vs. (M, N, K)? (Sorry in case I bring up topics that we may have discussed before)

Yes, this has been discussed, and it this keeps coming back up. I believe the primary discussions on this was at the weekly meeting around #28428 (comment) time. I know I have made multiple posts on this before, but I have a difficult time finding them among all the different PRs.
If we want to discuss this again I suggest we bring it up at a weekly meeting and make sure that this time we write some things in the meeting notes :)

Of the top of my head, I see the primary motivation in that (K, M, N) is the array shape of a list of component arrays: [component_1, ..., component_K]. Though that's not too compelling. There is precedence for handling "list of datasets" differently that 2D array of datasets, e.g. in boxplot() or in grouped_bar.

For RGB, the shape would be (M, N, 3), and I'm inclined to draw an analogy from the color channels to components. This would speak for (M, N, K).

But the more important argument would be: What is the typical data structure people already have when they are working with multivariate data? - And I'm completely blank here. Do you have insights?

The multivariate pipeline is triggered by a valid multivariate colormap, thus we have:

i.e.:

plt.imshow(np.random.random((3, 3, 3))                        →   interpreted as rgb image
plt.imshow(np.random.random((3, 3, 3), cmap='viridis')        →   interpreted as rgb image
plt.imshow(np.random.random((3, 3, 3), cmap='not_a_cmap')     →   raises an error
plt.imshow(np.random.random((3, 3, 3), cmap='3VarAddA')       →   uses a multivariate colormap

This also means, if you have multivariate data, you must always specify a colormap. Is this documented explicitly? If not please add it because that's important to know.

It's probably ok to request this as there would need to be different multivar colormaps depending on the data. OTOH it may be nice to be able to drop some multivar data and matplotlib figures out a nice visualization. This could be added in the future; i.e. infer multivar or not primarily from the data structure; in case of ambiguit, e.g. (3, 3, 3), check the colormap, and if none was given, fall back to a scalar interpretation. This would be a straight forward extension of the current logic.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@timhoffm

timhoffm commented Jul 24, 2026

Copy link
Copy Markdown
Member

Should we include the two ways to include multivariate data on one line, i.e. something like this?:

            - a (K, M, N) scalar array or a structured (M, N) array with K fields:
              a K-component M*N mesh for multivariate colormapping. This must be 
              used with a `.BivarColormap` (K=2) or generally with a K-component 
              `.MultivarColormap`.

Yes.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@trygvrad
updated docstring for multivariate imshow/pcolor/pcolormesh
c7988f4
@trygvrad

trygvrad commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

@timhoffm Thank you for coming back to this :)

This also means, if you have multivariate data, you must always specify a colormap. Is this documented explicitly? If not please add it because that's important to know.

This is a good point. I was thinking the docstring could get this across, the I do not think I was sufficiently clear with the language. This should be better:

            - a (K, M, N) scalar array or a structured (M, N) array with K fields:
              a K-component M*N mesh for multivariate colormapping. A valid
              `.BivarColormap` (K=2) or K-component `.MultivarColormap` must be
              specified using the *cmap* keyword argument.

NOTE: I also change the section that describes the array in the docstring of pcolor so that it matches the docstring of pcolormesh. Both support RGB image data, but this was undocumented in the docstring for pcolor.

We have the option to detect when the user might be trying to use this feature in colors._ensure_multivariate_data(), where we could raise an error with a hint, for example if the user attempts to input data of shape (k, n, m) with 2 <= k < 10 and n,m >10 without the required colormap. Currently such data will raise an error, but the error raised by 'imshow' is different from the error raised by 'pcolor', 'pcolormesh'. We don't want to change these errors, as there are other top level functions that can invoke those errors that do not support multivariate color mapping (yet and perhaps never). I think we can get the feature in first, and then see if we want to update the errors later.


But the more important argument would be: What is the typical data structure people already have when they are working with multivariate data? - And I'm completely blank here. Do you have insights?

I can mostly speak from my own experience, and I have had use for this feature in two contexts:

  1. I am plotting results from two separate experiments together – i.e. the data is loaded from two different sources.
  2. I am plotting the results from two separate data processing pipelines acting on the same dataset.

In both cases, I end up with two separate handles, data_A, data_B, and the call signature ax.imshow((data_A, data_B), ...) is the most convenient.

(I started implementing multivariate color mapping in matplotlib because I used to work with dark-field X-ray microscopy which typically produces 4D datasets that reduce to a series of 2D datasets through moment analysis (intensity, center of mass in x[orientation], center of mass in theta [strain], 2nd moments, ...), and we needed 2D colormaps to visualize them. Note that we would often plot things that are qualitatively very different together, such as orientation[mrad] and strain[dimensionless], in order to look for correlations. We would use different computational pipelines to get to the different moments, and represent them with different variable names.)

I have limited experience with fluorescence microscopy, but I think it is worth noting that the typical format OMETIFF stores data as (T, C, Z, Y, X), where T is typically time, and C is typically the color [fluorophore? wavelength?]. Both T, C, and Z follow before Y and X because the microscope always only reads 2D images sequentially, and this becomes the natural way to store the data as it comes out of the instruement.

The only argument I have seen for (N, M, K), is the analogy to RGB images, however to me this is a very weak argument, as the two approaches are trying to achieve very different things. You should not visualize data from an RGB camera using multivariate color mapping, and likewise, you should not map some arbitrary data space to RGB – doing so is likely to cause you to misinterpret your data. With this in mind, have different call signatures for the two approaches is to me a feature to help avoid mistakes.

jklymak reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

story645
story645 reviewed Jul 26, 2026
View reviewed changes
Comment thread lib/matplotlib/axes/_axes.py Outdated
Comment on lines +6241 to +6244
- a (K, M, N) scalar array or a structured (M, N) array with K fields:
a K-component M*N mesh for multivariate colormapping. A valid
`.BivarColormap` (K=2) or K-component `.MultivarColormap` must be
specified using the *cmap* keyword argument.

@story645 story645 Jul 26, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Suggested change
- a (K, M, N) scalar array or a structured (M, N) array with K fields:
a K-component M*N mesh for multivariate colormapping. A valid
`.BivarColormap` (K=2) or K-component `.MultivarColormap` must be
specified using the *cmap* keyword argument.
- (K, M, N) scalar array
- structured (M, N) array with K fields
- K-component M*N mesh
A valid `.BivarColormap` (K=2) or K-component `.MultivarColormap` must be
specified using the *cmap* keyword argument.

Wondering if something like this might be slightly cleaner cause I'm unclear about the various allowed k dimensional inputs.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@trygvrad trygvrad Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I think you are reading the first two lines as three options, rather than two, and if you are reading it this way, it means the current text is not sufficiently clear. Let me see if I can reformulate it in a less confusing way.

@story645 how about this?

            - a (K, M, N) scalar array or a structured (M, N) array with K fields.
              The K channels are mapped to colors using a `.MultiNorm` and a 
              `.BivarColormap` (K=2) or K-component `.MultivarColormap`.
              This input option is only available when a `.BivarColormap` or 
              `.MultivarColormap` is provided to the *cmap* keyword argument.

This formulation follows the same structure as the bullet point above to make it easier to parse:

            - (M, N) or M*N: a mesh with scalar data. The values are mapped to
              colors using normalization and a colormap. See parameters *norm*,
              *cmap*, *vmin*, *vmax*.

I would prefer to keep everything regarding multivariate color mapping behind one bullet, so that readers who do not want this can easily skip the entire bulletpoint.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

story645 reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

@story645 story645 Jul 26, 2026

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Much better, thanks!

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
timhoffm
timhoffm approved these changes Jul 26, 2026
View reviewed changes

@timhoffm timhoffm left a comment •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

The argument for (K, M, N) over (M, N, K) sounds reasonabe.

From my side, this is good to go!

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@story645

story645 commented Jul 26, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

This needs to pass PR cleanliness but otherwise looks good to me too. @trygve are you waiting on input from anybody else?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@trygvrad
another update to docstring for multivariate imshow/pcolor/pcolormesh
fa6c921
@trygvrad

trygvrad commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

This needs to pass PR cleanliness but otherwise looks good to me too. @trygvrad are you waiting on input from anybody else?

I think this can be squash merged now, and that should take care of the cleanliness :)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

timhoffm
timhoffm reviewed Jul 27, 2026
View reviewed changes
Comment thread lib/matplotlib/axes/_axes.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@timhoffm
Apply suggestion from @timhoffm
c62fa5a
@timhoffm
timhoffm merged commit ca156cf into matplotlib:main Jul 27, 2026
26 of 32 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@github-project-automation github-project-automation Bot moved this from Ready to be merged to Done in Bivariate and Multivariate Colormapping Jul 27, 2026
@timhoffm

timhoffm commented Jul 27, 2026

Copy link
Copy Markdown
Member

@trygvrad thanks for the thorough discussions and for going through all the review rounds with us! This has been a lot of effort, but I'm convinced we're on a good path for consistently adding multivariate plotting so that it will be intuitive and easy to use within matplotlib. 🚀

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@trygvrad

trygvrad commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Thank you @timhoffm.
The next step is that I rebase #31214 . I will probably get to it next weekend :)

story645 reacted with laugh emoji story645 reacted with hooray emoji
All reactions
  • 😄 1 reaction
  • 🎉 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

clin1234 pushed a commit to clin1234/matplotlib that referenced this pull request Aug 23, 2026
@trygvrad @ksunden
@timhoffm @clin1234
Multivar imshow (matplotlib#30597)
f46377d
* expose multivariate plotting functionality to top level functions imshow, pcolor, pcolormesh, and Collection

* updates from code review

thank you @story645 @ksunden

* Apply suggestions from code review

Co-authored-by: Kyle Sunden <[email protected]>

* improved consistency of return types for better typing

* implementing feedback from story645

* doc fix for _ColorizerInterface.get_clim()

* update based on feedback from @timhoffm

* Apply suggestions from code review

Co-authored-by: Tim Hoffmann <[email protected]>

* multivar imshow additional test

* updates based on feedback from @timhoffm

* updated docstring for multivariate imshow/pcolor/pcolormesh

* another update to docstring for multivariate imshow/pcolor/pcolormesh

* Apply suggestion from @timhoffm

---------

Co-authored-by: Kyle Sunden <[email protected]>
Co-authored-by: Tim Hoffmann <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@story645 story645 story645 approved these changes
@timhoffm timhoffm timhoffm approved these changes
@ksunden ksunden ksunden left review comments

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

Documentation: API files in lib/ and doc/api topic: collections and mappables topic: color/color & colormaps topic: images topic: pyplot API

Projects

Bivariate and Multivariate Colormapping
Status: Done

Milestone

v3.12.0

Development

Successfully merging this pull request may close these issues.

Imshow, pcolor and pcolormesh with Bivariate and Multivariate colormaps

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@trygvrad @story645 @timhoffm @ksunden @QuLogic
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.