3671 lines
		
	
	
		
			148 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			3671 lines
		
	
	
		
			148 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#/usr/bin/env python
 | 
						|
 | 
						|
# svgfig.py copyright (C) 2008 Jim Pivarski <jpivarski@gmail.com>
 | 
						|
#
 | 
						|
# This program is free software; you can redistribute it and/or
 | 
						|
# modify it under the terms of the GNU General Public License
 | 
						|
# as published by the Free Software Foundation; either version 2
 | 
						|
# of the License, or (at your option) any later version.
 | 
						|
#
 | 
						|
# This program is distributed in the hope that it will be useful,
 | 
						|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
# GNU General Public License for more details.
 | 
						|
#
 | 
						|
# You should have received a copy of the GNU General Public License
 | 
						|
# along with this program; if not, write to the Free Software
 | 
						|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 | 
						|
#
 | 
						|
# Full licence is in the file COPYING and at http://www.gnu.org/copyleft/gpl.html
 | 
						|
 | 
						|
import re, codecs, os, platform, copy, itertools, math, cmath, random, sys, copy
 | 
						|
_epsilon = 1e-5
 | 
						|
 | 
						|
 | 
						|
if re.search("windows", platform.system(), re.I):
 | 
						|
    try:
 | 
						|
        import _winreg
 | 
						|
        _default_directory = _winreg.QueryValueEx(_winreg.OpenKey(_winreg.HKEY_CURRENT_USER,
 | 
						|
                             r"Software\Microsoft\Windows\Current Version\Explorer\Shell Folders"), "Desktop")[0]
 | 
						|
#   tmpdir = _winreg.QueryValueEx(_winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Environment"), "TEMP")[0]
 | 
						|
#   if tmpdir[0:13] != "%USERPROFILE%":
 | 
						|
#     tmpdir = os.path.expanduser("~") + tmpdir[13:]
 | 
						|
    except:
 | 
						|
        _default_directory = os.path.expanduser("~") + os.sep + "Desktop"
 | 
						|
 | 
						|
_default_fileName = "tmp.svg"
 | 
						|
 | 
						|
_hacks = {}
 | 
						|
_hacks["inkscape-text-vertical-shift"] = False
 | 
						|
 | 
						|
 | 
						|
def rgb(r, g, b, maximum=1.):
 | 
						|
    """Create an SVG color string "#xxyyzz" from r, g, and b.
 | 
						|
 | 
						|
    r,g,b = 0 is black and r,g,b = maximum is white.
 | 
						|
    """
 | 
						|
    return "#%02x%02x%02x" % (max(0, min(r*255./maximum, 255)),
 | 
						|
                              max(0, min(g*255./maximum, 255)),
 | 
						|
                              max(0, min(b*255./maximum, 255)))
 | 
						|
 | 
						|
def attr_preprocess(attr):
 | 
						|
    for name in attr.keys():
 | 
						|
        name_colon = re.sub("__", ":", name)
 | 
						|
        if name_colon != name:
 | 
						|
            attr[name_colon] = attr[name]
 | 
						|
            del attr[name]
 | 
						|
            name = name_colon
 | 
						|
 | 
						|
        name_dash = re.sub("_", "-", name)
 | 
						|
        if name_dash != name:
 | 
						|
            attr[name_dash] = attr[name]
 | 
						|
            del attr[name]
 | 
						|
            name = name_dash
 | 
						|
 | 
						|
    return attr
 | 
						|
 | 
						|
 | 
						|
class SVG:
 | 
						|
    """A tree representation of an SVG image or image fragment.
 | 
						|
 | 
						|
    SVG(t, sub, sub, sub..., attribute=value)
 | 
						|
 | 
						|
    t                       required             SVG type name
 | 
						|
    sub                     optional list        nested SVG elements or text/Unicode
 | 
						|
    attribute=value pairs   optional keywords    SVG attributes
 | 
						|
 | 
						|
    In attribute names, "__" becomes ":" and "_" becomes "-".
 | 
						|
 | 
						|
    SVG in XML
 | 
						|
 | 
						|
    <g id="mygroup" fill="blue">
 | 
						|
        <rect x="1" y="1" width="2" height="2" />
 | 
						|
        <rect x="3" y="3" width="2" height="2" />
 | 
						|
    </g>
 | 
						|
 | 
						|
    SVG in Python
 | 
						|
 | 
						|
    >>> svg = SVG("g", SVG("rect", x=1, y=1, width=2, height=2), \
 | 
						|
    ...                SVG("rect", x=3, y=3, width=2, height=2), \
 | 
						|
    ...           id="mygroup", fill="blue")
 | 
						|
 | 
						|
    Sub-elements and attributes may be accessed through tree-indexing:
 | 
						|
 | 
						|
    >>> svg = SVG("text", SVG("tspan", "hello there"), stroke="none", fill="black")
 | 
						|
    >>> svg[0]
 | 
						|
    <tspan (1 sub) />
 | 
						|
    >>> svg[0, 0]
 | 
						|
    'hello there'
 | 
						|
    >>> svg["fill"]
 | 
						|
    'black'
 | 
						|
 | 
						|
    Iteration is depth-first:
 | 
						|
 | 
						|
    >>> svg = SVG("g", SVG("g", SVG("line", x1=0, y1=0, x2=1, y2=1)), \
 | 
						|
    ...                SVG("text", SVG("tspan", "hello again")))
 | 
						|
    ...
 | 
						|
    >>> for ti, s in svg:
 | 
						|
    ...     print ti, repr(s)
 | 
						|
    ...
 | 
						|
    (0,) <g (1 sub) />
 | 
						|
    (0, 0) <line x2=1 y1=0 x1=0 y2=1 />
 | 
						|
    (0, 0, 'x2') 1
 | 
						|
    (0, 0, 'y1') 0
 | 
						|
    (0, 0, 'x1') 0
 | 
						|
    (0, 0, 'y2') 1
 | 
						|
    (1,) <text (1 sub) />
 | 
						|
    (1, 0) <tspan (1 sub) />
 | 
						|
    (1, 0, 0) 'hello again'
 | 
						|
 | 
						|
    Use "print" to navigate:
 | 
						|
 | 
						|
    >>> print svg
 | 
						|
    None                 <g (2 sub) />
 | 
						|
    [0]                      <g (1 sub) />
 | 
						|
    [0, 0]                       <line x2=1 y1=0 x1=0 y2=1 />
 | 
						|
    [1]                      <text (1 sub) />
 | 
						|
    [1, 0]                       <tspan (1 sub) />
 | 
						|
    """
 | 
						|
    def __init__(self, *t_sub, **attr):
 | 
						|
        if len(t_sub) == 0:
 | 
						|
            raise TypeError, "SVG element must have a t (SVG type)"
 | 
						|
 | 
						|
        # first argument is t (SVG type)
 | 
						|
        self.t = t_sub[0]
 | 
						|
        # the rest are sub-elements
 | 
						|
        self.sub = list(t_sub[1:])
 | 
						|
 | 
						|
        # keyword arguments are attributes
 | 
						|
        # need to preprocess to handle differences between SVG and Python syntax
 | 
						|
        self.attr = attr_preprocess(attr)
 | 
						|
 | 
						|
    def __getitem__(self, ti):
 | 
						|
        """Index is a list that descends tree, returning a sub-element if
 | 
						|
        it ends with a number and an attribute if it ends with a string."""
 | 
						|
        obj = self
 | 
						|
        if isinstance(ti, (list, tuple)):
 | 
						|
            for i in ti[:-1]:
 | 
						|
                obj = obj[i]
 | 
						|
            ti = ti[-1]
 | 
						|
 | 
						|
        if isinstance(ti, (int, long, slice)):
 | 
						|
            return obj.sub[ti]
 | 
						|
        else:
 | 
						|
            return obj.attr[ti]
 | 
						|
 | 
						|
    def __setitem__(self, ti, value):
 | 
						|
        """Index is a list that descends tree, returning a sub-element if
 | 
						|
        it ends with a number and an attribute if it ends with a string."""
 | 
						|
        obj = self
 | 
						|
        if isinstance(ti, (list, tuple)):
 | 
						|
            for i in ti[:-1]:
 | 
						|
                obj = obj[i]
 | 
						|
            ti = ti[-1]
 | 
						|
 | 
						|
        if isinstance(ti, (int, long, slice)):
 | 
						|
            obj.sub[ti] = value
 | 
						|
        else:
 | 
						|
            obj.attr[ti] = value
 | 
						|
 | 
						|
    def __delitem__(self, ti):
 | 
						|
        """Index is a list that descends tree, returning a sub-element if
 | 
						|
        it ends with a number and an attribute if it ends with a string."""
 | 
						|
        obj = self
 | 
						|
        if isinstance(ti, (list, tuple)):
 | 
						|
            for i in ti[:-1]:
 | 
						|
                obj = obj[i]
 | 
						|
            ti = ti[-1]
 | 
						|
 | 
						|
        if isinstance(ti, (int, long, slice)):
 | 
						|
            del obj.sub[ti]
 | 
						|
        else:
 | 
						|
            del obj.attr[ti]
 | 
						|
 | 
						|
    def __contains__(self, value):
 | 
						|
        """x in svg == True iff x is an attribute in svg."""
 | 
						|
        return value in self.attr
 | 
						|
 | 
						|
    def __eq__(self, other):
 | 
						|
        """x == y iff x represents the same SVG as y."""
 | 
						|
        if id(self) == id(other):
 | 
						|
            return True
 | 
						|
        return (isinstance(other, SVG) and
 | 
						|
                self.t == other.t and self.sub == other.sub and self.attr == other.attr)
 | 
						|
 | 
						|
    def __ne__(self, other):
 | 
						|
        """x != y iff x does not represent the same SVG as y."""
 | 
						|
        return not (self == other)
 | 
						|
 | 
						|
    def append(self, x):
 | 
						|
        """Appends x to the list of sub-elements (drawn last, overlaps
 | 
						|
        other primitives)."""
 | 
						|
        self.sub.append(x)
 | 
						|
 | 
						|
    def prepend(self, x):
 | 
						|
        """Prepends x to the list of sub-elements (drawn first may be
 | 
						|
        overlapped by other primitives)."""
 | 
						|
        self.sub[0:0] = [x]
 | 
						|
 | 
						|
    def extend(self, x):
 | 
						|
        """Extends list of sub-elements by a list x."""
 | 
						|
        self.sub.extend(x)
 | 
						|
 | 
						|
    def clone(self, shallow=False):
 | 
						|
        """Deep copy of SVG tree.  Set shallow=True for a shallow copy."""
 | 
						|
        if shallow:
 | 
						|
            return copy.copy(self)
 | 
						|
        else:
 | 
						|
            return copy.deepcopy(self)
 | 
						|
 | 
						|
    ### nested class
 | 
						|
    class SVGDepthIterator:
 | 
						|
        """Manages SVG iteration."""
 | 
						|
 | 
						|
        def __init__(self, svg, ti, depth_limit):
 | 
						|
            self.svg = svg
 | 
						|
            self.ti = ti
 | 
						|
            self.shown = False
 | 
						|
            self.depth_limit = depth_limit
 | 
						|
 | 
						|
        def __iter__(self):
 | 
						|
            return self
 | 
						|
 | 
						|
        def next(self):
 | 
						|
            if not self.shown:
 | 
						|
                self.shown = True
 | 
						|
                if self.ti != ():
 | 
						|
                    return self.ti, self.svg
 | 
						|
 | 
						|
            if not isinstance(self.svg, SVG):
 | 
						|
                raise StopIteration
 | 
						|
            if self.depth_limit is not None and len(self.ti) >= self.depth_limit:
 | 
						|
                raise StopIteration
 | 
						|
 | 
						|
            if "iterators" not in self.__dict__:
 | 
						|
                self.iterators = []
 | 
						|
                for i, s in enumerate(self.svg.sub):
 | 
						|
                    self.iterators.append(self.__class__(s, self.ti + (i,), self.depth_limit))
 | 
						|
                for k, s in self.svg.attr.items():
 | 
						|
                    self.iterators.append(self.__class__(s, self.ti + (k,), self.depth_limit))
 | 
						|
                self.iterators = itertools.chain(*self.iterators)
 | 
						|
 | 
						|
            return self.iterators.next()
 | 
						|
    ### end nested class
 | 
						|
 | 
						|
    def depth_first(self, depth_limit=None):
 | 
						|
        """Returns a depth-first generator over the SVG.  If depth_limit
 | 
						|
        is a number, stop recursion at that depth."""
 | 
						|
        return self.SVGDepthIterator(self, (), depth_limit)
 | 
						|
 | 
						|
    def breadth_first(self, depth_limit=None):
 | 
						|
        """Not implemented yet.  Any ideas on how to do it?
 | 
						|
 | 
						|
        Returns a breadth-first generator over the SVG.  If depth_limit
 | 
						|
        is a number, stop recursion at that depth."""
 | 
						|
        raise NotImplementedError, "Got an algorithm for breadth-first searching a tree without effectively copying the tree?"
 | 
						|
 | 
						|
    def __iter__(self):
 | 
						|
        return self.depth_first()
 | 
						|
 | 
						|
    def items(self, sub=True, attr=True, text=True):
 | 
						|
        """Get a recursively-generated list of tree-index, sub-element/attribute pairs.
 | 
						|
 | 
						|
        If sub == False, do not show sub-elements.
 | 
						|
        If attr == False, do not show attributes.
 | 
						|
        If text == False, do not show text/Unicode sub-elements.
 | 
						|
        """
 | 
						|
        output = []
 | 
						|
        for ti, s in self:
 | 
						|
            show = False
 | 
						|
            if isinstance(ti[-1], (int, long)):
 | 
						|
                if isinstance(s, basestring):
 | 
						|
                    show = text
 | 
						|
                else:
 | 
						|
                    show = sub
 | 
						|
            else:
 | 
						|
                show = attr
 | 
						|
 | 
						|
            if show:
 | 
						|
                output.append((ti, s))
 | 
						|
        return output
 | 
						|
 | 
						|
    def keys(self, sub=True, attr=True, text=True):
 | 
						|
        """Get a recursively-generated list of tree-indexes.
 | 
						|
 | 
						|
        If sub == False, do not show sub-elements.
 | 
						|
        If attr == False, do not show attributes.
 | 
						|
        If text == False, do not show text/Unicode sub-elements.
 | 
						|
        """
 | 
						|
        return [ti for ti, s in self.items(sub, attr, text)]
 | 
						|
 | 
						|
    def values(self, sub=True, attr=True, text=True):
 | 
						|
        """Get a recursively-generated list of sub-elements and attributes.
 | 
						|
 | 
						|
        If sub == False, do not show sub-elements.
 | 
						|
        If attr == False, do not show attributes.
 | 
						|
        If text == False, do not show text/Unicode sub-elements.
 | 
						|
        """
 | 
						|
        return [s for ti, s in self.items(sub, attr, text)]
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return self.xml(depth_limit=0)
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        """Print (actually, return a string of) the tree in a form useful for browsing."""
 | 
						|
        return self.tree(sub=True, attr=False, text=False)
 | 
						|
 | 
						|
    def tree(self, depth_limit=None, sub=True, attr=True, text=True, tree_width=20, obj_width=80):
 | 
						|
        """Print (actually, return a string of) the tree in a form useful for browsing.
 | 
						|
 | 
						|
        If depth_limit == a number, stop recursion at that depth.
 | 
						|
        If sub == False, do not show sub-elements.
 | 
						|
        If attr == False, do not show attributes.
 | 
						|
        If text == False, do not show text/Unicode sub-elements.
 | 
						|
        tree_width is the number of characters reserved for printing tree indexes.
 | 
						|
        obj_width is the number of characters reserved for printing sub-elements/attributes.
 | 
						|
        """
 | 
						|
        output = []
 | 
						|
 | 
						|
        line = "%s %s" % (("%%-%ds" % tree_width) % repr(None),
 | 
						|
                          ("%%-%ds" % obj_width) % (repr(self))[0:obj_width])
 | 
						|
        output.append(line)
 | 
						|
 | 
						|
        for ti, s in self.depth_first(depth_limit):
 | 
						|
            show = False
 | 
						|
            if isinstance(ti[-1], (int, long)):
 | 
						|
                if isinstance(s, basestring):
 | 
						|
                    show = text
 | 
						|
                else:
 | 
						|
                    show = sub
 | 
						|
            else:
 | 
						|
                show = attr
 | 
						|
 | 
						|
            if show:
 | 
						|
                line = "%s %s" % (("%%-%ds" % tree_width) % repr(list(ti)),
 | 
						|
                                  ("%%-%ds" % obj_width) % ("    "*len(ti) + repr(s))[0:obj_width])
 | 
						|
                output.append(line)
 | 
						|
 | 
						|
        return "\n".join(output)
 | 
						|
 | 
						|
    def xml(self, indent=u"    ", newl=u"\n", depth_limit=None, depth=0):
 | 
						|
        """Get an XML representation of the SVG.
 | 
						|
 | 
						|
        indent      string used for indenting
 | 
						|
        newl        string used for newlines
 | 
						|
        If depth_limit == a number, stop recursion at that depth.
 | 
						|
        depth       starting depth (not useful for users)
 | 
						|
 | 
						|
        print svg.xml()
 | 
						|
        """
 | 
						|
        attrstr = []
 | 
						|
        for n, v in self.attr.items():
 | 
						|
            if isinstance(v, dict):
 | 
						|
                v = u"; ".join([u"%s:%s" % (ni, vi) for ni, vi in v.items()])
 | 
						|
            elif isinstance(v, (list, tuple)):
 | 
						|
                v = u", ".join(v)
 | 
						|
            attrstr.append(u" %s=%s" % (n, repr(v)))
 | 
						|
        attrstr = u"".join(attrstr)
 | 
						|
 | 
						|
        if len(self.sub) == 0:
 | 
						|
            return u"%s<%s%s />" % (indent * depth, self.t, attrstr)
 | 
						|
 | 
						|
        if depth_limit is None or depth_limit > depth:
 | 
						|
            substr = []
 | 
						|
            for s in self.sub:
 | 
						|
                if isinstance(s, SVG):
 | 
						|
                    substr.append(s.xml(indent, newl, depth_limit, depth + 1) + newl)
 | 
						|
                elif isinstance(s, basestring):
 | 
						|
                    substr.append(u"%s%s%s" % (indent * (depth + 1), s, newl))
 | 
						|
                else:
 | 
						|
                    substr.append("%s%s%s" % (indent * (depth + 1), repr(s), newl))
 | 
						|
            substr = u"".join(substr)
 | 
						|
 | 
						|
            return u"%s<%s%s>%s%s%s</%s>" % (indent * depth, self.t, attrstr, newl, substr, indent * depth, self.t)
 | 
						|
 | 
						|
        else:
 | 
						|
            return u"%s<%s (%d sub)%s />" % (indent * depth, self.t, len(self.sub), attrstr)
 | 
						|
 | 
						|
    def standalone_xml(self, indent=u"    ", newl=u"\n", encoding=u"utf-8"):
 | 
						|
        """Get an XML representation of the SVG that can be saved/rendered.
 | 
						|
 | 
						|
        indent      string used for indenting
 | 
						|
        newl        string used for newlines
 | 
						|
        """
 | 
						|
 | 
						|
        if self.t == "svg":
 | 
						|
            top = self
 | 
						|
        else:
 | 
						|
            top = canvas(self)
 | 
						|
        return u"""\
 | 
						|
<?xml version="1.0" encoding="%s" standalone="no"?>
 | 
						|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 | 
						|
 | 
						|
""" % encoding + (u"".join(top.__standalone_xml(indent, newl)))  # end of return statement
 | 
						|
 | 
						|
    def __standalone_xml(self, indent, newl):
 | 
						|
        output = [u"<%s" % self.t]
 | 
						|
 | 
						|
        for n, v in self.attr.items():
 | 
						|
            if isinstance(v, dict):
 | 
						|
                v = u"; ".join([u"%s:%s" % (ni, vi) for ni, vi in v.items()])
 | 
						|
            elif isinstance(v, (list, tuple)):
 | 
						|
                v = u", ".join(v)
 | 
						|
            output.append(u' %s="%s"' % (n, v))
 | 
						|
 | 
						|
        if len(self.sub) == 0:
 | 
						|
            output.append(u" />%s%s" % (newl, newl))
 | 
						|
            return output
 | 
						|
 | 
						|
        elif self.t == "text" or self.t == "tspan" or self.t == "style":
 | 
						|
            output.append(u">")
 | 
						|
 | 
						|
        else:
 | 
						|
            output.append(u">%s%s" % (newl, newl))
 | 
						|
 | 
						|
        for s in self.sub:
 | 
						|
            if isinstance(s, SVG):
 | 
						|
                output.extend(s.__standalone_xml(indent, newl))
 | 
						|
            else:
 | 
						|
                output.append(unicode(s))
 | 
						|
 | 
						|
        if self.t == "tspan":
 | 
						|
            output.append(u"</%s>" % self.t)
 | 
						|
        else:
 | 
						|
            output.append(u"</%s>%s%s" % (self.t, newl, newl))
 | 
						|
 | 
						|
        return output
 | 
						|
 | 
						|
    def interpret_fileName(self, fileName=None):
 | 
						|
        if fileName is None:
 | 
						|
            fileName = _default_fileName
 | 
						|
        if re.search("windows", platform.system(), re.I) and not os.path.isabs(fileName):
 | 
						|
            fileName = _default_directory + os.sep + fileName
 | 
						|
        return fileName
 | 
						|
 | 
						|
    def save(self, fileName=None, encoding="utf-8", compresslevel=None):
 | 
						|
        """Save to a file for viewing.  Note that svg.save() overwrites the file named _default_fileName.
 | 
						|
 | 
						|
        fileName        default=None            note that _default_fileName will be overwritten if
 | 
						|
                                                no fileName is specified. If the extension
 | 
						|
                                                is ".svgz" or ".gz", the output will be gzipped
 | 
						|
        encoding        default="utf-8"         file encoding
 | 
						|
        compresslevel   default=None            if a number, the output will be gzipped with that
 | 
						|
                                                compression level (1-9, 1 being fastest and 9 most
 | 
						|
                                                thorough)
 | 
						|
        """
 | 
						|
        fileName = self.interpret_fileName(fileName)
 | 
						|
 | 
						|
        if compresslevel is not None or re.search(r"\.svgz$", fileName, re.I) or re.search(r"\.gz$", fileName, re.I):
 | 
						|
            import gzip
 | 
						|
            if compresslevel is None:
 | 
						|
                f = gzip.GzipFile(fileName, "w")
 | 
						|
            else:
 | 
						|
                f = gzip.GzipFile(fileName, "w", compresslevel)
 | 
						|
 | 
						|
            f = codecs.EncodedFile(f, "utf-8", encoding)
 | 
						|
            f.write(self.standalone_xml(encoding=encoding))
 | 
						|
            f.close()
 | 
						|
 | 
						|
        else:
 | 
						|
            f = codecs.open(fileName, "w", encoding=encoding)
 | 
						|
            f.write(self.standalone_xml(encoding=encoding))
 | 
						|
            f.close()
 | 
						|
 | 
						|
    def inkview(self, fileName=None, encoding="utf-8"):
 | 
						|
        """View in "inkview", assuming that program is available on your system.
 | 
						|
 | 
						|
        fileName        default=None            note that any file named _default_fileName will be
 | 
						|
                                                overwritten if no fileName is specified. If the extension
 | 
						|
                                                is ".svgz" or ".gz", the output will be gzipped
 | 
						|
        encoding        default="utf-8"         file encoding
 | 
						|
        """
 | 
						|
        fileName = self.interpret_fileName(fileName)
 | 
						|
        self.save(fileName, encoding)
 | 
						|
        os.spawnvp(os.P_NOWAIT, "inkview", ("inkview", fileName))
 | 
						|
 | 
						|
    def inkscape(self, fileName=None, encoding="utf-8"):
 | 
						|
        """View in "inkscape", assuming that program is available on your system.
 | 
						|
 | 
						|
        fileName        default=None            note that any file named _default_fileName will be
 | 
						|
                                                overwritten if no fileName is specified. If the extension
 | 
						|
                                                is ".svgz" or ".gz", the output will be gzipped
 | 
						|
        encoding        default="utf-8"         file encoding
 | 
						|
        """
 | 
						|
        fileName = self.interpret_fileName(fileName)
 | 
						|
        self.save(fileName, encoding)
 | 
						|
        os.spawnvp(os.P_NOWAIT, "inkscape", ("inkscape", fileName))
 | 
						|
 | 
						|
    def firefox(self, fileName=None, encoding="utf-8"):
 | 
						|
        """View in "firefox", assuming that program is available on your system.
 | 
						|
 | 
						|
        fileName        default=None            note that any file named _default_fileName will be
 | 
						|
                                                overwritten if no fileName is specified. If the extension
 | 
						|
                                                is ".svgz" or ".gz", the output will be gzipped
 | 
						|
        encoding        default="utf-8"         file encoding
 | 
						|
        """
 | 
						|
        fileName = self.interpret_fileName(fileName)
 | 
						|
        self.save(fileName, encoding)
 | 
						|
        os.spawnvp(os.P_NOWAIT, "firefox", ("firefox", fileName))
 | 
						|
 | 
						|
