CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
svgfig.py
Go to the documentation of this file.
1 # svgfig.py copyright (C) 2008 Jim Pivarski <jpivarski@gmail.com>
2 #
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
16 #
17 # Full licence is in the file COPYING and at http://www.gnu.org/copyleft/gpl.html
18 
19 import re, codecs, os, platform, copy, itertools, math, cmath, random, sys, copy
20 _epsilon = 1e-5
21 
22 
23 if re.search("windows", platform.system(), re.I):
24  try:
25  import _winreg
26  _default_directory = _winreg.QueryValueEx(_winreg.OpenKey(_winreg.HKEY_CURRENT_USER, \
27  r"Software\Microsoft\Windows\Current Version\Explorer\Shell Folders"), "Desktop")[0]
28 # tmpdir = _winreg.QueryValueEx(_winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Environment"), "TEMP")[0]
29 # if tmpdir[0:13] != "%USERPROFILE%":
30 # tmpdir = os.path.expanduser("~") + tmpdir[13:]
31  except:
32  _default_directory = os.path.expanduser("~") + os.sep + "Desktop"
33 
34 _default_fileName = "tmp.svg"
35 
36 _hacks = {}
37 _hacks["inkscape-text-vertical-shift"] = False
38 
39 def rgb(r, g, b, maximum=1.):
40  """Create an SVG color string "#xxyyzz" from r, g, and b.
41 
42  r,g,b = 0 is black and r,g,b = maximum is white.
43  """
44  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)))
45 
46 def attr_preprocess(attr):
47  for name in attr.keys():
48  name_colon = re.sub("__", ":", name)
49  if name_colon != name:
50  attr[name_colon] = attr[name]
51  del attr[name]
52  name = name_colon
53 
54  name_dash = re.sub("_", "-", name)
55  if name_dash != name:
56  attr[name_dash] = attr[name]
57  del attr[name]
58  name = name_dash
59 
60  return attr
61 
62 class SVG:
63  """A tree representation of an SVG image or image fragment.
64 
65  SVG(t, sub, sub, sub..., attribute=value)
66 
67  t required SVG type name
68  sub optional list nested SVG elements or text/Unicode
69  attribute=value pairs optional keywords SVG attributes
70 
71  In attribute names, "__" becomes ":" and "_" becomes "-".
72 
73  SVG in XML
74 
75  <g id="mygroup" fill="blue">
76  <rect x="1" y="1" width="2" height="2" />
77  <rect x="3" y="3" width="2" height="2" />
78  </g>
79 
80  SVG in Python
81 
82  >>> svg = SVG("g", SVG("rect", x=1, y=1, width=2, height=2), \
83  ... SVG("rect", x=3, y=3, width=2, height=2), \
84  ... id="mygroup", fill="blue")
85 
86  Sub-elements and attributes may be accessed through tree-indexing:
87 
88  >>> svg = SVG("text", SVG("tspan", "hello there"), stroke="none", fill="black")
89  >>> svg[0]
90  <tspan (1 sub) />
91  >>> svg[0, 0]
92  'hello there'
93  >>> svg["fill"]
94  'black'
95 
96  Iteration is depth-first:
97 
98  >>> svg = SVG("g", SVG("g", SVG("line", x1=0, y1=0, x2=1, y2=1)), \
99  ... SVG("text", SVG("tspan", "hello again")))
100  ...
101  >>> for ti, s in svg:
102  ... print ti, repr(s)
103  ...
104  (0,) <g (1 sub) />
105  (0, 0) <line x2=1 y1=0 x1=0 y2=1 />
106  (0, 0, 'x2') 1
107  (0, 0, 'y1') 0
108  (0, 0, 'x1') 0
109  (0, 0, 'y2') 1
110  (1,) <text (1 sub) />
111  (1, 0) <tspan (1 sub) />
112  (1, 0, 0) 'hello again'
113 
114  Use "print" to navigate:
115 
116  >>> print svg
117  None <g (2 sub) />
118  [0] <g (1 sub) />
119  [0, 0] <line x2=1 y1=0 x1=0 y2=1 />
120  [1] <text (1 sub) />
121  [1, 0] <tspan (1 sub) />
122  """
123  def __init__(self, *t_sub, **attr):
124  if len(t_sub) == 0: raise TypeError, "SVG element must have a t (SVG type)"
125 
126  # first argument is t (SVG type)
127  self.t = t_sub[0]
128  # the rest are sub-elements
129  self.sub = list(t_sub[1:])
130 
131  # keyword arguments are attributes
132  # need to preprocess to handle differences between SVG and Python syntax
133  self.attr = attr_preprocess(attr)
134 
135  def __getitem__(self, ti):
136  """Index is a list that descends tree, returning a sub-element if
137  it ends with a number and an attribute if it ends with a string."""
138  obj = self
139  if isinstance(ti, (list, tuple)):
140  for i in ti[:-1]: obj = obj[i]
141  ti = ti[-1]
142 
143  if isinstance(ti, (int, long, slice)): return obj.sub[ti]
144  else: return obj.attr[ti]
145 
146  def __setitem__(self, ti, value):
147  """Index is a list that descends tree, returning a sub-element if
148  it ends with a number and an attribute if it ends with a string."""
149  obj = self
150  if isinstance(ti, (list, tuple)):
151  for i in ti[:-1]: obj = obj[i]
152  ti = ti[-1]
153 
154  if isinstance(ti, (int, long, slice)): obj.sub[ti] = value
155  else: obj.attr[ti] = value
156 
157  def __delitem__(self, ti):
158  """Index is a list that descends tree, returning a sub-element if
159  it ends with a number and an attribute if it ends with a string."""
160  obj = self
161  if isinstance(ti, (list, tuple)):
162  for i in ti[:-1]: obj = obj[i]
163  ti = ti[-1]
164 
165  if isinstance(ti, (int, long, slice)): del obj.sub[ti]
166  else: del obj.attr[ti]
167 
168  def __contains__(self, value):
169  """x in svg == True iff x is an attribute in svg."""
170  return value in self.attr
171 
172  def __eq__(self, other):
173  """x == y iff x represents the same SVG as y."""
174  if id(self) == id(other): return True
175  return isinstance(other, SVG) and self.t == other.t and self.sub == other.sub and self.attr == other.attr
176 
177  def __ne__(self, other):
178  """x != y iff x does not represent the same SVG as y."""
179  return not (self == other)
180 
181  def append(self, x):
182  """Appends x to the list of sub-elements (drawn last, overlaps
183  other primatives)."""
184  self.sub.append(x)
185 
186  def prepend(self, x):
187  """Prepends x to the list of sub-elements (drawn first may be
188  overlapped by other primatives)."""
189  self.sub[0:0] = [x]
190 
191  def extend(self, x):
192  """Extends list of sub-elements by a list x."""
193  self.sub.extend(x)
194 
195  def clone(self, shallow=False):
196  """Deep copy of SVG tree. Set shallow=True for a shallow copy."""
197  if shallow:
198  return copy.copy(self)
199  else:
200  return copy.deepcopy(self)
201 
202  ### nested class
204  """Manages SVG iteration."""
205 
206  def __init__(self, svg, ti, depth_limit):
207  self.svg = svg
208  self.ti = ti
209  self.shown = False
210  self.depth_limit = depth_limit
211 
212  def __iter__(self): return self
213 
214  def next(self):
215  if not self.shown:
216  self.shown = True
217  if self.ti != ():
218  return self.ti, self.svg
219 
220  if not isinstance(self.svg, SVG): raise StopIteration
221  if self.depth_limit != None and len(self.ti) >= self.depth_limit: raise StopIteration
222 
223  if "iterators" not in self.__dict__:
224  self.iterators = []
225  for i, s in enumerate(self.svg.sub):
226  self.iterators.append(self.__class__(s, self.ti + (i,), self.depth_limit))
227  for k, s in self.svg.attr.items():
228  self.iterators.append(self.__class__(s, self.ti + (k,), self.depth_limit))
229  self.iterators = itertools.chain(*self.iterators)
230 
231  return self.iterators.next()
232  ### end nested class
233 
234  def depth_first(self, depth_limit=None):
235  """Returns a depth-first generator over the SVG. If depth_limit
236  is a number, stop recursion at that depth."""
237  return self.SVGDepthIterator(self, (), depth_limit)
238 
239  def breadth_first(self, depth_limit=None):
240  """Not implemented yet. Any ideas on how to do it?
241 
242  Returns a breadth-first generator over the SVG. If depth_limit
243  is a number, stop recursion at that depth."""
244  raise NotImplementedError, "Got an algorithm for breadth-first searching a tree without effectively copying the tree?"
245 
246  def __iter__(self): return self.depth_first()
247 
248  def items(self, sub=True, attr=True, text=True):
249  """Get a recursively-generated list of tree-index, sub-element/attribute pairs.
250 
251  If sub == False, do not show sub-elements.
252  If attr == False, do not show attributes.
253  If text == False, do not show text/Unicode sub-elements.
254  """
255  output = []
256  for ti, s in self:
257  show = False
258  if isinstance(ti[-1], (int, long)):
259  if isinstance(s, basestring): show = text
260  else: show = sub
261  else: show = attr
262 
263  if show: output.append((ti, s))
264  return output
265 
266  def keys(self, sub=True, attr=True, text=True):
267  """Get a recursively-generated list of tree-indexes.
268 
269  If sub == False, do not show sub-elements.
270  If attr == False, do not show attributes.
271  If text == False, do not show text/Unicode sub-elements.
272  """
273  return [ti for ti, s in self.items(sub, attr, text)]
274 
275  def values(self, sub=True, attr=True, text=True):
276  """Get a recursively-generated list of sub-elements and attributes.
277 
278  If sub == False, do not show sub-elements.
279  If attr == False, do not show attributes.
280  If text == False, do not show text/Unicode sub-elements.
281  """
282  return [s for ti, s in self.items(sub, attr, text)]
283 
284  def __repr__(self): return self.xml(depth_limit=0)
285 
286  def __str__(self):
287  """Print (actually, return a string of) the tree in a form useful for browsing."""
288  return self.tree(sub=True, attr=False, text=False)
289 
290  def tree(self, depth_limit=None, sub=True, attr=True, text=True, tree_width=20, obj_width=80):
291  """Print (actually, return a string of) the tree in a form useful for browsing.
292 
293  If depth_limit == a number, stop recursion at that depth.
294  If sub == False, do not show sub-elements.
295  If attr == False, do not show attributes.
296  If text == False, do not show text/Unicode sub-elements.
297  tree_width is the number of characters reserved for printing tree indexes.
298  obj_width is the number of characters reserved for printing sub-elements/attributes.
299  """
300 
301  output = []
302 
303  line = "%s %s" % (("%%-%ds" % tree_width) % repr(None), ("%%-%ds" % obj_width) % (repr(self))[0:obj_width])
304  output.append(line)
305 
306  for ti, s in self.depth_first(depth_limit):
307  show = False
308  if isinstance(ti[-1], (int, long)):
309  if isinstance(s, basestring): show = text
310  else: show = sub
311  else: show = attr
312 
313  if show:
314  line = "%s %s" % (("%%-%ds" % tree_width) % repr(list(ti)), ("%%-%ds" % obj_width) % (" "*len(ti) + repr(s))[0:obj_width])
315  output.append(line)
316 
317  return "\n".join(output)
318 
319  def xml(self, indent=" ", newl="\n", depth_limit=None, depth=0):
320  """Get an XML representation of the SVG.
321 
322  indent string used for indenting
323  newl string used for newlines
324  If depth_limit == a number, stop recursion at that depth.
325  depth starting depth (not useful for users)
326 
327  print svg.xml()
328  """
329 
330  attrstr = []
331  for n, v in self.attr.items():
332  if isinstance(v, dict):
333  v = "; ".join(["%s:%s" % (ni, vi) for ni, vi in v.items()])
334  elif isinstance(v, (list, tuple)):
335  v = ", ".join(v)
336  attrstr.append(" %s=%s" % (n, repr(v)))
337  attrstr = "".join(attrstr)
338 
339  if len(self.sub) == 0: return "%s<%s%s />" % (indent * depth, self.t, attrstr)
340 
341  if depth_limit == None or depth_limit > depth:
342  substr = []
343  for s in self.sub:
344  if isinstance(s, SVG):
345  substr.append(s.xml(indent, newl, depth_limit, depth + 1) + newl)
346  elif isinstance(s, str):
347  substr.append("%s%s%s" % (indent * (depth + 1), s, newl))
348  else:
349  substr.append("%s%s%s" % (indent * (depth + 1), repr(s), newl))
350  substr = "".join(substr)
351 
352  return "%s<%s%s>%s%s%s</%s>" % (indent * depth, self.t, attrstr, newl, substr, indent * depth, self.t)
353 
354  else:
355  return "%s<%s (%d sub)%s />" % (indent * depth, self.t, len(self.sub), attrstr)
356 
357  def standalone_xml(self, indent=" ", newl="\n"):
358  """Get an XML representation of the SVG that can be saved/rendered.
359 
360  indent string used for indenting
361  newl string used for newlines
362  """
363 
364  if self.t == "svg": top = self
365  else: top = canvas(self)
366  return """\
367 <?xml version="1.0" standalone="no"?>
368 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
369 
370 """ + ("".join(top.__standalone_xml(indent, newl))) # end of return statement
371 
372  def __standalone_xml(self, indent, newl):
373  output = [u"<%s" % self.t]
374 
375  for n, v in self.attr.items():
376  if isinstance(v, dict):
377  v = "; ".join(["%s:%s" % (ni, vi) for ni, vi in v.items()])
378  elif isinstance(v, (list, tuple)):
379  v = ", ".join(v)
380  output.append(u" %s=\"%s\"" % (n, v))
381 
382  if len(self.sub) == 0:
383  output.append(u" />%s%s" % (newl, newl))
384  return output
385 
386  elif self.t == "text" or self.t == "tspan" or self.t == "style":
387  output.append(u">")
388 
389  else:
390  output.append(u">%s%s" % (newl, newl))
391 
392  for s in self.sub:
393  if isinstance(s, SVG): output.extend(s.__standalone_xml(indent, newl))
394  else: output.append(unicode(s))
395 
396  if self.t == "tspan": output.append(u"</%s>" % self.t)
397  else: output.append(u"</%s>%s%s" % (self.t, newl, newl))
398 
399  return output
400 
401  def interpret_fileName(self, fileName=None):
402  if fileName == None:
403  fileName = _default_fileName
404  if re.search("windows", platform.system(), re.I) and not os.path.isabs(fileName):
405  fileName = _default_directory + os.sep + fileName
406  return fileName
407 
408  def save(self, fileName=None, encoding="utf-8", compresslevel=None):
409  """Save to a file for viewing. Note that svg.save() overwrites the file named _default_fileName.
410 
411  fileName default=None note that _default_fileName will be overwritten if
412  no fileName is specified. If the extension
413  is ".svgz" or ".gz", the output will be gzipped
414  encoding default="utf-8" file encoding (default is Unicode)
415  compresslevel default=None if a number, the output will be gzipped with that
416  compression level (1-9, 1 being fastest and 9 most
417  thorough)
418  """
419  fileName = self.interpret_fileName(fileName)
420 
421  if compresslevel != None or re.search("\.svgz$", fileName, re.I) or re.search("\.gz$", fileName, re.I):
422  import gzip
423  if compresslevel == None:
424  f = gzip.GzipFile(fileName, "w")
425  else:
426  f = gzip.GzipFile(fileName, "w", compresslevel)
427 
428  f = codecs.EncodedFile(f, "utf-8", encoding)
429  f.write(self.standalone_xml())
430  f.close()
431 
432  else:
433  f = codecs.open(fileName, "w", encoding=encoding)
434  f.write(self.standalone_xml())
435  f.close()
436 
437  def inkview(self, fileName=None, encoding="utf-8"):
438  """View in "inkview", assuming that program is available on your system.
439 
440  fileName default=None note that any file named _default_fileName will be
441  overwritten if no fileName is specified. If the extension
442  is ".svgz" or ".gz", the output will be gzipped
443  encoding default="utf-8" file encoding (default is Unicode)
444  """
445  fileName = self.interpret_fileName(fileName)
446  self.save(fileName, encoding)
447  os.spawnvp(os.P_NOWAIT, "inkview", ("inkview", fileName))
448 
449  def inkscape(self, fileName=None, encoding="utf-8"):
450  """View in "inkscape", assuming that program is available on your system.
451 
452  fileName default=None note that any file named _default_fileName will be
453  overwritten if no fileName is specified. If the extension
454  is ".svgz" or ".gz", the output will be gzipped
455  encoding default="utf-8" file encoding (default is Unicode)
456  """
457  fileName = self.interpret_fileName(fileName)
458  self.save(fileName, encoding)
459  os.spawnvp(os.P_NOWAIT, "inkscape", ("inkscape", fileName))
460 
461  def firefox(self, fileName=None, encoding="utf-8"):
462  """View in "firefox", assuming that program is available on your system.
463 
464  fileName default=None note that any file named _default_fileName will be
465  overwritten if no fileName is specified. If the extension
466  is ".svgz" or ".gz", the output will be gzipped
467  encoding default="utf-8" file encoding (default is Unicode)
468  """
469  fileName = self.interpret_fileName(fileName)
470  self.save(fileName, encoding)
471  os.spawnvp(os.P_NOWAIT, "firefox", ("firefox", fileName))
472 
473 ######################################################################
474 
475 _canvas_defaults = {"width": "400px", "height": "400px", "viewBox": "0 0 100 100", \
476  "xmlns": "http://www.w3.org/2000/svg", "xmlns:xlink": "http://www.w3.org/1999/xlink", "version":"1.1", \
477  "style": {"stroke":"black", "fill":"none", "stroke-width":"0.5pt", "stroke-linejoin":"round", "text-anchor":"middle"}, \
478  "font-family": ["Helvetica", "Arial", "FreeSans", "Sans", "sans", "sans-serif"], \
479  }
480 
481 def canvas(*sub, **attr):
482  """Creates a top-level SVG object, allowing the user to control the
483  image size and aspect ratio.
484 
485  canvas(sub, sub, sub..., attribute=value)
486 
487  sub optional list nested SVG elements or text/Unicode
488  attribute=value pairs optional keywords SVG attributes
489 
490  Default attribute values:
491 
492  width "400px"
493  height "400px"
494  viewBox "0 0 100 100"
495  xmlns "http://www.w3.org/2000/svg"
496  xmlns:xlink "http://www.w3.org/1999/xlink"
497  version "1.1"
498  style "stroke:black; fill:none; stroke-width:0.5pt; stroke-linejoin:round; text-anchor:middle"
499  font-family "Helvetica,Arial,FreeSans?,Sans,sans,sans-serif"
500  """
501  attributes = dict(_canvas_defaults)
502  attributes.update(attr)
503 
504  if sub == None or sub == ():
505  return SVG("svg", **attributes)
506  else:
507  return SVG("svg", *sub, **attributes)
508 
509 def canvas_outline(*sub, **attr):
510  """Same as canvas(), but draws an outline around the drawable area,
511  so that you know how close your image is to the edges."""
512  svg = canvas(*sub, **attr)
513  match = re.match("[, \t]*([0-9e.+\-]+)[, \t]+([0-9e.+\-]+)[, \t]+([0-9e.+\-]+)[, \t]+([0-9e.+\-]+)[, \t]*", svg["viewBox"])
514  if match == None: raise ValueError, "canvas viewBox is incorrectly formatted"
515  x, y, width, height = [float(x) for x in match.groups()]
516  svg.prepend(SVG("rect", x=x, y=y, width=width, height=height, stroke="none", fill="cornsilk"))
517  svg.append(SVG("rect", x=x, y=y, width=width, height=height, stroke="black", fill="none"))
518  return svg
519 
520 def template(fileName, svg, replaceme="REPLACEME"):
521  """Loads an SVG image from a file, replacing instances of
522  <REPLACEME /> with a given svg object.
523 
524  fileName required name of the template SVG
525  svg required SVG object for replacement
526  replaceme default="REPLACEME" fake SVG element to be replaced by the given object
527 
528  >>> print load("template.svg")
529  None <svg (2 sub) style=u'stroke:black; fill:none; stroke-width:0.5pt; stroke-linejoi
530  [0] <rect height=u'100' width=u'100' stroke=u'none' y=u'0' x=u'0' fill=u'yellow'
531  [1] <REPLACEME />
532  >>>
533  >>> print template("template.svg", SVG("circle", cx=50, cy=50, r=30))
534  None <svg (2 sub) style=u'stroke:black; fill:none; stroke-width:0.5pt; stroke-linejoi
535  [0] <rect height=u'100' width=u'100' stroke=u'none' y=u'0' x=u'0' fill=u'yellow'
536  [1] <circle cy=50 cx=50 r=30 />
537  """
538  output = load(fileName)
539  for ti, s in output:
540  if isinstance(s, SVG) and s.t == replaceme:
541  output[ti] = svg
542  return output
543 
544 ######################################################################
545 
546 def load(fileName):
547  """Loads an SVG image from a file."""
548  return load_stream(file(fileName))
549 
550 def load_stream(stream):
551  """Loads an SVG image from a stream (can be a string or a file object)."""
552 
553  from xml.sax import handler, make_parser
554  from xml.sax.handler import feature_namespaces, feature_external_ges, feature_external_pes
555 
556  class ContentHandler(handler.ContentHandler):
557  def __init__(self):
558  self.stack = []
559  self.output = None
560  self.all_whitespace = re.compile("^\s*$")
561 
562  def startElement(self, name, attr):
563  s = SVG(name)
564  s.attr = dict(attr.items())
565  if len(self.stack) > 0:
566  last = self.stack[-1]
567  last.sub.append(s)
568  self.stack.append(s)
569 
570  def characters(self, ch):
571  if not isinstance(ch, basestring) or self.all_whitespace.match(ch) == None:
572  if len(self.stack) > 0:
573  last = self.stack[-1]
574  if len(last.sub) > 0 and isinstance(last.sub[-1], basestring):
575  last.sub[-1] = last.sub[-1] + "\n" + ch
576  else:
577  last.sub.append(ch)
578 
579  def endElement(self, name):
580  if len(self.stack) > 0:
581  last = self.stack[-1]
582  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):
583  last.sub[0] = "<![CDATA[\n" + last.sub[0] + "]]>"
584 
585  self.output = self.stack.pop()
586 
587  ch = ContentHandler()
588  parser = make_parser()
589  parser.setContentHandler(ch)
590  parser.setFeature(feature_namespaces, 0)
591  parser.setFeature(feature_external_ges, 0)
592  parser.parse(stream)
593  return ch.output
594 
595 ######################################################################
596 
597 def totrans(expr, vars=("x", "y"), globals=None, locals=None):
598  """Converts to a coordinate transformation (a function that accepts
599  two arguments and returns two values).
600 
601  expr required a string expression or a function
602  of two real or one complex value
603  vars default=("x", "y") independent variable names;
604  a singleton ("z",) is interpreted
605  as complex
606  globals default=None dict of global variables
607  locals default=None dict of local variables
608  """
609 
610  if callable(expr):
611  if expr.func_code.co_argcount == 2:
612  return expr
613 
614  elif expr.func_code.co_argcount == 1:
615  split = lambda z: (z.real, z.imag)
616  output = lambda x, y: split(expr(x + y*1j))
617  output.func_name = expr.func_name
618  return output
619 
620  else:
621  raise TypeError, "must be a function of 2 or 1 variables"
622 
623  if len(vars) == 2:
624  g = math.__dict__
625  if globals != None: g.update(globals)
626  output = eval("lambda %s, %s: (%s)" % (vars[0], vars[1], expr), g, locals)
627  output.func_name = "%s,%s -> %s" % (vars[0], vars[1], expr)
628  return output
629 
630  elif len(vars) == 1:
631  g = cmath.__dict__
632  if globals != None: g.update(globals)
633  output = eval("lambda %s: (%s)" % (vars[0], expr), g, locals)
634  split = lambda z: (z.real, z.imag)
635  output2 = lambda x, y: split(output(x + y*1j))
636  output2.func_name = "%s -> %s" % (vars[0], expr)
637  return output2
638 
639  else:
640  raise TypeError, "vars must have 2 or 1 elements"
641 
642 def window(xmin, xmax, ymin, ymax, x=0, y=0, width=100, height=100, xlogbase=None, ylogbase=None, minusInfinity=-1000, flipx=False, flipy=True):
643  """Creates and returns a coordinate transformation (a function that
644  accepts two arguments and returns two values) that transforms from
645  (xmin, ymin), (xmax, ymax)
646  to
647  (x, y), (x + width, y + height).
648 
649  xlogbase, ylogbase default=None, None if a number, transform
650  logarithmically with given base
651  minusInfinity default=-1000 what to return if
652  log(0 or negative) is attempted
653  flipx default=False if true, reverse the direction of x
654  flipy default=True if true, reverse the direction of y
655 
656  (When composing windows, be sure to set flipy=False.)
657  """
658 
659  if flipx:
660  ox1 = x + width
661  ox2 = x
662  else:
663  ox1 = x
664  ox2 = x + width
665  if flipy:
666  oy1 = y + height
667  oy2 = y
668  else:
669  oy1 = y
670  oy2 = y + height
671  ix1 = xmin
672  iy1 = ymin
673  ix2 = xmax
674  iy2 = ymax
675 
676  if xlogbase != None and (ix1 <= 0. or ix2 <= 0.): raise ValueError, "x range incompatible with log scaling: (%g, %g)" % (ix1, ix2)
677 
678  if ylogbase != None and (iy1 <= 0. or iy2 <= 0.): raise ValueError, "y range incompatible with log scaling: (%g, %g)" % (iy1, iy2)
679 
680  def maybelog(t, it1, it2, ot1, ot2, logbase):
681  if t <= 0.: return minusInfinity
682  else:
683  return ot1 + 1.*(math.log(t, logbase) - math.log(it1, logbase))/(math.log(it2, logbase) - math.log(it1, logbase)) * (ot2 - ot1)
684 
685  xlogstr, ylogstr = "", ""
686 
687  if xlogbase == None:
688  xfunc = lambda x: ox1 + 1.*(x - ix1)/(ix2 - ix1) * (ox2 - ox1)
689  else:
690  xfunc = lambda x: maybelog(x, ix1, ix2, ox1, ox2, xlogbase)
691  xlogstr = " xlog=%g" % xlogbase
692 
693  if ylogbase == None:
694  yfunc = lambda y: oy1 + 1.*(y - iy1)/(iy2 - iy1) * (oy2 - oy1)
695  else:
696  yfunc = lambda y: maybelog(y, iy1, iy2, oy1, oy2, ylogbase)
697  ylogstr = " ylog=%g" % ylogbase
698 
699  output = lambda x, y: (xfunc(x), yfunc(y))
700 
701  output.func_name = "(%g, %g), (%g, %g) -> (%g, %g), (%g, %g)%s%s" % (ix1, ix2, iy1, iy2, ox1, ox2, oy1, oy2, xlogstr, ylogstr)
702  return output
703 
704 def rotate(angle, cx=0, cy=0):
705  """Creates and returns a coordinate transformation which rotates
706  around (cx,cy) by "angle" degrees."""
707  angle *= math.pi/180.
708  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))
709 
710 class Fig:
711  """Stores graphics primitive objects and applies a single coordinate
712  transformation to them. To compose coordinate systems, nest Fig
713  objects.
714 
715  Fig(obj, obj, obj..., trans=function)
716 
717  obj optional list a list of drawing primatives
718  trans default=None a coordinate transformation function
719 
720  >>> fig = Fig(Line(0,0,1,1), Rect(0.2,0.2,0.8,0.8), trans="2*x, 2*y")
721  >>> print fig.SVG().xml()
722  <g>
723  <path d='M0 0L2 2' />
724  <path d='M0.4 0.4L1.6 0.4ZL1.6 1.6ZL0.4 1.6ZL0.4 0.4ZZ' />
725  </g>
726  >>> print Fig(fig, trans="x/2., y/2.").SVG().xml()
727  <g>
728  <path d='M0 0L1 1' />
729  <path d='M0.2 0.2L0.8 0.2ZL0.8 0.8ZL0.2 0.8ZL0.2 0.2ZZ' />
730  </g>
731  """
732 
733  def __repr__(self):
734  if self.trans == None:
735  return "<Fig (%d items)>" % len(self.d)
736  elif isinstance(self.trans, basestring):
737  return "<Fig (%d items) x,y -> %s>" % (len(self.d), self.trans)
738  else:
739  return "<Fig (%d items) %s>" % (len(self.d), self.trans.func_name)
740 
741  def __init__(self, *d, **kwds):
742  self.d = list(d)
743  defaults = {"trans":None}
744  defaults.update(kwds)
745  kwds = defaults
746 
747  self.trans = kwds["trans"]; del kwds["trans"]
748  if len(kwds) != 0:
749  raise TypeError, "Fig() got unexpected keyword arguments %s" % kwds.keys()
750 
751  def SVG(self, trans=None):
752  """Apply the transformation "trans" and return an SVG object.
753 
754  Coordinate transformations in nested Figs will be composed.
755  """
756 
757  if trans == None: trans = self.trans
758  if isinstance(trans, basestring): trans = totrans(trans)
759 
760  output = SVG("g")
761  for s in self.d:
762  if isinstance(s, SVG):
763  output.append(s)
764 
765  elif isinstance(s, Fig):
766  strans = s.trans
767  if isinstance(strans, basestring): strans = totrans(strans)
768 
769  if trans == None: subtrans = strans
770  elif strans == None: subtrans = trans
771  else: subtrans = lambda x,y: trans(*strans(x, y))
772 
773  output.sub += s.SVG(subtrans).sub
774 
775  elif s == None: pass
776 
777  else:
778  output.append(s.SVG(trans))
779 
780  return output
781 
782 class Plot:
783  """Acts like Fig, but draws a coordinate axis. You also need to supply plot ranges.
784 
785  Plot(xmin, xmax, ymin, ymax, obj, obj, obj..., keyword options...)
786 
787  xmin, xmax required minimum and maximum x values (in the objs' coordinates)
788  ymin, ymax required minimum and maximum y values (in the objs' coordinates)
789  obj optional list drawing primatives
790  keyword options keyword list options defined below
791 
792  The following are keyword options, with their default values:
793 
794  trans None transformation function
795  x, y 5, 5 upper-left corner of the Plot in SVG coordinates
796  width, height 90, 90 width and height of the Plot in SVG coordinates
797  flipx, flipy False, True flip the sign of the coordinate axis
798  minusInfinity -1000 if an axis is logarithmic and an object is plotted at 0 or
799  a negative value, -1000 will be used as a stand-in for NaN
800  atx, aty 0, 0 the place where the coordinate axes cross
801  xticks -10 request ticks according to the standard tick specification
802  (see help(Ticks))
803  xminiticks True request miniticks according to the standard minitick
804  specification
805  xlabels True request tick labels according to the standard tick label
806  specification
807  xlogbase None if a number, the axis and transformation are logarithmic
808  with ticks at the given base (10 being the most common)
809  (same for y)
810  arrows None if a new identifier, create arrow markers and draw them
811  at the ends of the coordinate axes
812  text_attr {} a dictionary of attributes for label text
813  axis_attr {} a dictionary of attributes for the axis lines
814  """
815 
816  def __repr__(self):
817  if self.trans == None:
818  return "<Plot (%d items)>" % len(self.d)
819  else:
820  return "<Plot (%d items) %s>" % (len(self.d), self.trans.func_name)
821 
822  def __init__(self, xmin, xmax, ymin, ymax, *d, **kwds):
823  self.xmin, self.xmax, self.ymin, self.ymax = xmin, xmax, ymin, ymax
824  self.d = list(d)
825  defaults = {"trans":None, "x":5, "y":5, "width":90, "height":90, "flipx":False, "flipy":True, "minusInfinity":-1000, \
826  "atx":0, "xticks":-10, "xminiticks":True, "xlabels":True, "xlogbase":None, \
827  "aty":0, "yticks":-10, "yminiticks":True, "ylabels":True, "ylogbase":None, \
828  "arrows":None, "text_attr":{}, "axis_attr":{}}
829  defaults.update(kwds)
830  kwds = defaults
831 
832  self.trans = kwds["trans"]; del kwds["trans"]
833  self.x = kwds["x"]; del kwds["x"]
834  self.y = kwds["y"]; del kwds["y"]
835  self.width = kwds["width"]; del kwds["width"]
836  self.height = kwds["height"]; del kwds["height"]
837  self.flipx = kwds["flipx"]; del kwds["flipx"]
838  self.flipy = kwds["flipy"]; del kwds["flipy"]
839  self.minusInfinity = kwds["minusInfinity"]; del kwds["minusInfinity"]
840  self.atx = kwds["atx"]; del kwds["atx"]
841  self.xticks = kwds["xticks"]; del kwds["xticks"]
842  self.xminiticks = kwds["xminiticks"]; del kwds["xminiticks"]
843  self.xlabels = kwds["xlabels"]; del kwds["xlabels"]
844  self.xlogbase = kwds["xlogbase"]; del kwds["xlogbase"]
845  self.aty = kwds["aty"]; del kwds["aty"]
846  self.yticks = kwds["yticks"]; del kwds["yticks"]
847  self.yminiticks = kwds["yminiticks"]; del kwds["yminiticks"]
848  self.ylabels = kwds["ylabels"]; del kwds["ylabels"]
849  self.ylogbase = kwds["ylogbase"]; del kwds["ylogbase"]
850  self.arrows = kwds["arrows"]; del kwds["arrows"]
851  self.text_attr = kwds["text_attr"]; del kwds["text_attr"]
852  self.axis_attr = kwds["axis_attr"]; del kwds["axis_attr"]
853  if len(kwds) != 0:
854  raise TypeError, "Plot() got unexpected keyword arguments %s" % kwds.keys()
855 
856  def SVG(self, trans=None):
857  """Apply the transformation "trans" and return an SVG object."""
858  if trans == None: trans = self.trans
859  if isinstance(trans, basestring): trans = totrans(trans)
860 
861  self.last_window = window(self.xmin, self.xmax, self.ymin, self.ymax, x=self.x, y=self.y, width=self.width, height=self.height, \
862  xlogbase=self.xlogbase, ylogbase=self.ylogbase, minusInfinity=self.minusInfinity, flipx=self.flipx, flipy=self.flipy)
863 
864  d = [Axes(self.xmin, self.xmax, self.ymin, self.ymax, self.atx, self.aty, \
865  self.xticks, self.xminiticks, self.xlabels, self.xlogbase, \
866  self.yticks, self.yminiticks, self.ylabels, self.ylogbase, \
867  self.arrows, self.text_attr, **self.axis_attr)] \
868  + self.d
869 
870  return Fig(Fig(*d, **{"trans":trans})).SVG(self.last_window)
871 
872 class Frame:
873  text_defaults = {"stroke":"none", "fill":"black", "font-size":5}
874  axis_defaults = {}
875 
876  tick_length = 1.5
877  minitick_length = 0.75
878  text_xaxis_offset = 1.
879  text_yaxis_offset = 2.
880  text_xtitle_offset = 6.
881  text_ytitle_offset = 12.
882 
883  def __repr__(self):
884  return "<Frame (%d items)>" % len(self.d)
885 
886  def __init__(self, xmin, xmax, ymin, ymax, *d, **kwds):
887  """Acts like Fig, but draws a coordinate frame around the data. You also need to supply plot ranges.
888 
889  Frame(xmin, xmax, ymin, ymax, obj, obj, obj..., keyword options...)
890 
891  xmin, xmax required minimum and maximum x values (in the objs' coordinates)
892  ymin, ymax required minimum and maximum y values (in the objs' coordinates)
893  obj optional list drawing primatives
894  keyword options keyword list options defined below
895 
896  The following are keyword options, with their default values:
897 
898  x, y 20, 5 upper-left corner of the Frame in SVG coordinates
899  width, height 75, 80 width and height of the Frame in SVG coordinates
900  flipx, flipy False, True flip the sign of the coordinate axis
901  minusInfinity -1000 if an axis is logarithmic and an object is plotted at 0 or
902  a negative value, -1000 will be used as a stand-in for NaN
903  xtitle None if a string, label the x axis
904  xticks -10 request ticks according to the standard tick specification
905  (see help(Ticks))
906  xminiticks True request miniticks according to the standard minitick
907  specification
908  xlabels True request tick labels according to the standard tick label
909  specification
910  xlogbase None if a number, the axis and transformation are logarithmic
911  with ticks at the given base (10 being the most common)
912  (same for y)
913  text_attr {} a dictionary of attributes for label text
914  axis_attr {} a dictionary of attributes for the axis lines
915  """
916 
917  self.xmin, self.xmax, self.ymin, self.ymax = xmin, xmax, ymin, ymax
918  self.d = list(d)
919  defaults = {"x":20, "y":5, "width":75, "height":80, "flipx":False, "flipy":True, "minusInfinity":-1000, \
920  "xtitle":None, "xticks":-10, "xminiticks":True, "xlabels":True, "x2labels":None, "xlogbase":None, \
921  "ytitle":None, "yticks":-10, "yminiticks":True, "ylabels":True, "y2labels":None, "ylogbase":None, \
922  "text_attr":{}, "axis_attr":{}}
923  defaults.update(kwds)
924  kwds = defaults
925 
926  self.x = kwds["x"]; del kwds["x"]
927  self.y = kwds["y"]; del kwds["y"]
928  self.width = kwds["width"]; del kwds["width"]
929  self.height = kwds["height"]; del kwds["height"]
930  self.flipx = kwds["flipx"]; del kwds["flipx"]
931  self.flipy = kwds["flipy"]; del kwds["flipy"]
932  self.minusInfinity = kwds["minusInfinity"]; del kwds["minusInfinity"]
933  self.xtitle = kwds["xtitle"]; del kwds["xtitle"]
934  self.xticks = kwds["xticks"]; del kwds["xticks"]
935  self.xminiticks = kwds["xminiticks"]; del kwds["xminiticks"]
936  self.xlabels = kwds["xlabels"]; del kwds["xlabels"]
937  self.x2labels = kwds["x2labels"]; del kwds["x2labels"]
938  self.xlogbase = kwds["xlogbase"]; del kwds["xlogbase"]
939  self.ytitle = kwds["ytitle"]; del kwds["ytitle"]
940  self.yticks = kwds["yticks"]; del kwds["yticks"]
941  self.yminiticks = kwds["yminiticks"]; del kwds["yminiticks"]
942  self.ylabels = kwds["ylabels"]; del kwds["ylabels"]
943  self.y2labels = kwds["y2labels"]; del kwds["y2labels"]
944  self.ylogbase = kwds["ylogbase"]; del kwds["ylogbase"]
945 
947  self.text_attr.update(kwds["text_attr"]); del kwds["text_attr"]
948 
950  self.axis_attr.update(kwds["axis_attr"]); del kwds["axis_attr"]
951 
952  if len(kwds) != 0:
953  raise TypeError, "Frame() got unexpected keyword arguments %s" % kwds.keys()
954 
955  def SVG(self):
956  """Apply the window transformation and return an SVG object."""
957 
958  self.last_window = window(self.xmin, self.xmax, self.ymin, self.ymax, x=self.x, y=self.y, width=self.width, height=self.height, \
959  xlogbase=self.xlogbase, ylogbase=self.ylogbase, minusInfinity=self.minusInfinity, flipx=self.flipx, flipy=self.flipy)
960 
961  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)
962  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)
963  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)
964  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)
965 
966  left.tick_start = -self.tick_length
967  left.tick_end = 0
968  left.minitick_start = -self.minitick_length
969  left.minitick_end = 0.
970  left.text_start = self.text_yaxis_offset
971 
972  right.tick_start = 0.
973  right.tick_end = self.tick_length
974  right.minitick_start = 0.
975  right.minitick_end = self.minitick_length
976  right.text_start = -self.text_yaxis_offset
977  right.text_attr["text-anchor"] = "start"
978 
979  bottom.tick_start = 0.
980  bottom.tick_end = self.tick_length
981  bottom.minitick_start = 0.
982  bottom.minitick_end = self.minitick_length
983  bottom.text_start = -self.text_xaxis_offset
984 
985  top.tick_start = -self.tick_length
986  top.tick_end = 0.
987  top.minitick_start = -self.minitick_length
988  top.minitick_end = 0.
989  top.text_start = self.text_xaxis_offset
990  top.text_attr["dominant-baseline"] = "text-after-edge"
991 
992  output = Fig(*self.d).SVG(self.last_window)
993  output.prepend(left.SVG(self.last_window))
994  output.prepend(bottom.SVG(self.last_window))
995  output.prepend(right.SVG(self.last_window))
996  output.prepend(top.SVG(self.last_window))
997 
998  if self.xtitle != None:
999  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))
1000  if self.ytitle != None:
1001  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))
1002  return output
1003 
1004 ######################################################################
1005 
1006 def pathtoPath(svg):
1007  """Converts SVG("path", d="...") into Path(d=[...])."""
1008  if not isinstance(svg, SVG) or svg.t != "path":
1009  raise TypeError, "Only SVG <path /> objects can be converted into Paths"
1010  attr = dict(svg.attr)
1011  d = attr["d"]
1012  del attr["d"]
1013  for key in attr.keys():
1014  if not isinstance(key, str):
1015  value = attr[key]
1016  del attr[key]
1017  attr[str(key)] = value
1018  return Path(d, **attr)
1019 
1020 class Path:
1021  """Path represents an SVG path, an arbitrary set of curves and
1022  straight segments. Unlike SVG("path", d="..."), Path stores
1023  coordinates as a list of numbers, rather than a string, so that it is
1024  transformable in a Fig.
1025 
1026  Path(d, attribute=value)
1027 
1028  d required path data
1029  attribute=value pairs keyword list SVG attributes
1030 
1031  See http://www.w3.org/TR/SVG/paths.html for specification of paths
1032  from text.
1033 
1034  Internally, Path data is a list of tuples with these definitions:
1035 
1036  * ("Z/z",): close the current path
1037  * ("H/h", x) or ("V/v", y): a horizontal or vertical line
1038  segment to x or y
1039  * ("M/m/L/l/T/t", x, y, global): moveto, lineto, or smooth
1040  quadratic curveto point (x, y). If global=True, (x, y) should
1041  not be transformed.
1042  * ("S/sQ/q", cx, cy, cglobal, x, y, global): polybezier or
1043  smooth quadratic curveto point (x, y) using (cx, cy) as a
1044  control point. If cglobal or global=True, (cx, cy) or (x, y)
1045  should not be transformed.
1046  * ("C/c", c1x, c1y, c1global, c2x, c2y, c2global, x, y, global):
1047  cubic curveto point (x, y) using (c1x, c1y) and (c2x, c2y) as
1048  control points. If c1global, c2global, or global=True, (c1x, c1y),
1049  (c2x, c2y), or (x, y) should not be transformed.
1050  * ("A/a", rx, ry, rglobal, x-axis-rotation, angle, large-arc-flag,
1051  sweep-flag, x, y, global): arcto point (x, y) using the
1052  aforementioned parameters.
1053  * (",/.", rx, ry, rglobal, angle, x, y, global): an ellipse at
1054  point (x, y) with radii (rx, ry). If angle is 0, the whole
1055  ellipse is drawn; otherwise, a partial ellipse is drawn.
1056  """
1057  defaults = {}
1058 
1059  def __repr__(self):
1060  return "<Path (%d nodes) %s>" % (len(self.d), self.attr)
1061 
1062  def __init__(self, d=[], **attr):
1063  if isinstance(d, basestring): self.d = self.parse(d)
1064  else: self.d = list(d)
1065 
1066  self.attr = dict(self.defaults)
1067  self.attr.update(attr)
1068 
1069  def parse_whitespace(self, index, pathdata):
1070  """Part of Path's text-command parsing algorithm; used internally."""
1071  while index < len(pathdata) and pathdata[index] in (" ", "\t", "\r", "\n", ","): index += 1
1072  return index, pathdata
1073 
1074  def parse_command(self, index, pathdata):
1075  """Part of Path's text-command parsing algorithm; used internally."""
1076  index, pathdata = self.parse_whitespace(index, pathdata)
1077 
1078  if index >= len(pathdata): return None, index, pathdata
1079  command = pathdata[index]
1080  if "A" <= command <= "Z" or "a" <= command <= "z":
1081  index += 1
1082  return command, index, pathdata
1083  else:
1084  return None, index, pathdata
1085 
1086  def parse_number(self, index, pathdata):
1087  """Part of Path's text-command parsing algorithm; used internally."""
1088  index, pathdata = self.parse_whitespace(index, pathdata)
1089 
1090  if index >= len(pathdata): return None, index, pathdata
1091  first_digit = pathdata[index]
1092 
1093  if "0" <= first_digit <= "9" or first_digit in ("-", "+", "."):
1094  start = index
1095  while index < len(pathdata) and ("0" <= pathdata[index] <= "9" or pathdata[index] in ("-", "+", ".", "e", "E")):
1096  index += 1
1097  end = index
1098 
1099  index = end
1100  return float(pathdata[start:end]), index, pathdata
1101  else:
1102  return None, index, pathdata
1103 
1104  def parse_boolean(self, index, pathdata):
1105  """Part of Path's text-command parsing algorithm; used internally."""
1106  index, pathdata = self.parse_whitespace(index, pathdata)
1107 
1108  if index >= len(pathdata): return None, index, pathdata
1109  first_digit = pathdata[index]
1110 
1111  if first_digit in ("0", "1"):
1112  index += 1
1113  return int(first_digit), index, pathdata
1114  else:
1115  return None, index, pathdata
1116 
1117  def parse(self, pathdata):
1118  """Parses text-commands, converting them into a list of tuples.
1119  Called by the constructor."""
1120  output = []
1121  index = 0
1122  while True:
1123  command, index, pathdata = self.parse_command(index, pathdata)
1124  index, pathdata = self.parse_whitespace(index, pathdata)
1125 
1126  if command == None and index == len(pathdata): break # this is the normal way out of the loop
1127  if command in ("Z", "z"):
1128  output.append((command,))
1129 
1130  ######################
1131  elif command in ("H", "h", "V", "v"):
1132  errstring = "Path command \"%s\" requires a number at index %d" % (command, index)
1133  num1, index, pathdata = self.parse_number(index, pathdata)
1134  if num1 == None: raise ValueError, errstring
1135 
1136  while num1 != None:
1137  output.append((command, num1))
1138  num1, index, pathdata = self.parse_number(index, pathdata)
1139 
1140  ######################
1141  elif command in ("M", "m", "L", "l", "T", "t"):
1142  errstring = "Path command \"%s\" requires an x,y pair at index %d" % (command, index)
1143  num1, index, pathdata = self.parse_number(index, pathdata)
1144  num2, index, pathdata = self.parse_number(index, pathdata)
1145 
1146  if num1 == None: raise ValueError, errstring
1147 
1148  while num1 != None:
1149  if num2 == None: raise ValueError, errstring
1150  output.append((command, num1, num2, False))
1151 
1152  num1, index, pathdata = self.parse_number(index, pathdata)
1153  num2, index, pathdata = self.parse_number(index, pathdata)
1154 
1155  ######################
1156  elif command in ("S", "s", "Q", "q"):
1157  errstring = "Path command \"%s\" requires a cx,cy,x,y quadruplet at index %d" % (command, index)
1158  num1, index, pathdata = self.parse_number(index, pathdata)
1159  num2, index, pathdata = self.parse_number(index, pathdata)
1160  num3, index, pathdata = self.parse_number(index, pathdata)
1161  num4, index, pathdata = self.parse_number(index, pathdata)
1162 
1163  if num1 == None: raise ValueError, errstring
1164 
1165  while num1 != None:
1166  if num2 == None or num3 == None or num4 == None: raise ValueError, errstring
1167  output.append((command, num1, num2, False, num3, num4, False))
1168 
1169  num1, index, pathdata = self.parse_number(index, pathdata)
1170  num2, index, pathdata = self.parse_number(index, pathdata)
1171  num3, index, pathdata = self.parse_number(index, pathdata)
1172  num4, index, pathdata = self.parse_number(index, pathdata)
1173 
1174  ######################
1175  elif command in ("C", "c"):
1176  errstring = "Path command \"%s\" requires a c1x,c1y,c2x,c2y,x,y sextuplet at index %d" % (command, index)
1177  num1, index, pathdata = self.parse_number(index, pathdata)
1178  num2, index, pathdata = self.parse_number(index, pathdata)
1179  num3, index, pathdata = self.parse_number(index, pathdata)
1180  num4, index, pathdata = self.parse_number(index, pathdata)
1181  num5, index, pathdata = self.parse_number(index, pathdata)
1182  num6, index, pathdata = self.parse_number(index, pathdata)
1183 
1184  if num1 == None: raise ValueError, errstring
1185 
1186  while num1 != None:
1187  if num2 == None or num3 == None or num4 == None or num5 == None or num6 == None: raise ValueError, errstring
1188 
1189  output.append((command, num1, num2, False, num3, num4, False, num5, num6, False))
1190 
1191  num1, index, pathdata = self.parse_number(index, pathdata)
1192  num2, index, pathdata = self.parse_number(index, pathdata)
1193  num3, index, pathdata = self.parse_number(index, pathdata)
1194  num4, index, pathdata = self.parse_number(index, pathdata)
1195  num5, index, pathdata = self.parse_number(index, pathdata)
1196  num6, index, pathdata = self.parse_number(index, pathdata)
1197 
1198  ######################
1199  elif command in ("A", "a"):
1200  errstring = "Path command \"%s\" requires a rx,ry,angle,large-arc-flag,sweep-flag,x,y septuplet at index %d" % (command, index)
1201  num1, index, pathdata = self.parse_number(index, pathdata)
1202  num2, index, pathdata = self.parse_number(index, pathdata)
1203  num3, index, pathdata = self.parse_number(index, pathdata)
1204  num4, index, pathdata = self.parse_boolean(index, pathdata)
1205  num5, index, pathdata = self.parse_boolean(index, pathdata)
1206  num6, index, pathdata = self.parse_number(index, pathdata)
1207  num7, index, pathdata = self.parse_number(index, pathdata)
1208 
1209  if num1 == None: raise ValueError, errstring
1210 
1211  while num1 != None:
1212  if num2 == None or num3 == None or num4 == None or num5 == None or num6 == None or num7 == None: raise ValueError, errstring
1213 
1214  output.append((command, num1, num2, False, num3, num4, num5, num6, num7, False))
1215 
1216  num1, index, pathdata = self.parse_number(index, pathdata)
1217  num2, index, pathdata = self.parse_number(index, pathdata)
1218  num3, index, pathdata = self.parse_number(index, pathdata)
1219  num4, index, pathdata = self.parse_boolean(index, pathdata)
1220  num5, index, pathdata = self.parse_boolean(index, pathdata)
1221  num6, index, pathdata = self.parse_number(index, pathdata)
1222  num7, index, pathdata = self.parse_number(index, pathdata)
1223 
1224  return output
1225 
1226  def SVG(self, trans=None):
1227  """Apply the transformation "trans" and return an SVG object."""
1228  if isinstance(trans, basestring): trans = totrans(trans)
1229 
1230  x, y, X, Y = None, None, None, None
1231  output = []
1232  for datum in self.d:
1233  if not isinstance(datum, (tuple, list)):
1234  raise TypeError, "pathdata elements must be tuples/lists"
1235 
1236  command = datum[0]
1237 
1238  ######################
1239  if command in ("Z", "z"):
1240  x, y, X, Y = None, None, None, None
1241  output.append("Z")
1242 
1243  ######################
1244  elif command in ("H", "h", "V", "v"):
1245  command, num1 = datum
1246 
1247  if command == "H" or (command == "h" and x == None): x = num1
1248  elif command == "h": x += num1
1249  elif command == "V" or (command == "v" and y == None): y = num1
1250  elif command == "v": y += num1
1251 
1252  if trans == None: X, Y = x, y
1253  else: X, Y = trans(x, y)
1254 
1255  output.append("L%g %g" % (X, Y))
1256 
1257  ######################
1258  elif command in ("M", "m", "L", "l", "T", "t"):
1259  command, num1, num2, isglobal12 = datum
1260 
1261  if trans == None or isglobal12:
1262  if command.isupper() or X == None or Y == None:
1263  X, Y = num1, num2
1264  else:
1265  X += num1
1266  Y += num2
1267  x, y = X, Y
1268 
1269  else:
1270  if command.isupper() or x == None or y == None:
1271  x, y = num1, num2
1272  else:
1273  x += num1
1274  y += num2
1275  X, Y = trans(x, y)
1276 
1277  COMMAND = command.capitalize()
1278  output.append("%s%g %g" % (COMMAND, X, Y))
1279 
1280  ######################
1281  elif command in ("S", "s", "Q", "q"):
1282  command, num1, num2, isglobal12, num3, num4, isglobal34 = datum
1283 
1284  if trans == None or isglobal12:
1285  if command.isupper() or X == None or Y == None:
1286  CX, CY = num1, num2
1287  else:
1288  CX = X + num1
1289  CY = Y + num2
1290 
1291  else:
1292  if command.isupper() or x == None or y == None:
1293  cx, cy = num1, num2
1294  else:
1295  cx = x + num1
1296  cy = y + num2
1297  CX, CY = trans(cx, cy)
1298 
1299  if trans == None or isglobal34:
1300  if command.isupper() or X == None or Y == None:
1301  X, Y = num3, num4
1302  else:
1303  X += num3
1304  Y += num4
1305  x, y = X, Y
1306 
1307  else:
1308  if command.isupper() or x == None or y == None:
1309  x, y = num3, num4
1310  else:
1311  x += num3
1312  y += num4
1313  X, Y = trans(x, y)
1314 
1315  COMMAND = command.capitalize()
1316  output.append("%s%g %g %g %g" % (COMMAND, CX, CY, X, Y))
1317 
1318  ######################
1319  elif command in ("C", "c"):
1320  command, num1, num2, isglobal12, num3, num4, isglobal34, num5, num6, isglobal56 = datum
1321 
1322  if trans == None or isglobal12:
1323  if command.isupper() or X == None or Y == None:
1324  C1X, C1Y = num1, num2
1325  else:
1326  C1X = X + num1
1327  C1Y = Y + num2
1328 
1329  else:
1330  if command.isupper() or x == None or y == None:
1331  c1x, c1y = num1, num2
1332  else:
1333  c1x = x + num1
1334  c1y = y + num2
1335  C1X, C1Y = trans(c1x, c1y)
1336 
1337  if trans == None or isglobal34:
1338  if command.isupper() or X == None or Y == None:
1339  C2X, C2Y = num3, num4
1340  else:
1341  C2X = X + num3
1342  C2Y = Y + num4
1343 
1344  else:
1345  if command.isupper() or x == None or y == None:
1346  c2x, c2y = num3, num4
1347  else:
1348  c2x = x + num3
1349  c2y = y + num4
1350  C2X, C2Y = trans(c2x, c2y)
1351 
1352  if trans == None or isglobal56:
1353  if command.isupper() or X == None or Y == None:
1354  X, Y = num5, num6
1355  else:
1356  X += num5
1357  Y += num6
1358  x, y = X, Y
1359 
1360  else:
1361  if command.isupper() or x == None or y == None:
1362  x, y = num5, num6
1363  else:
1364  x += num5
1365  y += num6
1366  X, Y = trans(x, y)
1367 
1368  COMMAND = command.capitalize()
1369  output.append("%s%g %g %g %g %g %g" % (COMMAND, C1X, C1Y, C2X, C2Y, X, Y))
1370 
1371  ######################
1372  elif command in ("A", "a"):
1373  command, num1, num2, isglobal12, angle, large_arc_flag, sweep_flag, num3, num4, isglobal34 = datum
1374 
1375  oldx, oldy = x, y
1376  OLDX, OLDY = X, Y
1377 
1378  if trans == None or isglobal34:
1379  if command.isupper() or X == None or Y == None:
1380  X, Y = num3, num4
1381  else:
1382  X += num3
1383  Y += num4
1384  x, y = X, Y
1385 
1386  else:
1387  if command.isupper() or x == None or y == None:
1388  x, y = num3, num4
1389  else:
1390  x += num3
1391  y += num4
1392  X, Y = trans(x, y)
1393 
1394  if x != None and y != None:
1395  centerx, centery = (x + oldx)/2., (y + oldy)/2.
1396  CENTERX, CENTERY = (X + OLDX)/2., (Y + OLDY)/2.
1397 
1398  if trans == None or isglobal12:
1399  RX = CENTERX + num1
1400  RY = CENTERY + num2
1401 
1402  else:
1403  rx = centerx + num1
1404  ry = centery + num2
1405  RX, RY = trans(rx, ry)
1406 
1407  COMMAND = command.capitalize()
1408  output.append("%s%g %g %g %d %d %g %g" % (COMMAND, RX - CENTERX, RY - CENTERY, angle, large_arc_flag, sweep_flag, X, Y))
1409 
1410  elif command in (",", "."):
1411  command, num1, num2, isglobal12, angle, num3, num4, isglobal34 = datum
1412  if trans == None or isglobal34:
1413  if command == "." or X == None or Y == None:
1414  X, Y = num3, num4
1415  else:
1416  X += num3
1417  Y += num4
1418  x, y = None, None
1419 
1420  else:
1421  if command == "." or x == None or y == None:
1422  x, y = num3, num4
1423  else:
1424  x += num3
1425  y += num4
1426  X, Y = trans(x, y)
1427 
1428  if trans == None or isglobal12:
1429  RX = X + num1
1430  RY = Y + num2
1431 
1432  else:
1433  rx = x + num1
1434  ry = y + num2
1435  RX, RY = trans(rx, ry)
1436 
1437  RX, RY = RX - X, RY - Y
1438 
1439  X1, Y1 = X + RX * math.cos(angle*math.pi/180.), Y + RX * math.sin(angle*math.pi/180.)
1440  X2, Y2 = X + RY * math.sin(angle*math.pi/180.), Y - RY * math.cos(angle*math.pi/180.)
1441  X3, Y3 = X - RX * math.cos(angle*math.pi/180.), Y - RX * math.sin(angle*math.pi/180.)
1442  X4, Y4 = X - RY * math.sin(angle*math.pi/180.), Y + RY * math.cos(angle*math.pi/180.)
1443 
1444  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" \
1445  % (X1, Y1, RX, RY, angle, X2, Y2, RX, RY, angle, X3, Y3, RX, RY, angle, X4, Y4, RX, RY, angle, X1, Y1))
1446 
1447  return SVG("path", d="".join(output), **self.attr)
1448 
1449 ######################################################################
1450 
1451 def funcRtoC(expr, var="t", globals=None, locals=None):
1452  """Converts a complex "z(t)" string to a function acceptable for Curve.
1453 
1454  expr required string in the form "z(t)"
1455  var default="t" name of the independent variable
1456  globals default=None dict of global variables used in the expression;
1457  you may want to use Python's builtin globals()
1458  locals default=None dict of local variables
1459  """
1460  g = cmath.__dict__
1461  if globals != None: g.update(globals)
1462  output = eval("lambda %s: (%s)" % (var, expr), g, locals)
1463  split = lambda z: (z.real, z.imag)
1464  output2 = lambda t: split(output(t))
1465  output2.func_name = "%s -> %s" % (var, expr)
1466  return output2
1467 
1468 def funcRtoR2(expr, var="t", globals=None, locals=None):
1469  """Converts a "f(t), g(t)" string to a function acceptable for Curve.
1470 
1471  expr required string in the form "f(t), g(t)"
1472  var default="t" name of the independent variable
1473  globals default=None dict of global variables used in the expression;
1474  you may want to use Python's builtin globals()
1475  locals default=None dict of local variables
1476  """
1477  g = math.__dict__
1478  if globals != None: g.update(globals)
1479  output = eval("lambda %s: (%s)" % (var, expr), g, locals)
1480  output.func_name = "%s -> %s" % (var, expr)
1481  return output
1482 
1483 def funcRtoR(expr, var="x", globals=None, locals=None):
1484  """Converts a "f(x)" string to a function acceptable for Curve.
1485 
1486  expr required string in the form "f(x)"
1487  var default="x" name of the independent variable
1488  globals default=None dict of global variables used in the expression;
1489  you may want to use Python's builtin globals()
1490  locals default=None dict of local variables
1491  """
1492  g = math.__dict__
1493  if globals != None: g.update(globals)
1494  output = eval("lambda %s: (%s, %s)" % (var, var, expr), g, locals)
1495  output.func_name = "%s -> %s" % (var, expr)
1496  return output
1497 
1498 class Curve:
1499  """Draws a parametric function as a path.
1500 
1501  Curve(f, low, high, loop, attribute=value)
1502 
1503  f required a Python callable or string in
1504  the form "f(t), g(t)"
1505  low, high required left and right endpoints
1506  loop default=False if True, connect the endpoints
1507  attribute=value pairs keyword list SVG attributes
1508  """
1509  defaults = {}
1510  random_sampling = True
1511  recursion_limit = 15
1512  linearity_limit = 0.05
1513  discontinuity_limit = 5.
1514 
1515  def __repr__(self):
1516  return "<Curve %s [%s, %s] %s>" % (self.f, self.low, self.high, self.attr)
1517 
1518  def __init__(self, f, low, high, loop=False, **attr):
1519  self.f = f
1520  self.low = low
1521  self.high = high
1522  self.loop = loop
1523 
1524  self.attr = dict(self.defaults)
1525  self.attr.update(attr)
1526 
1527  ### nested class Sample
1528  class Sample:
1529  def __repr__(self):
1530  t, x, y, X, Y = self.t, self.x, self.y, self.X, self.Y
1531  if t != None: t = "%g" % t
1532  if x != None: x = "%g" % x
1533  if y != None: y = "%g" % y
1534  if X != None: X = "%g" % X
1535  if Y != None: Y = "%g" % Y
1536  return "<Curve.Sample t=%s x=%s y=%s X=%s Y=%s>" % (t, x, y, X, Y)
1537 
1538  def __init__(self, t): self.t = t
1539 
1540  def link(self, left, right): self.left, self.right = left, right
1541 
1542  def evaluate(self, f, trans):
1543  self.x, self.y = f(self.t)
1544  if trans == None:
1545  self.X, self.Y = self.x, self.y
1546  else:
1547  self.X, self.Y = trans(self.x, self.y)
1548  ### end Sample
1549 
1550  ### nested class Samples
1551  class Samples:
1552  def __repr__(self): return "<Curve.Samples (%d samples)>" % len(self)
1553 
1554  def __init__(self, left, right): self.left, self.right = left, right
1555 
1556  def __len__(self):
1557  count = 0
1558  current = self.left
1559  while current != None:
1560  count += 1
1561  current = current.right
1562  return count
1563 
1564  def __iter__(self):
1565  self.current = self.left
1566  return self
1567 
1568  def next(self):
1569  current = self.current
1570  if current == None: raise StopIteration
1571  self.current = self.current.right
1572  return current
1573  ### end nested class
1574 
1575  def sample(self, trans=None):
1576  """Adaptive-sampling algorithm that chooses the best sample points
1577  for a parametric curve between two endpoints and detects
1578  discontinuities. Called by SVG()."""
1579  oldrecursionlimit = sys.getrecursionlimit()
1580  sys.setrecursionlimit(self.recursion_limit + 100)
1581  try:
1582  # the best way to keep all the information while sampling is to make a linked list
1583  if not (self.low < self.high): raise ValueError, "low must be less than high"
1584  low, high = self.Sample(float(self.low)), self.Sample(float(self.high))
1585  low.link(None, high)
1586  high.link(low, None)
1587 
1588  low.evaluate(self.f, trans)
1589  high.evaluate(self.f, trans)
1590 
1591  # adaptive sampling between the low and high points
1592  self.subsample(low, high, 0, trans)
1593 
1594  # Prune excess points where the curve is nearly linear
1595  left = low
1596  while left.right != None:
1597  # increment mid and right
1598  mid = left.right
1599  right = mid.right
1600  if right != None and left.X != None and left.Y != None and mid.X != None and mid.Y != None and right.X != None and right.Y != None:
1601  numer = left.X*(right.Y - mid.Y) + mid.X*(left.Y - right.Y) + right.X*(mid.Y - left.Y)
1602  denom = math.sqrt((left.X - right.X)**2 + (left.Y - right.Y)**2)
1603  if denom != 0. and abs(numer/denom) < self.linearity_limit:
1604  # drop mid (the garbage collector will get it)
1605  left.right = right
1606  right.left = left
1607  else:
1608  # increment left
1609  left = left.right
1610  else:
1611  left = left.right
1612 
1613  self.last_samples = self.Samples(low, high)
1614 
1615  finally:
1616  sys.setrecursionlimit(oldrecursionlimit)
1617 
1618  def subsample(self, left, right, depth, trans=None):
1619  """Part of the adaptive-sampling algorithm that chooses the best
1620  sample points. Called by sample()."""
1621 
1622  if self.random_sampling:
1623  mid = self.Sample(left.t + random.uniform(0.3, 0.7) * (right.t - left.t))
1624  else:
1625  mid = self.Sample(left.t + 0.5 * (right.t - left.t))
1626 
1627  left.right = mid
1628  right.left = mid
1629  mid.link(left, right)
1630  mid.evaluate(self.f, trans)
1631 
1632  # calculate the distance of closest approach of mid to the line between left and right
1633  numer = left.X*(right.Y - mid.Y) + mid.X*(left.Y - right.Y) + right.X*(mid.Y - left.Y)
1634  denom = math.sqrt((left.X - right.X)**2 + (left.Y - right.Y)**2)
1635 
1636  # if we haven't sampled enough or left fails to be close enough to right, or mid fails to be linear enough...
1637  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):
1638 
1639  # and we haven't sampled too many points
1640  if depth < self.recursion_limit:
1641  self.subsample(left, mid, depth+1, trans)
1642  self.subsample(mid, right, depth+1, trans)
1643 
1644  else:
1645  # We've sampled many points and yet it's still not a small linear gap.
1646  # Break the line: it's a discontinuity
1647  mid.y = mid.Y = None
1648 
1649  def SVG(self, trans=None):
1650  """Apply the transformation "trans" and return an SVG object."""
1651  return self.Path(trans).SVG()
1652 
1653  def Path(self, trans=None, local=False):
1654  """Apply the transformation "trans" and return a Path object in
1655  global coordinates. If local=True, return a Path in local coordinates
1656  (which must be transformed again)."""
1657 
1658  if isinstance(trans, basestring): trans = totrans(trans)
1659  if isinstance(self.f, basestring): self.f = funcRtoR2(self.f)
1660 
1661  self.sample(trans)
1662 
1663  output = []
1664  for s in self.last_samples:
1665  if s.X != None and s.Y != None:
1666  if s.left == None or s.left.Y == None:
1667  command = "M"
1668  else:
1669  command = "L"
1670 
1671  if local: output.append((command, s.x, s.y, False))
1672  else: output.append((command, s.X, s.Y, True))
1673 
1674  if self.loop: output.append(("Z",))
1675  return Path(output, **self.attr)
1676 
1677 ######################################################################
1678 
1679 class Poly:
1680  """Draws a curve specified by a sequence of points. The curve may be
1681  piecewise linear, like a polygon, or a Bezier curve.
1682 
1683  Poly(d, mode, loop, attribute=value)
1684 
1685  d required list of tuples representing points
1686  and possibly control points
1687  mode default="L" "lines", "bezier", "velocity",
1688  "foreback", "smooth", or an abbreviation
1689  loop default=False if True, connect the first and last
1690  point, closing the loop
1691  attribute=value pairs keyword list SVG attributes
1692 
1693  The format of the tuples in d depends on the mode.
1694 
1695  "lines"/"L" d=[(x,y), (x,y), ...]
1696  piecewise-linear segments joining the (x,y) points
1697  "bezier"/"B" d=[(x, y, c1x, c1y, c2x, c2y), ...]
1698  Bezier curve with two control points (control points
1699  preceed (x,y), as in SVG paths). If (c1x,c1y) and
1700  (c2x,c2y) both equal (x,y), you get a linear
1701  interpolation ("lines")
1702  "velocity"/"V" d=[(x, y, vx, vy), ...]
1703  curve that passes through (x,y) with velocity (vx,vy)
1704  (one unit of arclength per unit time); in other words,
1705  (vx,vy) is the tangent vector at (x,y). If (vx,vy) is
1706  (0,0), you get a linear interpolation ("lines").
1707  "foreback"/"F" d=[(x, y, bx, by, fx, fy), ...]
1708  like "velocity" except that there is a left derivative
1709  (bx,by) and a right derivative (fx,fy). If (bx,by)
1710  equals (fx,fy) (with no minus sign), you get a
1711  "velocity" curve
1712  "smooth"/"S" d=[(x,y), (x,y), ...]
1713  a "velocity" interpolation with (vx,vy)[i] equal to
1714  ((x,y)[i+1] - (x,y)[i-1])/2: the minimal derivative
1715  """
1716  defaults = {}
1717 
1718  def __repr__(self):
1719  return "<Poly (%d nodes) mode=%s loop=%s %s>" % (len(self.d), self.mode, repr(self.loop), self.attr)
1720 
1721  def __init__(self, d=[], mode="L", loop=False, **attr):
1722  self.d = list(d)
1723  self.mode = mode
1724  self.loop = loop
1725 
1726  self.attr = dict(self.defaults)
1727  self.attr.update(attr)
1728 
1729  def SVG(self, trans=None):
1730  """Apply the transformation "trans" and return an SVG object."""
1731  return self.Path(trans).SVG()
1732 
1733  def Path(self, trans=None, local=False):
1734  """Apply the transformation "trans" and return a Path object in
1735  global coordinates. If local=True, return a Path in local coordinates
1736  (which must be transformed again)."""
1737  if isinstance(trans, basestring): trans = totrans(trans)
1738 
1739  if self.mode[0] == "L" or self.mode[0] == "l": mode = "L"
1740  elif self.mode[0] == "B" or self.mode[0] == "b": mode = "B"
1741  elif self.mode[0] == "V" or self.mode[0] == "v": mode = "V"
1742  elif self.mode[0] == "F" or self.mode[0] == "f": mode = "F"
1743  elif self.mode[0] == "S" or self.mode[0] == "s":
1744  mode = "S"
1745 
1746  vx, vy = [0.]*len(self.d), [0.]*len(self.d)
1747  for i in xrange(len(self.d)):
1748  inext = (i+1) % len(self.d)
1749  iprev = (i-1) % len(self.d)
1750 
1751  vx[i] = (self.d[inext][0] - self.d[iprev][0])/2.
1752  vy[i] = (self.d[inext][1] - self.d[iprev][1])/2.
1753  if not self.loop and (i == 0 or i == len(self.d)-1):
1754  vx[i], vy[i] = 0., 0.
1755 
1756  else:
1757  raise ValueError, "mode must be \"lines\", \"bezier\", \"velocity\", \"foreback\", \"smooth\", or an abbreviation"
1758 
1759  d = []
1760  indexes = range(len(self.d))
1761  if self.loop and len(self.d) > 0: indexes.append(0)
1762 
1763  for i in indexes:
1764  inext = (i+1) % len(self.d)
1765  iprev = (i-1) % len(self.d)
1766 
1767  x, y = self.d[i][0], self.d[i][1]
1768 
1769  if trans == None: X, Y = x, y
1770  else: X, Y = trans(x, y)
1771 
1772  if d == []:
1773  if local: d.append(("M", x, y, False))
1774  else: d.append(("M", X, Y, True))
1775 
1776  elif mode == "L":
1777  if local: d.append(("L", x, y, False))
1778  else: d.append(("L", X, Y, True))
1779 
1780  elif mode == "B":
1781  c1x, c1y = self.d[i][2], self.d[i][3]
1782  if trans == None: C1X, C1Y = c1x, c1y
1783  else: C1X, C1Y = trans(c1x, c1y)
1784 
1785  c2x, c2y = self.d[i][4], self.d[i][5]
1786  if trans == None: C2X, C2Y = c2x, c2y
1787  else: C2X, C2Y = trans(c2x, c2y)
1788 
1789  if local: d.append(("C", c1x, c1y, False, c2x, c2y, False, x, y, False))
1790  else: d.append(("C", C1X, C1Y, True, C2X, C2Y, True, X, Y, True))
1791 
1792  elif mode == "V":
1793  c1x, c1y = self.d[iprev][2]/3. + self.d[iprev][0], self.d[iprev][3]/3. + self.d[iprev][1]
1794  c2x, c2y = self.d[i][2]/-3. + x, self.d[i][3]/-3. + y
1795 
1796  if trans == None: C1X, C1Y = c1x, c1y
1797  else: C1X, C1Y = trans(c1x, c1y)
1798  if trans == None: C2X, C2Y = c2x, c2y
1799  else: C2X, C2Y = trans(c2x, c2y)
1800 
1801  if local: d.append(("C", c1x, c1y, False, c2x, c2y, False, x, y, False))
1802  else: d.append(("C", C1X, C1Y, True, C2X, C2Y, True, X, Y, True))
1803 
1804  elif mode == "F":
1805  c1x, c1y = self.d[iprev][4]/3. + self.d[iprev][0], self.d[iprev][5]/3. + self.d[iprev][1]
1806  c2x, c2y = self.d[i][2]/-3. + x, self.d[i][3]/-3. + y
1807 
1808  if trans == None: C1X, C1Y = c1x, c1y
1809  else: C1X, C1Y = trans(c1x, c1y)
1810  if trans == None: C2X, C2Y = c2x, c2y
1811  else: C2X, C2Y = trans(c2x, c2y)
1812 
1813  if local: d.append(("C", c1x, c1y, False, c2x, c2y, False, x, y, False))
1814  else: d.append(("C", C1X, C1Y, True, C2X, C2Y, True, X, Y, True))
1815 
1816  elif mode == "S":
1817  c1x, c1y = vx[iprev]/3. + self.d[iprev][0], vy[iprev]/3. + self.d[iprev][1]
1818  c2x, c2y = vx[i]/-3. + x, vy[i]/-3. + y
1819 
1820  if trans == None: C1X, C1Y = c1x, c1y
1821  else: C1X, C1Y = trans(c1x, c1y)
1822  if trans == None: C2X, C2Y = c2x, c2y
1823  else: C2X, C2Y = trans(c2x, c2y)
1824 
1825  if local: d.append(("C", c1x, c1y, False, c2x, c2y, False, x, y, False))
1826  else: d.append(("C", C1X, C1Y, True, C2X, C2Y, True, X, Y, True))
1827 
1828  if self.loop and len(self.d) > 0: d.append(("Z",))
1829 
1830  return Path(d, **self.attr)
1831 
1832 ######################################################################
1833 
1834 class Text:
1835  """Draws at text string at a specified point in local coordinates.
1836 
1837  x, y required location of the point in local coordinates
1838  d required text/Unicode string
1839  attribute=value pairs keyword list SVG attributes
1840  """
1841 
1842  defaults = {"stroke":"none", "fill":"black", "font-size":5}
1843 
1844  def __repr__(self):
1845  return "<Text %s at (%g, %g) %s>" % (repr(self.d), self.x, self.y, self.attr)
1846 
1847  def __init__(self, x, y, d, **attr):
1848  self.x = x
1849  self.y = y
1850  self.d = str(d)
1851  self.attr = dict(self.defaults)
1852  self.attr.update(attr)
1853 
1854  def SVG(self, trans=None):
1855  """Apply the transformation "trans" and return an SVG object."""
1856  if isinstance(trans, basestring): trans = totrans(trans)
1857 
1858  X, Y = self.x, self.y
1859  if trans != None: X, Y = trans(X, Y)
1860  return SVG("text", self.d, x=X, y=Y, **self.attr)
1861 
1863  """Draws at text string at a specified point in global coordinates.
1864 
1865  x, y required location of the point in global coordinates
1866  d required text/Unicode string
1867  attribute=value pairs keyword list SVG attributes
1868  """
1869  defaults = {"stroke":"none", "fill":"black", "font-size":5}
1870 
1871  def __repr__(self):
1872  return "<TextGlobal %s at (%s, %s) %s>" % (repr(self.d), str(self.x), str(self.y), self.attr)
1873 
1874  def __init__(self, x, y, d, **attr):
1875  self.x = x
1876  self.y = y
1877  self.d = str(d)
1878  self.attr = dict(self.defaults)
1879  self.attr.update(attr)
1880 
1881  def SVG(self, trans=None):
1882  """Apply the transformation "trans" and return an SVG object."""
1883  return SVG("text", self.d, x=self.x, y=self.y, **self.attr)
1884 
1885 ######################################################################
1886 
1887 _symbol_templates = {"dot": SVG("symbol", SVG("circle", cx=0, cy=0, r=1, stroke="none", fill="black"), viewBox="0 0 1 1", overflow="visible"), \
1888  "box": SVG("symbol", SVG("rect", x1=-1, y1=-1, x2=1, y2=1, stroke="none", fill="black"), viewBox="0 0 1 1", overflow="visible"), \
1889  "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"), \
1890  "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"), \
1891  }
1892 
1893 def make_symbol(id, shape="dot", **attr):
1894  """Creates a new instance of an SVG symbol to avoid cross-linking objects.
1895 
1896  id required a new identifier (string/Unicode)
1897  shape default="dot" the shape name from _symbol_templates
1898  attribute=value list keyword list modify the SVG attributes of the new symbol
1899  """
1900  output = copy.deepcopy(_symbol_templates[shape])
1901  for i in output.sub: i.attr.update(attr_preprocess(attr))
1902  output["id"] = id
1903  return output
1904 
1905 _circular_dot = make_symbol("circular_dot")
1906 
1907 class Dots:
1908  """Dots draws SVG symbols at a set of points.
1909 
1910  d required list of (x,y) points
1911  symbol default=None SVG symbol or a new identifier to
1912  label an auto-generated symbol;
1913  if None, use pre-defined _circular_dot
1914  width, height default=1, 1 width and height of the symbols
1915  in SVG coordinates
1916  attribute=value pairs keyword list SVG attributes
1917  """
1918  defaults = {}
1919 
1920  def __repr__(self):
1921  return "<Dots (%d nodes) %s>" % (len(self.d), self.attr)
1922 
1923  def __init__(self, d=[], symbol=None, width=1., height=1., **attr):
1924  self.d = list(d)
1925  self.width = width
1926  self.height = height
1927 
1928  self.attr = dict(self.defaults)
1929  self.attr.update(attr)
1930 
1931  if symbol == None:
1932  self.symbol = _circular_dot
1933  elif isinstance(symbol, SVG):
1934  self.symbol = symbol
1935  else:
1936  self.symbol = make_symbol(symbol)
1937 
1938  def SVG(self, trans=None):
1939  """Apply the transformation "trans" and return an SVG object."""
1940  if isinstance(trans, basestring): trans = totrans(trans)
1941 
1942  output = SVG("g", SVG("defs", self.symbol))
1943  id = "#%s" % self.symbol["id"]
1944 
1945  for p in self.d:
1946  x, y = p[0], p[1]
1947 
1948  if trans == None: X, Y = x, y
1949  else: X, Y = trans(x, y)
1950 
1951  item = SVG("use", x=X, y=Y, xlink__href=id)
1952  if self.width != None: item["width"] = self.width
1953  if self.height != None: item["height"] = self.height
1954  output.append(item)
1955 
1956  return output
1957 
1958 ######################################################################
1959 
1960 _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"), \
1961  "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"), \
1962  }
1963 
1964 def make_marker(id, shape, **attr):
1965  """Creates a new instance of an SVG marker to avoid cross-linking objects.
1966 
1967  id required a new identifier (string/Unicode)
1968  shape required the shape name from _marker_templates
1969  attribute=value list keyword list modify the SVG attributes of the new marker
1970  """
1971  output = copy.deepcopy(_marker_templates[shape])
1972  for i in output.sub: i.attr.update(attr_preprocess(attr))
1973  output["id"] = id
1974  return output
1975 
1976 class Line(Curve):
1977  """Draws a line between two points.
1978 
1979  Line(x1, y1, x2, y2, arrow_start, arrow_end, attribute=value)
1980 
1981  x1, y1 required the starting point
1982  x2, y2 required the ending point
1983  arrow_start default=None if an identifier string/Unicode,
1984  draw a new arrow object at the
1985  beginning of the line; if a marker,
1986  draw that marker instead
1987  arrow_end default=None same for the end of the line
1988  attribute=value pairs keyword list SVG attributes
1989  """
1990  defaults = {}
1991 
1992  def __repr__(self):
1993  return "<Line (%g, %g) to (%g, %g) %s>" % (self.x1, self.y1, self.x2, self.y2, self.attr)
1994 
1995  def __init__(self, x1, y1, x2, y2, arrow_start=None, arrow_end=None, **attr):
1996  self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2
1997  self.arrow_start, self.arrow_end = arrow_start, arrow_end
1998 
1999  self.attr = dict(self.defaults)
2000  self.attr.update(attr)
2001 
2002  def SVG(self, trans=None):
2003  """Apply the transformation "trans" and return an SVG object."""
2004 
2005  line = self.Path(trans).SVG()
2006 
2007  if (self.arrow_start != False and self.arrow_start != None) or (self.arrow_end != False and self.arrow_end != None):
2008  defs = SVG("defs")
2009 
2010  if self.arrow_start != False and self.arrow_start != None:
2011  if isinstance(self.arrow_start, SVG):
2012  defs.append(self.arrow_start)
2013  line.attr["marker-start"] = "url(#%s)" % self.arrow_start["id"]
2014  elif isinstance(self.arrow_start, basestring):
2015  defs.append(make_marker(self.arrow_start, "arrow_start"))
2016  line.attr["marker-start"] = "url(#%s)" % self.arrow_start
2017  else:
2018  raise TypeError, "arrow_start must be False/None or an id string for the new marker"
2019 
2020  if self.arrow_end != False and self.arrow_end != None:
2021  if isinstance(self.arrow_end, SVG):
2022  defs.append(self.arrow_end)
2023  line.attr["marker-end"] = "url(#%s)" % self.arrow_end["id"]
2024  elif isinstance(self.arrow_end, basestring):
2025  defs.append(make_marker(self.arrow_end, "arrow_end"))
2026  line.attr["marker-end"] = "url(#%s)" % self.arrow_end
2027  else:
2028  raise TypeError, "arrow_end must be False/None or an id string for the new marker"
2029 
2030  return SVG("g", defs, line)
2031 
2032  return line
2033 
2034  def Path(self, trans=None, local=False):
2035  """Apply the transformation "trans" and return a Path object in
2036  global coordinates. If local=True, return a Path in local coordinates
2037  (which must be transformed again)."""
2038  self.f = lambda t: (self.x1 + t*(self.x2 - self.x1), self.y1 + t*(self.y2 - self.y1))
2039  self.low = 0.
2040  self.high = 1.
2041  self.loop = False
2042 
2043  if trans == None:
2044  return Path([("M", self.x1, self.y1, not local), ("L", self.x2, self.y2, not local)], **self.attr)
2045  else:
2046  return Curve.Path(self, trans, local)
2047 
2049  """Draws a line between two points, one or both of which is in
2050  global coordinates.
2051 
2052  Line(x1, y1, x2, y2, lcoal1, local2, arrow_start, arrow_end, attribute=value)
2053 
2054  x1, y1 required the starting point
2055  x2, y2 required the ending point
2056  local1 default=False if True, interpret first point as a
2057  local coordinate (apply transform)
2058  local2 default=False if True, interpret second point as a
2059  local coordinate (apply transform)
2060  arrow_start default=None if an identifier string/Unicode,
2061  draw a new arrow object at the
2062  beginning of the line; if a marker,
2063  draw that marker instead
2064  arrow_end default=None same for the end of the line
2065  attribute=value pairs keyword list SVG attributes
2066  """
2067  defaults = {}
2068 
2069  def __repr__(self):
2070  local1, local2 = "", ""
2071  if self.local1: local1 = "L"
2072  if self.local2: local2 = "L"
2073 
2074  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)
2075 
2076  def __init__(self, x1, y1, x2, y2, local1=False, local2=False, arrow_start=None, arrow_end=None, **attr):
2077  self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2
2078  self.local1, self.local2 = local1, local2
2079  self.arrow_start, self.arrow_end = arrow_start, arrow_end
2080 
2081  self.attr = dict(self.defaults)
2082  self.attr.update(attr)
2083 
2084  def SVG(self, trans=None):
2085  """Apply the transformation "trans" and return an SVG object."""
2086  if isinstance(trans, basestring): trans = totrans(trans)
2087 
2088  X1, Y1, X2, Y2 = self.x1, self.y1, self.x2, self.y2
2089 
2090  if self.local1: X1, Y1 = trans(X1, Y1)
2091  if self.local2: X2, Y2 = trans(X2, Y2)
2092 
2093  line = SVG("path", d="M%s %s L%s %s" % (X1, Y1, X2, Y2), **self.attr)
2094 
2095  if (self.arrow_start != False and self.arrow_start != None) or (self.arrow_end != False and self.arrow_end != None):
2096  defs = SVG("defs")
2097 
2098  if self.arrow_start != False and self.arrow_start != None:
2099  if isinstance(self.arrow_start, SVG):
2100  defs.append(self.arrow_start)
2101  line.attr["marker-start"] = "url(#%s)" % self.arrow_start["id"]
2102  elif isinstance(self.arrow_start, basestring):
2103  defs.append(make_marker(self.arrow_start, "arrow_start"))
2104  line.attr["marker-start"] = "url(#%s)" % self.arrow_start
2105  else:
2106  raise TypeError, "arrow_start must be False/None or an id string for the new marker"
2107 
2108  if self.arrow_end != False and self.arrow_end != None:
2109  if isinstance(self.arrow_end, SVG):
2110  defs.append(self.arrow_end)
2111  line.attr["marker-end"] = "url(#%s)" % self.arrow_end["id"]
2112  elif isinstance(self.arrow_end, basestring):
2113  defs.append(make_marker(self.arrow_end, "arrow_end"))
2114  line.attr["marker-end"] = "url(#%s)" % self.arrow_end
2115  else:
2116  raise TypeError, "arrow_end must be False/None or an id string for the new marker"
2117 
2118  return SVG("g", defs, line)
2119 
2120  return line
2121 
2122 class VLine(Line):
2123  """Draws a vertical line.
2124 
2125  VLine(y1, y2, x, attribute=value)
2126 
2127  y1, y2 required y range
2128  x required x position
2129  attribute=value pairs keyword list SVG attributes
2130  """
2131  defaults = {}
2132 
2133  def __repr__(self):
2134  return "<VLine (%g, %g) at x=%s %s>" % (self.y1, self.y2, self.x, self.attr)
2135 
2136  def __init__(self, y1, y2, x, **attr):
2137  self.x = x
2138  self.attr = dict(self.defaults)
2139  self.attr.update(attr)
2140  Line.__init__(self, x, y1, x, y2, **self.attr)
2141 
2142  def Path(self, trans=None, local=False):
2143  """Apply the transformation "trans" and return a Path object in
2144  global coordinates. If local=True, return a Path in local coordinates
2145  (which must be transformed again)."""
2146  self.x1 = self.x
2147  self.x2 = self.x
2148  return Line.Path(self, trans, local)
2149 
2150 class HLine(Line):
2151  """Draws a horizontal line.
2152 
2153  HLine(x1, x2, y, attribute=value)
2154 
2155  x1, x2 required x range
2156  y required y position
2157  attribute=value pairs keyword list SVG attributes
2158  """
2159  defaults = {}
2160 
2161  def __repr__(self):
2162  return "<HLine (%g, %g) at y=%s %s>" % (self.x1, self.x2, self.y, self.attr)
2163 
2164  def __init__(self, x1, x2, y, **attr):
2165  self.y = y
2166  self.attr = dict(self.defaults)
2167  self.attr.update(attr)
2168  Line.__init__(self, x1, y, x2, y, **self.attr)
2169 
2170  def Path(self, trans=None, local=False):
2171  """Apply the transformation "trans" and return a Path object in
2172  global coordinates. If local=True, return a Path in local coordinates
2173  (which must be transformed again)."""
2174  self.y1 = self.y
2175  self.y2 = self.y
2176  return Line.Path(self, trans, local)
2177 
2178 ######################################################################
2179 
2180 class Rect(Curve):
2181  """Draws a rectangle.
2182 
2183  Rect(x1, y1, x2, y2, attribute=value)
2184 
2185  x1, y1 required the starting point
2186  x2, y2 required the ending point
2187  attribute=value pairs keyword list SVG attributes
2188  """
2189  defaults = {}
2190 
2191  def __repr__(self):
2192  return "<Rect (%g, %g), (%g, %g) %s>" % (self.x1, self.y1, self.x2, self.y2, self.attr)
2193 
2194  def __init__(self, x1, y1, x2, y2, **attr):
2195  self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2
2196 
2197  self.attr = dict(self.defaults)
2198  self.attr.update(attr)
2199 
2200  def SVG(self, trans=None):
2201  """Apply the transformation "trans" and return an SVG object."""
2202  return self.Path(trans).SVG()
2203 
2204  def Path(self, trans=None, local=False):
2205  """Apply the transformation "trans" and return a Path object in
2206  global coordinates. If local=True, return a Path in local coordinates
2207  (which must be transformed again)."""
2208  if trans == None:
2209  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)
2210 
2211  else:
2212  self.low = 0.
2213  self.high = 1.
2214  self.loop = False
2215 
2216  self.f = lambda t: (self.x1 + t*(self.x2 - self.x1), self.y1)
2217  d1 = Curve.Path(self, trans, local).d
2218 
2219  self.f = lambda t: (self.x2, self.y1 + t*(self.y2 - self.y1))
2220  d2 = Curve.Path(self, trans, local).d
2221  del d2[0]
2222 
2223  self.f = lambda t: (self.x2 + t*(self.x1 - self.x2), self.y2)
2224  d3 = Curve.Path(self, trans, local).d
2225  del d3[0]
2226 
2227  self.f = lambda t: (self.x1, self.y2 + t*(self.y1 - self.y2))
2228  d4 = Curve.Path(self, trans, local).d
2229  del d4[0]
2230 
2231  return Path(d=(d1 + d2 + d3 + d4 + [("Z",)]), **self.attr)
2232 
2233 ######################################################################
2234 
2236  """Draws an ellipse from a semimajor vector (ax,ay) and a semiminor
2237  length (b).
2238 
2239  Ellipse(x, y, ax, ay, b, attribute=value)
2240 
2241  x, y required the center of the ellipse/circle
2242  ax, ay required a vector indicating the length
2243  and direction of the semimajor axis
2244  b required the length of the semiminor axis.
2245  If equal to sqrt(ax2 + ay2), the
2246  ellipse is a circle
2247  attribute=value pairs keyword list SVG attributes
2248 
2249  (If sqrt(ax**2 + ay**2) is less than b, then (ax,ay) is actually the
2250  semiminor axis.)
2251  """
2252  defaults = {}
2253 
2254  def __repr__(self):
2255  return "<Ellipse (%g, %g) a=(%g, %g), b=%g %s>" % (self.x, self.y, self.ax, self.ay, self.b, self.attr)
2256 
2257  def __init__(self, x, y, ax, ay, b, **attr):
2258  self.x, self.y, self.ax, self.ay, self.b = x, y, ax, ay, b
2259 
2260  self.attr = dict(self.defaults)
2261  self.attr.update(attr)
2262 
2263  def SVG(self, trans=None):
2264  """Apply the transformation "trans" and return an SVG object."""
2265  return self.Path(trans).SVG()
2266 
2267  def Path(self, trans=None, local=False):
2268  """Apply the transformation "trans" and return a Path object in
2269  global coordinates. If local=True, return a Path in local coordinates
2270  (which must be transformed again)."""
2271  angle = math.atan2(self.ay, self.ax) + math.pi/2.
2272  bx = self.b * math.cos(angle)
2273  by = self.b * math.sin(angle)
2274 
2275  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))
2276  self.low = -math.pi
2277  self.high = math.pi
2278  self.loop = True
2279  return Curve.Path(self, trans, local)
2280 
2281 ######################################################################
2282 
2283 def unumber(x):
2284  """Converts numbers to a Unicode string, taking advantage of special
2285  Unicode characters to make nice minus signs and scientific notation.
2286  """
2287  output = u"%g" % x
2288 
2289  if output[0] == u"-":
2290  output = u"\u2013" + output[1:]
2291 
2292  index = output.find(u"e")
2293  if index != -1:
2294  uniout = unicode(output[:index]) + u"\u00d710"
2295  saw_nonzero = False
2296  for n in output[index+1:]:
2297  if n == u"+": pass # uniout += u"\u207a"
2298  elif n == u"-": uniout += u"\u207b"
2299  elif n == u"0":
2300  if saw_nonzero: uniout += u"\u2070"
2301  elif n == u"1":
2302  saw_nonzero = True
2303  uniout += u"\u00b9"
2304  elif n == u"2":
2305  saw_nonzero = True
2306  uniout += u"\u00b2"
2307  elif n == u"3":
2308  saw_nonzero = True
2309  uniout += u"\u00b3"
2310  elif u"4" <= n <= u"9":
2311  saw_nonzero = True
2312  if saw_nonzero: uniout += eval("u\"\\u%x\"" % (0x2070 + ord(n) - ord(u"0")))
2313  else: uniout += n
2314 
2315  if uniout[:2] == u"1\u00d7": uniout = uniout[2:]
2316  return uniout
2317 
2318  return output
2319 
2320 class Ticks:
2321  """Superclass for all graphics primatives that draw ticks,
2322  miniticks, and tick labels. This class only draws the ticks.
2323 
2324  Ticks(f, low, high, ticks, miniticks, labels, logbase, arrow_start,
2325  arrow_end, text_attr, attribute=value)
2326 
2327  f required parametric function along which ticks
2328  will be drawn; has the same format as
2329  the function used in Curve
2330  low, high required range of the independent variable
2331  ticks default=-10 request ticks according to the standard
2332  tick specification (see below)
2333  miniticks default=True request miniticks according to the
2334  standard minitick specification (below)
2335  labels True request tick labels according to the
2336  standard tick label specification (below)
2337  logbase default=None if a number, the axis is logarithmic with
2338  ticks at the given base (usually 10)
2339  arrow_start default=None if a new string identifier, draw an arrow
2340  at the low-end of the axis, referenced by
2341  that identifier; if an SVG marker object,
2342  use that marker
2343  arrow_end default=None if a new string identifier, draw an arrow
2344  at the high-end of the axis, referenced by
2345  that identifier; if an SVG marker object,
2346  use that marker
2347  text_attr default={} SVG attributes for the text labels
2348  attribute=value pairs keyword list SVG attributes for the tick marks
2349 
2350  Standard tick specification:
2351 
2352  * True: same as -10 (below).
2353  * Positive number N: draw exactly N ticks, including the endpoints. To
2354  subdivide an axis into 10 equal-sized segments, ask for 11 ticks.
2355  * Negative number -N: draw at least N ticks. Ticks will be chosen with
2356  "natural" values, multiples of 2 or 5.
2357  * List of values: draw a tick mark at each value.
2358  * Dict of value, label pairs: draw a tick mark at each value, labeling
2359  it with the given string. This lets you say things like {3.14159: "pi"}.
2360  * False or None: no ticks.
2361 
2362  Standard minitick specification:
2363 
2364  * True: draw miniticks with "natural" values, more closely spaced than
2365  the ticks.
2366  * Positive number N: draw exactly N miniticks, including the endpoints.
2367  To subdivide an axis into 100 equal-sized segments, ask for 101 miniticks.
2368  * Negative number -N: draw at least N miniticks.
2369  * List of values: draw a minitick mark at each value.
2370  * False or None: no miniticks.
2371 
2372  Standard tick label specification:
2373 
2374  * True: use the unumber function (described below)
2375  * Format string: standard format strings, e.g. "%5.2f" for 12.34
2376  * Python callable: function that converts numbers to strings
2377  * False or None: no labels
2378  """
2379  defaults = {"stroke-width":"0.25pt"}
2380  text_defaults = {"stroke":"none", "fill":"black", "font-size":5}
2381  tick_start = -1.5
2382  tick_end = 1.5
2383  minitick_start = -0.75
2384  minitick_end = 0.75
2385  text_start = 2.5
2386  text_angle = 0.
2387 
2388  def __repr__(self):
2389  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)
2390 
2391  def __init__(self, f, low, high, ticks=-10, miniticks=True, labels=True, logbase=None, arrow_start=None, arrow_end=None, text_attr={}, **attr):
2392  self.f = f
2393  self.low = low
2394  self.high = high
2395  self.ticks = ticks
2396  self.miniticks = miniticks
2397  self.labels = labels
2398  self.logbase = logbase
2399  self.arrow_start = arrow_start
2400  self.arrow_end = arrow_end
2401 
2402  self.attr = dict(self.defaults)
2403  self.attr.update(attr)
2404 
2406  self.text_attr.update(text_attr)
2407 
2408  def orient_tickmark(self, t, trans=None):
2409  """Return the position, normalized local x vector, normalized
2410  local y vector, and angle of a tick at position t.
2411 
2412  Normally only used internally.
2413  """
2414  if isinstance(trans, basestring): trans = totrans(trans)
2415  if trans == None:
2416  f = self.f
2417  else:
2418  f = lambda t: trans(*self.f(t))
2419 
2420  eps = _epsilon * abs(self.high - self.low)
2421 
2422  X, Y = f(t)
2423  Xprime, Yprime = f(t + eps)
2424  xhatx, xhaty = (Xprime - X)/eps, (Yprime - Y)/eps
2425 
2426  norm = math.sqrt(xhatx**2 + xhaty**2)
2427  if norm != 0: xhatx, xhaty = xhatx/norm, xhaty/norm
2428  else: xhatx, xhaty = 1., 0.
2429 
2430  angle = math.atan2(xhaty, xhatx) + math.pi/2.
2431  yhatx, yhaty = math.cos(angle), math.sin(angle)
2432 
2433  return (X, Y), (xhatx, xhaty), (yhatx, yhaty), angle
2434 
2435  def SVG(self, trans=None):
2436  """Apply the transformation "trans" and return an SVG object."""
2437  if isinstance(trans, basestring): trans = totrans(trans)
2438 
2439  self.last_ticks, self.last_miniticks = self.interpret()
2440  tickmarks = Path([], **self.attr)
2441  minitickmarks = Path([], **self.attr)
2442  output = SVG("g")
2443 
2444  if (self.arrow_start != False and self.arrow_start != None) or (self.arrow_end != False and self.arrow_end != None):
2445  defs = SVG("defs")
2446 
2447  if self.arrow_start != False and self.arrow_start != None:
2448  if isinstance(self.arrow_start, SVG):
2449  defs.append(self.arrow_start)
2450  elif isinstance(self.arrow_start, basestring):
2451  defs.append(make_marker(self.arrow_start, "arrow_start"))
2452  else:
2453  raise TypeError, "arrow_start must be False/None or an id string for the new marker"
2454 
2455  if self.arrow_end != False and self.arrow_end != None:
2456  if isinstance(self.arrow_end, SVG):
2457  defs.append(self.arrow_end)
2458  elif isinstance(self.arrow_end, basestring):
2459  defs.append(make_marker(self.arrow_end, "arrow_end"))
2460  else:
2461  raise TypeError, "arrow_end must be False/None or an id string for the new marker"
2462 
2463  output.append(defs)
2464 
2465  eps = _epsilon * (self.high - self.low)
2466 
2467  for t, label in self.last_ticks.items():
2468  (X, Y), (xhatx, xhaty), (yhatx, yhaty), angle = self.orient_tickmark(t, trans)
2469 
2470  if (not self.arrow_start or abs(t - self.low) > eps) and (not self.arrow_end or abs(t - self.high) > eps):
2471  tickmarks.d.append(("M", X - yhatx*self.tick_start, Y - yhaty*self.tick_start, True))
2472  tickmarks.d.append(("L", X - yhatx*self.tick_end, Y - yhaty*self.tick_end, True))
2473 
2474  angle = (angle - math.pi/2.)*180./math.pi + self.text_angle
2475 
2476  ########### a HACK! ############ (to be removed when Inkscape handles baselines)
2477  if _hacks["inkscape-text-vertical-shift"]:
2478  if self.text_start > 0:
2479  X += math.cos(angle*math.pi/180. + math.pi/2.) * 2.
2480  Y += math.sin(angle*math.pi/180. + math.pi/2.) * 2.
2481  else:
2482  X += math.cos(angle*math.pi/180. + math.pi/2.) * 2. * 2.5
2483  Y += math.sin(angle*math.pi/180. + math.pi/2.) * 2. * 2.5
2484  ########### end hack ###########
2485 
2486  if label != "":
2487  output.append(SVG("text", label, transform="translate(%g, %g) rotate(%g)" % \
2488  (X - yhatx*self.text_start, Y - yhaty*self.text_start, angle), **self.text_attr))
2489 
2490  for t in self.last_miniticks:
2491  skip = False
2492  for tt in self.last_ticks.keys():
2493  if abs(t - tt) < eps:
2494  skip = True
2495  break
2496  if not skip:
2497  (X, Y), (xhatx, xhaty), (yhatx, yhaty), angle = self.orient_tickmark(t, trans)
2498 
2499  if (not self.arrow_start or abs(t - self.low) > eps) and (not self.arrow_end or abs(t - self.high) > eps):
2500  minitickmarks.d.append(("M", X - yhatx*self.minitick_start, Y - yhaty*self.minitick_start, True))
2501  minitickmarks.d.append(("L", X - yhatx*self.minitick_end, Y - yhaty*self.minitick_end, True))
2502 
2503  output.prepend(tickmarks.SVG(trans))
2504  output.prepend(minitickmarks.SVG(trans))
2505  return output
2506 
2507  def interpret(self):
2508  """Evaluate and return optimal ticks and miniticks according to
2509  the standard minitick specification.
2510 
2511  Normally only used internally.
2512  """
2513 
2514  if self.labels == None or self.labels == False:
2515  format = lambda x: ""
2516 
2517  elif self.labels == True:
2518  format = unumber
2519 
2520  elif isinstance(self.labels, basestring):
2521  format = lambda x: (self.labels % x)
2522 
2523  elif callable(self.labels):
2524  format = self.labels
2525 
2526  else: raise TypeError, "labels must be None/False, True, a format string, or a number->string function"
2527 
2528  # Now for the ticks
2529  ticks = self.ticks
2530 
2531  # Case 1: ticks is None/False
2532  if ticks == None or ticks == False: return {}, []
2533 
2534  # Case 2: ticks is the number of desired ticks
2535  elif isinstance(ticks, (int, long)):
2536  if ticks == True: ticks = -10
2537 
2538  if self.logbase == None:
2539  ticks = self.compute_ticks(ticks, format)
2540  else:
2541  ticks = self.compute_logticks(self.logbase, ticks, format)
2542 
2543  # Now for the miniticks
2544  if self.miniticks == True:
2545  if self.logbase == None:
2546  return ticks, self.compute_miniticks(ticks)
2547  else:
2548  return ticks, self.compute_logminiticks(self.logbase)
2549 
2550  elif isinstance(self.miniticks, (int, long)):
2551  return ticks, self.regular_miniticks(self.miniticks)
2552 
2553  elif getattr(self.miniticks, "__iter__", False):
2554  return ticks, self.miniticks
2555 
2556  elif self.miniticks == False or self.miniticks == None:
2557  return ticks, []
2558 
2559  else:
2560  raise TypeError, "miniticks must be None/False, True, a number of desired miniticks, or a list of numbers"
2561 
2562  # Cases 3 & 4: ticks is iterable
2563  elif getattr(ticks, "__iter__", False):
2564 
2565  # Case 3: ticks is some kind of list
2566  if not isinstance(ticks, dict):
2567  output = {}
2568  eps = _epsilon * (self.high - self.low)
2569  for x in ticks:
2570  if format == unumber and abs(x) < eps:
2571  output[x] = u"0"
2572  else:
2573  output[x] = format(x)
2574  ticks = output
2575 
2576  # Case 4: ticks is a dict
2577  else: pass
2578 
2579  # Now for the miniticks
2580  if self.miniticks == True:
2581  if self.logbase == None:
2582  return ticks, self.compute_miniticks(ticks)
2583  else:
2584  return ticks, self.compute_logminiticks(self.logbase)
2585 
2586  elif isinstance(self.miniticks, (int, long)):
2587  return ticks, self.regular_miniticks(self.miniticks)
2588 
2589  elif getattr(self.miniticks, "__iter__", False):
2590  return ticks, self.miniticks
2591 
2592  elif self.miniticks == False or self.miniticks == None:
2593  return ticks, []
2594 
2595  else:
2596  raise TypeError, "miniticks must be None/False, True, a number of desired miniticks, or a list of numbers"
2597 
2598  else:
2599  raise TypeError, "ticks must be None/False, a number of desired ticks, a list of numbers, or a dictionary of explicit markers"
2600 
2601  def compute_ticks(self, N, format):
2602  """Return less than -N or exactly N optimal linear ticks.
2603 
2604  Normally only used internally.
2605  """
2606  if self.low >= self.high: raise ValueError, "low must be less than high"
2607  if N == 1: raise ValueError, "N can be 0 or >1 to specify the exact number of ticks or negative to specify a maximum"
2608 
2609  eps = _epsilon * (self.high - self.low)
2610 
2611  if N >= 0:
2612  output = {}
2613  x = self.low
2614  for i in xrange(N):
2615  if format == unumber and abs(x) < eps: label = u"0"
2616  else: label = format(x)
2617  output[x] = label
2618  x += (self.high - self.low)/(N-1.)
2619  return output
2620 
2621  N = -N
2622 
2623  counter = 0
2624  granularity = 10**math.ceil(math.log10(max(abs(self.low), abs(self.high))))
2625  lowN = math.ceil(1.*self.low / granularity)
2626  highN = math.floor(1.*self.high / granularity)
2627 
2628  while (lowN > highN):
2629  countermod3 = counter % 3
2630  if countermod3 == 0: granularity *= 0.5
2631  elif countermod3 == 1: granularity *= 0.4
2632  else: granularity *= 0.5
2633  counter += 1
2634  lowN = math.ceil(1.*self.low / granularity)
2635  highN = math.floor(1.*self.high / granularity)
2636 
2637  last_granularity = granularity
2638  last_trial = None
2639 
2640  while True:
2641  trial = {}
2642  for n in range(int(lowN), int(highN)+1):
2643  x = n * granularity
2644  if format == unumber and abs(x) < eps: label = u"0"
2645  else: label = format(x)
2646  trial[x] = label
2647 
2648  if int(highN)+1 - int(lowN) >= N:
2649  if last_trial == None:
2650  v1, v2 = self.low, self.high
2651  return {v1: format(v1), v2: format(v2)}
2652  else:
2653  low_in_ticks, high_in_ticks = False, False
2654  for t in last_trial.keys():
2655  if 1.*abs(t - self.low)/last_granularity < _epsilon: low_in_ticks = True
2656  if 1.*abs(t - self.high)/last_granularity < _epsilon: high_in_ticks = True
2657 
2658  lowN = 1.*self.low / last_granularity
2659  highN = 1.*self.high / last_granularity
2660  if abs(lowN - round(lowN)) < _epsilon and not low_in_ticks:
2661  last_trial[self.low] = format(self.low)
2662  if abs(highN - round(highN)) < _epsilon and not high_in_ticks:
2663  last_trial[self.high] = format(self.high)
2664  return last_trial
2665 
2666  last_granularity = granularity
2667  last_trial = trial
2668 
2669  countermod3 = counter % 3
2670  if countermod3 == 0: granularity *= 0.5
2671  elif countermod3 == 1: granularity *= 0.4
2672  else: granularity *= 0.5
2673  counter += 1
2674  lowN = math.ceil(1.*self.low / granularity)
2675  highN = math.floor(1.*self.high / granularity)
2676 
2677  def regular_miniticks(self, N):
2678  """Return exactly N linear ticks.
2679 
2680  Normally only used internally.
2681  """
2682  output = []
2683  x = self.low
2684  for i in xrange(N):
2685  output.append(x)
2686  x += (self.high - self.low)/(N-1.)
2687  return output
2688 
2689  def compute_miniticks(self, original_ticks):
2690  """Return optimal linear miniticks, given a set of ticks.
2691 
2692  Normally only used internally.
2693  """
2694  if len(original_ticks) < 2: original_ticks = ticks(self.low, self.high)
2695  original_ticks = original_ticks.keys()
2696  original_ticks.sort()
2697 
2698  if self.low > original_ticks[0] + _epsilon or self.high < original_ticks[-1] - _epsilon:
2699  raise ValueError, "original_ticks {%g...%g} extend beyond [%g, %g]" % (original_ticks[0], original_ticks[-1], self.low, self.high)
2700 
2701  granularities = []
2702  for i in range(len(original_ticks)-1):
2703  granularities.append(original_ticks[i+1] - original_ticks[i])
2704  spacing = 10**(math.ceil(math.log10(min(granularities)) - 1))
2705 
2706  output = []
2707  x = original_ticks[0] - math.ceil(1.*(original_ticks[0] - self.low) / spacing) * spacing
2708 
2709  while x <= self.high:
2710  if x >= self.low:
2711  already_in_ticks = False
2712  for t in original_ticks:
2713  if abs(x-t) < _epsilon * (self.high - self.low): already_in_ticks = True
2714  if not already_in_ticks: output.append(x)
2715  x += spacing
2716  return output
2717 
2718  def compute_logticks(self, base, N, format):
2719  """Return less than -N or exactly N optimal logarithmic ticks.
2720 
2721  Normally only used internally.
2722  """
2723  if self.low >= self.high: raise ValueError, "low must be less than high"
2724  if N == 1: raise ValueError, "N can be 0 or >1 to specify the exact number of ticks or negative to specify a maximum"
2725 
2726  eps = _epsilon * (self.high - self.low)
2727 
2728  if N >= 0:
2729  output = {}
2730  x = self.low
2731  for i in xrange(N):
2732  if format == unumber and abs(x) < eps: label = u"0"
2733  else: label = format(x)
2734  output[x] = label
2735  x += (self.high - self.low)/(N-1.)
2736  return output
2737 
2738  N = -N
2739 
2740  lowN = math.floor(math.log(self.low, base))
2741  highN = math.ceil(math.log(self.high, base))
2742  output = {}
2743  for n in range(int(lowN), int(highN)+1):
2744  x = base**n
2745  label = format(x)
2746  if self.low <= x <= self.high: output[x] = label
2747 
2748  for i in range(1, len(output)):
2749  keys = output.keys()
2750  keys.sort()
2751  keys = keys[::i]
2752  values = map(lambda k: output[k], keys)
2753  if len(values) <= N:
2754  for k in output.keys():
2755  if k not in keys:
2756  output[k] = ""
2757  break
2758 
2759  if len(output) <= 2:
2760  output2 = self.compute_ticks(N=-int(math.ceil(N/2.)), format=format)
2761  lowest = min(output2)
2762 
2763  for k in output:
2764  if k < lowest: output2[k] = output[k]
2765  output = output2
2766 
2767  return output
2768 
2769  def compute_logminiticks(self, base):
2770  """Return optimal logarithmic miniticks, given a set of ticks.
2771 
2772  Normally only used internally.
2773  """
2774  if self.low >= self.high: raise ValueError, "low must be less than high"
2775 
2776  lowN = math.floor(math.log(self.low, base))
2777  highN = math.ceil(math.log(self.high, base))
2778  output = []
2779  num_ticks = 0
2780  for n in range(int(lowN), int(highN)+1):
2781  x = base**n
2782  if self.low <= x <= self.high: num_ticks += 1
2783  for m in range(2, int(math.ceil(base))):
2784  minix = m * x
2785  if self.low <= minix <= self.high: output.append(minix)
2786 
2787  if num_ticks <= 2: return []
2788  else: return output
2789 
2790 ######################################################################
2791 
2793  """Draw an axis with tick marks along a parametric curve.
2794 
2795  CurveAxis(f, low, high, ticks, miniticks, labels, logbase, arrow_start, arrow_end,
2796  text_attr, attribute=value)
2797 
2798  f required a Python callable or string in
2799  the form "f(t), g(t)", just like Curve
2800  low, high required left and right endpoints
2801  ticks default=-10 request ticks according to the standard
2802  tick specification (see help(Ticks))
2803  miniticks default=True request miniticks according to the
2804  standard minitick specification
2805  labels True request tick labels according to the
2806  standard tick label specification
2807  logbase default=None if a number, the x axis is logarithmic
2808  with ticks at the given base (10 being
2809  the most common)
2810  arrow_start default=None if a new string identifier, draw an
2811  arrow at the low-end of the axis,
2812  referenced by that identifier; if an
2813  SVG marker object, use that marker
2814  arrow_end default=None if a new string identifier, draw an
2815  arrow at the high-end of the axis,
2816  referenced by that identifier; if an
2817  SVG marker object, use that marker
2818  text_attr default={} SVG attributes for the text labels
2819  attribute=value pairs keyword list SVG attributes
2820  """
2821  defaults = {"stroke-width":"0.25pt"}
2822  text_defaults = {"stroke":"none", "fill":"black", "font-size":5}
2823 
2824  def __repr__(self):
2825  return "<CurveAxis %s [%s, %s] ticks=%s labels=%s %s>" % (self.f, self.low, self.high, str(self.ticks), str(self.labels), self.attr)
2826 
2827  def __init__(self, f, low, high, ticks=-10, miniticks=True, labels=True, logbase=None, arrow_start=None, arrow_end=None, text_attr={}, **attr):
2828  tattr = dict(self.text_defaults)
2829  tattr.update(text_attr)
2830  Curve.__init__(self, f, low, high)
2831  Ticks.__init__(self, f, low, high, ticks, miniticks, labels, logbase, arrow_start, arrow_end, tattr, **attr)
2832 
2833  def SVG(self, trans=None):
2834  """Apply the transformation "trans" and return an SVG object."""
2835  func = Curve.SVG(self, trans)
2836  ticks = Ticks.SVG(self, trans) # returns a <g />
2837 
2838  if self.arrow_start != False and self.arrow_start != None:
2839  if isinstance(self.arrow_start, basestring):
2840  func.attr["marker-start"] = "url(#%s)" % self.arrow_start
2841  else:
2842  func.attr["marker-start"] = "url(#%s)" % self.arrow_start.id
2843 
2844  if self.arrow_end != False and self.arrow_end != None:
2845  if isinstance(self.arrow_end, basestring):
2846  func.attr["marker-end"] = "url(#%s)" % self.arrow_end
2847  else:
2848  func.attr["marker-end"] = "url(#%s)" % self.arrow_end.id
2849 
2850  ticks.append(func)
2851  return ticks
2852 
2854  """Draws an axis with tick marks along a line.
2855 
2856  LineAxis(x1, y1, x2, y2, start, end, ticks, miniticks, labels, logbase,
2857  arrow_start, arrow_end, text_attr, attribute=value)
2858 
2859  x1, y1 required starting point
2860  x2, y2 required ending point
2861  start, end default=0, 1 values to start and end labeling
2862  ticks default=-10 request ticks according to the standard
2863  tick specification (see help(Ticks))
2864  miniticks default=True request miniticks according to the
2865  standard minitick specification
2866  labels True request tick labels according to the
2867  standard tick label specification
2868  logbase default=None if a number, the x axis is logarithmic
2869  with ticks at the given base (usually 10)
2870  arrow_start default=None if a new string identifier, draw an arrow
2871  at the low-end of the axis, referenced by
2872  that identifier; if an SVG marker object,
2873  use that marker
2874  arrow_end default=None if a new string identifier, draw an arrow
2875  at the high-end of the axis, referenced by
2876  that identifier; if an SVG marker object,
2877  use that marker
2878  text_attr default={} SVG attributes for the text labels
2879  attribute=value pairs keyword list SVG attributes
2880  """
2881  defaults = {"stroke-width":"0.25pt"}
2882  text_defaults = {"stroke":"none", "fill":"black", "font-size":5}
2883 
2884  def __repr__(self):
2885  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)
2886 
2887  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):
2888  self.start = start
2889  self.end = end
2890  self.exclude = exclude
2891  tattr = dict(self.text_defaults)
2892  tattr.update(text_attr)
2893  Line.__init__(self, x1, y1, x2, y2, **attr)
2894  Ticks.__init__(self, None, None, None, ticks, miniticks, labels, logbase, arrow_start, arrow_end, tattr, **attr)
2895 
2896  def interpret(self):
2897  if self.exclude != None and not (isinstance(self.exclude, (tuple, list)) and len(self.exclude) == 2 and \
2898  isinstance(self.exclude[0], (int, long, float)) and isinstance(self.exclude[1], (int, long, float))):
2899  raise TypeError, "exclude must either be None or (low, high)"
2900 
2901  ticks, miniticks = Ticks.interpret(self)
2902  if self.exclude == None: return ticks, miniticks
2903 
2904  ticks2 = {}
2905  for loc, label in ticks.items():
2906  if self.exclude[0] <= loc <= self.exclude[1]:
2907  ticks2[loc] = ""
2908  else:
2909  ticks2[loc] = label
2910 
2911  return ticks2, miniticks
2912 
2913  def SVG(self, trans=None):
2914  """Apply the transformation "trans" and return an SVG object."""
2915  line = Line.SVG(self, trans) # must be evaluated first, to set self.f, self.low, self.high
2916 
2917  f01 = self.f
2918  self.f = lambda t: f01(1. * (t - self.start) / (self.end - self.start))
2919  self.low = self.start
2920  self.high = self.end
2921 
2922  if self.arrow_start != False and self.arrow_start != None:
2923  if isinstance(self.arrow_start, basestring):
2924  line.attr["marker-start"] = "url(#%s)" % self.arrow_start
2925  else:
2926  line.attr["marker-start"] = "url(#%s)" % self.arrow_start.id
2927 
2928  if self.arrow_end != False and self.arrow_end != None:
2929  if isinstance(self.arrow_end, basestring):
2930  line.attr["marker-end"] = "url(#%s)" % self.arrow_end
2931  else:
2932  line.attr["marker-end"] = "url(#%s)" % self.arrow_end.id
2933 
2934  ticks = Ticks.SVG(self, trans) # returns a <g />
2935  ticks.append(line)
2936  return ticks
2937 
2939  """Draws an x axis with tick marks.
2940 
2941  XAxis(xmin, xmax, aty, ticks, miniticks, labels, logbase, arrow_start, arrow_end,
2942  exclude, text_attr, attribute=value)
2943 
2944  xmin, xmax required the x range
2945  aty default=0 y position to draw the axis
2946  ticks default=-10 request ticks according to the standard
2947  tick specification (see help(Ticks))
2948  miniticks default=True request miniticks according to the
2949  standard minitick specification
2950  labels True request tick labels according to the
2951  standard tick label specification
2952  logbase default=None if a number, the x axis is logarithmic
2953  with ticks at the given base (usually 10)
2954  arrow_start default=None if a new string identifier, draw an arrow
2955  at the low-end of the axis, referenced by
2956  that identifier; if an SVG marker object,
2957  use that marker
2958  arrow_end default=None if a new string identifier, draw an arrow
2959  at the high-end of the axis, referenced by
2960  that identifier; if an SVG marker object,
2961  use that marker
2962  exclude default=None if a (low, high) pair, don't draw text
2963  labels within this range
2964  text_attr default={} SVG attributes for the text labels
2965  attribute=value pairs keyword list SVG attributes for all lines
2966 
2967  The exclude option is provided for Axes to keep text from overlapping
2968  where the axes cross. Normal users are not likely to need it.
2969  """
2970  defaults = {"stroke-width":"0.25pt"}
2971  text_defaults = {"stroke":"none", "fill":"black", "font-size":5, "dominant-baseline":"text-before-edge"}
2972  text_start = -1.
2973  text_angle = 0.
2974 
2975  def __repr__(self):
2976  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)
2977 
2978  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):
2979  self.aty = aty
2980  tattr = dict(self.text_defaults)
2981  tattr.update(text_attr)
2982  LineAxis.__init__(self, xmin, aty, xmax, aty, xmin, xmax, ticks, miniticks, labels, logbase, arrow_start, arrow_end, exclude, tattr, **attr)
2983 
2984  def SVG(self, trans=None):
2985  """Apply the transformation "trans" and return an SVG object."""
2986  self.y1 = self.aty
2987  self.y2 = self.aty
2988  return LineAxis.SVG(self, trans)
2989 
2991  """Draws a y axis with tick marks.
2992 
2993  YAxis(ymin, ymax, atx, ticks, miniticks, labels, logbase, arrow_start, arrow_end,
2994  exclude, text_attr, attribute=value)
2995 
2996  ymin, ymax required the y range
2997  atx default=0 x position to draw the axis
2998  ticks default=-10 request ticks according to the standard
2999  tick specification (see help(Ticks))
3000  miniticks default=True request miniticks according to the
3001  standard minitick specification
3002  labels True request tick labels according to the
3003  standard tick label specification
3004  logbase default=None if a number, the y axis is logarithmic
3005  with ticks at the given base (usually 10)
3006  arrow_start default=None if a new string identifier, draw an arrow
3007  at the low-end of the axis, referenced by
3008  that identifier; if an SVG marker object,
3009  use that marker
3010  arrow_end default=None if a new string identifier, draw an arrow
3011  at the high-end of the axis, referenced by
3012  that identifier; if an SVG marker object,
3013  use that marker
3014  exclude default=None if a (low, high) pair, don't draw text
3015  labels within this range
3016  text_attr default={} SVG attributes for the text labels
3017  attribute=value pairs keyword list SVG attributes for all lines
3018 
3019  The exclude option is provided for Axes to keep text from overlapping
3020  where the axes cross. Normal users are not likely to need it.
3021  """
3022  defaults = {"stroke-width":"0.25pt"}
3023  text_defaults = {"stroke":"none", "fill":"black", "font-size":5, "text-anchor":"end", "dominant-baseline":"middle"}
3024  text_start = 2.5
3025  text_angle = 90.
3026 
3027  def __repr__(self):
3028  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)
3029 
3030  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):
3031  self.atx = atx
3032  tattr = dict(self.text_defaults)
3033  tattr.update(text_attr)
3034  LineAxis.__init__(self, atx, ymin, atx, ymax, ymin, ymax, ticks, miniticks, labels, logbase, arrow_start, arrow_end, exclude, tattr, **attr)
3035 
3036  def SVG(self, trans=None):
3037  """Apply the transformation "trans" and return an SVG object."""
3038  self.x1 = self.atx
3039  self.x2 = self.atx
3040  return LineAxis.SVG(self, trans)
3041 
3042 class Axes:
3043  """Draw a pair of intersecting x-y axes.
3044 
3045  Axes(xmin, xmax, ymin, ymax, atx, aty, xticks, xminiticks, xlabels, xlogbase,
3046  yticks, yminiticks, ylabels, ylogbase, arrows, text_attr, attribute=value)
3047 
3048  xmin, xmax required the x range
3049  ymin, ymax required the y range
3050  atx, aty default=0, 0 point where the axes try to cross;
3051  if outside the range, the axes will
3052  cross at the closest corner
3053  xticks default=-10 request ticks according to the standard
3054  tick specification (see help(Ticks))
3055  xminiticks default=True request miniticks according to the
3056  standard minitick specification
3057  xlabels True request tick labels according to the
3058  standard tick label specification
3059  xlogbase default=None if a number, the x axis is logarithmic
3060  with ticks at the given base (usually 10)
3061  yticks default=-10 request ticks according to the standard
3062  tick specification
3063  yminiticks default=True request miniticks according to the
3064  standard minitick specification
3065  ylabels True request tick labels according to the
3066  standard tick label specification
3067  ylogbase default=None if a number, the y axis is logarithmic
3068  with ticks at the given base (usually 10)
3069  arrows default=None if a new string identifier, draw arrows
3070  referenced by that identifier
3071  text_attr default={} SVG attributes for the text labels
3072  attribute=value pairs keyword list SVG attributes for all lines
3073  """
3074  defaults = {"stroke-width":"0.25pt"}
3075  text_defaults = {"stroke":"none", "fill":"black", "font-size":5}
3076 
3077  def __repr__(self):
3078  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)
3079 
3080  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):
3081  self.xmin, self.xmax = xmin, xmax
3082  self.ymin, self.ymax = ymin, ymax
3083  self.atx, self.aty = atx, aty
3084  self.xticks, self.xminiticks, self.xlabels, self.xlogbase = xticks, xminiticks, xlabels, xlogbase
3085  self.yticks, self.yminiticks, self.ylabels, self.ylogbase = yticks, yminiticks, ylabels, ylogbase
3086  self.arrows = arrows
3087 
3089  self.text_attr.update(text_attr)
3090 
3091  self.attr = dict(self.defaults)
3092  self.attr.update(attr)
3093 
3094  def SVG(self, trans=None):
3095  """Apply the transformation "trans" and return an SVG object."""
3096  atx, aty = self.atx, self.aty
3097  if atx < self.xmin: atx = self.xmin
3098  if atx > self.xmax: atx = self.xmax
3099  if aty < self.ymin: aty = self.ymin
3100  if aty > self.ymax: aty = self.ymax
3101 
3102  xmargin = 0.1 * abs(self.ymin - self.ymax)
3103  xexclude = atx - xmargin, atx + xmargin
3104 
3105  ymargin = 0.1 * abs(self.xmin - self.xmax)
3106  yexclude = aty - ymargin, aty + ymargin
3107 
3108  if self.arrows != None and self.arrows != False:
3109  xarrow_start = self.arrows + ".xstart"
3110  xarrow_end = self.arrows + ".xend"
3111  yarrow_start = self.arrows + ".ystart"
3112  yarrow_end = self.arrows + ".yend"
3113  else:
3114  xarrow_start = xarrow_end = yarrow_start = yarrow_end = None
3115 
3116  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)
3117  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)
3118  return SVG("g", *(xaxis.sub + yaxis.sub))
3119 
3120 ######################################################################
3121 
3122 class HGrid(Ticks):
3123  """Draws the horizontal lines of a grid over a specified region
3124  using the standard tick specification (see help(Ticks)) to place the
3125  grid lines.
3126 
3127  HGrid(xmin, xmax, low, high, ticks, miniticks, logbase, mini_attr, attribute=value)
3128 
3129  xmin, xmax required the x range
3130  low, high required the y range
3131  ticks default=-10 request ticks according to the standard
3132  tick specification (see help(Ticks))
3133  miniticks default=False request miniticks according to the
3134  standard minitick specification
3135  logbase default=None if a number, the axis is logarithmic
3136  with ticks at the given base (usually 10)
3137  mini_attr default={} SVG attributes for the minitick-lines
3138  (if miniticks != False)
3139  attribute=value pairs keyword list SVG attributes for the major tick lines
3140  """
3141  defaults = {"stroke-width":"0.25pt", "stroke":"gray"}
3142  mini_defaults = {"stroke-width":"0.25pt", "stroke":"lightgray", "stroke-dasharray":"1,1"}
3143 
3144  def __repr__(self):
3145  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)
3146 
3147  def __init__(self, xmin, xmax, low, high, ticks=-10, miniticks=False, logbase=None, mini_attr={}, **attr):
3148  self.xmin, self.xmax = xmin, xmax
3149 
3151  self.mini_attr.update(mini_attr)
3152 
3153  Ticks.__init__(self, None, low, high, ticks, miniticks, None, logbase)
3154 
3155  self.attr = dict(self.defaults)
3156  self.attr.update(attr)
3157 
3158  def SVG(self, trans=None):
3159  """Apply the transformation "trans" and return an SVG object."""
3160  self.last_ticks, self.last_miniticks = Ticks.interpret(self)
3161 
3162  ticksd = []
3163  for t in self.last_ticks.keys():
3164  ticksd += Line(self.xmin, t, self.xmax, t).Path(trans).d
3165 
3166  miniticksd = []
3167  for t in self.last_miniticks:
3168  miniticksd += Line(self.xmin, t, self.xmax, t).Path(trans).d
3169 
3170  return SVG("g", Path(d=ticksd, **self.attr).SVG(), Path(d=miniticksd, **self.mini_attr).SVG())
3171 
3172 class VGrid(Ticks):
3173  """Draws the vertical lines of a grid over a specified region
3174  using the standard tick specification (see help(Ticks)) to place the
3175  grid lines.
3176 
3177  HGrid(ymin, ymax, low, high, ticks, miniticks, logbase, mini_attr, attribute=value)
3178 
3179  ymin, ymax required the y range
3180  low, high required the x range
3181  ticks default=-10 request ticks according to the standard
3182  tick specification (see help(Ticks))
3183  miniticks default=False request miniticks according to the
3184  standard minitick specification
3185  logbase default=None if a number, the axis is logarithmic
3186  with ticks at the given base (usually 10)
3187  mini_attr default={} SVG attributes for the minitick-lines
3188  (if miniticks != False)
3189  attribute=value pairs keyword list SVG attributes for the major tick lines
3190  """
3191  defaults = {"stroke-width":"0.25pt", "stroke":"gray"}
3192  mini_defaults = {"stroke-width":"0.25pt", "stroke":"lightgray", "stroke-dasharray":"1,1"}
3193 
3194  def __repr__(self):
3195  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)
3196 
3197  def __init__(self, ymin, ymax, low, high, ticks=-10, miniticks=False, logbase=None, mini_attr={}, **attr):
3198  self.ymin, self.ymax = ymin, ymax
3199 
3201  self.mini_attr.update(mini_attr)
3202 
3203  Ticks.__init__(self, None, low, high, ticks, miniticks, None, logbase)
3204 
3205  self.attr = dict(self.defaults)
3206  self.attr.update(attr)
3207 
3208  def SVG(self, trans=None):
3209  """Apply the transformation "trans" and return an SVG object."""
3210  self.last_ticks, self.last_miniticks = Ticks.interpret(self)
3211 
3212  ticksd = []
3213  for t in self.last_ticks.keys():
3214  ticksd += Line(t, self.ymin, t, self.ymax).Path(trans).d
3215 
3216  miniticksd = []
3217  for t in self.last_miniticks:
3218  miniticksd += Line(t, self.ymin, t, self.ymax).Path(trans).d
3219 
3220  return SVG("g", Path(d=ticksd, **self.attr).SVG(), Path(d=miniticksd, **self.mini_attr).SVG())
3221 
3222 class Grid(Ticks):
3223  """Draws a grid over a specified region using the standard tick
3224  specification (see help(Ticks)) to place the grid lines.
3225 
3226  Grid(xmin, xmax, ymin, ymax, ticks, miniticks, logbase, mini_attr, attribute=value)
3227 
3228  xmin, xmax required the x range
3229  ymin, ymax required the y range
3230  ticks default=-10 request ticks according to the standard
3231  tick specification (see help(Ticks))
3232  miniticks default=False request miniticks according to the
3233  standard minitick specification
3234  logbase default=None if a number, the axis is logarithmic
3235  with ticks at the given base (usually 10)
3236  mini_attr default={} SVG attributes for the minitick-lines
3237  (if miniticks != False)
3238  attribute=value pairs keyword list SVG attributes for the major tick lines
3239  """
3240  defaults = {"stroke-width":"0.25pt", "stroke":"gray"}
3241  mini_defaults = {"stroke-width":"0.25pt", "stroke":"lightgray", "stroke-dasharray":"1,1"}
3242 
3243  def __repr__(self):
3244  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)
3245 
3246  def __init__(self, xmin, xmax, ymin, ymax, ticks=-10, miniticks=False, logbase=None, mini_attr={}, **attr):
3247  self.xmin, self.xmax = xmin, xmax
3248  self.ymin, self.ymax = ymin, ymax
3249 
3251  self.mini_attr.update(mini_attr)
3252 
3253  Ticks.__init__(self, None, None, None, ticks, miniticks, None, logbase)
3254 
3255  self.attr = dict(self.defaults)
3256  self.attr.update(attr)
3257 
3258  def SVG(self, trans=None):
3259  """Apply the transformation "trans" and return an SVG object."""
3260  self.low, self.high = self.xmin, self.xmax
3261  self.last_xticks, self.last_xminiticks = Ticks.interpret(self)
3262  self.low, self.high = self.ymin, self.ymax
3263  self.last_yticks, self.last_yminiticks = Ticks.interpret(self)
3264 
3265  ticksd = []
3266  for t in self.last_xticks.keys():
3267  ticksd += Line(t, self.ymin, t, self.ymax).Path(trans).d
3268  for t in self.last_yticks.keys():
3269  ticksd += Line(self.xmin, t, self.xmax, t).Path(trans).d
3270 
3271  miniticksd = []
3272  for t in self.last_xminiticks:
3273  miniticksd += Line(t, self.ymin, t, self.ymax).Path(trans).d
3274  for t in self.last_yminiticks:
3275  miniticksd += Line(self.xmin, t, self.xmax, t).Path(trans).d
3276 
3277  return SVG("g", Path(d=ticksd, **self.attr).SVG(), Path(d=miniticksd, **self.mini_attr).SVG())
3278 
3279 ######################################################################
3280 
3282  """Draws x error bars at a set of points. This is usually used
3283  before (under) a set of Dots at the same points.
3284 
3285  XErrorBars(d, attribute=value)
3286 
3287  d required list of (x,y,xerr...) points
3288  attribute=value pairs keyword list SVG attributes
3289 
3290  If points in d have
3291 
3292  * 3 elements, the third is the symmetric error bar
3293  * 4 elements, the third and fourth are the asymmetric lower and
3294  upper error bar. The third element should be negative,
3295  e.g. (5, 5, -1, 2) is a bar from 4 to 7.
3296  * more than 4, a tick mark is placed at each value. This lets
3297  you nest errors from different sources, correlated and
3298  uncorrelated, statistical and systematic, etc.
3299  """
3300  defaults = {"stroke-width":"0.25pt"}
3301 
3302  def __repr__(self):
3303  return "<XErrorBars (%d nodes)>" % len(self.d)
3304 
3305  def __init__(self, d=[], **attr):
3306  self.d = list(d)
3307 
3308  self.attr = dict(self.defaults)
3309  self.attr.update(attr)
3310 
3311  def SVG(self, trans=None):
3312  """Apply the transformation "trans" and return an SVG object."""
3313  if isinstance(trans, basestring): trans = totrans(trans) # only once
3314 
3315  output = SVG("g")
3316  for p in self.d:
3317  x, y = p[0], p[1]
3318 
3319  if len(p) == 3: bars = [x - p[2], x + p[2]]
3320  else: bars = [x + pi for pi in p[2:]]
3321 
3322  start, end = min(bars), max(bars)
3323  output.append(LineAxis(start, y, end, y, start, end, bars, False, False, **self.attr).SVG(trans))
3324 
3325  return output
3326 
3328  """Draws y error bars at a set of points. This is usually used
3329  before (under) a set of Dots at the same points.
3330 
3331  YErrorBars(d, attribute=value)
3332 
3333  d required list of (x,y,yerr...) points
3334  attribute=value pairs keyword list SVG attributes
3335 
3336  If points in d have
3337 
3338  * 3 elements, the third is the symmetric error bar
3339  * 4 elements, the third and fourth are the asymmetric lower and
3340  upper error bar. The third element should be negative,
3341  e.g. (5, 5, -1, 2) is a bar from 4 to 7.
3342  * more than 4, a tick mark is placed at each value. This lets
3343  you nest errors from different sources, correlated and
3344  uncorrelated, statistical and systematic, etc.
3345  """
3346  defaults = {"stroke-width":"0.25pt"}
3347 
3348  def __repr__(self):
3349  return "<YErrorBars (%d nodes)>" % len(self.d)
3350 
3351  def __init__(self, d=[], **attr):
3352  self.d = list(d)
3353 
3354  self.attr = dict(self.defaults)
3355  self.attr.update(attr)
3356 
3357  def SVG(self, trans=None):
3358  """Apply the transformation "trans" and return an SVG object."""
3359  if isinstance(trans, basestring): trans = totrans(trans) # only once
3360 
3361  output = SVG("g")
3362  for p in self.d:
3363  x, y = p[0], p[1]
3364 
3365  if len(p) == 3: bars = [y - p[2], y + p[2]]
3366  else: bars = [y + pi for pi in p[2:]]
3367 
3368  start, end = min(bars), max(bars)
3369  output.append(LineAxis(x, start, x, end, start, end, bars, False, False, **self.attr).SVG(trans))
3370 
3371  return output
def compute_logminiticks
Definition: svgfig.py:2769
def __init__
Definition: svgfig.py:2136
def __eq__
Definition: svgfig.py:172
def __init__
Definition: svgfig.py:1518
def load_stream
Definition: svgfig.py:550
dictionary defaults
Definition: svgfig.py:1057
def __getitem__
Definition: svgfig.py:135
def __repr__
Definition: svgfig.py:3243
int text_angle
Definition: svgfig.py:2386
def window
Definition: svgfig.py:642
dictionary mini_defaults
Definition: svgfig.py:3142
dictionary defaults
Definition: svgfig.py:3346
def __repr__
Definition: svgfig.py:816
def __delitem__
Definition: svgfig.py:157
def __init__
Definition: svgfig.py:2978
def __repr__
Definition: svgfig.py:3077
def Path
Definition: svgfig.py:2204
dictionary defaults
Definition: svgfig.py:1869
dictionary defaults
Definition: svgfig.py:1716
def __init__
Definition: svgfig.py:2391
int text_ytitle_offset
Definition: svgfig.py:881
def __init__
Definition: svgfig.py:3080
def __repr__
Definition: svgfig.py:3144
def parse
Definition: svgfig.py:1117
int text_xaxis_offset
Definition: svgfig.py:878
def funcRtoC
Definition: svgfig.py:1451
def __init__
Definition: svgfig.py:1847
#define abs(x)
Definition: mlp_lapack.h:159
dictionary axis_defaults
Definition: svgfig.py:874
def clone
Definition: svgfig.py:195
def __init__
Definition: svgfig.py:1995
#define min(a, b)
Definition: mlp_lapack.h:161
def canvas_outline
Definition: svgfig.py:509
def compute_miniticks
Definition: svgfig.py:2689
def __repr__
Definition: svgfig.py:1718
def inkscape
Definition: svgfig.py:449
def interpret
Definition: svgfig.py:2507
def funcRtoR2
Definition: svgfig.py:1468
def __repr__
Definition: svgfig.py:284
def items
Definition: svgfig.py:248
dictionary map
Definition: Association.py:205
end Sample
Definition: svgfig.py:1551
def __init__
Definition: svgfig.py:2164
def Path
Definition: svgfig.py:2034
def parse_whitespace
Definition: svgfig.py:1069
def __repr__
Definition: svgfig.py:1992
def canvas
Definition: svgfig.py:481
def __contains__
Definition: svgfig.py:168
float tick_end
Definition: svgfig.py:2382
def interpret_fileName
Definition: svgfig.py:401
def __repr__
Definition: svgfig.py:733
def subsample
Definition: svgfig.py:1618
def __repr__
Definition: svgfig.py:2161
def funcRtoR
Definition: svgfig.py:1483
def rgb
Definition: svgfig.py:39
int discontinuity_limit
Definition: svgfig.py:1513
dictionary text_defaults
Definition: svgfig.py:2380
dictionary mini_defaults
Definition: svgfig.py:3192
def __repr__
Definition: svgfig.py:1844
def make_symbol
Definition: svgfig.py:1893
def __repr__
Definition: svgfig.py:3027
const T & max(const T &a, const T &b)
def totrans
Definition: svgfig.py:597
def parse_command
Definition: svgfig.py:1074
float minitick_end
Definition: svgfig.py:2384
def __repr__
Definition: svgfig.py:2388
def pathtoPath
Definition: svgfig.py:1006
def depth_first
end nested class
Definition: svgfig.py:234
float minitick_length
Definition: svgfig.py:877
def __repr__
Definition: svgfig.py:1515
def compute_logticks
Definition: svgfig.py:2718
def load
Definition: svgfig.py:546
def __str__
Definition: svgfig.py:286
def make_marker
Definition: svgfig.py:1964
def __repr__
Definition: svgfig.py:2191
def keys
Definition: svgfig.py:266
def __init__
Definition: svgfig.py:1923
def __init__
Definition: svgfig.py:1062
dictionary text_defaults
Definition: svgfig.py:3075
def __repr__
Definition: svgfig.py:1920
def firefox
Definition: svgfig.py:461
def compute_ticks
Definition: svgfig.py:2601
def __standalone_xml
Definition: svgfig.py:372
def __init__
Definition: svgfig.py:886
int recursion_limit
Definition: svgfig.py:1511
dictionary defaults
Definition: svgfig.py:2379
def xml
Definition: svgfig.py:319
def inkview
Definition: svgfig.py:437
def orient_tickmark
Definition: svgfig.py:2408
def extend
Definition: svgfig.py:191
def SVG
Definition: svgfig.py:856
def __repr__
Definition: svgfig.py:3194
dictionary defaults
Definition: svgfig.py:3300
dictionary defaults
Definition: svgfig.py:1918
dictionary text_defaults
Definition: svgfig.py:873
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def SVG
Definition: svgfig.py:751
def __iter__
Definition: svgfig.py:246
float tick_length
Definition: svgfig.py:876
def __init__
Definition: svgfig.py:123
int text_yaxis_offset
Definition: svgfig.py:879
dictionary defaults
Definition: svgfig.py:3074
def __repr__
Definition: svgfig.py:2975
def rotate
Definition: svgfig.py:704
def __repr__
Definition: svgfig.py:883
float tick_start
Definition: svgfig.py:2381
def __init__
Definition: svgfig.py:822
def __setitem__
Definition: svgfig.py:146
def save
Definition: svgfig.py:408
def tree
Definition: svgfig.py:290
def __init__
Definition: svgfig.py:741
def unumber
Definition: svgfig.py:2283
dictionary mini_defaults
Definition: svgfig.py:3241
def standalone_xml
Definition: svgfig.py:357
def __init__
Definition: svgfig.py:2194
def values
Definition: svgfig.py:275
float linearity_limit
Definition: svgfig.py:1512
def prepend
Definition: svgfig.py:186
def sample
end nested class
Definition: svgfig.py:1575
def __repr__
Definition: svgfig.py:1059
def breadth_first
Definition: svgfig.py:239
def __init__
Definition: svgfig.py:3197
float minitick_start
Definition: svgfig.py:2383
dictionary defaults
Definition: svgfig.py:1842
int text_xtitle_offset
Definition: svgfig.py:880
def __init__
Definition: svgfig.py:1721
def Path
Definition: svgfig.py:1733
def __repr__
Definition: svgfig.py:2133
dictionary defaults
Definition: svgfig.py:1509
def regular_miniticks
Definition: svgfig.py:2677
def parse_boolean
Definition: svgfig.py:1104
def parse_number
Definition: svgfig.py:1086
def __init__
Definition: svgfig.py:3030
def attr_preprocess
Definition: svgfig.py:46
double split
Definition: MVATrainer.cc:139
dictionary defaults
Definition: svgfig.py:2067
def append
Definition: svgfig.py:181
def __init__
Definition: svgfig.py:3246
def __ne__
Definition: svgfig.py:177
float text_start
Definition: svgfig.py:2385
nested class Sample
Definition: svgfig.py:1528
def template
Definition: svgfig.py:520
output
Definition: svgfig.py:559
def __init__
Definition: svgfig.py:3147
How EventSelector::AcceptEvent() decides whether to accept an event for output otherwise it is excluding the probing of A single or multiple positive and the trigger will pass if any such matching triggers are PASS or EXCEPTION[A criterion thatmatches no triggers at all is detected and causes a throw.] A single negative with an expectation of appropriate bit checking in the decision and the trigger will pass if any such matching triggers are FAIL or EXCEPTION A wildcarded negative criterion that matches more than one trigger in the trigger list("!*","!HLTx*"if it matches 2 triggers or more) will accept the event if all the matching triggers are FAIL.It will reject the event if any of the triggers are PASS or EXCEPTION(this matches the behavior of"!*"before the partial wildcard feature was incorporated).Triggers which are in the READY state are completely ignored.(READY should never be returned since the trigger paths have been run