4 # Copyright (C) 2002 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2002 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 import types
, re
, math
, string
, sys
25 import bbox
, box
, canvas
, path
, unit
, mathtree
, trafo
, color
, helper
26 import text
as textmodule
27 import data
as datamodule
30 goldenmean
= 0.5 * (math
.sqrt(5) + 1)
33 ################################################################################
35 ################################################################################
38 """interface definition of a map
39 maps convert a value into another value by bijective transformation f"""
45 "returns f^-1(y) where f^-1 is the inverse transformation (x=f^-1(f(x)) for all x)"
47 def setbasepoint(self
, basepoints
):
48 """set basepoints for the convertions
49 basepoints are tuples (x, y) with y == f(x) and x == f^-1(y)
50 the number of basepoints needed might depend on the transformation
51 usually two pairs are needed like for linear maps, logarithmic maps, etc."""
56 __implements__
= _Imap
58 def setbasepoints(self
, basepoints
):
59 "method is part of the implementation of _Imap"
60 self
.dydx
= (basepoints
[1][1] - basepoints
[0][1]) / float(basepoints
[1][0] - basepoints
[0][0])
61 self
.dxdy
= (basepoints
[1][0] - basepoints
[0][0]) / float(basepoints
[1][1] - basepoints
[0][1])
62 self
.x1
= basepoints
[0][0]
63 self
.y1
= basepoints
[0][1]
66 def convert(self
, value
):
67 "method is part of the implementation of _Imap"
68 return self
.y1
+ self
.dydx
* (value
- self
.x1
)
70 def invert(self
, value
):
71 "method is part of the implementation of _Imap"
72 return self
.x1
+ self
.dxdy
* (value
- self
.y1
)
77 __implements__
= _Imap
79 def setbasepoints(self
, basepoints
):
80 self
.dydx
= ((basepoints
[1][1] - basepoints
[0][1]) /
81 float(math
.log(basepoints
[1][0]) - math
.log(basepoints
[0][0])))
82 self
.dxdy
= ((math
.log(basepoints
[1][0]) - math
.log(basepoints
[0][0])) /
83 float(basepoints
[1][1] - basepoints
[0][1]))
84 self
.x1
= math
.log(basepoints
[0][0])
85 self
.y1
= basepoints
[0][1]
88 def convert(self
, value
):
89 return self
.y1
+ self
.dydx
* (math
.log(value
) - self
.x1
)
91 def invert(self
, value
):
92 return math
.exp(self
.x1
+ self
.dxdy
* (value
- self
.y1
))
96 ################################################################################
98 # please note the nomenclature:
99 # - a partition is a ordered sequence of tick instances
100 # - a partition scheme is a class creating a single or several partitions
101 ################################################################################
105 """fraction class for rational arithmetics
106 the axis partitioning uses rational arithmetics (with infinite accuracy)
107 basically it contains self.enum and self.denom"""
109 def __init__(self
, enum
, denom
, power
=None):
110 "for power!=None: frac=(enum/denom)**power"
111 if not helper
.isinteger(enum
) or not helper
.isinteger(denom
): raise TypeError("integer type expected")
112 if not denom
: raise ZeroDivisionError("zero denominator")
114 if not helper
.isinteger(power
): raise TypeError("integer type expected")
116 self
.enum
= long(enum
) ** power
117 self
.denom
= long(denom
) ** power
119 self
.enum
= long(denom
) ** (-power
)
120 self
.denom
= long(enum
) ** (-power
)
125 def __cmp__(self
, other
):
128 return cmp(self
.enum
* other
.denom
, other
.enum
* self
.denom
)
130 def __mul__(self
, other
):
131 return frac(self
.enum
* other
.enum
, self
.denom
* other
.denom
)
134 "caution: avoid final precision of floats"
135 return float(self
.enum
) / self
.denom
138 return "%i/%i" % (self
.enum
, self
.denom
)
141 def _ensurefrac(arg
):
142 """helper function to convert arg into a frac
143 strings like 0.123, 1/-2, 1.23/34.21 are converted to a frac"""
144 # TODO: exponentials are not yet supported, e.g. 1e-10, etc.
145 # XXX: we don't need to force long on newer python versions
148 "converts a string 0.123 into a frac"
149 commaparts
= str.split(".")
150 for part
in commaparts
:
151 if not part
.isdigit(): raise ValueError("non-digits found in '%s'" % part
)
152 if len(commaparts
) == 1:
153 return frac(long(commaparts
[0]), 1)
154 elif len(commaparts
) == 2:
155 result
= frac(1, 10l, power
=len(commaparts
[1]))
156 result
.enum
= long(commaparts
[0])*result
.denom
+ long(commaparts
[1])
158 else: raise ValueError("multiple '.' found in '%s'" % str)
160 if helper
.isstring(arg
):
161 fraction
= arg
.split("/")
162 if len(fraction
) > 2: raise ValueError("multiple '/' found in '%s'" % arg
)
163 value
= createfrac(fraction
[0])
164 if len(fraction
) == 2:
165 value2
= createfrac(fraction
[1])
166 value
= frac(value
.enum
* value2
.denom
, value
.denom
* value2
.enum
)
168 if not isinstance(arg
, frac
): raise ValueError("can't convert argument to frac")
174 a tick is a frac enhanced by
175 - self.ticklevel (0 = tick, 1 = subtick, etc.)
176 - self.labellevel (0 = label, 1 = sublabel, etc.)
178 When ticklevel or labellevel is None, no tick or label is present at that value.
179 When text is None, it should be automatically created (and stored), once the
180 an axis painter needs it."""
182 def __init__(self
, enum
, denom
, ticklevel
=None, labellevel
=None, text
=None):
183 frac
.__init
__(self
, enum
, denom
)
184 self
.ticklevel
= ticklevel
185 self
.labellevel
= labellevel
188 def merge(self
, other
):
189 """merges two ticks together:
190 - the lower ticklevel/labellevel wins
191 - when present, self.text is taken over; otherwise the others text is taken
192 - the ticks should be at the same position (otherwise it doesn't make sense)
193 -> this is NOT checked
195 if self
.ticklevel
is None or (other
.ticklevel
is not None and other
.ticklevel
< self
.ticklevel
):
196 self
.ticklevel
= other
.ticklevel
197 if self
.labellevel
is None or (other
.labellevel
is not None and other
.labellevel
< self
.labellevel
):
198 self
.labellevel
= other
.labellevel
199 if self
.text
is None:
200 self
.text
= other
.text
203 return "tick(%r, %r, %s, %s, %s)" % (self
.enum
, self
.denom
, self
.ticklevel
, self
.labellevel
, self
.text
)
206 def _mergeticklists(list1
, list2
):
207 """helper function to merge tick lists
208 return a merged list of ticks out of list1 and list2
209 caution: original lists have to be ordered
210 (the returned list is also ordered)
211 caution: original lists are modified and they share references to
213 # TODO: improve this using bisect
217 while 1: # we keep on going until we reach an index error
218 while list2
[j
] < list1
[i
]: # insert tick
219 list1
.insert(i
, list2
[j
])
222 if list2
[j
] == list1
[i
]: # merge tick
223 list1
[i
].merge(list2
[j
])
232 def _mergetexts(ticks
, texts
):
233 """helper function to merge texts into ticks
234 - when texts is not None, the text of all ticks with
236 different from None are set
237 - texts need to be a sequence of sequences of strings,
238 where the first sequence contain the strings to be
239 used as texts for the ticks with labellevel 0,
240 the second sequence for labellevel 1, etc.
241 - when the maximum labellevel is 0, just a sequence of
242 strings might be provided as the texts argument
243 - IndexError is raised, when a sequence length doesn't match"""
244 if helper
.issequenceofsequences(texts
):
245 for text
, level
in zip(texts
, xrange(sys
.maxint
)):
246 usetext
= helper
.ensuresequence(text
)
249 if tick
.labellevel
== level
:
250 tick
.text
= usetext
[i
]
252 if i
!= len(usetext
):
253 raise IndexError("wrong sequence length of texts at level %i" % level
)
254 elif texts
is not None:
255 usetext
= helper
.ensuresequence(texts
)
258 if tick
.labellevel
== 0:
259 tick
.text
= usetext
[i
]
261 if i
!= len(usetext
):
262 raise IndexError("wrong sequence length of texts")
266 """interface definition of a partition scheme
267 partition schemes are used to create a list of ticks"""
269 def defaultpart(self
, min, max, extendmin
, extendmax
):
270 """create a partition
271 - returns an ordered list of ticks for the interval min to max
272 - the interval is given in float numbers, thus an appropriate
273 conversion to rational numbers has to be performed
274 - extendmin and extendmax are booleans (integers)
275 - when extendmin or extendmax is set, the ticks might
276 extend the min-max range towards lower and higher
277 ranges, respectively"""
280 """create another partition which contains less ticks
281 - this method is called several times after a call of defaultpart
282 - returns an ordered list of ticks with less ticks compared to
283 the partition returned by defaultpart and by previous calls
285 - the creation of a partition with strictly *less* ticks
286 is not to be taken serious
287 - the method might return None, when no other appropriate
288 partition can be created"""
292 """create another partition which contains more ticks
293 see lesspart, but increase the number of ticks"""
297 """manual partition scheme
298 ticks and labels at positions explicitly provided to the constructor"""
300 __implements__
= _Ipart
302 def __init__(self
, ticks
=None, labels
=None, texts
=None, mix
=()):
303 """configuration of the partition scheme
304 - ticks and labels should be a sequence of sequences, where
305 the first sequence contains the values to be used for
306 ticks with ticklevel/labellevel 0, the second sequence for
307 ticklevel/labellevel 1, etc.
308 - tick and label values must be frac instances or
309 strings convertable to fracs by the _ensurefrac function
310 - when the maximum ticklevel/labellevel is 0, just a sequence
311 might be provided in ticks and labels
312 - when labels is None and ticks is not None, the tick entries
313 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
314 - texts are applied to the resulting partition via the
315 mergetexts function (additional information available there)
316 - mix specifies another partition to be merged into the
318 if ticks
is None and labels
is not None:
319 self
.ticks
= helper
.ensuresequence(helper
.getsequenceno(labels
, 0))
322 if labels
is None and ticks
is not None:
323 self
.labels
= helper
.ensuresequence(helper
.getsequenceno(ticks
, 0))
329 def checkfraclist(self
, *fracs
):
330 """orders a list of fracs, equal entries are not allowed"""
331 if not len(fracs
): return ()
335 for item
in sorted[1:]:
337 raise ValueError("duplicate entry found")
342 "create the partition as described in the constructor"
343 ticks
= list(self
.mix
)
344 if helper
.issequenceofsequences(self
.ticks
):
345 for fracs
, level
in zip(self
.ticks
, xrange(sys
.maxint
)):
346 ticks
= _mergeticklists(ticks
, [tick(frac
.enum
, frac
.denom
, ticklevel
= level
)
347 for frac
in self
.checkfraclist(*map(_ensurefrac
, helper
.ensuresequence(fracs
)))])
349 ticks
= _mergeticklists(ticks
, [tick(frac
.enum
, frac
.denom
, ticklevel
= 0)
350 for frac
in self
.checkfraclist(*map(_ensurefrac
, helper
.ensuresequence(self
.ticks
)))])
352 if helper
.issequenceofsequences(self
.labels
):
353 for fracs
, level
in zip(self
.labels
, xrange(sys
.maxint
)):
354 ticks
= _mergeticklists(ticks
, [tick(frac
.enum
, frac
.denom
, labellevel
= level
)
355 for frac
in self
.checkfraclist(*map(_ensurefrac
, helper
.ensuresequence(fracs
)))])
357 ticks
= _mergeticklists(ticks
, [tick(frac
.enum
, frac
.denom
, labellevel
= 0)
358 for frac
in self
.checkfraclist(*map(_ensurefrac
, helper
.ensuresequence(self
.labels
)))])
360 _mergetexts(ticks
, self
.texts
)
364 def defaultpart(self
, min, max, extendmin
, extendmax
):
365 """method is part of the implementation of _Ipart
366 XXX: we do not take care of the parameters -> correct?"""
370 "method is part of the implementation of _Ipart"
374 "method is part of the implementation of _Ipart"
379 """linear partition scheme
380 ticks and label distances are explicitly provided to the constructor"""
382 __implements__
= _Ipart
384 def __init__(self
, ticks
=None, labels
=None, texts
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10, mix
=()):
385 """configuration of the partition scheme
386 - ticks and labels should be a sequence, where the first value
387 is the distance between ticks with ticklevel/labellevel 0,
388 the second sequence for ticklevel/labellevel 1, etc.
389 - tick and label values must be frac instances or
390 strings convertable to fracs by the _ensurefrac function
391 - when the maximum ticklevel/labellevel is 0, just a single value
392 might be provided in ticks and labels
393 - when labels is None and ticks is not None, the tick entries
394 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
395 - texts are applied to the resulting partition via the
396 mergetexts function (additional information available there)
397 - extendtick allows for the extension of the range given to the
398 defaultpart method to include the next tick with the specified
399 level (None turns off this feature); note, that this feature is
400 also disabled, when an axis prohibits its range extension by
401 the extendmin/extendmax variables given to the defaultpart method
402 - extendlabel is analogous to extendtick, but for labels
403 - epsilon allows for exceeding the axis range by this relative
404 value (relative to the axis range given to the defaultpart method)
405 without creating another tick specified by extendtick/extendlabel
406 - mix specifies another partition to be merged into the
408 if ticks
is None and labels
is not None:
409 self
.ticks
= (_ensurefrac(helper
.ensuresequence(labels
)[0]),)
411 self
.ticks
= map(_ensurefrac
, helper
.ensuresequence(ticks
))
412 if labels
is None and ticks
is not None:
413 self
.labels
= (_ensurefrac(helper
.ensuresequence(ticks
)[0]),)
415 self
.labels
= map(_ensurefrac
, helper
.ensuresequence(labels
))
417 self
.extendtick
= extendtick
418 self
.extendlabel
= extendlabel
419 self
.epsilon
= epsilon
422 def extendminmax(self
, min, max, frac
, extendmin
, extendmax
):
423 """return new min, max tuple extending the range min, max
424 frac is the tick distance to be used
425 extendmin and extendmax are booleans to allow for the extension"""
427 min = float(frac
) * math
.floor(min / float(frac
) + self
.epsilon
)
429 max = float(frac
) * math
.ceil(max / float(frac
) - self
.epsilon
)
432 def getticks(self
, min, max, frac
, ticklevel
=None, labellevel
=None):
433 """return a list of equal spaced ticks
434 - the tick distance is frac, the ticklevel is set to ticklevel and
435 the labellevel is set to labellevel
436 - min, max is the range where ticks should be placed"""
437 imin
= int(math
.ceil(min / float(frac
) - 0.5 * self
.epsilon
))
438 imax
= int(math
.floor(max / float(frac
) + 0.5 * self
.epsilon
))
440 for i
in range(imin
, imax
+ 1):
441 ticks
.append(tick(long(i
) * frac
.enum
, frac
.denom
, ticklevel
=ticklevel
, labellevel
=labellevel
))
444 def defaultpart(self
, min, max, extendmin
, extendmax
):
445 "method is part of the implementation of _Ipart"
446 if self
.extendtick
is not None and len(self
.ticks
) > self
.extendtick
:
447 min, max = self
.extendminmax(min, max, self
.ticks
[self
.extendtick
], extendmin
, extendmax
)
448 if self
.extendlabel
is not None and len(self
.labels
) > self
.extendlabel
:
449 min, max = self
.extendminmax(min, max, self
.labels
[self
.extendlabel
], extendmin
, extendmax
)
451 ticks
= list(self
.mix
)
452 for i
in range(len(self
.ticks
)):
453 ticks
= _mergeticklists(ticks
, self
.getticks(min, max, self
.ticks
[i
], ticklevel
= i
))
454 for i
in range(len(self
.labels
)):
455 ticks
= _mergeticklists(ticks
, self
.getticks(min, max, self
.labels
[i
], labellevel
= i
))
457 _mergetexts(ticks
, self
.texts
)
462 "method is part of the implementation of _Ipart"
466 "method is part of the implementation of _Ipart"
471 """automatic linear partition scheme
472 - possible tick distances are explicitly provided to the constructor
473 - tick distances are adjusted to the axis range by multiplication or division by 10"""
475 __implements__
= _Ipart
477 defaultlist
= ((frac(1, 1), frac(1, 2)),
478 (frac(2, 1), frac(1, 1)),
479 (frac(5, 2), frac(5, 4)),
480 (frac(5, 1), frac(5, 2)))
482 def __init__(self
, list=defaultlist
, extendtick
=0, epsilon
=1e-10, mix
=()):
483 """configuration of the partition scheme
484 - list should be a sequence of fracs
485 - ticks should be a sequence, where the first value
486 is the distance between ticks with ticklevel 0,
487 the second for ticklevel 1, etc.
488 - tick values must be frac instances or
489 strings convertable to fracs by the _ensurefrac function
490 - labellevel is set to None except for those ticks in the partitions,
491 where ticklevel is zero. There labellevel is also set to zero.
492 - extendtick allows for the extension of the range given to the
493 defaultpart method to include the next tick with the specified
494 level (None turns off this feature); note, that this feature is
495 also disabled, when an axis prohibits its range extension by
496 the extendmin/extendmax variables given to the defaultpart method
497 - epsilon allows for exceeding the axis range by this relative
498 value (relative to the axis range given to the defaultpart method)
499 without creating another tick specified by extendtick
500 - mix specifies another partition to be merged into the
503 self
.extendtick
= extendtick
504 self
.epsilon
= epsilon
507 def defaultpart(self
, min, max, extendmin
, extendmax
):
508 "method is part of the implementation of _Ipart"
509 base
= frac(10L, 1, int(math
.log(max - min) / math
.log(10)))
511 useticks
= [tick
* base
for tick
in ticks
]
512 self
.lesstickindex
= self
.moretickindex
= 0
513 self
.lessbase
= frac(base
.enum
, base
.denom
)
514 self
.morebase
= frac(base
.enum
, base
.denom
)
515 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
516 part
= linpart(ticks
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
, mix
=self
.mix
)
517 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
520 "method is part of the implementation of _Ipart"
521 if self
.lesstickindex
< len(self
.list) - 1:
522 self
.lesstickindex
+= 1
524 self
.lesstickindex
= 0
525 self
.lessbase
.enum
*= 10
526 ticks
= self
.list[self
.lesstickindex
]
527 useticks
= [tick
* self
.lessbase
for tick
in ticks
]
528 part
= linpart(ticks
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
, mix
=self
.mix
)
529 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
532 "method is part of the implementation of _Ipart"
533 if self
.moretickindex
:
534 self
.moretickindex
-= 1
536 self
.moretickindex
= len(self
.list) - 1
537 self
.morebase
.denom
*= 10
538 ticks
= self
.list[self
.moretickindex
]
539 useticks
= [tick
* self
.morebase
for tick
in ticks
]
540 part
= linpart(ticks
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
, mix
=self
.mix
)
541 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
545 """storage class for the definition of logarithmic axes partitions
546 instances of this class define tick positions suitable for
547 logarithmic axes by the following instance variables:
548 - shift: integer, which defines multiplicator
549 - fracs: list of tick positions (rational numbers, e.g. instances of frac)
550 possible positions are these tick positions and arbitrary divisions
551 and multiplications by the shift value"""
553 def __init__(self
, shift
, *fracs
):
554 "create a shiftfracs instance and store its shift and fracs information"
559 class logpart(linpart
):
560 """logarithmic partition scheme
561 ticks and label positions are explicitly provided to the constructor"""
563 __implements__
= _Ipart
565 shift5fracs1
= shiftfracs(100000, frac(1, 1))
566 shift4fracs1
= shiftfracs(10000, frac(1, 1))
567 shift3fracs1
= shiftfracs(1000, frac(1, 1))
568 shift2fracs1
= shiftfracs(100, frac(1, 1))
569 shiftfracs1
= shiftfracs(10, frac(1, 1))
570 shiftfracs125
= shiftfracs(10, frac(1, 1), frac(2, 1), frac(5, 1))
571 shiftfracs1to9
= shiftfracs(10, *map(lambda x
: frac(x
, 1), range(1, 10)))
572 # ^- we always include 1 in order to get extendto(tick|label)level to work as expected
574 def __init__(self
, ticks
=None, labels
=None, texts
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10, mix
=()):
575 """configuration of the partition scheme
576 - ticks and labels should be a sequence, where the first value
577 is a shiftfracs instance describing ticks with ticklevel/labellevel 0,
578 the second sequence for ticklevel/labellevel 1, etc.
579 - when the maximum ticklevel/labellevel is 0, just a single
580 shiftfracs instance might be provided in ticks and labels
581 - when labels is None and ticks is not None, the tick entries
582 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
583 - texts are applied to the resulting partition via the
584 mergetexts function (additional information available there)
585 - extendtick allows for the extension of the range given to the
586 defaultpart method to include the next tick with the specified
587 level (None turns off this feature); note, that this feature is
588 also disabled, when an axis prohibits its range extension by
589 the extendmin/extendmax variables given to the defaultpart method
590 - extendlabel is analogous to extendtick, but for labels
591 - epsilon allows for exceeding the axis range by this relative
592 logarithm value (relative to the logarithm axis range given
593 to the defaultpart method) without creating another tick
594 specified by extendtick/extendlabel
595 - mix specifies another partition to be merged into the
597 if ticks
is None and labels
is not None:
598 self
.ticks
= (helper
.ensuresequence(labels
)[0],)
600 self
.ticks
= helper
.ensuresequence(ticks
)
602 if labels
is None and ticks
is not None:
603 self
.labels
= (helper
.ensuresequence(ticks
)[0],)
605 self
.labels
= helper
.ensuresequence(labels
)
607 self
.extendtick
= extendtick
608 self
.extendlabel
= extendlabel
609 self
.epsilon
= epsilon
612 def extendminmax(self
, min, max, shiftfracs
, extendmin
, extendmax
):
613 """return new min, max tuple extending the range min, max
614 shiftfracs describes the allowed tick positions
615 extendmin and extendmax are booleans to allow for the extension"""
618 for i
in xrange(len(shiftfracs
.fracs
)):
619 imin
= int(math
.floor(math
.log(min / float(shiftfracs
.fracs
[i
])) /
620 math
.log(shiftfracs
.shift
) + self
.epsilon
)) + 1
621 imax
= int(math
.ceil(math
.log(max / float(shiftfracs
.fracs
[i
])) /
622 math
.log(shiftfracs
.shift
) - self
.epsilon
)) - 1
623 if minpower
is None or imin
< minpower
:
624 minpower
, minindex
= imin
, i
625 if maxpower
is None or imax
>= maxpower
:
626 maxpower
, maxindex
= imax
, i
628 minfrac
= shiftfracs
.fracs
[minindex
- 1]
630 minfrac
= shiftfracs
.fracs
[-1]
632 if maxindex
!= len(shiftfracs
.fracs
) - 1:
633 maxfrac
= shiftfracs
.fracs
[maxindex
+ 1]
635 maxfrac
= shiftfracs
.fracs
[0]
638 min = float(minfrac
) * float(shiftfracs
.shift
) ** minpower
640 max = float(maxfrac
) * float(shiftfracs
.shift
) ** maxpower
643 def getticks(self
, min, max, shiftfracs
, ticklevel
=None, labellevel
=None):
644 """return a list of ticks
645 - shiftfracs describes the allowed tick positions
646 - the ticklevel of the ticks is set to ticklevel and
647 the labellevel is set to labellevel
648 - min, max is the range where ticks should be placed"""
649 ticks
= list(self
.mix
)
652 for f
in shiftfracs
.fracs
:
654 imin
= int(math
.ceil(math
.log(min / float(f
)) /
655 math
.log(shiftfracs
.shift
) - 0.5 * self
.epsilon
))
656 imax
= int(math
.floor(math
.log(max / float(f
)) /
657 math
.log(shiftfracs
.shift
) + 0.5 * self
.epsilon
))
658 for i
in range(imin
, imax
+ 1):
659 pos
= f
* frac(shiftfracs
.shift
, 1, i
)
660 fracticks
.append(tick(pos
.enum
, pos
.denom
, ticklevel
= ticklevel
, labellevel
= labellevel
))
661 ticks
= _mergeticklists(ticks
, fracticks
)
665 class autologpart(logpart
):
666 """automatic logarithmic partition scheme
667 possible tick positions are explicitly provided to the constructor"""
669 __implements__
= _Ipart
671 defaultlist
= (((logpart
.shiftfracs1
, # ticks
672 logpart
.shiftfracs1to9
), # subticks
673 (logpart
.shiftfracs1
, # labels
674 logpart
.shiftfracs125
)), # sublevels
676 ((logpart
.shiftfracs1
, # ticks
677 logpart
.shiftfracs1to9
), # subticks
678 None), # labels like ticks
680 ((logpart
.shift2fracs1
, # ticks
681 logpart
.shiftfracs1
), # subticks
682 None), # labels like ticks
684 ((logpart
.shift3fracs1
, # ticks
685 logpart
.shiftfracs1
), # subticks
686 None), # labels like ticks
688 ((logpart
.shift4fracs1
, # ticks
689 logpart
.shiftfracs1
), # subticks
690 None), # labels like ticks
692 ((logpart
.shift5fracs1
, # ticks
693 logpart
.shiftfracs1
), # subticks
694 None)) # labels like ticks
696 def __init__(self
, list=defaultlist
, extendtick
=0, extendlabel
=None, epsilon
=1e-10, mix
=()):
697 """configuration of the partition scheme
698 - list should be a sequence of pairs of sequences of shiftfracs
700 - within each pair the first sequence contains shiftfracs, where
701 the first shiftfracs instance describes ticks positions with
702 ticklevel 0, the second shiftfracs for ticklevel 1, etc.
703 - the second sequence within each pair describes the same as
704 before, but for labels
705 - within each pair: when the second entry (for the labels) is None
706 and the first entry (for the ticks) ticks is not None, the tick
707 entries for ticklevel 0 are used for labels and vice versa
709 - extendtick allows for the extension of the range given to the
710 defaultpart method to include the next tick with the specified
711 level (None turns off this feature); note, that this feature is
712 also disabled, when an axis prohibits its range extension by
713 the extendmin/extendmax variables given to the defaultpart method
714 - extendlabel is analogous to extendtick, but for labels
715 - epsilon allows for exceeding the axis range by this relative
716 logarithm value (relative to the logarithm axis range given
717 to the defaultpart method) without creating another tick
718 specified by extendtick/extendlabel
719 - mix specifies another partition to be merged into the
723 self
.listindex
= divmod(len(list), 2)[0]
726 self
.extendtick
= extendtick
727 self
.extendlabel
= extendlabel
728 self
.epsilon
= epsilon
731 def defaultpart(self
, min, max, extendmin
, extendmax
):
732 "method is part of the implementation of _Ipart"
733 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
734 self
.morelistindex
= self
.listindex
735 self
.lesslistindex
= self
.listindex
736 part
= logpart(ticks
=self
.list[self
.listindex
][0], labels
=self
.list[self
.listindex
][1],
737 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
, mix
=self
.mix
)
738 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
741 "method is part of the implementation of _Ipart"
742 self
.lesslistindex
+= 1
743 if self
.lesslistindex
< len(self
.list):
744 part
= logpart(ticks
=self
.list[self
.lesslistindex
][0], labels
=self
.list[self
.lesslistindex
][1],
745 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
, mix
=self
.mix
)
746 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
749 "method is part of the implementation of _Ipart"
750 self
.morelistindex
-= 1
751 if self
.morelistindex
>= 0:
752 part
= logpart(ticks
=self
.list[self
.morelistindex
][0], labels
=self
.list[self
.morelistindex
][1],
753 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
, mix
=self
.mix
)
754 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
758 ################################################################################
760 # conseptional remarks:
761 # - raters are used to calculate a rating for a realization of something
762 # - here, a rating means a positive floating point value
763 # - ratings are used to order those realizations by their suitability (lower
764 # ratings are better)
765 # - a rating of None means not suitable at all (those realizations should be
767 ################################################################################
772 - a cube rater has an optimal value, where the rate becomes zero
773 - for a left (below the optimum) and a right value (above the optimum),
774 the rating is value is set to 1 (modified by an overall weight factor
776 - the analytic form of the rating is cubic for both, the left and
777 the right side of the rater, independently"""
779 def __init__(self
, opt
, left
=None, right
=None, weight
=1):
780 """initializes the rater
781 - by default, left is set to zero, right is set to 3*opt
782 - left should be smaller than opt, right should be bigger than opt
783 - weight should be positive and is a factor multiplicated to the rates"""
793 def rate(self
, value
, dense
=1):
794 """returns a rating for a value
795 - the dense factor lineary rescales the rater (the optimum etc.),
796 e.g. a value bigger than one increases the optimum (when it is
797 positive) and a value lower than one decreases the optimum (when
798 it is positive); the dense factor itself should be positive"""
799 opt
= self
.opt
* dense
801 other
= self
.left
* dense
803 other
= self
.right
* dense
806 factor
= (value
- opt
) / float(other
- opt
)
807 return self
.weight
* (factor
** 3)
811 """a distance rater (rates a list of distances)
812 - the distance rater rates a list of distances by rating each independently
813 and returning the average rate
814 - there is an optimal value, where the rate becomes zero
815 - the analytic form is linary for values above the optimal value
816 (twice the optimal value has the rating one, three times the optimal
817 value has the rating two, etc.)
818 - the analytic form is reciprocal subtracting one for values below the
819 optimal value (halve the optimal value has the rating one, one third of
820 the optimal value has the rating two, etc.)"""
822 def __init__(self
, opt
, weight
=0.1):
823 """inititializes the rater
824 - opt is the optimal length (a PyX length, by default a visual length)
825 - weight should be positive and is a factor multiplicated to the rates"""
829 def _rate(self
, distances
, dense
=1):
831 - the distances are a sequence of positive floats in PostScript points
832 - the dense factor lineary rescales the rater (the optimum etc.),
833 e.g. a value bigger than one increases the optimum (when it is
834 positive) and a value lower than one decreases the optimum (when
835 it is positive); the dense factor itself should be positive"""
837 opt
= unit
.topt(unit
.length(self
.opt_str
, default_type
="v")) / dense
839 for distance
in distances
:
841 rate
+= self
.weight
* (opt
/ distance
- 1)
843 rate
+= self
.weight
* (distance
/ opt
- 1)
844 return rate
/ float(len(distances
))
848 """a rater for axis partitions
849 - the rating of axes is splited into two separate parts:
850 - rating of the partitions in terms of the number of ticks,
851 subticks, labels, etc.
852 - rating of the label distances
853 - in the end, a rate for an axis partition is the sum of these rates
854 - it is useful to first just rate the number of ticks etc.
855 and selecting those partitions, where this fits well -> as soon
856 as an complete rate (the sum of both parts from the list above)
857 of a first partition is below a rate of just the ticks of another
858 partition, this second partition will never be better than the
859 first one -> we gain speed by minimizing the number of partitions,
860 where label distances have to be taken into account)
861 - both parts of the rating are shifted into instances of raters
862 defined above --- right now, there is not yet a strict interface
863 for this delegation (should be done as soon as it is needed)"""
865 linticks
= (cuberate(4), cuberate(10, weight
=0.5), )
866 linlabels
= (cuberate(4), )
867 logticks
= (cuberate(5, right
=20), cuberate(20, right
=100, weight
=0.5), )
868 loglabels
= (cuberate(5, right
=20), cuberate(5, left
=-20, right
=20, weight
=0.5), )
869 stdtickrange
= cuberate(1, weight
=2)
870 stddistance
= distancerate("1 cm")
872 def __init__(self
, ticks
=linticks
, labels
=linlabels
, tickrange
=stdtickrange
, distance
=stddistance
):
873 """initializes the axis rater
874 - ticks and labels are lists of instances of cuberate
875 - the first entry in ticks rate the number of ticks, the
876 second the number of subticks, etc.; when there are no
877 ticks of a level or there is not rater for a level, the
878 level is just ignored
879 - labels is analogous, but for labels
880 - within the rating, all ticks with a higher level are
881 considered as ticks for a given level
882 - tickrange is a cuberate instance, which rates the covering
883 of an axis range by the ticks (as a relative value of the
884 tick range vs. the axis range), ticks might cover less or
885 more than the axis range (for the standard automatic axis
886 partition schemes an extention of the axis range is normal
887 and should get some penalty)
888 - distance is an distancerate instance"""
891 self
.tickrange
= tickrange
892 self
.distance
= distance
894 def ratepart(self
, axis
, part
, dense
=1):
895 """rates a partition by some global parameters
896 - takes into account the number of ticks, subticks, etc.,
897 number of labels, etc., and the coverage of the axis
899 - when there are no ticks of a level or there was not rater
900 given in the constructor for a level, the level is just
902 - the method returns the sum of the rating results divided
903 by the sum of the weights of the raters
904 - within the rating, all ticks with a higher level are
905 considered as ticks for a given level"""
906 tickslen
= len(self
.ticks
)
907 labelslen
= len(self
.labels
)
909 labels
= [0]*labelslen
912 if tick
.ticklevel
is not None:
913 for level
in xrange(tick
.ticklevel
, tickslen
):
915 if tick
.labellevel
is not None:
916 for level
in xrange(tick
.labellevel
, labelslen
):
920 for tick
, rater
in zip(ticks
, self
.ticks
):
921 rate
+= rater
.rate(tick
, dense
=dense
)
922 weight
+= rater
.weight
923 for label
, rater
in zip(labels
, self
.labels
):
924 rate
+= rater
.rate(label
, dense
=dense
)
925 weight
+= rater
.weight
926 if part
is not None and len(part
):
927 tickmin
, tickmax
= axis
.gettickrange() # XXX: tickrange was not yet applied!?
928 rate
+= self
.tickrange
.rate((float(part
[-1]) - float(part
[0])) * axis
.divisor
/ (tickmax
- tickmin
))
930 rate
+= self
.tickrange
.rate(0)
931 weight
+= self
.tickrange
.weight
934 def _ratedistances(self
, distances
, dense
=1):
936 - the distances should be collected as box distances of
937 subsequent labels (of any level))
938 - the distances are a sequence of positive floats in
940 - the dense factor is used within the distancerate instance"""
941 return self
.distance
._rate
(distances
, dense
=dense
)
944 ################################################################################
946 ################################################################################
949 class layoutdata
: pass
952 class axistitlepainter
:
957 def __init__(self
, titledist
="0.3 cm",
958 titleattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
961 self
.titledist_str
= titledist
962 self
.titleattrs
= titleattrs
963 self
.titledirection
= titledirection
964 self
.titlepos
= titlepos
966 def reldirection(self
, direction
, dx
, dy
, epsilon
=1e-10):
967 direction
+= math
.atan2(dy
, dx
) * 180 / math
.pi
968 while (direction
> 90 + epsilon
):
970 while (direction
< -90 - epsilon
):
974 def dolayout(self
, graph
, axis
):
975 if axis
.title
is not None and self
.titleattrs
is not None:
976 titledist
= unit
.topt(unit
.length(self
.titledist_str
, default_type
="v"))
977 x
, y
= axis
._vtickpoint
(axis
, self
.titlepos
)
978 dx
, dy
= axis
.vtickdirection(axis
, self
.titlepos
)
979 # no not modify self.titleattrs ... the painter might be used by several axes!!!
980 titleattrs
= list(helper
.ensuresequence(self
.titleattrs
))
981 if self
.titledirection
is not None:
982 titleattrs
= titleattrs
+ [trafo
.rotate(self
.reldirection(self
.titledirection
, dx
, dy
))]
983 axis
.layoutdata
.titlebox
= graph
.texrunner
._text
(x
, y
, axis
.title
, *titleattrs
)
984 axis
.layoutdata
._extent
+= titledist
985 axis
.layoutdata
.titlebox
._linealign
(axis
.layoutdata
._extent
, dx
, dy
)
986 axis
.layoutdata
._extent
+= axis
.layoutdata
.titlebox
._extent
(dx
, dy
)
988 axis
.layoutdata
.titlebox
= None
990 def paint(self
, graph
, axis
):
991 if axis
.layoutdata
.titlebox
is not None:
992 graph
.insert(axis
.layoutdata
.titlebox
)
995 class axispainter(axistitlepainter
):
997 defaultticklengths
= ["%0.5f cm" % (0.2*goldenmean
**(-i
)) for i
in range(10)]
1004 def __init__(self
, innerticklengths
=defaultticklengths
,
1005 outerticklengths
=None,
1009 baselineattrs
=canvas
.linecap
.square
,
1011 labelattrs
=((textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1012 (textmodule
.size
.footnotesize
, textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
)),
1013 labeldirection
=None,
1016 fractype
=fractypeauto
,
1017 ratfracsuffixenum
=1,
1018 ratfracover
=r
"\over",
1021 expfractimes
=r
"\cdot",
1027 self
.innerticklengths_str
= innerticklengths
1028 self
.outerticklengths_str
= outerticklengths
1029 self
.tickattrs
= tickattrs
1030 self
.gridattrs
= gridattrs
1031 self
.zerolineattrs
= zerolineattrs
1032 self
.baselineattrs
= baselineattrs
1033 self
.labeldist_str
= labeldist
1034 self
.labelattrs
= labelattrs
1035 self
.labeldirection
= labeldirection
1036 self
.labelhequalize
= labelhequalize
1037 self
.labelvequalize
= labelvequalize
1038 self
.fractype
= fractype
1039 self
.ratfracsuffixenum
= ratfracsuffixenum
1040 self
.ratfracover
= ratfracover
1041 self
.decfracpoint
= decfracpoint
1042 self
.decfracequal
= decfracequal
1043 self
.expfractimes
= expfractimes
1044 self
.expfracpre1
= expfracpre1
1045 self
.expfracminexp
= expfracminexp
1046 self
.suffix0
= suffix0
1047 self
.suffix1
= suffix1
1048 axistitlepainter
.__init
__(self
, **args
)
1050 def gcd(self
, m
, n
):
1051 # greates common divisor, m & n must be non-negative
1055 m
, (dummy
, n
) = n
, divmod(m
, n
)
1058 def attachsuffix(self
, tick
, str):
1059 if self
.suffix0
or tick
.enum
:
1060 if tick
.suffix
is not None and not self
.suffix1
:
1065 if tick
.suffix
is not None:
1066 str = str + tick
.suffix
1069 def ratfrac(self
, tick
):
1070 m
, n
= tick
.enum
, tick
.denom
1072 if m
< 0: m
, sign
= -m
, -sign
1073 if n
< 0: n
, sign
= -n
, -sign
1074 gcd
= self
.gcd(m
, n
)
1075 (m
, dummy1
), (n
, dummy2
) = divmod(m
, gcd
), divmod(n
, gcd
)
1077 if self
.ratfracsuffixenum
:
1079 return "-{{%s}%s{%s}}" % (self
.attachsuffix(tick
, str(m
)), self
.ratfracover
, n
)
1081 return "{{%s}%s{%s}}" % (self
.attachsuffix(tick
, str(m
)), self
.ratfracover
, n
)
1084 return self
.attachsuffix(tick
, "-{{%s}%s{%s}}" % (m
, self
.ratfracover
, n
))
1086 return self
.attachsuffix(tick
, "{{%s}%s{%s}}" % (m
, self
.ratfracover
, n
))
1089 return self
.attachsuffix(tick
, "-%s" % m
)
1091 return self
.attachsuffix(tick
, "%s" % m
)
1093 def decfrac(self
, tick
, decfraclength
=None):
1094 m
, n
= tick
.enum
, tick
.denom
1096 if m
< 0: m
, sign
= -m
, -sign
1097 if n
< 0: n
, sign
= -n
, -sign
1098 gcd
= self
.gcd(m
, n
)
1099 (m
, dummy1
), (n
, dummy2
) = divmod(m
, gcd
), divmod(n
, gcd
)
1100 frac
, rest
= divmod(m
, n
)
1104 strfrac
+= self
.decfracpoint
1106 tick
.decfraclength
= 0
1108 tick
.decfraclength
+= 1
1110 periodstart
= len(strfrac
) - (len(oldrest
) - oldrest
.index(rest
))
1111 strfrac
= strfrac
[:periodstart
] + r
"\overline{" + strfrac
[periodstart
:] + "}"
1115 frac
, rest
= divmod(rest
, n
)
1116 strfrac
+= str(frac
)
1118 if decfraclength
is not None:
1119 while tick
.decfraclength
< decfraclength
:
1121 tick
.decfraclength
+= 1
1123 return self
.attachsuffix(tick
, "-%s" % strfrac
)
1125 return self
.attachsuffix(tick
, strfrac
)
1127 def expfrac(self
, tick
, minexp
= None):
1128 m
, n
= tick
.enum
, tick
.denom
1130 if m
< 0: m
, sign
= -m
, -sign
1131 if n
< 0: n
, sign
= -n
, -sign
1134 while divmod(m
, n
)[0] > 9:
1137 while divmod(m
, n
)[0] < 1:
1140 if minexp
is not None and ((exp
< 0 and -exp
< minexp
) or (exp
>= 0 and exp
< minexp
)):
1144 prefactor
= self
.decfrac(dummy
)
1145 if prefactor
== "1" and not self
.expfracpre1
:
1147 return self
.attachsuffix(tick
, "-10^{%i}" % exp
)
1149 return self
.attachsuffix(tick
, "10^{%i}" % exp
)
1152 return self
.attachsuffix(tick
, "-%s%s10^{%i}" % (prefactor
, self
.expfractimes
, exp
))
1154 return self
.attachsuffix(tick
, "%s%s10^{%i}" % (prefactor
, self
.expfractimes
, exp
))
1156 def createtext(self
, tick
):
1157 tick
.decfraclength
= None
1158 if self
.fractype
== self
.fractypeauto
:
1159 if tick
.suffix
is not None:
1160 tick
.text
= self
.ratfrac(tick
)
1162 tick
.text
= self
.expfrac(tick
, self
.expfracminexp
)
1163 if tick
.text
is None:
1164 tick
.text
= self
.decfrac(tick
)
1165 elif self
.fractype
== self
.fractypedec
:
1166 tick
.text
= self
.decfrac(tick
)
1167 elif self
.fractype
== self
.fractypeexp
:
1168 tick
.text
= self
.expfrac(tick
)
1169 elif self
.fractype
== self
.fractyperat
:
1170 tick
.text
= self
.ratfrac(tick
)
1172 raise ValueError("fractype invalid")
1173 if textmodule
.mathmode
not in tick
.labelattrs
:
1174 tick
.labelattrs
.append(textmodule
.mathmode
)
1176 def dolayout(self
, graph
, axis
):
1177 labeldist
= unit
.topt(unit
.length(self
.labeldist_str
, default_type
="v"))
1178 for tick
in axis
.ticks
:
1179 tick
.virtual
= axis
.convert(float(tick
) * axis
.divisor
)
1180 tick
.x
, tick
.y
= axis
._vtickpoint
(axis
, tick
.virtual
)
1181 tick
.dx
, tick
.dy
= axis
.vtickdirection(axis
, tick
.virtual
)
1182 for tick
in axis
.ticks
:
1184 if tick
.labellevel
is not None:
1185 tick
.labelattrs
= helper
.getsequenceno(self
.labelattrs
, tick
.labellevel
)
1186 if tick
.labelattrs
is not None:
1187 tick
.labelattrs
= list(helper
.ensuresequence(tick
.labelattrs
))
1188 if tick
.text
is None:
1189 tick
.suffix
= axis
.suffix
1190 self
.createtext(tick
)
1191 if self
.labeldirection
is not None:
1192 tick
.labelattrs
+= [trafo
.rotate(self
.reldirection(self
.labeldirection
, tick
.dx
, tick
.dy
))]
1193 tick
.textbox
= textmodule
._text
(tick
.x
, tick
.y
, tick
.text
, *tick
.labelattrs
)
1194 if self
.decfracequal
:
1195 maxdecfraclength
= max([tick
.decfraclength
for tick
in axis
.ticks
if tick
.labellevel
is not None and
1196 tick
.labelattrs
is not None and
1197 tick
.decfraclength
is not None])
1198 for tick
in axis
.ticks
:
1199 if (tick
.labellevel
is not None and
1200 tick
.labelattrs
is not None and
1201 tick
.decfraclength
is not None):
1202 tick
.text
= self
.decfrac(tick
, maxdecfraclength
)
1203 for tick
in axis
.ticks
:
1204 if tick
.labellevel
is not None and tick
.labelattrs
is not None:
1205 tick
.textbox
= textmodule
._text
(tick
.x
, tick
.y
, tick
.text
, *tick
.labelattrs
)
1206 if len(axis
.ticks
) > 1:
1208 for tick
in axis
.ticks
[1:]:
1209 if tick
.dx
!= axis
.ticks
[0].dx
or tick
.dy
!= axis
.ticks
[0].dy
:
1213 if equaldirection
and ((not axis
.ticks
[0].dx
and self
.labelvequalize
) or
1214 (not axis
.ticks
[0].dy
and self
.labelhequalize
)):
1215 box
._linealignequal
([tick
.textbox
for tick
in axis
.ticks
if tick
.textbox
],
1216 labeldist
, axis
.ticks
[0].dx
, axis
.ticks
[0].dy
)
1218 for tick
in axis
.ticks
:
1220 tick
.textbox
._linealign
(labeldist
, tick
.dx
, tick
.dy
)
1221 def topt_v_recursive(arg
):
1222 if helper
.issequence(arg
):
1223 # return map(topt_v_recursive, arg) needs python2.2
1224 return [unit
.topt(unit
.length(a
, default_type
="v")) for a
in arg
]
1227 return unit
.topt(unit
.length(arg
, default_type
="v"))
1228 innerticklengths
= topt_v_recursive(self
.innerticklengths_str
)
1229 outerticklengths
= topt_v_recursive(self
.outerticklengths_str
)
1230 axis
.layoutdata
._extent
= 0
1231 for tick
in axis
.ticks
:
1232 if tick
.ticklevel
is not None:
1233 tick
.innerticklength
= helper
.getitemno(innerticklengths
, tick
.ticklevel
)
1234 tick
.outerticklength
= helper
.getitemno(outerticklengths
, tick
.ticklevel
)
1235 if tick
.innerticklength
is not None and tick
.outerticklength
is None:
1236 tick
.outerticklength
= 0
1237 if tick
.outerticklength
is not None and tick
.innerticklength
is None:
1238 tick
.innerticklength
= 0
1240 if tick
.textbox
is None:
1241 if tick
.outerticklength
is not None and tick
.outerticklength
> 0:
1242 extent
= tick
.outerticklength
1244 extent
= tick
.textbox
._extent
(tick
.dx
, tick
.dy
) + labeldist
1245 if axis
.layoutdata
._extent
< extent
:
1246 axis
.layoutdata
._extent
= extent
1247 axistitlepainter
.dolayout(self
, graph
, axis
)
1249 def ratelayout(self
, graph
, axis
, dense
=1):
1250 ticktextboxes
= [tick
.textbox
for tick
in axis
.ticks
if tick
.textbox
is not None]
1251 if len(ticktextboxes
) > 1:
1253 distances
= [ticktextboxes
[i
]._boxdistance
(ticktextboxes
[i
+1]) for i
in range(len(ticktextboxes
) - 1)]
1254 except box
.BoxCrossError
:
1256 rate
= axis
.rate
._ratedistances
(distances
, dense
)
1259 if self
.labelattrs
is None:
1262 def paint(self
, graph
, axis
):
1263 for tick
in axis
.ticks
:
1264 if tick
.ticklevel
is not None:
1265 if tick
!= frac(0, 1) or self
.zerolineattrs
is None:
1266 gridattrs
= helper
.getsequenceno(self
.gridattrs
, tick
.ticklevel
)
1267 if gridattrs
is not None:
1268 graph
.stroke(axis
.vgridpath(tick
.virtual
), *helper
.ensuresequence(gridattrs
))
1269 tickattrs
= helper
.getsequenceno(self
.tickattrs
, tick
.ticklevel
)
1270 if None not in (tick
.innerticklength
, tick
.outerticklength
, tickattrs
):
1271 x1
= tick
.x
- tick
.dx
* tick
.innerticklength
1272 y1
= tick
.y
- tick
.dy
* tick
.innerticklength
1273 x2
= tick
.x
+ tick
.dx
* tick
.outerticklength
1274 y2
= tick
.y
+ tick
.dy
* tick
.outerticklength
1275 graph
.stroke(path
._line
(x1
, y1
, x2
, y2
), *helper
.ensuresequence(tickattrs
))
1276 if tick
.textbox
is not None:
1277 graph
.insert(tick
.textbox
)
1278 if self
.baselineattrs
is not None:
1279 graph
.stroke(axis
.vbaseline(axis
), *helper
.ensuresequence(self
.baselineattrs
))
1280 if self
.zerolineattrs
is not None:
1281 if len(axis
.ticks
) and axis
.ticks
[0] * axis
.ticks
[-1] < frac(0, 1):
1282 graph
.stroke(axis
.vgridpath(axis
.convert(0)), *helper
.ensuresequence(self
.zerolineattrs
))
1283 axistitlepainter
.paint(self
, graph
, axis
)
1286 class splitaxispainter(axistitlepainter
):
1288 def __init__(self
, breaklinesdist
=0.05,
1289 breaklineslength
=0.5,
1290 breaklinesangle
=-60,
1293 self
.breaklinesdist_str
= breaklinesdist
1294 self
.breaklineslength_str
= breaklineslength
1295 self
.breaklinesangle
= breaklinesangle
1296 self
.breaklinesattrs
= breaklinesattrs
1297 axistitlepainter
.__init
__(self
, **args
)
1299 def subvbaseline(self
, axis
, v1
=None, v2
=None):
1301 if self
.breaklinesattrs
is None:
1304 if axis
.vminover
is None:
1307 left
= axis
.vminover
1309 left
= axis
.vmin
+v1
*(axis
.vmax
-axis
.vmin
)
1311 if self
.breaklinesattrs
is None:
1314 if axis
.vmaxover
is None:
1317 right
= axis
.vmaxover
1319 right
= axis
.vmin
+v2
*(axis
.vmax
-axis
.vmin
)
1320 return axis
.baseaxis
.vbaseline(axis
.baseaxis
, left
, right
)
1322 def dolayout(self
, graph
, axis
):
1323 if self
.breaklinesattrs
is not None:
1324 self
.breaklinesdist
= unit
.length(self
.breaklinesdist_str
, default_type
="v")
1325 self
.breaklineslength
= unit
.length(self
.breaklineslength_str
, default_type
="v")
1326 self
._breaklinesdist
= unit
.topt(self
.breaklinesdist
)
1327 self
._breaklineslength
= unit
.topt(self
.breaklineslength
)
1328 self
.sin
= math
.sin(self
.breaklinesangle
*math
.pi
/180.0)
1329 self
.cos
= math
.cos(self
.breaklinesangle
*math
.pi
/180.0)
1330 axis
.layoutdata
._extent
= (math
.fabs(0.5 * self
._breaklinesdist
* self
.cos
) +
1331 math
.fabs(0.5 * self
._breaklineslength
* self
.sin
))
1333 axis
.layoutdata
._extent
= 0
1334 for subaxis
in axis
.axislist
:
1335 subaxis
.baseaxis
= axis
1336 subaxis
._vtickpoint
= lambda axis
, v
: axis
.baseaxis
._vtickpoint
(axis
.baseaxis
, axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
))
1337 subaxis
.vtickdirection
= lambda axis
, v
: axis
.baseaxis
.vtickdirection(axis
.baseaxis
, axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
))
1338 subaxis
.vbaseline
= self
.subvbaseline
1339 subaxis
.dolayout(graph
)
1340 if axis
.layoutdata
._extent
< subaxis
.layoutdata
._extent
:
1341 axis
.layoutdata
._extent
= subaxis
.layoutdata
._extent
1342 axistitlepainter
.dolayout(self
, graph
, axis
)
1344 def paint(self
, graph
, axis
):
1345 for subaxis
in axis
.axislist
:
1346 subaxis
.dopaint(graph
)
1347 if self
.breaklinesattrs
is not None:
1348 for subaxis1
, subaxis2
in zip(axis
.axislist
[:-1], axis
.axislist
[1:]):
1349 # use a tangent of the baseline (this is independent of the tickdirection)
1350 v
= 0.5 * (subaxis1
.vmax
+ subaxis2
.vmin
)
1351 breakline
= path
.normpath(axis
.vbaseline(axis
, v
, None)).tangent(0, self
.breaklineslength
)
1352 widthline
= path
.normpath(axis
.vbaseline(axis
, v
, None)).tangent(0, self
.breaklinesdist
).transformed(trafo
.rotate(self
.breaklinesangle
+90, *breakline
.begin()))
1353 tocenter
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(breakline
.begin(), breakline
.end()))
1354 towidth
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(widthline
.begin(), widthline
.end()))
1355 breakline
= breakline
.transformed(trafo
.translate(*tocenter
).rotated(self
.breaklinesangle
, *breakline
.begin()))
1356 breakline1
= breakline
.transformed(trafo
.translate(*towidth
))
1357 breakline2
= breakline
.transformed(trafo
.translate(-towidth
[0], -towidth
[1]))
1358 graph
.fill(path
.path(path
.moveto(*breakline1
.begin()),
1359 path
.lineto(*breakline1
.end()),
1360 path
.lineto(*breakline2
.end()),
1361 path
.lineto(*breakline2
.begin()),
1362 path
.closepath()), color
.gray
.white
)
1363 graph
.stroke(breakline1
, *helper
.ensuresequence(self
.breaklinesattrs
))
1364 graph
.stroke(breakline2
, *helper
.ensuresequence(self
.breaklinesattrs
))
1365 axistitlepainter
.paint(self
, graph
, axis
)
1368 class baraxispainter(axistitlepainter
):
1370 def __init__(self
, innerticklength
=None,
1371 outerticklength
=None,
1373 baselineattrs
=canvas
.linecap
.square
,
1375 nameattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1381 self
.innerticklength_str
= innerticklength
1382 self
.outerticklength_str
= outerticklength
1383 self
.tickattrs
= tickattrs
1384 self
.baselineattrs
= baselineattrs
1385 self
.namedist_str
= namedist
1386 self
.nameattrs
= nameattrs
1387 self
.namedirection
= namedirection
1388 self
.namepos
= namepos
1389 self
.namehequalize
= namehequalize
1390 self
.namevequalize
= namevequalize
1391 axistitlepainter
.__init
__(self
, **args
)
1393 def dolayout(self
, graph
, axis
):
1394 axis
.layoutdata
._extent
= 0
1395 if axis
.multisubaxis
:
1396 for name
, subaxis
in zip(axis
.names
, axis
.subaxis
):
1397 subaxis
.vmin
= axis
.convert((name
, 0))
1398 subaxis
.vmax
= axis
.convert((name
, 1))
1399 subaxis
.baseaxis
= axis
1400 subaxis
._vtickpoint
= lambda axis
, v
: axis
.baseaxis
._vtickpoint
(axis
.baseaxis
, axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
))
1401 subaxis
.vtickdirection
= lambda axis
, v
: axis
.baseaxis
.vtickdirection(axis
.baseaxis
, axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
))
1402 subaxis
.vbaseline
= None
1403 subaxis
.dolayout(graph
)
1404 if axis
.layoutdata
._extent
< subaxis
.layoutdata
._extent
:
1405 axis
.layoutdata
._extent
= subaxis
.layoutdata
._extent
1407 for name
in axis
.names
:
1408 v
= axis
.convert((name
, self
.namepos
))
1409 x
, y
= axis
._vtickpoint
(axis
, v
)
1410 dx
, dy
= axis
.vtickdirection(axis
, v
)
1411 axis
.namepos
.append((v
, x
, y
, dx
, dy
))
1413 for (v
, x
, y
, dx
, dy
), name
in zip(axis
.namepos
, axis
.names
):
1414 nameattrs
= helper
.ensurelist(self
.nameattrs
)
1415 if self
.namedirection
is not None:
1416 nameattrs
+= [trafo
.rotate(self
.reldirection(self
.namedirection
, dx
, dy
))]
1417 if axis
.texts
.has_key(name
):
1418 axis
.nameboxes
.append(textmodule
._text
(x
, y
, str(axis
.texts
[name
]), *nameattrs
))
1419 elif axis
.texts
.has_key(str(name
)):
1420 axis
.nameboxes
.append(textmodule
._text
(x
, y
, str(axis
.texts
[str(name
)]), *nameattrs
))
1422 axis
.nameboxes
.append(textmodule
._text
(x
, y
, str(name
), *nameattrs
))
1423 labeldist
= axis
.layoutdata
._extent
+ unit
.topt(unit
.length(self
.namedist_str
, default_type
="v"))
1424 if len(axis
.namepos
) > 1:
1426 for namepos
in axis
.namepos
[1:]:
1427 if namepos
[3] != axis
.namepos
[0][3] or namepos
[4] != axis
.namepos
[0][4]:
1431 if equaldirection
and ((not axis
.namepos
[0][3] and self
.namevequalize
) or
1432 (not axis
.namepos
[0][4] and self
.namehequalize
)):
1433 box
._linealignequal
(axis
.nameboxes
, labeldist
, axis
.namepos
[0][3], axis
.namepos
[0][4])
1435 for namebox
, namepos
in zip(axis
.nameboxes
, axis
.namepos
):
1436 namebox
._linealign
(labeldist
, namepos
[3], namepos
[4])
1437 if self
.innerticklength_str
is not None:
1438 axis
.innerticklength
= unit
.topt(unit
.length(self
.innerticklength_str
, default_type
="v"))
1440 if self
.outerticklength_str
is not None:
1441 axis
.innerticklength
= 0
1443 axis
.innerticklength
= None
1444 if self
.outerticklength_str
is not None:
1445 axis
.outerticklength
= unit
.topt(unit
.length(self
.outerticklength_str
, default_type
="v"))
1447 if self
.innerticklength_str
is not None:
1448 axis
.outerticklength
= 0
1450 axis
.outerticklength
= None
1451 if axis
.outerticklength
is not None and self
.tickattrs
is not None:
1452 axis
.layoutdata
._extent
+= axis
.outerticklength
1453 for (v
, x
, y
, dx
, dy
), namebox
in zip(axis
.namepos
, axis
.nameboxes
):
1454 newextent
= namebox
._extent
(dx
, dy
) + labeldist
1455 if axis
.layoutdata
._extent
< newextent
:
1456 axis
.layoutdata
._extent
= newextent
1457 axistitlepainter
.dolayout(self
, graph
, axis
)
1458 graph
.mindbbox(*[namebox
.bbox() for namebox
in axis
.nameboxes
])
1460 def paint(self
, graph
, axis
):
1461 if axis
.subaxis
is not None:
1462 if axis
.multisubaxis
:
1463 for subaxis
in axis
.subaxis
:
1464 subaxis
.dopaint(graph
)
1465 if None not in (self
.tickattrs
, axis
.innerticklength
, axis
.outerticklength
):
1466 for pos
in axis
.relsizes
:
1467 if pos
== axis
.relsizes
[0]:
1468 pos
-= axis
.firstdist
1469 elif pos
!= axis
.relsizes
[-1]:
1470 pos
-= 0.5 * axis
.dist
1471 v
= pos
/ axis
.relsizes
[-1]
1472 x
, y
= axis
._vtickpoint
(axis
, v
)
1473 dx
, dy
= axis
.vtickdirection(axis
, v
)
1474 x1
= x
- dx
* axis
.innerticklength
1475 y1
= y
- dy
* axis
.innerticklength
1476 x2
= x
+ dx
* axis
.outerticklength
1477 y2
= y
+ dy
* axis
.outerticklength
1478 graph
.stroke(path
._line
(x1
, y1
, x2
, y2
), *helper
.ensuresequence(self
.tickattrs
))
1479 if self
.baselineattrs
is not None:
1480 if axis
.vbaseline
is not None: # XXX: subbaselines (as for splitlines)
1481 graph
.stroke(axis
.vbaseline(axis
), *helper
.ensuresequence(self
.baselineattrs
))
1482 for namebox
in axis
.nameboxes
:
1483 graph
.insert(namebox
)
1484 axistitlepainter
.paint(self
, graph
, axis
)
1488 ################################################################################
1490 ################################################################################
1492 class PartitionError(Exception): pass
1496 def __init__(self
, min=None, max=None, reverse
=0, divisor
=1,
1497 datavmin
=None, datavmax
=None, tickvmin
=0, tickvmax
=1,
1498 title
=None, suffix
=None, painter
=axispainter(), dense
=None):
1499 if None not in (min, max) and min > max:
1500 min, max, reverse
= max, min, not reverse
1501 self
.fixmin
, self
.fixmax
, self
.min, self
.max, self
.reverse
= min is not None, max is not None, min, max, reverse
1503 self
.datamin
= self
.datamax
= self
.tickmin
= self
.tickmax
= None
1504 if datavmin
is None:
1508 self
.datavmin
= 0.05
1510 self
.datavmin
= datavmin
1511 if datavmax
is None:
1515 self
.datavmax
= 0.95
1517 self
.datavmax
= datavmax
1518 self
.tickvmin
= tickvmin
1519 self
.tickvmax
= tickvmax
1521 self
.divisor
= divisor
1523 self
.suffix
= suffix
1524 self
.painter
= painter
1527 self
.__setinternalrange
()
1529 def __setinternalrange(self
, min=None, max=None):
1530 if not self
.fixmin
and min is not None and (self
.min is None or min < self
.min):
1532 if not self
.fixmax
and max is not None and (self
.max is None or max > self
.max):
1534 if None not in (self
.min, self
.max):
1535 min, max, vmin
, vmax
= self
.min, self
.max, 0, 1
1537 self
.setbasepoints(((min, vmin
), (max, vmax
)))
1539 if self
.datamin
is not None and self
.convert(self
.datamin
) < self
.datavmin
:
1540 min, vmin
= self
.datamin
, self
.datavmin
1541 self
.setbasepoints(((min, vmin
), (max, vmax
)))
1542 if self
.tickmin
is not None and self
.convert(self
.tickmin
) < self
.tickvmin
:
1543 min, vmin
= self
.tickmin
, self
.tickvmin
1544 self
.setbasepoints(((min, vmin
), (max, vmax
)))
1546 if self
.datamax
is not None and self
.convert(self
.datamax
) > self
.datavmax
:
1547 max, vmax
= self
.datamax
, self
.datavmax
1548 self
.setbasepoints(((min, vmin
), (max, vmax
)))
1549 if self
.tickmax
is not None and self
.convert(self
.tickmax
) > self
.tickvmax
:
1550 max, vmax
= self
.tickmax
, self
.tickvmax
1551 self
.setbasepoints(((min, vmin
), (max, vmax
)))
1553 self
.setbasepoints(((min, vmax
), (max, vmin
)))
1555 def __getinternalrange(self
):
1556 return self
.min, self
.max, self
.datamin
, self
.datamax
, self
.tickmin
, self
.tickmax
1558 def __forceinternalrange(self
, range):
1559 self
.min, self
.max, self
.datamin
, self
.datamax
, self
.tickmin
, self
.tickmax
= range
1560 self
.__setinternalrange
()
1562 def setdatarange(self
, min, max):
1563 self
.datamin
, self
.datamax
= min, max
1564 self
.__setinternalrange
(min, max)
1566 def settickrange(self
, min, max):
1567 self
.tickmin
, self
.tickmax
= min, max
1568 self
.__setinternalrange
(min, max)
1570 def getdatarange(self
):
1573 return self
.invert(1-self
.datavmin
), self
.invert(1-self
.datavmax
)
1575 return self
.invert(self
.datavmin
), self
.invert(self
.datavmax
)
1577 def gettickrange(self
):
1580 return self
.invert(1-self
.tickvmin
), self
.invert(1-self
.tickvmax
)
1582 return self
.invert(self
.tickvmin
), self
.invert(self
.tickvmax
)
1584 def dolayout(self
, graph
):
1585 if self
.dense
is not None:
1589 min, max = self
.gettickrange()
1590 self
.ticks
= self
.part
.defaultpart(min/self
.divisor
, max/self
.divisor
, not self
.fixmin
, not self
.fixmax
)
1591 # lesspart and morepart can be called after defaultpart,
1592 # although some axes may share their autoparting ---
1593 # it works, because the axes are processed sequentially
1597 while worse
< maxworse
:
1598 newticks
= self
.part
.lesspart()
1599 if newticks
is not None:
1601 bestrate
= self
.rate
.ratepart(self
, self
.ticks
, dense
)
1602 variants
= [[bestrate
, self
.ticks
]]
1604 newrate
= self
.rate
.ratepart(self
, newticks
, dense
)
1605 variants
.append([newrate
, newticks
])
1606 if newrate
< bestrate
:
1614 while worse
< maxworse
:
1615 newticks
= self
.part
.morepart()
1616 if newticks
is not None:
1618 bestrate
= self
.rate
.ratepart(self
, self
.ticks
, dense
)
1619 variants
= [[bestrate
, self
.ticks
]]
1621 newrate
= self
.rate
.ratepart(self
, newticks
, dense
)
1622 variants
.append([newrate
, newticks
])
1623 if newrate
< bestrate
:
1632 if self
.painter
is not None:
1635 while i
< len(variants
) and (bestrate
is None or variants
[i
][0] < bestrate
):
1636 saverange
= self
.__getinternalrange
()
1637 self
.ticks
= variants
[i
][1]
1639 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
1640 self
.layoutdata
= layoutdata()
1641 self
.painter
.dolayout(graph
, self
)
1642 ratelayout
= self
.painter
.ratelayout(graph
, self
, dense
)
1643 if ratelayout
is not None:
1644 variants
[i
][0] += ratelayout
1645 variants
[i
].append(self
.layoutdata
)
1647 variants
[i
][0] = None
1648 if variants
[i
][0] is not None and (bestrate
is None or variants
[i
][0] < bestrate
):
1649 bestrate
= variants
[i
][0]
1650 self
.__forceinternalrange
(saverange
)
1652 if bestrate
is None:
1653 raise PartitionError("no valid axis partitioning found")
1654 variants
= [variant
for variant
in variants
[:i
] if variant
[0] is not None]
1656 self
.ticks
= variants
[0][1]
1657 self
.layoutdata
= variants
[0][2]
1659 for tick
in self
.ticks
:
1661 self
.layoutdata
= layoutdata()
1662 self
.layoutdata
._extent
= 0
1664 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
1667 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
1668 self
.layoutdata
= layoutdata()
1669 self
.painter
.dolayout(graph
, self
)
1670 graph
.mindbbox(*[tick
.textbox
.bbox() for tick
in self
.ticks
if tick
.textbox
is not None])
1672 def dopaint(self
, graph
):
1673 if self
.painter
is not None:
1674 self
.painter
.paint(graph
, self
)
1676 def createlinkaxis(self
, **args
):
1677 return linkaxis(self
, **args
)
1680 class linaxis(_axis
, _linmap
):
1682 def __init__(self
, part
=autolinpart(), rate
=axisrater(), **args
):
1683 _axis
.__init
__(self
, **args
)
1684 if self
.fixmin
and self
.fixmax
:
1685 self
.relsize
= self
.max - self
.min
1690 class logaxis(_axis
, _logmap
):
1692 def __init__(self
, part
=autologpart(), rate
=axisrater(ticks
=axisrater
.logticks
, labels
=axisrater
.loglabels
), **args
):
1693 _axis
.__init
__(self
, **args
)
1694 if self
.fixmin
and self
.fixmax
:
1695 self
.relsize
= math
.log(self
.max) - math
.log(self
.min)
1702 def __init__(self
, linkedaxis
, title
=None, skipticklevel
=None, skiplabellevel
=0, painter
=axispainter(zerolineattrs
=None)):
1703 self
.linkedaxis
= linkedaxis
1704 while isinstance(self
.linkedaxis
, linkaxis
):
1705 self
.linkedaxis
= self
.linkedaxis
.linkedaxis
1706 self
.fixmin
= self
.linkedaxis
.fixmin
1707 self
.fixmax
= self
.linkedaxis
.fixmax
1709 self
.min = self
.linkedaxis
.min
1711 self
.max = self
.linkedaxis
.max
1712 self
.skipticklevel
= skipticklevel
1713 self
.skiplabellevel
= skiplabellevel
1715 self
.painter
= painter
1717 def ticks(self
, ticks
):
1720 ticklevel
= _tick
.ticklevel
1721 labellevel
= _tick
.labellevel
1722 if self
.skipticklevel
is not None and ticklevel
>= self
.skipticklevel
:
1724 if self
.skiplabellevel
is not None and labellevel
>= self
.skiplabellevel
:
1726 if ticklevel
is not None or labellevel
is not None:
1727 result
.append(tick(_tick
.enum
, _tick
.denom
, ticklevel
, labellevel
))
1729 # XXX: don't forget to calculate new text positions as soon as this is moved
1730 # outside of the paint method (when rating is moved into the axispainter)
1732 def getdatarange(self
):
1733 return self
.linkedaxis
.getdatarange()
1735 def setdatarange(self
, min, max):
1736 prevrange
= self
.linkedaxis
.getdatarange()
1737 self
.linkedaxis
.setdatarange(min, max)
1738 if hasattr(self
.linkedaxis
, "ticks") and prevrange
!= self
.linkedaxis
.getdatarange():
1739 raise RuntimeError("linkaxis datarange setting performed while linked axis layout already fixed")
1741 def dolayout(self
, graph
):
1742 self
.ticks
= self
.ticks(self
.linkedaxis
.ticks
)
1743 self
.convert
= self
.linkedaxis
.convert
1744 self
.divisor
= self
.linkedaxis
.divisor
1745 self
.suffix
= self
.linkedaxis
.suffix
1746 self
.layoutdata
= layoutdata()
1747 self
.painter
.dolayout(graph
, self
)
1749 def dopaint(self
, graph
):
1750 self
.painter
.paint(graph
, self
)
1752 def createlinkaxis(self
, **args
):
1753 return linkaxis(self
.linkedaxis
)
1758 def __init__(self
, axislist
, splitlist
=0.5, splitdist
=0.1, relsizesplitdist
=1, title
=None, painter
=splitaxispainter()):
1760 self
.axislist
= axislist
1761 self
.painter
= painter
1762 self
.splitlist
= list(helper
.ensuresequence(splitlist
))
1763 self
.splitlist
.sort()
1764 if len(self
.axislist
) != len(self
.splitlist
) + 1:
1765 for subaxis
in self
.axislist
:
1766 if not isinstance(subaxis
, linkaxis
):
1767 raise ValueError("axislist and splitlist lengths do not fit together")
1768 for subaxis
in self
.axislist
:
1769 if isinstance(subaxis
, linkaxis
):
1770 subaxis
.vmin
= subaxis
.linkedaxis
.vmin
1771 subaxis
.vminover
= subaxis
.linkedaxis
.vminover
1772 subaxis
.vmax
= subaxis
.linkedaxis
.vmax
1773 subaxis
.vmaxover
= subaxis
.linkedaxis
.vmaxover
1777 self
.axislist
[0].vmin
= 0
1778 self
.axislist
[0].vminover
= None
1779 self
.axislist
[-1].vmax
= 1
1780 self
.axislist
[-1].vmaxover
= None
1781 for i
in xrange(len(self
.splitlist
)):
1782 if self
.splitlist
[i
] is not None:
1783 self
.axislist
[i
].vmax
= self
.splitlist
[i
] - 0.5*splitdist
1784 self
.axislist
[i
].vmaxover
= self
.splitlist
[i
]
1785 self
.axislist
[i
+1].vmin
= self
.splitlist
[i
] + 0.5*splitdist
1786 self
.axislist
[i
+1].vminover
= self
.splitlist
[i
]
1788 while i
< len(self
.axislist
):
1789 if self
.axislist
[i
].vmax
is None:
1790 j
= relsize
= relsize2
= 0
1791 while self
.axislist
[i
+ j
].vmax
is None:
1792 relsize
+= self
.axislist
[i
+ j
].relsize
+ relsizesplitdist
1794 relsize
+= self
.axislist
[i
+ j
].relsize
1795 vleft
= self
.axislist
[i
].vmin
1796 vright
= self
.axislist
[i
+ j
].vmax
1797 for k
in range(i
, i
+ j
):
1798 relsize2
+= self
.axislist
[k
].relsize
1799 self
.axislist
[k
].vmax
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
1800 relsize2
+= 0.5 * relsizesplitdist
1801 self
.axislist
[k
].vmaxover
= self
.axislist
[k
+ 1].vminover
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
1802 relsize2
+= 0.5 * relsizesplitdist
1803 self
.axislist
[k
+1].vmin
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
1804 if i
== 0 and i
+ j
+ 1 == len(self
.axislist
):
1805 self
.relsize
= relsize
1810 self
.fixmin
= self
.axislist
[0].fixmin
1812 self
.min = self
.axislist
[0].min
1813 self
.fixmax
= self
.axislist
[-1].fixmax
1815 self
.max = self
.axislist
[-1].max
1819 def getdatarange(self
):
1820 min = self
.axislist
[0].getdatarange()
1821 max = self
.axislist
[-1].getdatarange()
1823 return min[0], max[1]
1827 def setdatarange(self
, min, max):
1828 self
.axislist
[0].setdatarange(min, None)
1829 self
.axislist
[-1].setdatarange(None, max)
1831 def gettickrange(self
):
1832 min = self
.axislist
[0].gettickrange()
1833 max = self
.axislist
[-1].gettickrange()
1835 return min[0], max[1]
1839 def settickrange(self
, min, max):
1840 self
.axislist
[0].settickrange(min, None)
1841 self
.axislist
[-1].settickrange(None, max)
1843 def convert(self
, value
):
1844 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
1845 if value
< self
.axislist
[0].max:
1846 return self
.axislist
[0].vmin
+ self
.axislist
[0].convert(value
)*(self
.axislist
[0].vmax
-self
.axislist
[0].vmin
)
1847 for axis
in self
.axislist
[1:-1]:
1848 if value
> axis
.min and value
< axis
.max:
1849 return axis
.vmin
+ axis
.convert(value
)*(axis
.vmax
-axis
.vmin
)
1850 if value
> self
.axislist
[-1].min:
1851 return self
.axislist
[-1].vmin
+ self
.axislist
[-1].convert(value
)*(self
.axislist
[-1].vmax
-self
.axislist
[-1].vmin
)
1852 raise ValueError("value couldn't be assigned to a split region")
1854 def dolayout(self
, graph
):
1855 self
.layoutdata
= layoutdata()
1856 self
.painter
.dolayout(graph
, self
)
1858 def dopaint(self
, graph
):
1859 self
.painter
.paint(graph
, self
)
1861 def createlinkaxis(self
, painter
=None, *args
):
1863 return splitaxis([x
.createlinkaxis() for x
in self
.axislist
], splitlist
=None)
1864 if len(args
) != len(self
.axislist
):
1865 raise IndexError("length of the argument list doesn't fit to split number")
1867 painter
= self
.painter
1868 return splitaxis([x
.createlinkaxis(**arg
) for x
, arg
in zip(self
.axislist
, args
)], painter
=painter
)
1873 def __init__(self
, subaxis
=None, multisubaxis
=0, title
=None, dist
=0.5, firstdist
=None, lastdist
=None, names
=None, texts
={}, painter
=baraxispainter()):
1875 if firstdist
is not None:
1876 self
.firstdist
= firstdist
1878 self
.firstdist
= 0.5 * dist
1879 if lastdist
is not None:
1880 self
.lastdist
= lastdist
1882 self
.lastdist
= 0.5 * dist
1883 self
.relsizes
= None
1886 for name
in helper
.ensuresequence(names
):
1888 self
.fixnames
= names
is not None
1889 self
.multisubaxis
= multisubaxis
1890 if self
.multisubaxis
:
1891 self
.createsubaxis
= subaxis
1892 self
.subaxis
= [self
.createsubaxis
.createsubaxis() for name
in self
.names
]
1894 self
.subaxis
= subaxis
1898 self
.painter
= painter
1900 def getdatarange(self
):
1903 def setname(self
, name
, *subnames
):
1904 # setting self.relsizes to None forces later recalculation
1905 if not self
.fixnames
:
1906 if name
not in self
.names
:
1907 self
.relsizes
= None
1908 self
.names
.append(name
)
1909 if self
.multisubaxis
:
1910 self
.subaxis
.append(self
.createsubaxis
.createsubaxis())
1911 if (not self
.fixnames
or name
in self
.names
) and len(subnames
):
1912 if self
.multisubaxis
:
1913 if self
.subaxis
[self
.names
.index(name
)].setname(*subnames
):
1914 self
.relsizes
= None
1916 if self
.subaxis
.setname(*subnames
):
1917 self
.relsizes
= None
1918 return self
.relsizes
is not None
1920 def updaterelsizes(self
):
1921 self
.relsizes
= [i
*self
.dist
+ self
.firstdist
for i
in range(len(self
.names
) + 1)]
1922 self
.relsizes
[-1] += self
.lastdist
- self
.dist
1923 if self
.multisubaxis
:
1925 for i
in range(1, len(self
.relsizes
)):
1926 self
.subaxis
[i
-1].updaterelsizes()
1927 subrelsize
+= self
.subaxis
[i
-1].relsizes
[-1]
1928 self
.relsizes
[i
] += subrelsize
1930 if self
.subaxis
is None:
1933 self
.subaxis
.updaterelsizes()
1934 subrelsize
= self
.subaxis
.relsizes
[-1]
1935 for i
in range(1, len(self
.relsizes
)):
1936 self
.relsizes
[i
] += i
* subrelsize
1938 def convert(self
, value
):
1939 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
1940 if not self
.relsizes
:
1941 self
.updaterelsizes()
1942 pos
= self
.names
.index(value
[0])
1944 if self
.subaxis
is None:
1947 if self
.multisubaxis
:
1948 subvalue
= value
[1] * self
.subaxis
[pos
].relsizes
[-1]
1950 subvalue
= value
[1] * self
.subaxis
.relsizes
[-1]
1952 if self
.multisubaxis
:
1953 subvalue
= self
.subaxis
[pos
].convert(value
[1:]) * self
.subaxis
[pos
].relsizes
[-1]
1955 subvalue
= self
.subaxis
.convert(value
[1:]) * self
.subaxis
.relsizes
[-1]
1956 return (self
.relsizes
[pos
] + subvalue
) / float(self
.relsizes
[-1])
1958 def dolayout(self
, graph
):
1959 self
.layoutdata
= layoutdata()
1960 self
.painter
.dolayout(graph
, self
)
1962 def dopaint(self
, graph
):
1963 self
.painter
.paint(graph
, self
)
1965 def createlinkaxis(self
, **args
):
1966 if self
.subaxis
is not None:
1967 if self
.multisubaxis
:
1968 subaxis
= [subaxis
.createlinkaxis() for subaxis
in self
.subaxis
]
1970 subaxis
= self
.subaxis
.createlinkaxis()
1973 return baraxis(subaxis
=subaxis
, dist
=self
.dist
, firstdist
=self
.firstdist
, lastdist
=self
.lastdist
, **args
)
1975 createsubaxis
= createlinkaxis
1978 ################################################################################
1980 ################################################################################
1983 # g = graph.graphxy(key=graph.key())
1984 # g.addkey(graph.key(), ...)
1989 def __init__(self
, dist
="0.2 cm", pos
= "tr", hinside
= 1, vinside
= 1, hdist
="0.6 cm", vdist
="0.4 cm",
1990 symbolwidth
="0.5 cm", symbolheight
="0.25 cm", symbolspace
="0.2 cm",
1991 textattrs
=textmodule
.vshift
.mathaxis
):
1992 self
.dist_str
= dist
1994 self
.hinside
= hinside
1995 self
.vinside
= vinside
1996 self
.hdist_str
= hdist
1997 self
.vdist_str
= vdist
1998 self
.symbolwidth_str
= symbolwidth
1999 self
.symbolheight_str
= symbolheight
2000 self
.symbolspace_str
= symbolspace
2001 self
.textattrs
= textattrs
2002 self
.plotinfos
= None
2003 if self
.pos
in ("tr", "rt"):
2006 elif self
.pos
in ("br", "rb"):
2009 elif self
.pos
in ("tl", "lt"):
2012 elif self
.pos
in ("bl", "lb"):
2016 raise RuntimeError("invalid pos attribute")
2018 def setplotinfos(self
, *plotinfos
):
2019 """set the plotinfos to be used in the key
2020 - call it exactly once"""
2021 if self
.plotinfos
is not None:
2022 raise RuntimeError("setplotinfo is called multiple times")
2023 self
.plotinfos
= plotinfos
2025 def dolayout(self
, graph
):
2026 """creates the layout of the key"""
2027 self
._dist
= unit
.topt(unit
.length(self
.dist_str
, default_type
="v"))
2028 self
._hdist
= unit
.topt(unit
.length(self
.hdist_str
, default_type
="v"))
2029 self
._vdist
= unit
.topt(unit
.length(self
.vdist_str
, default_type
="v"))
2030 self
._symbolwidth
= unit
.topt(unit
.length(self
.symbolwidth_str
, default_type
="v"))
2031 self
._symbolheight
= unit
.topt(unit
.length(self
.symbolheight_str
, default_type
="v"))
2032 self
._symbolspace
= unit
.topt(unit
.length(self
.symbolspace_str
, default_type
="v"))
2034 for plotinfo
in self
.plotinfos
:
2035 self
.titles
.append(graph
.texrunner
._text
(0, 0, plotinfo
.data
.title
, *helper
.ensuresequence(self
.textattrs
)))
2036 box
._tile
(self
.titles
, self
._dist
, 0, -1)
2037 box
._linealignequal
(self
.titles
, self
._symbolwidth
+ self
._symbolspace
, 1, 0)
2040 """return a bbox for the key
2041 method should be called after dolayout"""
2042 result
= self
.titles
[0].bbox()
2043 for title
in self
.titles
[1:]:
2044 result
= result
+ title
.bbox() + bbox
.bbox(0, title
.center
[1] - 0.5 * self
._symbolheight
,
2045 0, title
.center
[1] + 0.5 * self
._symbolheight
)
2048 def paint(self
, c
, x
, y
):
2049 """paint the graph key into a canvas c at the position x and y (in postscript points)
2050 - method should be called after dolayout
2051 - the x, y alignment might be calculated by the graph using:
2052 - the bbox of the key as returned by the keys bbox method
2053 - the attributes _hdist, _vdist, hinside, and vinside of the key
2054 - the dimension and geometry of the graph"""
2055 sc
= c
.insert(canvas
.canvas(trafo
._translate
(x
, y
)))
2056 for plotinfo
, title
in zip(self
.plotinfos
, self
.titles
):
2057 plotinfo
.style
.key(sc
, 0, -0.5 * self
._symbolheight
+ title
.center
[1],
2058 self
._symbolwidth
, self
._symbolheight
)
2062 ################################################################################
2064 ################################################################################
2069 def __init__(self
, data
, style
):
2074 class graphxy(canvas
.canvas
):
2078 def clipcanvas(self
):
2079 return self
.insert(canvas
.canvas(canvas
.clip(path
._rect
(self
._xpos
, self
._ypos
, self
._width
, self
._height
))))
2081 def plot(self
, data
, style
=None):
2083 raise RuntimeError("layout setup was already performed")
2085 if helper
.issequence(data
):
2086 raise RuntimeError("list plot needs an explicit style")
2087 if self
.defaultstyle
.has_key(data
.defaultstyle
):
2088 style
= self
.defaultstyle
[data
.defaultstyle
].iterate()
2090 style
= data
.defaultstyle()
2091 self
.defaultstyle
[data
.defaultstyle
] = style
2094 for d
in helper
.ensuresequence(data
):
2096 style
= style
.iterate()
2099 d
.setstyle(self
, style
)
2100 plotinfos
.append(plotinfo(d
, style
))
2101 self
.plotinfos
.extend(plotinfos
)
2102 if helper
.issequence(data
):
2106 def addkey(self
, key
, *plotinfos
):
2108 raise RuntimeError("layout setup was already performed")
2109 self
.addkeys
.append((key
, plotinfos
))
2111 def _vxtickpoint(self
, axis
, v
):
2112 return (self
._xpos
+v
*self
._width
, axis
.axispos
)
2114 def _vytickpoint(self
, axis
, v
):
2115 return (axis
.axispos
, self
._ypos
+v
*self
._height
)
2117 def vtickdirection(self
, axis
, v
):
2118 return axis
.fixtickdirection
2120 def _pos(self
, x
, y
, xaxis
=None, yaxis
=None):
2121 if xaxis
is None: xaxis
= self
.axes
["x"]
2122 if yaxis
is None: yaxis
= self
.axes
["y"]
2123 return self
._xpos
+xaxis
.convert(x
)*self
._width
, self
._ypos
+yaxis
.convert(y
)*self
._height
2125 def pos(self
, x
, y
, xaxis
=None, yaxis
=None):
2126 if xaxis
is None: xaxis
= self
.axes
["x"]
2127 if yaxis
is None: yaxis
= self
.axes
["y"]
2128 return self
.xpos
+xaxis
.convert(x
)*self
.width
, self
.ypos
+yaxis
.convert(y
)*self
.height
2130 def _vpos(self
, vx
, vy
):
2131 return self
._xpos
+vx
*self
._width
, self
._ypos
+vy
*self
._height
2133 def vpos(self
, vx
, vy
):
2134 return self
.xpos
+vx
*self
.width
, self
.ypos
+vy
*self
.height
2136 def xbaseline(self
, axis
, x1
, x2
, shift
=0, xaxis
=None):
2137 if xaxis
is None: xaxis
= self
.axes
["x"]
2138 v1
, v2
= xaxis
.convert(x1
), xaxis
.convert(x2
)
2139 return path
._line
(self
._xpos
+v1
*self
._width
, axis
.axispos
+shift
,
2140 self
._xpos
+v2
*self
._width
, axis
.axispos
+shift
)
2142 def ybaseline(self
, axis
, y1
, y2
, shift
=0, yaxis
=None):
2143 if yaxis
is None: yaxis
= self
.axes
["y"]
2144 v1
, v2
= yaxis
.convert(y1
), yaxis
.convert(y2
)
2145 return path
._line
(axis
.axispos
+shift
, self
._ypos
+v1
*self
._height
,
2146 axis
.axispos
+shift
, self
._ypos
+v2
*self
._height
)
2148 def vxbaseline(self
, axis
, v1
=None, v2
=None, shift
=0):
2149 if v1
is None: v1
= 0
2150 if v2
is None: v2
= 1
2151 return path
._line
(self
._xpos
+v1
*self
._width
, axis
.axispos
+shift
,
2152 self
._xpos
+v2
*self
._width
, axis
.axispos
+shift
)
2154 def vybaseline(self
, axis
, v1
=None, v2
=None, shift
=0):
2155 if v1
is None: v1
= 0
2156 if v2
is None: v2
= 1
2157 return path
._line
(axis
.axispos
+shift
, self
._ypos
+v1
*self
._height
,
2158 axis
.axispos
+shift
, self
._ypos
+v2
*self
._height
)
2160 def xgridpath(self
, x
, xaxis
=None):
2161 if xaxis
is None: xaxis
= self
.axes
["x"]
2162 v
= xaxis
.convert(x
)
2163 return path
._line
(self
._xpos
+v
*self
._width
, self
._ypos
,
2164 self
._xpos
+v
*self
._width
, self
._ypos
+self
._height
)
2166 def ygridpath(self
, y
, yaxis
=None):
2167 if yaxis
is None: yaxis
= self
.axes
["y"]
2168 v
= yaxis
.convert(y
)
2169 return path
._line
(self
._xpos
, self
._ypos
+v
*self
._height
,
2170 self
._xpos
+self
._width
, self
._ypos
+v
*self
._height
)
2172 def vxgridpath(self
, v
):
2173 return path
._line
(self
._xpos
+v
*self
._width
, self
._ypos
,
2174 self
._xpos
+v
*self
._width
, self
._ypos
+self
._height
)
2176 def vygridpath(self
, v
):
2177 return path
._line
(self
._xpos
, self
._ypos
+v
*self
._height
,
2178 self
._xpos
+self
._width
, self
._ypos
+v
*self
._height
)
2180 def _addpos(self
, x
, y
, dx
, dy
):
2183 def _connect(self
, x1
, y1
, x2
, y2
):
2184 return path
._lineto
(x2
, y2
)
2186 def keynum(self
, key
):
2188 while key
[0] in string
.letters
:
2194 def gatherranges(self
):
2196 for plotinfo
in self
.plotinfos
:
2197 pdranges
= plotinfo
.data
.getranges()
2198 if pdranges
is not None:
2199 for key
in pdranges
.keys():
2200 if key
not in ranges
.keys():
2201 ranges
[key
] = pdranges
[key
]
2203 ranges
[key
] = (min(ranges
[key
][0], pdranges
[key
][0]),
2204 max(ranges
[key
][1], pdranges
[key
][1]))
2205 # known ranges are also set as ranges for the axes
2206 for key
, axis
in self
.axes
.items():
2207 if key
in ranges
.keys():
2208 axis
.setdatarange(*ranges
[key
])
2209 ranges
[key
] = axis
.getdatarange()
2210 if ranges
[key
] is None:
2214 def removedomethod(self
, method
):
2218 self
.domethods
.remove(method
)
2224 if not self
.removedomethod(self
.dolayout
): return
2226 # create list of ranges
2228 ranges
= self
.gatherranges()
2229 # 2. calculate additional ranges out of known ranges
2230 for plotinfo
in self
.plotinfos
:
2231 plotinfo
.data
.setranges(ranges
)
2232 # 3. gather ranges again
2235 # do the layout for all axes
2236 axesdist
= unit
.topt(unit
.length(self
.axesdist_str
, default_type
="v"))
2237 XPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[0])
2238 YPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[1])
2239 self
._xaxisextents
= [0, 0]
2240 self
._yaxisextents
= [0, 0]
2241 needxaxisdist
= [0, 0]
2242 needyaxisdist
= [0, 0]
2243 items
= list(self
.axes
.items())
2244 items
.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
2245 for key
, axis
in items
:
2246 num
= self
.keynum(key
)
2247 num2
= 1 - num
% 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
2248 num3
= 1 - 2 * (num
% 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
2249 if XPattern
.match(key
):
2250 if needxaxisdist
[num2
]:
2251 self
._xaxisextents
[num2
] += axesdist
2252 axis
.axispos
= self
._ypos
+num2
*self
._height
+ num3
*self
._xaxisextents
[num2
]
2253 axis
._vtickpoint
= self
._vxtickpoint
2254 axis
.fixtickdirection
= (0, num3
)
2255 axis
.vgridpath
= self
.vxgridpath
2256 axis
.vbaseline
= self
.vxbaseline
2257 axis
.gridpath
= self
.xgridpath
2258 axis
.baseline
= self
.xbaseline
2259 elif YPattern
.match(key
):
2260 if needyaxisdist
[num2
]:
2261 self
._yaxisextents
[num2
] += axesdist
2262 axis
.axispos
= self
._xpos
+num2
*self
._width
+ num3
*self
._yaxisextents
[num2
]
2263 axis
._vtickpoint
= self
._vytickpoint
2264 axis
.fixtickdirection
= (num3
, 0)
2265 axis
.vgridpath
= self
.vygridpath
2266 axis
.vbaseline
= self
.vybaseline
2267 axis
.gridpath
= self
.ygridpath
2268 axis
.baseline
= self
.ybaseline
2270 raise ValueError("Axis key '%s' not allowed" % key
)
2271 axis
.vtickdirection
= self
.vtickdirection
2273 if XPattern
.match(key
):
2274 self
._xaxisextents
[num2
] += axis
.layoutdata
._extent
2275 needxaxisdist
[num2
] = 1
2276 if YPattern
.match(key
):
2277 self
._yaxisextents
[num2
] += axis
.layoutdata
._extent
2278 needyaxisdist
[num2
] = 1
2280 def dobackground(self
):
2282 if not self
.removedomethod(self
.dobackground
): return
2283 if self
.backgroundattrs
is not None:
2284 self
.draw(path
._rect
(self
._xpos
, self
._ypos
, self
._width
, self
._height
),
2285 *helper
.ensuresequence(self
.backgroundattrs
))
2289 if not self
.removedomethod(self
.doaxes
): return
2290 for axis
in self
.axes
.values():
2295 if not self
.removedomethod(self
.dodata
): return
2296 for plotinfo
in self
.plotinfos
:
2297 plotinfo
.data
.draw(self
)
2299 def _dokey(self
, key
, *plotinfos
):
2300 key
.setplotinfos(*plotinfos
)
2305 x
= self
._xpos
+ self
._width
- bbox
.urx
- key
._hdist
2307 x
= self
._xpos
+ self
._width
- bbox
.llx
+ key
._hdist
2310 x
= self
._xpos
- bbox
.llx
+ key
._hdist
2312 x
= self
._xpos
- bbox
.urx
- key
._hdist
2315 y
= self
._ypos
+ self
._height
- bbox
.ury
- key
._vdist
2317 y
= self
._ypos
+ self
._height
- bbox
.lly
+ key
._vdist
2320 y
= self
._ypos
- bbox
.lly
+ key
._vdist
2322 y
= self
._ypos
- bbox
.ury
- key
._vdist
2323 self
.mindbbox(bbox
.transformed(trafo
._translate
(x
, y
)))
2324 key
.paint(self
, x
, y
)
2328 if not self
.removedomethod(self
.dokey
): return
2329 if self
.key
is not None:
2330 self
._dokey
(self
.key
, *self
.plotinfos
)
2331 for key
, plotinfos
in self
.addkeys
:
2332 self
._dokey
(key
, *plotinfos
)
2335 while len(self
.domethods
):
2338 def initwidthheight(self
, width
, height
, ratio
):
2339 if (width
is not None) and (height
is None):
2340 self
.width
= unit
.length(width
)
2341 self
.height
= (1.0/ratio
) * self
.width
2342 elif (height
is not None) and (width
is None):
2343 self
.height
= unit
.length(height
)
2344 self
.width
= ratio
* self
.height
2346 self
.width
= unit
.length(width
)
2347 self
.height
= unit
.length(height
)
2348 self
._width
= unit
.topt(self
.width
)
2349 self
._height
= unit
.topt(self
.height
)
2350 if self
._width
<= 0: raise ValueError("width <= 0")
2351 if self
._height
<= 0: raise ValueError("height <= 0")
2353 def initaxes(self
, axes
, addlinkaxes
=0):
2354 for key
in self
.Names
:
2355 if not axes
.has_key(key
):
2356 axes
[key
] = linaxis()
2357 elif axes
[key
] is None:
2360 if not axes
.has_key(key
+ "2") and axes
.has_key(key
):
2361 axes
[key
+ "2"] = axes
[key
].createlinkaxis()
2362 elif axes
[key
+ "2"] is None:
2366 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, ratio
=goldenmean
,
2367 key
=None, backgroundattrs
=None, dense
=1, axesdist
="0.8 cm", **axes
):
2368 canvas
.canvas
.__init
__(self
)
2369 self
.xpos
= unit
.length(xpos
)
2370 self
.ypos
= unit
.length(ypos
)
2371 self
._xpos
= unit
.topt(self
.xpos
)
2372 self
._ypos
= unit
.topt(self
.ypos
)
2373 self
.initwidthheight(width
, height
, ratio
)
2374 self
.initaxes(axes
, 1)
2376 self
.backgroundattrs
= backgroundattrs
2378 self
.axesdist_str
= axesdist
2380 self
.domethods
= [self
.dolayout
, self
.dobackground
, self
.doaxes
, self
.dodata
, self
.dokey
]
2382 self
.defaultstyle
= {}
2384 self
.mindbboxes
= []
2386 def mindbbox(self
, *boxes
):
2387 self
.mindbboxes
.extend(boxes
)
2391 result
= bbox
.bbox(self
._xpos
- self
._yaxisextents
[0],
2392 self
._ypos
- self
._xaxisextents
[0],
2393 self
._xpos
+ self
._width
+ self
._yaxisextents
[1],
2394 self
._ypos
+ self
._height
+ self
._xaxisextents
[1])
2395 for box
in self
.mindbboxes
:
2396 result
= result
+ box
2399 def write(self
, file):
2401 canvas
.canvas
.write(self
, file)
2405 # some thoughts, but deferred right now
2407 # class graphxyz(graphxy):
2409 # Names = "x", "y", "z"
2411 # def _vxtickpoint(self, axis, v):
2412 # return self._vpos(v, axis.vypos, axis.vzpos)
2414 # def _vytickpoint(self, axis, v):
2415 # return self._vpos(axis.vxpos, v, axis.vzpos)
2417 # def _vztickpoint(self, axis, v):
2418 # return self._vpos(axis.vxpos, axis.vypos, v)
2420 # def vxtickdirection(self, axis, v):
2421 # x1, y1 = self._vpos(v, axis.vypos, axis.vzpos)
2422 # x2, y2 = self._vpos(v, 0.5, 0)
2423 # dx, dy = x1 - x2, y1 - y2
2424 # norm = math.sqrt(dx*dx + dy*dy)
2425 # return dx/norm, dy/norm
2427 # def vytickdirection(self, axis, v):
2428 # x1, y1 = self._vpos(axis.vxpos, v, axis.vzpos)
2429 # x2, y2 = self._vpos(0.5, v, 0)
2430 # dx, dy = x1 - x2, y1 - y2
2431 # norm = math.sqrt(dx*dx + dy*dy)
2432 # return dx/norm, dy/norm
2434 # def vztickdirection(self, axis, v):
2436 # x1, y1 = self._vpos(axis.vxpos, axis.vypos, v)
2437 # x2, y2 = self._vpos(0.5, 0.5, v)
2438 # dx, dy = x1 - x2, y1 - y2
2439 # norm = math.sqrt(dx*dx + dy*dy)
2440 # return dx/norm, dy/norm
2442 # def _pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
2443 # if xaxis is None: xaxis = self.axes["x"]
2444 # if yaxis is None: yaxis = self.axes["y"]
2445 # if zaxis is None: zaxis = self.axes["z"]
2446 # return self._vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
2448 # def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
2449 # if xaxis is None: xaxis = self.axes["x"]
2450 # if yaxis is None: yaxis = self.axes["y"]
2451 # if zaxis is None: zaxis = self.axes["z"]
2452 # return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
2454 # def _vpos(self, vx, vy, vz):
2455 # x, y, z = (vx - 0.5)*self._depth, (vy - 0.5)*self._width, (vz - 0.5)*self._height
2456 # d0 = float(self.a[0]*self.b[1]*(z-self.eye[2])
2457 # + self.a[2]*self.b[0]*(y-self.eye[1])
2458 # + self.a[1]*self.b[2]*(x-self.eye[0])
2459 # - self.a[2]*self.b[1]*(x-self.eye[0])
2460 # - self.a[0]*self.b[2]*(y-self.eye[1])
2461 # - self.a[1]*self.b[0]*(z-self.eye[2]))
2462 # da = (self.eye[0]*self.b[1]*(z-self.eye[2])
2463 # + self.eye[2]*self.b[0]*(y-self.eye[1])
2464 # + self.eye[1]*self.b[2]*(x-self.eye[0])
2465 # - self.eye[2]*self.b[1]*(x-self.eye[0])
2466 # - self.eye[0]*self.b[2]*(y-self.eye[1])
2467 # - self.eye[1]*self.b[0]*(z-self.eye[2]))
2468 # db = (self.a[0]*self.eye[1]*(z-self.eye[2])
2469 # + self.a[2]*self.eye[0]*(y-self.eye[1])
2470 # + self.a[1]*self.eye[2]*(x-self.eye[0])
2471 # - self.a[2]*self.eye[1]*(x-self.eye[0])
2472 # - self.a[0]*self.eye[2]*(y-self.eye[1])
2473 # - self.a[1]*self.eye[0]*(z-self.eye[2]))
2474 # return da/d0 + self._xpos, db/d0 + self._ypos
2476 # def vpos(self, vx, vy, vz):
2477 # tx, ty = self._vpos(vx, vy, vz)
2478 # return unit.t_pt(tx), unit.t_pt(ty)
2480 # def xbaseline(self, axis, x1, x2, shift=0, xaxis=None):
2481 # if xaxis is None: xaxis = self.axes["x"]
2482 # return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2), shift)
2484 # def ybaseline(self, axis, y1, y2, shift=0, yaxis=None):
2485 # if yaxis is None: yaxis = self.axes["y"]
2486 # return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2), shift)
2488 # def zbaseline(self, axis, z1, z2, shift=0, zaxis=None):
2489 # if zaxis is None: zaxis = self.axes["z"]
2490 # return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2), shift)
2492 # def vxbaseline(self, axis, v1, v2, shift=0):
2493 # return (path._line(*(self._vpos(v1, 0, 0) + self._vpos(v2, 0, 0))) +
2494 # path._line(*(self._vpos(v1, 0, 1) + self._vpos(v2, 0, 1))) +
2495 # path._line(*(self._vpos(v1, 1, 1) + self._vpos(v2, 1, 1))) +
2496 # path._line(*(self._vpos(v1, 1, 0) + self._vpos(v2, 1, 0))))
2498 # def vybaseline(self, axis, v1, v2, shift=0):
2499 # return (path._line(*(self._vpos(0, v1, 0) + self._vpos(0, v2, 0))) +
2500 # path._line(*(self._vpos(0, v1, 1) + self._vpos(0, v2, 1))) +
2501 # path._line(*(self._vpos(1, v1, 1) + self._vpos(1, v2, 1))) +
2502 # path._line(*(self._vpos(1, v1, 0) + self._vpos(1, v2, 0))))
2504 # def vzbaseline(self, axis, v1, v2, shift=0):
2505 # return (path._line(*(self._vpos(0, 0, v1) + self._vpos(0, 0, v2))) +
2506 # path._line(*(self._vpos(0, 1, v1) + self._vpos(0, 1, v2))) +
2507 # path._line(*(self._vpos(1, 1, v1) + self._vpos(1, 1, v2))) +
2508 # path._line(*(self._vpos(1, 0, v1) + self._vpos(1, 0, v2))))
2510 # def xgridpath(self, x, xaxis=None):
2512 # if xaxis is None: xaxis = self.axes["x"]
2513 # v = xaxis.convert(x)
2514 # return path._line(self._xpos+v*self._width, self._ypos,
2515 # self._xpos+v*self._width, self._ypos+self._height)
2517 # def ygridpath(self, y, yaxis=None):
2519 # if yaxis is None: yaxis = self.axes["y"]
2520 # v = yaxis.convert(y)
2521 # return path._line(self._xpos, self._ypos+v*self._height,
2522 # self._xpos+self._width, self._ypos+v*self._height)
2524 # def zgridpath(self, z, zaxis=None):
2526 # if zaxis is None: zaxis = self.axes["z"]
2527 # v = zaxis.convert(z)
2528 # return path._line(self._xpos, self._zpos+v*self._height,
2529 # self._xpos+self._width, self._zpos+v*self._height)
2531 # def vxgridpath(self, v):
2532 # return path.path(path._moveto(*self._vpos(v, 0, 0)),
2533 # path._lineto(*self._vpos(v, 0, 1)),
2534 # path._lineto(*self._vpos(v, 1, 1)),
2535 # path._lineto(*self._vpos(v, 1, 0)),
2538 # def vygridpath(self, v):
2539 # return path.path(path._moveto(*self._vpos(0, v, 0)),
2540 # path._lineto(*self._vpos(0, v, 1)),
2541 # path._lineto(*self._vpos(1, v, 1)),
2542 # path._lineto(*self._vpos(1, v, 0)),
2545 # def vzgridpath(self, v):
2546 # return path.path(path._moveto(*self._vpos(0, 0, v)),
2547 # path._lineto(*self._vpos(0, 1, v)),
2548 # path._lineto(*self._vpos(1, 1, v)),
2549 # path._lineto(*self._vpos(1, 0, v)),
2552 # def _addpos(self, x, y, dx, dy):
2556 # def _connect(self, x1, y1, x2, y2):
2558 # return path._lineto(x2, y2)
2562 # if not self.removedomethod(self.doaxes): return
2563 # axesdist = unit.topt(unit.length(self.axesdist_str, default_type="v"))
2564 # XPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[0])
2565 # YPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[1])
2566 # ZPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[2])
2567 # items = list(self.axes.items())
2568 # items.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
2569 # for key, axis in items:
2570 # num = self.keynum(key)
2571 # num2 = 1 - num % 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
2572 # num3 = 1 - 2 * (num % 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
2573 # if XPattern.match(key):
2576 # axis._vtickpoint = self._vxtickpoint
2577 # axis.vgridpath = self.vxgridpath
2578 # axis.vbaseline = self.vxbaseline
2579 # axis.vtickdirection = self.vxtickdirection
2580 # elif YPattern.match(key):
2583 # axis._vtickpoint = self._vytickpoint
2584 # axis.vgridpath = self.vygridpath
2585 # axis.vbaseline = self.vybaseline
2586 # axis.vtickdirection = self.vytickdirection
2587 # elif ZPattern.match(key):
2590 # axis._vtickpoint = self._vztickpoint
2591 # axis.vgridpath = self.vzgridpath
2592 # axis.vbaseline = self.vzbaseline
2593 # axis.vtickdirection = self.vztickdirection
2595 # raise ValueError("Axis key '%s' not allowed" % key)
2596 # if axis.painter is not None:
2597 # axis.dopaint(self)
2598 # # if XPattern.match(key):
2599 # # self._xaxisextents[num2] += axis._extent
2600 # # needxaxisdist[num2] = 1
2601 # # if YPattern.match(key):
2602 # # self._yaxisextents[num2] += axis._extent
2603 # # needyaxisdist[num2] = 1
2605 # def __init__(self, tex, xpos=0, ypos=0, width=None, height=None, depth=None,
2606 # phi=30, theta=30, distance=1,
2607 # backgroundattrs=None, axesdist="0.8 cm", **axes):
2608 # canvas.canvas.__init__(self)
2612 # self._xpos = unit.topt(xpos)
2613 # self._ypos = unit.topt(ypos)
2614 # self._width = unit.topt(width)
2615 # self._height = unit.topt(height)
2616 # self._depth = unit.topt(depth)
2617 # self.width = width
2618 # self.height = height
2619 # self.depth = depth
2620 # if self._width <= 0: raise ValueError("width < 0")
2621 # if self._height <= 0: raise ValueError("height < 0")
2622 # if self._depth <= 0: raise ValueError("height < 0")
2623 # self._distance = distance*math.sqrt(self._width*self._width+
2624 # self._height*self._height+
2625 # self._depth*self._depth)
2626 # phi *= -math.pi/180
2627 # theta *= math.pi/180
2628 # self.a = (-math.sin(phi), math.cos(phi), 0)
2629 # self.b = (-math.cos(phi)*math.sin(theta),
2630 # -math.sin(phi)*math.sin(theta),
2632 # self.eye = (self._distance*math.cos(phi)*math.cos(theta),
2633 # self._distance*math.sin(phi)*math.cos(theta),
2634 # self._distance*math.sin(theta))
2635 # self.initaxes(axes)
2636 # self.axesdist_str = axesdist
2637 # self.backgroundattrs = backgroundattrs
2640 # self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
2641 # self.haslayout = 0
2642 # self.defaultstyle = {}
2646 # return bbox.bbox(self._xpos - 200, self._ypos - 200, self._xpos + 200, self._ypos + 200)
2649 ################################################################################
2651 ################################################################################
2654 #class _Ichangeattr:
2655 # """attribute changer
2656 # is an iterator for attributes where an attribute
2657 # is not refered by just a number (like for a sequence),
2658 # but also by the number of attributes requested
2659 # by calls of the next method (like for an color palette)
2660 # (you should ensure to call all needed next before the attr)
2662 # the attribute itself is implemented by overloading the _attr method"""
2665 # "get an attribute"
2668 # "get an attribute changer for the next attribute"
2671 class _changeattr
: pass
2674 class changeattr(_changeattr
):
2683 newindex
= self
.counter
2685 return refattr(self
, newindex
)
2688 class refattr(_changeattr
):
2690 def __init__(self
, ref
, index
):
2695 return self
.ref
.attr(self
.index
)
2698 return self
.ref
.iterate()
2701 # helper routines for a using attrs
2704 """get attr out of a attr/changeattr"""
2705 if isinstance(attr
, _changeattr
):
2706 return attr
.getattr()
2710 def _getattrs(attrs
):
2711 """get attrs out of a sequence of attr/changeattr"""
2712 if attrs
is not None:
2714 for attr
in helper
.ensuresequence(attrs
):
2715 if isinstance(attr
, _changeattr
):
2716 result
.append(attr
.getattr())
2722 def _iterateattr(attr
):
2723 """perform next to a attr/changeattr"""
2724 if isinstance(attr
, _changeattr
):
2725 return attr
.iterate()
2729 def _iterateattrs(attrs
):
2730 """perform next to a sequence of attr/changeattr"""
2731 if attrs
is not None:
2733 for attr
in helper
.ensuresequence(attrs
):
2734 if isinstance(attr
, _changeattr
):
2735 result
.append(attr
.iterate())
2741 class changecolor(changeattr
):
2743 def __init__(self
, palette
):
2744 changeattr
.__init
__(self
)
2745 self
.palette
= palette
2747 def attr(self
, index
):
2748 if self
.counter
!= 1:
2749 return self
.palette
.getcolor(index
/float(self
.counter
-1))
2751 return self
.palette
.getcolor(0)
2754 class _changecolorgray(changecolor
):
2756 def __init__(self
, palette
=color
.palette
.Gray
):
2757 changecolor
.__init
__(self
, palette
)
2759 _changecolorgrey
= _changecolorgray
2762 class _changecolorreversegray(changecolor
):
2764 def __init__(self
, palette
=color
.palette
.ReverseGray
):
2765 changecolor
.__init
__(self
, palette
)
2767 _changecolorreversegrey
= _changecolorreversegray
2770 class _changecolorredblack(changecolor
):
2772 def __init__(self
, palette
=color
.palette
.RedBlack
):
2773 changecolor
.__init
__(self
, palette
)
2776 class _changecolorblackred(changecolor
):
2778 def __init__(self
, palette
=color
.palette
.BlackRed
):
2779 changecolor
.__init
__(self
, palette
)
2782 class _changecolorredwhite(changecolor
):
2784 def __init__(self
, palette
=color
.palette
.RedWhite
):
2785 changecolor
.__init
__(self
, palette
)
2788 class _changecolorwhitered(changecolor
):
2790 def __init__(self
, palette
=color
.palette
.WhiteRed
):
2791 changecolor
.__init
__(self
, palette
)
2794 class _changecolorgreenblack(changecolor
):
2796 def __init__(self
, palette
=color
.palette
.GreenBlack
):
2797 changecolor
.__init
__(self
, palette
)
2800 class _changecolorblackgreen(changecolor
):
2802 def __init__(self
, palette
=color
.palette
.BlackGreen
):
2803 changecolor
.__init
__(self
, palette
)
2806 class _changecolorgreenwhite(changecolor
):
2808 def __init__(self
, palette
=color
.palette
.GreenWhite
):
2809 changecolor
.__init
__(self
, palette
)
2812 class _changecolorwhitegreen(changecolor
):
2814 def __init__(self
, palette
=color
.palette
.WhiteGreen
):
2815 changecolor
.__init
__(self
, palette
)
2818 class _changecolorblueblack(changecolor
):
2820 def __init__(self
, palette
=color
.palette
.BlueBlack
):
2821 changecolor
.__init
__(self
, palette
)
2824 class _changecolorblackblue(changecolor
):
2826 def __init__(self
, palette
=color
.palette
.BlackBlue
):
2827 changecolor
.__init
__(self
, palette
)
2830 class _changecolorbluewhite(changecolor
):
2832 def __init__(self
, palette
=color
.palette
.BlueWhite
):
2833 changecolor
.__init
__(self
, palette
)
2836 class _changecolorwhiteblue(changecolor
):
2838 def __init__(self
, palette
=color
.palette
.WhiteBlue
):
2839 changecolor
.__init
__(self
, palette
)
2842 class _changecolorredgreen(changecolor
):
2844 def __init__(self
, palette
=color
.palette
.RedGreen
):
2845 changecolor
.__init
__(self
, palette
)
2848 class _changecolorredblue(changecolor
):
2850 def __init__(self
, palette
=color
.palette
.RedBlue
):
2851 changecolor
.__init
__(self
, palette
)
2854 class _changecolorgreenred(changecolor
):
2856 def __init__(self
, palette
=color
.palette
.GreenRed
):
2857 changecolor
.__init
__(self
, palette
)
2860 class _changecolorgreenblue(changecolor
):
2862 def __init__(self
, palette
=color
.palette
.GreenBlue
):
2863 changecolor
.__init
__(self
, palette
)
2866 class _changecolorbluered(changecolor
):
2868 def __init__(self
, palette
=color
.palette
.BlueRed
):
2869 changecolor
.__init
__(self
, palette
)
2872 class _changecolorbluegreen(changecolor
):
2874 def __init__(self
, palette
=color
.palette
.BlueGreen
):
2875 changecolor
.__init
__(self
, palette
)
2878 class _changecolorrainbow(changecolor
):
2880 def __init__(self
, palette
=color
.palette
.Rainbow
):
2881 changecolor
.__init
__(self
, palette
)
2884 class _changecolorreverserainbow(changecolor
):
2886 def __init__(self
, palette
=color
.palette
.ReverseRainbow
):
2887 changecolor
.__init
__(self
, palette
)
2890 class _changecolorhue(changecolor
):
2892 def __init__(self
, palette
=color
.palette
.Hue
):
2893 changecolor
.__init
__(self
, palette
)
2896 class _changecolorreversehue(changecolor
):
2898 def __init__(self
, palette
=color
.palette
.ReverseHue
):
2899 changecolor
.__init
__(self
, palette
)
2902 changecolor
.Gray
= _changecolorgray
2903 changecolor
.Grey
= _changecolorgrey
2904 changecolor
.Reversegray
= _changecolorreversegray
2905 changecolor
.Reversegrey
= _changecolorreversegrey
2906 changecolor
.RedBlack
= _changecolorredblack
2907 changecolor
.BlackRed
= _changecolorblackred
2908 changecolor
.RedWhite
= _changecolorredwhite
2909 changecolor
.WhiteRed
= _changecolorwhitered
2910 changecolor
.GreenBlack
= _changecolorgreenblack
2911 changecolor
.BlackGreen
= _changecolorblackgreen
2912 changecolor
.GreenWhite
= _changecolorgreenwhite
2913 changecolor
.WhiteGreen
= _changecolorwhitegreen
2914 changecolor
.BlueBlack
= _changecolorblueblack
2915 changecolor
.BlackBlue
= _changecolorblackblue
2916 changecolor
.BlueWhite
= _changecolorbluewhite
2917 changecolor
.WhiteBlue
= _changecolorwhiteblue
2918 changecolor
.RedGreen
= _changecolorredgreen
2919 changecolor
.RedBlue
= _changecolorredblue
2920 changecolor
.GreenRed
= _changecolorgreenred
2921 changecolor
.GreenBlue
= _changecolorgreenblue
2922 changecolor
.BlueRed
= _changecolorbluered
2923 changecolor
.BlueGreen
= _changecolorbluegreen
2924 changecolor
.Rainbow
= _changecolorrainbow
2925 changecolor
.ReverseRainbow
= _changecolorreverserainbow
2926 changecolor
.Hue
= _changecolorhue
2927 changecolor
.ReverseHue
= _changecolorreversehue
2930 class changesequence(changeattr
):
2931 """cycles through a sequence"""
2933 def __init__(self
, *sequence
):
2934 changeattr
.__init
__(self
)
2935 if not len(sequence
):
2936 sequence
= self
.defaultsequence
2937 self
.sequence
= sequence
2939 def attr(self
, index
):
2940 return self
.sequence
[index
% len(self
.sequence
)]
2943 class changelinestyle(changesequence
):
2944 defaultsequence
= (canvas
.linestyle
.solid
,
2945 canvas
.linestyle
.dashed
,
2946 canvas
.linestyle
.dotted
,
2947 canvas
.linestyle
.dashdotted
)
2950 class changestrokedfilled(changesequence
):
2951 defaultsequence
= (canvas
.stroked(), canvas
.filled())
2954 class changefilledstroked(changesequence
):
2955 defaultsequence
= (canvas
.filled(), canvas
.stroked())
2959 ################################################################################
2961 ################################################################################
2966 def cross(self
, x
, y
):
2967 return (path
._moveto
(x
-0.5*self
._size
, y
-0.5*self
._size
),
2968 path
._lineto
(x
+0.5*self
._size
, y
+0.5*self
._size
),
2969 path
._moveto
(x
-0.5*self
._size
, y
+0.5*self
._size
),
2970 path
._lineto
(x
+0.5*self
._size
, y
-0.5*self
._size
))
2972 def plus(self
, x
, y
):
2973 return (path
._moveto
(x
-0.707106781*self
._size
, y
),
2974 path
._lineto
(x
+0.707106781*self
._size
, y
),
2975 path
._moveto
(x
, y
-0.707106781*self
._size
),
2976 path
._lineto
(x
, y
+0.707106781*self
._size
))
2978 def square(self
, x
, y
):
2979 return (path
._moveto
(x
-0.5*self
._size
, y
-0.5 * self
._size
),
2980 path
._lineto
(x
+0.5*self
._size
, y
-0.5 * self
._size
),
2981 path
._lineto
(x
+0.5*self
._size
, y
+0.5 * self
._size
),
2982 path
._lineto
(x
-0.5*self
._size
, y
+0.5 * self
._size
),
2985 def triangle(self
, x
, y
):
2986 return (path
._moveto
(x
-0.759835685*self
._size
, y
-0.438691337*self
._size
),
2987 path
._lineto
(x
+0.759835685*self
._size
, y
-0.438691337*self
._size
),
2988 path
._lineto
(x
, y
+0.877382675*self
._size
),
2991 def circle(self
, x
, y
):
2992 return (path
._arc
(x
, y
, 0.564189583*self
._size
, 0, 360),
2995 def diamond(self
, x
, y
):
2996 return (path
._moveto
(x
-0.537284965*self
._size
, y
),
2997 path
._lineto
(x
, y
-0.930604859*self
._size
),
2998 path
._lineto
(x
+0.537284965*self
._size
, y
),
2999 path
._lineto
(x
, y
+0.930604859*self
._size
),
3002 def __init__(self
, symbol
=helper
.nodefault
,
3003 size
="0.2 cm", symbolattrs
=canvas
.stroked(),
3004 errorscale
=0.5, errorbarattrs
=(),
3006 self
.size_str
= size
3007 if symbol
is helper
.nodefault
:
3008 self
._symbol
= changesymbol
.cross()
3010 self
._symbol
= symbol
3011 self
._symbolattrs
= symbolattrs
3012 self
.errorscale
= errorscale
3013 self
._errorbarattrs
= errorbarattrs
3014 self
._lineattrs
= lineattrs
3016 def iteratedict(self
):
3018 result
["symbol"] = _iterateattr(self
._symbol
)
3019 result
["size"] = _iterateattr(self
.size_str
)
3020 result
["symbolattrs"] = _iterateattrs(self
._symbolattrs
)
3021 result
["errorscale"] = _iterateattr(self
.errorscale
)
3022 result
["errorbarattrs"] = _iterateattrs(self
._errorbarattrs
)
3023 result
["lineattrs"] = _iterateattrs(self
._lineattrs
)
3027 return symbol(**self
.iteratedict())
3029 def othercolumnkey(self
, key
, index
):
3030 raise ValueError("unsuitable key '%s'" % key
)
3032 def setcolumns(self
, graph
, columns
):
3033 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
3035 match
= pattern
.match(key
)
3037 if isindex
is not None: raise ValueError("multiple key specification")
3038 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
3040 iskey
= match
.groups()[0]
3042 return key
, iskey
, isindex
3044 self
.xi
= self
.xmini
= self
.xmaxi
= None
3045 self
.dxi
= self
.dxmini
= self
.dxmaxi
= None
3046 self
.yi
= self
.ymini
= self
.ymaxi
= None
3047 self
.dyi
= self
.dymini
= self
.dymaxi
= None
3048 self
.xkey
= self
.ykey
= None
3049 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
3050 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
3051 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
3052 XMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
3053 YMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
3054 XMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
3055 YMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
3056 DXPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
3057 DYPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
3058 DXMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
3059 DYMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
3060 DXMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
3061 DYMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
3062 for key
, index
in columns
.items():
3063 key
, self
.xkey
, self
.xi
= checkpattern(key
, index
, XPattern
, self
.xkey
, self
.xi
)
3064 key
, self
.ykey
, self
.yi
= checkpattern(key
, index
, YPattern
, self
.ykey
, self
.yi
)
3065 key
, self
.xkey
, self
.xmini
= checkpattern(key
, index
, XMinPattern
, self
.xkey
, self
.xmini
)
3066 key
, self
.ykey
, self
.ymini
= checkpattern(key
, index
, YMinPattern
, self
.ykey
, self
.ymini
)
3067 key
, self
.xkey
, self
.xmaxi
= checkpattern(key
, index
, XMaxPattern
, self
.xkey
, self
.xmaxi
)
3068 key
, self
.ykey
, self
.ymaxi
= checkpattern(key
, index
, YMaxPattern
, self
.ykey
, self
.ymaxi
)
3069 key
, self
.xkey
, self
.dxi
= checkpattern(key
, index
, DXPattern
, self
.xkey
, self
.dxi
)
3070 key
, self
.ykey
, self
.dyi
= checkpattern(key
, index
, DYPattern
, self
.ykey
, self
.dyi
)
3071 key
, self
.xkey
, self
.dxmini
= checkpattern(key
, index
, DXMinPattern
, self
.xkey
, self
.dxmini
)
3072 key
, self
.ykey
, self
.dymini
= checkpattern(key
, index
, DYMinPattern
, self
.ykey
, self
.dymini
)
3073 key
, self
.xkey
, self
.dxmaxi
= checkpattern(key
, index
, DXMaxPattern
, self
.xkey
, self
.dxmaxi
)
3074 key
, self
.ykey
, self
.dymaxi
= checkpattern(key
, index
, DYMaxPattern
, self
.ykey
, self
.dymaxi
)
3076 self
.othercolumnkey(key
, index
)
3077 if None in (self
.xkey
, self
.ykey
): raise ValueError("incomplete axis specification")
3078 if (len(filter(None, (self
.xmini
, self
.dxmini
, self
.dxi
))) > 1 or
3079 len(filter(None, (self
.ymini
, self
.dymini
, self
.dyi
))) > 1 or
3080 len(filter(None, (self
.xmaxi
, self
.dxmaxi
, self
.dxi
))) > 1 or
3081 len(filter(None, (self
.ymaxi
, self
.dymaxi
, self
.dyi
))) > 1):
3082 raise ValueError("multiple errorbar definition")
3083 if ((self
.xi
is None and self
.dxi
is not None) or
3084 (self
.yi
is None and self
.dyi
is not None) or
3085 (self
.xi
is None and self
.dxmini
is not None) or
3086 (self
.yi
is None and self
.dymini
is not None) or
3087 (self
.xi
is None and self
.dxmaxi
is not None) or
3088 (self
.yi
is None and self
.dymaxi
is not None)):
3089 raise ValueError("errorbar definition start value missing")
3090 self
.xaxis
= graph
.axes
[self
.xkey
]
3091 self
.yaxis
= graph
.axes
[self
.ykey
]
3093 def minmidmax(self
, point
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
3094 min = max = mid
= None
3096 mid
= point
[i
] + 0.0
3097 except (TypeError, ValueError):
3100 if di
is not None: min = point
[i
] - point
[di
]
3101 elif dmini
is not None: min = point
[i
] - point
[dmini
]
3102 elif mini
is not None: min = point
[mini
] + 0.0
3103 except (TypeError, ValueError):
3106 if di
is not None: max = point
[i
] + point
[di
]
3107 elif dmaxi
is not None: max = point
[i
] + point
[dmaxi
]
3108 elif maxi
is not None: max = point
[maxi
] + 0.0
3109 except (TypeError, ValueError):
3112 if min is not None and min > mid
: raise ValueError("minimum error in errorbar")
3113 if max is not None and max < mid
: raise ValueError("maximum error in errorbar")
3115 if min is not None and max is not None and min > max: raise ValueError("minimum/maximum error in errorbar")
3116 return min, mid
, max
3118 def keyrange(self
, points
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
3119 allmin
= allmax
= None
3120 if filter(None, (mini
, maxi
, di
, dmini
, dmaxi
)) is not None:
3121 for point
in points
:
3122 min, mid
, max = self
.minmidmax(point
, i
, mini
, maxi
, di
, dmini
, dmaxi
)
3123 if min is not None and (allmin
is None or min < allmin
): allmin
= min
3124 if mid
is not None and (allmin
is None or mid
< allmin
): allmin
= mid
3125 if mid
is not None and (allmax
is None or mid
> allmax
): allmax
= mid
3126 if max is not None and (allmax
is None or max > allmax
): allmax
= max
3128 for point
in points
:
3130 value
= point
[i
] + 0.0
3131 if allmin
is None or point
[i
] < allmin
: allmin
= point
[i
]
3132 if allmax
is None or point
[i
] > allmax
: allmax
= point
[i
]
3133 except (TypeError, ValueError):
3135 return allmin
, allmax
3137 def getranges(self
, points
):
3138 xmin
, xmax
= self
.keyrange(points
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
3139 ymin
, ymax
= self
.keyrange(points
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
3140 return {self
.xkey
: (xmin
, xmax
), self
.ykey
: (ymin
, ymax
)}
3142 def _drawerrorbar(self
, graph
, topleft
, top
, topright
,
3143 left
, center
, right
,
3144 bottomleft
, bottom
, bottomright
, point
=None):
3145 if left
is not None:
3146 if right
is not None:
3147 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
3148 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
3149 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
3150 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
3151 graph
.stroke(path
.path(path
._moveto
(*left1
),
3152 graph
._connect
(*(left1
+left2
)),
3153 path
._moveto
(*left
),
3154 graph
._connect
(*(left
+right
)),
3155 path
._moveto
(*right1
),
3156 graph
._connect
(*(right1
+right2
))),
3157 *self
.errorbarattrs
)
3158 elif center
is not None:
3159 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
3160 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
3161 graph
.stroke(path
.path(path
._moveto
(*left1
),
3162 graph
._connect
(*(left1
+left2
)),
3163 path
._moveto
(*left
),
3164 graph
._connect
(*(left
+center
))),
3165 *self
.errorbarattrs
)
3167 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
3168 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
3169 left3
= graph
._addpos
(*(left
+(self
._errorsize
, 0)))
3170 graph
.stroke(path
.path(path
._moveto
(*left1
),
3171 graph
._connect
(*(left1
+left2
)),
3172 path
._moveto
(*left
),
3173 graph
._connect
(*(left
+left3
))),
3174 *self
.errorbarattrs
)
3175 if right
is not None and left
is None:
3176 if center
is not None:
3177 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
3178 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
3179 graph
.stroke(path
.path(path
._moveto
(*right1
),
3180 graph
._connect
(*(right1
+right2
)),
3181 path
._moveto
(*right
),
3182 graph
._connect
(*(right
+center
))),
3183 *self
.errorbarattrs
)
3185 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
3186 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
3187 right3
= graph
._addpos
(*(right
+(-self
._errorsize
, 0)))
3188 graph
.stroke(path
.path(path
._moveto
(*right1
),
3189 graph
._connect
(*(right1
+right2
)),
3190 path
._moveto
(*right
),
3191 graph
._connect
(*(right
+right3
))),
3192 *self
.errorbarattrs
)
3194 if bottom
is not None:
3196 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
3197 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
3198 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
3199 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
3200 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
3201 graph
._connect
(*(bottom1
+bottom2
)),
3202 path
._moveto
(*bottom
),
3203 graph
._connect
(*(bottom
+top
)),
3204 path
._moveto
(*top1
),
3205 graph
._connect
(*(top1
+top2
))),
3206 *self
.errorbarattrs
)
3207 elif center
is not None:
3208 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
3209 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
3210 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
3211 graph
._connect
(*(bottom1
+bottom2
)),
3212 path
._moveto
(*bottom
),
3213 graph
._connect
(*(bottom
+center
))),
3214 *self
.errorbarattrs
)
3216 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
3217 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
3218 bottom3
= graph
._addpos
(*(bottom
+(0, self
._errorsize
)))
3219 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
3220 graph
._connect
(*(bottom1
+bottom2
)),
3221 path
._moveto
(*bottom
),
3222 graph
._connect
(*(bottom
+bottom3
))),
3223 *self
.errorbarattrs
)
3224 if top
is not None and bottom
is None:
3225 if center
is not None:
3226 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
3227 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
3228 graph
.stroke(path
.path(path
._moveto
(*top1
),
3229 graph
._connect
(*(top1
+top2
)),
3231 graph
._connect
(*(top
+center
))),
3232 *self
.errorbarattrs
)
3234 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
3235 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
3236 top3
= graph
._addpos
(*(top
+(0, -self
._errorsize
)))
3237 graph
.stroke(path
.path(path
._moveto
(*top1
),
3238 graph
._connect
(*(top1
+top2
)),
3240 graph
._connect
(*(top
+top3
))),
3241 *self
.errorbarattrs
)
3242 if bottomleft
is not None:
3243 if topleft
is not None and bottomright
is None:
3244 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
._errorsize
, 0)))
3245 topleft1
= graph
._addpos
(*(topleft
+(self
._errorsize
, 0)))
3246 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
3247 graph
._connect
(*(bottomleft1
+bottomleft
)),
3248 graph
._connect
(*(bottomleft
+topleft
)),
3249 graph
._connect
(*(topleft
+topleft1
))),
3250 *self
.errorbarattrs
)
3251 elif bottomright
is not None and topleft
is None:
3252 bottomleft1
= graph
._addpos
(*(bottomleft
+(0, self
._errorsize
)))
3253 bottomright1
= graph
._addpos
(*(bottomright
+(0, self
._errorsize
)))
3254 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
3255 graph
._connect
(*(bottomleft1
+bottomleft
)),
3256 graph
._connect
(*(bottomleft
+bottomright
)),
3257 graph
._connect
(*(bottomright
+bottomright1
))),
3258 *self
.errorbarattrs
)
3259 elif bottomright
is None and topleft
is None:
3260 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
._errorsize
, 0)))
3261 bottomleft2
= graph
._addpos
(*(bottomleft
+(0, self
._errorsize
)))
3262 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
3263 graph
._connect
(*(bottomleft1
+bottomleft
)),
3264 graph
._connect
(*(bottomleft
+bottomleft2
))),
3265 *self
.errorbarattrs
)
3266 if topright
is not None:
3267 if bottomright
is not None and topleft
is None:
3268 topright1
= graph
._addpos
(*(topright
+(-self
._errorsize
, 0)))
3269 bottomright1
= graph
._addpos
(*(bottomright
+(-self
._errorsize
, 0)))
3270 graph
.stroke(path
.path(path
._moveto
(*topright1
),
3271 graph
._connect
(*(topright1
+topright
)),
3272 graph
._connect
(*(topright
+bottomright
)),
3273 graph
._connect
(*(bottomright
+bottomright1
))),
3274 *self
.errorbarattrs
)
3275 elif topleft
is not None and bottomright
is None:
3276 topright1
= graph
._addpos
(*(topright
+(0, -self
._errorsize
)))
3277 topleft1
= graph
._addpos
(*(topleft
+(0, -self
._errorsize
)))
3278 graph
.stroke(path
.path(path
._moveto
(*topright1
),
3279 graph
._connect
(*(topright1
+topright
)),
3280 graph
._connect
(*(topright
+topleft
)),
3281 graph
._connect
(*(topleft
+topleft1
))),
3282 *self
.errorbarattrs
)
3283 elif topleft
is None and bottomright
is None:
3284 topright1
= graph
._addpos
(*(topright
+(-self
._errorsize
, 0)))
3285 topright2
= graph
._addpos
(*(topright
+(0, -self
._errorsize
)))
3286 graph
.stroke(path
.path(path
._moveto
(*topright1
),
3287 graph
._connect
(*(topright1
+topright
)),
3288 graph
._connect
(*(topright
+topright2
))),
3289 *self
.errorbarattrs
)
3290 if bottomright
is not None and bottomleft
is None and topright
is None:
3291 bottomright1
= graph
._addpos
(*(bottomright
+(-self
._errorsize
, 0)))
3292 bottomright2
= graph
._addpos
(*(bottomright
+(0, self
._errorsize
)))
3293 graph
.stroke(path
.path(path
._moveto
(*bottomright1
),
3294 graph
._connect
(*(bottomright1
+bottomright
)),
3295 graph
._connect
(*(bottomright
+bottomright2
))),
3296 *self
.errorbarattrs
)
3297 if topleft
is not None and bottomleft
is None and topright
is None:
3298 topleft1
= graph
._addpos
(*(topleft
+(self
._errorsize
, 0)))
3299 topleft2
= graph
._addpos
(*(topleft
+(0, -self
._errorsize
)))
3300 graph
.stroke(path
.path(path
._moveto
(*topleft1
),
3301 graph
._connect
(*(topleft1
+topleft
)),
3302 graph
._connect
(*(topleft
+topleft2
))),
3303 *self
.errorbarattrs
)
3304 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
3305 graph
.stroke(path
.path(path
._moveto
(*bottomleft
),
3306 graph
._connect
(*(bottomleft
+bottomright
)),
3307 graph
._connect
(*(bottomright
+topright
)),
3308 graph
._connect
(*(topright
+topleft
)),
3310 *self
.errorbarattrs
)
3312 def _drawsymbol(self
, canvas
, x
, y
, point
=None):
3313 canvas
.draw(path
.path(*self
.symbol(self
, x
, y
)), *self
.symbolattrs
)
3315 def drawsymbol(self
, canvas
, x
, y
, point
=None):
3316 self
._drawsymbol
(canvas
, unit
.topt(x
), unit
.topt(y
), point
)
3318 def key(self
, c
, x
, y
, width
, height
):
3319 if self
._symbolattrs
is not None:
3320 self
._drawsymbol
(c
, x
+ 0.5 * width
, y
+ 0.5 * height
)
3321 if self
._lineattrs
is not None:
3322 c
.stroke(path
._line
(x
, y
+ 0.5 * height
, x
+ width
, y
+ 0.5 * height
), *self
.lineattrs
)
3324 def drawpoints(self
, graph
, points
):
3325 xaxismin
, xaxismax
= self
.xaxis
.getdatarange()
3326 yaxismin
, yaxismax
= self
.yaxis
.getdatarange()
3327 self
.size
= unit
.length(_getattr(self
.size_str
), default_type
="v")
3328 self
._size
= unit
.topt(self
.size
)
3329 self
.symbol
= _getattr(self
._symbol
)
3330 self
.symbolattrs
= _getattrs(helper
.ensuresequence(self
._symbolattrs
))
3331 self
.errorbarattrs
= _getattrs(helper
.ensuresequence(self
._errorbarattrs
))
3332 self
._errorsize
= self
.errorscale
* self
._size
3333 self
.errorsize
= self
.errorscale
* self
.size
3334 self
.lineattrs
= _getattrs(helper
.ensuresequence(self
._lineattrs
))
3335 if self
._lineattrs
is not None:
3336 clipcanvas
= graph
.clipcanvas()
3338 haserror
= filter(None, (self
.xmini
, self
.ymini
, self
.xmaxi
, self
.ymaxi
,
3339 self
.dxi
, self
.dyi
, self
.dxmini
, self
.dymini
, self
.dxmaxi
, self
.dymaxi
)) is not None
3341 for point
in points
:
3343 xmin
, x
, xmax
= self
.minmidmax(point
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
3344 ymin
, y
, ymax
= self
.minmidmax(point
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
3345 if x
is not None and x
< xaxismin
: drawsymbol
= 0
3346 elif x
is not None and x
> xaxismax
: drawsymbol
= 0
3347 elif y
is not None and y
< yaxismin
: drawsymbol
= 0
3348 elif y
is not None and y
> yaxismax
: drawsymbol
= 0
3350 if xmin
is not None and xmin
< xaxismin
: drawsymbol
= 0
3351 elif xmax
is not None and xmax
< xaxismin
: drawsymbol
= 0
3352 elif xmax
is not None and xmax
> xaxismax
: drawsymbol
= 0
3353 elif xmin
is not None and xmin
> xaxismax
: drawsymbol
= 0
3354 elif ymin
is not None and ymin
< yaxismin
: drawsymbol
= 0
3355 elif ymax
is not None and ymax
< yaxismin
: drawsymbol
= 0
3356 elif ymax
is not None and ymax
> yaxismax
: drawsymbol
= 0
3357 elif ymin
is not None and ymin
> yaxismax
: drawsymbol
= 0
3358 xpos
=ypos
=topleft
=top
=topright
=left
=center
=right
=bottomleft
=bottom
=bottomright
=None
3359 if x
is not None and y
is not None:
3361 center
= xpos
, ypos
= graph
._pos
(x
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3362 except (ValueError, OverflowError): # XXX: exceptions???
3366 if xmin
is not None: left
= graph
._pos
(xmin
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3367 if xmax
is not None: right
= graph
._pos
(xmax
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3369 if ymax
is not None: top
= graph
._pos
(x
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3370 if ymin
is not None: bottom
= graph
._pos
(x
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3371 if x
is None or y
is None:
3372 if ymax
is not None:
3373 if xmin
is not None: topleft
= graph
._pos
(xmin
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3374 if xmax
is not None: topright
= graph
._pos
(xmax
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3375 if ymin
is not None:
3376 if xmin
is not None: bottomleft
= graph
._pos
(xmin
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3377 if xmax
is not None: bottomright
= graph
._pos
(xmax
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3379 if self
._errorbarattrs
is not None and haserror
:
3380 self
._drawerrorbar
(graph
, topleft
, top
, topright
,
3381 left
, center
, right
,
3382 bottomleft
, bottom
, bottomright
, point
)
3383 if self
._symbolattrs
is not None and xpos
is not None and ypos
is not None:
3384 self
._drawsymbol
(graph
, xpos
, ypos
, point
)
3385 if xpos
is not None and ypos
is not None:
3387 lineels
.append(path
._moveto
(xpos
, ypos
))
3390 lineels
.append(path
._lineto
(xpos
, ypos
))
3393 self
.path
= path
.path(*lineels
)
3394 if self
._lineattrs
is not None:
3395 clipcanvas
.stroke(self
.path
, *self
.lineattrs
)
3398 class changesymbol(changesequence
): pass
3401 class _changesymbolcross(changesymbol
):
3402 defaultsequence
= (symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
)
3405 class _changesymbolplus(changesymbol
):
3406 defaultsequence
= (symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
)
3409 class _changesymbolsquare(changesymbol
):
3410 defaultsequence
= (symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
)
3413 class _changesymboltriangle(changesymbol
):
3414 defaultsequence
= (symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
)
3417 class _changesymbolcircle(changesymbol
):
3418 defaultsequence
= (symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
)
3421 class _changesymboldiamond(changesymbol
):
3422 defaultsequence
= (symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
)
3425 class _changesymbolsquaretwice(changesymbol
):
3426 defaultsequence
= (symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
,
3427 symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
)
3430 class _changesymboltriangletwice(changesymbol
):
3431 defaultsequence
= (symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
,
3432 symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
)
3435 class _changesymbolcircletwice(changesymbol
):
3436 defaultsequence
= (symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
,
3437 symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
)
3440 class _changesymboldiamondtwice(changesymbol
):
3441 defaultsequence
= (symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
,
3442 symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
)
3445 changesymbol
.cross
= _changesymbolcross
3446 changesymbol
.plus
= _changesymbolplus
3447 changesymbol
.square
= _changesymbolsquare
3448 changesymbol
.triangle
= _changesymboltriangle
3449 changesymbol
.circle
= _changesymbolcircle
3450 changesymbol
.diamond
= _changesymboldiamond
3451 changesymbol
.squaretwice
= _changesymbolsquaretwice
3452 changesymbol
.triangletwice
= _changesymboltriangletwice
3453 changesymbol
.circletwice
= _changesymbolcircletwice
3454 changesymbol
.diamondtwice
= _changesymboldiamondtwice
3459 def __init__(self
, lineattrs
=helper
.nodefault
):
3460 if lineattrs
is helper
.nodefault
:
3461 lineattrs
= (changelinestyle(), canvas
.linejoin
.round)
3462 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=None, lineattrs
=lineattrs
)
3467 def __init__(self
, palette
=color
.palette
.Gray
):
3468 self
.palette
= palette
3469 self
.colorindex
= None
3470 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=(), lineattrs
=None)
3473 raise RuntimeError("style is not iterateable")
3475 def othercolumnkey(self
, key
, index
):
3477 self
.colorindex
= index
3479 symbol
.othercolumnkey(self
, key
, index
)
3481 def _drawerrorbar(self
, graph
, topleft
, top
, topright
,
3482 left
, center
, right
,
3483 bottomleft
, bottom
, bottomright
, point
=None):
3484 color
= point
[self
.colorindex
]
3485 if color
is not None:
3486 if color
!= self
.lastcolor
:
3487 self
.rectclipcanvas
.set(self
.palette
.getcolor(color
))
3488 if bottom
is not None and left
is not None:
3489 bottomleft
= left
[0], bottom
[1]
3490 if bottom
is not None and right
is not None:
3491 bottomright
= right
[0], bottom
[1]
3492 if top
is not None and right
is not None:
3493 topright
= right
[0], top
[1]
3494 if top
is not None and left
is not None:
3495 topleft
= left
[0], top
[1]
3496 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
3497 self
.rectclipcanvas
.fill(path
.path(path
._moveto
(*bottomleft
),
3498 graph
._connect
(*(bottomleft
+bottomright
)),
3499 graph
._connect
(*(bottomright
+topright
)),
3500 graph
._connect
(*(topright
+topleft
)),
3503 def drawpoints(self
, graph
, points
):
3504 if self
.colorindex
is None:
3505 raise RuntimeError("column 'color' not set")
3506 self
.lastcolor
= None
3507 self
.rectclipcanvas
= graph
.clipcanvas()
3508 symbol
.drawpoints(self
, graph
, points
)
3510 def key(self
, c
, x
, y
, width
, height
):
3511 raise RuntimeError("style doesn't yet provide a key")
3516 def __init__(self
, textdx
="0", textdy
="0.3 cm", textattrs
=textmodule
.halign
.center
, **args
):
3517 self
.textindex
= None
3518 self
.textdx_str
= textdx
3519 self
.textdy_str
= textdy
3520 self
._textattrs
= textattrs
3521 symbol
.__init
__(self
, **args
)
3523 def iteratedict(self
):
3524 result
= symbol
.iteratedict()
3525 result
["textattrs"] = _iterateattr(self
._textattrs
)
3529 return textsymbol(**self
.iteratedict())
3531 def othercolumnkey(self
, key
, index
):
3533 self
.textindex
= index
3535 symbol
.othercolumnkey(self
, key
, index
)
3537 def _drawsymbol(self
, graph
, x
, y
, point
=None):
3538 symbol
._drawsymbol
(self
, graph
, x
, y
, point
)
3539 if None not in (x
, y
, point
[self
.textindex
], self
._textattrs
):
3540 graph
._text
(x
+ self
._textdx
, y
+ self
._textdy
, str(point
[self
.textindex
]), *helper
.ensuresequence(self
.textattrs
))
3542 def drawpoints(self
, graph
, points
):
3543 self
.textdx
= unit
.length(_getattr(self
.textdx_str
), default_type
="v")
3544 self
.textdy
= unit
.length(_getattr(self
.textdy_str
), default_type
="v")
3545 self
._textdx
= unit
.topt(self
.textdx
)
3546 self
._textdy
= unit
.topt(self
.textdy
)
3547 if self
._textattrs
is not None:
3548 self
.textattrs
= _getattr(self
._textattrs
)
3549 if self
.textindex
is None:
3550 raise RuntimeError("column 'text' not set")
3551 symbol
.drawpoints(self
, graph
, points
)
3553 def key(self
, c
, x
, y
, width
, height
):
3554 raise RuntimeError("style doesn't yet provide a key")
3557 class arrow(symbol
):
3559 def __init__(self
, linelength
="0.2 cm", arrowattrs
=(), arrowsize
="0.1 cm", arrowdict
={}, epsilon
=1e-10):
3560 self
.linelength_str
= linelength
3561 self
.arrowsize_str
= arrowsize
3562 self
.arrowattrs
= arrowattrs
3563 self
.arrowdict
= arrowdict
3564 self
.epsilon
= epsilon
3565 self
.sizeindex
= self
.angleindex
= None
3566 symbol
.__init
__(self
, symbolattrs
=(), errorbarattrs
=None, lineattrs
=None)
3569 raise RuntimeError("style is not iterateable")
3571 def othercolumnkey(self
, key
, index
):
3573 self
.sizeindex
= index
3574 elif key
== "angle":
3575 self
.angleindex
= index
3577 symbol
.othercolumnkey(self
, key
, index
)
3579 def _drawsymbol(self
, graph
, x
, y
, point
=None):
3580 if None not in (x
, y
, point
[self
.angleindex
], point
[self
.sizeindex
], self
.arrowattrs
, self
.arrowdict
):
3581 if point
[self
.sizeindex
] > self
.epsilon
:
3582 dx
, dy
= math
.cos(point
[self
.angleindex
]*math
.pi
/180.0), math
.sin(point
[self
.angleindex
]*math
.pi
/180)
3583 x1
= unit
.t_pt(x
)-0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
3584 y1
= unit
.t_pt(y
)-0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
3585 x2
= unit
.t_pt(x
)+0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
3586 y2
= unit
.t_pt(y
)+0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
3587 graph
.stroke(path
.line(x1
, y1
, x2
, y2
),
3588 canvas
.earrow(self
.arrowsize
*point
[self
.sizeindex
],
3590 *helper
.ensuresequence(self
.arrowattrs
))
3592 def drawpoints(self
, graph
, points
):
3593 self
.arrowsize
= unit
.length(_getattr(self
.arrowsize_str
), default_type
="v")
3594 self
.linelength
= unit
.length(_getattr(self
.linelength_str
), default_type
="v")
3595 self
._arrowsize
= unit
.topt(self
.arrowsize
)
3596 self
._linelength
= unit
.topt(self
.linelength
)
3597 if self
.sizeindex
is None:
3598 raise RuntimeError("column 'size' not set")
3599 if self
.angleindex
is None:
3600 raise RuntimeError("column 'angle' not set")
3601 symbol
.drawpoints(self
, graph
, points
)
3603 def key(self
, c
, x
, y
, width
, height
):
3604 raise RuntimeError("style doesn't yet provide a key")
3607 class _bariterator(changeattr
):
3609 def attr(self
, index
):
3610 return index
, self
.counter
3615 def __init__(self
, fromzero
=1, stacked
=0, skipmissing
=1, xbar
=0,
3616 barattrs
=(canvas
.stroked(color
.gray
.black
), changecolor
.Rainbow()),
3617 _bariterator
=_bariterator(), _previousbar
=None):
3618 self
.fromzero
= fromzero
3619 self
.stacked
= stacked
3620 self
.skipmissing
= skipmissing
3622 self
._barattrs
= barattrs
3623 self
.bariterator
= _bariterator
3624 self
.previousbar
= _previousbar
3626 def iteratedict(self
):
3628 result
["barattrs"] = _iterateattrs(self
._barattrs
)
3632 return bar(fromzero
=self
.fromzero
, stacked
=self
.stacked
, xbar
=self
.xbar
,
3633 _bariterator
=_iterateattr(self
.bariterator
), _previousbar
=self
, **self
.iteratedict())
3635 def setcolumns(self
, graph
, columns
):
3636 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
3638 match
= pattern
.match(key
)
3640 if isindex
is not None: raise ValueError("multiple key specification")
3641 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
3643 iskey
= match
.groups()[0]
3645 return key
, iskey
, isindex
3648 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
3649 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
3650 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
3652 for key
, index
in columns
.items():
3653 key
, xkey
, xi
= checkpattern(key
, index
, XPattern
, xkey
, xi
)
3654 key
, ykey
, yi
= checkpattern(key
, index
, YPattern
, ykey
, yi
)
3656 self
.othercolumnkey(key
, index
)
3657 if None in (xkey
, ykey
): raise ValueError("incomplete axis specification")
3659 self
.nkey
, self
.ni
= ykey
, yi
3660 self
.vkey
, self
.vi
= xkey
, xi
3662 self
.nkey
, self
.ni
= xkey
, xi
3663 self
.vkey
, self
.vi
= ykey
, yi
3664 self
.naxis
, self
.vaxis
= graph
.axes
[self
.nkey
], graph
.axes
[self
.vkey
]
3666 def getranges(self
, points
):
3667 index
, count
= _getattr(self
.bariterator
)
3668 if count
!= 1 and self
.stacked
!= 1:
3669 if self
.stacked
> 1:
3670 index
= divmod(index
, self
.stacked
)[0] # TODO: use this
3673 for point
in points
:
3674 if not self
.skipmissing
:
3676 self
.naxis
.setname(point
[self
.ni
])
3678 self
.naxis
.setname(point
[self
.ni
], index
)
3680 v
= point
[self
.vi
] + 0.0
3681 if vmin
is None or v
< vmin
: vmin
= v
3682 if vmax
is None or v
> vmax
: vmax
= v
3683 except (TypeError, ValueError):
3686 if self
.skipmissing
:
3688 self
.naxis
.setname(point
[self
.ni
])
3690 self
.naxis
.setname(point
[self
.ni
], index
)
3692 if vmin
> 0: vmin
= 0
3693 if vmax
< 0: vmax
= 0
3694 return {self
.vkey
: (vmin
, vmax
)}
3696 def drawpoints(self
, graph
, points
):
3697 index
, count
= _getattr(self
.bariterator
)
3698 dostacked
= (self
.stacked
!= 0 and
3699 (self
.stacked
== 1 or divmod(index
, self
.stacked
)[1]) and
3700 (self
.stacked
!= 1 or index
))
3701 if self
.stacked
> 1:
3702 index
= divmod(index
, self
.stacked
)[0]
3703 vmin
, vmax
= self
.vaxis
.getdatarange()
3704 self
.barattrs
= _getattrs(helper
.ensuresequence(self
._barattrs
))
3706 self
.stackedvalue
= {}
3707 for point
in points
:
3712 self
.stackedvalue
[n
] = v
3713 if count
!= 1 and self
.stacked
!= 1:
3714 minid
= (n
, index
, 0)
3715 maxid
= (n
, index
, 1)
3720 x1pos
, y1pos
= graph
._pos
(v
, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
3721 x2pos
, y2pos
= graph
._pos
(v
, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
3723 x1pos
, y1pos
= graph
._pos
(minid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
3724 x2pos
, y2pos
= graph
._pos
(maxid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
3727 x3pos
, y3pos
= graph
._pos
(self
.previousbar
.stackedvalue
[n
], maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
3728 x4pos
, y4pos
= graph
._pos
(self
.previousbar
.stackedvalue
[n
], minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
3730 x3pos
, y3pos
= graph
._pos
(maxid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
3731 x4pos
, y4pos
= graph
._pos
(minid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
3735 x3pos
, y3pos
= graph
._pos
(0, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
3736 x4pos
, y4pos
= graph
._pos
(0, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
3738 x3pos
, y3pos
= graph
._pos
(maxid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
3739 x4pos
, y4pos
= graph
._pos
(minid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
3741 x3pos
, y3pos
= self
.naxis
._vtickpoint
(self
.naxis
, self
.naxis
.convert(maxid
))
3742 x4pos
, y4pos
= self
.naxis
._vtickpoint
(self
.naxis
, self
.naxis
.convert(minid
))
3743 graph
.fill(path
.path(path
._moveto
(x1pos
, y1pos
),
3744 graph
._connect
(x1pos
, y1pos
, x2pos
, y2pos
),
3745 graph
._connect
(x2pos
, y2pos
, x3pos
, y3pos
),
3746 graph
._connect
(x3pos
, y3pos
, x4pos
, y4pos
),
3747 graph
._connect
(x4pos
, y4pos
, x1pos
, y1pos
), # no closepath (might not be straight)
3748 path
.closepath()), *self
.barattrs
)
3749 except (TypeError, ValueError): pass
3751 def key(self
, c
, x
, y
, width
, height
):
3752 c
.fill(path
._rect
(x
, y
, width
, height
), *self
.barattrs
)
3757 # def setcolumns(self, graph, columns):
3758 # self.columns = columns
3760 # def getranges(self, points):
3761 # return {"x": (0, 10), "y": (0, 10), "z": (0, 1)}
3763 # def drawpoints(self, graph, points):
3768 ################################################################################
3770 ################################################################################
3775 defaultstyle
= symbol
3777 def __init__(self
, file, title
=helper
.nodefault
, context
={}, **columns
):
3779 if helper
.isstring(file):
3780 self
.data
= datamodule
.datafile(file)
3783 if title
is helper
.nodefault
:
3784 self
.title
= "(unknown)"
3788 for key
, column
in columns
.items():
3790 self
.columns
[key
] = self
.data
.getcolumnno(column
)
3791 except datamodule
.ColumnError
:
3792 self
.columns
[key
] = len(self
.data
.titles
)
3793 self
.data
.addcolumn(column
, context
=context
)
3795 def setstyle(self
, graph
, style
):
3797 self
.style
.setcolumns(graph
, self
.columns
)
3799 def getranges(self
):
3800 return self
.style
.getranges(self
.data
.data
)
3802 def setranges(self
, ranges
):
3805 def draw(self
, graph
):
3806 self
.style
.drawpoints(graph
, self
.data
.data
)
3813 def __init__(self
, expression
, title
=helper
.nodefault
, min=None, max=None, points
=100, parser
=mathtree
.parser(), context
={}):
3814 if title
is helper
.nodefault
:
3815 self
.title
= expression
3820 self
.points
= points
3821 self
.context
= context
3822 self
.result
, expression
= expression
.split("=")
3823 self
.mathtree
= parser
.parse(expression
)
3824 self
.variable
= None
3827 def setstyle(self
, graph
, style
):
3828 for variable
in self
.mathtree
.VarList():
3829 if variable
in graph
.axes
.keys():
3830 if self
.variable
is None:
3831 self
.variable
= variable
3833 raise ValueError("multiple variables found")
3834 if self
.variable
is None:
3835 raise ValueError("no variable found")
3836 self
.xaxis
= graph
.axes
[self
.variable
]
3838 self
.style
.setcolumns(graph
, {self
.variable
: 0, self
.result
: 1})
3840 def getranges(self
):
3842 return self
.style
.getranges(self
.data
)
3843 if None not in (self
.min, self
.max):
3844 return {self
.variable
: (self
.min, self
.max)}
3846 def setranges(self
, ranges
):
3847 if ranges
.has_key(self
.variable
):
3848 min, max = ranges
[self
.variable
]
3849 if self
.min is not None: min = self
.min
3850 if self
.max is not None: max = self
.max
3851 vmin
= self
.xaxis
.convert(min)
3852 vmax
= self
.xaxis
.convert(max)
3854 for i
in range(self
.points
):
3855 self
.context
[self
.variable
] = x
= self
.xaxis
.invert(vmin
+ (vmax
-vmin
)*i
/ (self
.points
-1.0))
3857 y
= self
.mathtree
.Calc(**self
.context
)
3858 except (ArithmeticError, ValueError):
3860 self
.data
.append((x
, y
))
3863 def draw(self
, graph
):
3864 self
.style
.drawpoints(graph
, self
.data
)
3867 class paramfunction
:
3871 def __init__(self
, varname
, min, max, expression
, title
=helper
.nodefault
, points
=100, parser
=mathtree
.parser(), context
={}):
3872 if title
is helper
.nodefault
:
3873 self
.title
= expression
3876 self
.varname
= varname
3879 self
.points
= points
3880 self
.expression
= {}
3882 varlist
, expressionlist
= expression
.split("=")
3883 parsestr
= mathtree
.ParseStr(expressionlist
)
3884 for key
in varlist
.split(","):
3886 if self
.mathtrees
.has_key(key
):
3887 raise ValueError("multiple assignment in tuple")
3889 self
.mathtrees
[key
] = parser
.ParseMathTree(parsestr
)
3891 except mathtree
.CommaFoundMathTreeParseError
, e
:
3892 self
.mathtrees
[key
] = e
.MathTree
3894 raise ValueError("unpack tuple of wrong size")
3895 if len(varlist
.split(",")) != len(self
.mathtrees
.keys()):
3896 raise ValueError("unpack tuple of wrong size")
3898 for i
in range(self
.points
):
3899 context
[self
.varname
] = self
.min + (self
.max-self
.min)*i
/ (self
.points
-1.0)
3901 for key
, tree
in self
.mathtrees
.items():
3902 line
.append(tree
.Calc(**context
))
3903 self
.data
.append(line
)
3905 def setstyle(self
, graph
, style
):
3908 for key
, index
in zip(self
.mathtrees
.keys(), xrange(sys
.maxint
)):
3909 columns
[key
] = index
3910 self
.style
.setcolumns(graph
, columns
)
3912 def getranges(self
):
3913 return self
.style
.getranges(self
.data
)
3915 def setranges(self
, ranges
):
3918 def draw(self
, graph
):
3919 self
.style
.drawpoints(graph
, self
.data
)