######################################################################
 | 
						|
 | 
						|
_canvas_defaults = {"width": "400px",
 | 
						|
                    "height": "400px",
 | 
						|
                    "viewBox": "0 0 100 100",
 | 
						|
                    "xmlns": "http://www.w3.org/2000/svg",
 | 
						|
                    "xmlns:xlink": "http://www.w3.org/1999/xlink",
 | 
						|
                    "version": "1.1",
 | 
						|
                    "style": {"stroke": "black",
 | 
						|
                              "fill": "none",
 | 
						|
                              "stroke-width": "0.5pt",
 | 
						|
                              "stroke-linejoin": "round",
 | 
						|
                              "text-anchor": "middle",
 | 
						|
                             },
 | 
						|
                    "font-family": ["Helvetica", "Arial", "FreeSans", "Sans", "sans", "sans-serif"],
 | 
						|
                   }
 | 
						|
 | 
						|
def canvas(*sub, **attr):
 | 
						|
    """Creates a top-level SVG object, allowing the user to control the
 | 
						|
    image size and aspect ratio.
 | 
						|
 | 
						|
    canvas(sub, sub, sub..., attribute=value)
 | 
						|
 | 
						|
    sub                     optional list       nested SVG elements or text/Unicode
 | 
						|
    attribute=value pairs   optional keywords   SVG attributes
 | 
						|
 | 
						|
    Default attribute values:
 | 
						|
 | 
						|
    width           "400px"
 | 
						|
    height          "400px"
 | 
						|
    viewBox         "0 0 100 100"
 | 
						|
    xmlns           "http://www.w3.org/2000/svg"
 | 
						|
    xmlns:xlink     "http://www.w3.org/1999/xlink"
 | 
						|
    version         "1.1"
 | 
						|
    style           "stroke:black; fill:none; stroke-width:0.5pt; stroke-linejoin:round; text-anchor:middle"
 | 
						|
    font-family     "Helvetica,Arial,FreeSans?,Sans,sans,sans-serif"
 | 
						|
    """
 | 
						|
    attributes = dict(_canvas_defaults)
 | 
						|
    attributes.update(attr)
 | 
						|
 | 
						|
    if sub is None or sub == ():
 | 
						|
        return SVG("svg", **attributes)
 | 
						|
    else:
 | 
						|
        return SVG("svg", *sub, **attributes)
 | 
						|
 | 
						|
def canvas_outline(*sub, **attr):
 | 
						|
    """Same as canvas(), but draws an outline around the drawable area,
 | 
						|
    so that you know how close your image is to the edges."""
 | 
						|
    svg = canvas(*sub, **attr)
 | 
						|
    match = re.match(r"[, \t]*([0-9e.+\-]+)[, \t]+([0-9e.+\-]+)[, \t]+([0-9e.+\-]+)[, \t]+([0-9e.+\-]+)[, \t]*", svg["viewBox"])
 | 
						|
    if match is None:
 | 
						|
        raise ValueError, "canvas viewBox is incorrectly formatted"
 | 
						|
    x, y, width, height = [float(x) for x in match.groups()]
 | 
						|
    svg.prepend(SVG("rect", x=x, y=y, width=width, height=height, stroke="none", fill="cornsilk"))
 | 
						|
    svg.append(SVG("rect", x=x, y=y, width=width, height=height, stroke="black", fill="none"))
 | 
						|
    return svg
 | 
						|
 | 
						|
def template(fileName, svg, replaceme="REPLACEME"):
 | 
						|
    """Loads an SVG image from a file, replacing instances of
 | 
						|
    <REPLACEME /> with a given svg object.
 | 
						|
 | 
						|
    fileName         required                name of the template SVG
 | 
						|
    svg              required                SVG object for replacement
 | 
						|
    replaceme        default="REPLACEME"     fake SVG element to be replaced by the given object
 | 
						|
 | 
						|
    >>> print load("template.svg")
 | 
						|
    None                 <svg (2 sub) style=u'stroke:black; fill:none; stroke-width:0.5pt; stroke-linejoi
 | 
						|
    [0]                      <rect height=u'100' width=u'100' stroke=u'none' y=u'0' x=u'0' fill=u'yellow'
 | 
						|
    [1]                      <REPLACEME />
 | 
						|
    >>>
 | 
						|
    >>> print template("template.svg", SVG("circle", cx=50, cy=50, r=30))
 | 
						|
    None                 <svg (2 sub) style=u'stroke:black; fill:none; stroke-width:0.5pt; stroke-linejoi
 | 
						|
    [0]                      <rect height=u'100' width=u'100' stroke=u'none' y=u'0' x=u'0' fill=u'yellow'
 | 
						|
    [1]                      <circle cy=50 cx=50 r=30 />
 | 
						|
    """
 | 
						|
    output = load(fileName)
 | 
						|
    for ti, s in output:
 | 
						|
        if isinstance(s, SVG) and s.t == replaceme:
 | 
						|
            output[ti] = svg
 | 
						|
    return output
 | 
						|
 | 
						|
######################################################################
 | 
						|
 | 
						|
def load(fileName):
 | 
						|
    """Loads an SVG image from a file."""
 | 
						|
    return load_stream(file(fileName))
 | 
						|
 | 
						|
def load_stream(stream):
 | 
						|
    """Loads an SVG image from a stream (can be a string or a file object)."""
 | 
						|
 | 
						|
    from xml.sax import handler, make_parser
 | 
						|
    from xml.sax.handler import feature_namespaces, feature_external_ges, feature_external_pes
 | 
						|
 | 
						|
    class ContentHandler(handler.ContentHandler):
 | 
						|
        def __init__(self):
 | 
						|
            self.stack = []
 | 
						|
            self.output = None
 | 
						|
            self.all_whitespace = re.compile(r"^\s*$")
 | 
						|
 | 
						|
        def startElement(self, name, attr):
 | 
						|
            s = SVG(name)
 | 
						|
            s.attr = dict(attr.items())
 | 
						|
            if len(self.stack) > 0:
 | 
						|
                last = self.stack[-1]
 | 
						|
                last.sub.append(s)
 | 
						|
            self.stack.append(s)
 | 
						|
 | 
						|
        def characters(self, ch):
 | 
						|
            if not isinstance(ch, basestring) or self.all_whitespace.match(ch) is None:
 | 
						|
                if len(self.stack) > 0:
 | 
						|
                    last = self.stack[-1]
 | 
						|
                    if len(last.sub) > 0 and isinstance(last.sub[-1], basestring):
 | 
						|
                        last.sub[-1] = last.sub[-1] + "\n" + ch
 | 
						|
                    else:
 | 
						|
                        last.sub.append(ch)
 | 
						|
 | 
						|
        def endElement(self, name):
 | 
						|
            if len(self.stack) > 0:
 | 
						|
                last = self.stack[-1]
 | 
						|
                if (isinstance(last, SVG) and last.t == "style" and
 | 
						|
                    "type" in last.attr and last.attr["type"] == "text/css" and
 | 
						|
                    len(last.sub) == 1 and isinstance(last.sub[0], basestring)):
 | 
						|
                    last.sub[0] = "<![CDATA[\n" + last.sub[0] + "]]>"
 | 
						|
 | 
						|
            self.output = self.stack.pop()
 | 
						|
 | 
						|
    ch = ContentHandler()
 | 
						|
    parser = make_parser()
 | 
						|
    parser.setContentHandler(ch)
 | 
						|
    parser.setFeature(feature_namespaces, 0)
 | 
						|
    parser.setFeature(feature_external_ges, 0)
 | 
						|
    parser.parse(stream)
 | 
						|
    return ch.output
 | 
						|
 | 
						|
######################################################################
 | 
						|
def set_func_name(f, name):
 | 
						|
    """try to patch the function name string into a function object"""
 | 
						|
    try:
 | 
						|
        f.func_name = name
 | 
						|
    except TypeError:
 | 
						|
        # py 2.3 raises: TypeError: readonly attribute
 | 
						|
        pass
 | 
						|
 | 
						|
def totrans(expr, vars=("x", "y"), globals=None, locals=None):
 | 
						|
    """Converts to a coordinate transformation (a function that accepts
 | 
						|
    two arguments and returns two values).
 | 
						|
 | 
						|
    expr       required                  a string expression or a function
 | 
						|
                                         of two real or one complex value
 | 
						|
    vars       default=("x", "y")        independent variable names; a singleton
 | 
						|
                                         ("z",) is interpreted as complex
 | 
						|
    globals    default=None              dict of global variables
 | 
						|
    locals     default=None              dict of local variables
 | 
						|
    """
 | 
						|
    if locals is None:
 | 
						|
        locals = {}  # python 2.3's eval() won't accept None
 | 
						|
 | 
						|
    if callable(expr):
 | 
						|
        if expr.func_code.co_argcount == 2:
 | 
						|
            return expr
 | 
						|
 | 
						|
        elif expr.func_code.co_argcount == 1:
 | 
						|
            split = lambda z: (z.real, z.imag)
 | 
						|
            output = lambda x, y: split(expr(x + y*1j))
 | 
						|
            set_func_name(output, expr.func_name)
 | 
						|
            return output
 | 
						|
 | 
						|
        else:
 | 
						|
            raise TypeError, "must be a function of 2 or 1 variables"
 | 
						|
 | 
						|
    if len(vars) == 2:
 | 
						|
        g = math.__dict__
 | 
						|
        if globals is not None:
 | 
						|
            g.update(globals)
 | 
						|
        output = eval("lambda %s, %s: (%s)" % (vars[0], vars[1], expr), g, locals)
 | 
						|
        set_func_name(output, "%s,%s -> %s" % (vars[0], vars[1], expr))
 | 
						|
        return output
 | 
						|
 | 
						|
    elif len(vars) == 1:
 | 
						|
        g = cmath.__dict__
 | 
						|
        if globals is not None:
 | 
						|
            g.update(globals)
 | 
						|
        output = eval("lambda %s: (%s)" % (vars[0], expr), g, locals)
 | 
						|
        split = lambda z: (z.real, z.imag)
 | 
						|
        output2 = lambda x, y: split(output(x + y*1j))
 | 
						|
        set_func_name(output2, "%s -> %s" % (vars[0], expr))
 | 
						|
        return output2
 | 
						|
 | 
						|
    else:
 | 
						|
        raise TypeError, "vars must have 2 or 1 elements"
 | 
						|
 | 
						|
 | 
						|
def window(xmin, xmax, ymin, ymax, x=0, y=0, width=100, height=100,
 | 
						|
           xlogbase=None, ylogbase=None, minusInfinity=-1000, flipx=False, flipy=True):
 | 
						|
    """Creates and returns a coordinate transformation (a function that
 | 
						|
    accepts two arguments and returns two values) that transforms from
 | 
						|
        (xmin, ymin), (xmax, ymax)
 | 
						|
    to
 | 
						|
        (x, y), (x + width, y + height).
 | 
						|
 | 
						|
    xlogbase, ylogbase    default=None, None     if a number, transform
 | 
						|
                                                 logarithmically with given base
 | 
						|
    minusInfinity         default=-1000          what to return if
 | 
						|
                                                 log(0 or negative) is attempted
 | 
						|
    flipx                 default=False          if true, reverse the direction of x
 | 
						|
    flipy                 default=True           if true, reverse the direction of y
 | 
						|
 | 
						|
    (When composing windows, be sure to set flipy=False.)
 | 
						|
    """
 | 
						|
 | 
						|
    if flipx:
 | 
						|
        ox1 = x + width
 | 
						|
        ox2 = x
 | 
						|
    else:
 | 
						|
        ox1 = x
 | 
						|
        ox2 = x + width
 | 
						|
    if flipy:
 | 
						|
        oy1 = y + height
 | 
						|
        oy2 = y
 | 
						|
    else:
 | 
						|
        oy1 = y
 | 
						|
        oy2 = y + height
 | 
						|
    ix1 = xmin
 | 
						|
    iy1 = ymin
 | 
						|
    ix2 = xmax
 | 
						|
    iy2 = ymax
 | 
						|
 | 
						|
    if xlogbase is not None and (ix1 <= 0. or ix2 <= 0.):
 | 
						|
        raise ValueError, "x range incompatible with log scaling: (%g, %g)" % (ix1, ix2)
 | 
						|
 | 
						|
    if ylogbase is not None and (iy1 <= 0. or iy2 <= 0.):
 | 
						|
        raise ValueError, "y range incompatible with log scaling: (%g, %g)" % (iy1, iy2)
 | 
						|
 | 
						|
    def maybelog(t, it1, it2, ot1, ot2, logbase):
 | 
						|
        if t <= 0.:
 | 
						|
            return minusInfinity
 | 
						|
        else:
 | 
						|
            return ot1 + 1.*(math.log(t, logbase) - math.log(it1, logbase))/(math.log(it2, logbase) - math.log(it1, logbase)) * (ot2 - ot1)
 | 
						|
 | 
						|
    xlogstr, ylogstr = "", ""
 | 
						|
 | 
						|
    if xlogbase is None:
 | 
						|
        xfunc = lambda x: ox1 + 1.*(x - ix1)/(ix2 - ix1) * (ox2 - ox1)
 | 
						|
    else:
 | 
						|
        xfunc = lambda x: maybelog(x, ix1, ix2, ox1, ox2, xlogbase)
 | 
						|
        xlogstr = " xlog=%g" % xlogbase
 | 
						|
 | 
						|
    if ylogbase is None:
 | 
						|
        yfunc = lambda y: oy1 + 1.*(y - iy1)/(iy2 - iy1) * (oy2 - oy1)
 | 
						|
    else:
 | 
						|
        yfunc = lambda y: maybelog(y, iy1, iy2, oy1, oy2, ylogbase)
 | 
						|
        ylogstr = " ylog=%g" % ylogbase
 | 
						|
 | 
						|
    output = lambda x, y: (xfunc(x), yfunc(y))
 | 
						|
 | 
						|
    set_func_name(output, "(%g, %g), (%g, %g) -> (%g, %g), (%g, %g)%s%s" % (
 | 
						|
                          ix1, ix2, iy1, iy2, ox1, ox2, oy1, oy2, xlogstr, ylogstr))
 | 
						|
    return output
 | 
						|
 | 
						|
 | 
						|
def rotate(angle, cx=0, cy=0):
 | 
						|
    """Creates and returns a coordinate transformation which rotates
 | 
						|
    around (cx,cy) by "angle" degrees."""
 | 
						|
    angle *= math.pi/180.
 | 
						|
    return lambda x, y: (cx + math.cos(angle)*(x - cx) - math.sin(angle)*(y - cy), cy + math.sin(angle)*(x - cx) + math.cos(angle)*(y - cy))
 | 
						|
 | 
						|
 | 
						|
