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.)
177 - self.label (a string) and self.labelttrs (a list, defaults to [])
178 When ticklevel or labellevel is None, no tick or label is present at that value.
179 When label is None, it should be automatically created (and stored), once the
180 an axis painter needs it. Classes, which implement _Itexter do precisely that."""
182 def __init__(self
, enum
, denom
, ticklevel
=None, labellevel
=None, label
=None, labelattrs
=[]):
183 frac
.__init
__(self
, enum
, denom
)
184 self
.ticklevel
= ticklevel
185 self
.labellevel
= labellevel
187 self
.labelattrs
= labelattrs
189 def merge(self
, other
):
190 """merges two ticks together:
191 - the lower ticklevel/labellevel wins
192 - when present, self.label is taken over; otherwise the others label is taken
193 - the ticks should be at the same position (otherwise it doesn't make sense)
194 -> this is NOT checked
196 if self
.ticklevel
is None or (other
.ticklevel
is not None and other
.ticklevel
< self
.ticklevel
):
197 self
.ticklevel
= other
.ticklevel
198 if self
.labellevel
is None or (other
.labellevel
is not None and other
.labellevel
< self
.labellevel
):
199 self
.labellevel
= other
.labellevel
200 if self
.label
is None:
201 self
.label
= other
.label
204 return "tick(%r, %r, %s, %s, %s)" % (self
.enum
, self
.denom
, self
.ticklevel
, self
.labellevel
, self
.label
)
207 def _mergeticklists(list1
, list2
):
208 """helper function to merge tick lists
209 return a merged list of ticks out of list1 and list2
210 caution: original lists have to be ordered
211 (the returned list is also ordered)
212 caution: original lists are modified and they share references to
214 # TODO: improve this using bisect
218 while 1: # we keep on going until we reach an index error
219 while list2
[j
] < list1
[i
]: # insert tick
220 list1
.insert(i
, list2
[j
])
223 if list2
[j
] == list1
[i
]: # merge tick
224 list1
[i
].merge(list2
[j
])
233 def _mergelabels(ticks
, labels
):
234 """helper function to merge labels into ticks
235 - when labels is not None, the label of all ticks with
237 different from None are set
238 - labels need to be a sequence of sequences of strings,
239 where the first sequence contain the strings to be
240 used as labels for the ticks with labellevel 0,
241 the second sequence for labellevel 1, etc.
242 - when the maximum labellevel is 0, just a sequence of
243 strings might be provided as the labels argument
244 - IndexError is raised, when a sequence length doesn't match"""
245 if helper
.issequenceofsequences(labels
):
246 for label
, level
in zip(labels
, xrange(sys
.maxint
)):
247 usetext
= helper
.ensuresequence(label
)
250 if tick
.labellevel
== level
:
251 tick
.label
= usetext
[i
]
253 if i
!= len(usetext
):
254 raise IndexError("wrong sequence length of labels at level %i" % level
)
255 elif labels
is not None:
256 usetext
= helper
.ensuresequence(labels
)
259 if tick
.labellevel
== 0:
260 tick
.label
= usetext
[i
]
262 if i
!= len(usetext
):
263 raise IndexError("wrong sequence length of labels")
267 """interface definition of a partition scheme
268 partition schemes are used to create a list of ticks"""
270 def defaultpart(self
, min, max, extendmin
, extendmax
):
271 """create a partition
272 - returns an ordered list of ticks for the interval min to max
273 - the interval is given in float numbers, thus an appropriate
274 conversion to rational numbers has to be performed
275 - extendmin and extendmax are booleans (integers)
276 - when extendmin or extendmax is set, the ticks might
277 extend the min-max range towards lower and higher
278 ranges, respectively"""
281 """create another partition which contains less ticks
282 - this method is called several times after a call of defaultpart
283 - returns an ordered list of ticks with less ticks compared to
284 the partition returned by defaultpart and by previous calls
286 - the creation of a partition with strictly *less* ticks
287 is not to be taken serious
288 - the method might return None, when no other appropriate
289 partition can be created"""
293 """create another partition which contains more ticks
294 see lesspart, but increase the number of ticks"""
298 """manual partition scheme
299 ticks and labels at positions explicitly provided to the constructor"""
301 __implements__
= _Ipart
303 def __init__(self
, ticks
=None, labels
=None, texts
=None, mix
=()):
304 """configuration of the partition scheme
305 - ticks and labels should be a sequence of sequences, where
306 the first sequence contains the values to be used for
307 ticks with ticklevel/labellevel 0, the second sequence for
308 ticklevel/labellevel 1, etc.
309 - tick and label values must be frac instances or
310 strings convertable to fracs by the _ensurefrac function
311 - when the maximum ticklevel/labellevel is 0, just a sequence
312 might be provided in ticks and labels
313 - when labels is None and ticks is not None, the tick entries
314 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
315 - labels are applied to the resulting partition via the
316 mergelabels function (additional information available there)
317 - mix specifies another partition to be merged into the
319 if ticks
is None and labels
is not None:
320 self
.ticks
= helper
.ensuresequence(helper
.getsequenceno(labels
, 0))
323 if labels
is None and ticks
is not None:
324 self
.labels
= helper
.ensuresequence(helper
.getsequenceno(ticks
, 0))
330 def checkfraclist(self
, *fracs
):
331 """orders a list of fracs, equal entries are not allowed"""
332 if not len(fracs
): return ()
336 for item
in sorted[1:]:
338 raise ValueError("duplicate entry found")
343 "create the partition as described in the constructor"
344 ticks
= list(self
.mix
)
345 if helper
.issequenceofsequences(self
.ticks
):
346 for fracs
, level
in zip(self
.ticks
, xrange(sys
.maxint
)):
347 ticks
= _mergeticklists(ticks
, [tick(frac
.enum
, frac
.denom
, ticklevel
= level
)
348 for frac
in self
.checkfraclist(*map(_ensurefrac
, helper
.ensuresequence(fracs
)))])
350 ticks
= _mergeticklists(ticks
, [tick(frac
.enum
, frac
.denom
, ticklevel
= 0)
351 for frac
in self
.checkfraclist(*map(_ensurefrac
, helper
.ensuresequence(self
.ticks
)))])
353 if helper
.issequenceofsequences(self
.labels
):
354 for fracs
, level
in zip(self
.labels
, xrange(sys
.maxint
)):
355 ticks
= _mergeticklists(ticks
, [tick(frac
.enum
, frac
.denom
, labellevel
= level
)
356 for frac
in self
.checkfraclist(*map(_ensurefrac
, helper
.ensuresequence(fracs
)))])
358 ticks
= _mergeticklists(ticks
, [tick(frac
.enum
, frac
.denom
, labellevel
= 0)
359 for frac
in self
.checkfraclist(*map(_ensurefrac
, helper
.ensuresequence(self
.labels
)))])
361 _mergelabels(ticks
, self
.labels
)
365 def defaultpart(self
, min, max, extendmin
, extendmax
):
366 """method is part of the implementation of _Ipart
367 XXX: we do not take care of the parameters -> correct?"""
371 "method is part of the implementation of _Ipart"
375 "method is part of the implementation of _Ipart"
380 """linear partition scheme
381 ticks and label distances are explicitly provided to the constructor"""
383 __implements__
= _Ipart
385 def __init__(self
, ticks
=None, labels
=None, texts
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10, mix
=()):
386 """configuration of the partition scheme
387 - ticks and labels should be a sequence, where the first value
388 is the distance between ticks with ticklevel/labellevel 0,
389 the second sequence for ticklevel/labellevel 1, etc.
390 - tick and label values must be frac instances or
391 strings convertable to fracs by the _ensurefrac function
392 - when the maximum ticklevel/labellevel is 0, just a single value
393 might be provided in ticks and labels
394 - when labels is None and ticks is not None, the tick entries
395 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
396 - texts are applied to the resulting partition via the
397 mergetexts function (additional information available there)
398 - extendtick allows for the extension of the range given to the
399 defaultpart method to include the next tick with the specified
400 level (None turns off this feature); note, that this feature is
401 also disabled, when an axis prohibits its range extension by
402 the extendmin/extendmax variables given to the defaultpart method
403 - extendlabel is analogous to extendtick, but for labels
404 - epsilon allows for exceeding the axis range by this relative
405 value (relative to the axis range given to the defaultpart method)
406 without creating another tick specified by extendtick/extendlabel
407 - mix specifies another partition to be merged into the
409 if ticks
is None and labels
is not None:
410 self
.ticks
= (_ensurefrac(helper
.ensuresequence(labels
)[0]),)
412 self
.ticks
= map(_ensurefrac
, helper
.ensuresequence(ticks
))
413 if labels
is None and ticks
is not None:
414 self
.labels
= (_ensurefrac(helper
.ensuresequence(ticks
)[0]),)
416 self
.labels
= map(_ensurefrac
, helper
.ensuresequence(labels
))
418 self
.extendtick
= extendtick
419 self
.extendlabel
= extendlabel
420 self
.epsilon
= epsilon
423 def extendminmax(self
, min, max, frac
, extendmin
, extendmax
):
424 """return new min, max tuple extending the range min, max
425 frac is the tick distance to be used
426 extendmin and extendmax are booleans to allow for the extension"""
428 min = float(frac
) * math
.floor(min / float(frac
) + self
.epsilon
)
430 max = float(frac
) * math
.ceil(max / float(frac
) - self
.epsilon
)
433 def getticks(self
, min, max, frac
, ticklevel
=None, labellevel
=None):
434 """return a list of equal spaced ticks
435 - the tick distance is frac, the ticklevel is set to ticklevel and
436 the labellevel is set to labellevel
437 - min, max is the range where ticks should be placed"""
438 imin
= int(math
.ceil(min / float(frac
) - 0.5 * self
.epsilon
))
439 imax
= int(math
.floor(max / float(frac
) + 0.5 * self
.epsilon
))
441 for i
in range(imin
, imax
+ 1):
442 ticks
.append(tick(long(i
) * frac
.enum
, frac
.denom
, ticklevel
=ticklevel
, labellevel
=labellevel
))
445 def defaultpart(self
, min, max, extendmin
, extendmax
):
446 "method is part of the implementation of _Ipart"
447 if self
.extendtick
is not None and len(self
.ticks
) > self
.extendtick
:
448 min, max = self
.extendminmax(min, max, self
.ticks
[self
.extendtick
], extendmin
, extendmax
)
449 if self
.extendlabel
is not None and len(self
.labels
) > self
.extendlabel
:
450 min, max = self
.extendminmax(min, max, self
.labels
[self
.extendlabel
], extendmin
, extendmax
)
452 ticks
= list(self
.mix
)
453 for i
in range(len(self
.ticks
)):
454 ticks
= _mergeticklists(ticks
, self
.getticks(min, max, self
.ticks
[i
], ticklevel
= i
))
455 for i
in range(len(self
.labels
)):
456 ticks
= _mergeticklists(ticks
, self
.getticks(min, max, self
.labels
[i
], labellevel
= i
))
458 _mergelabels(ticks
, self
.labels
)
463 "method is part of the implementation of _Ipart"
467 "method is part of the implementation of _Ipart"
472 """automatic linear partition scheme
473 - possible tick distances are explicitly provided to the constructor
474 - tick distances are adjusted to the axis range by multiplication or division by 10"""
476 __implements__
= _Ipart
478 defaultlist
= ((frac(1, 1), frac(1, 2)),
479 (frac(2, 1), frac(1, 1)),
480 (frac(5, 2), frac(5, 4)),
481 (frac(5, 1), frac(5, 2)))
483 def __init__(self
, list=defaultlist
, extendtick
=0, epsilon
=1e-10, mix
=()):
484 """configuration of the partition scheme
485 - list should be a sequence of fracs
486 - ticks should be a sequence, where the first value
487 is the distance between ticks with ticklevel 0,
488 the second for ticklevel 1, etc.
489 - tick values must be frac instances or
490 strings convertable to fracs by the _ensurefrac function
491 - labellevel is set to None except for those ticks in the partitions,
492 where ticklevel is zero. There labellevel is also set to zero.
493 - extendtick allows for the extension of the range given to the
494 defaultpart method to include the next tick with the specified
495 level (None turns off this feature); note, that this feature is
496 also disabled, when an axis prohibits its range extension by
497 the extendmin/extendmax variables given to the defaultpart method
498 - epsilon allows for exceeding the axis range by this relative
499 value (relative to the axis range given to the defaultpart method)
500 without creating another tick specified by extendtick
501 - mix specifies another partition to be merged into the
504 self
.extendtick
= extendtick
505 self
.epsilon
= epsilon
508 def defaultpart(self
, min, max, extendmin
, extendmax
):
509 "method is part of the implementation of _Ipart"
510 base
= frac(10L, 1, int(math
.log(max - min) / math
.log(10)))
512 useticks
= [tick
* base
for tick
in ticks
]
513 self
.lesstickindex
= self
.moretickindex
= 0
514 self
.lessbase
= frac(base
.enum
, base
.denom
)
515 self
.morebase
= frac(base
.enum
, base
.denom
)
516 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
517 part
= linpart(ticks
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
, mix
=self
.mix
)
518 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
521 "method is part of the implementation of _Ipart"
522 if self
.lesstickindex
< len(self
.list) - 1:
523 self
.lesstickindex
+= 1
525 self
.lesstickindex
= 0
526 self
.lessbase
.enum
*= 10
527 ticks
= self
.list[self
.lesstickindex
]
528 useticks
= [tick
* self
.lessbase
for tick
in ticks
]
529 part
= linpart(ticks
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
, mix
=self
.mix
)
530 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
533 "method is part of the implementation of _Ipart"
534 if self
.moretickindex
:
535 self
.moretickindex
-= 1
537 self
.moretickindex
= len(self
.list) - 1
538 self
.morebase
.denom
*= 10
539 ticks
= self
.list[self
.moretickindex
]
540 useticks
= [tick
* self
.morebase
for tick
in ticks
]
541 part
= linpart(ticks
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
, mix
=self
.mix
)
542 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
546 """storage class for the definition of logarithmic axes partitions
547 instances of this class define tick positions suitable for
548 logarithmic axes by the following instance variables:
549 - shift: integer, which defines multiplicator
550 - fracs: list of tick positions (rational numbers, e.g. instances of frac)
551 possible positions are these tick positions and arbitrary divisions
552 and multiplications by the shift value"""
554 def __init__(self
, shift
, *fracs
):
555 "create a shiftfracs instance and store its shift and fracs information"
560 class logpart(linpart
):
561 """logarithmic partition scheme
562 ticks and label positions are explicitly provided to the constructor"""
564 __implements__
= _Ipart
566 shift5fracs1
= shiftfracs(100000, frac(1, 1))
567 shift4fracs1
= shiftfracs(10000, frac(1, 1))
568 shift3fracs1
= shiftfracs(1000, frac(1, 1))
569 shift2fracs1
= shiftfracs(100, frac(1, 1))
570 shiftfracs1
= shiftfracs(10, frac(1, 1))
571 shiftfracs125
= shiftfracs(10, frac(1, 1), frac(2, 1), frac(5, 1))
572 shiftfracs1to9
= shiftfracs(10, *map(lambda x
: frac(x
, 1), range(1, 10)))
573 # ^- we always include 1 in order to get extendto(tick|label)level to work as expected
575 def __init__(self
, ticks
=None, labels
=None, texts
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10, mix
=()):
576 """configuration of the partition scheme
577 - ticks and labels should be a sequence, where the first value
578 is a shiftfracs instance describing ticks with ticklevel/labellevel 0,
579 the second sequence for ticklevel/labellevel 1, etc.
580 - when the maximum ticklevel/labellevel is 0, just a single
581 shiftfracs instance might be provided in ticks and labels
582 - when labels is None and ticks is not None, the tick entries
583 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
584 - texts are applied to the resulting partition via the
585 mergetexts function (additional information available there)
586 - extendtick allows for the extension of the range given to the
587 defaultpart method to include the next tick with the specified
588 level (None turns off this feature); note, that this feature is
589 also disabled, when an axis prohibits its range extension by
590 the extendmin/extendmax variables given to the defaultpart method
591 - extendlabel is analogous to extendtick, but for labels
592 - epsilon allows for exceeding the axis range by this relative
593 logarithm value (relative to the logarithm axis range given
594 to the defaultpart method) without creating another tick
595 specified by extendtick/extendlabel
596 - mix specifies another partition to be merged into the
598 if ticks
is None and labels
is not None:
599 self
.ticks
= (helper
.ensuresequence(labels
)[0],)
601 self
.ticks
= helper
.ensuresequence(ticks
)
603 if labels
is None and ticks
is not None:
604 self
.labels
= (helper
.ensuresequence(ticks
)[0],)
606 self
.labels
= helper
.ensuresequence(labels
)
608 self
.extendtick
= extendtick
609 self
.extendlabel
= extendlabel
610 self
.epsilon
= epsilon
613 def extendminmax(self
, min, max, shiftfracs
, extendmin
, extendmax
):
614 """return new min, max tuple extending the range min, max
615 shiftfracs describes the allowed tick positions
616 extendmin and extendmax are booleans to allow for the extension"""
619 for i
in xrange(len(shiftfracs
.fracs
)):
620 imin
= int(math
.floor(math
.log(min / float(shiftfracs
.fracs
[i
])) /
621 math
.log(shiftfracs
.shift
) + self
.epsilon
)) + 1
622 imax
= int(math
.ceil(math
.log(max / float(shiftfracs
.fracs
[i
])) /
623 math
.log(shiftfracs
.shift
) - self
.epsilon
)) - 1
624 if minpower
is None or imin
< minpower
:
625 minpower
, minindex
= imin
, i
626 if maxpower
is None or imax
>= maxpower
:
627 maxpower
, maxindex
= imax
, i
629 minfrac
= shiftfracs
.fracs
[minindex
- 1]
631 minfrac
= shiftfracs
.fracs
[-1]
633 if maxindex
!= len(shiftfracs
.fracs
) - 1:
634 maxfrac
= shiftfracs
.fracs
[maxindex
+ 1]
636 maxfrac
= shiftfracs
.fracs
[0]
639 min = float(minfrac
) * float(shiftfracs
.shift
) ** minpower
641 max = float(maxfrac
) * float(shiftfracs
.shift
) ** maxpower
644 def getticks(self
, min, max, shiftfracs
, ticklevel
=None, labellevel
=None):
645 """return a list of ticks
646 - shiftfracs describes the allowed tick positions
647 - the ticklevel of the ticks is set to ticklevel and
648 the labellevel is set to labellevel
649 - min, max is the range where ticks should be placed"""
650 ticks
= list(self
.mix
)
653 for f
in shiftfracs
.fracs
:
655 imin
= int(math
.ceil(math
.log(min / float(f
)) /
656 math
.log(shiftfracs
.shift
) - 0.5 * self
.epsilon
))
657 imax
= int(math
.floor(math
.log(max / float(f
)) /
658 math
.log(shiftfracs
.shift
) + 0.5 * self
.epsilon
))
659 for i
in range(imin
, imax
+ 1):
660 pos
= f
* frac(shiftfracs
.shift
, 1, i
)
661 fracticks
.append(tick(pos
.enum
, pos
.denom
, ticklevel
= ticklevel
, labellevel
= labellevel
))
662 ticks
= _mergeticklists(ticks
, fracticks
)
666 class autologpart(logpart
):
667 """automatic logarithmic partition scheme
668 possible tick positions are explicitly provided to the constructor"""
670 __implements__
= _Ipart
672 defaultlist
= (((logpart
.shiftfracs1
, # ticks
673 logpart
.shiftfracs1to9
), # subticks
674 (logpart
.shiftfracs1
, # labels
675 logpart
.shiftfracs125
)), # sublevels
677 ((logpart
.shiftfracs1
, # ticks
678 logpart
.shiftfracs1to9
), # subticks
679 None), # labels like ticks
681 ((logpart
.shift2fracs1
, # ticks
682 logpart
.shiftfracs1
), # subticks
683 None), # labels like ticks
685 ((logpart
.shift3fracs1
, # ticks
686 logpart
.shiftfracs1
), # subticks
687 None), # labels like ticks
689 ((logpart
.shift4fracs1
, # ticks
690 logpart
.shiftfracs1
), # subticks
691 None), # labels like ticks
693 ((logpart
.shift5fracs1
, # ticks
694 logpart
.shiftfracs1
), # subticks
695 None)) # labels like ticks
697 def __init__(self
, list=defaultlist
, extendtick
=0, extendlabel
=None, epsilon
=1e-10, mix
=()):
698 """configuration of the partition scheme
699 - list should be a sequence of pairs of sequences of shiftfracs
701 - within each pair the first sequence contains shiftfracs, where
702 the first shiftfracs instance describes ticks positions with
703 ticklevel 0, the second shiftfracs for ticklevel 1, etc.
704 - the second sequence within each pair describes the same as
705 before, but for labels
706 - within each pair: when the second entry (for the labels) is None
707 and the first entry (for the ticks) ticks is not None, the tick
708 entries for ticklevel 0 are used for labels and vice versa
710 - extendtick allows for the extension of the range given to the
711 defaultpart method to include the next tick with the specified
712 level (None turns off this feature); note, that this feature is
713 also disabled, when an axis prohibits its range extension by
714 the extendmin/extendmax variables given to the defaultpart method
715 - extendlabel is analogous to extendtick, but for labels
716 - epsilon allows for exceeding the axis range by this relative
717 logarithm value (relative to the logarithm axis range given
718 to the defaultpart method) without creating another tick
719 specified by extendtick/extendlabel
720 - mix specifies another partition to be merged into the
724 self
.listindex
= divmod(len(list), 2)[0]
727 self
.extendtick
= extendtick
728 self
.extendlabel
= extendlabel
729 self
.epsilon
= epsilon
732 def defaultpart(self
, min, max, extendmin
, extendmax
):
733 "method is part of the implementation of _Ipart"
734 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
735 self
.morelistindex
= self
.listindex
736 self
.lesslistindex
= self
.listindex
737 part
= logpart(ticks
=self
.list[self
.listindex
][0], labels
=self
.list[self
.listindex
][1],
738 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
, mix
=self
.mix
)
739 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
742 "method is part of the implementation of _Ipart"
743 self
.lesslistindex
+= 1
744 if self
.lesslistindex
< len(self
.list):
745 part
= logpart(ticks
=self
.list[self
.lesslistindex
][0], labels
=self
.list[self
.lesslistindex
][1],
746 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
, mix
=self
.mix
)
747 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
750 "method is part of the implementation of _Ipart"
751 self
.morelistindex
-= 1
752 if self
.morelistindex
>= 0:
753 part
= logpart(ticks
=self
.list[self
.morelistindex
][0], labels
=self
.list[self
.morelistindex
][1],
754 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
, mix
=self
.mix
)
755 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
759 ################################################################################
761 # conseptional remarks:
762 # - raters are used to calculate a rating for a realization of something
763 # - here, a rating means a positive floating point value
764 # - ratings are used to order those realizations by their suitability (lower
765 # ratings are better)
766 # - a rating of None means not suitable at all (those realizations should be
768 ################################################################################
773 - a cube rater has an optimal value, where the rate becomes zero
774 - for a left (below the optimum) and a right value (above the optimum),
775 the rating is value is set to 1 (modified by an overall weight factor
777 - the analytic form of the rating is cubic for both, the left and
778 the right side of the rater, independently"""
780 def __init__(self
, opt
, left
=None, right
=None, weight
=1):
781 """initializes the rater
782 - by default, left is set to zero, right is set to 3*opt
783 - left should be smaller than opt, right should be bigger than opt
784 - weight should be positive and is a factor multiplicated to the rates"""
794 def rate(self
, value
, dense
=1):
795 """returns a rating for a value
796 - the dense factor lineary rescales the rater (the optimum etc.),
797 e.g. a value bigger than one increases the optimum (when it is
798 positive) and a value lower than one decreases the optimum (when
799 it is positive); the dense factor itself should be positive"""
800 opt
= self
.opt
* dense
802 other
= self
.left
* dense
804 other
= self
.right
* dense
807 factor
= (value
- opt
) / float(other
- opt
)
808 return self
.weight
* (factor
** 3)
812 """a distance rater (rates a list of distances)
813 - the distance rater rates a list of distances by rating each independently
814 and returning the average rate
815 - there is an optimal value, where the rate becomes zero
816 - the analytic form is linary for values above the optimal value
817 (twice the optimal value has the rating one, three times the optimal
818 value has the rating two, etc.)
819 - the analytic form is reciprocal subtracting one for values below the
820 optimal value (halve the optimal value has the rating one, one third of
821 the optimal value has the rating two, etc.)"""
823 def __init__(self
, opt
, weight
=0.1):
824 """inititializes the rater
825 - opt is the optimal length (a PyX length, by default a visual length)
826 - weight should be positive and is a factor multiplicated to the rates"""
830 def _rate(self
, distances
, dense
=1):
832 - the distances are a sequence of positive floats in PostScript points
833 - the dense factor lineary rescales the rater (the optimum etc.),
834 e.g. a value bigger than one increases the optimum (when it is
835 positive) and a value lower than one decreases the optimum (when
836 it is positive); the dense factor itself should be positive"""
838 opt
= unit
.topt(unit
.length(self
.opt_str
, default_type
="v")) / dense
840 for distance
in distances
:
842 rate
+= self
.weight
* (opt
/ distance
- 1)
844 rate
+= self
.weight
* (distance
/ opt
- 1)
845 return rate
/ float(len(distances
))
849 """a rater for axis partitions
850 - the rating of axes is splited into two separate parts:
851 - rating of the partitions in terms of the number of ticks,
852 subticks, labels, etc.
853 - rating of the label distances
854 - in the end, a rate for an axis partition is the sum of these rates
855 - it is useful to first just rate the number of ticks etc.
856 and selecting those partitions, where this fits well -> as soon
857 as an complete rate (the sum of both parts from the list above)
858 of a first partition is below a rate of just the ticks of another
859 partition, this second partition will never be better than the
860 first one -> we gain speed by minimizing the number of partitions,
861 where label distances have to be taken into account)
862 - both parts of the rating are shifted into instances of raters
863 defined above --- right now, there is not yet a strict interface
864 for this delegation (should be done as soon as it is needed)"""
866 linticks
= (cuberate(4), cuberate(10, weight
=0.5), )
867 linlabels
= (cuberate(4), )
868 logticks
= (cuberate(5, right
=20), cuberate(20, right
=100, weight
=0.5), )
869 loglabels
= (cuberate(5, right
=20), cuberate(5, left
=-20, right
=20, weight
=0.5), )
870 stdtickrange
= cuberate(1, weight
=2)
871 stddistance
= distancerate("1 cm")
873 def __init__(self
, ticks
=linticks
, labels
=linlabels
, tickrange
=stdtickrange
, distance
=stddistance
):
874 """initializes the axis rater
875 - ticks and labels are lists of instances of cuberate
876 - the first entry in ticks rate the number of ticks, the
877 second the number of subticks, etc.; when there are no
878 ticks of a level or there is not rater for a level, the
879 level is just ignored
880 - labels is analogous, but for labels
881 - within the rating, all ticks with a higher level are
882 considered as ticks for a given level
883 - tickrange is a cuberate instance, which rates the covering
884 of an axis range by the ticks (as a relative value of the
885 tick range vs. the axis range), ticks might cover less or
886 more than the axis range (for the standard automatic axis
887 partition schemes an extention of the axis range is normal
888 and should get some penalty)
889 - distance is an distancerate instance"""
892 self
.tickrange
= tickrange
893 self
.distance
= distance
895 def ratepart(self
, axis
, part
, dense
=1):
896 """rates a partition by some global parameters
897 - takes into account the number of ticks, subticks, etc.,
898 number of labels, etc., and the coverage of the axis
900 - when there are no ticks of a level or there was not rater
901 given in the constructor for a level, the level is just
903 - the method returns the sum of the rating results divided
904 by the sum of the weights of the raters
905 - within the rating, all ticks with a higher level are
906 considered as ticks for a given level"""
907 tickslen
= len(self
.ticks
)
908 labelslen
= len(self
.labels
)
910 labels
= [0]*labelslen
913 if tick
.ticklevel
is not None:
914 for level
in xrange(tick
.ticklevel
, tickslen
):
916 if tick
.labellevel
is not None:
917 for level
in xrange(tick
.labellevel
, labelslen
):
921 for tick
, rater
in zip(ticks
, self
.ticks
):
922 rate
+= rater
.rate(tick
, dense
=dense
)
923 weight
+= rater
.weight
924 for label
, rater
in zip(labels
, self
.labels
):
925 rate
+= rater
.rate(label
, dense
=dense
)
926 weight
+= rater
.weight
927 if part
is not None and len(part
):
928 tickmin
, tickmax
= axis
.gettickrange() # XXX: tickrange was not yet applied!?
929 rate
+= self
.tickrange
.rate((float(part
[-1]) - float(part
[0])) * axis
.divisor
/ (tickmax
- tickmin
))
931 rate
+= self
.tickrange
.rate(0)
932 weight
+= self
.tickrange
.weight
935 def _ratedistances(self
, distances
, dense
=1):
937 - the distances should be collected as box distances of
938 subsequent labels (of any level))
939 - the distances are a sequence of positive floats in
941 - the dense factor is used within the distancerate instance"""
942 return self
.distance
._rate
(distances
, dense
=dense
)
945 ################################################################################
947 # texter automatically create labels for tick instances
948 ################################################################################
953 def labels(self
, ticks
):
954 """fill the label attribute of ticks
955 - ticks is a list of instances of tick
956 - for each element of ticks the value of the attribute label is set to
957 a string appropriate to the attributes enum and denom of that tick
959 - label attributes of the tick instances are just kept, whenever they
960 are not equal to None
961 - the method might add texsetting instances to the labelattrs attribute
966 """a texter, which does just nothing (I'm not sure, if this makes sense)"""
968 __implements__
= _Itexter
970 def labels(self
, ticks
):
974 class rationaltexter
:
975 """a texter creating rational labels (e.g. "a/b" or even "a \over b")"""
976 # XXX: we use divmod here to be more portable
978 __implements__
= _Itexter
980 def __init__(self
, prefix
="", suffix
="",
981 enumprefix
="", enumsuffix
="",
982 denomprefix
="", denomsuffix
="",
983 equaldenom
=0, minuspos
=0, over
=r
"{{%s}\over{%s}}",
984 skipenum0
=1, skipenum1
=1, skipdenom1
=1,
985 labelattrs
=textmodule
.mathmode
):
986 """initializes the instance
987 - prefix and suffix (strings) are just added to the begin and to
988 the end of the label, respectively
989 - prefixenum and suffixenum (strings) are added to the labels
991 - prefixdenom and suffixdenom (strings) are added to the labels
992 denominator simularly
993 - the enumerator and denominator are +++TODO: KÜRZEN+++; however,
994 when equaldenom is set, the least common multiple of all
996 - minuspos is an integer, which determines the position, where the
997 minus sign has to be placed; the following values are allowed:
998 0 - written immediately before the prefix
999 1 - written immediately after the prefix
1000 2 - written immediately before the enumprefix
1001 3 - written immediately after the enumprefix
1002 4 - written immediately before the denomprefix
1003 5 - written immediately after the denomprefix
1004 - over (string) is taken as a format string generating the
1005 fraction bar; it has to contain exactly two string insert
1006 operators "%s" - the first for the enumerator and the second
1007 for the denominator; by far the most common examples are
1008 r"{{%s}\over{%s}}" and "{{%s}/{%s}}"
1009 - skipenum0 (boolean) just prints a zero instead of
1010 the hole fraction, when the enumerator is zero;
1011 all prefixes and suffixes are omitted as well
1012 - skipenum1 (boolean) just prints the enumprefix directly followed
1013 by the enumsuffix, when the enum value is one and at least either,
1014 the enumprefix or the enumsuffix is present
1015 - skipdenom1 (boolean) just prints the enumerator instead of
1016 the hole fraction, when the denominator is one
1017 - labelattrs is a sequence of texsetting instances; also just a single
1018 instance is allowed; an empty sequence is allowed as well, but None
1020 self
.prefix
= prefix
1021 self
.suffix
= suffix
1022 self
.enumprefix
= enumprefix
1023 self
.enumsuffix
= enumsuffix
1024 self
.denomprefix
= denomprefix
1025 self
.denomsuffix
= denomsuffix
1026 self
.equaldenom
= equaldenom
1027 self
.minuspos
= minuspos
1028 if self
.minuspos
< 0 or self
.minuspos
> 5:
1029 raise RuntimeError("invalid minuspos")
1031 self
.skipenum0
= skipenum0
1032 self
.skipenum1
= skipenum1
1033 self
.skipdenom1
= skipdenom1
1034 self
.labelattrs
= helper
.ensuresequence(labelattrs
)
1036 def gcd(self
, m
, n
):
1037 """returns the greates common divisor
1038 - m and n must be non-negative integers"""
1042 m
, (dummy
, n
) = n
, divmod(m
, n
) # ensure portable integer division
1046 """returns the least common multiple of all elements in n
1047 - the elements of n must be integers
1048 - return None if the number of elements is zero"""
1049 "TODO: fill it from knuth"
1052 def labels(self
, ticks
):
1053 # the temporary variables fracenum, fracdenom, and fracminus are
1054 # inserted into all tick instances, where label is not None
1056 if tick
.label
is None:
1058 tick
.fracenum
= tick
.enum
1059 tick
.fracdenom
= tick
.denom
1060 if tick
.fracenum
< 0:
1061 tick
.fracminus
*= -1
1063 if tick
.fracdenom
< 0:
1064 tick
.fracminus
*= -1
1065 tick
.fracdenom
*= -1
1066 gcd
= self
.gcd(tick
.fracenum
, tick
.fracdenom
)
1067 (tick
.fracenum
, dummy1
), (tick
.fracdenom
, dummy2
) = divmod(tick
.enum
, gcd
), divmod(tick
.fracdenom
, gcd
)
1069 equaldenom
= self
.lcm([tick
.fracdenom
for tick
in ticks
if tick
.label
is None])
1070 if equaldenom
is not None:
1072 if tick
.label
is None:
1073 factor
, dummy
= divmod(equaldenom
, tick
.fracdenom
)
1074 assert dummy
!= 0, "internal error: wrong lowest common multiple?" # TODO: remove that check
1075 tick
.fracenum
, tick
.fracdenom
= factor
* tick
.fracenum
, factor
* tick
.fracdenom
1077 if tick
.label
is None:
1078 if tick
.fracminus
== -1:
1079 tick
.fracminus
= "-"
1082 if self
.skipenum0
and tick
.fracenum
== 0:
1083 if self
.minuspos
== 2 or self
.minuspos
== 3:
1084 tick
.fracenum
= "%s0" % tick
.fracminus
1086 tick
.fracenum
= tick
.fracenum
1087 elif self
.skipenum1
and tick
.fracenum
== 1 and (len(self
.enumprefix
) or len(self
.enumsuffix
)):
1088 if self
.minuspos
== 2:
1089 tick
.fracenum
= "%s%s%s" % (tick
.fracminus
, self
.enumprefix
, self
.enumsuffix
)
1090 elif self
.minuspos
== 3:
1091 tick
.fracenum
= "%s%s%s" % (self
.enumprefix
, tick
.fracminus
, self
.enumsuffix
)
1093 tick
.fracenum
= "%s%s" % (self
.enumprefix
, self
.enumsuffix
)
1095 if self
.minuspos
== 2:
1096 tick
.fracenum
= "%s%s%i%s" % (tick
.fracminus
, self
.enumprefix
, tick
.fracenum
, self
.enumsuffix
)
1097 elif self
.minuspos
== 3:
1098 tick
.fracenum
= "%s%s%i%s" % (self
.enumprefix
, tick
.fracminus
, tick
.fracenum
, self
.enumsuffix
)
1100 tick
.fracenum
= "%s%i%s" % (self
.enumprefix
, tick
.fracenum
, self
.enumsuffix
)
1101 if self
.skipdenom1
and tick
.fracdenom
== 1 and self
.minuspos
!= 4 and self
.minuspos
!= 5:
1102 frac
= tick
.fracenum
1104 if self
.minuspos
== 4:
1105 tick
.fracdenom
= "%s%s%i%s" % (tick
.fracminus
, self
.denomprefix
, tick
.fracdenom
, self
.denomsuffix
)
1106 elif self
.minuspos
== 5:
1107 tick
.fracdenom
= "%s%s%i%s" % (self
.denomprefix
, tick
.fracminus
, tick
.fracdenom
, self
.denomsuffix
)
1109 tick
.fracdenom
= "%s%i%s" % (self
.denomprefix
, tick
.fracdenom
, self
.denomsuffix
)
1110 frac
= self
.over
% (tick
.fracenum
, tick
.fracdenom
)
1111 if self
.minuspos
== 0:
1112 tick
.label
= "%s%s%s%s" % (tick
.fracminus
, self
.prefix
, frac
, self
.suffix
)
1113 elif self
.minuspos
== 1:
1114 tick
.label
= "%s%s%s%s" % (self
.prefix
, tick
.fracminus
, frac
, self
.suffix
)
1116 tick
.label
= "%s%s%s" % (self
.prefix
, frac
, self
.suffix
)
1120 ################################################################################
1122 ################################################################################
1125 class layoutdata
: pass
1128 class axistitlepainter
:
1133 def __init__(self
, titledist
="0.3 cm",
1134 titleattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1137 self
.titledist_str
= titledist
1138 self
.titleattrs
= titleattrs
1139 self
.titledirection
= titledirection
1140 self
.titlepos
= titlepos
1142 def reldirection(self
, direction
, dx
, dy
, epsilon
=1e-10):
1143 direction
+= math
.atan2(dy
, dx
) * 180 / math
.pi
1144 while (direction
> 90 + epsilon
):
1146 while (direction
< -90 - epsilon
):
1150 def dolayout(self
, graph
, axis
):
1151 if axis
.title
is not None and self
.titleattrs
is not None:
1152 titledist
= unit
.topt(unit
.length(self
.titledist_str
, default_type
="v"))
1153 x
, y
= axis
._vtickpoint
(axis
, self
.titlepos
)
1154 dx
, dy
= axis
.vtickdirection(axis
, self
.titlepos
)
1155 # no not modify self.titleattrs ... the painter might be used by several axes!!!
1156 titleattrs
= list(helper
.ensuresequence(self
.titleattrs
))
1157 if self
.titledirection
is not None:
1158 titleattrs
= titleattrs
+ [trafo
.rotate(self
.reldirection(self
.titledirection
, dx
, dy
))]
1159 axis
.layoutdata
.titlebox
= graph
.texrunner
._text
(x
, y
, axis
.title
, *titleattrs
)
1160 axis
.layoutdata
._extent
+= titledist
1161 axis
.layoutdata
.titlebox
._linealign
(axis
.layoutdata
._extent
, dx
, dy
)
1162 axis
.layoutdata
._extent
+= axis
.layoutdata
.titlebox
._extent
(dx
, dy
)
1164 axis
.layoutdata
.titlebox
= None
1166 def paint(self
, graph
, axis
):
1167 if axis
.layoutdata
.titlebox
is not None:
1168 graph
.insert(axis
.layoutdata
.titlebox
)
1171 class axispainter(axistitlepainter
):
1173 defaultticklengths
= ["%0.5f cm" % (0.2*goldenmean
**(-i
)) for i
in range(10)]
1180 def __init__(self
, innerticklengths
=defaultticklengths
,
1181 outerticklengths
=None,
1185 baselineattrs
=canvas
.linecap
.square
,
1187 labelattrs
=((textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1188 (textmodule
.size
.footnotesize
, textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
)),
1189 labeldirection
=None,
1192 fractype
=fractypeauto
,
1193 ratfracsuffixenum
=1,
1194 ratfracover
=r
"\over",
1197 expfractimes
=r
"\cdot",
1203 self
.innerticklengths_str
= innerticklengths
1204 self
.outerticklengths_str
= outerticklengths
1205 self
.tickattrs
= tickattrs
1206 self
.gridattrs
= gridattrs
1207 self
.zerolineattrs
= zerolineattrs
1208 self
.baselineattrs
= baselineattrs
1209 self
.labeldist_str
= labeldist
1210 self
.labelattrs
= labelattrs
1211 self
.labeldirection
= labeldirection
1212 self
.labelhequalize
= labelhequalize
1213 self
.labelvequalize
= labelvequalize
1214 self
.fractype
= fractype
1215 self
.ratfracsuffixenum
= ratfracsuffixenum
1216 self
.ratfracover
= ratfracover
1217 self
.decfracpoint
= decfracpoint
1218 self
.decfracequal
= decfracequal
1219 self
.expfractimes
= expfractimes
1220 self
.expfracpre1
= expfracpre1
1221 self
.expfracminexp
= expfracminexp
1222 self
.suffix0
= suffix0
1223 self
.suffix1
= suffix1
1224 axistitlepainter
.__init
__(self
, **args
)
1226 def attachsuffix(self
, tick
, str):
1227 if self
.suffix0
or tick
.enum
:
1228 if tick
.suffix
is not None and not self
.suffix1
:
1233 if tick
.suffix
is not None:
1234 str = str + tick
.suffix
1237 def ratfrac(self
, tick
):
1238 m
, n
= tick
.enum
, tick
.denom
1240 if m
< 0: m
, sign
= -m
, -sign
1241 if n
< 0: n
, sign
= -n
, -sign
1242 gcd
= self
.gcd(m
, n
)
1243 (m
, dummy1
), (n
, dummy2
) = divmod(m
, gcd
), divmod(n
, gcd
)
1245 if self
.ratfracsuffixenum
:
1247 return "-{{%s}%s{%s}}" % (self
.attachsuffix(tick
, str(m
)), self
.ratfracover
, n
)
1249 return "{{%s}%s{%s}}" % (self
.attachsuffix(tick
, str(m
)), self
.ratfracover
, n
)
1252 return self
.attachsuffix(tick
, "-{{%s}%s{%s}}" % (m
, self
.ratfracover
, n
))
1254 return self
.attachsuffix(tick
, "{{%s}%s{%s}}" % (m
, self
.ratfracover
, n
))
1257 return self
.attachsuffix(tick
, "-%s" % m
)
1259 return self
.attachsuffix(tick
, "%s" % m
)
1261 def decfrac(self
, tick
, decfraclength
=None):
1262 m
, n
= tick
.enum
, tick
.denom
1264 if m
< 0: m
, sign
= -m
, -sign
1265 if n
< 0: n
, sign
= -n
, -sign
1266 gcd
= self
.gcd(m
, n
)
1267 (m
, dummy1
), (n
, dummy2
) = divmod(m
, gcd
), divmod(n
, gcd
)
1268 frac
, rest
= divmod(m
, n
)
1272 strfrac
+= self
.decfracpoint
1274 tick
.decfraclength
= 0
1276 tick
.decfraclength
+= 1
1278 periodstart
= len(strfrac
) - (len(oldrest
) - oldrest
.index(rest
))
1279 strfrac
= strfrac
[:periodstart
] + r
"\overline{" + strfrac
[periodstart
:] + "}"
1283 frac
, rest
= divmod(rest
, n
)
1284 strfrac
+= str(frac
)
1286 if decfraclength
is not None:
1287 while tick
.decfraclength
< decfraclength
:
1289 tick
.decfraclength
+= 1
1291 return self
.attachsuffix(tick
, "-%s" % strfrac
)
1293 return self
.attachsuffix(tick
, strfrac
)
1295 def expfrac(self
, tick
, minexp
= None):
1296 m
, n
= tick
.enum
, tick
.denom
1298 if m
< 0: m
, sign
= -m
, -sign
1299 if n
< 0: n
, sign
= -n
, -sign
1302 while divmod(m
, n
)[0] > 9:
1305 while divmod(m
, n
)[0] < 1:
1308 if minexp
is not None and ((exp
< 0 and -exp
< minexp
) or (exp
>= 0 and exp
< minexp
)):
1312 prefactor
= self
.decfrac(dummy
)
1313 if prefactor
== "1" and not self
.expfracpre1
:
1315 return self
.attachsuffix(tick
, "-10^{%i}" % exp
)
1317 return self
.attachsuffix(tick
, "10^{%i}" % exp
)
1320 return self
.attachsuffix(tick
, "-%s%s10^{%i}" % (prefactor
, self
.expfractimes
, exp
))
1322 return self
.attachsuffix(tick
, "%s%s10^{%i}" % (prefactor
, self
.expfractimes
, exp
))
1324 def createtext(self
, tick
):
1325 tick
.decfraclength
= None
1326 if self
.fractype
== self
.fractypeauto
:
1327 if tick
.suffix
is not None:
1328 tick
.label
= self
.ratfrac(tick
)
1330 tick
.label
= self
.expfrac(tick
, self
.expfracminexp
)
1331 if tick
.label
is None:
1332 tick
.label
= self
.decfrac(tick
)
1333 elif self
.fractype
== self
.fractypedec
:
1334 tick
.label
= self
.decfrac(tick
)
1335 elif self
.fractype
== self
.fractypeexp
:
1336 tick
.label
= self
.expfrac(tick
)
1337 elif self
.fractype
== self
.fractyperat
:
1338 tick
.label
= self
.ratfrac(tick
)
1340 raise ValueError("fractype invalid")
1341 if textmodule
.mathmode
not in helper
.getattrs(tick
.labelattrs
, textmodule
._texsetting
, []):
1342 tick
.labelattrs
.append(textmodule
.mathmode
)
1344 def dolayout(self
, graph
, axis
):
1345 labeldist
= unit
.topt(unit
.length(self
.labeldist_str
, default_type
="v"))
1346 for tick
in axis
.ticks
:
1347 tick
.virtual
= axis
.convert(float(tick
) * axis
.divisor
)
1348 tick
.x
, tick
.y
= axis
._vtickpoint
(axis
, tick
.virtual
)
1349 tick
.dx
, tick
.dy
= axis
.vtickdirection(axis
, tick
.virtual
)
1350 for tick
in axis
.ticks
:
1352 if tick
.labellevel
is not None:
1353 tick
.labelattrs
= helper
.getsequenceno(self
.labelattrs
, tick
.labellevel
)
1354 if tick
.labelattrs
is not None:
1355 tick
.labelattrs
= list(helper
.ensuresequence(tick
.labelattrs
))
1356 if tick
.label
is None:
1357 tick
.suffix
= axis
.suffix
1358 self
.createtext(tick
)
1359 if self
.labeldirection
is not None:
1360 tick
.labelattrs
+= [trafo
.rotate(self
.reldirection(self
.labeldirection
, tick
.dx
, tick
.dy
))]
1361 tick
.textbox
= textmodule
._text
(tick
.x
, tick
.y
, tick
.label
, *tick
.labelattrs
)
1362 if self
.decfracequal
:
1363 maxdecfraclength
= max([tick
.decfraclength
for tick
in axis
.ticks
if tick
.labellevel
is not None and
1364 tick
.labelattrs
is not None and
1365 tick
.decfraclength
is not None])
1366 for tick
in axis
.ticks
:
1367 if (tick
.labellevel
is not None and
1368 tick
.labelattrs
is not None and
1369 tick
.decfraclength
is not None):
1370 tick
.label
= self
.decfrac(tick
, maxdecfraclength
)
1371 for tick
in axis
.ticks
:
1372 if tick
.labellevel
is not None and tick
.labelattrs
is not None:
1373 tick
.textbox
= textmodule
._text
(tick
.x
, tick
.y
, tick
.label
, *tick
.labelattrs
)
1374 if len(axis
.ticks
) > 1:
1376 for tick
in axis
.ticks
[1:]:
1377 if tick
.dx
!= axis
.ticks
[0].dx
or tick
.dy
!= axis
.ticks
[0].dy
:
1381 if equaldirection
and ((not axis
.ticks
[0].dx
and self
.labelvequalize
) or
1382 (not axis
.ticks
[0].dy
and self
.labelhequalize
)):
1383 box
._linealignequal
([tick
.textbox
for tick
in axis
.ticks
if tick
.textbox
],
1384 labeldist
, axis
.ticks
[0].dx
, axis
.ticks
[0].dy
)
1386 for tick
in axis
.ticks
:
1388 tick
.textbox
._linealign
(labeldist
, tick
.dx
, tick
.dy
)
1389 def topt_v_recursive(arg
):
1390 if helper
.issequence(arg
):
1391 # return map(topt_v_recursive, arg) needs python2.2
1392 return [unit
.topt(unit
.length(a
, default_type
="v")) for a
in arg
]
1395 return unit
.topt(unit
.length(arg
, default_type
="v"))
1396 innerticklengths
= topt_v_recursive(self
.innerticklengths_str
)
1397 outerticklengths
= topt_v_recursive(self
.outerticklengths_str
)
1398 axis
.layoutdata
._extent
= 0
1399 for tick
in axis
.ticks
:
1400 if tick
.ticklevel
is not None:
1401 tick
.innerticklength
= helper
.getitemno(innerticklengths
, tick
.ticklevel
)
1402 tick
.outerticklength
= helper
.getitemno(outerticklengths
, tick
.ticklevel
)
1403 if tick
.innerticklength
is not None and tick
.outerticklength
is None:
1404 tick
.outerticklength
= 0
1405 if tick
.outerticklength
is not None and tick
.innerticklength
is None:
1406 tick
.innerticklength
= 0
1408 if tick
.textbox
is None:
1409 if tick
.outerticklength
is not None and tick
.outerticklength
> 0:
1410 extent
= tick
.outerticklength
1412 extent
= tick
.textbox
._extent
(tick
.dx
, tick
.dy
) + labeldist
1413 if axis
.layoutdata
._extent
< extent
:
1414 axis
.layoutdata
._extent
= extent
1415 axistitlepainter
.dolayout(self
, graph
, axis
)
1417 def ratelayout(self
, graph
, axis
, dense
=1):
1418 ticktextboxes
= [tick
.textbox
for tick
in axis
.ticks
if tick
.textbox
is not None]
1419 if len(ticktextboxes
) > 1:
1421 distances
= [ticktextboxes
[i
]._boxdistance
(ticktextboxes
[i
+1]) for i
in range(len(ticktextboxes
) - 1)]
1422 except box
.BoxCrossError
:
1424 rate
= axis
.rate
._ratedistances
(distances
, dense
)
1427 if self
.labelattrs
is None:
1430 def paint(self
, graph
, axis
):
1431 for tick
in axis
.ticks
:
1432 if tick
.ticklevel
is not None:
1433 if tick
!= frac(0, 1) or self
.zerolineattrs
is None:
1434 gridattrs
= helper
.getsequenceno(self
.gridattrs
, tick
.ticklevel
)
1435 if gridattrs
is not None:
1436 graph
.stroke(axis
.vgridpath(tick
.virtual
), *helper
.ensuresequence(gridattrs
))
1437 tickattrs
= helper
.getsequenceno(self
.tickattrs
, tick
.ticklevel
)
1438 if None not in (tick
.innerticklength
, tick
.outerticklength
, tickattrs
):
1439 x1
= tick
.x
- tick
.dx
* tick
.innerticklength
1440 y1
= tick
.y
- tick
.dy
* tick
.innerticklength
1441 x2
= tick
.x
+ tick
.dx
* tick
.outerticklength
1442 y2
= tick
.y
+ tick
.dy
* tick
.outerticklength
1443 graph
.stroke(path
._line
(x1
, y1
, x2
, y2
), *helper
.ensuresequence(tickattrs
))
1444 if tick
.textbox
is not None:
1445 graph
.insert(tick
.textbox
)
1446 if self
.baselineattrs
is not None:
1447 graph
.stroke(axis
.vbaseline(axis
), *helper
.ensuresequence(self
.baselineattrs
))
1448 if self
.zerolineattrs
is not None:
1449 if len(axis
.ticks
) and axis
.ticks
[0] * axis
.ticks
[-1] < frac(0, 1):
1450 graph
.stroke(axis
.vgridpath(axis
.convert(0)), *helper
.ensuresequence(self
.zerolineattrs
))
1451 axistitlepainter
.paint(self
, graph
, axis
)
1454 class splitaxispainter(axistitlepainter
):
1456 def __init__(self
, breaklinesdist
=0.05,
1457 breaklineslength
=0.5,
1458 breaklinesangle
=-60,
1461 self
.breaklinesdist_str
= breaklinesdist
1462 self
.breaklineslength_str
= breaklineslength
1463 self
.breaklinesangle
= breaklinesangle
1464 self
.breaklinesattrs
= breaklinesattrs
1465 axistitlepainter
.__init
__(self
, **args
)
1467 def subvbaseline(self
, axis
, v1
=None, v2
=None):
1469 if self
.breaklinesattrs
is None:
1472 if axis
.vminover
is None:
1475 left
= axis
.vminover
1477 left
= axis
.vmin
+v1
*(axis
.vmax
-axis
.vmin
)
1479 if self
.breaklinesattrs
is None:
1482 if axis
.vmaxover
is None:
1485 right
= axis
.vmaxover
1487 right
= axis
.vmin
+v2
*(axis
.vmax
-axis
.vmin
)
1488 return axis
.baseaxis
.vbaseline(axis
.baseaxis
, left
, right
)
1490 def dolayout(self
, graph
, axis
):
1491 if self
.breaklinesattrs
is not None:
1492 self
.breaklinesdist
= unit
.length(self
.breaklinesdist_str
, default_type
="v")
1493 self
.breaklineslength
= unit
.length(self
.breaklineslength_str
, default_type
="v")
1494 self
._breaklinesdist
= unit
.topt(self
.breaklinesdist
)
1495 self
._breaklineslength
= unit
.topt(self
.breaklineslength
)
1496 self
.sin
= math
.sin(self
.breaklinesangle
*math
.pi
/180.0)
1497 self
.cos
= math
.cos(self
.breaklinesangle
*math
.pi
/180.0)
1498 axis
.layoutdata
._extent
= (math
.fabs(0.5 * self
._breaklinesdist
* self
.cos
) +
1499 math
.fabs(0.5 * self
._breaklineslength
* self
.sin
))
1501 axis
.layoutdata
._extent
= 0
1502 for subaxis
in axis
.axislist
:
1503 subaxis
.baseaxis
= axis
1504 subaxis
._vtickpoint
= lambda axis
, v
: axis
.baseaxis
._vtickpoint
(axis
.baseaxis
, axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
))
1505 subaxis
.vtickdirection
= lambda axis
, v
: axis
.baseaxis
.vtickdirection(axis
.baseaxis
, axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
))
1506 subaxis
.vbaseline
= self
.subvbaseline
1507 subaxis
.dolayout(graph
)
1508 if axis
.layoutdata
._extent
< subaxis
.layoutdata
._extent
:
1509 axis
.layoutdata
._extent
= subaxis
.layoutdata
._extent
1510 axistitlepainter
.dolayout(self
, graph
, axis
)
1512 def paint(self
, graph
, axis
):
1513 for subaxis
in axis
.axislist
:
1514 subaxis
.dopaint(graph
)
1515 if self
.breaklinesattrs
is not None:
1516 for subaxis1
, subaxis2
in zip(axis
.axislist
[:-1], axis
.axislist
[1:]):
1517 # use a tangent of the baseline (this is independent of the tickdirection)
1518 v
= 0.5 * (subaxis1
.vmax
+ subaxis2
.vmin
)
1519 breakline
= path
.normpath(axis
.vbaseline(axis
, v
, None)).tangent(0, self
.breaklineslength
)
1520 widthline
= path
.normpath(axis
.vbaseline(axis
, v
, None)).tangent(0, self
.breaklinesdist
).transformed(trafo
.rotate(self
.breaklinesangle
+90, *breakline
.begin()))
1521 tocenter
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(breakline
.begin(), breakline
.end()))
1522 towidth
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(widthline
.begin(), widthline
.end()))
1523 breakline
= breakline
.transformed(trafo
.translate(*tocenter
).rotated(self
.breaklinesangle
, *breakline
.begin()))
1524 breakline1
= breakline
.transformed(trafo
.translate(*towidth
))
1525 breakline2
= breakline
.transformed(trafo
.translate(-towidth
[0], -towidth
[1]))
1526 graph
.fill(path
.path(path
.moveto(*breakline1
.begin()),
1527 path
.lineto(*breakline1
.end()),
1528 path
.lineto(*breakline2
.end()),
1529 path
.lineto(*breakline2
.begin()),
1530 path
.closepath()), color
.gray
.white
)
1531 graph
.stroke(breakline1
, *helper
.ensuresequence(self
.breaklinesattrs
))
1532 graph
.stroke(breakline2
, *helper
.ensuresequence(self
.breaklinesattrs
))
1533 axistitlepainter
.paint(self
, graph
, axis
)
1536 class baraxispainter(axistitlepainter
):
1538 def __init__(self
, innerticklength
=None,
1539 outerticklength
=None,
1541 baselineattrs
=canvas
.linecap
.square
,
1543 nameattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1549 self
.innerticklength_str
= innerticklength
1550 self
.outerticklength_str
= outerticklength
1551 self
.tickattrs
= tickattrs
1552 self
.baselineattrs
= baselineattrs
1553 self
.namedist_str
= namedist
1554 self
.nameattrs
= nameattrs
1555 self
.namedirection
= namedirection
1556 self
.namepos
= namepos
1557 self
.namehequalize
= namehequalize
1558 self
.namevequalize
= namevequalize
1559 axistitlepainter
.__init
__(self
, **args
)
1561 def dolayout(self
, graph
, axis
):
1562 axis
.layoutdata
._extent
= 0
1563 if axis
.multisubaxis
:
1564 for name
, subaxis
in zip(axis
.names
, axis
.subaxis
):
1565 subaxis
.vmin
= axis
.convert((name
, 0))
1566 subaxis
.vmax
= axis
.convert((name
, 1))
1567 subaxis
.baseaxis
= axis
1568 subaxis
._vtickpoint
= lambda axis
, v
: axis
.baseaxis
._vtickpoint
(axis
.baseaxis
, axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
))
1569 subaxis
.vtickdirection
= lambda axis
, v
: axis
.baseaxis
.vtickdirection(axis
.baseaxis
, axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
))
1570 subaxis
.vbaseline
= None
1571 subaxis
.dolayout(graph
)
1572 if axis
.layoutdata
._extent
< subaxis
.layoutdata
._extent
:
1573 axis
.layoutdata
._extent
= subaxis
.layoutdata
._extent
1575 for name
in axis
.names
:
1576 v
= axis
.convert((name
, self
.namepos
))
1577 x
, y
= axis
._vtickpoint
(axis
, v
)
1578 dx
, dy
= axis
.vtickdirection(axis
, v
)
1579 axis
.namepos
.append((v
, x
, y
, dx
, dy
))
1581 if self
.nameattrs
is not None:
1582 for (v
, x
, y
, dx
, dy
), name
in zip(axis
.namepos
, axis
.names
):
1583 nameattrs
= helper
.ensurelist(self
.nameattrs
)
1584 if self
.namedirection
is not None:
1585 nameattrs
+= [trafo
.rotate(self
.reldirection(self
.namedirection
, dx
, dy
))]
1586 if axis
.texts
.has_key(name
):
1587 axis
.nameboxes
.append(textmodule
._text
(x
, y
, str(axis
.texts
[name
]), *nameattrs
))
1588 elif axis
.texts
.has_key(str(name
)):
1589 axis
.nameboxes
.append(textmodule
._text
(x
, y
, str(axis
.texts
[str(name
)]), *nameattrs
))
1591 axis
.nameboxes
.append(textmodule
._text
(x
, y
, str(name
), *nameattrs
))
1592 labeldist
= axis
.layoutdata
._extent
+ unit
.topt(unit
.length(self
.namedist_str
, default_type
="v"))
1593 if len(axis
.namepos
) > 1:
1595 for namepos
in axis
.namepos
[1:]:
1596 if namepos
[3] != axis
.namepos
[0][3] or namepos
[4] != axis
.namepos
[0][4]:
1600 if equaldirection
and ((not axis
.namepos
[0][3] and self
.namevequalize
) or
1601 (not axis
.namepos
[0][4] and self
.namehequalize
)):
1602 box
._linealignequal
(axis
.nameboxes
, labeldist
, axis
.namepos
[0][3], axis
.namepos
[0][4])
1604 for namebox
, namepos
in zip(axis
.nameboxes
, axis
.namepos
):
1605 namebox
._linealign
(labeldist
, namepos
[3], namepos
[4])
1606 if self
.innerticklength_str
is not None:
1607 axis
.innerticklength
= unit
.topt(unit
.length(self
.innerticklength_str
, default_type
="v"))
1609 if self
.outerticklength_str
is not None:
1610 axis
.innerticklength
= 0
1612 axis
.innerticklength
= None
1613 if self
.outerticklength_str
is not None:
1614 axis
.outerticklength
= unit
.topt(unit
.length(self
.outerticklength_str
, default_type
="v"))
1616 if self
.innerticklength_str
is not None:
1617 axis
.outerticklength
= 0
1619 axis
.outerticklength
= None
1620 if axis
.outerticklength
is not None and self
.tickattrs
is not None:
1621 axis
.layoutdata
._extent
+= axis
.outerticklength
1622 for (v
, x
, y
, dx
, dy
), namebox
in zip(axis
.namepos
, axis
.nameboxes
):
1623 newextent
= namebox
._extent
(dx
, dy
) + labeldist
1624 if axis
.layoutdata
._extent
< newextent
:
1625 axis
.layoutdata
._extent
= newextent
1626 graph
.mindbbox(*[namebox
.bbox() for namebox
in axis
.nameboxes
])
1627 axistitlepainter
.dolayout(self
, graph
, axis
)
1629 def paint(self
, graph
, axis
):
1630 if axis
.subaxis
is not None:
1631 if axis
.multisubaxis
:
1632 for subaxis
in axis
.subaxis
:
1633 subaxis
.dopaint(graph
)
1634 if None not in (self
.tickattrs
, axis
.innerticklength
, axis
.outerticklength
):
1635 for pos
in axis
.relsizes
:
1636 if pos
== axis
.relsizes
[0]:
1637 pos
-= axis
.firstdist
1638 elif pos
!= axis
.relsizes
[-1]:
1639 pos
-= 0.5 * axis
.dist
1640 v
= pos
/ axis
.relsizes
[-1]
1641 x
, y
= axis
._vtickpoint
(axis
, v
)
1642 dx
, dy
= axis
.vtickdirection(axis
, v
)
1643 x1
= x
- dx
* axis
.innerticklength
1644 y1
= y
- dy
* axis
.innerticklength
1645 x2
= x
+ dx
* axis
.outerticklength
1646 y2
= y
+ dy
* axis
.outerticklength
1647 graph
.stroke(path
._line
(x1
, y1
, x2
, y2
), *helper
.ensuresequence(self
.tickattrs
))
1648 if self
.baselineattrs
is not None:
1649 if axis
.vbaseline
is not None: # XXX: subbaselines (as for splitlines)
1650 graph
.stroke(axis
.vbaseline(axis
), *helper
.ensuresequence(self
.baselineattrs
))
1651 for namebox
in axis
.nameboxes
:
1652 graph
.insert(namebox
)
1653 axistitlepainter
.paint(self
, graph
, axis
)
1657 ################################################################################
1659 ################################################################################
1661 class PartitionError(Exception): pass
1665 def __init__(self
, min=None, max=None, reverse
=0, divisor
=1,
1666 datavmin
=None, datavmax
=None, tickvmin
=0, tickvmax
=1,
1667 title
=None, suffix
=None, painter
=axispainter(), dense
=None):
1668 if None not in (min, max) and min > max:
1669 min, max, reverse
= max, min, not reverse
1670 self
.fixmin
, self
.fixmax
, self
.min, self
.max, self
.reverse
= min is not None, max is not None, min, max, reverse
1672 self
.datamin
= self
.datamax
= self
.tickmin
= self
.tickmax
= None
1673 if datavmin
is None:
1677 self
.datavmin
= 0.05
1679 self
.datavmin
= datavmin
1680 if datavmax
is None:
1684 self
.datavmax
= 0.95
1686 self
.datavmax
= datavmax
1687 self
.tickvmin
= tickvmin
1688 self
.tickvmax
= tickvmax
1690 self
.divisor
= divisor
1692 self
.suffix
= suffix
1693 self
.painter
= painter
1696 self
.__setinternalrange
()
1698 def __setinternalrange(self
, min=None, max=None):
1699 if not self
.fixmin
and min is not None and (self
.min is None or min < self
.min):
1701 if not self
.fixmax
and max is not None and (self
.max is None or max > self
.max):
1703 if None not in (self
.min, self
.max):
1704 min, max, vmin
, vmax
= self
.min, self
.max, 0, 1
1706 self
.setbasepoints(((min, vmin
), (max, vmax
)))
1708 if self
.datamin
is not None and self
.convert(self
.datamin
) < self
.datavmin
:
1709 min, vmin
= self
.datamin
, self
.datavmin
1710 self
.setbasepoints(((min, vmin
), (max, vmax
)))
1711 if self
.tickmin
is not None and self
.convert(self
.tickmin
) < self
.tickvmin
:
1712 min, vmin
= self
.tickmin
, self
.tickvmin
1713 self
.setbasepoints(((min, vmin
), (max, vmax
)))
1715 if self
.datamax
is not None and self
.convert(self
.datamax
) > self
.datavmax
:
1716 max, vmax
= self
.datamax
, self
.datavmax
1717 self
.setbasepoints(((min, vmin
), (max, vmax
)))
1718 if self
.tickmax
is not None and self
.convert(self
.tickmax
) > self
.tickvmax
:
1719 max, vmax
= self
.tickmax
, self
.tickvmax
1720 self
.setbasepoints(((min, vmin
), (max, vmax
)))
1722 self
.setbasepoints(((min, vmax
), (max, vmin
)))
1724 def __getinternalrange(self
):
1725 return self
.min, self
.max, self
.datamin
, self
.datamax
, self
.tickmin
, self
.tickmax
1727 def __forceinternalrange(self
, range):
1728 self
.min, self
.max, self
.datamin
, self
.datamax
, self
.tickmin
, self
.tickmax
= range
1729 self
.__setinternalrange
()
1731 def setdatarange(self
, min, max):
1732 self
.datamin
, self
.datamax
= min, max
1733 self
.__setinternalrange
(min, max)
1735 def settickrange(self
, min, max):
1736 self
.tickmin
, self
.tickmax
= min, max
1737 self
.__setinternalrange
(min, max)
1739 def getdatarange(self
):
1742 return self
.invert(1-self
.datavmin
), self
.invert(1-self
.datavmax
)
1744 return self
.invert(self
.datavmin
), self
.invert(self
.datavmax
)
1746 def gettickrange(self
):
1749 return self
.invert(1-self
.tickvmin
), self
.invert(1-self
.tickvmax
)
1751 return self
.invert(self
.tickvmin
), self
.invert(self
.tickvmax
)
1753 def dolayout(self
, graph
):
1754 if self
.dense
is not None:
1758 min, max = self
.gettickrange()
1759 if self
.part
is not None:
1760 self
.ticks
= self
.part
.defaultpart(min/self
.divisor
,
1766 # lesspart and morepart can be called after defaultpart,
1767 # although some axes may share their autoparting ---
1768 # it works, because the axes are processed sequentially
1772 while worse
< maxworse
:
1773 if self
.part
is not None:
1774 newticks
= self
.part
.lesspart()
1777 if newticks
is not None:
1779 bestrate
= self
.rate
.ratepart(self
, self
.ticks
, dense
)
1780 variants
= [[bestrate
, self
.ticks
]]
1782 newrate
= self
.rate
.ratepart(self
, newticks
, dense
)
1783 variants
.append([newrate
, newticks
])
1784 if newrate
< bestrate
:
1792 while worse
< maxworse
:
1793 if self
.part
is not None:
1794 newticks
= self
.part
.morepart()
1797 if newticks
is not None:
1799 bestrate
= self
.rate
.ratepart(self
, self
.ticks
, dense
)
1800 variants
= [[bestrate
, self
.ticks
]]
1802 newrate
= self
.rate
.ratepart(self
, newticks
, dense
)
1803 variants
.append([newrate
, newticks
])
1804 if newrate
< bestrate
:
1813 if self
.painter
is not None:
1816 while i
< len(variants
) and (bestrate
is None or variants
[i
][0] < bestrate
):
1817 saverange
= self
.__getinternalrange
()
1818 self
.ticks
= variants
[i
][1]
1820 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
1821 self
.layoutdata
= layoutdata()
1822 self
.painter
.dolayout(graph
, self
)
1823 ratelayout
= self
.painter
.ratelayout(graph
, self
, dense
)
1824 if ratelayout
is not None:
1825 variants
[i
][0] += ratelayout
1826 variants
[i
].append(self
.layoutdata
)
1828 variants
[i
][0] = None
1829 if variants
[i
][0] is not None and (bestrate
is None or variants
[i
][0] < bestrate
):
1830 bestrate
= variants
[i
][0]
1831 self
.__forceinternalrange
(saverange
)
1833 if bestrate
is None:
1834 raise PartitionError("no valid axis partitioning found")
1835 variants
= [variant
for variant
in variants
[:i
] if variant
[0] is not None]
1837 self
.ticks
= variants
[0][1]
1838 self
.layoutdata
= variants
[0][2]
1840 for tick
in self
.ticks
:
1842 self
.layoutdata
= layoutdata()
1843 self
.layoutdata
._extent
= 0
1845 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
1848 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
1849 self
.layoutdata
= layoutdata()
1850 self
.painter
.dolayout(graph
, self
)
1851 graph
.mindbbox(*[tick
.textbox
.bbox() for tick
in self
.ticks
if tick
.textbox
is not None])
1853 def dopaint(self
, graph
):
1854 if self
.painter
is not None:
1855 self
.painter
.paint(graph
, self
)
1857 def createlinkaxis(self
, **args
):
1858 return linkaxis(self
, **args
)
1861 class linaxis(_axis
, _linmap
):
1863 def __init__(self
, part
=autolinpart(), rate
=axisrater(), **args
):
1864 _axis
.__init
__(self
, **args
)
1865 if self
.fixmin
and self
.fixmax
:
1866 self
.relsize
= self
.max - self
.min
1871 class logaxis(_axis
, _logmap
):
1873 def __init__(self
, part
=autologpart(), rate
=axisrater(ticks
=axisrater
.logticks
, labels
=axisrater
.loglabels
), **args
):
1874 _axis
.__init
__(self
, **args
)
1875 if self
.fixmin
and self
.fixmax
:
1876 self
.relsize
= math
.log(self
.max) - math
.log(self
.min)
1883 def __init__(self
, linkedaxis
, title
=None, skipticklevel
=None, skiplabellevel
=0, painter
=axispainter(zerolineattrs
=None)):
1884 self
.linkedaxis
= linkedaxis
1885 while isinstance(self
.linkedaxis
, linkaxis
):
1886 self
.linkedaxis
= self
.linkedaxis
.linkedaxis
1887 self
.fixmin
= self
.linkedaxis
.fixmin
1888 self
.fixmax
= self
.linkedaxis
.fixmax
1890 self
.min = self
.linkedaxis
.min
1892 self
.max = self
.linkedaxis
.max
1893 self
.skipticklevel
= skipticklevel
1894 self
.skiplabellevel
= skiplabellevel
1896 self
.painter
= painter
1898 def ticks(self
, ticks
):
1901 ticklevel
= _tick
.ticklevel
1902 labellevel
= _tick
.labellevel
1903 if self
.skipticklevel
is not None and ticklevel
>= self
.skipticklevel
:
1905 if self
.skiplabellevel
is not None and labellevel
>= self
.skiplabellevel
:
1907 if ticklevel
is not None or labellevel
is not None:
1908 result
.append(tick(_tick
.enum
, _tick
.denom
, ticklevel
, labellevel
))
1910 # XXX: don't forget to calculate new label positions as soon as this is moved
1911 # outside of the paint method (when rating is moved into the axispainter)
1913 def getdatarange(self
):
1914 return self
.linkedaxis
.getdatarange()
1916 def setdatarange(self
, min, max):
1917 prevrange
= self
.linkedaxis
.getdatarange()
1918 self
.linkedaxis
.setdatarange(min, max)
1919 if hasattr(self
.linkedaxis
, "ticks") and prevrange
!= self
.linkedaxis
.getdatarange():
1920 raise RuntimeError("linkaxis datarange setting performed while linked axis layout already fixed")
1922 def dolayout(self
, graph
):
1923 self
.ticks
= self
.ticks(self
.linkedaxis
.ticks
)
1924 self
.convert
= self
.linkedaxis
.convert
1925 self
.divisor
= self
.linkedaxis
.divisor
1926 self
.suffix
= self
.linkedaxis
.suffix
1927 self
.layoutdata
= layoutdata()
1928 self
.painter
.dolayout(graph
, self
)
1930 def dopaint(self
, graph
):
1931 self
.painter
.paint(graph
, self
)
1933 def createlinkaxis(self
, **args
):
1934 return linkaxis(self
.linkedaxis
)
1939 def __init__(self
, axislist
, splitlist
=0.5, splitdist
=0.1, relsizesplitdist
=1, title
=None, painter
=splitaxispainter()):
1941 self
.axislist
= axislist
1942 self
.painter
= painter
1943 self
.splitlist
= list(helper
.ensuresequence(splitlist
))
1944 self
.splitlist
.sort()
1945 if len(self
.axislist
) != len(self
.splitlist
) + 1:
1946 for subaxis
in self
.axislist
:
1947 if not isinstance(subaxis
, linkaxis
):
1948 raise ValueError("axislist and splitlist lengths do not fit together")
1949 for subaxis
in self
.axislist
:
1950 if isinstance(subaxis
, linkaxis
):
1951 subaxis
.vmin
= subaxis
.linkedaxis
.vmin
1952 subaxis
.vminover
= subaxis
.linkedaxis
.vminover
1953 subaxis
.vmax
= subaxis
.linkedaxis
.vmax
1954 subaxis
.vmaxover
= subaxis
.linkedaxis
.vmaxover
1958 self
.axislist
[0].vmin
= 0
1959 self
.axislist
[0].vminover
= None
1960 self
.axislist
[-1].vmax
= 1
1961 self
.axislist
[-1].vmaxover
= None
1962 for i
in xrange(len(self
.splitlist
)):
1963 if self
.splitlist
[i
] is not None:
1964 self
.axislist
[i
].vmax
= self
.splitlist
[i
] - 0.5*splitdist
1965 self
.axislist
[i
].vmaxover
= self
.splitlist
[i
]
1966 self
.axislist
[i
+1].vmin
= self
.splitlist
[i
] + 0.5*splitdist
1967 self
.axislist
[i
+1].vminover
= self
.splitlist
[i
]
1969 while i
< len(self
.axislist
):
1970 if self
.axislist
[i
].vmax
is None:
1971 j
= relsize
= relsize2
= 0
1972 while self
.axislist
[i
+ j
].vmax
is None:
1973 relsize
+= self
.axislist
[i
+ j
].relsize
+ relsizesplitdist
1975 relsize
+= self
.axislist
[i
+ j
].relsize
1976 vleft
= self
.axislist
[i
].vmin
1977 vright
= self
.axislist
[i
+ j
].vmax
1978 for k
in range(i
, i
+ j
):
1979 relsize2
+= self
.axislist
[k
].relsize
1980 self
.axislist
[k
].vmax
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
1981 relsize2
+= 0.5 * relsizesplitdist
1982 self
.axislist
[k
].vmaxover
= self
.axislist
[k
+ 1].vminover
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
1983 relsize2
+= 0.5 * relsizesplitdist
1984 self
.axislist
[k
+1].vmin
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
1985 if i
== 0 and i
+ j
+ 1 == len(self
.axislist
):
1986 self
.relsize
= relsize
1991 self
.fixmin
= self
.axislist
[0].fixmin
1993 self
.min = self
.axislist
[0].min
1994 self
.fixmax
= self
.axislist
[-1].fixmax
1996 self
.max = self
.axislist
[-1].max
2000 def getdatarange(self
):
2001 min = self
.axislist
[0].getdatarange()
2002 max = self
.axislist
[-1].getdatarange()
2004 return min[0], max[1]
2008 def setdatarange(self
, min, max):
2009 self
.axislist
[0].setdatarange(min, None)
2010 self
.axislist
[-1].setdatarange(None, max)
2012 def gettickrange(self
):
2013 min = self
.axislist
[0].gettickrange()
2014 max = self
.axislist
[-1].gettickrange()
2016 return min[0], max[1]
2020 def settickrange(self
, min, max):
2021 self
.axislist
[0].settickrange(min, None)
2022 self
.axislist
[-1].settickrange(None, max)
2024 def convert(self
, value
):
2025 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
2026 if value
< self
.axislist
[0].max:
2027 return self
.axislist
[0].vmin
+ self
.axislist
[0].convert(value
)*(self
.axislist
[0].vmax
-self
.axislist
[0].vmin
)
2028 for axis
in self
.axislist
[1:-1]:
2029 if value
> axis
.min and value
< axis
.max:
2030 return axis
.vmin
+ axis
.convert(value
)*(axis
.vmax
-axis
.vmin
)
2031 if value
> self
.axislist
[-1].min:
2032 return self
.axislist
[-1].vmin
+ self
.axislist
[-1].convert(value
)*(self
.axislist
[-1].vmax
-self
.axislist
[-1].vmin
)
2033 raise ValueError("value couldn't be assigned to a split region")
2035 def dolayout(self
, graph
):
2036 self
.layoutdata
= layoutdata()
2037 self
.painter
.dolayout(graph
, self
)
2039 def dopaint(self
, graph
):
2040 self
.painter
.paint(graph
, self
)
2042 def createlinkaxis(self
, painter
=None, *args
):
2044 return splitaxis([x
.createlinkaxis() for x
in self
.axislist
], splitlist
=None)
2045 if len(args
) != len(self
.axislist
):
2046 raise IndexError("length of the argument list doesn't fit to split number")
2048 painter
= self
.painter
2049 return splitaxis([x
.createlinkaxis(**arg
) for x
, arg
in zip(self
.axislist
, args
)], painter
=painter
)
2054 def __init__(self
, subaxis
=None, multisubaxis
=0, title
=None, dist
=0.5, firstdist
=None, lastdist
=None, names
=None, texts
={}, painter
=baraxispainter()):
2056 if firstdist
is not None:
2057 self
.firstdist
= firstdist
2059 self
.firstdist
= 0.5 * dist
2060 if lastdist
is not None:
2061 self
.lastdist
= lastdist
2063 self
.lastdist
= 0.5 * dist
2064 self
.relsizes
= None
2067 for name
in helper
.ensuresequence(names
):
2069 self
.fixnames
= names
is not None
2070 self
.multisubaxis
= multisubaxis
2071 if self
.multisubaxis
:
2072 self
.createsubaxis
= subaxis
2073 self
.subaxis
= [self
.createsubaxis
.createsubaxis() for name
in self
.names
]
2075 self
.subaxis
= subaxis
2079 self
.painter
= painter
2081 def getdatarange(self
):
2084 def setname(self
, name
, *subnames
):
2085 # setting self.relsizes to None forces later recalculation
2086 if not self
.fixnames
:
2087 if name
not in self
.names
:
2088 self
.relsizes
= None
2089 self
.names
.append(name
)
2090 if self
.multisubaxis
:
2091 self
.subaxis
.append(self
.createsubaxis
.createsubaxis())
2092 if (not self
.fixnames
or name
in self
.names
) and len(subnames
):
2093 if self
.multisubaxis
:
2094 if self
.subaxis
[self
.names
.index(name
)].setname(*subnames
):
2095 self
.relsizes
= None
2097 #print self.subaxis, self.multisubaxis, subnames, name
2098 if self
.subaxis
.setname(*subnames
):
2099 self
.relsizes
= None
2100 return self
.relsizes
is not None
2102 def updaterelsizes(self
):
2103 self
.relsizes
= [i
*self
.dist
+ self
.firstdist
for i
in range(len(self
.names
) + 1)]
2104 self
.relsizes
[-1] += self
.lastdist
- self
.dist
2105 if self
.multisubaxis
:
2107 for i
in range(1, len(self
.relsizes
)):
2108 self
.subaxis
[i
-1].updaterelsizes()
2109 subrelsize
+= self
.subaxis
[i
-1].relsizes
[-1]
2110 self
.relsizes
[i
] += subrelsize
2112 if self
.subaxis
is None:
2115 self
.subaxis
.updaterelsizes()
2116 subrelsize
= self
.subaxis
.relsizes
[-1]
2117 for i
in range(1, len(self
.relsizes
)):
2118 self
.relsizes
[i
] += i
* subrelsize
2120 def convert(self
, value
):
2121 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
2122 if not self
.relsizes
:
2123 self
.updaterelsizes()
2124 pos
= self
.names
.index(value
[0])
2126 if self
.subaxis
is None:
2129 if self
.multisubaxis
:
2130 subvalue
= value
[1] * self
.subaxis
[pos
].relsizes
[-1]
2132 subvalue
= value
[1] * self
.subaxis
.relsizes
[-1]
2134 if self
.multisubaxis
:
2135 subvalue
= self
.subaxis
[pos
].convert(value
[1:]) * self
.subaxis
[pos
].relsizes
[-1]
2137 subvalue
= self
.subaxis
.convert(value
[1:]) * self
.subaxis
.relsizes
[-1]
2138 return (self
.relsizes
[pos
] + subvalue
) / float(self
.relsizes
[-1])
2140 def dolayout(self
, graph
):
2141 self
.layoutdata
= layoutdata()
2142 self
.painter
.dolayout(graph
, self
)
2144 def dopaint(self
, graph
):
2145 self
.painter
.paint(graph
, self
)
2147 def createlinkaxis(self
, **args
):
2148 if self
.subaxis
is not None:
2149 if self
.multisubaxis
:
2150 subaxis
= [subaxis
.createlinkaxis() for subaxis
in self
.subaxis
]
2152 subaxis
= self
.subaxis
.createlinkaxis()
2155 return baraxis(subaxis
=subaxis
, dist
=self
.dist
, firstdist
=self
.firstdist
, lastdist
=self
.lastdist
, **args
)
2157 createsubaxis
= createlinkaxis
2160 ################################################################################
2162 ################################################################################
2165 # g = graph.graphxy(key=graph.key())
2166 # g.addkey(graph.key(), ...)
2171 def __init__(self
, dist
="0.2 cm", pos
= "tr", hinside
= 1, vinside
= 1, hdist
="0.6 cm", vdist
="0.4 cm",
2172 symbolwidth
="0.5 cm", symbolheight
="0.25 cm", symbolspace
="0.2 cm",
2173 textattrs
=textmodule
.vshift
.mathaxis
):
2174 self
.dist_str
= dist
2176 self
.hinside
= hinside
2177 self
.vinside
= vinside
2178 self
.hdist_str
= hdist
2179 self
.vdist_str
= vdist
2180 self
.symbolwidth_str
= symbolwidth
2181 self
.symbolheight_str
= symbolheight
2182 self
.symbolspace_str
= symbolspace
2183 self
.textattrs
= textattrs
2184 self
.plotinfos
= None
2185 if self
.pos
in ("tr", "rt"):
2188 elif self
.pos
in ("br", "rb"):
2191 elif self
.pos
in ("tl", "lt"):
2194 elif self
.pos
in ("bl", "lb"):
2198 raise RuntimeError("invalid pos attribute")
2200 def setplotinfos(self
, *plotinfos
):
2201 """set the plotinfos to be used in the key
2202 - call it exactly once"""
2203 if self
.plotinfos
is not None:
2204 raise RuntimeError("setplotinfo is called multiple times")
2205 self
.plotinfos
= plotinfos
2207 def dolayout(self
, graph
):
2208 """creates the layout of the key"""
2209 self
._dist
= unit
.topt(unit
.length(self
.dist_str
, default_type
="v"))
2210 self
._hdist
= unit
.topt(unit
.length(self
.hdist_str
, default_type
="v"))
2211 self
._vdist
= unit
.topt(unit
.length(self
.vdist_str
, default_type
="v"))
2212 self
._symbolwidth
= unit
.topt(unit
.length(self
.symbolwidth_str
, default_type
="v"))
2213 self
._symbolheight
= unit
.topt(unit
.length(self
.symbolheight_str
, default_type
="v"))
2214 self
._symbolspace
= unit
.topt(unit
.length(self
.symbolspace_str
, default_type
="v"))
2216 for plotinfo
in self
.plotinfos
:
2217 self
.titles
.append(graph
.texrunner
._text
(0, 0, plotinfo
.data
.title
, *helper
.ensuresequence(self
.textattrs
)))
2218 box
._tile
(self
.titles
, self
._dist
, 0, -1)
2219 box
._linealignequal
(self
.titles
, self
._symbolwidth
+ self
._symbolspace
, 1, 0)
2222 """return a bbox for the key
2223 method should be called after dolayout"""
2224 result
= self
.titles
[0].bbox()
2225 for title
in self
.titles
[1:]:
2226 result
= result
+ title
.bbox() + bbox
._bbox
(0, title
.center
[1] - 0.5 * self
._symbolheight
,
2227 0, title
.center
[1] + 0.5 * self
._symbolheight
)
2230 def paint(self
, c
, x
, y
):
2231 """paint the graph key into a canvas c at the position x and y (in postscript points)
2232 - method should be called after dolayout
2233 - the x, y alignment might be calculated by the graph using:
2234 - the bbox of the key as returned by the keys bbox method
2235 - the attributes _hdist, _vdist, hinside, and vinside of the key
2236 - the dimension and geometry of the graph"""
2237 sc
= c
.insert(canvas
.canvas(trafo
._translate
(x
, y
)))
2238 for plotinfo
, title
in zip(self
.plotinfos
, self
.titles
):
2239 plotinfo
.style
.key(sc
, 0, -0.5 * self
._symbolheight
+ title
.center
[1],
2240 self
._symbolwidth
, self
._symbolheight
)
2244 ################################################################################
2246 ################################################################################
2251 def __init__(self
, data
, style
):
2256 class graphxy(canvas
.canvas
):
2260 def clipcanvas(self
):
2261 return self
.insert(canvas
.canvas(canvas
.clip(path
._rect
(self
._xpos
, self
._ypos
, self
._width
, self
._height
))))
2263 def plot(self
, data
, style
=None):
2265 raise RuntimeError("layout setup was already performed")
2267 if helper
.issequence(data
):
2268 raise RuntimeError("list plot needs an explicit style")
2269 if self
.defaultstyle
.has_key(data
.defaultstyle
):
2270 style
= self
.defaultstyle
[data
.defaultstyle
].iterate()
2272 style
= data
.defaultstyle()
2273 self
.defaultstyle
[data
.defaultstyle
] = style
2276 for d
in helper
.ensuresequence(data
):
2278 style
= style
.iterate()
2281 d
.setstyle(self
, style
)
2282 plotinfos
.append(plotinfo(d
, style
))
2283 self
.plotinfos
.extend(plotinfos
)
2284 if helper
.issequence(data
):
2288 def addkey(self
, key
, *plotinfos
):
2290 raise RuntimeError("layout setup was already performed")
2291 self
.addkeys
.append((key
, plotinfos
))
2293 def _vxtickpoint(self
, axis
, v
):
2294 return (self
._xpos
+v
*self
._width
, axis
.axispos
)
2296 def _vytickpoint(self
, axis
, v
):
2297 return (axis
.axispos
, self
._ypos
+v
*self
._height
)
2299 def vtickdirection(self
, axis
, v
):
2300 return axis
.fixtickdirection
2302 def _pos(self
, x
, y
, xaxis
=None, yaxis
=None):
2303 if xaxis
is None: xaxis
= self
.axes
["x"]
2304 if yaxis
is None: yaxis
= self
.axes
["y"]
2305 return self
._xpos
+xaxis
.convert(x
)*self
._width
, self
._ypos
+yaxis
.convert(y
)*self
._height
2307 def pos(self
, x
, y
, xaxis
=None, yaxis
=None):
2308 if xaxis
is None: xaxis
= self
.axes
["x"]
2309 if yaxis
is None: yaxis
= self
.axes
["y"]
2310 return self
.xpos
+xaxis
.convert(x
)*self
.width
, self
.ypos
+yaxis
.convert(y
)*self
.height
2312 def _vpos(self
, vx
, vy
):
2313 return self
._xpos
+vx
*self
._width
, self
._ypos
+vy
*self
._height
2315 def vpos(self
, vx
, vy
):
2316 return self
.xpos
+vx
*self
.width
, self
.ypos
+vy
*self
.height
2318 def xbaseline(self
, axis
, x1
, x2
, shift
=0, xaxis
=None):
2319 if xaxis
is None: xaxis
= self
.axes
["x"]
2320 v1
, v2
= xaxis
.convert(x1
), xaxis
.convert(x2
)
2321 return path
._line
(self
._xpos
+v1
*self
._width
, axis
.axispos
+shift
,
2322 self
._xpos
+v2
*self
._width
, axis
.axispos
+shift
)
2324 def ybaseline(self
, axis
, y1
, y2
, shift
=0, yaxis
=None):
2325 if yaxis
is None: yaxis
= self
.axes
["y"]
2326 v1
, v2
= yaxis
.convert(y1
), yaxis
.convert(y2
)
2327 return path
._line
(axis
.axispos
+shift
, self
._ypos
+v1
*self
._height
,
2328 axis
.axispos
+shift
, self
._ypos
+v2
*self
._height
)
2330 def vxbaseline(self
, axis
, v1
=None, v2
=None, shift
=0):
2331 if v1
is None: v1
= 0
2332 if v2
is None: v2
= 1
2333 return path
._line
(self
._xpos
+v1
*self
._width
, axis
.axispos
+shift
,
2334 self
._xpos
+v2
*self
._width
, axis
.axispos
+shift
)
2336 def vybaseline(self
, axis
, v1
=None, v2
=None, shift
=0):
2337 if v1
is None: v1
= 0
2338 if v2
is None: v2
= 1
2339 return path
._line
(axis
.axispos
+shift
, self
._ypos
+v1
*self
._height
,
2340 axis
.axispos
+shift
, self
._ypos
+v2
*self
._height
)
2342 def xgridpath(self
, x
, xaxis
=None):
2343 if xaxis
is None: xaxis
= self
.axes
["x"]
2344 v
= xaxis
.convert(x
)
2345 return path
._line
(self
._xpos
+v
*self
._width
, self
._ypos
,
2346 self
._xpos
+v
*self
._width
, self
._ypos
+self
._height
)
2348 def ygridpath(self
, y
, yaxis
=None):
2349 if yaxis
is None: yaxis
= self
.axes
["y"]
2350 v
= yaxis
.convert(y
)
2351 return path
._line
(self
._xpos
, self
._ypos
+v
*self
._height
,
2352 self
._xpos
+self
._width
, self
._ypos
+v
*self
._height
)
2354 def vxgridpath(self
, v
):
2355 return path
._line
(self
._xpos
+v
*self
._width
, self
._ypos
,
2356 self
._xpos
+v
*self
._width
, self
._ypos
+self
._height
)
2358 def vygridpath(self
, v
):
2359 return path
._line
(self
._xpos
, self
._ypos
+v
*self
._height
,
2360 self
._xpos
+self
._width
, self
._ypos
+v
*self
._height
)
2362 def _addpos(self
, x
, y
, dx
, dy
):
2365 def _connect(self
, x1
, y1
, x2
, y2
):
2366 return path
._lineto
(x2
, y2
)
2368 def keynum(self
, key
):
2370 while key
[0] in string
.letters
:
2376 def gatherranges(self
):
2378 for plotinfo
in self
.plotinfos
:
2379 pdranges
= plotinfo
.data
.getranges()
2380 if pdranges
is not None:
2381 for key
in pdranges
.keys():
2382 if key
not in ranges
.keys():
2383 ranges
[key
] = pdranges
[key
]
2385 ranges
[key
] = (min(ranges
[key
][0], pdranges
[key
][0]),
2386 max(ranges
[key
][1], pdranges
[key
][1]))
2387 # known ranges are also set as ranges for the axes
2388 for key
, axis
in self
.axes
.items():
2389 if key
in ranges
.keys():
2390 axis
.setdatarange(*ranges
[key
])
2391 ranges
[key
] = axis
.getdatarange()
2392 if ranges
[key
] is None:
2396 def removedomethod(self
, method
):
2400 self
.domethods
.remove(method
)
2406 if not self
.removedomethod(self
.dolayout
): return
2408 # create list of ranges
2410 ranges
= self
.gatherranges()
2411 # 2. calculate additional ranges out of known ranges
2412 for plotinfo
in self
.plotinfos
:
2413 plotinfo
.data
.setranges(ranges
)
2414 # 3. gather ranges again
2417 # do the layout for all axes
2418 axesdist
= unit
.topt(unit
.length(self
.axesdist_str
, default_type
="v"))
2419 XPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[0])
2420 YPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[1])
2421 self
._xaxisextents
= [0, 0]
2422 self
._yaxisextents
= [0, 0]
2423 needxaxisdist
= [0, 0]
2424 needyaxisdist
= [0, 0]
2425 items
= list(self
.axes
.items())
2426 items
.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
2427 for key
, axis
in items
:
2428 num
= self
.keynum(key
)
2429 num2
= 1 - num
% 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
2430 num3
= 1 - 2 * (num
% 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
2431 if XPattern
.match(key
):
2432 if needxaxisdist
[num2
]:
2433 self
._xaxisextents
[num2
] += axesdist
2434 axis
.axispos
= self
._ypos
+num2
*self
._height
+ num3
*self
._xaxisextents
[num2
]
2435 axis
._vtickpoint
= self
._vxtickpoint
2436 axis
.fixtickdirection
= (0, num3
)
2437 axis
.vgridpath
= self
.vxgridpath
2438 axis
.vbaseline
= self
.vxbaseline
2439 axis
.gridpath
= self
.xgridpath
2440 axis
.baseline
= self
.xbaseline
2441 elif YPattern
.match(key
):
2442 if needyaxisdist
[num2
]:
2443 self
._yaxisextents
[num2
] += axesdist
2444 axis
.axispos
= self
._xpos
+num2
*self
._width
+ num3
*self
._yaxisextents
[num2
]
2445 axis
._vtickpoint
= self
._vytickpoint
2446 axis
.fixtickdirection
= (num3
, 0)
2447 axis
.vgridpath
= self
.vygridpath
2448 axis
.vbaseline
= self
.vybaseline
2449 axis
.gridpath
= self
.ygridpath
2450 axis
.baseline
= self
.ybaseline
2452 raise ValueError("Axis key '%s' not allowed" % key
)
2453 axis
.vtickdirection
= self
.vtickdirection
2455 if XPattern
.match(key
):
2456 self
._xaxisextents
[num2
] += axis
.layoutdata
._extent
2457 needxaxisdist
[num2
] = 1
2458 if YPattern
.match(key
):
2459 self
._yaxisextents
[num2
] += axis
.layoutdata
._extent
2460 needyaxisdist
[num2
] = 1
2462 def dobackground(self
):
2464 if not self
.removedomethod(self
.dobackground
): return
2465 if self
.backgroundattrs
is not None:
2466 self
.draw(path
._rect
(self
._xpos
, self
._ypos
, self
._width
, self
._height
),
2467 *helper
.ensuresequence(self
.backgroundattrs
))
2471 if not self
.removedomethod(self
.doaxes
): return
2472 for axis
in self
.axes
.values():
2477 if not self
.removedomethod(self
.dodata
): return
2478 for plotinfo
in self
.plotinfos
:
2479 plotinfo
.data
.draw(self
)
2481 def _dokey(self
, key
, *plotinfos
):
2482 key
.setplotinfos(*plotinfos
)
2487 x
= self
._xpos
+ self
._width
- bbox
.urx
- key
._hdist
2489 x
= self
._xpos
+ self
._width
- bbox
.llx
+ key
._hdist
2492 x
= self
._xpos
- bbox
.llx
+ key
._hdist
2494 x
= self
._xpos
- bbox
.urx
- key
._hdist
2497 y
= self
._ypos
+ self
._height
- bbox
.ury
- key
._vdist
2499 y
= self
._ypos
+ self
._height
- bbox
.lly
+ key
._vdist
2502 y
= self
._ypos
- bbox
.lly
+ key
._vdist
2504 y
= self
._ypos
- bbox
.ury
- key
._vdist
2505 self
.mindbbox(bbox
.transformed(trafo
._translate
(x
, y
)))
2506 key
.paint(self
, x
, y
)
2510 if not self
.removedomethod(self
.dokey
): return
2511 if self
.key
is not None:
2512 self
._dokey
(self
.key
, *self
.plotinfos
)
2513 for key
, plotinfos
in self
.addkeys
:
2514 self
._dokey
(key
, *plotinfos
)
2517 while len(self
.domethods
):
2520 def initwidthheight(self
, width
, height
, ratio
):
2521 if (width
is not None) and (height
is None):
2522 self
.width
= unit
.length(width
)
2523 self
.height
= (1.0/ratio
) * self
.width
2524 elif (height
is not None) and (width
is None):
2525 self
.height
= unit
.length(height
)
2526 self
.width
= ratio
* self
.height
2528 self
.width
= unit
.length(width
)
2529 self
.height
= unit
.length(height
)
2530 self
._width
= unit
.topt(self
.width
)
2531 self
._height
= unit
.topt(self
.height
)
2532 if self
._width
<= 0: raise ValueError("width <= 0")
2533 if self
._height
<= 0: raise ValueError("height <= 0")
2535 def initaxes(self
, axes
, addlinkaxes
=0):
2536 for key
in self
.Names
:
2537 if not axes
.has_key(key
):
2538 axes
[key
] = linaxis()
2539 elif axes
[key
] is None:
2542 if not axes
.has_key(key
+ "2") and axes
.has_key(key
):
2543 axes
[key
+ "2"] = axes
[key
].createlinkaxis()
2544 elif axes
[key
+ "2"] is None:
2548 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, ratio
=goldenmean
,
2549 key
=None, backgroundattrs
=None, dense
=1, axesdist
="0.8 cm", **axes
):
2550 canvas
.canvas
.__init
__(self
)
2551 self
.xpos
= unit
.length(xpos
)
2552 self
.ypos
= unit
.length(ypos
)
2553 self
._xpos
= unit
.topt(self
.xpos
)
2554 self
._ypos
= unit
.topt(self
.ypos
)
2555 self
.initwidthheight(width
, height
, ratio
)
2556 self
.initaxes(axes
, 1)
2558 self
.backgroundattrs
= backgroundattrs
2560 self
.axesdist_str
= axesdist
2562 self
.domethods
= [self
.dolayout
, self
.dobackground
, self
.doaxes
, self
.dodata
, self
.dokey
]
2564 self
.defaultstyle
= {}
2566 self
.mindbboxes
= []
2568 def mindbbox(self
, *boxes
):
2569 self
.mindbboxes
.extend(boxes
)
2573 result
= bbox
._bbox
(self
._xpos
- self
._yaxisextents
[0],
2574 self
._ypos
- self
._xaxisextents
[0],
2575 self
._xpos
+ self
._width
+ self
._yaxisextents
[1],
2576 self
._ypos
+ self
._height
+ self
._xaxisextents
[1])
2577 for box
in self
.mindbboxes
:
2578 result
= result
+ box
2581 def write(self
, file):
2583 canvas
.canvas
.write(self
, file)
2587 # some thoughts, but deferred right now
2589 # class graphxyz(graphxy):
2591 # Names = "x", "y", "z"
2593 # def _vxtickpoint(self, axis, v):
2594 # return self._vpos(v, axis.vypos, axis.vzpos)
2596 # def _vytickpoint(self, axis, v):
2597 # return self._vpos(axis.vxpos, v, axis.vzpos)
2599 # def _vztickpoint(self, axis, v):
2600 # return self._vpos(axis.vxpos, axis.vypos, v)
2602 # def vxtickdirection(self, axis, v):
2603 # x1, y1 = self._vpos(v, axis.vypos, axis.vzpos)
2604 # x2, y2 = self._vpos(v, 0.5, 0)
2605 # dx, dy = x1 - x2, y1 - y2
2606 # norm = math.sqrt(dx*dx + dy*dy)
2607 # return dx/norm, dy/norm
2609 # def vytickdirection(self, axis, v):
2610 # x1, y1 = self._vpos(axis.vxpos, v, axis.vzpos)
2611 # x2, y2 = self._vpos(0.5, v, 0)
2612 # dx, dy = x1 - x2, y1 - y2
2613 # norm = math.sqrt(dx*dx + dy*dy)
2614 # return dx/norm, dy/norm
2616 # def vztickdirection(self, axis, v):
2618 # x1, y1 = self._vpos(axis.vxpos, axis.vypos, v)
2619 # x2, y2 = self._vpos(0.5, 0.5, v)
2620 # dx, dy = x1 - x2, y1 - y2
2621 # norm = math.sqrt(dx*dx + dy*dy)
2622 # return dx/norm, dy/norm
2624 # def _pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
2625 # if xaxis is None: xaxis = self.axes["x"]
2626 # if yaxis is None: yaxis = self.axes["y"]
2627 # if zaxis is None: zaxis = self.axes["z"]
2628 # return self._vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
2630 # def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
2631 # if xaxis is None: xaxis = self.axes["x"]
2632 # if yaxis is None: yaxis = self.axes["y"]
2633 # if zaxis is None: zaxis = self.axes["z"]
2634 # return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
2636 # def _vpos(self, vx, vy, vz):
2637 # x, y, z = (vx - 0.5)*self._depth, (vy - 0.5)*self._width, (vz - 0.5)*self._height
2638 # d0 = float(self.a[0]*self.b[1]*(z-self.eye[2])
2639 # + self.a[2]*self.b[0]*(y-self.eye[1])
2640 # + self.a[1]*self.b[2]*(x-self.eye[0])
2641 # - self.a[2]*self.b[1]*(x-self.eye[0])
2642 # - self.a[0]*self.b[2]*(y-self.eye[1])
2643 # - self.a[1]*self.b[0]*(z-self.eye[2]))
2644 # da = (self.eye[0]*self.b[1]*(z-self.eye[2])
2645 # + self.eye[2]*self.b[0]*(y-self.eye[1])
2646 # + self.eye[1]*self.b[2]*(x-self.eye[0])
2647 # - self.eye[2]*self.b[1]*(x-self.eye[0])
2648 # - self.eye[0]*self.b[2]*(y-self.eye[1])
2649 # - self.eye[1]*self.b[0]*(z-self.eye[2]))
2650 # db = (self.a[0]*self.eye[1]*(z-self.eye[2])
2651 # + self.a[2]*self.eye[0]*(y-self.eye[1])
2652 # + self.a[1]*self.eye[2]*(x-self.eye[0])
2653 # - self.a[2]*self.eye[1]*(x-self.eye[0])
2654 # - self.a[0]*self.eye[2]*(y-self.eye[1])
2655 # - self.a[1]*self.eye[0]*(z-self.eye[2]))
2656 # return da/d0 + self._xpos, db/d0 + self._ypos
2658 # def vpos(self, vx, vy, vz):
2659 # tx, ty = self._vpos(vx, vy, vz)
2660 # return unit.t_pt(tx), unit.t_pt(ty)
2662 # def xbaseline(self, axis, x1, x2, shift=0, xaxis=None):
2663 # if xaxis is None: xaxis = self.axes["x"]
2664 # return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2), shift)
2666 # def ybaseline(self, axis, y1, y2, shift=0, yaxis=None):
2667 # if yaxis is None: yaxis = self.axes["y"]
2668 # return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2), shift)
2670 # def zbaseline(self, axis, z1, z2, shift=0, zaxis=None):
2671 # if zaxis is None: zaxis = self.axes["z"]
2672 # return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2), shift)
2674 # def vxbaseline(self, axis, v1, v2, shift=0):
2675 # return (path._line(*(self._vpos(v1, 0, 0) + self._vpos(v2, 0, 0))) +
2676 # path._line(*(self._vpos(v1, 0, 1) + self._vpos(v2, 0, 1))) +
2677 # path._line(*(self._vpos(v1, 1, 1) + self._vpos(v2, 1, 1))) +
2678 # path._line(*(self._vpos(v1, 1, 0) + self._vpos(v2, 1, 0))))
2680 # def vybaseline(self, axis, v1, v2, shift=0):
2681 # return (path._line(*(self._vpos(0, v1, 0) + self._vpos(0, v2, 0))) +
2682 # path._line(*(self._vpos(0, v1, 1) + self._vpos(0, v2, 1))) +
2683 # path._line(*(self._vpos(1, v1, 1) + self._vpos(1, v2, 1))) +
2684 # path._line(*(self._vpos(1, v1, 0) + self._vpos(1, v2, 0))))
2686 # def vzbaseline(self, axis, v1, v2, shift=0):
2687 # return (path._line(*(self._vpos(0, 0, v1) + self._vpos(0, 0, v2))) +
2688 # path._line(*(self._vpos(0, 1, v1) + self._vpos(0, 1, v2))) +
2689 # path._line(*(self._vpos(1, 1, v1) + self._vpos(1, 1, v2))) +
2690 # path._line(*(self._vpos(1, 0, v1) + self._vpos(1, 0, v2))))
2692 # def xgridpath(self, x, xaxis=None):
2694 # if xaxis is None: xaxis = self.axes["x"]
2695 # v = xaxis.convert(x)
2696 # return path._line(self._xpos+v*self._width, self._ypos,
2697 # self._xpos+v*self._width, self._ypos+self._height)
2699 # def ygridpath(self, y, yaxis=None):
2701 # if yaxis is None: yaxis = self.axes["y"]
2702 # v = yaxis.convert(y)
2703 # return path._line(self._xpos, self._ypos+v*self._height,
2704 # self._xpos+self._width, self._ypos+v*self._height)
2706 # def zgridpath(self, z, zaxis=None):
2708 # if zaxis is None: zaxis = self.axes["z"]
2709 # v = zaxis.convert(z)
2710 # return path._line(self._xpos, self._zpos+v*self._height,
2711 # self._xpos+self._width, self._zpos+v*self._height)
2713 # def vxgridpath(self, v):
2714 # return path.path(path._moveto(*self._vpos(v, 0, 0)),
2715 # path._lineto(*self._vpos(v, 0, 1)),
2716 # path._lineto(*self._vpos(v, 1, 1)),
2717 # path._lineto(*self._vpos(v, 1, 0)),
2720 # def vygridpath(self, v):
2721 # return path.path(path._moveto(*self._vpos(0, v, 0)),
2722 # path._lineto(*self._vpos(0, v, 1)),
2723 # path._lineto(*self._vpos(1, v, 1)),
2724 # path._lineto(*self._vpos(1, v, 0)),
2727 # def vzgridpath(self, v):
2728 # return path.path(path._moveto(*self._vpos(0, 0, v)),
2729 # path._lineto(*self._vpos(0, 1, v)),
2730 # path._lineto(*self._vpos(1, 1, v)),
2731 # path._lineto(*self._vpos(1, 0, v)),
2734 # def _addpos(self, x, y, dx, dy):
2738 # def _connect(self, x1, y1, x2, y2):
2740 # return path._lineto(x2, y2)
2744 # if not self.removedomethod(self.doaxes): return
2745 # axesdist = unit.topt(unit.length(self.axesdist_str, default_type="v"))
2746 # XPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[0])
2747 # YPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[1])
2748 # ZPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[2])
2749 # items = list(self.axes.items())
2750 # items.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
2751 # for key, axis in items:
2752 # num = self.keynum(key)
2753 # num2 = 1 - num % 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
2754 # num3 = 1 - 2 * (num % 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
2755 # if XPattern.match(key):
2758 # axis._vtickpoint = self._vxtickpoint
2759 # axis.vgridpath = self.vxgridpath
2760 # axis.vbaseline = self.vxbaseline
2761 # axis.vtickdirection = self.vxtickdirection
2762 # elif YPattern.match(key):
2765 # axis._vtickpoint = self._vytickpoint
2766 # axis.vgridpath = self.vygridpath
2767 # axis.vbaseline = self.vybaseline
2768 # axis.vtickdirection = self.vytickdirection
2769 # elif ZPattern.match(key):
2772 # axis._vtickpoint = self._vztickpoint
2773 # axis.vgridpath = self.vzgridpath
2774 # axis.vbaseline = self.vzbaseline
2775 # axis.vtickdirection = self.vztickdirection
2777 # raise ValueError("Axis key '%s' not allowed" % key)
2778 # if axis.painter is not None:
2779 # axis.dopaint(self)
2780 # # if XPattern.match(key):
2781 # # self._xaxisextents[num2] += axis._extent
2782 # # needxaxisdist[num2] = 1
2783 # # if YPattern.match(key):
2784 # # self._yaxisextents[num2] += axis._extent
2785 # # needyaxisdist[num2] = 1
2787 # def __init__(self, tex, xpos=0, ypos=0, width=None, height=None, depth=None,
2788 # phi=30, theta=30, distance=1,
2789 # backgroundattrs=None, axesdist="0.8 cm", **axes):
2790 # canvas.canvas.__init__(self)
2794 # self._xpos = unit.topt(xpos)
2795 # self._ypos = unit.topt(ypos)
2796 # self._width = unit.topt(width)
2797 # self._height = unit.topt(height)
2798 # self._depth = unit.topt(depth)
2799 # self.width = width
2800 # self.height = height
2801 # self.depth = depth
2802 # if self._width <= 0: raise ValueError("width < 0")
2803 # if self._height <= 0: raise ValueError("height < 0")
2804 # if self._depth <= 0: raise ValueError("height < 0")
2805 # self._distance = distance*math.sqrt(self._width*self._width+
2806 # self._height*self._height+
2807 # self._depth*self._depth)
2808 # phi *= -math.pi/180
2809 # theta *= math.pi/180
2810 # self.a = (-math.sin(phi), math.cos(phi), 0)
2811 # self.b = (-math.cos(phi)*math.sin(theta),
2812 # -math.sin(phi)*math.sin(theta),
2814 # self.eye = (self._distance*math.cos(phi)*math.cos(theta),
2815 # self._distance*math.sin(phi)*math.cos(theta),
2816 # self._distance*math.sin(theta))
2817 # self.initaxes(axes)
2818 # self.axesdist_str = axesdist
2819 # self.backgroundattrs = backgroundattrs
2822 # self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
2823 # self.haslayout = 0
2824 # self.defaultstyle = {}
2828 # return bbox._bbox(self._xpos - 200, self._ypos - 200, self._xpos + 200, self._ypos + 200)
2831 ################################################################################
2833 ################################################################################
2836 #class _Ichangeattr:
2837 # """attribute changer
2838 # is an iterator for attributes where an attribute
2839 # is not refered by just a number (like for a sequence),
2840 # but also by the number of attributes requested
2841 # by calls of the next method (like for an color palette)
2842 # (you should ensure to call all needed next before the attr)
2844 # the attribute itself is implemented by overloading the _attr method"""
2847 # "get an attribute"
2850 # "get an attribute changer for the next attribute"
2853 class _changeattr
: pass
2856 class changeattr(_changeattr
):
2865 newindex
= self
.counter
2867 return refattr(self
, newindex
)
2870 class refattr(_changeattr
):
2872 def __init__(self
, ref
, index
):
2877 return self
.ref
.attr(self
.index
)
2880 return self
.ref
.iterate()
2883 # helper routines for a using attrs
2886 """get attr out of a attr/changeattr"""
2887 if isinstance(attr
, _changeattr
):
2888 return attr
.getattr()
2892 def _getattrs(attrs
):
2893 """get attrs out of a sequence of attr/changeattr"""
2894 if attrs
is not None:
2896 for attr
in helper
.ensuresequence(attrs
):
2897 if isinstance(attr
, _changeattr
):
2898 attr
= attr
.getattr()
2899 if attr
is not None:
2901 if len(result
) or not len(attrs
):
2905 def _iterateattr(attr
):
2906 """perform next to a attr/changeattr"""
2907 if isinstance(attr
, _changeattr
):
2908 return attr
.iterate()
2912 def _iterateattrs(attrs
):
2913 """perform next to a sequence of attr/changeattr"""
2914 if attrs
is not None:
2916 for attr
in helper
.ensuresequence(attrs
):
2917 if isinstance(attr
, _changeattr
):
2918 result
.append(attr
.iterate())
2924 class changecolor(changeattr
):
2926 def __init__(self
, palette
):
2927 changeattr
.__init
__(self
)
2928 self
.palette
= palette
2930 def attr(self
, index
):
2931 if self
.counter
!= 1:
2932 return self
.palette
.getcolor(index
/float(self
.counter
-1))
2934 return self
.palette
.getcolor(0)
2937 class _changecolorgray(changecolor
):
2939 def __init__(self
, palette
=color
.palette
.Gray
):
2940 changecolor
.__init
__(self
, palette
)
2942 _changecolorgrey
= _changecolorgray
2945 class _changecolorreversegray(changecolor
):
2947 def __init__(self
, palette
=color
.palette
.ReverseGray
):
2948 changecolor
.__init
__(self
, palette
)
2950 _changecolorreversegrey
= _changecolorreversegray
2953 class _changecolorredblack(changecolor
):
2955 def __init__(self
, palette
=color
.palette
.RedBlack
):
2956 changecolor
.__init
__(self
, palette
)
2959 class _changecolorblackred(changecolor
):
2961 def __init__(self
, palette
=color
.palette
.BlackRed
):
2962 changecolor
.__init
__(self
, palette
)
2965 class _changecolorredwhite(changecolor
):
2967 def __init__(self
, palette
=color
.palette
.RedWhite
):
2968 changecolor
.__init
__(self
, palette
)
2971 class _changecolorwhitered(changecolor
):
2973 def __init__(self
, palette
=color
.palette
.WhiteRed
):
2974 changecolor
.__init
__(self
, palette
)
2977 class _changecolorgreenblack(changecolor
):
2979 def __init__(self
, palette
=color
.palette
.GreenBlack
):
2980 changecolor
.__init
__(self
, palette
)
2983 class _changecolorblackgreen(changecolor
):
2985 def __init__(self
, palette
=color
.palette
.BlackGreen
):
2986 changecolor
.__init
__(self
, palette
)
2989 class _changecolorgreenwhite(changecolor
):
2991 def __init__(self
, palette
=color
.palette
.GreenWhite
):
2992 changecolor
.__init
__(self
, palette
)
2995 class _changecolorwhitegreen(changecolor
):
2997 def __init__(self
, palette
=color
.palette
.WhiteGreen
):
2998 changecolor
.__init
__(self
, palette
)
3001 class _changecolorblueblack(changecolor
):
3003 def __init__(self
, palette
=color
.palette
.BlueBlack
):
3004 changecolor
.__init
__(self
, palette
)
3007 class _changecolorblackblue(changecolor
):
3009 def __init__(self
, palette
=color
.palette
.BlackBlue
):
3010 changecolor
.__init
__(self
, palette
)
3013 class _changecolorbluewhite(changecolor
):
3015 def __init__(self
, palette
=color
.palette
.BlueWhite
):
3016 changecolor
.__init
__(self
, palette
)
3019 class _changecolorwhiteblue(changecolor
):
3021 def __init__(self
, palette
=color
.palette
.WhiteBlue
):
3022 changecolor
.__init
__(self
, palette
)
3025 class _changecolorredgreen(changecolor
):
3027 def __init__(self
, palette
=color
.palette
.RedGreen
):
3028 changecolor
.__init
__(self
, palette
)
3031 class _changecolorredblue(changecolor
):
3033 def __init__(self
, palette
=color
.palette
.RedBlue
):
3034 changecolor
.__init
__(self
, palette
)
3037 class _changecolorgreenred(changecolor
):
3039 def __init__(self
, palette
=color
.palette
.GreenRed
):
3040 changecolor
.__init
__(self
, palette
)
3043 class _changecolorgreenblue(changecolor
):
3045 def __init__(self
, palette
=color
.palette
.GreenBlue
):
3046 changecolor
.__init
__(self
, palette
)
3049 class _changecolorbluered(changecolor
):
3051 def __init__(self
, palette
=color
.palette
.BlueRed
):
3052 changecolor
.__init
__(self
, palette
)
3055 class _changecolorbluegreen(changecolor
):
3057 def __init__(self
, palette
=color
.palette
.BlueGreen
):
3058 changecolor
.__init
__(self
, palette
)
3061 class _changecolorrainbow(changecolor
):
3063 def __init__(self
, palette
=color
.palette
.Rainbow
):
3064 changecolor
.__init
__(self
, palette
)
3067 class _changecolorreverserainbow(changecolor
):
3069 def __init__(self
, palette
=color
.palette
.ReverseRainbow
):
3070 changecolor
.__init
__(self
, palette
)
3073 class _changecolorhue(changecolor
):
3075 def __init__(self
, palette
=color
.palette
.Hue
):
3076 changecolor
.__init
__(self
, palette
)
3079 class _changecolorreversehue(changecolor
):
3081 def __init__(self
, palette
=color
.palette
.ReverseHue
):
3082 changecolor
.__init
__(self
, palette
)
3085 changecolor
.Gray
= _changecolorgray
3086 changecolor
.Grey
= _changecolorgrey
3087 changecolor
.Reversegray
= _changecolorreversegray
3088 changecolor
.Reversegrey
= _changecolorreversegrey
3089 changecolor
.RedBlack
= _changecolorredblack
3090 changecolor
.BlackRed
= _changecolorblackred
3091 changecolor
.RedWhite
= _changecolorredwhite
3092 changecolor
.WhiteRed
= _changecolorwhitered
3093 changecolor
.GreenBlack
= _changecolorgreenblack
3094 changecolor
.BlackGreen
= _changecolorblackgreen
3095 changecolor
.GreenWhite
= _changecolorgreenwhite
3096 changecolor
.WhiteGreen
= _changecolorwhitegreen
3097 changecolor
.BlueBlack
= _changecolorblueblack
3098 changecolor
.BlackBlue
= _changecolorblackblue
3099 changecolor
.BlueWhite
= _changecolorbluewhite
3100 changecolor
.WhiteBlue
= _changecolorwhiteblue
3101 changecolor
.RedGreen
= _changecolorredgreen
3102 changecolor
.RedBlue
= _changecolorredblue
3103 changecolor
.GreenRed
= _changecolorgreenred
3104 changecolor
.GreenBlue
= _changecolorgreenblue
3105 changecolor
.BlueRed
= _changecolorbluered
3106 changecolor
.BlueGreen
= _changecolorbluegreen
3107 changecolor
.Rainbow
= _changecolorrainbow
3108 changecolor
.ReverseRainbow
= _changecolorreverserainbow
3109 changecolor
.Hue
= _changecolorhue
3110 changecolor
.ReverseHue
= _changecolorreversehue
3113 class changesequence(changeattr
):
3114 """cycles through a sequence"""
3116 def __init__(self
, *sequence
):
3117 changeattr
.__init
__(self
)
3118 if not len(sequence
):
3119 sequence
= self
.defaultsequence
3120 self
.sequence
= sequence
3122 def attr(self
, index
):
3123 return self
.sequence
[index
% len(self
.sequence
)]
3126 class changelinestyle(changesequence
):
3127 defaultsequence
= (canvas
.linestyle
.solid
,
3128 canvas
.linestyle
.dashed
,
3129 canvas
.linestyle
.dotted
,
3130 canvas
.linestyle
.dashdotted
)
3133 class changestrokedfilled(changesequence
):
3134 defaultsequence
= (canvas
.stroked(), canvas
.filled())
3137 class changefilledstroked(changesequence
):
3138 defaultsequence
= (canvas
.filled(), canvas
.stroked())
3142 ################################################################################
3144 ################################################################################
3149 def cross(self
, x
, y
):
3150 return (path
._moveto
(x
-0.5*self
._size
, y
-0.5*self
._size
),
3151 path
._lineto
(x
+0.5*self
._size
, y
+0.5*self
._size
),
3152 path
._moveto
(x
-0.5*self
._size
, y
+0.5*self
._size
),
3153 path
._lineto
(x
+0.5*self
._size
, y
-0.5*self
._size
))
3155 def plus(self
, x
, y
):
3156 return (path
._moveto
(x
-0.707106781*self
._size
, y
),
3157 path
._lineto
(x
+0.707106781*self
._size
, y
),
3158 path
._moveto
(x
, y
-0.707106781*self
._size
),
3159 path
._lineto
(x
, y
+0.707106781*self
._size
))
3161 def square(self
, x
, y
):
3162 return (path
._moveto
(x
-0.5*self
._size
, y
-0.5 * self
._size
),
3163 path
._lineto
(x
+0.5*self
._size
, y
-0.5 * self
._size
),
3164 path
._lineto
(x
+0.5*self
._size
, y
+0.5 * self
._size
),
3165 path
._lineto
(x
-0.5*self
._size
, y
+0.5 * self
._size
),
3168 def triangle(self
, x
, y
):
3169 return (path
._moveto
(x
-0.759835685*self
._size
, y
-0.438691337*self
._size
),
3170 path
._lineto
(x
+0.759835685*self
._size
, y
-0.438691337*self
._size
),
3171 path
._lineto
(x
, y
+0.877382675*self
._size
),
3174 def circle(self
, x
, y
):
3175 return (path
._arc
(x
, y
, 0.564189583*self
._size
, 0, 360),
3178 def diamond(self
, x
, y
):
3179 return (path
._moveto
(x
-0.537284965*self
._size
, y
),
3180 path
._lineto
(x
, y
-0.930604859*self
._size
),
3181 path
._lineto
(x
+0.537284965*self
._size
, y
),
3182 path
._lineto
(x
, y
+0.930604859*self
._size
),
3185 def __init__(self
, symbol
=helper
.nodefault
,
3186 size
="0.2 cm", symbolattrs
=canvas
.stroked(),
3187 errorscale
=0.5, errorbarattrs
=(),
3189 self
.size_str
= size
3190 if symbol
is helper
.nodefault
:
3191 self
._symbol
= changesymbol
.cross()
3193 self
._symbol
= symbol
3194 self
._symbolattrs
= symbolattrs
3195 self
.errorscale
= errorscale
3196 self
._errorbarattrs
= errorbarattrs
3197 self
._lineattrs
= lineattrs
3199 def iteratedict(self
):
3201 result
["symbol"] = _iterateattr(self
._symbol
)
3202 result
["size"] = _iterateattr(self
.size_str
)
3203 result
["symbolattrs"] = _iterateattrs(self
._symbolattrs
)
3204 result
["errorscale"] = _iterateattr(self
.errorscale
)
3205 result
["errorbarattrs"] = _iterateattrs(self
._errorbarattrs
)
3206 result
["lineattrs"] = _iterateattrs(self
._lineattrs
)
3210 return symbol(**self
.iteratedict())
3212 def othercolumnkey(self
, key
, index
):
3213 raise ValueError("unsuitable key '%s'" % key
)
3215 def setcolumns(self
, graph
, columns
):
3216 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
3218 match
= pattern
.match(key
)
3220 if isindex
is not None: raise ValueError("multiple key specification")
3221 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
3223 iskey
= match
.groups()[0]
3225 return key
, iskey
, isindex
3227 self
.xi
= self
.xmini
= self
.xmaxi
= None
3228 self
.dxi
= self
.dxmini
= self
.dxmaxi
= None
3229 self
.yi
= self
.ymini
= self
.ymaxi
= None
3230 self
.dyi
= self
.dymini
= self
.dymaxi
= None
3231 self
.xkey
= self
.ykey
= None
3232 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
3233 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
3234 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
3235 XMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
3236 YMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
3237 XMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
3238 YMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
3239 DXPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
3240 DYPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
3241 DXMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
3242 DYMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
3243 DXMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
3244 DYMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
3245 for key
, index
in columns
.items():
3246 key
, self
.xkey
, self
.xi
= checkpattern(key
, index
, XPattern
, self
.xkey
, self
.xi
)
3247 key
, self
.ykey
, self
.yi
= checkpattern(key
, index
, YPattern
, self
.ykey
, self
.yi
)
3248 key
, self
.xkey
, self
.xmini
= checkpattern(key
, index
, XMinPattern
, self
.xkey
, self
.xmini
)
3249 key
, self
.ykey
, self
.ymini
= checkpattern(key
, index
, YMinPattern
, self
.ykey
, self
.ymini
)
3250 key
, self
.xkey
, self
.xmaxi
= checkpattern(key
, index
, XMaxPattern
, self
.xkey
, self
.xmaxi
)
3251 key
, self
.ykey
, self
.ymaxi
= checkpattern(key
, index
, YMaxPattern
, self
.ykey
, self
.ymaxi
)
3252 key
, self
.xkey
, self
.dxi
= checkpattern(key
, index
, DXPattern
, self
.xkey
, self
.dxi
)
3253 key
, self
.ykey
, self
.dyi
= checkpattern(key
, index
, DYPattern
, self
.ykey
, self
.dyi
)
3254 key
, self
.xkey
, self
.dxmini
= checkpattern(key
, index
, DXMinPattern
, self
.xkey
, self
.dxmini
)
3255 key
, self
.ykey
, self
.dymini
= checkpattern(key
, index
, DYMinPattern
, self
.ykey
, self
.dymini
)
3256 key
, self
.xkey
, self
.dxmaxi
= checkpattern(key
, index
, DXMaxPattern
, self
.xkey
, self
.dxmaxi
)
3257 key
, self
.ykey
, self
.dymaxi
= checkpattern(key
, index
, DYMaxPattern
, self
.ykey
, self
.dymaxi
)
3259 self
.othercolumnkey(key
, index
)
3260 if None in (self
.xkey
, self
.ykey
): raise ValueError("incomplete axis specification")
3261 if (len(filter(None, (self
.xmini
, self
.dxmini
, self
.dxi
))) > 1 or
3262 len(filter(None, (self
.ymini
, self
.dymini
, self
.dyi
))) > 1 or
3263 len(filter(None, (self
.xmaxi
, self
.dxmaxi
, self
.dxi
))) > 1 or
3264 len(filter(None, (self
.ymaxi
, self
.dymaxi
, self
.dyi
))) > 1):
3265 raise ValueError("multiple errorbar definition")
3266 if ((self
.xi
is None and self
.dxi
is not None) or
3267 (self
.yi
is None and self
.dyi
is not None) or
3268 (self
.xi
is None and self
.dxmini
is not None) or
3269 (self
.yi
is None and self
.dymini
is not None) or
3270 (self
.xi
is None and self
.dxmaxi
is not None) or
3271 (self
.yi
is None and self
.dymaxi
is not None)):
3272 raise ValueError("errorbar definition start value missing")
3273 self
.xaxis
= graph
.axes
[self
.xkey
]
3274 self
.yaxis
= graph
.axes
[self
.ykey
]
3276 def minmidmax(self
, point
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
3277 min = max = mid
= None
3279 mid
= point
[i
] + 0.0
3280 except (TypeError, ValueError):
3283 if di
is not None: min = point
[i
] - point
[di
]
3284 elif dmini
is not None: min = point
[i
] - point
[dmini
]
3285 elif mini
is not None: min = point
[mini
] + 0.0
3286 except (TypeError, ValueError):
3289 if di
is not None: max = point
[i
] + point
[di
]
3290 elif dmaxi
is not None: max = point
[i
] + point
[dmaxi
]
3291 elif maxi
is not None: max = point
[maxi
] + 0.0
3292 except (TypeError, ValueError):
3295 if min is not None and min > mid
: raise ValueError("minimum error in errorbar")
3296 if max is not None and max < mid
: raise ValueError("maximum error in errorbar")
3298 if min is not None and max is not None and min > max: raise ValueError("minimum/maximum error in errorbar")
3299 return min, mid
, max
3301 def keyrange(self
, points
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
3302 allmin
= allmax
= None
3303 if filter(None, (mini
, maxi
, di
, dmini
, dmaxi
)) is not None:
3304 for point
in points
:
3305 min, mid
, max = self
.minmidmax(point
, i
, mini
, maxi
, di
, dmini
, dmaxi
)
3306 if min is not None and (allmin
is None or min < allmin
): allmin
= min
3307 if mid
is not None and (allmin
is None or mid
< allmin
): allmin
= mid
3308 if mid
is not None and (allmax
is None or mid
> allmax
): allmax
= mid
3309 if max is not None and (allmax
is None or max > allmax
): allmax
= max
3311 for point
in points
:
3313 value
= point
[i
] + 0.0
3314 if allmin
is None or point
[i
] < allmin
: allmin
= point
[i
]
3315 if allmax
is None or point
[i
] > allmax
: allmax
= point
[i
]
3316 except (TypeError, ValueError):
3318 return allmin
, allmax
3320 def getranges(self
, points
):
3321 xmin
, xmax
= self
.keyrange(points
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
3322 ymin
, ymax
= self
.keyrange(points
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
3323 return {self
.xkey
: (xmin
, xmax
), self
.ykey
: (ymin
, ymax
)}
3325 def _drawerrorbar(self
, graph
, topleft
, top
, topright
,
3326 left
, center
, right
,
3327 bottomleft
, bottom
, bottomright
, point
=None):
3328 if left
is not None:
3329 if right
is not None:
3330 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
3331 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
3332 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
3333 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
3334 graph
.stroke(path
.path(path
._moveto
(*left1
),
3335 graph
._connect
(*(left1
+left2
)),
3336 path
._moveto
(*left
),
3337 graph
._connect
(*(left
+right
)),
3338 path
._moveto
(*right1
),
3339 graph
._connect
(*(right1
+right2
))),
3340 *self
.errorbarattrs
)
3341 elif center
is not None:
3342 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
3343 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
3344 graph
.stroke(path
.path(path
._moveto
(*left1
),
3345 graph
._connect
(*(left1
+left2
)),
3346 path
._moveto
(*left
),
3347 graph
._connect
(*(left
+center
))),
3348 *self
.errorbarattrs
)
3350 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
3351 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
3352 left3
= graph
._addpos
(*(left
+(self
._errorsize
, 0)))
3353 graph
.stroke(path
.path(path
._moveto
(*left1
),
3354 graph
._connect
(*(left1
+left2
)),
3355 path
._moveto
(*left
),
3356 graph
._connect
(*(left
+left3
))),
3357 *self
.errorbarattrs
)
3358 if right
is not None and left
is None:
3359 if center
is not None:
3360 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
3361 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
3362 graph
.stroke(path
.path(path
._moveto
(*right1
),
3363 graph
._connect
(*(right1
+right2
)),
3364 path
._moveto
(*right
),
3365 graph
._connect
(*(right
+center
))),
3366 *self
.errorbarattrs
)
3368 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
3369 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
3370 right3
= graph
._addpos
(*(right
+(-self
._errorsize
, 0)))
3371 graph
.stroke(path
.path(path
._moveto
(*right1
),
3372 graph
._connect
(*(right1
+right2
)),
3373 path
._moveto
(*right
),
3374 graph
._connect
(*(right
+right3
))),
3375 *self
.errorbarattrs
)
3377 if bottom
is not None:
3379 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
3380 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
3381 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
3382 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
3383 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
3384 graph
._connect
(*(bottom1
+bottom2
)),
3385 path
._moveto
(*bottom
),
3386 graph
._connect
(*(bottom
+top
)),
3387 path
._moveto
(*top1
),
3388 graph
._connect
(*(top1
+top2
))),
3389 *self
.errorbarattrs
)
3390 elif center
is not None:
3391 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
3392 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
3393 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
3394 graph
._connect
(*(bottom1
+bottom2
)),
3395 path
._moveto
(*bottom
),
3396 graph
._connect
(*(bottom
+center
))),
3397 *self
.errorbarattrs
)
3399 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
3400 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
3401 bottom3
= graph
._addpos
(*(bottom
+(0, self
._errorsize
)))
3402 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
3403 graph
._connect
(*(bottom1
+bottom2
)),
3404 path
._moveto
(*bottom
),
3405 graph
._connect
(*(bottom
+bottom3
))),
3406 *self
.errorbarattrs
)
3407 if top
is not None and bottom
is None:
3408 if center
is not None:
3409 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
3410 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
3411 graph
.stroke(path
.path(path
._moveto
(*top1
),
3412 graph
._connect
(*(top1
+top2
)),
3414 graph
._connect
(*(top
+center
))),
3415 *self
.errorbarattrs
)
3417 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
3418 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
3419 top3
= graph
._addpos
(*(top
+(0, -self
._errorsize
)))
3420 graph
.stroke(path
.path(path
._moveto
(*top1
),
3421 graph
._connect
(*(top1
+top2
)),
3423 graph
._connect
(*(top
+top3
))),
3424 *self
.errorbarattrs
)
3425 if bottomleft
is not None:
3426 if topleft
is not None and bottomright
is None:
3427 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
._errorsize
, 0)))
3428 topleft1
= graph
._addpos
(*(topleft
+(self
._errorsize
, 0)))
3429 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
3430 graph
._connect
(*(bottomleft1
+bottomleft
)),
3431 graph
._connect
(*(bottomleft
+topleft
)),
3432 graph
._connect
(*(topleft
+topleft1
))),
3433 *self
.errorbarattrs
)
3434 elif bottomright
is not None and topleft
is None:
3435 bottomleft1
= graph
._addpos
(*(bottomleft
+(0, self
._errorsize
)))
3436 bottomright1
= graph
._addpos
(*(bottomright
+(0, self
._errorsize
)))
3437 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
3438 graph
._connect
(*(bottomleft1
+bottomleft
)),
3439 graph
._connect
(*(bottomleft
+bottomright
)),
3440 graph
._connect
(*(bottomright
+bottomright1
))),
3441 *self
.errorbarattrs
)
3442 elif bottomright
is None and topleft
is None:
3443 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
._errorsize
, 0)))
3444 bottomleft2
= graph
._addpos
(*(bottomleft
+(0, self
._errorsize
)))
3445 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
3446 graph
._connect
(*(bottomleft1
+bottomleft
)),
3447 graph
._connect
(*(bottomleft
+bottomleft2
))),
3448 *self
.errorbarattrs
)
3449 if topright
is not None:
3450 if bottomright
is not None and topleft
is None:
3451 topright1
= graph
._addpos
(*(topright
+(-self
._errorsize
, 0)))
3452 bottomright1
= graph
._addpos
(*(bottomright
+(-self
._errorsize
, 0)))
3453 graph
.stroke(path
.path(path
._moveto
(*topright1
),
3454 graph
._connect
(*(topright1
+topright
)),
3455 graph
._connect
(*(topright
+bottomright
)),
3456 graph
._connect
(*(bottomright
+bottomright1
))),
3457 *self
.errorbarattrs
)
3458 elif topleft
is not None and bottomright
is None:
3459 topright1
= graph
._addpos
(*(topright
+(0, -self
._errorsize
)))
3460 topleft1
= graph
._addpos
(*(topleft
+(0, -self
._errorsize
)))
3461 graph
.stroke(path
.path(path
._moveto
(*topright1
),
3462 graph
._connect
(*(topright1
+topright
)),
3463 graph
._connect
(*(topright
+topleft
)),
3464 graph
._connect
(*(topleft
+topleft1
))),
3465 *self
.errorbarattrs
)
3466 elif topleft
is None and bottomright
is None:
3467 topright1
= graph
._addpos
(*(topright
+(-self
._errorsize
, 0)))
3468 topright2
= graph
._addpos
(*(topright
+(0, -self
._errorsize
)))
3469 graph
.stroke(path
.path(path
._moveto
(*topright1
),
3470 graph
._connect
(*(topright1
+topright
)),
3471 graph
._connect
(*(topright
+topright2
))),
3472 *self
.errorbarattrs
)
3473 if bottomright
is not None and bottomleft
is None and topright
is None:
3474 bottomright1
= graph
._addpos
(*(bottomright
+(-self
._errorsize
, 0)))
3475 bottomright2
= graph
._addpos
(*(bottomright
+(0, self
._errorsize
)))
3476 graph
.stroke(path
.path(path
._moveto
(*bottomright1
),
3477 graph
._connect
(*(bottomright1
+bottomright
)),
3478 graph
._connect
(*(bottomright
+bottomright2
))),
3479 *self
.errorbarattrs
)
3480 if topleft
is not None and bottomleft
is None and topright
is None:
3481 topleft1
= graph
._addpos
(*(topleft
+(self
._errorsize
, 0)))
3482 topleft2
= graph
._addpos
(*(topleft
+(0, -self
._errorsize
)))
3483 graph
.stroke(path
.path(path
._moveto
(*topleft1
),
3484 graph
._connect
(*(topleft1
+topleft
)),
3485 graph
._connect
(*(topleft
+topleft2
))),
3486 *self
.errorbarattrs
)
3487 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
3488 graph
.stroke(path
.path(path
._moveto
(*bottomleft
),
3489 graph
._connect
(*(bottomleft
+bottomright
)),
3490 graph
._connect
(*(bottomright
+topright
)),
3491 graph
._connect
(*(topright
+topleft
)),
3493 *self
.errorbarattrs
)
3495 def _drawsymbol(self
, canvas
, x
, y
, point
=None):
3496 canvas
.draw(path
.path(*self
.symbol(self
, x
, y
)), *self
.symbolattrs
)
3498 def drawsymbol(self
, canvas
, x
, y
, point
=None):
3499 self
._drawsymbol
(canvas
, unit
.topt(x
), unit
.topt(y
), point
)
3501 def key(self
, c
, x
, y
, width
, height
):
3502 if self
._symbolattrs
is not None:
3503 self
._drawsymbol
(c
, x
+ 0.5 * width
, y
+ 0.5 * height
)
3504 if self
._lineattrs
is not None:
3505 c
.stroke(path
._line
(x
, y
+ 0.5 * height
, x
+ width
, y
+ 0.5 * height
), *self
.lineattrs
)
3507 def drawpoints(self
, graph
, points
):
3508 xaxismin
, xaxismax
= self
.xaxis
.getdatarange()
3509 yaxismin
, yaxismax
= self
.yaxis
.getdatarange()
3510 self
.size
= unit
.length(_getattr(self
.size_str
), default_type
="v")
3511 self
._size
= unit
.topt(self
.size
)
3512 self
.symbol
= _getattr(self
._symbol
)
3513 self
.symbolattrs
= _getattrs(helper
.ensuresequence(self
._symbolattrs
))
3514 self
.errorbarattrs
= _getattrs(helper
.ensuresequence(self
._errorbarattrs
))
3515 self
._errorsize
= self
.errorscale
* self
._size
3516 self
.errorsize
= self
.errorscale
* self
.size
3517 self
.lineattrs
= _getattrs(helper
.ensuresequence(self
._lineattrs
))
3518 if self
._lineattrs
is not None:
3519 clipcanvas
= graph
.clipcanvas()
3521 haserror
= filter(None, (self
.xmini
, self
.ymini
, self
.xmaxi
, self
.ymaxi
,
3522 self
.dxi
, self
.dyi
, self
.dxmini
, self
.dymini
, self
.dxmaxi
, self
.dymaxi
)) is not None
3524 for point
in points
:
3526 xmin
, x
, xmax
= self
.minmidmax(point
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
3527 ymin
, y
, ymax
= self
.minmidmax(point
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
3528 if x
is not None and x
< xaxismin
: drawsymbol
= 0
3529 elif x
is not None and x
> xaxismax
: drawsymbol
= 0
3530 elif y
is not None and y
< yaxismin
: drawsymbol
= 0
3531 elif y
is not None and y
> yaxismax
: drawsymbol
= 0
3533 if xmin
is not None and xmin
< xaxismin
: drawsymbol
= 0
3534 elif xmax
is not None and xmax
< xaxismin
: drawsymbol
= 0
3535 elif xmax
is not None and xmax
> xaxismax
: drawsymbol
= 0
3536 elif xmin
is not None and xmin
> xaxismax
: drawsymbol
= 0
3537 elif ymin
is not None and ymin
< yaxismin
: drawsymbol
= 0
3538 elif ymax
is not None and ymax
< yaxismin
: drawsymbol
= 0
3539 elif ymax
is not None and ymax
> yaxismax
: drawsymbol
= 0
3540 elif ymin
is not None and ymin
> yaxismax
: drawsymbol
= 0
3541 xpos
=ypos
=topleft
=top
=topright
=left
=center
=right
=bottomleft
=bottom
=bottomright
=None
3542 if x
is not None and y
is not None:
3544 center
= xpos
, ypos
= graph
._pos
(x
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3545 except (ValueError, OverflowError): # XXX: exceptions???
3549 if xmin
is not None: left
= graph
._pos
(xmin
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3550 if xmax
is not None: right
= graph
._pos
(xmax
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3552 if ymax
is not None: top
= graph
._pos
(x
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3553 if ymin
is not None: bottom
= graph
._pos
(x
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3554 if x
is None or y
is None:
3555 if ymax
is not None:
3556 if xmin
is not None: topleft
= graph
._pos
(xmin
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3557 if xmax
is not None: topright
= graph
._pos
(xmax
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3558 if ymin
is not None:
3559 if xmin
is not None: bottomleft
= graph
._pos
(xmin
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3560 if xmax
is not None: bottomright
= graph
._pos
(xmax
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
3562 if self
._errorbarattrs
is not None and haserror
:
3563 self
._drawerrorbar
(graph
, topleft
, top
, topright
,
3564 left
, center
, right
,
3565 bottomleft
, bottom
, bottomright
, point
)
3566 if self
._symbolattrs
is not None and xpos
is not None and ypos
is not None:
3567 self
._drawsymbol
(graph
, xpos
, ypos
, point
)
3568 if xpos
is not None and ypos
is not None:
3570 lineels
.append(path
._moveto
(xpos
, ypos
))
3573 lineels
.append(path
._lineto
(xpos
, ypos
))
3576 self
.path
= path
.path(*lineels
)
3577 if self
._lineattrs
is not None:
3578 clipcanvas
.stroke(self
.path
, *self
.lineattrs
)
3581 class changesymbol(changesequence
): pass
3584 class _changesymbolcross(changesymbol
):
3585 defaultsequence
= (symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
)
3588 class _changesymbolplus(changesymbol
):
3589 defaultsequence
= (symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
)
3592 class _changesymbolsquare(changesymbol
):
3593 defaultsequence
= (symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
)
3596 class _changesymboltriangle(changesymbol
):
3597 defaultsequence
= (symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
)
3600 class _changesymbolcircle(changesymbol
):
3601 defaultsequence
= (symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
)
3604 class _changesymboldiamond(changesymbol
):
3605 defaultsequence
= (symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
)
3608 class _changesymbolsquaretwice(changesymbol
):
3609 defaultsequence
= (symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
,
3610 symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
)
3613 class _changesymboltriangletwice(changesymbol
):
3614 defaultsequence
= (symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
,
3615 symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
)
3618 class _changesymbolcircletwice(changesymbol
):
3619 defaultsequence
= (symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
,
3620 symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
)
3623 class _changesymboldiamondtwice(changesymbol
):
3624 defaultsequence
= (symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
,
3625 symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
)
3628 changesymbol
.cross
= _changesymbolcross
3629 changesymbol
.plus
= _changesymbolplus
3630 changesymbol
.square
= _changesymbolsquare
3631 changesymbol
.triangle
= _changesymboltriangle
3632 changesymbol
.circle
= _changesymbolcircle
3633 changesymbol
.diamond
= _changesymboldiamond
3634 changesymbol
.squaretwice
= _changesymbolsquaretwice
3635 changesymbol
.triangletwice
= _changesymboltriangletwice
3636 changesymbol
.circletwice
= _changesymbolcircletwice
3637 changesymbol
.diamondtwice
= _changesymboldiamondtwice
3642 def __init__(self
, lineattrs
=helper
.nodefault
):
3643 if lineattrs
is helper
.nodefault
:
3644 lineattrs
= (changelinestyle(), canvas
.linejoin
.round)
3645 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=None, lineattrs
=lineattrs
)
3650 def __init__(self
, palette
=color
.palette
.Gray
):
3651 self
.palette
= palette
3652 self
.colorindex
= None
3653 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=(), lineattrs
=None)
3656 raise RuntimeError("style is not iterateable")
3658 def othercolumnkey(self
, key
, index
):
3660 self
.colorindex
= index
3662 symbol
.othercolumnkey(self
, key
, index
)
3664 def _drawerrorbar(self
, graph
, topleft
, top
, topright
,
3665 left
, center
, right
,
3666 bottomleft
, bottom
, bottomright
, point
=None):
3667 color
= point
[self
.colorindex
]
3668 if color
is not None:
3669 if color
!= self
.lastcolor
:
3670 self
.rectclipcanvas
.set(self
.palette
.getcolor(color
))
3671 if bottom
is not None and left
is not None:
3672 bottomleft
= left
[0], bottom
[1]
3673 if bottom
is not None and right
is not None:
3674 bottomright
= right
[0], bottom
[1]
3675 if top
is not None and right
is not None:
3676 topright
= right
[0], top
[1]
3677 if top
is not None and left
is not None:
3678 topleft
= left
[0], top
[1]
3679 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
3680 self
.rectclipcanvas
.fill(path
.path(path
._moveto
(*bottomleft
),
3681 graph
._connect
(*(bottomleft
+bottomright
)),
3682 graph
._connect
(*(bottomright
+topright
)),
3683 graph
._connect
(*(topright
+topleft
)),
3686 def drawpoints(self
, graph
, points
):
3687 if self
.colorindex
is None:
3688 raise RuntimeError("column 'color' not set")
3689 self
.lastcolor
= None
3690 self
.rectclipcanvas
= graph
.clipcanvas()
3691 symbol
.drawpoints(self
, graph
, points
)
3693 def key(self
, c
, x
, y
, width
, height
):
3694 raise RuntimeError("style doesn't yet provide a key")
3699 def __init__(self
, textdx
="0", textdy
="0.3 cm", textattrs
=textmodule
.halign
.center
, **args
):
3700 self
.textindex
= None
3701 self
.textdx_str
= textdx
3702 self
.textdy_str
= textdy
3703 self
._textattrs
= textattrs
3704 symbol
.__init
__(self
, **args
)
3706 def iteratedict(self
):
3707 result
= symbol
.iteratedict()
3708 result
["textattrs"] = _iterateattr(self
._textattrs
)
3712 return textsymbol(**self
.iteratedict())
3714 def othercolumnkey(self
, key
, index
):
3716 self
.textindex
= index
3718 symbol
.othercolumnkey(self
, key
, index
)
3720 def _drawsymbol(self
, graph
, x
, y
, point
=None):
3721 symbol
._drawsymbol
(self
, graph
, x
, y
, point
)
3722 if None not in (x
, y
, point
[self
.textindex
]) and self
._textattrs
is not None:
3723 graph
._text
(x
+ self
._textdx
, y
+ self
._textdy
, str(point
[self
.textindex
]), *helper
.ensuresequence(self
.textattrs
))
3725 def drawpoints(self
, graph
, points
):
3726 self
.textdx
= unit
.length(_getattr(self
.textdx_str
), default_type
="v")
3727 self
.textdy
= unit
.length(_getattr(self
.textdy_str
), default_type
="v")
3728 self
._textdx
= unit
.topt(self
.textdx
)
3729 self
._textdy
= unit
.topt(self
.textdy
)
3730 if self
._textattrs
is not None:
3731 self
.textattrs
= _getattr(self
._textattrs
)
3732 if self
.textindex
is None:
3733 raise RuntimeError("column 'text' not set")
3734 symbol
.drawpoints(self
, graph
, points
)
3736 def key(self
, c
, x
, y
, width
, height
):
3737 raise RuntimeError("style doesn't yet provide a key")
3740 class arrow(symbol
):
3742 def __init__(self
, linelength
="0.2 cm", arrowattrs
=(), arrowsize
="0.1 cm", arrowdict
={}, epsilon
=1e-10):
3743 self
.linelength_str
= linelength
3744 self
.arrowsize_str
= arrowsize
3745 self
.arrowattrs
= arrowattrs
3746 self
.arrowdict
= arrowdict
3747 self
.epsilon
= epsilon
3748 self
.sizeindex
= self
.angleindex
= None
3749 symbol
.__init
__(self
, symbolattrs
=(), errorbarattrs
=None, lineattrs
=None)
3752 raise RuntimeError("style is not iterateable")
3754 def othercolumnkey(self
, key
, index
):
3756 self
.sizeindex
= index
3757 elif key
== "angle":
3758 self
.angleindex
= index
3760 symbol
.othercolumnkey(self
, key
, index
)
3762 def _drawsymbol(self
, graph
, x
, y
, point
=None):
3763 if None not in (x
, y
, point
[self
.angleindex
], point
[self
.sizeindex
], self
.arrowattrs
, self
.arrowdict
):
3764 if point
[self
.sizeindex
] > self
.epsilon
:
3765 dx
, dy
= math
.cos(point
[self
.angleindex
]*math
.pi
/180.0), math
.sin(point
[self
.angleindex
]*math
.pi
/180)
3766 x1
= unit
.t_pt(x
)-0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
3767 y1
= unit
.t_pt(y
)-0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
3768 x2
= unit
.t_pt(x
)+0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
3769 y2
= unit
.t_pt(y
)+0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
3770 graph
.stroke(path
.line(x1
, y1
, x2
, y2
),
3771 canvas
.earrow(self
.arrowsize
*point
[self
.sizeindex
],
3773 *helper
.ensuresequence(self
.arrowattrs
))
3775 def drawpoints(self
, graph
, points
):
3776 self
.arrowsize
= unit
.length(_getattr(self
.arrowsize_str
), default_type
="v")
3777 self
.linelength
= unit
.length(_getattr(self
.linelength_str
), default_type
="v")
3778 self
._arrowsize
= unit
.topt(self
.arrowsize
)
3779 self
._linelength
= unit
.topt(self
.linelength
)
3780 if self
.sizeindex
is None:
3781 raise RuntimeError("column 'size' not set")
3782 if self
.angleindex
is None:
3783 raise RuntimeError("column 'angle' not set")
3784 symbol
.drawpoints(self
, graph
, points
)
3786 def key(self
, c
, x
, y
, width
, height
):
3787 raise RuntimeError("style doesn't yet provide a key")
3790 class _bariterator(changeattr
):
3792 def attr(self
, index
):
3793 return index
, self
.counter
3798 def __init__(self
, fromzero
=1, stacked
=0, skipmissing
=1, xbar
=0,
3799 barattrs
=helper
.nodefault
, _usebariterator
=helper
.nodefault
, _previousbar
=None):
3800 self
.fromzero
= fromzero
3801 self
.stacked
= stacked
3802 self
.skipmissing
= skipmissing
3804 if barattrs
is helper
.nodefault
:
3805 self
._barattrs
= (canvas
.stroked(color
.gray
.black
), changecolor
.Rainbow())
3807 self
._barattrs
= barattrs
3808 if _usebariterator
is helper
.nodefault
:
3809 self
.bariterator
= _bariterator()
3811 self
.bariterator
= _usebariterator
3812 self
.previousbar
= _previousbar
3814 def iteratedict(self
):
3816 result
["barattrs"] = _iterateattrs(self
._barattrs
)
3820 return bar(fromzero
=self
.fromzero
, stacked
=self
.stacked
, xbar
=self
.xbar
,
3821 _usebariterator
=_iterateattr(self
.bariterator
), _previousbar
=self
, **self
.iteratedict())
3823 def setcolumns(self
, graph
, columns
):
3824 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
3826 match
= pattern
.match(key
)
3828 if isindex
is not None: raise ValueError("multiple key specification")
3829 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
3831 iskey
= match
.groups()[0]
3833 return key
, iskey
, isindex
3836 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
3837 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
3838 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
3840 for key
, index
in columns
.items():
3841 key
, xkey
, xi
= checkpattern(key
, index
, XPattern
, xkey
, xi
)
3842 key
, ykey
, yi
= checkpattern(key
, index
, YPattern
, ykey
, yi
)
3844 self
.othercolumnkey(key
, index
)
3845 if None in (xkey
, ykey
): raise ValueError("incomplete axis specification")
3847 self
.nkey
, self
.ni
= ykey
, yi
3848 self
.vkey
, self
.vi
= xkey
, xi
3850 self
.nkey
, self
.ni
= xkey
, xi
3851 self
.vkey
, self
.vi
= ykey
, yi
3852 self
.naxis
, self
.vaxis
= graph
.axes
[self
.nkey
], graph
.axes
[self
.vkey
]
3854 def getranges(self
, points
):
3855 index
, count
= _getattr(self
.bariterator
)
3856 if count
!= 1 and self
.stacked
!= 1:
3857 if self
.stacked
> 1:
3858 index
= divmod(index
, self
.stacked
)[0]
3861 for point
in points
:
3862 if not self
.skipmissing
:
3863 if count
!= 1 and self
.stacked
!= 1:
3864 self
.naxis
.setname(point
[self
.ni
], index
)
3866 self
.naxis
.setname(point
[self
.ni
])
3868 v
= point
[self
.vi
] + 0.0
3869 if vmin
is None or v
< vmin
: vmin
= v
3870 if vmax
is None or v
> vmax
: vmax
= v
3871 except (TypeError, ValueError):
3874 if self
.skipmissing
:
3875 if count
!= 1 and self
.stacked
!= 1:
3876 self
.naxis
.setname(point
[self
.ni
], index
)
3878 self
.naxis
.setname(point
[self
.ni
])
3880 if vmin
> 0: vmin
= 0
3881 if vmax
< 0: vmax
= 0
3882 return {self
.vkey
: (vmin
, vmax
)}
3884 def drawpoints(self
, graph
, points
):
3885 index
, count
= _getattr(self
.bariterator
)
3886 dostacked
= (self
.stacked
!= 0 and
3887 (self
.stacked
== 1 or divmod(index
, self
.stacked
)[1]) and
3888 (self
.stacked
!= 1 or index
))
3889 if self
.stacked
> 1:
3890 index
= divmod(index
, self
.stacked
)[0]
3891 vmin
, vmax
= self
.vaxis
.getdatarange()
3892 self
.barattrs
= _getattrs(helper
.ensuresequence(self
._barattrs
))
3894 self
.stackedvalue
= {}
3895 for point
in points
:
3900 self
.stackedvalue
[n
] = v
3901 if count
!= 1 and self
.stacked
!= 1:
3902 minid
= (n
, index
, 0)
3903 maxid
= (n
, index
, 1)
3908 x1pos
, y1pos
= graph
._pos
(v
, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
3909 x2pos
, y2pos
= graph
._pos
(v
, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
3911 x1pos
, y1pos
= graph
._pos
(minid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
3912 x2pos
, y2pos
= graph
._pos
(maxid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
3915 x3pos
, y3pos
= graph
._pos
(self
.previousbar
.stackedvalue
[n
], maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
3916 x4pos
, y4pos
= graph
._pos
(self
.previousbar
.stackedvalue
[n
], minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
3918 x3pos
, y3pos
= graph
._pos
(maxid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
3919 x4pos
, y4pos
= graph
._pos
(minid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
3923 x3pos
, y3pos
= graph
._pos
(0, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
3924 x4pos
, y4pos
= graph
._pos
(0, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
3926 x3pos
, y3pos
= graph
._pos
(maxid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
3927 x4pos
, y4pos
= graph
._pos
(minid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
3929 x3pos
, y3pos
= self
.naxis
._vtickpoint
(self
.naxis
, self
.naxis
.convert(maxid
))
3930 x4pos
, y4pos
= self
.naxis
._vtickpoint
(self
.naxis
, self
.naxis
.convert(minid
))
3931 if self
.barattrs
is not None:
3932 graph
.fill(path
.path(path
._moveto
(x1pos
, y1pos
),
3933 graph
._connect
(x1pos
, y1pos
, x2pos
, y2pos
),
3934 graph
._connect
(x2pos
, y2pos
, x3pos
, y3pos
),
3935 graph
._connect
(x3pos
, y3pos
, x4pos
, y4pos
),
3936 graph
._connect
(x4pos
, y4pos
, x1pos
, y1pos
), # no closepath (might not be straight)
3937 path
.closepath()), *self
.barattrs
)
3938 except (TypeError, ValueError): pass
3940 def key(self
, c
, x
, y
, width
, height
):
3941 c
.fill(path
._rect
(x
, y
, width
, height
), *self
.barattrs
)
3946 # def setcolumns(self, graph, columns):
3947 # self.columns = columns
3949 # def getranges(self, points):
3950 # return {"x": (0, 10), "y": (0, 10), "z": (0, 1)}
3952 # def drawpoints(self, graph, points):
3957 ################################################################################
3959 ################################################################################
3964 defaultstyle
= symbol
3966 def __init__(self
, file, title
=helper
.nodefault
, context
={}, **columns
):
3968 if helper
.isstring(file):
3969 self
.data
= datamodule
.datafile(file)
3972 if title
is helper
.nodefault
:
3973 self
.title
= "(unknown)"
3977 for key
, column
in columns
.items():
3979 self
.columns
[key
] = self
.data
.getcolumnno(column
)
3980 except datamodule
.ColumnError
:
3981 self
.columns
[key
] = len(self
.data
.titles
)
3982 self
.data
.addcolumn(column
, context
=context
)
3984 def setstyle(self
, graph
, style
):
3986 self
.style
.setcolumns(graph
, self
.columns
)
3988 def getranges(self
):
3989 return self
.style
.getranges(self
.data
.data
)
3991 def setranges(self
, ranges
):
3994 def draw(self
, graph
):
3995 self
.style
.drawpoints(graph
, self
.data
.data
)
4002 def __init__(self
, expression
, title
=helper
.nodefault
, min=None, max=None, points
=100, parser
=mathtree
.parser(), context
={}):
4003 if title
is helper
.nodefault
:
4004 self
.title
= expression
4009 self
.points
= points
4010 self
.context
= context
4011 self
.result
, expression
= expression
.split("=")
4012 self
.mathtree
= parser
.parse(expression
)
4013 self
.variable
= None
4016 def setstyle(self
, graph
, style
):
4017 for variable
in self
.mathtree
.VarList():
4018 if variable
in graph
.axes
.keys():
4019 if self
.variable
is None:
4020 self
.variable
= variable
4022 raise ValueError("multiple variables found")
4023 if self
.variable
is None:
4024 raise ValueError("no variable found")
4025 self
.xaxis
= graph
.axes
[self
.variable
]
4027 self
.style
.setcolumns(graph
, {self
.variable
: 0, self
.result
: 1})
4029 def getranges(self
):
4031 return self
.style
.getranges(self
.data
)
4032 if None not in (self
.min, self
.max):
4033 return {self
.variable
: (self
.min, self
.max)}
4035 def setranges(self
, ranges
):
4036 if ranges
.has_key(self
.variable
):
4037 min, max = ranges
[self
.variable
]
4038 if self
.min is not None: min = self
.min
4039 if self
.max is not None: max = self
.max
4040 vmin
= self
.xaxis
.convert(min)
4041 vmax
= self
.xaxis
.convert(max)
4043 for i
in range(self
.points
):
4044 self
.context
[self
.variable
] = x
= self
.xaxis
.invert(vmin
+ (vmax
-vmin
)*i
/ (self
.points
-1.0))
4046 y
= self
.mathtree
.Calc(**self
.context
)
4047 except (ArithmeticError, ValueError):
4049 self
.data
.append((x
, y
))
4052 def draw(self
, graph
):
4053 self
.style
.drawpoints(graph
, self
.data
)
4056 class paramfunction
:
4060 def __init__(self
, varname
, min, max, expression
, title
=helper
.nodefault
, points
=100, parser
=mathtree
.parser(), context
={}):
4061 if title
is helper
.nodefault
:
4062 self
.title
= expression
4065 self
.varname
= varname
4068 self
.points
= points
4069 self
.expression
= {}
4071 varlist
, expressionlist
= expression
.split("=")
4072 parsestr
= mathtree
.ParseStr(expressionlist
)
4073 for key
in varlist
.split(","):
4075 if self
.mathtrees
.has_key(key
):
4076 raise ValueError("multiple assignment in tuple")
4078 self
.mathtrees
[key
] = parser
.ParseMathTree(parsestr
)
4080 except mathtree
.CommaFoundMathTreeParseError
, e
:
4081 self
.mathtrees
[key
] = e
.MathTree
4083 raise ValueError("unpack tuple of wrong size")
4084 if len(varlist
.split(",")) != len(self
.mathtrees
.keys()):
4085 raise ValueError("unpack tuple of wrong size")
4087 for i
in range(self
.points
):
4088 context
[self
.varname
] = self
.min + (self
.max-self
.min)*i
/ (self
.points
-1.0)
4090 for key
, tree
in self
.mathtrees
.items():
4091 line
.append(tree
.Calc(**context
))
4092 self
.data
.append(line
)
4094 def setstyle(self
, graph
, style
):
4097 for key
, index
in zip(self
.mathtrees
.keys(), xrange(sys
.maxint
)):
4098 columns
[key
] = index
4099 self
.style
.setcolumns(graph
, columns
)
4101 def getranges(self
):
4102 return self
.style
.getranges(self
.data
)
4104 def setranges(self
, ranges
):
4107 def draw(self
, graph
):
4108 self
.style
.drawpoints(graph
, self
.data
)