Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

MultiNorm class#29876

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
trygvrad wants to merge4 commits intomatplotlib:main
base:main
Choose a base branch
Loading
fromtrygvrad:multivariate-plot-prapare

Conversation

trygvrad
Copy link
Contributor

PR summary

This PR continues the work of#28658 and#28454, aiming toclose#14168. (Feature request: Bivariate colormapping)

This is part one of the former PR,#29221. Please see#29221 for the previous discussion

Featuresincluded in this PR:

  • AMultiNorm class. This is a subclass ofcolors.Normalize and holdsn_variate norms.
  • Testing of theMultiNorm class

Featuresnot included in this PR:

  • changes to colorizer.py needed to expose the MultiNorm class
  • Exposes the functionality provided byMultiNorm together withBivarColormap andMultivarColormap to the plotting functionsaxes.imshow(...),axes.pcolor, and `axes.pcolormesh(...)
  • Testing of the new plotting methods
  • Examples in the docs

Comment on lines 4103 to 4105
in the case where an invalid string is used. This cannot use
`_api.check_getitem()`, because the norm keyword accepts arguments
other than strings.
Copy link
Member

Choose a reason for hiding this comment

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

I'm confused because this function is only called forisinstance(norm, str).

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

So this function exists because the norm keyword acceptsNormalize objects in addition to strings.
This is fundamentally the same error you get if you give an invalid norm to aColorizer object.

In main, the@norm.setter oncolorizer.Colorizer reads:

@norm.setterdefnorm(self,norm):_api.check_isinstance((colors.Normalize,str,None),norm=norm)ifnormisNone:norm=colors.Normalize()elifisinstance(norm,str):try:scale_cls=scale._scale_mapping[norm]exceptKeyError:raiseValueError("Invalid norm str name; the following values are "f"supported:{', '.join(scale._scale_mapping)}"                )fromNonenorm=_auto_norm_from_scale(scale_cls)()    ...

The_get_scale_cls_from_str() exists in this PR because this functionality is now needed by bothcolorizer.Colorizer.norm() andcolors.MultiNorm.
Note this PR does not include changes tocolorizer.Colorizer.norm() so that it makes use of_get_scale_cls_from_str(). These changes follow in the next PR:#29877 .

@trygvradtrygvradforce-pushed themultivariate-plot-prapare branch from55b85e3 tof42d65bCompareApril 17, 2025 15:18
Copy link
Contributor

@anntzeranntzer left a comment

Choose a reason for hiding this comment

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

Just some minor points, plus re-pinging@timhoffm in case he has an opinion re: n_input / input_dims naming?

@trygvrad
Copy link
ContributorAuthor

Thank you for the feedback@anntzer !
Hopefully we can hear if@timhoffm has any thoughts on n_input / input_dims naming within the coming week.

@timhoffm
Copy link
Member

See#29876 (comment)

@trygvrad
Copy link
ContributorAuthor

Thank you@timhoffm
The PR should now be as we agreed (#29876 (comment)) :)

@trygvrad
Copy link
ContributorAuthor

@QuLogic Thank you again and apologies for my tardiness (I was sick)
@timhoffm Do you think you could approve this PR now?

vmin, vmax : float, None, or list of float or None
Limits of the constituent norms.
If a list, each value is assigned to each of the constituent
norms. Single values are repeated to form a list of appropriate size.
Copy link
Member

Choose a reason for hiding this comment

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

Is broadcasting reasonable here? I would assume that most MultiNorms have different scales and thus need per-element entries anyway. It could also be an oversight to pass a single value instead of multiple values.

I'm therefore tempted to not allow scalars here but require exactly n_variables values. A more narrow and explicit interface may be the better start. We can always later expand the API to broadcast scalars if we see that's a typical case and reasonable in terms of usability.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

@timhoffm Perhaps this is also a topic for the weekly meeting :)

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I'm perfectly fine with removing this here, and perhaps that is a good starting point.

My entry into this topic was a use case (dark-field X-ray microscopy, DFXRM) where we typically wantvmax0 = vmax1 = -vmin0 =-vmin1, i.e. equal normalizations, and centered on zero, and given that entry point it felt natural to me to include broadcasting.

@trygvradtrygvradforce-pushed themultivariate-plot-prapare branch from6b86d63 to32247f5CompareJune 4, 2025 21:01
@trygvrad
Copy link
ContributorAuthor

This is on hold until we sort out#30149 (Norm Protocol)
see#29876 (comment)

@trygvrad
Copy link
ContributorAuthor

I have rebased this PR and updated the MultiNorm to inherit from the Norm ABC now that#30178 has been merged :)

@trygvradtrygvradforce-pushed themultivariate-plot-prapare branch 2 times, most recently from47b5116 to63feb6bCompareJune 29, 2025 14:09
assertnorm.vmax[0]==2
assertnorm.vmax[1]==2

# test call with clip
Copy link
Member

Choose a reason for hiding this comment

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

We need more extensive testing for call with multiple values.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Agreed, we need some more tests.
Should we make them here, or test via the top level plotting functions once they are implemented?

Copy link
Member

Choose a reason for hiding this comment

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

I would do the tests here because you can directly test the numeric result of the norm function. With plotting functions, you'll get colors as the result, which are much harder to test / have less expressiveness.

Here you can do a lot of cases as one-liners.

@trygvrad
Copy link
ContributorAuthor

@timhoffm Thank you for taking the time to give detailed comments!

@trygvrad
Copy link
ContributorAuthor

trygvrad commentedJul 3, 2025
edited
Loading

@timhoffm You made a comment here#29876 (comment):

Then (if we still want to expose MultiNorm), let's just call this n_variables and keep it private.

The variable has since changed name ton_components, but I feel like we haven't really explored the merits of keeping it private/public.

The current status is the public property:

@propertydefn_components(self):"""Number of norms held by this `MultiNorm`."""returnlen(self._norms)

I think we actually have 3 options:

  1. The current status: A public property.
  2. Removen_components completely, and replace it withlen(self._norms) wherever it occurs. (This would require a type-check for MultiNorm beforelen(self._norms) is accessed, if the object could also be aNormalize).
  3. Maken_components private.

This impacts how we communicate with the user via docstrings, i.e. inMultiNorm.__call__()
image

Here we could have:

  1. - If tuple, must be of length n_components
  2. - If tuple, must be of length equal to the number of norms held by this MultiNorm

i.e. we can choose to maken_components part of the vocabulary we use, or we can choose not to. My hunch is that this is a useful term to have for both internal discussions, and external communications.
It is also useful to keep in mind thatMultiNorm should mirror the situation with colormaps, where currentlyColormap,BivarColormap andMultivarColormap hasn_components with value 1, 2, andlen(self), respectively.

Once the top level plotting functions are made, there will be a check if the data pipeline is consistent, i.e.
number of fields in the data == cmap.n_components == norm.n_components
If we believe that there are users (i.e. developers of packages that rely on matplotlib) that want to use this functionality in some interesting way, I think it would be useful for them to have access ton_components

On the other hand, while I am quite certain that mulitvariate plotting functionality will be used by a number of users, it is unclear to me if any/how many will build advanced functionality that requires access ton_components which in reality is most important for error handling etc, and the typical user does not write custom error messages.


@timhoffm Let me know if I should removen_components or make it private. Also if I should do the same for the colormap classes.

@github-actionsgithub-actionsbot added the Documentation: APIfiles in lib/ and doc/api labelJul 3, 2025
@trygvradtrygvradforce-pushed themultivariate-plot-prapare branch 2 times, most recently fromab61439 to910b343CompareJuly 3, 2025 12:54
@timhoffm
Copy link
Member

I think making it public is the right way to go. While it slightly complicates the mental model for all the current 1d norms, it is by design that it’s now only the special case and hence itis slightly more complicated.

trygvrad reacted with thumbs up emoji

This commit merges a number of commits now contained inhttps://github.com/trygvrad/matplotlib/tree/multivariate-plot-prapare-backup , keeping only the MultiNorm class
@trygvradtrygvradforce-pushed themultivariate-plot-prapare branch from910b343 todbeca30CompareJuly 10, 2025 19:18
"""
Parameters
----------
norms : list of (str, `Normalize` or None)
Copy link
Member

@timhoffmtimhoffmJul 10, 2025
edited
Loading

Choose a reason for hiding this comment

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

why do we acceptNone here? I don't see an advantage of[None, None] over["linear", "linear"]. Quite the opposite, the first one is less readable.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe in case folks want to update the list later?

Copy link
Member

@timhoffmtimhoffmJul 11, 2025
edited
Loading

Choose a reason for hiding this comment

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

That doesn't make sense to me. (1)None is resolved to Normalize immediately. (2) Not sure why one wanted to lated update but that's possible either way.

I rather suspect that it's because it along the lines of: In 1D some other places - e.g.ScalarMappable.set_norm - accept None. But likely the othe places only accept it intentionally or unintentionally because norm=None is somewhere a default kwarg and that is passed through.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I rather suspect that it's because it along the lines of: In 1D some other places - e.g. ScalarMappable.set_norm - accept None. But likely the othe places only accept it intentionally or unintentionally because norm=None is somewhere a default kwarg and that is passed through.

Exactly this.
My thinking was that a common use case is not to supply a norm:

ax.imshow((A,B),cmap='BiOrangeBlue')

In this case thenorm keyword (default:None) is singular, but to be compatible with the data/cmap it needs to have a length of 2, so in this case the norm keyword is repeated and becomes[None, None], and this then gets passed to theMultiNorm constructor.

In any case, it is better if, as you say we only accept strings.
I will make the change later.


see colorizer.py →_ensure_norm(norm, n_variates=n_variates)here for the proposed implementation. (This can also be simplified if we no longer acceptNone)

Copy link
Member

Choose a reason for hiding this comment

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

That doesn't make sense to me. (1) None is resolved to Normalize immediately. (2) Not sure why one wanted to lated update but that's possible either way.

What happens with['linear', None]?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

It would be parsed to['linear', 'linear'], but will now (with the update) produce an error.
If we find we need this functionality, we can always bring it back in a later PR.

Copy link
Member

@story645story645Jul 16, 2025
edited
Loading

Choose a reason for hiding this comment

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

So how do you denote['linear', mpl.NoNorm()]? is that['linear', 'none']?

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
@trygvradtrygvradforce-pushed themultivariate-plot-prapare branch from23777b3 to3a4f6b9CompareJuly 12, 2025 09:52
@trygvrad
Copy link
ContributorAuthor

Thank you@timhoffm , the changes should now be in :)

Data to normalize, as tuple, scalar array or structured array.
- If tuple, must be of length `n_components`
- If scalar array, the first axis must be of length `n_components`
Copy link
Member

Choose a reason for hiding this comment

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

Did we have a discussion on the axis order? I would have expected it the other way round. Note that we represent RGB arrays as (N, 3), where in is the color count and 3 are the r, g, b values. The call here should be comparable, i.e. the components are the second dimension.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

@anntzer had the same question above (#29876 (comment)) I will repeat my answer (#29876 (comment)) here:


We had a brief discussion on this here#14168 (comment), but we followed that up on a weekly meeting 14 September 2023https://hackmd.io/@matplotlib/Skaz1lrh6#Mutlivariate-colormapping .

The argument is not formulated well in the log, but basically(V, N, M) because there areV cmaps and norms, and in this way the input for the data mirrors that of the vmin, vmax and norm keywords, i.e.:ax.imshow((data0, data1), vmin=(vmin0, vmin1), cmap='BiOrangeBlue') or

ax.imshow((preassure,temperature),vmin=(vmin_preassure,vmin_temperature),cmap='BiOrangeBlue')

Quite often the data is qualitatively different, like the example at the bottom of the page here:https://trygvrad.github.io/mpl_docs/Multivariate%20colormaps.html whereGDP_per_capita andAnnual_growth are plotted together. I find it is not very intuitive to wrap these in a single array.

(also: this allows the different variates to have different data types)


While I see the argument that this has similarities to RGB images, I believe it is also qualitatively quite different, because this works through the norm→colormap pipeline while RGB images bypasses that machinery bydesign.

The way I see it is that the three values of a pixel in an RGB image (in the typical case, i.e. a drawing/diagram or picture taken with a camera) work together to encode one piece of information (color), while if you plotGDP_per_capita andAnnual_growth together using separate norms and a 2D colormap, you have two pieces of information at each point, even though those two pieces are represented by a single color via a colormap.

Copy link
Member

@timhoffmtimhoffmJul 13, 2025
edited
Loading

Choose a reason for hiding this comment

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

Thanks for repeating the answer. The topic is so large that it’s difficult to keep an overview of the state of the discussion.

Your arguments are valid, but only cover the case of list/tuple of 1d array-like, and there I agree. However, I argue that 2d arrays are a separate topic and should be handled differently, because there datasets is typically structured in columns. See e.g. theheights parameter inhttps://matplotlib.org/devdocs/api/_as_gen/matplotlib.axes.Axes.grouped_bar.html#matplotlib.axes.Axes.grouped_bar

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Aha!

Just to make sure, you are arguing that a tuple/list of 2D arrays should be handled differently than a 3D array?
I have no problems with this, if you think it is OK from a syntax point of view :)

In practice this would change only

"""            - If scalar array, the first axis must be of length `n_components`"""

to:

"""            - If scalar array, the last axis must be of length `n_components`"""

And in the implementation, this would add anelse: toMultiNorm._iterable_components_in_data():

@staticmethoddef_iterable_components_in_data(data,n_components):"""        Provides an iterable over the components contained in the data.        An input array with `n_components` fields is returned as a list of length n        referencing slices of the original array.        Parameters        ----------        data : np.ndarray, tuple or list            The input data, as tuple, scalar array or structured array.            - If tuple, must be of length `n_components`            - If scalar array, the last axis must be of length `n_components`            - If structured array, must have `n_components` fields.        Returns        -------        tuple of np.ndarray        """ifisinstance(data,np.ndarray):ifdata.dtype.fieldsisnotNone:data=tuple(data[descriptor[0]]fordescriptorindata.dtype.descr)else:data=tuple(data[...,i]foriinrange(data.shape[-1]))iflen(data)!=n_components:raiseValueError("The input to this `MultiNorm` must be of shape "f"({n_components}, ...), or be structured array or scalar "f"with{n_components} fields.")returndata

There will also be some implications when we check consistency between the data, colormap and norm in colorizer, but that is a problem for a later PR.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, this is my idea.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

RGB images are a recurring topic whenever this is discussed, and I find the comparison to RGB images is useful only in the abstract, seeing how:

  1. Matplotlib can already show RGB images
  2. Plotting RGB images with the colormaps discussed here is not very useful
  3. I see limited use for MultiNorm in an image processing pipeline for RGB images

Similarly, I don't see how this is of particular use for tabulated data.

  1. The tools for visualizing tabulated are well developed, with additional parameters such as marker/linesize, symbol/dashed line and scalar color. Typical use cases therefore do not need multidimensional colormaps.
  2. It is much more difficult to use multidimensional colormaps in a scatter plot on a white (or black) background, since changing the luminosity also changes the visibility of the markers. This makes multidimensional colormaps difficult to utilize for scatter/line plots.

When I say that this feature is not particularly useful for scatter/line plots, that isbecause I have actually tried.
You can go tohttps://colorstamps.readthedocs.io/en/latest/lineplots.html and see the following lineplot:
Untitled

This plot would have much greater clarity if it was shown instead as.

  • Single line with scalar color + error bars in each group (𝜔)
  • Different markers or dash style for each group

If the data was shown in one of these ways, it would also be accessible for those that are colorblind, which the above plotis not.


I believe this feature is most useful when not looking at a single image, but multiple images.
For example comparing two images taken at different times:
A two pictures showing the same scene, shown using the 'BiRedBlue' colormap, highlighting the differences. Cars can be seen to have changed position, and the effect of the wind is visible on the bushes.
A two pictures showing the same scene, shown using the 'BiOrangeBlue' colormap, highlighting the differences. Cars can be seen to have changed position, and the effect of the wind is visible on the bushes.

A sequence of images is typically stored as a data-cube [i, n, m], and storing it [n, m, i] would be quite odd. This is true for regular camera images, as above, but also for X-ray images and X-ray diffraction [my domain of expertise]. It is also how I have seen Z-stacks or T-series in an optical microscopy stored, and I imagine an infrared camera, a telescope, an electron microscope, and a satellite would store data in the same way.

This feature is also very useful when comparing before/after a processing [or filtering] step as part of an image processing pipeline. Again, it would be odd to store the before and after a processing step as thelast index of an array.

If you wish to argue that we shouldonly allow the channels to be encoded at the last index, I would really appreciate it if that was backed up with some use-cases where that would be the intuitive use caseand where this feature is needed. Because I do not see how:

complex=np.stack([a,phi],axis=-1)ax.imshow(complex,vmin=(0,0),vmax=(1,360),cmap='BiOrangeBlue')

improves the user experience as compared to:

ax.imshow((a,phi),vmin=(0,0),vmax=(1,360),cmap='BiOrangeBlue')

That said, it will not kill me to writecomplex = np.stack([a, phi], axis=-1).

However, how would you handle the case where the two channels have different data type?
Consider the feature where you can useNoNorm to address the colors in a (resampled) colormap directly. I.e.:

a=np.random.randint(0,5, (5,10))cmap=mpl.colormaps['RdBu']cmap=cmap.resampled(5)norm=mpl.colors.NoNorm()plt.imshow(a,norm=norm,cmap=cmap)plt.colorbar()
Untitled

which is triggered when the data sent to the colormap is of dtype int.

I would like be able to support this functionality also with multiple channels. Thiscould be achieved with the syntax:

ax.imshow((a,phi),norm=(mpl.color.NoNorm(),'linear'), ...)

But if this syntax is not allowed, I struggle to see how this functionality can be achieved in an intuitive and pedagogical way, given that most users are not familiar with structured numpy arrays.

jklymak and story645 reacted with thumbs up emoji
Copy link
Member

Choose a reason for hiding this comment

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

I think we can agree on the sequence-of-array case: The sequence goes over the components. This would be compatible with your image example if you have two images as separate arrays:imshow([image_before, image_after], ...).

The array-only representation is difficult as we run into the standard first-vs-last dimension encoding discussion. I think there are arguments for both. My other question would be: How large cann_components reasonably be? I think the most common case is 2, maybe sometimes 3. One solution could be to circumvent the discussion by only supportingsequence-of-array (and optionally stuctured arrays).

If you actually have an image cube [i, n, m], you'd need to explcitily select the elements e.g.imshow([cube[0], cube[1]]).

That would put the responsibility to interpret the data clearly to the user, and be in strict analogy to the sequence inputs to all other parameters (norm,vmin,vmax). Sincen_components is low that may be bearable in terms of typing effort and performance.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

How large can n_components reasonably be? I think the most common case is 2, maybe sometimes 3.

This is an example, from a quick image search for 'edx element map' [Yang, T., et al. "Nanoparticles-strengthened high-entropy alloys for cryogenic applications showing an exceptional strength-ductility synergy." Scripta Materialia 164 (2019): 30-35.] shows 6 channels.
image

However, I do not think it is commonplace even with EDX/EDS to plot so many channels in one image, as the visual clarity is greater with fewer channels, as below [O’Boyle, Sarah K., et al. "Expanded Tunability of Intraparticle Frameworks in Spherical Heterostructured Nanoparticles through Substoichiometric Partial Cation Exchange." ACS Materials Au 2.6 (2022): 690-698.]:

a-HAADF-STEM-image-and-b-corresponding-EDS-element-map-where-red-is-Cu-Ka-and-green

When I designed colormaps for possible future use in matplotlib, (link) I designed colormaps for up to 8 variables, with use cases like EDX/EDS in mind.

In other words: I think there will be users who go up to around half-a-dozen channels, and we should support that use, but I don't think it will be a typical use case.

Also: in this use case it is quite typical that you want individual colormaps for a number of elements, but never plot them all together in one plot, as in this example [Cook, Nigel J., et al. "Micron-to atomic-scale investigation of rare earth elements in iron oxides." Frontiers in Earth Science 10 (2022): 967189.]:
image

We will need some example demonstrating how to do this, but the MultivarColormap class is designed so that you can create subsets:

cmap=mpl.colormaps['6VarAddA']# a MultivarColormap with 6 componentscmap_35=mpl.colors.MultivarColormap([cmap[3],cmap[5]],combination_mode=cmap.combination_mode)ax.imshow([data[3],data[5]],cmap=cmap_35)

(if needed we can add a simplified syntax for this once everything is in)

And this is perhaps the main reason why I want to include MultivarColormaps that go as high as 8, but that is a topic for later :)


One solution could be to circumvent the discussion by only supporting sequence-of-array (and optionally stuctured arrays).

I think this could be a reasonable approach.
As you say, some users may find the need to do a conversion of the kind

data_as_list= [data[0],data[1]]

or

data_as_list= [data[i]foriinrange(data.shape[0])]

or, if the axis is the last in the data:

data_as_list= [data[...,i]foriinrange(data.shape[-1])]

or even

dtype=np.dtype([(f'{i}',data.dtype)foriinrange(data.shape[-1])])as_structured_array=data.view(dtype=dtype)[...,0]

For conversion to list or structured array, respectively.

I'll have a think about what the error message should be if the user gives a not-structured array and only a list/tuple or structured array is allowed. We can perhaps be quite detailed here? with bespoke error messages depending on if the input is an array and if so, what size it has.

"""ValueError: Invalid input to MultiNorm with 3 components, the input array of shape (3, 7 25) must be converted to a list or tuple of length 3, i.e.: `data_as_list = [data[i] for i in range(data.shape[0])]`."""

Copy link
Member

Choose a reason for hiding this comment

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

Conversion is even simpler than that 😄

data_as_list= [data[i]foriinrange(data.shape[0])]

is just

list(data)
dtype=np.dtype([(f'{i}',data.dtype)foriinrange(data.shape[-1])])as_structured_array=data.view(dtype=dtype)[...,0]

is just

numpy.lib.recfunctions.unstructured_to_structured(data)

It's possibly good to add this to the documentation.

trygvrad reacted with thumbs up emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I updated the MultiNorm so that only tuples, lists, or structured arrays are acceptable as input to call, inverse, etc.

The details are inMultiNorm._iterable_components_in_data(data, n_components)

I made an attempt to have error messages that steer the user in the right direction.

multi_norm=mpl.colors.MultiNorm(['linear','linear'])multi_norm(np.zeros((3,2)))

gives the error:

ValueError: The input to thisMultiNorm must be a list or tuple of length 2, or be structured array with 2 fields. You can userfn.unstructured_to_structured(data) available withfrom numpy.lib import recfunctions as rfn to convert the input array of shape (3, 2) to a structured format

while

multi_norm(np.zeros((3,2)))

gives:

The input to thisMultiNorm must be a list or tuple of length 2, or be structured array with 2 fields. You can uselist(data) to convert the input data of shape (2, 3) to a compatible list

Does this make sense to you@timhoffm?
I would really like to point users in the right direction, but I have limited experience writing errors of this kind :)

(PS: we may want to revisit the wording here in a later PR, because I suspect this error message will come up most commonly form the top level plotting functions, and in that case I would prefer a error message of the kind "Usingaxes.imshow(...) with a bivariate or multivariate colormap requires the input data..." rather than mentioningMultiNorm by name, but time will tell how easily that can be achieved, and if it is worth it.)

@trygvradtrygvradforce-pushed themultivariate-plot-prapare branch 2 times, most recently froma19c5ad todd8f0c6CompareJuly 13, 2025 11:04
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@QuLogicQuLogicQuLogic left review comments

@story645story645story645 left review comments

@anntzeranntzeranntzer left review comments

@jklymakjklymakjklymak left review comments

@timhoffmtimhoffmtimhoffm left review comments

@oscargusoscargusoscargus left review comments

At least 1 approving review is required to merge this pull request.

Assignees
No one assigned
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

Feature request: Bivariate colormapping
7 participants
@trygvrad@timhoffm@QuLogic@story645@anntzer@jklymak@oscargus

[8]ページ先頭

©2009-2025 Movatter.jp