class Fig:
 | 
						|
    """Stores graphics primitive objects and applies a single coordinate
 | 
						|
    transformation to them. To compose coordinate systems, nest Fig
 | 
						|
    objects.
 | 
						|
 | 
						|
    Fig(obj, obj, obj..., trans=function)
 | 
						|
 | 
						|
    obj     optional list    a list of drawing primitives
 | 
						|
    trans   default=None     a coordinate transformation function
 | 
						|
 | 
						|
    >>> fig = Fig(Line(0,0,1,1), Rect(0.2,0.2,0.8,0.8), trans="2*x, 2*y")
 | 
						|
    >>> print fig.SVG().xml()
 | 
						|
    <g>
 | 
						|
        <path d='M0 0L2 2' />
 | 
						|
        <path d='M0.4 0.4L1.6 0.4ZL1.6 1.6ZL0.4 1.6ZL0.4 0.4ZZ' />
 | 
						|
    </g>
 | 
						|
    >>> print Fig(fig, trans="x/2., y/2.").SVG().xml()
 | 
						|
    <g>
 | 
						|
        <path d='M0 0L1 1' />
 | 
						|
        <path d='M0.2 0.2L0.8 0.2ZL0.8 0.8ZL0.2 0.8ZL0.2 0.2ZZ' />
 | 
						|
    </g>
 | 
						|
    """
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        if self.trans is None:
 | 
						|
            return "<Fig (%d items)>" % len(self.d)
 | 
						|
        elif isinstance(self.trans, basestring):
 | 
						|
            return "<Fig (%d items) x,y -> %s>" % (len(self.d), self.trans)
 | 
						|
        else:
 | 
						|
            return "<Fig (%d items) %s>" % (len(self.d), self.trans.func_name)
 | 
						|
 | 
						|
    def __init__(self, *d, **kwds):
 | 
						|
        self.d = list(d)
 | 
						|
        defaults = {"trans": None, }
 | 
						|
        defaults.update(kwds)
 | 
						|
        kwds = defaults
 | 
						|
 | 
						|
        self.trans = kwds["trans"]; del kwds["trans"]
 | 
						|
        if len(kwds) != 0:
 | 
						|
            raise TypeError, "Fig() got unexpected keyword arguments %s" % kwds.keys()
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object.
 | 
						|
 | 
						|
        Coordinate transformations in nested Figs will be composed.
 | 
						|
        """
 | 
						|
 | 
						|
        if trans is None:
 | 
						|
            trans = self.trans
 | 
						|
        if isinstance(trans, basestring):
 | 
						|
            trans = totrans(trans)
 | 
						|
 | 
						|
        output = SVG("g")
 | 
						|
        for s in self.d:
 | 
						|
            if isinstance(s, SVG):
 | 
						|
                output.append(s)
 | 
						|
 | 
						|
            elif isinstance(s, Fig):
 | 
						|
                strans = s.trans
 | 
						|
                if isinstance(strans, basestring):
 | 
						|
                    strans = totrans(strans)
 | 
						|
 | 
						|
                if trans is None:
 | 
						|
                    subtrans = strans
 | 
						|
                elif strans is None:
 | 
						|
                    subtrans = trans
 | 
						|
                else:
 | 
						|
                    subtrans = lambda x, y: trans(*strans(x, y))
 | 
						|
 | 
						|
                output.sub += s.SVG(subtrans).sub
 | 
						|
 | 
						|
            elif s is None:
 | 
						|
                pass
 | 
						|
 | 
						|
            else:
 | 
						|
                output.append(s.SVG(trans))
 | 
						|
 | 
						|
        return output
 | 
						|
 | 
						|
 | 
						|
class Plot:
 | 
						|
    """Acts like Fig, but draws a coordinate axis. You also need to supply plot ranges.
 | 
						|
 | 
						|
    Plot(xmin, xmax, ymin, ymax, obj, obj, obj..., keyword options...)
 | 
						|
 | 
						|
    xmin, xmax      required        minimum and maximum x values (in the objs' coordinates)
 | 
						|
    ymin, ymax      required        minimum and maximum y values (in the objs' coordinates)
 | 
						|
    obj             optional list   drawing primitives
 | 
						|
    keyword options keyword list    options defined below
 | 
						|
 | 
						|
    The following are keyword options, with their default values:
 | 
						|
 | 
						|
    trans           None          transformation function
 | 
						|
    x, y            5, 5          upper-left corner of the Plot in SVG coordinates
 | 
						|
    width, height   90, 90        width and height of the Plot in SVG coordinates
 | 
						|
    flipx, flipy    False, True   flip the sign of the coordinate axis
 | 
						|
    minusInfinity   -1000         if an axis is logarithmic and an object is plotted at 0 or
 | 
						|
                                  a negative value, -1000 will be used as a stand-in for NaN
 | 
						|
    atx, aty        0, 0          the place where the coordinate axes cross
 | 
						|
    xticks          -10           request ticks according to the standard tick specification
 | 
						|
                                  (see help(Ticks))
 | 
						|
    xminiticks      True          request miniticks according to the standard minitick
 | 
						|
                                  specification
 | 
						|
    xlabels         True          request tick labels according to the standard tick label
 | 
						|
                                  specification
 | 
						|
    xlogbase        None          if a number, the axis and transformation are logarithmic
 | 
						|
                                  with ticks at the given base (10 being the most common)
 | 
						|
    (same for y)
 | 
						|
    arrows          None          if a new identifier, create arrow markers and draw them
 | 
						|
                                  at the ends of the coordinate axes
 | 
						|
    text_attr       {}            a dictionary of attributes for label text
 | 
						|
    axis_attr       {}            a dictionary of attributes for the axis lines
 | 
						|
    """
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        if self.trans is None:
 | 
						|
            return "<Plot (%d items)>" % len(self.d)
 | 
						|
        else:
 | 
						|
            return "<Plot (%d items) %s>" % (len(self.d), self.trans.func_name)
 | 
						|
 | 
						|
    def __init__(self, xmin, xmax, ymin, ymax, *d, **kwds):
 | 
						|
        self.xmin, self.xmax, self.ymin, self.ymax = xmin, xmax, ymin, ymax
 | 
						|
        self.d = list(d)
 | 
						|
        defaults = {"trans": None,
 | 
						|
                    "x": 5, "y": 5, "width": 90, "height": 90,
 | 
						|
                    "flipx": False, "flipy": True,
 | 
						|
                    "minusInfinity": -1000,
 | 
						|
                    "atx": 0, "xticks": -10, "xminiticks": True, "xlabels": True, "xlogbase": None,
 | 
						|
                    "aty": 0, "yticks": -10, "yminiticks": True, "ylabels": True, "ylogbase": None,
 | 
						|
                    "arrows": None,
 | 
						|
                    "text_attr": {}, "axis_attr": {},
 | 
						|
                   }
 | 
						|
        defaults.update(kwds)
 | 
						|
        kwds = defaults
 | 
						|
 | 
						|
        self.trans = kwds["trans"]; del kwds["trans"]
 | 
						|
        self.x = kwds["x"]; del kwds["x"]
 | 
						|
        self.y = kwds["y"]; del kwds["y"]
 | 
						|
        self.width = kwds["width"]; del kwds["width"]
 | 
						|
        self.height = kwds["height"]; del kwds["height"]
 | 
						|
        self.flipx = kwds["flipx"]; del kwds["flipx"]
 | 
						|
        self.flipy = kwds["flipy"]; del kwds["flipy"]
 | 
						|
        self.minusInfinity = kwds["minusInfinity"]; del kwds["minusInfinity"]
 | 
						|
        self.atx = kwds["atx"]; del kwds["atx"]
 | 
						|
        self.xticks = kwds["xticks"]; del kwds["xticks"]
 | 
						|
        self.xminiticks = kwds["xminiticks"]; del kwds["xminiticks"]
 | 
						|
        self.xlabels = kwds["xlabels"]; del kwds["xlabels"]
 | 
						|
        self.xlogbase = kwds["xlogbase"]; del kwds["xlogbase"]
 | 
						|
        self.aty = kwds["aty"]; del kwds["aty"]
 | 
						|
        self.yticks = kwds["yticks"]; del kwds["yticks"]
 | 
						|
        self.yminiticks = kwds["yminiticks"]; del kwds["yminiticks"]
 | 
						|
        self.ylabels = kwds["ylabels"]; del kwds["ylabels"]
 | 
						|
        self.ylogbase = kwds["ylogbase"]; del kwds["ylogbase"]
 | 
						|
        self.arrows = kwds["arrows"]; del kwds["arrows"]
 | 
						|
        self.text_attr = kwds["text_attr"]; del kwds["text_attr"]
 | 
						|
        self.axis_attr = kwds["axis_attr"]; del kwds["axis_attr"]
 | 
						|
        if len(kwds) != 0:
 | 
						|
            raise TypeError, "Plot() got unexpected keyword arguments %s" % kwds.keys()
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        if trans is None:
 | 
						|
            trans = self.trans
 | 
						|
        if isinstance(trans, basestring):
 | 
						|
            trans = totrans(trans)
 | 
						|
 | 
						|
        self.last_window = window(self.xmin, self.xmax, self.ymin, self.ymax,
 | 
						|
                                  x=self.x, y=self.y, width=self.width, height=self.height,
 | 
						|
                                  xlogbase=self.xlogbase, ylogbase=self.ylogbase,
 | 
						|
                                  minusInfinity=self.minusInfinity, flipx=self.flipx, flipy=self.flipy)
 | 
						|
 | 
						|
        d = ([Axes(self.xmin, self.xmax, self.ymin, self.ymax, self.atx, self.aty,
 | 
						|
                   self.xticks, self.xminiticks, self.xlabels, self.xlogbase,
 | 
						|
                   self.yticks, self.yminiticks, self.ylabels, self.ylogbase,
 | 
						|
                   self.arrows, self.text_attr, **self.axis_attr)]
 | 
						|
             + self.d)
 | 
						|
 | 
						|
        return Fig(Fig(*d, **{"trans": trans})).SVG(self.last_window)
 | 
						|
 | 
						|
 | 
						|
class Frame:
 | 
						|
    text_defaults = {"stroke": "none", "fill": "black", "font-size": 5, }
 | 
						|
    axis_defaults = {}
 | 
						|
 | 
						|
    tick_length = 1.5
 | 
						|
    minitick_length = 0.75
 | 
						|
    text_xaxis_offset = 1.
 | 
						|
    text_yaxis_offset = 2.
 | 
						|
    text_xtitle_offset = 6.
 | 
						|
    text_ytitle_offset = 12.
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<Frame (%d items)>" % len(self.d)
 | 
						|
 | 
						|
    def __init__(self, xmin, xmax, ymin, ymax, *d, **kwds):
 | 
						|
        """Acts like Fig, but draws a coordinate frame around the data. You also need to supply plot ranges.
 | 
						|
 | 
						|
        Frame(xmin, xmax, ymin, ymax, obj, obj, obj..., keyword options...)
 | 
						|
 | 
						|
        xmin, xmax      required        minimum and maximum x values (in the objs' coordinates)
 | 
						|
        ymin, ymax      required        minimum and maximum y values (in the objs' coordinates)
 | 
						|
        obj             optional list   drawing primitives
 | 
						|
        keyword options keyword list    options defined below
 | 
						|
 | 
						|
        The following are keyword options, with their default values:
 | 
						|
 | 
						|
        x, y            20, 5         upper-left corner of the Frame in SVG coordinates
 | 
						|
        width, height   75, 80        width and height of the Frame in SVG coordinates
 | 
						|
        flipx, flipy    False, True   flip the sign of the coordinate axis
 | 
						|
        minusInfinity   -1000         if an axis is logarithmic and an object is plotted at 0 or
 | 
						|
                                      a negative value, -1000 will be used as a stand-in for NaN
 | 
						|
        xtitle          None          if a string, label the x axis
 | 
						|
        xticks          -10           request ticks according to the standard tick specification
 | 
						|
                                      (see help(Ticks))
 | 
						|
        xminiticks      True          request miniticks according to the standard minitick
 | 
						|
                                      specification
 | 
						|
        xlabels         True          request tick labels according to the standard tick label
 | 
						|
                                      specification
 | 
						|
        xlogbase        None          if a number, the axis and transformation are logarithmic
 | 
						|
                                      with ticks at the given base (10 being the most common)
 | 
						|
        (same for y)
 | 
						|
        text_attr       {}            a dictionary of attributes for label text
 | 
						|
        axis_attr       {}            a dictionary of attributes for the axis lines
 | 
						|
        """
 | 
						|
 | 
						|
        self.xmin, self.xmax, self.ymin, self.ymax = xmin, xmax, ymin, ymax
 | 
						|
        self.d = list(d)
 | 
						|
        defaults = {"x": 20, "y": 5, "width": 75, "height": 80,
 | 
						|
                    "flipx": False, "flipy": True, "minusInfinity": -1000,
 | 
						|
                    "xtitle": None, "xticks": -10, "xminiticks": True, "xlabels": True,
 | 
						|
                    "x2labels": None, "xlogbase": None,
 | 
						|
                    "ytitle": None, "yticks": -10, "yminiticks": True, "ylabels": True,
 | 
						|
                    "y2labels": None, "ylogbase": None,
 | 
						|
                    "text_attr": {}, "axis_attr": {},
 | 
						|
                   }
 | 
						|
        defaults.update(kwds)
 | 
						|
        kwds = defaults
 | 
						|
 | 
						|
        self.x = kwds["x"]; del kwds["x"]
 | 
						|
        self.y = kwds["y"]; del kwds["y"]
 | 
						|
        self.width = kwds["width"]; del kwds["width"]
 | 
						|
        self.height = kwds["height"]; del kwds["height"]
 | 
						|
        self.flipx = kwds["flipx"]; del kwds["flipx"]
 | 
						|
        self.flipy = kwds["flipy"]; del kwds["flipy"]
 | 
						|
        self.minusInfinity = kwds["minusInfinity"]; del kwds["minusInfinity"]
 | 
						|
        self.xtitle = kwds["xtitle"]; del kwds["xtitle"]
 | 
						|
        self.xticks = kwds["xticks"]; del kwds["xticks"]
 | 
						|
        self.xminiticks = kwds["xminiticks"]; del kwds["xminiticks"]
 | 
						|
        self.xlabels = kwds["xlabels"]; del kwds["xlabels"]
 | 
						|
        self.x2labels = kwds["x2labels"]; del kwds["x2labels"]
 | 
						|
        self.xlogbase = kwds["xlogbase"]; del kwds["xlogbase"]
 | 
						|
        self.ytitle = kwds["ytitle"]; del kwds["ytitle"]
 | 
						|
        self.yticks = kwds["yticks"]; del kwds["yticks"]
 | 
						|
        self.yminiticks = kwds["yminiticks"]; del kwds["yminiticks"]
 | 
						|
        self.ylabels = kwds["ylabels"]; del kwds["ylabels"]
 | 
						|
        self.y2labels = kwds["y2labels"]; del kwds["y2labels"]
 | 
						|
        self.ylogbase = kwds["ylogbase"]; del kwds["ylogbase"]
 | 
						|
 | 
						|
        self.text_attr = dict(self.text_defaults)
 | 
						|
        self.text_attr.update(kwds["text_attr"]); del kwds["text_attr"]
 | 
						|
 | 
						|
        self.axis_attr = dict(self.axis_defaults)
 | 
						|
        self.axis_attr.update(kwds["axis_attr"]); del kwds["axis_attr"]
 | 
						|
 | 
						|
        if len(kwds) != 0:
 | 
						|
            raise TypeError, "Frame() got unexpected keyword arguments %s" % kwds.keys()
 | 
						|
 | 
						|
    def SVG(self):
 | 
						|
        """Apply the window transformation and return an SVG object."""
 | 
						|
 | 
						|
        self.last_window = window(self.xmin, self.xmax, self.ymin, self.ymax,
 | 
						|
                                  x=self.x, y=self.y, width=self.width, height=self.height,
 | 
						|
                                  xlogbase=self.xlogbase, ylogbase=self.ylogbase,
 | 
						|
                                  minusInfinity=self.minusInfinity, flipx=self.flipx, flipy=self.flipy)
 | 
						|
 | 
						|
        left = YAxis(self.ymin, self.ymax, self.xmin, self.yticks, self.yminiticks, self.ylabels, self.ylogbase,
 | 
						|
                     None, None, None, self.text_attr, **self.axis_attr)
 | 
						|
        right = YAxis(self.ymin, self.ymax, self.xmax, self.yticks, self.yminiticks, self.y2labels, self.ylogbase,
 | 
						|
                      None, None, None, self.text_attr, **self.axis_attr)
 | 
						|
        bottom = XAxis(self.xmin, self.xmax, self.ymin, self.xticks, self.xminiticks, self.xlabels, self.xlogbase,
 | 
						|
                       None, None, None, self.text_attr, **self.axis_attr)
 | 
						|
        top = XAxis(self.xmin, self.xmax, self.ymax, self.xticks, self.xminiticks, self.x2labels, self.xlogbase,
 | 
						|
                    None, None, None, self.text_attr, **self.axis_attr)
 | 
						|
 | 
						|
        left.tick_start = -self.tick_length
 | 
						|
        left.tick_end = 0
 | 
						|
        left.minitick_start = -self.minitick_length
 | 
						|
        left.minitick_end = 0.
 | 
						|
        left.text_start = self.text_yaxis_offset
 | 
						|
 | 
						|
        right.tick_start = 0.
 | 
						|
        right.tick_end = self.tick_length
 | 
						|
        right.minitick_start = 0.
 | 
						|
        right.minitick_end = self.minitick_length
 | 
						|
        right.text_start = -self.text_yaxis_offset
 | 
						|
        right.text_attr["text-anchor"] = "start"
 | 
						|
 | 
						|
        bottom.tick_start = 0.
 | 
						|
        bottom.tick_end = self.tick_length
 | 
						|
        bottom.minitick_start = 0.
 | 
						|
        bottom.minitick_end = self.minitick_length
 | 
						|
        bottom.text_start = -self.text_xaxis_offset
 | 
						|
 | 
						|
        top.tick_start = -self.tick_length
 | 
						|
        top.tick_end = 0.
 | 
						|
        top.minitick_start = -self.minitick_length
 | 
						|
        top.minitick_end = 0.
 | 
						|
        top.text_start = self.text_xaxis_offset
 | 
						|
        top.text_attr["dominant-baseline"] = "text-after-edge"
 | 
						|
 | 
						|
        output = Fig(*self.d).SVG(self.last_window)
 | 
						|
        output.prepend(left.SVG(self.last_window))
 | 
						|
        output.prepend(bottom.SVG(self.last_window))
 | 
						|
        output.prepend(right.SVG(self.last_window))
 | 
						|
        output.prepend(top.SVG(self.last_window))
 | 
						|
 | 
						|
        if self.xtitle is not None:
 | 
						|
            output.append(SVG("text", self.xtitle, transform="translate(%g, %g)" % ((self.x + self.width/2.), (self.y + self.height + self.text_xtitle_offset)), dominant_baseline="text-before-edge", **self.text_attr))
 | 
						|
        if self.ytitle is not None:
 | 
						|
            output.append(SVG("text", self.ytitle, transform="translate(%g, %g) rotate(-90)" % ((self.x - self.text_ytitle_offset), (self.y + self.height/2.)), **self.text_attr))
 | 
						|
        return output
 | 
						|
 | 
						|
######################################################################
 | 
						|
 | 
						|
def pathtoPath(svg):
 | 
						|
    """Converts SVG("path", d="...") into Path(d=[...])."""
 | 
						|
    if not isinstance(svg, SVG) or svg.t != "path":
 | 
						|
        raise TypeError, "Only SVG <path /> objects can be converted into Paths"
 | 
						|
    attr = dict(svg.attr)
 | 
						|
    d = attr["d"]
 | 
						|
    del attr["d"]
 | 
						|
    for key in attr.keys():
 | 
						|
        if not isinstance(key, str):
 | 
						|
            value = attr[key]
 | 
						|
            del attr[key]
 | 
						|
            attr[str(key)] = value
 | 
						|
    return Path(d, **attr)
 | 
						|
 | 
						|
 | 
						|
class Path:
 | 
						|
    """Path represents an SVG path, an arbitrary set of curves and
 | 
						|
    straight segments. Unlike SVG("path", d="..."), Path stores
 | 
						|
    coordinates as a list of numbers, rather than a string, so that it is
 | 
						|
    transformable in a Fig.
 | 
						|
 | 
						|
    Path(d, attribute=value)
 | 
						|
 | 
						|
    d                       required        path data
 | 
						|
    attribute=value pairs   keyword list    SVG attributes
 | 
						|
 | 
						|
    See http://www.w3.org/TR/SVG/paths.html for specification of paths
 | 
						|
    from text.
 | 
						|
 | 
						|
    Internally, Path data is a list of tuples with these definitions:
 | 
						|
 | 
						|
        * ("Z/z",): close the current path
 | 
						|
        * ("H/h", x) or ("V/v", y): a horizontal or vertical line
 | 
						|
          segment to x or y
 | 
						|
        * ("M/m/L/l/T/t", x, y, global): moveto, lineto, or smooth
 | 
						|
          quadratic curveto point (x, y). If global=True, (x, y) should
 | 
						|
          not be transformed.
 | 
						|
        * ("S/sQ/q", cx, cy, cglobal, x, y, global): polybezier or
 | 
						|
          smooth quadratic curveto point (x, y) using (cx, cy) as a
 | 
						|
          control point. If cglobal or global=True, (cx, cy) or (x, y)
 | 
						|
          should not be transformed.
 | 
						|
        * ("C/c", c1x, c1y, c1global, c2x, c2y, c2global, x, y, global):
 | 
						|
          cubic curveto point (x, y) using (c1x, c1y) and (c2x, c2y) as
 | 
						|
          control points. If c1global, c2global, or global=True, (c1x, c1y),
 | 
						|
          (c2x, c2y), or (x, y) should not be transformed.
 | 
						|
        * ("A/a", rx, ry, rglobal, x-axis-rotation, angle, large-arc-flag,
 | 
						|
          sweep-flag, x, y, global): arcto point (x, y) using the
 | 
						|
          aforementioned parameters.
 | 
						|
        * (",/.", rx, ry, rglobal, angle, x, y, global): an ellipse at
 | 
						|
          point (x, y) with radii (rx, ry). If angle is 0, the whole
 | 
						|
          ellipse is drawn; otherwise, a partial ellipse is drawn.
 | 
						|
    """
 | 
						|
    defaults = {}
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<Path (%d nodes) %s>" % (len(self.d), self.attr)
 | 
						|
 | 
						|
    def __init__(self, d=[], **attr):
 | 
						|
        if isinstance(d, basestring):
 | 
						|
            self.d = self.parse(d)
 | 
						|
        else:
 | 
						|
            self.d = list(d)
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    def parse_whitespace(self, index, pathdata):
 | 
						|
        """Part of Path's text-command parsing algorithm; used internally."""
 | 
						|
        while index < len(pathdata) and pathdata[index] in (" ", "\t", "\r", "\n", ","):
 | 
						|
            index += 1
 | 
						|
        return index, pathdata
 | 
						|
 | 
						|
    def parse_command(self, index, pathdata):
 | 
						|
        """Part of Path's text-command parsing algorithm; used internally."""
 | 
						|
        index, pathdata = self.parse_whitespace(index, pathdata)
 | 
						|
 | 
						|
        if index >= len(pathdata):
 | 
						|
            return None, index, pathdata
 | 
						|
        command = pathdata[index]
 | 
						|
        if "A" <= command <= "Z" or "a" <= command <= "z":
 | 
						|
            index += 1
 | 
						|
            return command, index, pathdata
 | 
						|
        else:
 | 
						|
            return None, index, pathdata
 | 
						|
 | 
						|
    def parse_number(self, index, pathdata):
 | 
						|
        """Part of Path's text-command parsing algorithm; used internally."""
 | 
						|
        index, pathdata = self.parse_whitespace(index, pathdata)
 | 
						|
 | 
						|
        if index >= len(pathdata):
 | 
						|
            return None, index, pathdata
 | 
						|
        first_digit = pathdata[index]
 | 
						|
 | 
						|
        if "0" <= first_digit <= "9" or first_digit in ("-", "+", "."):
 | 
						|
            start = index
 | 
						|
            while index < len(pathdata) and ("0" <= pathdata[index] <= "9" or pathdata[index] in ("-", "+", ".", "e", "E")):
 | 
						|
                index += 1
 | 
						|
            end = index
 | 
						|
 | 
						|
            index = end
 | 
						|
            return float(pathdata[start:end]), index, pathdata
 | 
						|
        else:
 | 
						|
            return None, index, pathdata
 | 
						|
 | 
						|
    def parse_boolean(self, index, pathdata):
 | 
						|
        """Part of Path's text-command parsing algorithm; used internally."""
 | 
						|
        index, pathdata = self.parse_whitespace(index, pathdata)
 | 
						|
 | 
						|
        if index >= len(pathdata):
 | 
						|
            return None, index, pathdata
 | 
						|
        first_digit = pathdata[index]
 | 
						|
 | 
						|
        if first_digit in ("0", "1"):
 | 
						|
            index += 1
 | 
						|
            return int(first_digit), index, pathdata
 | 
						|
        else:
 | 
						|
            return None, index, pathdata
 | 
						|
 | 
						|
    def parse(self, pathdata):
 | 
						|
        """Parses text-commands, converting them into a list of tuples.
 | 
						|
        Called by the constructor."""
 | 
						|
        output = []
 | 
						|
        index = 0
 | 
						|
        while True:
 | 
						|
            command, index, pathdata = self.parse_command(index, pathdata)
 | 
						|
            index, pathdata = self.parse_whitespace(index, pathdata)
 | 
						|
 | 
						|
            if command is None and index == len(pathdata):
 | 
						|
                break  # this is the normal way out of the loop
 | 
						|
            if command in ("Z", "z"):
 | 
						|
                output.append((command,))
 | 
						|
 | 
						|
            ######################
 | 
						|
            elif command in ("H", "h", "V", "v"):
 | 
						|
                errstring = "Path command \"%s\" requires a number at index %d" % (command, index)
 | 
						|
                num1, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                if num1 is None:
 | 
						|
                    raise ValueError, errstring
 | 
						|
 | 
						|
                while num1 is not None:
 | 
						|
                    output.append((command, num1))
 | 
						|
                    num1, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
 | 
						|
            ######################
 | 
						|
            elif command in ("M", "m", "L", "l", "T", "t"):
 | 
						|
                errstring = "Path command \"%s\" requires an x,y pair at index %d" % (command, index)
 | 
						|
                num1, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                num2, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
 | 
						|
                if num1 is None:
 | 
						|
                    raise ValueError, errstring
 | 
						|
 | 
						|
                while num1 is not None:
 | 
						|
                    if num2 is None:
 | 
						|
                        raise ValueError, errstring
 | 
						|
                    output.append((command, num1, num2, False))
 | 
						|
 | 
						|
                    num1, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                    num2, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
 | 
						|
            ######################
 | 
						|
            elif command in ("S", "s", "Q", "q"):
 | 
						|
                errstring = "Path command \"%s\" requires a cx,cy,x,y quadruplet at index %d" % (command, index)
 | 
						|
                num1, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                num2, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                num3, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                num4, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
 | 
						|
                if num1 is None:
 | 
						|
                    raise ValueError, errstring
 | 
						|
 | 
						|
                while num1 is not None:
 | 
						|
                    if num2 is None or num3 is None or num4 is None:
 | 
						|
                        raise ValueError, errstring
 | 
						|
                    output.append((command, num1, num2, False, num3, num4, False))
 | 
						|
 | 
						|
                    num1, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                    num2, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                    num3, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                    num4, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
 | 
						|
            ######################
 | 
						|
            elif command in ("C", "c"):
 | 
						|
                errstring = "Path command \"%s\" requires a c1x,c1y,c2x,c2y,x,y sextuplet at index %d" % (command, index)
 | 
						|
                num1, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                num2, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                num3, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                num4, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                num5, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                num6, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
 | 
						|
                if num1 is None:
 | 
						|
                    raise ValueError, errstring
 | 
						|
 | 
						|
                while num1 is not None:
 | 
						|
                    if num2 is None or num3 is None or num4 is None or num5 is None or num6 is None:
 | 
						|
                        raise ValueError, errstring
 | 
						|
 | 
						|
                    output.append((command, num1, num2, False, num3, num4, False, num5, num6, False))
 | 
						|
 | 
						|
                    num1, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                    num2, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                    num3, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                    num4, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                    num5, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                    num6, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
 | 
						|
            ######################
 | 
						|
            elif command in ("A", "a"):
 | 
						|
                errstring = "Path command \"%s\" requires a rx,ry,angle,large-arc-flag,sweep-flag,x,y septuplet at index %d" % (command, index)
 | 
						|
                num1, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                num2, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                num3, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                num4, index, pathdata = self.parse_boolean(index, pathdata)
 | 
						|
                num5, index, pathdata = self.parse_boolean(index, pathdata)
 | 
						|
                num6, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                num7, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
 | 
						|
                if num1 is None:
 | 
						|
                    raise ValueError, errstring
 | 
						|
 | 
						|
                while num1 is not None:
 | 
						|
                    if num2 is None or num3 is None or num4 is None or num5 is None or num6 is None or num7 is None:
 | 
						|
                        raise ValueError, errstring
 | 
						|
 | 
						|
                    output.append((command, num1, num2, False, num3, num4, num5, num6, num7, False))
 | 
						|
 | 
						|
                    num1, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                    num2, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                    num3, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                    num4, index, pathdata = self.parse_boolean(index, pathdata)
 | 
						|
                    num5, index, pathdata = self.parse_boolean(index, pathdata)
 | 
						|
                    num6, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
                    num7, index, pathdata = self.parse_number(index, pathdata)
 | 
						|
 | 
						|
        return output
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        if isinstance(trans, basestring):
 | 
						|
            trans = totrans(trans)
 | 
						|
 | 
						|
        x, y, X, Y = None, None, None, None
 | 
						|
        output = []
 | 
						|
        for datum in self.d:
 | 
						|
            if not isinstance(datum, (tuple, list)):
 | 
						|
                raise TypeError, "pathdata elements must be tuples/lists"
 | 
						|
 | 
						|
            command = datum[0]
 | 
						|
 | 
						|
            ######################
 | 
						|
            if command in ("Z", "z"):
 | 
						|
                x, y, X, Y = None, None, None, None
 | 
						|
                output.append("Z")
 | 
						|
 | 
						|
            ######################
 | 
						|
            elif command in ("H", "h", "V", "v"):
 | 
						|
                command, num1 = datum
 | 
						|
 | 
						|
                if command == "H" or (command == "h" and x is None):
 | 
						|
                    x = num1
 | 
						|
                elif command == "h":
 | 
						|
                    x += num1
 | 
						|
                elif command == "V" or (command == "v" and y is None):
 | 
						|
                    y = num1
 | 
						|
                elif command == "v":
 | 
						|
                    y += num1
 | 
						|
 | 
						|
                if trans is None:
 | 
						|
                    X, Y = x, y
 | 
						|
                else:
 | 
						|
                    X, Y = trans(x, y)
 | 
						|
 | 
						|
                output.append("L%g %g" % (X, Y))
 | 
						|
 | 
						|
            ######################
 | 
						|
            elif command in ("M", "m", "L", "l", "T", "t"):
 | 
						|
                command, num1, num2, isglobal12 = datum
 | 
						|
 | 
						|
                if trans is None or isglobal12:
 | 
						|
                    if command.isupper() or X is None or Y is None:
 | 
						|
                        X, Y = num1, num2
 | 
						|
                    else:
 | 
						|
                        X += num1
 | 
						|
                        Y += num2
 | 
						|
                    x, y = X, Y
 | 
						|
 | 
						|
                else:
 | 
						|
                    if command.isupper() or x is None or y is None:
 | 
						|
                        x, y = num1, num2
 | 
						|
                    else:
 | 
						|
                        x += num1
 | 
						|
                        y += num2
 | 
						|
                    X, Y = trans(x, y)
 | 
						|
 | 
						|
                COMMAND = command.capitalize()
 | 
						|
                output.append("%s%g %g" % (COMMAND, X, Y))
 | 
						|
 | 
						|
            ######################
 | 
						|
            elif command in ("S", "s", "Q", "q"):
 | 
						|
                command, num1, num2, isglobal12, num3, num4, isglobal34 = datum
 | 
						|
 | 
						|
                if trans is None or isglobal12:
 | 
						|
                    if command.isupper() or X is None or Y is None:
 | 
						|
                        CX, CY = num1, num2
 | 
						|
                    else:
 | 
						|
                        CX = X + num1
 | 
						|
                        CY = Y + num2
 | 
						|
 | 
						|
                else:
 | 
						|
                    if command.isupper() or x is None or y is None:
 | 
						|
                        cx, cy = num1, num2
 | 
						|
                    else:
 | 
						|
                        cx = x + num1
 | 
						|
                        cy = y + num2
 | 
						|
                    CX, CY = trans(cx, cy)
 | 
						|
 | 
						|
                if trans is None or isglobal34:
 | 
						|
                    if command.isupper() or X is None or Y is None:
 | 
						|
                        X, Y = num3, num4
 | 
						|
                    else:
 | 
						|
                        X += num3
 | 
						|
                        Y += num4
 | 
						|
                    x, y = X, Y
 | 
						|
 | 
						|
                else:
 | 
						|
                    if command.isupper() or x is None or y is None:
 | 
						|
                        x, y = num3, num4
 | 
						|
                    else:
 | 
						|
                        x += num3
 | 
						|
                        y += num4
 | 
						|
                    X, Y = trans(x, y)
 | 
						|
 | 
						|
                COMMAND = command.capitalize()
 | 
						|
                output.append("%s%g %g %g %g" % (COMMAND, CX, CY, X, Y))
 | 
						|
 | 
						|
            ######################
 | 
						|
            elif command in ("C", "c"):
 | 
						|
                command, num1, num2, isglobal12, num3, num4, isglobal34, num5, num6, isglobal56 = datum
 | 
						|
 | 
						|
                if trans is None or isglobal12:
 | 
						|
                    if command.isupper() or X is None or Y is None:
 | 
						|
                        C1X, C1Y = num1, num2
 | 
						|
                    else:
 | 
						|
                        C1X = X + num1
 | 
						|
                        C1Y = Y + num2
 | 
						|
 | 
						|
                else:
 | 
						|
                    if command.isupper() or x is None or y is None:
 | 
						|
                        c1x, c1y = num1, num2
 | 
						|
                    else:
 | 
						|
                        c1x = x + num1
 | 
						|
                        c1y = y + num2
 | 
						|
                    C1X, C1Y = trans(c1x, c1y)
 | 
						|
 | 
						|
                if trans is None or isglobal34:
 | 
						|
                    if command.isupper() or X is None or Y is None:
 | 
						|
                        C2X, C2Y = num3, num4
 | 
						|
                    else:
 | 
						|
                        C2X = X + num3
 | 
						|
                        C2Y = Y + num4
 | 
						|
 | 
						|
                else:
 | 
						|
                    if command.isupper() or x is None or y is None:
 | 
						|
                        c2x, c2y = num3, num4
 | 
						|
                    else:
 | 
						|
                        c2x = x + num3
 | 
						|
                        c2y = y + num4
 | 
						|
                    C2X, C2Y = trans(c2x, c2y)
 | 
						|
 | 
						|
                if trans is None or isglobal56:
 | 
						|
                    if command.isupper() or X is None or Y is None:
 | 
						|
                        X, Y = num5, num6
 | 
						|
                    else:
 | 
						|
                        X += num5
 | 
						|
                        Y += num6
 | 
						|
                    x, y = X, Y
 | 
						|
 | 
						|
                else:
 | 
						|
                    if command.isupper() or x is None or y is None:
 | 
						|
                        x, y = num5, num6
 | 
						|
                    else:
 | 
						|
                        x += num5
 | 
						|
                        y += num6
 | 
						|
                    X, Y = trans(x, y)
 | 
						|
 | 
						|
                COMMAND = command.capitalize()
 | 
						|
                output.append("%s%g %g %g %g %g %g" % (COMMAND, C1X, C1Y, C2X, C2Y, X, Y))
 | 
						|
 | 
						|
            ######################
 | 
						|
            elif command in ("A", "a"):
 | 
						|
                command, num1, num2, isglobal12, angle, large_arc_flag, sweep_flag, num3, num4, isglobal34 = datum
 | 
						|
 | 
						|
                oldx, oldy = x, y
 | 
						|
                OLDX, OLDY = X, Y
 | 
						|
 | 
						|
                if trans is None or isglobal34:
 | 
						|
                    if command.isupper() or X is None or Y is None:
 | 
						|
                        X, Y = num3, num4
 | 
						|
                    else:
 | 
						|
                        X += num3
 | 
						|
                        Y += num4
 | 
						|
                    x, y = X, Y
 | 
						|
 | 
						|
                else:
 | 
						|
                    if command.isupper() or x is None or y is None:
 | 
						|
                        x, y = num3, num4
 | 
						|
                    else:
 | 
						|
                        x += num3
 | 
						|
                        y += num4
 | 
						|
                    X, Y = trans(x, y)
 | 
						|
 | 
						|
                if x is not None and y is not None:
 | 
						|
                    centerx, centery = (x + oldx)/2., (y + oldy)/2.
 | 
						|
                CENTERX, CENTERY = (X + OLDX)/2., (Y + OLDY)/2.
 | 
						|
 | 
						|
                if trans is None or isglobal12:
 | 
						|
                    RX = CENTERX + num1
 | 
						|
                    RY = CENTERY + num2
 | 
						|
 | 
						|
                else:
 | 
						|
                    rx = centerx + num1
 | 
						|
                    ry = centery + num2
 | 
						|
                    RX, RY = trans(rx, ry)
 | 
						|
 | 
						|
                COMMAND = command.capitalize()
 | 
						|
                output.append("%s%g %g %g %d %d %g %g" % (COMMAND, RX - CENTERX, RY - CENTERY, angle, large_arc_flag, sweep_flag, X, Y))
 | 
						|
 | 
						|
            elif command in (",", "."):
 | 
						|
                command, num1, num2, isglobal12, angle, num3, num4, isglobal34 = datum
 | 
						|
                if trans is None or isglobal34:
 | 
						|
                    if command == "." or X is None or Y is None:
 | 
						|
                        X, Y = num3, num4
 | 
						|
                    else:
 | 
						|
                        X += num3
 | 
						|
                        Y += num4
 | 
						|
                        x, y = None, None
 | 
						|
 | 
						|
                else:
 | 
						|
                    if command == "." or x is None or y is None:
 | 
						|
                        x, y = num3, num4
 | 
						|
                    else:
 | 
						|
                        x += num3
 | 
						|
                        y += num4
 | 
						|
                    X, Y = trans(x, y)
 | 
						|
 | 
						|
                if trans is None or isglobal12:
 | 
						|
                    RX = X + num1
 | 
						|
                    RY = Y + num2
 | 
						|
 | 
						|
                else:
 | 
						|
                    rx = x + num1
 | 
						|
                    ry = y + num2
 | 
						|
                    RX, RY = trans(rx, ry)
 | 
						|
 | 
						|
                RX, RY = RX - X, RY - Y
 | 
						|
 | 
						|
                X1, Y1 = X + RX * math.cos(angle*math.pi/180.), Y + RX * math.sin(angle*math.pi/180.)
 | 
						|
                X2, Y2 = X + RY * math.sin(angle*math.pi/180.), Y - RY * math.cos(angle*math.pi/180.)
 | 
						|
                X3, Y3 = X - RX * math.cos(angle*math.pi/180.), Y - RX * math.sin(angle*math.pi/180.)
 | 
						|
                X4, Y4 = X - RY * math.sin(angle*math.pi/180.), Y + RY * math.cos(angle*math.pi/180.)
 | 
						|
 | 
						|
                output.append("M%g %gA%g %g %g 0 0 %g %gA%g %g %g 0 0 %g %gA%g %g %g 0 0 %g %gA%g %g %g 0 0 %g %g" % (
 | 
						|
                              X1, Y1, RX, RY, angle, X2, Y2, RX, RY, angle, X3, Y3, RX, RY, angle, X4, Y4, RX, RY, angle, X1, Y1))
 | 
						|
 | 
						|
        return SVG("path", d="".join(output), **self.attr)
 | 
						|
 | 
						|
######################################################################
 | 
						|
 | 
						|
def funcRtoC(expr, var="t", globals=None, locals=None):
 | 
						|
    """Converts a complex "z(t)" string to a function acceptable for Curve.
 | 
						|
 | 
						|
    expr    required        string in the form "z(t)"
 | 
						|
    var     default="t"     name of the independent variable
 | 
						|
    globals default=None    dict of global variables used in the expression;
 | 
						|
                            you may want to use Python's builtin globals()
 | 
						|
    locals  default=None    dict of local variables
 | 
						|
    """
 | 
						|
    if locals is None:
 | 
						|
        locals = {}  # python 2.3's eval() won't accept None
 | 
						|
    g = cmath.__dict__
 | 
						|
    if globals is not None:
 | 
						|
        g.update(globals)
 | 
						|
    output = eval("lambda %s: (%s)" % (var, expr), g, locals)
 | 
						|
    split = lambda z: (z.real, z.imag)
 | 
						|
    output2 = lambda t: split(output(t))
 | 
						|
    set_func_name(output2, "%s -> %s" % (var, expr))
 | 
						|
    return output2
 | 
						|
 | 
						|
 | 
						|
def funcRtoR2(expr, var="t", globals=None, locals=None):
 | 
						|
    """Converts a "f(t), g(t)" string to a function acceptable for Curve.
 | 
						|
 | 
						|
    expr    required        string in the form "f(t), g(t)"
 | 
						|
    var     default="t"     name of the independent variable
 | 
						|
    globals default=None    dict of global variables used in the expression;
 | 
						|
                            you may want to use Python's builtin globals()
 | 
						|
    locals  default=None    dict of local variables
 | 
						|
    """
 | 
						|
    if locals is None:
 | 
						|
        locals = {}  # python 2.3's eval() won't accept None
 | 
						|
    g = math.__dict__
 | 
						|
    if globals is not None:
 | 
						|
        g.update(globals)
 | 
						|
    output = eval("lambda %s: (%s)" % (var, expr), g, locals)
 | 
						|
    set_func_name(output, "%s -> %s" % (var, expr))
 | 
						|
    return output
 | 
						|
 | 
						|
 | 
						|
def funcRtoR(expr, var="x", globals=None, locals=None):
 | 
						|
    """Converts a "f(x)" string to a function acceptable for Curve.
 | 
						|
 | 
						|
    expr    required        string in the form "f(x)"
 | 
						|
    var     default="x"     name of the independent variable
 | 
						|
    globals default=None    dict of global variables used in the expression;
 | 
						|
                            you may want to use Python's builtin globals()
 | 
						|
    locals  default=None    dict of local variables
 | 
						|
    """
 | 
						|
    if locals is None:
 | 
						|
        locals = {}  # python 2.3's eval() won't accept None
 | 
						|
    g = math.__dict__
 | 
						|
    if globals is not None:
 | 
						|
        g.update(globals)
 | 
						|
    output = eval("lambda %s: (%s, %s)" % (var, var, expr), g, locals)
 | 
						|
    set_func_name(output, "%s -> %s" % (var, expr))
 | 
						|
    return output
 | 
						|
 | 
						|
 | 
						|
class Curve:
 | 
						|
    """Draws a parametric function as a path.
 | 
						|
 | 
						|
    Curve(f, low, high, loop, attribute=value)
 | 
						|
 | 
						|
    f                      required         a Python callable or string in
 | 
						|
                                            the form "f(t), g(t)"
 | 
						|
    low, high              required         left and right endpoints
 | 
						|
    loop                   default=False    if True, connect the endpoints
 | 
						|
    attribute=value pairs  keyword list     SVG attributes
 | 
						|
    """
 | 
						|
    defaults = {}
 | 
						|
    random_sampling = True
 | 
						|
    recursion_limit = 15
 | 
						|
    linearity_limit = 0.05
 | 
						|
    discontinuity_limit = 5.
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<Curve %s [%s, %s] %s>" % (self.f, self.low, self.high, self.attr)
 | 
						|
 | 
						|
    def __init__(self, f, low, high, loop=False, **attr):
 | 
						|
        self.f = f
 | 
						|
        self.low = low
 | 
						|
        self.high = high
 | 
						|
        self.loop = loop
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    ### nested class Sample
 | 
						|
    class Sample:
 | 
						|
        def __repr__(self):
 | 
						|
            t, x, y, X, Y = self.t, self.x, self.y, self.X, self.Y
 | 
						|
            if t is not None:
 | 
						|
                t = "%g" % t
 | 
						|
            if x is not None:
 | 
						|
                x = "%g" % x
 | 
						|
            if y is not None:
 | 
						|
                y = "%g" % y
 | 
						|
            if X is not None:
 | 
						|
                X = "%g" % X
 | 
						|
            if Y is not None:
 | 
						|
                Y = "%g" % Y
 | 
						|
            return "<Curve.Sample t=%s x=%s y=%s X=%s Y=%s>" % (t, x, y, X, Y)
 | 
						|
 | 
						|
        def __init__(self, t):
 | 
						|
            self.t = t
 | 
						|
 | 
						|
        def link(self, left, right):
 | 
						|
            self.left, self.right = left, right
 | 
						|
 | 
						|
        def evaluate(self, f, trans):
 | 
						|
            self.x, self.y = f(self.t)
 | 
						|
            if trans is None:
 | 
						|
                self.X, self.Y = self.x, self.y
 | 
						|
            else:
 | 
						|
                self.X, self.Y = trans(self.x, self.y)
 | 
						|
    ### end Sample
 | 
						|
 | 
						|
    ### nested class Samples
 | 
						|
    class Samples:
 | 
						|
        def __repr__(self):
 | 
						|
            return "<Curve.Samples (%d samples)>" % len(self)
 | 
						|
 | 
						|
        def __init__(self, left, right):
 | 
						|
            self.left, self.right = left, right
 | 
						|
 | 
						|
        def __len__(self):
 | 
						|
            count = 0
 | 
						|
            current = self.left
 | 
						|
            while current is not None:
 | 
						|
                count += 1
 | 
						|
                current = current.right
 | 
						|
            return count
 | 
						|
 | 
						|
        def __iter__(self):
 | 
						|
            self.current = self.left
 | 
						|
            return self
 | 
						|
 | 
						|
        def next(self):
 | 
						|
            current = self.current
 | 
						|
            if current is None:
 | 
						|
                raise StopIteration
 | 
						|
            self.current = self.current.right
 | 
						|
            return current
 | 
						|
    ### end nested class
 | 
						|
 | 
						|
    def sample(self, trans=None):
 | 
						|
        """Adaptive-sampling algorithm that chooses the best sample points
 | 
						|
        for a parametric curve between two endpoints and detects
 | 
						|
        discontinuities.  Called by SVG()."""
 | 
						|
        oldrecursionlimit = sys.getrecursionlimit()
 | 
						|
        sys.setrecursionlimit(self.recursion_limit + 100)
 | 
						|
        try:
 | 
						|
            # the best way to keep all the information while sampling is to make a linked list
 | 
						|
            if not (self.low < self.high):
 | 
						|
                raise ValueError, "low must be less than high"
 | 
						|
            low, high = self.Sample(float(self.low)), self.Sample(float(self.high))
 | 
						|
            low.link(None, high)
 | 
						|
            high.link(low, None)
 | 
						|
 | 
						|
            low.evaluate(self.f, trans)
 | 
						|
            high.evaluate(self.f, trans)
 | 
						|
 | 
						|
            # adaptive sampling between the low and high points
 | 
						|
            self.subsample(low, high, 0, trans)
 | 
						|
 | 
						|
            # Prune excess points where the curve is nearly linear
 | 
						|
            left = low
 | 
						|
            while left.right is not None:
 | 
						|
                # increment mid and right
 | 
						|
                mid = left.right
 | 
						|
                right = mid.right
 | 
						|
                if (right is not None and
 | 
						|
                    left.X is not None and left.Y is not None and
 | 
						|
                    mid.X is not None and mid.Y is not None and
 | 
						|
                    right.X is not None and right.Y is not None):
 | 
						|
                    numer = left.X*(right.Y - mid.Y) + mid.X*(left.Y - right.Y) + right.X*(mid.Y - left.Y)
 | 
						|
                    denom = math.sqrt((left.X - right.X)**2 + (left.Y - right.Y)**2)
 | 
						|
                    if denom != 0. and abs(numer/denom) < self.linearity_limit:
 | 
						|
                        # drop mid (the garbage collector will get it)
 | 
						|
                        left.right = right
 | 
						|
                        right.left = left
 | 
						|
                    else:
 | 
						|
                        # increment left
 | 
						|
                        left = left.right
 | 
						|
                else:
 | 
						|
                    left = left.right
 | 
						|
 | 
						|
            self.last_samples = self.Samples(low, high)
 | 
						|
 | 
						|
        finally:
 | 
						|
            sys.setrecursionlimit(oldrecursionlimit)
 | 
						|
 | 
						|
    def subsample(self, left, right, depth, trans=None):
 | 
						|
        """Part of the adaptive-sampling algorithm that chooses the best
 | 
						|
        sample points.  Called by sample()."""
 | 
						|
 | 
						|
        if self.random_sampling:
 | 
						|
            mid = self.Sample(left.t + random.uniform(0.3, 0.7) * (right.t - left.t))
 | 
						|
        else:
 | 
						|
            mid = self.Sample(left.t + 0.5 * (right.t - left.t))
 | 
						|
 | 
						|
        left.right = mid
 | 
						|
        right.left = mid
 | 
						|
        mid.link(left, right)
 | 
						|
        mid.evaluate(self.f, trans)
 | 
						|
 | 
						|
        # calculate the distance of closest approach of mid to the line between left and right
 | 
						|
        numer = left.X*(right.Y - mid.Y) + mid.X*(left.Y - right.Y) + right.X*(mid.Y - left.Y)
 | 
						|
        denom = math.sqrt((left.X - right.X)**2 + (left.Y - right.Y)**2)
 | 
						|
 | 
						|
        # if we haven't sampled enough or left fails to be close enough to right, or mid fails to be linear enough...
 | 
						|
        if (depth < 3 or
 | 
						|
            (denom == 0 and left.t != right.t) or
 | 
						|
            denom > self.discontinuity_limit or
 | 
						|
            (denom != 0. and abs(numer/denom) > self.linearity_limit)):
 | 
						|
 | 
						|
            # and we haven't sampled too many points
 | 
						|
            if depth < self.recursion_limit:
 | 
						|
                self.subsample(left, mid, depth+1, trans)
 | 
						|
                self.subsample(mid, right, depth+1, trans)
 | 
						|
 | 
						|
            else:
 | 
						|
                # We've sampled many points and yet it's still not a small linear gap.
 | 
						|
                # Break the line: it's a discontinuity
 | 
						|
                mid.y = mid.Y = None
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        return self.Path(trans).SVG()
 | 
						|
 | 
						|
    def Path(self, trans=None, local=False):
 | 
						|
        """Apply the transformation "trans" and return a Path object in
 | 
						|
        global coordinates.  If local=True, return a Path in local coordinates
 | 
						|
        (which must be transformed again)."""
 | 
						|
 | 
						|
        if isinstance(trans, basestring):
 | 
						|
            trans = totrans(trans)
 | 
						|
        if isinstance(self.f, basestring):
 | 
						|
            self.f = funcRtoR2(self.f)
 | 
						|
 | 
						|
        self.sample(trans)
 | 
						|
 | 
						|
        output = []
 | 
						|
        for s in self.last_samples:
 | 
						|
            if s.X is not None and s.Y is not None:
 | 
						|
                if s.left is None or s.left.Y is None:
 | 
						|
                    command = "M"
 | 
						|
                else:
 | 
						|
                    command = "L"
 | 
						|
 | 
						|
                if local:
 | 
						|
                    output.append((command, s.x, s.y, False))
 | 
						|
                else:
 | 
						|
                    output.append((command, s.X, s.Y, True))
 | 
						|
 | 
						|
        if self.loop:
 | 
						|
            output.append(("Z",))
 | 
						|
        return Path(output, **self.attr)
 | 
						|
 | 
						|
######################################################################
 | 
						|
 | 
						|
class Poly:
 | 
						|
    """Draws a curve specified by a sequence of points. The curve may be
 | 
						|
    piecewise linear, like a polygon, or a Bezier curve.
 | 
						|
 | 
						|
    Poly(d, mode, loop, attribute=value)
 | 
						|
 | 
						|
    d                       required        list of tuples representing points
 | 
						|
                                            and possibly control points
 | 
						|
    mode                    default="L"     "lines", "bezier", "velocity",
 | 
						|
                                            "foreback", "smooth", or an abbreviation
 | 
						|
    loop                    default=False   if True, connect the first and last
 | 
						|
                                            point, closing the loop
 | 
						|
    attribute=value pairs   keyword list    SVG attributes
 | 
						|
 | 
						|
    The format of the tuples in d depends on the mode.
 | 
						|
 | 
						|
    "lines"/"L"         d=[(x,y), (x,y), ...]
 | 
						|
                                            piecewise-linear segments joining the (x,y) points
 | 
						|
    "bezier"/"B"        d=[(x, y, c1x, c1y, c2x, c2y), ...]
 | 
						|
                                            Bezier curve with two control points (control points
 | 
						|
                                            preceed (x,y), as in SVG paths). If (c1x,c1y) and
 | 
						|
                                            (c2x,c2y) both equal (x,y), you get a linear
 | 
						|
                                            interpolation ("lines")
 | 
						|
    "velocity"/"V"      d=[(x, y, vx, vy), ...]
 | 
						|
                                            curve that passes through (x,y) with velocity (vx,vy)
 | 
						|
                                            (one unit of arclength per unit time); in other words,
 | 
						|
                                            (vx,vy) is the tangent vector at (x,y). If (vx,vy) is
 | 
						|
                                            (0,0), you get a linear interpolation ("lines").
 | 
						|
    "foreback"/"F"      d=[(x, y, bx, by, fx, fy), ...]
 | 
						|
                                            like "velocity" except that there is a left derivative
 | 
						|
                                            (bx,by) and a right derivative (fx,fy). If (bx,by)
 | 
						|
                                            equals (fx,fy) (with no minus sign), you get a
 | 
						|
                                            "velocity" curve
 | 
						|
    "smooth"/"S"        d=[(x,y), (x,y), ...]
 | 
						|
                                            a "velocity" interpolation with (vx,vy)[i] equal to
 | 
						|
                                            ((x,y)[i+1] - (x,y)[i-1])/2: the minimal derivative
 | 
						|
    """
 | 
						|
    defaults = {}
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<Poly (%d nodes) mode=%s loop=%s %s>" % (
 | 
						|
               len(self.d), self.mode, repr(self.loop), self.attr)
 | 
						|
 | 
						|
    def __init__(self, d=[], mode="L", loop=False, **attr):
 | 
						|
        self.d = list(d)
 | 
						|
        self.mode = mode
 | 
						|
        self.loop = loop
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        return self.Path(trans).SVG()
 | 
						|
 | 
						|
    def Path(self, trans=None, local=False):
 | 
						|
        """Apply the transformation "trans" and return a Path object in
 | 
						|
        global coordinates.  If local=True, return a Path in local coordinates
 | 
						|
        (which must be transformed again)."""
 | 
						|
        if isinstance(trans, basestring):
 | 
						|
            trans = totrans(trans)
 | 
						|
 | 
						|
        if self.mode[0] == "L" or self.mode[0] == "l":
 | 
						|
            mode = "L"
 | 
						|
        elif self.mode[0] == "B" or self.mode[0] == "b":
 | 
						|
            mode = "B"
 | 
						|
        elif self.mode[0] == "V" or self.mode[0] == "v":
 | 
						|
            mode = "V"
 | 
						|
        elif self.mode[0] == "F" or self.mode[0] == "f":
 | 
						|
            mode = "F"
 | 
						|
        elif self.mode[0] == "S" or self.mode[0] == "s":
 | 
						|
            mode = "S"
 | 
						|
 | 
						|
            vx, vy = [0.]*len(self.d), [0.]*len(self.d)
 | 
						|
            for i in xrange(len(self.d)):
 | 
						|
                inext = (i+1) % len(self.d)
 | 
						|
                iprev = (i-1) % len(self.d)
 | 
						|
 | 
						|
                vx[i] = (self.d[inext][0] - self.d[iprev][0])/2.
 | 
						|
                vy[i] = (self.d[inext][1] - self.d[iprev][1])/2.
 | 
						|
                if not self.loop and (i == 0 or i == len(self.d)-1):
 | 
						|
                    vx[i], vy[i] = 0., 0.
 | 
						|
 | 
						|
        else:
 | 
						|
            raise ValueError, "mode must be \"lines\", \"bezier\", \"velocity\", \"foreback\", \"smooth\", or an abbreviation"
 | 
						|
 | 
						|
        d = []
 | 
						|
        indexes = range(len(self.d))
 | 
						|
        if self.loop and len(self.d) > 0:
 | 
						|
            indexes.append(0)
 | 
						|
 | 
						|
        for i in indexes:
 | 
						|
            inext = (i+1) % len(self.d)
 | 
						|
            iprev = (i-1) % len(self.d)
 | 
						|
 | 
						|
            x, y = self.d[i][0], self.d[i][1]
 | 
						|
 | 
						|
            if trans is None:
 | 
						|
                X, Y = x, y
 | 
						|
            else:
 | 
						|
                X, Y = trans(x, y)
 | 
						|
 | 
						|
            if d == []:
 | 
						|
                if local:
 | 
						|
                    d.append(("M", x, y, False))
 | 
						|
                else:
 | 
						|
                    d.append(("M", X, Y, True))
 | 
						|
 | 
						|
            elif mode == "L":
 | 
						|
                if local:
 | 
						|
                    d.append(("L", x, y, False))
 | 
						|
                else:
 | 
						|
                    d.append(("L", X, Y, True))
 | 
						|
 | 
						|
            elif mode == "B":
 | 
						|
                c1x, c1y = self.d[i][2], self.d[i][3]
 | 
						|
                if trans is None:
 | 
						|
                    C1X, C1Y = c1x, c1y
 | 
						|
                else:
 | 
						|
                    C1X, C1Y = trans(c1x, c1y)
 | 
						|
 | 
						|
                c2x, c2y = self.d[i][4], self.d[i][5]
 | 
						|
                if trans is None:
 | 
						|
                    C2X, C2Y = c2x, c2y
 | 
						|
                else:
 | 
						|
                    C2X, C2Y = trans(c2x, c2y)
 | 
						|
 | 
						|
                if local:
 | 
						|
                    d.append(("C", c1x, c1y, False, c2x, c2y, False, x, y, False))
 | 
						|
                else:
 | 
						|
                    d.append(("C", C1X, C1Y, True, C2X, C2Y, True, X, Y, True))
 | 
						|
 | 
						|
            elif mode == "V":
 | 
						|
                c1x, c1y = self.d[iprev][2]/3. + self.d[iprev][0], self.d[iprev][3]/3. + self.d[iprev][1]
 | 
						|
                c2x, c2y = self.d[i][2]/-3. + x, self.d[i][3]/-3. + y
 | 
						|
 | 
						|
                if trans is None:
 | 
						|
                    C1X, C1Y = c1x, c1y
 | 
						|
                else:
 | 
						|
                    C1X, C1Y = trans(c1x, c1y)
 | 
						|
                if trans is None:
 | 
						|
                    C2X, C2Y = c2x, c2y
 | 
						|
                else:
 | 
						|
                    C2X, C2Y = trans(c2x, c2y)
 | 
						|
 | 
						|
                if local:
 | 
						|
                    d.append(("C", c1x, c1y, False, c2x, c2y, False, x, y, False))
 | 
						|
                else:
 | 
						|
                    d.append(("C", C1X, C1Y, True, C2X, C2Y, True, X, Y, True))
 | 
						|
 | 
						|
            elif mode == "F":
 | 
						|
                c1x, c1y = self.d[iprev][4]/3. + self.d[iprev][0], self.d[iprev][5]/3. + self.d[iprev][1]
 | 
						|
                c2x, c2y = self.d[i][2]/-3. + x, self.d[i][3]/-3. + y
 | 
						|
 | 
						|
                if trans is None:
 | 
						|
                    C1X, C1Y = c1x, c1y
 | 
						|
                else:
 | 
						|
                    C1X, C1Y = trans(c1x, c1y)
 | 
						|
                if trans is None:
 | 
						|
                    C2X, C2Y = c2x, c2y
 | 
						|
                else:
 | 
						|
                    C2X, C2Y = trans(c2x, c2y)
 | 
						|
 | 
						|
                if local:
 | 
						|
                    d.append(("C", c1x, c1y, False, c2x, c2y, False, x, y, False))
 | 
						|
                else:
 | 
						|
                    d.append(("C", C1X, C1Y, True, C2X, C2Y, True, X, Y, True))
 | 
						|
 | 
						|
            elif mode == "S":
 | 
						|
                c1x, c1y = vx[iprev]/3. + self.d[iprev][0], vy[iprev]/3. + self.d[iprev][1]
 | 
						|
                c2x, c2y = vx[i]/-3. + x, vy[i]/-3. + y
 | 
						|
 | 
						|
                if trans is None:
 | 
						|
                    C1X, C1Y = c1x, c1y
 | 
						|
                else:
 | 
						|
                    C1X, C1Y = trans(c1x, c1y)
 | 
						|
                if trans is None:
 | 
						|
                    C2X, C2Y = c2x, c2y
 | 
						|
                else:
 | 
						|
                    C2X, C2Y = trans(c2x, c2y)
 | 
						|
 | 
						|
                if local:
 | 
						|
                    d.append(("C", c1x, c1y, False, c2x, c2y, False, x, y, False))
 | 
						|
                else:
 | 
						|
                    d.append(("C", C1X, C1Y, True, C2X, C2Y, True, X, Y, True))
 | 
						|
 | 
						|
        if self.loop and len(self.d) > 0:
 | 
						|
            d.append(("Z",))
 | 
						|
 | 
						|
        return Path(d, **self.attr)
 | 
						|
 | 
						|
######################################################################
 | 
						|
 | 
						|
class Text:
 | 
						|
    """Draws a text string at a specified point in local coordinates.
 | 
						|
 | 
						|
    x, y                   required      location of the point in local coordinates
 | 
						|
    d                      required      text/Unicode string
 | 
						|
    attribute=value pairs  keyword list  SVG attributes
 | 
						|
    """
 | 
						|
 | 
						|
    defaults = {"stroke": "none", "fill": "black", "font-size": 5, }
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<Text %s at (%g, %g) %s>" % (repr(self.d), self.x, self.y, self.attr)
 | 
						|
 | 
						|
    def __init__(self, x, y, d, **attr):
 | 
						|
        self.x = x
 | 
						|
        self.y = y
 | 
						|
        self.d = unicode(d)
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        if isinstance(trans, basestring):
 | 
						|
            trans = totrans(trans)
 | 
						|
 | 
						|
        X, Y = self.x, self.y
 | 
						|
        if trans is not None:
 | 
						|
            X, Y = trans(X, Y)
 | 
						|
        return SVG("text", self.d, x=X, y=Y, **self.attr)
 | 
						|
 | 
						|
 | 
						|
class TextGlobal:
 | 
						|
    """Draws a text string at a specified point in global coordinates.
 | 
						|
 | 
						|
    x, y                   required      location of the point in global coordinates
 | 
						|
    d                      required      text/Unicode string
 | 
						|
    attribute=value pairs  keyword list  SVG attributes
 | 
						|
    """
 | 
						|
    defaults = {"stroke": "none", "fill": "black", "font-size": 5, }
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<TextGlobal %s at (%s, %s) %s>" % (repr(self.d), str(self.x), str(self.y), self.attr)
 | 
						|
 | 
						|
    def __init__(self, x, y, d, **attr):
 | 
						|
        self.x = x
 | 
						|
        self.y = y
 | 
						|
        self.d = unicode(d)
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        return SVG("text", self.d, x=self.x, y=self.y, **self.attr)
 | 
						|
 | 
						|
######################################################################
 | 
						|
 | 
						|
_symbol_templates = {"dot": SVG("symbol", SVG("circle", cx=0, cy=0, r=1, stroke="none", fill="black"), viewBox="0 0 1 1", overflow="visible"),
 | 
						|
                    "box": SVG("symbol", SVG("rect", x1=-1, y1=-1, x2=1, y2=1, stroke="none", fill="black"), viewBox="0 0 1 1", overflow="visible"),
 | 
						|
                    "uptri": SVG("symbol", SVG("path", d="M -1 0.866 L 1 0.866 L 0 -0.866 Z", stroke="none", fill="black"), viewBox="0 0 1 1", overflow="visible"),
 | 
						|
                    "downtri": SVG("symbol", SVG("path", d="M -1 -0.866 L 1 -0.866 L 0 0.866 Z", stroke="none", fill="black"), viewBox="0 0 1 1", overflow="visible"),
 | 
						|
                    }
 | 
						|
 | 
						|
def make_symbol(id, shape="dot", **attr):
 | 
						|
    """Creates a new instance of an SVG symbol to avoid cross-linking objects.
 | 
						|
 | 
						|
    id                    required         a new identifier (string/Unicode)
 | 
						|
    shape                 default="dot"  the shape name from _symbol_templates
 | 
						|
    attribute=value list  keyword list     modify the SVG attributes of the new symbol
 | 
						|
    """
 | 
						|
    output = copy.deepcopy(_symbol_templates[shape])
 | 
						|
    for i in output.sub:
 | 
						|
        i.attr.update(attr_preprocess(attr))
 | 
						|
    output["id"] = id
 | 
						|
    return output
 | 
						|
 | 
						|
_circular_dot = make_symbol("circular_dot")
 | 
						|
 | 
						|
 | 
						|
class Dots:
 | 
						|
    """Dots draws SVG symbols at a set of points.
 | 
						|
 | 
						|
    d                      required               list of (x,y) points
 | 
						|
    symbol                 default=None           SVG symbol or a new identifier to
 | 
						|
                                                  label an auto-generated symbol;
 | 
						|
                                                  if None, use pre-defined _circular_dot
 | 
						|
    width, height          default=1, 1           width and height of the symbols
 | 
						|
                                                  in SVG coordinates
 | 
						|
    attribute=value pairs  keyword list           SVG attributes
 | 
						|
    """
 | 
						|
    defaults = {}
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<Dots (%d nodes) %s>" % (len(self.d), self.attr)
 | 
						|
 | 
						|
    def __init__(self, d=[], symbol=None, width=1., height=1., **attr):
 | 
						|
        self.d = list(d)
 | 
						|
        self.width = width
 | 
						|
        self.height = height
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
        if symbol is None:
 | 
						|
            self.symbol = _circular_dot
 | 
						|
        elif isinstance(symbol, SVG):
 | 
						|
            self.symbol = symbol
 | 
						|
        else:
 | 
						|
            self.symbol = make_symbol(symbol)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        if isinstance(trans, basestring):
 | 
						|
            trans = totrans(trans)
 | 
						|
 | 
						|
        output = SVG("g", SVG("defs", self.symbol))
 | 
						|
        id = "#%s" % self.symbol["id"]
 | 
						|
 | 
						|
        for p in self.d:
 | 
						|
            x, y = p[0], p[1]
 | 
						|
 | 
						|
            if trans is None:
 | 
						|
                X, Y = x, y
 | 
						|
            else:
 | 
						|
                X, Y = trans(x, y)
 | 
						|
 | 
						|
            item = SVG("use", x=X, y=Y, xlink__href=id)
 | 
						|
            if self.width is not None:
 | 
						|
                item["width"] = self.width
 | 
						|
            if self.height is not None:
 | 
						|
                item["height"] = self.height
 | 
						|
            output.append(item)
 | 
						|
 | 
						|
        return output
 | 
						|
 | 
						|
######################################################################
 | 
						|
 | 
						|
_marker_templates = {"arrow_start": SVG("marker", SVG("path", d="M 9 3.6 L 10.5 0 L 0 3.6 L 10.5 7.2 L 9 3.6 Z"), viewBox="0 0 10.5 7.2", refX="9", refY="3.6", markerWidth="10.5", markerHeight="7.2", markerUnits="strokeWidth", orient="auto", stroke="none", fill="black"),
 | 
						|
                    "arrow_end": SVG("marker", SVG("path", d="M 1.5 3.6 L 0 0 L 10.5 3.6 L 0 7.2 L 1.5 3.6 Z"), viewBox="0 0 10.5 7.2", refX="1.5", refY="3.6", markerWidth="10.5", markerHeight="7.2", markerUnits="strokeWidth", orient="auto", stroke="none", fill="black"),
 | 
						|
                    }
 | 
						|
 | 
						|
def make_marker(id, shape, **attr):
 | 
						|
    """Creates a new instance of an SVG marker to avoid cross-linking objects.
 | 
						|
 | 
						|
    id                     required         a new identifier (string/Unicode)
 | 
						|
    shape                  required         the shape name from _marker_templates
 | 
						|
    attribute=value list   keyword list     modify the SVG attributes of the new marker
 | 
						|
    """
 | 
						|
    output = copy.deepcopy(_marker_templates[shape])
 | 
						|
    for i in output.sub:
 | 
						|
        i.attr.update(attr_preprocess(attr))
 | 
						|
    output["id"] = id
 | 
						|
    return output
 | 
						|
 | 
						|
 | 
						|
class Line(Curve):
 | 
						|
    """Draws a line between two points.
 | 
						|
 | 
						|
    Line(x1, y1, x2, y2, arrow_start, arrow_end, attribute=value)
 | 
						|
 | 
						|
    x1, y1                  required        the starting point
 | 
						|
    x2, y2                  required        the ending point
 | 
						|
    arrow_start             default=None    if an identifier string/Unicode,
 | 
						|
                                            draw a new arrow object at the
 | 
						|
                                            beginning of the line; if a marker,
 | 
						|
                                            draw that marker instead
 | 
						|
    arrow_end               default=None    same for the end of the line
 | 
						|
    attribute=value pairs   keyword list    SVG attributes
 | 
						|
    """
 | 
						|
    defaults = {}
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<Line (%g, %g) to (%g, %g) %s>" % (
 | 
						|
               self.x1, self.y1, self.x2, self.y2, self.attr)
 | 
						|
 | 
						|
    def __init__(self, x1, y1, x2, y2, arrow_start=None, arrow_end=None, **attr):
 | 
						|
        self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2
 | 
						|
        self.arrow_start, self.arrow_end = arrow_start, arrow_end
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
 | 
						|
        line = self.Path(trans).SVG()
 | 
						|
 | 
						|
        if ((self.arrow_start != False and self.arrow_start is not None) or
 | 
						|
            (self.arrow_end != False and self.arrow_end is not None)):
 | 
						|
            defs = SVG("defs")
 | 
						|
 | 
						|
            if self.arrow_start != False and self.arrow_start is not None:
 | 
						|
                if isinstance(self.arrow_start, SVG):
 | 
						|
                    defs.append(self.arrow_start)
 | 
						|
                    line.attr["marker-start"] = "url(#%s)" % self.arrow_start["id"]
 | 
						|
                elif isinstance(self.arrow_start, basestring):
 | 
						|
                    defs.append(make_marker(self.arrow_start, "arrow_start"))
 | 
						|
                    line.attr["marker-start"] = "url(#%s)" % self.arrow_start
 | 
						|
                else:
 | 
						|
                    raise TypeError, "arrow_start must be False/None or an id string for the new marker"
 | 
						|
 | 
						|
            if self.arrow_end != False and self.arrow_end is not None:
 | 
						|
                if isinstance(self.arrow_end, SVG):
 | 
						|
                    defs.append(self.arrow_end)
 | 
						|
                    line.attr["marker-end"] = "url(#%s)" % self.arrow_end["id"]
 | 
						|
                elif isinstance(self.arrow_end, basestring):
 | 
						|
                    defs.append(make_marker(self.arrow_end, "arrow_end"))
 | 
						|
                    line.attr["marker-end"] = "url(#%s)" % self.arrow_end
 | 
						|
                else:
 | 
						|
                    raise TypeError, "arrow_end must be False/None or an id string for the new marker"
 | 
						|
 | 
						|
            return SVG("g", defs, line)
 | 
						|
 | 
						|
        return line
 | 
						|
 | 
						|
    def Path(self, trans=None, local=False):
 | 
						|
        """Apply the transformation "trans" and return a Path object in
 | 
						|
        global coordinates.  If local=True, return a Path in local coordinates
 | 
						|
        (which must be transformed again)."""
 | 
						|
        self.f = lambda t: (self.x1 + t*(self.x2 - self.x1), self.y1 + t*(self.y2 - self.y1))
 | 
						|
        self.low = 0.
 | 
						|
        self.high = 1.
 | 
						|
        self.loop = False
 | 
						|
 | 
						|
        if trans is None:
 | 
						|
            return Path([("M", self.x1, self.y1, not local), ("L", self.x2, self.y2, not local)], **self.attr)
 | 
						|
        else:
 | 
						|
            return Curve.Path(self, trans, local)
 | 
						|
 | 
						|
 | 
						|
class LineGlobal:
 | 
						|
    """Draws a line between two points, one or both of which is in
 | 
						|
    global coordinates.
 | 
						|
 | 
						|
    Line(x1, y1, x2, y2, lcoal1, local2, arrow_start, arrow_end, attribute=value)
 | 
						|
 | 
						|
    x1, y1                  required        the starting point
 | 
						|
    x2, y2                  required        the ending point
 | 
						|
    local1                  default=False   if True, interpret first point as a
 | 
						|
                                            local coordinate (apply transform)
 | 
						|
    local2                  default=False   if True, interpret second point as a
 | 
						|
                                            local coordinate (apply transform)
 | 
						|
    arrow_start             default=None    if an identifier string/Unicode,
 | 
						|
                                            draw a new arrow object at the
 | 
						|
                                            beginning of the line; if a marker,
 | 
						|
                                            draw that marker instead
 | 
						|
    arrow_end               default=None    same for the end of the line
 | 
						|
    attribute=value pairs   keyword list    SVG attributes
 | 
						|
    """
 | 
						|
    defaults = {}
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        local1, local2 = "", ""
 | 
						|
        if self.local1:
 | 
						|
            local1 = "L"
 | 
						|
        if self.local2:
 | 
						|
            local2 = "L"
 | 
						|
 | 
						|
        return "<LineGlobal %s(%s, %s) to %s(%s, %s) %s>" % (
 | 
						|
               local1, str(self.x1), str(self.y1), local2, str(self.x2), str(self.y2), self.attr)
 | 
						|
 | 
						|
    def __init__(self, x1, y1, x2, y2, local1=False, local2=False, arrow_start=None, arrow_end=None, **attr):
 | 
						|
        self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2
 | 
						|
        self.local1, self.local2 = local1, local2
 | 
						|
        self.arrow_start, self.arrow_end = arrow_start, arrow_end
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        if isinstance(trans, basestring):
 | 
						|
            trans = totrans(trans)
 | 
						|
 | 
						|
        X1, Y1, X2, Y2 = self.x1, self.y1, self.x2, self.y2
 | 
						|
 | 
						|
        if self.local1:
 | 
						|
            X1, Y1 = trans(X1, Y1)
 | 
						|
        if self.local2:
 | 
						|
            X2, Y2 = trans(X2, Y2)
 | 
						|
 | 
						|
        line = SVG("path", d="M%s %s L%s %s" % (X1, Y1, X2, Y2), **self.attr)
 | 
						|
 | 
						|
        if ((self.arrow_start != False and self.arrow_start is not None) or
 | 
						|
            (self.arrow_end != False and self.arrow_end is not None)):
 | 
						|
            defs = SVG("defs")
 | 
						|
 | 
						|
            if self.arrow_start != False and self.arrow_start is not None:
 | 
						|
                if isinstance(self.arrow_start, SVG):
 | 
						|
                    defs.append(self.arrow_start)
 | 
						|
                    line.attr["marker-start"] = "url(#%s)" % self.arrow_start["id"]
 | 
						|
                elif isinstance(self.arrow_start, basestring):
 | 
						|
                    defs.append(make_marker(self.arrow_start, "arrow_start"))
 | 
						|
                    line.attr["marker-start"] = "url(#%s)" % self.arrow_start
 | 
						|
                else:
 | 
						|
                    raise TypeError, "arrow_start must be False/None or an id string for the new marker"
 | 
						|
 | 
						|
            if self.arrow_end != False and self.arrow_end is not None:
 | 
						|
                if isinstance(self.arrow_end, SVG):
 | 
						|
                    defs.append(self.arrow_end)
 | 
						|
                    line.attr["marker-end"] = "url(#%s)" % self.arrow_end["id"]
 | 
						|
                elif isinstance(self.arrow_end, basestring):
 | 
						|
                    defs.append(make_marker(self.arrow_end, "arrow_end"))
 | 
						|
                    line.attr["marker-end"] = "url(#%s)" % self.arrow_end
 | 
						|
                else:
 | 
						|
                    raise TypeError, "arrow_end must be False/None or an id string for the new marker"
 | 
						|
 | 
						|
            return SVG("g", defs, line)
 | 
						|
 | 
						|
        return line
 | 
						|
 | 
						|
 | 
						|
class VLine(Line):
 | 
						|
    """Draws a vertical line.
 | 
						|
 | 
						|
    VLine(y1, y2, x, attribute=value)
 | 
						|
 | 
						|
    y1, y2                  required        y range
 | 
						|
    x                       required        x position
 | 
						|
    attribute=value pairs   keyword list    SVG attributes
 | 
						|
    """
 | 
						|
    defaults = {}
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<VLine (%g, %g) at x=%s %s>" % (self.y1, self.y2, self.x, self.attr)
 | 
						|
 | 
						|
    def __init__(self, y1, y2, x, **attr):
 | 
						|
        self.x = x
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
        Line.__init__(self, x, y1, x, y2, **self.attr)
 | 
						|
 | 
						|
    def Path(self, trans=None, local=False):
 | 
						|
        """Apply the transformation "trans" and return a Path object in
 | 
						|
        global coordinates.  If local=True, return a Path in local coordinates
 | 
						|
        (which must be transformed again)."""
 | 
						|
        self.x1 = self.x
 | 
						|
        self.x2 = self.x
 | 
						|
        return Line.Path(self, trans, local)
 | 
						|
 | 
						|
 | 
						|
class HLine(Line):
 | 
						|
    """Draws a horizontal line.
 | 
						|
 | 
						|
    HLine(x1, x2, y, attribute=value)
 | 
						|
 | 
						|
    x1, x2                  required        x range
 | 
						|
    y                       required        y position
 | 
						|
    attribute=value pairs   keyword list    SVG attributes
 | 
						|
    """
 | 
						|
    defaults = {}
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<HLine (%g, %g) at y=%s %s>" % (self.x1, self.x2, self.y, self.attr)
 | 
						|
 | 
						|
    def __init__(self, x1, x2, y, **attr):
 | 
						|
        self.y = y
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
        Line.__init__(self, x1, y, x2, y, **self.attr)
 | 
						|
 | 
						|
    def Path(self, trans=None, local=False):
 | 
						|
        """Apply the transformation "trans" and return a Path object in
 | 
						|
        global coordinates.  If local=True, return a Path in local coordinates
 | 
						|
        (which must be transformed again)."""
 | 
						|
        self.y1 = self.y
 | 
						|
        self.y2 = self.y
 | 
						|
        return Line.Path(self, trans, local)
 | 
						|
 | 
						|
######################################################################
 | 
						|
 | 
						|
class Rect(Curve):
 | 
						|
    """Draws a rectangle.
 | 
						|
 | 
						|
    Rect(x1, y1, x2, y2, attribute=value)
 | 
						|
 | 
						|
    x1, y1                  required        the starting point
 | 
						|
    x2, y2                  required        the ending point
 | 
						|
    attribute=value pairs   keyword list    SVG attributes
 | 
						|
    """
 | 
						|
    defaults = {}
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<Rect (%g, %g), (%g, %g) %s>" % (
 | 
						|
               self.x1, self.y1, self.x2, self.y2, self.attr)
 | 
						|
 | 
						|
    def __init__(self, x1, y1, x2, y2, **attr):
 | 
						|
        self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        return self.Path(trans).SVG()
 | 
						|
 | 
						|
    def Path(self, trans=None, local=False):
 | 
						|
        """Apply the transformation "trans" and return a Path object in
 | 
						|
        global coordinates.  If local=True, return a Path in local coordinates
 | 
						|
        (which must be transformed again)."""
 | 
						|
        if trans is None:
 | 
						|
            return Path([("M", self.x1, self.y1, not local), ("L", self.x2, self.y1, not local), ("L", self.x2, self.y2, not local), ("L", self.x1, self.y2, not local), ("Z",)], **self.attr)
 | 
						|
 | 
						|
        else:
 | 
						|
            self.low = 0.
 | 
						|
            self.high = 1.
 | 
						|
            self.loop = False
 | 
						|
 | 
						|
            self.f = lambda t: (self.x1 + t*(self.x2 - self.x1), self.y1)
 | 
						|
            d1 = Curve.Path(self, trans, local).d
 | 
						|
 | 
						|
            self.f = lambda t: (self.x2, self.y1 + t*(self.y2 - self.y1))
 | 
						|
            d2 = Curve.Path(self, trans, local).d
 | 
						|
            del d2[0]
 | 
						|
 | 
						|
            self.f = lambda t: (self.x2 + t*(self.x1 - self.x2), self.y2)
 | 
						|
            d3 = Curve.Path(self, trans, local).d
 | 
						|
            del d3[0]
 | 
						|
 | 
						|
            self.f = lambda t: (self.x1, self.y2 + t*(self.y1 - self.y2))
 | 
						|
            d4 = Curve.Path(self, trans, local).d
 | 
						|
            del d4[0]
 | 
						|
 | 
						|
            return Path(d=(d1 + d2 + d3 + d4 + [("Z",)]), **self.attr)
 | 
						|
 | 
						|
######################################################################
 | 
						|
 | 
						|
class Ellipse(Curve):
 | 
						|
    """Draws an ellipse from a semimajor vector (ax,ay) and a semiminor
 | 
						|
    length (b).
 | 
						|
 | 
						|
    Ellipse(x, y, ax, ay, b, attribute=value)
 | 
						|
 | 
						|
    x, y                    required        the center of the ellipse/circle
 | 
						|
    ax, ay                  required        a vector indicating the length
 | 
						|
                                            and direction of the semimajor axis
 | 
						|
    b                       required        the length of the semiminor axis.
 | 
						|
                                            If equal to sqrt(ax2 + ay2), the
 | 
						|
                                            ellipse is a circle
 | 
						|
    attribute=value pairs   keyword list    SVG attributes
 | 
						|
 | 
						|
    (If sqrt(ax**2 + ay**2) is less than b, then (ax,ay) is actually the
 | 
						|
    semiminor axis.)
 | 
						|
    """
 | 
						|
    defaults = {}
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<Ellipse (%g, %g) a=(%g, %g), b=%g %s>" % (
 | 
						|
               self.x, self.y, self.ax, self.ay, self.b, self.attr)
 | 
						|
 | 
						|
    def __init__(self, x, y, ax, ay, b, **attr):
 | 
						|
        self.x, self.y, self.ax, self.ay, self.b = x, y, ax, ay, b
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        return self.Path(trans).SVG()
 | 
						|
 | 
						|
    def Path(self, trans=None, local=False):
 | 
						|
        """Apply the transformation "trans" and return a Path object in
 | 
						|
        global coordinates.  If local=True, return a Path in local coordinates
 | 
						|
        (which must be transformed again)."""
 | 
						|
        angle = math.atan2(self.ay, self.ax) + math.pi/2.
 | 
						|
        bx = self.b * math.cos(angle)
 | 
						|
        by = self.b * math.sin(angle)
 | 
						|
 | 
						|
        self.f = lambda t: (self.x + self.ax*math.cos(t) + bx*math.sin(t), self.y + self.ay*math.cos(t) + by*math.sin(t))
 | 
						|
        self.low = -math.pi
 | 
						|
        self.high = math.pi
 | 
						|
        self.loop = True
 | 
						|
        return Curve.Path(self, trans, local)
 | 
						|
 | 
						|
