3670 lines
		
	
	
		
			148 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			3670 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
 | 
