Skip to content

Commit f9ac8d0

Browse files
committed
ENH : add dirty flag
First pass at adding awareness of 'dirty' state to the draw tree. Changing properties of an artist (as indicated by a call to `self.pchanged()`) will make that artists axes and figure as 'dirty'. The state of the figure is queried by the `Gcf.draw_all` to only re-draw figure that are dirty. This is a major performance gain for expensive to draw figures. This is also (maybe) setting the stage for partial re-draws.
1 parent 28dcf3c commit f9ac8d0

File tree

4 files changed

+46
-2
lines changed

4 files changed

+46
-2
lines changed

lib/matplotlib/_pylab_helpers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ def draw_all(cls):
147147
"""
148148
for f_mgr in cls.get_all_fig_managers():
149149
# TODO add logic to check if figure is dirty
150-
f_mgr.canvas.draw()
150+
if f_mgr.canvas.figure.dirty:
151+
f_mgr.canvas.draw()
151152

152153
atexit.register(Gcf.destroy_all)

lib/matplotlib/artist.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ def draw_wrapper(artist, renderer, *args, **kwargs):
6868
return draw_wrapper
6969

7070

71+
def _dirty_figure_callback(self):
72+
self.figure.dirty = True
73+
74+
75+
def _dirty_axes_callback(self):
76+
self.axes.dirty = True
77+
78+
7179
class Artist(object):
7280
"""
7381
Abstract base class for someone who renders into a
@@ -109,6 +117,7 @@ def __init__(self):
109117
self._snap = None
110118
self._sketch = rcParams['path.sketch']
111119
self._path_effects = rcParams['path.effects']
120+
self._dirty = True
112121

113122
def __getstate__(self):
114123
d = self.__dict__.copy()
@@ -210,9 +219,33 @@ def axes(self, new_axes):
210219
"probably trying to re-use an artist "
211220
"in more than one Axes which is not "
212221
"supported")
222+
213223
self._axes = new_axes
224+
if new_axes is not None and new_axes is not self:
225+
self.add_callback(_dirty_axes_callback)
226+
214227
return new_axes
215228

229+
@property
230+
def dirty(self):
231+
"""
232+
If the artist is 'dirty' and needs to be re-drawn for the output to
233+
match the internal state of the artist.
234+
"""
235+
return self._dirty
236+
237+
@dirty.setter
238+
def dirty(self, val):
239+
# only trigger call-back stack on being marked as 'dirty'
240+
# when not already dirty
241+
# the draw process will take care of propagating the cleaning
242+
# process
243+
if not (self._dirty == val):
244+
self._dirty = val
245+
# only trigger propagation if marking as dirty
246+
if self._dirty:
247+
self.pchanged()
248+
216249
def get_window_extent(self, renderer):
217250
"""
218251
Get the axes bounding box in display space.
@@ -572,6 +605,7 @@ def set_figure(self, fig):
572605
ACCEPTS: a :class:`matplotlib.figure.Figure` instance
573606
"""
574607
self.figure = fig
608+
self.add_callback(_dirty_figure_callback)
575609
self.pchanged()
576610

577611
def set_clip_box(self, clipbox):
@@ -728,6 +762,7 @@ def draw(self, renderer, *args, **kwargs):
728762
'Derived classes drawing method'
729763
if not self.get_visible():
730764
return
765+
self._dirty = False
731766

732767
def set_alpha(self, alpha):
733768
"""

lib/matplotlib/axes/_base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2064,6 +2064,7 @@ def draw(self, renderer=None, inframe=False):
20642064
# will draw the edges
20652065
if self.axison and self._frameon:
20662066
self.patch.draw(renderer)
2067+
self.patch.dirty = False
20672068

20682069
if _do_composite:
20692070
# make a composite image, blending alpha
@@ -2096,18 +2097,22 @@ def draw(self, renderer=None, inframe=False):
20962097
self.patch.get_transform()))
20972098

20982099
renderer.draw_image(gc, round(l), round(b), im)
2100+
im.dirty = False
20992101
gc.restore()
21002102

21012103
if dsu_rasterized:
21022104
for zorder, a in dsu_rasterized:
21032105
a.draw(renderer)
2106+
a.dirty = False
21042107
renderer.stop_rasterizing()
21052108

21062109
for zorder, a in dsu:
21072110
a.draw(renderer)
2111+
a.dirty = False
21082112

21092113
renderer.close_group('axes')
21102114
self._cachedRenderer = renderer
2115+
self.dirty = False
21112116

21122117
def draw_artist(self, a):
21132118
"""
@@ -2120,6 +2125,7 @@ def draw_artist(self, a):
21202125
' caches the render')
21212126
raise AttributeError(msg)
21222127
a.draw(self._cachedRenderer)
2128+
a.dirty = False
21232129

21242130
def redraw_in_frame(self):
21252131
"""

lib/matplotlib/figure.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,7 @@ def draw(self, renderer):
10291029
Render the figure using :class:`matplotlib.backend_bases.RendererBase`
10301030
instance *renderer*.
10311031
"""
1032+
10321033
# draw the figure bounding box, perhaps none for white figure
10331034
if not self.get_visible():
10341035
return
@@ -1105,11 +1106,12 @@ def draw_composite():
11051106
dsu.sort(key=itemgetter(0))
11061107
for zorder, a, func, args in dsu:
11071108
func(*args)
1109+
a.dirty = False
11081110

11091111
renderer.close_group('figure')
11101112

11111113
self._cachedRenderer = renderer
1112-
1114+
self.dirty = False
11131115
self.canvas.draw_event(renderer)
11141116

11151117
def draw_artist(self, a):

0 commit comments

Comments
 (0)