8000 fix for multivariate tests · matplotlib/matplotlib@b4af71e · GitHub
[go: up one dir, main page]

Skip to content

Commit b4af71e

Browse files
committed
fix for multivariate tests
VectorMappable with data type objects Creation of the VectorMappable class correctetion to _ensure_multivariate_norm() since colors.Normalize does not have a copy() function, we cannot easily convert a single norm to a sequence of norms. better use of union for colormap types Defines ColorMapType as Union[colors.Colormap, colors.BivarColormap, colors.MultivarColormap] in typing.py Suport for multivariate colormaps in imshow, pcolor and pcolormesh safe_masked_invalid() for data types with multiple fields NormAndColor class cleanup of _ImageBase._make_image() Rename To Mapper and reorganizatino changed name of new class from Mapper to Colorizer and removed calls to old api changed name of new class from Mapper to Colorizer Mapper → Colorizer ScalarMappableShim → ColorizerShim ColorableArtist → ColorizingArtist also removed all internal calls using the ColorizerShim api
1 parent 44a3b00 commit b4af71e

38 files changed

+2071
-476
lines changed

lib/matplotlib/artist.py

Lines changed: 128 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
import numpy as np
1313

1414
import matplotlib as mpl
15-
from . import _api, cbook
16-
from .colors import BoundaryNorm
15+
from . import _api, cbook, cm
1716
from .cm import ScalarMappable
1817
from .path import Path
1918
from .transforms import (BboxBase, Bbox, IdentityTransform, Transform, TransformedBbox,
@@ -1346,35 +1345,16 @@ def format_cursor_data(self, data):
13461345
--------
13471346
get_cursor_data
13481347
"""
1349-
if np.ndim(data) == 0 and isinstance(self, ScalarMappable):
1348+
if isinstance(self, ScalarMappable):
1349+
# Internal classes no longer inherit from ScalarMappable, and this
1350+
# block should never be executed by the internal API
1351+
13501352
# This block logically belongs to ScalarMappable, but can't be
1351-
# implemented in it because most ScalarMappable subclasses inherit
1352-
# from Artist first and from ScalarMappable second, so
1353+
# implemented in it in case custom ScalarMappable subclasses
1354+
# inherit from Artist first and from ScalarMappable second, so
13531355
# Artist.format_cursor_data would always have precedence over
13541356
# ScalarMappable.format_cursor_data.
1355-
n = self.cmap.N
1356-
if np.ma.getmask(data):
1357-
return "[]"
1358-
normed = self.norm(data)
1359-
if np.isfinite(normed):
1360-
if isinstance(self.norm, BoundaryNorm):
1361-
# not an invertible normalization mapping
1362-
cur_idx = np.argmin(np.abs(self.norm.boundaries - data))
1363-
neigh_idx = max(0, cur_idx - 1)
1364-
# use max diff to prevent delta == 0
1365-
delta = np.diff(
1366-
self.norm.boundaries[neigh_idx:cur_idx + 2]
1367-
).max()
1368-
1369-
else:
1370-
# Midpoints of neighboring color intervals.
1371-
neighbors = self.norm.inverse(
1372-
(int(normed * n) + np.array([0, 1])) / n)
1373-
delta = abs(neighbors - data).max()
1374-
g_sig_digits = cbook._g_sig_digits(data, delta)
1375-
else:
1376-
g_sig_digits = 3 # Consistent with default below.
1377-
return f"[{data:-#.{g_sig_digits}g}]"
1357+
return self.colorizer._format_cursor_data(data)
13781358
else:
13791359
try:
13801360
data[0]
@@ -1417,6 +1397,126 @@ def set_mouseover(self, mouseover):
14171397
mouseover = property(get_mouseover, set_mouseover) # backcompat.
14181398

14191399

1400+
class ColorizingArtist(Artist):
1401+
def __init__(self, norm=None, cmap=None):
1402+
"""
1403+
Parameters
1404+
----------
1405+
norm : `.Normalize` (or subclass thereof) or str or None
1406+
The normalizing object which scales data, typically into the
1407+
interval ``[0, 1]``.
1408+
If a `str`, a `.Normalize` subclass is dynamically generated based
1409+
on the scale with the corresponding name.
1410+
If *None*, *norm* defaults to a *colors.Normalize* object which
1411+
initializes its scaling based on the first data processed.
1412+
cmap : str or `~matplotlib.colors.Colormap`
1413+
The colormap used to map normalized data values to RGBA colors.
1414+
"""
1415+
1416+
Artist.__init__(self)
1417+
1418+
self._A = None
1419+
if isinstance(norm, cm.Colorizer):
1420+
self._colorizer = norm
1421+
else:
1422+
self._colorizer = cm.Colorizer(cmap, norm)
1423+
1424+
self._id_colorizer = self.colorizer.callbacks.connect('changed', self.changed)
1425+
self.callbacks = cbook.CallbackRegistry(signals=["changed"])
1426+
1427+
def set_array(self, A):
1428+
"""
1429+
Set the value array from array-like *A*.
1430+
1431+
Parameters
1432+
----------
1433+
A : array-like or None
1434+
The values that are mapped to colors.
1435+
1436+
The base class `.VectorMappable` does not make any assumptions on
1437+
the dimensionality and shape of the value array *A*.
1438+
"""
1439+
if A is None:
1440+
self._A = None
1441+
return
1442+
A = cm._ensure_multivariate_data(self.colorizer.cmap.n_variates, A)
1443+
1444+
A = cbook.safe_masked_invalid(A, copy=True)
1445+
if not np.can_cast(A.dtype, float, "same_kind"):
1446+
if A.dtype.fields is None:
1447+
raise TypeError(f"Image data of dtype {A.dtype} cannot be "
1448+
f"converted to float")
1449+
else:
1450+
for key in A.dtype.fields:
1451+
if not np.can_cast(A[key].dtype, float, "same_kind"):
1452+
raise TypeError(f"Image data of dtype {A.dtype} cannot be "
1453+
f"converted to a sequence of floats")
1454+
self._A = A
1455+
self.colorizer.autoscale_None(A)
1456+
1457+
def get_array(self):
1458+
"""
1459+
Return the array of values, that are mapped to colors.
1460+
1461+
The base class `.VectorMappable` does not make any assumptions on
1462+
the dimensionality and shape of the array.
1463+
"""
1464+
return self._A
1465+
1466+
@property
1467+
def colorizer(self):
1468+
return self._colorizer
1469+
1470+
@colorizer.setter
1471+
def colorizer(self, colorizer):
1472+
self._set_colorizer(colorizer)
1473+
1474+
def _set_colorizer(self, colorizer):
1475+
if isinstance(colorizer, cm.Colorizer):
1476+
if self._A is not None:
1477+
if not colorizer.cmap.n_variates == self.colorizer.cmap.n_variates:
1478+
raise ValueError('The new Colorizer object must have the same'
1479+
' number of variates as the existing data.')
1480+
else:
1481+
self.colorizer.callbacks.disconnect(self._id_colorizer)
1482+
self._colorizer = colorizer
1483+
self._id_colorizer = colorizer.callbacks.connect('changed',
1484+
self.changed)
1485+
self.changed()
1486+
else:
1487+
raise ValueError('Only a Colorizer object can be set to colorizer.')
1488+
1489+
def _get_colorizer(self):
1490+
"""
1491+
Function to get the colorizer.
1492+
Useful in edge cases where you want a standalone variable with the colorizer,
1493+
but also want the colorizer to update if the colorizer on the artist changes.
1494+
1495+
used in `contour.ContourLabeler.label_colorizer`
1496+
"""
1497+
return self._colorizer
1498+
1499+
def changed(self):
1500+
"""
1501+
Call this whenever the mappable is changed to notify all the
1502+
callbackSM listeners to the 'changed' signal.
1503+
"""
1504+
self.callbacks.process('changed')
1505+
self.stale = True
1506+
1507+
def format_cursor_data(self, data):
1508+
"""
1509+
Return a string representation of *data*.
1510+
1511+
Uses the colorbar's formatter to format the data.
1512+
1513+
See Also
1514+
--------
1515+
get_cursor_data
1516+
"""
1517+
return self.colorizer._format_cursor_data(data)
1518+
1519+
14201520
def _get_tightbbox_for_layout_only(obj, *args, **kwargs):
14211521
"""
14221522
Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a

0 commit comments

Comments
 (0)
0