CMS 3D CMS Logo

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 next(self.iterators)
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, str): 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, str): 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, str) 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], str):
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], str):
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.__code__.co_argcount == 2:
612  return expr
613 
614  elif expr.__code__.co_argcount == 1:
615  split = lambda z: (z.real, z.imag)
616  output = lambda x, y: split(expr(x + y*1j))
617  output.__name__ = expr.__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.__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.__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.__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, str):
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.__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, str): 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, str): 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.__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, str): 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, str): 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, str): 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.__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.__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.__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, str): trans = totrans(trans)
1659  if isinstance(self.f, str): 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, str): 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, str): 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, str): 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, str):
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, str):
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, str): 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, str):
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, str):
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, str): 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, str): 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, str):
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, str):
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, str):
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 = sorted(original_ticks.keys())
2696 
2697  if self.low > original_ticks[0] + _epsilon or self.high < original_ticks[-1] - _epsilon:
2698  raise ValueError("original_ticks {%g...%g} extend beyond [%g, %g]" % (original_ticks[0], original_ticks[-1], self.low, self.high))
2699 
2700  granularities = []
2701  for i in range(len(original_ticks)-1):
2702  granularities.append(original_ticks[i+1] - original_ticks[i])
2703  spacing = 10**(math.ceil(math.log10(min(granularities)) - 1))
2704 
2705  output = []
2706  x = original_ticks[0] - math.ceil(1.*(original_ticks[0] - self.low) / spacing) * spacing
2707 
2708  while x <= self.high:
2709  if x >= self.low:
2710  already_in_ticks = False
2711  for t in original_ticks:
2712  if abs(x-t) < _epsilon * (self.high - self.low): already_in_ticks = True
2713  if not already_in_ticks: output.append(x)
2714  x += spacing
2715  return output
2716 
2717  def compute_logticks(self, base, N, format):
2718  """Return less than -N or exactly N optimal logarithmic ticks.
2719 
2720  Normally only used internally.
2721  """
2722  if self.low >= self.high: raise ValueError("low must be less than high")
2723  if N == 1: raise ValueError("N can be 0 or >1 to specify the exact number of ticks or negative to specify a maximum")
2724 
2725  eps = _epsilon * (self.high - self.low)
2726 
2727  if N >= 0:
2728  output = {}
2729  x = self.low
2730  for i in xrange(N):
2731  if format == unumber and abs(x) < eps: label = u"0"
2732  else: label = format(x)
2733  output[x] = label
2734  x += (self.high - self.low)/(N-1.)
2735  return output
2736 
2737  N = -N
2738 
2739  lowN = math.floor(math.log(self.low, base))
2740  highN = math.ceil(math.log(self.high, base))
2741  output = {}
2742  for n in range(int(lowN), int(highN)+1):
2743  x = base**n
2744  label = format(x)
2745  if self.low <= x <= self.high: output[x] = label
2746 
2747  for i in range(1, len(output)):
2748  keys = sorted(output.keys())
2749  keys = keys[::i]
2750  values = map(lambda k: output[k], keys)
2751  if len(values) <= N:
2752  for k in output.keys():
2753  if k not in keys:
2754  output[k] = ""
2755  break
2756 
2757  if len(output) <= 2:
2758  output2 = self.compute_ticks(N=-int(math.ceil(N/2.)), format=format)
2759  lowest = min(output2)
2760 
2761  for k in output:
2762  if k < lowest: output2[k] = output[k]
2763  output = output2
2764 
2765  return output
2766 
2767  def compute_logminiticks(self, base):
2768  """Return optimal logarithmic miniticks, given a set of ticks.
2769 
2770  Normally only used internally.
2771  """
2772  if self.low >= self.high: raise ValueError("low must be less than high")
2773 
2774  lowN = math.floor(math.log(self.low, base))
2775  highN = math.ceil(math.log(self.high, base))
2776  output = []
2777  num_ticks = 0
2778  for n in range(int(lowN), int(highN)+1):
2779  x = base**n
2780  if self.low <= x <= self.high: num_ticks += 1
2781  for m in range(2, int(math.ceil(base))):
2782  minix = m * x
2783  if self.low <= minix <= self.high: output.append(minix)
2784 
2785  if num_ticks <= 2: return []
2786  else: return output
2787 
2788 ######################################################################
2789 
2791  """Draw an axis with tick marks along a parametric curve.
2792 
2793  CurveAxis(f, low, high, ticks, miniticks, labels, logbase, arrow_start, arrow_end,
2794  text_attr, attribute=value)
2795 
2796  f required a Python callable or string in
2797  the form "f(t), g(t)", just like Curve
2798  low, high required left and right endpoints
2799  ticks default=-10 request ticks according to the standard
2800  tick specification (see help(Ticks))
2801  miniticks default=True request miniticks according to the
2802  standard minitick specification
2803  labels True request tick labels according to the
2804  standard tick label specification
2805  logbase default=None if a number, the x axis is logarithmic
2806  with ticks at the given base (10 being
2807  the most common)
2808  arrow_start default=None if a new string identifier, draw an
2809  arrow at the low-end of the axis,
2810  referenced by that identifier; if an
2811  SVG marker object, use that marker
2812  arrow_end default=None if a new string identifier, draw an
2813  arrow at the high-end of the axis,
2814  referenced by that identifier; if an
2815  SVG marker object, use that marker
2816  text_attr default={} SVG attributes for the text labels
2817  attribute=value pairs keyword list SVG attributes
2818  """
2819  defaults = {"stroke-width":"0.25pt"}
2820  text_defaults = {"stroke":"none", "fill":"black", "font-size":5}
2821 
2822  def __repr__(self):
2823  return "<CurveAxis %s [%s, %s] ticks=%s labels=%s %s>" % (self.f, self.low, self.high, str(self.ticks), str(self.labels), self.attr)
2824 
2825  def __init__(self, f, low, high, ticks=-10, miniticks=True, labels=True, logbase=None, arrow_start=None, arrow_end=None, text_attr={}, **attr):
2826  tattr = dict(self.text_defaults)
2827  tattr.update(text_attr)
2828  Curve.__init__(self, f, low, high)
2829  Ticks.__init__(self, f, low, high, ticks, miniticks, labels, logbase, arrow_start, arrow_end, tattr, **attr)
2830 
2831  def SVG(self, trans=None):
2832  """Apply the transformation "trans" and return an SVG object."""
2833  func = Curve.SVG(self, trans)
2834  ticks = Ticks.SVG(self, trans) # returns a <g />
2835 
2836  if self.arrow_start != False and self.arrow_start != None:
2837  if isinstance(self.arrow_start, str):
2838  func.attr["marker-start"] = "url(#%s)" % self.arrow_start
2839  else:
2840  func.attr["marker-start"] = "url(#%s)" % self.arrow_start.id
2841 
2842  if self.arrow_end != False and self.arrow_end != None:
2843  if isinstance(self.arrow_end, str):
2844  func.attr["marker-end"] = "url(#%s)" % self.arrow_end
2845  else:
2846  func.attr["marker-end"] = "url(#%s)" % self.arrow_end.id
2847 
2848  ticks.append(func)
2849  return ticks
2850 
2852  """Draws an axis with tick marks along a line.
2853 
2854  LineAxis(x1, y1, x2, y2, start, end, ticks, miniticks, labels, logbase,
2855  arrow_start, arrow_end, text_attr, attribute=value)
2856 
2857  x1, y1 required starting point
2858  x2, y2 required ending point
2859  start, end default=0, 1 values to start and end labeling
2860  ticks default=-10 request ticks according to the standard
2861  tick specification (see help(Ticks))
2862  miniticks default=True request miniticks according to the
2863  standard minitick specification
2864  labels True request tick labels according to the
2865  standard tick label specification
2866  logbase default=None if a number, the x axis is logarithmic
2867  with ticks at the given base (usually 10)
2868  arrow_start default=None if a new string identifier, draw an arrow
2869  at the low-end of the axis, referenced by
2870  that identifier; if an SVG marker object,
2871  use that marker
2872  arrow_end default=None if a new string identifier, draw an arrow
2873  at the high-end of the axis, referenced by
2874  that identifier; if an SVG marker object,
2875  use that marker
2876  text_attr default={} SVG attributes for the text labels
2877  attribute=value pairs keyword list SVG attributes
2878  """
2879  defaults = {"stroke-width":"0.25pt"}
2880  text_defaults = {"stroke":"none", "fill":"black", "font-size":5}
2881 
2882  def __repr__(self):
2883  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)
2884 
2885  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):
2886  self.start = start
2887  self.end = end
2888  self.exclude = exclude
2889  tattr = dict(self.text_defaults)
2890  tattr.update(text_attr)
2891  Line.__init__(self, x1, y1, x2, y2, **attr)
2892  Ticks.__init__(self, None, None, None, ticks, miniticks, labels, logbase, arrow_start, arrow_end, tattr, **attr)
2893 
2894  def interpret(self):
2895  if self.exclude != None and not (isinstance(self.exclude, (tuple, list)) and len(self.exclude) == 2 and \
2896  isinstance(self.exclude[0], (int, long, float)) and isinstance(self.exclude[1], (int, long, float))):
2897  raise TypeError("exclude must either be None or (low, high)")
2898 
2899  ticks, miniticks = Ticks.interpret(self)
2900  if self.exclude == None: return ticks, miniticks
2901 
2902  ticks2 = {}
2903  for loc, label in ticks.items():
2904  if self.exclude[0] <= loc <= self.exclude[1]:
2905  ticks2[loc] = ""
2906  else:
2907  ticks2[loc] = label
2908 
2909  return ticks2, miniticks
2910 
2911  def SVG(self, trans=None):
2912  """Apply the transformation "trans" and return an SVG object."""
2913  line = Line.SVG(self, trans) # must be evaluated first, to set self.f, self.low, self.high
2914 
2915  f01 = self.f
2916  self.f = lambda t: f01(1. * (t - self.start) / (self.end - self.start))
2917  self.low = self.start
2918  self.high = self.end
2919 
2920  if self.arrow_start != False and self.arrow_start != None:
2921  if isinstance(self.arrow_start, str):
2922  line.attr["marker-start"] = "url(#%s)" % self.arrow_start
2923  else:
2924  line.attr["marker-start"] = "url(#%s)" % self.arrow_start.id
2925 
2926  if self.arrow_end != False and self.arrow_end != None:
2927  if isinstance(self.arrow_end, str):
2928  line.attr["marker-end"] = "url(#%s)" % self.arrow_end
2929  else:
2930  line.attr["marker-end"] = "url(#%s)" % self.arrow_end.id
2931 
2932  ticks = Ticks.SVG(self, trans) # returns a <g />
2933  ticks.append(line)
2934  return ticks
2935 
2937  """Draws an x axis with tick marks.
2938 
2939  XAxis(xmin, xmax, aty, ticks, miniticks, labels, logbase, arrow_start, arrow_end,
2940  exclude, text_attr, attribute=value)
2941 
2942  xmin, xmax required the x range
2943  aty default=0 y position to draw the axis
2944  ticks default=-10 request ticks according to the standard
2945  tick specification (see help(Ticks))
2946  miniticks default=True request miniticks according to the
2947  standard minitick specification
2948  labels True request tick labels according to the
2949  standard tick label specification
2950  logbase default=None if a number, the x axis is logarithmic
2951  with ticks at the given base (usually 10)
2952  arrow_start default=None if a new string identifier, draw an arrow
2953  at the low-end of the axis, referenced by
2954  that identifier; if an SVG marker object,
2955  use that marker
2956  arrow_end default=None if a new string identifier, draw an arrow
2957  at the high-end of the axis, referenced by
2958  that identifier; if an SVG marker object,
2959  use that marker
2960  exclude default=None if a (low, high) pair, don't draw text
2961  labels within this range
2962  text_attr default={} SVG attributes for the text labels
2963  attribute=value pairs keyword list SVG attributes for all lines
2964 
2965  The exclude option is provided for Axes to keep text from overlapping
2966  where the axes cross. Normal users are not likely to need it.
2967  """
2968  defaults = {"stroke-width":"0.25pt"}
2969  text_defaults = {"stroke":"none", "fill":"black", "font-size":5, "dominant-baseline":"text-before-edge"}
2970  text_start = -1.
2971  text_angle = 0.
2972 
2973  def __repr__(self):
2974  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)
2975 
2976  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):
2977  self.aty = aty
2978  tattr = dict(self.text_defaults)
2979  tattr.update(text_attr)
2980  LineAxis.__init__(self, xmin, aty, xmax, aty, xmin, xmax, ticks, miniticks, labels, logbase, arrow_start, arrow_end, exclude, tattr, **attr)
2981 
2982  def SVG(self, trans=None):
2983  """Apply the transformation "trans" and return an SVG object."""
2984  self.y1 = self.aty
2985  self.y2 = self.aty
2986  return LineAxis.SVG(self, trans)
2987 
2989  """Draws a y axis with tick marks.
2990 
2991  YAxis(ymin, ymax, atx, ticks, miniticks, labels, logbase, arrow_start, arrow_end,
2992  exclude, text_attr, attribute=value)
2993 
2994  ymin, ymax required the y range
2995  atx default=0 x position to draw the axis
2996  ticks default=-10 request ticks according to the standard
2997  tick specification (see help(Ticks))
2998  miniticks default=True request miniticks according to the
2999  standard minitick specification
3000  labels True request tick labels according to the
3001  standard tick label specification
3002  logbase default=None if a number, the y axis is logarithmic
3003  with ticks at the given base (usually 10)
3004  arrow_start default=None if a new string identifier, draw an arrow
3005  at the low-end of the axis, referenced by
3006  that identifier; if an SVG marker object,
3007  use that marker
3008  arrow_end default=None if a new string identifier, draw an arrow
3009  at the high-end of the axis, referenced by
3010  that identifier; if an SVG marker object,
3011  use that marker
3012  exclude default=None if a (low, high) pair, don't draw text
3013  labels within this range
3014  text_attr default={} SVG attributes for the text labels
3015  attribute=value pairs keyword list SVG attributes for all lines
3016 
3017  The exclude option is provided for Axes to keep text from overlapping
3018  where the axes cross. Normal users are not likely to need it.
3019  """
3020  defaults = {"stroke-width":"0.25pt"}
3021  text_defaults = {"stroke":"none", "fill":"black", "font-size":5, "text-anchor":"end", "dominant-baseline":"middle"}
3022  text_start = 2.5
3023  text_angle = 90.
3024 
3025  def __repr__(self):
3026  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)
3027 
3028  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):
3029  self.atx = atx
3030  tattr = dict(self.text_defaults)
3031  tattr.update(text_attr)
3032  LineAxis.__init__(self, atx, ymin, atx, ymax, ymin, ymax, ticks, miniticks, labels, logbase, arrow_start, arrow_end, exclude, tattr, **attr)
3033 
3034  def SVG(self, trans=None):
3035  """Apply the transformation "trans" and return an SVG object."""
3036  self.x1 = self.atx
3037  self.x2 = self.atx
3038  return LineAxis.SVG(self, trans)
3039 
3040 class Axes:
3041  """Draw a pair of intersecting x-y axes.
3042 
3043  Axes(xmin, xmax, ymin, ymax, atx, aty, xticks, xminiticks, xlabels, xlogbase,
3044  yticks, yminiticks, ylabels, ylogbase, arrows, text_attr, attribute=value)
3045 
3046  xmin, xmax required the x range
3047  ymin, ymax required the y range
3048  atx, aty default=0, 0 point where the axes try to cross;
3049  if outside the range, the axes will
3050  cross at the closest corner
3051  xticks default=-10 request ticks according to the standard
3052  tick specification (see help(Ticks))
3053  xminiticks default=True request miniticks according to the
3054  standard minitick specification
3055  xlabels True request tick labels according to the
3056  standard tick label specification
3057  xlogbase default=None if a number, the x axis is logarithmic
3058  with ticks at the given base (usually 10)
3059  yticks default=-10 request ticks according to the standard
3060  tick specification
3061  yminiticks default=True request miniticks according to the
3062  standard minitick specification
3063  ylabels True request tick labels according to the
3064  standard tick label specification
3065  ylogbase default=None if a number, the y axis is logarithmic
3066  with ticks at the given base (usually 10)
3067  arrows default=None if a new string identifier, draw arrows
3068  referenced by that identifier
3069  text_attr default={} SVG attributes for the text labels
3070  attribute=value pairs keyword list SVG attributes for all lines
3071  """
3072  defaults = {"stroke-width":"0.25pt"}
3073  text_defaults = {"stroke":"none", "fill":"black", "font-size":5}
3074 
3075  def __repr__(self):
3076  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)
3077 
3078  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):
3079  self.xmin, self.xmax = xmin, xmax
3080  self.ymin, self.ymax = ymin, ymax
3081  self.atx, self.aty = atx, aty
3082  self.xticks, self.xminiticks, self.xlabels, self.xlogbase = xticks, xminiticks, xlabels, xlogbase
3083  self.yticks, self.yminiticks, self.ylabels, self.ylogbase = yticks, yminiticks, ylabels, ylogbase
3084  self.arrows = arrows
3085 
3087  self.text_attr.update(text_attr)
3088 
3089  self.attr = dict(self.defaults)
3090  self.attr.update(attr)
3091 
3092  def SVG(self, trans=None):
3093  """Apply the transformation "trans" and return an SVG object."""
3094  atx, aty = self.atx, self.aty
3095  if atx < self.xmin: atx = self.xmin
3096  if atx > self.xmax: atx = self.xmax
3097  if aty < self.ymin: aty = self.ymin
3098  if aty > self.ymax: aty = self.ymax
3099 
3100  xmargin = 0.1 * abs(self.ymin - self.ymax)
3101  xexclude = atx - xmargin, atx + xmargin
3102 
3103  ymargin = 0.1 * abs(self.xmin - self.xmax)
3104  yexclude = aty - ymargin, aty + ymargin
3105 
3106  if self.arrows != None and self.arrows != False:
3107  xarrow_start = self.arrows + ".xstart"
3108  xarrow_end = self.arrows + ".xend"
3109  yarrow_start = self.arrows + ".ystart"
3110  yarrow_end = self.arrows + ".yend"
3111  else:
3112  xarrow_start = xarrow_end = yarrow_start = yarrow_end = None
3113 
3114  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)
3115  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)
3116  return SVG("g", *(xaxis.sub + yaxis.sub))
3117 
3118 ######################################################################
3119 
3120 class HGrid(Ticks):
3121  """Draws the horizontal lines of a grid over a specified region
3122  using the standard tick specification (see help(Ticks)) to place the
3123  grid lines.
3124 
3125  HGrid(xmin, xmax, low, high, ticks, miniticks, logbase, mini_attr, attribute=value)
3126 
3127  xmin, xmax required the x range
3128  low, high required the y range
3129  ticks default=-10 request ticks according to the standard
3130  tick specification (see help(Ticks))
3131  miniticks default=False request miniticks according to the
3132  standard minitick specification
3133  logbase default=None if a number, the axis is logarithmic
3134  with ticks at the given base (usually 10)
3135  mini_attr default={} SVG attributes for the minitick-lines
3136  (if miniticks != False)
3137  attribute=value pairs keyword list SVG attributes for the major tick lines
3138  """
3139  defaults = {"stroke-width":"0.25pt", "stroke":"gray"}
3140  mini_defaults = {"stroke-width":"0.25pt", "stroke":"lightgray", "stroke-dasharray":"1,1"}
3141 
3142  def __repr__(self):
3143  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)
3144 
3145  def __init__(self, xmin, xmax, low, high, ticks=-10, miniticks=False, logbase=None, mini_attr={}, **attr):
3146  self.xmin, self.xmax = xmin, xmax
3147 
3149  self.mini_attr.update(mini_attr)
3150 
3151  Ticks.__init__(self, None, low, high, ticks, miniticks, None, logbase)
3152 
3153  self.attr = dict(self.defaults)
3154  self.attr.update(attr)
3155 
3156  def SVG(self, trans=None):
3157  """Apply the transformation "trans" and return an SVG object."""
3158  self.last_ticks, self.last_miniticks = Ticks.interpret(self)
3159 
3160  ticksd = []
3161  for t in self.last_ticks.keys():
3162  ticksd += Line(self.xmin, t, self.xmax, t).Path(trans).d
3163 
3164  miniticksd = []
3165  for t in self.last_miniticks:
3166  miniticksd += Line(self.xmin, t, self.xmax, t).Path(trans).d
3167 
3168  return SVG("g", Path(d=ticksd, **self.attr).SVG(), Path(d=miniticksd, **self.mini_attr).SVG())
3169 
3170 class VGrid(Ticks):
3171  """Draws the vertical lines of a grid over a specified region
3172  using the standard tick specification (see help(Ticks)) to place the
3173  grid lines.
3174 
3175  HGrid(ymin, ymax, low, high, ticks, miniticks, logbase, mini_attr, attribute=value)
3176 
3177  ymin, ymax required the y range
3178  low, high required the x range
3179  ticks default=-10 request ticks according to the standard
3180  tick specification (see help(Ticks))
3181  miniticks default=False request miniticks according to the
3182  standard minitick specification
3183  logbase default=None if a number, the axis is logarithmic
3184  with ticks at the given base (usually 10)
3185  mini_attr default={} SVG attributes for the minitick-lines
3186  (if miniticks != False)
3187  attribute=value pairs keyword list SVG attributes for the major tick lines
3188  """
3189  defaults = {"stroke-width":"0.25pt", "stroke":"gray"}
3190  mini_defaults = {"stroke-width":"0.25pt", "stroke":"lightgray", "stroke-dasharray":"1,1"}
3191 
3192  def __repr__(self):
3193  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)
3194 
3195  def __init__(self, ymin, ymax, low, high, ticks=-10, miniticks=False, logbase=None, mini_attr={}, **attr):
3196  self.ymin, self.ymax = ymin, ymax
3197 
3199  self.mini_attr.update(mini_attr)
3200 
3201  Ticks.__init__(self, None, low, high, ticks, miniticks, None, logbase)
3202 
3203  self.attr = dict(self.defaults)
3204  self.attr.update(attr)
3205 
3206  def SVG(self, trans=None):
3207  """Apply the transformation "trans" and return an SVG object."""
3208  self.last_ticks, self.last_miniticks = Ticks.interpret(self)
3209 
3210  ticksd = []
3211  for t in self.last_ticks.keys():
3212  ticksd += Line(t, self.ymin, t, self.ymax).Path(trans).d
3213 
3214  miniticksd = []
3215  for t in self.last_miniticks:
3216  miniticksd += Line(t, self.ymin, t, self.ymax).Path(trans).d
3217 
3218  return SVG("g", Path(d=ticksd, **self.attr).SVG(), Path(d=miniticksd, **self.mini_attr).SVG())
3219 
3220 class Grid(Ticks):
3221  """Draws a grid over a specified region using the standard tick
3222  specification (see help(Ticks)) to place the grid lines.
3223 
3224  Grid(xmin, xmax, ymin, ymax, ticks, miniticks, logbase, mini_attr, attribute=value)
3225 
3226  xmin, xmax required the x range
3227  ymin, ymax required the y range
3228  ticks default=-10 request ticks according to the standard
3229  tick specification (see help(Ticks))
3230  miniticks default=False request miniticks according to the
3231  standard minitick specification
3232  logbase default=None if a number, the axis is logarithmic
3233  with ticks at the given base (usually 10)
3234  mini_attr default={} SVG attributes for the minitick-lines
3235  (if miniticks != False)
3236  attribute=value pairs keyword list SVG attributes for the major tick lines
3237  """
3238  defaults = {"stroke-width":"0.25pt", "stroke":"gray"}
3239  mini_defaults = {"stroke-width":"0.25pt", "stroke":"lightgray", "stroke-dasharray":"1,1"}
3240 
3241  def __repr__(self):
3242  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)
3243 
3244  def __init__(self, xmin, xmax, ymin, ymax, ticks=-10, miniticks=False, logbase=None, mini_attr={}, **attr):
3245  self.xmin, self.xmax = xmin, xmax
3246  self.ymin, self.ymax = ymin, ymax
3247 
3249  self.mini_attr.update(mini_attr)
3250 
3251  Ticks.__init__(self, None, None, None, ticks, miniticks, None, logbase)
3252 
3253  self.attr = dict(self.defaults)
3254  self.attr.update(attr)
3255 
3256  def SVG(self, trans=None):
3257  """Apply the transformation "trans" and return an SVG object."""
3258  self.low, self.high = self.xmin, self.xmax
3259  self.last_xticks, self.last_xminiticks = Ticks.interpret(self)
3260  self.low, self.high = self.ymin, self.ymax
3261  self.last_yticks, self.last_yminiticks = Ticks.interpret(self)
3262 
3263  ticksd = []
3264  for t in self.last_xticks.keys():
3265  ticksd += Line(t, self.ymin, t, self.ymax).Path(trans).d
3266  for t in self.last_yticks.keys():
3267  ticksd += Line(self.xmin, t, self.xmax, t).Path(trans).d
3268 
3269  miniticksd = []
3270  for t in self.last_xminiticks:
3271  miniticksd += Line(t, self.ymin, t, self.ymax).Path(trans).d
3272  for t in self.last_yminiticks:
3273  miniticksd += Line(self.xmin, t, self.xmax, t).Path(trans).d
3274 
3275  return SVG("g", Path(d=ticksd, **self.attr).SVG(), Path(d=miniticksd, **self.mini_attr).SVG())
3276 
3277 ######################################################################
3278 
3280  """Draws x error bars at a set of points. This is usually used
3281  before (under) a set of Dots at the same points.
3282 
3283  XErrorBars(d, attribute=value)
3284 
3285  d required list of (x,y,xerr...) points
3286  attribute=value pairs keyword list SVG attributes
3287 
3288  If points in d have
3289 
3290  * 3 elements, the third is the symmetric error bar
3291  * 4 elements, the third and fourth are the asymmetric lower and
3292  upper error bar. The third element should be negative,
3293  e.g. (5, 5, -1, 2) is a bar from 4 to 7.
3294  * more than 4, a tick mark is placed at each value. This lets
3295  you nest errors from different sources, correlated and
3296  uncorrelated, statistical and systematic, etc.
3297  """
3298  defaults = {"stroke-width":"0.25pt"}
3299 
3300  def __repr__(self):
3301  return "<XErrorBars (%d nodes)>" % len(self.d)
3302 
3303  def __init__(self, d=[], **attr):
3304  self.d = list(d)
3305 
3306  self.attr = dict(self.defaults)
3307  self.attr.update(attr)
3308 
3309  def SVG(self, trans=None):
3310  """Apply the transformation "trans" and return an SVG object."""
3311  if isinstance(trans, str): trans = totrans(trans) # only once
3312 
3313  output = SVG("g")
3314  for p in self.d:
3315  x, y = p[0], p[1]
3316 
3317  if len(p) == 3: bars = [x - p[2], x + p[2]]
3318  else: bars = [x + pi for pi in p[2:]]
3319 
3320  start, end = min(bars), max(bars)
3321  output.append(LineAxis(start, y, end, y, start, end, bars, False, False, **self.attr).SVG(trans))
3322 
3323  return output
3324 
3326  """Draws y error bars at a set of points. This is usually used
3327  before (under) a set of Dots at the same points.
3328 
3329  YErrorBars(d, attribute=value)
3330 
3331  d required list of (x,y,yerr...) points
3332  attribute=value pairs keyword list SVG attributes
3333 
3334  If points in d have
3335 
3336  * 3 elements, the third is the symmetric error bar
3337  * 4 elements, the third and fourth are the asymmetric lower and
3338  upper error bar. The third element should be negative,
3339  e.g. (5, 5, -1, 2) is a bar from 4 to 7.
3340  * more than 4, a tick mark is placed at each value. This lets
3341  you nest errors from different sources, correlated and
3342  uncorrelated, statistical and systematic, etc.
3343  """
3344  defaults = {"stroke-width":"0.25pt"}
3345 
3346  def __repr__(self):
3347  return "<YErrorBars (%d nodes)>" % len(self.d)
3348 
3349  def __init__(self, d=[], **attr):
3350  self.d = list(d)
3351 
3352  self.attr = dict(self.defaults)
3353  self.attr.update(attr)
3354 
3355  def SVG(self, trans=None):
3356  """Apply the transformation "trans" and return an SVG object."""
3357  if isinstance(trans, str): trans = totrans(trans) # only once
3358 
3359  output = SVG("g")
3360  for p in self.d:
3361  x, y = p[0], p[1]
3362 
3363  if len(p) == 3: bars = [y - p[2], y + p[2]]
3364  else: bars = [y + pi for pi in p[2:]]
3365 
3366  start, end = min(bars), max(bars)
3367  output.append(LineAxis(x, start, x, end, start, end, bars, False, False, **self.attr).SVG(trans))
3368 
3369  return output
def inkscape(self, fileName=None, encoding="utf-8")
Definition: svgfig.py:449
def __repr__(self)
Definition: svgfig.py:2254
dictionary defaults
Definition: svgfig.py:1057
def Path(self, trans=None, local=False)
Definition: svgfig.py:1733
def unumber(x)
Definition: svgfig.py:2283
def __init__(self, x, y, d, attr)
Definition: svgfig.py:1874
def parse_number(self, index, pathdata)
Definition: svgfig.py:1086
int text_angle
Definition: svgfig.py:2386
dictionary mini_defaults
Definition: svgfig.py:3140
def Path(self, trans=None, local=False)
Definition: svgfig.py:2170
def __repr__(self)
Definition: svgfig.py:2882
dictionary defaults
Definition: svgfig.py:3344
def __repr__(self)
Definition: svgfig.py:3075
def __init__(self, x1, y1, x2, y2, attr)
Definition: svgfig.py:2194
def __init__(self, d=[], symbol=None, width=1., height=1., attr)
Definition: svgfig.py:1923
def __init__(self, f, low, high, ticks=-10, miniticks=True, labels=True, logbase=None, arrow_start=None, arrow_end=None, text_attr={}, attr)
Definition: svgfig.py:2391
def __init__(self, x1, x2, y, attr)
Definition: svgfig.py:2164
def subsample(self, left, right, depth, trans=None)
Definition: svgfig.py:1618
def funcRtoR2(expr, var="t", globals=None, locals=None)
Definition: svgfig.py:1468
bool random_sampling
Definition: svgfig.py:1510
def extend(self, x)
Definition: svgfig.py:191
def funcRtoC(expr, var="t", globals=None, locals=None)
Definition: svgfig.py:1451
def SVG(self, trans=None)
Definition: svgfig.py:1649
dictionary defaults
Definition: svgfig.py:1869
def __eq__(self, other)
Definition: svgfig.py:172
def depth_first(self, depth_limit=None)
end nested class
Definition: svgfig.py:234
dictionary defaults
Definition: svgfig.py:1716
def make_marker(id, shape, attr)
Definition: svgfig.py:1964
def __repr__(self)
Definition: svgfig.py:1920
def __repr__(self)
Definition: svgfig.py:2388
int text_ytitle_offset
Definition: svgfig.py:881
def __init__(self, xmin, xmax, low, high, ticks=-10, miniticks=False, logbase=None, mini_attr={}, attr)
Definition: svgfig.py:3145
int text_xaxis_offset
Definition: svgfig.py:878
def __repr__(self)
Definition: svgfig.py:2133
def SVG(self, trans=None)
Definition: svgfig.py:3156
def __init__(self, f, low, high, loop=False, attr)
Definition: svgfig.py:1518
def prepend(self, x)
Definition: svgfig.py:186
dictionary axis_defaults
Definition: svgfig.py:874
def parse(self, pathdata)
Definition: svgfig.py:1117
def __repr__(self)
Definition: svgfig.py:3142
def evaluate(self, f, trans)
Definition: svgfig.py:1542
def parse_boolean(self, index, pathdata)
Definition: svgfig.py:1104
def SVG(self, trans=None)
Definition: svgfig.py:2263
def compute_logminiticks(self, base)
Definition: svgfig.py:2767
def compute_miniticks(self, original_ticks)
Definition: svgfig.py:2689
def __init__(self, xmin, xmax, ymin, ymax, d, kwds)
Definition: svgfig.py:822
def SVG(self, trans=None)
Definition: svgfig.py:1938
def __init__(self, x, y, ax, ay, b, attr)
Definition: svgfig.py:2257
def __init__(self, d=[], attr)
Definition: svgfig.py:1062
def __repr__(self)
Definition: svgfig.py:3346
def SVG(self, trans=None)
Definition: svgfig.py:751
def append(self, x)
Definition: svgfig.py:181
def SVG(self, trans=None)
Definition: svgfig.py:3309
def __repr__(self)
Definition: svgfig.py:1552
end Sample
Definition: svgfig.py:1551
def canvas_outline(sub, attr)
Definition: svgfig.py:509
float tick_end
Definition: svgfig.py:2382
def __init__(self, xmin, xmax, ymin, ymax, d, kwds)
Definition: svgfig.py:886
def __init__(self, f, low, high, ticks=-10, miniticks=True, labels=True, logbase=None, arrow_start=None, arrow_end=None, text_attr={}, attr)
Definition: svgfig.py:2825
def link(self, left, right)
Definition: svgfig.py:1540
def __init__(self, x1, y1, x2, y2, arrow_start=None, arrow_end=None, attr)
Definition: svgfig.py:1995
def SVG(self, trans=None)
Definition: svgfig.py:3092
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)
Definition: svgfig.py:3078
def __init__(self, d, kwds)
Definition: svgfig.py:741
def SVG(self, trans=None)
Definition: svgfig.py:1854
int discontinuity_limit
Definition: svgfig.py:1513
dictionary text_defaults
Definition: svgfig.py:2380
def SVG(self, trans=None)
Definition: svgfig.py:3206
def __repr__(self)
Definition: svgfig.py:2973
def attr_preprocess(attr)
Definition: svgfig.py:46
dictionary mini_defaults
Definition: svgfig.py:3190
def __init__(self, x, y, d, attr)
Definition: svgfig.py:1847
def __repr__(self)
Definition: svgfig.py:2069
def regular_miniticks(self, N)
Definition: svgfig.py:2677
def __init__(self, x1, y1, x2, y2, local1=False, local2=False, arrow_start=None, arrow_end=None, attr)
Definition: svgfig.py:2076
def __repr__(self)
Definition: svgfig.py:1871
def __getitem__(self, ti)
Definition: svgfig.py:135
def __repr__(self)
Definition: svgfig.py:1718
def __init__(self, t)
Definition: svgfig.py:1538
def SVG(self, trans=None)
Definition: svgfig.py:2982
def save(self, fileName=None, encoding="utf-8", compresslevel=None)
Definition: svgfig.py:408
def interpret(self)
Definition: svgfig.py:2894
def Path(self, trans=None, local=False)
Definition: svgfig.py:1653
float minitick_end
Definition: svgfig.py:2384
def firefox(self, fileName=None, encoding="utf-8")
Definition: svgfig.py:461
def SVG(self, trans=None)
Definition: svgfig.py:3256
float minitick_length
Definition: svgfig.py:877
def __init__(self, svg, ti, depth_limit)
Definition: svgfig.py:206
def __repr__(self)
Definition: svgfig.py:816
def SVG(self, trans=None)
Definition: svgfig.py:3355
def SVG(self, trans=None)
Definition: svgfig.py:2084
def standalone_xml(self, indent=" ", newl="\n")
Definition: svgfig.py:357
def __repr__(self)
Definition: svgfig.py:3241
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)
Definition: svgfig.py:3028
Abs< T >::type abs(const T &t)
Definition: Abs.h:22
def __repr__(self)
Definition: svgfig.py:2191
def __repr__(self)
Definition: svgfig.py:2822
def template(fileName, svg, replaceme="REPLACEME")
Definition: svgfig.py:520
def sample(self, trans=None)
end nested class
Definition: svgfig.py:1575
def window(xmin, xmax, ymin, ymax, x=0, y=0, width=100, height=100, xlogbase=None, ylogbase=None, minusInfinity=-1000, flipx=False, flipy=True)
Definition: svgfig.py:642
dictionary text_defaults
Definition: svgfig.py:3073
def tree(self, depth_limit=None, sub=True, attr=True, text=True, tree_width=20, obj_width=80)
Definition: svgfig.py:290
def load_stream(stream)
Definition: svgfig.py:550
T min(T a, T b)
Definition: MathUtil.h:58
def inkview(self, fileName=None, encoding="utf-8")
Definition: svgfig.py:437
def __len__(self)
Definition: svgfig.py:1556
def SVG(self, trans=None)
Definition: svgfig.py:2435
int recursion_limit
Definition: svgfig.py:1511
def breadth_first(self, depth_limit=None)
Definition: svgfig.py:239
dictionary defaults
Definition: svgfig.py:2379
def __str__(self)
Definition: svgfig.py:286
def Path(self, trans=None, local=False)
Definition: svgfig.py:2034
def compute_logticks(self, base, N, format)
Definition: svgfig.py:2717
def __repr__(self)
Definition: svgfig.py:3192
def __repr__(self)
Definition: svgfig.py:284
def SVG(self, trans=None)
Definition: svgfig.py:856
def __repr__(self)
Definition: svgfig.py:1992
def SVG(self, trans=None)
Definition: svgfig.py:1226
def __init__(self, t_sub, attr)
Definition: svgfig.py:123
def SVG(self, trans=None)
Definition: svgfig.py:3034
dictionary defaults
Definition: svgfig.py:3298
def next(self)
Definition: svgfig.py:1568
def interpret_fileName(self, fileName=None)
Definition: svgfig.py:401
def __init__(self, ymin, ymax, low, high, ticks=-10, miniticks=False, logbase=None, mini_attr={}, attr)
Definition: svgfig.py:3195
dictionary defaults
Definition: svgfig.py:1918
dictionary text_defaults
Definition: svgfig.py:873
def __ne__(self, other)
Definition: svgfig.py:177
def make_symbol(id, shape="dot", attr)
Definition: svgfig.py:1893
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def values(self, sub=True, attr=True, text=True)
Definition: svgfig.py:275
def __init__(self, d=[], mode="L", loop=False, attr)
Definition: svgfig.py:1721
def __repr__(self)
Definition: svgfig.py:883
float tick_length
Definition: svgfig.py:876
int text_yaxis_offset
Definition: svgfig.py:879
def __repr__(self)
Definition: svgfig.py:733
def load(fileName)
Definition: svgfig.py:546
def funcRtoR(expr, var="x", globals=None, locals=None)
Definition: svgfig.py:1483
dictionary defaults
Definition: svgfig.py:3072
def __repr__(self)
Definition: svgfig.py:3300
def orient_tickmark(self, t, trans=None)
Definition: svgfig.py:2408
def __repr__(self)
Definition: svgfig.py:2161
def __repr__(self)
Definition: svgfig.py:1515
def __init__(self, left, right)
Definition: svgfig.py:1554
def __repr__(self)
Definition: svgfig.py:3025
float tick_start
Definition: svgfig.py:2381
def pathtoPath(svg)
Definition: svgfig.py:1006
def __iter__(self)
Definition: svgfig.py:1564
def SVG(self, trans=None)
Definition: svgfig.py:2002
def __setitem__(self, ti, value)
Definition: svgfig.py:146
def clone(self, shallow=False)
Definition: svgfig.py:195
def Path(self, trans=None, local=False)
Definition: svgfig.py:2204
def __contains__(self, value)
Definition: svgfig.py:168
def __init__(self, d=[], attr)
Definition: svgfig.py:3349
def interpret(self)
Definition: svgfig.py:2507
dictionary mini_defaults
Definition: svgfig.py:3239
float linearity_limit
Definition: svgfig.py:1512
def __delitem__(self, ti)
Definition: svgfig.py:157
def canvas(sub, attr)
Definition: svgfig.py:481
def SVG(self, trans=None)
Definition: svgfig.py:1729
def __standalone_xml(self, indent, newl)
Definition: svgfig.py:372
def SVG(self)
Definition: svgfig.py:955
float minitick_start
Definition: svgfig.py:2383
dictionary defaults
Definition: svgfig.py:1842
def SVG(self, trans=None)
Definition: svgfig.py:1881
int text_xtitle_offset
Definition: svgfig.py:880
def __repr__(self)
Definition: svgfig.py:1529
def SVG(self, trans=None)
Definition: svgfig.py:2831
def __iter__(self)
Definition: svgfig.py:246
def xml(self, indent=" ", newl="\n", depth_limit=None, depth=0)
Definition: svgfig.py:319
def SVG(self, trans=None)
Definition: svgfig.py:2911
#define str(s)
dictionary defaults
Definition: svgfig.py:1509
def __repr__(self)
Definition: svgfig.py:1844
def keys(self, sub=True, attr=True, text=True)
Definition: svgfig.py:266
def __init__(self, xmin, xmax, ymin, ymax, ticks=-10, miniticks=False, logbase=None, mini_attr={}, attr)
Definition: svgfig.py:3244
def Path(self, trans=None, local=False)
Definition: svgfig.py:2267
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)
Definition: svgfig.py:2976
double split
Definition: MVATrainer.cc:139
dictionary defaults
Definition: svgfig.py:2067
def __init__(self, y1, y2, x, attr)
Definition: svgfig.py:2136
def rotate(angle, cx=0, cy=0)
Definition: svgfig.py:704
def rgb(r, g, b, maximum=1.)
Definition: svgfig.py:39
def parse_whitespace(self, index, pathdata)
Definition: svgfig.py:1069
def items(self, sub=True, attr=True, text=True)
Definition: svgfig.py:248
float text_start
Definition: svgfig.py:2385
nested class Sample
Definition: svgfig.py:1528
def Path(self, trans=None, local=False)
Definition: svgfig.py:2142
output
Definition: svgfig.py:559
def parse_command(self, index, pathdata)
Definition: svgfig.py:1074
def totrans(expr, vars=("x","y"), globals=None, locals=None)
Definition: svgfig.py:597
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)
Definition: svgfig.py:2885
def SVG(self, trans=None)
Definition: svgfig.py:2200
def compute_ticks(self, N, format)
Definition: svgfig.py:2601
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
def __repr__(self)
Definition: svgfig.py:1059
def __init__(self, d=[], attr)
Definition: svgfig.py:3303