8000 Merge remote-tracking branch 'upstream/v1.3.x' · matplotlib/matplotlib@c38a71e · GitHub
[go: up one dir, main page]

Skip to content

Commit c38a71e

Browse files
committed
Merge remote-tracking branch 'upstream/v1.3.x'
Conflicts: lib/matplotlib/quiver.py
2 parents c16d2c2 + 3d297ff commit c38a71e

File tree

3 files changed

+114
-26
lines changed

3 files changed

+114
-26
lines changed

lib/matplotlib/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,7 @@ def tk_window_focus():
13291329
'matplotlib.tests.test_patheffects',
13301330
'matplotlib.tests.test_pickle',
13311331
'matplotlib.tests.test_png',
1332+
'matplotlib.tests.test_quiver',
13321333
'matplotlib.tests.test_rcparams',
13331334
'matplotlib.tests.test_scale',
13341335
'matplotlib.tests.test_simplification',

lib/matplotlib/quiver.py

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
the Quiver code.
1515
"""
1616

17-
1817
from __future__ import (absolute_import, division, print_function,
1918
unicode_literals)
2019

2120
import six
21+
import weakref
2222

2323
import numpy as np
2424
from numpy import ma
25-
import matplotlib.collections as collections
25+
import matplotlib.collections as mcollections
2626
import matplotlib.transforms as transforms
2727
import matplotlib.text as mtext
2828
import matplotlib.artist as martist
@@ -223,9 +223,9 @@
223223

224224
class QuiverKey(martist.Artist):
225225
""" Labelled arrow for use as a quiver plot scale key."""
226-
halign = {'N': 'center', 'S': 'center', 'E': 'left', 'W': 'right'}
227-
valign = {'N': 'bottom', 'S': 'top', 'E': 'center', 'W': 'center'}
228-
pivot = {'N': 'mid', 'S': 'mid', 'E': 'tip', 'W': 'tail'}
226+
halign = {'N': 'center', 'S': 'center', 'E': 'left', 'W': 'right'}
227+
valign = {'N': 'bottom', 'S': 'top', 'E': 'center', 'W': 'center'}
228+
pivot = {'N': 'mid', 'S': 'mid', 'E': 'tip', 'W': 'tail'}
229229

230230
def __init__(self, Q, X, Y, U, label, **kw):
231231
martist.Artist.__init__(self)
@@ -239,13 +239,19 @@ def __init__(self, Q, X, Y, U, label, **kw):
239239
self._labelsep_inches = kw.pop('labelsep', 0.1)
240240
self.labelsep = (self._labelsep_inches * Q.ax.figure.dpi)
241241

242+
# try to prevent closure over the real self
243+
weak_self = weakref.ref(self)
244+
242245
def on_dpi_change(fig):
243-
self.labelsep = (self._labelsep_inches * fig.dpi)
244-
self._initialized = False # simple brute force update
245-
# works because _init is called
246-
# at the start of draw.
246+
self_weakref = weak_self()
247+
if self_weakref is not None:
248+
self_weakref.labelsep = (self_weakref._labelsep_inches*fig.dpi)
249+
self_weakref._initialized = False # simple brute force update
250+
# works because _init is called
251+
# at the start of draw.
247252

248-
Q.ax.figure.callbacks.connect('dpi_changed', on_dpi_change)
253+
self._cid = Q.ax.figure.callbacks.connect('dpi_changed',
254+
on_dpi_change)
249255

250256
self.labelpos = kw.pop('labelpos', 'N')
251257
self.labelcolor = kw.pop('labelcolor', None)
@@ -258,11 +264,21 @@ def on_dpi_change(fig):
258264
horizontalalignment=self.halign[self.labelpos],
259265
verticalalignment=self.valign[self.labelpos],
260266
fontproperties=font_manager.FontProperties(**_fp))
267+
261268
if self.labelcolor is not None:
262269
self.text.set_color(self.labelcolor)
263270
self._initialized = False
264271
self.zorder = Q.zorder + 0.1
265272

273+
def remove(self):
274+
"""
275+
Overload the remove method
276+
"""
277+
self.Q.ax.figure.callbacks.disconnect(self._cid)
278+
self._cid = None
279+
# pass the remove call up the stack
280+
martist.Artist.remove(self)
281+
266282
__init__.__doc__ = _quiverkey_doc
267283

268284
def _init(self):
@@ -279,7 +295,7 @@ def _init(self):
279295
self.Q.pivot = _pivot
280296
kw = self.Q.polykw
281297
kw.update(self.kw)
282-
self.vector = collections.PolyCollection(
298+
self.vector = mcollections.PolyCollection(
283299
self.verts,
284300
offsets=[(self.X, self.Y)],
285301
transOffset=self.get_transform(),
@@ -368,7 +384,7 @@ def _parse_args(*args):
368384
return X, Y, U, V, C
369385

370386

371-
class Quiver(collections.PolyCollection):
387+
class Quiver(mcollections.PolyCollection):
372388
"""
373389
Specialized PolyCollection for arrows.
374390
@@ -415,7 +431,7 @@ def __init__(self, ax, *args, **kw):
415431
self.transform = kw.pop('transform', ax.transData)
416432
kw.setdefault('facecolors', self.color)
417433
kw.setdefault('linewidths', (0,))
418-
collections.PolyCollection.__init__(self, [], offsets=self.XY,
434+
mcollections.PolyCollection.__init__(self, [], offsets=self.XY,
419435
transOffset=self.transform,
420436
closed=False,
421437
**kw)
@@ -426,14 +442,30 @@ def __init__(self, ax, *args, **kw):
426442
self.keyvec = None
427443
self.keytext = None
428444

429-
def on_dpi_change(fig):
430-
self._new_UV = True # vertices depend on width, span
431-
# which in turn depend on dpi
432-
self._initialized = False # simple brute force update
433-
# works because _init is called
434-
# at the start of draw.
445+
# try to prevent closure over the real self
446+
weak_self = weakref.ref(self)
435447

436-
self.ax.figure.callbacks.connect('dpi_changed', on_dpi_change)
448+
def on_dpi_change(fig):
449+
self_weakref = weak_self()
450+
if self_weakref is not None:
451+
self_weakref._new_UV = True # vertices depend on width, span
452+
# which in turn depend on dpi
453+
self_weakref._initialized = False # simple brute force update
454+
# works because _init is called
455+
# at the start of draw.
456+
457+
self._cid = self.ax.figure.callbacks.connect('dpi_changed',
458+
on_dpi_change)
459+
460+
def remove(self):
461+
"""
462+
Overload the remove method
463+
"""
464+
# disconnect the call back
465+
self.ax.figure.callbacks.disconnect(self._cid)
466+
self._cid = None
467+
# pass the remove call up the stack
468+
mcollections.PolyCollection.remove(self)
437469

438470
def _init(self):
439471
"""
@@ -460,7 +492,7 @@ def draw(self, renderer):
460492
verts = self._make_verts(self.U, self.V)
461493
self.set_verts(verts, closed=False)
462494
self._new_UV = False
463-
collections.PolyCollection.draw(self, renderer)
495+
mcollections.PolyCollection.draw(self, renderer)
464496

465497
def set_UVC(self, U, V, C=None):
466498
U = ma.masked_invalid(U, copy=False).ravel()
@@ -793,7 +825,7 @@ def _h_arrows(self, length):
793825
docstring.interpd.update(barbs_doc=_barbs_doc)
794826

795827

796-
class Barbs(collections.PolyCollection):
828+
class Barbs(mcollections.PolyCollection):
797829
'''
798830
Specialized PolyCollection for barbs.
799831
@@ -854,8 +886,9 @@ def __init__(self, ax, *args, **kw):
854886

855887
#Make a collection
856888
barb_size = self._length ** 2 / 4 # Empirically determined
857-
collections.PolyCollection.__init__(self, [], (barb_size,), offsets=xy,
858-
transOffset=transform, **kw)
889+
mcollections.PolyCollection.__init__(self, [], (barb_size,),
890+
offsets=xy,
891+
transOffset=transform, **kw)
859892
self.set_transform(transforms.IdentityTransform())
860893

861894
self.set_UVC(u, v, c)
@@ -1078,7 +1111,7 @@ def set_offsets(self, xy):
10781111
x, y, u, v = delete_masked_points(self.x.ravel(), self.y.ravel(),
10791112
self.u, self.v)
10801113
xy = np.hstack((x[:, np.newaxis], y[:, np.newaxis]))
1081-
collections.PolyCollection.set_offsets(self, xy)
1082-
set_offsets.__doc__ = collections.PolyCollection.set_offsets.__doc__
1114+
mcollections.PolyCollection.set_offsets(self, xy)
1115+
set_offsets.__doc__ = mcollections.PolyCollection.set_offsets.__doc__
10831116

10841117
barbs_doc = _barbs_doc

lib/matplotlib/tests/test_quiver.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from __future__ import print_function
2+
import os
3+
import tempfile
4+
import numpy as np
5+
import sys
6+
from matplotlib import pyplot as plt
7+
from matplotlib.testing.decorators import cleanup
8+
9+
10+
WRITER_OUTPUT = dict(ffmpeg='mp4', ffmpeg_file='mp4',
11+
mencoder='mp4', mencoder_file='mp4',
12+
avconv='mp4', avconv_file='mp4',
13+
imagemagick='gif', imagemagick_file='gif')
14+
15+
16+
@cleanup
17+
def test_quiver_memory_leak():
18+
fig, ax = plt.subplots()
19+
20+
X, Y = np.meshgrid(np.arange(0, 2 * np.pi, .04),
21+
np.arange(0, 2 * np.pi, .04))
22+
U = np.cos(X)
23+
V = np.sin(Y)
24+
25+
Q = ax.quiver(U, V)
26+
ttX = Q.X
27+
Q.remove()
28+
29+
del Q
30+
31+
assert sys.getrefcount(ttX) == 2
32+
33+
34+
@cleanup
35+
def test_quiver_key_memory_leak():
36+
fig, ax = plt.subplots()
37+
38+
X, Y = np.meshgrid(np.arange(0, 2 * np.pi, .04),
39+
np.arange(0, 2 * np.pi, .04))
40+
U = np.cos(X)
41+
V = np.sin(Y)
42+
43+
Q = ax.quiver(U, V)
44+
45+
qk = ax.quiverkey(Q, 0.5, 0.92, 2, r'$2 \frac{m}{s}$',
46+
labelpos='W',
47+
fontproperties={'weight': 'bold'})
48+
assert sys.getrefcount(qk) == 3
49+
qk.remove()
50+
assert sys.getrefcount(qk) == 2
51+
52+
if __name__ == '__main__':
53+
import nose
54+
nose.runmodule()

0 commit comments

Comments
 (0)
0