Skip to content

Draft for multivariate and bivariate colormaps #26996

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

Draft
wants to merge 23 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fe6ace1
initial implementation of VectorMappable, BivarColormap and MultivCol…
trygvrad Sep 19, 2023
859c1b3
multivariate fix for alpha and masked arrays
trygvrad Nov 20, 2023
3de4379
testing multivar vmin, vmax, norm and alpha
trygvrad Nov 21, 2023
94fe497
multivariate colormaps in pcolor
trygvrad Nov 21, 2023
ce09ccb
moved ensure_cmap
trygvrad Nov 21, 2023
e3d6490
multivariate data in PatchCollection
trygvrad Nov 21, 2023
b39b644
test for multivariate pcolormesh specifying C, X, Y
trygvrad Nov 22, 2023
800b7e1
refactor of esnure multivariate
trygvrad Nov 22, 2023
d12391f
fix & test multivariate figimage
trygvrad Nov 22, 2023
465b53a
test missing multivariate colormap imshow
trygvrad Nov 22, 2023
f7bd870
tests for vectormappable
trygvrad Nov 22, 2023
b845cd3
additional tests for multivariate colormaps
trygvrad Nov 22, 2023
8e2d814
tests for _repr_html_() of multivariate colormaps
trygvrad Nov 22, 2023
8481d2e
stubs for multivariate: cm.pyi
trygvrad Nov 26, 2023
171834d
Update colors.py
trygvrad Nov 27, 2023
aadc3a1
__call__ for MultivarColormap + test of same
trygvrad Nov 27, 2023
1e8e93d
bugfix for calling multivariate colormaps with tuple
trygvrad Nov 27, 2023
c4c9761
enforce shape when using cm.BivarColormap.__call__()
trygvrad Nov 27, 2023
c9f1718
stubs for multivariate colorbars
trygvrad Nov 28, 2023
d88d841
additional tests for multivariate colormaps
trygvrad Nov 28, 2023
92599f4
fix for fig.colorbar_2D() and fig.colorbars()
trygvrad May 23, 2024
945dc75
updated multivariate and bivariate colormaps
trygvrad Jun 4, 2024
d6dd9c4
For multivariate colormaps, removed duplicated code
trygvrad Jun 7, 2024
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: 5 additions & 0 deletions doc/api/colors_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ Colormaps
Colormap
LinearSegmentedColormap
ListedColormap
Colormap
MultivarColormap
BivarColormap
SegmentedBivarColormap
BivarColormapFromImage

Other classes
-------------
Expand Down
4 changes: 4 additions & 0 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@
"interactive",
"is_interactive",
"colormaps",
"multivar_colormaps",
"bivar_colormaps",
"color_sequences",
]

Expand Down Expand Up @@ -1513,4 +1515,6 @@ def inner(ax, *args, data=None, **kwargs):
# workaround: we must defer colormaps import to after loading rcParams, because
# colormap creation depends on rcParams
from matplotlib.cm import _colormaps as colormaps # noqa: E402
from matplotlib.cm import _multivar_colormaps as multivar_colormaps # noqa: E402
from matplotlib.cm import _bivar_colormaps as bivar_colormaps # noqa: E402
from matplotlib.colors import _color_sequences as color_sequences # noqa: E402
4 changes: 4 additions & 0 deletions lib/matplotlib/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ __all__ = [
"interactive",
"is_interactive",
"colormaps",
"multivar_colormaps",
"bivar_colormaps",
"color_sequences",
]

Expand Down Expand Up @@ -112,4 +114,6 @@ def _preprocess_data(
) -> Callable: ...

from matplotlib.cm import _colormaps as colormaps
from matplotlib.cm import _multivar_colormaps as multivar_colormaps
from matplotlib.cm import _bivar_colormaps as bivar_colormaps
from matplotlib.colors import _color_sequences as color_sequences
1,312 changes: 1,312 additions & 0 deletions lib/matplotlib/_cm_bivar.py

Large diffs are not rendered by default.

166 changes: 166 additions & 0 deletions lib/matplotlib/_cm_multivar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# auto-genreated by https://github.com/trygvrad/multivariate_colormaps
# date: 2024-05-28

from .colors import LinearSegmentedColormap, MultivarColormap
import matplotlib as mpl
_LUTSIZE = mpl.rcParams['image.lut']

