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

Skip to content

Commit fce2a6d

Browse files
committed
Merge pull request #4091 from tacaswell/repl_hook
ENH : add function to add displayhook
2 parents 2ac9ac2 + 0abbcf6 commit fce2a6d

23 files changed

+617
-208
lines changed

boilerplate.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ def %(func)s(%(argspec)s):
5656
%(ax)s.hold(hold)
5757
try:
5858
%(ret)s = %(ax)s.%(func)s(%(call)s)
59-
draw_if_interactive()
6059
finally:
6160
%(ax)s.hold(%(washold)s)
6261
%(mappable)s
@@ -69,7 +68,6 @@ def %(func)s(%(argspec)s):
6968
@docstring.copy_dedent(Axes.%(func)s)
7069
def %(func)s(%(argspec)s):
7170
%(ret)s = gca().%(func)s(%(call)s)
72-
draw_if_interactive()
7371
return %(ret)s
7472
"""
7573

@@ -85,7 +83,6 @@ def {name}():
8583
8684
if im is not None:
8785
im.set_cmap(cm.{name})
88-
draw_if_interactive()
8986
9087
"""
9188

@@ -219,8 +216,9 @@ def format_value(value):
219216
else:
220217
def_edited = []
221218
for val in defaults:
222-
if isinstance(val, unicode):
223-
val = val.encode('ascii', 'ignore')
219+
if six.PY2:
220+
if isinstance(val, unicode):
221+
val = val.encode('ascii', 'ignore')
224222
def_edited.append(val)
225223
defaults = tuple(def_edited)
226224

@@ -273,7 +271,7 @@ def format_value(value):
273271

274272
# Since we can't avoid using some function names,
275273
# bail out if they are used as argument names
276-
for reserved in ('gca', 'gci', 'draw_if_interactive'):
274+
for reserved in ('gca', 'gci'):
277275
if reserved in bad:
278276
msg = 'Axes method %s has kwarg named %s' % (func, reserved)
279277
raise ValueError(msg)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
Interactive OO usage
2+
--------------------
3+
4+
All `Artists` now keep track of if their internal state has been
5+
changed but not reflected in the display ('stale') by a call to
6+
``draw``. It is thus possible to pragmatically determine if a given
7+
`Figure` needs to be re-drawn in an interactive session.
8+
9+
To facilitate interactive usage a ``draw_all`` method has been added
10+
to ``pyplot`` which will redraw all of the figures which are 'stale'.
11+
12+
To make this convenient for interactive use matplotlib now registers
13+
a function either with IPython's 'post_execute' event or with the
14+
displayhook in the standard python REPL to automatically call
15+
``plt.draw_all`` just before control is returned to the REPL. This ensures
16+
that the draw command is deferred and only called once.
17+
18+
The upshot of this is that for interactive backends (including
19+
``%matplotlib notebook``) in interactive mode (with ``plt.ion()``)
20+
21+
.. ipython :: python
22+
23+
import matplotlib.pyplot as plt
24+
25+
fig, ax = plt.subplots()
26+
27+
ln, = ax.plot([0, 1, 4, 9, 16])
28+
29+
plt.show()
30+
31+
ln.set_color('g')
32+
33+
34+
will automatically update the plot to be green. Any subsequent
35+
modifications to the ``Artist`` objects will do likewise.
36+
37+
This is the first step of a larger consolidation and simplification of
38+
the pyplot internals.

lib/matplotlib/_pylab_helpers.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,5 +139,15 @@ def set_active(cls, manager):
139139
cls._activeQue.append(manager)
140140
cls.figs[manager.num] = manager
141141

142+
@classmethod
143+
def draw_all(cls, force=False):
144+
"""
145+
Redraw all figures registered with the pyplot
146+
state machine.
147+
"""
148+
for f_mgr in cls.get_all_fig_managers():
149+
# TODO add logic to check if figure is stale
150+
if force or f_mgr.canvas.figure.stale:
151+
f_mgr.canvas.draw_idle()
142152

143153
atexit.register(Gcf.destroy_all)

lib/matplotlib/artist.py

Lines changed: 57 additions & 2 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 _stale_figure_callback(self):
72+
self.figure.stale = True
73+
74+
75+
def _stale_axes_callback(self):
76+
self.axes.stale = True
77+
78+
7179
class Artist(object):
7280
"""
7381
Abstract base class for someone who renders into a
@@ -78,6 +86,7 @@ class Artist(object):
7886
zorder = 0
7987

8088
def __init__(self):
89+
self._stale = True
8190
self._axes = None
8291
self.figure = None
8392

@@ -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(_stale_axes_callback)
226+
214227
return new_axes
215228

229+
@property
230+
def stale(self):
231+
"""
232+
If the artist is 'stale' and needs to be re-drawn for the output to
233+
match the internal state of the artist.
234+
"""
235+
return self._stale
236+
237+
@stale.setter
238+
def stale(self, val):
239+
# only trigger call-back stack on being marked as 'stale'
240+
# when not already stale
241+
# the draw process will take care of propagating the cleaning
242+
# process
243+
if not (self._stale == val):
244+
self._stale = val
245+
# only trigger propagation if marking as stale
246+
if self._stale:
247+
self.pchanged()
248+
216249
def get_window_extent(self, renderer):
217250
"""
218251
Get the axes bounding box in display space.
@@ -283,6 +316,7 @@ def set_transform(self, t):
283316
self._transform = t
284317
self._transformSet = True
285318
self.pchanged()
319+
self.stale = True
286320

