|
12 | 12 | import numpy as np
|
13 | 13 |
|
14 | 14 | import matplotlib as mpl
|
15 |
| -from . import _api, cbook
D7AF
span> |
16 |
| -from .colors import BoundaryNorm |
| 15 | +from . import _api, cbook, cm |
17 | 16 | from .cm import ScalarMappable
|
18 | 17 | from .path import Path
|
19 | 18 | from .transforms import (BboxBase, Bbox, IdentityTransform, Transform, TransformedBbox,
|
@@ -1346,35 +1345,16 @@ def format_cursor_data(self, data):
|
1346 | 1345 | --------
|
1347 | 1346 | get_cursor_data
|
1348 | 1347 | """
|
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 | + |
1350 | 1352 | # 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 |
1353 | 1355 | # Artist.format_cursor_data would always have precedence over
|
1354 | 1356 | # 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) |
1378 | 1358 | else:
|
1379 | 1359 | try:
|
1380 | 1360 | data[0]
|
@@ -1417,6 +1397,126 @@ def set_mouseover(self, mouseover):
|
1417 | 1397 | mouseover = property(get_mouseover, set_mouseover) # backcompat.
|
1418 | 1398 |
|
1419 | 1399 |
|
| 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 | + |
1420 | 1520 | def _get_tightbbox_for_layout_only(obj, *args, **kwargs):
|
1421 | 1521 | """
|
1422 | 1522 | Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
|
|
0 commit comments