[Matplotlib-users] rc parameters and config file

I'm moving this discussion over to mpl-dev. Its gettin too hairy for the more
general audience.

If I may make another suggestion (I don't have time to volunteer for
implementing them in the forseeable future, but I might eventually if
there's interest): Maybe the way configuration is handled could be
improved:

1. It seems to me that letting users manipulate some raw dictionary
   (`rcParams`) in order to customize behavior is bad.

   Just now, on my first dig into matplotlib I've found a number of bugs
that result from bad state in rcParams. As a user this has also caused me
some wasted time, because typos and other illegal param choices are not
caught early (but are relativley likely, because matplotlib has many and
complex options).

   Using some dedicated class-instance instead of a dict would allow for:
   - automatic conistency checks on setting
   - possibly even change propagation (likely faster and certainly less
error prone if done right: I'm not sure how many places rcParams dependent
results apart from the tex-stuff are cached and should be recomputed when
certain params change, but obviously an push/'event'-based solutin could be
faster than pull/polling)
   - possibly better performance, because e.g. using immutable values would
     allow identity based checks to see if a rcParam changed and obviate
the need to copy;
   - convenient doc lookup and searching an display of available
alternatives (e.g. font-families etc)
   - better factoring of the matplotlibrc parsing and validation see below:

This sounds like a good proposal to me. Some other devs had considered using
the traits package to address some of these points. Maybe they will comment.

2. It also seems to me that having some custom config file format rather
than just (a literal-only subset of) python is suboptimal. Why make people
learn another and more limited syntax when python literals are already
familiar and both more powerful and easier to use

If someone donated a nickel for every time I have seen this argument on the
mailing list, we might have enough money to buy John a doughnut.

(e.g. the latex-preamble
setting can't express multiple package options, because param values can't
contain ',')?

Thats a pretty big bug. See the disclaimer concerning preamble
customizations :slight_smile:

It would also get rid of the IMO undesirable coupling of
   string-parsing and type-checking that the validate_* functions do at the
   moment; really setting an rcParam should also do the latter but not the
   former (and guaranteed validation on setting would also avoid the need
to sprinkle random checks on reading through the code).

   IMO it would be much nicer if there was some more declarative way to
   specify options, check their validity on setting and assoicate docs with
   them (sort of like e.g. emcs-custom vars).

   Valid files in the old-syntax could be translated automatically quite
   easily, so I don't think it's a compatiblity issue.

These are all good points, most of which have been brought up before. What we
need is someone with sufficient time and motivation...

Darren

···

On Friday 18 May 2007 12:02:30 pm Alexander Schmolck wrote:

Yes, we've often talked about using traits here and elsewhere but the
idea has languished because it has never made it to the top of
anyone's priority queue. I just added examples/rc_traits.py, which is
the example code I wrote some time ago when I was exploring how to use
traits for rc and other mpl properties. I'll also post it again here
in case someone wants to run with it.

# 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()

···

On 5/18/07, Darren Dale <dd55@...143...> wrote:

> Using some dedicated class-instance instead of a dict would allow for:
> - automatic conistency checks on setting
> - possibly even change propagation (likely faster and certainly less
> error prone if done right: I'm not sure how many places rcParams dependent
> results apart from the tex-stuff are cached and should be recomputed when
> certain params change, but obviously an push/'event'-based solutin could be
> faster than pull/polling)
This sounds like a good proposal to me. Some other devs had considered using
the traits package to address some of these points. Maybe they will comment.

> 2. It also seems to me that having some custom config file format rather
> than just (a literal-only subset of) python is suboptimal. Why make people
> learn another and more limited syntax when python literals are already
> familiar and both more powerful and easier to use

If someone donated a nickel for every time I have seen this argument on the
mailing list, we might have enough money to buy John a doughnut.

John Hunter wrote:
[...]

Yes, we've often talked about using traits here and elsewhere but the
idea has languished because it has never made it to the top of
anyone's priority queue. I just added examples/rc_traits.py, which is
the example code I wrote some time ago when I was exploring how to use
traits for rc and other mpl properties. I'll also post it again here
in case someone wants to run with it.

Apart from the (significant) questions of time and priorities, I backed off a couple times from investigating traits because the enthought package does not seem to be a nice, clean, easily-installable chunk as it stands, and because the last time I looked at it, it used its own numerix support for arrays; I suspect it still does. And there is always the concern about adding yet another learning curve to mpl development; so I still don't know whether it would be a net benefit if the other impediments were removed.

Eric