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