|
1 | 1 | from collections import OrderedDict
|
| 2 | +from collections.abc import MutableSequence |
2 | 3 | from contextlib import ExitStack
|
3 | 4 | import functools
|
4 | 5 | import inspect
|
@@ -1304,40 +1305,149 @@ def cla(self):
|
1304 | 1305 |
|
1305 | 1306 | self.stale = True
|
1306 | 1307 |
|
| 1308 | + class ArtistList(MutableSequence): |
| 1309 | + """ |
| 1310 | + A sublist of Axes children based on their type. |
| 1311 | +
|
| 1312 | + This exists solely to warn on modification. In the future, the |
| 1313 | + type-specific children sublists will be immutable tuples. |
| 1314 | + """ |
| 1315 | + def __init__(self, axes, prop_name, add_name, |
| 1316 | + valid_types=None, invalid_types=None): |
| 1317 | + """ |
| 1318 | + Parameters |
| 1319 | + ---------- |
| 1320 | + axes : .axes.Axes |
| 1321 | + The Axes from which this sublist will pull the children |
| 1322 | + Artists. |
| 1323 | + prop_name : str |
| 1324 | + The property name used to access this sublist from the Axes; |
| 1325 | + used to generate deprecation warnings. |
| 1326 | + add_name : str |
| 1327 | + The method name used to add Artists of this sublist's type to |
| 1328 | + the Axes; used to generate deprecation warnings. |
| 1329 | + valid_types : list of type, optional |
| 1330 | + A list of types that determine which children will be returned |
| 1331 | + by this sublist. If specified, then the Artists in the sublist |
| 1332 | + must be instances of any of these types. If unspecified, then |
| 1333 | + any type of Artist is valid (unless limited by |
| 1334 | + *invalid_types*.) |
| 1335 | + invalid_types : tuple, optional |
| 1336 | + A list of types that determine which children will *not* be |
| 1337 | + returned by this sublist. If specified, then Artists in the |
| 1338 | + sublist will never be an instance of these types. Otherwise, no |
| 1339 | + types will be excluded. |
| 1340 | + """ |
| 1341 | + self._axes = axes |
| 1342 | + self._prop_name = prop_name |
| 1343 | + self._add_name = add_name |
| 1344 | + self._type_check = lambda artist: ( |
| 1345 | + (not valid_types or isinstance(artist, valid_types)) and |
| 1346 | + (not invalid_types or not isinstance(artist, invalid_types)) |
| 1347 | + ) |
| 1348 | + |
| 1349 | + def __repr__(self): |
| 1350 | + return f'<Axes.ArtistList of {self._prop_name}>' |
| 1351 | + |
| 1352 | + def __len__(self): |
| 1353 | + return sum(self._type_check(artist) |
| 1354 | + for artist in self._axes._children) |
| 1355 | + |
| 1356 | + def __getitem__(self, key): |
| 1357 | + return [artist |
| 1358 | + for artist in self._axes._children |
| 1359 | + if self._type_check(artist)][key] |
| 1360 | + |
| 1361 | + def insert(self, index, item): |
| 1362 | + _api.warn_deprecated( |
| 1363 | + '3.5', |
| 1364 | + name=f'modification of the Axes.{self._prop_name}', |
| 1365 | + obj_type='property', |
| 1366 | + alternative=f'Axes.{self._add_name}') |
| 1367 | + try: |
| 1368 | + index = self._axes._children.index(self[index]) |
| 1369 | + except IndexError: |
| 1370 | + index = None |
| 1371 | + getattr(self._axes, self._add_name)(item) |
| 1372 | + if index is not None: |
| 1373 | + # Move new item to the specified index, if there's something to |
| 1374 | + # put it before. |
| 1375 | + self._axes._children[index:index] = self._axes._children[-1:] |
| 1376 | + del self._axes._children[-1] |
| 1377 | + |
| 1378 | + def __setitem__(self, key, item): |
| 1379 | + _api.warn_deprecated( |
| 1380 | + '3.5', |
| 1381 | + name=f'modification of the Axes.{self._prop_name}', |
| 1382 | + obj_type='property', |
| 1383 | + alternative=f'Artist.remove() and Axes.f{self._add_name}') |
| 1384 | + del self[key] |
| 1385 | + if isinstance(key, slice): |
| 1386 | + key = key.start |
| 1387 | + if not np.iterable(item): |
| 1388 | + self.insert(key, item) |
| 1389 | + return |
| 1390 | + |
| 1391 | + try: |
| 1392 | + index = self._axes._children.index(self[key]) |
| 1393 | + except IndexError: |
| 1394 | + index = None |
| 1395 | + for i, artist in enumerate(item): |
| 1396 | + getattr(self._axes, self._add_name)(artist) |
| 1397 | + if index is not None: |
| 1398 | + # Move new items to the specified index, if there's something |
| 1399 | + # to put it before. |
| 1400 | + i = -(i + 1) |
| 1401 | + self._axes._children[index:index] = self._axes._children[i:] |
| 1402 | + del self._axes._children[i:] |
| 1403 | + |
| 1404 | + def __delitem__(self, key): |
| 1405 | + _api.warn_deprecated( |
| 1406 | + '3.5', |
| 1407 | + name=f'modification of the Axes.{self._prop_name}', |
| 1408 | + obj_type='property', |
| 1409 | + alternative='Artist.remove()') |
| 1410 | + if isinstance(key, slice): |
| 1411 | + for artist in self[key]: |
| 1412 | + artist.remove() |
| 1413 | + else: |
| 1414 | + self[key].remove() |
| 1415 | + |
1307 | 1416 | @property
|
1308 | 1417 | def artists(self):
|
1309 |
| - return tuple( |
1310 |
| - a for a in self._children |
1311 |
| - if not isinstance(a, ( |
1312 |
| - mcoll.Collection, mimage.AxesImage, mlines.Line2D, |
1313 |
| - mpatches.Patch, mtable.Table, mtext.Text))) |
| 1418 | + return self.ArtistList(self, 'artists', 'add_artist', invalid_types=( |
| 1419 | + mcoll.Collection, mimage.AxesImage, mlines.Line2D, mpatches.Patch, |
| 1420 | + mtable.Table, mtext.Text)) |
1314 | 1421 |
|
1315 | 1422 | @property
|
1316 | 1423 | def collections(self):
|
1317 |
| - return tuple(a for a in self._children |
1318 |
| - if isinstance(a, mcoll.Collection)) |
| 1424 | + return self.ArtistList(self, 'collections', 'add_collection', |
| 1425 | + valid_types=mcoll.Collection) |
1319 | 1426 |
|
1320 | 1427 | @property
|
1321 | 1428 | def images(self):
|
1322 |
| - return tuple(a for a in self._children |
1323 |
| - if isinstance(a, mimage.AxesImage)) |
| 1429 | + return self.ArtistList(self, 'images', 'add_image', |
| 1430 | + valid_types=mimage.AxesImage) |
1324 | 1431 |
|
1325 | 1432 | @property
|
1326 | 1433 | def lines(self):
|
1327 |
| - return tuple(a for a in self._children if isinstance(a, mlines.Line2D)) |
| 1434 | + return self.ArtistList(self, 'lines', 'add_line', |
| 1435 | + valid_types=mlines.Line2D) |
1328 | 1436 |
|
1329 | 1437 | @property
|
1330 | 1438 | def patches(self):
|
1331 |
| - return tuple(a for a in self._children |
1332 |
| - if isinstance(a, mpatches.Patch)) |
| 1439 | + return self.ArtistList(self, 'patches', 'add_patch', |
| 1440 | + valid_types=mpatches.Patch) |
1333 | 1441 |
|
1334 | 1442 | @property
|
1335 | 1443 | def tables(self):
|
1336 |
| - return tuple(a for a in self._children if isinstance(a, mtable.Table)) |
| 1444 | + return self.ArtistList(self, 'tables', 'add_table', |
| 1445 | + valid_types=mtable.Table) |
1337 | 1446 |
|
1338 | 1447 | @property
|
1339 | 1448 | def texts(self):
|
1340 |
| - return tuple(a for a in self._children if isinstance(a, mtext.Text)) |
| 1449 | + return self.ArtistList(self, 'texts', 'add_text', |
| 1450 | + valid_types=mtext.Text) |
1341 | 1451 |
|
1342 | 1452 | def clear(self):
|
1343 | 1453 | """Clear the axes."""
|
|
0 commit comments