_2VarAddA0_data = [[0.000, 0.000, 0.000],
[0.020, 0.026, 0.031],
[0.049, 0.068, 0.085],
[0.075, 0.107, 0.135],
[0.097, 0.144, 0.183],
[0.116, 0.178, 0.231],
[0.133, 0.212, 0.279],
[0.148, 0.244, 0.326],
[0.161, 0.276, 0.374],
[0.173, 0.308, 0.422],
[0.182, 0.339, 0.471],
[0.190, 0.370, 0.521],
[0.197, 0.400, 0.572],
[0.201, 0.431, 0.623],
[0.204, 0.461, 0.675],
[0.204, 0.491, 0.728],
[0.202, 0.520, 0.783],
[0.197, 0.549, 0.838],
[0.187, 0.577, 0.895]]

_2VarAddA1_data = [[0.000, 0.000, 0.000],
[0.030, 0.023, 0.018],
[0.079, 0.060, 0.043],
[0.125, 0.093, 0.065],
[0.170, 0.123, 0.083],
[0.213, 0.151, 0.098],
[0.255, 0.177, 0.110],
[0.298, 0.202, 0.120],
[0.341, 0.226, 0.128],
[0.384, 0.249, 0.134],
[0.427, 0.271, 0.138],
[0.472, 0.292, 0.141],
[0.517, 0.313, 0.142],
[0.563, 0.333, 0.141],
[0.610, 0.353, 0.139],
[0.658, 0.372, 0.134],
[0.708, 0.390, 0.127],
[0.759, 0.407, 0.118],
[0.813, 0.423, 0.105]]

_2VarSubA0_data = [[1.000, 1.000, 1.000],
[0.959, 0.973, 0.986],
[0.916, 0.948, 0.974],
[0.874, 0.923, 0.965],
[0.832, 0.899, 0.956],
[0.790, 0.875, 0.948],
[0.748, 0.852, 0.940],
[0.707, 0.829, 0.934],
[0.665, 0.806, 0.927],
[0.624, 0.784, 0.921],
[0.583, 0.762, 0.916],
[0.541, 0.740, 0.910],
[0.500, 0.718, 0.905],
[0.457, 0.697, 0.901],
[0.414, 0.675, 0.896],
[0.369, 0.652, 0.892],
[0.320, 0.629, 0.888],
[0.266, 0.604, 0.884],
[0.199, 0.574, 0.881]]

_2VarSubA1_data = [[1.000, 1.000, 1.000],
[0.982, 0.967, 0.955],
[0.966, 0.935, 0.908],
[0.951, 0.902, 0.860],
[0.937, 0.870, 0.813],
[0.923, 0.838, 0.765],
[0.910, 0.807, 0.718],
[0.898, 0.776, 0.671],
[0.886, 0.745, 0.624],
[0.874, 0.714, 0.577],
[0.862, 0.683, 0.530],
[0.851, 0.653, 0.483],
[0.841, 0.622, 0.435],
[0.831, 0.592, 0.388],
[0.822, 0.561, 0.340],
[0.813, 0.530, 0.290],
[0.806, 0.498, 0.239],
[0.802, 0.464, 0.184],
[0.801, 0.426, 0.119]]

_3VarAddA0_data = [[0.000, 0.000, 0.000],
[0.018, 0.023, 0.028],
[0.040, 0.056, 0.071],
[0.059, 0.087, 0.110],
[0.074, 0.114, 0.147],
[0.086, 0.139, 0.183],
[0.095, 0.163, 0.219],
[0.101, 0.187, 0.255],
[0.105, 0.209, 0.290],
[0.107, 0.230, 0.326],
[0.105, 0.251, 0.362],
[0.101, 0.271, 0.398],
[0.091, 0.291, 0.434],
[0.075, 0.309, 0.471],
[0.046, 0.325, 0.507],
[0.021, 0.341, 0.546],
[0.021, 0.363, 0.584],
[0.022, 0.385, 0.622],
[0.023, 0.408, 0.661]]

