artist.py

This sounds like a job for properties! make

    > _transform a property, and code that gets and sets it
    > directly should still work. though People that were
    > accessing an underscored name directly should expect
    > this kind of problem.

The matplotlib artist kwarg properties act like python properties or
enthought traits, with some advantages and disadvantages over each
(mostly disadvantages, alas). We've discussed migrating to one or
another over the years, but haven't bitten the bullet. At each point
it's easier to extend the exiting implementation than refactor the
whole bit; the tyranny of small decisions.

Here are some of the pros and cons as I see them of enthought traits
vs python properties

   Pros:
   * compatibility with the rest of the enthought tool suite
   * built in observer pattern
   * automatic UI for wx users
   * performance is better than python properties last time I looked
   * matplotlib ships with enthought traits built in

   Cons:
   * smaller user base than python properties may imply
     fewer 3rd party enhancements, less support, etc
   * we have to maintain our copy of enthought traits to keep it
     current and building or require an additional dependency

I spent some time working on matplotlib rc properties as enthought
traits as a precursor to porting matplotlib properties to traits.
Here is some example code showing how to define some representative rc
properties and construct a matplotlib artist using traits. Because
matplotlib ships with enthought traits already, you can run this
script with just matplotlib. Unfortunately, we do not ship the ex UI
component so you can't test that part. I'm a bit of a traits newbie
so there are probably better ways to do what I have done below.

import sys, os, re
import matplotlib.enthought.traits as traits
from matplotlib.cbook import is_string_like
from matplotlib.artist import Artist

doprint = True
flexible_true_trait = traits.Trait(
    True,
    { 'true': True, 't': True, 'yes': True, 'y': True, 'on': True, True: True,
      'false': False, 'f': False, 'no': False, 'n': False, 'off': False, False: False
                               } )
flexible_false_trait = traits.Trait( False, flexible_true_trait )

colors = {
    'c' : '#00bfbf',
    'b' : '#0000ff',
    'g' : '#008000',
    'k' : '#000000',
    'm' : '#bf00bf',
    'r' : '#ff0000',
    'w' : '#ffffff',
    'y' : '#bfbf00',
    'gold' : '#FFD700',
    'peachpuff' : '#FFDAB9',
    'navajowhite' : '#FFDEAD',
    }

def hex2color(s):
    "Convert hex string (like html uses, eg, #efefef) to a r,g,b tuple"
    return tuple([int(n, 16)/255.0 for n in (s[1:3], s[3:5], s[5:7])])

class RGBA(traits.HasTraits):
    # r,g,b,a in the range 0-1 with default color 0,0,0,1 (black)
    r = traits.Range(0., 1., 0.)
    g = traits.Range(0., 1., 0.)
    b = traits.Range(0., 1., 0.)
    a = traits.Range(0., 1., 1.)
    def __init__(self, r=0., g=0., b=0., a=1.):
        self.r = r
        self.g = g
        self.b = b
        self.a = a
    def __repr__(self):
        return 'r,g,b,a = (%1.2f, %1.2f, %1.2f, %1.2f)'%\
               (self.r, self.g, self.b, self.a)

def tuple_to_rgba(ob, name, val):
    tup = [float(x) for x in val]
    if len(tup)==3:
        r,g,b = tup
        return RGBA(r,g,b)
    elif len(tup)==4:
        r,g,b,a = tup
        return RGBA(r,g,b,a)
    else:
        raise ValueError
tuple_to_rgba.info = 'a RGB or RGBA tuple of floats'

def hex_to_rgba(ob, name, val):
    rgx = re.compile('^#[0-9A-Fa-f]{6}$')

    if not is_string_like(val):
        raise TypeError
    if rgx.match(val) is None:
        raise ValueError
    r,g,b = hex2color(val)
    return RGBA(r,g,b,1.0)
hex_to_rgba.info = 'a hex color string'

def colorname_to_rgba(ob, name, val):
    hex = colors[val.lower()]
    r,g,b = hex2color(hex)
    return RGBA(r,g,b,1.0)