######################################################################
 | 
						|
 | 
						|
def unumber(x):
 | 
						|
    """Converts numbers to a Unicode string, taking advantage of special
 | 
						|
    Unicode characters to make nice minus signs and scientific notation.
 | 
						|
    """
 | 
						|
    output = u"%g" % x
 | 
						|
 | 
						|
    if output[0] == u"-":
 | 
						|
        output = u"\u2013" + output[1:]
 | 
						|
 | 
						|
    index = output.find(u"e")
 | 
						|
    if index != -1:
 | 
						|
        uniout = unicode(output[:index]) + u"\u00d710"
 | 
						|
        saw_nonzero = False
 | 
						|
        for n in output[index+1:]:
 | 
						|
            if n == u"+":
 | 
						|
                pass # uniout += u"\u207a"
 | 
						|
            elif n == u"-":
 | 
						|
                uniout += u"\u207b"
 | 
						|
            elif n == u"0":
 | 
						|
                if saw_nonzero:
 | 
						|
                    uniout += u"\u2070"
 | 
						|
            elif n == u"1":
 | 
						|
                saw_nonzero = True
 | 
						|
                uniout += u"\u00b9"
 | 
						|
            elif n == u"2":
 | 
						|
                saw_nonzero = True
 | 
						|
                uniout += u"\u00b2"
 | 
						|
            elif n == u"3":
 | 
						|
                saw_nonzero = True
 | 
						|
                uniout += u"\u00b3"
 | 
						|
            elif u"4" <= n <= u"9":
 | 
						|
                saw_nonzero = True
 | 
						|
                if saw_nonzero:
 | 
						|
                    uniout += eval("u\"\\u%x\"" % (0x2070 + ord(n) - ord(u"0")))
 | 
						|
            else:
 | 
						|
                uniout += n
 | 
						|
 | 
						|
        if uniout[:2] == u"1\u00d7":
 | 
						|
            uniout = uniout[2:]
 | 
						|
        return uniout
 | 
						|
 | 
						|
    return output
 | 
						|
 | 
						|
 | 
						|
