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