_3VarAddA1_data = [[0.000, 0.000, 0.000],
[0.020, 0.024, 0.016],
[0.047, 0.058, 0.034],
[0.072, 0.088, 0.048],
[0.093, 0.116, 0.059],
[0.113, 0.142, 0.067],
[0.131, 0.167, 0.071],
[0.149, 0.190, 0.074],
[0.166, 0.213, 0.074],
[0.182, 0.235, 0.072],
[0.198, 0.256, 0.068],
[0.215, 0.276, 0.061],
[0.232, 0.296, 0.051],
[0.249, 0.314, 0.037],
[0.270, 0.330, 0.018],
[0.288, 0.347, 0.000],
[0.302, 0.369, 0.000],
[0.315, 0.391, 0.000],
[0.328, 0.414, 0.000]]

_3VarAddA2_data = [[0.000, 0.000, 0.000],
[0.029, 0.020, 0.023],
[0.072, 0.045, 0.055],
[0.111, 0.067, 0.084],
[0.148, 0.085, 0.109],
[0.184, 0.101, 0.133],
[0.219, 0.115, 0.155],
[0.254, 0.127, 0.176],
[0.289, 0.138, 0.195],
[0.323, 0.147, 0.214],
[0.358, 0.155, 0.232],
[0.393, 0.161, 0.250],
[0.429, 0.166, 0.267],
[0.467, 0.169, 0.283],
[0.507, 0.168, 0.298],
[0.546, 0.168, 0.313],
[0.580, 0.172, 0.328],
[0.615, 0.175, 0.341],
[0.649, 0.178, 0.355]]

cmaps = {
name: LinearSegmentedColormap.from_list(name, data, _LUTSIZE) for name, data in [
('2VarAddA0', _2VarAddA0_data),
('2VarAddA1', _2VarAddA1_data),
('2VarSubA0', _2VarSubA0_data),
('2VarSubA1', _2VarSubA1_data),
('3VarAddA0', _3VarAddA0_data),
('3VarAddA1', _3VarAddA1_data),
('3VarAddA2', _3VarAddA2_data),
]}

cmap_families = {
'2VarAddA': MultivarColormap('2VarAddA', [cmaps['2VarAddA' + str(i)] for
i in range(2)], 'Add'),
'2VarSubA': MultivarColormap('2VarSubA', [cmaps['2VarSubA' + str(i)] for
i in range(2)], 'Sub'),
'3VarAddA': MultivarColormap('3VarAddA', [cmaps['3VarAddA' + str(i)] for
i in range(3)], 'Add'),
}
13 changes: 7 additions & 6 deletions lib/matplotlib/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import matplotlib as mpl
from . import _api, cbook
from .colors import BoundaryNorm
from .cm import ScalarMappable
from .cm import ScalarMappable, VectorMappable
from .path import Path
from .transforms import (BboxBase, Bbox, IdentityTransform, Transform, TransformedBbox,
TransformedPatchPath, TransformedPath)
Expand Down Expand Up @@ -1327,12 +1327,13 @@ def format_cursor_data(self, data):
--------
get_cursor_data
"""
if np.ndim(data) == 0 and isinstance(self, ScalarMappable):
# This block logically belongs to ScalarMappable, but can't be
# implemented in it because most ScalarMappable subclasses inherit
# from Artist first and from ScalarMappable second, so
if np.ndim(data) == 0 and \
(isinstance(self, ScalarMappable) or isinstance(self, VectorMappable)):
# This block logically belongs to Scalar/VectorMappable, but can't be
# implemented in it because most Scalar/VectorMappable subclasses
# inherit from Artist first and from Scalar/VectorMappable second, so
# Artist.format_cursor_data would always have precedence over
# ScalarMappable.format_cursor_data.
# Scalar/VectorMappable.format_cursor_data.
n = self.cmap.N
if np.ma.getmask(data):
return "[]"
Expand Down
45 changes: 39 additions & 6 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from matplotlib import _api, _docstring, _preprocess_data
from matplotlib.axes._base import (
_AxesBase, _TransformedBoundsLocator, _process_plot_format)
from matplotlib.cm import ensure_cmap, ensure_multivariate_params
from matplotlib.axes._secondary_axes import SecondaryAxis
from matplotlib.container import BarContainer, ErrorbarContainer, StemContainer

Expand Down Expand Up @@ -5755,6 +5756,7 @@ def imshow(self, X, cmap=None, norm=None, *, aspect=None,
- (M, N): an image with scalar data. The values are mapped to
colors using normalization and a colormap. See parameters *norm*,
*cmap*, *vmin*, *vmax*.
- (v, M, N): if coupled with a cmap that supports v scalars
- (M, N, 3): an image with RGB values (0-1 float or 0-255 int).
- (M, N, 4): an image with RGBA values (0-1 float or 0-255 int),
i.e. including transparency.
Expand Down Expand Up @@ -5929,6 +5931,11 @@ def imshow(self, X, cmap=None, norm=None, *, aspect=None,
`~matplotlib.pyplot.imshow` expects RGB images adopting the straight
(unassociated) alpha representation.
"""
cmap = ensure_cmap(cmap)
if cmap.n_variates > 1:
norm, vmin, vmax = ensure_multivariate_params(cmap.n_variates, X,
norm, vmin, vmax)

