19 from builtins
import range
20 import re, codecs, os, platform, copy, itertools, math, cmath, random, sys, copy
24 if re.search(
"windows", platform.system(), re.I):
27 _default_directory = _winreg.QueryValueEx(_winreg.OpenKey(_winreg.HKEY_CURRENT_USER, \
28 r"Software\Microsoft\Windows\Current Version\Explorer\Shell Folders"),
"Desktop")[0]
33 _default_directory = os.path.expanduser(
"~") + os.sep +
"Desktop"
35 _default_fileName =
"tmp.svg"
38 _hacks[
"inkscape-text-vertical-shift"] =
False
40 def rgb(r, g, b, maximum=1.):
41 """Create an SVG color string "#xxyyzz" from r, g, and b.
43 r,g,b = 0 is black and r,g,b = maximum is white.
45 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)))
48 for name
in attr.keys():
49 name_colon = re.sub(
"__",
":", name)
50 if name_colon != name:
51 attr[name_colon] = attr[name]
55 name_dash = re.sub(
"_",
"-", name)
57 attr[name_dash] = attr[name]
64 """A tree representation of an SVG image or image fragment.
66 SVG(t, sub, sub, sub..., attribute=value)
68 t required SVG type name
69 sub optional list nested SVG elements or text/Unicode
70 attribute=value pairs optional keywords SVG attributes
72 In attribute names, "__" becomes ":" and "_" becomes "-".
76 <g id="mygroup" fill="blue">
77 <rect x="1" y="1" width="2" height="2" />
78 <rect x="3" y="3" width="2" height="2" />
83 >>> svg = SVG("g", SVG("rect", x=1, y=1, width=2, height=2), \
84 ... SVG("rect", x=3, y=3, width=2, height=2), \
85 ... id="mygroup", fill="blue")
87 Sub-elements and attributes may be accessed through tree-indexing:
89 >>> svg = SVG("text", SVG("tspan", "hello there"), stroke="none", fill="black")
97 Iteration is depth-first:
99 >>> svg = SVG("g", SVG("g", SVG("line", x1=0, y1=0, x2=1, y2=1)), \
100 ... SVG("text", SVG("tspan", "hello again")))
102 >>> for ti, s in svg:
103 ... print ti, repr(s)
106 (0, 0) <line x2=1 y1=0 x1=0 y2=1 />
111 (1,) <text (1 sub) />
112 (1, 0) <tspan (1 sub) />
113 (1, 0, 0) 'hello again'
115 Use "print" to navigate:
120 [0, 0] <line x2=1 y1=0 x1=0 y2=1 />
122 [1, 0] <tspan (1 sub) />
125 if len(t_sub) == 0:
raise TypeError(
"SVG element must have a t (SVG type)")
137 """Index is a list that descends tree, returning a sub-element if
138 it ends with a number and an attribute if it ends with a string."""
140 if isinstance(ti, (list, tuple)):
141 for i
in ti[:-1]: obj = obj[i]
144 if isinstance(ti, (int, long, slice)):
return obj.sub[ti]
145 else:
return obj.attr[ti]
148 """Index is a list that descends tree, returning a sub-element if
149 it ends with a number and an attribute if it ends with a string."""
151 if isinstance(ti, (list, tuple)):
152 for i
in ti[:-1]: obj = obj[i]
155 if isinstance(ti, (int, long, slice)): obj.sub[ti] = value
156 else: obj.attr[ti] = value
159 """Index is a list that descends tree, returning a sub-element if
160 it ends with a number and an attribute if it ends with a string."""
162 if isinstance(ti, (list, tuple)):
163 for i
in ti[:-1]: obj = obj[i]
166 if isinstance(ti, (int, long, slice)): del obj.sub[ti]
167 else: del obj.attr[ti]
170 """x in svg == True iff x is an attribute in svg."""
171 return value
in self.
attr
174 """x == y iff x represents the same SVG as y."""
175 if id(self) ==
id(other):
return True
176 return isinstance(other, SVG)
and self.
t == other.t
and self.
sub == other.sub
and self.
attr == other.attr
179 """x != y iff x does not represent the same SVG as y."""
180 return not (self == other)
183 """Appends x to the list of sub-elements (drawn last, overlaps
184 other primatives)."""
188 """Prepends x to the list of sub-elements (drawn first may be
189 overlapped by other primatives)."""
193 """Extends list of sub-elements by a list x."""
197 """Deep copy of SVG tree. Set shallow=True for a shallow copy."""
199 return copy.copy(self)
201 return copy.deepcopy(self)
205 """Manages SVG iteration."""
219 return self.
ti, self.
svg
221 if not isinstance(self.
svg, SVG):
raise StopIteration
224 if "iterators" not in self.__dict__:
226 for i, s
in enumerate(self.
svg.sub):
228 for k, s
in self.
svg.attr.items():
236 """Returns a depth-first generator over the SVG. If depth_limit
237 is a number, stop recursion at that depth."""
241 """Not implemented yet. Any ideas on how to do it?
243 Returns a breadth-first generator over the SVG. If depth_limit
244 is a number, stop recursion at that depth."""
245 raise NotImplementedError(
"Got an algorithm for breadth-first searching a tree without effectively copying the tree?")
249 def items(self, sub=True, attr=True, text=True):
250 """Get a recursively-generated list of tree-index, sub-element/attribute pairs.
252 If sub == False, do not show sub-elements.
253 If attr == False, do not show attributes.
254 If text == False, do not show text/Unicode sub-elements.
259 if isinstance(ti[-1], (int, long)):
260 if isinstance(s, str): show = text
264 if show: output.append((ti, s))
267 def keys(self, sub=True, attr=True, text=True):
268 """Get a recursively-generated list of tree-indexes.
270 If sub == False, do not show sub-elements.
271 If attr == False, do not show attributes.
272 If text == False, do not show text/Unicode sub-elements.
274 return [ti
for ti, s
in self.
items(sub, attr, text)]
276 def values(self, sub=True, attr=True, text=True):
277 """Get a recursively-generated list of sub-elements and attributes.
279 If sub == False, do not show sub-elements.
280 If attr == False, do not show attributes.
281 If text == False, do not show text/Unicode sub-elements.
283 return [s
for ti, s
in self.
items(sub, attr, text)]
288 """Print (actually, return a string of) the tree in a form useful for browsing."""
289 return self.
tree(sub=
True, attr=
False, text=
False)
291 def tree(self, depth_limit=None, sub=True, attr=True, text=True, tree_width=20, obj_width=80):
292 """Print (actually, return a string of) the tree in a form useful for browsing.
294 If depth_limit == a number, stop recursion at that depth.
295 If sub == False, do not show sub-elements.
296 If attr == False, do not show attributes.
297 If text == False, do not show text/Unicode sub-elements.
298 tree_width is the number of characters reserved for printing tree indexes.
299 obj_width is the number of characters reserved for printing sub-elements/attributes.
304 line =
"%s %s" % ((
"%%-%ds" % tree_width) % repr(
None), (
"%%-%ds" % obj_width) % (repr(self))[0:obj_width])
309 if isinstance(ti[-1], (int, long)):
310 if isinstance(s, str): show = text
315 line =
"%s %s" % ((
"%%-%ds" % tree_width) % repr(
list(ti)), (
"%%-%ds" % obj_width) % (
" "*len(ti) + repr(s))[0:obj_width])
318 return "\n".
join(output)
320 def xml(self, indent=" ", newl="\n", depth_limit=None, depth=0):
321 """Get an XML representation of the SVG.
323 indent string used for indenting
324 newl string used for newlines
325 If depth_limit == a number, stop recursion at that depth.
326 depth starting depth (not useful for users)
333 if isinstance(v, dict):
334 v =
"; ".
join([
"%s:%s" % (ni, vi)
for ni, vi
in v.items()])
335 elif isinstance(v, (list, tuple)):
337 attrstr.append(
" %s=%s" % (n, repr(v)))
338 attrstr =
"".
join(attrstr)
340 if len(self.
sub) == 0:
return "%s<%s%s />" % (indent * depth, self.
t, attrstr)
342 if depth_limit ==
None or depth_limit > depth:
345 if isinstance(s, SVG):
346 substr.append(s.xml(indent, newl, depth_limit, depth + 1) + newl)
347 elif isinstance(s, str):
348 substr.append(
"%s%s%s" % (indent * (depth + 1), s, newl))
350 substr.append(
"%s%s%s" % (indent * (depth + 1), repr(s), newl))
351 substr =
"".
join(substr)
353 return "%s<%s%s>%s%s%s</%s>" % (indent * depth, self.
t, attrstr, newl, substr, indent * depth, self.
t)
356 return "%s<%s (%d sub)%s />" % (indent * depth, self.
t, len(self.
sub), attrstr)
359 """Get an XML representation of the SVG that can be saved/rendered.
361 indent string used for indenting
362 newl string used for newlines
365 if self.
t ==
"svg": top = self
368 <?xml version="1.0" standalone="no"?>
369 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
371 """ + (
"".
join(top.__standalone_xml(indent, newl)))
374 output = [
u"<%s" % self.
t]
377 if isinstance(v, dict):
378 v =
"; ".
join([
"%s:%s" % (ni, vi)
for ni, vi
in v.items()])
379 elif isinstance(v, (list, tuple)):
381 output.append(
u" %s=\"%s\"" % (n, v))
383 if len(self.
sub) == 0:
384 output.append(
u" />%s%s" % (newl, newl))
387 elif self.
t ==
"text" or self.
t ==
"tspan" or self.
t ==
"style":
391 output.append(
u">%s%s" % (newl, newl))
394 if isinstance(s, SVG): output.extend(s.__standalone_xml(indent, newl))
395 else: output.append(
unicode(s))
397 if self.
t ==
"tspan": output.append(
u"</%s>" % self.
t)
398 else: output.append(
u"</%s>%s%s" % (self.
t, newl, newl))
404 fileName = _default_fileName
405 if re.search(
"windows", platform.system(), re.I)
and not os.path.isabs(fileName):
406 fileName = _default_directory + os.sep + fileName
409 def save(self, fileName=None, encoding="utf-8", compresslevel=None):
410 """Save to a file for viewing. Note that svg.save() overwrites the file named _default_fileName.
412 fileName default=None note that _default_fileName will be overwritten if
413 no fileName is specified. If the extension
414 is ".svgz" or ".gz", the output will be gzipped
415 encoding default="utf-8" file encoding (default is Unicode)
416 compresslevel default=None if a number, the output will be gzipped with that
417 compression level (1-9, 1 being fastest and 9 most
422 if compresslevel !=
None or re.search(
"\.svgz$", fileName, re.I)
or re.search(
"\.gz$", fileName, re.I):
424 if compresslevel ==
None:
425 f = gzip.GzipFile(fileName,
"w")
427 f = gzip.GzipFile(fileName,
"w", compresslevel)
429 f = codecs.EncodedFile(f,
"utf-8", encoding)
434 f = codecs.open(fileName,
"w", encoding=encoding)
438 def inkview(self, fileName=None, encoding="utf-8"):
439 """View in "inkview", assuming that program is available on your system.
441 fileName default=None note that any file named _default_fileName will be
442 overwritten if no fileName is specified. If the extension
443 is ".svgz" or ".gz", the output will be gzipped
444 encoding default="utf-8" file encoding (default is Unicode)
447 self.
save(fileName, encoding)
448 os.spawnvp(os.P_NOWAIT,
"inkview", (
"inkview", fileName))
450 def inkscape(self, fileName=None, encoding="utf-8"):
451 """View in "inkscape", assuming that program is available on your system.
453 fileName default=None note that any file named _default_fileName will be
454 overwritten if no fileName is specified. If the extension
455 is ".svgz" or ".gz", the output will be gzipped
456 encoding default="utf-8" file encoding (default is Unicode)
459 self.
save(fileName, encoding)
460 os.spawnvp(os.P_NOWAIT,
"inkscape", (
"inkscape", fileName))
462 def firefox(self, fileName=None, encoding="utf-8"):
463 """View in "firefox", assuming that program is available on your system.
465 fileName default=None note that any file named _default_fileName will be
466 overwritten if no fileName is specified. If the extension
467 is ".svgz" or ".gz", the output will be gzipped
468 encoding default="utf-8" file encoding (default is Unicode)
471 self.
save(fileName, encoding)
472 os.spawnvp(os.P_NOWAIT,
"firefox", (
"firefox", fileName))
476 _canvas_defaults = {
"width":
"400px",
"height":
"400px",
"viewBox":
"0 0 100 100", \
477 "xmlns":
"http://www.w3.org/2000/svg",
"xmlns:xlink":
"http://www.w3.org/1999/xlink",
"version":
"1.1", \
478 "style": {
"stroke":
"black",
"fill":
"none",
"stroke-width":
"0.5pt",
"stroke-linejoin":
"round",
"text-anchor":
"middle"}, \
479 "font-family": [
"Helvetica",
"Arial",
"FreeSans",
"Sans",
"sans",
"sans-serif"], \
483 """Creates a top-level SVG object, allowing the user to control the
484 image size and aspect ratio.
486 canvas(sub, sub, sub..., attribute=value)
488 sub optional list nested SVG elements or text/Unicode
489 attribute=value pairs optional keywords SVG attributes
491 Default attribute values:
495 viewBox "0 0 100 100"
496 xmlns "http://www.w3.org/2000/svg"
497 xmlns:xlink "http://www.w3.org/1999/xlink"
499 style "stroke:black; fill:none; stroke-width:0.5pt; stroke-linejoin:round; text-anchor:middle"
500 font-family "Helvetica,Arial,FreeSans?,Sans,sans,sans-serif"
502 attributes = dict(_canvas_defaults)
503 attributes.update(attr)
505 if sub ==
None or sub == ():
506 return SVG(
"svg", **attributes)
508 return SVG(
"svg", *sub, **attributes)
511 """Same as canvas(), but draws an outline around the drawable area,
512 so that you know how close your image is to the edges."""
513 svg =
canvas(*sub, **attr)
514 match = re.match(
"[, \t]*([0-9e.+\-]+)[, \t]+([0-9e.+\-]+)[, \t]+([0-9e.+\-]+)[, \t]+([0-9e.+\-]+)[, \t]*", svg[
"viewBox"])
515 if match ==
None:
raise ValueError(
"canvas viewBox is incorrectly formatted")
516 x, y, width, height = [
float(x)
for x
in match.groups()]
517 svg.prepend(
SVG(
"rect", x=x, y=y, width=width, height=height, stroke=
"none", fill=
"cornsilk"))
518 svg.append(
SVG(
"rect", x=x, y=y, width=width, height=height, stroke=
"black", fill=
"none"))
521 def template(fileName, svg, replaceme="REPLACEME"):
522 """Loads an SVG image from a file, replacing instances of
523 <REPLACEME /> with a given svg object.
525 fileName required name of the template SVG
526 svg required SVG object for replacement
527 replaceme default="REPLACEME" fake SVG element to be replaced by the given object
529 >>> print load("template.svg")
530 None <svg (2 sub) style=u'stroke:black; fill:none; stroke-width:0.5pt; stroke-linejoi
531 [0] <rect height=u'100' width=u'100' stroke=u'none' y=u'0' x=u'0' fill=u'yellow'
534 >>> print template("template.svg", SVG("circle", cx=50, cy=50, r=30))
535 None <svg (2 sub) style=u'stroke:black; fill:none; stroke-width:0.5pt; stroke-linejoi
536 [0] <rect height=u'100' width=u'100' stroke=u'none' y=u'0' x=u'0' fill=u'yellow'
537 [1] <circle cy=50 cx=50 r=30 />
539 output =
load(fileName)
541 if isinstance(s, SVG)
and s.t == replaceme:
548 """Loads an SVG image from a file."""
552 """Loads an SVG image from a stream (can be a string or a file object)."""
554 from xml.sax
import handler, make_parser
555 from xml.sax.handler
import feature_namespaces, feature_external_ges, feature_external_pes
557 class ContentHandler(handler.ContentHandler):
561 self.all_whitespace = re.compile(
"^\s*$")
563 def startElement(self, name, attr):
565 s.attr = dict(attr.items())
566 if len(self.stack) > 0:
567 last = self.stack[-1]
571 def characters(self, ch):
572 if not isinstance(ch, str)
or self.all_whitespace.
match(ch) ==
None:
573 if len(self.stack) > 0:
574 last = self.stack[-1]
575 if len(last.sub) > 0
and isinstance(last.sub[-1], str):
576 last.sub[-1] = last.sub[-1] +
"\n" + ch
580 def endElement(self, name):
581 if len(self.stack) > 0:
582 last = self.stack[-1]
583 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):
584 last.sub[0] =
"<![CDATA[\n" + last.sub[0] +
"]]>"
586 self.output = self.stack.pop()
588 ch = ContentHandler()
589 parser = make_parser()
590 parser.setContentHandler(ch)
591 parser.setFeature(feature_namespaces, 0)
592 parser.setFeature(feature_external_ges, 0)
598 def totrans(expr, vars=(
"x",
"y"), globals=
None, locals=
None):
599 """Converts to a coordinate transformation (a function that accepts
600 two arguments and returns two values).
602 expr required a string expression or a function
603 of two real or one complex value
604 vars default=("x", "y") independent variable names;
605 a singleton ("z",) is interpreted
607 globals default=None dict of global variables
608 locals default=None dict of local variables
612 if expr.__code__.co_argcount == 2:
615 elif expr.__code__.co_argcount == 1:
616 split =
lambda z: (z.real, z.imag)
617 output =
lambda x, y:
split(
expr(x + y*1j))
618 output.__name__ = expr.__name__
622 raise TypeError(
"must be a function of 2 or 1 variables")
626 if globals !=
None: g.update(globals)
627 output = eval(
"lambda %s, %s: (%s)" % (vars[0], vars[1], expr), g, locals)
628 output.__name__ =
"%s,%s -> %s" % (vars[0], vars[1], expr)
633 if globals !=
None: g.update(globals)
634 output = eval(
"lambda %s: (%s)" % (vars[0], expr), g, locals)
635 split =
lambda z: (z.real, z.imag)
637 output2.__name__ =
"%s -> %s" % (vars[0], expr)
641 raise TypeError(
"vars must have 2 or 1 elements")
643 def window(xmin, xmax, ymin, ymax, x=0, y=0, width=100, height=100, xlogbase=None, ylogbase=None, minusInfinity=-1000, flipx=False, flipy=True):
644 """Creates and returns a coordinate transformation (a function that
645 accepts two arguments and returns two values) that transforms from
646 (xmin, ymin), (xmax, ymax)
648 (x, y), (x + width, y + height).
650 xlogbase, ylogbase default=None, None if a number, transform
651 logarithmically with given base
652 minusInfinity default=-1000 what to return if
653 log(0 or negative) is attempted
654 flipx default=False if true, reverse the direction of x
655 flipy default=True if true, reverse the direction of y
657 (When composing windows, be sure to set flipy=False.)
677 if xlogbase !=
None and (ix1 <= 0.
or ix2 <= 0.):
raise ValueError(
"x range incompatible with log scaling: (%g, %g)" % (ix1, ix2))
679 if ylogbase !=
None and (iy1 <= 0.
or iy2 <= 0.):
raise ValueError(
"y range incompatible with log scaling: (%g, %g)" % (iy1, iy2))
681 def maybelog(t, it1, it2, ot1, ot2, logbase):
682 if t <= 0.:
return minusInfinity
684 return ot1 + 1.*(math.log(t, logbase) - math.log(it1, logbase))/(math.log(it2, logbase) - math.log(it1, logbase)) * (ot2 - ot1)
686 xlogstr, ylogstr =
"",
""
689 xfunc =
lambda x: ox1 + 1.*(x - ix1)/(ix2 - ix1) * (ox2 - ox1)
691 xfunc =
lambda x: maybelog(x, ix1, ix2, ox1, ox2, xlogbase)
692 xlogstr =
" xlog=%g" % xlogbase
695 yfunc =
lambda y: oy1 + 1.*(y - iy1)/(iy2 - iy1) * (oy2 - oy1)
697 yfunc =
lambda y: maybelog(y, iy1, iy2, oy1, oy2, ylogbase)
698 ylogstr =
" ylog=%g" % ylogbase
700 output =
lambda x, y: (xfunc(x), yfunc(y))
702 output.__name__ =
"(%g, %g), (%g, %g) -> (%g, %g), (%g, %g)%s%s" % (ix1, ix2, iy1, iy2, ox1, ox2, oy1, oy2, xlogstr, ylogstr)
706 """Creates and returns a coordinate transformation which rotates
707 around (cx,cy) by "angle" degrees."""
708 angle *= math.pi/180.
709 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))
712 """Stores graphics primitive objects and applies a single coordinate
713 transformation to them. To compose coordinate systems, nest Fig
716 Fig(obj, obj, obj..., trans=function)
718 obj optional list a list of drawing primatives
719 trans default=None a coordinate transformation function
721 >>> fig = Fig(Line(0,0,1,1), Rect(0.2,0.2,0.8,0.8), trans="2*x, 2*y")
722 >>> print fig.SVG().xml()
724 <path d='M0 0L2 2' />
725 <path d='M0.4 0.4L1.6 0.4ZL1.6 1.6ZL0.4 1.6ZL0.4 0.4ZZ' />
727 >>> print Fig(fig, trans="x/2., y/2.").SVG().xml()
729 <path d='M0 0L1 1' />
730 <path d='M0.2 0.2L0.8 0.2ZL0.8 0.8ZL0.2 0.8ZL0.2 0.2ZZ' />
736 return "<Fig (%d items)>" % len(self.
d)
737 elif isinstance(self.
trans, str):
738 return "<Fig (%d items) x,y -> %s>" % (len(self.
d), self.
trans)
740 return "<Fig (%d items) %s>" % (len(self.
d), self.
trans.__name__)
744 defaults = {
"trans":
None}
745 defaults.update(kwds)
748 self.
trans = kwds[
"trans"]; del kwds[
"trans"]
750 raise TypeError(
"Fig() got unexpected keyword arguments %s" % kwds.keys())
752 def SVG(self, trans=None):
753 """Apply the transformation "trans" and return an SVG object.
755 Coordinate transformations in nested Figs will be composed.
758 if trans ==
None: trans = self.
trans
759 if isinstance(trans, str): trans =
totrans(trans)
763 if isinstance(s, SVG):
766 elif isinstance(s, Fig):
768 if isinstance(strans, str): strans =
totrans(strans)
770 if trans ==
None: subtrans = strans
771 elif strans ==
None: subtrans = trans
772 else: subtrans =
lambda x,y:
trans(*strans(x, y))
774 output.sub += s.SVG(subtrans).sub
779 output.append(s.SVG(trans))
784 """Acts like Fig, but draws a coordinate axis. You also need to supply plot ranges.
786 Plot(xmin, xmax, ymin, ymax, obj, obj, obj..., keyword options...)
788 xmin, xmax required minimum and maximum x values (in the objs' coordinates)
789 ymin, ymax required minimum and maximum y values (in the objs' coordinates)
790 obj optional list drawing primatives
791 keyword options keyword list options defined below
793 The following are keyword options, with their default values:
795 trans None transformation function
796 x, y 5, 5 upper-left corner of the Plot in SVG coordinates
797 width, height 90, 90 width and height of the Plot in SVG coordinates
798 flipx, flipy False, True flip the sign of the coordinate axis
799 minusInfinity -1000 if an axis is logarithmic and an object is plotted at 0 or
800 a negative value, -1000 will be used as a stand-in for NaN
801 atx, aty 0, 0 the place where the coordinate axes cross
802 xticks -10 request ticks according to the standard tick specification
804 xminiticks True request miniticks according to the standard minitick
806 xlabels True request tick labels according to the standard tick label
808 xlogbase None if a number, the axis and transformation are logarithmic
809 with ticks at the given base (10 being the most common)
811 arrows None if a new identifier, create arrow markers and draw them
812 at the ends of the coordinate axes
813 text_attr {} a dictionary of attributes for label text
814 axis_attr {} a dictionary of attributes for the axis lines
819 return "<Plot (%d items)>" % len(self.
d)
821 return "<Plot (%d items) %s>" % (len(self.
d), self.
trans.__name__)
823 def __init__(self, xmin, xmax, ymin, ymax, *d, **kwds):
824 self.xmin, self.xmax, self.ymin, self.
ymax = xmin, xmax, ymin, ymax
826 defaults = {
"trans":
None,
"x":5,
"y":5,
"width":90,
"height":90,
"flipx":
False,
"flipy":
True,
"minusInfinity":-1000, \
827 "atx":0,
"xticks":-10,
"xminiticks":
True,
"xlabels":
True,
"xlogbase":
None, \
828 "aty":0,
"yticks":-10,
"yminiticks":
True,
"ylabels":
True,
"ylogbase":
None, \
829 "arrows":
None,
"text_attr":{},
"axis_attr":{}}
830 defaults.update(kwds)
833 self.
trans = kwds[
"trans"]; del kwds[
"trans"]
834 self.
x = kwds[
"x"]; del kwds[
"x"]
835 self.
y = kwds[
"y"]; del kwds[
"y"]
836 self.
width = kwds[
"width"]; del kwds[
"width"]
837 self.
height = kwds[
"height"]; del kwds[
"height"]
838 self.
flipx = kwds[
"flipx"]; del kwds[
"flipx"]
839 self.
flipy = kwds[
"flipy"]; del kwds[
"flipy"]
841 self.
atx = kwds[
"atx"]; del kwds[
"atx"]
842 self.
xticks = kwds[
"xticks"]; del kwds[
"xticks"]
844 self.
xlabels = kwds[
"xlabels"]; del kwds[
"xlabels"]
845 self.
xlogbase = kwds[
"xlogbase"]; del kwds[
"xlogbase"]
846 self.
aty = kwds[
"aty"]; del kwds[
"aty"]
847 self.
yticks = kwds[
"yticks"]; del kwds[
"yticks"]
849 self.
ylabels = kwds[
"ylabels"]; del kwds[
"ylabels"]
850 self.
ylogbase = kwds[
"ylogbase"]; del kwds[
"ylogbase"]
851 self.
arrows = kwds[
"arrows"]; del kwds[
"arrows"]
852 self.
text_attr = kwds[
"text_attr"]; del kwds[
"text_attr"]
853 self.
axis_attr = kwds[
"axis_attr"]; del kwds[
"axis_attr"]
855 raise TypeError(
"Plot() got unexpected keyword arguments %s" % kwds.keys())
857 def SVG(self, trans=None):
858 """Apply the transformation "trans" and return an SVG object."""
859 if trans ==
None: trans = self.
trans
860 if isinstance(trans, str): trans =
totrans(trans)
865 d = [
Axes(self.xmin, self.xmax, self.ymin, self.
ymax, self.
atx, self.
aty, \
874 text_defaults = {
"stroke":
"none",
"fill":
"black",
"font-size":5}
878 minitick_length = 0.75
879 text_xaxis_offset = 1.
880 text_yaxis_offset = 2.
881 text_xtitle_offset = 6.
882 text_ytitle_offset = 12.
885 return "<Frame (%d items)>" % len(self.
d)
887 def __init__(self, xmin, xmax, ymin, ymax, *d, **kwds):
888 """Acts like Fig, but draws a coordinate frame around the data. You also need to supply plot ranges.
890 Frame(xmin, xmax, ymin, ymax, obj, obj, obj..., keyword options...)
892 xmin, xmax required minimum and maximum x values (in the objs' coordinates)
893 ymin, ymax required minimum and maximum y values (in the objs' coordinates)
894 obj optional list drawing primatives
895 keyword options keyword list options defined below
897 The following are keyword options, with their default values:
899 x, y 20, 5 upper-left corner of the Frame in SVG coordinates
900 width, height 75, 80 width and height of the Frame in SVG coordinates
901 flipx, flipy False, True flip the sign of the coordinate axis
902 minusInfinity -1000 if an axis is logarithmic and an object is plotted at 0 or
903 a negative value, -1000 will be used as a stand-in for NaN
904 xtitle None if a string, label the x axis
905 xticks -10 request ticks according to the standard tick specification
907 xminiticks True request miniticks according to the standard minitick
909 xlabels True request tick labels according to the standard tick label
911 xlogbase None if a number, the axis and transformation are logarithmic
912 with ticks at the given base (10 being the most common)
914 text_attr {} a dictionary of attributes for label text
915 axis_attr {} a dictionary of attributes for the axis lines
918 self.xmin, self.xmax, self.ymin, self.
ymax = xmin, xmax, ymin, ymax
920 defaults = {
"x":20,
"y":5,
"width":75,
"height":80,
"flipx":
False,
"flipy":
True,
"minusInfinity":-1000, \
921 "xtitle":
None,
"xticks":-10,
"xminiticks":
True,
"xlabels":
True,
"x2labels":
None,
"xlogbase":
None, \
922 "ytitle":
None,
"yticks":-10,
"yminiticks":
True,
"ylabels":
True,
"y2labels":
None,
"ylogbase":
None, \
923 "text_attr":{},
"axis_attr":{}}
924 defaults.update(kwds)
927 self.
x = kwds[
"x"]; del kwds[
"x"]
928 self.
y = kwds[
"y"]; del kwds[
"y"]
929 self.
width = kwds[
"width"]; del kwds[
"width"]
930 self.
height = kwds[
"height"]; del kwds[
"height"]
931 self.
flipx = kwds[
"flipx"]; del kwds[
"flipx"]
932 self.
flipy = kwds[
"flipy"]; del kwds[
"flipy"]
934 self.
xtitle = kwds[
"xtitle"]; del kwds[
"xtitle"]
935 self.
xticks = kwds[
"xticks"]; del kwds[
"xticks"]
937 self.
xlabels = kwds[
"xlabels"]; del kwds[
"xlabels"]
938 self.
x2labels = kwds[
"x2labels"]; del kwds[
"x2labels"]
939 self.
xlogbase = kwds[
"xlogbase"]; del kwds[
"xlogbase"]
940 self.
ytitle = kwds[
"ytitle"]; del kwds[
"ytitle"]
941 self.
yticks = kwds[
"yticks"]; del kwds[
"yticks"]
943 self.
ylabels = kwds[
"ylabels"]; del kwds[
"ylabels"]
944 self.
y2labels = kwds[
"y2labels"]; del kwds[
"y2labels"]
945 self.
ylogbase = kwds[
"ylogbase"]; del kwds[
"ylogbase"]
954 raise TypeError(
"Frame() got unexpected keyword arguments %s" % kwds.keys())
957 """Apply the window transformation and return an SVG object."""
962 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)
963 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)
964 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)
965 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)
970 left.minitick_end = 0.
973 right.tick_start = 0.
975 right.minitick_start = 0.
978 right.text_attr[
"text-anchor"] =
"start"
980 bottom.tick_start = 0.
982 bottom.minitick_start = 0.
989 top.minitick_end = 0.
991 top.text_attr[
"dominant-baseline"] =
"text-after-edge"
1008 """Converts SVG("path", d="...") into Path(d=[...])."""
1009 if not isinstance(svg, SVG)
or svg.t !=
"path":
1010 raise TypeError(
"Only SVG <path /> objects can be converted into Paths")
1011 attr = dict(svg.attr)
1014 for key
in attr.keys():
1015 if not isinstance(key, str):
1018 attr[
str(key)] = value
1019 return Path(d, **attr)
1022 """Path represents an SVG path, an arbitrary set of curves and
1023 straight segments. Unlike SVG("path", d="..."), Path stores
1024 coordinates as a list of numbers, rather than a string, so that it is
1025 transformable in a Fig.
1027 Path(d, attribute=value)
1029 d required path data
1030 attribute=value pairs keyword list SVG attributes
1032 See http://www.w3.org/TR/SVG/paths.html for specification of paths
1035 Internally, Path data is a list of tuples with these definitions:
1037 * ("Z/z",): close the current path
1038 * ("H/h", x) or ("V/v", y): a horizontal or vertical line
1040 * ("M/m/L/l/T/t", x, y, global): moveto, lineto, or smooth
1041 quadratic curveto point (x, y). If global=True, (x, y) should
1043 * ("S/sQ/q", cx, cy, cglobal, x, y, global): polybezier or
1044 smooth quadratic curveto point (x, y) using (cx, cy) as a
1045 control point. If cglobal or global=True, (cx, cy) or (x, y)
1046 should not be transformed.
1047 * ("C/c", c1x, c1y, c1global, c2x, c2y, c2global, x, y, global):
1048 cubic curveto point (x, y) using (c1x, c1y) and (c2x, c2y) as
1049 control points. If c1global, c2global, or global=True, (c1x, c1y),
1050 (c2x, c2y), or (x, y) should not be transformed.
1051 * ("A/a", rx, ry, rglobal, x-axis-rotation, angle, large-arc-flag,
1052 sweep-flag, x, y, global): arcto point (x, y) using the
1053 aforementioned parameters.
1054 * (",/.", rx, ry, rglobal, angle, x, y, global): an ellipse at
1055 point (x, y) with radii (rx, ry). If angle is 0, the whole
1056 ellipse is drawn; otherwise, a partial ellipse is drawn.
1061 return "<Path (%d nodes) %s>" % (len(self.
d), self.
attr)
1064 if isinstance(d, str): self.
d = self.
parse(d)
1065 else: self.
d =
list(d)
1071 """Part of Path's text-command parsing algorithm; used internally."""
1072 while index < len(pathdata)
and pathdata[index]
in (
" ",
"\t",
"\r",
"\n",
","): index += 1
1073 return index, pathdata
1076 """Part of Path's text-command parsing algorithm; used internally."""
1079 if index >= len(pathdata):
return None, index, pathdata
1080 command = pathdata[index]
1081 if "A" <= command <=
"Z" or "a" <= command <=
"z":
1083 return command, index, pathdata
1085 return None, index, pathdata
1088 """Part of Path's text-command parsing algorithm; used internally."""
1091 if index >= len(pathdata):
return None, index, pathdata
1092 first_digit = pathdata[index]
1094 if "0" <= first_digit <=
"9" or first_digit
in (
"-",
"+",
"."):
1096 while index < len(pathdata)
and (
"0" <= pathdata[index] <=
"9" or pathdata[index]
in (
"-",
"+",
".",
"e",
"E")):
1101 return float(pathdata[start:end]), index, pathdata
1103 return None, index, pathdata
1106 """Part of Path's text-command parsing algorithm; used internally."""
1109 if index >= len(pathdata):
return None, index, pathdata
1110 first_digit = pathdata[index]
1112 if first_digit
in (
"0",
"1"):
1114 return int(first_digit), index, pathdata
1116 return None, index, pathdata
1119 """Parses text-commands, converting them into a list of tuples.
1120 Called by the constructor."""
1124 command, index, pathdata = self.
parse_command(index, pathdata)
1127 if command ==
None and index == len(pathdata):
break
1128 if command
in (
"Z",
"z"):
1129 output.append((command,))
1132 elif command
in (
"H",
"h",
"V",
"v"):
1133 errstring =
"Path command \"%s\" requires a number at index %d" % (command, index)
1134 num1, index, pathdata = self.
parse_number(index, pathdata)
1135 if num1 ==
None:
raise ValueError(errstring)
1138 output.append((command, num1))
1139 num1, index, pathdata = self.
parse_number(index, pathdata)
1142 elif command
in (
"M",
"m",
"L",
"l",
"T",
"t"):
1143 errstring =
"Path command \"%s\" requires an x,y pair at index %d" % (command, index)
1144 num1, index, pathdata = self.
parse_number(index, pathdata)
1145 num2, index, pathdata = self.
parse_number(index, pathdata)
1147 if num1 ==
None:
raise ValueError(errstring)
1150 if num2 ==
None:
raise ValueError(errstring)
1151 output.append((command, num1, num2,
False))
1153 num1, index, pathdata = self.
parse_number(index, pathdata)
1154 num2, index, pathdata = self.
parse_number(index, pathdata)
1157 elif command
in (
"S",
"s",
"Q",
"q"):
1158 errstring =
"Path command \"%s\" requires a cx,cy,x,y quadruplet at index %d" % (command, index)
1159 num1, index, pathdata = self.
parse_number(index, pathdata)
1160 num2, index, pathdata = self.
parse_number(index, pathdata)
1161 num3, index, pathdata = self.
parse_number(index, pathdata)
1162 num4, index, pathdata = self.
parse_number(index, pathdata)
1164 if num1 ==
None:
raise ValueError(errstring)
1167 if num2 ==
None or num3 ==
None or num4 ==
None:
raise ValueError(errstring)
1168 output.append((command, num1, num2,
False, num3, num4,
False))
1170 num1, index, pathdata = self.
parse_number(index, pathdata)
1171 num2, index, pathdata = self.
parse_number(index, pathdata)
1172 num3, index, pathdata = self.
parse_number(index, pathdata)
1173 num4, index, pathdata = self.
parse_number(index, pathdata)
1176 elif command
in (
"C",
"c"):
1177 errstring =
"Path command \"%s\" requires a c1x,c1y,c2x,c2y,x,y sextuplet at index %d" % (command, index)
1178 num1, index, pathdata = self.
parse_number(index, pathdata)
1179 num2, index, pathdata = self.
parse_number(index, pathdata)
1180 num3, index, pathdata = self.
parse_number(index, pathdata)
1181 num4, index, pathdata = self.
parse_number(index, pathdata)
1182 num5, index, pathdata = self.
parse_number(index, pathdata)
1183 num6, index, pathdata = self.
parse_number(index, pathdata)
1185 if num1 ==
None:
raise ValueError(errstring)
1188 if num2 ==
None or num3 ==
None or num4 ==
None or num5 ==
None or num6 ==
None:
raise ValueError(errstring)
1190 output.append((command, num1, num2,
False, num3, num4,
False, num5, num6,
False))
1192 num1, index, pathdata = self.
parse_number(index, pathdata)
1193 num2, index, pathdata = self.
parse_number(index, pathdata)
1194 num3, index, pathdata = self.
parse_number(index, pathdata)
1195 num4, index, pathdata = self.
parse_number(index, pathdata)
1196 num5, index, pathdata = self.
parse_number(index, pathdata)
1197 num6, index, pathdata = self.
parse_number(index, pathdata)
1200 elif command
in (
"A",
"a"):
1201 errstring =
"Path command \"%s\" requires a rx,ry,angle,large-arc-flag,sweep-flag,x,y septuplet at index %d" % (command, index)
1202 num1, index, pathdata = self.
parse_number(index, pathdata)
1203 num2, index, pathdata = self.
parse_number(index, pathdata)
1204 num3, index, pathdata = self.
parse_number(index, pathdata)
1207 num6, index, pathdata = self.
parse_number(index, pathdata)
1208 num7, index, pathdata = self.
parse_number(index, pathdata)
1210 if num1 ==
None:
raise ValueError(errstring)
1213 if num2 ==
None or num3 ==
None or num4 ==
None or num5 ==
None or num6 ==
None or num7 ==
None:
raise ValueError(errstring)
1215 output.append((command, num1, num2,
False, num3, num4, num5, num6, num7,
False))
1217 num1, index, pathdata = self.
parse_number(index, pathdata)
1218 num2, index, pathdata = self.
parse_number(index, pathdata)
1219 num3, index, pathdata = self.
parse_number(index, pathdata)
1222 num6, index, pathdata = self.
parse_number(index, pathdata)
1223 num7, index, pathdata = self.
parse_number(index, pathdata)
1228 """Apply the transformation "trans" and return an SVG object."""
1229 if isinstance(trans, str): trans =
totrans(trans)
1231 x, y, X, Y =
None,
None,
None,
None
1233 for datum
in self.
d:
1234 if not isinstance(datum, (tuple, list)):
1235 raise TypeError(
"pathdata elements must be tuples/lists")
1240 if command
in (
"Z",
"z"):
1241 x, y, X, Y =
None,
None,
None,
None
1245 elif command
in (
"H",
"h",
"V",
"v"):
1246 command, num1 = datum
1248 if command ==
"H" or (command ==
"h" and x ==
None): x = num1
1249 elif command ==
"h": x += num1
1250 elif command ==
"V" or (command ==
"v" and y ==
None): y = num1
1251 elif command ==
"v": y += num1
1253 if trans ==
None: X, Y = x, y
1254 else: X, Y = trans(x, y)
1256 output.append(
"L%g %g" % (X, Y))
1259 elif command
in (
"M",
"m",
"L",
"l",
"T",
"t"):
1260 command, num1, num2, isglobal12 = datum
1262 if trans ==
None or isglobal12:
1263 if command.isupper()
or X ==
None or Y ==
None:
1271 if command.isupper()
or x ==
None or y ==
None:
1278 COMMAND = command.capitalize()
1279 output.append(
"%s%g %g" % (COMMAND, X, Y))
1282 elif command
in (
"S",
"s",
"Q",
"q"):
1283 command, num1, num2, isglobal12, num3, num4, isglobal34 = datum
1285 if trans ==
None or isglobal12:
1286 if command.isupper()
or X ==
None or Y ==
None:
1293 if command.isupper()
or x ==
None or y ==
None:
1298 CX, CY = trans(cx, cy)
1300 if trans ==
None or isglobal34:
1301 if command.isupper()
or X ==
None or Y ==
None:
1309 if command.isupper()
or x ==
None or y ==
None:
1316 COMMAND = command.capitalize()
1317 output.append(
"%s%g %g %g %g" % (COMMAND, CX, CY, X, Y))
1320 elif command
in (
"C",
"c"):
1321 command, num1, num2, isglobal12, num3, num4, isglobal34, num5, num6, isglobal56 = datum
1323 if trans ==
None or isglobal12:
1324 if command.isupper()
or X ==
None or Y ==
None:
1325 C1X, C1Y = num1, num2
1331 if command.isupper()
or x ==
None or y ==
None:
1332 c1x, c1y = num1, num2
1336 C1X, C1Y = trans(c1x, c1y)
1338 if trans ==
None or isglobal34:
1339 if command.isupper()
or X ==
None or Y ==
None:
1340 C2X, C2Y = num3, num4
1346 if command.isupper()
or x ==
None or y ==
None:
1347 c2x, c2y = num3, num4
1351 C2X, C2Y = trans(c2x, c2y)
1353 if trans ==
None or isglobal56:
1354 if command.isupper()
or X ==
None or Y ==
None:
1362 if command.isupper()
or x ==
None or y ==
None:
1369 COMMAND = command.capitalize()
1370 output.append(
"%s%g %g %g %g %g %g" % (COMMAND, C1X, C1Y, C2X, C2Y, X, Y))
1373 elif command
in (
"A",
"a"):
1374 command, num1, num2, isglobal12, angle, large_arc_flag, sweep_flag, num3, num4, isglobal34 = datum
1379 if trans ==
None or isglobal34:
1380 if command.isupper()
or X ==
None or Y ==
None:
1388 if command.isupper()
or x ==
None or y ==
None:
1395 if x !=
None and y !=
None:
1396 centerx, centery = (x + oldx)/2., (y + oldy)/2.
1397 CENTERX, CENTERY = (X + OLDX)/2., (Y + OLDY)/2.
1399 if trans ==
None or isglobal12:
1406 RX, RY = trans(rx, ry)
1408 COMMAND = command.capitalize()
1409 output.append(
"%s%g %g %g %d %d %g %g" % (COMMAND, RX - CENTERX, RY - CENTERY, angle, large_arc_flag, sweep_flag, X, Y))
1411 elif command
in (
",",
"."):
1412 command, num1, num2, isglobal12, angle, num3, num4, isglobal34 = datum
1413 if trans ==
None or isglobal34:
1414 if command ==
"." or X ==
None or Y ==
None:
1422 if command ==
"." or x ==
None or y ==
None:
1429 if trans ==
None or isglobal12:
1436 RX, RY = trans(rx, ry)
1438 RX, RY = RX - X, RY - Y
1440 X1, Y1 = X + RX * math.cos(angle*math.pi/180.), Y + RX * math.sin(angle*math.pi/180.)
1441 X2, Y2 = X + RY * math.sin(angle*math.pi/180.), Y - RY * math.cos(angle*math.pi/180.)
1442 X3, Y3 = X - RX * math.cos(angle*math.pi/180.), Y - RX * math.sin(angle*math.pi/180.)
1443 X4, Y4 = X - RY * math.sin(angle*math.pi/180.), Y + RY * math.cos(angle*math.pi/180.)
1445 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" \
1446 % (X1, Y1, RX, RY, angle, X2, Y2, RX, RY, angle, X3, Y3, RX, RY, angle, X4, Y4, RX, RY, angle, X1, Y1))
1448 return SVG(
"path", d=
"".
join(output), **self.
attr)
1452 def funcRtoC(expr, var="t", globals=None, locals=None):
1453 """Converts a complex "z(t)" string to a function acceptable for Curve.
1455 expr required string in the form "z(t)"
1456 var default="t" name of the independent variable
1457 globals default=None dict of global variables used in the expression;
1458 you may want to use Python's builtin globals()
1459 locals default=None dict of local variables
1462 if globals !=
None: g.update(globals)
1463 output = eval(
"lambda %s: (%s)" % (var, expr), g, locals)
1464 split =
lambda z: (z.real, z.imag)
1466 output2.__name__ =
"%s -> %s" % (var, expr)
1470 """Converts a "f(t), g(t)" string to a function acceptable for Curve.
1472 expr required string in the form "f(t), g(t)"
1473 var default="t" name of the independent variable
1474 globals default=None dict of global variables used in the expression;
1475 you may want to use Python's builtin globals()
1476 locals default=None dict of local variables
1479 if globals !=
None: g.update(globals)
1480 output = eval(
"lambda %s: (%s)" % (var, expr), g, locals)
1481 output.__name__ =
"%s -> %s" % (var, expr)
1484 def funcRtoR(expr, var="x", globals=None, locals=None):
1485 """Converts a "f(x)" string to a function acceptable for Curve.
1487 expr required string in the form "f(x)"
1488 var default="x" name of the independent variable
1489 globals default=None dict of global variables used in the expression;
1490 you may want to use Python's builtin globals()
1491 locals default=None dict of local variables
1494 if globals !=
None: g.update(globals)
1495 output = eval(
"lambda %s: (%s, %s)" % (var, var, expr), g, locals)
1496 output.__name__ =
"%s -> %s" % (var, expr)
1500 """Draws a parametric function as a path.
1502 Curve(f, low, high, loop, attribute=value)
1504 f required a Python callable or string in
1505 the form "f(t), g(t)"
1506 low, high required left and right endpoints
1507 loop default=False if True, connect the endpoints
1508 attribute=value pairs keyword list SVG attributes
1511 random_sampling =
True
1512 recursion_limit = 15
1513 linearity_limit = 0.05
1514 discontinuity_limit = 5.
1517 return "<Curve %s [%s, %s] %s>" % (self.
f, self.
low, self.
high, self.
attr)
1531 t, x, y, X, Y = self.
t, self.x, self.
y, self.X, self.
Y
1532 if t !=
None: t =
"%g" % t
1533 if x !=
None: x =
"%g" % x
1534 if y !=
None: y =
"%g" % y
1535 if X !=
None: X =
"%g" % X
1536 if Y !=
None: Y =
"%g" % Y
1537 return "<Curve.Sample t=%s x=%s y=%s X=%s Y=%s>" % (t, x, y, X, Y)
1541 def link(self, left, right): self.left, self.
right = left, right
1546 self.X, self.
Y = self.x, self.
y
1548 self.X, self.
Y = trans(self.x, self.
y)
1553 def __repr__(self):
return "<Curve.Samples (%d samples)>" % len(self)
1560 while current !=
None:
1562 current = current.right
1571 if current ==
None:
raise StopIteration
1577 """Adaptive-sampling algorithm that chooses the best sample points
1578 for a parametric curve between two endpoints and detects
1579 discontinuities. Called by SVG()."""
1580 oldrecursionlimit = sys.getrecursionlimit()
1584 if not (self.
low < self.
high):
raise ValueError(
"low must be less than high")
1586 low.link(
None, high)
1587 high.link(low,
None)
1589 low.evaluate(self.
f, trans)
1590 high.evaluate(self.
f, trans)
1597 while left.right !=
None:
1601 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:
1602 numer = left.X*(right.Y - mid.Y) + mid.X*(left.Y - right.Y) + right.X*(mid.Y - left.Y)
1603 denom = math.sqrt((left.X - right.X)**2 + (left.Y - right.Y)**2)
1617 sys.setrecursionlimit(oldrecursionlimit)
1620 """Part of the adaptive-sampling algorithm that chooses the best
1621 sample points. Called by sample()."""
1624 mid = self.
Sample(left.t + random.uniform(0.3, 0.7) * (right.t - left.t))
1626 mid = self.
Sample(left.t + 0.5 * (right.t - left.t))
1630 mid.link(left, right)
1631 mid.evaluate(self.
f, trans)
1634 numer = left.X*(right.Y - mid.Y) + mid.X*(left.Y - right.Y) + right.X*(mid.Y - left.Y)
1635 denom = math.sqrt((left.X - right.X)**2 + (left.Y - right.Y)**2)
1642 self.
subsample(left, mid, depth+1, trans)
1643 self.
subsample(mid, right, depth+1, trans)
1648 mid.y = mid.Y =
None
1651 """Apply the transformation "trans" and return an SVG object."""
1654 def Path(self, trans=None, local=False):
1655 """Apply the transformation "trans" and return a Path object in
1656 global coordinates. If local=True, return a Path in local coordinates
1657 (which must be transformed again)."""
1659 if isinstance(trans, str): trans =
totrans(trans)
1666 if s.X !=
None and s.Y !=
None:
1667 if s.left ==
None or s.left.Y ==
None:
1672 if local: output.append((command, s.x, s.y,
False))
1673 else: output.append((command, s.X, s.Y,
True))
1675 if self.
loop: output.append((
"Z",))
1681 """Draws a curve specified by a sequence of points. The curve may be
1682 piecewise linear, like a polygon, or a Bezier curve.
1684 Poly(d, mode, loop, attribute=value)
1686 d required list of tuples representing points
1687 and possibly control points
1688 mode default="L" "lines", "bezier", "velocity",
1689 "foreback", "smooth", or an abbreviation
1690 loop default=False if True, connect the first and last
1691 point, closing the loop
1692 attribute=value pairs keyword list SVG attributes
1694 The format of the tuples in d depends on the mode.
1696 "lines"/"L" d=[(x,y), (x,y), ...]
1697 piecewise-linear segments joining the (x,y) points
1698 "bezier"/"B" d=[(x, y, c1x, c1y, c2x, c2y), ...]
1699 Bezier curve with two control points (control points
1700 preceed (x,y), as in SVG paths). If (c1x,c1y) and
1701 (c2x,c2y) both equal (x,y), you get a linear
1702 interpolation ("lines")
1703 "velocity"/"V" d=[(x, y, vx, vy), ...]
1704 curve that passes through (x,y) with velocity (vx,vy)
1705 (one unit of arclength per unit time); in other words,
1706 (vx,vy) is the tangent vector at (x,y). If (vx,vy) is
1707 (0,0), you get a linear interpolation ("lines").
1708 "foreback"/"F" d=[(x, y, bx, by, fx, fy), ...]
1709 like "velocity" except that there is a left derivative
1710 (bx,by) and a right derivative (fx,fy). If (bx,by)
1711 equals (fx,fy) (with no minus sign), you get a
1713 "smooth"/"S" d=[(x,y), (x,y), ...]
1714 a "velocity" interpolation with (vx,vy)[i] equal to
1715 ((x,y)[i+1] - (x,y)[i-1])/2: the minimal derivative
1720 return "<Poly (%d nodes) mode=%s loop=%s %s>" % (len(self.
d), self.
mode, repr(self.
loop), self.
attr)
1722 def __init__(self, d=[], mode="L", loop=False, **attr):
1731 """Apply the transformation "trans" and return an SVG object."""
1734 def Path(self, trans=None, local=False):
1735 """Apply the transformation "trans" and return a Path object in
1736 global coordinates. If local=True, return a Path in local coordinates
1737 (which must be transformed again)."""
1738 if isinstance(trans, str): trans =
totrans(trans)
1740 if self.
mode[0] ==
"L" or self.
mode[0] ==
"l": mode =
"L"
1741 elif self.
mode[0] ==
"B" or self.
mode[0] ==
"b": mode =
"B"
1742 elif self.
mode[0] ==
"V" or self.
mode[0] ==
"v": mode =
"V"
1743 elif self.
mode[0] ==
"F" or self.
mode[0] ==
"f": mode =
"F"
1744 elif self.
mode[0] ==
"S" or self.
mode[0] ==
"s":
1747 vx, vy = [0.]*len(self.
d), [0.]*len(self.
d)
1748 for i
in range(len(self.
d)):
1749 inext = (i+1) % len(self.
d)
1750 iprev = (i-1) % len(self.
d)
1752 vx[i] = (self.
d[inext][0] - self.
d[iprev][0])/2.
1753 vy[i] = (self.
d[inext][1] - self.
d[iprev][1])/2.
1754 if not self.
loop and (i == 0
or i == len(self.
d)-1):
1755 vx[i], vy[i] = 0., 0.
1758 raise ValueError(
"mode must be \"lines\", \"bezier\", \"velocity\", \"foreback\", \"smooth\", or an abbreviation")
1762 if self.
loop and len(self.
d) > 0: indexes.append(0)
1765 inext = (i+1) % len(self.
d)
1766 iprev = (i-1) % len(self.
d)
1768 x, y = self.
d[i][0], self.
d[i][1]
1770 if trans ==
None: X, Y = x, y
1771 else: X, Y = trans(x, y)
1774 if local: d.append((
"M", x, y,
False))
1775 else: d.append((
"M", X, Y,
True))
1778 if local: d.append((
"L", x, y,
False))
1779 else: d.append((
"L", X, Y,
True))
1782 c1x, c1y = self.
d[i][2], self.
d[i][3]
1783 if trans ==
None: C1X, C1Y = c1x, c1y
1784 else: C1X, C1Y = trans(c1x, c1y)
1786 c2x, c2y = self.
d[i][4], self.
d[i][5]
1787 if trans ==
None: C2X, C2Y = c2x, c2y
1788 else: C2X, C2Y = trans(c2x, c2y)
1790 if local: d.append((
"C", c1x, c1y,
False, c2x, c2y,
False, x, y,
False))
1791 else: d.append((
"C", C1X, C1Y,
True, C2X, C2Y,
True, X, Y,
True))
1794 c1x, c1y = self.
d[iprev][2]/3. + self.
d[iprev][0], self.
d[iprev][3]/3. + self.
d[iprev][1]
1795 c2x, c2y = self.
d[i][2]/-3. + x, self.
d[i][3]/-3. + y
1797 if trans ==
None: C1X, C1Y = c1x, c1y
1798 else: C1X, C1Y = trans(c1x, c1y)
1799 if trans ==
None: C2X, C2Y = c2x, c2y
1800 else: C2X, C2Y = trans(c2x, c2y)
1802 if local: d.append((
"C", c1x, c1y,
False, c2x, c2y,
False, x, y,
False))
1803 else: d.append((
"C", C1X, C1Y,
True, C2X, C2Y,
True, X, Y,
True))
1806 c1x, c1y = self.
d[iprev][4]/3. + self.
d[iprev][0], self.
d[iprev][5]/3. + self.
d[iprev][1]
1807 c2x, c2y = self.
d[i][2]/-3. + x, self.
d[i][3]/-3. + y
1809 if trans ==
None: C1X, C1Y = c1x, c1y
1810 else: C1X, C1Y = trans(c1x, c1y)
1811 if trans ==
None: C2X, C2Y = c2x, c2y
1812 else: C2X, C2Y = trans(c2x, c2y)
1814 if local: d.append((
"C", c1x, c1y,
False, c2x, c2y,
False, x, y,
False))
1815 else: d.append((
"C", C1X, C1Y,
True, C2X, C2Y,
True, X, Y,
True))
1818 c1x, c1y = vx[iprev]/3. + self.
d[iprev][0], vy[iprev]/3. + self.
d[iprev][1]
1819 c2x, c2y = vx[i]/-3. + x, vy[i]/-3. + y
1821 if trans ==
None: C1X, C1Y = c1x, c1y
1822 else: C1X, C1Y = trans(c1x, c1y)
1823 if trans ==
None: C2X, C2Y = c2x, c2y
1824 else: C2X, C2Y = trans(c2x, c2y)
1826 if local: d.append((
"C", c1x, c1y,
False, c2x, c2y,
False, x, y,
False))
1827 else: d.append((
"C", C1X, C1Y,
True, C2X, C2Y,
True, X, Y,
True))
1829 if self.
loop and len(self.
d) > 0: d.append((
"Z",))
1836 """Draws at text string at a specified point in local coordinates.
1838 x, y required location of the point in local coordinates
1839 d required text/Unicode string
1840 attribute=value pairs keyword list SVG attributes
1843 defaults = {
"stroke":
"none",
"fill":
"black",
"font-size":5}
1846 return "<Text %s at (%g, %g) %s>" % (repr(self.
d), self.
x, self.
y, self.
attr)
1856 """Apply the transformation "trans" and return an SVG object."""
1857 if isinstance(trans, str): trans =
totrans(trans)
1859 X, Y = self.
x, self.
y
1860 if trans !=
None: X, Y = trans(X, Y)
1861 return SVG(
"text", self.
d, x=X, y=Y, **self.
attr)
1864 """Draws at text string at a specified point in global coordinates.
1866 x, y required location of the point in global coordinates
1867 d required text/Unicode string
1868 attribute=value pairs keyword list SVG attributes
1870 defaults = {
"stroke":
"none",
"fill":
"black",
"font-size":5}
1873 return "<TextGlobal %s at (%s, %s) %s>" % (repr(self.
d),
str(self.
x),
str(self.
y), self.
attr)
1883 """Apply the transformation "trans" and return an SVG object."""
1884 return SVG(
"text", self.
d, x=self.
x, y=self.
y, **self.
attr)
1888 _symbol_templates = {
"dot":
SVG(
"symbol",
SVG(
"circle", cx=0, cy=0, r=1, stroke=
"none", fill=
"black"), viewBox=
"0 0 1 1", overflow=
"visible"), \
1889 "box":
SVG(
"symbol",
SVG(
"rect", x1=-1, y1=-1, x2=1, y2=1, stroke=
"none", fill=
"black"), viewBox=
"0 0 1 1", overflow=
"visible"), \
1890 "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"), \
1891 "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"), \
1895 """Creates a new instance of an SVG symbol to avoid cross-linking objects.
1897 id required a new identifier (string/Unicode)
1898 shape default="dot" the shape name from _symbol_templates
1899 attribute=value list keyword list modify the SVG attributes of the new symbol
1901 output = copy.deepcopy(_symbol_templates[shape])
1909 """Dots draws SVG symbols at a set of points.
1911 d required list of (x,y) points
1912 symbol default=None SVG symbol or a new identifier to
1913 label an auto-generated symbol;
1914 if None, use pre-defined _circular_dot
1915 width, height default=1, 1 width and height of the symbols
1917 attribute=value pairs keyword list SVG attributes
1922 return "<Dots (%d nodes) %s>" % (len(self.
d), self.
attr)
1924 def __init__(self, d=[], symbol=None, width=1., height=1., **attr):
1934 elif isinstance(symbol, SVG):
1940 """Apply the transformation "trans" and return an SVG object."""
1941 if isinstance(trans, str): trans =
totrans(trans)
1944 id =
"#%s" % self.
symbol[
"id"]
1949 if trans ==
None: X, Y = x, y
1950 else: X, Y = trans(x, y)
1952 item =
SVG(
"use", x=X, y=Y, xlink__href=id)
1953 if self.
width !=
None: item[
"width"] = self.
width
1961 _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"), \
1962 "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"), \
1966 """Creates a new instance of an SVG marker to avoid cross-linking objects.
1968 id required a new identifier (string/Unicode)
1969 shape required the shape name from _marker_templates
1970 attribute=value list keyword list modify the SVG attributes of the new marker
1972 output = copy.deepcopy(_marker_templates[shape])
1978 """Draws a line between two points.
1980 Line(x1, y1, x2, y2, arrow_start, arrow_end, attribute=value)
1982 x1, y1 required the starting point
1983 x2, y2 required the ending point
1984 arrow_start default=None if an identifier string/Unicode,
1985 draw a new arrow object at the
1986 beginning of the line; if a marker,
1987 draw that marker instead
1988 arrow_end default=None same for the end of the line
1989 attribute=value pairs keyword list SVG attributes
1994 return "<Line (%g, %g) to (%g, %g) %s>" % (self.x1, self.y1, self.x2, self.
y2, self.
attr)
1996 def __init__(self, x1, y1, x2, y2, arrow_start=None, arrow_end=None, **attr):
1997 self.x1, self.y1, self.x2, self.
y2 = x1, y1, x2, y2
2004 """Apply the transformation "trans" and return an SVG object."""
2008 if (self.arrow_start !=
False and self.arrow_start !=
None)
or (self.
arrow_end !=
False and self.
arrow_end !=
None):
2011 if self.arrow_start !=
False and self.arrow_start !=
None:
2012 if isinstance(self.arrow_start, SVG):
2013 defs.append(self.arrow_start)
2014 line.attr[
"marker-start"] =
"url(#%s)" % self.arrow_start[
"id"]
2015 elif isinstance(self.arrow_start, str):
2016 defs.append(
make_marker(self.arrow_start,
"arrow_start"))
2017 line.attr[
"marker-start"] =
"url(#%s)" % self.arrow_start
2019 raise TypeError(
"arrow_start must be False/None or an id string for the new marker")
2024 line.attr[
"marker-end"] =
"url(#%s)" % self.
arrow_end[
"id"]
2027 line.attr[
"marker-end"] =
"url(#%s)" % self.
arrow_end
2029 raise TypeError(
"arrow_end must be False/None or an id string for the new marker")
2031 return SVG(
"g", defs, line)
2035 def Path(self, trans=None, local=False):
2036 """Apply the transformation "trans" and return a Path object in
2037 global coordinates. If local=True, return a Path in local coordinates
2038 (which must be transformed again)."""
2039 self.
f =
lambda t: (self.x1 + t*(self.x2 - self.x1), self.y1 + t*(self.
y2 - self.y1))
2045 return Path([(
"M", self.x1, self.y1,
not local), (
"L", self.x2, self.
y2,
not local)], **self.
attr)
2047 return Curve.Path(self, trans, local)
2050 """Draws a line between two points, one or both of which is in
2053 Line(x1, y1, x2, y2, lcoal1, local2, arrow_start, arrow_end, attribute=value)
2055 x1, y1 required the starting point
2056 x2, y2 required the ending point
2057 local1 default=False if True, interpret first point as a
2058 local coordinate (apply transform)
2059 local2 default=False if True, interpret second point as a
2060 local coordinate (apply transform)
2061 arrow_start default=None if an identifier string/Unicode,
2062 draw a new arrow object at the
2063 beginning of the line; if a marker,
2064 draw that marker instead
2065 arrow_end default=None same for the end of the line
2066 attribute=value pairs keyword list SVG attributes
2071 local1, local2 =
"",
""
2072 if self.local1: local1 =
"L"
2073 if self.
local2: local2 =
"L"
2075 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)
2077 def __init__(self, x1, y1, x2, y2, local1=False, local2=False, arrow_start=None, arrow_end=None, **attr):
2078 self.x1, self.y1, self.x2, self.
y2 = x1, y1, x2, y2
2086 """Apply the transformation "trans" and return an SVG object."""
2087 if isinstance(trans, str): trans =
totrans(trans)
2089 X1, Y1, X2, Y2 = self.x1, self.y1, self.x2, self.
y2
2091 if self.local1: X1, Y1 = trans(X1, Y1)
2092 if self.
local2: X2, Y2 = trans(X2, Y2)
2094 line =
SVG(
"path", d=
"M%s %s L%s %s" % (X1, Y1, X2, Y2), **self.
attr)
2096 if (self.arrow_start !=
False and self.arrow_start !=
None)
or (self.
arrow_end !=
False and self.
arrow_end !=
None):
2099 if self.arrow_start !=
False and self.arrow_start !=
None:
2100 if isinstance(self.arrow_start, SVG):
2101 defs.append(self.arrow_start)
2102 line.attr[
"marker-start"] =
"url(#%s)" % self.arrow_start[
"id"]
2103 elif isinstance(self.arrow_start, str):
2104 defs.append(
make_marker(self.arrow_start,
"arrow_start"))
2105 line.attr[
"marker-start"] =
"url(#%s)" % self.arrow_start
2107 raise TypeError(
"arrow_start must be False/None or an id string for the new marker")
2112 line.attr[
"marker-end"] =
"url(#%s)" % self.
arrow_end[
"id"]
2115 line.attr[
"marker-end"] =
"url(#%s)" % self.
arrow_end
2117 raise TypeError(
"arrow_end must be False/None or an id string for the new marker")
2119 return SVG(
"g", defs, line)
2124 """Draws a vertical line.
2126 VLine(y1, y2, x, attribute=value)
2128 y1, y2 required y range
2129 x required x position
2130 attribute=value pairs keyword list SVG attributes
2135 return "<VLine (%g, %g) at x=%s %s>" % (self.y1, self.
y2, self.
x, self.
attr)
2141 Line.__init__(self, x, y1, x, y2, **self.
attr)
2143 def Path(self, trans=None, local=False):
2144 """Apply the transformation "trans" and return a Path object in
2145 global coordinates. If local=True, return a Path in local coordinates
2146 (which must be transformed again)."""
2149 return Line.Path(self, trans, local)
2152 """Draws a horizontal line.
2154 HLine(x1, x2, y, attribute=value)
2156 x1, x2 required x range
2157 y required y position
2158 attribute=value pairs keyword list SVG attributes
2163 return "<HLine (%g, %g) at y=%s %s>" % (self.x1, self.x2, self.
y, self.
attr)
2169 Line.__init__(self, x1, y, x2, y, **self.
attr)
2171 def Path(self, trans=None, local=False):
2172 """Apply the transformation "trans" and return a Path object in
2173 global coordinates. If local=True, return a Path in local coordinates
2174 (which must be transformed again)."""
2177 return Line.Path(self, trans, local)
2182 """Draws a rectangle.
2184 Rect(x1, y1, x2, y2, attribute=value)
2186 x1, y1 required the starting point
2187 x2, y2 required the ending point
2188 attribute=value pairs keyword list SVG attributes
2193 return "<Rect (%g, %g), (%g, %g) %s>" % (self.x1, self.y1, self.x2, self.
y2, self.
attr)
2196 self.x1, self.y1, self.x2, self.
y2 = x1, y1, x2, y2
2202 """Apply the transformation "trans" and return an SVG object."""
2205 def Path(self, trans=None, local=False):
2206 """Apply the transformation "trans" and return a Path object in
2207 global coordinates. If local=True, return a Path in local coordinates
2208 (which must be transformed again)."""
2210 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)
2217 self.
f =
lambda t: (self.x1 + t*(self.x2 - self.x1), self.y1)
2218 d1 = Curve.Path(self, trans, local).d
2220 self.
f =
lambda t: (self.x2, self.y1 + t*(self.
y2 - self.y1))
2221 d2 = Curve.Path(self, trans, local).d
2224 self.
f =
lambda t: (self.x2 + t*(self.x1 - self.x2), self.
y2)
2225 d3 = Curve.Path(self, trans, local).d
2228 self.
f =
lambda t: (self.x1, self.
y2 + t*(self.y1 - self.
y2))
2229 d4 = Curve.Path(self, trans, local).d
2232 return Path(d=(d1 + d2 + d3 + d4 + [(
"Z",)]), **self.
attr)
2237 """Draws an ellipse from a semimajor vector (ax,ay) and a semiminor
2240 Ellipse(x, y, ax, ay, b, attribute=value)
2242 x, y required the center of the ellipse/circle
2243 ax, ay required a vector indicating the length
2244 and direction of the semimajor axis
2245 b required the length of the semiminor axis.
2246 If equal to sqrt(ax2 + ay2), the
2248 attribute=value pairs keyword list SVG attributes
2250 (If sqrt(ax**2 + ay**2) is less than b, then (ax,ay) is actually the
2256 return "<Ellipse (%g, %g) a=(%g, %g), b=%g %s>" % (self.x, self.y, self.ax, self.ay, self.
b, self.
attr)
2259 self.x, self.y, self.ax, self.ay, self.
b = x, y, ax, ay, b
2265 """Apply the transformation "trans" and return an SVG object."""
2268 def Path(self, trans=None, local=False):
2269 """Apply the transformation "trans" and return a Path object in
2270 global coordinates. If local=True, return a Path in local coordinates
2271 (which must be transformed again)."""
2272 angle = math.atan2(self.ay, self.ax) + math.pi/2.
2273 bx = self.
b * math.cos(angle)
2274 by = self.
b * math.sin(angle)
2276 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))
2280 return Curve.Path(self, trans, local)
2285 """Converts numbers to a Unicode string, taking advantage of special
2286 Unicode characters to make nice minus signs and scientific notation.
2290 if output[0] ==
u"-":
2291 output =
u"\u2013" + output[1:]
2293 index = output.find(
u"e")
2295 uniout =
unicode(output[:index]) +
u"\u00d710"
2297 for n
in output[index+1:]:
2299 elif n ==
u"-": uniout +=
u"\u207b"
2301 if saw_nonzero: uniout +=
u"\u2070"
2311 elif u"4" <= n <=
u"9":
2313 if saw_nonzero: uniout += eval(
"u\"\\u%x\"" % (0x2070 + ord(n) - ord(
u"0")))
2316 if uniout[:2] ==
u"1\u00d7": uniout = uniout[2:]
2322 """Superclass for all graphics primatives that draw ticks,
2323 miniticks, and tick labels. This class only draws the ticks.
2325 Ticks(f, low, high, ticks, miniticks, labels, logbase, arrow_start,
2326 arrow_end, text_attr, attribute=value)
2328 f required parametric function along which ticks
2329 will be drawn; has the same format as
2330 the function used in Curve
2331 low, high required range of the independent variable
2332 ticks default=-10 request ticks according to the standard
2333 tick specification (see below)
2334 miniticks default=True request miniticks according to the
2335 standard minitick specification (below)
2336 labels True request tick labels according to the
2337 standard tick label specification (below)
2338 logbase default=None if a number, the axis is logarithmic with
2339 ticks at the given base (usually 10)
2340 arrow_start default=None if a new string identifier, draw an arrow
2341 at the low-end of the axis, referenced by
2342 that identifier; if an SVG marker object,
2344 arrow_end default=None if a new string identifier, draw an arrow
2345 at the high-end of the axis, referenced by
2346 that identifier; if an SVG marker object,
2348 text_attr default={} SVG attributes for the text labels
2349 attribute=value pairs keyword list SVG attributes for the tick marks
2351 Standard tick specification:
2353 * True: same as -10 (below).
2354 * Positive number N: draw exactly N ticks, including the endpoints. To
2355 subdivide an axis into 10 equal-sized segments, ask for 11 ticks.
2356 * Negative number -N: draw at least N ticks. Ticks will be chosen with
2357 "natural" values, multiples of 2 or 5.
2358 * List of values: draw a tick mark at each value.
2359 * Dict of value, label pairs: draw a tick mark at each value, labeling
2360 it with the given string. This lets you say things like {3.14159: "pi"}.
2361 * False or None: no ticks.
2363 Standard minitick specification:
2365 * True: draw miniticks with "natural" values, more closely spaced than
2367 * Positive number N: draw exactly N miniticks, including the endpoints.
2368 To subdivide an axis into 100 equal-sized segments, ask for 101 miniticks.
2369 * Negative number -N: draw at least N miniticks.
2370 * List of values: draw a minitick mark at each value.
2371 * False or None: no miniticks.
2373 Standard tick label specification:
2375 * True: use the unumber function (described below)
2376 * Format string: standard format strings, e.g. "%5.2f" for 12.34
2377 * Python callable: function that converts numbers to strings
2378 * False or None: no labels
2380 defaults = {
"stroke-width":
"0.25pt"}
2381 text_defaults = {
"stroke":
"none",
"fill":
"black",
"font-size":5}
2384 minitick_start = -0.75
2392 def __init__(self, f, low, high, ticks=-10, miniticks=True, labels=True, logbase=None, arrow_start=None, arrow_end=None, text_attr={}, **attr):
2410 """Return the position, normalized local x vector, normalized
2411 local y vector, and angle of a tick at position t.
2413 Normally only used internally.
2415 if isinstance(trans, str): trans =
totrans(trans)
2419 f =
lambda t: trans(*self.
f(t))
2424 Xprime, Yprime =
f(t + eps)
2425 xhatx, xhaty = (Xprime - X)/eps, (Yprime - Y)/eps
2427 norm = math.sqrt(xhatx**2 + xhaty**2)
2428 if norm != 0: xhatx, xhaty = xhatx/norm, xhaty/norm
2429 else: xhatx, xhaty = 1., 0.
2431 angle = math.atan2(xhaty, xhatx) + math.pi/2.
2432 yhatx, yhaty = math.cos(angle), math.sin(angle)
2434 return (X, Y), (xhatx, xhaty), (yhatx, yhaty), angle
2437 """Apply the transformation "trans" and return an SVG object."""
2438 if isinstance(trans, str): trans =
totrans(trans)
2442 minitickmarks =
Path([], **self.
attr)
2454 raise TypeError(
"arrow_start must be False/None or an id string for the new marker")
2462 raise TypeError(
"arrow_end must be False/None or an id string for the new marker")
2466 eps = _epsilon * (self.
high - self.
low)
2468 for t, label
in self.last_ticks.
items():
2469 (X, Y), (xhatx, xhaty), (yhatx, yhaty), angle = self.
orient_tickmark(t, trans)
2473 tickmarks.d.append((
"L", X - yhatx*self.
tick_end, Y - yhaty*self.
tick_end,
True))
2475 angle = (angle - math.pi/2.)*180./math.pi + self.
text_angle
2478 if _hacks[
"inkscape-text-vertical-shift"]:
2480 X += math.cos(angle*math.pi/180. + math.pi/2.) * 2.
2481 Y += math.sin(angle*math.pi/180. + math.pi/2.) * 2.
2483 X += math.cos(angle*math.pi/180. + math.pi/2.) * 2. * 2.5
2484 Y += math.sin(angle*math.pi/180. + math.pi/2.) * 2. * 2.5
2488 output.append(
SVG(
"text", label, transform=
"translate(%g, %g) rotate(%g)" % \
2493 for tt
in self.last_ticks.
keys():
2494 if abs(t - tt) < eps:
2498 (X, Y), (xhatx, xhaty), (yhatx, yhaty), angle = self.
orient_tickmark(t, trans)
2504 output.prepend(tickmarks.SVG(trans))
2505 output.prepend(minitickmarks.SVG(trans))
2509 """Evaluate and return optimal ticks and miniticks according to
2510 the standard minitick specification.
2512 Normally only used internally.
2516 format =
lambda x:
""
2518 elif self.
labels ==
True:
2521 elif isinstance(self.
labels, str):
2522 format =
lambda x: (self.
labels % x)
2524 elif callable(self.
labels):
2527 else:
raise TypeError(
"labels must be None/False, True, a format string, or a number->string function")
2533 if ticks ==
None or ticks ==
False:
return {}, []
2536 elif isinstance(ticks, (int, long)):
2537 if ticks ==
True: ticks = -10
2551 elif isinstance(self.
miniticks, (int, long)):
2554 elif getattr(self.
miniticks,
"__iter__",
False):
2561 raise TypeError(
"miniticks must be None/False, True, a number of desired miniticks, or a list of numbers")
2564 elif getattr(ticks,
"__iter__",
False):
2567 if not isinstance(ticks, dict):
2569 eps = _epsilon * (self.
high - self.
low)
2571 if format == unumber
and abs(x) < eps:
2587 elif isinstance(self.
miniticks, (int, long)):
2590 elif getattr(self.
miniticks,
"__iter__",
False):
2597 raise TypeError(
"miniticks must be None/False, True, a number of desired miniticks, or a list of numbers")
2600 raise TypeError(
"ticks must be None/False, a number of desired ticks, a list of numbers, or a dictionary of explicit markers")
2603 """Return less than -N or exactly N optimal linear ticks.
2605 Normally only used internally.
2607 if self.
low >= self.
high:
raise ValueError(
"low must be less than high")
2608 if N == 1:
raise ValueError(
"N can be 0 or >1 to specify the exact number of ticks or negative to specify a maximum")
2610 eps = _epsilon * (self.
high - self.
low)
2616 if format == unumber
and abs(x) < eps: label =
u"0"
2619 x += (self.
high - self.
low)/(N-1.)
2625 granularity = 10**math.ceil(math.log10(
max(
abs(self.
low),
abs(self.
high))))
2626 lowN = math.ceil(1.*self.
low / granularity)
2627 highN = math.floor(1.*self.
high / granularity)
2629 while (lowN > highN):
2630 countermod3 = counter % 3
2631 if countermod3 == 0: granularity *= 0.5
2632 elif countermod3 == 1: granularity *= 0.4
2633 else: granularity *= 0.5
2635 lowN = math.ceil(1.*self.
low / granularity)
2636 highN = math.floor(1.*self.
high / granularity)
2638 last_granularity = granularity
2645 if format == unumber
and abs(x) < eps: label =
u"0"
2649 if int(highN)+1 -
int(lowN) >= N:
2650 if last_trial ==
None:
2654 low_in_ticks, high_in_ticks =
False,
False
2655 for t
in last_trial.keys():
2656 if 1.*
abs(t - self.
low)/last_granularity < _epsilon: low_in_ticks =
True
2657 if 1.*
abs(t - self.
high)/last_granularity < _epsilon: high_in_ticks =
True
2659 lowN = 1.*self.
low / last_granularity
2660 highN = 1.*self.
high / last_granularity
2661 if abs(lowN - round(lowN)) < _epsilon
and not low_in_ticks:
2663 if abs(highN - round(highN)) < _epsilon
and not high_in_ticks:
2667 last_granularity = granularity
2670 countermod3 = counter % 3
2671 if countermod3 == 0: granularity *= 0.5
2672 elif countermod3 == 1: granularity *= 0.4
2673 else: granularity *= 0.5
2675 lowN = math.ceil(1.*self.
low / granularity)
2676 highN = math.floor(1.*self.
high / granularity)
2679 """Return exactly N linear ticks.
2681 Normally only used internally.
2687 x += (self.
high - self.
low)/(N-1.)
2691 """Return optimal linear miniticks, given a set of ticks.
2693 Normally only used internally.
2695 if len(original_ticks) < 2: original_ticks =
ticks(self.
low, self.
high)
2696 original_ticks = sorted(original_ticks.keys())
2698 if self.
low > original_ticks[0] + _epsilon
or self.
high < original_ticks[-1] - _epsilon:
2699 raise ValueError(
"original_ticks {%g...%g} extend beyond [%g, %g]" % (original_ticks[0], original_ticks[-1], self.
low, self.
high))
2702 for i
in range(len(original_ticks)-1):
2703 granularities.append(original_ticks[i+1] - original_ticks[i])
2704 spacing = 10**(math.ceil(math.log10(
min(granularities)) - 1))
2707 x = original_ticks[0] - math.ceil(1.*(original_ticks[0] - self.
low) / spacing) * spacing
2709 while x <= self.
high:
2711 already_in_ticks =
False
2712 for t
in original_ticks:
2713 if abs(x-t) < _epsilon * (self.
high - self.
low): already_in_ticks =
True
2714 if not already_in_ticks: output.append(x)
2719 """Return less than -N or exactly N optimal logarithmic ticks.
2721 Normally only used internally.
2723 if self.
low >= self.
high:
raise ValueError(
"low must be less than high")
2724 if N == 1:
raise ValueError(
"N can be 0 or >1 to specify the exact number of ticks or negative to specify a maximum")
2726 eps = _epsilon * (self.
high - self.
low)
2732 if format == unumber
and abs(x) < eps: label =
u"0"
2735 x += (self.
high - self.
low)/(N-1.)
2740 lowN = math.floor(math.log(self.
low, base))
2741 highN = math.ceil(math.log(self.
high, base))
2746 if self.
low <= x <= self.
high: output[x] = label
2748 for i
in range(1, len(output)):
2749 keys = sorted(output.keys())
2751 values =
map(
lambda k: output[k], keys)
2752 if len(values) <= N:
2753 for k
in output.keys():
2758 if len(output) <= 2:
2760 lowest =
min(output2)
2763 if k < lowest: output2[k] = output[k]
2769 """Return optimal logarithmic miniticks, given a set of ticks.
2771 Normally only used internally.
2773 if self.
low >= self.
high:
raise ValueError(
"low must be less than high")
2775 lowN = math.floor(math.log(self.
low, base))
2776 highN = math.ceil(math.log(self.
high, base))
2781 if self.
low <= x <= self.
high: num_ticks += 1
2782 for m
in range(2,
int(math.ceil(base))):
2784 if self.
low <= minix <= self.
high: output.append(minix)
2786 if num_ticks <= 2:
return []
2792 """Draw an axis with tick marks along a parametric curve.
2794 CurveAxis(f, low, high, ticks, miniticks, labels, logbase, arrow_start, arrow_end,
2795 text_attr, attribute=value)
2797 f required a Python callable or string in
2798 the form "f(t), g(t)", just like Curve
2799 low, high required left and right endpoints
2800 ticks default=-10 request ticks according to the standard
2801 tick specification (see help(Ticks))
2802 miniticks default=True request miniticks according to the
2803 standard minitick specification
2804 labels True request tick labels according to the
2805 standard tick label specification
2806 logbase default=None if a number, the x axis is logarithmic
2807 with ticks at the given base (10 being
2809 arrow_start default=None if a new string identifier, draw an
2810 arrow at the low-end of the axis,
2811 referenced by that identifier; if an
2812 SVG marker object, use that marker
2813 arrow_end default=None if a new string identifier, draw an
2814 arrow at the high-end of the axis,
2815 referenced by that identifier; if an
2816 SVG marker object, use that marker
2817 text_attr default={} SVG attributes for the text labels
2818 attribute=value pairs keyword list SVG attributes
2820 defaults = {
"stroke-width":
"0.25pt"}
2821 text_defaults = {
"stroke":
"none",
"fill":
"black",
"font-size":5}
2826 def __init__(self, f, low, high, ticks=-10, miniticks=True, labels=True, logbase=None, arrow_start=None, arrow_end=None, text_attr={}, **attr):
2828 tattr.update(text_attr)
2829 Curve.__init__(self, f, low, high)
2830 Ticks.__init__(self, f, low, high, ticks, miniticks, labels, logbase, arrow_start, arrow_end, tattr, **attr)
2833 """Apply the transformation "trans" and return an SVG object."""
2834 func = Curve.SVG(self, trans)
2835 ticks = Ticks.SVG(self, trans)
2839 func.attr[
"marker-start"] =
"url(#%s)" % self.
arrow_start
2841 func.attr[
"marker-start"] =
"url(#%s)" % self.
arrow_start.id
2845 func.attr[
"marker-end"] =
"url(#%s)" % self.
arrow_end
2847 func.attr[
"marker-end"] =
"url(#%s)" % self.
arrow_end.id
2853 """Draws an axis with tick marks along a line.
2855 LineAxis(x1, y1, x2, y2, start, end, ticks, miniticks, labels, logbase,
2856 arrow_start, arrow_end, text_attr, attribute=value)
2858 x1, y1 required starting point
2859 x2, y2 required ending point
2860 start, end default=0, 1 values to start and end labeling
2861 ticks default=-10 request ticks according to the standard
2862 tick specification (see help(Ticks))
2863 miniticks default=True request miniticks according to the
2864 standard minitick specification
2865 labels True request tick labels according to the
2866 standard tick label specification
2867 logbase default=None if a number, the x axis is logarithmic
2868 with ticks at the given base (usually 10)
2869 arrow_start default=None if a new string identifier, draw an arrow
2870 at the low-end of the axis, referenced by
2871 that identifier; if an SVG marker object,
2873 arrow_end default=None if a new string identifier, draw an arrow
2874 at the high-end of the axis, referenced by
2875 that identifier; if an SVG marker object,
2877 text_attr default={} SVG attributes for the text labels
2878 attribute=value pairs keyword list SVG attributes
2880 defaults = {
"stroke-width":
"0.25pt"}
2881 text_defaults = {
"stroke":
"none",
"fill":
"black",
"font-size":5}
2884 return "<LineAxis (%g, %g) to (%g, %g) ticks=%s labels=%s %s>" % (self.x1, self.y1, self.x2, self.
y2,
str(self.
ticks),
str(self.
labels), self.
attr)
2886 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):
2891 tattr.update(text_attr)
2892 Line.__init__(self, x1, y1, x2, y2, **attr)
2893 Ticks.__init__(self,
None,
None,
None, ticks, miniticks, labels, logbase, arrow_start, arrow_end, tattr, **attr)
2896 if self.
exclude !=
None and not (isinstance(self.
exclude, (tuple, list))
and len(self.
exclude) == 2
and \
2897 isinstance(self.
exclude[0], (int, long, float))
and isinstance(self.
exclude[1], (int, long, float))):
2898 raise TypeError(
"exclude must either be None or (low, high)")
2900 ticks, miniticks = Ticks.interpret(self)
2901 if self.
exclude ==
None:
return ticks, miniticks
2904 for loc, label
in ticks.items():
2910 return ticks2, miniticks
2913 """Apply the transformation "trans" and return an SVG object."""
2914 line = Line.SVG(self, trans)
2923 line.attr[
"marker-start"] =
"url(#%s)" % self.
arrow_start
2925 line.attr[
"marker-start"] =
"url(#%s)" % self.
arrow_start.id
2929 line.attr[
"marker-end"] =
"url(#%s)" % self.
arrow_end
2931 line.attr[
"marker-end"] =
"url(#%s)" % self.
arrow_end.id
2933 ticks = Ticks.SVG(self, trans)
2938 """Draws an x axis with tick marks.
2940 XAxis(xmin, xmax, aty, ticks, miniticks, labels, logbase, arrow_start, arrow_end,
2941 exclude, text_attr, attribute=value)
2943 xmin, xmax required the x range
2944 aty default=0 y position to draw the axis
2945 ticks default=-10 request ticks according to the standard
2946 tick specification (see help(Ticks))
2947 miniticks default=True request miniticks according to the
2948 standard minitick specification
2949 labels True request tick labels according to the
2950 standard tick label specification
2951 logbase default=None if a number, the x axis is logarithmic
2952 with ticks at the given base (usually 10)
2953 arrow_start default=None if a new string identifier, draw an arrow
2954 at the low-end of the axis, referenced by
2955 that identifier; if an SVG marker object,
2957 arrow_end default=None if a new string identifier, draw an arrow
2958 at the high-end of the axis, referenced by
2959 that identifier; if an SVG marker object,
2961 exclude default=None if a (low, high) pair, don't draw text
2962 labels within this range
2963 text_attr default={} SVG attributes for the text labels
2964 attribute=value pairs keyword list SVG attributes for all lines
2966 The exclude option is provided for Axes to keep text from overlapping
2967 where the axes cross. Normal users are not likely to need it.
2969 defaults = {
"stroke-width":
"0.25pt"}
2970 text_defaults = {
"stroke":
"none",
"fill":
"black",
"font-size":5,
"dominant-baseline":
"text-before-edge"}
2975 return "<XAxis (%g, %g) at y=%g ticks=%s labels=%s %s>" % (self.xmin, self.xmax, self.
aty,
str(self.
ticks),
str(self.
labels), self.
attr)
2977 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):
2980 tattr.update(text_attr)
2981 LineAxis.__init__(self, xmin, aty, xmax, aty, xmin, xmax, ticks, miniticks, labels, logbase, arrow_start, arrow_end, exclude, tattr, **attr)
2984 """Apply the transformation "trans" and return an SVG object."""
2987 return LineAxis.SVG(self, trans)
2990 """Draws a y axis with tick marks.
2992 YAxis(ymin, ymax, atx, ticks, miniticks, labels, logbase, arrow_start, arrow_end,
2993 exclude, text_attr, attribute=value)
2995 ymin, ymax required the y range
2996 atx default=0 x position to draw the axis
2997 ticks default=-10 request ticks according to the standard
2998 tick specification (see help(Ticks))
2999 miniticks default=True request miniticks according to the
3000 standard minitick specification
3001 labels True request tick labels according to the
3002 standard tick label specification
3003 logbase default=None if a number, the y axis is logarithmic
3004 with ticks at the given base (usually 10)
3005 arrow_start default=None if a new string identifier, draw an arrow
3006 at the low-end of the axis, referenced by
3007 that identifier; if an SVG marker object,
3009 arrow_end default=None if a new string identifier, draw an arrow
3010 at the high-end of the axis, referenced by
3011 that identifier; if an SVG marker object,
3013 exclude default=None if a (low, high) pair, don't draw text
3014 labels within this range
3015 text_attr default={} SVG attributes for the text labels
3016 attribute=value pairs keyword list SVG attributes for all lines
3018 The exclude option is provided for Axes to keep text from overlapping
3019 where the axes cross. Normal users are not likely to need it.
3021 defaults = {
"stroke-width":
"0.25pt"}
3022 text_defaults = {
"stroke":
"none",
"fill":
"black",
"font-size":5,
"text-anchor":
"end",
"dominant-baseline":
"middle"}
3027 return "<YAxis (%g, %g) at x=%g ticks=%s labels=%s %s>" % (self.ymin, self.ymax, self.
atx,
str(self.
ticks),
str(self.
labels), self.
attr)
3029 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):
3032 tattr.update(text_attr)
3033 LineAxis.__init__(self, atx, ymin, atx, ymax, ymin, ymax, ticks, miniticks, labels, logbase, arrow_start, arrow_end, exclude, tattr, **attr)
3036 """Apply the transformation "trans" and return an SVG object."""
3039 return LineAxis.SVG(self, trans)
3042 """Draw a pair of intersecting x-y axes.
3044 Axes(xmin, xmax, ymin, ymax, atx, aty, xticks, xminiticks, xlabels, xlogbase,
3045 yticks, yminiticks, ylabels, ylogbase, arrows, text_attr, attribute=value)
3047 xmin, xmax required the x range
3048 ymin, ymax required the y range
3049 atx, aty default=0, 0 point where the axes try to cross;
3050 if outside the range, the axes will
3051 cross at the closest corner
3052 xticks default=-10 request ticks according to the standard
3053 tick specification (see help(Ticks))
3054 xminiticks default=True request miniticks according to the
3055 standard minitick specification
3056 xlabels True request tick labels according to the
3057 standard tick label specification
3058 xlogbase default=None if a number, the x axis is logarithmic
3059 with ticks at the given base (usually 10)
3060 yticks default=-10 request ticks according to the standard
3062 yminiticks default=True request miniticks according to the
3063 standard minitick specification
3064 ylabels True request tick labels according to the
3065 standard tick label specification
3066 ylogbase default=None if a number, the y axis is logarithmic
3067 with ticks at the given base (usually 10)
3068 arrows default=None if a new string identifier, draw arrows
3069 referenced by that identifier
3070 text_attr default={} SVG attributes for the text labels
3071 attribute=value pairs keyword list SVG attributes for all lines
3073 defaults = {
"stroke-width":
"0.25pt"}
3074 text_defaults = {
"stroke":
"none",
"fill":
"black",
"font-size":5}
3077 return "<Axes x=(%g, %g) y=(%g, %g) at (%g, %g) %s>" % (self.xmin, self.
xmax, self.ymin, self.
ymax, self.atx, self.
aty, self.
attr)
3079 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):
3083 self.xticks, self.xminiticks, self.xlabels, self.
xlogbase = xticks, xminiticks, xlabels, xlogbase
3084 self.yticks, self.yminiticks, self.ylabels, self.
ylogbase = yticks, yminiticks, ylabels, ylogbase
3094 """Apply the transformation "trans" and return an SVG object."""
3095 atx, aty = self.atx, self.
aty
3096 if atx < self.xmin: atx = self.xmin
3097 if atx > self.
xmax: atx = self.
xmax
3098 if aty < self.ymin: aty = self.ymin
3099 if aty > self.
ymax: aty = self.
ymax
3101 xmargin = 0.1 *
abs(self.ymin - self.
ymax)
3102 xexclude = atx - xmargin, atx + xmargin
3104 ymargin = 0.1 *
abs(self.xmin - self.
xmax)
3105 yexclude = aty - ymargin, aty + ymargin
3108 xarrow_start = self.
arrows +
".xstart"
3109 xarrow_end = self.
arrows +
".xend"
3110 yarrow_start = self.
arrows +
".ystart"
3111 yarrow_end = self.
arrows +
".yend"
3113 xarrow_start = xarrow_end = yarrow_start = yarrow_end =
None
3115 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)
3116 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)
3117 return SVG(
"g", *(xaxis.sub + yaxis.sub))
3122 """Draws the horizontal lines of a grid over a specified region
3123 using the standard tick specification (see help(Ticks)) to place the
3126 HGrid(xmin, xmax, low, high, ticks, miniticks, logbase, mini_attr, attribute=value)
3128 xmin, xmax required the x range
3129 low, high required the y range
3130 ticks default=-10 request ticks according to the standard
3131 tick specification (see help(Ticks))
3132 miniticks default=False request miniticks according to the
3133 standard minitick specification
3134 logbase default=None if a number, the axis is logarithmic
3135 with ticks at the given base (usually 10)
3136 mini_attr default={} SVG attributes for the minitick-lines
3137 (if miniticks != False)
3138 attribute=value pairs keyword list SVG attributes for the major tick lines
3140 defaults = {
"stroke-width":
"0.25pt",
"stroke":
"gray"}
3141 mini_defaults = {
"stroke-width":
"0.25pt",
"stroke":
"lightgray",
"stroke-dasharray":
"1,1"}
3146 def __init__(self, xmin, xmax, low, high, ticks=-10, miniticks=False, logbase=None, mini_attr={}, **attr):
3152 Ticks.__init__(self,
None, low, high, ticks, miniticks,
None, logbase)
3158 """Apply the transformation "trans" and return an SVG object."""
3162 for t
in self.last_ticks.
keys():
3163 ticksd +=
Line(self.xmin, t, self.
xmax, t).
Path(trans).d
3167 miniticksd +=
Line(self.xmin, t, self.
xmax, t).
Path(trans).d
3172 """Draws the vertical lines of a grid over a specified region
3173 using the standard tick specification (see help(Ticks)) to place the
3176 HGrid(ymin, ymax, low, high, ticks, miniticks, logbase, mini_attr, attribute=value)
3178 ymin, ymax required the y range
3179 low, high required the x range
3180 ticks default=-10 request ticks according to the standard
3181 tick specification (see help(Ticks))
3182 miniticks default=False request miniticks according to the
3183 standard minitick specification
3184 logbase default=None if a number, the axis is logarithmic
3185 with ticks at the given base (usually 10)
3186 mini_attr default={} SVG attributes for the minitick-lines
3187 (if miniticks != False)
3188 attribute=value pairs keyword list SVG attributes for the major tick lines
3190 defaults = {
"stroke-width":
"0.25pt",
"stroke":
"gray"}
3191 mini_defaults = {
"stroke-width":
"0.25pt",
"stroke":
"lightgray",
"stroke-dasharray":
"1,1"}
3196 def __init__(self, ymin, ymax, low, high, ticks=-10, miniticks=False, logbase=None, mini_attr={}, **attr):
3202 Ticks.__init__(self,
None, low, high, ticks, miniticks,
None, logbase)
3208 """Apply the transformation "trans" and return an SVG object."""
3212 for t
in self.last_ticks.
keys():
3213 ticksd +=
Line(t, self.ymin, t, self.
ymax).
Path(trans).d
3217 miniticksd +=
Line(t, self.ymin, t, self.
ymax).
Path(trans).d
3222 """Draws a grid over a specified region using the standard tick
3223 specification (see help(Ticks)) to place the grid lines.
3225 Grid(xmin, xmax, ymin, ymax, ticks, miniticks, logbase, mini_attr, attribute=value)
3227 xmin, xmax required the x range
3228 ymin, ymax required the y range
3229 ticks default=-10 request ticks according to the standard
3230 tick specification (see help(Ticks))
3231 miniticks default=False request miniticks according to the
3232 standard minitick specification
3233 logbase default=None if a number, the axis is logarithmic
3234 with ticks at the given base (usually 10)
3235 mini_attr default={} SVG attributes for the minitick-lines
3236 (if miniticks != False)
3237 attribute=value pairs keyword list SVG attributes for the major tick lines
3239 defaults = {
"stroke-width":
"0.25pt",
"stroke":
"gray"}
3240 mini_defaults = {
"stroke-width":
"0.25pt",
"stroke":
"lightgray",
"stroke-dasharray":
"1,1"}
3243 return "<Grid x=(%g, %g) y=(%g, %g) ticks=%s miniticks=%s %s>" % (self.xmin, self.
xmax, self.ymin, self.
ymax,
str(self.
ticks),
str(self.
miniticks), self.
attr)
3245 def __init__(self, xmin, xmax, ymin, ymax, ticks=-10, miniticks=False, logbase=None, mini_attr={}, **attr):
3252 Ticks.__init__(self,
None,
None,
None, ticks, miniticks,
None, logbase)
3258 """Apply the transformation "trans" and return an SVG object."""
3265 for t
in self.last_xticks.
keys():
3266 ticksd +=
Line(t, self.ymin, t, self.
ymax).
Path(trans).d
3267 for t
in self.last_yticks.
keys():
3268 ticksd +=
Line(self.xmin, t, self.
xmax, t).
Path(trans).d
3272 miniticksd +=
Line(t, self.ymin, t, self.
ymax).
Path(trans).d
3274 miniticksd +=
Line(self.xmin, t, self.
xmax, t).
Path(trans).d
3281 """Draws x error bars at a set of points. This is usually used
3282 before (under) a set of Dots at the same points.
3284 XErrorBars(d, attribute=value)
3286 d required list of (x,y,xerr...) points
3287 attribute=value pairs keyword list SVG attributes
3291 * 3 elements, the third is the symmetric error bar
3292 * 4 elements, the third and fourth are the asymmetric lower and
3293 upper error bar. The third element should be negative,
3294 e.g. (5, 5, -1, 2) is a bar from 4 to 7.
3295 * more than 4, a tick mark is placed at each value. This lets
3296 you nest errors from different sources, correlated and
3297 uncorrelated, statistical and systematic, etc.
3299 defaults = {
"stroke-width":
"0.25pt"}
3302 return "<XErrorBars (%d nodes)>" % len(self.
d)
3311 """Apply the transformation "trans" and return an SVG object."""
3312 if isinstance(trans, str): trans =
totrans(trans)
3318 if len(p) == 3: bars = [x - p[2], x + p[2]]
3319 else: bars = [x + pi
for pi
in p[2:]]
3321 start, end =
min(bars),
max(bars)
3322 output.append(
LineAxis(start, y, end, y, start, end, bars,
False,
False, **self.
attr).
SVG(trans))
3327 """Draws y error bars at a set of points. This is usually used
3328 before (under) a set of Dots at the same points.
3330 YErrorBars(d, attribute=value)
3332 d required list of (x,y,yerr...) points
3333 attribute=value pairs keyword list SVG attributes
3337 * 3 elements, the third is the symmetric error bar
3338 * 4 elements, the third and fourth are the asymmetric lower and
3339 upper error bar. The third element should be negative,
3340 e.g. (5, 5, -1, 2) is a bar from 4 to 7.
3341 * more than 4, a tick mark is placed at each value. This lets
3342 you nest errors from different sources, correlated and
3343 uncorrelated, statistical and systematic, etc.
3345 defaults = {
"stroke-width":
"0.25pt"}
3348 return "<YErrorBars (%d nodes)>" % len(self.
d)
3357 """Apply the transformation "trans" and return an SVG object."""
3358 if isinstance(trans, str): trans =
totrans(trans)
3364 if len(p) == 3: bars = [y - p[2], y + p[2]]
3365 else: bars = [y + pi
for pi
in p[2:]]
3367 start, end =
min(bars),
max(bars)
3368 output.append(
LineAxis(x, start, x, end, start, end, bars,
False,
False, **self.
attr).
SVG(trans))