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 8000 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