CHANGES
[PyX/mjg.git] / pyx / graph.py
blob37ae73db700c4e0141a73ad9e9d120e63ac5b394
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, unit, mathtree, trafo, attrlist, color, helper, text
26 import text as textmodule
27 import data as datamodule
30 goldenrule = 0.5 * (math.sqrt(5) + 1)
33 ################################################################################
34 # maps
35 ################################################################################
37 class _Imap:
38 "maps convert a value into another value by bijective transformation f"
40 def convert(self, x):
41 "returns f(x)"
43 def invert(self, y):
44 "returns f^-1(y) where f^-1 is the inverse transformation (x=f^-1(f(x)) for all x)"
46 def setbasepoint(self, basepoints):
47 """set basepoints for the convertions
48 basepoints are tuples (x, y) with y == f(x) and x == f^-1(y)
49 the number of basepoints needed might depend on the transformation
50 usually two pairs are needed like for linear maps, logarithmic maps, etc."""
53 class _linmap:
54 "linear mapping"
55 __implements__ = _Imap
57 def setbasepoints(self, basepoints):
58 self.dydx = (basepoints[1][1] - basepoints[0][1]) / float(basepoints[1][0] - basepoints[0][0])
59 self.dxdy = (basepoints[1][0] - basepoints[0][0]) / float(basepoints[1][1] - basepoints[0][1])
60 self.x1 = basepoints[0][0]
61 self.y1 = basepoints[0][1]
62 return self
64 def convert(self, value):
65 return self.y1 + self.dydx * (value - self.x1)
67 def invert(self, value):
68 return self.x1 + self.dxdy * (value - self.y1)
71 class _logmap:
72 "logarithmic mapping"
73 __implements__ = _Imap
75 def setbasepoints(self, basepoints):
76 self.dydx = ((basepoints[1][1] - basepoints[0][1]) /
77 float(math.log(basepoints[1][0]) - math.log(basepoints[0][0])))
78 self.dxdy = ((math.log(basepoints[1][0]) - math.log(basepoints[0][0])) /
79 float(basepoints[1][1] - basepoints[0][1]))
80 self.x1 = math.log(basepoints[0][0])
81 self.y1 = basepoints[0][1]
82 return self
84 def convert(self, value):
85 return self.y1 + self.dydx * (math.log(value) - self.x1)
87 def invert(self, value):
88 return math.exp(self.x1 + self.dxdy * (value - self.y1))
92 ################################################################################
93 # tick lists = partitions
94 ################################################################################
97 class frac:
98 "fraction type for rational arithmetics"
100 def __init__(self, enum, denom, power=None):
101 "for power!=None: frac=(enum/denom)**power"
102 if not helper.isinteger(enum) or not helper.isinteger(denom): raise TypeError("integer type expected")
103 if not denom: raise ZeroDivisionError("zero denominator")
104 if power != None:
105 if not helper.isinteger(power): raise TypeError("integer type expected")
106 if power >= 0:
107 self.enum = long(enum) ** power
108 self.denom = long(denom) ** power
109 else:
110 self.enum = long(denom) ** (-power)
111 self.denom = long(enum) ** (-power)
112 else:
113 self.enum = enum
114 self.denom = denom
116 def __cmp__(self, other):
117 if other is None:
118 return 1
119 return cmp(self.enum * other.denom, other.enum * self.denom)
121 def __mul__(self, other):
122 return frac(self.enum * other.enum, self.denom * other.denom)
124 def __float__(self):
125 return float(self.enum) / self.denom
127 def __str__(self):
128 return "%i/%i" % (self.enum, self.denom)
130 def __repr__(self):
131 return "frac(%r, %r)" % (self.enum, self.denom) # I want to see the "L"
134 def _ensurefrac(arg):
135 "ensure frac by converting a string to frac"
137 def createfrac(str):
138 commaparts = str.split(".")
139 for part in commaparts:
140 if not part.isdigit(): raise ValueError("non-digits found in '%s'" % part)
141 if len(commaparts) == 1:
142 return frac(long(commaparts[0]), 1)
143 elif len(commaparts) == 2:
144 result = frac(1, 10l, power=len(commaparts[1]))
145 result.enum = long(commaparts[0])*result.denom + long(commaparts[1])
146 return result
147 else: raise ValueError("multiple '.' found in '%s'" % str)
149 if helper.isstring(arg):
150 fraction = arg.split("/")
151 if len(fraction) > 2: raise ValueError("multiple '/' found in '%s'" % arg)
152 value = createfrac(fraction[0])
153 if len(fraction) == 2:
154 value2 = createfrac(fraction[1])
155 value = frac(value.enum * value2.denom, value.denom * value2.enum)
156 return value
157 if not isinstance(arg, frac): raise ValueError("can't convert argument to frac")
158 return arg
161 class tick(frac):
162 "a tick is a frac enhanced by a ticklevel, a labellevel and a text (they all might be None)"
164 def __init__(self, enum, denom, ticklevel=None, labellevel=None, text=None):
165 frac.__init__(self, enum, denom)
166 self.ticklevel = ticklevel
167 self.labellevel = labellevel
168 self.text = text
170 def merge(self, other):
171 if self.ticklevel is None or (other.ticklevel is not None and other.ticklevel < self.ticklevel):
172 self.ticklevel = other.ticklevel
173 if self.labellevel is None or (other.labellevel is not None and other.labellevel < self.labellevel):
174 self.labellevel = other.labellevel
175 if self.text is None:
176 self.text = other.text
178 def __repr__(self):
179 return "tick(%r, %r, %s, %s, %s)" % (self.enum, self.denom, self.ticklevel, self.labellevel, self.text)
182 def _mergeticklists(list1, list2):
183 """return a merged list of ticks out of list1 and list2
184 lists have to be ordered (returned list is also ordered)
185 caution: side effects (input lists might be altered)"""
186 # TODO: improve this using bisect
187 i = 0
188 j = 0
189 try:
190 while 1: # we keep on going until we reach an index error
191 while list2[j] < list1[i]: # insert tick
192 list1.insert(i, list2[j])
193 i += 1
194 j += 1
195 if list2[j] == list1[i]: # merge tick
196 list1[i].merge(list2[j])
197 j += 1
198 i += 1
199 except IndexError:
200 if j < len(list2):
201 list1 += list2[j:]
202 return list1
205 def _mergetexts(ticks, texts):
206 "merges texts into ticks"
207 if helper.issequenceofsequences(texts):
208 for text, level in zip(texts, xrange(sys.maxint)):
209 usetext = helper.ensuresequence(text)
210 i = 0
211 for tick in ticks:
212 if tick.labellevel == level:
213 tick.text = usetext[i]
214 i += 1
215 if i != len(usetext):
216 raise IndexError("wrong sequence length of texts at level %i" % level)
217 elif texts is not None:
218 usetext = helper.ensuresequence(texts)
219 i = 0
220 for tick in ticks:
221 if tick.labellevel == 0:
222 tick.text = usetext[i]
223 i += 1
224 if i != len(usetext):
225 raise IndexError("wrong sequence length of texts")
228 class manualpart:
230 def __init__(self, ticks=None, labels=None, texts=None, mix=()):
231 self.multipart = 0
232 if ticks is None and labels is not None:
233 self.ticks = helper.ensuresequence(helper.getsequenceno(labels, 0))
234 else:
235 self.ticks = ticks
237 if labels is None and ticks is not None:
238 self.labels = helper.ensuresequence(helper.getsequenceno(ticks, 0))
239 else:
240 self.labels = labels
241 self.texts = texts
242 self.mix = mix
245 def checkfraclist(self, *fracs):
246 if not len(fracs): return ()
247 sorted = list(fracs)
248 sorted.sort()
249 last = sorted[0]
250 for item in sorted[1:]:
251 if last == item:
252 raise ValueError("duplicate entry found")
253 last = item
254 return sorted
256 def part(self):
257 ticks = list(self.mix)
258 if helper.issequenceofsequences(self.ticks):
259 for fracs, level in zip(self.ticks, xrange(sys.maxint)):
260 ticks = _mergeticklists(ticks, [tick(frac.enum, frac.denom, ticklevel = level)
261 for frac in self.checkfraclist(*map(_ensurefrac, helper.ensuresequence(fracs)))])
262 else:
263 ticks = _mergeticklists(ticks, [tick(frac.enum, frac.denom, ticklevel = 0)
264 for frac in self.checkfraclist(*map(_ensurefrac, helper.ensuresequence(self.ticks)))])
266 if helper.issequenceofsequences(self.labels):
267 for fracs, level in zip(self.labels, xrange(sys.maxint)):
268 ticks = _mergeticklists(ticks, [tick(frac.enum, frac.denom, labellevel = level)
269 for frac in self.checkfraclist(*map(_ensurefrac, helper.ensuresequence(fracs)))])
270 else:
271 ticks = _mergeticklists(ticks, [tick(frac.enum, frac.denom, labellevel = 0)
272 for frac in self.checkfraclist(*map(_ensurefrac, helper.ensuresequence(self.labels)))])
274 _mergetexts(ticks, self.texts)
276 return ticks
278 def defaultpart(self, min, max, extendmin, extendmax):
279 return self.part()
282 class linpart:
284 def __init__(self, ticks=None, labels=None, texts=None, extendtick=0, extendlabel=None, epsilon=1e-10, mix=()):
285 self.multipart = 0
286 if ticks is None and labels is not None:
287 self.ticks = (_ensurefrac(helper.ensuresequence(labels)[0]),)
288 else:
289 self.ticks = map(_ensurefrac, helper.ensuresequence(ticks))
290 if labels is None and ticks is not None:
291 self.labels = (_ensurefrac(helper.ensuresequence(ticks)[0]),)
292 else:
293 self.labels = map(_ensurefrac, helper.ensuresequence(labels))
294 self.texts = texts
295 self.extendtick = extendtick
296 self.extendlabel = extendlabel
297 self.epsilon = epsilon
298 self.mix = mix
300 def extendminmax(self, min, max, frac, extendmin, extendmax):
301 if extendmin:
302 min = float(frac) * math.floor(min / float(frac) + self.epsilon)
303 if extendmax:
304 max = float(frac) * math.ceil(max / float(frac) - self.epsilon)
305 return min, max
307 def getticks(self, min, max, frac, ticklevel=None, labellevel=None):
308 imin = int(math.ceil(min / float(frac) - 0.5 * self.epsilon))
309 imax = int(math.floor(max / float(frac) + 0.5 * self.epsilon))
310 ticks = []
311 for i in range(imin, imax + 1):
312 ticks.append(tick(long(i) * frac.enum, frac.denom, ticklevel = ticklevel, labellevel = labellevel))
313 return ticks
315 def defaultpart(self, min, max, extendmin, extendmax):
316 if self.extendtick is not None and len(self.ticks) > self.extendtick:
317 min, max = self.extendminmax(min, max, self.ticks[self.extendtick], extendmin, extendmax)
318 if self.extendlabel is not None and len(self.labels) > self.extendlabel:
319 min, max = self.extendminmax(min, max, self.labels[self.extendlabel], extendmin, extendmax)
321 ticks = list(self.mix)
322 for i in range(len(self.ticks)):
323 ticks = _mergeticklists(ticks, self.getticks(min, max, self.ticks[i], ticklevel = i))
324 for i in range(len(self.labels)):
325 ticks = _mergeticklists(ticks, self.getticks(min, max, self.labels[i], labellevel = i))
327 _mergetexts(ticks, self.texts)
329 return ticks
332 class autolinpart:
334 defaultlist = ((frac(1, 1), frac(1, 2)),
335 (frac(2, 1), frac(1, 1)),
336 (frac(5, 2), frac(5, 4)),
337 (frac(5, 1), frac(5, 2)))
339 def __init__(self, list=defaultlist, extendtick=0, epsilon=1e-10, mix=()):
340 self.multipart = 1
341 self.list = list
342 self.extendtick = extendtick
343 self.epsilon = epsilon
344 self.mix = mix
346 def defaultpart(self, min, max, extendmin, extendmax):
347 base = frac(10L, 1, int(math.log(max - min) / math.log(10)))
348 ticks = self.list[0]
349 useticks = [tick * base for tick in ticks]
350 self.lesstickindex = self.moretickindex = 0
351 self.lessbase = frac(base.enum, base.denom)
352 self.morebase = frac(base.enum, base.denom)
353 self.min, self.max, self.extendmin, self.extendmax = min, max, extendmin, extendmax
354 part = linpart(ticks=useticks, extendtick=self.extendtick, epsilon=self.epsilon, mix=self.mix)
355 return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
357 def lesspart(self):
358 if self.lesstickindex < len(self.list) - 1:
359 self.lesstickindex += 1
360 else:
361 self.lesstickindex = 0
362 self.lessbase.enum *= 10
363 ticks = self.list[self.lesstickindex]
364 useticks = [tick * self.lessbase for tick in ticks]
365 part = linpart(ticks=useticks, extendtick=self.extendtick, epsilon=self.epsilon, mix=self.mix)
366 return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
368 def morepart(self):
369 if self.moretickindex:
370 self.moretickindex -= 1
371 else:
372 self.moretickindex = len(self.list) - 1
373 self.morebase.denom *= 10
374 ticks = self.list[self.moretickindex]
375 useticks = [tick * self.morebase for tick in ticks]
376 part = linpart(ticks=useticks, extendtick=self.extendtick, epsilon=self.epsilon, mix=self.mix)
377 return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
380 class shiftfracs:
382 def __init__(self, shift, *fracs):
383 self.shift = shift
384 self.fracs = fracs
387 class logpart(linpart):
389 shift5fracs1 = shiftfracs(100000, frac(1, 1))
390 shift4fracs1 = shiftfracs(10000, frac(1, 1))
391 shift3fracs1 = shiftfracs(1000, frac(1, 1))
392 shift2fracs1 = shiftfracs(100, frac(1, 1))
393 shiftfracs1 = shiftfracs(10, frac(1, 1))
394 shiftfracs125 = shiftfracs(10, frac(1, 1), frac(2, 1), frac(5, 1))
395 shiftfracs1to9 = shiftfracs(10, *list(map(lambda x: frac(x, 1), range(1, 10))))
396 # ^- we always include 1 in order to get extendto(tick|label)level to work as expected
398 def __init__(self, ticks=None, labels=None, texts=None, extendtick=0, extendlabel=None, epsilon=1e-10, mix=()):
399 self.multipart = 0
400 if ticks is None and labels is not None:
401 self.ticks = (helper.ensuresequence(labels)[0],)
402 else:
403 self.ticks = helper.ensuresequence(ticks)
405 if labels is None and ticks is not None:
406 self.labels = (helper.ensuresequence(ticks)[0],)
407 else:
408 self.labels = helper.ensuresequence(labels)
409 self.texts = texts
410 self.extendtick = extendtick
411 self.extendlabel = extendlabel
412 self.epsilon = epsilon
413 self.mix = mix
415 def extendminmax(self, min, max, shiftfracs, extendmin, extendmax):
416 minpower = None
417 maxpower = None
418 for i in xrange(len(shiftfracs.fracs)):
419 imin = int(math.floor(math.log(min / float(shiftfracs.fracs[i])) /
420 math.log(shiftfracs.shift) + self.epsilon)) + 1
421 imax = int(math.ceil(math.log(max / float(shiftfracs.fracs[i])) /
422 math.log(shiftfracs.shift) - self.epsilon)) - 1
423 if minpower is None or imin < minpower:
424 minpower, minindex = imin, i
425 if maxpower is None or imax >= maxpower:
426 maxpower, maxindex = imax, i
427 if minindex:
428 minfrac = shiftfracs.fracs[minindex - 1]
429 else:
430 minfrac = shiftfracs.fracs[-1]
431 minpower -= 1
432 if maxindex != len(shiftfracs.fracs) - 1:
433 maxfrac = shiftfracs.fracs[maxindex + 1]
434 else:
435 maxfrac = shiftfracs.fracs[0]
436 maxpower += 1
437 if extendmin:
438 min = float(minfrac) * float(shiftfracs.shift) ** minpower
439 if extendmax:
440 max = float(maxfrac) * float(shiftfracs.shift) ** maxpower
441 return min, max
443 def getticks(self, min, max, shiftfracs, ticklevel=None, labellevel=None):
444 ticks = list(self.mix)
445 minimin = 0
446 maximax = 0
447 for f in shiftfracs.fracs:
448 fracticks = []
449 imin = int(math.ceil(math.log(min / float(f)) /
450 math.log(shiftfracs.shift) - 0.5 * self.epsilon))
451 imax = int(math.floor(math.log(max / float(f)) /
452 math.log(shiftfracs.shift) + 0.5 * self.epsilon))
453 for i in range(imin, imax + 1):
454 pos = f * frac(shiftfracs.shift, 1, i)
455 fracticks.append(tick(pos.enum, pos.denom, ticklevel = ticklevel, labellevel = labellevel))
456 ticks = _mergeticklists(ticks, fracticks)
457 return ticks
460 class autologpart(logpart):
462 defaultlist = (((logpart.shiftfracs1, # ticks
463 logpart.shiftfracs1to9), # subticks
464 (logpart.shiftfracs1, # labels
465 logpart.shiftfracs125)), # sublevels
467 ((logpart.shiftfracs1, # ticks
468 logpart.shiftfracs1to9), # subticks
469 None), # labels like ticks
471 ((logpart.shift2fracs1, # ticks
472 logpart.shiftfracs1), # subticks
473 None), # labels like ticks
475 ((logpart.shift3fracs1, # ticks
476 logpart.shiftfracs1), # subticks
477 None), # labels like ticks
479 ((logpart.shift4fracs1, # ticks
480 logpart.shiftfracs1), # subticks
481 None), # labels like ticks
483 ((logpart.shift5fracs1, # ticks
484 logpart.shiftfracs1), # subticks
485 None)) # labels like ticks
487 def __init__(self, list=defaultlist, extendtick=0, extendlabel=None, epsilon=1e-10, mix=()):
488 self.multipart = 1
489 self.list = list
490 if len(list) > 2:
491 self.listindex = divmod(len(list), 2)[0]
492 else:
493 self.listindex = 0
494 self.extendtick = extendtick
495 self.extendlabel = extendlabel
496 self.epsilon = epsilon
497 self.mix = mix
499 def defaultpart(self, min, max, extendmin, extendmax):
500 self.min, self.max, self.extendmin, self.extendmax = min, max, extendmin, extendmax
501 self.morelistindex = self.listindex
502 self.lesslistindex = self.listindex
503 part = logpart(ticks=self.list[self.listindex][0], labels=self.list[self.listindex][1],
504 extendtick=self.extendtick, extendlabel=self.extendlabel, epsilon=self.epsilon, mix=self.mix)
505 return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
507 def lesspart(self):
508 self.lesslistindex += 1
509 if self.lesslistindex < len(self.list):
510 part = logpart(ticks=self.list[self.lesslistindex][0], labels=self.list[self.lesslistindex][1],
511 extendtick=self.extendtick, extendlabel=self.extendlabel, epsilon=self.epsilon, mix=self.mix)
512 return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
513 return None
515 def morepart(self):
516 self.morelistindex -= 1
517 if self.morelistindex >= 0:
518 part = logpart(ticks=self.list[self.morelistindex][0], labels=self.list[self.morelistindex][1],
519 extendtick=self.extendtick, extendlabel=self.extendlabel, epsilon=self.epsilon, mix=self.mix)
520 return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
521 return None
525 ################################################################################
526 # rate partitions
527 ################################################################################
530 class cuberate:
532 def __init__(self, opt, left=None, right=None, weight=1):
533 if left is None:
534 left = 0
535 if right is None:
536 right = 3*opt
537 self.opt = opt
538 self.left = left
539 self.right = right
540 self.weight = weight
542 def rate(self, value, dense=1):
543 opt = self.opt * dense
544 if value < opt:
545 other = self.left * dense
546 elif value > opt:
547 other = self.right * dense
548 else:
549 return 0
550 factor = (value - opt) / float(other - opt)
551 return self.weight * (factor ** 3)
554 class distancerate:
556 def __init__(self, opt, weight=0.1):
557 self.opt_str = opt
558 self.weight = weight
560 def _rate(self, distances, dense=1):
561 if len(distances):
562 opt = unit.topt(unit.length(self.opt_str, default_type="v")) / dense
563 rate = 0
564 for distance in distances:
565 if distance < opt:
566 rate += self.weight * (opt / distance - 1)
567 else:
568 rate += self.weight * (distance / opt - 1)
569 return rate / float(len(distances))
572 class axisrater:
574 linticks = (cuberate(4), cuberate(10, weight=0.5), )
575 linlabels = (cuberate(4), )
576 logticks = (cuberate(5, right=20), cuberate(20, right=100, weight=0.5), )
577 loglabels = (cuberate(5, right=20), cuberate(5, left=-20, right=20, weight=0.5), )
578 stdtickrange = cuberate(1, weight=2)
579 stddistance = distancerate("1 cm")
581 def __init__(self, ticks=linticks, labels=linlabels, tickrange=stdtickrange, distance=stddistance):
582 self.ticks = ticks
583 self.labels = labels
584 self.tickrange = tickrange
585 self.distance = distance
587 def ratepart(self, axis, part, dense=1):
588 tickslen = len(self.ticks)
589 labelslen = len(self.labels)
590 ticks = [0]*tickslen
591 labels = [0]*labelslen
592 if part is not None:
593 for tick in part:
594 if tick.ticklevel is not None:
595 for level in xrange(tick.ticklevel, tickslen):
596 ticks[level] += 1
597 if tick.labellevel is not None:
598 for level in xrange(tick.labellevel, labelslen):
599 labels[level] += 1
600 rate = 0
601 weight = 0
602 for tick, rater in zip(ticks, self.ticks):
603 rate += rater.rate(tick, dense=dense)
604 weight += rater.weight
605 for label, rater in zip(labels, self.labels):
606 rate += rater.rate(label, dense=dense)
607 weight += rater.weight
608 if part is not None and len(part):
609 tickmin, tickmax = axis.gettickrange() # tickrange was not yet applied!?
610 rate += self.tickrange.rate((float(part[-1]) - float(part[0])) * axis.divisor / (tickmax - tickmin))
611 else:
612 rate += self.tickrange.rate(0)
613 weight += self.tickrange.weight
614 return rate/weight
616 def _ratedistances(self, distances, dense=1):
617 return self.distance._rate(distances, dense=dense)
620 ################################################################################
621 # axis painter
622 ################################################################################
625 class axistitlepainter(attrlist.attrlist):
627 paralleltext = -90
628 orthogonaltext = 0
630 def __init__(self, titledist="0.3 cm",
631 titleattrs=(text.halign.center, text.valign.centerline()),
632 titledirection=-90,
633 titlepos=0.5):
634 self.titledist_str = titledist
635 self.titleattrs = titleattrs
636 self.titledirection = titledirection
637 self.titlepos = titlepos
639 def reldirection(self, direction, dx, dy, epsilon=1e-10):
640 direction += math.atan2(dy, dx) * 180 / math.pi
641 while (direction > 90 + epsilon):
642 direction -= 180
643 while (direction < -90 - epsilon):
644 direction += 180
645 return direction
647 def dolayout(self, graph, axis):
648 titledist = unit.topt(unit.length(self.titledist_str, default_type="v"))
649 if axis.title is not None and self.titleattrs is not None:
650 x, y = axis._vtickpoint(axis, self.titlepos)
651 dx, dy = axis.vtickdirection(axis, self.titlepos)
652 # no not modify self.titleattrs ... the painter might be used by several axes!!!
653 titleattrs = list(helper.ensuresequence(self.titleattrs))
654 if self.titledirection is not None:
655 titleattrs = titleattrs + [trafo.rotate(self.reldirection(self.titledirection, dx, dy))]
656 axis.titlebox = textmodule._text(x, y, axis.title, *titleattrs)
657 axis._extent += titledist
658 axis.titlebox._linealign(axis._extent, dx, dy)
659 axis._extent += axis.titlebox._extent(dx, dy)
661 def paint(self, graph, axis):
662 if axis.title is not None and self.titleattrs is not None:
663 graph.insert(axis.titlebox)
666 class axispainter(axistitlepainter):
668 defaultticklengths = ["%0.5f cm" % (0.2*goldenrule**(-i)) for i in range(10)]
670 fractypeauto = 1
671 fractyperat = 2
672 fractypedec = 3
673 fractypeexp = 4
675 def __init__(self, innerticklengths=defaultticklengths,
676 outerticklengths=None,
677 tickattrs=(),
678 gridattrs=None,
679 zerolineattrs=(),
680 baselineattrs=canvas.linecap.square,
681 labeldist="0.3 cm",
682 labelattrs=((text.halign.center, text.valign.centerline()),
683 (text.halign.center, text.valign.centerline(), text.size.footnotesize)),
684 labeldirection=None,
685 labelhequalize=0,
686 labelvequalize=1,
687 fractype=fractypeauto,
688 ratfracsuffixenum=1,
689 ratfracover=r"\over",
690 decfracpoint=".",
691 expfractimes=r"\cdot",
692 expfracpre1=0,
693 expfracminexp=4,
694 suffix0=0,
695 suffix1=0,
696 **args):
697 self.innerticklengths_str = innerticklengths
698 self.outerticklengths_str = outerticklengths
699 self.tickattrs = tickattrs
700 self.gridattrs = gridattrs
701 self.zerolineattrs = zerolineattrs
702 self.baselineattrs = baselineattrs
703 self.labeldist_str = labeldist
704 self.labelattrs = labelattrs
705 self.labeldirection = labeldirection
706 self.labelhequalize = labelhequalize
707 self.labelvequalize = labelvequalize
708 self.fractype = fractype
709 self.ratfracsuffixenum = ratfracsuffixenum
710 self.ratfracover = ratfracover
711 self.decfracpoint = decfracpoint
712 self.expfractimes = expfractimes
713 self.expfracpre1 = expfracpre1
714 self.expfracminexp = expfracminexp
715 self.suffix0 = suffix0
716 self.suffix1 = suffix1
717 axistitlepainter.__init__(self, **args)
719 def gcd(self, m, n):
720 # greates common divisor, m & n must be non-negative
721 if m < n:
722 m, n = n, m
723 while n > 0:
724 m, (dummy, n) = n, divmod(m, n)
725 return m
727 def attachsuffix(self, tick, str):
728 if self.suffix0 or tick.enum:
729 if tick.suffix is not None and not self.suffix1:
730 if str == "1":
731 str = ""
732 elif str == "-1":
733 str = "-"
734 if tick.suffix is not None:
735 str = str + tick.suffix
736 return str
738 def ratfrac(self, tick):
739 m, n = tick.enum, tick.denom
740 sign = 1
741 if m < 0: m, sign = -m, -sign
742 if n < 0: n, sign = -n, -sign
743 gcd = self.gcd(m, n)
744 (m, dummy1), (n, dummy2) = divmod(m, gcd), divmod(n, gcd)
745 if n != 1:
746 if self.ratfracsuffixenum:
747 if sign == -1:
748 return "-{{%s}%s{%s}}" % (self.attachsuffix(tick, str(m)), self.ratfracover, n)
749 else:
750 return "{{%s}%s{%s}}" % (self.attachsuffix(tick, str(m)), self.ratfracover, n)
751 else:
752 if sign == -1:
753 return self.attachsuffix(tick, "-{{%s}%s{%s}}" % (m, self.ratfracover, n))
754 else:
755 return self.attachsuffix(tick, "{{%s}%s{%s}}" % (m, self.ratfracover, n))
756 else:
757 if sign == -1:
758 return self.attachsuffix(tick, "-%s" % m)
759 else:
760 return self.attachsuffix(tick, "%s" % m)
762 def decfrac(self, tick):
763 m, n = tick.enum, tick.denom
764 sign = 1
765 if m < 0: m, sign = -m, -sign
766 if n < 0: n, sign = -n, -sign
767 gcd = self.gcd(m, n)
768 (m, dummy1), (n, dummy2) = divmod(m, gcd), divmod(n, gcd)
769 frac, rest = divmod(m, n)
770 strfrac = str(frac)
771 rest = m % n
772 if rest:
773 strfrac += self.decfracpoint
774 oldrest = []
775 while (rest):
776 if rest in oldrest:
777 periodstart = len(strfrac) - (len(oldrest) - oldrest.index(rest))
778 strfrac = strfrac[:periodstart] + r"\overline{" + strfrac[periodstart:] + "}"
779 break
780 oldrest += [rest]
781 rest *= 10
782 frac, rest = divmod(rest, n)
783 strfrac += str(frac)
784 if sign == -1:
785 return self.attachsuffix(tick, "-%s" % strfrac)
786 else:
787 return self.attachsuffix(tick, strfrac)
789 def expfrac(self, tick, minexp = None):
790 m, n = tick.enum, tick.denom
791 sign = 1
792 if m < 0: m, sign = -m, -sign
793 if n < 0: n, sign = -n, -sign
794 exp = 0
795 if m:
796 while divmod(m, n)[0] > 9:
797 n *= 10
798 exp += 1
799 while divmod(m, n)[0] < 1:
800 m *= 10
801 exp -= 1
802 if minexp is not None and ((exp < 0 and -exp < minexp) or (exp >= 0 and exp < minexp)):
803 return None
804 dummy = frac(m, n)
805 dummy.suffix = None
806 prefactor = self.decfrac(dummy)
807 if prefactor == "1" and not self.expfracpre1:
808 if sign == -1:
809 return self.attachsuffix(tick, "-10^{%i}" % exp)
810 else:
811 return self.attachsuffix(tick, "10^{%i}" % exp)
812 else:
813 if sign == -1:
814 return self.attachsuffix(tick, "-%s%s10^{%i}" % (prefactor, self.expfractimes, exp))
815 else:
816 return self.attachsuffix(tick, "%s%s10^{%i}" % (prefactor, self.expfractimes, exp))
818 def createtext(self, tick):
819 if self.fractype == self.fractypeauto:
820 if tick.suffix is not None:
821 tick.text = self.ratfrac(tick)
822 else:
823 tick.text = self.expfrac(tick, self.expfracminexp)
824 if tick.text is None:
825 tick.text = self.decfrac(tick)
826 elif self.fractype == self.fractypedec:
827 tick.text = self.decfrac(tick)
828 elif self.fractype == self.fractypeexp:
829 tick.text = self.expfrac(tick)
830 elif self.fractype == self.fractyperat:
831 tick.text = self.ratfrac(tick)
832 else:
833 raise ValueError("fractype invalid")
834 if textmodule.mathmode not in tick.labelattrs:
835 tick.labelattrs = [textmodule.mathmode] + tick.labelattrs
837 def dolayout(self, graph, axis):
838 labeldist = unit.topt(unit.length(self.labeldist_str, default_type="v"))
839 for tick in axis.ticks:
840 tick.virtual = axis.convert(float(tick) * axis.divisor)
841 tick.x, tick.y = axis._vtickpoint(axis, tick.virtual)
842 tick.dx, tick.dy = axis.vtickdirection(axis, tick.virtual)
843 for tick in axis.ticks:
844 tick.textbox = None
845 if tick.labellevel is not None:
846 tick.labelattrs = helper.getsequenceno(self.labelattrs, tick.labellevel)
847 if tick.labelattrs is not None:
848 tick.labelattrs = list(helper.ensuresequence(tick.labelattrs))
849 if tick.text is None:
850 tick.suffix = axis.suffix
851 self.createtext(tick)
852 if self.labeldirection is not None:
853 tick.labelattrs += [trafo.rotate(self.reldirection(self.labeldirection, tick.dx, tick.dy))]
854 tick.textbox = textmodule._text(tick.x, tick.y, tick.text, *tick.labelattrs)
855 equaldirection = 1
856 if len(axis.ticks):
857 for tick in axis.ticks[1:]:
858 if tick.dx != axis.ticks[0].dx or tick.dy != axis.ticks[0].dy:
859 equaldirection = 0
860 if equaldirection and ((not axis.ticks[0].dx and self.labelvequalize) or
861 (not axis.ticks[0].dy and self.labelhequalize)):
862 box._linealignequal([tick.textbox for tick in axis.ticks if tick.textbox],
863 labeldist, axis.ticks[0].dx, axis.ticks[0].dy)
864 else:
865 for tick in axis.ticks:
866 if tick.textbox:
867 tick.textbox._linealign(labeldist, axis.ticks[0].dx, axis.ticks[0].dy)
868 def topt_v_recursive(arg):
869 if helper.issequence(arg):
870 # return map(topt_v_recursive, arg) needs python2.2
871 return [unit.topt(unit.length(a, default_type="v")) for a in arg]
872 else:
873 if arg is not None:
874 return unit.topt(unit.length(arg, default_type="v"))
875 innerticklengths = topt_v_recursive(self.innerticklengths_str)
876 outerticklengths = topt_v_recursive(self.outerticklengths_str)
877 axis._extent = 0
878 for tick in axis.ticks:
879 if tick.ticklevel is not None:
880 tick.innerticklength = helper.getitemno(innerticklengths, tick.ticklevel)
881 tick.outerticklength = helper.getitemno(outerticklengths, tick.ticklevel)
882 if tick.innerticklength is not None and tick.outerticklength is None:
883 tick.outerticklength = 0
884 if tick.outerticklength is not None and tick.innerticklength is None:
885 tick.innerticklength = 0
886 extent = 0
887 if tick.textbox is None:
888 if tick.outerticklength is not None and tick.outerticklength > 0:
889 extent = tick.outerticklength
890 else:
891 extent = tick.textbox._extent(tick.dx, tick.dy) + labeldist
892 if axis._extent < extent:
893 axis._extent = extent
894 axistitlepainter.dolayout(self, graph, axis)
896 def ratelayout(self, graph, axis, dense=1):
897 ticktextboxes = [tick.textbox for tick in axis.ticks if tick.textbox is not None]
898 if len(ticktextboxes) > 1:
899 try:
900 distances = [ticktextboxes[i]._boxdistance(ticktextboxes[i+1]) for i in range(len(ticktextboxes) - 1)]
901 except box.BoxCrossError:
902 return None
903 rate = axis.rate._ratedistances(distances, dense)
904 return rate
905 else:
906 if self.labelattrs is None:
907 return 0
909 def paint(self, graph, axis):
910 for tick in axis.ticks:
911 if tick.ticklevel is not None:
912 if tick != frac(0, 1) or self.zerolineattrs is None:
913 gridattrs = helper.getsequenceno(self.gridattrs, tick.ticklevel)
914 if gridattrs is not None:
915 graph.stroke(axis.vgridpath(tick.virtual), *helper.ensuresequence(gridattrs))
916 tickattrs = helper.getsequenceno(self.tickattrs, tick.ticklevel)
917 if None not in (tick.innerticklength, tick.outerticklength, tickattrs):
918 x1 = tick.x - tick.dx * tick.innerticklength
919 y1 = tick.y - tick.dy * tick.innerticklength
920 x2 = tick.x + tick.dx * tick.outerticklength
921 y2 = tick.y + tick.dy * tick.outerticklength
922 graph.stroke(path._line(x1, y1, x2, y2), *helper.ensuresequence(tickattrs))
923 if tick.textbox is not None:
924 graph.insert(tick.textbox)
925 if self.baselineattrs is not None:
926 graph.stroke(axis.vbaseline(axis), *helper.ensuresequence(self.baselineattrs))
927 if self.zerolineattrs is not None:
928 if len(axis.ticks) and axis.ticks[0] * axis.ticks[-1] < frac(0, 1):
929 graph.stroke(axis.vgridpath(axis.convert(0)), *helper.ensuresequence(self.zerolineattrs))
930 axistitlepainter.paint(self, graph, axis)
933 class splitaxispainter(axistitlepainter):
935 def __init__(self, breaklinesdist=0.05,
936 breaklineslength=0.5,
937 breaklinesangle=-60,
938 breaklinesattrs=(),
939 **args):
940 self.breaklinesdist_str = breaklinesdist
941 self.breaklineslength_str = breaklineslength
942 self.breaklinesangle = breaklinesangle
943 self.breaklinesattrs = breaklinesattrs
944 axistitlepainter.__init__(self, **args)
946 def subvbaseline(self, axis, v1=None, v2=None):
947 if v1 is None:
948 if self.breaklinesattrs is None:
949 left = axis.vmin
950 else:
951 if axis.vminover is None:
952 left = None
953 else:
954 left = axis.vminover
955 else:
956 left = axis.vmin+v1*(axis.vmax-axis.vmin)
957 if v2 is None:
958 if self.breaklinesattrs is None:
959 right = axis.vmax
960 else:
961 if axis.vmaxover is None:
962 right = None
963 else:
964 right = axis.vmaxover
965 else:
966 right = axis.vmin+v2*(axis.vmax-axis.vmin)
967 return axis.baseaxis.vbaseline(axis.baseaxis, left, right)
969 def dolayout(self, graph, axis):
970 if self.breaklinesattrs is not None:
971 self.breaklinesdist = unit.length(self.breaklinesdist_str, default_type="v")
972 self.breaklineslength = unit.length(self.breaklineslength_str, default_type="v")
973 self._breaklinesdist = unit.topt(self.breaklinesdist)
974 self._breaklineslength = unit.topt(self.breaklineslength)
975 self.sin = math.sin(self.breaklinesangle*math.pi/180.0)
976 self.cos = math.cos(self.breaklinesangle*math.pi/180.0)
977 axis._extent = (math.fabs(0.5 * self._breaklinesdist * self.cos) +
978 math.fabs(0.5 * self._breaklineslength * self.sin))
979 else:
980 axis._extent = 0
981 for subaxis in axis.axislist:
982 subaxis.baseaxis = axis
983 subaxis._vtickpoint = lambda axis, v: axis.baseaxis._vtickpoint(axis.baseaxis, axis.vmin+v*(axis.vmax-axis.vmin))
984 subaxis.vtickdirection = lambda axis, v: axis.baseaxis.vtickdirection(axis.baseaxis, axis.vmin+v*(axis.vmax-axis.vmin))
985 subaxis.vbaseline = self.subvbaseline
986 subaxis.dolayout(graph)
987 if axis._extent < subaxis._extent:
988 axis._extent = subaxis._extent
989 axistitlepainter.dolayout(self, graph, axis)
991 def paint(self, graph, axis):
992 for subaxis in axis.axislist:
993 subaxis.dopaint(graph)
994 if self.breaklinesattrs is not None:
995 for subaxis1, subaxis2 in zip(axis.axislist[:-1], axis.axislist[1:]):
996 # use a tangent of the baseline (this is independend of the tickdirection)
997 v = 0.5 * (subaxis1.vmax + subaxis2.vmin)
998 breakline = path.normpath(axis.vbaseline(axis, v, None)).tangent(0, self.breaklineslength)
999 widthline = path.normpath(axis.vbaseline(axis, v, None)).tangent(0, self.breaklinesdist).transformed(trafo.rotate(self.breaklinesangle+90, *breakline.begin()))
1000 tocenter = map(lambda x: 0.5*(x[0]-x[1]), zip(breakline.begin(), breakline.end()))
1001 towidth = map(lambda x: 0.5*(x[0]-x[1]), zip(widthline.begin(), widthline.end()))
1002 breakline = breakline.transformed(trafo.translate(*tocenter).rotated(self.breaklinesangle, *breakline.begin()))
1003 breakline1 = breakline.transformed(trafo.translate(*towidth))
1004 breakline2 = breakline.transformed(trafo.translate(-towidth[0], -towidth[1]))
1005 graph.fill(path.path(path.moveto(*breakline1.begin()),
1006 path.lineto(*breakline1.end()),
1007 path.lineto(*breakline2.end()),
1008 path.lineto(*breakline2.begin()),
1009 path.closepath()), color.gray.white)
1010 graph.stroke(breakline1, *helper.ensuresequence(self.breaklinesattrs))
1011 graph.stroke(breakline2, *helper.ensuresequence(self.breaklinesattrs))
1012 axistitlepainter.paint(self, graph, axis)
1015 class baraxispainter(axistitlepainter):
1017 def __init__(self, innerticklength=None,
1018 outerticklength=None,
1019 tickattrs=(),
1020 baselineattrs=canvas.linecap.square,
1021 namedist="0.3 cm",
1022 nameattrs=(text.halign.center, text.valign.centerline),
1023 namedirection=None,
1024 namepos=0.5,
1025 namehequalize=0,
1026 namevequalize=1,
1027 **args):
1028 self.innerticklength_str = innerticklength
1029 self.outerticklength_str = outerticklength
1030 self.tickattrs = tickattrs
1031 self.baselineattrs = baselineattrs
1032 self.namedist_str = namedist
1033 self.nameattrs = nameattrs
1034 self.namedirection = namedirection
1035 self.namepos = namepos
1036 self.namehequalize = namehequalize
1037 self.namevequalize = namevequalize
1038 axistitlepainter.__init__(self, **args)
1040 def dolayout(self, graph, axis):
1041 axis._extent = 0
1042 if axis.multisubaxis:
1043 for name, subaxis in zip(axis.names, axis.subaxis):
1044 subaxis.vmin = axis.convert((name, 0))
1045 subaxis.vmax = axis.convert((name, 1))
1046 subaxis.baseaxis = axis
1047 subaxis._vtickpoint = lambda axis, v: axis.baseaxis._vtickpoint(axis.baseaxis, axis.vmin+v*(axis.vmax-axis.vmin))
1048 subaxis.vtickdirection = lambda axis, v: axis.baseaxis.vtickdirection(axis.baseaxis, axis.vmin+v*(axis.vmax-axis.vmin))
1049 subaxis.vbaseline = None
1050 subaxis.dolayout(graph)
1051 if axis._extent < subaxis._extent:
1052 axis._extent = subaxis._extent
1053 equaldirection = 1
1054 axis.namepos = []
1055 for name in axis.names:
1056 v = axis.convert((name, self.namepos))
1057 x, y = axis._vtickpoint(axis, v)
1058 dx, dy = axis.vtickdirection(axis, v)
1059 axis.namepos.append((v, x, y, dx, dy))
1060 if equaldirection and (dx != axis.namepos[0][3] or dy != axis.namepos[0][4]):
1061 equaldirection = 0
1062 axis.nameboxes = []
1063 for (v, x, y, dx, dy), name in zip(axis.namepos, axis.names):
1064 nameattrs = list(helper.ensuresequence(self.nameattrs))
1065 if self.namedirection is not None:
1066 nameattrs += [trafo.rotate(self.reldirection(self.namedirection, dx, dy))]
1067 if axis.texts.has_key(name):
1068 axis.nameboxes.append(textmodule.text(0, 0, str(axis.texts[name]), *nameattrs))
1069 elif axis.texts.has_key(str(name)):
1070 axis.nameboxes.append(textmodule.text(0, 0, str(axis.texts[str(name)]), *nameattrs))
1071 else:
1072 axis.nameboxes.append(textmodule.text(0, 0, str(name), *nameattrs))
1073 #if equaldirection:
1074 # maxht, maxwd, maxdp = 0, 0, 0
1075 # for namebox in axis.nameboxes:
1076 # if maxht < namebox.ht: maxht = namebox.ht
1077 # if maxwd < namebox.wd: maxwd = namebox.wd
1078 # if maxdp < namebox.dp: maxdp = namebox.dp
1079 # for namebox in axis.nameboxes:
1080 # if self.namehequalize:
1081 # namebox.manualextents(wd = maxwd)
1082 # if self.namevequalize:
1083 # namebox.manualextents(ht = maxht, dp = maxdp)
1084 labeldist = axis._extent + unit.topt(unit.length(self.namedist_str, default_type="v"))
1085 if self.innerticklength_str is not None:
1086 axis.innerticklength = unit.topt(unit.length(self.innerticklength_str, default_type="v"))
1087 else:
1088 if self.outerticklength_str is not None:
1089 axis.innerticklength = 0
1090 else:
1091 axis.innerticklength = None
1092 if self.outerticklength_str is not None:
1093 axis.outerticklength = unit.topt(unit.length(self.outerticklength_str, default_type="v"))
1094 else:
1095 if self.innerticklength_str is not None:
1096 axis.outerticklength = 0
1097 else:
1098 axis.outerticklength = None
1099 if axis.outerticklength is not None and self.tickattrs is not None:
1100 axis._extent += axis.outerticklength
1101 for (v, x, y, dx, dy), namebox in zip(axis.namepos, axis.nameboxes):
1102 namebox._linealign(labeldist, dx, dy)
1103 namebox.transform(trafo._translate(x, y))
1104 newextent = namebox._extent(dx, dy) + labeldist
1105 if axis._extent < newextent:
1106 axis._extent = newextent
1107 axistitlepainter.dolayout(self, graph, axis)
1109 def paint(self, graph, axis):
1110 if axis.subaxis is not None:
1111 if axis.multisubaxis:
1112 for subaxis in axis.subaxis:
1113 subaxis.dopaint(graph)
1114 if None not in (self.tickattrs, axis.innerticklength, axis.outerticklength):
1115 for pos in axis.relsizes:
1116 if pos == axis.relsizes[0]:
1117 pos -= axis.firstdist
1118 elif pos != axis.relsizes[-1]:
1119 pos -= 0.5 * axis.dist
1120 v = pos / axis.relsizes[-1]
1121 x, y = axis._vtickpoint(axis, v)
1122 dx, dy = axis.vtickdirection(axis, v)
1123 x1 = x - dx * axis.innerticklength
1124 y1 = y - dy * axis.innerticklength
1125 x2 = x + dx * axis.outerticklength
1126 y2 = y + dy * axis.outerticklength
1127 graph.stroke(path._line(x1, y1, x2, y2), *helper.ensuresequence(self.tickattrs))
1128 if self.baselineattrs is not None:
1129 if axis.vbaseline is not None: # XXX: subbaselines (as for splitlines)
1130 graph.stroke(axis.vbaseline(axis), *helper.ensuresequence(self.baselineattrs))
1131 for namebox in axis.nameboxes:
1132 graph.insert(namebox)
1133 axistitlepainter.paint(self, graph, axis)
1137 ################################################################################
1138 # axes
1139 ################################################################################
1141 class PartitionError(Exception): pass
1143 class _axis:
1145 def __init__(self, min=None, max=None, reverse=0, divisor=1,
1146 datavmin=None, datavmax=None, tickvmin=0, tickvmax=1,
1147 title=None, suffix=None, painter=axispainter(), dense=None):
1148 if None not in (min, max) and min > max:
1149 min, max, reverse = max, min, not reverse
1150 self.fixmin, self.fixmax, self.min, self.max, self.reverse = min is not None, max is not None, min, max, reverse
1152 self.datamin = self.datamax = self.tickmin = self.tickmax = None
1153 if datavmin is None:
1154 if self.fixmin:
1155 self.datavmin = 0
1156 else:
1157 self.datavmin = 0.05
1158 else:
1159 self.datavmin = datavmin
1160 if datavmax is None:
1161 if self.fixmax:
1162 self.datavmax = 1
1163 else:
1164 self.datavmax = 0.95
1165 else:
1166 self.datavmax = datavmax
1167 self.tickvmin = tickvmin
1168 self.tickvmax = tickvmax
1170 self.divisor = divisor
1171 self.title = title
1172 self.suffix = suffix
1173 self.painter = painter
1174 self.dense = dense
1175 self.canconvert = 0
1176 self.__setinternalrange()
1178 def __setinternalrange(self, min=None, max=None):
1179 if not self.fixmin and min is not None and (self.min is None or min < self.min):
1180 self.min = min
1181 if not self.fixmax and max is not None and (self.max is None or max > self.max):
1182 self.max = max
1183 if None not in (self.min, self.max):
1184 min, max, vmin, vmax = self.min, self.max, 0, 1
1185 self.canconvert = 1
1186 self.setbasepoints(((min, vmin), (max, vmax)))
1187 if not self.fixmin:
1188 if self.datamin is not None and self.convert(self.datamin) < self.datavmin:
1189 min, vmin = self.datamin, self.datavmin
1190 self.setbasepoints(((min, vmin), (max, vmax)))
1191 if self.tickmin is not None and self.convert(self.tickmin) < self.tickvmin:
1192 min, vmin = self.tickmin, self.tickvmin
1193 self.setbasepoints(((min, vmin), (max, vmax)))
1194 if not self.fixmax:
1195 if self.datamax is not None and self.convert(self.datamax) > self.datavmax:
1196 max, vmax = self.datamax, self.datavmax
1197 self.setbasepoints(((min, vmin), (max, vmax)))
1198 if self.tickmax is not None and self.convert(self.tickmax) > self.tickvmax:
1199 max, vmax = self.tickmax, self.tickvmax
1200 self.setbasepoints(((min, vmin), (max, vmax)))
1201 if self.reverse:
1202 self.setbasepoints(((min, vmax), (max, vmin)))
1204 def __getinternalrange(self):
1205 return self.min, self.max, self.datamin, self.datamax, self.tickmin, self.tickmax
1207 def __forceinternalrange(self, range):
1208 self.min, self.max, self.datamin, self.datamax, self.tickmin, self.tickmax = range
1209 self.__setinternalrange()
1211 def setdatarange(self, min, max):
1212 self.datamin, self.datamax = min, max
1213 self.__setinternalrange(min, max)
1215 def settickrange(self, min, max):
1216 self.tickmin, self.tickmax = min, max
1217 self.__setinternalrange(min, max)
1219 def getdatarange(self):
1220 if self.canconvert:
1221 if self.reverse:
1222 return self.invert(1-self.datavmin), self.invert(1-self.datavmax)
1223 else:
1224 return self.invert(self.datavmin), self.invert(self.datavmax)
1226 def gettickrange(self):
1227 if self.canconvert:
1228 if self.reverse:
1229 return self.invert(1-self.tickvmin), self.invert(1-self.tickvmax)
1230 else:
1231 return self.invert(self.tickvmin), self.invert(self.tickvmax)
1233 def dolayout(self, graph):
1234 if self.dense is not None:
1235 dense = self.dense
1236 else:
1237 dense = graph.dense
1238 min, max = self.gettickrange()
1239 self.ticks = self.part.defaultpart(min/self.divisor, max/self.divisor, not self.fixmin, not self.fixmax)
1240 if self.part.multipart:
1241 # lesspart and morepart can be called after defaultpart,
1242 # although some axes may share their autoparting ---
1243 # it works, because the axes are processed sequentially
1244 bestrate = self.rate.ratepart(self, self.ticks, dense)
1245 variants = [[bestrate, self.ticks]]
1246 maxworse = 2
1247 worse = 0
1248 while worse < maxworse:
1249 newticks = self.part.lesspart()
1250 if newticks is not None:
1251 newrate = self.rate.ratepart(self, newticks, dense)
1252 variants.append([newrate, newticks])
1253 if newrate < bestrate:
1254 bestrate = newrate
1255 worse = 0
1256 else:
1257 worse += 1
1258 else:
1259 worse += 1
1260 worse = 0
1261 while worse < maxworse:
1262 newticks = self.part.morepart()
1263 if newticks is not None:
1264 newrate = self.rate.ratepart(self, newticks, dense)
1265 variants.append([newrate, newticks])
1266 if newrate < bestrate:
1267 bestrate = newrate
1268 worse = 0
1269 else:
1270 worse += 1
1271 else:
1272 worse += 1
1273 variants.sort()
1274 i = 0
1275 bestrate = None
1276 while i < len(variants) and (bestrate is None or variants[i][0] < bestrate):
1277 saverange = self.__getinternalrange()
1278 self.ticks = variants[i][1]
1279 if len(self.ticks):
1280 self.settickrange(float(self.ticks[0])*self.divisor, float(self.ticks[-1])*self.divisor)
1281 self.painter.dolayout(graph, self)
1282 ratelayout = self.painter.ratelayout(graph, self, dense)
1283 if ratelayout is not None:
1284 variants[i][0] += ratelayout
1285 else:
1286 variants[i][0] = None
1287 if variants[i][0] is not None and (bestrate is None or variants[i][0] < bestrate):
1288 bestrate = variants[i][0]
1289 self.__forceinternalrange(saverange)
1290 i += 1
1291 if bestrate is None:
1292 raise PartitionError("no valid axis partitioning found")
1293 variants = [variant for variant in variants[:i] if variant[0] is not None]
1294 variants.sort()
1295 self.ticks = variants[0][1]
1296 if len(self.ticks):
1297 self.settickrange(float(self.ticks[0])*self.divisor, float(self.ticks[-1])*self.divisor)
1298 else:
1299 if len(self.ticks):
1300 self.settickrange(float(self.ticks[0])*self.divisor, float(self.ticks[-1])*self.divisor)
1301 self.painter.dolayout(graph, self)
1303 def dopaint(self, graph):
1304 self.painter.paint(graph, self)
1306 def createlinkaxis(self, **args):
1307 return linkaxis(self, **args)
1310 class linaxis(_axis, _linmap):
1312 def __init__(self, part=autolinpart(), rate=axisrater(), **args):
1313 _axis.__init__(self, **args)
1314 if self.fixmin and self.fixmax:
1315 self.relsize = self.max - self.min
1316 self.part = part
1317 self.rate = rate
1320 class logaxis(_axis, _logmap):
1322 def __init__(self, part=autologpart(), rate=axisrater(ticks=axisrater.logticks, labels=axisrater.loglabels), **args):
1323 _axis.__init__(self, **args)
1324 if self.fixmin and self.fixmax:
1325 self.relsize = math.log(self.max) - math.log(self.min)
1326 self.part = part
1327 self.rate = rate
1330 class linkaxis:
1332 def __init__(self, linkedaxis, title=None, skipticklevel=None, skiplabellevel=0, painter=axispainter(zerolineattrs=None)):
1333 self.linkedaxis = linkedaxis
1334 while isinstance(self.linkedaxis, linkaxis):
1335 self.linkedaxis = self.linkedaxis.linkedaxis
1336 self.fixmin = self.linkedaxis.fixmin
1337 self.fixmax = self.linkedaxis.fixmax
1338 if self.fixmin:
1339 self.min = self.linkedaxis.min
1340 if self.fixmax:
1341 self.max = self.linkedaxis.max
1342 self.skipticklevel = skipticklevel
1343 self.skiplabellevel = skiplabellevel
1344 self.title = title
1345 self.painter = painter
1347 def ticks(self, ticks):
1348 result = []
1349 for _tick in ticks:
1350 ticklevel = _tick.ticklevel
1351 labellevel = _tick.labellevel
1352 if self.skipticklevel is not None and ticklevel >= self.skipticklevel:
1353 ticklevel = None
1354 if self.skiplabellevel is not None and labellevel >= self.skiplabellevel:
1355 labellevel = None
1356 if ticklevel is not None or labellevel is not None:
1357 result.append(tick(_tick.enum, _tick.denom, ticklevel, labellevel))
1358 return result
1359 # XXX: don't forget to calculate new text positions as soon as this is moved
1360 # outside of the paint method (when rating is moved into the axispainter)
1362 def getdatarange(self):
1363 return self.linkedaxis.getdatarange()
1365 def setdatarange(self, min, max):
1366 prevrange = self.linkedaxis.getdatarange()
1367 self.linkedaxis.setdatarange(min, max)
1368 if hasattr(self.linkedaxis, "ticks") and prevrange != self.linkedaxis.getdatarange():
1369 raise RuntimeError("linkaxis datarange setting performed while linked axis layout already fixed")
1371 def dolayout(self, graph):
1372 self.ticks = self.ticks(self.linkedaxis.ticks)
1373 self.convert = self.linkedaxis.convert
1374 self.divisor = self.linkedaxis.divisor
1375 self.suffix = self.linkedaxis.suffix
1376 self.painter.dolayout(graph, self)
1378 def dopaint(self, graph):
1379 self.painter.paint(graph, self)
1381 def createlinkaxis(self, **args):
1382 return linkaxis(self.linkedaxis)
1385 class splitaxis:
1387 def __init__(self, axislist, splitlist=0.5, splitdist=0.1, relsizesplitdist=1, title=None, painter=splitaxispainter()):
1388 self.title = title
1389 self.axislist = axislist
1390 self.painter = painter
1391 self.splitlist = list(helper.ensuresequence(splitlist))
1392 self.splitlist.sort()
1393 if len(self.axislist) != len(self.splitlist) + 1:
1394 for subaxis in self.axislist:
1395 if not isinstance(subaxis, linkaxis):
1396 raise ValueError("axislist and splitlist lengths do not fit together")
1397 for subaxis in self.axislist:
1398 if isinstance(subaxis, linkaxis):
1399 subaxis.vmin = subaxis.linkedaxis.vmin
1400 subaxis.vminover = subaxis.linkedaxis.vminover
1401 subaxis.vmax = subaxis.linkedaxis.vmax
1402 subaxis.vmaxover = subaxis.linkedaxis.vmaxover
1403 else:
1404 subaxis.vmin = None
1405 subaxis.vmax = None
1406 self.axislist[0].vmin = 0
1407 self.axislist[0].vminover = None
1408 self.axislist[-1].vmax = 1
1409 self.axislist[-1].vmaxover = None
1410 for i in xrange(len(self.splitlist)):
1411 if self.splitlist[i] is not None:
1412 self.axislist[i].vmax = self.splitlist[i] - 0.5*splitdist
1413 self.axislist[i].vmaxover = self.splitlist[i]
1414 self.axislist[i+1].vmin = self.splitlist[i] + 0.5*splitdist
1415 self.axislist[i+1].vminover = self.splitlist[i]
1416 i = 0
1417 while i < len(self.axislist):
1418 if self.axislist[i].vmax is None:
1419 j = relsize = relsize2 = 0
1420 while self.axislist[i + j].vmax is None:
1421 relsize += self.axislist[i + j].relsize + relsizesplitdist
1422 j += 1
1423 relsize += self.axislist[i + j].relsize
1424 vleft = self.axislist[i].vmin
1425 vright = self.axislist[i + j].vmax
1426 for k in range(i, i + j):
1427 relsize2 += self.axislist[k].relsize
1428 self.axislist[k].vmax = vleft + (vright - vleft) * relsize2 / float(relsize)
1429 relsize2 += 0.5 * relsizesplitdist
1430 self.axislist[k].vmaxover = self.axislist[k + 1].vminover = vleft + (vright - vleft) * relsize2 / float(relsize)
1431 relsize2 += 0.5 * relsizesplitdist
1432 self.axislist[k+1].vmin = vleft + (vright - vleft) * relsize2 / float(relsize)
1433 if i == 0 and i + j + 1 == len(self.axislist):
1434 self.relsize = relsize
1435 i += j + 1
1436 else:
1437 i += 1
1439 self.fixmin = self.axislist[0].fixmin
1440 if self.fixmin:
1441 self.min = self.axislist[0].min
1442 self.fixmax = self.axislist[-1].fixmax
1443 if self.fixmax:
1444 self.max = self.axislist[-1].max
1445 self.divisor = 1
1446 self.suffix = ""
1448 def getdatarange(self):
1449 min = self.axislist[0].getdatarange()
1450 max = self.axislist[-1].getdatarange()
1451 try:
1452 return min[0], max[1]
1453 except TypeError:
1454 return None
1456 def setdatarange(self, min, max):
1457 self.axislist[0].setdatarange(min, None)
1458 self.axislist[-1].setdatarange(None, max)
1460 def gettickrange(self):
1461 min = self.axislist[0].gettickrange()
1462 max = self.axislist[-1].gettickrange()
1463 try:
1464 return min[0], max[1]
1465 except TypeError:
1466 return None
1468 def settickrange(self, min, max):
1469 self.axislist[0].settickrange(min, None)
1470 self.axislist[-1].settickrange(None, max)
1472 def convert(self, value):
1473 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
1474 if value < self.axislist[0].max:
1475 return self.axislist[0].vmin + self.axislist[0].convert(value)*(self.axislist[0].vmax-self.axislist[0].vmin)
1476 for axis in self.axislist[1:-1]:
1477 if value > axis.min and value < axis.max:
1478 return axis.vmin + axis.convert(value)*(axis.vmax-axis.vmin)
1479 if value > self.axislist[-1].min:
1480 return self.axislist[-1].vmin + self.axislist[-1].convert(value)*(self.axislist[-1].vmax-self.axislist[-1].vmin)
1481 raise ValueError("value couldn't be assigned to a split region")
1483 def dolayout(self, graph):
1484 self.painter.dolayout(graph, self)
1486 def dopaint(self, graph):
1487 self.painter.paint(graph, self)
1489 def createlinkaxis(self, painter=None, *args):
1490 if not len(args):
1491 return splitaxis([x.createlinkaxis() for x in self.axislist], splitlist=None)
1492 if len(args) != len(self.axislist):
1493 raise IndexError("length of the argument list doesn't fit to split number")
1494 if painter is None:
1495 painter = self.painter
1496 return splitaxis([x.createlinkaxis(**arg) for x, arg in zip(self.axislist, args)], painter=painter)
1499 class baraxis:
1501 def __init__(self, subaxis=None, multisubaxis=0, title=None, dist=0.5, firstdist=None, lastdist=None, names=None, texts={}, painter=baraxispainter()):
1502 self.dist = dist
1503 if firstdist is not None:
1504 self.firstdist = firstdist
1505 else:
1506 self.firstdist = 0.5 * dist
1507 if lastdist is not None:
1508 self.lastdist = lastdist
1509 else:
1510 self.lastdist = 0.5 * dist
1511 self.relsizes = None
1512 self.fixnames = 0
1513 self.names = []
1514 for name in helper.ensuresequence(names):
1515 self.setname(name)
1516 self.fixnames = names is not None
1517 self.multisubaxis = multisubaxis
1518 if self.multisubaxis:
1519 self.createsubaxis = subaxis
1520 self.subaxis = [self.createsubaxis.createsubaxis() for name in self.names]
1521 else:
1522 self.subaxis = subaxis
1523 self.title = title
1524 self.fixnames = 0
1525 self.texts = texts
1526 self.painter = painter
1528 def getdatarange(self):
1529 return None
1531 def setname(self, name, *subnames):
1532 # setting self.relsizes to None forces later recalculation
1533 if not self.fixnames:
1534 if name not in self.names:
1535 self.relsizes = None
1536 self.names.append(name)
1537 if self.multisubaxis:
1538 self.subaxis.append(self.createsubaxis.createsubaxis())
1539 if (not self.fixnames or name in self.names) and len(subnames):
1540 if self.multisubaxis:
1541 if self.subaxis[self.names.index(name)].setname(*subnames):
1542 self.relsizes = None
1543 else:
1544 if self.subaxis.setname(*subnames):
1545 self.relsizes = None
1546 return self.relsizes is not None
1548 def updaterelsizes(self):
1549 self.relsizes = [i*self.dist + self.firstdist for i in range(len(self.names) + 1)]
1550 self.relsizes[-1] += self.lastdist - self.dist
1551 if self.multisubaxis:
1552 subrelsize = 0
1553 for i in range(1, len(self.relsizes)):
1554 self.subaxis[i-1].updaterelsizes()
1555 subrelsize += self.subaxis[i-1].relsizes[-1]
1556 self.relsizes[i] += subrelsize
1557 else:
1558 if self.subaxis is None:
1559 subrelsize = 1
1560 else:
1561 self.subaxis.updaterelsizes()
1562 subrelsize = self.subaxis.relsizes[-1]
1563 for i in range(1, len(self.relsizes)):
1564 self.relsizes[i] += i * subrelsize
1566 def convert(self, value):
1567 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
1568 if not self.relsizes:
1569 self.updaterelsizes()
1570 pos = self.names.index(value[0])
1571 if len(value) == 2:
1572 if self.subaxis is None:
1573 subvalue = value[1]
1574 else:
1575 if self.multisubaxis:
1576 subvalue = value[1] * self.subaxis[pos].relsizes[-1]
1577 else:
1578 subvalue = value[1] * self.subaxis.relsizes[-1]
1579 else:
1580 if self.multisubaxis:
1581 subvalue = self.subaxis[pos].convert(value[1:]) * self.subaxis[pos].relsizes[-1]
1582 else:
1583 subvalue = self.subaxis.convert(value[1:]) * self.subaxis.relsizes[-1]
1584 return (self.relsizes[pos] + subvalue) / float(self.relsizes[-1])
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, **args):
1593 if self.subaxis is not None:
1594 if self.multisubaxis:
1595 subaxis = [subaxis.createlinkaxis() for subaxis in self.subaxis]
1596 else:
1597 subaxis = self.subaxis.createlinkaxis()
1598 else:
1599 subaxis = None
1600 return baraxis(subaxis=subaxis, dist=self.dist, firstdist=self.firstdist, lastdist=self.lastdist, **args)
1602 createsubaxis = createlinkaxis
1605 ################################################################################
1606 # graph
1607 ################################################################################
1610 class graphxy(canvas.canvas):
1612 Names = "x", "y"
1614 def clipcanvas(self):
1615 return self.insert(canvas.canvas(canvas.clip(path._rect(self._xpos, self._ypos, self._width, self._height))))
1617 def plot(self, data, style=None):
1618 if self.haslayout:
1619 raise RuntimeError("layout setup was already performed")
1620 if style is None:
1621 if self.defaultstyle.has_key(data.defaultstyle):
1622 style = self.defaultstyle[data.defaultstyle].iterate()
1623 else:
1624 style = data.defaultstyle()
1625 self.defaultstyle[data.defaultstyle] = style
1626 styles = []
1627 first = 1
1628 for d in helper.ensuresequence(data):
1629 if first:
1630 styles.append(style)
1631 else:
1632 styles.append(style.iterate())
1633 first = 0
1634 if d is not None:
1635 d.setstyle(self, styles[-1])
1636 self.data.append(d)
1637 if helper.issequence(data):
1638 return styles
1639 return styles[0]
1641 def _vxtickpoint(self, axis, v):
1642 return (self._xpos+v*self._width, axis.axispos)
1644 def _vytickpoint(self, axis, v):
1645 return (axis.axispos, self._ypos+v*self._height)
1647 def vtickdirection(self, axis, v):
1648 return axis.fixtickdirection
1650 def _pos(self, x, y, xaxis=None, yaxis=None):
1651 if xaxis is None: xaxis = self.axes["x"]
1652 if yaxis is None: yaxis = self.axes["y"]
1653 return self._xpos+xaxis.convert(x)*self._width, self._ypos+yaxis.convert(y)*self._height
1655 def pos(self, x, y, xaxis=None, yaxis=None):
1656 if xaxis is None: xaxis = self.axes["x"]
1657 if yaxis is None: yaxis = self.axes["y"]
1658 return self.xpos+xaxis.convert(x)*self.width, self.ypos+yaxis.convert(y)*self.height
1660 def _vpos(self, vx, vy):
1661 return self._xpos+vx*self._width, self._ypos+vy*self._height
1663 def vpos(self, vx, vy):
1664 return self.xpos+vx*self.width, self.ypos+vy*self.height
1666 def xbaseline(self, axis, x1, x2, shift=0, xaxis=None):
1667 if xaxis is None: xaxis = self.axes["x"]
1668 v1, v2 = xaxis.convert(x1), xaxis.convert(x2)
1669 return path._line(self._xpos+v1*self._width, axis.axispos+shift,
1670 self._xpos+v2*self._width, axis.axispos+shift)
1672 def ybaseline(self, axis, y1, y2, shift=0, yaxis=None):
1673 if yaxis is None: yaxis = self.axes["y"]
1674 v1, v2 = yaxis.convert(y1), yaxis.convert(y2)
1675 return path._line(axis.axispos+shift, self._ypos+v1*self._height,
1676 axis.axispos+shift, self._ypos+v2*self._height)
1678 def vxbaseline(self, axis, v1=None, v2=None, shift=0):
1679 if v1 is None: v1 = 0
1680 if v2 is None: v2 = 1
1681 return path._line(self._xpos+v1*self._width, axis.axispos+shift,
1682 self._xpos+v2*self._width, axis.axispos+shift)
1684 def vybaseline(self, axis, v1=None, v2=None, shift=0):
1685 if v1 is None: v1 = 0
1686 if v2 is None: v2 = 1
1687 return path._line(axis.axispos+shift, self._ypos+v1*self._height,
1688 axis.axispos+shift, self._ypos+v2*self._height)
1690 def xgridpath(self, x, xaxis=None):
1691 if xaxis is None: xaxis = self.axes["x"]
1692 v = xaxis.convert(x)
1693 return path._line(self._xpos+v*self._width, self._ypos,
1694 self._xpos+v*self._width, self._ypos+self._height)
1696 def ygridpath(self, y, yaxis=None):
1697 if yaxis is None: yaxis = self.axes["y"]
1698 v = yaxis.convert(y)
1699 return path._line(self._xpos, self._ypos+v*self._height,
1700 self._xpos+self._width, self._ypos+v*self._height)
1702 def vxgridpath(self, v):
1703 return path._line(self._xpos+v*self._width, self._ypos,
1704 self._xpos+v*self._width, self._ypos+self._height)
1706 def vygridpath(self, v):
1707 return path._line(self._xpos, self._ypos+v*self._height,
1708 self._xpos+self._width, self._ypos+v*self._height)
1710 def _addpos(self, x, y, dx, dy):
1711 return x+dx, y+dy
1713 def _connect(self, x1, y1, x2, y2):
1714 return path._lineto(x2, y2)
1716 def keynum(self, key):
1717 try:
1718 while key[0] in string.letters:
1719 key = key[1:]
1720 return int(key)
1721 except IndexError:
1722 return 1
1724 def gatherranges(self):
1725 ranges = {}
1726 for data in self.data:
1727 pdranges = data.getranges()
1728 if pdranges is not None:
1729 for key in pdranges.keys():
1730 if key not in ranges.keys():
1731 ranges[key] = pdranges[key]
1732 else:
1733 ranges[key] = (min(ranges[key][0], pdranges[key][0]),
1734 max(ranges[key][1], pdranges[key][1]))
1735 # known ranges are also set as ranges for the axes
1736 for key, axis in self.axes.items():
1737 if key in ranges.keys():
1738 axis.setdatarange(*ranges[key])
1739 ranges[key] = axis.getdatarange()
1740 if ranges[key] is None:
1741 del ranges[key]
1742 return ranges
1744 def removedomethod(self, method):
1745 hadmethod = 0
1746 while 1:
1747 try:
1748 self.domethods.remove(method)
1749 hadmethod = 1
1750 except ValueError:
1751 return hadmethod
1753 def dolayout(self):
1754 if not self.removedomethod(self.dolayout): return
1755 self.haslayout = 1
1756 # create list of ranges
1757 # 1. gather ranges
1758 ranges = self.gatherranges()
1759 # 2. calculate additional ranges out of known ranges
1760 for data in self.data:
1761 data.setranges(ranges)
1762 # 3. gather ranges again
1763 self.gatherranges()
1765 # do the layout for all axes
1766 axesdist = unit.topt(unit.length(self.axesdist_str, default_type="v"))
1767 XPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[0])
1768 YPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[1])
1769 self._xaxisextents = [0, 0]
1770 self._yaxisextents = [0, 0]
1771 needxaxisdist = [0, 0]
1772 needyaxisdist = [0, 0]
1773 items = list(self.axes.items())
1774 items.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
1775 for key, axis in items:
1776 num = self.keynum(key)
1777 num2 = 1 - num % 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
1778 num3 = 1 - 2 * (num % 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
1779 if XPattern.match(key):
1780 if needxaxisdist[num2]:
1781 self._xaxisextents[num2] += axesdist
1782 axis.axispos = self._ypos+num2*self._height + num3*self._xaxisextents[num2]
1783 axis._vtickpoint = self._vxtickpoint
1784 axis.fixtickdirection = (0, num3)
1785 axis.vgridpath = self.vxgridpath
1786 axis.vbaseline = self.vxbaseline
1787 axis.gridpath = self.xgridpath
1788 axis.baseline = self.xbaseline
1789 elif YPattern.match(key):
1790 if needyaxisdist[num2]:
1791 self._yaxisextents[num2] += axesdist
1792 axis.axispos = self._xpos+num2*self._width + num3*self._yaxisextents[num2]
1793 axis._vtickpoint = self._vytickpoint
1794 axis.fixtickdirection = (num3, 0)
1795 axis.vgridpath = self.vygridpath
1796 axis.vbaseline = self.vybaseline
1797 axis.gridpath = self.ygridpath
1798 axis.baseline = self.ybaseline
1799 else:
1800 raise ValueError("Axis key '%s' not allowed" % key)
1801 axis.vtickdirection = self.vtickdirection
1802 axis.dolayout(self)
1803 if XPattern.match(key):
1804 self._xaxisextents[num2] += axis._extent
1805 needxaxisdist[num2] = 1
1806 if YPattern.match(key):
1807 self._yaxisextents[num2] += axis._extent
1808 needyaxisdist[num2] = 1
1810 def dobackground(self):
1811 self.dolayout()
1812 if not self.removedomethod(self.dobackground): return
1813 if self.backgroundattrs is not None:
1814 self.draw(path._rect(self._xpos, self._ypos, self._width, self._height),
1815 *helper.ensuresequence(self.backgroundattrs))
1817 def doaxes(self):
1818 self.dolayout()
1819 if not self.removedomethod(self.doaxes): return
1820 for axis in self.axes.values():
1821 axis.dopaint(self)
1823 def dodata(self):
1824 self.dolayout()
1825 if not self.removedomethod(self.dodata): return
1826 for data in self.data:
1827 data.draw(self)
1829 def finish(self):
1830 while len(self.domethods):
1831 self.domethods[0]()
1833 def initwidthheight(self, width, height, ratio):
1834 if (width is not None) and (height is None):
1835 self.width = unit.length(width)
1836 self.height = (1/ratio) * self.width
1837 elif (height is not None) and (width is None):
1838 self.height = unit.length(height)
1839 self.width = ratio * self.height
1840 else:
1841 self.width = unit.length(width)
1842 self.height = unit.length(height)
1843 self._width = unit.topt(self.width)
1844 self._height = unit.topt(self.height)
1845 if self._width <= 0: raise ValueError("width <= 0")
1846 if self._height <= 0: raise ValueError("height <= 0")
1848 def initaxes(self, axes, addlinkaxes=0):
1849 for key in self.Names:
1850 if not axes.has_key(key):
1851 axes[key] = linaxis()
1852 elif axes[key] is None:
1853 del axes[key]
1854 if addlinkaxes:
1855 if not axes.has_key(key + "2") and axes.has_key(key):
1856 axes[key + "2"] = axes[key].createlinkaxis()
1857 elif axes[key + "2"] is None:
1858 del axes[key + "2"]
1859 self.axes = axes
1861 def __init__(self, xpos=0, ypos=0, width=None, height=None, ratio=goldenrule,
1862 backgroundattrs=None, dense=1, axesdist="0.8 cm", **axes):
1863 canvas.canvas.__init__(self)
1864 self.xpos = unit.length(xpos)
1865 self.ypos = unit.length(ypos)
1866 self._xpos = unit.topt(self.xpos)
1867 self._ypos = unit.topt(self.ypos)
1868 self.initwidthheight(width, height, ratio)
1869 self.initaxes(axes, 1)
1870 self.dense = dense
1871 self.axesdist_str = axesdist
1872 self.backgroundattrs = backgroundattrs
1873 self.data = []
1874 self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
1875 self.haslayout = 0
1876 self.defaultstyle = {}
1878 def bbox(self):
1879 self.finish()
1880 return canvas.canvas.bbox(self)
1881 #return bbox.bbox(self._xpos - self._yaxisextents[0],
1882 # self._ypos - self._xaxisextents[0],
1883 # self._xpos + self._width + self._yaxisextents[1],
1884 # self._ypos + self._height + self._xaxisextents[1])
1886 def write(self, file):
1887 self.finish()
1888 canvas.canvas.write(self, file)
1892 # some thoughts, but deferred right now
1894 # class graphxyz(graphxy):
1896 # Names = "x", "y", "z"
1898 # def _vxtickpoint(self, axis, v):
1899 # return self._vpos(v, axis.vypos, axis.vzpos)
1901 # def _vytickpoint(self, axis, v):
1902 # return self._vpos(axis.vxpos, v, axis.vzpos)
1904 # def _vztickpoint(self, axis, v):
1905 # return self._vpos(axis.vxpos, axis.vypos, v)
1907 # def vxtickdirection(self, axis, v):
1908 # x1, y1 = self._vpos(v, axis.vypos, axis.vzpos)
1909 # x2, y2 = self._vpos(v, 0.5, 0)
1910 # dx, dy = x1 - x2, y1 - y2
1911 # norm = math.sqrt(dx*dx + dy*dy)
1912 # return dx/norm, dy/norm
1914 # def vytickdirection(self, axis, v):
1915 # x1, y1 = self._vpos(axis.vxpos, v, axis.vzpos)
1916 # x2, y2 = self._vpos(0.5, v, 0)
1917 # dx, dy = x1 - x2, y1 - y2
1918 # norm = math.sqrt(dx*dx + dy*dy)
1919 # return dx/norm, dy/norm
1921 # def vztickdirection(self, axis, v):
1922 # return -1, 0
1923 # x1, y1 = self._vpos(axis.vxpos, axis.vypos, v)
1924 # x2, y2 = self._vpos(0.5, 0.5, v)
1925 # dx, dy = x1 - x2, y1 - y2
1926 # norm = math.sqrt(dx*dx + dy*dy)
1927 # return dx/norm, dy/norm
1929 # def _pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
1930 # if xaxis is None: xaxis = self.axes["x"]
1931 # if yaxis is None: yaxis = self.axes["y"]
1932 # if zaxis is None: zaxis = self.axes["z"]
1933 # return self._vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
1935 # def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
1936 # if xaxis is None: xaxis = self.axes["x"]
1937 # if yaxis is None: yaxis = self.axes["y"]
1938 # if zaxis is None: zaxis = self.axes["z"]
1939 # return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
1941 # def _vpos(self, vx, vy, vz):
1942 # x, y, z = (vx - 0.5)*self._depth, (vy - 0.5)*self._width, (vz - 0.5)*self._height
1943 # d0 = float(self.a[0]*self.b[1]*(z-self.eye[2])
1944 # + self.a[2]*self.b[0]*(y-self.eye[1])
1945 # + self.a[1]*self.b[2]*(x-self.eye[0])
1946 # - self.a[2]*self.b[1]*(x-self.eye[0])
1947 # - self.a[0]*self.b[2]*(y-self.eye[1])
1948 # - self.a[1]*self.b[0]*(z-self.eye[2]))
1949 # da = (self.eye[0]*self.b[1]*(z-self.eye[2])
1950 # + self.eye[2]*self.b[0]*(y-self.eye[1])
1951 # + self.eye[1]*self.b[2]*(x-self.eye[0])
1952 # - self.eye[2]*self.b[1]*(x-self.eye[0])
1953 # - self.eye[0]*self.b[2]*(y-self.eye[1])
1954 # - self.eye[1]*self.b[0]*(z-self.eye[2]))
1955 # db = (self.a[0]*self.eye[1]*(z-self.eye[2])
1956 # + self.a[2]*self.eye[0]*(y-self.eye[1])
1957 # + self.a[1]*self.eye[2]*(x-self.eye[0])
1958 # - self.a[2]*self.eye[1]*(x-self.eye[0])
1959 # - self.a[0]*self.eye[2]*(y-self.eye[1])
1960 # - self.a[1]*self.eye[0]*(z-self.eye[2]))
1961 # return da/d0 + self._xpos, db/d0 + self._ypos
1963 # def vpos(self, vx, vy, vz):
1964 # tx, ty = self._vpos(vx, vy, vz)
1965 # return unit.t_pt(tx), unit.t_pt(ty)
1967 # def xbaseline(self, axis, x1, x2, shift=0, xaxis=None):
1968 # if xaxis is None: xaxis = self.axes["x"]
1969 # return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2), shift)
1971 # def ybaseline(self, axis, y1, y2, shift=0, yaxis=None):
1972 # if yaxis is None: yaxis = self.axes["y"]
1973 # return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2), shift)
1975 # def zbaseline(self, axis, z1, z2, shift=0, zaxis=None):
1976 # if zaxis is None: zaxis = self.axes["z"]
1977 # return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2), shift)
1979 # def vxbaseline(self, axis, v1, v2, shift=0):
1980 # return (path._line(*(self._vpos(v1, 0, 0) + self._vpos(v2, 0, 0))) +
1981 # path._line(*(self._vpos(v1, 0, 1) + self._vpos(v2, 0, 1))) +
1982 # path._line(*(self._vpos(v1, 1, 1) + self._vpos(v2, 1, 1))) +
1983 # path._line(*(self._vpos(v1, 1, 0) + self._vpos(v2, 1, 0))))
1985 # def vybaseline(self, axis, v1, v2, shift=0):
1986 # return (path._line(*(self._vpos(0, v1, 0) + self._vpos(0, v2, 0))) +
1987 # path._line(*(self._vpos(0, v1, 1) + self._vpos(0, v2, 1))) +
1988 # path._line(*(self._vpos(1, v1, 1) + self._vpos(1, v2, 1))) +
1989 # path._line(*(self._vpos(1, v1, 0) + self._vpos(1, v2, 0))))
1991 # def vzbaseline(self, axis, v1, v2, shift=0):
1992 # return (path._line(*(self._vpos(0, 0, v1) + self._vpos(0, 0, v2))) +
1993 # path._line(*(self._vpos(0, 1, v1) + self._vpos(0, 1, v2))) +
1994 # path._line(*(self._vpos(1, 1, v1) + self._vpos(1, 1, v2))) +
1995 # path._line(*(self._vpos(1, 0, v1) + self._vpos(1, 0, v2))))
1997 # def xgridpath(self, x, xaxis=None):
1998 # assert 0
1999 # if xaxis is None: xaxis = self.axes["x"]
2000 # v = xaxis.convert(x)
2001 # return path._line(self._xpos+v*self._width, self._ypos,
2002 # self._xpos+v*self._width, self._ypos+self._height)
2004 # def ygridpath(self, y, yaxis=None):
2005 # assert 0
2006 # if yaxis is None: yaxis = self.axes["y"]
2007 # v = yaxis.convert(y)
2008 # return path._line(self._xpos, self._ypos+v*self._height,
2009 # self._xpos+self._width, self._ypos+v*self._height)
2011 # def zgridpath(self, z, zaxis=None):
2012 # assert 0
2013 # if zaxis is None: zaxis = self.axes["z"]
2014 # v = zaxis.convert(z)
2015 # return path._line(self._xpos, self._zpos+v*self._height,
2016 # self._xpos+self._width, self._zpos+v*self._height)
2018 # def vxgridpath(self, v):
2019 # return path.path(path._moveto(*self._vpos(v, 0, 0)),
2020 # path._lineto(*self._vpos(v, 0, 1)),
2021 # path._lineto(*self._vpos(v, 1, 1)),
2022 # path._lineto(*self._vpos(v, 1, 0)),
2023 # path.closepath())
2025 # def vygridpath(self, v):
2026 # return path.path(path._moveto(*self._vpos(0, v, 0)),
2027 # path._lineto(*self._vpos(0, v, 1)),
2028 # path._lineto(*self._vpos(1, v, 1)),
2029 # path._lineto(*self._vpos(1, v, 0)),
2030 # path.closepath())
2032 # def vzgridpath(self, v):
2033 # return path.path(path._moveto(*self._vpos(0, 0, v)),
2034 # path._lineto(*self._vpos(0, 1, v)),
2035 # path._lineto(*self._vpos(1, 1, v)),
2036 # path._lineto(*self._vpos(1, 0, v)),
2037 # path.closepath())
2039 # def _addpos(self, x, y, dx, dy):
2040 # assert 0
2041 # return x+dx, y+dy
2043 # def _connect(self, x1, y1, x2, y2):
2044 # assert 0
2045 # return path._lineto(x2, y2)
2047 # def doaxes(self):
2048 # self.dolayout()
2049 # if not self.removedomethod(self.doaxes): return
2050 # axesdist = unit.topt(unit.length(self.axesdist_str, default_type="v"))
2051 # XPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[0])
2052 # YPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[1])
2053 # ZPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[2])
2054 # items = list(self.axes.items())
2055 # items.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
2056 # for key, axis in items:
2057 # num = self.keynum(key)
2058 # num2 = 1 - num % 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
2059 # num3 = 1 - 2 * (num % 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
2060 # if XPattern.match(key):
2061 # axis.vypos = 0
2062 # axis.vzpos = 0
2063 # axis._vtickpoint = self._vxtickpoint
2064 # axis.vgridpath = self.vxgridpath
2065 # axis.vbaseline = self.vxbaseline
2066 # axis.vtickdirection = self.vxtickdirection
2067 # elif YPattern.match(key):
2068 # axis.vxpos = 0
2069 # axis.vzpos = 0
2070 # axis._vtickpoint = self._vytickpoint
2071 # axis.vgridpath = self.vygridpath
2072 # axis.vbaseline = self.vybaseline
2073 # axis.vtickdirection = self.vytickdirection
2074 # elif ZPattern.match(key):
2075 # axis.vxpos = 0
2076 # axis.vypos = 0
2077 # axis._vtickpoint = self._vztickpoint
2078 # axis.vgridpath = self.vzgridpath
2079 # axis.vbaseline = self.vzbaseline
2080 # axis.vtickdirection = self.vztickdirection
2081 # else:
2082 # raise ValueError("Axis key '%s' not allowed" % key)
2083 # if axis.painter is not None:
2084 # axis.dopaint(self)
2085 # # if XPattern.match(key):
2086 # # self._xaxisextents[num2] += axis._extent
2087 # # needxaxisdist[num2] = 1
2088 # # if YPattern.match(key):
2089 # # self._yaxisextents[num2] += axis._extent
2090 # # needyaxisdist[num2] = 1
2092 # def __init__(self, tex, xpos=0, ypos=0, width=None, height=None, depth=None,
2093 # phi=30, theta=30, distance=1,
2094 # backgroundattrs=None, axesdist="0.8 cm", **axes):
2095 # canvas.canvas.__init__(self)
2096 # self.tex = tex
2097 # self.xpos = xpos
2098 # self.ypos = ypos
2099 # self._xpos = unit.topt(xpos)
2100 # self._ypos = unit.topt(ypos)
2101 # self._width = unit.topt(width)
2102 # self._height = unit.topt(height)
2103 # self._depth = unit.topt(depth)
2104 # self.width = width
2105 # self.height = height
2106 # self.depth = depth
2107 # if self._width <= 0: raise ValueError("width < 0")
2108 # if self._height <= 0: raise ValueError("height < 0")
2109 # if self._depth <= 0: raise ValueError("height < 0")
2110 # self._distance = distance*math.sqrt(self._width*self._width+
2111 # self._height*self._height+
2112 # self._depth*self._depth)
2113 # phi *= -math.pi/180
2114 # theta *= math.pi/180
2115 # self.a = (-math.sin(phi), math.cos(phi), 0)
2116 # self.b = (-math.cos(phi)*math.sin(theta),
2117 # -math.sin(phi)*math.sin(theta),
2118 # math.cos(theta))
2119 # self.eye = (self._distance*math.cos(phi)*math.cos(theta),
2120 # self._distance*math.sin(phi)*math.cos(theta),
2121 # self._distance*math.sin(theta))
2122 # self.initaxes(axes)
2123 # self.axesdist_str = axesdist
2124 # self.backgroundattrs = backgroundattrs
2126 # self.data = []
2127 # self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
2128 # self.haslayout = 0
2129 # self.defaultstyle = {}
2131 # def bbox(self):
2132 # self.finish()
2133 # return bbox.bbox(self._xpos - 200, self._ypos - 200, self._xpos + 200, self._ypos + 200)
2136 ################################################################################
2137 # attr changers
2138 ################################################################################
2141 #class _Ichangeattr:
2142 # """attribute changer
2143 # is an iterator for attributes where an attribute
2144 # is not refered by just a number (like for a sequence),
2145 # but also by the number of attributes requested
2146 # by calls of the next method (like for an color gradient)
2147 # (you should ensure to call all needed next before the attr)
2149 # the attribute itself is implemented by overloading the _attr method"""
2151 # def attr(self):
2152 # "get an attribute"
2154 # def next(self):
2155 # "get an attribute changer for the next attribute"
2158 class _changeattr: pass
2161 class changeattr(_changeattr):
2163 def __init__(self):
2164 self.counter = 1
2166 def getattr(self):
2167 return self.attr(0)
2169 def iterate(self):
2170 newindex = self.counter
2171 self.counter += 1
2172 return refattr(self, newindex)
2175 class refattr(_changeattr):
2177 def __init__(self, ref, index):
2178 self.ref = ref
2179 self.index = index
2181 def getattr(self):
2182 return self.ref.attr(self.index)
2184 def iterate(self):
2185 return self.ref.iterate()
2188 # helper routines for a using attrs
2190 def _getattr(attr):
2191 """get attr out of a attr/changeattr"""
2192 if isinstance(attr, _changeattr):
2193 return attr.getattr()
2194 return attr
2197 def _getattrs(attrs):
2198 """get attrs out of a sequence of attr/changeattr"""
2199 if attrs is not None:
2200 result = []
2201 for attr in helper.ensuresequence(attrs):
2202 if isinstance(attr, _changeattr):
2203 result.append(attr.getattr())
2204 else:
2205 result.append(attr)
2206 return result
2209 def _iterateattr(attr):
2210 """perform next to a attr/changeattr"""
2211 if isinstance(attr, _changeattr):
2212 return attr.iterate()
2213 return attr
2216 def _iterateattrs(attrs):
2217 """perform next to a sequence of attr/changeattr"""
2218 if attrs is not None:
2219 result = []
2220 for attr in helper.ensuresequence(attrs):
2221 if isinstance(attr, _changeattr):
2222 result.append(attr.iterate())
2223 else:
2224 result.append(attr)
2225 return result
2228 class changecolor(changeattr):
2230 def __init__(self, gradient):
2231 changeattr.__init__(self)
2232 self.gradient = gradient
2234 def attr(self, index):
2235 if self.counter != 1:
2236 return self.gradient.getcolor(index/float(self.counter-1))
2237 else:
2238 return self.gradient.getcolor(0)
2241 class _changecolorgray(changecolor):
2243 def __init__(self, gradient=color.gradient.Gray):
2244 changecolor.__init__(self, gradient)
2246 _changecolorgrey = _changecolorgray
2249 class _changecolorreversegray(changecolor):
2251 def __init__(self, gradient=color.gradient.ReverseGray):
2252 changecolor.__init__(self, gradient)
2254 _changecolorreversegrey = _changecolorreversegray
2257 class _changecolorredblack(changecolor):
2259 def __init__(self, gradient=color.gradient.RedBlack):
2260 changecolor.__init__(self, gradient)
2263 class _changecolorblackred(changecolor):
2265 def __init__(self, gradient=color.gradient.BlackRed):
2266 changecolor.__init__(self, gradient)
2269 class _changecolorredwhite(changecolor):
2271 def __init__(self, gradient=color.gradient.RedWhite):
2272 changecolor.__init__(self, gradient)
2275 class _changecolorwhitered(changecolor):
2277 def __init__(self, gradient=color.gradient.WhiteRed):
2278 changecolor.__init__(self, gradient)
2281 class _changecolorgreenblack(changecolor):
2283 def __init__(self, gradient=color.gradient.GreenBlack):
2284 changecolor.__init__(self, gradient)
2287 class _changecolorblackgreen(changecolor):
2289 def __init__(self, gradient=color.gradient.BlackGreen):
2290 changecolor.__init__(self, gradient)
2293 class _changecolorgreenwhite(changecolor):
2295 def __init__(self, gradient=color.gradient.GreenWhite):
2296 changecolor.__init__(self, gradient)
2299 class _changecolorwhitegreen(changecolor):
2301 def __init__(self, gradient=color.gradient.WhiteGreen):
2302 changecolor.__init__(self, gradient)
2305 class _changecolorblueblack(changecolor):
2307 def __init__(self, gradient=color.gradient.BlueBlack):
2308 changecolor.__init__(self, gradient)
2311 class _changecolorblackblue(changecolor):
2313 def __init__(self, gradient=color.gradient.BlackBlue):
2314 changecolor.__init__(self, gradient)
2317 class _changecolorbluewhite(changecolor):
2319 def __init__(self, gradient=color.gradient.BlueWhite):
2320 changecolor.__init__(self, gradient)
2323 class _changecolorwhiteblue(changecolor):
2325 def __init__(self, gradient=color.gradient.WhiteBlue):
2326 changecolor.__init__(self, gradient)
2329 class _changecolorredgreen(changecolor):
2331 def __init__(self, gradient=color.gradient.RedGreen):
2332 changecolor.__init__(self, gradient)
2335 class _changecolorredblue(changecolor):
2337 def __init__(self, gradient=color.gradient.RedBlue):
2338 changecolor.__init__(self, gradient)
2341 class _changecolorgreenred(changecolor):
2343 def __init__(self, gradient=color.gradient.GreenRed):
2344 changecolor.__init__(self, gradient)
2347 class _changecolorgreenblue(changecolor):
2349 def __init__(self, gradient=color.gradient.GreenBlue):
2350 changecolor.__init__(self, gradient)
2353 class _changecolorbluered(changecolor):
2355 def __init__(self, gradient=color.gradient.BlueRed):
2356 changecolor.__init__(self, gradient)
2359 class _changecolorbluegreen(changecolor):
2361 def __init__(self, gradient=color.gradient.BlueGreen):
2362 changecolor.__init__(self, gradient)
2365 class _changecolorrainbow(changecolor):
2367 def __init__(self, gradient=color.gradient.Rainbow):
2368 changecolor.__init__(self, gradient)
2371 class _changecolorreverserainbow(changecolor):
2373 def __init__(self, gradient=color.gradient.ReverseRainbow):
2374 changecolor.__init__(self, gradient)
2377 class _changecolorhue(changecolor):
2379 def __init__(self, gradient=color.gradient.Hue):
2380 changecolor.__init__(self, gradient)
2383 class _changecolorreversehue(changecolor):
2385 def __init__(self, gradient=color.gradient.ReverseHue):
2386 changecolor.__init__(self, gradient)
2389 changecolor.Gray = _changecolorgray
2390 changecolor.Grey = _changecolorgrey
2391 changecolor.Reversegray = _changecolorreversegray
2392 changecolor.Reversegrey = _changecolorreversegrey
2393 changecolor.RedBlack = _changecolorredblack
2394 changecolor.BlackRed = _changecolorblackred
2395 changecolor.RedWhite = _changecolorredwhite
2396 changecolor.WhiteRed = _changecolorwhitered
2397 changecolor.GreenBlack = _changecolorgreenblack
2398 changecolor.BlackGreen = _changecolorblackgreen
2399 changecolor.GreenWhite = _changecolorgreenwhite
2400 changecolor.WhiteGreen = _changecolorwhitegreen
2401 changecolor.BlueBlack = _changecolorblueblack
2402 changecolor.BlackBlue = _changecolorblackblue
2403 changecolor.BlueWhite = _changecolorbluewhite
2404 changecolor.WhiteBlue = _changecolorwhiteblue
2405 changecolor.RedGreen = _changecolorredgreen
2406 changecolor.RedBlue = _changecolorredblue
2407 changecolor.GreenRed = _changecolorgreenred
2408 changecolor.GreenBlue = _changecolorgreenblue
2409 changecolor.BlueRed = _changecolorbluered
2410 changecolor.BlueGreen = _changecolorbluegreen
2411 changecolor.Rainbow = _changecolorrainbow
2412 changecolor.ReverseRainbow = _changecolorreverserainbow
2413 changecolor.Hue = _changecolorhue
2414 changecolor.ReverseHue = _changecolorreversehue
2417 class changesequence(changeattr):
2418 """cycles through a sequence"""
2420 def __init__(self, *sequence):
2421 changeattr.__init__(self)
2422 if not len(sequence):
2423 sequence = self.defaultsequence
2424 self.sequence = sequence
2426 def attr(self, index):
2427 return self.sequence[index % len(self.sequence)]
2430 class changelinestyle(changesequence):
2431 defaultsequence = (canvas.linestyle.solid,
2432 canvas.linestyle.dashed,
2433 canvas.linestyle.dotted,
2434 canvas.linestyle.dashdotted)
2437 class changestrokedfilled(changesequence):
2438 defaultsequence = (canvas.stroked(), canvas.filled())
2441 class changefilledstroked(changesequence):
2442 defaultsequence = (canvas.filled(), canvas.stroked())
2446 ################################################################################
2447 # styles
2448 ################################################################################
2451 class symbol:
2453 def cross(self, x, y):
2454 return (path._moveto(x-0.5*self._size, y-0.5*self._size),
2455 path._lineto(x+0.5*self._size, y+0.5*self._size),
2456 path._moveto(x-0.5*self._size, y+0.5*self._size),
2457 path._lineto(x+0.5*self._size, y-0.5*self._size))
2459 def plus(self, x, y):
2460 return (path._moveto(x-0.707106781*self._size, y),
2461 path._lineto(x+0.707106781*self._size, y),
2462 path._moveto(x, y-0.707106781*self._size),
2463 path._lineto(x, y+0.707106781*self._size))
2465 def square(self, x, y):
2466 return (path._moveto(x-0.5*self._size, y-0.5 * self._size),
2467 path._lineto(x+0.5*self._size, y-0.5 * self._size),
2468 path._lineto(x+0.5*self._size, y+0.5 * self._size),
2469 path._lineto(x-0.5*self._size, y+0.5 * self._size),
2470 path.closepath())
2472 def triangle(self, x, y):
2473 return (path._moveto(x-0.759835685*self._size, y-0.438691337*self._size),
2474 path._lineto(x+0.759835685*self._size, y-0.438691337*self._size),
2475 path._lineto(x, y+0.877382675*self._size),
2476 path.closepath())
2478 def circle(self, x, y):
2479 return (path._arc(x, y, 0.564189583*self._size, 0, 360),
2480 path.closepath())
2482 def diamond(self, x, y):
2483 return (path._moveto(x-0.537284965*self._size, y),
2484 path._lineto(x, y-0.930604859*self._size),
2485 path._lineto(x+0.537284965*self._size, y),
2486 path._lineto(x, y+0.930604859*self._size),
2487 path.closepath())
2489 def __init__(self, symbol=helper.nodefault,
2490 size="0.2 cm", symbolattrs=canvas.stroked(),
2491 errorscale=0.5, errorbarattrs=(),
2492 lineattrs=None):
2493 self.size_str = size
2494 if symbol is helper.nodefault:
2495 self._symbol = changesymbol.cross()
2496 else:
2497 self._symbol = symbol
2498 self._symbolattrs = symbolattrs
2499 self.errorscale = errorscale
2500 self._errorbarattrs = errorbarattrs
2501 self._lineattrs = lineattrs
2503 def iteratedict(self):
2504 result = {}
2505 result["symbol"] = _iterateattr(self._symbol)
2506 result["size"] = _iterateattr(self.size_str)
2507 result["symbolattrs"] = _iterateattrs(self._symbolattrs)
2508 result["errorscale"] = _iterateattr(self.errorscale)
2509 result["errorbarattrs"] = _iterateattrs(self._errorbarattrs)
2510 result["lineattrs"] = _iterateattrs(self._lineattrs)
2511 return result
2513 def iterate(self):
2514 return symbol(**self.iteratedict())
2516 def othercolumnkey(self, key, index):
2517 raise ValueError("unsuitable key '%s'" % key)
2519 def setcolumns(self, graph, columns):
2520 def checkpattern(key, index, pattern, iskey, isindex):
2521 if key is not None:
2522 match = pattern.match(key)
2523 if match:
2524 if isindex is not None: raise ValueError("multiple key specification")
2525 if iskey is not None and iskey != match.groups()[0]: raise ValueError("inconsistent key names")
2526 key = None
2527 iskey = match.groups()[0]
2528 isindex = index
2529 return key, iskey, isindex
2531 self.xi = self.xmini = self.xmaxi = None
2532 self.dxi = self.dxmini = self.dxmaxi = None
2533 self.yi = self.ymini = self.ymaxi = None
2534 self.dyi = self.dymini = self.dymaxi = None
2535 self.xkey = self.ykey = None
2536 if len(graph.Names) != 2: raise TypeError("style not applicable in graph")
2537 XPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.Names[0])
2538 YPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.Names[1])
2539 XMinPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)min$" % graph.Names[0])
2540 YMinPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)min$" % graph.Names[1])
2541 XMaxPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)max$" % graph.Names[0])
2542 YMaxPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)max$" % graph.Names[1])
2543 DXPattern = re.compile(r"d(%s([2-9]|[1-9][0-9]+)?)$" % graph.Names[0])
2544 DYPattern = re.compile(r"d(%s([2-9]|[1-9][0-9]+)?)$" % graph.Names[1])
2545 DXMinPattern = re.compile(r"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph.Names[0])
2546 DYMinPattern = re.compile(r"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph.Names[1])
2547 DXMaxPattern = re.compile(r"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph.Names[0])
2548 DYMaxPattern = re.compile(r"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph.Names[1])
2549 for key, index in columns.items():
2550 key, self.xkey, self.xi = checkpattern(key, index, XPattern, self.xkey, self.xi)
2551 key, self.ykey, self.yi = checkpattern(key, index, YPattern, self.ykey, self.yi)
2552 key, self.xkey, self.xmini = checkpattern(key, index, XMinPattern, self.xkey, self.xmini)
2553 key, self.ykey, self.ymini = checkpattern(key, index, YMinPattern, self.ykey, self.ymini)
2554 key, self.xkey, self.xmaxi = checkpattern(key, index, XMaxPattern, self.xkey, self.xmaxi)
2555 key, self.ykey, self.ymaxi = checkpattern(key, index, YMaxPattern, self.ykey, self.ymaxi)
2556 key, self.xkey, self.dxi = checkpattern(key, index, DXPattern, self.xkey, self.dxi)
2557 key, self.ykey, self.dyi = checkpattern(key, index, DYPattern, self.ykey, self.dyi)
2558 key, self.xkey, self.dxmini = checkpattern(key, index, DXMinPattern, self.xkey, self.dxmini)
2559 key, self.ykey, self.dymini = checkpattern(key, index, DYMinPattern, self.ykey, self.dymini)
2560 key, self.xkey, self.dxmaxi = checkpattern(key, index, DXMaxPattern, self.xkey, self.dxmaxi)
2561 key, self.ykey, self.dymaxi = checkpattern(key, index, DYMaxPattern, self.ykey, self.dymaxi)
2562 if key is not None:
2563 self.othercolumnkey(key, index)
2564 if None in (self.xkey, self.ykey): raise ValueError("incomplete axis specification")
2565 if (len(filter(None, (self.xmini, self.dxmini, self.dxi))) > 1 or
2566 len(filter(None, (self.ymini, self.dymini, self.dyi))) > 1 or
2567 len(filter(None, (self.xmaxi, self.dxmaxi, self.dxi))) > 1 or
2568 len(filter(None, (self.ymaxi, self.dymaxi, self.dyi))) > 1):
2569 raise ValueError("multiple errorbar definition")
2570 if ((self.xi is None and self.dxi is not None) or
2571 (self.yi is None and self.dyi is not None) or
2572 (self.xi is None and self.dxmini is not None) or
2573 (self.yi is None and self.dymini is not None) or
2574 (self.xi is None and self.dxmaxi is not None) or
2575 (self.yi is None and self.dymaxi is not None)):
2576 raise ValueError("errorbar definition start value missing")
2577 self.xaxis = graph.axes[self.xkey]
2578 self.yaxis = graph.axes[self.ykey]
2580 def minmidmax(self, point, i, mini, maxi, di, dmini, dmaxi):
2581 min = max = mid = None
2582 try:
2583 mid = point[i] + 0.0
2584 except (TypeError, ValueError):
2585 pass
2586 try:
2587 if di is not None: min = point[i] - point[di]
2588 elif dmini is not None: min = point[i] - point[dmini]
2589 elif mini is not None: min = point[mini] + 0.0
2590 except (TypeError, ValueError):
2591 pass
2592 try:
2593 if di is not None: max = point[i] + point[di]
2594 elif dmaxi is not None: max = point[i] + point[dmaxi]
2595 elif maxi is not None: max = point[maxi] + 0.0
2596 except (TypeError, ValueError):
2597 pass
2598 if mid is not None:
2599 if min is not None and min > mid: raise ValueError("minimum error in errorbar")
2600 if max is not None and max < mid: raise ValueError("maximum error in errorbar")
2601 else:
2602 if min is not None and max is not None and min > max: raise ValueError("minimum/maximum error in errorbar")
2603 return min, mid, max
2605 def keyrange(self, points, i, mini, maxi, di, dmini, dmaxi):
2606 allmin = allmax = None
2607 if filter(None, (mini, maxi, di, dmini, dmaxi)) is not None:
2608 for point in points:
2609 min, mid, max = self.minmidmax(point, i, mini, maxi, di, dmini, dmaxi)
2610 if min is not None and (allmin is None or min < allmin): allmin = min
2611 if mid is not None and (allmin is None or mid < allmin): allmin = mid
2612 if mid is not None and (allmax is None or mid > allmax): allmax = mid
2613 if max is not None and (allmax is None or max > allmax): allmax = max
2614 else:
2615 for point in points:
2616 try:
2617 value = point[i] + 0.0
2618 if allmin is None or point[i] < allmin: allmin = point[i]
2619 if allmax is None or point[i] > allmax: allmax = point[i]
2620 except (TypeError, ValueError):
2621 pass
2622 return allmin, allmax
2624 def getranges(self, points):
2625 xmin, xmax = self.keyrange(points, self.xi, self.xmini, self.xmaxi, self.dxi, self.dxmini, self.dxmaxi)
2626 ymin, ymax = self.keyrange(points, self.yi, self.ymini, self.ymaxi, self.dyi, self.dymini, self.dymaxi)
2627 return {self.xkey: (xmin, xmax), self.ykey: (ymin, ymax)}
2629 def _drawerrorbar(self, graph, topleft, top, topright,
2630 left, center, right,
2631 bottomleft, bottom, bottomright, point=None):
2632 if left is not None:
2633 if right is not None:
2634 left1 = graph._addpos(*(left+(0, -self._errorsize)))
2635 left2 = graph._addpos(*(left+(0, self._errorsize)))
2636 right1 = graph._addpos(*(right+(0, -self._errorsize)))
2637 right2 = graph._addpos(*(right+(0, self._errorsize)))
2638 graph.stroke(path.path(path._moveto(*left1),
2639 graph._connect(*(left1+left2)),
2640 path._moveto(*left),
2641 graph._connect(*(left+right)),
2642 path._moveto(*right1),
2643 graph._connect(*(right1+right2))),
2644 *self.errorbarattrs)
2645 elif center is not None:
2646 left1 = graph._addpos(*(left+(0, -self._errorsize)))
2647 left2 = graph._addpos(*(left+(0, self._errorsize)))
2648 graph.stroke(path.path(path._moveto(*left1),
2649 graph._connect(*(left1+left2)),
2650 path._moveto(*left),
2651 graph._connect(*(left+center))),
2652 *self.errorbarattrs)
2653 else:
2654 left1 = graph._addpos(*(left+(0, -self._errorsize)))
2655 left2 = graph._addpos(*(left+(0, self._errorsize)))
2656 left3 = graph._addpos(*(left+(self._errorsize, 0)))
2657 graph.stroke(path.path(path._moveto(*left1),
2658 graph._connect(*(left1+left2)),
2659 path._moveto(*left),
2660 graph._connect(*(left+left3))),
2661 *self.errorbarattrs)
2662 if right is not None and left is None:
2663 if center is not None:
2664 right1 = graph._addpos(*(right+(0, -self._errorsize)))
2665 right2 = graph._addpos(*(right+(0, self._errorsize)))
2666 graph.stroke(path.path(path._moveto(*right1),
2667 graph._connect(*(right1+right2)),
2668 path._moveto(*right),
2669 graph._connect(*(right+center))),
2670 *self.errorbarattrs)
2671 else:
2672 right1 = graph._addpos(*(right+(0, -self._errorsize)))
2673 right2 = graph._addpos(*(right+(0, self._errorsize)))
2674 right3 = graph._addpos(*(right+(-self._errorsize, 0)))
2675 graph.stroke(path.path(path._moveto(*right1),
2676 graph._connect(*(right1+right2)),
2677 path._moveto(*right),
2678 graph._connect(*(right+right3))),
2679 *self.errorbarattrs)
2681 if bottom is not None:
2682 if top is not None:
2683 bottom1 = graph._addpos(*(bottom+(-self._errorsize, 0)))
2684 bottom2 = graph._addpos(*(bottom+(self._errorsize, 0)))
2685 top1 = graph._addpos(*(top+(-self._errorsize, 0)))
2686 top2 = graph._addpos(*(top+(self._errorsize, 0)))
2687 graph.stroke(path.path(path._moveto(*bottom1),
2688 graph._connect(*(bottom1+bottom2)),
2689 path._moveto(*bottom),
2690 graph._connect(*(bottom+top)),
2691 path._moveto(*top1),
2692 graph._connect(*(top1+top2))),
2693 *self.errorbarattrs)
2694 elif center is not None:
2695 bottom1 = graph._addpos(*(bottom+(-self._errorsize, 0)))
2696 bottom2 = graph._addpos(*(bottom+(self._errorsize, 0)))
2697 graph.stroke(path.path(path._moveto(*bottom1),
2698 graph._connect(*(bottom1+bottom2)),
2699 path._moveto(*bottom),
2700 graph._connect(*(bottom+center))),
2701 *self.errorbarattrs)
2702 else:
2703 bottom1 = graph._addpos(*(bottom+(-self._errorsize, 0)))
2704 bottom2 = graph._addpos(*(bottom+(self._errorsize, 0)))
2705 bottom3 = graph._addpos(*(bottom+(0, self._errorsize)))
2706 graph.stroke(path.path(path._moveto(*bottom1),
2707 graph._connect(*(bottom1+bottom2)),
2708 path._moveto(*bottom),
2709 graph._connect(*(bottom+bottom3))),
2710 *self.errorbarattrs)
2711 if top is not None and bottom is None:
2712 if center is not None:
2713 top1 = graph._addpos(*(top+(-self._errorsize, 0)))
2714 top2 = graph._addpos(*(top+(self._errorsize, 0)))
2715 graph.stroke(path.path(path._moveto(*top1),
2716 graph._connect(*(top1+top2)),
2717 path._moveto(*top),
2718 graph._connect(*(top+center))),
2719 *self.errorbarattrs)
2720 else:
2721 top1 = graph._addpos(*(top+(-self._errorsize, 0)))
2722 top2 = graph._addpos(*(top+(self._errorsize, 0)))
2723 top3 = graph._addpos(*(top+(0, -self._errorsize)))
2724 graph.stroke(path.path(path._moveto(*top1),
2725 graph._connect(*(top1+top2)),
2726 path._moveto(*top),
2727 graph._connect(*(top+top3))),
2728 *self.errorbarattrs)
2729 if bottomleft is not None:
2730 if topleft is not None and bottomright is None:
2731 bottomleft1 = graph._addpos(*(bottomleft+(self._errorsize, 0)))
2732 topleft1 = graph._addpos(*(topleft+(self._errorsize, 0)))
2733 graph.stroke(path.path(path._moveto(*bottomleft1),
2734 graph._connect(*(bottomleft1+bottomleft)),
2735 graph._connect(*(bottomleft+topleft)),
2736 graph._connect(*(topleft+topleft1))),
2737 *self.errorbarattrs)
2738 elif bottomright is not None and topleft is None:
2739 bottomleft1 = graph._addpos(*(bottomleft+(0, self._errorsize)))
2740 bottomright1 = graph._addpos(*(bottomright+(0, self._errorsize)))
2741 graph.stroke(path.path(path._moveto(*bottomleft1),
2742 graph._connect(*(bottomleft1+bottomleft)),
2743 graph._connect(*(bottomleft+bottomright)),
2744 graph._connect(*(bottomright+bottomright1))),
2745 *self.errorbarattrs)
2746 elif bottomright is None and topleft is None:
2747 bottomleft1 = graph._addpos(*(bottomleft+(self._errorsize, 0)))
2748 bottomleft2 = graph._addpos(*(bottomleft+(0, self._errorsize)))
2749 graph.stroke(path.path(path._moveto(*bottomleft1),
2750 graph._connect(*(bottomleft1+bottomleft)),
2751 graph._connect(*(bottomleft+bottomleft2))),
2752 *self.errorbarattrs)
2753 if topright is not None:
2754 if bottomright is not None and topleft is None:
2755 topright1 = graph._addpos(*(topright+(-self._errorsize, 0)))
2756 bottomright1 = graph._addpos(*(bottomright+(-self._errorsize, 0)))
2757 graph.stroke(path.path(path._moveto(*topright1),
2758 graph._connect(*(topright1+topright)),
2759 graph._connect(*(topright+bottomright)),
2760 graph._connect(*(bottomright+bottomright1))),
2761 *self.errorbarattrs)
2762 elif topleft is not None and bottomright is None:
2763 topright1 = graph._addpos(*(topright+(0, -self._errorsize)))
2764 topleft1 = graph._addpos(*(topleft+(0, -self._errorsize)))
2765 graph.stroke(path.path(path._moveto(*topright1),
2766 graph._connect(*(topright1+topright)),
2767 graph._connect(*(topright+topleft)),
2768 graph._connect(*(topleft+topleft1))),
2769 *self.errorbarattrs)
2770 elif topleft is None and bottomright is None:
2771 topright1 = graph._addpos(*(topright+(-self._errorsize, 0)))
2772 topright2 = graph._addpos(*(topright+(0, -self._errorsize)))
2773 graph.stroke(path.path(path._moveto(*topright1),
2774 graph._connect(*(topright1+topright)),
2775 graph._connect(*(topright+topright2))),
2776 *self.errorbarattrs)
2777 if bottomright is not None and bottomleft is None and topright is None:
2778 bottomright1 = graph._addpos(*(bottomright+(-self._errorsize, 0)))
2779 bottomright2 = graph._addpos(*(bottomright+(0, self._errorsize)))
2780 graph.stroke(path.path(path._moveto(*bottomright1),
2781 graph._connect(*(bottomright1+bottomright)),
2782 graph._connect(*(bottomright+bottomright2))),
2783 *self.errorbarattrs)
2784 if topleft is not None and bottomleft is None and topright is None:
2785 topleft1 = graph._addpos(*(topleft+(self._errorsize, 0)))
2786 topleft2 = graph._addpos(*(topleft+(0, -self._errorsize)))
2787 graph.stroke(path.path(path._moveto(*topleft1),
2788 graph._connect(*(topleft1+topleft)),
2789 graph._connect(*(topleft+topleft2))),
2790 *self.errorbarattrs)
2791 if bottomleft is not None and bottomright is not None and topright is not None and topleft is not None:
2792 graph.stroke(path.path(path._moveto(*bottomleft),
2793 graph._connect(*(bottomleft+bottomright)),
2794 graph._connect(*(bottomright+topright)),
2795 graph._connect(*(topright+topleft)),
2796 path.closepath()),
2797 *self.errorbarattrs)
2799 def _drawsymbol(self, canvas, x, y, point=None):
2800 canvas.draw(path.path(*self.symbol(self, x, y)), *self.symbolattrs)
2802 def drawsymbol(self, canvas, x, y, point=None):
2803 self._drawsymbol(canvas, unit.topt(x), unit.topt(y), point)
2805 def drawpoints(self, graph, points):
2806 xaxismin, xaxismax = self.xaxis.getdatarange()
2807 yaxismin, yaxismax = self.yaxis.getdatarange()
2808 self.size = unit.length(_getattr(self.size_str), default_type="v")
2809 self._size = unit.topt(self.size)
2810 self.symbol = _getattr(self._symbol)
2811 self.symbolattrs = _getattrs(helper.ensuresequence(self._symbolattrs))
2812 self.errorbarattrs = _getattrs(helper.ensuresequence(self._errorbarattrs))
2813 self._errorsize = self.errorscale * self._size
2814 self.errorsize = self.errorscale * self.size
2815 self.lineattrs = _getattrs(helper.ensuresequence(self._lineattrs))
2816 if self._lineattrs is not None:
2817 clipcanvas = graph.clipcanvas()
2818 lineels = []
2819 haserror = filter(None, (self.xmini, self.ymini, self.xmaxi, self.ymaxi,
2820 self.dxi, self.dyi, self.dxmini, self.dymini, self.dxmaxi, self.dymaxi)) is not None
2821 moveto = 1
2822 for point in points:
2823 drawsymbol = 1
2824 xmin, x, xmax = self.minmidmax(point, self.xi, self.xmini, self.xmaxi, self.dxi, self.dxmini, self.dxmaxi)
2825 ymin, y, ymax = self.minmidmax(point, self.yi, self.ymini, self.ymaxi, self.dyi, self.dymini, self.dymaxi)
2826 if x is not None and x < xaxismin: drawsymbol = 0
2827 elif x is not None and x > xaxismax: drawsymbol = 0
2828 elif y is not None and y < yaxismin: drawsymbol = 0
2829 elif y is not None and y > yaxismax: drawsymbol = 0
2830 elif haserror:
2831 if xmin is not None and xmin < xaxismin: drawsymbol = 0
2832 elif xmax is not None and xmax < xaxismin: drawsymbol = 0
2833 elif xmax is not None and xmax > xaxismax: drawsymbol = 0
2834 elif xmin is not None and xmin > xaxismax: drawsymbol = 0
2835 elif ymin is not None and ymin < yaxismin: drawsymbol = 0
2836 elif ymax is not None and ymax < yaxismin: drawsymbol = 0
2837 elif ymax is not None and ymax > yaxismax: drawsymbol = 0
2838 elif ymin is not None and ymin > yaxismax: drawsymbol = 0
2839 xpos=ypos=topleft=top=topright=left=center=right=bottomleft=bottom=bottomright=None
2840 if x is not None and y is not None:
2841 try:
2842 center = xpos, ypos = graph._pos(x, y, xaxis=self.xaxis, yaxis=self.yaxis)
2843 except ValueError:
2844 pass
2845 if haserror:
2846 if y is not None:
2847 if xmin is not None: left = graph._pos(xmin, y, xaxis=self.xaxis, yaxis=self.yaxis)
2848 if xmax is not None: right = graph._pos(xmax, y, xaxis=self.xaxis, yaxis=self.yaxis)
2849 if x is not None:
2850 if ymax is not None: top = graph._pos(x, ymax, xaxis=self.xaxis, yaxis=self.yaxis)
2851 if ymin is not None: bottom = graph._pos(x, ymin, xaxis=self.xaxis, yaxis=self.yaxis)
2852 if x is None or y is None:
2853 if ymax is not None:
2854 if xmin is not None: topleft = graph._pos(xmin, ymax, xaxis=self.xaxis, yaxis=self.yaxis)
2855 if xmax is not None: topright = graph._pos(xmax, ymax, xaxis=self.xaxis, yaxis=self.yaxis)
2856 if ymin is not None:
2857 if xmin is not None: bottomleft = graph._pos(xmin, ymin, xaxis=self.xaxis, yaxis=self.yaxis)
2858 if xmax is not None: bottomright = graph._pos(xmax, ymin, xaxis=self.xaxis, yaxis=self.yaxis)
2859 if drawsymbol:
2860 if self._errorbarattrs is not None and haserror:
2861 self._drawerrorbar(graph, topleft, top, topright,
2862 left, center, right,
2863 bottomleft, bottom, bottomright, point)
2864 if self._symbolattrs is not None and xpos is not None and ypos is not None:
2865 self._drawsymbol(graph, xpos, ypos, point)
2866 if xpos is not None and ypos is not None:
2867 if moveto:
2868 lineels.append(path._moveto(xpos, ypos))
2869 moveto = 0
2870 else:
2871 lineels.append(path._lineto(xpos, ypos))
2872 else:
2873 moveto = 1
2874 self.path = path.path(*lineels)
2875 if self._lineattrs is not None:
2876 clipcanvas.stroke(self.path, *self.lineattrs)
2879 class changesymbol(changesequence): pass
2882 class _changesymbolcross(changesymbol):
2883 defaultsequence = (symbol.cross, symbol.plus, symbol.square, symbol.triangle, symbol.circle, symbol.diamond)
2886 class _changesymbolplus(changesymbol):
2887 defaultsequence = (symbol.plus, symbol.square, symbol.triangle, symbol.circle, symbol.diamond, symbol.cross)
2890 class _changesymbolsquare(changesymbol):
2891 defaultsequence = (symbol.square, symbol.triangle, symbol.circle, symbol.diamond, symbol.cross, symbol.plus)
2894 class _changesymboltriangle(changesymbol):
2895 defaultsequence = (symbol.triangle, symbol.circle, symbol.diamond, symbol.cross, symbol.plus, symbol.square)
2898 class _changesymbolcircle(changesymbol):
2899 defaultsequence = (symbol.circle, symbol.diamond, symbol.cross, symbol.plus, symbol.square, symbol.triangle)
2902 class _changesymboldiamond(changesymbol):
2903 defaultsequence = (symbol.diamond, symbol.cross, symbol.plus, symbol.square, symbol.triangle, symbol.circle)
2906 class _changesymbolsquaretwice(changesymbol):
2907 defaultsequence = (symbol.square, symbol.square, symbol.triangle, symbol.triangle,
2908 symbol.circle, symbol.circle, symbol.diamond, symbol.diamond)
2911 class _changesymboltriangletwice(changesymbol):
2912 defaultsequence = (symbol.triangle, symbol.triangle, symbol.circle, symbol.circle,
2913 symbol.diamond, symbol.diamond, symbol.square, symbol.square)
2916 class _changesymbolcircletwice(changesymbol):
2917 defaultsequence = (symbol.circle, symbol.circle, symbol.diamond, symbol.diamond,
2918 symbol.square, symbol.square, symbol.triangle, symbol.triangle)
2921 class _changesymboldiamondtwice(changesymbol):
2922 defaultsequence = (symbol.diamond, symbol.diamond, symbol.square, symbol.square,
2923 symbol.triangle, symbol.triangle, symbol.circle, symbol.circle)
2926 changesymbol.cross = _changesymbolcross
2927 changesymbol.plus = _changesymbolplus
2928 changesymbol.square = _changesymbolsquare
2929 changesymbol.triangle = _changesymboltriangle
2930 changesymbol.circle = _changesymbolcircle
2931 changesymbol.diamond = _changesymboldiamond
2932 changesymbol.squaretwice = _changesymbolsquaretwice
2933 changesymbol.triangletwice = _changesymboltriangletwice
2934 changesymbol.circletwice = _changesymbolcircletwice
2935 changesymbol.diamondtwice = _changesymboldiamondtwice
2938 class line(symbol):
2940 def __init__(self, lineattrs=helper.nodefault):
2941 if lineattrs is helper.nodefault:
2942 lineattrs = changelinestyle()
2943 symbol.__init__(self, symbolattrs=None, errorbarattrs=None, lineattrs=lineattrs)
2946 class rect(symbol):
2948 def __init__(self, gradient=color.gradient.Gray):
2949 self.gradient = gradient
2950 self.colorindex = None
2951 symbol.__init__(self, symbolattrs=None, errorbarattrs=(), lineattrs=None)
2953 def iterate(self):
2954 raise RuntimeError("style is not iterateable")
2956 def othercolumnkey(self, key, index):
2957 if key == "color":
2958 self.colorindex = index
2959 else:
2960 symbol.othercolumnkey(self, key, index)
2962 def _drawerrorbar(self, graph, topleft, top, topright,
2963 left, center, right,
2964 bottomleft, bottom, bottomright, point=None):
2965 color = point[self.colorindex]
2966 if color is not None:
2967 if color != self.lastcolor:
2968 self.rectclipcanvas.set(self.gradient.getcolor(color))
2969 if bottom is not None and left is not None:
2970 bottomleft = left[0], bottom[1]
2971 if bottom is not None and right is not None:
2972 bottomright = right[0], bottom[1]
2973 if top is not None and right is not None:
2974 topright = right[0], top[1]
2975 if top is not None and left is not None:
2976 topleft = left[0], top[1]
2977 if bottomleft is not None and bottomright is not None and topright is not None and topleft is not None:
2978 self.rectclipcanvas.fill(path.path(path._moveto(*bottomleft),
2979 graph._connect(*(bottomleft+bottomright)),
2980 graph._connect(*(bottomright+topright)),
2981 graph._connect(*(topright+topleft)),
2982 path.closepath()))
2984 def drawpoints(self, graph, points):
2985 if self.colorindex is None:
2986 raise RuntimeError("column 'color' not set")
2987 self.lastcolor = None
2988 self.rectclipcanvas = graph.clipcanvas()
2989 symbol.drawpoints(self, graph, points)
2993 class text(symbol):
2995 def __init__(self, textdx="0", textdy="0.3 cm", textattrs=text.halign.center, **args):
2996 self.textindex = None
2997 self.textdx_str = textdx
2998 self.textdy_str = textdy
2999 self._textattrs = textattrs
3000 symbol.__init__(self, **args)
3002 def iteratedict(self):
3003 result = symbol.iteratedict()
3004 result["textattrs"] = _iterateattr(self._textattrs)
3005 return result
3007 def iterate(self):
3008 return textsymbol(**self.iteratedict())
3010 def othercolumnkey(self, key, index):
3011 if key == "text":
3012 self.textindex = index
3013 else:
3014 symbol.othercolumnkey(self, key, index)
3016 def _drawsymbol(self, graph, x, y, point=None):
3017 symbol._drawsymbol(self, graph, x, y, point)
3018 #TODO
3019 #if None not in (x, y, point[self.textindex], self._textattrs):
3020 # graph.tex._text(x + self._textdx, y + self._textdy, str(point[self.textindex]), *helper.ensuresequence(self.textattrs))
3022 def drawpoints(self, graph, points):
3023 self.textdx = unit.length(_getattr(self.textdx_str), default_type="v")
3024 self.textdy = unit.length(_getattr(self.textdy_str), default_type="v")
3025 self._textdx = unit.topt(self.textdx)
3026 self._textdy = unit.topt(self.textdy)
3027 if self._textattrs is not None:
3028 self.textattrs = _getattr(self._textattrs)
3029 if self.textindex is None:
3030 raise RuntimeError("column 'text' not set")
3031 symbol.drawpoints(self, graph, points)
3034 class arrow(symbol):
3036 def __init__(self, linelength="0.2 cm", arrowattrs=(), arrowsize="0.1 cm", arrowdict={}, epsilon=1e-10):
3037 self.linelength_str = linelength
3038 self.arrowsize_str = arrowsize
3039 self.arrowattrs = arrowattrs
3040 self.arrowdict = arrowdict
3041 self.epsilon = epsilon
3042 self.sizeindex = self.angleindex = None
3043 symbol.__init__(self, symbolattrs=(), errorbarattrs=None, lineattrs=None)
3045 def iterate(self):
3046 raise RuntimeError("style is not iterateable")
3048 def othercolumnkey(self, key, index):
3049 if key == "size":
3050 self.sizeindex = index
3051 elif key == "angle":
3052 self.angleindex = index
3053 else:
3054 symbol.othercolumnkey(self, key, index)
3056 def _drawsymbol(self, graph, x, y, point=None):
3057 if None not in (x, y, point[self.angleindex], point[self.sizeindex], self.arrowattrs, self.arrowdict):
3058 if point[self.sizeindex] > self.epsilon:
3059 dx, dy = math.cos(point[self.angleindex]*math.pi/180.0), math.sin(point[self.angleindex]*math.pi/180)
3060 x1 = unit.t_pt(x)-0.5*dx*self.linelength*point[self.sizeindex]
3061 y1 = unit.t_pt(y)-0.5*dy*self.linelength*point[self.sizeindex]
3062 x2 = unit.t_pt(x)+0.5*dx*self.linelength*point[self.sizeindex]
3063 y2 = unit.t_pt(y)+0.5*dy*self.linelength*point[self.sizeindex]
3064 graph.stroke(path.line(x1, y1, x2, y2),
3065 canvas.earrow(self.arrowsize*point[self.sizeindex],
3066 **self.arrowdict),
3067 *helper.ensuresequence(self.arrowattrs))
3069 def drawpoints(self, graph, points):
3070 self.arrowsize = unit.length(_getattr(self.arrowsize_str), default_type="v")
3071 self.linelength = unit.length(_getattr(self.linelength_str), default_type="v")
3072 self._arrowsize = unit.topt(self.arrowsize)
3073 self._linelength = unit.topt(self.linelength)
3074 if self.sizeindex is None:
3075 raise RuntimeError("column 'size' not set")
3076 if self.angleindex is None:
3077 raise RuntimeError("column 'angle' not set")
3078 symbol.drawpoints(self, graph, points)
3081 class _bariterator(changeattr):
3083 def attr(self, index):
3084 return index, self.counter
3087 class bar:
3089 def __init__(self, fromzero=1, stacked=0, xbar=0,
3090 barattrs=(canvas.stroked(color.gray.black), changecolor.Rainbow()),
3091 _bariterator=_bariterator(), _previousbar=None):
3092 self.fromzero = fromzero
3093 self.stacked = stacked
3094 self.xbar = xbar
3095 self._barattrs = barattrs
3096 self.bariterator = _bariterator
3097 self.previousbar = _previousbar
3099 def iteratedict(self):
3100 result = {}
3101 result["barattrs"] = _iterateattrs(self._barattrs)
3102 return result
3104 def iterate(self):
3105 return bar(fromzero=self.fromzero, stacked=self.stacked, xbar=self.xbar,
3106 _bariterator=_iterateattr(self.bariterator), _previousbar=self, **self.iteratedict())
3108 def setcolumns(self, graph, columns):
3109 def checkpattern(key, index, pattern, iskey, isindex):
3110 if key is not None:
3111 match = pattern.match(key)
3112 if match:
3113 if isindex is not None: raise ValueError("multiple key specification")
3114 if iskey is not None and iskey != match.groups()[0]: raise ValueError("inconsistent key names")
3115 key = None
3116 iskey = match.groups()[0]
3117 isindex = index
3118 return key, iskey, isindex
3120 xkey = ykey = None
3121 if len(graph.Names) != 2: raise TypeError("style not applicable in graph")
3122 XPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.Names[0])
3123 YPattern = re.compile(r"(%s([2-9]|[1-9][0-9]+)?)$" % graph.Names[1])
3124 xi = yi = None
3125 for key, index in columns.items():
3126 key, xkey, xi = checkpattern(key, index, XPattern, xkey, xi)
3127 key, ykey, yi = checkpattern(key, index, YPattern, ykey, yi)
3128 if key is not None:
3129 self.othercolumnkey(key, index)
3130 if None in (xkey, ykey): raise ValueError("incomplete axis specification")
3131 if self.xbar:
3132 self.nkey, self.ni = ykey, yi
3133 self.vkey, self.vi = xkey, xi
3134 else:
3135 self.nkey, self.ni = xkey, xi
3136 self.vkey, self.vi = ykey, yi
3137 self.naxis, self.vaxis = graph.axes[self.nkey], graph.axes[self.vkey]
3139 def getranges(self, points):
3140 index, count = _getattr(self.bariterator)
3141 if count != 1 and self.stacked != 1:
3142 if self.stacked > 1:
3143 index = divmod(index, self.stacked)[0] # TODO: use this
3145 vmin = vmax = None
3146 for point in points:
3147 try:
3148 v = point[self.vi] + 0.0
3149 if vmin is None or v < vmin: vmin = v
3150 if vmax is None or v > vmax: vmax = v
3151 except (TypeError, ValueError):
3152 pass
3153 else:
3154 if count == 1:
3155 self.naxis.setname(point[self.ni])
3156 else:
3157 self.naxis.setname(point[self.ni], index)
3158 if self.fromzero:
3159 if vmin > 0: vmin = 0
3160 if vmax < 0: vmax = 0
3161 return {self.vkey: (vmin, vmax)}
3163 def drawpoints(self, graph, points):
3164 index, count = _getattr(self.bariterator)
3165 dostacked = (self.stacked != 0 and
3166 (self.stacked == 1 or divmod(index, self.stacked)[1]) and
3167 (self.stacked != 1 or index))
3168 if self.stacked > 1:
3169 index = divmod(index, self.stacked)[0]
3170 vmin, vmax = self.vaxis.getdatarange()
3171 self.barattrs = _getattrs(helper.ensuresequence(self._barattrs))
3172 if self.stacked:
3173 self.stackedvalue = {}
3174 for point in points:
3175 try:
3176 n = point[self.ni]
3177 v = point[self.vi]
3178 if self.stacked:
3179 self.stackedvalue[n] = v
3180 if count != 1 and self.stacked != 1:
3181 minid = (n, index, 0)
3182 maxid = (n, index, 1)
3183 else:
3184 minid = (n, 0)
3185 maxid = (n, 1)
3186 if self.xbar:
3187 x1pos, y1pos = graph._pos(v, minid, xaxis=self.vaxis, yaxis=self.naxis)
3188 x2pos, y2pos = graph._pos(v, maxid, xaxis=self.vaxis, yaxis=self.naxis)
3189 else:
3190 x1pos, y1pos = graph._pos(minid, v, xaxis=self.naxis, yaxis=self.vaxis)
3191 x2pos, y2pos = graph._pos(maxid, v, xaxis=self.naxis, yaxis=self.vaxis)
3192 if dostacked:
3193 if self.xbar:
3194 x3pos, y3pos = graph._pos(self.previousbar.stackedvalue[n], maxid, xaxis=self.vaxis, yaxis=self.naxis)
3195 x4pos, y4pos = graph._pos(self.previousbar.stackedvalue[n], minid, xaxis=self.vaxis, yaxis=self.naxis)
3196 else:
3197 x3pos, y3pos = graph._pos(maxid, self.previousbar.stackedvalue[n], xaxis=self.naxis, yaxis=self.vaxis)
3198 x4pos, y4pos = graph._pos(minid, self.previousbar.stackedvalue[n], xaxis=self.naxis, yaxis=self.vaxis)
3199 else:
3200 if self.fromzero:
3201 if self.xbar:
3202 x3pos, y3pos = graph._pos(0, maxid, xaxis=self.vaxis, yaxis=self.naxis)
3203 x4pos, y4pos = graph._pos(0, minid, xaxis=self.vaxis, yaxis=self.naxis)
3204 else:
3205 x3pos, y3pos = graph._pos(maxid, 0, xaxis=self.naxis, yaxis=self.vaxis)
3206 x4pos, y4pos = graph._pos(minid, 0, xaxis=self.naxis, yaxis=self.vaxis)
3207 else:
3208 x3pos, y3pos = self.naxis._vtickpoint(self.naxis, self.naxis.convert(maxid))
3209 x4pos, y4pos = self.naxis._vtickpoint(self.naxis, self.naxis.convert(minid))
3210 graph.fill(path.path(path._moveto(x1pos, y1pos),
3211 graph._connect(x1pos, y1pos, x2pos, y2pos),
3212 graph._connect(x2pos, y2pos, x3pos, y3pos),
3213 graph._connect(x3pos, y3pos, x4pos, y4pos),
3214 graph._connect(x4pos, y4pos, x1pos, y1pos), # no closepath (might not be straight)
3215 path.closepath()), *self.barattrs)
3216 except (TypeError, ValueError): pass
3219 #class surface:
3221 # def setcolumns(self, graph, columns):
3222 # self.columns = columns
3224 # def getranges(self, points):
3225 # return {"x": (0, 10), "y": (0, 10), "z": (0, 1)}
3227 # def drawpoints(self, graph, points):
3228 # pass
3232 ################################################################################
3233 # data
3234 ################################################################################
3237 class data:
3239 defaultstyle = symbol
3241 def __init__(self, file, **columns):
3242 if helper.isstring(file):
3243 self.data = datamodule.datafile(file)
3244 else:
3245 self.data = file
3246 self.columns = {}
3247 usedkeys = []
3248 for key, column in columns.items():
3249 try:
3250 self.columns[key] = self.data.getcolumnno(column)
3251 except datamodule.ColumnError:
3252 self.columns[key] = len(self.data.titles)
3253 usedkeys.extend(self.data._addcolumn(column, **columns))
3254 for usedkey in usedkeys:
3255 if usedkey in self.columns.keys():
3256 del self.columns[usedkey]
3258 def setstyle(self, graph, style):
3259 self.style = style
3260 self.style.setcolumns(graph, self.columns)
3262 def getranges(self):
3263 return self.style.getranges(self.data.data)
3265 def setranges(self, ranges):
3266 pass
3268 def draw(self, graph):
3269 self.style.drawpoints(graph, self.data.data)
3272 class function:
3274 defaultstyle = line
3276 def __init__(self, expression, min=None, max=None, points=100, parser=mathtree.parser(), extern=None):
3277 self.min = min
3278 self.max = max
3279 self.points = points
3280 self.extern = extern
3281 self.result, expression = expression.split("=")
3282 self.mathtree = parser.parse(expression, extern=self.extern)
3283 if extern is None:
3284 self.variable, = self.mathtree.VarList()
3285 else:
3286 self.variable = None
3287 for variable in self.mathtree.VarList():
3288 if variable not in self.extern.keys():
3289 if self.variable is None:
3290 self.variable = variable
3291 else:
3292 raise ValueError("multiple variables found (identifiers might be externally defined)")
3293 if self.variable is None:
3294 raise ValueError("no variable found (identifiers are all defined externally)")
3295 self.evalranges = 0
3297 def setstyle(self, graph, style):
3298 self.xaxis = graph.axes[self.variable]
3299 self.style = style
3300 self.style.setcolumns(graph, {self.variable: 0, self.result: 1})
3302 def getranges(self):
3303 if self.evalranges:
3304 return self.style.getranges(self.data)
3305 if None not in (self.min, self.max):
3306 return {self.variable: (self.min, self.max)}
3308 def setranges(self, ranges):
3309 if ranges.has_key(self.variable):
3310 min, max = ranges[self.variable]
3311 if self.min is not None: min = self.min
3312 if self.max is not None: max = self.max
3313 vmin = self.xaxis.convert(min)
3314 vmax = self.xaxis.convert(max)
3315 self.data = []
3316 for i in range(self.points):
3317 x = self.xaxis.invert(vmin + (vmax-vmin)*i / (self.points-1.0))
3318 try:
3319 y = self.mathtree.Calc({self.variable: x}, self.extern)
3320 except (ArithmeticError, ValueError):
3321 y = None
3322 self.data.append((x, y))
3323 self.evalranges = 1
3325 def draw(self, graph):
3326 self.style.drawpoints(graph, self.data)
3329 class paramfunction:
3331 defaultstyle = line
3333 def __init__(self, varname, min, max, expression, points=100, parser=mathtree.parser(), extern=None):
3334 self.varname = varname
3335 self.min = min
3336 self.max = max
3337 self.points = points
3338 self.expression = {}
3339 self.mathtrees = {}
3340 varlist, expressionlist = expression.split("=")
3341 parsestr = mathtree.ParseStr(expressionlist)
3342 for key in varlist.split(","):
3343 key = key.strip()
3344 if self.mathtrees.has_key(key):
3345 raise ValueError("multiple assignment in tuple")
3346 try:
3347 self.mathtrees[key] = parser.ParseMathTree(parsestr, extern)
3348 break
3349 except mathtree.CommaFoundMathTreeParseError, e:
3350 self.mathtrees[key] = e.MathTree
3351 else:
3352 raise ValueError("unpack tuple of wrong size")
3353 if len(varlist.split(",")) != len(self.mathtrees.keys()):
3354 raise ValueError("unpack tuple of wrong size")
3355 self.data = []
3356 for i in range(self.points):
3357 value = self.min + (self.max-self.min)*i / (self.points-1.0)
3358 line = []
3359 for key, tree in self.mathtrees.items():
3360 line.append(tree.Calc({self.varname: value}, extern))
3361 self.data.append(line)
3363 def setstyle(self, graph, style):
3364 self.style = style
3365 columns = {}
3366 for key, index in zip(self.mathtrees.keys(), xrange(sys.maxint)):
3367 columns[key] = index
3368 self.style.setcolumns(graph, columns)
3370 def getranges(self):
3371 return self.style.getranges(self.data)
3373 def setranges(self, ranges):
3374 pass
3376 def draw(self, graph):
3377 self.style.drawpoints(graph, self.data)