im = mimage.AxesImage(self, cmap=cmap, norm=norm,
interpolation=interpolation, origin=origin,
extent=extent, filternorm=filternorm,
Expand Down Expand Up @@ -5958,7 +5965,8 @@ def imshow(self, X, cmap=None, norm=None, *, aspect=None,
self.add_image(im)
return im

def _pcolorargs(self, funcname, *args, shading='auto', **kwargs):
def _pcolorargs(self, funcname, *args, n_variates=1,
shading='auto', **kwargs):
# - create X and Y if not present;
# - reshape X and Y as needed if they are 1-D;
# - check for proper sizes based on `shading` kwarg;
Expand All @@ -5976,7 +5984,11 @@ def _pcolorargs(self, funcname, *args, shading='auto', **kwargs):

if len(args) == 1:
C = np.asanyarray(args[0])
nrows, ncols = C.shape[:2]
if n_variates == 1:
nrows, ncols = C.shape[:2]
else:
nrows, ncols = C.shape[1:3]

if shading in ['gouraud', 'nearest']:
X, Y = np.meshgrid(np.arange(ncols), np.arange(nrows))
else:
Expand All @@ -5999,7 +6011,10 @@ def _pcolorargs(self, funcname, *args, shading='auto', **kwargs):
'x and y arguments to pcolormesh cannot have '
'non-finite values or be of type '
'numpy.ma.MaskedArray with masked values')
nrows, ncols = C.shape[:2]
if n_variates == 1:
nrows, ncols = C.shape[:2]
else:
nrows, ncols = C.shape[1:3]
else:
raise _api.nargs_error(funcname, takes="1 or 3", given=len(args))

Expand Down Expand Up @@ -6222,8 +6237,17 @@ def pcolor(self, *args, shading=None, alpha=None, norm=None, cmap=None,
if shading is None:
shading = mpl.rcParams['pcolor.shading']
shading = shading.lower()

cmap = ensure_cmap(cmap)
n_variates = cmap.n_variates

X, Y, C, shading = self._pcolorargs('pcolor', *args, shading=shading,
kwargs=kwargs)
n_variates=n_variates, kwargs=kwargs)

if n_variates > 1:
norm, vmin, vmax = ensure_multivariate_params(n_variates, C,
norm, vmin, vmax)

linewidths = (0.25,)
if 'linewidth' in kwargs:
kwargs['linewidths'] = kwargs.pop('linewidth')
Expand Down Expand Up @@ -6317,6 +6341,7 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None,
- (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*.
- (v, M, N): if coupled with a cmap that supports v scalars
- (M, N, 3): an image with RGB values (0-1 float or 0-255 int).
- (M, N, 4): an image with RGBA values (0-1 float or 0-255 int),
i.e. including transparency.
Expand Down Expand Up @@ -6480,8 +6505,16 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None,
shading = shading.lower()
kwargs.setdefault('edgecolors', 'none')

X, Y, C, shading = self._pcolorargs('pcolormesh', *args,
shading=shading, kwargs=kwargs)
cmap = ensure_cmap(cmap)
n_variates = cmap.n_variates

X, Y, C, shading = self._pcolorargs('pcolormesh', *args, shading=shading,
n_variates=n_variates, kwargs=kwargs)

if n_variates > 1:
norm, vmin, vmax = ensure_multivariate_params(n_variates, C,
norm, vmin, vmax)

coords = np.stack([X, Y], axis=-1)

kwargs.setdefault('snap', mpl.rcParams['pcolormesh.snap'])
Expand Down
Loading
Loading