|
1 | 1 | from collections import OrderedDict
|
| 2 | +from collections.abc import MutableSequence |
2 | 3 | from contextlib import ExitStack
|
3 | 4 | import inspect
|
4 | 5 | import itertools
|
@@ -1221,40 +1222,149 @@ def cla(self):
|
1221 | 1222 |
|
1222 | 1223 | self.stale = True
|
1223 | 1224 |
|
| 1225 | + class ArtistList(MutableSequence): |
| 1226 | + """ |
| 1227 | + A sublist of Axes children based on their type. |
| 1228 | +
|
| 1229 | + This exists solely to warn on modification. In the future, the |
| 1230 | + type-specific children sublists will be immutable tuples. |
| 1231 | + """ |
| 1232 | + def __init__(self, axes, prop_name, add_name, |
| 1233 | + valid_types=None, invalid_types=None): |
| 1234 | + """ |
| 1235 | + Parameters |
| 1236 | + ---------- |
| 1237 | + axes : .axes.Axes |
| 1238 | + The Axes from which this sublist will pull the children |
| 1239 | + Artists. |
| 1240 | + prop_name : str |
| 1241 | + The property name used to access this sublist from the Axes; |
| 1242 | + used to generate deprecation warnings. |
| 1243 | + add_name : str |
| 1244 | + The method name used to add Artists of this sublist's type to |
| 1245 | + the Axes; used to generate deprecation warnings. |
| 1246 | + valid_types : list of type, optional |
| 1247 | + A list of types that determine which children will be returned |
| 1248 | + by this sublist. If specified, then the Artists in the sublist |
| 1249 | + must be instances of any of these types. If unspecified, then |
| 1250 | + any type of Artist is valid (unless limited by |
| 1251 | + *invalid_types*.) |
| 1252 | + invalid_types : tuple, optional |
| 1253 | + A list of types that determine which children will *not* be |
| 1254 | + returned by this sublist. If specified, then Artists in the |
| 1255 | + sublist will never be an instance of these types. Otherwise, no |
| 1256 | + types will be excluded. |
| 1257 | + """ |
| 1258 | + self._axes = axes |
| 1259 | + self._prop_name = prop_name |
| 1260 | + self._add_name = add_name |
| 1261 | + self._type_check = lambda artist: ( |
| 1262 | + (not valid_types or isinstance(artist, valid_types)) and |
| 1263 | + (not invalid_types or not isinstance(artist, invalid_types)) |
| 1264 | + ) |
| 1265 | + |
| 1266 | + def __repr__(self): |
| 1267 | + return f'<Axes.ArtistList of {self._prop_name}>' |
| 1268 | + |
| 1269 | + def __len__(self): |
| 1270 | + return sum(self._type_check(artist) |
| 1271 | + for artist in self._axes._children) |
| 1272 | + |
| 1273 | + def __getitem__(self, key): |
| 1274 | + return [artist |
| 1275 | + for artist in self._axes._children |
| 1276 | + if self._type_check(artist)][key] |
| 1277 | + |
| 1278 | + def insert(self, index, item): |
| 1279 | + cbook.warn_deprecated( |
| 1280 | + '3.4', |
| 1281 | + name=f'modification of the Axes.{self._prop_name}', |
| 1282 | + obj_type='property', |
| 1283 | + alternative=f'Axes.{self._add_name}') |
| 1284 | + try: |
| 1285 | + index = self._axes._children.index(self[index]) |
| 1286 | + except IndexError: |
| 1287 | + index = None |
| 1288 | + getattr(self._axes, self._add_name)(item) |
| 1289 | + if index is not None: |
| 1290 | + # Move new item to the specified index, if there's something to |
| 1291 | + # put it before. |
| 1292 | + self._axes._children[index:index] = self._axes._children[-1:] |
| 1293 | + del self._axes._children[-1] |
| 1294 | + |
| 1295 | + def __setitem__(self, key, item): |
| 1296 | + cbook.warn_deprecated( |
| 1297 | + '3.4', |
| 1298 | + name=f'modification of the Axes.{self._prop_name}', |
| 1299 | + obj_type='property', |
| 1300 | + alternative=f'Artist.remove() and Axes.f{self._add_name}') |
| 1301 | + del self[key] |
| 1302 | + if isinstance(key, slice): |
| 1303 | + key = key.start |
| 1304 | + if not np.iterable(item): |
| 1305 | + self.insert(key, item) |
| 1306 | + return |
| 1307 | + |
| 1308 | + try: |
| 1309 | + index = self._axes._children.index(self[key]) |
| 1310 | + except IndexError: |
| 1311 | + index = None |
| 1312 | + for i, artist in enumerate(item): |
| 1313 | + getattr(self._axes, self._add_name)(artist) |
| 1314 | + if index is not None: |
| 1315 | + # Move new items to the specified index, if there's something |
| 1316 | + # to put it before. |
| 1317 | + i = -(i + 1) |
| 1318 | + self._axes._children[index:index] = self._axes._children[i:] |
| 1319 | + del self._axes._children[i:] |
| 1320 | + |
| 1321 | + def __delitem__(self, key): |
| 1322 | + cbook.warn_deprecated( |
| 1323 | + '3.4', |
| 1324 | + name=f'modification of the Axes.{self._prop_name}', |
| 1325 | + obj_type='property', |
| 1326 | + alternative='Artist.remove()') |
| 1327 | + if isinstance(key, slice): |
| 1328 | + for artist in self[key]: |
| 1329 | + artist.remove() |
| 1330 | + else: |
| 1331 | + self[key].remove() |
| 1332 | + |
1224 | 1333 | @property
|
1225 | 1334 | def artists(self):
|
1226 |
| - return tuple( |
1227 |
| - a for a in self._children |
1228 |
| - if not isinstance(a, ( |
1229 |
| - mcoll.Collection, mimage.AxesImage, mlines.Line2D, |
1230 |
| - mpatches.Patch, mtable.Table, mtext.Text))) |
| 1335 | + return self.ArtistList(self, 'artists', 'add_artist', invalid_types=( |
| 1336 | + mcoll.Collection, mimage.AxesImage, mlines.Line2D, mpatches.Patch, |
| 1337 | + mtable.Table, mtext.Text)) |
1231 | 1338 |
|
1232 | 1339 | @property
|
1233 | 1340 | def collections(self):
|
1234 |
| - return tuple(a for a in self._children |
1235 |
| - if isinstance(a, mcoll.Collection)) |
| 1341 | + return self.ArtistList(self, 'collections', 'add_collection', |
| 1342 | + valid_types=mcoll.Collection) |
1236 | 1343 |
|
1237 | 1344 | @property
|
1238 | 1345 | def images(self):
|
1239 |
| - return tuple(a for a in self._children |
1240 |
| - if isinstance(a, mimage.AxesImage)) |
| 1346 | + return self.ArtistList(self, 'images', 'add_image', |
| 1347 | + valid_types=mimage.AxesImage) |
1241 | 1348 |
|
1242 | 1349 | @property
|
1243 | 1350 | def lines(self):
|
1244 |
| - return tuple(a for a in self._children if isinstance(a, mlines.Line2D)) |
| 1351 | + return self.ArtistList(self, 'lines', 'add_line', |
| 1352 | + valid_types=mlines.Line2D) |
1245 | 1353 |
|
1246 | 1354 | @property
|
1247 | 1355 | def patches(self):
|
1248 |
| - return tuple(a for a in self._children |
1249 |
| - if isinstance(a, mpatches.Patch)) |
| 1356 | + return self.ArtistList(self, 'patches', 'add_patch', |
| 1357 | + valid_types=mpatches.Patch) |
1250 | 1358 |
|
1251 | 1359 | @property
|
1252 | 1360 | def tables(self):
|
1253 |
| - return tuple(a for a in self._children if isinstance(a, mtable.Table)) |
| 1361 | + return self.ArtistList(self, 'tables', 'add_table', |
| 1362 | + valid_types=mtable.Table) |
1254 | 1363 |
|
1255 | 1364 | @property
|
1256 | 1365 | def texts(self):
|
1257 |
| - return tuple(a for a in self._children if isinstance(a, mtext.Text)) |
| 1366 | + return self.ArtistList(self, 'texts', 'add_text', |
| 1367 | + valid_types=mtext.Text) |
1258 | 1368 |
|
1259 | 1369 | def clear(self):
|
1260 | 1370 | """Clear the axes."""
|
|
0 commit comments