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 ################################################################################
35 ################################################################################
38 "maps convert a value into another value by bijective transformation f"
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."""
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]
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
)
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]
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 ################################################################################
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")
105 if not helper
.isinteger(power
): raise TypeError("integer type expected")
107 self
.enum
= long(enum
) ** power
108 self
.denom
= long(denom
) ** power
110 self
.enum
= long(denom
) ** (-power
)
111 self
.denom
= long(enum
) ** (-power
)
116 def __cmp__(self
, other
):
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
)
125 return float(self
.enum
) / self
.denom
128 return "%i/%i" % (self
.enum
, self
.denom
)
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"
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])
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
)
157 if not isinstance(arg
, frac
): raise ValueError("can't convert argument to 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
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
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
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
])
195 if list2
[j
] == list1
[i
]: # merge tick
196 list1
[i
].merge(list2
[j
])
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
)
212 if tick
.labellevel
== level
:
213 tick
.text
= usetext
[i
]
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
)
221 if tick
.labellevel
== 0:
222 tick
.text
= usetext
[i
]
224 if i
!= len(usetext
):
225 raise IndexError("wrong sequence length of texts")
230 def __init__(self
, ticks
=None, labels
=None, texts
=None, mix
=()):
232 if ticks
is None and labels
is not None:
233 self
.ticks
= helper
.ensuresequence(helper
.getsequenceno(labels
, 0))
237 if labels
is None and ticks
is not None:
238 self
.labels
= helper
.ensuresequence(helper
.getsequenceno(ticks
, 0))
245 def checkfraclist(self
, *fracs
):
246 if not len(fracs
): return ()
250 for item
in sorted[1:]:
252 raise ValueError("duplicate entry found")
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
)))])
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
)))])
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
)
278 def defaultpart(self
, min, max, extendmin
, extendmax
):
284 def __init__(self
, ticks
=None, labels
=None, texts
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10, mix
=()):
286 if ticks
is None and labels
is not None:
287 self
.ticks
= (_ensurefrac(helper
.ensuresequence(labels
)[0]),)
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]),)
293 self
.labels
= map(_ensurefrac
, helper
.ensuresequence(labels
))
295 self
.extendtick
= extendtick
296 self
.extendlabel
= extendlabel
297 self
.epsilon
= epsilon
300 def extendminmax(self
, min, max, frac
, extendmin
, extendmax
):
302 min = float(frac
) * math
.floor(min / float(frac
) + self
.epsilon
)
304 max = float(frac
) * math
.ceil(max / float(frac
) - self
.epsilon
)
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
))
311 for i
in range(imin
, imax
+ 1):
312 ticks
.append(tick(long(i
) * frac
.enum
, frac
.denom
, ticklevel
= ticklevel
, labellevel
= labellevel
))
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
)
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
=()):
342 self
.extendtick
= extendtick
343 self
.epsilon
= epsilon
346 def defaultpart(self
, min, max, extendmin
, extendmax
):
347 base
= frac(10L, 1, int(math
.log(max - min) / math
.log(10)))
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
)
358 if self
.lesstickindex
< len(self
.list) - 1:
359 self
.lesstickindex
+= 1
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
)
369 if self
.moretickindex
:
370 self
.moretickindex
-= 1
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
)
382 def __init__(self
, shift
, *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
=()):
400 if ticks
is None and labels
is not None:
401 self
.ticks
= (helper
.ensuresequence(labels
)[0],)
403 self
.ticks
= helper
.ensuresequence(ticks
)
405 if labels
is None and ticks
is not None:
406 self
.labels
= (helper
.ensuresequence(ticks
)[0],)
408 self
.labels
= helper
.ensuresequence(labels
)
410 self
.extendtick
= extendtick
411 self
.extendlabel
= extendlabel
412 self
.epsilon
= epsilon
415 def extendminmax(self
, min, max, shiftfracs
, extendmin
, extendmax
):
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
428 minfrac
= shiftfracs
.fracs
[minindex
- 1]
430 minfrac
= shiftfracs
.fracs
[-1]
432 if maxindex
!= len(shiftfracs
.fracs
) - 1:
433 maxfrac
= shiftfracs
.fracs
[maxindex
+ 1]
435 maxfrac
= shiftfracs
.fracs
[0]
438 min = float(minfrac
) * float(shiftfracs
.shift
) ** minpower
440 max = float(maxfrac
) * float(shiftfracs
.shift
) ** maxpower
443 def getticks(self
, min, max, shiftfracs
, ticklevel
=None, labellevel
=None):
444 ticks
= list(self
.mix
)
447 for f
in shiftfracs
.fracs
:
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
)
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
=()):
491 self
.listindex
= divmod(len(list), 2)[0]
494 self
.extendtick
= extendtick
495 self
.extendlabel
= extendlabel
496 self
.epsilon
= epsilon
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
)
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
)
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
)
525 ################################################################################
527 ################################################################################
532 def __init__(self
, opt
, left
=None, right
=None, weight
=1):
542 def rate(self
, value
, dense
=1):
543 opt
= self
.opt
* dense
545 other
= self
.left
* dense
547 other
= self
.right
* dense
550 factor
= (value
- opt
) / float(other
- opt
)
551 return self
.weight
* (factor
** 3)
556 def __init__(self
, opt
, weight
=0.1):
560 def _rate(self
, distances
, dense
=1):
562 opt
= unit
.topt(unit
.length(self
.opt_str
, default_type
="v")) / dense
564 for distance
in distances
:
566 rate
+= self
.weight
* (opt
/ distance
- 1)
568 rate
+= self
.weight
* (distance
/ opt
- 1)
569 return rate
/ float(len(distances
))
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
):
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
)
591 labels
= [0]*labelslen
594 if tick
.ticklevel
is not None:
595 for level
in xrange(tick
.ticklevel
, tickslen
):
597 if tick
.labellevel
is not None:
598 for level
in xrange(tick
.labellevel
, labelslen
):
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
))
612 rate
+= self
.tickrange
.rate(0)
613 weight
+= self
.tickrange
.weight
616 def _ratedistances(self
, distances
, dense
=1):
617 return self
.distance
._rate
(distances
, dense
=dense
)
620 ################################################################################
622 ################################################################################
625 class axistitlepainter(attrlist
.attrlist
):
630 def __init__(self
, titledist
="0.3 cm",
631 titleattrs
=(text
.halign
.center
, text
.valign
.centerline()),
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
):
643 while (direction
< -90 - epsilon
):
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)]
675 def __init__(self
, innerticklengths
=defaultticklengths
,
676 outerticklengths
=None,
680 baselineattrs
=canvas
.linecap
.square
,
682 labelattrs
=((text
.halign
.center
, text
.valign
.centerline()),
683 (text
.halign
.center
, text
.valign
.centerline(), text
.size
.footnotesize
)),
687 fractype
=fractypeauto
,
689 ratfracover
=r
"\over",
691 expfractimes
=r
"\cdot",
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
)
720 # greates common divisor, m & n must be non-negative
724 m
, (dummy
, n
) = n
, divmod(m
, n
)
727 def attachsuffix(self
, tick
, str):
728 if self
.suffix0
or tick
.enum
:
729 if tick
.suffix
is not None and not self
.suffix1
:
734 if tick
.suffix
is not None:
735 str = str + tick
.suffix
738 def ratfrac(self
, tick
):
739 m
, n
= tick
.enum
, tick
.denom
741 if m
< 0: m
, sign
= -m
, -sign
742 if n
< 0: n
, sign
= -n
, -sign
744 (m
, dummy1
), (n
, dummy2
) = divmod(m
, gcd
), divmod(n
, gcd
)
746 if self
.ratfracsuffixenum
:
748 return "-{{%s}%s{%s}}" % (self
.attachsuffix(tick
, str(m
)), self
.ratfracover
, n
)
750 return "{{%s}%s{%s}}" % (self
.attachsuffix(tick
, str(m
)), self
.ratfracover
, n
)
753 return self
.attachsuffix(tick
, "-{{%s}%s{%s}}" % (m
, self
.ratfracover
, n
))
755 return self
.attachsuffix(tick
, "{{%s}%s{%s}}" % (m
, self
.ratfracover
, n
))
758 return self
.attachsuffix(tick
, "-%s" % m
)
760 return self
.attachsuffix(tick
, "%s" % m
)
762 def decfrac(self
, tick
):
763 m
, n
= tick
.enum
, tick
.denom
765 if m
< 0: m
, sign
= -m
, -sign
766 if n
< 0: n
, sign
= -n
, -sign
768 (m
, dummy1
), (n
, dummy2
) = divmod(m
, gcd
), divmod(n
, gcd
)
769 frac
, rest
= divmod(m
, n
)
773 strfrac
+= self
.decfracpoint
777 periodstart
= len(strfrac
) - (len(oldrest
) - oldrest
.index(rest
))
778 strfrac
= strfrac
[:periodstart
] + r
"\overline{" + strfrac
[periodstart
:] + "}"
782 frac
, rest
= divmod(rest
, n
)
785 return self
.attachsuffix(tick
, "-%s" % strfrac
)
787 return self
.attachsuffix(tick
, strfrac
)
789 def expfrac(self
, tick
, minexp
= None):
790 m
, n
= tick
.enum
, tick
.denom
792 if m
< 0: m
, sign
= -m
, -sign
793 if n
< 0: n
, sign
= -n
, -sign
796 while divmod(m
, n
)[0] > 9:
799 while divmod(m
, n
)[0] < 1:
802 if minexp
is not None and ((exp
< 0 and -exp
< minexp
) or (exp
>= 0 and exp
< minexp
)):
806 prefactor
= self
.decfrac(dummy
)
807 if prefactor
== "1" and not self
.expfracpre1
:
809 return self
.attachsuffix(tick
, "-10^{%i}" % exp
)
811 return self
.attachsuffix(tick
, "10^{%i}" % exp
)
814 return self
.attachsuffix(tick
, "-%s%s10^{%i}" % (prefactor
, self
.expfractimes
, exp
))
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
)
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
)
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
:
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
)
857 for tick
in axis
.ticks
[1:]:
858 if tick
.dx
!= axis
.ticks
[0].dx
or tick
.dy
!= axis
.ticks
[0].dy
:
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
)
865 for tick
in axis
.ticks
:
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
]
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
)
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
887 if tick
.textbox
is None:
888 if tick
.outerticklength
is not None and tick
.outerticklength
> 0:
889 extent
= tick
.outerticklength
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:
900 distances
= [ticktextboxes
[i
]._boxdistance
(ticktextboxes
[i
+1]) for i
in range(len(ticktextboxes
) - 1)]
901 except box
.BoxCrossError
:
903 rate
= axis
.rate
._ratedistances
(distances
, dense
)
906 if self
.labelattrs
is None:
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,
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):
948 if self
.breaklinesattrs
is None:
951 if axis
.vminover
is None:
956 left
= axis
.vmin
+v1
*(axis
.vmax
-axis
.vmin
)
958 if self
.breaklinesattrs
is None:
961 if axis
.vmaxover
is None:
964 right
= axis
.vmaxover
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
))
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,
1020 baselineattrs
=canvas
.linecap
.square
,
1022 nameattrs
=(text
.halign
.center
, text
.valign
.centerline
),
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
):
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
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]):
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
))
1072 axis
.nameboxes
.append(textmodule
.text(0, 0, str(name
), *nameattrs
))
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"))
1088 if self
.outerticklength_str
is not None:
1089 axis
.innerticklength
= 0
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"))
1095 if self
.innerticklength_str
is not None:
1096 axis
.outerticklength
= 0
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 ################################################################################
1139 ################################################################################
1141 class PartitionError(Exception): pass
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:
1157 self
.datavmin
= 0.05
1159 self
.datavmin
= datavmin
1160 if datavmax
is None:
1164 self
.datavmax
= 0.95
1166 self
.datavmax
= datavmax
1167 self
.tickvmin
= tickvmin
1168 self
.tickvmax
= tickvmax
1170 self
.divisor
= divisor
1172 self
.suffix
= suffix
1173 self
.painter
= painter
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):
1181 if not self
.fixmax
and max is not None and (self
.max is None or max > self
.max):
1183 if None not in (self
.min, self
.max):
1184 min, max, vmin
, vmax
= self
.min, self
.max, 0, 1
1186 self
.setbasepoints(((min, vmin
), (max, vmax
)))
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
)))
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
)))
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
):
1222 return self
.invert(1-self
.datavmin
), self
.invert(1-self
.datavmax
)
1224 return self
.invert(self
.datavmin
), self
.invert(self
.datavmax
)
1226 def gettickrange(self
):
1229 return self
.invert(1-self
.tickvmin
), self
.invert(1-self
.tickvmax
)
1231 return self
.invert(self
.tickvmin
), self
.invert(self
.tickvmax
)
1233 def dolayout(self
, graph
):
1234 if self
.dense
is not None:
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
]]
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
:
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
:
1276 while i
< len(variants
) and (bestrate
is None or variants
[i
][0] < bestrate
):
1277 saverange
= self
.__getinternalrange
()
1278 self
.ticks
= variants
[i
][1]
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
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
)
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]
1295 self
.ticks
= variants
[0][1]
1297 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
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
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)
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
1339 self
.min = self
.linkedaxis
.min
1341 self
.max = self
.linkedaxis
.max
1342 self
.skipticklevel
= skipticklevel
1343 self
.skiplabellevel
= skiplabellevel
1345 self
.painter
= painter
1347 def ticks(self
, ticks
):
1350 ticklevel
= _tick
.ticklevel
1351 labellevel
= _tick
.labellevel
1352 if self
.skipticklevel
is not None and ticklevel
>= self
.skipticklevel
:
1354 if self
.skiplabellevel
is not None and labellevel
>= self
.skiplabellevel
:
1356 if ticklevel
is not None or labellevel
is not None:
1357 result
.append(tick(_tick
.enum
, _tick
.denom
, ticklevel
, labellevel
))
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
)
1387 def __init__(self
, axislist
, splitlist
=0.5, splitdist
=0.1, relsizesplitdist
=1, title
=None, painter
=splitaxispainter()):
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
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
]
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
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
1439 self
.fixmin
= self
.axislist
[0].fixmin
1441 self
.min = self
.axislist
[0].min
1442 self
.fixmax
= self
.axislist
[-1].fixmax
1444 self
.max = self
.axislist
[-1].max
1448 def getdatarange(self
):
1449 min = self
.axislist
[0].getdatarange()
1450 max = self
.axislist
[-1].getdatarange()
1452 return min[0], max[1]
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()
1464 return min[0], max[1]
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
):
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")
1495 painter
= self
.painter
1496 return splitaxis([x
.createlinkaxis(**arg
) for x
, arg
in zip(self
.axislist
, args
)], painter
=painter
)
1501 def __init__(self
, subaxis
=None, multisubaxis
=0, title
=None, dist
=0.5, firstdist
=None, lastdist
=None, names
=None, texts
={}, painter
=baraxispainter()):
1503 if firstdist
is not None:
1504 self
.firstdist
= firstdist
1506 self
.firstdist
= 0.5 * dist
1507 if lastdist
is not None:
1508 self
.lastdist
= lastdist
1510 self
.lastdist
= 0.5 * dist
1511 self
.relsizes
= None
1514 for name
in helper
.ensuresequence(names
):
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
]
1522 self
.subaxis
= subaxis
1526 self
.painter
= painter
1528 def getdatarange(self
):
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
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
:
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
1558 if self
.subaxis
is None:
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])
1572 if self
.subaxis
is None:
1575 if self
.multisubaxis
:
1576 subvalue
= value
[1] * self
.subaxis
[pos
].relsizes
[-1]
1578 subvalue
= value
[1] * self
.subaxis
.relsizes
[-1]
1580 if self
.multisubaxis
:
1581 subvalue
= self
.subaxis
[pos
].convert(value
[1:]) * self
.subaxis
[pos
].relsizes
[-1]
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
]
1597 subaxis
= self
.subaxis
.createlinkaxis()
1600 return baraxis(subaxis
=subaxis
, dist
=self
.dist
, firstdist
=self
.firstdist
, lastdist
=self
.lastdist
, **args
)
1602 createsubaxis
= createlinkaxis
1605 ################################################################################
1607 ################################################################################
1610 class graphxy(canvas
.canvas
):
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):
1619 raise RuntimeError("layout setup was already performed")
1621 if self
.defaultstyle
.has_key(data
.defaultstyle
):
1622 style
= self
.defaultstyle
[data
.defaultstyle
].iterate()
1624 style
= data
.defaultstyle()
1625 self
.defaultstyle
[data
.defaultstyle
] = style
1628 for d
in helper
.ensuresequence(data
):
1630 styles
.append(style
)
1632 styles
.append(style
.iterate())
1635 d
.setstyle(self
, styles
[-1])
1637 if helper
.issequence(data
):
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
):
1713 def _connect(self
, x1
, y1
, x2
, y2
):
1714 return path
._lineto
(x2
, y2
)
1716 def keynum(self
, key
):
1718 while key
[0] in string
.letters
:
1724 def gatherranges(self
):
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
]
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:
1744 def removedomethod(self
, method
):
1748 self
.domethods
.remove(method
)
1754 if not self
.removedomethod(self
.dolayout
): return
1756 # create list of 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
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
1800 raise ValueError("Axis key '%s' not allowed" % key
)
1801 axis
.vtickdirection
= self
.vtickdirection
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
):
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
))
1819 if not self
.removedomethod(self
.doaxes
): return
1820 for axis
in self
.axes
.values():
1825 if not self
.removedomethod(self
.dodata
): return
1826 for data
in self
.data
:
1830 while len(self
.domethods
):
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
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:
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:
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)
1871 self
.axesdist_str
= axesdist
1872 self
.backgroundattrs
= backgroundattrs
1874 self
.domethods
= [self
.dolayout
, self
.dobackground
, self
.doaxes
, self
.dodata
]
1876 self
.defaultstyle
= {}
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):
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):
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):
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):
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):
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)),
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)),
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)),
2039 # def _addpos(self, x, y, dx, dy):
2043 # def _connect(self, x1, y1, x2, y2):
2045 # return path._lineto(x2, y2)
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):
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):
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):
2077 # axis._vtickpoint = self._vztickpoint
2078 # axis.vgridpath = self.vzgridpath
2079 # axis.vbaseline = self.vzbaseline
2080 # axis.vtickdirection = self.vztickdirection
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)
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),
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
2127 # self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
2128 # self.haslayout = 0
2129 # self.defaultstyle = {}
2133 # return bbox.bbox(self._xpos - 200, self._ypos - 200, self._xpos + 200, self._ypos + 200)
2136 ################################################################################
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"""
2152 # "get an attribute"
2155 # "get an attribute changer for the next attribute"
2158 class _changeattr
: pass
2161 class changeattr(_changeattr
):
2170 newindex
= self
.counter
2172 return refattr(self
, newindex
)
2175 class refattr(_changeattr
):
2177 def __init__(self
, ref
, index
):
2182 return self
.ref
.attr(self
.index
)
2185 return self
.ref
.iterate()
2188 # helper routines for a using attrs
2191 """get attr out of a attr/changeattr"""
2192 if isinstance(attr
, _changeattr
):
2193 return attr
.getattr()
2197 def _getattrs(attrs
):
2198 """get attrs out of a sequence of attr/changeattr"""
2199 if attrs
is not None:
2201 for attr
in helper
.ensuresequence(attrs
):
2202 if isinstance(attr
, _changeattr
):
2203 result
.append(attr
.getattr())
2209 def _iterateattr(attr
):
2210 """perform next to a attr/changeattr"""
2211 if isinstance(attr
, _changeattr
):
2212 return attr
.iterate()
2216 def _iterateattrs(attrs
):
2217 """perform next to a sequence of attr/changeattr"""
2218 if attrs
is not None:
2220 for attr
in helper
.ensuresequence(attrs
):
2221 if isinstance(attr
, _changeattr
):
2222 result
.append(attr
.iterate())
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))
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 ################################################################################
2448 ################################################################################
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
),
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
),
2478 def circle(self
, x
, y
):
2479 return (path
._arc
(x
, y
, 0.564189583*self
._size
, 0, 360),
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
),
2489 def __init__(self
, symbol
=helper
.nodefault
,
2490 size
="0.2 cm", symbolattrs
=canvas
.stroked(),
2491 errorscale
=0.5, errorbarattrs
=(),
2493 self
.size_str
= size
2494 if symbol
is helper
.nodefault
:
2495 self
._symbol
= changesymbol
.cross()
2497 self
._symbol
= symbol
2498 self
._symbolattrs
= symbolattrs
2499 self
.errorscale
= errorscale
2500 self
._errorbarattrs
= errorbarattrs
2501 self
._lineattrs
= lineattrs
2503 def iteratedict(self
):
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
)
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
):
2522 match
= pattern
.match(key
)
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")
2527 iskey
= match
.groups()[0]
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
)
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
2583 mid
= point
[i
] + 0.0
2584 except (TypeError, ValueError):
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):
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):
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")
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
2615 for point
in points
:
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):
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
)
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
)
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:
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
)
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
)),
2718 graph
._connect
(*(top
+center
))),
2719 *self
.errorbarattrs
)
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
)),
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
)),
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()
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
2822 for point
in points
:
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
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:
2842 center
= xpos
, ypos
= graph
._pos
(x
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
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
)
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
)
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:
2868 lineels
.append(path
._moveto
(xpos
, ypos
))
2871 lineels
.append(path
._lineto
(xpos
, ypos
))
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
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
)
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)
2954 raise RuntimeError("style is not iterateable")
2956 def othercolumnkey(self
, key
, index
):
2958 self
.colorindex
= index
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
)),
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
)
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
)
3008 return textsymbol(**self
.iteratedict())
3010 def othercolumnkey(self
, key
, index
):
3012 self
.textindex
= index
3014 symbol
.othercolumnkey(self
, key
, index
)
3016 def _drawsymbol(self
, graph
, x
, y
, point
=None):
3017 symbol
._drawsymbol
(self
, graph
, x
, y
, point
)
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)
3046 raise RuntimeError("style is not iterateable")
3048 def othercolumnkey(self
, key
, index
):
3050 self
.sizeindex
= index
3051 elif key
== "angle":
3052 self
.angleindex
= index
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
],
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
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
3095 self
._barattrs
= barattrs
3096 self
.bariterator
= _bariterator
3097 self
.previousbar
= _previousbar
3099 def iteratedict(self
):
3101 result
["barattrs"] = _iterateattrs(self
._barattrs
)
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
):
3111 match
= pattern
.match(key
)
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")
3116 iskey
= match
.groups()[0]
3118 return key
, iskey
, isindex
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])
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
)
3129 self
.othercolumnkey(key
, index
)
3130 if None in (xkey
, ykey
): raise ValueError("incomplete axis specification")
3132 self
.nkey
, self
.ni
= ykey
, yi
3133 self
.vkey
, self
.vi
= xkey
, xi
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
3146 for point
in points
:
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):
3155 self
.naxis
.setname(point
[self
.ni
])
3157 self
.naxis
.setname(point
[self
.ni
], index
)
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
))
3173 self
.stackedvalue
= {}
3174 for point
in points
:
3179 self
.stackedvalue
[n
] = v
3180 if count
!= 1 and self
.stacked
!= 1:
3181 minid
= (n
, index
, 0)
3182 maxid
= (n
, index
, 1)
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
)
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
)
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
)
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
)
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
)
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
)
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
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):
3232 ################################################################################
3234 ################################################################################
3239 defaultstyle
= symbol
3241 def __init__(self
, file, **columns
):
3242 if helper
.isstring(file):
3243 self
.data
= datamodule
.datafile(file)
3248 for key
, column
in columns
.items():
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
):
3260 self
.style
.setcolumns(graph
, self
.columns
)
3262 def getranges(self
):
3263 return self
.style
.getranges(self
.data
.data
)
3265 def setranges(self
, ranges
):
3268 def draw(self
, graph
):
3269 self
.style
.drawpoints(graph
, self
.data
.data
)
3276 def __init__(self
, expression
, min=None, max=None, points
=100, parser
=mathtree
.parser(), extern
=None):
3279 self
.points
= points
3280 self
.extern
= extern
3281 self
.result
, expression
= expression
.split("=")
3282 self
.mathtree
= parser
.parse(expression
, extern
=self
.extern
)
3284 self
.variable
, = self
.mathtree
.VarList()
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
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)")
3297 def setstyle(self
, graph
, style
):
3298 self
.xaxis
= graph
.axes
[self
.variable
]
3300 self
.style
.setcolumns(graph
, {self
.variable
: 0, self
.result
: 1})
3302 def getranges(self
):
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)
3316 for i
in range(self
.points
):
3317 x
= self
.xaxis
.invert(vmin
+ (vmax
-vmin
)*i
/ (self
.points
-1.0))
3319 y
= self
.mathtree
.Calc({self
.variable
: x
}, self
.extern
)
3320 except (ArithmeticError, ValueError):
3322 self
.data
.append((x
, y
))
3325 def draw(self
, graph
):
3326 self
.style
.drawpoints(graph
, self
.data
)
3329 class paramfunction
:
3333 def __init__(self
, varname
, min, max, expression
, points
=100, parser
=mathtree
.parser(), extern
=None):
3334 self
.varname
= varname
3337 self
.points
= points
3338 self
.expression
= {}
3340 varlist
, expressionlist
= expression
.split("=")
3341 parsestr
= mathtree
.ParseStr(expressionlist
)
3342 for key
in varlist
.split(","):
3344 if self
.mathtrees
.has_key(key
):
3345 raise ValueError("multiple assignment in tuple")
3347 self
.mathtrees
[key
] = parser
.ParseMathTree(parsestr
, extern
)
3349 except mathtree
.CommaFoundMathTreeParseError
, e
:
3350 self
.mathtrees
[key
] = e
.MathTree
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")
3356 for i
in range(self
.points
):
3357 value
= self
.min + (self
.max-self
.min)*i
/ (self
.points
-1.0)
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
):
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
):
3376 def draw(self
, graph
):
3377 self
.style
.drawpoints(graph
, self
.data
)