287321
def get_transform(self):
288322
"""
@@ -499,6 +533,7 @@ def set_snap(self, snap):
499533
Only supported by the Agg and MacOSX backends.
500534
"""
501535
self._snap = snap
536+
self.stale = True
502537

503538
def get_sketch_params(self):
504539
"""
@@ -546,13 +581,15 @@ def set_sketch_params(self, scale=None, length=None, randomness=None):
546581
self._sketch = None
547582
else:
548583
self._sketch = (scale, length or 128.0, randomness or 16.0)
584+
self.stale = True
549585

550586
def set_path_effects(self, path_effects):
551587
"""
552588
set path_effects, which should be a list of instances of
553589
matplotlib.patheffect._Base class or its derivatives.
554590
"""
555591
self._path_effects = path_effects
592+
self.stale = True
556593

557594
def get_path_effects(self):
558595
return self._path_effects
@@ -572,7 +609,10 @@ def set_figure(self, fig):
572609
ACCEPTS: a :class:`matplotlib.figure.Figure` instance
573610
"""
574611
self.figure = fig
575-
self.pchanged()
612+
if self.figure and self.figure is not self:
613+
self.add_callback(_stale_figure_callback)
614+
self.pchanged()
615+
self.stale = True
576616

577617
def set_clip_box(self, clipbox):
578618
"""
@@ -582,6 +622,7 @@ def set_clip_box(self, clipbox):
582622
"""
583623
self.clipbox = clipbox
584624
self.pchanged()
625+
self.stale = True
585626

586627
def set_clip_path(self, path, transform=None):
587628
"""
@@ -634,8 +675,10 @@ def set_clip_path(self, path, transform=None):
634675
if not success:
635676
print(type(path), type(transform))
636677
raise TypeError("Invalid arguments to set_clip_path")
637-
678+
# this may result in the callbacks being hit twice, but grantees they
679+
# will be hit at least once
638680
self.pchanged()
681+
self.stale = True
639682

640683
def get_alpha(self):
641684
"""
@@ -684,7 +727,10 @@ def set_clip_on(self, b):
684727
ACCEPTS: [True | False]
685728
"""
686729
self._clipon = b
730+
# This may result in the callbacks being hit twice, but ensures they
731+
# are hit at least once
687732
self.pchanged()
733+
self.stale = True
688734

689735
def _set_gc_clip(self, gc):
690736
'Set the clip properly for the gc'
@@ -723,11 +769,13 @@ def set_agg_filter(self, filter_func):
723769
724770
"""
725771
self._agg_filter = filter_func
772+
self.stale = True
726773

727774
def draw(self, renderer, *args, **kwargs):
728775
'Derived classes drawing method'
729776
if not self.get_visible():
730777
return
778+
self.stale = False
731779

732780
def set_alpha(self, alpha):
733781
"""
@@ -738,6 +786,7 @@ def set_alpha(self, alpha):
738786
"""
739787
self._alpha = alpha
740788
self.pchanged()
789+
self.stale = True
741790

742791
def set_visible(self, b):
743792
"""
@@ -747,6 +796,7 @@ def set_visible(self, b):
747796
"""
748797
self._visible = b
749798
self.pchanged()
799+
self.stale = True
750800

751801
def set_animated(self, b):
752802
"""
@@ -756,6 +806,7 @@ def set_animated(self, b):
756806
"""
757807
self._animated = b
758808
self.pchanged()
809+
self.stale = True
759810

760811
def update(self, props):
761812
"""
@@ -778,6 +829,7 @@ def update(self, props):
778829
self.eventson = store
779830
if changed:
780831
self.pchanged()
832+
self.stale = True
781833

782834
def get_label(self):
783835
"""
@@ -796,6 +848,7 @@ def set_label(self, s):
796848
else:
797849
self._label = None
798850
self.pchanged()
851+
self.stale = True
799852

800853
def get_zorder(self):
801854
"""
@@ -812,6 +865,7 @@ def set_zorder(self, level):
812865
"""
813866
self.zorder = level
814867
self.pchanged()
868+
self.stale = True
815869

816870
def update_from(self, other):
817871
'Copy properties from *other* to *self*.'
@@ -826,6 +880,7 @@ def update_from(self, other):
826880
self._sketch = other._sketch
827881
self._path_effects = other._path_effects
828882
self.pchanged()
883+
self.stale = True
829884

830885
def properties(self):
831886
"""

0 commit comments

Comments
 (0)