use listings.sty
[PyX/mjg.git] / pyx / graph.py
blob86cfa8ca8533a7e7fdae2c9dfa155acf0efbc365
1 #!/usr/bin/env python
4 # Copyright (C) 2002 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2002 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 import types, re, math, string, sys
25 import bbox, box, canvas, path, tex, unit, mathtree, trafo, attrlist, color, helper
28 goldenrule = 0.5 * (math.sqrt(5) + 1)
31 ################################################################################
32 # maps
33 ################################################################################
35 class _Imap:
36 "maps convert a value into another value by bijective transformation f"
38 def convert(self, x):
39 "returns f(x)"
41 def invert(self, y):
42 "returns f^-1(y) where f^-1 is the inverse transformation (x=f^-1(f(x)) for all x)"
44 def setbasepoint(self, basepoints):
45 """set basepoints for the convertions
46 basepoints are tuples (x, y) with y == f(x) and x == f^-1(y)
47 the number of basepoints needed might depend on the transformation
48 usually two pairs are needed like for linear maps, logarithmic maps, etc."""
51 class _linmap:
52 "linear mapping"
53 __implements__ = _Imap
55 def setbasepoints(self, basepoints):
56 self.dydx = (basepoints[1][1] - basepoints[0][1]) / float(basepoints[1][0] - basepoints[0][0])
57 self.dxdy = (basepoints[1][0] - basepoints[0][0]) / float(basepoints[1][1] - basepoints[0][1])
58 self.x1 = basepoints[0][0]
59 self.y1 = basepoints[0][1]
60 return self
62 def convert(self, value):
63 return self.y1 + self.dydx * (value - self.x1)
65 def invert(self, value):
66 return self.x1 + self.dxdy * (value - self.y1)
69 class _logmap:
70 "logarithmic mapping"
71 __implements__ = _Imap
73 def setbasepoints(self, basepoints):
74 self.dydx = ((basepoints[1][1] - basepoints[0][1]) /
75 float(math.log(basepoints[1][0]) - math.log(basepoints[0][0])))
76 self.dxdy = ((math.log(basepoints[1][0]) - math.log(basepoints[0][0])) /
77 float(basepoints[1][1] - basepoints[0][1]))
78 self.x1 = math.log(basepoints[0][0])
79 self.y1 = basepoints[0][1]
80 return self
82 def convert(self, value):
83 return self.y1 + self.dydx * (math.log(value) - self.x1)
85 def invert(self, value):
86 return math.exp(self.x1 + self.dxdy * (value - self.y1))
90 ################################################################################
91 # tick lists = partitions
92 ################################################################################
95 class frac:
96 "fraction type for rational arithmetics"
98 def __init__(self, enum, denom, power=None):
99 "for power!=None: frac=(enum/denom)**power"
100 if not helper._isinteger(enum) or not helper._isinteger(denom): raise TypeError("integer type expected")
101 if not denom: raise ZeroDivisionError("zero denominator")
102 if power != None:
103 if not helper._isinteger(power): raise TypeError("integer type expected")
104 if power >= 0:
105 self.enum = long(enum) ** power
106 self.denom = long(denom) ** power
107 else:
108 self.enum = long(denom) ** (-power)
109 self.denom = long(enum) ** (-power)
110 else:
111 self.enum = enum
112 self.denom = denom
114 def __cmp__(self, other):
115 if other is None:
116 return 1
117 return cmp(self.enum * other.denom, other.enum * self.denom)
119 def __mul__(self, other):
120 return frac(self.enum * other.enum, self.denom * other.denom)
122 def __float__(self):
123 return float(self.enum) / self.denom
125 def __str__(self):
126 return "%i/%i" % (self.enum, self.denom)
128 def __repr__(self):
129 return "frac(%r, %r)" % (self.enum, self.denom) # I want to see the "L"
132 def _ensurefrac(arg):
133 "ensure frac by converting a string to frac"
135 def createfrac(str):
136 commaparts = str.split(".")
137 for part in commaparts:
138 if not part.isdigit(): raise ValueError("non-digits found in '%s'" % part)
139 if len(commaparts) == 1:
140 return frac(long(commaparts[0]), 1)
141 elif len(commaparts) == 2:
142 result = frac(1, 10l, power=len(commaparts[1]))
143 result.enum = long(commaparts[0])*result.denom + long(commaparts[1])
144 return result
145 else: raise ValueError("multiple '.' found in '%s'" % str)
147 if helper._isstring(arg):
148 fraction = arg.split("/")
149 if len(fraction) > 2: raise ValueError("multiple '/' found in '%s'" % arg)
150 value = createfrac(fraction[0])
151 if len(fraction) == 2:
152 value2 = createfrac(fraction[1])
153 value = frac(value.enum * value2.denom, value.denom * value2.enum)
154 return value
155 if not isinstance(arg, frac): raise ValueError("can't convert argument to frac")
156 return arg
159 class tick(frac):
160 "a tick is a frac enhanced by a ticklevel, a labellevel and a text (they all might be None)"
162 def __init__(self, enum, denom, ticklevel=None, labellevel=None, text=None):
163 frac.__init__(self, enum, denom)
164 self.ticklevel = ticklevel
165 self.labellevel = labellevel
166 self.text = text
168 def merge(self, other):
169 if self.ticklevel is None or (other.ticklevel is not None and other.ticklevel < self.ticklevel):
170 self.ticklevel = other.ticklevel
171 if self.labellevel is None or (other.labellevel is not None and other.labellevel < self.labellevel):
172 self.labellevel = other.labellevel
173 if self.text is None:
174 self.text = other.text
176 def __repr__(self):
177 return "tick(%r, %r, %s, %s, %s)" % (self.enum, self.denom, self.ticklevel, self.labellevel, self.text)
180 def _mergeticklists(list1, list2):
181 """return a merged list of ticks out of list1 and list2
182 lists have to be ordered (returned list is also ordered)
183 caution: side effects (input lists might be altered)"""
184 # TODO: improve this using bisect
185 i = 0
186 j = 0
187 try:
188 while 1: # we keep on going until we reach an index error
189 while list2[j] < list1[i]: # insert tick
190 list1.insert(i, list2[j])
191 i += 1
192 j += 1
193 if list2[j] == list1[i]: # merge tick
194 list1[i].merge(list2[j])
195 j += 1
196 i += 1
197 except IndexError:
198 if j < len(list2):
199 list1 += list2[j:]
200 return list1
203 def _mergetexts(ticks, texts):
204 "merges texts into ticks"
205 if helper._issequenceofsequences(texts):
206 for text, level in zip(texts, xrange(sys.maxint)):
207 usetext = helper._ensuresequence(text)
208 i = 0
209 for tick in ticks:
210 if tick.labellevel == level:
211 tick.text = usetext[i]
212 i += 1
213 if i != len(usetext):
214 raise IndexError("wrong sequence length of texts at level %i" % level)
215 elif texts is not None:
216 usetext = helper._ensuresequence(texts)
217 i = 0
218 for tick in ticks:
219 if tick.labellevel == 0:
220 tick.text = usetext[i]
221 i += 1
222 if i != len(usetext):
223 raise IndexError("wrong sequence length of texts")
226 class manualpart:
228 def __init__(self, ticks=None, labels=None, texts=None, mix=()):
229 self.multipart = 0
230 if ticks is None and labels is not None:
231 self.ticks = helper._ensuresequence(helper._getsequenceno(labels, 0))
232 else:
233 self.ticks = ticks
235 if labels is None and ticks is not None:
236 self.labels = helper._ensuresequence(helper._getsequenceno(ticks, 0))
237 else:
238 self.labels = labels
239 self.texts = texts
240 self.mix = mix
243 def checkfraclist(self, *fracs):
244 if not len(fracs): return ()
245 sorted = list(fracs)
246 sorted.sort()
247 last = sorted[0]
248 for item in sorted[1:]:
249 if last == item:
250 raise ValueError("duplicate entry found")
251 last = item
252 return sorted
254 def part(self):
255 ticks = list(self.mix)
256 if helper._issequenceofsequences(self.ticks):
257 for fracs, level in zip(self.ticks, xrange(sys.maxint)):
258 ticks = _mergeticklists(ticks, [tick(frac.enum, frac.denom, ticklevel = level)
259 for frac in self.checkfraclist(*map(_ensurefrac, helper._ensuresequence(fracs)))])
260 else:
261 ticks = _mergeticklists(ticks, [tick(frac.enum, frac.denom, ticklevel = 0)
262 for frac in self.checkfraclist(*map(_ensurefrac, helper._ensuresequence(self.ticks)))])
264 if helper._issequenceofsequences(self.labels):
265 for fracs, level in zip(self.labels, xrange(sys.maxint)):
266 ticks = _mergeticklists(ticks, [tick(frac.enum, frac.denom, labellevel = level)
267 for frac in self.checkfraclist(*map(_ensurefrac, helper._ensuresequence(fracs)))])
268 else:
269 ticks = _mergeticklists(ticks, [tick(frac.enum, frac.denom, labellevel = 0)
270 for frac in self.checkfraclist(*map(_ensurefrac, helper._ensuresequence(self.labels)))])
272 _mergetexts(ticks, self.texts)
274 return ticks
276 def defaultpart(self, min, max, extendmin, extendmax):
277 return self.part()
280 class linpart:
282 def __init__(self, ticks=None, labels=None, texts=None, extendtick=0, extendlabel=None, epsilon=1e-10, mix=()):
283 self.multipart = 0
284 if ticks is None and labels is not None:
285 self.ticks = (_ensurefrac(helper._ensuresequence(labels)[0]),)
286 else:
287 self.ticks = map(_ensurefrac, helper._ensuresequence(ticks))
288 if labels is None and ticks is not None:
289 self.labels = (_ensurefrac(helper._ensuresequence(ticks)[0]),)
290 else:
291 self.labels = map(_ensurefrac, helper._ensuresequence(labels))
292 self.texts = texts
293 self.extendtick = extendtick
294 self.extendlabel = extendlabel
295 self.epsilon = epsilon
296 self.mix = mix
298 def extendminmax(self, min, max, frac, extendmin, extendmax):
299 if extendmin:
300 min = float(frac) * math.floor(min / float(frac) + self.epsilon)
301 if extendmax:
302 max = float(frac) * math.ceil(max / float(frac) - self.epsilon)
303 return min, max
305 def getticks(self, min, max, frac, ticklevel=None, labellevel=None):
306 imin = int(math.ceil(min / float(frac) - 0.5 * self.epsilon))
307 imax = int(math.floor(max / float(frac) + 0.5 * self.epsilon))
308 ticks = []
309 for i in range(imin, imax + 1):
310 ticks.append(tick(long(i) * frac.enum, frac.denom, ticklevel = ticklevel, labellevel = labellevel))
311 return ticks
313 def defaultpart(self, min, max, extendmin, extendmax):
314 if self.extendtick is not None and len(self.ticks) > self.extendtick:
315 min, max = self.extendminmax(min, max, self.ticks[self.extendtick], extendmin, extendmax)
316 if self.extendlabel is not None and len(self.labels) > self.extendlabel:
317 min, max = self.extendminmax(min, max, self.labels[self.extendlabel], extendmin, extendmax)
319 ticks = list(self.mix)
320 for i in range(len(self.ticks)):
321 ticks = _mergeticklists(ticks, self.getticks(min, max, self.ticks[i], ticklevel = i))
322 for i in range(len(self.labels)):
323 ticks = _mergeticklists(ticks, self.getticks(min, max, self.labels[i], labellevel = i))
325 _mergetexts(ticks, self.texts)
327 return ticks
330 class autolinpart:
332 defaultlist = ((frac(1, 1), frac(1, 2)),
333 (frac(2, 1), frac(1, 1)),
334 (frac(5, 2), frac(5, 4)),
335 (frac(5, 1), frac(5, 2)))
337 def __init__(self, list=defaultlist, extendtick=0, epsilon=1e-10, mix=()):
338 self.multipart = 1
339 self.list = list
340 self.extendtick = extendtick
341 self.epsilon = epsilon
342 self.mix = mix
344 def defaultpart(self, min, max, extendmin, extendmax):
345 base = frac(10L, 1, int(math.log(max - min) / math.log(10)))
346 ticks = self.list[0]
347 useticks = [tick * base for tick in ticks]
348 self.lesstickindex = self.moretickindex = 0
349 self.lessbase = frac(base.enum, base.denom)
350 self.morebase = frac(base.enum, base.denom)
351 self.min, self.max, self.extendmin, self.extendmax = min, max, extendmin, extendmax
352 part = linpart(ticks=useticks, extendtick=self.extendtick, epsilon=self.epsilon, mix=self.mix)
353 return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
355 def lesspart(self):
356 if self.lesstickindex < len(self.list) - 1:
357 self.lesstickindex += 1
358 else:
359 self.lesstickindex = 0
360 self.lessbase.enum *= 10
361 ticks = self.list[self.lesstickindex]
362 useticks = [tick * self.lessbase for tick in ticks]
363 part = linpart(ticks=useticks, extendtick=self.extendtick, epsilon=self.epsilon, mix=self.mix)
364 return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
366 def morepart(self):
367 if self.moretickindex:
368 self.moretickindex -= 1
369 else:
370 self.moretickindex = len(self.list) - 1
371 self.morebase.denom *= 10
372 ticks = self.list[self.moretickindex]
373 useticks = [tick * self.morebase for tick in ticks]
374 part = linpart(ticks=useticks, extendtick=self.extendtick, epsilon=self.epsilon, mix=self.mix)
375 return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
378 class shiftfracs:
380 def __init__(self, shift, *fracs):
381 self.shift = shift
382 self.fracs = fracs
385 class logpart(linpart):
387 shift5fracs1 = shiftfracs(100000, frac(1, 1))
388 shift4fracs1 = shiftfracs(10000, frac(1, 1))
389 shift3fracs1 = shiftfracs(1000, frac(1, 1))
390 shift2fracs1 = shiftfracs(100, frac(1, 1))
391 shiftfracs1 = shiftfracs(10, frac(1, 1))
392 shiftfracs125 = shiftfracs(10, frac(1, 1), frac(2, 1), frac(5, 1))
393 shiftfracs1to9 = shiftfracs(10, *list(map(lambda x: frac(x, 1), range(1, 10))))
394 # ^- we always include 1 in order to get extendto(tick|label)level to work as expected
396 def __init__(self, ticks=None, labels=None, texts=None, extendtick=0, extendlabel=None, epsilon=1e-10, mix=()):
397 self.multipart = 0
398 if ticks is None and labels is not None:
399 self.ticks = (helper._ensuresequence(labels)[0],)
400 else:
401 self.ticks = helper._ensuresequence(ticks)
403 if labels is None and ticks is not None:
404 self.labels = (helper._ensuresequence(ticks)[0],)
405 else:
406 self.labels = helper._ensuresequence(labels)
407 self.texts = texts
408 self.extendtick = extendtick
409 self.extendlabel = extendlabel
410 self.epsilon = epsilon
411 self.mix = mix
413 def extendminmax(self, min, max, shiftfracs, extendmin, extendmax):
414 minpower = None
415 maxpower = None
416 for i in xrange(len(shiftfracs.fracs)):
417 imin = int(math.floor(math.log(min / float(shiftfracs.fracs[i])) /
418 math.log(shiftfracs.shift) + self.epsilon)) + 1
419 imax = int(math.ceil(math.log(max / float(shiftfracs.fracs[i])) /
420 math.log(shiftfracs.shift) - self.epsilon)) - 1
421 if minpower is None or imin < minpower:
422 minpower, minindex = imin, i
423 if maxpower is None or imax >= maxpower:
424 maxpower, maxindex = imax, i
425 if minindex:
426 minfrac = shiftfracs.fracs[minindex - 1]
427 else:
428 minfrac = shiftfracs.fracs[-1]
429 minpower -= 1
430 if maxindex != len(shiftfracs.fracs) - 1:
431 maxfrac = shiftfracs.fracs[maxindex + 1]
432 else:
433 maxfrac = shiftfracs.fracs[0]
434 maxpower += 1
435 if extendmin:
436 min = float(minfrac) * float(shiftfracs.shift) ** minpower
437 if extendmax:
438 max = float(maxfrac) * float(shiftfracs.shift) ** maxpower
439 return min, max
441 def getticks(self, min, max, shiftfracs, ticklevel=None, labellevel=None):
442 ticks = list(self.mix)
443 minimin = 0
444 maximax = 0
445 for f in shiftfracs.fracs:
446 fracticks = []
447 imin = int(math.ceil(math.log(min / float(f)) /
448 math.log(shiftfracs.shift) - 0.5 * self.epsilon))
449 imax = int(math.floor(math.log(max / float(f)) /
450 math.log(shiftfracs.shift) + 0.5 * self.epsilon))
451 for i in range(imin, imax + 1):
452 pos = f * frac(shiftfracs.shift, 1, i)
453 fracticks.append(tick(pos.enum, pos.denom, ticklevel = ticklevel, labellevel = labellevel))
454 ticks = _mergeticklists(ticks, fracticks)
455 return ticks
458 class autologpart(logpart):
460 defaultlist = (((logpart.shiftfracs1, # ticks
461 logpart.shiftfracs1to9), # subticks
462 (logpart.shiftfracs1, # labels
463 logpart.shiftfracs125)), # sublevels
465 ((logpart.shiftfracs1, # ticks
466 logpart.shiftfracs1to9), # subticks
467 None), # labels like ticks
469 ((logpart.shift2fracs1, # ticks
470 logpart.shiftfracs1), # subticks
471 None), # labels like ticks
473 ((logpart.shift3fracs1, # ticks
474 logpart.shiftfracs1), # subticks
475 None), # labels like ticks
477 ((logpart.shift4fracs1, # ticks
478 logpart.shiftfracs1), # subticks
479 None), # labels like ticks
481 ((logpart.shift5fracs1, # ticks
482 logpart.shiftfracs1), # subticks
483 None)) # labels like ticks
485 def __init__(self, list=defaultlist, extendtick=0, extendlabel=None, epsilon=1e-10, mix=()):
486 self.multipart = 1
487 self.list = list
488 if len(list) > 2:
489 self.listindex = divmod(len(list), 2)[0]
490 else:
491 self.listindex = 0
492 self.extendtick = extendtick
493 self.extendlabel = extendlabel
494 self.epsilon = epsilon
495 self.mix = mix
497 def defaultpart(self, min, max, extendmin, extendmax):
498 self.min, self.max, self.extendmin, self.extendmax = min, max, extendmin, extendmax
499 self.morelistindex = self.listindex
500 self.lesslistindex = self.listindex
501 part = logpart(ticks=self.list[self.listindex][0], labels=self.list[self.listindex][1],
502 extendtick=self.extendtick, extendlabel=self.extendlabel, epsilon=self.epsilon, mix=self.mix)
503 return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
505 def lesspart(self):
506 self.lesslistindex += 1
507 if self.lesslistindex < len(self.list):
508 part = logpart(ticks=self.list[self.lesslistindex][0], labels=self.list[self.lesslistindex][1],
509 extendtick=self.extendtick, extendlabel=self.extendlabel, epsilon=self.epsilon, mix=self.mix)
510 return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
511 return None
513 def morepart(self):
514 self.morelistindex -= 1
515 if self.morelistindex >= 0:
516 part = logpart(ticks=self.list[self.morelistindex][0], labels=self.list[self.morelistindex][1],
517 extendtick=self.extendtick, extendlabel=self.extendlabel, epsilon=self.epsilon, mix=self.mix)
518 return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
519 return None
523 ################################################################################
524 # rate partitions
525 ################################################################################
528 class cuberate:
530 def __init__(self, opt, left=None, right=None, weight=1):
531 if left is None:
532 left = 0
533 if right is None:
534 right = 3*opt
535 self.opt = opt
536 self.left = left
537 self.right = right
538 self.weight = weight
540 def rate(self, value, dense=1):
541 opt = self.opt * dense
542 if value < opt:
543 other = self.left * dense
544 elif value > opt:
545 other = self.right * dense
546 else:
547 return 0
548 factor = (value - opt) / float(other - opt)
549 return self.weight * (factor ** 3)
552 class distancerate:
554 def __init__(self, opt, weight=0.1):
555 self.opt_str = opt
556 self.weight = weight
558 def _rate(self, distances, dense=1):
559 if len(distances):
560 opt = unit.topt(unit.length(self.opt_str, default_type="v")) / dense
561 rate = 0
562 for distance in distances:
563 if distance < opt:
564 rate += self.weight * (opt / distance - 1)
565 else:
566 rate += self.weight * (distance / opt - 1)
567 return rate / float(len(distances))
570 class axisrater:
572 linticks = (cuberate(4), cuberate(10, weight=0.5), )
573 linlabels = (cuberate(4), )
574 logticks = (cuberate(5, right=20), cuberate(20, right=100, weight=0.5), )
575 loglabels = (cuberate(5, right=20), cuberate(5, left=-20, right=20, weight=0.5), )
576 stdtickrange = cuberate(1, weight=2)
577 stddistance = distancerate("1 cm")
579 def __init__(self, ticks=linticks, labels=linlabels, tickrange=stdtickrange, distance=stddistance):
580 self.ticks = ticks
581 self.labels = labels
582 self.tickrange = tickrange
583 self.distance = distance
585 def ratepart(self, axis, part, dense=1):
586 tickslen = len(self.ticks)
587 labelslen = len(self.labels)
588 ticks = [0]*tickslen
589 labels = [0]*labelslen
590 if part is not None:
591 for tick in part:
592 if tick.ticklevel is not None:
593 for level in xrange(tick.ticklevel, tickslen):
594 ticks[level] += 1
595 if tick.labellevel is not None:
596 for level in xrange(tick.labellevel, labelslen):
597 labels[level] += 1
598 rate = 0
599 weight = 0
600 for tick, rater in zip(ticks, self.ticks):
601 rate += rater.rate(tick, dense=dense)
602 weight += rater.weight
603 for label, rater in zip(labels, self.labels):
604 rate += rater.rate(label, dense=dense)
605 weight += rater.weight
606 if part is not None and len(part):
607 tickmin, tickmax = axis.gettickrange() # tickrange was not yet applied!?
608 rate += self.tickrange.rate((float(part[-1]) - float(part[0])) * axis.divisor / (tickmax - tickmin))
609 else:
610 rate += self.tickrange.rate(0)
611 weight += self.tickrange.weight
612 return rate/weight
614 def _ratedistances(self, distances, dense=1):
615 return self.distance._rate(distances, dense=dense)
618 ################################################################################
619 # box alignment, connections, distances ...
620 # (we may create a box drawing module and move all this stuff there)
621 ################################################################################
624 class textbox(box._rectbox, attrlist.attrlist):
626 def __init__(self, _tex, text, textattrs = (), vtext="0"):
627 self.tex = _tex
628 self.text = text
629 self.textattrs = textattrs
630 self.reldx, self.reldy = 1, 0
631 self.halign = self.attrget(self.textattrs, tex.halign, None)
632 self.textattrs = self.attrdel(self.textattrs, tex.halign)
633 self.direction = self.attrget(self.textattrs, tex.direction, None)
634 hwdtextattrs = self.attrdel(self.textattrs, tex.direction)
635 self.ht = unit.topt(self.tex.textht(text, *hwdtextattrs))
636 self.wd = unit.topt(self.tex.textwd(text, *hwdtextattrs))
637 self.dp = unit.topt(self.tex.textdp(text, *hwdtextattrs))
638 self.shiftht = 0.5 * unit.topt(self.tex.textht(vtext, *hwdtextattrs))
639 self.manualextents()
641 def manualextents(self, ht = None, wd = None, dp = None, shiftht = None):
642 if ht is not None: self.ht = ht
643 if wd is not None: self.wd = wd
644 if dp is not None: self.dp = dp
645 if shiftht is not None: self.shiftht = None
646 self.xtext = 0
647 self.ytext = 0
648 xorigin = 0.5 * self.wd
649 if self.halign is not None:
650 # centered by default!
651 if self.halign is tex.halign.left:
652 xorigin = 0
653 if self.halign is tex.halign.right:
654 xorigin = self.wd
655 box._rectbox.__init__(self, 0, -self.dp, self.wd, self.dp + self.ht, abscenter=(xorigin, self.shiftht))
656 if self.direction is not None:
657 self.transform(trafo._rotate(self.direction.value))
659 def transform(self, trafo):
660 box._rectbox.transform(self, trafo)
661 self.xtext, self.ytext = trafo._apply(self.xtext, self.ytext)
663 def printtext(self):
664 self.tex._text(self.xtext, self.ytext, self.text, *self.textattrs)
668 ################################################################################
669 # axis painter
670 ################################################################################
673 class axispainter(attrlist.attrlist):
675 defaultticklengths = ["%0.5f cm" % (0.2*goldenrule**(-i)) for i in range(10)]
677 paralleltext = -90
678 orthogonaltext = 0
680 fractypeauto = 1
681 fractyperat = 2
682 fractypedec = 3
683 fractypeexp = 4
685 def __init__(self, innerticklengths=defaultticklengths,
686 outerticklengths=None,
687 tickattrs=(),
688 gridattrs=None,
689 zerolineattrs=(),
690 baselineattrs=canvas.linecap.square,
691 labeldist="0.3 cm",
692 labelattrs=((), tex.fontsize.footnotesize),
693 labeldirection=None,
694 labelhequalize=0,
695 labelvequalize=1,
696 titledist="0.3 cm",
697 titleattrs=(),
698 titledirection=-90,
699 titlepos=0.5,
700 fractype=fractypeauto,
701 ratfracsuffixenum=1,
702 ratfracover=r"\over",
703 decfracpoint=".",
704 expfractimes=r"\cdot",
705 expfracpre1=0,
706 expfracminexp=4,
707 suffix0=0,
708 suffix1=0):
709 self.innerticklengths_str = innerticklengths
710 self.outerticklengths_str = outerticklengths
711 self.tickattrs = tickattrs
712 self.gridattrs = gridattrs
713 self.zerolineattrs = zerolineattrs
714 self.baselineattrs = baselineattrs
715 self.labeldist_str = labeldist
716 self.labelattrs = labelattrs
717 self.labeldirection = labeldirection
718 self.labelhequalize = labelhequalize
719 self.labelvequalize = labelvequalize
720 self.titledist_str = titledist
721 self.titleattrs = titleattrs
722 self.titledirection = titledirection
723 self.titlepos = titlepos
724 self.fractype = fractype
725 self.ratfracsuffixenum = ratfracsuffixenum
726 self.ratfracover = ratfracover
727 self.decfracpoint = decfracpoint
728 self.expfractimes = expfractimes
729 self.expfracpre1 = expfracpre1
730 self.expfracminexp = expfracminexp
731 self.suffix0 = suffix0
732 self.suffix1 = suffix1
734 def reldirection(self, direction, dx, dy, epsilon=1e-10):
735 direction += math.atan2(dy, dx) * 180 / math.pi
736 while (direction > 90 + epsilon):
737 direction -= 180
738 while (direction < -90 - epsilon):
739 direction += 180
740 return direction
742 def gcd(self, m, n):
743 # greates common divisor, m & n must be non-negative
744 if m < n:
745 m, n = n, m
746 while n > 0:
747 m, (dummy, n) = n, divmod(m, n)
748 return m
750 def attachsuffix(self, tick, str):
751 if self.suffix0 or tick.enum:
752 if tick.suffix is not None and not self.suffix1:
753 if str == "1":
754 str = ""
755 elif str == "-1":
756 str = "-"
757 if tick.suffix is not None:
758 str = str + tick.suffix
759 return str
761 def ratfrac(self, tick):
762 m, n = tick.enum, tick.denom
763 sign = 1
764 if m < 0: m, sign = -m, -sign
765 if n < 0: n, sign = -n, -sign
766 gcd = self.gcd(m, n)
767 (m, dummy1), (n, dummy2) = divmod(m, gcd), divmod(n, gcd)
768 if n != 1:
769 if self.ratfracsuffixenum:
770 if sign == -1:
771 return "-{{%s}%s{%s}}" % (self.attachsuffix(tick, str(m)), self.ratfracover, n)
772 else:
773 return "{{%s}%s{%s}}" % (self.attachsuffix(tick, str(m)), self.ratfracover, n)
774 else:
775 if sign == -1:
776 return self.attachsuffix(tick, "-{{%s}%s{%s}}" % (m, self.ratfracover, n))
777 else:
778 return self.attachsuffix(tick, "{{%s}%s{%s}}" % (m, self.ratfracover, n))
779 else:
780 if sign == -1:
781 return self.attachsuffix(tick, "-%s" % m)
782 else:
783 return self.attachsuffix(tick, "%s" % m)
785 def decfrac(self, tick):
786 m, n = tick.enum, tick.denom
787 sign = 1
788 if m < 0: m, sign = -m, -sign
789 if n < 0: n, sign = -n, -sign
790 gcd = self.gcd(m, n)
791 (m, dummy1), (n, dummy2) = divmod(m, gcd), divmod(n, gcd)
792 frac, rest = divmod(m, n)
793 strfrac = str(frac)
794 rest = m % n
795 if rest:
796 strfrac += self.decfracpoint
797 oldrest = []
798 while (rest):
799 if rest in oldrest:
800 periodstart = len(strfrac) - (len(oldrest) - oldrest.index(rest))
801 strfrac = strfrac[:periodstart] + r"\overline{" + strfrac[periodstart:] + "}"
802 break
803 oldrest += [rest]
804 rest *= 10
805 frac, rest = divmod(rest, n)
806 strfrac += str(frac)
807 if sign == -1:
808 return self.attachsuffix(tick, "-%s" % strfrac)
809 else:
810 return self.attachsuffix(tick, strfrac)
812 def expfrac(self, tick, minexp = None):
813 m, n = tick.enum, tick.denom
814 sign = 1
815 if m < 0: m, sign = -m, -sign
816 if n < 0: n, sign = -n, -sign
817 exp = 0
818 if m:
819 while divmod(m, n)[0] > 9:
820 n *= 10
821 exp += 1
822 while divmod(m, n)[0] < 1:
823 m *= 10
824 exp -= 1
825 if minexp is not None and ((exp < 0 and -exp < minexp) or (exp >= 0 and exp < minexp)):
826 return None
827 dummy = frac(m, n)
828 dummy.suffix = None
829 prefactor = self.decfrac(dummy)
830 if prefactor == "1" and not self.expfracpre1:
831 if sign == -1:
832 return self.attachsuffix(tick, "-10^{%i}" % exp)
833 else:
834 return self.attachsuffix(tick, "10^{%i}" % exp)
835 else:
836 if sign == -1:
837 return self.attachsuffix(tick, "-%s%s10^{%i}" % (prefactor, self.expfractimes, exp))
838 else:
839 return self.attachsuffix(tick, "%s%s10^{%i}" % (prefactor, self.expfractimes, exp))
841 def createtext(self, tick):
842 if self.fractype == axispainter.fractypeauto:
843 if tick.suffix is not None:
844 tick.text = self.ratfrac(tick)
845 else:
846 tick.text = self.expfrac(tick, self.expfracminexp)
847 if tick.text is None:
848 tick.text = self.decfrac(tick)
849 elif self.fractype == axispainter.fractypedec:
850 tick.text = self.decfrac(tick)
851 elif self.fractype == axispainter.fractypeexp:
852 tick.text = self.expfrac(tick)
853 elif self.fractype == axispainter.fractyperat:
854 tick.text = self.ratfrac(tick)
855 else:
856 raise ValueError("fractype invalid")
857 if not self.attrcount(tick.labelattrs, tex.style):
858 tick.labelattrs += [tex.style.math]
860 def dolayout(self, graph, axis):
861 labeldist = unit.topt(unit.length(self.labeldist_str, default_type="v"))
862 for tick in axis.ticks:
863 tick.virtual = axis.convert(float(tick) * axis.divisor)
864 tick.x, tick.y = axis._vtickpoint(axis, tick.virtual)
865 tick.dx, tick.dy = axis.vtickdirection(axis, tick.virtual)
866 for tick in axis.ticks:
867 if tick.labellevel is not None:
868 tick.labelattrs = helper._getsequenceno(self.labelattrs, tick.labellevel)
869 if tick.labelattrs is not None:
870 tick.labelattrs = list(helper._ensuresequence(tick.labelattrs))
871 if tick.text is None:
872 tick.suffix = axis.suffix
873 self.createtext(tick)
874 if self.labeldirection is not None and not self.attrcount(tick.labelattrs, tex.direction):
875 tick.labelattrs += [tex.direction(self.reldirection(self.labeldirection, tick.dx, tick.dy))]
876 tick.textbox = textbox(graph.tex, tick.text, textattrs=tick.labelattrs)
877 else:
878 tick.textbox = None
879 else:
880 tick.textbox = None
881 for tick in axis.ticks[1:]:
882 if tick.dx != axis.ticks[0].dx or tick.dy != axis.ticks[0].dy:
883 equaldirection = 0
884 break
885 else:
886 equaldirection = 1
887 if equaldirection:
888 maxht, maxwd, maxdp = 0, 0, 0
889 for tick in axis.ticks:
890 if tick.textbox is not None:
891 if maxht < tick.textbox.ht: maxht = tick.textbox.ht
892 if maxwd < tick.textbox.wd: maxwd = tick.textbox.wd
893 if maxdp < tick.textbox.dp: maxdp = tick.textbox.dp
894 for tick in axis.ticks:
895 if tick.textbox is not None:
896 if self.labelhequalize:
897 tick.textbox.manualextents(wd = maxwd)
898 if self.labelvequalize:
899 tick.textbox.manualextents(ht = maxht, dp = maxdp)
900 for tick in axis.ticks:
901 if tick.textbox is not None:
902 tick.textbox._linealign(labeldist, tick.dx, tick.dy)
903 tick._extent = tick.textbox._extent(tick.dx, tick.dy) + labeldist
904 tick.textbox.transform(trafo._translate(tick.x, tick.y))
905 def topt_v_recursive(arg):
906 if helper._issequence(arg):
907 # return map(topt_v_recursive, arg) needs python2.2
908 return [unit.topt(unit.length(a, default_type="v")) for a in arg]
909 else:
910 if arg is not None:
911 return unit.topt(unit.length(arg, default_type="v"))
912 innerticklengths = topt_v_recursive(self.innerticklengths_str)
913 outerticklengths = topt_v_recursive(self.outerticklengths_str)
914 titledist = unit.topt(unit.length(self.titledist_str, default_type="v"))
915 axis._extent = 0
916 for tick in axis.ticks:
917 if tick.ticklevel is not None:
918 tick.innerticklength = helper._getitemno(innerticklengths, tick.ticklevel)
919 tick.outerticklength = helper._getitemno(outerticklengths, tick.ticklevel)
920 if tick.innerticklength is not None and tick.outerticklength is None:
921 tick.outerticklength = 0
922 if tick.outerticklength is not None and tick.innerticklength is None:
923 tick.innerticklength = 0
924 if tick.textbox is None:
925 if tick.outerticklength is not None and tick.outerticklength > 0:
926 tick._extent = tick.outerticklength
927 else:
928 tick._extent = 0
929 if axis._extent < tick._extent:
930 axis._extent = tick._extent
932 if axis.title is not None and self.titleattrs is not None:
933 dx, dy = axis.vtickdirection(axis, self.titlepos)
934 # no not modify self.titleattrs ... the painter might be used by several axes!!!
935 titleattrs = list(helper._ensuresequence(self.titleattrs))
936 if self.titledirection is not None and not self.attrcount(titleattrs, tex.direction):
937 titleattrs = titleattrs + [tex.direction(self.reldirection(self.titledirection, dx, dy))]
938 axis.titlebox = textbox(graph.tex, axis.title, textattrs=titleattrs)
939 axis._extent += titledist
940 axis.titlebox._linealign(axis._extent, dx, dy)
941 axis.titlebox.transform(trafo._translate(*axis._vtickpoint(axis, self.titlepos)))
942 axis._extent += axis.titlebox._extent(dx, dy)
944 def ratelayout(self, graph, axis, dense=1):
945 ticktextboxes = [tick.textbox for tick in axis.ticks if tick.textbox is not None]
946 if len(ticktextboxes) > 1:
947 try:
948 distances = [ticktextboxes[i]._boxdistance(ticktextboxes[i+1]) for i in range(len(ticktextboxes) - 1)]
949 except box.BoxCrossError:
950 return None
951 rate = axis.rate._ratedistances(distances, dense)
952 return rate
954 def paint(self, graph, axis):
955 for tick in axis.ticks:
956 if tick.ticklevel is not None:
957 if tick != frac(0, 1) or self.zerolineattrs is None:
958 gridattrs = helper._getsequenceno(self.gridattrs, tick.ticklevel)
959 if gridattrs is not None:
960 graph.stroke(axis.vgridpath(tick.virtual), *helper._ensuresequence(gridattrs))
961 tickattrs = helper._getsequenceno(self.tickattrs, tick.ticklevel)
962 if None not in (tick.innerticklength, tick.outerticklength, tickattrs):
963 x1 = tick.x - tick.dx * tick.innerticklength
964 y1 = tick.y - tick.dy * tick.innerticklength
965 x2 = tick.x + tick.dx * tick.outerticklength
966 y2 = tick.y + tick.dy * tick.outerticklength
967 graph.stroke(path._line(x1, y1, x2, y2), *helper._ensuresequence(tickattrs))
968 if tick.textbox is not None:
969 tick.textbox.printtext()
970 if self.baselineattrs is not None:
971 graph.stroke(axis.vbaseline(axis), *helper._ensuresequence(self.baselineattrs))
972 if self.zerolineattrs is not None:
973 if len(axis.ticks) and axis.ticks[0] * axis.ticks[-1] < frac(0, 1):
974 graph.stroke(axis.vgridpath(axis.convert(0)), *helper._ensuresequence(self.zerolineattrs))
975 if axis.title is not None and self.titleattrs is not None:
976 axis.titlebox.printtext()
978 class splitaxispainter(attrlist.attrlist): # XXX: avoid code duplication with axispainter via inheritance
980 paralleltext = -90
981 orthogonaltext = 0
983 def __init__(self, breaklinesdist=0.05,
984 breaklineslength=0.5,
985 breaklinesangle=-60,
986 breaklinesattrs=(),
987 titledist="0.3 cm",
988 titleattrs=(),
989 titledirection=-90,
990 titlepos=0.5):
991 self.breaklinesdist_str = breaklinesdist
992 self.breaklineslength_str = breaklineslength
993 self.breaklinesangle = breaklinesangle
994 self.breaklinesattrs = breaklinesattrs
995 self.titledist_str = titledist
996 self.titleattrs = titleattrs
997 self.titledirection = titledirection
998 self.titlepos = titlepos
1000 def reldirection(self, direction, dx, dy, epsilon=1e-10):
1001 # XXX: direct code duplication from axispainter
1002 direction += math.atan2(dy, dx) * 180 / math.pi
1003 while (direction > 90 + epsilon):
1004 direction -= 180
1005 while (direction < -90 - epsilon):
1006 direction += 180
1007 return direction
1009 def subvbaseline(self, axis, v1=None, v2=None):
1010 if v1 is None:
1011 if self.breaklinesattrs is None:
1012 left = axis.vmin
1013 else:
1014 if axis.vminover is None:
1015 left = None
1016 else:
1017 left = axis.vminover
1018 else:
1019 left = axis.vmin+v1*(axis.vmax-axis.vmin)
1020 if v2 is None:
1021 if self.breaklinesattrs is None:
1022 right = axis.vmax
1023 else:
1024 if axis.vmaxover is None:
1025 right = None
1026 else:
1027 right = axis.vmaxover
1028 else:
1029 right = axis.vmin+v2*(axis.vmax-axis.vmin)
1030 return axis.baseaxis.vbaseline(axis.baseaxis, left, right)
1032 def dolayout(self, graph, axis):
1033 if self.breaklinesattrs is not None:
1034 self.breaklinesdist = unit.length(self.breaklinesdist_str, default_type="v")
1035 self.breaklineslength = unit.length(self.breaklineslength_str, default_type="v")
1036 self._breaklinesdist = unit.topt(self.breaklinesdist)
1037 self._breaklineslength = unit.topt(self.breaklineslength)
1038 self.sin = math.sin(self.breaklinesangle*math.pi/180.0)
1039 self.cos = math.cos(self.breaklinesangle*math.pi/180.0)
1040 axis._extent = (math.fabs(0.5 * self._breaklinesdist * self.cos) +
1041 math.fabs(0.5 * self._breaklineslength * self.sin))
1042 else:
1043 axis._extent = 0
1044 for subaxis in axis.axislist:
1045 subaxis.baseaxis = axis
1046 subaxis._vtickpoint = lambda axis, v: axis.baseaxis._vtickpoint(axis.baseaxis, axis.vmin+v*(axis.vmax-axis.vmin))
1047 subaxis.vtickdirection = lambda axis, v: axis.baseaxis.vtickdirection(axis.baseaxis, axis.vmin+v*(axis.vmax-axis.vmin))
1048 subaxis.vbaseline = self.subvbaseline
1049 subaxis.dolayout(graph)
1050 if axis._extent < subaxis._extent:
1051 axis._extent = subaxis._extent
1052 titledist = unit.topt(unit.length(self.titledist_str, default_type="v"))
1053 if axis.title is not None and self.titleattrs is not None:
1054 dx, dy = axis.vtickdirection(axis, self.titlepos)
1055 titleattrs = list(helper._ensuresequence(self.titleattrs))
1056 if self.titledirection is not None and not self.attrcount(titleattrs, tex.direction):
1057 titleattrs = titleattrs + [tex.direction(self.reldirection(self.titledirection, dx, dy))]
1058 axis.titlebox = textbox(graph.tex, axis.title, textattrs=titleattrs)
1059 axis._extent += titledist
1060 axis.titlebox._linealign(axis._extent, dx, dy)
1061 axis.titlebox.transform(trafo._translate(*axis._vtickpoint(axis, self.titlepos)))
1062 axis._extent += axis.titlebox._extent(dx, dy)
1064 def paint(self, graph, axis):
1065 for subaxis in axis.axislist:
1066 subaxis.dopaint(graph)
1067 if self.breaklinesattrs is not None:
1068 for subaxis1, subaxis2 in zip(axis.axislist[:-1], axis.axislist[1:]):
1069 # use a tangent of the baseline (this is independend of the tickdirection)
1070 v = 0.5 * (subaxis1.vmax + subaxis2.vmin)
1071 breakline = path.normpath(axis.vbaseline(axis, v, None)).tangent(0, self.breaklineslength)
1072 widthline = path.normpath(axis.vbaseline(axis, v, None)).tangent(0, self.breaklinesdist).transformed(trafo.rotate(self.breaklinesangle+90, *breakline.begin()))
1073 tocenter = map(lambda x: 0.5*(x[0]-x[1]), zip(breakline.begin(), breakline.end()))
1074 towidth = map(lambda x: 0.5*(x[0]-x[1]), zip(widthline.begin(), widthline.end()))
1075 breakline = breakline.transformed(trafo.translate(*tocenter).rotated(self.breaklinesangle, *breakline.begin()))
1076 breakline1 = breakline.transformed(trafo.translate(*towidth))
1077 breakline2 = breakline.transformed(trafo.translate(-towidth[0], -towidth[1]))
1078 graph.fill(path.path(path.moveto(*breakline1.begin()),
1079 path.lineto(*breakline1.end()),
1080 path.lineto(*breakline2.end()),
1081 path.lineto(*breakline2.begin()),
1082 path.closepath()), color.gray.white)
1083 graph.stroke(breakline1, *helper._ensuresequence(self.breaklinesattrs))
1084 graph.stroke(breakline2, *helper._ensuresequence(self.breaklinesattrs))
1085 if axis.title is not None and self.titleattrs is not None:
1086 axis.titlebox.printtext()
1089 class baraxispainter(attrlist.attrlist): # XXX: avoid code duplication with axispainter via inheritance
1091 paralleltext = -90
1092 orthogonaltext = 0
1094 def __init__(self, innerticklength=None,
1095 outerticklength=None,
1096 tickattrs=(),
1097 baselineattrs=canvas.linecap.square,
1098 namedist="0.3 cm",
1099 nameattrs=(),
1100 namedirection=None,
1101 namepos=0.5,
1102 namehequalize=0,
1103 namevequalize=1,
1104 titledist="0.3 cm",
1105 titleattrs=(),
1106 titledirection=-90,
1107 titlepos=0.5):
1108 self.innerticklength_str = innerticklength
1109 self.outerticklength_str = outerticklength
1110 self.tickattrs = tickattrs
1111 self.baselineattrs = baselineattrs
1112 self.namedist_str = namedist
1113 self.nameattrs = nameattrs
1114 self.namedirection = namedirection
1115 self.namepos = namepos
1116 self.namehequalize = namehequalize
1117 self.namevequalize = namevequalize
1118 self.titledist_str = titledist
1119 self.titleattrs = titleattrs
1120 self.titledirection = titledirection
1121 self.titlepos = titlepos
1123 def reldirection(self, direction, dx, dy, epsilon=1e-10):
1124 # XXX: direct code duplication from axispainter
1125 direction += math.atan2(dy, dx) * 180 / math.pi
1126 while (direction > 90 + epsilon):
1127 direction -= 180
1128 while (direction < -90 - epsilon):
1129 direction += 180
1130 return direction
1132 def dolayout(self, graph, axis):
1133 axis._extent = 0
1134 if axis.multisubaxis:
1135 for name, subaxis in zip(axis.names, axis.subaxis):
1136 subaxis.vmin = axis.convert((name, 0))
1137 subaxis.vmax = axis.convert((name, 1))
1138 subaxis.baseaxis = axis
1139 subaxis._vtickpoint = lambda axis, v: axis.baseaxis._vtickpoint(axis.baseaxis, axis.vmin+v*(axis.vmax-axis.vmin))
1140 subaxis.vtickdirection = lambda axis, v: axis.baseaxis.vtickdirection(axis.baseaxis, axis.vmin+v*(axis.vmax-axis.vmin))
1141 subaxis.vbaseline = None
1142 subaxis.dolayout(graph)
1143 if axis._extent < subaxis._extent:
1144 axis._extent = subaxis._extent
1145 equaldirection = 1
1146 axis.namepos = []
1147 for name in axis.names:
1148 v = axis.convert((name, self.namepos))
1149 x, y = axis._vtickpoint(axis, v)
1150 dx, dy = axis.vtickdirection(axis, v)
1151 axis.namepos.append((v, x, y, dx, dy))
1152 if equaldirection and (dx != axis.namepos[0][3] or dy != axis.namepos[0][4]):
1153 equaldirection = 0
1154 axis.nameboxes = []
1155 for (v, x, y, dx, dy), name in zip(axis.namepos, axis.names):
1156 nameattrs = list(helper._ensuresequence(self.nameattrs))
1157 if self.namedirection is not None and not self.attrcount(nameattrs, tex.direction):
1158 nameattrs += [tex.direction(self.reldirection(self.namedirection, dx, dy))]
1159 if axis.texts.has_key(name):
1160 axis.nameboxes.append(textbox(graph.tex, str(axis.texts[name]), textattrs=nameattrs))
1161 elif axis.texts.has_key(str(name)):
1162 axis.nameboxes.append(textbox(graph.tex, str(axis.texts[str(name)]), textattrs=nameattrs))
1163 else:
1164 axis.nameboxes.append(textbox(graph.tex, str(name), textattrs=nameattrs))
1165 if equaldirection:
1166 maxht, maxwd, maxdp = 0, 0, 0
1167 for namebox in axis.nameboxes:
1168 if maxht < namebox.ht: maxht = namebox.ht
1169 if maxwd < namebox.wd: maxwd = namebox.wd
1170 if maxdp < namebox.dp: maxdp = namebox.dp
1171 for namebox in axis.nameboxes:
1172 if self.namehequalize:
1173 namebox.manualextents(wd = maxwd)
1174 if self.namevequalize:
1175 namebox.manualextents(ht = maxht, dp = maxdp)
1176 labeldist = axis._extent + unit.topt(unit.length(self.namedist_str, default_type="v"))
1177 if self.innerticklength_str is not None:
1178 axis.innerticklength = unit.topt(unit.length(self.innerticklength_str, default_type="v"))
1179 else:
1180 if self.outerticklength_str is not None:
1181 axis.innerticklength = 0
1182 else:
1183 axis.innerticklength = None
1184 if self.outerticklength_str is not None:
1185 axis.outerticklength = unit.topt(unit.length(self.outerticklength_str, default_type="v"))
1186 else:
1187 if self.innerticklength_str is not None:
1188 axis.outerticklength = 0
1189 else:
1190 axis.outerticklength = None
1191 if axis.outerticklength is not None and self.tickattrs is not None:
1192 axis._extent += axis.outerticklength
1193 for (v, x, y, dx, dy), namebox in zip(axis.namepos, axis.nameboxes):
1194 namebox._linealign(labeldist, dx, dy)
1195 namebox.transform(trafo._translate(x, y))
1196 newextent = namebox._extent(dx, dy) + labeldist
1197 if axis._extent < newextent:
1198 axis._extent = newextent
1199 titledist = unit.topt(unit.length(self.titledist_str, default_type="v"))
1200 if axis.title is not None and self.titleattrs is not None:
1201 dx, dy = axis.vtickdirection(axis, self.titlepos)
1202 titleattrs = list(helper._ensuresequence(self.titleattrs))
1203 if self.titledirection is not None and not self.attrcount(titleattrs, tex.direction):
1204 titleattrs = titleattrs + [tex.direction(self.reldirection(self.titledirection, dx, dy))]
1205 axis.titlebox = textbox(graph.tex, axis.title, textattrs=titleattrs)
1206 axis._extent += titledist
1207 axis.titlebox._linealign(axis._extent, dx, dy)
1208 axis.titlebox.transform(trafo._translate(*axis._vtickpoint(axis, self.titlepos)))
1209 axis._extent += axis.titlebox._extent(dx, dy)
1211 def paint(self, graph, axis):
1212 if axis.subaxis is not None:
1213 if axis.multisubaxis:
1214 for subaxis in axis.subaxis:
1215 subaxis.dopaint(graph)
1216 if None not in (self.tickattrs, axis.innerticklength, axis.outerticklength):
1217 for pos in axis.relsizes:
1218 if pos == axis.relsizes[0]:
1219 pos -= axis.firstdist
1220 elif pos != axis.relsizes[-1]:
1221 pos -= 0.5 * axis.dist
1222 v = pos / axis.relsizes[-1]
1223 x, y = axis._vtickpoint(axis, v)
1224 dx, dy = axis.vtickdirection(axis, v)
1225 x1 = x - dx * axis.innerticklength
1226 y1 = y - dy * axis.innerticklength
1227 x2 = x + dx * axis.outerticklength
1228 y2 = y + dy * axis.outerticklength
1229 graph.stroke(path._line(x1, y1, x2, y2), *helper._ensuresequence(self.tickattrs))
1230 if self.baselineattrs is not None:
1231 if axis.vbaseline is not None: # XXX: subbaselines (as for splitlines)
1232 graph.stroke(axis.vbaseline(axis), *helper._ensuresequence(self.baselineattrs))
1233 for namebox in axis.nameboxes:
1234 namebox.printtext()
1235 if axis.title is not None and self.titleattrs is not None:
1236 axis.titlebox.printtext()
1240 ################################################################################
1241 # axes
1242 ################################################################################
1244 class PartitionError(Exception): pass
1246 class _axis:
1248 def __init__(self, min=None, max=None, reverse=0, divisor=1,
1249 datavmin=None, datavmax=None, tickvmin=0, tickvmax=1,
1250 title=None, suffix=None, painter=axispainter(), dense=None):
1251 if None not in (min, max) and min > max:
1252 min, max, reverse = max, min, not reverse
1253 self.fixmin, self.fixmax, self.min, self.max, self.reverse = min is not None, max is not None, min, max, reverse
1255 self.datamin = self.datamax = self.tickmin = self.tickmax = None
1256 if datavmin is None:
1257 if self.fixmin:
1258 self.datavmin = 0
1259 else:
1260 self.datavmin = 0.05
1261 else:
1262 self.datavmin = datavmin
1263 if datavmax is None:
1264 if self.fixmax:
1265 self.datavmax = 1
1266 else:
1267 self.datavmax = 0.95
1268 else:
1269 self.datavmax = datavmax
1270 self.tickvmin = tickvmin
1271 self.tickvmax = tickvmax
1273 self.divisor = divisor
1274 self.title = title
1275 self.suffix = suffix
1276 self.painter = painter
1277 self.dense = dense
1278 self.canconvert = 0
1279 self.__setinternalrange()
1281 def __setinternalrange(self, min=None, max=None):
1282 if not self.fixmin and min is not None and (self.min is None or min < self.min):
1283 self.min = min
1284 if not self.fixmax and max is not None and (self.max is None or max > self.max):
1285 self.max = max
1286 if None not in (self.min, self.max):
1287 min, max, vmin, vmax = self.min, self.max, 0, 1
1288 self.canconvert = 1
1289 self.setbasepoints(((min, vmin), (max, vmax)))
1290 if not self.fixmin:
1291 if self.datamin is not None and self.convert(self.datamin) < self.datavmin:
1292 min, vmin = self.datamin, self.datavmin
1293 self.setbasepoints(((min, vmin), (max, vmax)))
1294 if self.tickmin is not None and self.convert(self.tickmin) < self.tickvmin:
1295 min, vmin = self.tickmin, self.tickvmin
1296 self.setbasepoints(((min, vmin), (max, vmax)))
1297 if not self.fixmax:
1298 if self.datamax is not None and self.convert(self.datamax) > self.datavmax:
1299 max, vmax = self.datamax, self.datavmax
1300 self.setbasepoints(((min, vmin), (max, vmax)))
1301 if self.tickmax is not None and self.convert(self.tickmax) > self.tickvmax:
1302 max, vmax = self.tickmax, self.tickvmax
1303 self.setbasepoints(((min, vmin), (max, vmax)))
1304 if self.reverse:
1305 self.setbasepoints(((min, vmax), (max, vmin)))
1307 def __getinternalrange(self):
1308 return self.min, self.max, self.datamin, self.datamax, self.tickmin, self.tickmax
1310 def __forceinternalrange(self, range):
1311 self.min, self.max, self.datamin, self.datamax, self.tickmin, self.tickmax = range
1312 self.__setinternalrange()
1314 def setdatarange(self, min, max):
1315 self.datamin, self.datamax = min, max
1316 self.__setinternalrange(min, max)
1318 def settickrange(self, min, max):
1319 self.tickmin, self.tickmax = min, max
1320 self.__setinternalrange(min, max)
1322 def getdatarange(self):
1323 if self.canconvert:
1324 if self.reverse:
1325 return self.invert(1-self.datavmin), self.invert(1-self.datavmax)
1326 else:
1327 return self.invert(self.datavmin), self.invert(self.datavmax)
1329 def gettickrange(self):
1330 if self.canconvert:
1331 if self.reverse:
1332 return self.invert(1-self.tickvmin), self.invert(1-self.tickvmax)
1333 else:
1334 return self.invert(self.tickvmin), self.invert(self.tickvmax)
1336 def dolayout(self, graph):
1337 if self.dense is not None:
1338 dense = self.dense
1339 else:
1340 dense = graph.dense
1341 min, max = self.gettickrange()
1342 self.ticks = self.part.defaultpart(min/self.divisor, max/self.divisor, not self.fixmin, not self.fixmax)
1343 if self.part.multipart:
1344 # lesspart and morepart can be called after defaultpart,
1345 # although some axes may share their autoparting ---
1346 # it works, because the axes are processed sequentially
1347 bestrate = self.rate.ratepart(self, self.ticks, dense)
1348 variants = [[bestrate, self.ticks]]
1349 maxworse = 2
1350 worse = 0
1351 while worse < maxworse:
1352 newticks = self.part.lesspart()
1353 if newticks is not None:
1354 newrate = self.rate.ratepart(self, newticks, dense)
1355 variants.append([newrate, newticks])
1356 if newrate < bestrate:
1357 bestrate = newrate
1358 worse = 0
1359 else:
1360 worse += 1
1361 else:
1362 worse += 1
1363 worse = 0
1364 while worse < maxworse:
1365 newticks = self.part.morepart()
1366 if newticks is not None:
1367 newrate = self.rate.ratepart(self, newticks, dense)
1368 variants.append([newrate, newticks])
1369 if newrate < bestrate:
1370 bestrate = newrate
1371 worse = 0
1372 else:
1373 worse += 1
1374 else:
1375 worse += 1
1376 variants.sort()
1377 i = 0
1378 bestrate = None
1379 while i < len(variants) and (bestrate is None or variants[i][0] < bestrate):
1380 saverange = self.__getinternalrange()
1381 self.ticks = variants[i][1]
1382 if len(self.ticks):
1383 self.settickrange(float(self.ticks[0])*self.divisor, float(self.ticks[-1])*self.divisor)
1384 self.painter.dolayout(graph, self)
1385 ratelayout = self.painter.ratelayout(graph, self, dense)
1386 if ratelayout is not None:
1387 variants[i][0] += ratelayout
1388 else:
1389 variants[i][0] = None
1390 if variants[i][0] is not None and (bestrate is None or variants[i][0] < bestrate):
1391 bestrate = variants[i][0]
1392 self.__forceinternalrange(saverange)
1393 i += 1
1394 if bestrate is None:
1395 raise PartitionError("no valid axis partitioning found")
1396 variants = [variant for variant in variants[:i] if variant[0] is not None]
1397 variants.sort()
1398 self.ticks = variants[0][1]
1399 if len(self.ticks):
1400 self.settickrange(float(self.ticks[0])*self.divisor, float(self.ticks[-1])*self.divisor)
1401 else:
1402 if len(self.ticks):
1403 self.settickrange(float(self.ticks[0])*self.divisor, float(self.ticks[-1])*self.divisor)
1404 self.painter.dolayout(graph, self)
1406 def dopaint(self, graph):
1407 self.painter.paint(graph, self)
1409 def createlinkaxis(self, **args):
1410 return linkaxis(self, **args)
1413 class linaxis(_axis, _linmap):
1415 def __init__(self, part=autolinpart(), rate=axisrater(), **args):
1416 _axis.__init__(self, **args)
1417 if self.fixmin and self.fixmax:
1418 self.relsize = self.max - self.min
1419 self.part = part
1420 self.rate = rate
1423 class logaxis(_axis, _logmap):
1425 def __init__(self, part=autologpart(), rate=axisrater(ticks=axisrater.logticks, labels=axisrater.loglabels), **args):
1426 _axis.__init__(self, **args)
1427 if self.fixmin and self.fixmax:
1428 self.relsize = math.log(self.max) - math.log(self.min)
1429 self.part = part
1430 self.rate = rate
1433 class linkaxis:
1435 def __init__(self, linkedaxis, title=None, skipticklevel=None, skiplabellevel=0, painter=axispainter(zerolineattrs=None)):
1436 self.linkedaxis = linkedaxis
1437 while isinstance(self.linkedaxis, linkaxis):
1438 self.linkedaxis = self.linkedaxis.linkedaxis
1439 self.fixmin = self.linkedaxis.fixmin
1440 self.fixmax = self.linkedaxis.fixmax
1441 if self.fixmin:
1442 self.min = self.linkedaxis.min
1443 if self.fixmax:
1444 self.max = self.linkedaxis.max
1445 self.skipticklevel = skipticklevel
1446 self.skiplabellevel = skiplabellevel
1447 self.title = title
1448 self.painter = painter
1450 def ticks(self, ticks):
1451 result = []
1452 for _tick in ticks:
1453 ticklevel = _tick.ticklevel
1454 labellevel = _tick.labellevel
1455 if self.skipticklevel is not None and ticklevel >= self.skipticklevel:
1456 ticklevel = None
1457 if self.skiplabellevel is not None and labellevel >= self.skiplabellevel:
1458 labellevel = None
1459 if ticklevel is not None or labellevel is not None:
1460 result.append(tick(_tick.enum, _tick.denom, ticklevel, labellevel))
1461 return result
1462 # XXX: don't forget to calculate new text positions as soon as this is moved
1463 # outside of the paint method (when rating is moved into the axispainter)
1465 def getdatarange(self):
1466 return self.linkedaxis.getdatarange()
1468 def setdatarange(self, min, max):
1469 prevrange = self.linkedaxis.getdatarange()
1470 self.linkedaxis.setdatarange(min, max)
1471 if hasattr(self.linkedaxis, "ticks") and prevrange != self.linkedaxis.getdatarange():
1472 raise RuntimeError("linkaxis datarange setting performed while linked axis layout already fixed")
1474 def dolayout(self, graph):
1475 self.ticks = self.ticks(self.linkedaxis.ticks)
1476 self.convert = self.linkedaxis.convert
1477 self.divisor = self.linkedaxis.divisor
1478 self.suffix = self.linkedaxis.suffix
1479 self.painter.dolayout(graph, self)
1481 def dopaint(self, graph):
1482 self.painter.paint(graph, self)
1484 def createlinkaxis(self, **args):
1485 return linkaxis(self.linkedaxis)
1488 class splitaxis:
1490 def __init__(self, axislist, splitlist=0.5, splitdist=0.1, relsizesplitdist=1, title=None, painter=splitaxispainter()):
1491 self.title = title
1492 self.axislist = axislist
1493 self.painter = painter
1494 self.splitlist = list(helper._ensuresequence(splitlist))
1495 self.splitlist.sort()
1496 if len(self.axislist) != len(self.splitlist) + 1:
1497 for subaxis in self.axislist:
1498 if not isinstance(subaxis, linkaxis):
1499 raise ValueError("axislist and splitlist lengths do not fit together")
1500 for subaxis in self.axislist:
1501 if isinstance(subaxis, linkaxis):
1502 subaxis.vmin = subaxis.linkedaxis.vmin
1503 subaxis.vminover = subaxis.linkedaxis.vminover
1504 subaxis.vmax = subaxis.linkedaxis.vmax
1505 subaxis.vmaxover = subaxis.linkedaxis.vmaxover
1506 else:
1507 subaxis.vmin = None
1508 subaxis.vmax = None
1509 self.axislist[0].vmin = 0
1510 self.axislist[0].vminover = None
1511 self.axislist[-1].vmax = 1
1512 self.axislist[-1].vmaxover = None
1513 for i in xrange(len(self.splitlist)):
1514 if self.splitlist[i] is not None:
1515 self.axislist[i].vmax = self.splitlist[i] - 0.5*splitdist
1516 self.axislist[i].vmaxover = self.splitlist[i]
1517 self.axislist[i+1].vmin = self.splitlist[i] + 0.5*splitdist
1518 self.axislist[i+1].vminover = self.splitlist[i]
1519 i = 0
1520 while i < len(self.axislist):
1521 if self.axislist[i].vmax is None:
1522 j = relsize = relsize2 = 0
1523 while self.axislist[i + j].vmax is None:
1524 relsize += self.axislist[i + j].relsize + relsizesplitdist
1525 j += 1
1526 relsize += self.axislist[i + j].relsize
1527 vleft = self.axislist[i].vmin
1528 vright = self.axislist[i + j].vmax
1529 for k in range(i, i + j):
1530 relsize2 += self.axislist[k].relsize
1531 self.axislist[k].vmax = vleft + (vright - vleft) * relsize2 / float(relsize)
1532 relsize2 += 0.5 * relsizesplitdist
1533 self.axislist[k].vmaxover = self.axislist[k + 1].vminover = vleft + (vright - vleft) * relsize2 / float(relsize)
1534 relsize2 += 0.5 * relsizesplitdist
1535 self.axislist[k+1].vmin = vleft + (vright - vleft) * relsize2 / float(relsize)
1536 if i == 0 and i + j + 1 == len(self.axislist):
1537 self.relsize = relsize
1538 i += j + 1
1539 else:
1540 i += 1
1542 self.fixmin = self.axislist[0].fixmin
1543 if self.fixmin:
1544 self.min = self.axislist[0].min
1545 self.fixmax = self.axislist[-1].fixmax
1546 if self.fixmax:
1547 self.max = self.axislist[-1].max
1548 self.divisor = 1
1549 self.suffix = ""
1551 def getdatarange(self):
1552 min = self.axislist[0].getdatarange()
1553 max = self.axislist[-1].getdatarange()
1554 try:
1555 return min[0], max[1]
1556 except TypeError:
1557 return None
1559 def setdatarange(self, min, max):
1560 self.axislist[0].setdatarange(min, None)
1561 self.axislist[-1].setdatarange(None, max)
1563 def gettickrange(self):
1564 min = self.axislist[0].gettickrange()
1565 max = self.axislist[-1].gettickrange()
1566 try:
1567 return min[0], max[1]
1568 except TypeError:
1569 return None
1571 def settickrange(self, min, max):
1572 self.axislist[0].settickrange(min, None)
1573 self.axislist[-1].settickrange(None, max)
1575 def convert(self, value):
1576 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
1577 if value < self.axislist[0].max:
1578 return self.axislist[0].vmin + self.axislist[0].convert(value)*(self.axislist[0].vmax-self.axislist[0].vmin)
1579 for axis in self.axislist[1:-1]:
1580 if value > axis.min and value < axis.max:
1581 return axis.vmin + axis.convert(value)*(axis.vmax-axis.vmin)
1582 if value > self.axislist[-1].min:
1583 return self.axislist[-1].vmin + self.axislist[-1].convert(value)*(self.axislist[-1].vmax-self.axislist[-1].vmin)
1584 raise ValueError("value couldn't be assigned to a split region")
1586 def dolayout(self, graph):
1587 self.painter.dolayout(graph, self)
1589 def dopaint(self, graph):
1590 self.painter.paint(graph, self)
1592 def createlinkaxis(self, painter=None, *args):
1593 if not len(args):
1594 return splitaxis([x.createlinkaxis() for x in self.axislist], splitlist=None)
1595 if len(args) != len(self.axislist):
1596 raise IndexError("length of the argument list doesn't fit to split number")
1597 if painter is None:
1598 painter = self.painter
1599 return splitaxis([x.createlinkaxis(**arg) for x, arg in zip(self.axislist, args)], painter=painter)
1602 class baraxis:
1604 def __init__(self, subaxis=None, multisubaxis=0, title=None, dist=0.5, firstdist=None, lastdist=None, names=None, texts={}, painter=baraxispainter()):
1605 self.dist = dist
1606 if firstdist is not None:
1607 self.firstdist = firstdist
1608 else:
1609 self.firstdist = 0.5 * dist
1610 if lastdist is not None:
1611 self.lastdist = lastdist
1612 else:
1613 self.lastdist = 0.5 * dist
1614 self.relsizes = None
1615 self.fixnames = 0
1616 self.names = []
1617 for name in helper._ensuresequence(names):
1618 self.setname(name)
1619 self.fixnames = names is not None
1620 self.multisubaxis = multisubaxis
1621 if self.multisubaxis:
1622 self.createsubaxis = subaxis
1623 self.subaxis = [self.createsubaxis.createsubaxis() for name in self.names]
1624 else:
1625 self.subaxis = subaxis
1626 self.title = title
1627 self.fixnames = 0
1628 self.texts = texts
1629 self.painter = painter
1631 def getdatarange(self):
1632 return None
1634 def setname(self, name, *subnames):
1635 # setting self.relsizes to None forces later recalculation
1636 if not self.fixnames:
1637 if name not in self.names:
1638 self.relsizes = None
1639 self.names.append(name)
1640 if self.multisubaxis:
1641 self.subaxis.append(self.createsubaxis.createsubaxis())
1642 if (not self.fixnames or name in self.names) and len(subnames):
1643 if self.multisubaxis:
1644 if self.subaxis[self.names.index(name)].setname(*subnames):
1645 self.relsizes = None
1646 else:
1647 if self.subaxis.setname(*subnames):
1648 self.relsizes = None
1649 return self.relsizes is not None
1651 def updaterelsizes(self):
1652 self.relsizes = [i*self.dist + self.firstdist for i in range(len(self.names) + 1)]
1653 self.relsizes[-1] += self.lastdist - self.dist
1654 if self.multisubaxis:
1655 subrelsize = 0
1656 for i in range(1, len(self.relsizes)):
1657 self.subaxis[i-1].updaterelsizes()
1658 subrelsize += self.subaxis[i-1].relsizes[-1]
1659 self.relsizes[i] += subrelsize
1660 else:
1661 if self.subaxis is None:
1662 subrelsize = 1
1663 else:
1664 self.subaxis.updaterelsizes()
1665 subrelsize = self.subaxis.relsizes[-1]
1666 for i in range(1, len(self.relsizes)):
1667 self.relsizes[i] += i * subrelsize
1669 def convert(self, value):
1670 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
1671 if not self.relsizes:
1672 self.updaterelsizes()
1673 pos = self.names.index(value[0])
1674 if len(value) == 2:
1675 if self.subaxis is None:
1676 subvalue = value[1]
1677 else:
1678 if self.multisubaxis:
1679 subvalue = value[1] * self.subaxis[pos].relsizes[-1]
1680 else:
1681 subvalue = value[1] * self.subaxis.relsizes[-1]
1682 else:
1683 if self.multisubaxis:
1684 subvalue = self.subaxis[pos].convert(value[1:]) * self.subaxis[pos].relsizes[-1]
1685 else:
1686 subvalue = self.subaxis.convert(value[1:]) * self.subaxis.relsizes[-1]
1687 return (self.relsizes[pos] + subvalue) / float(self.relsizes[-1])
1689 def dolayout(self, graph):
1690 self.painter.dolayout(graph, self)
1692 def dopaint(self, graph):
1693 self.painter.paint(graph, self)
1695 def createlinkaxis(self, **args):
1696 if self.subaxis is not None:
1697 if self.multisubaxis:
1698 subaxis = [subaxis.createlinkaxis() for subaxis in self.subaxis]
1699 else:
1700 subaxis = self.subaxis.createlinkaxis()
1701 else:
1702 subaxis = None
1703 return baraxis(subaxis=subaxis, dist=self.dist, firstdist=self.firstdist, lastdist=self.lastdist, **args)
1705 createsubaxis = createlinkaxis
1708 ################################################################################
1709 # graph
1710 ################################################################################
1713 class graphxy(canvas.canvas):
1715 Names = "x", "y"
1717 def clipcanvas(self):
1718 return self.insert(canvas.canvas(canvas.clip(path._rect(self._xpos, self._ypos, self._width, self._height))))
1720 def plot(self, data, style=None):
1721 if self.haslayout:
1722 raise RuntimeError("layout setup was already performed")
1723 if style is None:
1724 if self.defaultstyle.has_key(data.defaultstyle):
1725 style = self.defaultstyle[data.defaultstyle].iterate()
1726 else:
1727 style = data.defaultstyle()
1728 self.defaultstyle[data.defaultstyle] = style
1729 styles = []
1730 first = 1
1731 for d in helper._ensuresequence(data):
1732 if first:
1733 styles.append(style)
1734 else:
1735 styles.append(style.iterate())
1736 first = 0
1737 if d is not None:
1738 d.setstyle(self, styles[-1])
1739 self.data.append(d)
1740 if helper._issequence(data):
1741 return styles
1742 return styles[0]
1744 def _vxtickpoint(self, axis, v):
1745 return (self._xpos+v*self._width, axis.axispos)
1747 def _vytickpoint(self, axis, v):
1748 return (axis.axispos, self._ypos+v*self._height)
1750 def vtickdirection(self, axis, v):
1751 return axis.fixtickdirection
1753 def _pos(self, x, y, xaxis=None, yaxis=None):
1754 if xaxis is None: xaxis = self.axes["x"]
1755 if yaxis is None: yaxis = self.axes["y"]
1756 return self._xpos+xaxis.convert(x)*self._width, self._ypos+yaxis.convert(y)*self._height
1758 def pos(self, x, y, xaxis=None, yaxis=None):
1759 if xaxis is None: xaxis = self.axes["x"]
1760 if yaxis is None: yaxis = self.axes["y"]
1761 return self.xpos+xaxis.convert(x)*self.width, self.ypos+yaxis.convert(y)*self.height
1763 def _vpos(self, vx, vy):
1764 return self._xpos+vx*self._width, self._ypos+vy*self._height
1766 def vpos(self, vx, vy):
1767 return self.xpos+vx*self.width, self.ypos+vy*self.height
1769 def xbaseline(self, axis, x1, x2, shift=0, xaxis=None):
1770 if xaxis is None: xaxis = self.axes["x"]
1771 v1, v2 = xaxis.convert(x1), xaxis.convert(x2)
1772 return path._line(self._xpos+v1*self._width, axis.axispos+shift,
1773 self._xpos+v2*self._width, axis.axispos+shift)
1775 def ybaseline(self, axis, y1, y2, shift=0, yaxis=None):
1776 if yaxis is None: yaxis = self.axes["y"]
1777 v1, v2 = yaxis.convert(y1), yaxis.convert(y2)
1778 return path._line(axis.axispos+shift, self._ypos+v1*self._height,
1779 axis.axispos+shift, self._ypos+v2*self._height)
1781 def vxbaseline(self, axis, v1=None, v2=None, shift=0):
1782 if v1 is None: v1 = 0
1783 if v2 is None: v2 = 1
1784 return path._line(self._xpos+v1*self._width, axis.axispos+shift,
1785 self._xpos+v2*self._width, axis.axispos+shift)
1787 def vybaseline(self, axis, v1=None, v2=None, shift=0):
1788 if v1 is None: v1 = 0
1789 if v2 is None: v2 = 1
1790 return path._line(axis.axispos+shift, self._ypos+v1*self._height,
1791 axis.axispos+shift, self._ypos+v2*self._height)
1793 def xgridpath(self, x, xaxis=None):
1794 if xaxis is None: xaxis = self.axes["x"]
1795 v = xaxis.convert(x)
1796 return path._line(self._xpos+v*self._width, self._ypos,
1797 self._xpos+v*self._width, self._ypos+self._height)
1799 def ygridpath(self, y, yaxis=None):
1800 if yaxis is None: yaxis = self.axes["y"]
1801 v = yaxis.convert(y)
1802 return path._line(self._xpos, self._ypos+v*self._height,
1803 self._xpos+self._width, self._ypos+v*self._height)
1805 def vxgridpath(self, v):
1806 return path._line(self._xpos+v*self._width, self._ypos,
1807 self._xpos+v*self._width, self._ypos+self._height)
1809 def vygridpath(self, v):
1810 return path._line(self._xpos, self._ypos+v*self._height,
1811 self._xpos+self._width, self._ypos+v*self._height)
1813 def _addpos(self, x, y, dx, dy):
1814 return x+dx, y+dy
1816 def _connect(self, x1, y1, x2, y2):
1817 return path._lineto(x2, y2)
1819 def keynum(self, key):
1820 try:
1821 while key[0] in string.letters:
1822 key = key[1:]
1823 return int(key)
1824 except IndexError:
1825 return 1
1827 def gatherranges(self):
1828 ranges = {}
1829 for data in self.data:
1830 pdranges = data.getranges()
1831 if pdranges is not None:
1832 for key in pdranges.keys():
1833 if key not in ranges.keys():
1834 ranges[key] = pdranges[key]
1835 else:
1836 ranges[key] = (min(ranges[key][0], pdranges[key][0]),
1837 max(ranges[key][1], pdranges[key][1]))
1838 # known ranges are also set as ranges for the axes
1839 for key, axis in self.axes.items():
1840 if key in ranges.keys():
1841 axis.setdatarange(*ranges[key])
1842 ranges[key] = axis.getdatarange()
1843 if ranges[key] is None:
1844 del ranges[key]
1845 return ranges
1847 def removedomethod(self, method):
1848 hadmethod = 0
1849 while 1:
1850 try:
1851 self.domethods.remove(method)
1852 hadmethod = 1
1853 except ValueError:
1854 return hadmethod
1856 def dolayout(self):
1857 if not self.removedomethod(self.dolayout): return
1858 self.haslayout = 1
1859 # create list of ranges
1860 # 1. gather ranges
1861 ranges = self.gatherranges()
1862 # 2. calculate additional ranges out of known ranges
1863 for data in self.data:
1864 data.setranges(ranges)
1865 # 3. gather ranges again
1866 self.gatherranges()
1868 # do the layout for all axes
1869 axesdist = unit.topt(unit.length(self.axesdist_str, default_type="v"))
1870 XPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[0])
1871 YPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[1])
1872 self._xaxisextents = [0, 0]
1873 self._yaxisextents = [0, 0]
1874 needxaxisdist = [0, 0]
1875 needyaxisdist = [0, 0]
1876 items = list(self.axes.items())
1877 items.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
1878 for key, axis in items:
1879 num = self.keynum(key)
1880 num2 = 1 - num % 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
1881 num3 = 1 - 2 * (num % 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
1882 if XPattern.match(key):
1883 if needxaxisdist[num2]:
1884 self._xaxisextents[num2] += axesdist
1885 axis.axispos = self._ypos+num2*self._height + num3*self._xaxisextents[num2]
1886 axis._vtickpoint = self._vxtickpoint
1887 axis.fixtickdirection = (0, num3)
1888 axis.vgridpath = self.vxgridpath
1889 axis.vbaseline = self.vxbaseline
1890 axis.gridpath = self.xgridpath
1891 axis.baseline = self.xbaseline
1892 elif YPattern.match(key):
1893 if needyaxisdist[num2]:
1894 self._yaxisextents[num2] += axesdist
1895 axis.axispos = self._xpos+num2*self._width + num3*self._yaxisextents[num2]
1896 axis._vtickpoint = self._vytickpoint
1897 axis.fixtickdirection = (num3, 0)
1898 axis.vgridpath = self.vygridpath
1899 axis.vbaseline = self.vybaseline
1900 axis.gridpath = self.ygridpath
1901 axis.baseline = self.ybaseline
1902 else:
1903 raise ValueError("Axis key '%s' not allowed" % key)
1904 axis.vtickdirection = self.vtickdirection
1905 axis.dolayout(self)
1906 if XPattern.match(key):
1907 self._xaxisextents[num2] += axis._extent
1908 needxaxisdist[num2] = 1
1909 if YPattern.match(key):
1910 self._yaxisextents[num2] += axis._extent
1911 needyaxisdist[num2] = 1
1913 def dobackground(self):
1914 self.dolayout()
1915 if not self.removedomethod(self.dobackground): return
1916 if self.backgroundattrs is not None:
1917 self.draw(path._rect(self._xpos, self._ypos, self._width, self._height),
1918 *helper._ensuresequence(self.backgroundattrs))
1920 def doaxes(self):
1921 self.dolayout()
1922 if not self.removedomethod(self.doaxes): return
1923 for axis in self.axes.values():
1924 axis.dopaint(self)
1926 def dodata(self):
1927 self.dolayout()
1928 if not self.removedomethod(self.dodata): return
1929 for data in self.data:
1930 data.draw(self)
1932 def finish(self):
1933 while len(self.domethods):
1934 self.domethods[0]()
1936 def initwidthheight(self, width, height, ratio):
1937 if (width is not None) and (height is None):
1938 height = (1/ratio) * width
1939 if (height is not None) and (width is None):
1940 width = ratio * height
1941 self._width = unit.topt(width)
1942 self._height = unit.topt(height)
1943 self.width = width
1944 self.height = height
1945 if self._width <= 0: raise ValueError("width < 0")
1946 if self._height <= 0: raise ValueError("height < 0")
1948 def initaxes(self, axes, addlinkaxes=0):
1949 for key in self.Names:
1950 if not axes.has_key(key):
1951 axes[key] = linaxis()
1952 elif axes[key] is None:
1953 del axes[key]
1954 if addlinkaxes:
1955 if not axes.has_key(key + "2") and axes.has_key(key):
1956 axes[key + "2"] = axes[key].createlinkaxis()
1957 elif axes[key + "2"] is None:
1958 del axes[key + "2"]
1959 self.axes = axes
1961 def __init__(self, tex, xpos=0, ypos=0, width=None, height=None, ratio=goldenrule,
1962 backgroundattrs=None, dense=1, axesdist="0.8 cm", **axes):
1963 canvas.canvas.__init__(self)
1964 self.tex = tex
1965 self.xpos = xpos
1966 self.ypos = ypos
1967 self._xpos = unit.topt(xpos)
1968 self._ypos = unit.topt(ypos)
1969 self.initwidthheight(width, height, ratio)
1970 self.initaxes(axes, 1)
1971 self.dense = dense
1972 self.axesdist_str = axesdist
1973 self.backgroundattrs = backgroundattrs
1974 self.data = []
1975 self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
1976 self.haslayout = 0
1977 self.defaultstyle = {}
1979 def bbox(self):
1980 self.dolayout()
1981 return bbox.bbox(self._xpos - self._yaxisextents[0],
1982 self._ypos - self._xaxisextents[0],
1983 self._xpos + self._width + self._yaxisextents[1],
1984 self._ypos + self._height + self._xaxisextents[1])
1986 def write(self, file):
1987 self.finish()
1988 canvas.canvas.write(self, file)
1992 # some thoughts, but deferred right now
1994 # class graphxyz(graphxy):
1996 # Names = "x", "y", "z"
1998 # def _vxtickpoint(self, axis, v):
1999 # return self._vpos(v, axis.vypos, axis.vzpos)
2001 # def _vytickpoint(self, axis, v):
2002 # return self._vpos(axis.vxpos, v, axis.vzpos)
2004 # def _vztickpoint(self, axis, v):
2005 # return self._vpos(axis.vxpos, axis.vypos, v)
2007 # def vxtickdirection(self, axis, v):
2008 # x1, y1 = self._vpos(v, axis.vypos, axis.vzpos)
2009 # x2, y2 = self._vpos(v, 0.5, 0)
2010 # dx, dy = x1 - x2, y1 - y2
2011 # norm = math.sqrt(dx*dx + dy*dy)
2012 # return dx/norm, dy/norm
2014 # def vytickdirection(self, axis, v):
2015 # x1, y1 = self._vpos(axis.vxpos, v, axis.vzpos)
2016 # x2, y2 = self._vpos(0.5, v, 0)
2017 # dx, dy = x1 - x2, y1 - y2
2018 # norm = math.sqrt(dx*dx + dy*dy)
2019 # return dx/norm, dy/norm
2021 # def vztickdirection(self, axis, v):
2022 # return -1, 0
2023 # x1, y1 = self._vpos(axis.vxpos, axis.vypos, v)
2024 # x2, y2 = self._vpos(0.5, 0.5, v)
2025 # dx, dy = x1 - x2, y1 - y2
2026 # norm = math.sqrt(dx*dx + dy*dy)
2027 # return dx/norm, dy/norm
2029 # def _pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
2030 # if xaxis is None: xaxis = self.axes["x"]
2031 # if yaxis is None: yaxis = self.axes["y"]
2032 # if zaxis is None: zaxis = self.axes["z"]
2033 # return self._vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
2035 # def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
2036 # if xaxis is None: xaxis = self.axes["x"]
2037 # if yaxis is None: yaxis = self.axes["y"]
2038 # if zaxis is None: zaxis = self.axes["z"]
2039 # return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
2041 # def _vpos(self, vx, vy, vz):
2042 # x, y, z = (vx - 0.5)*self._depth, (vy - 0.5)*self._width, (vz - 0.5)*self._height
2043 # d0 = float(self.a[0]*self.b[1]*(z-self.eye[2])
2044 # + self.a[2]*self.b[0]*(y-self.eye[1])
2045 # + self.a[1]*self.b[2]*(x-self.eye[0])
2046 # - self.a[2]*self.b[1]*(x-self.eye[0])
2047 # - self.a[0]*self.b[2]*(y-self.eye[1])
2048 # - self.a[1]*self.b[0]*(z-self.eye[2]))
2049 # da = (self.eye[0]*self.b[1]*(z-self.eye[2])
2050 # + self.eye[2]*self.b[0]*(y-self.eye[1])
2051 # + self.eye[1]*self.b[2]*(x-self.eye[0])
2052 # - self.eye[2]*self.b[1]*(x-self.eye[0])
2053 # - self.eye[0]*self.b[2]*(y-self.eye[1])
2054 # - self.eye[1]*self.b[0]*(z-self.eye[2]))
2055 # db = (self.a[0]*self.eye[1]*(z-self.eye[2])
2056 # + self.a[2]*self.eye[0]*(y-self.eye[1])
2057 # + self.a[1]*self.eye[2]*(x-self.eye[0])
2058 # - self.a[2]*self.eye[1]*(x-self.eye[0])
2059 # - self.a[0]*self.eye[2]*(y-self.eye[1])
2060 # - self.a[1]*self.eye[0]*(z-self.eye[2]))
2061 # return da/d0 + self._xpos, db/d0 + self._ypos
2063 # def vpos(self, vx, vy, vz):
2064 # tx, ty = self._vpos(vx, vy, vz)
2065 # return unit.t_pt(tx), unit.t_pt(ty)
2067 # def xbaseline(self, axis, x1, x2, shift=0, xaxis=None):
2068 # if xaxis is None: xaxis = self.axes["x"]
2069 # return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2), shift)
2071 # def ybaseline(self, axis, y1, y2, shift=0, yaxis=None):
2072 # if yaxis is None: yaxis = self.axes["y"]
2073 # return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2), shift)
2075 # def zbaseline(self, axis, z1, z2, shift=0, zaxis=None):
2076 # if zaxis is None: zaxis = self.axes["z"]
2077 # return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2), shift)
2079 # def vxbaseline(self, axis, v1, v2, shift=0):
2080 # return (path._line(*(self._vpos(v1, 0, 0) + self._vpos(v2, 0, 0))) +
2081 # path._line(*(self._vpos(v1, 0, 1) + self._vpos(v2, 0, 1))) +
2082 # path._line(*(self._vpos(v1, 1, 1) + self._vpos(v2, 1, 1))) +
2083 # path._line(*(self._vpos(v1, 1, 0) + self._vpos(v2, 1, 0))))
2085 # def vybaseline(self, axis, v1, v2, shift=0):
2086 # return (path._line(*(self._vpos(0, v1, 0) + self._vpos(0, v2, 0))) +
2087 # path._line(*(self._vpos(0, v1, 1) + self._vpos(0, v2, 1))) +
2088 # path._line(*(self._vpos(1, v1, 1) + self._vpos(1, v2, 1))) +
2089 # path._line(*(self._vpos(1, v1, 0) + self._vpos(1, v2, 0))))
2091 # def vzbaseline(self, axis, v1, v2, shift=0):
2092 # return (path._line(*(self._vpos(0, 0, v1) + self._vpos(0, 0, v2))) +
2093 # path._line(*(self._vpos(0, 1, v1) + self._vpos(0, 1, v2))) +
2094 # path._line(*(self._vpos(1, 1, v1) + self._vpos(1, 1, v2))) +
2095 # path._line(*(self._vpos(1, 0, v1) + self._vpos(1, 0, v2))))
2097 # def xgridpath(self, x, xaxis=None):
2098 # assert 0
2099 # if xaxis is None: xaxis = self.axes["x"]
2100 # v = xaxis.convert(x)
2101 # return path._line(self._xpos+v*self._width, self._ypos,
2102 # self._xpos+v*self._width, self._ypos+self._height)
2104 # def ygridpath(self, y, yaxis=None):
2105 # assert 0
2106 # if yaxis is None: yaxis = self.axes["y"]
2107 # v = yaxis.convert(y)
2108 # return path._line(self._xpos, self._ypos+v*self._height,
2109 # self._xpos+self._width, self._ypos+v*self._height)
2111 # def zgridpath(self, z, zaxis=None):
2112 # assert 0
2113 # if zaxis is None: zaxis = self.axes["z"]
2114 # v = zaxis.convert(z)
2115 # return path._line(self._xpos, self._zpos+v*self._height,
2116 # self._xpos+self._width, self._zpos+v*self._height)
2118 # def vxgridpath(self, v):
2119 # return path.path(path._moveto(*self._vpos(v, 0, 0)),
2120 # path._lineto(*self._vpos(v, 0, 1)),
2121 # path._lineto(*self._vpos(v, 1, 1)),
2122 # path._lineto(*self._vpos(v, 1, 0)),
2123 # path.closepath())
2125 # def vygridpath(self, v):
2126 # return path.path(path._moveto(*self._vpos(0, v, 0)),
2127 # path._lineto(*self._vpos(0, v, 1)),
2128 # path._lineto(*self._vpos(1, v, 1)),
2129 # path._lineto(*self._vpos(1, v, 0)),
2130 # path.closepath())
2132 # def vzgridpath(self, v):
2133 # return path.path(path._moveto(*self._vpos(0, 0, v)),
2134 # path._lineto(*self._vpos(0, 1, v)),
2135 # path._lineto(*self._vpos(1, 1, v)),
2136 # path._lineto(*self._vpos(1, 0, v)),
2137 # path.closepath())
2139 # def _addpos(self, x, y, dx, dy):
2140 # assert 0
2141 # return x+dx, y+dy
2143 # def _connect(self, x1, y1, x2, y2):
2144 # assert 0
2145 # return path._lineto(x2, y2)
2147 # def doaxes(self):
2148 # self.dolayout()
2149 # if not self.removedomethod(self.doaxes): return
2150 # axesdist = unit.topt(unit.length(self.axesdist_str, default_type="v"))
2151 # XPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[0])
2152 # YPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[1])
2153 # ZPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[2])
2154 # items = list(self.axes.items())
2155 # items.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
2156 # for key, axis in items:
2157 # num = self.keynum(key)
2158 # num2 = 1 - num % 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
2159 # num3 = 1 - 2 * (num % 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
2160 # if XPattern.match(key):
2161 # axis.vypos = 0
2162 # axis.vzpos = 0
2163 # axis._vtickpoint = self._vxtickpoint
2164 # axis.vgridpath = self.vxgridpath
2165 # axis.vbaseline = self.vxbaseline
2166 # axis.vtickdirection = self.vxtickdirection
2167 # elif YPattern.match(key):
2168 # axis.vxpos = 0
2169 # axis.vzpos = 0
2170 # axis._vtickpoint = self._vytickpoint
2171 # axis.vgridpath = self.vygridpath
2172 # axis.vbaseline = self.vybaseline
2173 # axis.vtickdirection = self.vytickdirection
2174 # elif ZPattern.match(key):
2175 # axis.vxpos = 0
2176 # axis.vypos = 0
2177 # axis._vtickpoint = self._vztickpoint
2178 # axis.vgridpath = self.vzgridpath
2179 # axis.vbaseline = self.vzbaseline
2180 # axis.vtickdirection = self.vztickdirection
2181 # else:
2182 # raise ValueError("Axis key '%s' not allowed" % key)
2183 # if axis.painter is not None:
2184 # axis.dopaint(self)
2185 # # if XPattern.match(key):
2186 # # self._xaxisextents[num2] += axis._extent
2187 # # needxaxisdist[num2] = 1
2188 # # if YPattern.match(key):
2189 # # self._yaxisextents[num2] += axis._extent
2190 # # needyaxisdist[num2] = 1
2192 # def __init__(self, tex, xpos=0, ypos=0, width=None, height=None, depth=None,
2193 # phi=30, theta=30, distance=1,
2194 # backgroundattrs=None, axesdist="0.8 cm", **axes):
2195 # canvas.canvas.__init__(self)
2196 # self.tex = tex
2197 # self.xpos = xpos
2198 # self.ypos = ypos
2199 # self._xpos = unit.topt(xpos)
2200 # self._ypos = unit.topt(ypos)
2201 # self._width = unit.topt(width)
2202 # self._height = unit.topt(height)
2203 # self._depth = unit.topt(depth)
2204 # self.width = width
2205 # self.height = height
2206 # self.depth = depth
2207 # if self._width <= 0: raise ValueError("width < 0")
2208 # if self._height <= 0: raise ValueError("height < 0")
2209 # if self._depth <= 0: raise ValueError("height < 0")
2210 # self._distance = distance*math.sqrt(self._width*self._width+
2211 # self._height*self._height+
2212 # self._depth*self._depth)
2213 # phi *= -math.pi/180
2214 # theta *= math.pi/180
2215 # self.a = (-math.sin(phi), math.cos(phi), 0)
2216 # self.b = (-math.cos(phi)*math.sin(theta),
2217 # -math.sin(phi)*math.sin(theta),
2218 # math.cos(theta))
2219 # self.eye = (self._distance*math.cos(phi)*math.cos(theta),
2220 # self._distance*math.sin(phi)*math.cos(theta),
2221 # self._distance*math.sin(theta))
2222 # self.initaxes(axes)
2223 # self.axesdist_str = axesdist
2224 # self.backgroundattrs = backgroundattrs
2226 # self.data = []
2227 # self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
2228 # self.haslayout = 0
2229 # self.defaultstyle = {}
2231 # def bbox(self):
2232 # self.finish()
2233 # return bbox.bbox(self._xpos - 200, self._ypos - 200, self._xpos + 200, self._ypos + 200)
2236 ################################################################################
2237 # attr changers
2238 ################################################################################
2241 #class _Ichangeattr:
2242 # """attribute changer
2243 # is an iterator for attributes where an attribute
2244 # is not refered by just a number (like for a sequence),
2245 # but also by the number of attributes requested
2246 # by calls of the next method (like for an color gradient)
2247 # (you should ensure to call all needed next before the attr)
2249 # the attribute itself is implemented by overloading the _attr method"""
2251 # def attr(self):
2252 # "get an attribute"
2254 # def next(self):
2255 # "get an attribute changer for the next attribute"
2258 class _changeattr: pass
2261 class changeattr(_changeattr):
2263 def __init__(self):
2264 self.counter = 1
2266 def getattr(self):
2267 return self.attr(0)
2269 def iterate(self):
2270 newindex = self.counter
2271 self.counter += 1
2272 return refattr(self, newindex)
2275 class refattr(_changeattr):
2277 def __init__(self, ref, index):
2278 self.ref = ref
2279 self.index = index
2281 def getattr(self):
2282 return self.ref.attr(self.index)
2284 def iterate(self):
2285 return self.ref.iterate()
2288 # helper routines for a using attrs
2290 def _getattr(attr):
2291 """get attr out of a attr/changeattr"""
2292 if isinstance(attr, _changeattr):
2293 return attr.getattr()
2294 return attr
2297 def _getattrs(attrs):
2298 """get attrs out of a sequence of attr/changeattr"""
2299 if attrs is not None:
2300 result = []
2301 for attr in helper._ensuresequence(attrs):
2302 if isinstance(attr, _changeattr):
2303 result.append(attr.getattr())
2304 else:
2305 result.append(attr)
2306 return result
2309 def _iterateattr(attr):
2310 """perform next to a attr/changeattr"""
2311 if isinstance(attr, _changeattr):
2312 return attr.iterate()
2313 return attr
2316 def _iterateattrs(attrs):
2317 """perform next to a sequence of attr/changeattr"""
2318 if attrs is not None:
2319 result = []
2320 for attr in helper._ensuresequence(attrs):
2321 if isinstance(attr, _changeattr):
2322 result.append(attr.iterate())
2323 else:
2324 result.append(attr)
2325 return result
2328 class changecolor(changeattr):
2330 def __init__(self, gradient):
2331 changeattr.__init__(self)
2332 self.gradient = gradient
2334 def attr(self, index):
2335 if self.counter != 1:
2336 return self.gradient.getcolor(index/float(self.counter-1))
2337 else:
2338 return self.gradient.getcolor(0)
2341 class _changecolorgray(changecolor):
2343 def __init__(self, gradient=color.gradient.Gray):
2344 changecolor.__init__(self, gradient)
2346 _changecolorgrey = _changecolorgray
2349 class _changecolorreversegray(changecolor):
2351 def __init__(self, gradient=color.gradient.ReverseGray):
2352 changecolor.__init__(self, gradient)
2354 _changecolorreversegrey = _changecolorreversegray
2357 class _changecolorredblack(changecolor):
2359 def __init__(self, gradient=color.gradient.RedBlack):
2360 changecolor.__init__(self, gradient)
2363 class _changecolorblackred(changecolor):
2365 def __init__(self, gradient=color.gradient.BlackRed):
2366 changecolor.__init__(self, gradient)
2369 class _changecolorredwhite(changecolor):
2371 def __init__(self, gradient=color.gradient.RedWhite):
2372 changecolor.__init__(self, gradient)
2375 class _changecolorwhitered(changecolor):
2377 def __init__(self, gradient=color.gradient.WhiteRed):
2378 changecolor.__init__(self, gradient)
2381 class _changecolorgreenblack(changecolor):
2383 def __init__(self, gradient=color.gradient.GreenBlack):
2384 changecolor.__init__(self, gradient)
2387 class _changecolorblackgreen(changecolor):
2389 def __init__(self, gradient=color.gradient.BlackGreen):
2390 changecolor.__init__(self, gradient)
2393 class _changecolorgreenwhite(changecolor):
2395 def __init__(self, gradient=color.gradient.GreenWhite):
2396 changecolor.__init__(self, gradient)
2399 class _changecolorwhitegreen(changecolor):
2401 def __init__(self, gradient=color.gradient.WhiteGreen):
2402 changecolor.__init__(self, gradient)
2405 class _changecolorblueblack(changecolor):
2407 def __init__(self, gradient=color.gradient.BlueBlack):
2408 changecolor.__init__(self, gradient)
2411 class _changecolorblackblue(changecolor):
2413 def __init__(self, gradient=color.gradient.BlackBlue):
2414 changecolor.__init__(self, gradient)
2417 class _changecolorbluewhite(changecolor):
2419 def __init__(self, gradient=color.gradient.BlueWhite):
2420 changecolor.__init__(self, gradient)
2423 class _changecolorwhiteblue(changecolor):
2425 def __init__(self, gradient=color.gradient.WhiteBlue):
2426 changecolor.__init__(self, gradient)
2429 class _changecolorredgreen(changecolor):
2431 def __init__(self, gradient=color.gradient.RedGreen):
2432 changecolor.__init__(self, gradient)
2435 class _changecolorredblue(changecolor):
2437 def __init__(self, gradient=color.gradient.RedBlue):
2438 changecolor.__init__(self, gradient)
2441 class _changecolorgreenred(changecolor):
2443 def __init__(self, gradient=color.gradient.GreenRed):
2444 changecolor.__init__(self, gradient)
2447 class _changecolorgreenblue(changecolor):
2449 def __init__(self, gradient=color.gradient.GreenBlue):
2450 changecolor.__init__(self, gradient)
2453 class _changecolorbluered(changecolor):
2455 def __init__(self, gradient=color.gradient.BlueRed):
2456 changecolor.__init__(self, gradient)
2459 class _changecolorbluegreen(changecolor):
2461 def __init__(self, gradient=color.gradient.BlueGreen):
2462 changecolor.__init__(self, gradient)
2465 class _changecolorrainbow(changecolor):
2467 def __init__(self, gradient=color.gradient.Rainbow):
2468 changecolor.__init__(self, gradient)
2471 class _changecolorreverserainbow(changecolor):
2473 def __init__(self, gradient=color.gradient.ReverseRainbow):
2474 changecolor.__init__(self, gradient)
2477 class _changecolorhue(changecolor):
2479 def __init__(self, gradient=color.gradient.Hue):
2480 changecolor.__init__(self, gradient)
2483 class _changecolorreversehue(changecolor):
2485 def __init__(self, gradient=color.gradient.ReverseHue):
2486 changecolor.__init__(self, gradient)
2489 changecolor.Gray = _changecolorgray
2490 changecolor.Grey = _changecolorgrey
2491 changecolor.Reversegray = _changecolorreversegray
2492 changecolor.Reversegrey = _changecolorreversegrey
2493 changecolor.RedBlack = _changecolorredblack
2494 changecolor.BlackRed = _changecolorblackred
2495 changecolor.RedWhite = _changecolorredwhite
2496 changecolor.WhiteRed = _changecolorwhitered
2497 changecolor.GreenBlack = _changecolorgreenblack
2498 changecolor.BlackGreen = _changecolorblackgreen
2499 changecolor.GreenWhite = _changecolorgreenwhite
2500 changecolor.WhiteGreen = _changecolorwhitegreen
2501 changecolor.BlueBlack = _changecolorblueblack
2502 changecolor.BlackBlue = _changecolorblackblue
2503 changecolor.BlueWhite = _changecolorbluewhite
2504 changecolor.WhiteBlue = _changecolorwhiteblue
2505 changecolor.RedGreen = _changecolorredgreen
2506 changecolor.RedBlue = _changecolorredblue
2507 changecolor.GreenRed = _changecolorgreenred
2508 changecolor.GreenBlue = _changecolorgreenblue
2509 changecolor.BlueRed = _changecolorbluered
2510 changecolor.BlueGreen = _changecolorbluegreen
2511 changecolor.Rainbow = _changecolorrainbow
2512 changecolor.ReverseRainbow = _changecolorreverserainbow
2513 changecolor.Hue = _changecolorhue
2514 changecolor.ReverseHue = _changecolorreversehue
2517 class changesequence(changeattr):
2518 """cycles through a sequence"""
2520 def __init__(self, *sequence):
2521 changeattr.__init__(self)
2522 if not len(sequence):
2523 sequence = self.defaultsequence
2524 self.sequence = sequence
2526 def attr(self, index):
2527 return self.sequence[index % len(self.sequence)]
2530 class changelinestyle(changesequence):
2531 defaultsequence = (canvas.linestyle.solid,
2532 canvas.linestyle.dashed,
2533 canvas.linestyle.dotted,
2534 canvas.linestyle.dashdotted)
2537 class changestrokedfilled(changesequence):
2538 defaultsequence = (canvas.stroked(), canvas.filled())
2541 class changefilledstroked(changesequence):
2542 defaultsequence = (canvas.filled(), canvas.stroked())
2546 ################################################################################
2547 # styles
2548 ################################################################################
2551 class symbol:
2553 def cross(self, x, y):
2554 return (path._moveto(x-0.5*self._size, y-0.5*self._size),
2555 path._lineto(x+0.5*self._size, y+0.5*self._size),
2556 path._moveto(x-0.5*self._size, y+0.5*self._size),
2557 path._lineto(x+0.5*self._size, y-0.5*self._size))
2559 def plus(self, x, y):
2560 return (path._moveto(x-0.707106781*self._size, y),
2561 path._lineto(x+0.707106781*self._size, y),
2562 path._moveto(x, y-0.707106781*self._size),
2563 path._lineto(x, y+0.707106781*self._size))
2565 def square(self, x, y):
2566 return (path._moveto(x-0.5*self._size, y-0.5 * self._size),
2567 path._lineto(x+0.5*self._size, y-0.5 * self._size),
2568 path._lineto(x+0.5*self._size, y+0.5 * self._size),
2569 path._lineto(x-0.5*self._size, y+0.5 * self._size),
2570 path.closepath())
2572 def triangle(self, x, y):
2573 return (path._moveto(x-0.759835685*self._size, y-0.438691337*self._size),
2574 path._lineto(x+0.759835685*self._size, y-0.438691337*self._size),
2575 path._lineto(x, y+0.877382675*self._size),
2576 path.closepath())
2578 def circle(self, x, y):
2579 return (path._arc(x, y, 0.564189583*self._size, 0, 360),
2580 path.closepath())
2582 def diamond(self, x, y):
2583 return (path._moveto(x-0.537284965*self._size, y),
2584 path._lineto(x, y-0.930604859*self._size),
2585 path._lineto(x+0.537284965*self._size, y),
2586 path._lineto(x, y+0.930604859*self._size),
2587 path.closepath())
2589 def __init__(self, symbol=helper._nodefault,
2590 size="0.2 cm", symbolattrs=canvas.stroked(),
2591 errorscale=0.5, errorbarattrs=(),
2592 lineattrs=None):
2593 self.size_str = size
2594 if symbol is helper._nodefault:
2595 self._symbol = changesymbol.cross()
2596 else:
2597 self._symbol = symbol
2598 self._symbolattrs = symbolattrs
2599 self.errorscale = errorscale
2600 self._errorbarattrs = errorbarattrs
2601 self._lineattrs = lineattrs
2603 def iteratedict(self):
2604 result = {}
2605 result["symbol"] = _iterateattr(self._symbol)
2606 result["size"] = _iterateattr(self.size_str)
2607 result["symbolattrs"] = _iterateattrs(self._symbolattrs)
2608 result["errorscale"] = _iterateattr(self.errorscale)
2609 result["errorbarattrs"] = _iterateattrs(self._errorbarattrs)
2610 result["lineattrs"] = _iterateattrs(self._lineattrs)
2611 return result
2613 def iterate(self):
2614 return symbol(**self.iteratedict())
2616 def othercolumnkey(self, key, index):
2617 raise ValueError("unsuitable key '%s'" % key)
2619 def setcolumns(self, graph, columns):
2620 def checkpattern(key, index, pattern, iskey, isindex):
2621 if key is not None:
2622 match = pattern.match(key)
2623 if match:
2624 if isindex is not None: raise ValueError("multiple key specification")
2625 if iskey is not None and iskey != match.groups()[0]: raise ValueError("inconsistent key names")
2626 key = None
2627 iskey = match.groups()[0]
2628 isindex = index
2629 return key, iskey, isindex
2631 self.xi = self.xmini = self.xmaxi = None
2632 self.dxi = self.dxmini = self.dxmaxi = None
2633 self.yi = self.ymini = self.ymaxi = None
2634 self.dyi = self.dymini = self.dymaxi = None
2635 self.xkey = self.ykey = None
2636 if len(graph.Names) != 2: raise TypeError("style not applicable in graph")
2637 XPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.Names[0])
2638 YPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.Names[1])
2639 XMinPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)min$" % graph.Names[0])
2640 YMinPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)min$" % graph.Names[1])
2641 XMaxPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)max$" % graph.Names[0])
2642 YMaxPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)max$" % graph.Names[1])
2643 DXPattern = re.compile(r"d(%s([2-9]|[1-9][0-9]+)?)$" % graph.Names[0])
2644 DYPattern = re.compile(r"d(%s([2-9]|[1-9][0-9]+)?)$" % graph.Names[1])
2645 DXMinPattern = re.compile(r"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph.Names[0])
2646 DYMinPattern = re.compile(r"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph.Names[1])
2647 DXMaxPattern = re.compile(r"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph.Names[0])
2648 DYMaxPattern = re.compile(r"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph.Names[1])
2649 for key, index in columns.items():
2650 key, self.xkey, self.xi = checkpattern(key, index, XPattern, self.xkey, self.xi)
2651 key, self.ykey, self.yi = checkpattern(key, index, YPattern, self.ykey, self.yi)
2652 key, self.xkey, self.xmini = checkpattern(key, index, XMinPattern, self.xkey, self.xmini)
2653 key, self.ykey, self.ymini = checkpattern(key, index, YMinPattern, self.ykey, self.ymini)
2654 key, self.xkey, self.xmaxi = checkpattern(key, index, XMaxPattern, self.xkey, self.xmaxi)
2655 key, self.ykey, self.ymaxi = checkpattern(key, index, YMaxPattern, self.ykey, self.ymaxi)
2656 key, self.xkey, self.dxi = checkpattern(key, index, DXPattern, self.xkey, self.dxi)
2657 key, self.ykey, self.dyi = checkpattern(key, index, DYPattern, self.ykey, self.dyi)
2658 key, self.xkey, self.dxmini = checkpattern(key, index, DXMinPattern, self.xkey, self.dxmini)
2659 key, self.ykey, self.dymini = checkpattern(key, index, DYMinPattern, self.ykey, self.dymini)
2660 key, self.xkey, self.dxmaxi = checkpattern(key, index, DXMaxPattern, self.xkey, self.dxmaxi)
2661 key, self.ykey, self.dymaxi = checkpattern(key, index, DYMaxPattern, self.ykey, self.dymaxi)
2662 if key is not None:
2663 self.othercolumnkey(key, index)
2664 if None in (self.xkey, self.ykey): raise ValueError("incomplete axis specification")
2665 if (len(filter(None, (self.xmini, self.dxmini, self.dxi))) > 1 or
2666 len(filter(None, (self.ymini, self.dymini, self.dyi))) > 1 or
2667 len(filter(None, (self.xmaxi, self.dxmaxi, self.dxi))) > 1 or
2668 len(filter(None, (self.ymaxi, self.dymaxi, self.dyi))) > 1):
2669 raise ValueError("multiple errorbar definition")
2670 if ((self.xi is None and self.dxi is not None) or
2671 (self.yi is None and self.dyi is not None) or
2672 (self.xi is None and self.dxmini is not None) or
2673 (self.yi is None and self.dymini is not None) or
2674 (self.xi is None and self.dxmaxi is not None) or
2675 (self.yi is None and self.dymaxi is not None)):
2676 raise ValueError("errorbar definition start value missing")
2677 self.xaxis = graph.axes[self.xkey]
2678 self.yaxis = graph.axes[self.ykey]
2680 def minmidmax(self, point, i, mini, maxi, di, dmini, dmaxi):
2681 min = max = mid = None
2682 try:
2683 mid = point[i] + 0.0
2684 except (TypeError, ValueError):
2685 pass
2686 try:
2687 if di is not None: min = point[i] - point[di]
2688 elif dmini is not None: min = point[i] - point[dmini]
2689 elif mini is not None: min = point[mini] + 0.0
2690 except (TypeError, ValueError):
2691 pass
2692 try:
2693 if di is not None: max = point[i] + point[di]
2694 elif dmaxi is not None: max = point[i] + point[dmaxi]
2695 elif maxi is not None: max = point[maxi] + 0.0
2696 except (TypeError, ValueError):
2697 pass
2698 if mid is not None:
2699 if min is not None and min > mid: raise ValueError("minimum error in errorbar")
2700 if max is not None and max < mid: raise ValueError("maximum error in errorbar")
2701 else:
2702 if min is not None and max is not None and min > max: raise ValueError("minimum/maximum error in errorbar")
2703 return min, mid, max
2705 def keyrange(self, points, i, mini, maxi, di, dmini, dmaxi):
2706 allmin = allmax = None
2707 if filter(None, (mini, maxi, di, dmini, dmaxi)) is not None:
2708 for point in points:
2709 min, mid, max = self.minmidmax(point, i, mini, maxi, di, dmini, dmaxi)
2710 if min is not None and (allmin is None or min < allmin): allmin = min
2711 if mid is not None and (allmin is None or mid < allmin): allmin = mid
2712 if mid is not None and (allmax is None or mid > allmax): allmax = mid
2713 if max is not None and (allmax is None or max > allmax): allmax = max
2714 else:
2715 for point in points:
2716 try:
2717 value = point[i] + 0.0
2718 if allmin is None or point[i] < allmin: allmin = point[i]
2719 if allmax is None or point[i] > allmax: allmax = point[i]
2720 except (TypeError, ValueError):
2721 pass
2722 return allmin, allmax
2724 def getranges(self, points):
2725 xmin, xmax = self.keyrange(points, self.xi, self.xmini, self.xmaxi, self.dxi, self.dxmini, self.dxmaxi)
2726 ymin, ymax = self.keyrange(points, self.yi, self.ymini, self.ymaxi, self.dyi, self.dymini, self.dymaxi)
2727 return {self.xkey: (xmin, xmax), self.ykey: (ymin, ymax)}
2729 def _drawerrorbar(self, graph, topleft, top, topright,
2730 left, center, right,
2731 bottomleft, bottom, bottomright, point=None):
2732 if left is not None:
2733 if right is not None:
2734 left1 = graph._addpos(*(left+(0, -self._errorsize)))
2735 left2 = graph._addpos(*(left+(0, self._errorsize)))
2736 right1 = graph._addpos(*(right+(0, -self._errorsize)))
2737 right2 = graph._addpos(*(right+(0, self._errorsize)))
2738 graph.stroke(path.path(path._moveto(*left1),
2739 graph._connect(*(left1+left2)),
2740 path._moveto(*left),
2741 graph._connect(*(left+right)),
2742 path._moveto(*right1),
2743 graph._connect(*(right1+right2))),
2744 *self.errorbarattrs)
2745 elif center is not None:
2746 left1 = graph._addpos(*(left+(0, -self._errorsize)))
2747 left2 = graph._addpos(*(left+(0, self._errorsize)))
2748 graph.stroke(path.path(path._moveto(*left1),
2749 graph._connect(*(left1+left2)),
2750 path._moveto(*left),
2751 graph._connect(*(left+center))),
2752 *self.errorbarattrs)
2753 else:
2754 left1 = graph._addpos(*(left+(0, -self._errorsize)))
2755 left2 = graph._addpos(*(left+(0, self._errorsize)))
2756 left3 = graph._addpos(*(left+(self._errorsize, 0)))
2757 graph.stroke(path.path(path._moveto(*left1),
2758 graph._connect(*(left1+left2)),
2759 path._moveto(*left),
2760 graph._connect(*(left+left3))),
2761 *self.errorbarattrs)
2762 if right is not None and left is None:
2763 if center is not None:
2764 right1 = graph._addpos(*(right+(0, -self._errorsize)))
2765 right2 = graph._addpos(*(right+(0, self._errorsize)))
2766 graph.stroke(path.path(path._moveto(*right1),
2767 graph._connect(*(right1+right2)),
2768 path._moveto(*right),
2769 graph._connect(*(right+center))),
2770 *self.errorbarattrs)
2771 else:
2772 right1 = graph._addpos(*(right+(0, -self._errorsize)))
2773 right2 = graph._addpos(*(right+(0, self._errorsize)))
2774 right3 = graph._addpos(*(right+(-self._errorsize, 0)))
2775 graph.stroke(path.path(path._moveto(*right1),
2776 graph._connect(*(right1+right2)),
2777 path._moveto(*right),
2778 graph._connect(*(right+right3))),
2779 *self.errorbarattrs)
2781 if bottom is not None:
2782 if top is not None:
2783 bottom1 = graph._addpos(*(bottom+(-self._errorsize, 0)))
2784 bottom2 = graph._addpos(*(bottom+(self._errorsize, 0)))
2785 top1 = graph._addpos(*(top+(-self._errorsize, 0)))
2786 top2 = graph._addpos(*(top+(self._errorsize, 0)))
2787 graph.stroke(path.path(path._moveto(*bottom1),
2788 graph._connect(*(bottom1+bottom2)),
2789 path._moveto(*bottom),
2790 graph._connect(*(bottom+top)),
2791 path._moveto(*top1),
2792 graph._connect(*(top1+top2))),
2793 *self.errorbarattrs)
2794 elif center is not None:
2795 bottom1 = graph._addpos(*(bottom+(-self._errorsize, 0)))
2796 bottom2 = graph._addpos(*(bottom+(self._errorsize, 0)))
2797 graph.stroke(path.path(path._moveto(*bottom1),
2798 graph._connect(*(bottom1+bottom2)),
2799 path._moveto(*bottom),
2800 graph._connect(*(bottom+center))),
2801 *self.errorbarattrs)
2802 else:
2803 bottom1 = graph._addpos(*(bottom+(-self._errorsize, 0)))
2804 bottom2 = graph._addpos(*(bottom+(self._errorsize, 0)))
2805 bottom3 = graph._addpos(*(bottom+(0, self._errorsize)))
2806 graph.stroke(path.path(path._moveto(*bottom1),
2807 graph._connect(*(bottom1+bottom2)),
2808 path._moveto(*bottom),
2809 graph._connect(*(bottom+bottom3))),
2810 *self.errorbarattrs)
2811 if top is not None and bottom is None:
2812 if center is not None:
2813 top1 = graph._addpos(*(top+(-self._errorsize, 0)))
2814 top2 = graph._addpos(*(top+(self._errorsize, 0)))
2815 graph.stroke(path.path(path._moveto(*top1),
2816 graph._connect(*(top1+top2)),
2817 path._moveto(*top),
2818 graph._connect(*(top+center))),
2819 *self.errorbarattrs)
2820 else:
2821 top1 = graph._addpos(*(top+(-self._errorsize, 0)))
2822 top2 = graph._addpos(*(top+(self._errorsize, 0)))
2823 top3 = graph._addpos(*(top+(0, -self._errorsize)))
2824 graph.stroke(path.path(path._moveto(*top1),
2825 graph._connect(*(top1+top2)),
2826 path._moveto(*top),
2827 graph._connect(*(top+top3))),
2828 *self.errorbarattrs)
2829 if bottomleft is not None:
2830 if topleft is not None and bottomright is None:
2831 bottomleft1 = graph._addpos(*(bottomleft+(self._errorsize, 0)))
2832 topleft1 = graph._addpos(*(topleft+(self._errorsize, 0)))
2833 graph.stroke(path.path(path._moveto(*bottomleft1),
2834 graph._connect(*(bottomleft1+bottomleft)),
2835 graph._connect(*(bottomleft+topleft)),
2836 graph._connect(*(topleft+topleft1))),
2837 *self.errorbarattrs)
2838 elif bottomright is not None and topleft is None:
2839 bottomleft1 = graph._addpos(*(bottomleft+(0, self._errorsize)))
2840 bottomright1 = graph._addpos(*(bottomright+(0, self._errorsize)))
2841 graph.stroke(path.path(path._moveto(*bottomleft1),
2842 graph._connect(*(bottomleft1+bottomleft)),
2843 graph._connect(*(bottomleft+bottomright)),
2844 graph._connect(*(bottomright+bottomright1))),
2845 *self.errorbarattrs)
2846 elif bottomright is None and topleft is None:
2847 bottomleft1 = graph._addpos(*(bottomleft+(self._errorsize, 0)))
2848 bottomleft2 = graph._addpos(*(bottomleft+(0, self._errorsize)))
2849 graph.stroke(path.path(path._moveto(*bottomleft1),
2850 graph._connect(*(bottomleft1+bottomleft)),
2851 graph._connect(*(bottomleft+bottomleft2))),
2852 *self.errorbarattrs)
2853 if topright is not None:
2854 if bottomright is not None and topleft is None:
2855 topright1 = graph._addpos(*(topright+(-self._errorsize, 0)))
2856 bottomright1 = graph._addpos(*(bottomright+(-self._errorsize, 0)))
2857 graph.stroke(path.path(path._moveto(*topright1),
2858 graph._connect(*(topright1+topright)),
2859 graph._connect(*(topright+bottomright)),
2860 graph._connect(*(bottomright+bottomright1))),
2861 *self.errorbarattrs)
2862 elif topleft is not None and bottomright is None:
2863 topright1 = graph._addpos(*(topright+(0, -self._errorsize)))
2864 topleft1 = graph._addpos(*(topleft+(0, -self._errorsize)))
2865 graph.stroke(path.path(path._moveto(*topright1),
2866 graph._connect(*(topright1+topright)),
2867 graph._connect(*(topright+topleft)),
2868 graph._connect(*(topleft+topleft1))),
2869 *self.errorbarattrs)
2870 elif topleft is None and bottomright is None:
2871 topright1 = graph._addpos(*(topright+(-self._errorsize, 0)))
2872 topright2 = graph._addpos(*(topright+(0, -self._errorsize)))
2873 graph.stroke(path.path(path._moveto(*topright1),
2874 graph._connect(*(topright1+topright)),
2875 graph._connect(*(topright+topright2))),
2876 *self.errorbarattrs)
2877 if bottomright is not None and bottomleft is None and topright is None:
2878 bottomright1 = graph._addpos(*(bottomright+(-self._errorsize, 0)))
2879 bottomright2 = graph._addpos(*(bottomright+(0, self._errorsize)))
2880 graph.stroke(path.path(path._moveto(*bottomright1),
2881 graph._connect(*(bottomright1+bottomright)),
2882 graph._connect(*(bottomright+bottomright2))),
2883 *self.errorbarattrs)
2884 if topleft is not None and bottomleft is None and topright is None:
2885 topleft1 = graph._addpos(*(topleft+(self._errorsize, 0)))
2886 topleft2 = graph._addpos(*(topleft+(0, -self._errorsize)))
2887 graph.stroke(path.path(path._moveto(*topleft1),
2888 graph._connect(*(topleft1+topleft)),
2889 graph._connect(*(topleft+topleft2))),
2890 *self.errorbarattrs)
2891 if bottomleft is not None and bottomright is not None and topright is not None and topleft is not None:
2892 graph.stroke(path.path(path._moveto(*bottomleft),
2893 graph._connect(*(bottomleft+bottomright)),
2894 graph._connect(*(bottomright+topright)),
2895 graph._connect(*(topright+topleft)),
2896 path.closepath()),
2897 *self.errorbarattrs)
2899 def _drawsymbol(self, graph, x, y, point=None):
2900 if x is not None and y is not None:
2901 graph.draw(path.path(*self.symbol(self, x, y)), *self.symbolattrs)
2903 def drawsymbol(self, graph, x, y, point=None):
2904 self._drawsymbol(graph, unit.topt(x), unit.topt(y), point)
2906 def drawpoints(self, graph, points):
2907 xaxismin, xaxismax = self.xaxis.getdatarange()
2908 yaxismin, yaxismax = self.yaxis.getdatarange()
2909 self.size = unit.length(_getattr(self.size_str), default_type="v")
2910 self._size = unit.topt(self.size)
2911 self.symbol = _getattr(self._symbol)
2912 self.symbolattrs = _getattrs(helper._ensuresequence(self._symbolattrs))
2913 self.errorbarattrs = _getattrs(helper._ensuresequence(self._errorbarattrs))
2914 self._errorsize = self.errorscale * self._size
2915 self.errorsize = self.errorscale * self.size
2916 self.lineattrs = _getattrs(helper._ensuresequence(self._lineattrs))
2917 if self._lineattrs is not None:
2918 clipcanvas = graph.clipcanvas()
2919 lineels = []
2920 haserror = filter(None, (self.xmini, self.ymini, self.xmaxi, self.ymaxi,
2921 self.dxi, self.dyi, self.dxmini, self.dymini, self.dxmaxi, self.dymaxi)) is not None
2922 moveto = 1
2923 for point in points:
2924 drawsymbol = 1
2925 xmin, x, xmax = self.minmidmax(point, self.xi, self.xmini, self.xmaxi, self.dxi, self.dxmini, self.dxmaxi)
2926 ymin, y, ymax = self.minmidmax(point, self.yi, self.ymini, self.ymaxi, self.dyi, self.dymini, self.dymaxi)
2927 if x is not None and x < xaxismin: drawsymbol = 0
2928 elif x is not None and x > xaxismax: drawsymbol = 0
2929 elif y is not None and y < yaxismin: drawsymbol = 0
2930 elif y is not None and y > yaxismax: drawsymbol = 0
2931 elif haserror:
2932 if xmin is not None and xmin < xaxismin: drawsymbol = 0
2933 elif xmax is not None and xmax < xaxismin: drawsymbol = 0
2934 elif xmax is not None and xmax > xaxismax: drawsymbol = 0
2935 elif xmin is not None and xmin > xaxismax: drawsymbol = 0
2936 elif ymin is not None and ymin < yaxismin: drawsymbol = 0
2937 elif ymax is not None and ymax < yaxismin: drawsymbol = 0
2938 elif ymax is not None and ymax > yaxismax: drawsymbol = 0
2939 elif ymin is not None and ymin > yaxismax: drawsymbol = 0
2940 xpos=ypos=topleft=top=topright=left=center=right=bottomleft=bottom=bottomright=None
2941 if x is not None and y is not None:
2942 try:
2943 center = xpos, ypos = graph._pos(x, y, xaxis=self.xaxis, yaxis=self.yaxis)
2944 except ValueError:
2945 pass
2946 if haserror:
2947 if y is not None:
2948 if xmin is not None: left = graph._pos(xmin, y, xaxis=self.xaxis, yaxis=self.yaxis)
2949 if xmax is not None: right = graph._pos(xmax, y, xaxis=self.xaxis, yaxis=self.yaxis)
2950 if x is not None:
2951 if ymax is not None: top = graph._pos(x, ymax, xaxis=self.xaxis, yaxis=self.yaxis)
2952 if ymin is not None: bottom = graph._pos(x, ymin, xaxis=self.xaxis, yaxis=self.yaxis)
2953 if x is None or y is None:
2954 if ymax is not None:
2955 if xmin is not None: topleft = graph._pos(xmin, ymax, xaxis=self.xaxis, yaxis=self.yaxis)
2956 if xmax is not None: topright = graph._pos(xmax, ymax, xaxis=self.xaxis, yaxis=self.yaxis)
2957 if ymin is not None:
2958 if xmin is not None: bottomleft = graph._pos(xmin, ymin, xaxis=self.xaxis, yaxis=self.yaxis)
2959 if xmax is not None: bottomright = graph._pos(xmax, ymin, xaxis=self.xaxis, yaxis=self.yaxis)
2960 if drawsymbol:
2961 if self._errorbarattrs is not None and haserror:
2962 self._drawerrorbar(graph, topleft, top, topright,
2963 left, center, right,
2964 bottomleft, bottom, bottomright, point)
2965 if self._symbolattrs is not None:
2966 self._drawsymbol(graph, xpos, ypos, point)
2967 if xpos is not None and ypos is not None:
2968 if moveto:
2969 lineels.append(path._moveto(xpos, ypos))
2970 moveto = 0
2971 else:
2972 lineels.append(path._lineto(xpos, ypos))
2973 else:
2974 moveto = 1
2975 self.path = path.path(*lineels)
2976 if self._lineattrs is not None:
2977 clipcanvas.stroke(self.path, *self.lineattrs)
2980 class changesymbol(changesequence): pass
2983 class _changesymbolcross(changesymbol):
2984 defaultsequence = (symbol.cross, symbol.plus, symbol.square, symbol.triangle, symbol.circle, symbol.diamond)
2987 class _changesymbolplus(changesymbol):
2988 defaultsequence = (symbol.plus, symbol.square, symbol.triangle, symbol.circle, symbol.diamond, symbol.cross)
2991 class _changesymbolsquare(changesymbol):
2992 defaultsequence = (symbol.square, symbol.triangle, symbol.circle, symbol.diamond, symbol.cross, symbol.plus)
2995 class _changesymboltriangle(changesymbol):
2996 defaultsequence = (symbol.triangle, symbol.circle, symbol.diamond, symbol.cross, symbol.plus, symbol.square)
2999 class _changesymbolcircle(changesymbol):
3000 defaultsequence = (symbol.circle, symbol.diamond, symbol.cross, symbol.plus, symbol.square, symbol.triangle)
3003 class _changesymboldiamond(changesymbol):
3004 defaultsequence = (symbol.diamond, symbol.cross, symbol.plus, symbol.square, symbol.triangle, symbol.circle)
3007 class _changesymbolsquaretwice(changesymbol):
3008 defaultsequence = (symbol.square, symbol.square, symbol.triangle, symbol.triangle,
3009 symbol.circle, symbol.circle, symbol.diamond, symbol.diamond)
3012 class _changesymboltriangletwice(changesymbol):
3013 defaultsequence = (symbol.triangle, symbol.triangle, symbol.circle, symbol.circle,
3014 symbol.diamond, symbol.diamond, symbol.square, symbol.square)
3017 class _changesymbolcircletwice(changesymbol):
3018 defaultsequence = (symbol.circle, symbol.circle, symbol.diamond, symbol.diamond,
3019 symbol.square, symbol.square, symbol.triangle, symbol.triangle)
3022 class _changesymboldiamondtwice(changesymbol):
3023 defaultsequence = (symbol.diamond, symbol.diamond, symbol.square, symbol.square,
3024 symbol.triangle, symbol.triangle, symbol.circle, symbol.circle)
3027 changesymbol.cross = _changesymbolcross
3028 changesymbol.plus = _changesymbolplus
3029 changesymbol.square = _changesymbolsquare
3030 changesymbol.triangle = _changesymboltriangle
3031 changesymbol.circle = _changesymbolcircle
3032 changesymbol.diamond = _changesymboldiamond
3033 changesymbol.squaretwice = _changesymbolsquaretwice
3034 changesymbol.triangletwice = _changesymboltriangletwice
3035 changesymbol.circletwice = _changesymbolcircletwice
3036 changesymbol.diamondtwice = _changesymboldiamondtwice
3039 class line(symbol):
3041 def __init__(self, lineattrs=helper._nodefault):
3042 if lineattrs is helper._nodefault:
3043 lineattrs = changelinestyle()
3044 symbol.__init__(self, symbolattrs=None, errorbarattrs=None, lineattrs=lineattrs)
3047 class rect(symbol):
3049 def __init__(self, gradient=color.gradient.Gray):
3050 self.gradient = gradient
3051 self.colorindex = None
3052 symbol.__init__(self, symbolattrs=None, errorbarattrs=(), lineattrs=None)
3054 def iterate(self):
3055 raise RuntimeError("style is not iterateable")
3057 def othercolumnkey(self, key, index):
3058 if key == "color":
3059 self.colorindex = index
3060 else:
3061 symbol.othercolumnkey(self, key, index)
3063 def _drawerrorbar(self, graph, topleft, top, topright,
3064 left, center, right,
3065 bottomleft, bottom, bottomright, point=None):
3066 color = point[self.colorindex]
3067 if color is not None:
3068 if color != self.lastcolor:
3069 self.rectclipcanvas.set(self.gradient.getcolor(color))
3070 if bottom is not None and left is not None:
3071 bottomleft = left[0], bottom[1]
3072 if bottom is not None and right is not None:
3073 bottomright = right[0], bottom[1]
3074 if top is not None and right is not None:
3075 topright = right[0], top[1]
3076 if top is not None and left is not None:
3077 topleft = left[0], top[1]
3078 if bottomleft is not None and bottomright is not None and topright is not None and topleft is not None:
3079 self.rectclipcanvas.fill(path.path(path._moveto(*bottomleft),
3080 graph._connect(*(bottomleft+bottomright)),
3081 graph._connect(*(bottomright+topright)),
3082 graph._connect(*(topright+topleft)),
3083 path.closepath()))
3085 def drawpoints(self, graph, points):
3086 if self.colorindex is None:
3087 raise RuntimeError("column 'color' not set")
3088 self.lastcolor = None
3089 self.rectclipcanvas = graph.clipcanvas()
3090 symbol.drawpoints(self, graph, points)
3094 class text(symbol):
3096 def __init__(self, textdx="0", textdy="0.3 cm", textattrs=tex.halign.center, **args):
3097 self.textindex = None
3098 self.textdx_str = textdx
3099 self.textdy_str = textdy
3100 self._textattrs = textattrs
3101 symbol.__init__(self, **args)
3103 def iteratedict(self):
3104 result = symbol.iteratedict()
3105 result["textattrs"] = _iterateattr(self._textattrs)
3106 return result
3108 def iterate(self):
3109 return textsymbol(**self.iteratedict())
3111 def othercolumnkey(self, key, index):
3112 if key == "text":
3113 self.textindex = index
3114 else:
3115 symbol.othercolumnkey(self, key, index)
3117 def _drawsymbol(self, graph, x, y, point=None):
3118 symbol._drawsymbol(self, graph, x, y, point)
3119 if None not in (x, y, point[self.textindex], self._textattrs):
3120 graph.tex._text(x + self._textdx, y + self._textdy, str(point[self.textindex]), *helper._ensuresequence(self.textattrs))
3122 def drawpoints(self, graph, points):
3123 self.textdx = unit.length(_getattr(self.textdx_str), default_type="v")
3124 self.textdy = unit.length(_getattr(self.textdy_str), default_type="v")
3125 self._textdx = unit.topt(self.textdx)
3126 self._textdy = unit.topt(self.textdy)
3127 if self._textattrs is not None:
3128 self.textattrs = _getattr(self._textattrs)
3129 if self.textindex is None:
3130 raise RuntimeError("column 'text' not set")
3131 symbol.drawpoints(self, graph, points)
3134 class arrow(symbol):
3136 def __init__(self, linelength="0.2 cm", arrowattrs=(), arrowsize="0.1 cm", arrowdict={}, epsilon=1e-10):
3137 self.linelength_str = linelength
3138 self.arrowsize_str = arrowsize
3139 self.arrowattrs = arrowattrs
3140 self.arrowdict = arrowdict
3141 self.epsilon = epsilon
3142 self.sizeindex = self.angleindex = None
3143 symbol.__init__(self, symbolattrs=(), errorbarattrs=None, lineattrs=None)
3145 def iterate(self):
3146 raise RuntimeError("style is not iterateable")
3148 def othercolumnkey(self, key, index):
3149 if key == "size":
3150 self.sizeindex = index
3151 elif key == "angle":
3152 self.angleindex = index
3153 else:
3154 symbol.othercolumnkey(self, key, index)
3156 def _drawsymbol(self, graph, x, y, point=None):
3157 if None not in (x, y, point[self.angleindex], point[self.sizeindex], self.arrowattrs, self.arrowdict):
3158 if point[self.sizeindex] > self.epsilon:
3159 dx, dy = math.cos(point[self.angleindex]*math.pi/180.0), math.sin(point[self.angleindex]*math.pi/180)
3160 x1 = unit.t_pt(x)-0.5*dx*self.linelength*point[self.sizeindex]
3161 y1 = unit.t_pt(y)-0.5*dy*self.linelength*point[self.sizeindex]
3162 x2 = unit.t_pt(x)+0.5*dx*self.linelength*point[self.sizeindex]
3163 y2 = unit.t_pt(y)+0.5*dy*self.linelength*point[self.sizeindex]
3164 graph.stroke(path.line(x1, y1, x2, y2),
3165 canvas.earrow(self.arrowsize*point[self.sizeindex],
3166 **self.arrowdict),
3167 *helper._ensuresequence(self.arrowattrs))
3169 def drawpoints(self, graph, points):
3170 self.arrowsize = unit.length(_getattr(self.arrowsize_str), default_type="v")
3171 self.linelength = unit.length(_getattr(self.linelength_str), default_type="v")
3172 self._arrowsize = unit.topt(self.arrowsize)
3173 self._linelength = unit.topt(self.linelength)
3174 if self.sizeindex is None:
3175 raise RuntimeError("column 'size' not set")
3176 if self.angleindex is None:
3177 raise RuntimeError("column 'angle' not set")
3178 symbol.drawpoints(self, graph, points)
3181 class _bariterator(changeattr):
3183 def attr(self, index):
3184 return index, self.counter
3187 class bar:
3189 def __init__(self, fromzero=1, stacked=0, xbar=0,
3190 barattrs=(canvas.stroked(color.gray.black), changecolor.Rainbow()),
3191 _bariterator=_bariterator(), _previousbar=None):
3192 self.fromzero = fromzero
3193 self.stacked = stacked
3194 self.xbar = xbar
3195 self._barattrs = barattrs
3196 self.bariterator = _bariterator
3197 self.previousbar = _previousbar
3199 def iteratedict(self):
3200 result = {}
3201 result["barattrs"] = _iterateattrs(self._barattrs)
3202 return result
3204 def iterate(self):
3205 return bar(fromzero=self.fromzero, stacked=self.stacked, xbar=self.xbar,
3206 _bariterator=_iterateattr(self.bariterator), _previousbar=self, **self.iteratedict())
3208 def setcolumns(self, graph, columns):
3209 def checkpattern(key, index, pattern, iskey, isindex):
3210 if key is not None:
3211 match = pattern.match(key)
3212 if match:
3213 if isindex is not None: raise ValueError("multiple key specification")
3214 if iskey is not None and iskey != match.groups()[0]: raise ValueError("inconsistent key names")
3215 key = None
3216 iskey = match.groups()[0]
3217 isindex = index
3218 return key, iskey, isindex
3220 xkey = ykey = None
3221 if len(graph.Names) != 2: raise TypeError("style not applicable in graph")
3222 XPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.Names[0])
3223 YPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.Names[1])
3224 xi = yi = None
3225 for key, index in columns.items():
3226 key, xkey, xi = checkpattern(key, index, XPattern, xkey, xi)
3227 key, ykey, yi = checkpattern(key, index, YPattern, ykey, yi)
3228 if key is not None:
3229 self.othercolumnkey(key, index)
3230 if None in (xkey, ykey): raise ValueError("incomplete axis specification")
3231 if self.xbar:
3232 self.nkey, self.ni = ykey, yi
3233 self.vkey, self.vi = xkey, xi
3234 else:
3235 self.nkey, self.ni = xkey, xi
3236 self.vkey, self.vi = ykey, yi
3237 self.naxis, self.vaxis = graph.axes[self.nkey], graph.axes[self.vkey]
3239 def getranges(self, points):
3240 index, count = _getattr(self.bariterator)
3241 if count != 1 and self.stacked != 1:
3242 if self.stacked > 1:
3243 index = divmod(index, self.stacked)[0] # TODO: use this
3245 vmin = vmax = None
3246 for point in points:
3247 try:
3248 v = point[self.vi] + 0.0
3249 if vmin is None or v < vmin: vmin = v
3250 if vmax is None or v > vmax: vmax = v
3251 except (TypeError, ValueError):
3252 pass
3253 else:
3254 if count == 1:
3255 self.naxis.setname(point[self.ni])
3256 else:
3257 self.naxis.setname(point[self.ni], index)
3258 if self.fromzero:
3259 if vmin > 0: vmin = 0
3260 if vmax < 0: vmax = 0
3261 return {self.vkey: (vmin, vmax)}
3263 def drawpoints(self, graph, points):
3264 index, count = _getattr(self.bariterator)
3265 dostacked = (self.stacked != 0 and
3266 (self.stacked == 1 or divmod(index, self.stacked)[1]) and
3267 (self.stacked != 1 or index))
3268 if self.stacked > 1:
3269 index = divmod(index, self.stacked)[0]
3270 vmin, vmax = self.vaxis.getdatarange()
3271 self.barattrs = _getattrs(helper._ensuresequence(self._barattrs))
3272 if self.stacked:
3273 self.stackedvalue = {}
3274 for point in points:
3275 try:
3276 n = point[self.ni]
3277 v = point[self.vi]
3278 if self.stacked:
3279 self.stackedvalue[n] = v
3280 if count != 1 and self.stacked != 1:
3281 minid = (n, index, 0)
3282 maxid = (n, index, 1)
3283 else:
3284 minid = (n, 0)
3285 maxid = (n, 1)
3286 if self.xbar:
3287 x1pos, y1pos = graph._pos(v, minid, xaxis=self.vaxis, yaxis=self.naxis)
3288 x2pos, y2pos = graph._pos(v, maxid, xaxis=self.vaxis, yaxis=self.naxis)
3289 else:
3290 x1pos, y1pos = graph._pos(minid, v, xaxis=self.naxis, yaxis=self.vaxis)
3291 x2pos, y2pos = graph._pos(maxid, v, xaxis=self.naxis, yaxis=self.vaxis)
3292 if dostacked:
3293 if self.xbar:
3294 x3pos, y3pos = graph._pos(self.previousbar.stackedvalue[n], maxid, xaxis=self.vaxis, yaxis=self.naxis)
3295 x4pos, y4pos = graph._pos(self.previousbar.stackedvalue[n], minid, xaxis=self.vaxis, yaxis=self.naxis)
3296 else:
3297 x3pos, y3pos = graph._pos(maxid, self.previousbar.stackedvalue[n], xaxis=self.naxis, yaxis=self.vaxis)
3298 x4pos, y4pos = graph._pos(minid, self.previousbar.stackedvalue[n], xaxis=self.naxis, yaxis=self.vaxis)
3299 else:
3300 if self.fromzero:
3301 if self.xbar:
3302 x3pos, y3pos = graph._pos(0, maxid, xaxis=self.vaxis, yaxis=self.naxis)
3303 x4pos, y4pos = graph._pos(0, minid, xaxis=self.vaxis, yaxis=self.naxis)
3304 else:
3305 x3pos, y3pos = graph._pos(maxid, 0, xaxis=self.naxis, yaxis=self.vaxis)
3306 x4pos, y4pos = graph._pos(minid, 0, xaxis=self.naxis, yaxis=self.vaxis)
3307 else:
3308 x3pos, y3pos = self.naxis._vtickpoint(self.naxis, self.naxis.convert(maxid))
3309 x4pos, y4pos = self.naxis._vtickpoint(self.naxis, self.naxis.convert(minid))
3310 graph.fill(path.path(path._moveto(x1pos, y1pos),
3311 graph._connect(x1pos, y1pos, x2pos, y2pos),
3312 graph._connect(x2pos, y2pos, x3pos, y3pos),
3313 graph._connect(x3pos, y3pos, x4pos, y4pos),
3314 graph._connect(x4pos, y4pos, x1pos, y1pos), # no closepath (might not be straight)
3315 path.closepath()), *self.barattrs)
3316 except (TypeError, ValueError): pass
3319 #class surface:
3321 # def setcolumns(self, graph, columns):
3322 # self.columns = columns
3324 # def getranges(self, points):
3325 # return {"x": (0, 10), "y": (0, 10), "z": (0, 1)}
3327 # def drawpoints(self, graph, points):
3328 # pass
3332 ################################################################################
3333 # data
3334 ################################################################################
3337 import data as datamodule # well, ugly ???
3339 class data:
3341 defaultstyle = symbol
3343 def __init__(self, file, **columns):
3344 if helper._isstring(file):
3345 self.data = datamodule.datafile(file)
3346 else:
3347 self.data = file
3348 self.columns = {}
3349 usedkeys = []
3350 for key, column in columns.items():
3351 try:
3352 self.columns[key] = self.data.getcolumnno(column)
3353 except datamodule.ColumnError:
3354 self.columns[key] = len(self.data.titles)
3355 usedkeys.extend(self.data._addcolumn(column, **columns))
3356 for usedkey in usedkeys:
3357 if usedkey in self.columns.keys():
3358 del self.columns[usedkey]
3360 def setstyle(self, graph, style):
3361 self.style = style
3362 self.style.setcolumns(graph, self.columns)
3364 def getranges(self):
3365 return self.style.getranges(self.data.data)
3367 def setranges(self, ranges):
3368 pass
3370 def draw(self, graph):
3371 self.style.drawpoints(graph, self.data.data)
3374 class function:
3376 defaultstyle = line
3378 def __init__(self, expression, min=None, max=None, points=100, parser=mathtree.parser(), extern=None):
3379 self.min = min
3380 self.max = max
3381 self.points = points
3382 self.extern = extern
3383 self.result, expression = expression.split("=")
3384 self.mathtree = parser.parse(expression, extern=self.extern)
3385 if extern is None:
3386 self.variable, = self.mathtree.VarList()
3387 else:
3388 self.variable = None
3389 for variable in self.mathtree.VarList():
3390 if variable not in self.extern.keys():
3391 if self.variable is None:
3392 self.variable = variable
3393 else:
3394 raise ValueError("multiple variables found (identifiers might be externally defined)")
3395 if self.variable is None:
3396 raise ValueError("no variable found (identifiers are all defined externally)")
3397 self.evalranges = 0
3399 def setstyle(self, graph, style):
3400 self.xaxis = graph.axes[self.variable]
3401 self.style = style
3402 self.style.setcolumns(graph, {self.variable: 0, self.result: 1})
3404 def getranges(self):
3405 if self.evalranges:
3406 return self.style.getranges(self.data)
3407 if None not in (self.min, self.max):
3408 return {self.variable: (self.min, self.max)}
3410 def setranges(self, ranges):
3411 if ranges.has_key(self.variable):
3412 min, max = ranges[self.variable]
3413 if self.min is not None: min = self.min
3414 if self.max is not None: max = self.max
3415 vmin = self.xaxis.convert(min)
3416 vmax = self.xaxis.convert(max)
3417 self.data = []
3418 for i in range(self.points):
3419 x = self.xaxis.invert(vmin + (vmax-vmin)*i / (self.points-1.0))
3420 try:
3421 y = self.mathtree.Calc({self.variable: x}, self.extern)
3422 except (ArithmeticError, ValueError):
3423 y = None
3424 self.data.append((x, y))
3425 self.evalranges = 1
3427 def draw(self, graph):
3428 self.style.drawpoints(graph, self.data)
3431 class paramfunction:
3433 defaultstyle = line
3435 def __init__(self, varname, min, max, expression, points=100, parser=mathtree.parser(), extern=None):
3436 self.varname = varname
3437 self.min = min
3438 self.max = max
3439 self.points = points
3440 self.expression = {}
3441 self.mathtrees = {}
3442 varlist, expressionlist = expression.split("=")
3443 parsestr = mathtree.ParseStr(expressionlist)
3444 for key in varlist.split(","):
3445 key = key.strip()
3446 if self.mathtrees.has_key(key):
3447 raise ValueError("multiple assignment in tuple")
3448 try:
3449 self.mathtrees[key] = parser.ParseMathTree(parsestr, extern)
3450 break
3451 except mathtree.CommaFoundMathTreeParseError, e:
3452 self.mathtrees[key] = e.MathTree
3453 else:
3454 raise ValueError("unpack tuple of wrong size")
3455 if len(varlist.split(",")) != len(self.mathtrees.keys()):
3456 raise ValueError("unpack tuple of wrong size")
3457 self.data = []
3458 for i in range(self.points):
3459 value = self.min + (self.max-self.min)*i / (self.points-1.0)
3460 line = []
3461 for key, tree in self.mathtrees.items():
3462 line.append(tree.Calc({self.varname: value}, extern))
3463 self.data.append(line)
3465 def setstyle(self, graph, style):
3466 self.style = style
3467 columns = {}
3468 for key, index in zip(self.mathtrees.keys(), xrange(sys.maxint)):
3469 columns[key] = index
3470 self.style.setcolumns(graph, columns)
3472 def getranges(self):
3473 return self.style.getranges(self.data)
3475 def setranges(self, ranges):
3476 pass
3478 def draw(self, graph):
3479 self.style.drawpoints(graph, self.data)