class Ticks:
 | 
						|
    """Superclass for all graphics primitives that draw ticks,
 | 
						|
    miniticks, and tick labels.  This class only draws the ticks.
 | 
						|
 | 
						|
    Ticks(f, low, high, ticks, miniticks, labels, logbase, arrow_start,
 | 
						|
          arrow_end, text_attr, attribute=value)
 | 
						|
 | 
						|
    f                       required        parametric function along which ticks
 | 
						|
                                            will be drawn; has the same format as
 | 
						|
                                            the function used in Curve
 | 
						|
    low, high               required        range of the independent variable
 | 
						|
    ticks                   default=-10     request ticks according to the standard
 | 
						|
                                            tick specification (see below)
 | 
						|
    miniticks               default=True    request miniticks according to the
 | 
						|
                                            standard minitick specification (below)
 | 
						|
    labels                  True            request tick labels according to the
 | 
						|
                                            standard tick label specification (below)
 | 
						|
    logbase                 default=None    if a number, the axis is logarithmic with
 | 
						|
                                            ticks at the given base (usually 10)
 | 
						|
    arrow_start             default=None    if a new string identifier, draw an arrow
 | 
						|
                                            at the low-end of the axis, referenced by
 | 
						|
                                            that identifier; if an SVG marker object,
 | 
						|
                                            use that marker
 | 
						|
    arrow_end               default=None    if a new string identifier, draw an arrow
 | 
						|
                                            at the high-end of the axis, referenced by
 | 
						|
                                            that identifier; if an SVG marker object,
 | 
						|
                                            use that marker
 | 
						|
    text_attr               default={}      SVG attributes for the text labels
 | 
						|
    attribute=value pairs   keyword list    SVG attributes for the tick marks
 | 
						|
 | 
						|
    Standard tick specification:
 | 
						|
 | 
						|
        * True: same as -10 (below).
 | 
						|
        * Positive number N: draw exactly N ticks, including the endpoints. To
 | 
						|
          subdivide an axis into 10 equal-sized segments, ask for 11 ticks.
 | 
						|
        * Negative number -N: draw at least N ticks. Ticks will be chosen with
 | 
						|
          "natural" values, multiples of 2 or 5.
 | 
						|
        * List of values: draw a tick mark at each value.
 | 
						|
        * Dict of value, label pairs: draw a tick mark at each value, labeling
 | 
						|
          it with the given string. This lets you say things like {3.14159: "pi"}.
 | 
						|
        * False or None: no ticks.
 | 
						|
 | 
						|
    Standard minitick specification:
 | 
						|
 | 
						|
        * True: draw miniticks with "natural" values, more closely spaced than
 | 
						|
          the ticks.
 | 
						|
        * Positive number N: draw exactly N miniticks, including the endpoints.
 | 
						|
          To subdivide an axis into 100 equal-sized segments, ask for 101 miniticks.
 | 
						|
        * Negative number -N: draw at least N miniticks.
 | 
						|
        * List of values: draw a minitick mark at each value.
 | 
						|
        * False or None: no miniticks.
 | 
						|
 | 
						|
    Standard tick label specification:
 | 
						|
 | 
						|
        * True: use the unumber function (described below)
 | 
						|
        * Format string: standard format strings, e.g. "%5.2f" for 12.34
 | 
						|
        * Python callable: function that converts numbers to strings
 | 
						|
        * False or None: no labels
 | 
						|
    """
 | 
						|
    defaults = {"stroke-width": "0.25pt", }
 | 
						|
    text_defaults = {"stroke": "none", "fill": "black", "font-size": 5, }
 | 
						|
    tick_start = -1.5
 | 
						|
    tick_end = 1.5
 | 
						|
    minitick_start = -0.75
 | 
						|
    minitick_end = 0.75
 | 
						|
    text_start = 2.5
 | 
						|
    text_angle = 0.
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<Ticks %s from %s to %s ticks=%s labels=%s %s>" % (
 | 
						|
               self.f, self.low, self.high, str(self.ticks), str(self.labels), self.attr)
 | 
						|
 | 
						|
    def __init__(self, f, low, high, ticks=-10, miniticks=True, labels=True, logbase=None,
 | 
						|
                 arrow_start=None, arrow_end=None, text_attr={}, **attr):
 | 
						|
        self.f = f
 | 
						|
        self.low = low
 | 
						|
        self.high = high
 | 
						|
        self.ticks = ticks
 | 
						|
        self.miniticks = miniticks
 | 
						|
        self.labels = labels
 | 
						|
        self.logbase = logbase
 | 
						|
        self.arrow_start = arrow_start
 | 
						|
        self.arrow_end = arrow_end
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
        self.text_attr = dict(self.text_defaults)
 | 
						|
        self.text_attr.update(text_attr)
 | 
						|
 | 
						|
    def orient_tickmark(self, t, trans=None):
 | 
						|
        """Return the position, normalized local x vector, normalized
 | 
						|
        local y vector, and angle of a tick at position t.
 | 
						|
 | 
						|
        Normally only used internally.
 | 
						|
        """
 | 
						|
        if isinstance(trans, basestring):
 | 
						|
            trans = totrans(trans)
 | 
						|
        if trans is None:
 | 
						|
            f = self.f
 | 
						|
        else:
 | 
						|
            f = lambda t: trans(*self.f(t))
 | 
						|
 | 
						|
        eps = _epsilon * abs(self.high - self.low)
 | 
						|
 | 
						|
        X, Y = f(t)
 | 
						|
        Xprime, Yprime = f(t + eps)
 | 
						|
        xhatx, xhaty = (Xprime - X)/eps, (Yprime - Y)/eps
 | 
						|
 | 
						|
        norm = math.sqrt(xhatx**2 + xhaty**2)
 | 
						|
        if norm != 0:
 | 
						|
            xhatx, xhaty = xhatx/norm, xhaty/norm
 | 
						|
        else:
 | 
						|
            xhatx, xhaty = 1., 0.
 | 
						|
 | 
						|
        angle = math.atan2(xhaty, xhatx) + math.pi/2.
 | 
						|
        yhatx, yhaty = math.cos(angle), math.sin(angle)
 | 
						|
 | 
						|
        return (X, Y), (xhatx, xhaty), (yhatx, yhaty), angle
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        if isinstance(trans, basestring):
 | 
						|
            trans = totrans(trans)
 | 
						|
 | 
						|
        self.last_ticks, self.last_miniticks = self.interpret()
 | 
						|
        tickmarks = Path([], **self.attr)
 | 
						|
        minitickmarks = Path([], **self.attr)
 | 
						|
        output = SVG("g")
 | 
						|
 | 
						|
        if ((self.arrow_start != False and self.arrow_start is not None) or
 | 
						|
            (self.arrow_end != False and self.arrow_end is not None)):
 | 
						|
            defs = SVG("defs")
 | 
						|
 | 
						|
            if self.arrow_start != False and self.arrow_start is not None:
 | 
						|
                if isinstance(self.arrow_start, SVG):
 | 
						|
                    defs.append(self.arrow_start)
 | 
						|
                elif isinstance(self.arrow_start, basestring):
 | 
						|
                    defs.append(make_marker(self.arrow_start, "arrow_start"))
 | 
						|
                else:
 | 
						|
                    raise TypeError, "arrow_start must be False/None or an id string for the new marker"
 | 
						|
 | 
						|
            if self.arrow_end != False and self.arrow_end is not None:
 | 
						|
                if isinstance(self.arrow_end, SVG):
 | 
						|
                    defs.append(self.arrow_end)
 | 
						|
                elif isinstance(self.arrow_end, basestring):
 | 
						|
                    defs.append(make_marker(self.arrow_end, "arrow_end"))
 | 
						|
                else:
 | 
						|
                    raise TypeError, "arrow_end must be False/None or an id string for the new marker"
 | 
						|
 | 
						|
            output.append(defs)
 | 
						|
 | 
						|
        eps = _epsilon * (self.high - self.low)
 | 
						|
 | 
						|
        for t, label in self.last_ticks.items():
 | 
						|
            (X, Y), (xhatx, xhaty), (yhatx, yhaty), angle = self.orient_tickmark(t, trans)
 | 
						|
 | 
						|
            if ((not self.arrow_start or abs(t - self.low) > eps) and
 | 
						|
                (not self.arrow_end or abs(t - self.high) > eps)):
 | 
						|
                tickmarks.d.append(("M", X - yhatx*self.tick_start, Y - yhaty*self.tick_start, True))
 | 
						|
                tickmarks.d.append(("L", X - yhatx*self.tick_end, Y - yhaty*self.tick_end, True))
 | 
						|
 | 
						|
            angle = (angle - math.pi/2.)*180./math.pi + self.text_angle
 | 
						|
 | 
						|
            ########### a HACK! ############ (to be removed when Inkscape handles baselines)
 | 
						|
            if _hacks["inkscape-text-vertical-shift"]:
 | 
						|
                if self.text_start > 0:
 | 
						|
                    X += math.cos(angle*math.pi/180. + math.pi/2.) * 2.
 | 
						|
                    Y += math.sin(angle*math.pi/180. + math.pi/2.) * 2.
 | 
						|
                else:
 | 
						|
                    X += math.cos(angle*math.pi/180. + math.pi/2.) * 2. * 2.5
 | 
						|
                    Y += math.sin(angle*math.pi/180. + math.pi/2.) * 2. * 2.5
 | 
						|
            ########### end hack ###########
 | 
						|
 | 
						|
            if label != "":
 | 
						|
                output.append(SVG("text", label, transform="translate(%g, %g) rotate(%g)" %
 | 
						|
                                  (X - yhatx*self.text_start, Y - yhaty*self.text_start, angle), **self.text_attr))
 | 
						|
 | 
						|
        for t in self.last_miniticks:
 | 
						|
            skip = False
 | 
						|
            for tt in self.last_ticks.keys():
 | 
						|
                if abs(t - tt) < eps:
 | 
						|
                    skip = True
 | 
						|
                    break
 | 
						|
            if not skip:
 | 
						|
                (X, Y), (xhatx, xhaty), (yhatx, yhaty), angle = self.orient_tickmark(t, trans)
 | 
						|
 | 
						|
            if ((not self.arrow_start or abs(t - self.low) > eps) and
 | 
						|
                (not self.arrow_end or abs(t - self.high) > eps)):
 | 
						|
                minitickmarks.d.append(("M", X - yhatx*self.minitick_start, Y - yhaty*self.minitick_start, True))
 | 
						|
                minitickmarks.d.append(("L", X - yhatx*self.minitick_end, Y - yhaty*self.minitick_end, True))
 | 
						|
 | 
						|
        output.prepend(tickmarks.SVG(trans))
 | 
						|
        output.prepend(minitickmarks.SVG(trans))
 | 
						|
        return output
 | 
						|
 | 
						|
    def interpret(self):
 | 
						|
        """Evaluate and return optimal ticks and miniticks according to
 | 
						|
        the standard minitick specification.
 | 
						|
 | 
						|
        Normally only used internally.
 | 
						|
        """
 | 
						|
 | 
						|
        if self.labels is None or self.labels == False:
 | 
						|
            format = lambda x: ""
 | 
						|
 | 
						|
        elif self.labels == True:
 | 
						|
            format = unumber
 | 
						|
 | 
						|
        elif isinstance(self.labels, basestring):
 | 
						|
            format = lambda x: (self.labels % x)
 | 
						|
 | 
						|
        elif callable(self.labels):
 | 
						|
            format = self.labels
 | 
						|
 | 
						|
        else:
 | 
						|
            raise TypeError, "labels must be None/False, True, a format string, or a number->string function"
 | 
						|
 | 
						|
        # Now for the ticks
 | 
						|
        ticks = self.ticks
 | 
						|
 | 
						|
        # Case 1: ticks is None/False
 | 
						|
        if ticks is None or ticks == False:
 | 
						|
            return {}, []
 | 
						|
 | 
						|
        # Case 2: ticks is the number of desired ticks
 | 
						|
        elif isinstance(ticks, (int, long)):
 | 
						|
            if ticks == True:
 | 
						|
                ticks = -10
 | 
						|
 | 
						|
            if self.logbase is None:
 | 
						|
                ticks = self.compute_ticks(ticks, format)
 | 
						|
            else:
 | 
						|
                ticks = self.compute_logticks(self.logbase, ticks, format)
 | 
						|
 | 
						|
            # Now for the miniticks
 | 
						|
            if self.miniticks == True:
 | 
						|
                if self.logbase is None:
 | 
						|
                    return ticks, self.compute_miniticks(ticks)
 | 
						|
                else:
 | 
						|
                    return ticks, self.compute_logminiticks(self.logbase)
 | 
						|
 | 
						|
            elif isinstance(self.miniticks, (int, long)):
 | 
						|
                return ticks, self.regular_miniticks(self.miniticks)
 | 
						|
 | 
						|
            elif getattr(self.miniticks, "__iter__", False):
 | 
						|
                return ticks, self.miniticks
 | 
						|
 | 
						|
            elif self.miniticks == False or self.miniticks is None:
 | 
						|
                return ticks, []
 | 
						|
 | 
						|
            else:
 | 
						|
                raise TypeError, "miniticks must be None/False, True, a number of desired miniticks, or a list of numbers"
 | 
						|
 | 
						|
        # Cases 3 & 4: ticks is iterable
 | 
						|
        elif getattr(ticks, "__iter__", False):
 | 
						|
 | 
						|
            # Case 3: ticks is some kind of list
 | 
						|
            if not isinstance(ticks, dict):
 | 
						|
                output = {}
 | 
						|
                eps = _epsilon * (self.high - self.low)
 | 
						|
                for x in ticks:
 | 
						|
                    if format == unumber and abs(x) < eps:
 | 
						|
                        output[x] = u"0"
 | 
						|
                    else:
 | 
						|
                        output[x] = format(x)
 | 
						|
                ticks = output
 | 
						|
 | 
						|
            # Case 4: ticks is a dict
 | 
						|
            else:
 | 
						|
                pass
 | 
						|
 | 
						|
            # Now for the miniticks
 | 
						|
            if self.miniticks == True:
 | 
						|
                if self.logbase is None:
 | 
						|
                    return ticks, self.compute_miniticks(ticks)
 | 
						|
                else:
 | 
						|
                    return ticks, self.compute_logminiticks(self.logbase)
 | 
						|
 | 
						|
            elif isinstance(self.miniticks, (int, long)):
 | 
						|
                return ticks, self.regular_miniticks(self.miniticks)
 | 
						|
 | 
						|
            elif getattr(self.miniticks, "__iter__", False):
 | 
						|
                return ticks, self.miniticks
 | 
						|
 | 
						|
            elif self.miniticks == False or self.miniticks is None:
 | 
						|
                return ticks, []
 | 
						|
 | 
						|
            else:
 | 
						|
                raise TypeError, "miniticks must be None/False, True, a number of desired miniticks, or a list of numbers"
 | 
						|
 | 
						|
        else:
 | 
						|
            raise TypeError, "ticks must be None/False, a number of desired ticks, a list of numbers, or a dictionary of explicit markers"
 | 
						|
 | 
						|
    def compute_ticks(self, N, format):
 | 
						|
        """Return less than -N or exactly N optimal linear ticks.
 | 
						|
 | 
						|
        Normally only used internally.
 | 
						|
        """
 | 
						|
        if self.low >= self.high:
 | 
						|
            raise ValueError, "low must be less than high"
 | 
						|
        if N == 1:
 | 
						|
            raise ValueError, "N can be 0 or >1 to specify the exact number of ticks or negative to specify a maximum"
 | 
						|
 | 
						|
        eps = _epsilon * (self.high - self.low)
 | 
						|
 | 
						|
        if N >= 0:
 | 
						|
            output = {}
 | 
						|
            x = self.low
 | 
						|
            for i in xrange(N):
 | 
						|
                if format == unumber and abs(x) < eps:
 | 
						|
                    label = u"0"
 | 
						|
                else:
 | 
						|
                    label = format(x)
 | 
						|
                output[x] = label
 | 
						|
                x += (self.high - self.low)/(N-1.)
 | 
						|
            return output
 | 
						|
 | 
						|
        N = -N
 | 
						|
 | 
						|
        counter = 0
 | 
						|
        granularity = 10**math.ceil(math.log10(max(abs(self.low), abs(self.high))))
 | 
						|
        lowN = math.ceil(1.*self.low / granularity)
 | 
						|
        highN = math.floor(1.*self.high / granularity)
 | 
						|
 | 
						|
        while lowN > highN:
 | 
						|
            countermod3 = counter % 3
 | 
						|
            if countermod3 == 0:
 | 
						|
                granularity *= 0.5
 | 
						|
            elif countermod3 == 1:
 | 
						|
                granularity *= 0.4
 | 
						|
            else:
 | 
						|
                granularity *= 0.5
 | 
						|
            counter += 1
 | 
						|
            lowN = math.ceil(1.*self.low / granularity)
 | 
						|
            highN = math.floor(1.*self.high / granularity)
 | 
						|
 | 
						|
        last_granularity = granularity
 | 
						|
        last_trial = None
 | 
						|
 | 
						|
        while True:
 | 
						|
            trial = {}
 | 
						|
            for n in range(int(lowN), int(highN)+1):
 | 
						|
                x = n * granularity
 | 
						|
                if format == unumber and abs(x) < eps:
 | 
						|
                    label = u"0"
 | 
						|
                else:
 | 
						|
                    label = format(x)
 | 
						|
                trial[x] = label
 | 
						|
 | 
						|
            if int(highN)+1 - int(lowN) >= N:
 | 
						|
                if last_trial is None:
 | 
						|
                    v1, v2 = self.low, self.high
 | 
						|
                    return {v1: format(v1), v2: format(v2)}
 | 
						|
                else:
 | 
						|
                    low_in_ticks, high_in_ticks = False, False
 | 
						|
                    for t in last_trial.keys():
 | 
						|
                        if 1.*abs(t - self.low)/last_granularity < _epsilon:
 | 
						|
                            low_in_ticks = True
 | 
						|
                        if 1.*abs(t - self.high)/last_granularity < _epsilon:
 | 
						|
                            high_in_ticks = True
 | 
						|
 | 
						|
                    lowN = 1.*self.low / last_granularity
 | 
						|
                    highN = 1.*self.high / last_granularity
 | 
						|
                    if abs(lowN - round(lowN)) < _epsilon and not low_in_ticks:
 | 
						|
                        last_trial[self.low] = format(self.low)
 | 
						|
                    if abs(highN - round(highN)) < _epsilon and not high_in_ticks:
 | 
						|
                        last_trial[self.high] = format(self.high)
 | 
						|
                    return last_trial
 | 
						|
 | 
						|
            last_granularity = granularity
 | 
						|
            last_trial = trial
 | 
						|
 | 
						|
            countermod3 = counter % 3
 | 
						|
            if countermod3 == 0:
 | 
						|
                granularity *= 0.5
 | 
						|
            elif countermod3 == 1:
 | 
						|
                granularity *= 0.4
 | 
						|
            else:
 | 
						|
                granularity *= 0.5
 | 
						|
            counter += 1
 | 
						|
            lowN = math.ceil(1.*self.low / granularity)
 | 
						|
            highN = math.floor(1.*self.high / granularity)
 | 
						|
 | 
						|
    def regular_miniticks(self, N):
 | 
						|
        """Return exactly N linear ticks.
 | 
						|
 | 
						|
        Normally only used internally.
 | 
						|
        """
 | 
						|
        output = []
 | 
						|
        x = self.low
 | 
						|
        for i in xrange(N):
 | 
						|
            output.append(x)
 | 
						|
            x += (self.high - self.low)/(N-1.)
 | 
						|
        return output
 | 
						|
 | 
						|
    def compute_miniticks(self, original_ticks):
 | 
						|
        """Return optimal linear miniticks, given a set of ticks.
 | 
						|
 | 
						|
        Normally only used internally.
 | 
						|
        """
 | 
						|
        if len(original_ticks) < 2:
 | 
						|
            original_ticks = ticks(self.low, self.high) # XXX ticks is undefined!
 | 
						|
        original_ticks = original_ticks.keys()
 | 
						|
        original_ticks.sort()
 | 
						|
 | 
						|
        if self.low > original_ticks[0] + _epsilon or self.high < original_ticks[-1] - _epsilon:
 | 
						|
            raise ValueError, "original_ticks {%g...%g} extend beyond [%g, %g]" % (original_ticks[0], original_ticks[-1], self.low, self.high)
 | 
						|
 | 
						|
        granularities = []
 | 
						|
        for i in range(len(original_ticks)-1):
 | 
						|
            granularities.append(original_ticks[i+1] - original_ticks[i])
 | 
						|
        spacing = 10**(math.ceil(math.log10(min(granularities)) - 1))
 | 
						|
 | 
						|
        output = []
 | 
						|
        x = original_ticks[0] - math.ceil(1.*(original_ticks[0] - self.low) / spacing) * spacing
 | 
						|
 | 
						|
        while x <= self.high:
 | 
						|
            if x >= self.low:
 | 
						|
                already_in_ticks = False
 | 
						|
                for t in original_ticks:
 | 
						|
                    if abs(x-t) < _epsilon * (self.high - self.low):
 | 
						|
                        already_in_ticks = True
 | 
						|
                if not already_in_ticks:
 | 
						|
                    output.append(x)
 | 
						|
            x += spacing
 | 
						|
        return output
 | 
						|
 | 
						|
    def compute_logticks(self, base, N, format):
 | 
						|
        """Return less than -N or exactly N optimal logarithmic ticks.
 | 
						|
 | 
						|
        Normally only used internally.
 | 
						|
        """
 | 
						|
        if self.low >= self.high:
 | 
						|
            raise ValueError, "low must be less than high"
 | 
						|
        if N == 1:
 | 
						|
            raise ValueError, "N can be 0 or >1 to specify the exact number of ticks or negative to specify a maximum"
 | 
						|
 | 
						|
        eps = _epsilon * (self.high - self.low)
 | 
						|
 | 
						|
        if N >= 0:
 | 
						|
            output = {}
 | 
						|
            x = self.low
 | 
						|
            for i in xrange(N):
 | 
						|
                if format == unumber and abs(x) < eps:
 | 
						|
                    label = u"0"
 | 
						|
                else:
 | 
						|
                    label = format(x)
 | 
						|
                output[x] = label
 | 
						|
                x += (self.high - self.low)/(N-1.)
 | 
						|
            return output
 | 
						|
 | 
						|
        N = -N
 | 
						|
 | 
						|
        lowN = math.floor(math.log(self.low, base))
 | 
						|
        highN = math.ceil(math.log(self.high, base))
 | 
						|
        output = {}
 | 
						|
        for n in range(int(lowN), int(highN)+1):
 | 
						|
            x = base**n
 | 
						|
            label = format(x)
 | 
						|
            if self.low <= x <= self.high:
 | 
						|
                output[x] = label
 | 
						|
 | 
						|
        for i in range(1, len(output)):
 | 
						|
            keys = output.keys()
 | 
						|
            keys.sort()
 | 
						|
            keys = keys[::i]
 | 
						|
            values = map(lambda k: output[k], keys)
 | 
						|
            if len(values) <= N:
 | 
						|
                for k in output.keys():
 | 
						|
                    if k not in keys:
 | 
						|
                        output[k] = ""
 | 
						|
                break
 | 
						|
 | 
						|
        if len(output) <= 2:
 | 
						|
            output2 = self.compute_ticks(N=-int(math.ceil(N/2.)), format=format)
 | 
						|
            lowest = min(output2)
 | 
						|
 | 
						|
            for k in output:
 | 
						|
                if k < lowest:
 | 
						|
                    output2[k] = output[k]
 | 
						|
            output = output2
 | 
						|
 | 
						|
        return output
 | 
						|
 | 
						|
    def compute_logminiticks(self, base):
 | 
						|
        """Return optimal logarithmic miniticks, given a set of ticks.
 | 
						|
 | 
						|
        Normally only used internally.
 | 
						|
        """
 | 
						|
        if self.low >= self.high:
 | 
						|
            raise ValueError, "low must be less than high"
 | 
						|
 | 
						|
        lowN = math.floor(math.log(self.low, base))
 | 
						|
        highN = math.ceil(math.log(self.high, base))
 | 
						|
        output = []
 | 
						|
        num_ticks = 0
 | 
						|
        for n in range(int(lowN), int(highN)+1):
 | 
						|
            x = base**n
 | 
						|
            if self.low <= x <= self.high:
 | 
						|
                num_ticks += 1
 | 
						|
            for m in range(2, int(math.ceil(base))):
 | 
						|
                minix = m * x
 | 
						|
                if self.low <= minix <= self.high:
 | 
						|
                    output.append(minix)
 | 
						|
 | 
						|
        if num_ticks <= 2:
 | 
						|
            return []
 | 
						|
        else:
 | 
						|
            return output
 | 
						|
 | 
						|
