diff --git a/boilerplate.py b/boilerplate.py index 4136dae102a3..eabc728b4a64 100644 --- a/boilerplate.py +++ b/boilerplate.py @@ -56,7 +56,6 @@ def %(func)s(%(argspec)s): %(ax)s.hold(hold) try: %(ret)s = %(ax)s.%(func)s(%(call)s) - draw_if_interactive() finally: %(ax)s.hold(%(washold)s) %(mappable)s @@ -69,7 +68,6 @@ def %(func)s(%(argspec)s): @docstring.copy_dedent(Axes.%(func)s) def %(func)s(%(argspec)s): %(ret)s = gca().%(func)s(%(call)s) - draw_if_interactive() return %(ret)s """ @@ -85,7 +83,6 @@ def {name}(): if im is not None: im.set_cmap(cm.{name}) - draw_if_interactive() """ @@ -219,8 +216,9 @@ def format_value(value): else: def_edited = [] for val in defaults: - if isinstance(val, unicode): - val = val.encode('ascii', 'ignore') + if six.PY2: + if isinstance(val, unicode): + val = val.encode('ascii', 'ignore') def_edited.append(val) defaults = tuple(def_edited) @@ -273,7 +271,7 @@ def format_value(value): # Since we can't avoid using some function names, # bail out if they are used as argument names - for reserved in ('gca', 'gci', 'draw_if_interactive'): + for reserved in ('gca', 'gci'): if reserved in bad: msg = 'Axes method %s has kwarg named %s' % (func, reserved) raise ValueError(msg) diff --git a/doc/users/whats_new/2015-05_interactive_OO.rst b/doc/users/whats_new/2015-05_interactive_OO.rst new file mode 100644 index 000000000000..926a2edbfe2c --- /dev/null +++ b/doc/users/whats_new/2015-05_interactive_OO.rst @@ -0,0 +1,38 @@ +Interactive OO usage +-------------------- + +All `Artists` now keep track of if their internal state has been +changed but not reflected in the display ('stale') by a call to +``draw``. It is thus possible to pragmatically determine if a given +`Figure` needs to be re-drawn in an interactive session. + +To facilitate interactive usage a ``draw_all`` method has been added +to ``pyplot`` which will redraw all of the figures which are 'stale'. + +To make this convenient for interactive use matplotlib now registers +a function either with IPython's 'post_execute' event or with the +displayhook in the standard python REPL to automatically call +``plt.draw_all`` just before control is returned to the REPL. This ensures +that the draw command is deferred and only called once. + +The upshot of this is that for interactive backends (including +``%matplotlib notebook``) in interactive mode (with ``plt.ion()``) + +.. ipython :: python + + import matplotlib.pyplot as plt + + fig, ax = plt.subplots() + + ln, = ax.plot([0, 1, 4, 9, 16]) + + plt.show() + + ln.set_color('g') + + +will automatically update the plot to be green. Any subsequent +modifications to the ``Artist`` objects will do likewise. + +This is the first step of a larger consolidation and simplification of +the pyplot internals. diff --git a/lib/matplotlib/_pylab_helpers.py b/lib/matplotlib/_pylab_helpers.py index 840f6bd8e652..b851f06981d7 100644 --- a/lib/matplotlib/_pylab_helpers.py +++ b/lib/matplotlib/_pylab_helpers.py @@ -139,5 +139,15 @@ def set_active(cls, manager): cls._activeQue.append(manager) cls.figs[manager.num] = manager + @classmethod + def draw_all(cls, force=False): + """ + Redraw all figures registered with the pyplot + state machine. + """ + for f_mgr in cls.get_all_fig_managers(): + # TODO add logic to check if figure is stale + if force or f_mgr.canvas.figure.stale: + f_mgr.canvas.draw_idle() atexit.register(Gcf.destroy_all) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 0d95f9877e4f..b0d47abff004 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -68,6 +68,14 @@ def draw_wrapper(artist, renderer, *args, **kwargs): return draw_wrapper +def _stale_figure_callback(self): + self.figure.stale = True + + +def _stale_axes_callback(self): + self.axes.stale = True + + class Artist(object): """ Abstract base class for someone who renders into a @@ -78,6 +86,7 @@ class Artist(object): zorder = 0 def __init__(self): + self._stale = True self._axes = None self.figure = None @@ -210,9 +219,33 @@ def axes(self, new_axes): "probably trying to re-use an artist " "in more than one Axes which is not " "supported") + self._axes = new_axes + if new_axes is not None and new_axes is not self: + self.add_callback(_stale_axes_callback) + return new_axes + @property + def stale(self): + """ + If the artist is 'stale' and needs to be re-drawn for the output to + match the internal state of the artist. + """ + return self._stale + + @stale.setter + def stale(self, val): + # only trigger call-back stack on being marked as 'stale' + # when not already stale + # the draw process will take care of propagating the cleaning + # process + if not (self._stale == val): + self._stale = val + # only trigger propagation if marking as stale + if self._stale: + self.pchanged() + def get_window_extent(self, renderer): """ Get the axes bounding box in display space. @@ -283,6 +316,7 @@ def set_transform(self, t): self._transform = t self._transformSet = True self.pchanged() + self.stale = True def get_transform(self): """ @@ -499,6 +533,7 @@ def set_snap(self, snap): Only supported by the Agg and MacOSX backends. """ self._snap = snap + self.stale = True def get_sketch_params(self): """ @@ -546,6 +581,7 @@ def set_sketch_params(self, scale=None, length=None, randomness=None): self._sketch = None else: self._sketch = (scale, length or 128.0, randomness or 16.0) + self.stale = True def set_path_effects(self, path_effects): """ @@ -553,6 +589,7 @@ def set_path_effects(self, path_effects): matplotlib.patheffect._Base class or its derivatives. """ self._path_effects = path_effects + self.stale = True def get_path_effects(self): return self._path_effects @@ -572,7 +609,10 @@ def set_figure(self, fig): ACCEPTS: a :class:`matplotlib.figure.Figure` instance """ self.figure = fig - self.pchanged() + if self.figure and self.figure is not self: + self.add_callback(_stale_figure_callback) + self.pchanged() + self.stale = True def set_clip_box(self, clipbox): """ @@ -582,6 +622,7 @@ def set_clip_box(self, clipbox): """ self.clipbox = clipbox self.pchanged() + self.stale = True def set_clip_path(self, path, transform=None): """ @@ -634,8 +675,10 @@ def set_clip_path(self, path, transform=None): if not success: print(type(path), type(transform)) raise TypeError("Invalid arguments to set_clip_path") - + # this may result in the callbacks being hit twice, but grantees they + # will be hit at least once self.pchanged() + self.stale = True def get_alpha(self): """ @@ -684,7 +727,10 @@ def set_clip_on(self, b): ACCEPTS: [True | False] """ self._clipon = b + # This may result in the callbacks being hit twice, but ensures they + # are hit at least once self.pchanged() + self.stale = True def _set_gc_clip(self, gc): 'Set the clip properly for the gc' @@ -723,11 +769,13 @@ def set_agg_filter(self, filter_func): """ self._agg_filter = filter_func + self.stale = True def draw(self, renderer, *args, **kwargs): 'Derived classes drawing method' if not self.get_visible(): return + self.stale = False def set_alpha(self, alpha): """ @@ -738,6 +786,7 @@ def set_alpha(self, alpha): """ self._alpha = alpha self.pchanged() + self.stale = True def set_visible(self, b): """ @@ -747,6 +796,7 @@ def set_visible(self, b): """ self._visible = b self.pchanged() + self.stale = True def set_animated(self, b): """ @@ -756,6 +806,7 @@ def set_animated(self, b): """ self._animated = b self.pchanged() + self.stale = True def update(self, props): """ @@ -778,6 +829,7 @@ def update(self, props): self.eventson = store if changed: self.pchanged() + self.stale = True def get_label(self): """ @@ -796,6 +848,7 @@ def set_label(self, s): else: self._label = None self.pchanged() + self.stale = True def get_zorder(self): """ @@ -812,6 +865,7 @@ def set_zorder(self, level): """ self.zorder = level self.pchanged() + self.stale = True def update_from(self, other): 'Copy properties from *other* to *self*.' @@ -826,6 +880,7 @@ def update_from(self, other): self._sketch = other._sketch self._path_effects = other._path_effects self.pchanged() + self.stale = True def properties(self): """ diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 4bda0a0d55cf..7622e5fa8281 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -474,6 +474,7 @@ def __setstate__(self, state): container = getattr(self, container_name) for artist in container: artist._remove_method = container.remove + self.stale = True def get_window_extent(self, *args, **kwargs): """ @@ -754,6 +755,7 @@ def set_position(self, pos, which='both'): self._position.set(pos) if which in ('both', 'original'): self._originalPosition.set(pos) + self.stale = True def reset_position(self): """Make the original position the active position""" @@ -768,6 +770,7 @@ def set_axes_locator(self, locator): returns a bbox. """ self._axes_locator = locator + self.stale = True def get_axes_locator(self): """ @@ -973,6 +976,7 @@ def cla(self): if self._sharey: self.yaxis.set_visible(yaxis_visible) self.patch.set_visible(patch_visible) + self.stale = True def clear(self): """clear the axes""" @@ -1082,6 +1086,7 @@ def set_aspect(self, aspect, adjustable=None, anchor=None): self.set_adjustable(adjustable) if anchor is not None: self.set_anchor(anchor) + self.stale = True def get_adjustable(self): return self._adjustable @@ -1098,6 +1103,7 @@ def set_adjustable(self, adjustable): self._adjustable = adjustable else: raise ValueError('argument must be "box", or "datalim"') + self.stale = True def get_anchor(self): return self._anchor @@ -1127,6 +1133,7 @@ def set_anchor(self, anchor): else: raise ValueError('argument must be among %s' % ', '.join(six.iterkeys(mtransforms.Bbox.coefs))) + self.stale = True def get_data_ratio(self): """ @@ -1788,6 +1795,7 @@ def set_xmargin(self, m): if m < 0 or m > 1: raise ValueError("margin must be in range 0 to 1") self._xmargin = m + self.stale = True def set_ymargin(self, m): """ @@ -1801,6 +1809,7 @@ def set_ymargin(self, m): if m < 0 or m > 1: raise ValueError("margin must be in range 0 to 1") self._ymargin = m + self.stale = True def margins(self, *args, **kw): """ @@ -1866,6 +1875,7 @@ def set_rasterization_zorder(self, z): zorder. """ self._rasterization_zorder = z + self.stale = True def get_rasterization_zorder(self): """ @@ -2108,6 +2118,7 @@ def draw(self, renderer=None, inframe=False): renderer.close_group('axes') self._cachedRenderer = renderer + self.stale = False def draw_artist(self, a): """ @@ -2151,6 +2162,7 @@ def set_frame_on(self, b): ACCEPTS: [ *True* | *False* ] """ self._frameon = b + self.stale = True def get_axisbelow(self): """ @@ -2166,6 +2178,7 @@ def set_axisbelow(self, b): ACCEPTS: [ *True* | *False* ] """ self._axisbelow = b + self.stale = True @docstring.dedent_interpd def grid(self, b=None, which='major', axis='both', **kwargs): @@ -2413,10 +2426,12 @@ def tick_params(self, axis='both', **kwargs): def set_axis_off(self): """turn off the axis""" self.axison = False + self.stale = True def set_axis_on(self): """turn on the axis""" self.axison = True + self.stale = True def get_axis_bgcolor(self): """Return the axis background color""" @@ -2432,7 +2447,7 @@ def set_axis_bgcolor(self, color): self._axisbg = color self.patch.set_facecolor(color) - + self.stale = True # data limits, ticks, tick labels, and formatting def invert_xaxis(self): @@ -2580,7 +2595,7 @@ def set_xlim(self, left=None, right=None, emit=True, auto=False, **kw): if (other.figure != self.figure and other.figure.canvas is not None): other.figure.canvas.draw_idle() - + self.stale = True return left, right def get_xscale(self): @@ -2609,6 +2624,7 @@ def set_xscale(self, value, **kwargs): self.xaxis._set_scale(value, **kwargs) self.autoscale_view(scaley=False) self._update_transScale() + self.stale = True def get_xticks(self, minor=False): """Return the x ticks as a list of locations""" @@ -2620,7 +2636,9 @@ def set_xticks(self, ticks, minor=False): ACCEPTS: sequence of floats """ - return self.xaxis.set_ticks(ticks, minor=minor) + ret = self.xaxis.set_ticks(ticks, minor=minor) + self.stale = True + return ret def get_xmajorticklabels(self): """ @@ -2679,8 +2697,10 @@ def set_xticklabels(self, labels, fontdict=None, minor=False, **kwargs): ACCEPTS: sequence of strings """ - return self.xaxis.set_ticklabels(labels, fontdict, - minor=minor, **kwargs) + ret = self.xaxis.set_ticklabels(labels, fontdict, + minor=minor, **kwargs) + self.stale = True + return ret def invert_yaxis(self): """ @@ -2828,7 +2848,7 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False, **kw): if (other.figure != self.figure and other.figure.canvas is not None): other.figure.canvas.draw_idle() - + self.stale = True return bottom, top def get_yscale(self): @@ -2857,6 +2877,7 @@ def set_yscale(self, value, **kwargs): self.yaxis._set_scale(value, **kwargs) self.autoscale_view(scalex=False) self._update_transScale() + self.stale = True def get_yticks(self, minor=False): """Return the y ticks as a list of locations""" @@ -2873,7 +2894,8 @@ def set_yticks(self, ticks, minor=False): *minor*: [ *False* | *True* ] Sets the minor ticks if *True* """ - return self.yaxis.set_ticks(ticks, minor=minor) + ret = self.yaxis.set_ticks(ticks, minor=minor) + return ret def get_ymajorticklabels(self): """ diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 4c76686c88b7..ba5e4e86497a 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -177,6 +177,8 @@ def get_children(self): def set_clip_path(self, clippath, transform=None): artist.Artist.set_clip_path(self, clippath, transform) self.gridline.set_clip_path(clippath, transform) + self.stale = True + set_clip_path.__doc__ = artist.Artist.set_clip_path.__doc__ def get_pad_pixels(self): @@ -200,6 +202,7 @@ def set_pad(self, val): ACCEPTS: float """ self._apply_params(pad=val) + self.stale = True def get_pad(self): 'Get the value of the tick label pad in points' @@ -251,6 +254,7 @@ def draw(self, renderer): self.label2.draw(renderer) renderer.close_group(self.__name__) + self.stale = False def set_label1(self, s): """ @@ -259,6 +263,8 @@ def set_label1(self, s): ACCEPTS: str """ self.label1.set_text(s) + self.stale = True + set_label = set_label1 def set_label2(self, s): @@ -268,6 +274,7 @@ def set_label2(self, s): ACCEPTS: str """ self.label2.set_text(s) + self.stale = True def _set_artist_props(self, a): a.set_figure(self.figure) @@ -349,6 +356,7 @@ def apply_tickdir(self, tickdir): else: self._tickmarkers = (mlines.TICKDOWN, mlines.TICKUP) self._pad = self._base_pad + self._size + self.stale = True def _get_text1(self): 'Get the default Text instance' @@ -450,6 +458,7 @@ def update_position(self, loc): self.gridline._invalid = True self._loc = loc + self.stale = True def get_view_interval(self): 'return the Interval instance for this axis view limits' @@ -483,6 +492,7 @@ def apply_tickdir(self, tickdir): else: self._tickmarkers = (mlines.TICKLEFT, mlines.TICKRIGHT) self._pad = self._base_pad + self._size + self.stale = True # how far from the y axis line the right of the ticklabel are def _get_text1(self): @@ -584,6 +594,7 @@ def update_position(self, loc): self.gridline._invalid = True self._loc = loc + self.stale = True def get_view_interval(self): 'return the Interval instance for this axis view limits' @@ -668,6 +679,7 @@ def set_label_coords(self, x, y, transform=None): self.label.set_transform(transform) self.label.set_position((x, y)) + self.stale = True def get_transform(self): return self._scale.get_transform() @@ -732,6 +744,7 @@ def cla(self): self.converter = None self.units = None self.set_units(None) + self.stale = True def reset_ticks(self): # build a few default ticks; grow as necessary later; only @@ -771,6 +784,7 @@ def set_tick_params(self, which='major', reset=False, **kw): if which == 'minor' or which == 'both': for tick in self.minorTicks: tick._apply_params(**self._minor_tick_kw) + self.stale = True @staticmethod def _translate_tick_kw(kw, to_init_kw=True): @@ -835,6 +849,7 @@ def set_clip_path(self, clippath, transform=None): artist.Artist.set_clip_path(self, clippath, transform) for child in self.majorTicks + self.minorTicks: child.set_clip_path(clippath, transform) + self.stale = True def get_view_interval(self): 'return the Interval instance for this axis view limits' @@ -917,6 +932,7 @@ def get_ticklabel_extents(self, renderer): def set_smart_bounds(self, value): """set the axis to have smart bounds""" self._smart_bounds = value + self.stale = True def get_smart_bounds(self): """get whether the axis has smart bounds""" @@ -1114,6 +1130,7 @@ def draw(self, renderer, *args, **kwargs): mpatches.bbox_artist(self.label, renderer) renderer.close_group(__name__) + self.stale = False def _get_label(self): raise NotImplementedError('Derived must override') @@ -1354,6 +1371,7 @@ def grid(self, b=None, which='major', **kwargs): if len(kwargs): tick.gridline.update(kwargs) self._major_tick_kw['gridOn'] = self._gridOnMajor + self.stale = True def update_units(self, data): """ @@ -1374,6 +1392,7 @@ def update_units(self, data): if neednew: self._update_axisinfo() + self.stale = True return True def _update_axisinfo(self): @@ -1441,6 +1460,7 @@ def set_units(self, u): self._update_axisinfo() self.callbacks.process('units') self.callbacks.process('units finalize') + self.stale = True def get_units(self): 'return the units for axis' @@ -1456,6 +1476,7 @@ def set_label_text(self, label, fontdict=None, **kwargs): if fontdict is not None: self.label.update(fontdict) self.label.update(kwargs) + self.stale = True return self.label def set_major_formatter(self, formatter): @@ -1467,6 +1488,7 @@ def set_major_formatter(self, formatter): self.isDefault_majfmt = False self.major.formatter = formatter formatter.set_axis(self) + self.stale = True def set_minor_formatter(self, formatter): """ @@ -1477,6 +1499,7 @@ def set_minor_formatter(self, formatter): self.isDefault_minfmt = False self.minor.formatter = formatter formatter.set_axis(self) + self.stale = True def set_major_locator(self, locator): """ @@ -1487,6 +1510,7 @@ def set_major_locator(self, locator): self.isDefault_majloc = False self.major.locator = locator locator.set_axis(self) + self.stale = True def set_minor_locator(self, locator): """ @@ -1497,6 +1521,7 @@ def set_minor_locator(self, locator): self.isDefault_minloc = False self.minor.locator = locator locator.set_axis(self) + self.stale = True def set_pickradius(self, pickradius): """ @@ -1556,6 +1581,7 @@ def set_ticklabels(self, ticklabels, *args, **kwargs): if tick.label2On: ret.append(tick.label2) + self.stale = True return ret def set_ticks(self, ticks, minor=False): @@ -1736,6 +1762,7 @@ def set_label_position(self, position): msg = "Position accepts only [ 'top' | 'bottom' ]" raise ValueError(msg) self.label_position = position + self.stale = True def _update_label_position(self, bboxes, bboxes2): """ @@ -1840,6 +1867,7 @@ def set_ticks_position(self, position): bottom=True, labelbottom=True) else: raise ValueError("invalid position: %s" % position) + self.stale = True def tick_top(self): 'use ticks only on top' @@ -1918,6 +1946,7 @@ def set_data_interval(self, vmin, vmax, ignore=False): else: Vmin, Vmax = self.get_data_interval() self.axes.dataLim.intervalx = min(vmin, Vmin), max(vmax, Vmax) + self.stale = True def set_default_intervals(self): 'set the default limits for the axis interval if they are not mutated' @@ -1935,6 +1964,7 @@ def set_default_intervals(self): self.axes.dataLim.intervalx = xmin, xmax if not viewMutated: self.axes.viewLim.intervalx = xmin, xmax + self.stale = True class YAxis(Axis): @@ -2054,6 +2084,7 @@ def set_label_position(self, position): msg = "Position accepts only [ 'left' | 'right' ]" raise ValueError(msg) self.label_position = position + self.stale = True def _update_label_position(self, bboxes, bboxes2): """ @@ -2116,6 +2147,7 @@ def set_offset_position(self, position): self.offsetText.set_ha(position) self.offsetText.set_position((x, y)) + self.stale = True def get_text_widths(self, renderer): bbox, bbox2 = self.get_ticklabel_extents(renderer) @@ -2165,6 +2197,7 @@ def set_ticks_position(self, position): left=True, labelleft=True) else: raise ValueError("invalid position: %s" % position) + self.stale = True def tick_right(self): 'use ticks only on right' @@ -2228,6 +2261,7 @@ def set_view_interval(self, vmin, vmax, ignore=False): else: self.axes.viewLim.intervaly = (max(vmin, vmax, Vmin), min(vmin, vmax, Vmax)) + self.stale = True def get_minpos(self): return self.axes.dataLim.minposy @@ -2243,6 +2277,7 @@ def set_data_interval(self, vmin, vmax, ignore=False): else: Vmin, Vmax = self.get_data_interval() self.axes.dataLim.intervaly = min(vmin, Vmin), max(vmax, Vmax) + self.stale = True def set_default_intervals(self): 'set the default limits for the axis interval if they are not mutated' @@ -2260,3 +2295,4 @@ def set_default_intervals(self): self.axes.dataLim.intervaly = ymin, ymax if not viewMutated: self.axes.viewLim.intervaly = ymin, ymax + self.stale = True diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index d2f7a7081666..aa00ca9c2c07 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -321,6 +321,7 @@ def draw(self, renderer): gc.restore() renderer.close_group(self.__class__.__name__) + self.stale = False def set_pickradius(self, pr): self._pickradius = pr @@ -370,6 +371,7 @@ def set_urls(self, urls): self._urls = [None, ] else: self._urls = urls + self.stale = True def get_urls(self): return self._urls @@ -405,6 +407,7 @@ def set_hatch(self, hatch): ACCEPTS: [ '/' | '\\\\' | '|' | '-' | '+' | 'x' | 'o' | 'O' | '.' | '*' ] """ self._hatch = hatch + self.stale = True def get_hatch(self): 'Return the current hatching pattern' @@ -424,6 +427,7 @@ def set_offsets(self, offsets): self._offsets = offsets else: self._uniform_offsets = offsets + self.stale = True def get_offsets(self): """ @@ -446,6 +450,7 @@ def set_offset_position(self, offset_position): if offset_position not in ('screen', 'data'): raise ValueError("offset_position must be 'screen' or 'data'") self._offset_position = offset_position + self.stale = True def get_offset_position(self): """ @@ -469,6 +474,7 @@ def set_linewidth(self, lw): if lw is None: lw = mpl.rcParams['patch.linewidth'] self._linewidths = self._get_value(lw) + self.stale = True def set_linewidths(self, lw): """alias for set_linewidth""" @@ -540,6 +546,7 @@ def set_linestyle(self, ls): except ValueError: raise ValueError('Do not know how to convert %s to dashes' % ls) self._linestyles = dashes + self.stale = True def set_linestyles(self, ls): """alias for set_linestyle""" @@ -558,6 +565,7 @@ def set_antialiased(self, aa): if aa is None: aa = mpl.rcParams['patch.antialiased'] self._antialiaseds = self._get_bool(aa) + self.stale = True def set_antialiaseds(self, aa): """alias for set_antialiased""" @@ -598,6 +606,7 @@ def set_facecolor(self, c): c = mpl.rcParams['patch.facecolor'] self._facecolors_original = c self._facecolors = mcolors.colorConverter.to_rgba_array(c, self._alpha) + self.stale = True def set_facecolors(self, c): """alias for set_facecolor""" @@ -644,6 +653,7 @@ def set_edgecolor(self, c): c = mpl.rcParams['patch.edgecolor'] self._edgecolors_original = c self._edgecolors = mcolors.colorConverter.to_rgba_array(c, self._alpha) + self.stale = True def set_edgecolors(self, c): """alias for set_edgecolor""" @@ -697,6 +707,7 @@ def update_scalarmappable(self): self._facecolors = self.to_rgba(self._A, self._alpha) elif self._is_stroked: self._edgecolors = self.to_rgba(self._A, self._alpha) + self.stale = True def get_fill(self): 'return whether fill is set' @@ -721,7 +732,7 @@ def update_from(self, other): self.norm = other.norm self.cmap = other.cmap # self.update_dict = other.update_dict # do we need to copy this? -JJL - + self.stale = True # these are not available for the object inspector until after the # class is built so we define an initial set here for the init @@ -790,6 +801,7 @@ def set_sizes(self, sizes, dpi=72.0): self._transforms[:, 0, 0] = scale self._transforms[:, 1, 1] = scale self._transforms[:, 2, 2] = 1.0 + self.stale = True @allow_rasterization def draw(self, renderer): @@ -813,9 +825,11 @@ def __init__(self, paths, sizes=None, **kwargs): Collection.__init__(self, **kwargs) self.set_paths(paths) self.set_sizes(sizes) + self.stale = True def set_paths(self, paths): self._paths = paths + self.stale = True def get_paths(self): return self._paths @@ -845,6 +859,7 @@ def __init__(self, verts, sizes=None, closed=True, **kwargs): Collection.__init__(self, **kwargs) self.set_sizes(sizes) self.set_verts(verts, closed) + self.stale = True def set_verts(self, verts, closed=True): '''This allows one to delay initialization of the vertices.''' @@ -869,6 +884,7 @@ def set_verts(self, verts, closed=True): self._paths.append(mpath.Path(xy)) else: self._paths = [mpath.Path(xy) for xy in verts] + self.stale = True set_paths = set_verts @@ -1011,7 +1027,6 @@ class LineCollection(Collection): number of segments. """ - def __init__(self, segments, # Can be None. linewidths=None, colors=None, @@ -1088,7 +1103,6 @@ def __init__(self, segments, # Can be None. linewidths = (mpl.rcParams['lines.linewidth'],) if antialiaseds is None: antialiaseds = (mpl.rcParams['lines.antialiased'],) - self.set_linestyles(linestyles) colors = mcolors.colorConverter.to_rgba_array(colors) @@ -1115,13 +1129,15 @@ def set_segments(self, segments): _segments = [] for seg in segments: - if not np.ma.isMaskedArray(seg): seg = np.asarray(seg, np.float_) _segments.append(seg) + if self._uniform_offsets is not None: _segments = self._add_offsets(_segments) - self._paths = [mpath.Path(seg) for seg in _segments] + + self._paths = [mpath.Path(_seg) for _seg in _segments] + self.stale = True set_verts = set_segments # for compatibility with PolyCollection set_paths = set_segments @@ -1159,9 +1175,11 @@ def set_color(self, c): ACCEPTS: matplotlib color arg or sequence of rgba tuples """ self.set_edgecolor(c) + self.stale = True def get_color(self): return self._edgecolors + get_colors = get_color # for compatibility with old versions @@ -1336,6 +1354,7 @@ def switch_orientation(self): segments[i] = np.fliplr(segment) self.set_segments(segments) self._is_horizontal = not self.is_horizontal() + self.stale = True def set_orientation(self, orientation=None): ''' @@ -1709,6 +1728,7 @@ def get_paths(self): def set_paths(self): self._paths = self.convert_mesh_to_paths( self._meshWidth, self._meshHeight, self._coordinates) + self.stale = True def get_datalim(self, transData): return (self.get_transform() - transData).transform_bbox(self._bbox) @@ -1832,6 +1852,7 @@ def draw(self, renderer): self._antialiased, self.get_edgecolors()) gc.restore() renderer.close_group(self.__class__.__name__) + self.stale = False patchstr = artist.kwdoc(Collection) diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index c4caae295681..3334b953f923 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -392,6 +392,7 @@ def set_ticks(self, ticks, update_ticks=True): if update_ticks: self.update_ticks() + self.stale = True def set_ticklabels(self, ticklabels, update_ticks=True): """ @@ -405,6 +406,7 @@ def set_ticklabels(self, ticklabels, update_ticks=True): self.update_ticks() else: warnings.warn("set_ticks() must have been called.") + self.stale = True def _config_axes(self, X, Y): ''' @@ -444,6 +446,7 @@ def _set_label(self): self.ax.set_ylabel(self._label, **self._labelkw) else: self.ax.set_xlabel(self._label, **self._labelkw) + self.stale = True def set_label(self, label, **kw): ''' @@ -548,6 +551,7 @@ def add_lines(self, levels, colors, linewidths, erase=True): self.lines.append(col) col.set_color(colors) self.ax.add_collection(col) + self.stale = True def _ticker(self): ''' @@ -942,6 +946,7 @@ def update_normal(self, mappable): CS = self.mappable if not CS.filled: self.add_lines(CS) + self.stale = True def update_bruteforce(self, mappable): ''' diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 9d6a73f12c8f..970a4b5df8f3 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -964,7 +964,7 @@ def __init__(self, ax, *args, **kwargs): segs, antialiaseds=aa, linewidths=width, - linestyle=[lstyle], + linestyles=[lstyle], alpha=self.alpha, transform=self.get_transform(), zorder=zorder) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 78ce8d98e18d..4012e6ace1f3 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -426,6 +426,7 @@ def set_tight_layout(self, tight): tight = rcParams['figure.autolayout'] self._tight = bool(tight) self._tight_parameters = tight if isinstance(tight, dict) else {} + self.stale = True def autofmt_xdate(self, bottom=0.2, rotation=30, ha='right'): """ @@ -465,6 +466,7 @@ def autofmt_xdate(self, bottom=0.2, rotation=30, ha='right'): if allsubplots: self.subplots_adjust(bottom=bottom) + self.stale = True def get_children(self): 'get a list of artists contained in the figure' @@ -541,6 +543,7 @@ def suptitle(self, t, **kwargs): sup.remove() else: self._suptitle = sup + self.stale = True return self._suptitle def set_canvas(self, canvas): @@ -550,6 +553,7 @@ def set_canvas(self, canvas): ACCEPTS: a FigureCanvas instance """ self.canvas = canvas + self.stale = True def hold(self, b=None): """ @@ -652,6 +656,7 @@ def figimage(self, X, im.set_clim(vmin, vmax) self.images.append(im) im._remove_method = lambda h: self.images.remove(h) + self.stale = True return im def set_size_inches(self, *args, **kwargs): @@ -693,6 +698,7 @@ def set_size_inches(self, *args, **kwargs): manager = getattr(self.canvas, 'manager', None) if manager is not None: manager.resize(int(canvasw), int(canvash)) + self.stale = True def get_size_inches(self): """ @@ -758,6 +764,7 @@ def set_dpi(self, val): ACCEPTS: float """ self.dpi = val + self.stale = True def set_figwidth(self, val): """ @@ -766,6 +773,7 @@ def set_figwidth(self, val): ACCEPTS: float """ self.bbox_inches.x1 = val + self.stale = True def set_figheight(self, val): """ @@ -774,6 +782,7 @@ def set_figheight(self, val): ACCEPTS: float """ self.bbox_inches.y1 = val + self.stale = True def set_frameon(self, b): """ @@ -782,12 +791,14 @@ def set_frameon(self, b): ACCEPTS: boolean """ self.frameon = b + self.stale = True def delaxes(self, a): 'remove a from the figure and update the current axes' self._axstack.remove(a) for func in self._axobservers: func(self) + self.stale = True def _make_key(self, *args, **kwargs): 'make a hashable key out of args and kwargs' @@ -900,6 +911,7 @@ def add_axes(self, *args, **kwargs): self._axstack.add(key, a) self.sca(a) + self.stale = True return a @docstring.dedent_interpd @@ -987,6 +999,7 @@ def add_subplot(self, *args, **kwargs): self._axstack.add(key, a) self.sca(a) + self.stale = True return a def clf(self, keep_observers=False): @@ -1016,6 +1029,7 @@ def clf(self, keep_observers=False): if not keep_observers: self._axobservers = [] self._suptitle = None + self.stale = True def clear(self): """ @@ -1029,6 +1043,7 @@ def draw(self, renderer): Render the figure using :class:`matplotlib.backend_bases.RendererBase` instance *renderer*. """ + # draw the figure bounding box, perhaps none for white figure if not self.get_visible(): return @@ -1105,11 +1120,12 @@ def draw_composite(): dsu.sort(key=itemgetter(0)) for zorder, a, func, args in dsu: func(*args) + a.stale = False renderer.close_group('figure') self._cachedRenderer = renderer - + self.stale = False self.canvas.draw_event(renderer) def draw_artist(self, a): @@ -1225,6 +1241,7 @@ def legend(self, handles, labels, *args, **kwargs): l = Legend(self, handles, labels, *args, **kwargs) self.legends.append(l) l._remove_method = lambda h: self.legends.remove(h) + self.stale = True return l @docstring.dedent_interpd @@ -1252,6 +1269,7 @@ def text(self, x, y, s, *args, **kwargs): self._set_artist_props(t) self.texts.append(t) t._remove_method = lambda h: self.texts.remove(h) + self.stale = True return t def _set_artist_props(self, a): @@ -1397,6 +1415,7 @@ def make_active(event): self.number = num plt.draw_if_interactive() + self.stale = True def add_axobserver(self, func): 'whenever the axes state change, ``func(self)`` will be called' @@ -1539,6 +1558,7 @@ def colorbar(self, mappable, cax=None, ax=None, use_gridspec=True, **kw): cb = cbar.colorbar_factory(cax, mappable, **kw) self.sca(current_ax) + self.stale = True return cb def subplots_adjust(self, *args, **kwargs): @@ -1567,6 +1587,7 @@ def subplots_adjust(self, *args, **kwargs): else: ax.update_params() ax.set_position(ax.figbox) + self.stale = True def ginput(self, n=1, timeout=30, show_clicks=True, mouse_add=1, mouse_pop=3, mouse_stop=2): diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py index d45726fccc15..d45782af5fa0 100644 --- a/lib/matplotlib/image.py +++ b/lib/matplotlib/image.py @@ -375,6 +375,7 @@ def draw(self, renderer, *args, **kwargs): im._gid = self.get_gid() renderer.draw_image(gc, l, b, im) gc.restore() + self.stale = False def contains(self, mouseevent): """ @@ -437,6 +438,7 @@ def set_data(self, A): self._rgbacache = None self._oldxslice = None self._oldyslice = None + self.stale = True def set_array(self, A): """ @@ -481,6 +483,7 @@ def set_interpolation(self, s): if s not in self._interpd: raise ValueError('Illegal interpolation string') self._interpolation = s + self.stale = True def set_resample(self, v): """ @@ -491,6 +494,7 @@ def set_resample(self, v): if v is None: v = rcParams['image.resample'] self._resample = v + self.stale = True def get_resample(self): """Return the image resample boolean""" @@ -508,6 +512,8 @@ def set_filternorm(self, filternorm): else: self._filternorm = 0 + self.stale = True + def get_filternorm(self): """Return the filternorm setting""" return self._filternorm @@ -523,6 +529,7 @@ def set_filterrad(self, filterrad): if r <= 0: raise ValueError("The filter radius must be a positive number") self._filterrad = r + self.stale = True def get_filterrad(self): """return the filterrad setting""" @@ -671,6 +678,7 @@ def set_extent(self, extent): self.axes.set_xlim((xmin, xmax), auto=None) if self.axes._autoscaleYon: self.axes.set_ylim((ymin, ymax), auto=None) + self.stale = True def get_extent(self): """Get the image extent: left, right, bottom, top""" @@ -792,6 +800,7 @@ def set_data(self, x, y, A): # accessed - JDH 3/3/2010 self._oldxslice = None self._oldyslice = None + self.stale = True def set_array(self, *args): raise NotImplementedError('Method not supported') @@ -904,6 +913,7 @@ def draw(self, renderer, *args, **kwargs): round(self.axes.bbox.ymin), im) gc.restore() + self.stale = False def set_data(self, x, y, A): A = cbook.safe_masked_invalid(A) @@ -937,6 +947,7 @@ def set_data(self, x, y, A): self._Ax = x self._Ay = y self._rgbacache = None + self.stale = True def set_array(self, *args): raise NotImplementedError('Method not supported') @@ -1060,6 +1071,7 @@ def draw(self, renderer, *args, **kwargs): gc.set_alpha(self.get_alpha()) renderer.draw_image(gc, round(self.ox), round(self.oy), im) gc.restore() + self.stale = False def write_png(self, fname): """Write the image to png file with fname""" @@ -1211,6 +1223,7 @@ def draw(self, renderer, *args, **kwargs): b = np.min([y0, y1]) renderer.draw_image(gc, round(l), round(b), im) gc.restore() + self.stale = True def imread(fname, format=None): diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 243064a492ec..98e107849535 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -112,7 +112,7 @@ class Legend(Artist): The location codes are:: - 'best' : 0, (only implemented for axis legends) + 'best' : 0, (only implemented for axes legends) 'upper right' : 1, 'upper left' : 2, 'lower left' : 3, @@ -128,7 +128,7 @@ class Legend(Artist): respect its parent. """ - codes = {'best': 0, # only implemented for axis legends + codes = {'best': 0, # only implemented for axes legends 'upper right': 1, 'upper left': 2, 'lower left': 3, @@ -421,6 +421,7 @@ def _set_loc(self, loc): self._legend_box.set_offset(_findoffset) self._loc_real = loc + self.stale = True def _get_loc(self): return self._loc_real @@ -481,6 +482,7 @@ def draw(self, renderer): self._legend_box.draw(renderer) renderer.close_group('legend') + self.stale = False def _approx_text_height(self, renderer=None): """ @@ -807,6 +809,7 @@ def set_title(self, title, prop=None): self._legend_title_box.set_visible(True) else: self._legend_title_box.set_visible(False) + self.stale = True def get_title(self): 'return Text instance for the legend title' @@ -829,6 +832,7 @@ def set_frame_on(self, b): ACCEPTS: [ *True* | *False* ] """ self._drawFrame = b + self.stale = True def get_bbox_to_anchor(self): """ @@ -869,6 +873,7 @@ def set_bbox_to_anchor(self, bbox, transform=None): self._bbox_to_anchor = TransformedBbox(self._bbox_to_anchor, transform) + self.stale = True def _get_anchored_bbox(self, loc, bbox, parentbbox, renderer): """ diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 130f820e68bc..0b0216f77b68 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -314,26 +314,47 @@ def __init__(self, xdata, ydata, if drawstyle is None: drawstyle = 'default' + self._dashcapstyle = None + self._dashjoinstyle = None + self._solidjoinstyle = None + self._solidcapstyle = None self.set_dash_capstyle(dash_capstyle) self.set_dash_joinstyle(dash_joinstyle) self.set_solid_capstyle(solid_capstyle) self.set_solid_joinstyle(solid_joinstyle) + self._linestyles = None + self._drawstyle = None + self._linewidth = None self.set_linestyle(linestyle) self.set_drawstyle(drawstyle) self.set_linewidth(linewidth) + + self._color = None self.set_color(color) self._marker = MarkerStyle() self.set_marker(marker) + + self._markevery = None + self._markersize = None + self._antialiased = None + self.set_markevery(markevery) self.set_antialiased(antialiased) self.set_markersize(markersize) + self._dashSeq = None + self._markeredgecolor = None + self._markeredgewidth = None + self._markerfacecolor = None + self._markerfacecoloralt = None + self.set_markerfacecolor(markerfacecolor) self.set_markerfacecoloralt(markerfacecoloralt) self.set_markeredgecolor(markeredgecolor) self.set_markeredgewidth(markeredgewidth) + self.set_fillstyle(fillstyle) self.verticalOffset = None @@ -457,6 +478,7 @@ def set_fillstyle(self, fs): ACCEPTS: ['full' | 'left' | 'right' | 'bottom' | 'top' | 'none'] """ self._marker.set_fillstyle(fs) + self.stale = True def set_markevery(self, every): """Set the markevery property to subsample the plot when using markers. @@ -509,7 +531,8 @@ def set_markevery(self, every): axes-bounding-box-diagonal regardless of the actual axes data limits. """ - + if self._markevery != every: + self.stale = True self._markevery = every def get_markevery(self): @@ -653,6 +676,7 @@ def set_transform(self, t): Artist.set_transform(self, t) self._invalidx = True self._invalidy = True + self.stale = True def _is_sorted(self, x): """return true if x is sorted""" @@ -776,6 +800,7 @@ def draw(self, renderer): gc.restore() renderer.close_group('line2d') + self.stale = False def get_antialiased(self): return self._antialiased @@ -890,6 +915,8 @@ def set_antialiased(self, b): ACCEPTS: [True | False] """ + if self._antialiased != b: + self.stale = True self._antialiased = b def set_color(self, color): @@ -898,6 +925,8 @@ def set_color(self, color): ACCEPTS: any matplotlib color """ + if color != self._color: + self.stale = True self._color = color def set_drawstyle(self, drawstyle): @@ -911,6 +940,8 @@ def set_drawstyle(self, drawstyle): ACCEPTS: ['default' | 'steps' | 'steps-pre' | 'steps-mid' | 'steps-post'] """ + if self._drawstyle != drawstyle: + self.stale = True self._drawstyle = drawstyle def set_linewidth(self, w): @@ -919,7 +950,10 @@ def set_linewidth(self, w): ACCEPTS: float value in points """ - self._linewidth = float(w) + w = float(w) + if self._linewidth != w: + self.stale = True + self._linewidth = w def set_linestyle(self, ls): """ @@ -1012,6 +1046,7 @@ def set_marker(self, marker): """ self._marker.set_marker(marker) + self.stale = True def set_markeredgecolor(self, ec): """ @@ -1021,6 +1056,8 @@ def set_markeredgecolor(self, ec): """ if ec is None: ec = 'auto' + if self._markeredgecolor != ec: + self.stale = True self._markeredgecolor = ec def set_markeredgewidth(self, ew): @@ -1031,6 +1068,8 @@ def set_markeredgewidth(self, ew): """ if ew is None: ew = rcParams['lines.markeredgewidth'] + if self._markeredgewidth != ew: + self.stale = True self._markeredgewidth = ew def set_markerfacecolor(self, fc): @@ -1041,7 +1080,8 @@ def set_markerfacecolor(self, fc): """ if fc is None: fc = 'auto' - + if self._markerfacecolor != fc: + self.stale = True self._markerfacecolor = fc def set_markerfacecoloralt(self, fc): @@ -1052,7 +1092,8 @@ def set_markerfacecoloralt(self, fc): """ if fc is None: fc = 'auto' - + if self._markerfacecoloralt != fc: + self.stale = True self._markerfacecoloralt = fc def set_markersize(self, sz): @@ -1061,7 +1102,10 @@ def set_markersize(self, sz): ACCEPTS: float """ - self._markersize = float(sz) + sz = float(sz) + if self._markersize != sz: + self.stale = True + self._markersize = sz def set_xdata(self, x): """ @@ -1071,6 +1115,7 @@ def set_xdata(self, x): """ self._xorig = x self._invalidx = True + self.stale = True def set_ydata(self, y): """ @@ -1080,6 +1125,7 @@ def set_ydata(self, y): """ self._yorig = y self._invalidy = True + self.stale = True def set_dashes(self, seq): """ @@ -1093,6 +1139,8 @@ def set_dashes(self, seq): self.set_linestyle('-') else: self.set_linestyle('--') + if self._dashSeq != seq: + self.stale = True self._dashSeq = seq # TODO: offset ignored for now def _draw_lines(self, renderer, gc, path, trans): @@ -1276,6 +1324,8 @@ def set_dash_joinstyle(self, s): if s not in self.validJoin: raise ValueError('set_dash_joinstyle passed "%s";\n' % (s,) + 'valid joinstyles are %s' % (self.validJoin,)) + if self._dashjoinstyle != s: + self.stale = True self._dashjoinstyle = s def set_solid_joinstyle(self, s): @@ -1287,6 +1337,9 @@ def set_solid_joinstyle(self, s): if s not in self.validJoin: raise ValueError('set_solid_joinstyle passed "%s";\n' % (s,) + 'valid joinstyles are %s' % (self.validJoin,)) + + if self._solidjoinstyle != s: + self.stale = True self._solidjoinstyle = s def get_dash_joinstyle(self): @@ -1311,7 +1364,8 @@ def set_dash_capstyle(self, s): if s not in self.validCap: raise ValueError('set_dash_capstyle passed "%s";\n' % (s,) + 'valid capstyles are %s' % (self.validCap,)) - + if self._dashcapstyle != s: + self.stale = True self._dashcapstyle = s def set_solid_capstyle(self, s): @@ -1324,7 +1378,8 @@ def set_solid_capstyle(self, s): if s not in self.validCap: raise ValueError('set_solid_capstyle passed "%s";\n' % (s,) + 'valid capstyles are %s' % (self.validCap,)) - + if self._solidcapstyle != s: + self.stale = True self._solidcapstyle = s def get_dash_capstyle(self): diff --git a/lib/matplotlib/offsetbox.py b/lib/matplotlib/offsetbox.py index b014950a4930..47a402911b37 100644 --- a/lib/matplotlib/offsetbox.py +++ b/lib/matplotlib/offsetbox.py @@ -174,6 +174,7 @@ def __setstate__(self, state): from .cbook import _InstanceMethodPickler if isinstance(self._offset, _InstanceMethodPickler): self._offset = self._offset.get_instancemethod() + self.stale = True def set_figure(self, fig): """ @@ -199,6 +200,7 @@ def set_offset(self, xy): accepts x, y, tuple, or a callable object. """ self._offset = xy + self.stale = True def get_offset(self, width, height, xdescent, ydescent, renderer): """ @@ -218,6 +220,7 @@ def set_width(self, width): accepts float """ self.width = width + self.stale = True def set_height(self, height): """ @@ -226,6 +229,7 @@ def set_height(self, height): accepts float """ self.height = height + self.stale = True def get_visible_children(self): """ @@ -273,6 +277,7 @@ def draw(self, renderer): c.draw(renderer) bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) + self.stale = False class PackerBase(OffsetBox): @@ -541,6 +546,7 @@ def draw(self, renderer): c.draw(renderer) #bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) + self.stale = False def update_frame(self, bbox, fontsize=None): self.patch.set_bounds(bbox.x0, bbox.y0, @@ -548,6 +554,7 @@ def update_frame(self, bbox, fontsize=None): if fontsize: self.patch.set_mutation_scale(fontsize) + self.stale = True def draw_frame(self, renderer): # update the location and size of the legend @@ -608,6 +615,7 @@ def set_offset(self, xy): self.offset_transform.clear() self.offset_transform.translate(xy[0], xy[1]) + self.stale = True def get_offset(self): """ @@ -652,6 +660,7 @@ def draw(self, renderer): c.draw(renderer) bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) + self.stale = False class TextArea(OffsetBox): @@ -707,6 +716,7 @@ def __init__(self, s, def set_text(self, s): "set text" self._text.set_text(s) + self.stale = True def get_text(self): "get text" @@ -721,6 +731,7 @@ def set_multilinebaseline(self, t): singleline text. """ self._multilinebaseline = t + self.stale = True def get_multilinebaseline(self): """ @@ -736,6 +747,7 @@ def set_minimumdescent(self, t): it has minimum descent of "p" """ self._minimumdescent = t + self.stale = True def get_minimumdescent(self): """ @@ -759,6 +771,7 @@ def set_offset(self, xy): self.offset_transform.clear() self.offset_transform.translate(xy[0], xy[1]) + self.stale = True def get_offset(self): """ @@ -814,6 +827,7 @@ def draw(self, renderer): self._text.draw(renderer) bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) + self.stale = False class AuxTransformBox(OffsetBox): @@ -848,6 +862,7 @@ def add_artist(self, a): 'Add any :class:`~matplotlib.artist.Artist` to the container box' self._children.append(a) a.set_transform(self.get_transform()) + self.stale = True def get_transform(self): """ @@ -874,6 +889,7 @@ def set_offset(self, xy): self.offset_transform.clear() self.offset_transform.translate(xy[0], xy[1]) + self.stale = True def get_offset(self): """ @@ -918,6 +934,7 @@ def draw(self, renderer): c.draw(renderer) bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) + self.stale = False class AnchoredOffsetbox(OffsetBox): @@ -997,6 +1014,7 @@ def __init__(self, loc, def set_child(self, child): "set the child to be anchored" self._child = child + self.stale = True def get_child(self): "return the child" @@ -1054,6 +1072,7 @@ def set_bbox_to_anchor(self, bbox, transform=None): self._bbox_to_anchor = Bbox.from_bounds(*bbox) self._bbox_to_anchor_transform = transform + self.stale = True def get_window_extent(self, renderer): ''' @@ -1087,11 +1106,11 @@ def _offset(w, h, xd, yd, renderer, fontsize=fontsize, self=self): self.set_offset(_offset) def update_frame(self, bbox, fontsize=None): - self.patch.set_bounds(bbox.x0, bbox.y0, - bbox.width, bbox.height) + self.patch.set_bounds(bbox.x0, bbox.y0, + bbox.width, bbox.height) - if fontsize: - self.patch.set_mutation_scale(fontsize) + if fontsize: + self.patch.set_mutation_scale(fontsize) def draw(self, renderer): "draw the artist" @@ -1114,6 +1133,7 @@ def draw(self, renderer): self.get_child().set_offset((px, py)) self.get_child().draw(renderer) + self.stale = False def _get_anchored_bbox(self, loc, bbox, parentbbox, borderpad): """ @@ -1205,6 +1225,7 @@ def __init__(self, arr, **kwargs ): + OffsetBox.__init__(self) self._dpi_cor = dpi_cor self.image = BboxImage(bbox=self.get_window_extent, @@ -1223,17 +1244,17 @@ def __init__(self, arr, self.set_zoom(zoom) self.set_data(arr) - OffsetBox.__init__(self) - def set_data(self, arr): self._data = np.asarray(arr) self.image.set_data(self._data) + self.stale = True def get_data(self): return self._data def set_zoom(self, zoom): self._zoom = zoom + self.stale = True def get_zoom(self): return self._zoom @@ -1290,7 +1311,8 @@ def draw(self, renderer): Draw the children """ self.image.draw(renderer) - #bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) + # bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) + self.stale = False class AnnotationBbox(martist.Artist, _AnnotationBase): @@ -1330,6 +1352,13 @@ def __init__(self, offsetbox, xy, other parameters are identical to that of Annotation. """ + + martist.Artist.__init__(self, **kwargs) + _AnnotationBase.__init__(self, + xy, + xycoords=xycoords, + annotation_clip=annotation_clip) + self.offsetbox = offsetbox self.arrowprops = arrowprops @@ -1354,13 +1383,6 @@ def __init__(self, offsetbox, xy, self._arrow_relpos = None self.arrow_patch = None - _AnnotationBase.__init__(self, - xy, - xycoords=xycoords, - annotation_clip=annotation_clip) - - martist.Artist.__init__(self, **kwargs) - #self._fw, self._fh = 0., 0. # for alignment self._box_alignment = box_alignment @@ -1383,6 +1405,7 @@ def xyann(self): @xyann.setter def xyann(self, xyann): self.xybox = xyann + self.stale = True @property def anncoords(self): @@ -1391,6 +1414,7 @@ def anncoords(self): @anncoords.setter def anncoords(self, coords): self.boxcoords = coords + self.stale = True def contains(self, event): t, tinfo = self.offsetbox.contains(event) @@ -1423,6 +1447,7 @@ def set_fontsize(self, s=None): s = rcParams["legend.fontsize"] self.prop = FontProperties(size=s) + self.stale = True def get_fontsize(self, s=None): """ @@ -1529,6 +1554,7 @@ def draw(self, renderer): self.patch.draw(renderer) self.offsetbox.draw(renderer) + self.stale = False class DraggableBase(object): diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index bf65b002ccb6..0e467fbd0ab1 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -257,6 +257,7 @@ def set_antialiased(self, aa): if aa is None: aa = mpl.rcParams['patch.antialiased'] self._antialiased = aa + self.stale = True def set_aa(self, aa): """alias for set_antialiased""" @@ -272,6 +273,7 @@ def set_edgecolor(self, color): color = mpl.rcParams['patch.edgecolor'] self._original_edgecolor = color self._edgecolor = colors.colorConverter.to_rgba(color, self._alpha) + self.stale = True def set_ec(self, color): """alias for set_edgecolor""" @@ -291,6 +293,7 @@ def set_facecolor(self, color): if not self._fill: self._facecolor = list(self._facecolor) self._facecolor[3] = 0 + self.stale = True def set_fc(self, color): """alias for set_facecolor""" @@ -325,6 +328,7 @@ def set_alpha(self, alpha): # using self._fill and self._alpha self.set_facecolor(self._original_facecolor) self.set_edgecolor(self._original_edgecolor) + self.stale = True def set_linewidth(self, w): """ @@ -334,8 +338,11 @@ def set_linewidth(self, w): """ if w is None: w = mpl.rcParams['patch.linewidth'] + self._linewidth = float(w) + self.stale = True + def set_lw(self, lw): """alias for set_linewidth""" return self.set_linewidth(lw) @@ -375,6 +382,7 @@ def set_linestyle(self, ls): ls = cbook.ls_mapper.get(ls, ls) self._linestyle = ls + self.stale = True def set_ls(self, ls): """alias for set_linestyle""" @@ -388,6 +396,7 @@ def set_fill(self, b): """ self._fill = bool(b) self.set_facecolor(self._original_facecolor) + self.stale = True def get_fill(self): 'return whether fill is set' @@ -409,6 +418,7 @@ def set_capstyle(self, s): raise ValueError('set_capstyle passed "%s";\n' % (s,) + 'valid capstyles are %s' % (self.validCap,)) self._capstyle = s + self.stale = True def get_capstyle(self): "Return the current capstyle" @@ -425,6 +435,7 @@ def set_joinstyle(self, s): raise ValueError('set_joinstyle passed "%s";\n' % (s,) + 'valid joinstyles are %s' % (self.validJoin,)) self._joinstyle = s + self.stale = True def get_joinstyle(self): "Return the current joinstyle" @@ -457,6 +468,7 @@ def set_hatch(self, hatch): ACCEPTS: ['/' | '\\\\' | '|' | '-' | '+' | 'x' | 'o' | 'O' | '.' | '*'] """ self._hatch = hatch + self.stale = True def get_hatch(self): 'Return the current hatching pattern' @@ -511,6 +523,7 @@ def draw(self, renderer): gc.restore() renderer.close_group('patch') + self.stale = False def get_path(self): """ @@ -693,6 +706,7 @@ def set_x(self, x): ACCEPTS: float """ self._x = x + self.stale = True def set_y(self, y): """ @@ -701,6 +715,7 @@ def set_y(self, y): ACCEPTS: float """ self._y = y + self.stale = True def set_xy(self, xy): """ @@ -709,6 +724,7 @@ def set_xy(self, xy): ACCEPTS: 2-item sequence """ self._x, self._y = xy + self.stale = True def set_width(self, w): """ @@ -717,6 +733,7 @@ def set_width(self, w): ACCEPTS: float """ self._width = w + self.stale = True def set_height(self, h): """ @@ -725,6 +742,7 @@ def set_height(self, h): ACCEPTS: float """ self._height = h + self.stale = True def set_bounds(self, *args): """ @@ -740,6 +758,7 @@ def set_bounds(self, *args): self._y = b self._width = w self._height = h + self.stale = True def get_bbox(self): return transforms.Bbox.from_bounds(self._x, self._y, @@ -924,6 +943,7 @@ def set_closed(self, closed): return self._closed = bool(closed) self.set_xy(self.get_xy()) + self.stale = True def get_xy(self): """ @@ -955,6 +975,7 @@ def set_xy(self, xy): if len(xy) > 2 and (xy[0] == xy[-1]).all(): xy = xy[:-1] self._path = Path(xy, closed=self._closed) + self.stale = True _get_xy = get_xy _set_xy = set_xy @@ -1026,22 +1047,27 @@ def _recompute_path(self): def set_center(self, center): self._path = None self.center = center + self.stale = True def set_radius(self, radius): self._path = None self.r = radius + self.stale = True def set_theta1(self, theta1): self._path = None self.theta1 = theta1 + self.stale = True def set_theta2(self, theta2): self._path = None self.theta2 = theta2 + self.stale = True def set_width(self, width): self._path = None self.width = width + self.stale = True def get_path(self): if self._path is None: @@ -1418,8 +1444,8 @@ def __init__(self, xy, radius=5, **kwargs): %(Patch)s """ - self.radius = radius Ellipse.__init__(self, xy, radius * 2, radius * 2, **kwargs) + self.radius = radius def set_radius(self, radius): """ @@ -1428,6 +1454,7 @@ def set_radius(self, radius): ACCEPTS: float """ self.width = self.height = 2 * radius + self.stale = True def get_radius(self): 'return the radius of the circle' @@ -2431,6 +2458,7 @@ def __init__(self, xy, width, height, self._mutation_scale = mutation_scale self._mutation_aspect = mutation_aspect + self.stale = True @docstring.dedent_interpd def set_boxstyle(self, boxstyle=None, **kw): @@ -2464,6 +2492,7 @@ def set_boxstyle(self, boxstyle=None, **kw): self._bbox_transmuter = boxstyle else: self._bbox_transmuter = BoxStyle(boxstyle, **kw) + self.stale = True def set_mutation_scale(self, scale): """ @@ -2472,6 +2501,7 @@ def set_mutation_scale(self, scale): ACCEPTS: float """ self._mutation_scale = scale + self.stale = True def get_mutation_scale(self): """ @@ -2486,6 +2516,7 @@ def set_mutation_aspect(self, aspect): ACCEPTS: float """ self._mutation_aspect = aspect + self.stale = True def get_mutation_aspect(self): """ @@ -2533,6 +2564,7 @@ def set_x(self, x): ACCEPTS: float """ self._x = x + self.stale = True def set_y(self, y): """ @@ -2541,6 +2573,7 @@ def set_y(self, y): ACCEPTS: float """ self._y = y + self.stale = True def set_width(self, w): """ @@ -2549,6 +2582,7 @@ def set_width(self, w): ACCEPTS: float """ self._width = w + self.stale = True def set_height(self, h): """ @@ -2557,6 +2591,7 @@ def set_height(self, h): ACCEPTS: float """ self._height = h + self.stale = True def set_bounds(self, *args): """ @@ -2572,6 +2607,7 @@ def set_bounds(self, *args): self._y = b self._width = w self._height = h + self.stale = True def get_bbox(self): return transforms.Bbox.from_bounds(self._x, self._y, @@ -3966,6 +4002,7 @@ def __init__(self, posA=None, posB=None, Valid kwargs are: %(Patch)s """ + Patch.__init__(self, **kwargs) if posA is not None and posB is not None and path is None: self._posA_posB = [posA, posB] @@ -3985,8 +4022,6 @@ def __init__(self, posA=None, posB=None, self.shrinkA = shrinkA self.shrinkB = shrinkB - Patch.__init__(self, **kwargs) - self._path_original = path self.set_arrowstyle(arrowstyle) @@ -4004,6 +4039,7 @@ def set_dpi_cor(self, dpi_cor): """ self._dpi_cor = dpi_cor + self.stale = True def get_dpi_cor(self): """ @@ -4021,16 +4057,19 @@ def set_positions(self, posA, posB): self._posA_posB[0] = posA if posB is not None: self._posA_posB[1] = posB + self.stale = True def set_patchA(self, patchA): """ set the begin patch. """ self.patchA = patchA + self.stale = True def set_patchB(self, patchB): """ set the begin patch """ self.patchB = patchB + self.stale = True def set_connectionstyle(self, connectionstyle, **kw): """ @@ -4059,6 +4098,7 @@ def set_connectionstyle(self, connectionstyle, **kw): self._connector = connectionstyle else: self._connector = ConnectionStyle(connectionstyle, **kw) + self.stale = True def get_connectionstyle(self): """ @@ -4090,6 +4130,7 @@ def set_arrowstyle(self, arrowstyle=None, **kw): self._arrow_transmuter = arrowstyle else: self._arrow_transmuter = ArrowStyle(arrowstyle, **kw) + self.stale = True def get_arrowstyle(self): """ @@ -4104,6 +4145,7 @@ def set_mutation_scale(self, scale): ACCEPTS: float """ self._mutation_scale = scale + self.stale = True def get_mutation_scale(self): """ @@ -4118,6 +4160,7 @@ def set_mutation_aspect(self, aspect): ACCEPTS: float """ self._mutation_aspect = aspect + self.stale = True def get_mutation_aspect(self): """ @@ -4225,6 +4268,7 @@ def draw(self, renderer): gc.restore() renderer.close_group('patch') + self.stale = False class ConnectionPatch(FancyArrowPatch): @@ -4443,6 +4487,7 @@ def set_annotation_clip(self, b): * None: the self.xy will be checked only if *xycoords* is "data" """ self._annotation_clip = b + self.stale = True def get_annotation_clip(self): """ diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index a44d8e15c730..2de9711df408 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -22,6 +22,7 @@ import sys import warnings +import types import matplotlib import matplotlib.colorbar @@ -107,6 +108,98 @@ def _backend_selection(): from matplotlib.backends import pylab_setup _backend_mod, new_figure_manager, draw_if_interactive, _show = pylab_setup() +_BASE_DH = None +_IP_REGISTERED = None + + +def install_repl_displayhook(): + """ + Install a repl display hook so that any stale figure are automatically + redrawn when control is returned to the repl. + + This works with both IPython terminals and vanilla python shells. + """ + global _BASE_DH + global _IP_REGISTERED + + class _NotIPython(Exception): + pass + + # see if we have IPython hooks around, if use them + + try: + from IPython import get_ipython + ip = get_ipython() + if ip is None: + raise _NotIPython() + + if _IP_REGISTERED: + return + + def displayhook(): + if matplotlib.is_interactive(): + draw_all() + + # IPython >= 2 + try: + ip.events.register('post_execute', displayhook) + except AttributeError: + # IPython 1.x + ip.register_post_execute(displayhook) + + _IP_REGISTERED = displayhook + + # import failed or ipython is not running + except (ImportError, _NotIPython): + + if _BASE_DH is not None: + return + + dh = _BASE_DH = sys.displayhook + + def displayhook(*args): + dh(*args) + if matplotlib.is_interactive(): + draw_all() + + sys.displayhook = displayhook + + +def uninstall_repl_displayhook(): + """ + Uninstalls the matplotlib display hook. + + .. warning + + Need IPython >= 2 for this to work. For IPython < 2 will raise a + ``NotImplementedError`` + + .. warning + + If you are using vanilla python and have installed another + display hook this will reset ``sys.displayhook`` to what ever + function was there when matplotlib installed it's displayhook, + possibly discarding your changes. + """ + global _BASE_DH + global _IP_REGISTERED + if _IP_REGISTERED: + from IPython import get_ipython + ip = get_ipython() + try: + ip.events.unregister('post_execute', _IP_REGISTERED) + except AttributeError: + raise NotImplementedError("Can not unregister events " + "in IPython < 2.0") + _IP_REGISTERED = None + + if _BASE_DH: + sys.displayhook = _BASE_DH + _BASE_DH = None + + +draw_all = _pylab_helpers.Gcf.draw_all + @docstring.copy_dedent(Artist.findobj) def findobj(o=None, match=None, include_self=True): @@ -164,11 +257,13 @@ def isinteractive(): def ioff(): 'Turn interactive mode off.' matplotlib.interactive(False) + uninstall_repl_displayhook() def ion(): 'Turn interactive mode on.' matplotlib.interactive(True) + install_repl_displayhook() def pause(interval): @@ -216,7 +311,8 @@ def rc_context(rc=None, fname=None): @docstring.copy_dedent(matplotlib.rcdefaults) def rcdefaults(): matplotlib.rcdefaults() - draw_if_interactive() + if matplotlib.is_interactive(): + draw_all() # The current "image" (ScalarMappable) is retrieved or set @@ -254,9 +350,7 @@ def sci(im): # (getp is simply imported) @docstring.copy(_setp) def setp(*args, **kwargs): - ret = _setp(*args, **kwargs) - draw_if_interactive() - return ret + return _setp(*args, **kwargs) def xkcd(scale=1, length=100, randomness=2): @@ -449,7 +543,6 @@ def make_active(event): _pylab_helpers.Gcf.set_active(figManager) figManager.canvas.figure.number = num - draw_if_interactive() return figManager.canvas.figure @@ -547,7 +640,6 @@ def clf(): Clear the current figure. """ gcf().clf() - draw_if_interactive() def draw(): @@ -612,27 +704,20 @@ def waitforbuttonpress(*args, **kwargs): @docstring.copy_dedent(Figure.text) def figtext(*args, **kwargs): - - ret = gcf().text(*args, **kwargs) - draw_if_interactive() - return ret + return gcf().text(*args, **kwargs) @docstring.copy_dedent(Figure.suptitle) def suptitle(*args, **kwargs): - ret = gcf().suptitle(*args, **kwargs) - draw_if_interactive() - return ret + return gcf().suptitle(*args, **kwargs) @docstring.Appender("Addition kwargs: hold = [True|False] overrides default hold state", "\n") @docstring.copy_dedent(Figure.figimage) def figimage(*args, **kwargs): # allow callers to override the hold state by passing hold=True|False - ret = gcf().figimage(*args, **kwargs) - draw_if_interactive() #sci(ret) # JDH figimage should not set current image -- it is not mappable, etc - return ret + return gcf().figimage(*args, **kwargs) def figlegend(handles, labels, loc, **kwargs): @@ -663,9 +748,7 @@ def figlegend(handles, labels, loc, **kwargs): :func:`~matplotlib.pyplot.legend` """ - l = gcf().legend(handles, labels, loc, **kwargs) - draw_if_interactive() - return l + return gcf().legend(handles, labels, loc, **kwargs) ## Figure and Axes hybrid ## @@ -774,7 +857,6 @@ def axes(*args, **kwargs): else: rect = arg a = gcf().add_axes(rect, **kwargs) - draw_if_interactive() return a @@ -790,7 +872,6 @@ def delaxes(*args): else: ax = args[0] ret = gcf().delaxes(ax) - draw_if_interactive() return ret @@ -827,11 +908,11 @@ def gca(**kwargs): -------- matplotlib.figure.Figure.gca : The figure's gca method. """ - ax = gcf().gca(**kwargs) - return ax + return gcf().gca(**kwargs) # More ways of creating axes: + def subplot(*args, **kwargs): """ Return a subplot axes positioned by the given grid definition. @@ -930,7 +1011,6 @@ def subplot(*args, **kwargs): byebye.append(other) for ax in byebye: delaxes(ax) - draw_if_interactive() return a @@ -1182,7 +1262,6 @@ def subplot2grid(shape, loc, rowspan=1, colspan=1, **kwargs): byebye.append(other) for ax in byebye: delaxes(ax) - draw_if_interactive() return a @@ -1201,7 +1280,6 @@ def twinx(ax=None): if ax is None: ax=gca() ax1 = ax.twinx() - draw_if_interactive() return ax1 @@ -1215,7 +1293,6 @@ def twiny(ax=None): if ax is None: ax=gca() ax1 = ax.twiny() - draw_if_interactive() return ax1 @@ -1241,7 +1318,6 @@ def subplots_adjust(*args, **kwargs): """ fig = gcf() fig.subplots_adjust(*args, **kwargs) - draw_if_interactive() def subplot_tool(targetfig=None): @@ -1288,7 +1364,6 @@ def tight_layout(pad=1.08, h_pad=None, w_pad=None, rect=None): fig = gcf() fig.tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) - draw_if_interactive() def box(on=None): @@ -1303,7 +1378,6 @@ def box(on=None): if on is None: on = not ax.get_frame_on() ax.set_frame_on(on) - draw_if_interactive() def title(s, *args, **kwargs): @@ -1348,9 +1422,7 @@ def title(s, *args, **kwargs): properties. """ - l = gca().set_title(s, *args, **kwargs) - draw_if_interactive() - return l + return gca().set_title(s, *args, **kwargs) ## Axis ## @@ -1415,10 +1487,7 @@ def axis(*v, **kwargs): :func:`xlim`, :func:`ylim` For setting the x- and y-limits individually. """ - ax = gca() - v = ax.axis(*v, **kwargs) - draw_if_interactive() - return v + return gca().axis(*v, **kwargs) def xlabel(s, *args, **kwargs): @@ -1438,9 +1507,7 @@ def xlabel(s, *args, **kwargs): :func:`~matplotlib.pyplot.text` For information on how override and the optional args work """ - l = gca().set_xlabel(s, *args, **kwargs) - draw_if_interactive() - return l + return gca().set_xlabel(s, *args, **kwargs) def ylabel(s, *args, **kwargs): @@ -1461,9 +1528,7 @@ def ylabel(s, *args, **kwargs): For information on how override and the optional args work. """ - l = gca().set_ylabel(s, *args, **kwargs) - draw_if_interactive() - return l + return gca().set_ylabel(s, *args, **kwargs) def xlim(*args, **kwargs): @@ -1491,7 +1556,6 @@ def xlim(*args, **kwargs): if not args and not kwargs: return ax.get_xlim() ret = ax.set_xlim(*args, **kwargs) - draw_if_interactive() return ret @@ -1519,7 +1583,6 @@ def ylim(*args, **kwargs): if not args and not kwargs: return ax.get_ylim() ret = ax.set_ylim(*args, **kwargs) - draw_if_interactive() return ret @@ -1538,9 +1601,7 @@ def xscale(*args, **kwargs): %(scale_docs)s """ - ax = gca() - ax.set_xscale(*args, **kwargs) - draw_if_interactive() + gca().set_xscale(*args, **kwargs) @docstring.dedent_interpd @@ -1558,9 +1619,7 @@ def yscale(*args, **kwargs): %(scale_docs)s """ - ax = gca() - ax.set_yscale(*args, **kwargs) - draw_if_interactive() + gca().set_yscale(*args, **kwargs) def xticks(*args, **kwargs): @@ -1600,7 +1659,6 @@ def xticks(*args, **kwargs): for l in labels: l.update(kwargs) - draw_if_interactive() return locs, silent_list('Text xticklabel', labels) @@ -1641,7 +1699,6 @@ def yticks(*args, **kwargs): for l in labels: l.update(kwargs) - draw_if_interactive() return ( locs, silent_list('Text yticklabel', labels) @@ -1656,7 +1713,6 @@ def minorticks_on(): minorticks_off() if drawing speed is a problem. """ gca().minorticks_on() - draw_if_interactive() def minorticks_off(): @@ -1664,7 +1720,6 @@ def minorticks_off(): Remove minor ticks from the current plot. """ gca().minorticks_off() - draw_if_interactive() def rgrids(*args, **kwargs): @@ -1707,7 +1762,6 @@ def rgrids(*args, **kwargs): else: lines, labels = ax.set_rgrids(*args, **kwargs) - draw_if_interactive() return ( silent_list('Line2D rgridline', lines), silent_list('Text rgridlabel', labels) ) @@ -1768,7 +1822,6 @@ def thetagrids(*args, **kwargs): else: lines, labels = ax.set_thetagrids(*args, **kwargs) - draw_if_interactive() return (silent_list('Line2D thetagridline', lines), silent_list('Text thetagridlabel', labels) ) @@ -2159,7 +2212,6 @@ def colorbar(mappable=None, cax=None, ax=None, **kw): ax = gca() ret = gcf().colorbar(mappable, cax = cax, ax=ax, **kw) - draw_if_interactive() return ret colorbar.__doc__ = matplotlib.colorbar.colorbar_doc @@ -2187,7 +2239,6 @@ def clim(vmin=None, vmax=None): raise RuntimeError('You must first define an image, e.g., with imshow') im.set_clim(vmin, vmax) - draw_if_interactive() def set_cmap(cmap): @@ -2209,7 +2260,6 @@ def set_cmap(cmap): if im is not None: im.set_cmap(cmap) - draw_if_interactive() @docstring.copy_dedent(_imread) @@ -2260,7 +2310,6 @@ def matshow(A, fignum=None, **kw): im = ax.matshow(A, **kw) sci(im) - draw_if_interactive() return im @@ -2278,7 +2327,6 @@ def polar(*args, **kwargs): """ ax = gca(polar=True) ret = ax.plot(*args, **kwargs) - draw_if_interactive() return ret @@ -2400,8 +2448,6 @@ def getname_val(identifier): if xname=='date': fig.autofmt_xdate() - draw_if_interactive() - def _autogen_docstring(base): """Autogenerated wrappers will get their docstring from a base function @@ -2422,13 +2468,18 @@ def spy(Z, precision=0, marker=None, markersize=None, aspect='equal', hold=None, ax.hold(hold) try: ret = ax.spy(Z, precision, marker, markersize, aspect, **kwargs) - draw_if_interactive() finally: ax.hold(washold) if isinstance(ret, cm.ScalarMappable): sci(ret) return ret +# just to be safe. Interactive mode can be turned on without +# calling `plt.ion()` so register it again here. +# This is safe because multiple calls to `install_repl_displayhook` +# are no-ops and the registered function respect `mpl.is_interactive()` +# to determine if they should trigger a draw. +install_repl_displayhook() ################# REMAINING CONTENT GENERATED BY boilerplate.py ############## @@ -2445,7 +2496,6 @@ def acorr(x, hold=None, **kwargs): ax.hold(hold) try: ret = ax.acorr(x, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2465,7 +2515,6 @@ def angle_spectrum(x, Fs=None, Fc=None, window=None, pad_to=None, sides=None, try: ret = ax.angle_spectrum(x, Fs=Fs, Fc=Fc, window=window, pad_to=pad_to, sides=sides, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2483,7 +2532,6 @@ def arrow(x, y, dx, dy, hold=None, **kwargs): ax.hold(hold) try: ret = ax.arrow(x, y, dx, dy, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2501,7 +2549,6 @@ def axhline(y=0, xmin=0, xmax=1, hold=None, **kwargs): ax.hold(hold) try: ret = ax.axhline(y=y, xmin=xmin, xmax=xmax, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2519,7 +2566,6 @@ def axhspan(ymin, ymax, xmin=0, xmax=1, hold=None, **kwargs): ax.hold(hold) try: ret = ax.axhspan(ymin, ymax, xmin=xmin, xmax=xmax, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2537,7 +2583,6 @@ def axvline(x=0, ymin=0, ymax=1, hold=None, **kwargs): ax.hold(hold) try: ret = ax.axvline(x=x, ymin=ymin, ymax=ymax, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2555,7 +2600,6 @@ def axvspan(xmin, xmax, ymin=0, ymax=1, hold=None, **kwargs): ax.hold(hold) try: ret = ax.axvspan(xmin, xmax, ymin=ymin, ymax=ymax, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2573,7 +2617,6 @@ def bar(left, height, width=0.8, bottom=None, hold=None, **kwargs): ax.hold(hold) try: ret = ax.bar(left, height, width=width, bottom=bottom, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2591,7 +2634,6 @@ def barh(bottom, width, height=0.8, left=None, hold=None, **kwargs): ax.hold(hold) try: ret = ax.barh(bottom, width, height=height, left=left, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2609,7 +2651,6 @@ def broken_barh(xranges, yrange, hold=None, **kwargs): ax.hold(hold) try: ret = ax.broken_barh(xranges, yrange, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2642,7 +2683,6 @@ def boxplot(x, notch=False, sym=None, vert=True, whis=1.5, positions=None, flierprops=flierprops, medianprops=medianprops, meanprops=meanprops, capprops=capprops, whiskerprops=whiskerprops, manage_xticks=manage_xticks) - draw_if_interactive() finally: ax.hold(washold) @@ -2664,7 +2704,6 @@ def cohere(x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, ret = ax.cohere(x, y, NFFT=NFFT, Fs=Fs, Fc=Fc, detrend=detrend, window=window, noverlap=noverlap, pad_to=pad_to, sides=sides, scale_by_freq=scale_by_freq, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2682,7 +2721,6 @@ def clabel(CS, *args, **kwargs): ax.hold(hold) try: ret = ax.clabel(CS, *args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2700,7 +2738,6 @@ def contour(*args, **kwargs): ax.hold(hold) try: ret = ax.contour(*args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) if ret._A is not None: sci(ret) @@ -2718,7 +2755,6 @@ def contourf(*args, **kwargs): ax.hold(hold) try: ret = ax.contourf(*args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) if ret._A is not None: sci(ret) @@ -2741,7 +2777,6 @@ def csd(x, y, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, window=window, noverlap=noverlap, pad_to=pad_to, sides=sides, scale_by_freq=scale_by_freq, return_line=return_line, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2766,7 +2801,6 @@ def errorbar(x, y, yerr=None, xerr=None, fmt='', ecolor=None, elinewidth=None, barsabove=barsabove, lolims=lolims, uplims=uplims, xlolims=xlolims, xuplims=xuplims, errorevery=errorevery, capthick=capthick, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2789,7 +2823,6 @@ def eventplot(positions, orientation='horizontal', lineoffsets=1, linelengths=1, lineoffsets=lineoffsets, linelengths=linelengths, linewidths=linewidths, colors=colors, linestyles=linestyles, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2807,7 +2840,6 @@ def fill(*args, **kwargs): ax.hold(hold) try: ret = ax.fill(*args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2826,7 +2858,6 @@ def fill_between(x, y1, y2=0, where=None, interpolate=False, hold=None, **kwargs try: ret = ax.fill_between(x, y1, y2=y2, where=where, interpolate=interpolate, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2844,7 +2875,6 @@ def fill_betweenx(y, x1, x2=0, where=None, hold=None, **kwargs): ax.hold(hold) try: ret = ax.fill_betweenx(y, x1, x2=x2, where=where, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2871,7 +2901,6 @@ def hexbin(x, y, C=None, gridsize=100, bins=None, xscale='linear', linewidths=linewidths, edgecolors=edgecolors, reduce_C_function=reduce_C_function, mincnt=mincnt, marginals=marginals, **kwargs) - draw_if_interactive() finally: ax.hold(washold) sci(ret) @@ -2896,7 +2925,6 @@ def hist(x, bins=10, range=None, normed=False, weights=None, cumulative=False, histtype=histtype, align=align, orientation=orientation, rwidth=rwidth, log=log, color=color, label=label, stacked=stacked, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2916,7 +2944,6 @@ def hist2d(x, y, bins=10, range=None, normed=False, weights=None, cmin=None, try: ret = ax.hist2d(x, y, bins=bins, range=range, normed=normed, weights=weights, cmin=cmin, cmax=cmax, **kwargs) - draw_if_interactive() finally: ax.hold(washold) sci(ret[-1]) @@ -2936,7 +2963,6 @@ def hlines(y, xmin, xmax, colors='k', linestyles='solid', label='', hold=None, try: ret = ax.hlines(y, xmin, xmax, colors=colors, linestyles=linestyles, label=label, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -2961,7 +2987,6 @@ def imshow(X, cmap=None, norm=None, aspect=None, interpolation=None, alpha=None, vmax=vmax, origin=origin, extent=extent, shape=shape, filternorm=filternorm, filterrad=filterrad, imlim=imlim, resample=resample, url=url, **kwargs) - draw_if_interactive() finally: ax.hold(washold) sci(ret) @@ -2979,7 +3004,6 @@ def loglog(*args, **kwargs): ax.hold(hold) try: ret = ax.loglog(*args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -3000,7 +3024,6 @@ def magnitude_spectrum(x, Fs=None, Fc=None, window=None, pad_to=None, ret = ax.magnitude_spectrum(x, Fs=Fs, Fc=Fc, window=window, pad_to=pad_to, sides=sides, scale=scale, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -3018,7 +3041,6 @@ def pcolor(*args, **kwargs): ax.hold(hold) try: ret = ax.pcolor(*args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) sci(ret) @@ -3036,7 +3058,6 @@ def pcolormesh(*args, **kwargs): ax.hold(hold) try: ret = ax.pcolormesh(*args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) sci(ret) @@ -3056,7 +3077,6 @@ def phase_spectrum(x, Fs=None, Fc=None, window=None, pad_to=None, sides=None, try: ret = ax.phase_spectrum(x, Fs=Fs, Fc=Fc, window=window, pad_to=pad_to, sides=sides, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -3082,7 +3102,6 @@ def pie(x, explode=None, labels=None, colors=None, autopct=None, radius=radius, counterclock=counterclock, wedgeprops=wedgeprops, textprops=textprops, center=center, frame=frame) - draw_if_interactive() finally: ax.hold(washold) @@ -3100,7 +3119,6 @@ def plot(*args, **kwargs): ax.hold(hold) try: ret = ax.plot(*args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -3120,7 +3138,6 @@ def plot_date(x, y, fmt='o', tz=None, xdate=True, ydate=False, hold=None, try: ret = ax.plot_date(x, y, fmt=fmt, tz=tz, xdate=xdate, ydate=ydate, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -3143,7 +3160,6 @@ def psd(x, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, window=window, noverlap=noverlap, pad_to=pad_to, sides=sides, scale_by_freq=scale_by_freq, return_line=return_line, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -3161,7 +3177,6 @@ def quiver(*args, **kw): ax.hold(hold) try: ret = ax.quiver(*args, **kw) - draw_if_interactive() finally: ax.hold(washold) sci(ret) @@ -3179,7 +3194,6 @@ def quiverkey(*args, **kw): ax.hold(hold) try: ret = ax.quiverkey(*args, **kw) - draw_if_interactive() finally: ax.hold(washold) @@ -3188,9 +3202,9 @@ def quiverkey(*args, **kw): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.scatter) -def scatter(x, y, s=20, c='b', marker='o', cmap=None, norm=None, vmin=None, - vmax=None, alpha=None, linewidths=None, verts=None, hold=None, - **kwargs): +def scatter(x, y, s=20, c=None, marker='o', cmap=None, norm=None, vmin=None, + vmax=None, alpha=None, linewidths=None, verts=None, edgecolors=None, + hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False washold = ax.ishold() @@ -3200,8 +3214,8 @@ def scatter(x, y, s=20, c='b', marker='o', cmap=None, norm=None, vmin=None, try: ret = ax.scatter(x, y, s=s, c=c, marker=marker, cmap=cmap, norm=norm, vmin=vmin, vmax=vmax, alpha=alpha, - linewidths=linewidths, verts=verts, **kwargs) - draw_if_interactive() + linewidths=linewidths, verts=verts, + edgecolors=edgecolors, **kwargs) finally: ax.hold(washold) sci(ret) @@ -3219,7 +3233,6 @@ def semilogx(*args, **kwargs): ax.hold(hold) try: ret = ax.semilogx(*args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -3237,7 +3250,6 @@ def semilogy(*args, **kwargs): ax.hold(hold) try: ret = ax.semilogy(*args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -3262,7 +3274,6 @@ def specgram(x, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, xextent=xextent, pad_to=pad_to, sides=sides, scale_by_freq=scale_by_freq, mode=mode, scale=scale, vmin=vmin, vmax=vmax, **kwargs) - draw_if_interactive() finally: ax.hold(washold) sci(ret[-1]) @@ -3280,7 +3291,6 @@ def stackplot(x, *args, **kwargs): ax.hold(hold) try: ret = ax.stackplot(x, *args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -3298,7 +3308,6 @@ def stem(*args, **kwargs): ax.hold(hold) try: ret = ax.stem(*args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -3316,7 +3325,6 @@ def step(x, y, *args, **kwargs): ax.hold(hold) try: ret = ax.step(x, y, *args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -3340,7 +3348,6 @@ def streamplot(x, y, u, v, density=1, linewidth=None, color=None, cmap=None, arrowsize=arrowsize, arrowstyle=arrowstyle, minlength=minlength, transform=transform, zorder=zorder) - draw_if_interactive() finally: ax.hold(washold) sci(ret.lines) @@ -3358,7 +3365,6 @@ def tricontour(*args, **kwargs): ax.hold(hold) try: ret = ax.tricontour(*args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) if ret._A is not None: sci(ret) @@ -3376,7 +3382,6 @@ def tricontourf(*args, **kwargs): ax.hold(hold) try: ret = ax.tricontourf(*args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) if ret._A is not None: sci(ret) @@ -3394,7 +3399,6 @@ def tripcolor(*args, **kwargs): ax.hold(hold) try: ret = ax.tripcolor(*args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) sci(ret) @@ -3412,7 +3416,6 @@ def triplot(*args, **kwargs): ax.hold(hold) try: ret = ax.triplot(*args, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -3435,7 +3438,6 @@ def violinplot(dataset, positions=None, vert=True, widths=0.5, showmeans=False, widths=widths, showmeans=showmeans, showextrema=showextrema, showmedians=showmedians, points=points, bw_method=bw_method) - draw_if_interactive() finally: ax.hold(washold) @@ -3455,7 +3457,6 @@ def vlines(x, ymin, ymax, colors='k', linestyles='solid', label='', hold=None, try: ret = ax.vlines(x, ymin, ymax, colors=colors, linestyles=linestyles, label=label, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -3475,7 +3476,6 @@ def xcorr(x, y, normed=True, detrend=mlab.detrend_none, usevlines=True, try: ret = ax.xcorr(x, y, normed=normed, detrend=detrend, usevlines=usevlines, maxlags=maxlags, **kwargs) - draw_if_interactive() finally: ax.hold(washold) @@ -3493,7 +3493,6 @@ def barbs(*args, **kw): ax.hold(hold) try: ret = ax.barbs(*args, **kw) - draw_if_interactive() finally: ax.hold(washold) @@ -3504,7 +3503,6 @@ def barbs(*args, **kw): @docstring.copy_dedent(Axes.cla) def cla(): ret = gca().cla() - draw_if_interactive() return ret # This function was autogenerated by boilerplate.py. Do not edit as @@ -3512,7 +3510,6 @@ def cla(): @docstring.copy_dedent(Axes.grid) def grid(b=None, which='major', axis='both', **kwargs): ret = gca().grid(b=b, which=which, axis=axis, **kwargs) - draw_if_interactive() return ret # This function was autogenerated by boilerplate.py. Do not edit as @@ -3520,7 +3517,6 @@ def grid(b=None, which='major', axis='both', **kwargs): @docstring.copy_dedent(Axes.legend) def legend(*args, **kwargs): ret = gca().legend(*args, **kwargs) - draw_if_interactive() return ret # This function was autogenerated by boilerplate.py. Do not edit as @@ -3528,7 +3524,6 @@ def legend(*args, **kwargs): @docstring.copy_dedent(Axes.table) def table(**kwargs): ret = gca().table(**kwargs) - draw_if_interactive() return ret # This function was autogenerated by boilerplate.py. Do not edit as @@ -3536,7 +3531,6 @@ def table(**kwargs): @docstring.copy_dedent(Axes.text) def text(x, y, s, fontdict=None, withdash=False, **kwargs): ret = gca().text(x, y, s, fontdict=fontdict, withdash=withdash, **kwargs) - draw_if_interactive() return ret # This function was autogenerated by boilerplate.py. Do not edit as @@ -3544,7 +3538,6 @@ def text(x, y, s, fontdict=None, withdash=False, **kwargs): @docstring.copy_dedent(Axes.annotate) def annotate(*args, **kwargs): ret = gca().annotate(*args, **kwargs) - draw_if_interactive() return ret # This function was autogenerated by boilerplate.py. Do not edit as @@ -3552,7 +3545,6 @@ def annotate(*args, **kwargs): @docstring.copy_dedent(Axes.ticklabel_format) def ticklabel_format(**kwargs): ret = gca().ticklabel_format(**kwargs) - draw_if_interactive() return ret # This function was autogenerated by boilerplate.py. Do not edit as @@ -3560,7 +3552,6 @@ def ticklabel_format(**kwargs): @docstring.copy_dedent(Axes.locator_params) def locator_params(axis='both', tight=None, **kwargs): ret = gca().locator_params(axis=axis, tight=tight, **kwargs) - draw_if_interactive() return ret # This function was autogenerated by boilerplate.py. Do not edit as @@ -3568,7 +3559,6 @@ def locator_params(axis='both', tight=None, **kwargs): @docstring.copy_dedent(Axes.tick_params) def tick_params(axis='both', **kwargs): ret = gca().tick_params(axis=axis, **kwargs) - draw_if_interactive() return ret # This function was autogenerated by boilerplate.py. Do not edit as @@ -3576,7 +3566,6 @@ def tick_params(axis='both', **kwargs): @docstring.copy_dedent(Axes.margins) def margins(*args, **kw): ret = gca().margins(*args, **kw) - draw_if_interactive() return ret # This function was autogenerated by boilerplate.py. Do not edit as @@ -3584,7 +3573,6 @@ def margins(*args, **kw): @docstring.copy_dedent(Axes.autoscale) def autoscale(enable=True, axis='both', tight=None): ret = gca().autoscale(enable=enable, axis=axis, tight=tight) - draw_if_interactive() return ret # This function was autogenerated by boilerplate.py. Do not edit as @@ -3599,7 +3587,6 @@ def autumn(): if im is not None: im.set_cmap(cm.autumn) - draw_if_interactive() # This function was autogenerated by boilerplate.py. Do not edit as @@ -3614,7 +3601,6 @@ def bone(): if im is not None: im.set_cmap(cm.bone) - draw_if_interactive() # This function was autogenerated by boilerplate.py. Do not edit as @@ -3629,7 +3615,6 @@ def cool(): if im is not None: im.set_cmap(cm.cool) - draw_if_interactive() # This function was autogenerated by boilerplate.py. Do not edit as @@ -3644,7 +3629,6 @@ def copper(): if im is not None: im.set_cmap(cm.copper) - draw_if_interactive() # This function was autogenerated by boilerplate.py. Do not edit as @@ -3659,7 +3643,6 @@ def flag(): if im is not None: im.set_cmap(cm.flag) - draw_if_interactive() # This function was autogenerated by boilerplate.py. Do not edit as @@ -3674,7 +3657,6 @@ def gray(): if im is not None: im.set_cmap(cm.gray) - draw_if_interactive() # This function was autogenerated by boilerplate.py. Do not edit as @@ -3689,7 +3671,6 @@ def hot(): if im is not None: im.set_cmap(cm.hot) - draw_if_interactive() # This function was autogenerated by boilerplate.py. Do not edit as @@ -3704,7 +3685,6 @@ def hsv(): if im is not None: im.set_cmap(cm.hsv) - draw_if_interactive() # This function was autogenerated by boilerplate.py. Do not edit as @@ -3719,7 +3699,6 @@ def jet(): if im is not None: im.set_cmap(cm.jet) - draw_if_interactive() # This function was autogenerated by boilerplate.py. Do not edit as @@ -3734,7 +3713,6 @@ def pink(): if im is not None: im.set_cmap(cm.pink) - draw_if_interactive() # This function was autogenerated by boilerplate.py. Do not edit as @@ -3749,7 +3727,6 @@ def prism(): if im is not None: im.set_cmap(cm.prism) - draw_if_interactive() # This function was autogenerated by boilerplate.py. Do not edit as @@ -3764,7 +3741,6 @@ def spring(): if im is not None: im.set_cmap(cm.spring) - draw_if_interactive() # This function was autogenerated by boilerplate.py. Do not edit as @@ -3779,7 +3755,6 @@ def summer(): if im is not None: im.set_cmap(cm.summer) - draw_if_interactive() # This function was autogenerated by boilerplate.py. Do not edit as @@ -3794,7 +3769,6 @@ def winter(): if im is not None: im.set_cmap(cm.winter) - draw_if_interactive() # This function was autogenerated by boilerplate.py. Do not edit as @@ -3809,6 +3783,5 @@ def spectral(): if im is not None: im.set_cmap(cm.spectral) - draw_if_interactive() _setup_pyplot_info_docstrings() diff --git a/lib/matplotlib/quiver.py b/lib/matplotlib/quiver.py index d9ee0cf5d1b1..ddac93bd71d4 100644 --- a/lib/matplotlib/quiver.py +++ b/lib/matplotlib/quiver.py @@ -336,6 +336,7 @@ def draw(self, renderer): self.text.set_x(self._text_x(x)) self.text.set_y(self._text_y(y)) self.text.draw(renderer) + self.stale = False def _set_transform(self): if self.coord == 'data': @@ -527,6 +528,7 @@ def draw(self, renderer): self.set_verts(verts, closed=False) self._new_UV = False mcollections.PolyCollection.draw(self, renderer) + self.stale = False def set_UVC(self, U, V, C=None): # We need to ensure we have a copy, not a reference @@ -547,6 +549,7 @@ def set_UVC(self, U, V, C=None): if C is not None: self.set_array(C) self._new_UV = True + self.stale = True def _dots_per_unit(self, units): """ @@ -1134,6 +1137,7 @@ def set_UVC(self, U, V, C=None): # Update the offsets in case the masked data changed xy = np.hstack((x[:, np.newaxis], y[:, np.newaxis])) self._offsets = xy + self.stale = True def set_offsets(self, xy): """ @@ -1149,6 +1153,8 @@ def set_offsets(self, xy): self.u, self.v) xy = np.hstack((x[:, np.newaxis], y[:, np.newaxis])) mcollections.PolyCollection.set_offsets(self, xy) + self.stale = True + set_offsets.__doc__ = mcollections.PolyCollection.set_offsets.__doc__ barbs_doc = _barbs_doc diff --git a/lib/matplotlib/spines.py b/lib/matplotlib/spines.py index f73ee15fbc73..a6938b9d899d 100644 --- a/lib/matplotlib/spines.py +++ b/lib/matplotlib/spines.py @@ -96,6 +96,7 @@ def set_smart_bounds(self, value): self.axes.yaxis.set_smart_bounds(value) elif self.spine_type in ('top', 'bottom'): self.axes.xaxis.set_smart_bounds(value) + self.stale = True def get_smart_bounds(self): """get whether the spine has smart bounds""" @@ -110,10 +111,12 @@ def set_patch_circle(self, center, radius): self._angle = 0 # circle drawn on axes transform self.set_transform(self.axes.transAxes) + self.stale = True def set_patch_line(self): """set the spine to be linear""" self._patch_type = 'line' + self.stale = True # Behavior copied from mpatches.Ellipse: def _recompute_transform(self): @@ -158,6 +161,7 @@ def register_axis(self, axis): self.axis = axis if self.axis is not None: self.axis.cla() + self.stale = True def cla(self): """Clear the current spine""" @@ -274,7 +278,9 @@ def _adjust_location(self): @allow_rasterization def draw(self, renderer): self._adjust_location() - return super(Spine, self).draw(renderer) + ret = super(Spine, self).draw(renderer) + self.stale = False + return ret def _calc_offset_transform(self): """calculate the offset transform performed by the spine""" @@ -388,6 +394,7 @@ def set_position(self, position): if self.axis is not None: self.axis.reset_ticks() + self.stale = True def get_position(self): """get the spine position""" @@ -437,6 +444,7 @@ def set_bounds(self, low, high): raise ValueError( 'set_bounds() method incompatible with circular spines') self._bounds = (low, high) + self.stale = True def get_bounds(self): """Get the bounds of the spine.""" @@ -486,3 +494,4 @@ def set_color(self, c): # The facecolor of a spine is always 'none' by default -- let # the user change it manually if desired. self.set_edgecolor(c) + self.stale = True diff --git a/lib/matplotlib/table.py b/lib/matplotlib/table.py index 605cbd92492a..cc6f6a1970ad 100644 --- a/lib/matplotlib/table.py +++ b/lib/matplotlib/table.py @@ -68,6 +68,7 @@ def __init__(self, xy, width, height, def set_transform(self, trans): Rectangle.set_transform(self, trans) # the text does not get the transform! + self.stale = True def set_figure(self, fig): Rectangle.set_figure(self, fig) @@ -79,6 +80,7 @@ def get_text(self): def set_fontsize(self, size): self._text.set_fontsize(size) + self.stale = True def get_fontsize(self): 'Return the cell fontsize' @@ -105,6 +107,7 @@ def draw(self, renderer): # position the text self._set_text_position(renderer) self._text.draw(renderer) + self.stale = False def _set_text_position(self, renderer): """ Set text up so it draws in the right place. @@ -145,6 +148,7 @@ def get_required_width(self, renderer): def set_text_props(self, **kwargs): 'update the text properties with kwargs' self._text.update(kwargs) + self.stale = True class CustomCell(Cell): @@ -186,6 +190,7 @@ def visible_edges(self, value): ) raise ValueError(msg) self._visible_edges = value + self.stale = True def get_path(self): 'Return a path where the edges specificed by _visible_edges are drawn' @@ -285,6 +290,7 @@ def add_cell(self, row, col, *args, **kwargs): cell.set_clip_on(False) self._cells[(row, col)] = cell + self.stale = True @property def edges(self): @@ -293,6 +299,7 @@ def edges(self): @edges.setter def edges(self, value): self._edges = value + self.stale = True def _approx_text_height(self): return (self.FONTSIZE / 72.0 * self.figure.dpi / @@ -320,6 +327,7 @@ def draw(self, renderer): # for c in self._cells.itervalues(): # c.draw(renderer) renderer.close_group('table') + self.stale = False def _get_grid_bbox(self, renderer): """Get a bbox, in axes co-ordinates for the cells. @@ -403,6 +411,7 @@ def _do_cell_alignment(self): def auto_set_column_width(self, col): self._autoColumns.append(col) + self.stale = True def _auto_set_column_width(self, col, renderer): """ Automagically set width for column. @@ -422,6 +431,7 @@ def _auto_set_column_width(self, col, renderer): def auto_set_font_size(self, value=True): """ Automatically set font size. """ self._autoFontsize = value + self.stale = True def _auto_set_font_size(self, renderer): @@ -456,6 +466,7 @@ def set_fontsize(self, size): for cell in six.itervalues(self._cells): cell.set_fontsize(size) + self.stale = True def _offset(self, ox, oy): 'Move all the artists by ox,oy (axes coords)' diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index c7a22ad39b7c..00ac38d355ed 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -297,6 +297,7 @@ def set_rotation_mode(self, m): self._rotation_mode = m else: raise ValueError("Unknown rotation_mode : %s" % repr(m)) + self.stale = True def get_rotation_mode(self): "get text rotation mode" @@ -313,6 +314,7 @@ def update_from(self, other): self._rotation = other._rotation self._picker = other._picker self._linespacing = other._linespacing + self.stale = True def _get_layout(self, renderer): """ @@ -786,6 +788,7 @@ def draw(self, renderer): gc.restore() renderer.close_group('text') + self.stale = False def get_color(self): "Return the color of the text" @@ -963,6 +966,7 @@ def set_backgroundcolor(self, color): self._bbox.update(dict(facecolor=color)) self._update_clip_properties() + self.stale = True def set_color(self, color): """ @@ -976,6 +980,7 @@ def set_color(self, color): except TypeError: color = tuple(color) self._color = color + self.stale = True def set_ha(self, align): 'alias for set_horizontalalignment' @@ -992,6 +997,7 @@ def set_horizontalalignment(self, align): raise ValueError('Horizontal alignment must be one of %s' % str(legal)) self._horizontalalignment = align + self.stale = True def set_ma(self, align): 'alias for set_verticalalignment' @@ -1011,6 +1017,7 @@ def set_multialignment(self, align): raise ValueError('Horizontal alignment must be one of %s' % str(legal)) self._multialignment = align + self.stale = True def set_linespacing(self, spacing): """ @@ -1020,6 +1027,7 @@ def set_linespacing(self, spacing): ACCEPTS: float (multiple of font size) """ self._linespacing = spacing + self.stale = True def set_family(self, fontname): """ @@ -1033,6 +1041,7 @@ def set_family(self, fontname): 'monospace' ] """ self._fontproperties.set_family(fontname) + self.stale = True def set_variant(self, variant): """ @@ -1041,6 +1050,7 @@ def set_variant(self, variant): ACCEPTS: [ 'normal' | 'small-caps' ] """ self._fontproperties.set_variant(variant) + self.stale = True def set_fontvariant(self, variant): 'alias for set_variant' @@ -1061,6 +1071,7 @@ def set_style(self, fontstyle): ACCEPTS: [ 'normal' | 'italic' | 'oblique'] """ self._fontproperties.set_style(fontstyle) + self.stale = True def set_fontstyle(self, fontstyle): 'alias for set_style' @@ -1075,6 +1086,7 @@ def set_size(self, fontsize): 'medium' | 'large' | 'x-large' | 'xx-large' ] """ self._fontproperties.set_size(fontsize) + self.stale = True def set_fontsize(self, fontsize): 'alias for set_size' @@ -1090,6 +1102,7 @@ def set_weight(self, weight): 'extra bold' | 'black' ] """ self._fontproperties.set_weight(weight) + self.stale = True def set_fontweight(self, weight): 'alias for set_weight' @@ -1105,6 +1118,7 @@ def set_stretch(self, stretch): 'ultra-expanded' ] """ self._fontproperties.set_stretch(stretch) + self.stale = True def set_fontstretch(self, stretch): 'alias for set_stretch' @@ -1126,6 +1140,7 @@ def set_x(self, x): ACCEPTS: float """ self._x = x + self.stale = True def set_y(self, y): """ @@ -1134,6 +1149,7 @@ def set_y(self, y): ACCEPTS: float """ self._y = y + self.stale = True def set_rotation(self, s): """ @@ -1142,6 +1158,7 @@ def set_rotation(self, s): ACCEPTS: [ angle in degrees | 'vertical' | 'horizontal' ] """ self._rotation = s + self.stale = True def set_va(self, align): 'alias for set_verticalalignment' @@ -1159,6 +1176,7 @@ def set_verticalalignment(self, align): str(legal)) self._verticalalignment = align + self.stale = True def set_text(self, s): """ @@ -1169,6 +1187,7 @@ def set_text(self, s): ACCEPTS: string or anything printable with '%s' conversion. """ self._text = '%s' % (s,) + self.stale = True @staticmethod def is_math_text(s): @@ -1201,6 +1220,7 @@ def set_fontproperties(self, fp): if is_string_like(fp): fp = FontProperties(fp) self._fontproperties = fp.copy() + self.stale = True def set_font_properties(self, fp): 'alias for set_fontproperties' @@ -1217,6 +1237,7 @@ def set_usetex(self, usetex): self._usetex = None else: self._usetex = bool(usetex) + self.stale = True def get_usetex(self): """ @@ -1364,6 +1385,7 @@ def draw(self, renderer): Text.draw(self, renderer) if self.get_dashlength() > 0.0: self.dashline.draw(renderer) + self.stale = False def update_coords(self, renderer): """ @@ -1483,6 +1505,7 @@ def set_dashlength(self, dl): ACCEPTS: float (canvas units) """ self._dashlength = dl + self.stale = True def get_dashdirection(self): """ @@ -1500,6 +1523,7 @@ def set_dashdirection(self, dd): ACCEPTS: int (1 is before, 0 is after) """ self._dashdirection = dd + self.stale = True def get_dashrotation(self): """ @@ -1517,6 +1541,7 @@ def set_dashrotation(self, dr): ACCEPTS: float (degrees) """ self._dashrotation = dr + self.stale = True def get_dashpad(self): """ @@ -1532,6 +1557,7 @@ def set_dashpad(self, dp): ACCEPTS: float (canvas units) """ self._dashpad = dp + self.stale = True def get_dashpush(self): """ @@ -1549,6 +1575,7 @@ def set_dashpush(self, dp): ACCEPTS: float (canvas units) """ self._dashpush = dp + self.stale = True def set_position(self, xy): """ @@ -1566,6 +1593,7 @@ def set_x(self, x): ACCEPTS: float """ self._dashx = float(x) + self.stale = True def set_y(self, y): """ @@ -1574,6 +1602,7 @@ def set_y(self, y): ACCEPTS: float """ self._dashy = float(y) + self.stale = True def set_transform(self, t): """ @@ -1584,6 +1613,7 @@ def set_transform(self, t): """ Text.set_transform(self, t) self.dashline.set_transform(t) + self.stale = True def get_figure(self): 'return the figure instance the artist belongs to' diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 6381a685239f..28ee8ef37e50 100755 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -75,6 +75,7 @@ def set_3d_properties(self, z=0, zdir='z'): x, y = self.get_position() self._position3d = np.array((x, y, z)) self._dir_vec = get_dir_vector(zdir) + self.stale = True def draw(self, renderer): proj = proj3d.proj_trans_points([self._position3d, \ @@ -89,12 +90,15 @@ def draw(self, renderer): self.set_position((proj[0][0], proj[1][0])) self.set_rotation(norm_text_angle(angle)) mtext.Text.draw(self, renderer) + self.stale = False + def text_2d_to_3d(obj, z=0, zdir='z'): """Convert a Text to a Text3D object.""" obj.__class__ = Text3D obj.set_3d_properties(z, zdir) + class Line3D(lines.Line2D): ''' 3D line object. @@ -119,12 +123,15 @@ def set_3d_properties(self, zs=0, zdir='z'): except TypeError: pass self._verts3d = juggle_axes(xs, ys, zs, zdir) + self.stale = True def draw(self, renderer): xs3d, ys3d, zs3d = self._verts3d xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M) self.set_data(xs, ys) lines.Line2D.draw(self, renderer) + self.stale = False + def line_2d_to_3d(line, zs=0, zdir='z'): ''' @@ -170,9 +177,10 @@ def __init__(self, segments, *args, **kwargs): ''' LineCollection.__init__(self, segments, *args, **kwargs) - def set_sort_zpos(self,val): + def set_sort_zpos(self, val): '''Set the position to use for z-sorting.''' self._sort_zpos = val + self.stale = True def set_segments(self, segments): ''' @@ -202,12 +210,14 @@ def draw(self, renderer, project=False): self.do_3d_projection(renderer) LineCollection.draw(self, renderer) + def line_collection_2d_to_3d(col, zs=0, zdir='z'): """Convert a LineCollection to a Line3DCollection object.""" segments3d = paths_to_3d_segments(col.get_paths(), zs, zdir) col.__class__ = Line3DCollection col.set_segments(segments3d) + class Patch3D(Patch): ''' 3D patch object. @@ -322,10 +332,10 @@ def __init__(self, *args, **kwargs): PatchCollection.__init__(self, *args, **kwargs) self.set_3d_properties(zs, zdir) - - def set_sort_zpos(self,val): + def set_sort_zpos(self, val): '''Set the position to use for z-sorting.''' self._sort_zpos = val + self.stale = True def set_3d_properties(self, zs, zdir): # Force the collection to initialize the face and edgecolors @@ -340,6 +350,7 @@ def set_3d_properties(self, zs, zdir): self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir) self._facecolor3d = self.get_facecolor() self._edgecolor3d = self.get_edgecolor() + self.stale = True def do_3d_projection(self, renderer): xs, ys, zs = self._offsets3d @@ -356,9 +367,9 @@ def do_3d_projection(self, renderer): self.set_edgecolors(ecs) PatchCollection.set_offsets(self, list(zip(vxs, vys))) - if vzs.size > 0 : + if vzs.size > 0: return min(vzs) - else : + else: return np.nan @@ -392,6 +403,7 @@ def __init__(self, *args, **kwargs): def set_sort_zpos(self, val): '''Set the position to use for z-sorting.''' self._sort_zpos = val + self.stale = True def set_3d_properties(self, zs, zdir): # Force the collection to initialize the face and edgecolors @@ -406,6 +418,7 @@ def set_3d_properties(self, zs, zdir): self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir) self._facecolor3d = self.get_facecolor() self._edgecolor3d = self.get_edgecolor() + self.stale = True def do_3d_projection(self, renderer): xs, ys, zs = self._offsets3d @@ -453,6 +466,7 @@ def patch_collection_2d_to_3d(col, zs=0, zdir='z', depthshade=True): col._depthshade = depthshade col.set_3d_properties(zs, zdir) + class Poly3DCollection(PolyCollection): ''' A collection of 3D polygons. @@ -470,10 +484,9 @@ def __init__(self, verts, *args, **kwargs): Note that this class does a bit of magic with the _facecolors and _edgecolors properties. ''' - - self.set_zsort(kwargs.pop('zsort', True)) - + zsort = kwargs.pop('zsort', True) PolyCollection.__init__(self, verts, *args, **kwargs) + self.set_zsort(zsort) _zsort_functions = { 'average': np.average, @@ -502,6 +515,7 @@ def set_zsort(self, zsort): self._zsort = zsort self._sort_zpos = None self._zsortfunc = zsortfunc + self.stale = True def get_vector(self, segments3d): """Optimize points for projection""" @@ -540,10 +554,12 @@ def set_3d_properties(self): self._facecolors3d = PolyCollection.get_facecolors(self) self._edgecolors3d = PolyCollection.get_edgecolors(self) self._alpha3d = PolyCollection.get_alpha(self) + self.stale = True def set_sort_zpos(self,val): '''Set the position to use for z-sorting.''' self._sort_zpos = val + self.stale = True def do_3d_projection(self, renderer): ''' @@ -631,6 +647,7 @@ def set_alpha(self, alpha): self._edgecolors3d, self._alpha) except (AttributeError, TypeError, IndexError): pass + self.stale = True def get_facecolors(self): return self._facecolors2d @@ -643,6 +660,7 @@ def get_edgecolors(self): def draw(self, renderer): return Collection.draw(self, renderer) + def poly_collection_2d_to_3d(col, zs=0, zdir='z'): """Convert a PolyCollection to a Poly3DCollection object.""" segments_3d = paths_to_3d_segments(col.get_paths(), zs, zdir) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index b06186f6b955..3881370beab7 100755 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -126,9 +126,11 @@ def __init__(self, fig, rect=None, *args, **kwargs): def set_axis_off(self): self._axis3don = False + self.stale = True def set_axis_on(self): self._axis3don = True + self.stale = True def have_units(self): """ @@ -342,6 +344,7 @@ def set_zmargin(self, m) : if m < 0 or m > 1 : raise ValueError("margin must be in range 0 to 1") self._zmargin = m + self.stale = True def margins(self, *args, **kw) : """ @@ -610,7 +613,7 @@ def set_xlim3d(self, left=None, right=None, emit=True, auto=False, **kw): if (other.figure != self.figure and other.figure.canvas is not None): other.figure.canvas.draw_idle() - + self.stale = True return left, right set_xlim = set_xlim3d @@ -665,7 +668,7 @@ def set_ylim3d(self, bottom=None, top=None, emit=True, auto=False, **kw): if (other.figure != self.figure and other.figure.canvas is not None): other.figure.canvas.draw_idle() - + self.stale = True return bottom, top set_ylim = set_ylim3d @@ -719,7 +722,7 @@ def set_zlim3d(self, bottom=None, top=None, emit=True, auto=False, **kw): if (other.figure != self.figure and other.figure.canvas is not None): other.figure.canvas.draw_idle() - + self.stale = True return bottom, top set_zlim = set_zlim3d @@ -771,6 +774,7 @@ def set_yscale(self, value, **kwargs) : self.yaxis._set_scale(value, **kwargs) self.autoscale_view(scalex=False, scalez=False) self._update_transScale() + self.stale = True set_yscale.__doc__ = maxes.Axes.set_yscale.__doc__ + """ .. versionadded :: 1.1.0 @@ -802,6 +806,7 @@ def set_zscale(self, value, **kwargs) : self.zaxis._set_scale(value, **kwargs) self.autoscale_view(scalex=False, scaley=False) self._update_transScale() + self.stale = True def set_zticks(self, *args, **kwargs): """ @@ -1216,6 +1221,7 @@ def set_frame_on(self, b): .. versionadded :: 1.1.0 """ self._frameon = bool(b) + self.stale = True def get_axisbelow(self): """ @@ -1241,6 +1247,7 @@ def set_axisbelow(self, b): This function was added for completeness. """ self._axisbelow = True + self.stale = True def grid(self, b=True, **kwargs): ''' @@ -1259,6 +1266,7 @@ def grid(self, b=True, **kwargs): if len(kwargs) : b = True self._draw_grid = cbook._string_to_bool(b) + self.stale = True def ticklabel_format(self, **kwargs) : """ diff --git a/lib/mpl_toolkits/mplot3d/axis3d.py b/lib/mpl_toolkits/mplot3d/axis3d.py index f0eb7b8107e1..6170203d1342 100755 --- a/lib/mpl_toolkits/mplot3d/axis3d.py +++ b/lib/mpl_toolkits/mplot3d/axis3d.py @@ -141,6 +141,7 @@ def set_pane_pos(self, xys): xys = np.asarray(xys) xys = xys[:,:2] self.pane.xy = xys + self.stale = True def set_pane_color(self, color): '''Set pane color to a RGBA tuple''' @@ -148,6 +149,7 @@ def set_pane_color(self, color): self.pane.set_edgecolor(color) self.pane.set_facecolor(color) self.pane.set_alpha(color[-1]) + self.stale = True def set_rotate_label(self, val): ''' @@ -155,6 +157,7 @@ def set_rotate_label(self, val): If set to None the label will be rotated if longer than 4 chars. ''' self._rotate_label = val + self.stale = True def get_rotate_label(self, text): if self._rotate_label is not None: @@ -416,6 +419,7 @@ def draw(self, renderer): tick.draw(renderer) renderer.close_group('axis3d') + self.stale = False def get_view_interval(self): """return the Interval instance for this 3d axis view limits"""