colorname_to_rgba.info = 'a named color'

def float_to_rgba(ob, name, val):
    val = float(val)
    return RGBA(val, val, val, 1.)
float_to_rgba.info = 'a grayscale intensity'

Color = traits.Trait(RGBA(), float_to_rgba, colorname_to_rgba, RGBA,
              hex_to_rgba, tuple_to_rgba)

def file_exists(ob, name, val):
    fh = file(val, 'r')
    return val

def path_exists(ob, name, val):
    os.path.exists(val)
linestyles = ('-', '--', '-.', ':', 'steps', 'None')
TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN = range(4)
linemarkers = (None, '.', ',', 'o', '^', 'v', '<', '>', 's',
                  '+', 'x', 'd', 'D', '|', '_', 'h', 'H',
                  'p', '1', '2', '3', '4',
                  TICKLEFT,
                  TICKRIGHT,
                  TICKUP,
                  TICKDOWN,
                  'None'
               )

class LineRC(traits.HasTraits):
    linewidth = traits.Float(0.5)
    linestyle = traits.Trait(*linestyles)
    color = Color
    marker = traits.Trait(*linemarkers)
    markerfacecolor = Color
    markeredgecolor = Color
    markeredgewidth = traits.Float(0.5)
    markersize = traits.Float(6)
    antialiased = flexible_true_trait
    data_clipping = flexible_false_trait

class PatchRC(traits.HasTraits):
    linewidth = traits.Float(1.0)
    facecolor = Color
    edgecolor = Color
    antialiased = flexible_true_trait

timezones = 'UTC', 'US/Central', 'ES/Eastern' # fixme: and many more
backends = ('GTKAgg', 'Cairo', 'FltkAgg', 'GD', 'GDK', 'GTK', 'Agg',
            'GTKCairo', 'Paint', 'PS', 'SVG', 'Template', 'TkAgg',
            'WX')

class RC(traits.HasTraits):
    backend = traits.Trait(*backends)
    numerix = traits.Trait('Numeric', 'numarray')
    interactive = flexible_false_trait
    toolbar = traits.Trait('toolbar2', 'classic', None)
    timezone = traits.Trait(*timezones)
    lines = traits.Trait(LineRC())
    patch = traits.Trait(PatchRC())

rc = RC()
rc.lines.color = 'r'
if doprint:
    print 'RC'
    rc.print_traits()
    print 'RC lines'
    rc.lines.print_traits()
    print 'RC patches'
    rc.patch.print_traits()

class Patch(Artist, traits.HasTraits):
    linewidth = traits.Float(0.5)
    facecolor = Color
    fc = facecolor
    edgecolor = Color
    fill = flexible_true_trait
    def __init__(self,
                 edgecolor=None,
                 facecolor=None,
                 linewidth=None,
                 antialiased = None,
                 fill=1,
                 **kwargs
                 ):
        Artist.__init__(self)

        if edgecolor is None: edgecolor = rc.patch.edgecolor
        if facecolor is None: facecolor = rc.patch.facecolor
        if linewidth is None: linewidth = rc.patch.linewidth
        if antialiased is None: antialiased = rc.patch.antialiased

        self.edgecolor = edgecolor
        self.facecolor = facecolor
        self.linewidth = linewidth
        self.antialiased = antialiased
        self.fill = fill

p = Patch()
p.facecolor = '#bfbf00'
p.edgecolor = 'gold'
p.facecolor = (1,.5,.5,.25)
p.facecolor = 0.25
p.fill = 'f'
print 'p.facecolor', type(p.facecolor), p.facecolor
print 'p.fill', type(p.fill), p.fill
if p.fill_: print 'fill'
else: print 'no fill'
if doprint:
    print
    print 'Patch'
    p.print_traits()

JDH

Sorry meant to send this to the whole list:

···

----------------------------

John,

Thanks for the fix. After reading Eric's email I started to question
my profiling results but I still (without your fix) seem to see a
significant time when in Artist when generating large volumes of
graphs. I need to rebuild my matplotlib against svn and test your fix
now to see if that solves the problem.

