From b83af3f7ebc1c5157e3a8df71b5d92bdae2e11af Mon Sep 17 00:00:00 2001 From: Ryan Morshead Date: Sun, 12 Jul 2015 11:41:36 -0500 Subject: [PATCH 1/8] begin merge of mpl and ipython traitlets --- lib/matplotlib/artist.py | 535 ++++++++++++++++---------------- lib/matplotlib/mpl_traitlets.py | 54 ++++ 2 files changed, 325 insertions(+), 264 deletions(-) create mode 100644 lib/matplotlib/mpl_traitlets.py diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index e6d428fba997..dda7f3fa7a62 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -1,7 +1,8 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -from matplotlib.externals import six +import six +import types import re import warnings @@ -14,6 +15,8 @@ TransformedPath, Transform) from .path import Path +import .mpl_traitlets as mpltr + # Note, matplotlib artists use the doc strings for set and get # methods to enable the introspection methods of setp and getp. Every # set_* method should have a docstring containing the line @@ -75,8 +78,7 @@ def _stale_figure_callback(self): def _stale_axes_callback(self): self.axes.stale = True - -class Artist(object): +class Artist(mpltr.Configurable): """ Abstract base class for someone who renders into a :class:`FigureCanvas`. @@ -85,13 +87,51 @@ class Artist(object): aname = 'Artist' zorder = 0 - def __init__(self): - self._stale = True + + # warn on all : check whether serialize is/isn't required. + + # perishable=True ==> set stale = True + _transformSet = Bool(False, serialize=True) + stale = Bool(True, serialize=True) + # warn : oInstance used, new TraitType? + transform = ipytr.oInstance(matplotlib.transforms.Transform, + serialize=True, perishable=True) + axes = ipytr.Instance(matplotlib.axes.Axes,allow_none=True, + serialize=True) + contains = Callable(allow_none=True) + figure = ipytr.Instance(matplotlib.figure.Figure, + allow_none=True,serialize=True, + perishable=True, prop=True) + visible = Bool(True, perishable=True) + animated = Bool(False, perishable=True) + alpha = Float(allow_none=True, perishable=True) + # clipbox = Instance(matplotlib.transforms.Bbox) + # clippath = Union([matplotlib.patches.Patch, + # matplotlib.path.Path], + # allow_none=True) + # clipon = Bool(True) + # label = String('') + # picker = Union([Bool,Float,Callable],allow_none=True) + # rasterized = Bool(allow_none=True) + # agg_filter = Callable(alow_none=True) + # eventson = Bool(False) + # oid = Int(0) + # propobservers = Dict(trait=Callable) + # eventson = Bool(False) + # axes = Instance(matplotlib.axes.Axes, allow_none=True) + # _remove_method = Instance(Callable, allow_none=True) + # url = String(allow_none=True) + # grid = String(allow_none=True) + # snap = Bool(allow_none=True) + # sketch = Tuple(allow_none=True) + # path_effects = List(trait=Instance(matplotlib.patheffect._Base)) + + def __init__(self, *args, **kwargs): + super(Artist,self).__init__(*args, **kwargs) + pnames = self.trait_names(self._fire_callbacks, perishable=True) + self._axes = None - self.figure = None - self._transform = None - self._transformSet = False self._visible = True self._animated = False self._alpha = None @@ -118,7 +158,7 @@ def __init__(self): self._snap = None self._sketch = rcParams['path.sketch'] self._path_effects = rcParams['path.effects'] - + def __getstate__(self): d = self.__dict__.copy() # remove the unpicklable remove method, this will get re-added on load @@ -126,156 +166,28 @@ def __getstate__(self): d['_remove_method'] = None return d - def remove(self): - """ - Remove the artist from the figure if possible. The effect - will not be visible until the figure is redrawn, e.g., with - :meth:`matplotlib.axes.Axes.draw_idle`. Call - :meth:`matplotlib.axes.Axes.relim` to update the axes limits - if desired. - - Note: :meth:`~matplotlib.axes.Axes.relim` will not see - collections even if the collection was added to axes with - *autolim* = True. - - Note: there is no support for removing the artist's legend entry. - """ - - # There is no method to set the callback. Instead the parent should - # set the _remove_method attribute directly. This would be a - # protected attribute if Python supported that sort of thing. The - # callback has one parameter, which is the child to be removed. - if self._remove_method is not None: - self._remove_method(self) - else: - raise NotImplementedError('cannot remove artist') - # TODO: the fix for the collections relim problem is to move the - # limits calculation into the artist itself, including the property of - # whether or not the artist should affect the limits. Then there will - # be no distinction between axes.add_line, axes.add_patch, etc. - # TODO: add legend support - - def have_units(self): - 'Return *True* if units are set on the *x* or *y* axes' - ax = self.axes - if ax is None or ax.xaxis is None: - return False - return ax.xaxis.have_units() or ax.yaxis.have_units() - - def convert_xunits(self, x): - """For artists in an axes, if the xaxis has units support, - convert *x* using xaxis unit type - """ - ax = getattr(self, 'axes', None) - if ax is None or ax.xaxis is None: - return x - return ax.xaxis.convert_units(x) - - def convert_yunits(self, y): - """For artists in an axes, if the yaxis has units support, - convert *y* using yaxis unit type - """ - ax = getattr(self, 'axes', None) - if ax is None or ax.yaxis is None: - return y - return ax.yaxis.convert_units(y) - - def set_axes(self, axes): - """ - Set the :class:`~matplotlib.axes.Axes` instance in which the - artist resides, if any. - - This has been deprecated in mpl 1.5, please use the - axes property. Will be removed in 1.7 or 2.0. - - ACCEPTS: an :class:`~matplotlib.axes.Axes` instance - """ - warnings.warn(_get_axes_msg, mplDeprecation, stacklevel=1) - self.axes = axes - - def get_axes(self): - """ - Return the :class:`~matplotlib.axes.Axes` instance the artist - resides in, or *None*. - - This has been deprecated in mpl 1.5, please use the - axes property. Will be removed in 1.7 or 2.0. - """ - warnings.warn(_get_axes_msg, mplDeprecation, stacklevel=1) - return self.axes - - @property - def axes(self): - """ - The :class:`~matplotlib.axes.Axes` instance the artist - resides in, or *None*. - """ - return self._axes - - @axes.setter - def axes(self, new_axes): - if self._axes is not None and new_axes != self._axes: - raise ValueError("Can not reset the axes. You are " - "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. - Subclasses should override for inclusion in the bounding box - "tight" calculation. Default is to return an empty bounding - box at 0, 0. - - Be careful when using this function, the results will not update - if the artist window extent of the artist changes. The extent - can change due to any changes in the transform stack, such as - changing the axes limits, the figure size, or the canvas used - (as is done when saving a figure). This can lead to unexpected - behavior where interactive figures will look fine on the screen, - but will save incorrectly. - """ - return Bbox([[0, 0], [0, 0]]) + # can be superseded by on_trait_change or _%_changed methods + def _fire_callbacks(self): + """Calls all of the registered callbacks.""" + self.stale = True + for oid, func in six.iteritems(self._propobservers): + func(self) + # can be superseded by on_trait_change or _%_changed methods def add_callback(self, func): """ Adds a callback function that will be called whenever one of - the :class:`Artist`'s properties changes. + the :class:`Artist`'s "perishable" properties changes. Returns an *id* that is useful for removing the callback with :meth:`remove_callback` later. """ oid = self._oid self._propobservers[oid] = func - self._oid += 1 - return oid + self_oid += 1 + return self.oid + # can be superseded by on_trait_change or _%_changed methods def remove_callback(self, oid): """ Remove a callback based on its *id*. @@ -291,109 +203,100 @@ def remove_callback(self, oid): except KeyError: pass - def pchanged(self): - """ - Fire an event when property changed, calling all of the - registered callbacks. - """ - for oid, func in six.iteritems(self._propobservers): - func(self) + # - - - - - - - - + # change handlers + # - - - - - - - - - def is_transform_set(self): - """ - Returns *True* if :class:`Artist` has a transform explicitly - set. - """ - return self._transformSet + def _transform_changed(self, name, new): + self._transformSet = True - def set_transform(self, t): - """ - Set the :class:`~matplotlib.transforms.Transform` instance - used by this artist. + def _transform_default(self): + return IdentityTransform() - ACCEPTS: :class:`~matplotlib.transforms.Transform` instance - """ - self._transform = t - self._transformSet = True - self.pchanged() - self.stale = True + def _transform_overload(self, trait, value): + if (not isinstance(value, Transform) + and hasattr(value, '_as_mpl_transform')): + return value._as_mpl_transform(self.axes) + else: + trait.error(self, value) def get_transform(self): - """ - Return the :class:`~matplotlib.transforms.Transform` - instance used by this artist. - """ - if self._transform is None: - self._transform = IdentityTransform() - elif (not isinstance(self._transform, Transform) - and hasattr(self._transform, '_as_mpl_transform')): - self._transform = self._transform._as_mpl_transform(self.axes) - return self._transform + # add warn + return self.transform - def hitlist(self, event): - """ - List the children of the artist which contain the mouse event *event*. - """ - L = [] - try: - hascursor, info = self.contains(event) - if hascursor: - L.append(self) - except: - import traceback - traceback.print_exc() - print("while checking", self.__class__) + def set_transform(self, t): + # add warn + self.transform = t - for a in self.get_children(): - L.extend(a.hitlist(event)) - return L + def _axes_changed(self, name, old, new): + if old is not None: + # old != true already checked in `TraitType._validate` + raise ValueError("Can not reset the axes. You are " + "probably trying to re-use an artist " + "in more than one Axes which is not " + "supported") + self.axes = new + if new is not None and new is not self: + self.add_callback(_stale_axes_callback) + # return ? + return new - def get_children(self): - """ - Return a list of the child :class:`Artist`s this - :class:`Artist` contains. + def set_axes(self, axes): """ - return [] + Set the :class:`~matplotlib.axes.Axes` instance in which the + artist resides, if any. - def contains(self, mouseevent): - """Test whether the artist contains the mouse event. + This has been deprecated in mpl 1.5, please use the + axes property. Will be removed in 1.7 or 2.0. - Returns the truth value and a dictionary of artist specific details of - selection, such as which points are contained in the pick radius. See - individual artists for details. + ACCEPTS: an :class:`~matplotlib.axes.Axes` instance """ - if six.callable(self._contains): - return self._contains(self, mouseevent) - warnings.warn("'%s' needs 'contains' method" % self.__class__.__name__) - return False, {} + warnings.warn(_get_axes_msg, mplDeprecation, stacklevel=1) + self.axes = axes - def set_contains(self, picker): + def get_axes(self): """ - Replace the contains test used by this artist. The new picker - should be a callable function which determines whether the - artist is hit by the mouse event:: + Return the :class:`~matplotlib.axes.Axes` instance the artist + resides in, or *None*. - hit, props = picker(artist, mouseevent) + This has been deprecated in mpl 1.5, please use the + axes property. Will be removed in 1.7 or 2.0. + """ + warnings.warn(_get_axes_msg, mplDeprecation, stacklevel=1) + return self.axes - If the mouse event is over the artist, return *hit* = *True* - and *props* is a dictionary of properties you want returned - with the contains test. + # only present to maintain current api. + def _contains_changed(self, name, new): + self._trait_values[name] = types.MethodType(new,self) - ACCEPTS: a callable function - """ - self._contains = picker + def _contains_default(self): + def contains_defualt(*args, **kwargs): + warnings.warn("'%s' obj needs 'contains' method" % self.__class__.__name__) + return False, {} + return contains_default - def get_contains(self): + def _figure_changed(self, name, old, new): + self.figure = new + if old and old is not self: + self.add_callback(_stale_figure_callback) + self.pchanged() + self.stale = True + + def get_figure(self): """ - Return the _contains test used by the artist, or *None* for default. + Return the :class:`~matplotlib.figure.Figure` instance the + artist belongs to. """ - return self._contains + return self.figure - def pickable(self): - 'Return *True* if :class:`Artist` is pickable.' - return (self.figure is not None and - self.figure.canvas is not None and - self._picker is not None) + def set_figure(self, fig): + """ + Set the :class:`~matplotlib.figure.Figure` instance the artist + belongs to. + + ACCEPTS: a :class:`matplotlib.figure.Figure` instance + """ + self.figure = fig def pick(self, mouseevent): """ @@ -466,6 +369,119 @@ def get_picker(self): 'Return the picker object used by this artist' return self._picker +# - - - - - - - - +# other handlers +# - - - - - - - - + + def remove(self): + """ + Remove the artist from the figure if possible. The effect + will not be visible until the figure is redrawn, e.g., with + :meth:`matplotlib.axes.Axes.draw_idle`. Call + :meth:`matplotlib.axes.Axes.relim` to update the axes limits + if desired. + + Note: :meth:`~matplotlib.axes.Axes.relim` will not see + collections even if the collection was added to axes with + *autolim* = True. + + Note: there is no support for removing the artist's legend entry. + """ + + # There is no method to set the callback. Instead the parent should + # set the _remove_method attribute directly. This would be a + # protected attribute if Python supported that sort of thing. The + # callback has one parameter, which is the child to be removed. + if self._remove_method is not None: + self._remove_method(self) + else: + raise NotImplementedError('cannot remove artist') + # TODO: the fix for the collections relim problem is to move the + # limits calculation into the artist itself, including the property of + # whether or not the artist should affect the limits. Then there will + # be no distinction between axes.add_line, axes.add_patch, etc. + # TODO: add legend support + + def have_units(self): + 'Return *True* if units are set on the *x* or *y* axes' + ax = self.axes + if ax is None or ax.xaxis is None: + return False + return ax.xaxis.have_units() or ax.yaxis.have_units() + + def convert_xunits(self, x): + """For artists in an axes, if the xaxis has units support, + convert *x* using xaxis unit type + """ + ax = getattr(self, 'axes', None) + if ax is None or ax.xaxis is None: + return x + return ax.xaxis.convert_units(x) + + def convert_yunits(self, y): + """For artists in an axes, if the yaxis has units support, + convert *y* using yaxis unit type + """ + ax = getattr(self, 'axes', None) + if ax is None or ax.yaxis is None: + return y + return ax.yaxis.convert_units(y) + + def is_transform_set(self): + """ + Returns *True* if :class:`Artist` has a transform explicitly + set. + """ + return self._transformSet + + def get_window_extent(self, renderer): + """ + Get the axes bounding box in display space. + Subclasses should override for inclusion in the bounding box + "tight" calculation. Default is to return an empty bounding + box at 0, 0. + + Be careful when using this function, the results will not update + if the artist window extent of the artist changes. The extent + can change due to any changes in the transform stack, such as + changing the axes limits, the figure size, or the canvas used + (as is done when saving a figure). This can lead to unexpected + behavior where interactive figures will look fine on the screen, + but will save incorrectly. + """ + return Bbox([[0, 0], [0, 0]]) + + def hitlist(self, event): + """ + List the children of the artist which contain the mouse event *event*. + """ + L = [] + try: + hascursor, info = self.contains(event) + if hascursor: + L.append(self) + except: + import traceback + traceback.print_exc() + print("while checking", self.__class__) + + for a in self.get_children(): + L.extend(a.hitlist(event)) + return L + + def get_children(self): + """ + Return a list of the child :class:`Artist`s this + :class:`Artist` contains. + """ + return [] + + def pickable(self): + 'Return *True* if :class:`Artist` is pickable.' + return (self.figure is not None and + self.figure.canvas is not None and + self._picker is not None) + def is_figure_set(self): """ Returns True if the artist is assigned to a @@ -594,26 +610,6 @@ def set_path_effects(self, path_effects): def get_path_effects(self): return self._path_effects - def get_figure(self): - """ - Return the :class:`~matplotlib.figure.Figure` instance the - artist belongs to. - """ - return self.figure - - def set_figure(self, fig): - """ - Set the :class:`~matplotlib.figure.Figure` instance the artist - belongs to. - - ACCEPTS: a :class:`matplotlib.figure.Figure` instance - """ - self.figure = fig - 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): """ Set the artist's clip :class:`~matplotlib.transforms.Bbox`. @@ -650,8 +646,7 @@ def set_clip_path(self, path, transform=None): success = False if transform is None: if isinstance(path, Rectangle): - self.clipbox = TransformedBbox(Bbox.unit(), - path.get_transform()) + self.clipbox = TransformedBbox(Bbox.unit(), path.get_transform()) self._clippath = None success = True elif isinstance(path, Patch): @@ -687,13 +682,7 @@ def get_alpha(self): """ return self._alpha - def get_visible(self): - "Return the artist's visiblity" - return self._visible - - def get_animated(self): - "Return the artist's animated state" - return self._animated + def get_clip_on(self): 'Return whether artist uses clipping' @@ -788,13 +777,27 @@ def set_alpha(self, alpha): self.pchanged() self.stale = True + def _visible_changed(self, name, new): + self.visible = new + self.pchanged() + self.stale = True + def set_visible(self, b): """ Set the artist's visiblity. ACCEPTS: [True | False] """ - self._visible = b + # add warn + self.visible = b + + def get_visible(self): + "Return the artist's visiblity" + # add warn + return self._visible + + def _animated_changed(self, name, new): + self.animated = new self.pchanged() self.stale = True @@ -804,9 +807,13 @@ def set_animated(self, b): ACCEPTS: [True | False] """ - self._animated = b - self.pchanged() - self.stale = True + # add warn + self.animated = b + + def get_animated(self): + "Return the artist's animated state" + # add warn + return self.animated def update(self, props): """ @@ -869,7 +876,7 @@ def set_zorder(self, level): def update_from(self, other): 'Copy properties from *other* to *self*.' - self._transform = other._transform + self.transform = other.transform self._transformSet = other._transformSet self._visible = other._visible self._alpha = other._alpha diff --git a/lib/matplotlib/mpl_traitlets.py b/lib/matplotlib/mpl_traitlets.py new file mode 100644 index 000000000000..e49476d74b5d --- /dev/null +++ b/lib/matplotlib/mpl_traitlets.py @@ -0,0 +1,54 @@ +from __future__ import (absolute_import, division, + print_function, unicode_literals) + +from traitlets.config import (Configurable, TraitType) as (ipyConfigurbale, ipyTraitType) + +from traitlets import (Int, Float, Bool, Dict, List, Instance, + Union, TraitError, HasTraits, NoDefaultSpecified) + +class Configurable(ipyConfigurable): pass + +class OverloadMixin(object): + + def validate(self, obj, value): + try: + return super(OverloadMixin,self).validate(obj,value) + except TraitError: + if self.name: + ohandle = '_%s_overload'%self.name + if hasattr(obj, ohandle): + return getattr(obj, ohandle)(self, value) + self.error(obj, value) + + def info(self): + i = super(OverloadMixin,self).info() + return 'overload resolvable, ' + i + +class String(TraitType): + """A string trait""" + + info_text = 'a string' + + def validate(self, obj, value): + if isinstance(value, str): + return value + self.error(obj, value) + + +class Callable(TraitType): + """A trait which is callable. + + Notes + ----- + Classes are callable, as are instances + with a __call__() method.""" + + info_text = 'a callable' + + def validate(self, obj, value): + if callable(value): + return value + else: + self.error(obj, value) + +class oInstance(OverloadMixin,Instance): pass \ No newline at end of file From 0d88251ef5f300c4285a055af755a5a00b7c056d Mon Sep 17 00:00:00 2001 From: Andre Lobato Date: Sun, 12 Jul 2015 12:27:20 -0500 Subject: [PATCH 2/8] Added Color TraitType and tests --- lib/matplotlib/mpl_traitlets.py | 139 ++++++++++++++++++++++++- lib/matplotlib/tests/test_traitlets.py | 109 +++++++++++++++++++ 2 files changed, 243 insertions(+), 5 deletions(-) create mode 100644 lib/matplotlib/tests/test_traitlets.py diff --git a/lib/matplotlib/mpl_traitlets.py b/lib/matplotlib/mpl_traitlets.py index e49476d74b5d..5c78cf03102c 100644 --- a/lib/matplotlib/mpl_traitlets.py +++ b/lib/matplotlib/mpl_traitlets.py @@ -1,12 +1,20 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -from traitlets.config import (Configurable, TraitType) as (ipyConfigurbale, ipyTraitType) +#from traitlets.config import Configurable +#from traitlets import (Int, Float, Bool, Dict, List, Instance, +# Union, TraitError, HasTraits, +# NoDefaultSpecified, TraitType) -from traitlets import (Int, Float, Bool, Dict, List, Instance, - Union, TraitError, HasTraits, NoDefaultSpecified) +from IPython.config import Configurable +from IPython.utils.traitlets import (Int, Float, Bool, Dict, List, Instance, + Union, TraitError, HasTraits, + NoDefaultSpecified, TraitType) +import numpy as np -class Configurable(ipyConfigurable): pass +class Configurable(Configurable): pass + +class TraitType(TraitType): pass class OverloadMixin(object): @@ -51,4 +59,125 @@ def validate(self, obj, value): else: self.error(obj, value) -class oInstance(OverloadMixin,Instance): pass \ No newline at end of file +class oInstance(OverloadMixin,Instance): pass + +class Color(TraitType): + """A trait representing a color, can be either in RGB, or RGBA format. + + Arguments: + force_rgb: bool: Force the return in RGB format instead of RGB. Default: False + as_hex: bool: Return the hex value instead. Default: False + default_alpha: float (0.0-1.0) or integer (0-255) default alpha value. + + Accepts: + string: a valid hex color string (i.e. #FFFFFF). 7 chars + tuple: a tuple of ints (0-255), or tuple of floats (0.0-1.0) + float: A gray shade (0-1) + integer: A gray shade (0-255) + + Defaults: RGBA tuple, color black (0.0, 0.0, 0.0, 0.0) + + Return: + A hex color string, a rgb or a rgba tuple. Defaults to rgba. When + returning hex string, the alpha property will be ignored. A warning + will be emitted if alpha information is passed different then 0.0 + + """ + metadata = { + 'force_rgb': False, + 'as_hex' : False, + 'default_alpha' : 0.0, + } + allow_none = False + info_text = 'float, int, tuple of float or int, or a hex string color' + default_value = (0.0,0.0,0.0,0.0) + named_colors = {} + + def _int_to_float(self, value): + as_float = (np.array(value)/255).tolist() + return as_float + + def _float_to_hex(self, value): + as_hex = '#%02x%02x%02x' % tuple([int(np.round(v * 255)) for v in\ + value[:3]]) + return as_hex + + def _int_to_hex(self, value): + as_hex = '#%02x%02x%02x' % value[:3] + return as_hex + + def _hex_to_float(self, value): + # Expects #FFFFFF format + split_hex = (value[1:3],value[3:5],value[5:7]) + as_float = (np.array([int(v,16) for v in split_hex])/255.0).tolist() + return as_float + + def _is_hex16(self, value): + try: + int(value, 16) + return True + except: + return False + + def _float_to_shade(self, value): + grade = value*255.0 + return (grade,grade,grade) + + def _int_to_shade(self, value): + grade = value/255.0 + return (grade,grade,grade) + + def validate(self, obj, value): + in_range = False + if value is None or value is False or value in ['none','']: + # Return transparent if no other default alpha was set + return (0.0, 0.0, 0.0, 1.0) + + if isinstance(value, float) and 0 <= value <= 1: + value = self._float_to_shade(value) + else: + in_range = False + + if isinstance(value, int) and 0 <= value <= 255: + value = self._int_to_shade(value) + else: + in_range = False + + if isinstance(value, (tuple, list)) and len(value) in (3,4): + is_all_float = np.prod([isinstance(v, (float)) for v in value]) + in_range = np.prod([(0 <= v <= 1) for v in value]) + if is_all_float and in_range: + value = value + else: + is_all_int = np.prod([isinstance(v, int) for v in value]) + in_range = np.prod([(0 <= v <= 255) for v in value]) + if is_all_int and in_range: + value = self._int_to_float(value) + + if isinstance(value, str) and len(value) == 7 and value[0] == '#': + is_all_hex16 = np.prod([self._is_hex16(v) for v in\ + (value[1:3],value[3:5],value[5:7])]) + if is_all_hex16: + value = self._hex_to_float(value) + in_range = np.prod([(0 <= v <= 1) for v in value]) + if in_range: + value = value + + elif isinstance(value, str) and value in self.named_colors: + value = self.validate(obj, self.named_colors[value]) + in_range = True + + if in_range: + if self._metadata['as_hex']: + return self._float_to_hex(value) + if self._metadata['force_rgb'] and in_range: + return tuple(np.round(value[:3],5).tolist()) + else: + if len(value) == 3: + value = tuple(np.round((value[0], value[1], value[2], + self._metadata['default_alpha']),5).tolist()) + elif len(value) == 4: + value = tuple(np.round(value,5).tolist()) + return value + + self.error(obj, value) \ No newline at end of file diff --git a/lib/matplotlib/tests/test_traitlets.py b/lib/matplotlib/tests/test_traitlets.py new file mode 100644 index 000000000000..c51674c9514a --- /dev/null +++ b/lib/matplotlib/tests/test_traitlets.py @@ -0,0 +1,109 @@ +from __future__ import absolute_import + +from nose.tools import * +from unittest import TestCase +from matplotlib.mpl_traitlets import Color, HasTraits + +class ColorTestCase(TestCase): + """Tests for the Color traits""" + + def setUp(self): + self.transparent_values = [None, False, '', 'none'] + self.black_values = ['#000000', (0,0,0,0), 0, 0.0, (.0,.0,.0), (.0,.0,.0,.0)] + self.colored_values = ['#BE3537', (190,53,55), (0.7451, 0.20784, 0.21569)] + self.unvalid_values = ['áfaef', '#FFF', '#0SX#$S', (0,0,0), (0.45,0.3), (()), {}, True] + + def _evaluate_unvalids(self, a): + for values in self.unvalid_values: + try: + a.color = values + except: + assert_raises(TypeError) + + def test_noargs(self): + class A(HasTraits): + color = Color() + a = A() + for values in self.black_values: + a.color = values + assert_equal(a.color, (0.0,0.0,0.0,0.0)) + + for values in self.colored_values: + a.color = values + assert_equal(a.color, (0.7451, 0.20784, 0.21569, 0.0)) + self._evaluate_unvalids(a) + + + def test_hexcolor(self): + class A(HasTraits): + color = Color(as_hex=True) + + a = A() + + for values in self.black_values: + a.color = values + assert_equal(a.color, '#000000') + + for values in self.colored_values: + a.color = values + assert_equal(a.color, '#be3537') + + self._evaluate_unvalids(a) + + def test_rgb(self): + class A(HasTraits): + color = Color(force_rgb=True) + + a = A() + + for values in self.black_values: + a.color = values + assert_equal(a.color, (0.0,0.0,0.0)) + + for values in self.colored_values: + a.color = values + assert_equal(a.color, (0.7451, 0.20784, 0.21569)) + + self._evaluate_unvalids(a) + + def test_named(self): + ncolors = {'hexblue': '#0000FF', + 'floatbllue': (0.0,0.0,1.0), + 'intblue' : (0,0,255)} + + class A(HasTraits): + color = Color() + color.named_colors = ncolors + + a = A() + + for colorname in ncolors: + a.color = colorname + assert_equal(a.color, (0.0,0.0,1.0,0.0)) + + def test_alpha(self): + class A(HasTraits): + color = Color(default_alpha=0.4) + + a = A() + + assert_equal(a.color, (0.0, 0.0, 0.0, 0.0)) + + for values in self.transparent_values: + a.color = values + assert_equal(a.color, (0.0,0.0,0.0,1.0)) + + for values in self.black_values: + a.color = values + if isinstance(values, (tuple,list)) and len(values) == 4: + assert_equal(a.color, (0.0,0.0,0.0,0.0)) + else: + assert_equal(a.color, (0.0,0.0,0.0,0.4)) + + for values in self.colored_values: + a.color = values + assert_equal(a.color, (0.7451, 0.20784, 0.21569, 0.4)) + +if __name__ == '__main__': + import nose + nose.runmodule(argv=['-s', '--with-doctest'], exit=False) From 19a9ca218ebb0e534d27bfdc603424e2a65e494f Mon Sep 17 00:00:00 2001 From: Ryan Morshead Date: Sun, 12 Jul 2015 15:25:42 -0500 Subject: [PATCH 3/8] update mpl traitlets --- lib/matplotlib/artist.py | 29 ++++++++++---- lib/matplotlib/tests/test_traitlets.py | 4 +- .../{mpl_traitlets.py => traitlets.py} | 38 ++++++++----------- 3 files changed, 38 insertions(+), 33 deletions(-) rename lib/matplotlib/{mpl_traitlets.py => traitlets.py} (88%) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index dda7f3fa7a62..03963b609195 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -15,7 +15,7 @@ TransformedPath, Transform) from .path import Path -import .mpl_traitlets as mpltr +import .traitlets as mpltr # Note, matplotlib artists use the doc strings for set and get # methods to enable the introspection methods of setp and getp. Every @@ -105,6 +105,7 @@ class Artist(mpltr.Configurable): visible = Bool(True, perishable=True) animated = Bool(False, perishable=True) alpha = Float(allow_none=True, perishable=True) + pickable = Bool(False) # clipbox = Instance(matplotlib.transforms.Bbox) # clippath = Union([matplotlib.patches.Patch, # matplotlib.path.Path], @@ -129,7 +130,8 @@ class Artist(mpltr.Configurable): def __init__(self, *args, **kwargs): super(Artist,self).__init__(*args, **kwargs) pnames = self.trait_names(self._fire_callbacks, perishable=True) - + self.on_trait_change(self._fire_callbacks, pnames) + self.on_trait_change(self._update_pickable, ('picker', 'figure')) self._axes = None self._visible = True @@ -166,6 +168,16 @@ def __getstate__(self): d['_remove_method'] = None return d + def _update_pickable(self, name, new): + pickable = self.pickable + if name == 'figure': + if new is None or new.canvas is None: + pickable = False + elif name == 'picker': + if new is None: + packable = False + self.pickable = pickable + # can be superseded by on_trait_change or _%_changed methods def _fire_callbacks(self): """Calls all of the registered callbacks.""" @@ -203,9 +215,9 @@ def remove_callback(self, oid): except KeyError: pass - # - - - - - - - - - # change handlers - # - - - - - - - - +# - - - - - - - - +# change handlers +# - - - - - - - - def _transform_changed(self, name, new): self._transformSet = True @@ -265,7 +277,7 @@ def get_axes(self): warnings.warn(_get_axes_msg, mplDeprecation, stacklevel=1) return self.axes - # only present to maintain current api. + # required until new api : `obj.contains(obj, mouseevent)` def _contains_changed(self, name, new): self._trait_values[name] = types.MethodType(new,self) @@ -276,10 +288,8 @@ def contains_defualt(*args, **kwargs): return contains_default def _figure_changed(self, name, old, new): - self.figure = new if old and old is not self: self.add_callback(_stale_figure_callback) - self.pchanged() self.stale = True def get_figure(self): @@ -363,10 +373,12 @@ def set_picker(self, picker): ACCEPTS: [None|float|boolean|callable] """ + # add warn self._picker = picker def get_picker(self): 'Return the picker object used by this artist' + # add warn return self._picker # - - - - - - - - @@ -478,6 +490,7 @@ def get_children(self): def pickable(self): 'Return *True* if :class:`Artist` is pickable.' + return self.pickable return (self.figure is not None and self.figure.canvas is not None and self._picker is not None) diff --git a/lib/matplotlib/tests/test_traitlets.py b/lib/matplotlib/tests/test_traitlets.py index c51674c9514a..6f80bd6255c4 100644 --- a/lib/matplotlib/tests/test_traitlets.py +++ b/lib/matplotlib/tests/test_traitlets.py @@ -11,10 +11,10 @@ def setUp(self): self.transparent_values = [None, False, '', 'none'] self.black_values = ['#000000', (0,0,0,0), 0, 0.0, (.0,.0,.0), (.0,.0,.0,.0)] self.colored_values = ['#BE3537', (190,53,55), (0.7451, 0.20784, 0.21569)] - self.unvalid_values = ['áfaef', '#FFF', '#0SX#$S', (0,0,0), (0.45,0.3), (()), {}, True] + self.invalid_values = ['áfaef', '#FFF', '#0SX#$S', (0,0,0), (0.45,0.3), (()), {}, True] def _evaluate_unvalids(self, a): - for values in self.unvalid_values: + for values in self.invalid_values: try: a.color = values except: diff --git a/lib/matplotlib/mpl_traitlets.py b/lib/matplotlib/traitlets.py similarity index 88% rename from lib/matplotlib/mpl_traitlets.py rename to lib/matplotlib/traitlets.py index 5c78cf03102c..3cfc8d76961b 100644 --- a/lib/matplotlib/mpl_traitlets.py +++ b/lib/matplotlib/traitlets.py @@ -1,21 +1,24 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -#from traitlets.config import Configurable -#from traitlets import (Int, Float, Bool, Dict, List, Instance, -# Union, TraitError, HasTraits, -# NoDefaultSpecified, TraitType) - -from IPython.config import Configurable -from IPython.utils.traitlets import (Int, Float, Bool, Dict, List, Instance, - Union, TraitError, HasTraits, - NoDefaultSpecified, TraitType) +#ipython 4 import +from traitlets.config import Configurable +from traitlets import (Int, Float, Bool, Dict, List, Instance, + Union, TraitError, HasTraits, + NoDefaultSpecified, TraitType) + +# ipython 3 imports +# from IPython.config import Configurable +# from IPython.utils.traitlets import (Int, Float, Bool, Dict, List, Instance, +# Union, TraitError, HasTraits, +# NoDefaultSpecified, TraitType) import numpy as np +# override for backward compatability class Configurable(Configurable): pass - class TraitType(TraitType): pass +# overload handle may not be temporary class OverloadMixin(object): def validate(self, obj, value): @@ -32,16 +35,7 @@ def info(self): i = super(OverloadMixin,self).info() return 'overload resolvable, ' + i -class String(TraitType): - """A string trait""" - - info_text = 'a string' - - def validate(self, obj, value): - if isinstance(value, str): - return value - self.error(obj, value) - +class oInstance(OverloadMixin,Instance): pass class Callable(TraitType): """A trait which is callable. @@ -59,8 +53,6 @@ def validate(self, obj, value): else: self.error(obj, value) -class oInstance(OverloadMixin,Instance): pass - class Color(TraitType): """A trait representing a color, can be either in RGB, or RGBA format. @@ -88,7 +80,7 @@ class Color(TraitType): 'as_hex' : False, 'default_alpha' : 0.0, } - allow_none = False + allow_none = True info_text = 'float, int, tuple of float or int, or a hex string color' default_value = (0.0,0.0,0.0,0.0) named_colors = {} From 9c3c6397b7cadc36dc64e8ebdeb94ebb4a712cdb Mon Sep 17 00:00:00 2001 From: Ryan Morshead Date: Tue, 14 Jul 2015 18:57:28 -0700 Subject: [PATCH 4/8] bad plot from pyplot --- lib/matplotlib/artist.py | 972 ++++++++++++++++++++---------------- lib/matplotlib/traitlets.py | 7 +- 2 files changed, 543 insertions(+), 436 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 03963b609195..ad4030691011 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -15,13 +15,14 @@ TransformedPath, Transform) from .path import Path -import .traitlets as mpltr +from .traitlets import (Configurable, Unicode, Bool, Int, Float, Bool, Tuple, + Dict, List, Instance, Union, Callable, oInstance, Undefined) # Note, matplotlib artists use the doc strings for set and get # methods to enable the introspection methods of setp and getp. Every # set_* method should have a docstring containing the line # -# ACCEPTS: [ legal | values ] +# ACCEPTS: [ legal | values ]s # # and aliases for setters and getters should have a docstring that # starts with 'alias for ', as in 'alias for set_somemethod' @@ -78,7 +79,7 @@ def _stale_figure_callback(self): def _stale_axes_callback(self): self.axes.stale = True -class Artist(mpltr.Configurable): +class Artist(Configurable): """ Abstract base class for someone who renders into a :class:`FigureCanvas`. @@ -92,72 +93,54 @@ class Artist(mpltr.Configurable): # perishable=True ==> set stale = True _transformSet = Bool(False, serialize=True) - stale = Bool(True, serialize=True) # warn : oInstance used, new TraitType? - transform = ipytr.oInstance(matplotlib.transforms.Transform, - serialize=True, perishable=True) - axes = ipytr.Instance(matplotlib.axes.Axes,allow_none=True, - serialize=True) + transform = oInstance('matplotlib.transforms.Transform', + serialize=True, perishable=True) + axes = Instance('matplotlib.axes._axes.Axes',allow_none=True, + serialize=True) contains = Callable(allow_none=True) - figure = ipytr.Instance(matplotlib.figure.Figure, - allow_none=True,serialize=True, - perishable=True, prop=True) - visible = Bool(True, perishable=True) - animated = Bool(False, perishable=True) - alpha = Float(allow_none=True, perishable=True) - pickable = Bool(False) - # clipbox = Instance(matplotlib.transforms.Bbox) - # clippath = Union([matplotlib.patches.Patch, - # matplotlib.path.Path], - # allow_none=True) - # clipon = Bool(True) - # label = String('') - # picker = Union([Bool,Float,Callable],allow_none=True) - # rasterized = Bool(allow_none=True) - # agg_filter = Callable(alow_none=True) - # eventson = Bool(False) - # oid = Int(0) - # propobservers = Dict(trait=Callable) - # eventson = Bool(False) - # axes = Instance(matplotlib.axes.Axes, allow_none=True) - # _remove_method = Instance(Callable, allow_none=True) - # url = String(allow_none=True) - # grid = String(allow_none=True) - # snap = Bool(allow_none=True) - # sketch = Tuple(allow_none=True) - # path_effects = List(trait=Instance(matplotlib.patheffect._Base)) - - def __init__(self, *args, **kwargs): - super(Artist,self).__init__(*args, **kwargs) - pnames = self.trait_names(self._fire_callbacks, perishable=True) + figure = Instance('matplotlib.figure.Figure', allow_none=True, + serialize=True, perishable=True) + visible = Bool(True, perishable=True, serialize=True) + animated = Bool(False, perishable=True, serialize=True) + alpha = Float(allow_none=True, perishable=True, serialize=True) + url = Unicode(allow_none=True, serialize=True) + gid = Unicode(allow_none=True, serialize=True) + clipbox = Instance('matplotlib.transforms.BboxBase', allow_none=True, + perishable=True, serialize=True) + snap = Bool(allow_none=True, perishable=True) + clipon = Bool(True, perishable=True) + # * setter and getter methods for `self._clippath` could be refactored + # using TraitTypes potentially ==> clippath = ? + label = Union([Unicode(''),Instance('matplotlib.text.Text')],allow_none=True, perishable=True) + rasterized = Bool(allow_none=True) + _agg_filter = Callable(None,allow_none=True, perishable=True) + eventson = Bool(False) + _sketch = Tuple(rcParams['path.sketch'], allow_none=True, + perishable=True,serialize=True) + _path_effects = List(trait=Instance('matplotlib.patheffects.AbstractPathEffect'), + perishable=True, serialize=True) + _propobservers = Dict({}) # a dict from oids to funcs + _oid = Int(0) # an observer id + + # sketch = mpltr.Tuple(allow_none=True) + # path_effects = mpltr. + + def __init__(self, config=None, parent=None): + + super(Artist, self).__init__(config=config, parent=parent) + + pnames = self.trait_names(perishable=True) self.on_trait_change(self._fire_callbacks, pnames) - self.on_trait_change(self._update_pickable, ('picker', 'figure')) - self._axes = None - self._visible = True - self._animated = False - self._alpha = None - self.clipbox = None + self.stale = True + self._pickable = False self._clippath = None - self._clipon = True - self._label = '' self._picker = None - self._contains = None - self._rasterized = None - self._agg_filter = None - - self.eventson = False # fire events only if eventson - self._oid = 0 # an observer id - self._propobservers = {} # a dict from oids to funcs - try: - self.axes = None - except AttributeError: - # Handle self.axes as a read-only property, as in Figure. - pass + # self._oid = 0 + # self._propobservers = {} self._remove_method = None - self._url = None - self._gid = None - self._snap = None + self._sketch = rcParams['path.sketch'] self._path_effects = rcParams['path.effects'] @@ -168,19 +151,14 @@ def __getstate__(self): d['_remove_method'] = None return d - def _update_pickable(self, name, new): - pickable = self.pickable - if name == 'figure': - if new is None or new.canvas is None: - pickable = False - elif name == 'picker': - if new is None: - packable = False - self.pickable = pickable + # handled by _fire_callbacks + def pchanged(self): + # add warn + self._fire_callbacks() # can be superseded by on_trait_change or _%_changed methods def _fire_callbacks(self): - """Calls all of the registered callbacks.""" + """Set as stale and fire the registered callbacks.""" self.stale = True for oid, func in six.iteritems(self._propobservers): func(self) @@ -196,8 +174,8 @@ def add_callback(self, func): """ oid = self._oid self._propobservers[oid] = func - self_oid += 1 - return self.oid + self._oid += 1 + return self._oid # can be superseded by on_trait_change or _%_changed methods def remove_callback(self, oid): @@ -215,9 +193,9 @@ def remove_callback(self, oid): except KeyError: pass -# - - - - - - - - -# change handlers -# - - - - - - - - + # - - - - - - - - - - - - - + # traitlet change handlers + # - - - - - - - - - - - - - def _transform_changed(self, name, new): self._transformSet = True @@ -232,16 +210,8 @@ def _transform_overload(self, trait, value): else: trait.error(self, value) - def get_transform(self): - # add warn - return self.transform - - def set_transform(self, t): - # add warn - self.transform = t - def _axes_changed(self, name, old, new): - if old is not None: + if old is not Undefined: # old != true already checked in `TraitType._validate` raise ValueError("Can not reset the axes. You are " "probably trying to re-use an artist " @@ -250,34 +220,7 @@ def _axes_changed(self, name, old, new): self.axes = new if new is not None and new is not self: self.add_callback(_stale_axes_callback) - # return ? - return new - - def set_axes(self, axes): - """ - Set the :class:`~matplotlib.axes.Axes` instance in which the - artist resides, if any. - - This has been deprecated in mpl 1.5, please use the - axes property. Will be removed in 1.7 or 2.0. - - ACCEPTS: an :class:`~matplotlib.axes.Axes` instance - """ - warnings.warn(_get_axes_msg, mplDeprecation, stacklevel=1) - self.axes = axes - - def get_axes(self): - """ - Return the :class:`~matplotlib.axes.Axes` instance the artist - resides in, or *None*. - This has been deprecated in mpl 1.5, please use the - axes property. Will be removed in 1.7 or 2.0. - """ - warnings.warn(_get_axes_msg, mplDeprecation, stacklevel=1) - return self.axes - - # required until new api : `obj.contains(obj, mouseevent)` def _contains_changed(self, name, new): self._trait_values[name] = types.MethodType(new,self) @@ -287,16 +230,46 @@ def contains_defualt(*args, **kwargs): return False, {} return contains_default - def _figure_changed(self, name, old, new): - if old and old is not self: - self.add_callback(_stale_figure_callback) - self.stale = True + def _figure_changed(self, name, new): + self.add_callback(_stale_figure_callback) + + def _snap_changed(self, name, new): + if not rcParams['path.snap']: + self._trait_values[name] = False + + def _rasterized_changed(self, name, new): + if new and not hasattr(self.draw, "_supports_rasterization"): + warnings.warn("Rasterization of '%s' will be ignored" % self) + + def _eventson_changed(self): + # add warn + # this feature will be removed + # it's handled by configurables + pass + + def _picker_changed(self, name, new): + if new is None: + self._pickable = False + self._pickable = True + + # - - - - - - - - - - - - - - - + # warned setters and getters + # - - - - - - - - - - - - - - - + + def get_transform(self): + # add warn + return self.transform + + def set_transform(self, t): + # add warn + self.transform = t def get_figure(self): """ Return the :class:`~matplotlib.figure.Figure` instance the artist belongs to. """ + # add warn return self.figure def set_figure(self, fig): @@ -306,40 +279,279 @@ def set_figure(self, fig): ACCEPTS: a :class:`matplotlib.figure.Figure` instance """ + # add warn self.figure = fig - def pick(self, mouseevent): + + @property + def _url(self): + #add warn + return self.url + @_url.setter + def _url(self, value): + # add warn + self.url = value + + def get_url(self): """ - call signature:: + Returns the url + """ + # add warn + return self.url - pick(mouseevent) + def set_url(self, url): + """ + Sets the url for the artist - each child artist will fire a pick event if *mouseevent* is over - the artist and the artist has picker set + ACCEPTS: a url string """ - # Pick self - if self.pickable(): - picker = self.get_picker() - if six.callable(picker): - inside, prop = picker(self, mouseevent) - else: - inside, prop = self.contains(mouseevent) - if inside: - self.figure.canvas.pick_event(mouseevent, self, **prop) + # add warn + self.url = url + + @property + def _alpha(self): + #add warn + return self.alpha + @_alpha.setter + def _alpha(self, value): + # add warn + self.alpha = value - # Pick children - for a in self.get_children(): - # make sure the event happened in the same axes - ax = getattr(a, 'axes', None) - if mouseevent.inaxes is None or ax is None or \ - mouseevent.inaxes == ax: - # we need to check if mouseevent.inaxes is None - # because some objects associated with an axes (e.g., a - # tick label) can be outside the bounding box of the - # axes and inaxes will be None - # also check that ax is None so that it traverse objects - # which do no have an axes property but children might - a.pick(mouseevent) + def set_alpha(self, alpha): + """ + Set the alpha value used for blending - not supported on + all backends. + + ACCEPTS: float (0.0 transparent through 1.0 opaque) + """ + # add warn + self.alpha = alpha + + def get_alpha(self): + """ + Return the alpha value used for blending - not supported on all + backends + """ + # add warn + return self.alpha + + def get_gid(self): + """ + Returns the group id + """ + # add warn + return self.gid + + def set_gid(self, gid): + """ + Sets the (group) id for the artist + + ACCEPTS: an id string + """ + # add warn + self.gid = gid + + def set_clip_box(self, clipbox): + """ + Set the artist's clip :class:`~matplotlib.transforms.Bbox`. + + ACCEPTS: a :class:`matplotlib.transforms.Bbox` instance + """ + # add warn + self.clipbox = clipbox + + def get_clip_box(self): + 'Return artist clipbox' + # add warn + return self.clipbox + + def get_snap(self): + """ + Returns the snap setting which may be: + + * True: snap vertices to the nearest pixel center + + * False: leave vertices as-is + + * None: (auto) If the path contains only rectilinear line + segments, round to the nearest pixel center + + Only supported by the Agg and MacOSX backends. + """ + # add warn + return self.snap + + def set_snap(self, snap): + """ + Sets the snap setting which may be: + + * True: snap vertices to the nearest pixel center + + * False: leave vertices as-is + + * None: (auto) If the path contains only rectilinear line + segments, round to the nearest pixel center + + Only supported by the Agg and MacOSX backends. + """ + # add warn + self.snap = snap + + # temp properties + @property + def _clipon(self): + # add warn + return self.clipon + @_clipon.setter + def _clipon(self, value): + # add warn + self.clipon = value + + def set_clip_on(self, b): + """ + Set whether artist uses clipping. + + When False artists will be visible out side of the axes which + can lead to unexpected results. + + ACCEPTS: [True | False] + """ + # add warn + + # This may result in the callbacks being hit twice, but ensures they + # are hit at least once + self.clipon = b + + def get_clip_on(self): + 'Return whether artist uses clipping' + # add warn + return self.clipon + + @property + def _label(self): + # add warn + return self.label + @_label.setter + def _label(self, value): + # add warn + self.label = value + + def set_label(self, s): + """ + Set the label to *s* for auto legend. + + ACCEPTS: string or anything printable with '%s' conversion. + """ + # add warn + self.label = s + + def get_label(self): + """ + Get the label used for this artist in the legend. + """ + # add warn + return self.label + + def set_rasterized(self, rasterized): + """ + Force rasterized (bitmap) drawing in vector backend output. + + Defaults to None, which implies the backend's default behavior + + ACCEPTS: [True | False | None] + """ + # add warn + self.rasterized = rasterized + + def get_rasterized(self): + "return True if the artist is to be rasterized" + # add warn + return self.rasterized + + # temp properties + @property + def _axes(self): + # add warn + return self.axes + @_axes.setter + def _axes(self, value): + # add warn + self.axes = value + @_axes.deleter + def _axes(self): + # add warn + self._trait_values.pop('axes',None) + + def set_animated(self, b): + """ + Set the artist's animation state. + + ACCEPTS: [True | False] + """ + # add warn + self.animated = b + + def get_animated(self): + "Return the artist's animated state" + # add warn + return self.animated + + @property + def _visible(self): + # add warn + return self.visible + @_visible.setter + def _visible(self, value): + # add warn + self.visible = value + + def set_visible(self, b): + """ + Set the artist's visiblity. + + ACCEPTS: [True | False] + """ + # add warn + self.visible = b + + def get_visible(self): + "Return the artist's visiblity" + # add warn + return self.visible + + def set_axes(self, axes): + """ + Set the :class:`~matplotlib.axes.Axes` instance in which the + artist resides, if any. + + This has been deprecated in mpl 1.5, please use the + axes property. Will be removed in 1.7 or 2.0. + + ACCEPTS: an :class:`~matplotlib.axes.Axes` instance + """ + warnings.warn(_get_axes_msg, mplDeprecation, stacklevel=1) + self.axes = axes + + def get_axes(self): + """ + Return the :class:`~matplotlib.axes.Axes` instance the artist + resides in, or *None*. + + This has been deprecated in mpl 1.5, please use the + axes property. Will be removed in 1.7 or 2.0. + """ + warnings.warn(_get_axes_msg, mplDeprecation, stacklevel=1) + return self.axes + + # - - - - - - - - - - - - - - + # generic getters and setters + # - - - - - - - - - - - - - - + + def set_agg_filter(self, filter_func): + """ + set agg_filter fuction. + """ + self._agg_filter = filter_func def set_picker(self, picker): """ @@ -373,17 +585,137 @@ def set_picker(self, picker): ACCEPTS: [None|float|boolean|callable] """ - # add warn self._picker = picker def get_picker(self): 'Return the picker object used by this artist' - # add warn return self._picker -# - - - - - - - - -# other handlers -# - - - - - - - - + def pickable(self): + """Return *True* if :class:`Artist` is pickable. + + Truth value is updated by traitlets change handlers""" + return self._pickable + + def set_clip_path(self, path, transform=None): + """ + Set the artist's clip path, which may be: + + * a :class:`~matplotlib.patches.Patch` (or subclass) instance + + * a :class:`~matplotlib.path.Path` instance, in which case + an optional :class:`~matplotlib.transforms.Transform` + instance may be provided, which will be applied to the + path before using it for clipping. + + * *None*, to remove the clipping path + + For efficiency, if the path happens to be an axis-aligned + rectangle, this method will set the clipping box to the + corresponding rectangle and set the clipping path to *None*. + + ACCEPTS: [ (:class:`~matplotlib.path.Path`, + :class:`~matplotlib.transforms.Transform`) | + :class:`~matplotlib.patches.Patch` | None ] + """ + from matplotlib.patches import Patch, Rectangle + + success = False + if transform is None: + if isinstance(path, Rectangle): + self.clipbox = TransformedBbox(Bbox.unit(), path.get_transform()) + self._clippath = None + success = True + elif isinstance(path, Patch): + self._clippath = TransformedPath( + path.get_path(), + path.get_transform()) + success = True + elif isinstance(path, tuple): + path, transform = path + + if path is None: + self._clippath = None + success = True + elif isinstance(path, Path): + self._clippath = TransformedPath(path, transform) + success = True + elif isinstance(path, TransformedPath): + self._clippath = path + success = True + + 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_clip_path(self): + 'Return artist clip path' + return self._clippath + + def get_sketch_params(self): + """ + Returns the sketch parameters for the artist. + + Returns + ------- + sketch_params : tuple or `None` + + A 3-tuple with the following elements: + + * `scale`: The amplitude of the wiggle perpendicular to the + source line. + + * `length`: The length of the wiggle along the line. + + * `randomness`: The scale factor by which the length is + shrunken or expanded. + + May return `None` if no sketch parameters were set. + """ + return self._sketch + + def set_sketch_params(self, scale=None, length=None, randomness=None): + """ + Sets the sketch parameters. + + Parameters + ---------- + + scale : float, optional + The amplitude of the wiggle perpendicular to the source + line, in pixels. If scale is `None`, or not provided, no + sketch filter will be provided. + + length : float, optional + The length of the wiggle along the line, in pixels + (default 128.0) + + randomness : float, optional + The scale factor by which the length is shrunken or + expanded (default 16.0) + """ + if scale is None: + self._sketch = None + else: + self._sketch = (scale, length or 128.0, randomness or 16.0) + + def set_path_effects(self, path_effects): + """ + set path_effects, which should be a list of instances of + matplotlib.patheffect._Base class or its derivatives. + """ + self._path_effects = path_effects + + def get_path_effects(self): + return self._path_effects + + # - - - - - - - - - - - - - + # general member functions + # - - - - - - - - - - - - - def remove(self): """ @@ -414,6 +746,39 @@ def remove(self): # be no distinction between axes.add_line, axes.add_patch, etc. # TODO: add legend support + def pick(self, mouseevent): + """ + call signature:: + + pick(mouseevent) + + each child artist will fire a pick event if *mouseevent* is over + the artist and the artist has picker set + """ + # Pick self + if self.pickable(): + picker = self.get_picker() + if six.callable(picker): + inside, prop = picker(self, mouseevent) + else: + inside, prop = self.contains(mouseevent) + if inside: + self.figure.canvas.pick_event(mouseevent, self, **prop) + + # Pick children + for a in self.get_children(): + # make sure the event happened in the same axes + ax = getattr(a, 'axes', None) + if mouseevent.inaxes is None or ax is None or \ + mouseevent.inaxes == ax: + # we need to check if mouseevent.inaxes is None + # because some objects associated with an axes (e.g., a + # tick label) can be outside the bounding box of the + # axes and inaxes will be None + # also check that ax is None so that it traverse objects + # which do no have an axes property but children might + a.pick(mouseevent) + def have_units(self): 'Return *True* if units are set on the *x* or *y* axes' ax = self.axes @@ -481,6 +846,8 @@ def hitlist(self, event): L.extend(a.hitlist(event)) return L + # should be superseded by `on_trait_change` methods in + # `__init__` constructor of inherited classes def get_children(self): """ Return a list of the child :class:`Artist`s this @@ -488,13 +855,6 @@ def get_children(self): """ return [] - def pickable(self): - 'Return *True* if :class:`Artist` is pickable.' - return self.pickable - return (self.figure is not None and - self.figure.canvas is not None and - self._picker is not None) - def is_figure_set(self): """ Returns True if the artist is assigned to a @@ -502,137 +862,6 @@ def is_figure_set(self): """ return self.figure is not None - def get_url(self): - """ - Returns the url - """ - return self._url - - def set_url(self, url): - """ - Sets the url for the artist - - ACCEPTS: a url string - """ - self._url = url - - def get_gid(self): - """ - Returns the group id - """ - return self._gid - - def set_gid(self, gid): - """ - Sets the (group) id for the artist - - ACCEPTS: an id string - """ - self._gid = gid - - def get_snap(self): - """ - Returns the snap setting which may be: - - * True: snap vertices to the nearest pixel center - - * False: leave vertices as-is - - * None: (auto) If the path contains only rectilinear line - segments, round to the nearest pixel center - - Only supported by the Agg and MacOSX backends. - """ - if rcParams['path.snap']: - return self._snap - else: - return False - - def set_snap(self, snap): - """ - Sets the snap setting which may be: - - * True: snap vertices to the nearest pixel center - - * False: leave vertices as-is - - * None: (auto) If the path contains only rectilinear line - segments, round to the nearest pixel center - - Only supported by the Agg and MacOSX backends. - """ - self._snap = snap - self.stale = True - - def get_sketch_params(self): - """ - Returns the sketch parameters for the artist. - - Returns - ------- - sketch_params : tuple or `None` - - A 3-tuple with the following elements: - - * `scale`: The amplitude of the wiggle perpendicular to the - source line. - - * `length`: The length of the wiggle along the line. - - * `randomness`: The scale factor by which the length is - shrunken or expanded. - - May return `None` if no sketch parameters were set. - """ - return self._sketch - - def set_sketch_params(self, scale=None, length=None, randomness=None): - """ - Sets the sketch parameters. - - Parameters - ---------- - - scale : float, optional - The amplitude of the wiggle perpendicular to the source - line, in pixels. If scale is `None`, or not provided, no - sketch filter will be provided. - - length : float, optional - The length of the wiggle along the line, in pixels - (default 128.0) - - randomness : float, optional - The scale factor by which the length is shrunken or - expanded (default 16.0) - """ - if scale is 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): - """ - set path_effects, which should be a list of instances of - matplotlib.patheffect._Base class or its derivatives. - """ - self._path_effects = path_effects - self.stale = True - - def get_path_effects(self): - return self._path_effects - - def set_clip_box(self, clipbox): - """ - Set the artist's clip :class:`~matplotlib.transforms.Bbox`. - - ACCEPTS: a :class:`matplotlib.transforms.Bbox` instance - """ - self.clipbox = clipbox - self.pchanged() - self.stale = True - def set_clip_path(self, path, transform=None): """ Set the artist's clip path, which may be: @@ -688,27 +917,6 @@ def set_clip_path(self, path, transform=None): self.pchanged() self.stale = True - def get_alpha(self): - """ - Return the alpha value used for blending - not supported on all - backends - """ - return self._alpha - - - - def get_clip_on(self): - 'Return whether artist uses clipping' - return self._clipon - - def get_clip_box(self): - 'Return artist clipbox' - return self.clipbox - - def get_clip_path(self): - 'Return artist clip path' - return self._clippath - def get_transformed_clip_path_and_affine(self): ''' Return the clip path with the non-affine part of its @@ -719,21 +927,6 @@ def get_transformed_clip_path_and_affine(self): return self._clippath.get_transformed_path_and_affine() return None, None - def set_clip_on(self, b): - """ - Set whether artist uses clipping. - - When False artists will be visible out side of the axes which - can lead to unexpected results. - - 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' if self._clipon: @@ -744,131 +937,42 @@ def _set_gc_clip(self, gc): gc.set_clip_rectangle(None) gc.set_clip_path(None) - def get_rasterized(self): - "return True if the artist is to be rasterized" - return self._rasterized - - def set_rasterized(self, rasterized): - """ - Force rasterized (bitmap) drawing in vector backend output. - - Defaults to None, which implies the backend's default behavior - - ACCEPTS: [True | False | None] - """ - if rasterized and not hasattr(self.draw, "_supports_rasterization"): - warnings.warn("Rasterization of '%s' will be ignored" % self) - - self._rasterized = rasterized - def get_agg_filter(self): "return filter function to be used for agg filter" return self._agg_filter - def set_agg_filter(self, filter_func): - """ - set agg_filter fuction. - - """ - 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): - """ - Set the alpha value used for blending - not supported on - all backends. - - ACCEPTS: float (0.0 transparent through 1.0 opaque) - """ - self._alpha = alpha - self.pchanged() - self.stale = True - - def _visible_changed(self, name, new): - self.visible = new - self.pchanged() - self.stale = True - - def set_visible(self, b): - """ - Set the artist's visiblity. - - ACCEPTS: [True | False] - """ - # add warn - self.visible = b - - def get_visible(self): - "Return the artist's visiblity" - # add warn - return self._visible - - def _animated_changed(self, name, new): - self.animated = new - self.pchanged() - self.stale = True - - def set_animated(self, b): - """ - Set the artist's animation state. - - ACCEPTS: [True | False] - """ - # add warn - self.animated = b - - def get_animated(self): - "Return the artist's animated state" - # add warn - return self.animated - def update(self, props): """ Update the properties of this :class:`Artist` from the dictionary *prop*. """ - store = self.eventson - self.eventson = False - changed = False - - for k, v in six.iteritems(props): - if k in ['axes']: - setattr(self, k, v) - else: - func = getattr(self, 'set_' + k, None) - if func is None or not six.callable(func): - raise AttributeError('Unknown property %s' % k) - func(v) - changed = True - self.eventson = store - if changed: - self.pchanged() - self.stale = True - - def get_label(self): - """ - Get the label used for this artist in the legend. - """ - return self._label - - def set_label(self, s): - """ - Set the label to *s* for auto legend. - - ACCEPTS: string or anything printable with '%s' conversion. - """ - if s is not None: - self._label = '%s' % (s, ) - else: - self._label = None - self.pchanged() - self.stale = True + # add warn + # all handled by configurable + self.update_config(props) + + # store = self.eventson + # self.eventson = False + # changed = False + + # for k, v in six.iteritems(props): + # if k in ['axes']: + # setattr(self, k, v) + # else: + # func = getattr(self, 'set_' + k, None) + # if func is None or not six.callable(func): + # raise AttributeError('Unknown property %s' % k) + # func(v) + # changed = True + # self.eventson = store + # if changed: + # self.pchanged() + # self.stale = True def get_zorder(self): """ @@ -1427,6 +1531,8 @@ def setp(obj, *args, **kwargs): ret.extend([func(val)]) return [x for x in cbook.flatten(ret)] +Artist.ps = [] +Artist.s = [] def kwdoc(a): hardcopy = matplotlib.rcParams['docstring.hardcopy'] diff --git a/lib/matplotlib/traitlets.py b/lib/matplotlib/traitlets.py index 3cfc8d76961b..20c803307999 100644 --- a/lib/matplotlib/traitlets.py +++ b/lib/matplotlib/traitlets.py @@ -4,8 +4,9 @@ #ipython 4 import from traitlets.config import Configurable from traitlets import (Int, Float, Bool, Dict, List, Instance, - Union, TraitError, HasTraits, - NoDefaultSpecified, TraitType) + Union, TraitError, HasTraits, Unicode, + NoDefaultSpecified, TraitType, Tuple, + Undefined) # ipython 3 imports # from IPython.config import Configurable @@ -18,7 +19,7 @@ class Configurable(Configurable): pass class TraitType(TraitType): pass -# overload handle may not be temporary +# overload handle is probably temporary class OverloadMixin(object): def validate(self, obj, value): From 3ac867260d4b1d8c0d624f619915795b55df8baf Mon Sep 17 00:00:00 2001 From: Ryan Morshead Date: Wed, 15 Jul 2015 14:31:44 -0700 Subject: [PATCH 5/8] resolved some issues, simple pyplot.plot(x,y) works --- lib/matplotlib/artist.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index ad4030691011..c09a373ac0b1 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -95,7 +95,7 @@ class Artist(Configurable): _transformSet = Bool(False, serialize=True) # warn : oInstance used, new TraitType? transform = oInstance('matplotlib.transforms.Transform', - serialize=True, perishable=True) + allow_none=True, serialize=True, perishable=True) axes = Instance('matplotlib.axes._axes.Axes',allow_none=True, serialize=True) contains = Callable(allow_none=True) @@ -112,14 +112,14 @@ class Artist(Configurable): clipon = Bool(True, perishable=True) # * setter and getter methods for `self._clippath` could be refactored # using TraitTypes potentially ==> clippath = ? - label = Union([Unicode(''),Instance('matplotlib.text.Text')],allow_none=True, perishable=True) + label = Union([Unicode(''),Instance('matplotlib.text.Text'),Int()],allow_none=True, perishable=True) rasterized = Bool(allow_none=True) _agg_filter = Callable(None,allow_none=True, perishable=True) eventson = Bool(False) _sketch = Tuple(rcParams['path.sketch'], allow_none=True, perishable=True,serialize=True) _path_effects = List(trait=Instance('matplotlib.patheffects.AbstractPathEffect'), - perishable=True, serialize=True) + allow_none=True, perishable=True, serialize=True) _propobservers = Dict({}) # a dict from oids to funcs _oid = Int(0) # an observer id @@ -256,6 +256,15 @@ def _picker_changed(self, name, new): # warned setters and getters # - - - - - - - - - - - - - - - + @property + def _transform(self): + #add warn + return self.transform + @_transform.setter + def _transform(self, value): + # add warn + self.transform = value + def get_transform(self): # add warn return self.transform From 4056c30f1d034aadecc3e3c057e272f24b845d68 Mon Sep 17 00:00:00 2001 From: Ryan Morshead Date: Wed, 15 Jul 2015 14:33:05 -0700 Subject: [PATCH 6/8] resolved some issues, simple pyplot.plot(x,y) works --- lib/matplotlib/artist.py | 45 +++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index c09a373ac0b1..9fcd78075fc2 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -88,14 +88,13 @@ class Artist(Configurable): aname = 'Artist' zorder = 0 - # warn on all : check whether serialize is/isn't required. # perishable=True ==> set stale = True _transformSet = Bool(False, serialize=True) # warn : oInstance used, new TraitType? - transform = oInstance('matplotlib.transforms.Transform', - allow_none=True, serialize=True, perishable=True) + transform = oInstance('matplotlib.transforms.Transform', allow_none=True, + serialize=True, perishable=True) axes = Instance('matplotlib.axes._axes.Axes',allow_none=True, serialize=True) contains = Callable(allow_none=True) @@ -103,7 +102,7 @@ class Artist(Configurable): serialize=True, perishable=True) visible = Bool(True, perishable=True, serialize=True) animated = Bool(False, perishable=True, serialize=True) - alpha = Float(allow_none=True, perishable=True, serialize=True) + alpha = Float(None, allow_none=True, perishable=True, serialize=True) url = Unicode(allow_none=True, serialize=True) gid = Unicode(allow_none=True, serialize=True) clipbox = Instance('matplotlib.transforms.BboxBase', allow_none=True, @@ -136,9 +135,7 @@ def __init__(self, config=None, parent=None): self.stale = True self._pickable = False self._clippath = None - self._picker = None - # self._oid = 0 - # self._propobservers = {} + self._picker = None self._remove_method = None self._sketch = rcParams['path.sketch'] @@ -256,6 +253,13 @@ def _picker_changed(self, name, new): # warned setters and getters # - - - - - - - - - - - - - - - + @property + def _contains(self): + return self.contains + @_contains.setter + def _contains(self, value): + self.contains = value + @property def _transform(self): #add warn @@ -344,6 +348,15 @@ def get_alpha(self): # add warn return self.alpha + @property + def _gid(self): + #add warn + return self.gid + @_gid.setter + def _gid(self, value): + # add warn + self.gid = value + def get_gid(self): """ Returns the group id @@ -360,6 +373,15 @@ def set_gid(self, gid): # add warn self.gid = gid + @property + def _clipbox(self): + #add warn + return self.clipbox + @_clipbox.setter + def _clipbox(self, value): + # add warn + self.clipbox = value + def set_clip_box(self, clipbox): """ Set the artist's clip :class:`~matplotlib.transforms.Bbox`. @@ -374,6 +396,15 @@ def get_clip_box(self): # add warn return self.clipbox + @property + def _snap(self): + #add warn + return self.snap + @_snap.setter + def _snap(self, value): + # add warn + self.snap = value + def get_snap(self): """ Returns the snap setting which may be: From 96c23a98567b0899583ec7ffa7219737172036d7 Mon Sep 17 00:00:00 2001 From: Ryan Morshead Date: Fri, 17 Jul 2015 10:46:03 -0700 Subject: [PATCH 7/8] more tests passed --- lib/matplotlib/traitlets.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/traitlets.py b/lib/matplotlib/traitlets.py index 20c803307999..299dbb51318c 100644 --- a/lib/matplotlib/traitlets.py +++ b/lib/matplotlib/traitlets.py @@ -1,18 +1,19 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -#ipython 4 import -from traitlets.config import Configurable -from traitlets import (Int, Float, Bool, Dict, List, Instance, - Union, TraitError, HasTraits, Unicode, - NoDefaultSpecified, TraitType, Tuple, - Undefined) - -# ipython 3 imports -# from IPython.config import Configurable -# from IPython.utils.traitlets import (Int, Float, Bool, Dict, List, Instance, -# Union, TraitError, HasTraits, -# NoDefaultSpecified, TraitType) +try: + # IPython 4 import + from traitlets.config import Configurable + from traitlets import (Int, Float, Bool, Dict, List, Instance, + Union, TraitError, HasTraits, Unicode, + NoDefaultSpecified, TraitType, Tuple, + Undefined, TraitError, getargspec) +except ImportError: + # IPython 3 import + from IPython.utils.traitlest.config import Configurable + from IPython.utils.traitlets import (Int, Float, Bool, Dict, List, Instance, + Union, TraitError, HasTraits, TraitError, + NoDefaultSpecified, TraitType) import numpy as np # override for backward compatability From 1ddcd3dfcce58ddfec457ed5cac4f26180c62c48 Mon Sep 17 00:00:00 2001 From: Ryan Morshead Date: Fri, 17 Jul 2015 10:58:56 -0700 Subject: [PATCH 8/8] update --- lib/matplotlib/artist.py | 104 ++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 55 deletions(-) diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 9fcd78075fc2..11ee15ba05f4 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -75,7 +75,6 @@ def draw_wrapper(artist, renderer, *args, **kwargs): def _stale_figure_callback(self): self.figure.stale = True - def _stale_axes_callback(self): self.axes.stale = True @@ -91,37 +90,38 @@ class Artist(Configurable): # warn on all : check whether serialize is/isn't required. # perishable=True ==> set stale = True - _transformSet = Bool(False, serialize=True) + _transformSet = Bool(False, serialize=True, config=True) # warn : oInstance used, new TraitType? - transform = oInstance('matplotlib.transforms.Transform', allow_none=True, - serialize=True, perishable=True) + transform = oInstance('matplotlib.transforms.Transform', + serialize=True, perishable=True, config=True) axes = Instance('matplotlib.axes._axes.Axes',allow_none=True, - serialize=True) - contains = Callable(allow_none=True) + serialize=True, config=True) + contains = Callable(allow_none=True, config=True) figure = Instance('matplotlib.figure.Figure', allow_none=True, - serialize=True, perishable=True) - visible = Bool(True, perishable=True, serialize=True) - animated = Bool(False, perishable=True, serialize=True) - alpha = Float(None, allow_none=True, perishable=True, serialize=True) - url = Unicode(allow_none=True, serialize=True) - gid = Unicode(allow_none=True, serialize=True) + serialize=True, perishable=True, config=True) + visible = Bool(True, perishable=True, serialize=True, config=True) + animated = Bool(False, perishable=True, serialize=True, config=True) + alpha = Float(None, allow_none=True, perishable=True, serialize=True, config=True) + url = Unicode(allow_none=True, serialize=True, config=True) + gid = Unicode(allow_none=True, serialize=True, config=True) clipbox = Instance('matplotlib.transforms.BboxBase', allow_none=True, - perishable=True, serialize=True) - snap = Bool(allow_none=True, perishable=True) - clipon = Bool(True, perishable=True) + perishable=True, serialize=True, config=True) + snap = Bool(allow_none=True, perishable=True, config=True) + clipon = Bool(True, perishable=True, config=True) # * setter and getter methods for `self._clippath` could be refactored # using TraitTypes potentially ==> clippath = ? - label = Union([Unicode(''),Instance('matplotlib.text.Text'),Int()],allow_none=True, perishable=True) - rasterized = Bool(allow_none=True) - _agg_filter = Callable(None,allow_none=True, perishable=True) - eventson = Bool(False) + label = Union([Unicode(''),Instance('matplotlib.text.Text'),Int()], + allow_none=True, perishable=True, config=True) + rasterized = Bool(allow_none=True, config=True) + _agg_filter = Callable(None,allow_none=True, perishable=True, config=True) + eventson = Bool(True, config=True) _sketch = Tuple(rcParams['path.sketch'], allow_none=True, - perishable=True,serialize=True) + perishable=True,serialize=True, config=True) _path_effects = List(trait=Instance('matplotlib.patheffects.AbstractPathEffect'), - allow_none=True, perishable=True, serialize=True) - _propobservers = Dict({}) # a dict from oids to funcs - _oid = Int(0) # an observer id - + allow_none=True, perishable=True, serialize=True, config=True) + _propobservers = Dict({}, config=True) # a dict from oids to funcs + _oid = Int(0, config=True) # an observer id + # sketch = mpltr.Tuple(allow_none=True) # path_effects = mpltr. @@ -135,12 +135,12 @@ def __init__(self, config=None, parent=None): self.stale = True self._pickable = False self._clippath = None - self._picker = None + self._picker = None self._remove_method = None self._sketch = rcParams['path.sketch'] self._path_effects = rcParams['path.effects'] - + def __getstate__(self): d = self.__dict__.copy() # remove the unpicklable remove method, this will get re-added on load @@ -197,18 +197,16 @@ def remove_callback(self, oid): def _transform_changed(self, name, new): self._transformSet = True - def _transform_default(self): - return IdentityTransform() - def _transform_overload(self, trait, value): - if (not isinstance(value, Transform) + if value is None: + return IdentityTransform() + elif (not isinstance(value, Transform) and hasattr(value, '_as_mpl_transform')): return value._as_mpl_transform(self.axes) - else: - trait.error(self, value) + trait.error(self, value) def _axes_changed(self, name, old, new): - if old is not Undefined: + if old not in [Undefined, None]: # old != true already checked in `TraitType._validate` raise ValueError("Can not reset the axes. You are " "probably trying to re-use an artist " @@ -248,7 +246,7 @@ def _picker_changed(self, name, new): if new is None: self._pickable = False self._pickable = True - + # - - - - - - - - - - - - - - - # warned setters and getters # - - - - - - - - - - - - - - - @@ -992,27 +990,23 @@ def update(self, props): Update the properties of this :class:`Artist` from the dictionary *prop*. """ - # add warn - # all handled by configurable - self.update_config(props) - - # store = self.eventson - # self.eventson = False - # changed = False - - # for k, v in six.iteritems(props): - # if k in ['axes']: - # setattr(self, k, v) - # else: - # func = getattr(self, 'set_' + k, None) - # if func is None or not six.callable(func): - # raise AttributeError('Unknown property %s' % k) - # func(v) - # changed = True - # self.eventson = store - # if changed: - # self.pchanged() - # self.stale = True + # all can be handleded by configurable + # self.update_config(config) + + store = self.eventson + self.eventson = False + changed = False + + for k, v in six.iteritems(props): + if k in ['axes']: + setattr(self, k, v) + else: + func = getattr(self, 'set_' + k, None) + if func is None or not six.callable(func): + raise AttributeError('Unknown property %s' % k) + func(v) + changed = True + self.eventson = store def get_zorder(self): """