######################################################################
 | 
						|
 | 
						|
class CurveAxis(Curve, Ticks):
 | 
						|
    """Draw an axis with tick marks along a parametric curve.
 | 
						|
 | 
						|
    CurveAxis(f, low, high, ticks, miniticks, labels, logbase, arrow_start, arrow_end,
 | 
						|
    text_attr, attribute=value)
 | 
						|
 | 
						|
    f                      required         a Python callable or string in
 | 
						|
                                            the form "f(t), g(t)", just like Curve
 | 
						|
    low, high              required         left and right endpoints
 | 
						|
    ticks                  default=-10      request ticks according to the standard
 | 
						|
                                            tick specification (see help(Ticks))
 | 
						|
    miniticks              default=True     request miniticks according to the
 | 
						|
                                            standard minitick specification
 | 
						|
    labels                 True             request tick labels according to the
 | 
						|
                                            standard tick label specification
 | 
						|
    logbase                default=None     if a number, the x axis is logarithmic
 | 
						|
                                            with ticks at the given base (10 being
 | 
						|
                                            the most common)
 | 
						|
    arrow_start            default=None     if a new string identifier, draw an
 | 
						|
                                            arrow at the low-end of the axis,
 | 
						|
                                            referenced by that identifier; if an
 | 
						|
                                            SVG marker object, use that marker
 | 
						|
    arrow_end              default=None     if a new string identifier, draw an
 | 
						|
                                            arrow at the high-end of the axis,
 | 
						|
                                            referenced by that identifier; if an
 | 
						|
                                            SVG marker object, use that marker
 | 
						|
    text_attr              default={}       SVG attributes for the text labels
 | 
						|
    attribute=value pairs  keyword list     SVG attributes
 | 
						|
    """
 | 
						|
    defaults = {"stroke-width": "0.25pt", }
 | 
						|
    text_defaults = {"stroke": "none", "fill": "black", "font-size": 5, }
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<CurveAxis %s [%s, %s] ticks=%s labels=%s %s>" % (
 | 
						|
               self.f, self.low, self.high, str(self.ticks), str(self.labels), self.attr)
 | 
						|
 | 
						|
    def __init__(self, f, low, high, ticks=-10, miniticks=True, labels=True, logbase=None,
 | 
						|
                 arrow_start=None, arrow_end=None, text_attr={}, **attr):
 | 
						|
        tattr = dict(self.text_defaults)
 | 
						|
        tattr.update(text_attr)
 | 
						|
        Curve.__init__(self, f, low, high)
 | 
						|
        Ticks.__init__(self, f, low, high, ticks, miniticks, labels, logbase, arrow_start, arrow_end, tattr, **attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        func = Curve.SVG(self, trans)
 | 
						|
        ticks = Ticks.SVG(self, trans) # returns a <g />
 | 
						|
 | 
						|
        if self.arrow_start != False and self.arrow_start is not None:
 | 
						|
            if isinstance(self.arrow_start, basestring):
 | 
						|
                func.attr["marker-start"] = "url(#%s)" % self.arrow_start
 | 
						|
            else:
 | 
						|
                func.attr["marker-start"] = "url(#%s)" % self.arrow_start.id
 | 
						|
 | 
						|
        if self.arrow_end != False and self.arrow_end is not None:
 | 
						|
            if isinstance(self.arrow_end, basestring):
 | 
						|
                func.attr["marker-end"] = "url(#%s)" % self.arrow_end
 | 
						|
            else:
 | 
						|
                func.attr["marker-end"] = "url(#%s)" % self.arrow_end.id
 | 
						|
 | 
						|
        ticks.append(func)
 | 
						|
        return ticks
 | 
						|
 | 
						|
 | 
						|
class LineAxis(Line, Ticks):
 | 
						|
    """Draws an axis with tick marks along a line.
 | 
						|
 | 
						|
    LineAxis(x1, y1, x2, y2, start, end, ticks, miniticks, labels, logbase,
 | 
						|
    arrow_start, arrow_end, text_attr, attribute=value)
 | 
						|
 | 
						|
    x1, y1                  required        starting point
 | 
						|
    x2, y2                  required        ending point
 | 
						|
    start, end              default=0, 1    values to start and end labeling
 | 
						|
    ticks                   default=-10     request ticks according to the standard
 | 
						|
                                            tick specification (see help(Ticks))
 | 
						|
    miniticks               default=True    request miniticks according to the
 | 
						|
                                            standard minitick specification
 | 
						|
    labels                  True            request tick labels according to the
 | 
						|
                                            standard tick label specification
 | 
						|
    logbase                 default=None    if a number, the x axis is logarithmic
 | 
						|
                                            with ticks at the given base (usually 10)
 | 
						|
    arrow_start             default=None    if a new string identifier, draw an arrow
 | 
						|
                                            at the low-end of the axis, referenced by
 | 
						|
                                            that identifier; if an SVG marker object,
 | 
						|
                                            use that marker
 | 
						|
    arrow_end               default=None    if a new string identifier, draw an arrow
 | 
						|
                                            at the high-end of the axis, referenced by
 | 
						|
                                            that identifier; if an SVG marker object,
 | 
						|
                                            use that marker
 | 
						|
    text_attr               default={}      SVG attributes for the text labels
 | 
						|
    attribute=value pairs   keyword list    SVG attributes
 | 
						|
    """
 | 
						|
    defaults = {"stroke-width": "0.25pt", }
 | 
						|
    text_defaults = {"stroke": "none", "fill": "black", "font-size": 5, }
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<LineAxis (%g, %g) to (%g, %g) ticks=%s labels=%s %s>" % (
 | 
						|
               self.x1, self.y1, self.x2, self.y2, str(self.ticks), str(self.labels), self.attr)
 | 
						|
 | 
						|
    def __init__(self, x1, y1, x2, y2, start=0., end=1., ticks=-10, miniticks=True, labels=True,
 | 
						|
                 logbase=None, arrow_start=None, arrow_end=None, exclude=None, text_attr={}, **attr):
 | 
						|
        self.start = start
 | 
						|
        self.end = end
 | 
						|
        self.exclude = exclude
 | 
						|
        tattr = dict(self.text_defaults)
 | 
						|
        tattr.update(text_attr)
 | 
						|
        Line.__init__(self, x1, y1, x2, y2, **attr)
 | 
						|
        Ticks.__init__(self, None, None, None, ticks, miniticks, labels, logbase, arrow_start, arrow_end, tattr, **attr)
 | 
						|
 | 
						|
    def interpret(self):
 | 
						|
        if self.exclude is not None and not (isinstance(self.exclude, (tuple, list)) and len(self.exclude) == 2 and
 | 
						|
                                             isinstance(self.exclude[0], (int, long, float)) and isinstance(self.exclude[1], (int, long, float))):
 | 
						|
            raise TypeError, "exclude must either be None or (low, high)"
 | 
						|
 | 
						|
        ticks, miniticks = Ticks.interpret(self)
 | 
						|
        if self.exclude is None:
 | 
						|
            return ticks, miniticks
 | 
						|
 | 
						|
        ticks2 = {}
 | 
						|
        for loc, label in ticks.items():
 | 
						|
            if self.exclude[0] <= loc <= self.exclude[1]:
 | 
						|
                ticks2[loc] = ""
 | 
						|
            else:
 | 
						|
                ticks2[loc] = label
 | 
						|
 | 
						|
        return ticks2, miniticks
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        line = Line.SVG(self, trans) # must be evaluated first, to set self.f, self.low, self.high
 | 
						|
 | 
						|
        f01 = self.f
 | 
						|
        self.f = lambda t: f01(1. * (t - self.start) / (self.end - self.start))
 | 
						|
        self.low = self.start
 | 
						|
        self.high = self.end
 | 
						|
 | 
						|
        if self.arrow_start != False and self.arrow_start is not None:
 | 
						|
            if isinstance(self.arrow_start, basestring):
 | 
						|
                line.attr["marker-start"] = "url(#%s)" % self.arrow_start
 | 
						|
            else:
 | 
						|
                line.attr["marker-start"] = "url(#%s)" % self.arrow_start.id
 | 
						|
 | 
						|
        if self.arrow_end != False and self.arrow_end is not None:
 | 
						|
            if isinstance(self.arrow_end, basestring):
 | 
						|
                line.attr["marker-end"] = "url(#%s)" % self.arrow_end
 | 
						|
            else:
 | 
						|
                line.attr["marker-end"] = "url(#%s)" % self.arrow_end.id
 | 
						|
 | 
						|
        ticks = Ticks.SVG(self, trans) # returns a <g />
 | 
						|
        ticks.append(line)
 | 
						|
        return ticks
 | 
						|
 | 
						|
 | 
						|
