Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 53e6f94

Browse files
committed
Add warn-on-modification wrappers around Axes.* lists.
1 parent bfc6844 commit 53e6f94

File tree

2 files changed

+198
-14
lines changed

2 files changed

+198
-14
lines changed

lib/matplotlib/axes/_base.py

Lines changed: 124 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from collections import OrderedDict
2+
from collections.abc import MutableSequence
23
from contextlib import ExitStack
34
import functools
45
import inspect
@@ -1218,40 +1219,149 @@ def cla(self):
12181219

12191220
self.stale = True
12201221

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+
12211330
@property
12221331
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))
12281335

12291336
@property
12301337
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)
12331340

12341341
@property
12351342
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)
12381345

12391346
@property
12401347
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)
12421350

12431351
@property
12441352
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)
12471355

12481356
@property
12491357
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)
12511360

12521361
@property
12531362
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)
12551365

12561366
def clear(self):
12571367
"""Clear the axes."""

lib/matplotlib/tests/test_axes.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6856,3 +6856,77 @@ def test_ylabel_ha_with_position(ha):
68566856
ax.set_ylabel("test", y=1, ha=ha)
68576857
ax.yaxis.set_label_position("right")
68586858
assert ax.yaxis.get_label().get_ha() == ha
6859+
6860+
6861+
def test_artist_sublists():
6862+
fig, ax = plt.subplots()
6863+
lines = [ax.plot(np.arange(i, i + 5))[0] for i in range(6)]
6864+
col = ax.scatter(np.arange(5), np.arange(5))
6865+
im = ax.imshow(np.zeros((5, 5)))
6866+
patch = ax.add_patch(mpatches.Rectangle((0, 0), 5, 5))
6867+
text = ax.text(0, 0, 'foo')
6868+
6869+
# Get items, which should not be mixed.
6870+
assert list(ax.collections) == [col]
6871+
assert list(ax.images) == [im]
6872+
assert list(ax.lines) == lines
6873+
assert list(ax.patches) == [patch]
6874+
assert not ax.tables
6875+
assert list(ax.texts) == [text]
6876+
6877+
# Get items should work like lists/tuple.
6878+
assert ax.lines[0] == lines[0]
6879+
assert ax.lines[-1] == lines[-1]
6880+
with pytest.raises(IndexError, match='out of range'):
6881+
ax.lines[len(lines) + 1]
6882+
6883+
# Deleting items (multiple or single) should warn.
6884+
with pytest.warns(MatplotlibDeprecationWarning,
6885+
match='modification of the Axes.lines property'):
6886+
del ax.lines[-1]
6887+
with pytest.warns(MatplotlibDeprecationWarning,
6888+
match='modification of the Axes.lines property'):
6889+
del ax.lines[-1:]
6890+
with pytest.warns(MatplotlibDeprecationWarning,
6891+
match='modification of the Axes.lines property'):
6892+
del ax.lines[1:]
6893+
with pytest.warns(MatplotlibDeprecationWarning,
6894+
match='modification of the Axes.lines property'):
6895+
del ax.lines[0]
6896+
6897+
col.remove()
6898+
im.remove()
6899+
patch.remove()
6900+
text.remove()
6901+
6902+
# Everything should be empty now.
6903+
assert not ax.collections
6904+
assert not ax.images
6905+
assert not ax.lines
6906+
assert not ax.patches
6907+
assert not ax.tables
6908+
assert not ax.texts
6909+
6910+
# Adding items should warn.
6911+
with pytest.warns(MatplotlibDeprecationWarning,
6912+
match='modification of the Axes.lines property'):
6913+
ax.lines.append(lines[-2])
6914+
assert list(ax.lines) == [lines[-2]]
6915+
with pytest.warns(MatplotlibDeprecationWarning,
6916+
match='modification of the Axes.lines property'):
6917+
ax.lines.append(lines[-1])
6918+
assert list(ax.lines) == lines[-2:]
6919+
with pytest.warns(MatplotlibDeprecationWarning,
6920+
match='modification of the Axes.lines property'):
6921+
ax.lines.insert(-2, lines[0])
6922+
assert list(ax.lines) == [lines[0], lines[-2], lines[-1]]
6923+
6924+
# Modifying items should warn.
6925+
with pytest.warns(MatplotlibDeprecationWarning,
6926+
match='modification of the Axes.lines property'):
6927+
ax.lines[0] = lines[0]
6928+
assert list(ax.lines) == [lines[0], lines[-2], lines[-1]]
6929+
with pytest.warns(MatplotlibDeprecationWarning,
6930+
match='modification of the Axes.lines property'):
6931+
ax.lines[1:1] = lines[1:-2]
6932+
assert list(ax.lines) == lines

0 commit comments

Comments
 (0)