Thanks for your help.

--Tom

On 12/5/06, John Hunter <jdhunter@...4...> wrote:

   > This sounds like a job for properties! make
   > _transform a property, and code that gets and sets it
   > directly should still work. though People that were
   > accessing an underscored name directly should expect
   > this kind of problem.

The matplotlib artist kwarg properties act like python properties or
enthought traits, with some advantages and disadvantages over each
(mostly disadvantages, alas). We've discussed migrating to one or
another over the years, but haven't bitten the bullet. At each point
it's easier to extend the exiting implementation than refactor the
whole bit; the tyranny of small decisions.

Here are some of the pros and cons as I see them of enthought traits
vs python properties

  Pros:
  * compatibility with the rest of the enthought tool suite
  * built in observer pattern
  * automatic UI for wx users
  * performance is better than python properties last time I looked
  * matplotlib ships with enthought traits built in

  Cons:
  * smaller user base than python properties may imply
    fewer 3rd party enhancements, less support, etc
  * we have to maintain our copy of enthought traits to keep it
    current and building or require an additional dependency

I spent some time working on matplotlib rc properties as enthought
traits as a precursor to porting matplotlib properties to traits.
Here is some example code showing how to define some representative rc
properties and construct a matplotlib artist using traits. Because
matplotlib ships with enthought traits already, you can run this
script with just matplotlib. Unfortunately, we do not ship the ex UI
component so you can't test that part. I'm a bit of a traits newbie
so there are probably better ways to do what I have done below.

import sys, os, re
import matplotlib.enthought.traits as traits
from matplotlib.cbook import is_string_like
from matplotlib.artist import Artist

doprint = True
flexible_true_trait = traits.Trait(
   True,
   { 'true': True, 't': True, 'yes': True, 'y': True, 'on': True, True: True,
     'false': False, 'f': False, 'no': False, 'n': False, 'off': False, False: False
                              } )
flexible_false_trait = traits.Trait( False, flexible_true_trait )

colors = {
   'c' : '#00bfbf',
   'b' : '#0000ff',
   'g' : '#008000',
   'k' : '#000000',
   'm' : '#bf00bf',
   'r' : '#ff0000',
   'w' : '#ffffff',
   'y' : '#bfbf00',
   'gold' : '#FFD700',
   'peachpuff' : '#FFDAB9',
   'navajowhite' : '#FFDEAD',
   }

def hex2color(s):
   "Convert hex string (like html uses, eg, #efefef) to a r,g,b tuple"
   return tuple([int(n, 16)/255.0 for n in (s[1:3], s[3:5], s[5:7])])

class RGBA(traits.HasTraits):
   # r,g,b,a in the range 0-1 with default color 0,0,0,1 (black)
   r = traits.Range(0., 1., 0.)
   g = traits.Range(0., 1., 0.)
   b = traits.Range(0., 1., 0.)
   a = traits.Range(0., 1., 1.)
   def __init__(self, r=0., g=0., b=0., a=1.):
       self.r = r
       self.g = g
       self.b = b
       self.a = a
   def __repr__(self):
       return 'r,g,b,a = (%1.2f, %1.2f, %1.2f, %1.2f)'%\
              (self.r, self.g, self.b, self.a)

def tuple_to_rgba(ob, name, val):
   tup = [float(x) for x in val]
   if len(tup)==3:
       r,g,b = tup
       return RGBA(r,g,b)
   elif len(tup)==4:
       r,g,b,a = tup
       return RGBA(r,g,b,a)
   else:
       raise ValueError
tuple_to_rgba.info = 'a RGB or RGBA tuple of floats'

def hex_to_rgba(ob, name, val):
   rgx = re.compile('^#[0-9A-Fa-f]{6}$')

   if not is_string_like(val):
       raise TypeError
   if rgx.match(val) is None:
       raise ValueError
   r,g,b = hex2color(val)
   return RGBA(r,g,b,1.0)