class XAxis(LineAxis):
 | 
						|
    """Draws an x axis with tick marks.
 | 
						|
 | 
						|
    XAxis(xmin, xmax, aty, ticks, miniticks, labels, logbase, arrow_start, arrow_end,
 | 
						|
    exclude, text_attr, attribute=value)
 | 
						|
 | 
						|
    xmin, xmax              required        the x range
 | 
						|
    aty                     default=0       y position to draw the axis
 | 
						|
    ticks                   default=-10     request ticks according to the standard
 | 
						|
                                            tick specification (see help(Ticks))
 | 
						|
    miniticks               default=True    request miniticks according to the
 | 
						|
                                            standard minitick specification
 | 
						|
    labels                  True            request tick labels according to the
 | 
						|
                                            standard tick label specification
 | 
						|
    logbase                 default=None    if a number, the x axis is logarithmic
 | 
						|
                                            with ticks at the given base (usually 10)
 | 
						|
    arrow_start             default=None    if a new string identifier, draw an arrow
 | 
						|
                                            at the low-end of the axis, referenced by
 | 
						|
                                            that identifier; if an SVG marker object,
 | 
						|
                                            use that marker
 | 
						|
    arrow_end               default=None    if a new string identifier, draw an arrow
 | 
						|
                                            at the high-end of the axis, referenced by
 | 
						|
                                            that identifier; if an SVG marker object,
 | 
						|
                                            use that marker
 | 
						|
    exclude                 default=None    if a (low, high) pair, don't draw text
 | 
						|
                                            labels within this range
 | 
						|
    text_attr               default={}      SVG attributes for the text labels
 | 
						|
    attribute=value pairs   keyword list    SVG attributes for all lines
 | 
						|
 | 
						|
    The exclude option is provided for Axes to keep text from overlapping
 | 
						|
    where the axes cross. Normal users are not likely to need it.
 | 
						|
    """
 | 
						|
    defaults = {"stroke-width": "0.25pt", }
 | 
						|
    text_defaults = {"stroke": "none", "fill": "black", "font-size": 5, "dominant-baseline": "text-before-edge", }
 | 
						|
    text_start = -1.
 | 
						|
    text_angle = 0.
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<XAxis (%g, %g) at y=%g ticks=%s labels=%s %s>" % (
 | 
						|
               self.xmin, self.xmax, self.aty, str(self.ticks), str(self.labels), self.attr) # XXX self.xmin/xmax undefd!
 | 
						|
 | 
						|
    def __init__(self, xmin, xmax, aty=0, ticks=-10, miniticks=True, labels=True, logbase=None,
 | 
						|
                 arrow_start=None, arrow_end=None, exclude=None, text_attr={}, **attr):
 | 
						|
        self.aty = aty
 | 
						|
        tattr = dict(self.text_defaults)
 | 
						|
        tattr.update(text_attr)
 | 
						|
        LineAxis.__init__(self, xmin, aty, xmax, aty, xmin, xmax, ticks, miniticks, labels, logbase, arrow_start, arrow_end, exclude, tattr, **attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        self.y1 = self.aty
 | 
						|
        self.y2 = self.aty
 | 
						|
        return LineAxis.SVG(self, trans)
 | 
						|
 | 
						|
 | 
						|
class YAxis(LineAxis):
 | 
						|
    """Draws a y axis with tick marks.
 | 
						|
 | 
						|
    YAxis(ymin, ymax, atx, ticks, miniticks, labels, logbase, arrow_start, arrow_end,
 | 
						|
    exclude, text_attr, attribute=value)
 | 
						|
 | 
						|
    ymin, ymax              required        the y range
 | 
						|
    atx                     default=0       x position to draw the axis
 | 
						|
    ticks                   default=-10     request ticks according to the standard
 | 
						|
                                            tick specification (see help(Ticks))
 | 
						|
    miniticks               default=True    request miniticks according to the
 | 
						|
                                            standard minitick specification
 | 
						|
    labels                  True            request tick labels according to the
 | 
						|
                                            standard tick label specification
 | 
						|
    logbase                 default=None    if a number, the y axis is logarithmic
 | 
						|
                                            with ticks at the given base (usually 10)
 | 
						|
    arrow_start             default=None    if a new string identifier, draw an arrow
 | 
						|
                                            at the low-end of the axis, referenced by
 | 
						|
                                            that identifier; if an SVG marker object,
 | 
						|
                                            use that marker
 | 
						|
    arrow_end               default=None    if a new string identifier, draw an arrow
 | 
						|
                                            at the high-end of the axis, referenced by
 | 
						|
                                            that identifier; if an SVG marker object,
 | 
						|
                                            use that marker
 | 
						|
    exclude                 default=None    if a (low, high) pair, don't draw text
 | 
						|
                                            labels within this range
 | 
						|
    text_attr               default={}      SVG attributes for the text labels
 | 
						|
    attribute=value pairs   keyword list    SVG attributes for all lines
 | 
						|
 | 
						|
    The exclude option is provided for Axes to keep text from overlapping
 | 
						|
    where the axes cross. Normal users are not likely to need it.
 | 
						|
    """
 | 
						|
    defaults = {"stroke-width": "0.25pt", }
 | 
						|
    text_defaults = {"stroke": "none", "fill": "black", "font-size": 5, "text-anchor": "end", "dominant-baseline": "middle", }
 | 
						|
    text_start = 2.5
 | 
						|
    text_angle = 90.
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<YAxis (%g, %g) at x=%g ticks=%s labels=%s %s>" % (
 | 
						|
               self.ymin, self.ymax, self.atx, str(self.ticks), str(self.labels), self.attr) # XXX self.ymin/ymax undefd!
 | 
						|
 | 
						|
    def __init__(self, ymin, ymax, atx=0, ticks=-10, miniticks=True, labels=True, logbase=None,
 | 
						|
                 arrow_start=None, arrow_end=None, exclude=None, text_attr={}, **attr):
 | 
						|
        self.atx = atx
 | 
						|
        tattr = dict(self.text_defaults)
 | 
						|
        tattr.update(text_attr)
 | 
						|
        LineAxis.__init__(self, atx, ymin, atx, ymax, ymin, ymax, ticks, miniticks, labels, logbase, arrow_start, arrow_end, exclude, tattr, **attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        self.x1 = self.atx
 | 
						|
        self.x2 = self.atx
 | 
						|
        return LineAxis.SVG(self, trans)
 | 
						|
 | 
						|
 | 
						|
class Axes:
 | 
						|
    """Draw a pair of intersecting x-y axes.
 | 
						|
 | 
						|
    Axes(xmin, xmax, ymin, ymax, atx, aty, xticks, xminiticks, xlabels, xlogbase,
 | 
						|
    yticks, yminiticks, ylabels, ylogbase, arrows, text_attr, attribute=value)
 | 
						|
 | 
						|
    xmin, xmax               required       the x range
 | 
						|
    ymin, ymax               required       the y range
 | 
						|
    atx, aty                 default=0, 0   point where the axes try to cross;
 | 
						|
                                            if outside the range, the axes will
 | 
						|
                                            cross at the closest corner
 | 
						|
    xticks                   default=-10    request ticks according to the standard
 | 
						|
                                            tick specification (see help(Ticks))
 | 
						|
    xminiticks               default=True   request miniticks according to the
 | 
						|
                                            standard minitick specification
 | 
						|
    xlabels                  True           request tick labels according to the
 | 
						|
                                            standard tick label specification
 | 
						|
    xlogbase                 default=None   if a number, the x axis is logarithmic
 | 
						|
                                            with ticks at the given base (usually 10)
 | 
						|
    yticks                   default=-10    request ticks according to the standard
 | 
						|
                                            tick specification
 | 
						|
    yminiticks               default=True   request miniticks according to the
 | 
						|
                                            standard minitick specification
 | 
						|
    ylabels                  True           request tick labels according to the
 | 
						|
                                            standard tick label specification
 | 
						|
    ylogbase                 default=None   if a number, the y axis is logarithmic
 | 
						|
                                            with ticks at the given base (usually 10)
 | 
						|
    arrows                   default=None   if a new string identifier, draw arrows
 | 
						|
                                            referenced by that identifier
 | 
						|
    text_attr                default={}     SVG attributes for the text labels
 | 
						|
    attribute=value pairs    keyword list   SVG attributes for all lines
 | 
						|
    """
 | 
						|
    defaults = {"stroke-width": "0.25pt", }
 | 
						|
    text_defaults = {"stroke": "none", "fill": "black", "font-size": 5, }
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<Axes x=(%g, %g) y=(%g, %g) at (%g, %g) %s>" % (
 | 
						|
               self.xmin, self.xmax, self.ymin, self.ymax, self.atx, self.aty, self.attr)
 | 
						|
 | 
						|
    def __init__(self, xmin, xmax, ymin, ymax, atx=0, aty=0,
 | 
						|
                 xticks=-10, xminiticks=True, xlabels=True, xlogbase=None,
 | 
						|
                 yticks=-10, yminiticks=True, ylabels=True, ylogbase=None,
 | 
						|
                 arrows=None, text_attr={}, **attr):
 | 
						|
        self.xmin, self.xmax = xmin, xmax
 | 
						|
        self.ymin, self.ymax = ymin, ymax
 | 
						|
        self.atx, self.aty = atx, aty
 | 
						|
        self.xticks, self.xminiticks, self.xlabels, self.xlogbase = xticks, xminiticks, xlabels, xlogbase
 | 
						|
        self.yticks, self.yminiticks, self.ylabels, self.ylogbase = yticks, yminiticks, ylabels, ylogbase
 | 
						|
        self.arrows = arrows
 | 
						|
 | 
						|
        self.text_attr = dict(self.text_defaults)
 | 
						|
        self.text_attr.update(text_attr)
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        atx, aty = self.atx, self.aty
 | 
						|
        if atx < self.xmin:
 | 
						|
            atx = self.xmin
 | 
						|
        if atx > self.xmax:
 | 
						|
            atx = self.xmax
 | 
						|
        if aty < self.ymin:
 | 
						|
            aty = self.ymin
 | 
						|
        if aty > self.ymax:
 | 
						|
            aty = self.ymax
 | 
						|
 | 
						|
        xmargin = 0.1 * abs(self.ymin - self.ymax)
 | 
						|
        xexclude = atx - xmargin, atx + xmargin
 | 
						|
 | 
						|
        ymargin = 0.1 * abs(self.xmin - self.xmax)
 | 
						|
        yexclude = aty - ymargin, aty + ymargin
 | 
						|
 | 
						|
        if self.arrows is not None and self.arrows != False:
 | 
						|
            xarrow_start = self.arrows + ".xstart"
 | 
						|
            xarrow_end = self.arrows + ".xend"
 | 
						|
            yarrow_start = self.arrows + ".ystart"
 | 
						|
            yarrow_end = self.arrows + ".yend"
 | 
						|
        else:
 | 
						|
            xarrow_start = xarrow_end = yarrow_start = yarrow_end = None
 | 
						|
 | 
						|
        xaxis = XAxis(self.xmin, self.xmax, aty, self.xticks, self.xminiticks, self.xlabels, self.xlogbase, xarrow_start, xarrow_end, exclude=xexclude, text_attr=self.text_attr, **self.attr).SVG(trans)
 | 
						|
        yaxis = YAxis(self.ymin, self.ymax, atx, self.yticks, self.yminiticks, self.ylabels, self.ylogbase, yarrow_start, yarrow_end, exclude=yexclude, text_attr=self.text_attr, **self.attr).SVG(trans)
 | 
						|
        return SVG("g", *(xaxis.sub + yaxis.sub))
 | 
						|
 | 
						|
######################################################################
 | 
						|
 | 
						|
class HGrid(Ticks):
 | 
						|
    """Draws the horizontal lines of a grid over a specified region
 | 
						|
    using the standard tick specification (see help(Ticks)) to place the
 | 
						|
    grid lines.
 | 
						|
 | 
						|
    HGrid(xmin, xmax, low, high, ticks, miniticks, logbase, mini_attr, attribute=value)
 | 
						|
 | 
						|
    xmin, xmax              required        the x range
 | 
						|
    low, high               required        the y range
 | 
						|
    ticks                   default=-10     request ticks according to the standard
 | 
						|
                                            tick specification (see help(Ticks))
 | 
						|
    miniticks               default=False   request miniticks according to the
 | 
						|
                                            standard minitick specification
 | 
						|
    logbase                 default=None    if a number, the axis is logarithmic
 | 
						|
                                            with ticks at the given base (usually 10)
 | 
						|
    mini_attr               default={}      SVG attributes for the minitick-lines
 | 
						|
                                            (if miniticks != False)
 | 
						|
    attribute=value pairs   keyword list    SVG attributes for the major tick lines
 | 
						|
    """
 | 
						|
    defaults = {"stroke-width": "0.25pt", "stroke": "gray", }
 | 
						|
    mini_defaults = {"stroke-width": "0.25pt", "stroke": "lightgray", "stroke-dasharray": "1,1", }
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<HGrid x=(%g, %g) %g <= y <= %g ticks=%s miniticks=%s %s>" % (
 | 
						|
               self.xmin, self.xmax, self.low, self.high, str(self.ticks), str(self.miniticks), self.attr)
 | 
						|
 | 
						|
    def __init__(self, xmin, xmax, low, high, ticks=-10, miniticks=False, logbase=None, mini_attr={}, **attr):
 | 
						|
        self.xmin, self.xmax = xmin, xmax
 | 
						|
 | 
						|
        self.mini_attr = dict(self.mini_defaults)
 | 
						|
        self.mini_attr.update(mini_attr)
 | 
						|
 | 
						|
        Ticks.__init__(self, None, low, high, ticks, miniticks, None, logbase)
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        self.last_ticks, self.last_miniticks = Ticks.interpret(self)
 | 
						|
 | 
						|
        ticksd = []
 | 
						|
        for t in self.last_ticks.keys():
 | 
						|
            ticksd += Line(self.xmin, t, self.xmax, t).Path(trans).d
 | 
						|
 | 
						|
        miniticksd = []
 | 
						|
        for t in self.last_miniticks:
 | 
						|
            miniticksd += Line(self.xmin, t, self.xmax, t).Path(trans).d
 | 
						|
 | 
						|
        return SVG("g", Path(d=ticksd, **self.attr).SVG(), Path(d=miniticksd, **self.mini_attr).SVG())
 | 
						|
 | 
						|
 | 
						|
class VGrid(Ticks):
 | 
						|
    """Draws the vertical lines of a grid over a specified region
 | 
						|
    using the standard tick specification (see help(Ticks)) to place the
 | 
						|
    grid lines.
 | 
						|
 | 
						|
    HGrid(ymin, ymax, low, high, ticks, miniticks, logbase, mini_attr, attribute=value)
 | 
						|
 | 
						|
    ymin, ymax              required        the y range
 | 
						|
    low, high               required        the x range
 | 
						|
    ticks                   default=-10     request ticks according to the standard
 | 
						|
                                            tick specification (see help(Ticks))
 | 
						|
    miniticks               default=False   request miniticks according to the
 | 
						|
                                            standard minitick specification
 | 
						|
    logbase                 default=None    if a number, the axis is logarithmic
 | 
						|
                                            with ticks at the given base (usually 10)
 | 
						|
    mini_attr               default={}      SVG attributes for the minitick-lines
 | 
						|
                                            (if miniticks != False)
 | 
						|
    attribute=value pairs   keyword list    SVG attributes for the major tick lines
 | 
						|
    """
 | 
						|
    defaults = {"stroke-width": "0.25pt", "stroke": "gray", }
 | 
						|
    mini_defaults = {"stroke-width": "0.25pt", "stroke": "lightgray", "stroke-dasharray": "1,1", }
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<VGrid y=(%g, %g) %g <= x <= %g ticks=%s miniticks=%s %s>" % (
 | 
						|
               self.ymin, self.ymax, self.low, self.high, str(self.ticks), str(self.miniticks), self.attr)
 | 
						|
 | 
						|
    def __init__(self, ymin, ymax, low, high, ticks=-10, miniticks=False, logbase=None, mini_attr={}, **attr):
 | 
						|
        self.ymin, self.ymax = ymin, ymax
 | 
						|
 | 
						|
        self.mini_attr = dict(self.mini_defaults)
 | 
						|
        self.mini_attr.update(mini_attr)
 | 
						|
 | 
						|
        Ticks.__init__(self, None, low, high, ticks, miniticks, None, logbase)
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        self.last_ticks, self.last_miniticks = Ticks.interpret(self)
 | 
						|
 | 
						|
        ticksd = []
 | 
						|
        for t in self.last_ticks.keys():
 | 
						|
            ticksd += Line(t, self.ymin, t, self.ymax).Path(trans).d
 | 
						|
 | 
						|
        miniticksd = []
 | 
						|
        for t in self.last_miniticks:
 | 
						|
            miniticksd += Line(t, self.ymin, t, self.ymax).Path(trans).d
 | 
						|
 | 
						|
        return SVG("g", Path(d=ticksd, **self.attr).SVG(), Path(d=miniticksd, **self.mini_attr).SVG())
 | 
						|
 | 
						|
 | 
						|
class Grid(Ticks):
 | 
						|
    """Draws a grid over a specified region using the standard tick
 | 
						|
    specification (see help(Ticks)) to place the grid lines.
 | 
						|
 | 
						|
    Grid(xmin, xmax, ymin, ymax, ticks, miniticks, logbase, mini_attr, attribute=value)
 | 
						|
 | 
						|
    xmin, xmax              required        the x range
 | 
						|
    ymin, ymax              required        the y range
 | 
						|
    ticks                   default=-10     request ticks according to the standard
 | 
						|
                                            tick specification (see help(Ticks))
 | 
						|
    miniticks               default=False   request miniticks according to the
 | 
						|
                                            standard minitick specification
 | 
						|
    logbase                 default=None    if a number, the axis is logarithmic
 | 
						|
                                            with ticks at the given base (usually 10)
 | 
						|
    mini_attr               default={}      SVG attributes for the minitick-lines
 | 
						|
                                            (if miniticks != False)
 | 
						|
    attribute=value pairs   keyword list    SVG attributes for the major tick lines
 | 
						|
    """
 | 
						|
    defaults = {"stroke-width": "0.25pt", "stroke": "gray", }
 | 
						|
    mini_defaults = {"stroke-width": "0.25pt", "stroke": "lightgray", "stroke-dasharray": "1,1", }
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<Grid x=(%g, %g) y=(%g, %g) ticks=%s miniticks=%s %s>" % (
 | 
						|
               self.xmin, self.xmax, self.ymin, self.ymax, str(self.ticks), str(self.miniticks), self.attr)
 | 
						|
 | 
						|
    def __init__(self, xmin, xmax, ymin, ymax, ticks=-10, miniticks=False, logbase=None, mini_attr={}, **attr):
 | 
						|
        self.xmin, self.xmax = xmin, xmax
 | 
						|
        self.ymin, self.ymax = ymin, ymax
 | 
						|
 | 
						|
        self.mini_attr = dict(self.mini_defaults)
 | 
						|
        self.mini_attr.update(mini_attr)
 | 
						|
 | 
						|
        Ticks.__init__(self, None, None, None, ticks, miniticks, None, logbase)
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        self.low, self.high = self.xmin, self.xmax
 | 
						|
        self.last_xticks, self.last_xminiticks = Ticks.interpret(self)
 | 
						|
        self.low, self.high = self.ymin, self.ymax
 | 
						|
        self.last_yticks, self.last_yminiticks = Ticks.interpret(self)
 | 
						|
 | 
						|
        ticksd = []
 | 
						|
        for t in self.last_xticks.keys():
 | 
						|
            ticksd += Line(t, self.ymin, t, self.ymax).Path(trans).d
 | 
						|
        for t in self.last_yticks.keys():
 | 
						|
            ticksd += Line(self.xmin, t, self.xmax, t).Path(trans).d
 | 
						|
 | 
						|
        miniticksd = []
 | 
						|
        for t in self.last_xminiticks:
 | 
						|
            miniticksd += Line(t, self.ymin, t, self.ymax).Path(trans).d
 | 
						|
        for t in self.last_yminiticks:
 | 
						|
            miniticksd += Line(self.xmin, t, self.xmax, t).Path(trans).d
 | 
						|
 | 
						|
        return SVG("g", Path(d=ticksd, **self.attr).SVG(), Path(d=miniticksd, **self.mini_attr).SVG())
 | 
						|
 | 
						|
######################################################################
 | 
						|
 | 
						|
class XErrorBars:
 | 
						|
    """Draws x error bars at a set of points. This is usually used
 | 
						|
    before (under) a set of Dots at the same points.
 | 
						|
 | 
						|
    XErrorBars(d, attribute=value)
 | 
						|
 | 
						|
    d                       required        list of (x,y,xerr...) points
 | 
						|
    attribute=value pairs   keyword list    SVG attributes
 | 
						|
 | 
						|
    If points in d have
 | 
						|
 | 
						|
        * 3 elements, the third is the symmetric error bar
 | 
						|
        * 4 elements, the third and fourth are the asymmetric lower and
 | 
						|
          upper error bar. The third element should be negative,
 | 
						|
          e.g. (5, 5, -1, 2) is a bar from 4 to 7.
 | 
						|
        * more than 4, a tick mark is placed at each value. This lets
 | 
						|
          you nest errors from different sources, correlated and
 | 
						|
          uncorrelated, statistical and systematic, etc.
 | 
						|
    """
 | 
						|
    defaults = {"stroke-width": "0.25pt", }
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<XErrorBars (%d nodes)>" % len(self.d)
 | 
						|
 | 
						|
    def __init__(self, d=[], **attr):
 | 
						|
        self.d = list(d)
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        if isinstance(trans, basestring):
 | 
						|
            trans = totrans(trans) # only once
 | 
						|
 | 
						|
        output = SVG("g")
 | 
						|
        for p in self.d:
 | 
						|
            x, y = p[0], p[1]
 | 
						|
 | 
						|
            if len(p) == 3:
 | 
						|
                bars = [x - p[2], x + p[2]]
 | 
						|
            else:
 | 
						|
                bars = [x + pi for pi in p[2:]]
 | 
						|
 | 
						|
            start, end = min(bars), max(bars)
 | 
						|
            output.append(LineAxis(start, y, end, y, start, end, bars, False, False, **self.attr).SVG(trans))
 | 
						|
 | 
						|
        return output
 | 
						|
 | 
						|
 | 
						|
class YErrorBars:
 | 
						|
    """Draws y error bars at a set of points. This is usually used
 | 
						|
    before (under) a set of Dots at the same points.
 | 
						|
 | 
						|
    YErrorBars(d, attribute=value)
 | 
						|
 | 
						|
    d                       required        list of (x,y,yerr...) points
 | 
						|
    attribute=value pairs   keyword list    SVG attributes
 | 
						|
 | 
						|
    If points in d have
 | 
						|
 | 
						|
        * 3 elements, the third is the symmetric error bar
 | 
						|
        * 4 elements, the third and fourth are the asymmetric lower and
 | 
						|
          upper error bar. The third element should be negative,
 | 
						|
          e.g. (5, 5, -1, 2) is a bar from 4 to 7.
 | 
						|
        * more than 4, a tick mark is placed at each value. This lets
 | 
						|
          you nest errors from different sources, correlated and
 | 
						|
          uncorrelated, statistical and systematic, etc.
 | 
						|
    """
 | 
						|
    defaults = {"stroke-width": "0.25pt", }
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<YErrorBars (%d nodes)>" % len(self.d)
 | 
						|
 | 
						|
    def __init__(self, d=[], **attr):
 | 
						|
        self.d = list(d)
 | 
						|
 | 
						|
        self.attr = dict(self.defaults)
 | 
						|
        self.attr.update(attr)
 | 
						|
 | 
						|
    def SVG(self, trans=None):
 | 
						|
        """Apply the transformation "trans" and return an SVG object."""
 | 
						|
        if isinstance(trans, basestring):
 | 
						|
            trans = totrans(trans) # only once
 | 
						|
 | 
						|
        output = SVG("g")
 | 
						|
        for p in self.d:
 | 
						|
            x, y = p[0], p[1]
 | 
						|
 | 
						|
            if len(p) == 3:
 | 
						|
                bars = [y - p[2], y + p[2]]
 | 
						|
            else:
 | 
						|
                bars = [y + pi for pi in p[2:]]
 | 
						|
 | 
						|
            start, end = min(bars), max(bars)
 | 
						|
            output.append(LineAxis(x, start, x, end, start, end, bars, False, False, **self.attr).SVG(trans))
 | 
						|
 | 
						|
        return output
 | 
						|
 |