hex_to_rgba.info = 'a hex color string'

def colorname_to_rgba(ob, name, val):
   hex = colors[val.lower()]
   r,g,b = hex2color(hex)
   return RGBA(r,g,b,1.0)
colorname_to_rgba.info = 'a named color'

def float_to_rgba(ob, name, val):
   val = float(val)
   return RGBA(val, val, val, 1.)
float_to_rgba.info = 'a grayscale intensity'

Color = traits.Trait(RGBA(), float_to_rgba, colorname_to_rgba, RGBA,
             hex_to_rgba, tuple_to_rgba)

def file_exists(ob, name, val):
   fh = file(val, 'r')
   return val

def path_exists(ob, name, val):
   os.path.exists(val)
linestyles = ('-', '--', '-.', ':', 'steps', 'None')
TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN = range(4)
linemarkers = (None, '.', ',', 'o', '^', 'v', '<', '>', 's',
                 '+', 'x', 'd', 'D', '|', '_', 'h', 'H',
                 'p', '1', '2', '3', '4',
                 TICKLEFT,
                 TICKRIGHT,
                 TICKUP,
                 TICKDOWN,
                 'None'
              )

class LineRC(traits.HasTraits):
   linewidth = traits.Float(0.5)
   linestyle = traits.Trait(*linestyles)
   color = Color
   marker = traits.Trait(*linemarkers)
   markerfacecolor = Color
   markeredgecolor = Color
   markeredgewidth = traits.Float(0.5)
   markersize = traits.Float(6)
   antialiased = flexible_true_trait
   data_clipping = flexible_false_trait

class PatchRC(traits.HasTraits):
   linewidth = traits.Float(1.0)
   facecolor = Color
   edgecolor = Color
   antialiased = flexible_true_trait

timezones = 'UTC', 'US/Central', 'ES/Eastern' # fixme: and many more
backends = ('GTKAgg', 'Cairo', 'FltkAgg', 'GD', 'GDK', 'GTK', 'Agg',
           'GTKCairo', 'Paint', 'PS', 'SVG', 'Template', 'TkAgg',
           'WX')

class RC(traits.HasTraits):
   backend = traits.Trait(*backends)
   numerix = traits.Trait('Numeric', 'numarray')
   interactive = flexible_false_trait
   toolbar = traits.Trait('toolbar2', 'classic', None)
   timezone = traits.Trait(*timezones)
   lines = traits.Trait(LineRC())
   patch = traits.Trait(PatchRC())

rc = RC()
rc.lines.color = 'r'
if doprint:
   print 'RC'
   rc.print_traits()
   print 'RC lines'
   rc.lines.print_traits()
   print 'RC patches'
   rc.patch.print_traits()

class Patch(Artist, traits.HasTraits):
   linewidth = traits.Float(0.5)
   facecolor = Color
   fc = facecolor
   edgecolor = Color
   fill = flexible_true_trait
   def __init__(self,
                edgecolor=None,
                facecolor=None,
                linewidth=None,
                antialiased = None,
                fill=1,
                **kwargs
                ):
       Artist.__init__(self)

       if edgecolor is None: edgecolor = rc.patch.edgecolor
       if facecolor is None: facecolor = rc.patch.facecolor
       if linewidth is None: linewidth = rc.patch.linewidth
       if antialiased is None: antialiased = rc.patch.antialiased

       self.edgecolor = edgecolor
       self.facecolor = facecolor
       self.linewidth = linewidth
       self.antialiased = antialiased
       self.fill = fill

p = Patch()
p.facecolor = '#bfbf00'
p.edgecolor = 'gold'
p.facecolor = (1,.5,.5,.25)
p.facecolor = 0.25
p.fill = 'f'
print 'p.facecolor', type(p.facecolor), p.facecolor
print 'p.fill', type(p.fill), p.fill
if p.fill_: print 'fill'
else: print 'no fill'
if doprint:
   print
   print 'Patch'
   p.print_traits()

JDH

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys - and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-users