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 re
, math
, string
, sys
25 import bbox
, box
, canvas
, path
, unit
, mathtree
, color
, helper
26 import text
as textmodule
27 import data
as datamodule
28 import trafo
as trafomodule
31 goldenmean
= 0.5 * (math
.sqrt(5) + 1)
34 ################################################################################
36 ################################################################################
39 """interface definition of a map
40 maps convert a value into another value by bijective transformation f"""
46 "returns f^-1(y) where f^-1 is the inverse transformation (x=f^-1(f(x)) for all x)"
48 def setbasepoint(self
, basepoints
):
49 """set basepoints for the convertions
50 basepoints are tuples (x, y) with y == f(x) and x == f^-1(y)
51 the number of basepoints needed might depend on the transformation
52 usually two pairs are needed like for linear maps, logarithmic maps, etc."""
58 __implements__
= _Imap
60 def setbasepoints(self
, basepoints
):
61 self
.dydx
= (basepoints
[1][1] - basepoints
[0][1]) / float(basepoints
[1][0] - basepoints
[0][0])
62 self
.dxdy
= (basepoints
[1][0] - basepoints
[0][0]) / float(basepoints
[1][1] - basepoints
[0][1])
63 self
.x1
= basepoints
[0][0]
64 self
.y1
= basepoints
[0][1]
67 def convert(self
, value
):
68 return self
.y1
+ self
.dydx
* (value
- self
.x1
)
70 def invert(self
, value
):
71 return self
.x1
+ self
.dxdy
* (value
- self
.y1
)
76 __implements__
= _Imap
78 def setbasepoints(self
, basepoints
):
79 self
.dydx
= ((basepoints
[1][1] - basepoints
[0][1]) /
80 float(math
.log(basepoints
[1][0]) - math
.log(basepoints
[0][0])))
81 self
.dxdy
= ((math
.log(basepoints
[1][0]) - math
.log(basepoints
[0][0])) /
82 float(basepoints
[1][1] - basepoints
[0][1]))
83 self
.x1
= math
.log(basepoints
[0][0])
84 self
.y1
= basepoints
[0][1]
87 def convert(self
, value
):
88 return self
.y1
+ self
.dydx
* (math
.log(value
) - self
.x1
)
90 def invert(self
, value
):
91 return math
.exp(self
.x1
+ self
.dxdy
* (value
- self
.y1
))
95 ################################################################################
97 # please note the nomenclature:
98 # - a partition is a list of tick instances; to reduce name clashes, a
99 # partition is called ticks
100 # - a partitioner is a class creating a single or several ticks
101 # - a axis has a part attribute where it stores a partitioner or/and some
102 # (manually set) ticks -> the part attribute is used to create the ticks
103 # in the axis finish method
104 ################################################################################
108 """fraction class for rational arithmetics
109 the axis partitioning uses rational arithmetics (with infinite accuracy)
110 basically it contains self.enum and self.denom"""
112 def stringfrac(self
, s
):
113 "converts a string 0.123 into a frac"
114 expparts
= s
.split("e")
115 if len(expparts
) > 2:
116 raise ValueError("multiple 'e' found in '%s'" % s
)
117 commaparts
= expparts
[0].split(".")
118 if len(commaparts
) > 2:
119 raise ValueError("multiple '.' found in '%s'" % expparts
[0])
120 if len(commaparts
) == 1:
121 commaparts
= [commaparts
[0], ""]
122 result
= frac((1, 10l), power
=len(commaparts
[1]))
123 neg
= len(commaparts
[0]) and commaparts
[0][0] == "-"
125 commaparts
[0] = commaparts
[0][1:]
126 elif len(commaparts
[0]) and commaparts
[0][0] == "+":
127 commaparts
[0] = commaparts
[0][1:]
128 if len(commaparts
[0]):
129 if not commaparts
[0].isdigit():
130 raise ValueError("unrecognized characters in '%s'" % s
)
131 x
= long(commaparts
[0])
134 if len(commaparts
[1]):
135 if not commaparts
[1].isdigit():
136 raise ValueError("unrecognized characters in '%s'" % s
)
137 y
= long(commaparts
[1])
140 result
.enum
= x
*result
.denom
+y
142 result
.enum
= -result
.enum
143 if len(expparts
) == 2:
144 neg
= expparts
[1][0] == "-"
146 expparts
[1] = expparts
[1][1:]
147 elif expparts
[1][0] == "+":
148 expparts
[1] = expparts
[1][1:]
149 if not expparts
[1].isdigit():
150 raise ValueError("unrecognized characters in '%s'" % s
)
152 result
*= frac((1, 10l), power
=long(expparts
[1]))
154 result
*= frac((10, 1l), power
=long(expparts
[1]))
157 def floatfrac(self
, x
, floatprecision
):
158 "converts a float into a frac with final resolution"
159 if helper
.isinteger(floatprecision
) and floatprecision
< 0:
160 # this would be extremly vulnerable
161 raise RuntimeError("float resolution must be non-negative integer")
162 return self
.stringfrac(("%%.%ig" % floatprecision
) % x
)
164 def __init__(self
, x
, power
=None, floatprecision
=10):
165 "for power!=None: frac=(enum/denom)**power"
166 if helper
.isnumber(x
):
167 value
= self
.floatfrac(x
, floatprecision
)
168 enum
, denom
= value
.enum
, value
.denom
169 elif helper
.isstring(x
):
170 fraction
= x
.split("/")
171 if len(fraction
) > 2:
172 raise ValueError("multiple '/' found in '%s'" % x
)
173 value
= self
.stringfrac(fraction
[0])
174 if len(fraction
) == 2:
175 value2
= self
.stringfrac(fraction
[1])
176 value
= value
/ value2
177 enum
, denom
= value
.enum
, value
.denom
182 enum
, denom
= x
.enum
, x
.denom
183 if not helper
.isinteger(enum
) or not helper
.isinteger(denom
): raise TypeError("integer type expected")
184 if not denom
: raise ZeroDivisionError("zero denominator")
186 if not helper
.isinteger(power
): raise TypeError("integer type expected")
188 self
.enum
= long(enum
) ** power
189 self
.denom
= long(denom
) ** power
191 self
.enum
= long(denom
) ** (-power
)
192 self
.denom
= long(enum
) ** (-power
)
197 def __cmp__(self
, other
):
200 return cmp(self
.enum
* other
.denom
, other
.enum
* self
.denom
)
203 return frac((abs(self
.enum
), abs(self
.denom
)))
205 def __mul__(self
, other
):
206 return frac((self
.enum
* other
.enum
, self
.denom
* other
.denom
))
208 def __div__(self
, other
):
209 return frac((self
.enum
* other
.denom
, self
.denom
* other
.enum
))
212 "caution: avoid final precision of floats"
213 return float(self
.enum
) / self
.denom
216 return "%i/%i" % (self
.enum
, self
.denom
)
221 a tick is a frac enhanced by
222 - self.ticklevel (0 = tick, 1 = subtick, etc.)
223 - self.labellevel (0 = label, 1 = sublabel, etc.)
224 - self.label (a string) and self.labelattrs (a list, defaults to [])
225 When ticklevel or labellevel is None, no tick or label is present at that value.
226 When label is None, it should be automatically created (and stored), once the
227 an axis painter needs it. Classes, which implement _Itexter do precisely that."""
229 def __init__(self
, pos
, ticklevel
=0, labellevel
=0, label
=None, labelattrs
=[], **kwargs
):
230 """initializes the instance
231 - see class description for the parameter description
232 - **kwargs are passed to the frac constructor"""
233 frac
.__init
__(self
, pos
, **kwargs
)
234 self
.ticklevel
= ticklevel
235 self
.labellevel
= labellevel
237 self
.labelattrs
= helper
.ensurelist(labelattrs
)[:]
239 def merge(self
, other
):
240 """merges two ticks together:
241 - the lower ticklevel/labellevel wins
242 - the label is *never* taken over from other
243 - the ticks should be at the same position (otherwise it doesn't make sense)
244 -> this is NOT checked"""
245 if self
.ticklevel
is None or (other
.ticklevel
is not None and other
.ticklevel
< self
.ticklevel
):
246 self
.ticklevel
= other
.ticklevel
247 if self
.labellevel
is None or (other
.labellevel
is not None and other
.labellevel
< self
.labellevel
):
248 self
.labellevel
= other
.labellevel
251 return "tick(%r, %r, %s, %s, %s)" % (self
.enum
, self
.denom
, self
.ticklevel
, self
.labellevel
, self
.label
)
254 def _mergeticklists(list1
, list2
):
255 """helper function to merge tick lists
256 - return a merged list of ticks out of list1 and list2
257 - CAUTION: original lists have to be ordered
258 (the returned list is also ordered)
259 - CAUTION: original lists are modified and they share references to
261 # TODO: improve this using bisect?!
262 if list1
is None: return list2
263 if list2
is None: return list1
267 while 1: # we keep on going until we reach an index error
268 while list2
[j
] < list1
[i
]: # insert tick
269 list1
.insert(i
, list2
[j
])
272 if list2
[j
] == list1
[i
]: # merge tick
273 list1
[i
].merge(list2
[j
])
282 def _mergelabels(ticks
, labels
):
283 """helper function to merge labels into ticks
284 - when labels is not None, the label of all ticks with
285 labellevel different from None are set
286 - labels need to be a sequence of sequences of strings,
287 where the first sequence contain the strings to be
288 used as labels for the ticks with labellevel 0,
289 the second sequence for labellevel 1, etc.
290 - when the maximum labellevel is 0, just a sequence of
291 strings might be provided as the labels argument
292 - IndexError is raised, when a sequence length doesn't match"""
293 if helper
.issequenceofsequences(labels
):
294 for label
, level
in zip(labels
, xrange(sys
.maxint
)):
295 usetext
= helper
.ensuresequence(label
)
298 if tick
.labellevel
== level
:
299 tick
.label
= usetext
[i
]
301 if i
!= len(usetext
):
302 raise IndexError("wrong sequence length of labels at level %i" % level
)
303 elif labels
is not None:
304 usetext
= helper
.ensuresequence(labels
)
307 if tick
.labellevel
== 0:
308 tick
.label
= usetext
[i
]
310 if i
!= len(usetext
):
311 raise IndexError("wrong sequence length of labels")
315 """interface definition of a partition scheme
316 partition schemes are used to create a list of ticks"""
318 def defaultpart(self
, min, max, extendmin
, extendmax
):
319 """create a partition
320 - returns an ordered list of ticks for the interval min to max
321 - the interval is given in float numbers, thus an appropriate
322 conversion to rational numbers has to be performed
323 - extendmin and extendmax are booleans (integers)
324 - when extendmin or extendmax is set, the ticks might
325 extend the min-max range towards lower and higher
326 ranges, respectively"""
329 """create another partition which contains less ticks
330 - this method is called several times after a call of defaultpart
331 - returns an ordered list of ticks with less ticks compared to
332 the partition returned by defaultpart and by previous calls
334 - the creation of a partition with strictly *less* ticks
335 is not to be taken serious
336 - the method might return None, when no other appropriate
337 partition can be created"""
341 """create another partition which contains more ticks
342 see lesspart, but increase the number of ticks"""
346 """manual partition scheme
347 ticks and labels at positions explicitly provided to the constructor"""
349 __implements__
= _Ipart
351 def __init__(self
, tickpos
=None, labelpos
=None, labels
=None, mix
=()):
352 """configuration of the partition scheme
353 - tickpos and labelpos should be a sequence of sequences, where
354 the first sequence contains the values to be used for
355 ticks with ticklevel/labellevel 0, the second sequence for
356 ticklevel/labellevel 1, etc.
357 - tickpos and labelpos values are passed to the frac constructor
358 - when the maximum ticklevel/labellevel is 0, just a sequence
359 might be provided in tickpos and labelpos
360 - when labelpos is None and tickpos is not None, the tick entries
361 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
362 - labels are applied to the resulting partition via the
363 mergelabels function (additional information available there)
364 - mix specifies another partition to be merged into the
366 if tickpos
is None and labelpos
is not None:
367 self
.tickpos
= helper
.ensuresequence(helper
.getsequenceno(labelpos
, 0))
369 self
.tickpos
= tickpos
370 if labelpos
is None and tickpos
is not None:
371 self
.labelpos
= helper
.ensuresequence(helper
.getsequenceno(tickpos
, 0))
373 self
.labelpos
= labelpos
377 def checkfraclist(self
, *fracs
):
378 "orders a list of fracs, equal entries are not allowed"
379 if not len(fracs
): return ()
383 for item
in sorted[1:]:
385 raise ValueError("duplicate entry found")
390 "create the partition as described in the constructor"
391 ticks
= list(self
.mix
)
392 if helper
.issequenceofsequences(self
.tickpos
):
393 for fracs
, level
in zip(self
.tickpos
, xrange(sys
.maxint
)):
394 ticks
= _mergeticklists(ticks
, [tick((f
.enum
, f
.denom
), ticklevel
=level
, labellevel
=None)
395 for f
in self
.checkfraclist(*map(frac
, helper
.ensuresequence(fracs
)))])
397 map(frac
, helper
.ensuresequence(self
.tickpos
))
398 ticks
= _mergeticklists(ticks
, [tick((f
.enum
, f
.denom
), ticklevel
=0, labellevel
=None)
399 for f
in self
.checkfraclist(*map(frac
, helper
.ensuresequence(self
.tickpos
)))])
401 if helper
.issequenceofsequences(self
.labelpos
):
402 for fracs
, level
in zip(self
.labelpos
, xrange(sys
.maxint
)):
403 ticks
= _mergeticklists(ticks
, [tick((f
.enum
, f
.denom
), ticklevel
=None, labellevel
= level
)
404 for f
in self
.checkfraclist(*map(frac
, helper
.ensuresequence(fracs
)))])
406 ticks
= _mergeticklists(ticks
, [tick((f
.enum
, f
.denom
), ticklevel
=None, labellevel
= 0)
407 for f
in self
.checkfraclist(*map(frac
, helper
.ensuresequence(self
.labelpos
)))])
409 _mergelabels(ticks
, self
.labels
)
413 def defaultpart(self
, min, max, extendmin
, extendmax
):
414 # XXX: we do not take care of the parameters -> correct?
425 """linear partition scheme
426 ticks and label distances are explicitly provided to the constructor"""
428 __implements__
= _Ipart
430 def __init__(self
, tickdist
=None, labeldist
=None, labels
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10, mix
=()):
431 """configuration of the partition scheme
432 - tickdist and labeldist should be a sequence, where the first value
433 is the distance between ticks with ticklevel/labellevel 0,
434 the second sequence for ticklevel/labellevel 1, etc.;
435 a single entry is allowed without being a sequence
436 - tickdist and labeldist values are passed to the frac constructor
437 - when labeldist is None and tickdist is not None, the tick entries
438 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
439 - labels are applied to the resulting partition via the
440 mergelabels function (additional information available there)
441 - extendtick allows for the extension of the range given to the
442 defaultpart method to include the next tick with the specified
443 level (None turns off this feature); note, that this feature is
444 also disabled, when an axis prohibits its range extension by
445 the extendmin/extendmax variables given to the defaultpart method
446 - extendlabel is analogous to extendtick, but for labels
447 - epsilon allows for exceeding the axis range by this relative
448 value (relative to the axis range given to the defaultpart method)
449 without creating another tick specified by extendtick/extendlabel
450 - mix specifies another partition to be merged into the
452 if tickdist
is None and labeldist
is not None:
453 self
.ticklist
= (frac(helper
.ensuresequence(labeldist
)[0]),)
455 self
.ticklist
= map(frac
, helper
.ensuresequence(tickdist
))
456 if labeldist
is None and tickdist
is not None:
457 self
.labellist
= (frac(helper
.ensuresequence(tickdist
)[0]),)
459 self
.labellist
= map(frac
, helper
.ensuresequence(labeldist
))
461 self
.extendtick
= extendtick
462 self
.extendlabel
= extendlabel
463 self
.epsilon
= epsilon
466 def extendminmax(self
, min, max, frac
, extendmin
, extendmax
):
467 """return new min, max tuple extending the range min, max
468 - frac is the tick distance to be used
469 - extendmin and extendmax are booleans to allow for the extension"""
471 min = float(frac
) * math
.floor(min / float(frac
) + self
.epsilon
)
473 max = float(frac
) * math
.ceil(max / float(frac
) - self
.epsilon
)
476 def getticks(self
, min, max, frac
, ticklevel
=None, labellevel
=None):
477 """return a list of equal spaced ticks
478 - the tick distance is frac, the ticklevel is set to ticklevel and
479 the labellevel is set to labellevel
480 - min, max is the range where ticks should be placed"""
481 imin
= int(math
.ceil(min / float(frac
) - 0.5 * self
.epsilon
))
482 imax
= int(math
.floor(max / float(frac
) + 0.5 * self
.epsilon
))
484 for i
in range(imin
, imax
+ 1):
485 ticks
.append(tick((long(i
) * frac
.enum
, frac
.denom
), ticklevel
=ticklevel
, labellevel
=labellevel
))
488 def defaultpart(self
, min, max, extendmin
, extendmax
):
489 if self
.extendtick
is not None and len(self
.ticklist
) > self
.extendtick
:
490 min, max = self
.extendminmax(min, max, self
.ticklist
[self
.extendtick
], extendmin
, extendmax
)
491 if self
.extendlabel
is not None and len(self
.labellist
) > self
.extendlabel
:
492 min, max = self
.extendminmax(min, max, self
.labellist
[self
.extendlabel
], extendmin
, extendmax
)
494 ticks
= list(self
.mix
)
495 for i
in range(len(self
.ticklist
)):
496 ticks
= _mergeticklists(ticks
, self
.getticks(min, max, self
.ticklist
[i
], ticklevel
= i
))
497 for i
in range(len(self
.labellist
)):
498 ticks
= _mergeticklists(ticks
, self
.getticks(min, max, self
.labellist
[i
], labellevel
= i
))
500 _mergelabels(ticks
, self
.labels
)
512 """automatic linear partition scheme
513 - possible tick distances are explicitly provided to the constructor
514 - tick distances are adjusted to the axis range by multiplication or division by 10"""
516 __implements__
= _Ipart
518 defaultvariants
= ((frac((1, 1)), frac((1, 2))),
519 (frac((2, 1)), frac((1, 1))),
520 (frac((5, 2)), frac((5, 4))),
521 (frac((5, 1)), frac((5, 2))))
523 def __init__(self
, variants
=defaultvariants
, extendtick
=0, epsilon
=1e-10, mix
=()):
524 """configuration of the partition scheme
525 - variants is a sequence of tickdist
526 - tickdist should be a sequence, where the first value
527 is the distance between ticks with ticklevel 0,
528 the second for ticklevel 1, etc.
529 - tickdist values are passed to the frac constructor
530 - labellevel is set to None except for those ticks in the partitions,
531 where ticklevel is zero. There labellevel is also set to zero.
532 - extendtick allows for the extension of the range given to the
533 defaultpart method to include the next tick with the specified
534 level (None turns off this feature); note, that this feature is
535 also disabled, when an axis prohibits its range extension by
536 the extendmin/extendmax variables given to the defaultpart method
537 - epsilon allows for exceeding the axis range by this relative
538 value (relative to the axis range given to the defaultpart method)
539 without creating another tick specified by extendtick
540 - mix specifies another partition to be merged into the
542 self
.variants
= variants
543 self
.extendtick
= extendtick
544 self
.epsilon
= epsilon
547 def defaultpart(self
, min, max, extendmin
, extendmax
):
548 logmm
= math
.log(max - min) / math
.log(10)
549 if logmm
< 0: # correction for rounding towards zero of the int routine
550 base
= frac((10L, 1), int(logmm
- 1))
552 base
= frac((10L, 1), int(logmm
))
553 ticks
= map(frac
, self
.variants
[0])
554 useticks
= [tick
* base
for tick
in ticks
]
555 self
.lesstickindex
= self
.moretickindex
= 0
556 self
.lessbase
= frac((base
.enum
, base
.denom
))
557 self
.morebase
= frac((base
.enum
, base
.denom
))
558 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
559 part
= linpart(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
, mix
=self
.mix
)
560 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
563 if self
.lesstickindex
< len(self
.variants
) - 1:
564 self
.lesstickindex
+= 1
566 self
.lesstickindex
= 0
567 self
.lessbase
.enum
*= 10
568 ticks
= map(frac
, self
.variants
[self
.lesstickindex
])
569 useticks
= [tick
* self
.lessbase
for tick
in ticks
]
570 part
= linpart(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
, mix
=self
.mix
)
571 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
574 if self
.moretickindex
:
575 self
.moretickindex
-= 1
577 self
.moretickindex
= len(self
.variants
) - 1
578 self
.morebase
.denom
*= 10
579 ticks
= map(frac
, self
.variants
[self
.moretickindex
])
580 useticks
= [tick
* self
.morebase
for tick
in ticks
]
581 part
= linpart(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
, mix
=self
.mix
)
582 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
586 """storage class for the definition of logarithmic axes partitions
587 instances of this class define tick positions suitable for
588 logarithmic axes by the following instance variables:
589 - exp: integer, which defines multiplicator (usually 10)
590 - pres: sequence of tick positions (rational numbers, e.g. instances of frac)
591 possible positions are these tick positions and arbitrary divisions
592 and multiplications by the exp value"""
594 def __init__(self
, pres
, exp
):
595 "create a preexp instance and store its pres and exp information"
596 self
.pres
= helper
.ensuresequence(pres
)
600 class logpart(linpart
):
601 """logarithmic partition scheme
602 ticks and label positions are explicitly provided to the constructor"""
604 __implements__
= _Ipart
606 pre1exp5
= preexp(frac((1, 1)), 100000)
607 pre1exp4
= preexp(frac((1, 1)), 10000)
608 pre1exp3
= preexp(frac((1, 1)), 1000)
609 pre1exp2
= preexp(frac((1, 1)), 100)
610 pre1exp
= preexp(frac((1, 1)), 10)
611 pre125exp
= preexp((frac((1, 1)), frac((2, 1)), frac((5, 1))), 10)
612 pre1to9exp
= preexp(map(lambda x
: frac((x
, 1)), range(1, 10)), 10)
613 # ^- we always include 1 in order to get extendto(tick|label)level to work as expected
615 def __init__(self
, tickpos
=None, labelpos
=None, labels
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10, mix
=()):
616 """configuration of the partition scheme
617 - tickpos and labelpos should be a sequence, where the first entry
618 is a preexp instance describing ticks with ticklevel/labellevel 0,
619 the second is a preexp instance for ticklevel/labellevel 1, etc.;
620 a single entry is allowed without being a sequence
621 - when labelpos is None and tickpos is not None, the tick entries
622 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
623 - labels are applied to the resulting partition via the
624 mergetexts function (additional information available there)
625 - extendtick allows for the extension of the range given to the
626 defaultpart method to include the next tick with the specified
627 level (None turns off this feature); note, that this feature is
628 also disabled, when an axis prohibits its range extension by
629 the extendmin/extendmax variables given to the defaultpart method
630 - extendlabel is analogous to extendtick, but for labels
631 - epsilon allows for exceeding the axis range by this relative
632 logarithm value (relative to the logarithm axis range given
633 to the defaultpart method) without creating another tick
634 specified by extendtick/extendlabel
635 - mix specifies another partition to be merged into the
637 if tickpos
is None and labels
is not None:
638 self
.ticklist
= (helper
.ensuresequence(labelpos
)[0],)
640 self
.ticklist
= helper
.ensuresequence(tickpos
)
642 if labelpos
is None and tickpos
is not None:
643 self
.labellist
= (helper
.ensuresequence(tickpos
)[0],)
645 self
.labellist
= helper
.ensuresequence(labelpos
)
647 self
.extendtick
= extendtick
648 self
.extendlabel
= extendlabel
649 self
.epsilon
= epsilon
652 def extendminmax(self
, min, max, preexp
, extendmin
, extendmax
):
653 """return new min, max tuple extending the range min, max
654 preexp describes the allowed tick positions
655 extendmin and extendmax are booleans to allow for the extension"""
658 for i
in xrange(len(preexp
.pres
)):
659 imin
= int(math
.floor(math
.log(min / float(preexp
.pres
[i
])) /
660 math
.log(preexp
.exp
) + self
.epsilon
)) + 1
661 imax
= int(math
.ceil(math
.log(max / float(preexp
.pres
[i
])) /
662 math
.log(preexp
.exp
) - self
.epsilon
)) - 1
663 if minpower
is None or imin
< minpower
:
664 minpower
, minindex
= imin
, i
665 if maxpower
is None or imax
>= maxpower
:
666 maxpower
, maxindex
= imax
, i
668 minfrac
= preexp
.pres
[minindex
- 1]
670 minfrac
= preexp
.pres
[-1]
672 if maxindex
!= len(preexp
.pres
) - 1:
673 maxfrac
= preexp
.pres
[maxindex
+ 1]
675 maxfrac
= preexp
.pres
[0]
678 min = float(minfrac
) * float(preexp
.exp
) ** minpower
680 max = float(maxfrac
) * float(preexp
.exp
) ** maxpower
683 def getticks(self
, min, max, preexp
, ticklevel
=None, labellevel
=None):
684 """return a list of ticks
685 - preexp describes the allowed tick positions
686 - the ticklevel of the ticks is set to ticklevel and
687 the labellevel is set to labellevel
688 - min, max is the range where ticks should be placed"""
689 ticks
= list(self
.mix
)
692 for f
in preexp
.pres
:
694 imin
= int(math
.ceil(math
.log(min / float(f
)) /
695 math
.log(preexp
.exp
) - 0.5 * self
.epsilon
))
696 imax
= int(math
.floor(math
.log(max / float(f
)) /
697 math
.log(preexp
.exp
) + 0.5 * self
.epsilon
))
698 for i
in range(imin
, imax
+ 1):
699 pos
= f
* frac((preexp
.exp
, 1), i
)
700 fracticks
.append(tick((pos
.enum
, pos
.denom
), ticklevel
= ticklevel
, labellevel
= labellevel
))
701 ticks
= _mergeticklists(ticks
, fracticks
)
705 class autologpart(logpart
):
706 """automatic logarithmic partition scheme
707 possible tick positions are explicitly provided to the constructor"""
709 __implements__
= _Ipart
711 defaultvariants
= (((logpart
.pre1exp
, # ticks
712 logpart
.pre1to9exp
), # subticks
713 (logpart
.pre1exp
, # labels
714 logpart
.pre125exp
)), # sublevels
716 ((logpart
.pre1exp
, # ticks
717 logpart
.pre1to9exp
), # subticks
718 None), # labels like ticks
720 ((logpart
.pre1exp2
, # ticks
721 logpart
.pre1exp
), # subticks
722 None), # labels like ticks
724 ((logpart
.pre1exp3
, # ticks
725 logpart
.pre1exp
), # subticks
726 None), # labels like ticks
728 ((logpart
.pre1exp4
, # ticks
729 logpart
.pre1exp
), # subticks
730 None), # labels like ticks
732 ((logpart
.pre1exp5
, # ticks
733 logpart
.pre1exp
), # subticks
734 None)) # labels like ticks
736 def __init__(self
, variants
=defaultvariants
, extendtick
=0, extendlabel
=None, epsilon
=1e-10, mix
=()):
737 """configuration of the partition scheme
738 - variants should be a sequence of pairs of sequences of preexp
740 - within each pair the first sequence contains preexp, where
741 the first preexp instance describes ticks positions with
742 ticklevel 0, the second preexp for ticklevel 1, etc.
743 - the second sequence within each pair describes the same as
744 before, but for labels
745 - within each pair: when the second entry (for the labels) is None
746 and the first entry (for the ticks) ticks is not None, the tick
747 entries for ticklevel 0 are used for labels and vice versa
749 - extendtick allows for the extension of the range given to the
750 defaultpart method to include the next tick with the specified
751 level (None turns off this feature); note, that this feature is
752 also disabled, when an axis prohibits its range extension by
753 the extendmin/extendmax variables given to the defaultpart method
754 - extendlabel is analogous to extendtick, but for labels
755 - epsilon allows for exceeding the axis range by this relative
756 logarithm value (relative to the logarithm axis range given
757 to the defaultpart method) without creating another tick
758 specified by extendtick/extendlabel
759 - mix specifies another partition to be merged into the
761 self
.variants
= variants
762 if len(variants
) > 2:
763 self
.variantsindex
= divmod(len(variants
), 2)[0]
765 self
.variantsindex
= 0
766 self
.extendtick
= extendtick
767 self
.extendlabel
= extendlabel
768 self
.epsilon
= epsilon
771 def defaultpart(self
, min, max, extendmin
, extendmax
):
772 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
773 self
.morevariantsindex
= self
.variantsindex
774 self
.lessvariantsindex
= self
.variantsindex
775 part
= logpart(tickpos
=self
.variants
[self
.variantsindex
][0], labelpos
=self
.variants
[self
.variantsindex
][1],
776 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
, mix
=self
.mix
)
777 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
780 self
.lessvariantsindex
+= 1
781 if self
.lessvariantsindex
< len(self
.variants
):
782 part
= logpart(tickpos
=self
.variants
[self
.lessvariantsindex
][0], labelpos
=self
.variants
[self
.lessvariantsindex
][1],
783 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
, mix
=self
.mix
)
784 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
787 self
.morevariantsindex
-= 1
788 if self
.morevariantsindex
>= 0:
789 part
= logpart(tickpos
=self
.variants
[self
.morevariantsindex
][0], labelpos
=self
.variants
[self
.morevariantsindex
][1],
790 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
, mix
=self
.mix
)
791 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
795 ################################################################################
797 # conseptional remarks:
798 # - raters are used to calculate a rating for a realization of something
799 # - here, a rating means a positive floating point value
800 # - ratings are used to order those realizations by their suitability (lower
801 # ratings are better)
802 # - a rating of None means not suitable at all (those realizations should be
804 ################################################################################
809 - a cube rater has an optimal value, where the rate becomes zero
810 - for a left (below the optimum) and a right value (above the optimum),
811 the rating is value is set to 1 (modified by an overall weight factor
813 - the analytic form of the rating is cubic for both, the left and
814 the right side of the rater, independently"""
816 # __implements__ = sole implementation
818 def __init__(self
, opt
, left
=None, right
=None, weight
=1):
819 """initializes the rater
820 - by default, left is set to zero, right is set to 3*opt
821 - left should be smaller than opt, right should be bigger than opt
822 - weight should be positive and is a factor multiplicated to the rates"""
832 def rate(self
, value
, density
):
833 """returns a rating for a value
834 - the density lineary rescales the rater (the optimum etc.),
835 e.g. a value bigger than one increases the optimum (when it is
836 positive) and a value lower than one decreases the optimum (when
837 it is positive); the density itself should be positive"""
838 opt
= self
.opt
* density
840 other
= self
.left
* density
842 other
= self
.right
* density
845 factor
= (value
- opt
) / float(other
- opt
)
846 return self
.weight
* (factor
** 3)
850 # TODO: update docstring
851 """a distance rater (rates a list of distances)
852 - the distance rater rates a list of distances by rating each independently
853 and returning the average rate
854 - there is an optimal value, where the rate becomes zero
855 - the analytic form is linary for values above the optimal value
856 (twice the optimal value has the rating one, three times the optimal
857 value has the rating two, etc.)
858 - the analytic form is reciprocal subtracting one for values below the
859 optimal value (halve the optimal value has the rating one, one third of
860 the optimal value has the rating two, etc.)"""
862 # __implements__ = sole implementation
864 def __init__(self
, opt
, weight
=0.1):
865 """inititializes the rater
866 - opt is the optimal length (a visual PyX length)
867 - weight should be positive and is a factor multiplicated to the rates"""
871 def rate(self
, distances
, density
):
873 - the distances are a sequence of positive floats in PostScript points
874 - the density lineary rescales the rater (the optimum etc.),
875 e.g. a value bigger than one increases the optimum (when it is
876 positive) and a value lower than one decreases the optimum (when
877 it is positive); the density itself should be positive"""
879 opt
= unit
.topt(unit
.length(self
.opt_str
, default_type
="v")) / density
881 for distance
in distances
:
883 rate
+= self
.weight
* (opt
/ distance
- 1)
885 rate
+= self
.weight
* (distance
/ opt
- 1)
886 return rate
/ float(len(distances
))
891 - the rating of axes is splited into two separate parts:
892 - rating of the ticks in terms of the number of ticks, subticks,
894 - rating of the label distances
895 - in the end, a rate for ticks is the sum of these rates
896 - it is useful to first just rate the number of ticks etc.
897 and selecting those partitions, where this fits well -> as soon
898 as an complete rate (the sum of both parts from the list above)
899 of a first ticks is below a rate of just the number of ticks,
900 subticks labels etc. of other ticks, those other ticks will never
901 be better than the first one -> we gain speed by minimizing the
902 number of ticks, where label distances have to be taken into account)
903 - both parts of the rating are shifted into instances of raters
904 defined above --- right now, there is not yet a strict interface
905 for this delegation (should be done as soon as it is needed)"""
907 # __implements__ = sole implementation
909 linticks
= (cuberater(4), cuberater(10, weight
=0.5), )
910 linlabels
= (cuberater(4), )
911 logticks
= (cuberater(5, right
=20), cuberater(20, right
=100, weight
=0.5), )
912 loglabels
= (cuberater(5, right
=20), cuberater(5, left
=-20, right
=20, weight
=0.5), )
913 stdtickrange
= cuberater(1, weight
=2)
914 stddistance
= distancerater("1 cm")
916 def __init__(self
, ticks
=linticks
, labels
=linlabels
, tickrange
=stdtickrange
, distance
=stddistance
):
917 """initializes the axis rater
918 - ticks and labels are lists of instances of a value rater
919 - the first entry in ticks rate the number of ticks, the
920 second the number of subticks, etc.; when there are no
921 ticks of a level or there is not rater for a level, the
922 level is just ignored
923 - labels is analogous, but for labels
924 - within the rating, all ticks with a higher level are
925 considered as ticks for a given level
926 - tickrange is a value rater instance, which rates the covering
927 of an axis range by the ticks (as a relative value of the
928 tick range vs. the axis range), ticks might cover less or
929 more than the axis range (for the standard automatic axis
930 partition schemes an extention of the axis range is normal
931 and should get some penalty)
932 - distance is an distance rater instance"""
933 self
.rateticks
= ticks
934 self
.ratelabels
= labels
935 self
.tickrange
= tickrange
936 self
.distance
= distance
938 def ratepart(self
, axis
, ticks
, density
):
939 """rates ticks by the number of ticks, subticks, labels etc.
940 - takes into account the number of ticks, subticks, labels
941 etc. and the coverage of the axis range by the ticks
942 - when there are no ticks of a level or there was not rater
943 given in the constructor for a level, the level is just
945 - the method returns the sum of the rating results divided
946 by the sum of the weights of the raters
947 - within the rating, all ticks with a higher level are
948 considered as ticks for a given level"""
949 maxticklevel
= maxlabellevel
= 0
951 if tick
.ticklevel
>= maxticklevel
:
952 maxticklevel
= tick
.ticklevel
+ 1
953 if tick
.labellevel
>= maxlabellevel
:
954 maxlabellevel
= tick
.labellevel
+ 1
955 numticks
= [0]*maxticklevel
956 numlabels
= [0]*maxlabellevel
958 if tick
.ticklevel
is not None:
959 for level
in range(tick
.ticklevel
, maxticklevel
):
961 if tick
.labellevel
is not None:
962 for level
in range(tick
.labellevel
, maxlabellevel
):
963 numlabels
[level
] += 1
966 for numtick
, rater
in zip(numticks
, self
.rateticks
):
967 rate
+= rater
.rate(numtick
, density
)
968 weight
+= rater
.weight
969 for numlabel
, rater
in zip(numlabels
, self
.ratelabels
):
970 rate
+= rater
.rate(numlabel
, density
)
971 weight
+= rater
.weight
973 # XXX: tickrange was not yet applied
974 # TODO: density == 1?
975 rate
+= self
.tickrange
.rate(axis
.convert(float(ticks
[-1]) * axis
.divisor
) -
976 axis
.convert(float(ticks
[0]) * axis
.divisor
), 1)
978 rate
+= self
.tickrange
.rate(0, 1)
979 weight
+= self
.tickrange
.weight
982 def ratelayout(self
, axiscanvas
, density
):
983 """rate distances of the labels in an axis canvas
984 - the distances should be collected as box distances of
986 - the axiscanvas provides a labels attribute for easy
987 access to the labels whose distances have to be taken
989 - the density is used within the distancerate instance"""
990 if len(axiscanvas
.labels
) > 1:
992 distances
= [axiscanvas
.labels
[i
]._boxdistance
(axiscanvas
.labels
[i
+1]) for i
in range(len(axiscanvas
.labels
) - 1)]
993 except box
.BoxCrossError
:
995 return self
.distance
.rate(distances
, density
)
1000 ################################################################################
1002 # texter automatically create labels for tick instances
1003 ################################################################################
1008 def labels(self
, ticks
):
1009 """fill the label attribute of ticks
1010 - ticks is a list of instances of tick
1011 - for each element of ticks the value of the attribute label is set to
1012 a string appropriate to the attributes enum and denom of that tick
1014 - label attributes of the tick instances are just kept, whenever they
1015 are not equal to None
1016 - the method might extend the labelattrs attribute of the ticks"""
1019 class rationaltexter
:
1020 "a texter creating rational labels (e.g. 'a/b' or even 'a \over b')"
1021 # XXX: we use divmod here to be more expicit
1023 __implements__
= _Itexter
1025 def __init__(self
, prefix
="", infix
="", suffix
="",
1026 enumprefix
="", enuminfix
="", enumsuffix
="",
1027 denomprefix
="", denominfix
="", denomsuffix
="",
1028 minus
="-", minuspos
=0, over
=r
"{{%s}\over{%s}}",
1029 equaldenom
=0, skip1
=1, skipenum0
=1, skipenum1
=1, skipdenom1
=1,
1030 labelattrs
=textmodule
.mathmode
):
1031 r
"""initializes the instance
1032 - prefix, infix, and suffix (strings) are added at the begin,
1033 immediately after the minus, and at the end of the label,
1035 - prefixenum, infixenum, and suffixenum (strings) are added
1036 to the labels enumerator correspondingly
1037 - prefixdenom, infixdenom, and suffixdenom (strings) are added
1038 to the labels denominator correspondingly
1039 - minus (string) is inserted for negative numbers
1040 - minuspos is an integer, which determines the position, where the
1041 minus sign has to be placed; the following values are allowed:
1042 1 - writes the minus in front of the enumerator
1043 0 - writes the minus in front of the hole fraction
1044 -1 - writes the minus in front of the denominator
1045 - over (string) is taken as a format string generating the
1046 fraction bar; it has to contain exactly two string insert
1047 operators "%s" -- the first for the enumerator and the second
1048 for the denominator; by far the most common examples are
1049 r"{{%s}\over{%s}}" and "{{%s}/{%s}}"
1050 - usually the enumerator and denominator are canceled; however,
1051 when equaldenom is set, the least common multiple of all
1052 denominators is used
1053 - skip1 (boolean) just prints the prefix, the minus (if present),
1054 the infix and the suffix, when the value is plus or minus one
1055 and at least one of prefix, infix and the suffix is present
1056 - skipenum0 (boolean) just prints a zero instead of
1057 the hole fraction, when the enumerator is zero;
1058 no prefixes, infixes, and suffixes are taken into account
1059 - skipenum1 (boolean) just prints the enumprefix, the minus (if present),
1060 the enuminfix and the enumsuffix, when the enum value is plus or minus one
1061 and at least one of enumprefix, enuminfix and the enumsuffix is present
1062 - skipdenom1 (boolean) just prints the enumerator instead of
1063 the hole fraction, when the denominator is one and none of the parameters
1064 denomprefix, denominfix and denomsuffix are set and minuspos is not -1 or the
1065 fraction is positive
1066 - labelattrs is a sequence of attributes for a texrunners text method;
1067 a single is allowed without being a sequence; None is considered as
1068 an empty sequence"""
1069 self
.prefix
= prefix
1071 self
.suffix
= suffix
1072 self
.enumprefix
= enumprefix
1073 self
.enuminfix
= enuminfix
1074 self
.enumsuffix
= enumsuffix
1075 self
.denomprefix
= denomprefix
1076 self
.denominfix
= denominfix
1077 self
.denomsuffix
= denomsuffix
1079 self
.minuspos
= minuspos
1081 self
.equaldenom
= equaldenom
1083 self
.skipenum0
= skipenum0
1084 self
.skipenum1
= skipenum1
1085 self
.skipdenom1
= skipdenom1
1086 self
.labelattrs
= helper
.ensurelist(labelattrs
)
1089 """returns the greates common divisor of all elements in n
1090 - the elements of n must be non-negative integers
1091 - return None if the number of elements is zero
1092 - the greates common divisor is not affected when some
1093 of the elements are zero, but it becomes zero when
1094 all elements are zero"""
1100 i
, (dummy
, j
) = j
, divmod(i
, j
)
1105 res
= self
.gcd(res
, i
)
1109 """returns the least common multiple of all elements in n
1110 - the elements of n must be non-negative integers
1111 - return None if the number of elements is zero
1112 - the least common multiple is zero when some of the
1113 elements are zero"""
1117 res
= divmod(res
* i
, self
.gcd(res
, i
))[0]
1120 def labels(self
, ticks
):
1123 if tick
.label
is None and tick
.labellevel
is not None:
1124 labeledticks
.append(tick
)
1125 tick
.temp_fracenum
= tick
.enum
1126 tick
.temp_fracdenom
= tick
.denom
1127 tick
.temp_fracminus
= 1
1128 if tick
.temp_fracenum
< 0:
1129 tick
.temp_fracminus
*= -1
1130 tick
.temp_fracenum
*= -1
1131 if tick
.temp_fracdenom
< 0:
1132 tick
.temp_fracminus
*= -1
1133 tick
.temp_fracdenom
*= -1
1134 gcd
= self
.gcd(tick
.temp_fracenum
, tick
.temp_fracdenom
)
1135 (tick
.temp_fracenum
, dummy1
), (tick
.temp_fracdenom
, dummy2
) = divmod(tick
.temp_fracenum
, gcd
), divmod(tick
.temp_fracdenom
, gcd
)
1137 equaldenom
= self
.lcm(*[tick
.temp_fracdenom
for tick
in ticks
if tick
.label
is None])
1138 if equaldenom
is not None:
1139 for tick
in labeledticks
:
1140 factor
, dummy
= divmod(equaldenom
, tick
.temp_fracdenom
)
1141 tick
.temp_fracenum
, tick
.temp_fracdenom
= factor
* tick
.temp_fracenum
, factor
* tick
.temp_fracdenom
1142 for tick
in labeledticks
:
1143 fracminus
= fracenumminus
= fracdenomminus
= ""
1144 if tick
.temp_fracminus
== -1:
1145 if self
.minuspos
== 0:
1146 fracminus
= self
.minus
1147 elif self
.minuspos
== 1:
1148 fracenumminus
= self
.minus
1149 elif self
.minuspos
== -1:
1150 fracdenomminus
= self
.minus
1152 raise RuntimeError("invalid minuspos")
1153 if self
.skipenum0
and tick
.temp_fracenum
== 0:
1155 elif (self
.skip1
and self
.skipdenom1
and tick
.temp_fracenum
== 1 and tick
.temp_fracdenom
== 1 and
1156 (len(self
.prefix
) or len(self
.infix
) or len(self
.suffix
)) and
1157 not len(fracenumminus
) and not len(self
.enumprefix
) and not len(self
.enuminfix
) and not len(self
.enumsuffix
) and
1158 not len(fracdenomminus
) and not len(self
.denomprefix
) and not len(self
.denominfix
) and not len(self
.denomsuffix
)):
1159 tick
.label
= "%s%s%s%s" % (self
.prefix
, fracminus
, self
.infix
, self
.suffix
)
1161 if self
.skipenum1
and tick
.temp_fracenum
== 1 and (len(self
.enumprefix
) or len(self
.enuminfix
) or len(self
.enumsuffix
)):
1162 tick
.temp_fracenum
= "%s%s%s%s" % (self
.enumprefix
, fracenumminus
, self
.enuminfix
, self
.enumsuffix
)
1164 tick
.temp_fracenum
= "%s%s%s%i%s" % (self
.enumprefix
, fracenumminus
, self
.enuminfix
, tick
.temp_fracenum
, self
.enumsuffix
)
1165 if self
.skipdenom1
and tick
.temp_fracdenom
== 1 and not len(fracdenomminus
) and not len(self
.denomprefix
) and not len(self
.denominfix
) and not len(self
.denomsuffix
):
1166 frac
= tick
.temp_fracenum
1168 tick
.temp_fracdenom
= "%s%s%s%i%s" % (self
.denomprefix
, fracdenomminus
, self
.denominfix
, tick
.temp_fracdenom
, self
.denomsuffix
)
1169 frac
= self
.over
% (tick
.temp_fracenum
, tick
.temp_fracdenom
)
1170 tick
.label
= "%s%s%s%s%s" % (self
.prefix
, fracminus
, self
.infix
, frac
, self
.suffix
)
1171 tick
.labelattrs
.extend(self
.labelattrs
)
1173 # del tick.temp_fracenum # we've inserted those temporary variables ... and do not care any longer about them
1174 # del tick.temp_fracdenom
1175 # del tick.temp_fracminus
1179 class decimaltexter
:
1180 "a texter creating decimal labels (e.g. '1.234' or even '0.\overline{3}')"
1182 __implements__
= _Itexter
1184 def __init__(self
, prefix
="", infix
="", suffix
="", equalprecision
=0,
1185 decimalsep
=".", thousandsep
="", thousandthpartsep
="",
1186 minus
="-", period
=r
"\overline{%s}", labelattrs
=textmodule
.mathmode
):
1187 r
"""initializes the instance
1188 - prefix, infix, and suffix (strings) are added at the begin,
1189 immediately after the minus, and at the end of the label,
1191 - decimalsep, thousandsep, and thousandthpartsep (strings)
1192 are used as separators
1193 - minus (string) is inserted for negative numbers
1194 - period (string) is taken as a format string generating a period;
1195 it has to contain exactly one string insert operators "%s" for the
1196 period; usually it should be r"\overline{%s}"
1197 - labelattrs is a sequence of attributes for a texrunners text method;
1198 a single is allowed without being a sequence; None is considered as
1199 an empty sequence"""
1200 self
.prefix
= prefix
1202 self
.suffix
= suffix
1203 self
.equalprecision
= equalprecision
1204 self
.decimalsep
= decimalsep
1205 self
.thousandsep
= thousandsep
1206 self
.thousandthpartsep
= thousandthpartsep
1208 self
.period
= period
1209 self
.labelattrs
= helper
.ensurelist(labelattrs
)
1211 def labels(self
, ticks
):
1215 if tick
.label
is None and tick
.labellevel
is not None:
1216 labeledticks
.append(tick
)
1217 m
, n
= tick
.enum
, tick
.denom
1220 whole
, reminder
= divmod(m
, n
)
1222 if len(self
.thousandsep
):
1226 tick
.label
+= whole
[i
]
1227 if not ((l
-i
-1) % 3) and l
> i
+1:
1228 tick
.label
+= self
.thousandsep
1232 tick
.label
+= self
.decimalsep
1234 tick
.temp_decprecision
= 0
1236 tick
.temp_decprecision
+= 1
1237 if reminder
in oldreminders
:
1238 tick
.temp_decprecision
= None
1239 periodstart
= len(tick
.label
) - (len(oldreminders
) - oldreminders
.index(reminder
))
1240 tick
.label
= tick
.label
[:periodstart
] + self
.period
% tick
.label
[periodstart
:]
1242 oldreminders
+= [reminder
]
1244 whole
, reminder
= divmod(reminder
, n
)
1245 if not ((tick
.temp_decprecision
- 1) % 3) and tick
.temp_decprecision
> 1:
1246 tick
.label
+= self
.thousandthpartsep
1247 tick
.label
+= str(whole
)
1248 if maxdecprecision
< tick
.temp_decprecision
:
1249 maxdecprecision
= tick
.temp_decprecision
1250 if self
.equalprecision
:
1251 for tick
in labeledticks
:
1252 if tick
.temp_decprecision
is not None:
1253 if tick
.temp_decprecision
== 0 and maxdecprecision
> 0:
1254 tick
.label
+= self
.decimalsep
1255 for i
in range(tick
.temp_decprecision
, maxdecprecision
):
1256 if not ((i
- 1) % 3) and i
> 1:
1257 tick
.label
+= self
.thousandthpartsep
1259 for tick
in labeledticks
:
1260 if tick
.enum
* tick
.denom
< 0:
1264 tick
.label
= "%s%s%s%s%s" % (self
.prefix
, minus
, self
.infix
, tick
.label
, self
.suffix
)
1265 tick
.labelattrs
.extend(self
.labelattrs
)
1267 # del tick.temp_decprecision # we've inserted this temporary variable ... and do not care any longer about it
1270 class exponentialtexter
:
1271 "a texter creating labels with exponentials (e.g. '2\cdot10^5')"
1273 __implements__
= _Itexter
1275 def __init__(self
, plus
="", minus
="-",
1276 mantissaexp
=r
"{{%s}\cdot10^{%s}}",
1277 nomantissaexp
=r
"{10^{%s}}",
1278 minusnomantissaexp
=r
"{-10^{%s}}",
1279 mantissamin
=frac((1, 1)), mantissamax
=frac((10, 1)),
1280 skipmantissa1
=0, skipallmantissa1
=1,
1281 mantissatexter
=decimaltexter()):
1282 r
"""initializes the instance
1283 - plus or minus (string) is inserted for positive or negative exponents
1284 - mantissaexp (string) is taken as a format string generating the exponent;
1285 it has to contain exactly two string insert operators "%s" --
1286 the first for the mantissa and the second for the exponent;
1287 examples are r"{{%s}\cdot10^{%s}}" and r"{{%s}{\rm e}^{%s}}"
1288 - nomantissaexp (string) is taken as a format string generating the exponent
1289 when the mantissa is one and should be skipped; it has to contain
1290 exactly one string insert operators "%s" for the exponent;
1291 an examples is r"{10^{%s}}"
1292 - minusnomantissaexp (string) is taken as a format string generating the exponent
1293 when the mantissa is minus one and should be skipped; it has to contain
1294 exactly one string insert operators "%s" for the exponent; might be set to None
1295 to disallow skipping of any mantissa minus one
1296 an examples is r"{-10^{%s}}"
1297 - mantissamin and mantissamax are the minimum and maximum of the mantissa;
1298 they are frac instances greater than zero and mantissamin < mantissamax;
1299 the sign of the tick is ignored here
1300 - skipmantissa1 (boolean) turns on skipping of any mantissa equals one
1301 (and minus when minusnomantissaexp is set)
1302 - skipallmantissa1 (boolean) as above, but all mantissas must be 1
1303 - mantissatexter is the texter for the mantissa"""
1306 self
.mantissaexp
= mantissaexp
1307 self
.nomantissaexp
= nomantissaexp
1308 self
.minusnomantissaexp
= minusnomantissaexp
1309 self
.mantissamin
= mantissamin
1310 self
.mantissamax
= mantissamax
1311 self
.mantissamindivmax
= self
.mantissamin
/ self
.mantissamax
1312 self
.mantissamaxdivmin
= self
.mantissamax
/ self
.mantissamin
1313 self
.skipmantissa1
= skipmantissa1
1314 self
.skipallmantissa1
= skipallmantissa1
1315 self
.mantissatexter
= mantissatexter
1317 def labels(self
, ticks
):
1320 if tick
.label
is None and tick
.labellevel
is not None:
1321 tick
.temp_orgenum
, tick
.temp_orgdenom
= tick
.enum
, tick
.denom
1322 labeledticks
.append(tick
)
1325 while abs(tick
) >= self
.mantissamax
:
1327 x
= tick
* self
.mantissamindivmax
1328 tick
.enum
, tick
.denom
= x
.enum
, x
.denom
1329 while abs(tick
) < self
.mantissamin
:
1331 x
= tick
* self
.mantissamaxdivmin
1332 tick
.enum
, tick
.denom
= x
.enum
, x
.denom
1333 if tick
.temp_exp
< 0:
1334 tick
.temp_exp
= "%s%i" % (self
.minus
, -tick
.temp_exp
)
1336 tick
.temp_exp
= "%s%i" % (self
.plus
, tick
.temp_exp
)
1337 self
.mantissatexter
.labels(labeledticks
)
1338 if self
.minusnomantissaexp
is not None:
1339 allmantissa1
= len(labeledticks
) == len([tick
for tick
in labeledticks
if abs(tick
.enum
) == abs(tick
.denom
)])
1341 allmantissa1
= len(labeledticks
) == len([tick
for tick
in labeledticks
if tick
.enum
== tick
.denom
])
1342 for tick
in labeledticks
:
1343 if (self
.skipallmantissa1
and allmantissa1
or
1344 (self
.skipmantissa1
and (tick
.enum
== tick
.denom
or
1345 (tick
.enum
== -tick
.denom
and self
.minusnomantissaexp
is not None)))):
1346 if tick
.enum
== tick
.denom
:
1347 tick
.label
= self
.nomantissaexp
% tick
.temp_exp
1349 tick
.label
= self
.minusnomantissaexp
% tick
.temp_exp
1351 tick
.label
= self
.mantissaexp
% (tick
.label
, tick
.temp_exp
)
1352 tick
.enum
, tick
.denom
= tick
.temp_orgenum
, tick
.temp_orgdenom
1354 # del tick.temp_orgenum # we've inserted those temporary variables ... and do not care any longer about them
1355 # del tick.temp_orgdenom
1359 class defaulttexter
:
1360 "a texter creating decimal or exponential labels"
1362 __implements__
= _Itexter
1364 def __init__(self
, smallestdecimal
=frac((1, 1000)),
1365 biggestdecimal
=frac((9999, 1)),
1367 decimaltexter
=decimaltexter(),
1368 exponentialtexter
=exponentialtexter()):
1369 r
"""initializes the instance
1370 - smallestdecimal and biggestdecimal are the smallest and
1371 biggest decimal values, where the decimaltexter should be used;
1372 they are frac instances; the sign of the tick is ignored here;
1373 a tick at zero is considered for the decimaltexter as well
1374 - equaldecision (boolean) uses decimaltexter or exponentialtexter
1375 globaly (set) or for each tick separately (unset)
1376 - decimaltexter and exponentialtexter are texters to be used"""
1377 self
.smallestdecimal
= smallestdecimal
1378 self
.biggestdecimal
= biggestdecimal
1379 self
.equaldecision
= equaldecision
1380 self
.decimaltexter
= decimaltexter
1381 self
.exponentialtexter
= exponentialtexter
1383 def labels(self
, ticks
):
1387 if tick
.label
is None and tick
.labellevel
is not None:
1388 if not tick
.enum
or (abs(tick
) >= self
.smallestdecimal
and abs(tick
) <= self
.biggestdecimal
):
1389 decticks
.append(tick
)
1391 expticks
.append(tick
)
1392 if self
.equaldecision
:
1394 self
.exponentialtexter
.labels(ticks
)
1396 self
.decimaltexter
.labels(ticks
)
1398 for tick
in decticks
:
1399 self
.decimaltexter
.labels([tick
])
1400 for tick
in expticks
:
1401 self
.exponentialtexter
.labels([tick
])
1404 ################################################################################
1406 ################################################################################
1409 class axiscanvas(canvas
._canvas
):
1411 - an axis canvas is a regular canvas to be filled by
1412 a axispainters painter method
1413 - it contains a PyX length extent to be used for the
1414 alignment of additional axes; the axis extent should
1415 be filled by the axispainters painter method; you may
1416 grasp this as a size information comparable to a bounding
1418 - it contains a list of textboxes called labels which are
1419 used to rate the distances between the labels if needed
1420 by the axis later on; the painter method has not only to
1421 insert the labels into this canvas, but should also fill
1422 this list, when a rating of the distances should be
1423 performed by the axis"""
1425 # __implements__ = sole implementation
1427 def __init__(self
, texrunner
, *args
, **kwargs
):
1428 """initializes the instance
1429 - sets the texrunner
1430 - sets extent to zero
1431 - sets labels to an empty list"""
1432 canvas
._canvas
.__init
__(self
, *args
, **kwargs
)
1433 self
.settexrunner(texrunner
)
1439 """create rotations accordingly to tick directions
1440 - upsidedown rotations are suppressed by rotating them by another 180 degree"""
1442 # __implements__ = sole implementation
1444 def __init__(self
, direction
, epsilon
=1e-10):
1445 """initializes the instance
1446 - direction is an angle to be used relative to the tick direction
1447 - epsilon is the value by which 90 degrees can be exceeded before
1448 an 180 degree rotation is added"""
1449 self
.direction
= direction
1450 self
.epsilon
= epsilon
1452 def trafo(self
, dx
, dy
):
1453 """returns a rotation transformation accordingly to the tick direction
1454 - dx and dy are the direction of the tick"""
1455 direction
= self
.direction
+ math
.atan2(dy
, dx
) * 180 / math
.pi
1456 while (direction
> 90 + self
.epsilon
):
1458 while (direction
< -90 - self
.epsilon
):
1460 return trafomodule
.rotate(direction
)
1463 paralleltext
= rotatetext(-90)
1464 orthogonaltext
= rotatetext(0)
1467 class _Iaxispainter
:
1468 "class for painting axes"
1470 def paint(self
, axispos
, axis
, axiscanvas
):
1471 """paint the axis into the axiscanvas
1472 - axispos is an instance, which implements _Iaxispos to
1473 define the tick positions
1474 - the axis and should not be modified (we may
1475 add some temporary variables like axis.ticks[i].temp_xxx,
1476 which might be used just temporary) -- the idea is that
1477 all things can be used several times
1478 - also do not modify the instance (self) -- even this
1479 instance might be used several times; thus do not modify
1480 attributes like self.titleattrs etc. (just use a copy of it)
1481 - the method might access some additional attributes from
1482 the axis, e.g. the axis title -- the axis painter should
1483 document this behavior and rely on the availability of
1484 those attributes -> it becomes a question of the proper
1485 usage of the combination of axis & axispainter
1486 - the axiscanvas is a axiscanvas instance and should be
1487 filled with ticks, labels, title, etc.; note that the
1488 extent and labels instance variables should be filled as
1489 documented in the axiscanvas"""
1492 class axistitlepainter
:
1493 """class for painting an axis title
1494 - the axis must have a title attribute when using this painter;
1495 this title might be None"""
1497 __implements__
= _Iaxispainter
1499 def __init__(self
, titledist
="0.3 cm",
1500 titleattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1501 titledirection
=paralleltext
,
1503 """initialized the instance
1504 - titledist is a visual PyX length giving the distance
1505 of the title from the axis extent already there (a title might
1506 be added after labels or other things are plotted already)
1507 - labelattrs is a sequence of attributes for a texrunners text
1508 method; a single is allowed without being a sequence; None
1510 - titledirection is an instance of rotatetext or None
1511 - titlepos is the position of the title in graph coordinates"""
1512 self
.titledist_str
= titledist
1513 self
.titleattrs
= titleattrs
1514 self
.titledirection
= titledirection
1515 self
.titlepos
= titlepos
1517 def paint(self
, axispos
, axis
, axiscanvas
):
1518 if axis
.title
is not None and self
.titleattrs
is not None:
1519 titledist
= unit
.length(self
.titledist_str
, default_type
="v")
1520 x
, y
= axispos
._vtickpoint
(self
.titlepos
, axis
=axis
)
1521 dx
, dy
= axispos
.vtickdirection(self
.titlepos
, axis
=axis
)
1522 titleattrs
= helper
.ensurelist(self
.titleattrs
)
1523 if self
.titledirection
is not None:
1524 titleattrs
.append(self
.titledirection
.trafo(dx
, dy
))
1525 title
= axiscanvas
.texrunner
._text
(x
, y
, axis
.title
, *titleattrs
)
1526 axiscanvas
.extent
+= titledist
1527 title
.linealign(axiscanvas
.extent
, dx
, dy
)
1528 axiscanvas
.extent
+= title
.extent(dx
, dy
)
1529 axiscanvas
.insert(title
)
1532 class axispainter(axistitlepainter
):
1533 """class for painting the ticks and labels of an axis
1534 - the inherited titleaxispainter is used to paint the title of
1536 - note that the type of the elements of ticks given as an argument
1537 of the paint method must be suitable for the tick position methods
1540 __implements__
= _Iaxispainter
1542 defaultticklengths
= ["%0.5f cm" % (0.2*goldenmean
**(-i
)) for i
in range(10)]
1544 def __init__(self
, innerticklengths
=defaultticklengths
,
1545 outerticklengths
=None,
1549 baselineattrs
=canvas
.linecap
.square
,
1551 labelattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1552 labeldirection
=None,
1556 """initializes the instance
1557 - innerticklenths and outerticklengths are two sequences of
1558 visual PyX lengths for ticks, subticks, etc. plotted inside
1559 and outside of the graph; when a single value is given, it
1560 is used for all tick levels; None turns off ticks inside or
1561 outside of the graph
1562 - tickattrs are a sequence of stroke attributes for the ticks;
1563 a single entry is allowed without being a sequence; None turns
1565 - gridlineattrs are a sequence of sequences used as stroke
1566 attributes for ticks, subticks etc.; when a single sequence
1567 is given, it is used for ticks, subticks, etc.; a single
1568 entry is allowed without being a sequence; None turns off
1570 - zerolineattrs are a sequence of stroke attributes for a grid
1571 line at axis value zero; a single entry is allowed without
1572 being a sequence; None turns off the zeroline
1573 - baselineattrs are a sequence of stroke attributes for a grid
1574 line at axis value zero; a single entry is allowed without
1575 being a sequence; None turns off the baseline
1576 - labeldist is a visual PyX length for the distance of the labels
1577 from the axis baseline
1578 - labelattrs is a sequence of attributes for a texrunners text
1579 method; a single entry is allowed without being a sequence;
1580 None turns off the labels
1581 - titledirection is an instance of rotatetext or None
1582 - labelhequalize and labelvequalize (booleans) perform an equal
1583 alignment for straight vertical and horizontal axes, respectively
1584 - futher keyword arguments are passed to axistitlepainter"""
1585 # TODO: access to axis.divisor -- document, remove, ... ???
1586 self
.innerticklengths_str
= innerticklengths
1587 self
.outerticklengths_str
= outerticklengths
1588 self
.tickattrs
= tickattrs
1589 self
.gridattrs
= gridattrs
1590 self
.zerolineattrs
= zerolineattrs
1591 self
.baselineattrs
= baselineattrs
1592 self
.labeldist_str
= labeldist
1593 self
.labelattrs
= labelattrs
1594 self
.labeldirection
= labeldirection
1595 self
.labelhequalize
= labelhequalize
1596 self
.labelvequalize
= labelvequalize
1597 axistitlepainter
.__init
__(self
, **kwargs
)
1599 def paint(self
, axispos
, axis
, axiscanvas
):
1600 labeldist
= unit
.length(self
.labeldist_str
, default_type
="v")
1601 for tick
in axis
.ticks
:
1602 tick
.temp_v
= axis
.convert(float(tick
) * axis
.divisor
)
1603 tick
.temp_x
, tick
.temp_y
= axispos
._vtickpoint
(tick
.temp_v
, axis
=axis
)
1604 tick
.temp_dx
, tick
.temp_dy
= axispos
.vtickdirection(tick
.temp_v
, axis
=axis
)
1606 # create & align tick.temp_labelbox
1607 for tick
in axis
.ticks
:
1608 if tick
.labellevel
is not None:
1609 labelattrs
= helper
.getsequenceno(self
.labelattrs
, tick
.labellevel
)
1610 if labelattrs
is not None:
1611 labelattrs
= helper
.ensurelist(labelattrs
)[:]
1612 if self
.labeldirection
is not None:
1613 labelattrs
.append(self
.labeldirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
1614 if tick
.labelattrs
is not None:
1615 labelattrs
.extend(helper
.ensurelist(tick
.labelattrs
))
1616 tick
.temp_labelbox
= axiscanvas
.texrunner
._text
(tick
.temp_x
, tick
.temp_y
, tick
.label
, *labelattrs
)
1617 if len(axis
.ticks
) > 1:
1619 for tick
in axis
.ticks
[1:]:
1620 if tick
.temp_dx
!= axis
.ticks
[0].temp_dx
or tick
.temp_dy
!= axis
.ticks
[0].temp_dy
:
1624 if equaldirection
and ((not axis
.ticks
[0].temp_dx
and self
.labelvequalize
) or
1625 (not axis
.ticks
[0].temp_dy
and self
.labelhequalize
)):
1626 if self
.labelattrs
is not None:
1627 box
.linealignequal([tick
.temp_labelbox
for tick
in axis
.ticks
if tick
.labellevel
is not None],
1628 labeldist
, axis
.ticks
[0].temp_dx
, axis
.ticks
[0].temp_dy
)
1630 for tick
in axis
.ticks
:
1631 if tick
.labellevel
is not None and self
.labelattrs
is not None:
1632 tick
.temp_labelbox
.linealign(labeldist
, tick
.temp_dx
, tick
.temp_dy
)
1635 if helper
.issequence(arg
):
1636 return [unit
.length(a
, default_type
="v") for a
in arg
]
1638 return unit
.length(arg
, default_type
="v")
1639 innerticklengths
= mkv(self
.innerticklengths_str
)
1640 outerticklengths
= mkv(self
.outerticklengths_str
)
1642 for tick
in axis
.ticks
:
1643 if tick
.ticklevel
is not None:
1644 innerticklength
= helper
.getitemno(innerticklengths
, tick
.ticklevel
)
1645 outerticklength
= helper
.getitemno(outerticklengths
, tick
.ticklevel
)
1646 if innerticklength
is not None or outerticklength
is not None:
1647 if innerticklength
is None:
1649 if outerticklength
is None:
1651 tickattrs
= helper
.getsequenceno(self
.tickattrs
, tick
.ticklevel
)
1652 if tickattrs
is not None:
1653 _innerticklength
= unit
.topt(innerticklength
)
1654 _outerticklength
= unit
.topt(outerticklength
)
1655 x1
= tick
.temp_x
- tick
.temp_dx
* _innerticklength
1656 y1
= tick
.temp_y
- tick
.temp_dy
* _innerticklength
1657 x2
= tick
.temp_x
+ tick
.temp_dx
* _outerticklength
1658 y2
= tick
.temp_y
+ tick
.temp_dy
* _outerticklength
1659 axiscanvas
.stroke(path
._line
(x1
, y1
, x2
, y2
), *helper
.ensuresequence(tickattrs
))
1660 if tick
!= frac((0, 1)) or self
.zerolineattrs
is None:
1661 gridattrs
= helper
.getsequenceno(self
.gridattrs
, tick
.ticklevel
)
1662 if gridattrs
is not None:
1663 axiscanvas
.stroke(axispos
.vgridline(tick
.temp_v
, axis
=axis
), *helper
.ensuresequence(gridattrs
))
1664 if outerticklength
is not None and unit
.topt(outerticklength
) > unit
.topt(axiscanvas
.extent
):
1665 axiscanvas
.extent
= outerticklength
1666 if outerticklength
is not None and unit
.topt(-innerticklength
) > unit
.topt(axiscanvas
.extent
):
1667 axiscanvas
.extent
= -innerticklength
1668 if tick
.labellevel
is not None and self
.labelattrs
is not None:
1669 axiscanvas
.insert(tick
.temp_labelbox
)
1670 axiscanvas
.labels
.append(tick
.temp_labelbox
)
1671 extent
= tick
.temp_labelbox
.extent(tick
.temp_dx
, tick
.temp_dy
) + labeldist
1672 if unit
.topt(extent
) > unit
.topt(axiscanvas
.extent
):
1673 axiscanvas
.extent
= extent
1674 if self
.baselineattrs
is not None:
1675 axiscanvas
.stroke(axispos
.vbaseline(axis
=axis
), *helper
.ensuresequence(self
.baselineattrs
))
1676 if self
.zerolineattrs
is not None:
1677 if len(axis
.ticks
) and axis
.ticks
[0] * axis
.ticks
[-1] < frac((0, 1)):
1678 axiscanvas
.stroke(axispos
.gridline(0, axis
=axis
), *helper
.ensuresequence(self
.zerolineattrs
))
1680 # for tick in axis.ticks:
1681 # del tick.temp_v # we've inserted those temporary variables ... and do not care any longer about them
1686 # if tick.labellevel is not None and self.labelattrs is not None:
1687 # del tick.temp_labelbox
1689 axistitlepainter
.paint(self
, axispos
, axis
, axiscanvas
)
1692 class linkaxispainter(axispainter
):
1693 """class for painting a linked axis
1694 - the inherited axispainter is used to paint the axis
1695 - modifies some constructor defaults"""
1697 __implements__
= _Iaxispainter
1699 def __init__(self
, gridattrs
=None,
1704 """initializes the instance
1705 - the gridattrs default is set to None thus skipping the girdlines
1706 - the zerolineattrs default is set to None thus skipping the zeroline
1707 - the labelattrs default is set to None thus skipping the labels
1708 - the titleattrs default is set to None thus skipping the title
1709 - all keyword arguments are passed to axispainter"""
1710 axispainter
.__init
__(self
, gridattrs
=gridattrs
,
1711 zerolineattrs
=zerolineattrs
,
1712 labelattrs
=labelattrs
,
1713 titleattrs
=titleattrs
,
1717 class splitaxispainter(axistitlepainter
):
1718 """class for painting a splitaxis
1719 - the inherited titleaxispainter is used to paint the title of
1721 - the splitaxispainter access the subaxes attribute of the axis"""
1723 __implements__
= _Iaxispainter
1725 def __init__(self
, breaklinesdist
="0.05 cm",
1726 breaklineslength
="0.5 cm",
1727 breaklinesangle
=-60,
1730 """initializes the instance
1731 - breaklinesdist is a visual length of the distance between
1732 the two lines of the axis break
1733 - breaklineslength is a visual length of the length of the
1734 two lines of the axis break
1735 - breaklinesangle is the angle of the lines of the axis break
1736 - breaklinesattrs are a sequence of stroke attributes for the
1737 axis break lines; a single entry is allowed without being a
1738 sequence; None turns off the break lines
1739 - futher keyword arguments are passed to axistitlepainter"""
1740 self
.breaklinesdist_str
= breaklinesdist
1741 self
.breaklineslength_str
= breaklineslength
1742 self
.breaklinesangle
= breaklinesangle
1743 self
.breaklinesattrs
= breaklinesattrs
1744 axistitlepainter
.__init
__(self
, **args
)
1746 def paint(self
, axispos
, axis
, axiscanvas
):
1747 for subaxis
in axis
.subaxes
:
1748 subaxis
.finish(axis
, axiscanvas
.texrunner
)
1749 axiscanvas
.insert(subaxis
.axiscanvas
)
1750 if unit
.topt(axiscanvas
.extent
) < unit
.topt(subaxis
.axiscanvas
.extent
):
1751 axiscanvas
.extent
= subaxis
.axiscanvas
.extent
1752 if self
.breaklinesattrs
is not None:
1753 self
.breaklinesdist
= unit
.length(self
.breaklinesdist_str
, default_type
="v")
1754 self
.breaklineslength
= unit
.length(self
.breaklineslength_str
, default_type
="v")
1755 self
.sin
= math
.sin(self
.breaklinesangle
*math
.pi
/180.0)
1756 self
.cos
= math
.cos(self
.breaklinesangle
*math
.pi
/180.0)
1757 breaklinesextent
= (0.5*self
.breaklinesdist
*math
.fabs(self
.cos
) +
1758 0.5*self
.breaklineslength
*math
.fabs(self
.sin
))
1759 if unit
.topt(axiscanvas
.extent
) < unit
.topt(breaklinesextent
):
1760 axiscanvas
.extent
= breaklinesextent
1761 for subaxis1
, subaxis2
in zip(axis
.subaxes
[:-1], axis
.subaxes
[1:]):
1762 # use a tangent of the baseline (this is independent of the tickdirection)
1763 v
= 0.5 * (subaxis1
.vmax
+ subaxis2
.vmin
)
1764 breakline
= path
.normpath(axispos
.vbaseline(v
, None, axis
=axis
)).tangent(0, self
.breaklineslength
)
1765 widthline
= path
.normpath(axispos
.vbaseline(v
, None, axis
=axis
)).tangent(0, self
.breaklinesdist
).transformed(trafomodule
.rotate(self
.breaklinesangle
+90, *breakline
.begin()))
1766 tocenter
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(breakline
.begin(), breakline
.end()))
1767 towidth
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(widthline
.begin(), widthline
.end()))
1768 breakline
= breakline
.transformed(trafomodule
.translate(*tocenter
).rotated(self
.breaklinesangle
, *breakline
.begin()))
1769 breakline1
= breakline
.transformed(trafomodule
.translate(*towidth
))
1770 breakline2
= breakline
.transformed(trafomodule
.translate(-towidth
[0], -towidth
[1]))
1771 axiscanvas
.fill(path
.path(path
.moveto(*breakline1
.begin()),
1772 path
.lineto(*breakline1
.end()),
1773 path
.lineto(*breakline2
.end()),
1774 path
.lineto(*breakline2
.begin()),
1775 path
.closepath()), color
.gray
.white
)
1776 axiscanvas
.stroke(breakline1
, *helper
.ensuresequence(self
.breaklinesattrs
))
1777 axiscanvas
.stroke(breakline2
, *helper
.ensuresequence(self
.breaklinesattrs
))
1778 axistitlepainter
.paint(self
, axispos
, axis
, axiscanvas
)
1781 class linksplitaxispainter(splitaxispainter
):
1782 """class for painting a linked splitaxis
1783 - the inherited splitaxispainter is used to paint the axis
1784 - modifies some constructor defaults"""
1786 __implements__
= _Iaxispainter
1788 def __init__(self
, titleattrs
=None, **kwargs
):
1789 """initializes the instance
1790 - the titleattrs default is set to None thus skipping the title
1791 - all keyword arguments are passed to splitaxispainter"""
1792 splitaxispainter
.__init
__(self
, titleattrs
=titleattrs
, **kwargs
)
1795 class baraxispainter(axistitlepainter
):
1796 """class for painting a baraxis
1797 - the inherited titleaxispainter is used to paint the title of
1799 - the baraxispainter access the multisubaxis, subaxis names, texts, and
1800 relsizes attributes"""
1802 __implements__
= _Iaxispainter
1804 def __init__(self
, innerticklength
=None,
1805 outerticklength
=None,
1807 baselineattrs
=canvas
.linecap
.square
,
1809 nameattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1815 """initializes the instance
1816 - innerticklength and outerticklength are a visual length of
1817 the ticks to be plotted at the axis baseline to visually
1818 separate the bars; if neither innerticklength nor
1819 outerticklength are set, not ticks are plotted
1820 - breaklinesattrs are a sequence of stroke attributes for the
1821 axis tick; a single entry is allowed without being a
1822 sequence; None turns off the ticks
1823 - namedist is a visual PyX length for the distance of the bar
1824 names from the axis baseline
1825 - nameattrs is a sequence of attributes for a texrunners text
1826 method; a single entry is allowed without being a sequence;
1827 None turns off the names
1828 - namedirection is an instance of rotatetext or None
1829 - namehequalize and namevequalize (booleans) perform an equal
1830 alignment for straight vertical and horizontal axes, respectively
1831 - futher keyword arguments are passed to axistitlepainter"""
1832 self
.innerticklength_str
= innerticklength
1833 self
.outerticklength_str
= outerticklength
1834 self
.tickattrs
= tickattrs
1835 self
.baselineattrs
= baselineattrs
1836 self
.namedist_str
= namedist
1837 self
.nameattrs
= nameattrs
1838 self
.namedirection
= namedirection
1839 self
.namepos
= namepos
1840 self
.namehequalize
= namehequalize
1841 self
.namevequalize
= namevequalize
1842 axistitlepainter
.__init
__(self
, **args
)
1844 def paint(self
, axispos
, axis
, axiscanvas
):
1845 if axis
.multisubaxis
is not None:
1846 for subaxis
in axis
.subaxis
:
1847 subaxis
.finish(axis
, axiscanvas
.texrunner
)
1848 axiscanvas
.insert(subaxis
.axiscanvas
)
1849 if unit
.topt(axiscanvas
.extent
) < unit
.topt(subaxis
.axiscanvas
.extent
):
1850 axiscanvas
.extent
= subaxis
.axiscanvas
.extent
1852 for name
in axis
.names
:
1853 v
= axis
.convert((name
, self
.namepos
))
1854 x
, y
= axispos
._vtickpoint
(v
, axis
=axis
)
1855 dx
, dy
= axispos
.vtickdirection(v
, axis
=axis
)
1856 namepos
.append((v
, x
, y
, dx
, dy
))
1858 if self
.nameattrs
is not None:
1859 for (v
, x
, y
, dx
, dy
), name
in zip(namepos
, axis
.names
):
1860 nameattrs
= helper
.ensurelist(self
.nameattrs
)[:]
1861 if self
.namedirection
is not None:
1862 nameattrs
.append(self
.namedirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
1863 if axis
.texts
.has_key(name
):
1864 nameboxes
.append(axiscanvas
.texrunner
._text
(x
, y
, str(axis
.texts
[name
]), *nameattrs
))
1865 elif axis
.texts
.has_key(str(name
)):
1866 nameboxes
.append(axiscanvas
.texrunner
._text
(x
, y
, str(axis
.texts
[str(name
)]), *nameattrs
))
1868 nameboxes
.append(axiscanvas
.texrunner
._text
(x
, y
, str(name
), *nameattrs
))
1869 labeldist
= axiscanvas
.extent
+ unit
.length(self
.namedist_str
, default_type
="v")
1870 if len(namepos
) > 1:
1872 for np
in namepos
[1:]:
1873 if np
[3] != namepos
[0][3] or np
[4] != namepos
[0][4]:
1877 if equaldirection
and ((not namepos
[0][3] and self
.namevequalize
) or
1878 (not namepos
[0][4] and self
.namehequalize
)):
1879 box
.linealignequal(nameboxes
, labeldist
, namepos
[0][3], namepos
[0][4])
1881 for namebox
, np
in zip(nameboxes
, namepos
):
1882 namebox
.linealign(labeldist
, np
[3], np
[4])
1883 if self
.innerticklength_str
is not None:
1884 innerticklength
= unit
.length(self
.innerticklength_str
, default_type
="v")
1885 _innerticklength
= unit
.topt(innerticklength
)
1886 if self
.tickattrs
is not None and unit
.topt(axiscanvas
.extent
) < -_innerticklength
:
1887 axiscanvas
.extent
= -innerticklength
1888 elif self
.outerticklength_str
is not None:
1889 innerticklength
= _innerticklength
= 0
1890 if self
.outerticklength_str
is not None:
1891 outerticklength
= unit
.length(self
.outerticklength_str
, default_type
="v")
1892 _outerticklength
= unit
.topt(outerticklength
)
1893 if self
.tickattrs
is not None and unit
.topt(axiscanvas
.extent
) < _outerticklength
:
1894 axiscanvas
.extent
= outerticklength
1895 elif self
.innerticklength_str
is not None:
1896 outerticklength
= _outerticklength
= 0
1897 for (v
, x
, y
, dx
, dy
), namebox
in zip(namepos
, nameboxes
):
1898 newextent
= namebox
.extent(dx
, dy
) + labeldist
1899 if unit
.topt(axiscanvas
.extent
) < unit
.topt(newextent
):
1900 axiscanvas
.extent
= newextent
1901 if self
.tickattrs
is not None and (self
.innerticklength_str
is not None or self
.outerticklength_str
is not None):
1902 for pos
in axis
.relsizes
:
1903 if pos
== axis
.relsizes
[0]:
1904 pos
-= axis
.firstdist
1905 elif pos
!= axis
.relsizes
[-1]:
1906 pos
-= 0.5 * axis
.dist
1907 v
= pos
/ axis
.relsizes
[-1]
1908 x
, y
= axispos
._vtickpoint
(v
, axis
=axis
)
1909 dx
, dy
= axispos
.vtickdirection(v
, axis
=axis
)
1910 x1
= x
- dx
* _innerticklength
1911 y1
= y
- dy
* _innerticklength
1912 x2
= x
+ dx
* _outerticklength
1913 y2
= y
+ dy
* _outerticklength
1914 axiscanvas
.stroke(path
._line
(x1
, y1
, x2
, y2
), *helper
.ensuresequence(self
.tickattrs
))
1915 if self
.baselineattrs
is not None:
1916 p
= axispos
.vbaseline(axis
=axis
)
1918 axiscanvas
.stroke(axispos
.vbaseline(axis
=axis
), *helper
.ensuresequence(self
.baselineattrs
))
1919 for namebox
in nameboxes
:
1920 axiscanvas
.insert(namebox
)
1921 axistitlepainter
.paint(self
, axispos
, axis
, axiscanvas
)
1924 class linkbaraxispainter(baraxispainter
):
1925 """class for painting a linked baraxis
1926 - the inherited baraxispainter is used to paint the axis
1927 - modifies some constructor defaults"""
1929 __implements__
= _Iaxispainter
1931 def __init__(self
, nameattrs
=None, titleattrs
=None, **kwargs
):
1932 """initializes the instance
1933 - the titleattrs default is set to None thus skipping the title
1934 - the nameattrs default is set to None thus skipping the names
1935 - all keyword arguments are passed to axispainter"""
1936 baraxispainter
.__init
__(self
, nameattrs
=nameattrs
, titleattrs
=titleattrs
, **kwargs
)
1939 ################################################################################
1941 ################################################################################
1945 """interface definition of a axis
1946 - data and tick range are handled separately
1947 - an axis should implement an convert and invert method like
1948 _Imap, but this is not part of this interface definition;
1949 one possibility is to mix-in a proper map class, but special
1950 purpose axes might do something else
1951 - instance variables:
1952 - axiscanvas: an axiscanvas instance, which is available after
1953 calling the finish method (the idea is, that an axis might
1954 fill different axiscanvas instances by calling the painter
1955 for different ticks and rate those ticks afterwards -- thus
1956 the axis finally can set this best canvas as its axis canvas)
1957 - relsize: relative size (width) of the axis (for use
1958 in splitaxis, baraxis etc.) -- this might not be available for
1959 all axes, which will just create an name error right now
1960 - configuration of the range handling is not subject
1961 of this interface definition"""
1962 # TODO: - add a mechanism to allow for a different range
1963 # handling of x and y ranges
1964 # - should we document instance variables this way?
1966 def convert(self
, x
):
1967 "convert a value into graph coordinates"
1969 def invert(self
, v
):
1970 "invert a graph coordinate to a axis value"
1972 def setdatarange(self
, min, max):
1973 """set the axis data range
1974 - the type of min and max must fit to the axis
1975 - min<max; the axis might be reversed, but this is
1976 expressed internally only
1977 - the axis might not apply this change of the range
1978 (e.g. when the axis range is fixed by the user)
1979 - for invalid parameters (e.g. negativ values at an
1980 logarithmic axis), an exception should be raised"""
1981 # TODO: be more specific about exceptions
1983 def settickrange(self
, min, max):
1984 """set the axis tick range
1985 - as before, but for the tick range (whatever this
1986 means for the axis)"""
1988 def getdatarange(self
):
1989 """return data range as a tuple (min, max)
1990 - min<max; the axis might be reversed, but this is
1991 expressed internally only"""
1992 # TODO: be more specific about exceptions
1994 def gettickrange(self
):
1995 """return tick range as a tuple (min, max)
1996 - as before, but for the tick range"""
1997 # TODO: be more specific about exceptions
1999 def finish(self
, axispos
, texrunner
):
2000 """finishes the axis
2001 - axispos implements _Iaxispos (usually the graph itself
2003 - the finish method creates an axiscanvas, which should be
2004 insertable into the graph to finally paint the axis
2005 - any modification of the axis range should be disabled after
2006 the finish method was called; a possible implementation
2007 would be to raise an error in these methods as soon as an
2008 axiscanvas is available"""
2009 # TODO: be more specific about exceptions
2011 def createlinkaxis(self
, **kwargs
):
2012 """create a link axis to the axis itself
2013 - typically, a link axis is a axis, which share almost
2014 all properties with the axis it is linked to
2015 - typically, the painter gets replaced by a painter
2016 which doesn't put any text to the axis"""
2020 """interface definition of axis tick position methods
2021 - these methods are used for the postitioning of the ticks
2022 when painting an axis
2023 - you may try to avoid calling of these methods from each
2024 other to gain speed"""
2026 def baseline(self
, x1
=None, x2
=None, axis
=None):
2027 """return the baseline as a path
2028 - x1 is the start position; if not set, the baseline starts
2029 from the beginning of the axis, which might imply a
2030 value outside of the graph coordinate range [0; 1]
2031 - x2 is analogous to x1, but for the end position"""
2033 def vbaseline(self
, v1
=None, v2
=None, axis
=None):
2034 """return the baseline as a path
2035 - like baseline, but for graph coordinates"""
2037 def gridline(self
, x
, axis
=None):
2038 "return the gridline as a path for a given position x"
2040 def vgridline(self
, v
, axis
=None):
2041 """return the gridline as a path for a given position v
2042 in graph coordinates"""
2044 def _tickpoint(self
, x
, axis
=None):
2045 """return the position at the baseline as a tuple (x, y) in
2046 postscript points for the position x"""
2048 def tickpoint(self
, x
, axis
=None):
2049 """return the position at the baseline as a tuple (x, y) in
2050 in PyX length for the position x"""
2052 def _vtickpoint(self
, v
, axis
=None):
2053 "like _tickpoint, but for graph coordinates"
2055 def vtickpoint(self
, v
, axis
=None):
2056 "like tickpoint, but for graph coordinates"
2058 def tickdirection(self
, x
, axis
=None):
2059 """return the direction of a tick as a tuple (dx, dy) for the
2062 def vtickdirection(self
, v
, axis
=None):
2063 """like tickposition, but for graph coordinates"""
2067 """base implementation a regular axis
2068 - typical usage is to mix-in a linmap or a logmap to
2069 complete the definition"""
2071 def __init__(self
, min=None, max=None, reverse
=0, divisor
=1,
2072 datavmin
=None, datavmax
=None, tickvmin
=0, tickvmax
=1,
2073 title
=None, painter
=axispainter(), texter
=defaulttexter(),
2074 density
=1, maxworse
=2):
2075 """initializes the instance
2076 - min and max fix the axis minimum and maximum, respectively;
2077 they are determined by the data to be plotted, when not fixed
2078 - reverse (boolean) reverses the minimum and the maximum of
2080 - numerical divisor for the axis partitioning
2081 - datavmin and datavmax are the minimal and maximal graph
2082 coordinate when adjusting the axis to the data range
2083 (likely to be changed in the future)
2084 - as before, but for the tick range
2085 - title is a string containing the axis title
2086 - axispainter is the axis painter (should implement _Ipainter)
2087 - texter is the texter (should implement _Itexter)
2088 - density is a global parameter for the axis paritioning and
2089 axis rating; its default is 1, but the range 0.5 to 2.5 should
2090 be usefull to get less or more ticks by the automatic axis
2092 - maxworse is a number of trials with worse tick rating
2093 before giving up (usually it should not be needed to increase
2094 this value; increasing the number will slow down the automatic
2095 axis partitioning considerably)
2096 - note that some methods of this class want to access a
2097 part and a rating attribute of the instance; those
2098 attributes should be initialized by the constructors
2099 of derived classes"""
2100 # TODO: improve datavmin/datavmax/tickvmin/tickvmax
2101 if None not in (min, max) and min > max:
2102 min, max, reverse
= max, min, not reverse
2103 self
.fixmin
, self
.fixmax
, self
.min, self
.max, self
.reverse
= min is not None, max is not None, min, max, reverse
2105 self
.datamin
= self
.datamax
= self
.tickmin
= self
.tickmax
= None
2106 if datavmin
is None:
2110 self
.datavmin
= 0.05
2112 self
.datavmin
= datavmin
2113 if datavmax
is None:
2117 self
.datavmax
= 0.95
2119 self
.datavmax
= datavmax
2120 self
.tickvmin
= tickvmin
2121 self
.tickvmax
= tickvmax
2123 self
.divisor
= divisor
2125 self
.painter
= painter
2126 self
.texter
= texter
2127 self
.density
= density
2128 self
.maxworse
= maxworse
2129 self
.axiscanvas
= None
2132 self
.__setinternalrange
()
2134 def __setinternalrange(self
, min=None, max=None):
2135 if not self
.fixmin
and min is not None and (self
.min is None or min < self
.min):
2137 if not self
.fixmax
and max is not None and (self
.max is None or max > self
.max):
2139 if None not in (self
.min, self
.max):
2140 min, max, vmin
, vmax
= self
.min, self
.max, 0, 1
2142 self
.setbasepoints(((min, vmin
), (max, vmax
)))
2144 if self
.datamin
is not None and self
.convert(self
.datamin
) < self
.datavmin
:
2145 min, vmin
= self
.datamin
, self
.datavmin
2146 self
.setbasepoints(((min, vmin
), (max, vmax
)))
2147 if self
.tickmin
is not None and self
.convert(self
.tickmin
) < self
.tickvmin
:
2148 min, vmin
= self
.tickmin
, self
.tickvmin
2149 self
.setbasepoints(((min, vmin
), (max, vmax
)))
2151 if self
.datamax
is not None and self
.convert(self
.datamax
) > self
.datavmax
:
2152 max, vmax
= self
.datamax
, self
.datavmax
2153 self
.setbasepoints(((min, vmin
), (max, vmax
)))
2154 if self
.tickmax
is not None and self
.convert(self
.tickmax
) > self
.tickvmax
:
2155 max, vmax
= self
.tickmax
, self
.tickvmax
2156 self
.setbasepoints(((min, vmin
), (max, vmax
)))
2158 self
.setbasepoints(((min, vmax
), (max, vmin
)))
2160 def __getinternalrange(self
):
2161 return self
.min, self
.max, self
.datamin
, self
.datamax
, self
.tickmin
, self
.tickmax
2163 def __forceinternalrange(self
, range):
2164 self
.min, self
.max, self
.datamin
, self
.datamax
, self
.tickmin
, self
.tickmax
= range
2165 self
.__setinternalrange
()
2167 def setdatarange(self
, min, max):
2168 if self
.axiscanvas
is not None:
2169 raise RuntimeError("axis was already finished")
2170 self
.datamin
, self
.datamax
= min, max
2171 self
.__setinternalrange
(min, max)
2173 def settickrange(self
, min, max):
2174 if self
.axiscanvas
is not None:
2175 raise RuntimeError("axis was already finished")
2176 self
.tickmin
, self
.tickmax
= min, max
2177 self
.__setinternalrange
(min, max)
2179 def getdatarange(self
):
2182 return self
.invert(1-self
.datavmin
), self
.invert(1-self
.datavmax
)
2184 return self
.invert(self
.datavmin
), self
.invert(self
.datavmax
)
2186 def gettickrange(self
):
2189 return self
.invert(1-self
.tickvmin
), self
.invert(1-self
.tickvmax
)
2191 return self
.invert(self
.tickvmin
), self
.invert(self
.tickvmax
)
2193 def checkfraclist(self
, fracs
):
2194 "orders a list of fracs, equal entries are not allowed"
2195 if not len(fracs
): return []
2196 sorted = list(fracs
)
2199 for item
in sorted[1:]:
2201 raise ValueError("duplicate entry found")
2205 def finish(self
, axispos
, texrunner
):
2210 min, max = self
.gettickrange()
2211 parter
= parterpos
= None
2212 if self
.part
is not None:
2213 self
.part
= helper
.ensurelist(self
.part
)
2214 for p
, i
in zip(self
.part
, xrange(sys
.maxint
)):
2215 if hasattr(p
, "defaultpart"):
2216 if parter
is not None:
2217 raise RuntimeError("only one partitioner allowed")
2221 self
.ticks
= self
.checkfraclist(self
.part
)
2223 self
.part
[:parterpos
] = self
.checkfraclist(self
.part
[:parterpos
])
2224 self
.part
[parterpos
+1:] = self
.checkfraclist(self
.part
[parterpos
+1:])
2225 self
.ticks
= _mergeticklists(
2226 _mergeticklists(self
.part
[:parterpos
],
2227 parter
.defaultpart(min/self
.divisor
,
2231 self
.part
[parterpos
+1:])
2234 # lesspart and morepart can be called after defaultpart;
2235 # this works although some axes may share their autoparting,
2236 # because the axes are processed sequentially
2239 while worse
< self
.maxworse
:
2240 if parter
is not None:
2241 newticks
= parter
.lesspart()
2242 if parterpos
is not None:
2243 newticks
= _mergeticklists(_mergeticklists(self
.part
[:parterpos
], newticks
), self
.part
[parterpos
+1:])
2246 if newticks
is not None:
2248 bestrate
= self
.rater
.ratepart(self
, self
.ticks
, self
.density
)
2249 variants
= [[bestrate
, self
.ticks
]]
2251 newrate
= self
.rater
.ratepart(self
, newticks
, self
.density
)
2252 variants
.append([newrate
, newticks
])
2253 if newrate
< bestrate
:
2261 while worse
< self
.maxworse
:
2262 if parter
is not None:
2263 newticks
= parter
.morepart()
2264 if parterpos
is not None:
2265 newticks
= _mergeticklists(_mergeticklists(self
.part
[:parterpos
], newticks
), self
.part
[parterpos
+1:])
2268 if newticks
is not None:
2270 bestrate
= self
.rater
.ratepart(self
, self
.ticks
, self
.density
)
2271 variants
= [[bestrate
, self
.ticks
]]
2273 newrate
= self
.rater
.ratepart(self
, newticks
, self
.density
)
2274 variants
.append([newrate
, newticks
])
2275 if newrate
< bestrate
:
2285 if self
.painter
is not None:
2288 while i
< len(variants
) and (bestrate
is None or variants
[i
][0] < bestrate
):
2289 saverange
= self
.__getinternalrange
()
2290 self
.ticks
= variants
[i
][1]
2292 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2293 ac
= axiscanvas(texrunner
)
2294 self
.texter
.labels(self
.ticks
)
2295 self
.painter
.paint(axispos
, self
, ac
)
2296 ratelayout
= self
.rater
.ratelayout(ac
, self
.density
)
2297 if ratelayout
is not None:
2298 variants
[i
][0] += ratelayout
2299 variants
[i
].append(ac
)
2301 variants
[i
][0] = None
2302 if variants
[i
][0] is not None and (bestrate
is None or variants
[i
][0] < bestrate
):
2303 bestrate
= variants
[i
][0]
2304 self
.__forceinternalrange
(saverange
)
2306 if bestrate
is None:
2307 raise RuntimeError("no valid axis partitioning found")
2308 variants
= [variant
for variant
in variants
[:i
] if variant
[0] is not None]
2310 self
.ticks
= variants
[0][1]
2312 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2313 self
.axiscanvas
= variants
[0][2]
2316 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2317 self
.axiscanvas
= axiscanvas(texrunner
)
2320 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2321 self
.axiscanvas
= axiscanvas(texrunner
)
2322 self
.texter
.labels(self
.ticks
)
2323 self
.painter
.paint(axispos
, self
, self
.axiscanvas
)
2325 def createlinkaxis(self
, **args
):
2326 return linkaxis(self
, **args
)
2329 class linaxis(_axis
, _linmap
):
2330 """implementation of a linear axis"""
2332 __implements__
= _Iaxis
2334 def __init__(self
, part
=autolinpart(), rater
=axisrater(), **args
):
2335 """initializes the instance
2336 - the part attribute contains a list of one partitioner
2337 (a partitioner implements _Ipart) or/and some (manually
2338 set) ticks (implementing _Itick); a single entry might
2339 be passed without wrapping it into a list; the partitioner
2340 and the tick instances must fit to the type of the axis
2341 (e.g. they should be valid parameters to the axis convert
2342 method); the ticks and the partitioner results are mixed
2344 - the rater implements _Irater and is used to rate different
2345 tick lists created by the partitioner (after merging with
2347 - futher keyword arguments are passed to _axis"""
2348 _axis
.__init
__(self
, **args
)
2349 if self
.fixmin
and self
.fixmax
:
2350 self
.relsize
= self
.max - self
.min
2355 class logaxis(_axis
, _logmap
):
2356 """implementation of a logarithmic axis"""
2358 __implements__
= _Iaxis
2360 def __init__(self
, part
=autologpart(), rater
=axisrater(ticks
=axisrater
.logticks
, labels
=axisrater
.loglabels
), **args
):
2361 """initializes the instance
2362 - the part attribute contains a list of one partitioner
2363 (a partitioner implements _Ipart) or/and some (manually
2364 set) ticks (implementing _Itick); a single entry might
2365 be passed without wrapping it into a list; the partitioner
2366 and the tick instances must fit to the type of the axis
2367 (e.g. they should be valid parameters to the axis convert
2368 method); the ticks and the partitioner results are mixed
2370 - the rater implements _Irater and is used to rate different
2371 tick lists created by the partitioner (after merging with
2373 - futher keyword arguments are passed to _axis"""
2374 _axis
.__init
__(self
, **args
)
2375 if self
.fixmin
and self
.fixmax
:
2376 self
.relsize
= math
.log(self
.max) - math
.log(self
.min)
2382 """a axis linked to an already existing regular axis
2383 - almost all properties of the axis are "copied" from the
2384 axis this axis is linked to
2385 - usually, linked axis are used to create an axis to an
2386 existing axis with different painting properties; linked
2387 axis can be used to plot an axis twice at the opposite
2388 sides of a graphxy or even to share an axis between
2389 different graphs!"""
2391 __implements__
= _Iaxis
2393 def __init__(self
, linkedaxis
, painter
=linkaxispainter()):
2394 """initializes the instance
2395 - it gets a axis this linkaxis is linked to
2396 - it gets a painter to be used for this linked axis"""
2397 self
.linkedaxis
= linkedaxis
2398 self
.painter
= painter
2401 def __getattr__(self
, attr
):
2402 """access to unkown attributes are handed over to the
2403 axis this linkaxis is linked to"""
2404 return getattr(self
.linkedaxis
, attr
)
2406 def finish(self
, axispos
, texrunner
):
2407 """finishes the axis
2408 - instead of performing the hole finish process
2409 (paritioning, rating, etc.) just a painter call
2414 self
.linkedaxis
.finish(axispos
, texrunner
)
2415 self
.axiscanvas
= axiscanvas(texrunner
)
2416 self
.painter
.paint(axispos
, self
, self
.axiscanvas
)
2420 """implementation of the _Iaxispos interface for subaxis
2421 - provides the _Iaxispos interface for axis which contain
2423 - typical usage by mix-in this class into an "main" axis and
2424 calling the painter(s) of the subaxes from the specialized
2425 painter of the "main" axis; the subaxes need to have the
2426 attributes "baseaxis" (reference to the "main" axis) and
2427 "baseaxispos" (reference to the instance implementing the
2428 _Iaxispos interface of the "main" axis)"""
2430 __implements__
= _Iaxispos
2432 def baseline(self
, x1
=None, x2
=None, axis
=None):
2434 v1
= axis
.convert(x1
)
2438 v2
= axis
.convert(x2
)
2441 return axis
.baseaxispos
.vbaseline(v1
, v2
, axis
=axis
.baseaxis
)
2443 def vbaseline(self
, v1
=None, v2
=None, axis
=None):
2447 left
= axis
.vmin
+v1
*(axis
.vmax
-axis
.vmin
)
2451 right
= axis
.vmin
+v2
*(axis
.vmax
-axis
.vmin
)
2452 return axis
.baseaxispos
.vbaseline(left
, right
, axis
=axis
.baseaxis
)
2454 def gridline(self
, x
, axis
=None):
2455 return axis
.baseaxispos
.vgridline(axis
.vmin
+axis
.convert(x
)*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2457 def vgridline(self
, v
, axis
=None):
2458 return axis
.baseaxispos
.vgridline(axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2460 def _tickpoint(self
, x
, axis
=None):
2461 return axis
.baseaxispos
._vtickpoint
(axis
.vmin
+axis
.convert(x
)*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2463 def tickpoint(self
, x
, axis
=None):
2464 return axis
.baseaxispos
.vtickpoint(axis
.vmin
+axis
.convert(x
)*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2466 def _vtickpoint(self
, v
, axis
=None):
2467 return axis
.baseaxispos
._vtickpoint
(axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2469 def vtickpoint(self
, v
, axis
=None):
2470 return axis
.baseaxispos
.vtickpoint(axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2472 def tickdirection(self
, x
, axis
=None):
2473 return axis
.baseaxispos
.vtickdirection(axis
.vmin
+axis
.convert(x
)*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2475 def vtickdirection(self
, v
, axis
=None):
2476 return axis
.baseaxispos
.vtickdirection(axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2479 class splitaxis(_subaxispos
):
2480 """implementation of a split axis
2481 - a split axis contains several (sub-)axes with
2482 non-overlapping data ranges -- between these subaxes
2483 the axis is "splitted"
2484 - (just to get sure: a splitaxis can contain other
2485 splitaxes as its subaxes)
2486 - a splitaxis implements the _Iaxispos for its subaxes
2487 by inheritance from _subaxispos"""
2489 __implements__
= _Iaxis
, _Iaxispos
2491 def __init__(self
, subaxes
, splitlist
=0.5, splitdist
=0.1, relsizesplitdist
=1,
2492 title
=None, painter
=splitaxispainter()):
2493 """initializes the instance
2494 - subaxes is a list of subaxes
2495 - splitlist is a list of graph coordinates, where the splitting
2496 of the main axis should be performed; a single entry (splitting
2497 two axes) doesn't need to be wrapped into a list; if the list
2498 isn't long enough for the subaxes, missing entries are considered
2500 - splitdist is the size of the splitting in graph coordinates, when
2501 the associated splitlist entry is not None
2502 - relsizesplitdist: a None entry in splitlist means, that the
2503 position of the splitting should be calculated out of the
2504 relsize values of conrtibuting subaxes (the size of the
2505 splitting is relsizesplitdist in values of the relsize values
2507 - title is the title of the axis as a string
2508 - painter is the painter of the axis; it should be specialized to
2510 - the relsize of the splitaxis is the sum of the relsizes of the
2511 subaxes including the relsizesplitdist"""
2512 self
.subaxes
= subaxes
2513 self
.painter
= painter
2515 self
.splitlist
= helper
.ensurelist(splitlist
)
2516 for subaxis
in self
.subaxes
:
2519 self
.subaxes
[0].vmin
= 0
2520 self
.subaxes
[0].vminover
= None
2521 self
.subaxes
[-1].vmax
= 1
2522 self
.subaxes
[-1].vmaxover
= None
2523 for i
in xrange(len(self
.splitlist
)):
2524 if self
.splitlist
[i
] is not None:
2525 self
.subaxes
[i
].vmax
= self
.splitlist
[i
] - 0.5*splitdist
2526 self
.subaxes
[i
].vmaxover
= self
.splitlist
[i
]
2527 self
.subaxes
[i
+1].vmin
= self
.splitlist
[i
] + 0.5*splitdist
2528 self
.subaxes
[i
+1].vminover
= self
.splitlist
[i
]
2530 while i
< len(self
.subaxes
):
2531 if self
.subaxes
[i
].vmax
is None:
2532 j
= relsize
= relsize2
= 0
2533 while self
.subaxes
[i
+ j
].vmax
is None:
2534 relsize
+= self
.subaxes
[i
+ j
].relsize
+ relsizesplitdist
2536 relsize
+= self
.subaxes
[i
+ j
].relsize
2537 vleft
= self
.subaxes
[i
].vmin
2538 vright
= self
.subaxes
[i
+ j
].vmax
2539 for k
in range(i
, i
+ j
):
2540 relsize2
+= self
.subaxes
[k
].relsize
2541 self
.subaxes
[k
].vmax
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2542 relsize2
+= 0.5 * relsizesplitdist
2543 self
.subaxes
[k
].vmaxover
= self
.subaxes
[k
+ 1].vminover
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2544 relsize2
+= 0.5 * relsizesplitdist
2545 self
.subaxes
[k
+1].vmin
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2546 if i
== 0 and i
+ j
+ 1 == len(self
.subaxes
):
2547 self
.relsize
= relsize
2552 self
.fixmin
= self
.subaxes
[0].fixmin
2554 self
.min = self
.subaxes
[0].min
2555 self
.fixmax
= self
.subaxes
[-1].fixmax
2557 self
.max = self
.subaxes
[-1].max
2560 def getdatarange(self
):
2561 min = self
.subaxes
[0].getdatarange()
2562 max = self
.subaxes
[-1].getdatarange()
2564 return min[0], max[1]
2568 def setdatarange(self
, min, max):
2569 self
.subaxes
[0].setdatarange(min, None)
2570 self
.subaxes
[-1].setdatarange(None, max)
2572 def gettickrange(self
):
2573 min = self
.subaxes
[0].gettickrange()
2574 max = self
.subaxes
[-1].gettickrange()
2576 return min[0], max[1]
2580 def settickrange(self
, min, max):
2581 self
.subaxes
[0].settickrange(min, None)
2582 self
.subaxes
[-1].settickrange(None, max)
2584 def convert(self
, value
):
2585 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
2586 if value
< self
.subaxes
[0].max:
2587 return self
.subaxes
[0].vmin
+ self
.subaxes
[0].convert(value
)*(self
.subaxes
[0].vmax
-self
.subaxes
[0].vmin
)
2588 for axis
in self
.subaxes
[1:-1]:
2589 if value
> axis
.min and value
< axis
.max:
2590 return axis
.vmin
+ axis
.convert(value
)*(axis
.vmax
-axis
.vmin
)
2591 if value
> self
.subaxes
[-1].min:
2592 return self
.subaxes
[-1].vmin
+ self
.subaxes
[-1].convert(value
)*(self
.subaxes
[-1].vmax
-self
.subaxes
[-1].vmin
)
2593 raise ValueError("value couldn't be assigned to a split region")
2595 def vbaseline(self
, v1
=None, v2
=None, axis
=None):
2597 if axis
.baseaxis
.painter
.breaklinesattrs
is None: # XXX undocumented access to painter!?
2600 if axis
.vminover
is None:
2603 left
= axis
.vminover
2605 left
= axis
.vmin
+v1
*(axis
.vmax
-axis
.vmin
)
2607 if axis
.baseaxis
.painter
.breaklinesattrs
is None:
2610 if axis
.vmaxover
is None:
2613 right
= axis
.vmaxover
2615 right
= axis
.vmin
+v2
*(axis
.vmax
-axis
.vmin
)
2616 return axis
.baseaxispos
.vbaseline(left
, right
, axis
=axis
.baseaxis
)
2618 def finish(self
, axispos
, texrunner
):
2622 for subaxis
in self
.subaxes
:
2623 subaxis
.baseaxispos
= axispos
2624 subaxis
.baseaxis
= self
2625 self
.axiscanvas
= axiscanvas(texrunner
)
2626 self
.painter
.paint(axispos
, self
, self
.axiscanvas
)
2628 def createlinkaxis(self
, **args
):
2629 return linksplitaxis(self
, **args
)
2632 class linksplitaxis(linkaxis
):
2633 """a splitaxis linked to an already existing splitaxis
2634 - inherits the access to a linked axis -- as before,
2635 basically only the painter is replaced
2636 - it must take care of the creation of linked axes of
2639 __implements__
= _Iaxis
2641 def __init__(self
, linkedaxis
, painter
=linksplitaxispainter(), subaxispainter
=None):
2642 """initializes the instance
2643 - it gets a axis this linkaxis is linked to
2644 - it gets a painter to be used for this linked axis
2645 - it gets a list of painters to be used for the linkaxes
2646 of the subaxes; if None, the createlinkaxis of the subaxes
2647 are called without a painter parameter; if it is not a
2648 sequence, the subaxispainter is passed as the painter
2649 parameter to all createlinkaxis of the subaxes"""
2650 if subaxispainter
is not None:
2651 if _issequence(subaxispainter
):
2652 if len(linkedaxis
.subaxes
) != len(subaxispainter
):
2653 raise RuntimeError("subaxes and subaxispainter lengths do not fit")
2654 self
.subaxes
= [a
.createlinkaxis(painter
=p
) for a
, p
in zip(linkedaxis
.subaxes
, subaxispainter
)]
2656 self
.subaxes
= [a
.createlinkaxis(painter
=subaxispainter
) for a
in linkedaxis
.subaxes
]
2658 self
.subaxes
= [a
.createlinkaxis() for a
in linkedaxis
.subaxes
]
2659 linkaxis
.__init
__(self
, linkedaxis
, painter
=painter
)
2661 def finish(self
, axispos
, texrunner
):
2662 for subaxis
in self
.subaxes
:
2663 subaxis
.baseaxispos
= axispos
2664 subaxis
.baseaxis
= self
2665 linkaxis
.finish(self
, axispos
, texrunner
)
2668 class baraxis(_subaxispos
):
2669 """implementation of a axis for bar graphs
2670 - a bar axes is different from a splitaxis by the way it
2671 selects its subaxes: the convert method gets a list,
2672 where the first entry is a name selecting a subaxis out
2673 of a list; instead of the term "bar" or "subaxis" the term
2674 "item" will be used here
2675 - the baraxis stores a list of names be identify the items;
2676 the names might be of any time (strings, integers, etc.);
2677 the names can be printed as the titles for the items, but
2678 alternatively the names might be transformed by the texts
2679 dictionary, which maps a name to a text to be used to label
2680 the items in the painter
2681 - usually, there is only one subaxis, which is used as
2682 the subaxis for all items
2683 - alternatively it is also possible to use another baraxis
2684 as a multisubaxis; it is copied via the createsubaxis
2685 method whenever another subaxis is needed (by that a
2686 nested bar axis with a different number of subbars at
2687 each item can be created)
2688 - any axis can be a subaxis of a baraxis; if no subaxis
2689 is specified at all, the baraxis simulates a linear
2690 subaxis with a fixed range of 0 to 1
2691 - a splitaxis implements the _Iaxispos for its subaxes
2692 by inheritance from _subaxispos when the multisubaxis
2693 feature is turned on"""
2695 def __init__(self
, subaxis
=None, multisubaxis
=None, title
=None,
2696 dist
=0.5, firstdist
=None, lastdist
=None, names
=None,
2697 texts
={}, painter
=baraxispainter()):
2698 """initialize the instance
2699 - subaxis contains a axis to be used as the subaxis
2701 - multisubaxis might contain another baraxis instance
2702 to be used to construct a new subaxis for each item;
2703 (by that a nested bar axis with a different number
2704 of subbars at each item can be created)
2705 - only one of subaxis or multisubaxis can be set; if neither
2706 of them is set, the baraxis behaves like having a linaxis
2707 as its subaxis with a fixed range 0 to 1
2708 - the title attribute contains the axis title as a string
2709 - the dist is a relsize to be used as the distance between
2711 - the firstdist and lastdist are the distance before the
2712 first and after the last item, respectively; when set
2713 to None (the default), 0.5*dist is used
2714 - names is a predefined list of names to identify the
2715 items; if set, the name list is fixed
2716 - texts is a dictionary transforming a name to a text in
2717 the painter; if a name isn't found in the dictionary
2719 - the relsize of the baraxis is the sum of the
2720 relsizes including all distances between the items"""
2722 if firstdist
is not None:
2723 self
.firstdist
= firstdist
2725 self
.firstdist
= 0.5 * dist
2726 if lastdist
is not None:
2727 self
.lastdist
= lastdist
2729 self
.lastdist
= 0.5 * dist
2730 self
.relsizes
= None
2733 for name
in helper
.ensuresequence(names
):
2735 self
.fixnames
= names
is not None
2736 self
.multisubaxis
= multisubaxis
2737 if self
.multisubaxis
is not None:
2738 if subaxis
is not None:
2739 raise RuntimeError("either use subaxis or multisubaxis")
2740 self
.subaxis
= [self
.createsubaxis() for name
in self
.names
]
2742 self
.subaxis
= subaxis
2746 self
.painter
= painter
2748 def createsubaxis(self
):
2749 return baraxis(subaxis
=self
.multisubaxis
.subaxis
,
2750 multisubaxis
=self
.multisubaxis
.multisubaxis
,
2751 title
=self
.multisubaxis
.title
,
2752 dist
=self
.multisubaxis
.dist
,
2753 firstdist
=self
.multisubaxis
.firstdist
,
2754 lastdist
=self
.multisubaxis
.lastdist
,
2755 names
=self
.multisubaxis
.names
,
2756 texts
=self
.multisubaxis
.texts
,
2757 painter
=self
.multisubaxis
.painter
)
2759 def getdatarange(self
):
2760 # TODO: we do not yet have a proper range handling for a baraxis
2763 def gettickrange(self
):
2764 # TODO: we do not yet have a proper range handling for a baraxis
2765 raise RuntimeError("range handling for a baraxis is not implemented")
2767 def setdatarange(self
):
2768 # TODO: we do not yet have a proper range handling for a baraxis
2769 raise RuntimeError("range handling for a baraxis is not implemented")
2771 def settickrange(self
):
2772 # TODO: we do not yet have a proper range handling for a baraxis
2773 raise RuntimeError("range handling for a baraxis is not implemented")
2775 def setname(self
, name
, *subnames
):
2776 """add a name to identify an item at the baraxis
2777 - by using subnames, nested name definitions are
2779 - a style (or the user itself) might use this to
2780 insert new items into a baraxis
2781 - setting self.relsizes to None forces later recalculation"""
2782 if not self
.fixnames
:
2783 if name
not in self
.names
:
2784 self
.relsizes
= None
2785 self
.names
.append(name
)
2786 if self
.multisubaxis
is not None:
2787 self
.subaxis
.append(self
.createsubaxis())
2788 if (not self
.fixnames
or name
in self
.names
) and len(subnames
):
2789 if self
.multisubaxis
is not None:
2790 if self
.subaxis
[self
.names
.index(name
)].setname(*subnames
):
2791 self
.relsizes
= None
2793 if self
.subaxis
.setname(*subnames
):
2794 self
.relsizes
= None
2795 return self
.relsizes
is not None
2797 def updaterelsizes(self
):
2798 # guess what it does: it recalculates relsize attribute
2799 self
.relsizes
= [i
*self
.dist
+ self
.firstdist
for i
in range(len(self
.names
) + 1)]
2800 self
.relsizes
[-1] += self
.lastdist
- self
.dist
2801 if self
.multisubaxis
is not None:
2803 for i
in range(1, len(self
.relsizes
)):
2804 self
.subaxis
[i
-1].updaterelsizes()
2805 subrelsize
+= self
.subaxis
[i
-1].relsizes
[-1]
2806 self
.relsizes
[i
] += subrelsize
2808 if self
.subaxis
is None:
2811 self
.subaxis
.updaterelsizes()
2812 subrelsize
= self
.subaxis
.relsizes
[-1]
2813 for i
in range(1, len(self
.relsizes
)):
2814 self
.relsizes
[i
] += i
* subrelsize
2816 def convert(self
, value
):
2817 """baraxis convert method
2818 - the value should be a list, where the first entry is
2819 a member of the names (set in the constructor or by the
2820 setname method); this first entry identifies an item in
2822 - following values are passed to the appropriate subaxis
2824 - when there is no subaxis, the convert method will behave
2825 like having a linaxis from 0 to 1 as subaxis"""
2826 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
2827 if not self
.relsizes
:
2828 self
.updaterelsizes()
2829 pos
= self
.names
.index(value
[0])
2831 if self
.subaxis
is None:
2834 if self
.multisubaxis
is not None:
2835 subvalue
= value
[1] * self
.subaxis
[pos
].relsizes
[-1]
2837 subvalue
= value
[1] * self
.subaxis
.relsizes
[-1]
2839 if self
.multisubaxis
is not None:
2840 subvalue
= self
.subaxis
[pos
].convert(value
[1:]) * self
.subaxis
[pos
].relsizes
[-1]
2842 subvalue
= self
.subaxis
.convert(value
[1:]) * self
.subaxis
.relsizes
[-1]
2843 return (self
.relsizes
[pos
] + subvalue
) / float(self
.relsizes
[-1])
2845 def baseline(self
, x1
=None, x2
=None, axis
=None):
2846 "None is returned -> subaxis should not paint any baselines"
2847 # TODO: quick hack ?!
2850 def vbaseline(self
, v1
=None, v2
=None, axis
=None):
2851 "None is returned -> subaxis should not paint any baselines"
2852 # TODO: quick hack ?!
2855 def finish(self
, axispos
, texrunner
):
2856 if self
.multisubaxis
is not None:
2857 for name
, subaxis
in zip(self
.names
, self
.subaxis
):
2858 subaxis
.vmin
= self
.convert((name
, 0))
2859 subaxis
.vmax
= self
.convert((name
, 1))
2860 subaxis
.baseaxispos
= axispos
2861 subaxis
.baseaxis
= self
2862 self
.axiscanvas
= axiscanvas(texrunner
)
2863 self
.painter
.paint(axispos
, self
, self
.axiscanvas
)
2865 def createlinkaxis(self
, **args
):
2866 return linkbaraxis(self
, **args
)
2869 class linkbaraxis(linkaxis
):
2870 """a baraxis linked to an already existing baraxis
2871 - inherits the access to a linked axis -- as before,
2872 basically only the painter is replaced
2873 - it must take care of the creation of linked axes of
2876 __implements__
= _Iaxis
2878 def __init__(self
, linkedaxis
, painter
=linkbaraxispainter()):
2879 """initializes the instance
2880 - it gets a axis this linkaxis is linked to
2881 - it gets a painter to be used for this linked axis"""
2882 linkaxis
.__init
__(self
, linkedaxis
, painter
=painter
)
2884 def finish(self
, axispos
, texrunner
):
2885 if self
.multisubaxis
is not None:
2886 self
.subaxis
= [subaxis
.createlinkaxis() for subaxis
in self
.linkedaxis
.subaxis
]
2887 for subaxis
in self
.subaxis
:
2888 subaxis
.baseaxispos
= axispos
2889 subaxis
.baseaxis
= self
2890 elif self
.linkedaxis
.subaxis
is not None:
2891 self
.subaxis
= self
.linkedaxis
.subaxis
.createlinkaxis()
2892 self
.subaxis
.baseaxispos
= axispos
2893 self
.subaxis
.baseaxis
= self
2894 linkaxis
.finish(self
, axispos
, texrunner
)
2897 ################################################################################
2899 ################################################################################
2902 # g = graph.graphxy(key=graph.key())
2903 # g.addkey(graph.key(), ...)
2908 def __init__(self
, dist
="0.2 cm", pos
= "tr", hinside
= 1, vinside
= 1, hdist
="0.6 cm", vdist
="0.4 cm",
2909 symbolwidth
="0.5 cm", symbolheight
="0.25 cm", symbolspace
="0.2 cm",
2910 textattrs
=textmodule
.vshift
.mathaxis
):
2911 self
.dist_str
= dist
2913 self
.hinside
= hinside
2914 self
.vinside
= vinside
2915 self
.hdist_str
= hdist
2916 self
.vdist_str
= vdist
2917 self
.symbolwidth_str
= symbolwidth
2918 self
.symbolheight_str
= symbolheight
2919 self
.symbolspace_str
= symbolspace
2920 self
.textattrs
= textattrs
2921 self
.plotinfos
= None
2922 if self
.pos
in ("tr", "rt"):
2925 elif self
.pos
in ("br", "rb"):
2928 elif self
.pos
in ("tl", "lt"):
2931 elif self
.pos
in ("bl", "lb"):
2935 raise RuntimeError("invalid pos attribute")
2937 def setplotinfos(self
, *plotinfos
):
2938 """set the plotinfos to be used in the key
2939 - call it exactly once"""
2940 if self
.plotinfos
is not None:
2941 raise RuntimeError("setplotinfo is called multiple times")
2942 self
.plotinfos
= plotinfos
2944 def dolayout(self
, graph
):
2945 "creates the layout of the key"
2946 self
._dist
= unit
.topt(unit
.length(self
.dist_str
, default_type
="v"))
2947 self
._hdist
= unit
.topt(unit
.length(self
.hdist_str
, default_type
="v"))
2948 self
._vdist
= unit
.topt(unit
.length(self
.vdist_str
, default_type
="v"))
2949 self
._symbolwidth
= unit
.topt(unit
.length(self
.symbolwidth_str
, default_type
="v"))
2950 self
._symbolheight
= unit
.topt(unit
.length(self
.symbolheight_str
, default_type
="v"))
2951 self
._symbolspace
= unit
.topt(unit
.length(self
.symbolspace_str
, default_type
="v"))
2953 for plotinfo
in self
.plotinfos
:
2954 self
.titles
.append(graph
.texrunner
._text
(0, 0, plotinfo
.data
.title
, *helper
.ensuresequence(self
.textattrs
)))
2955 box
._tile
(self
.titles
, self
._dist
, 0, -1)
2956 box
._linealignequal
(self
.titles
, self
._symbolwidth
+ self
._symbolspace
, 1, 0)
2959 """return a bbox for the key
2960 method should be called after dolayout"""
2961 result
= self
.titles
[0].bbox()
2962 for title
in self
.titles
[1:]:
2963 result
= result
+ title
.bbox() + bbox
._bbox
(0, title
.center
[1] - 0.5 * self
._symbolheight
,
2964 0, title
.center
[1] + 0.5 * self
._symbolheight
)
2967 def paint(self
, c
, x
, y
):
2968 """paint the graph key into a canvas c at the position x and y (in postscript points)
2969 - method should be called after dolayout
2970 - the x, y alignment might be calculated by the graph using:
2971 - the bbox of the key as returned by the keys bbox method
2972 - the attributes _hdist, _vdist, hinside, and vinside of the key
2973 - the dimension and geometry of the graph"""
2974 sc
= c
.insert(canvas
.canvas(trafomodule
._translate
(x
, y
)))
2975 for plotinfo
, title
in zip(self
.plotinfos
, self
.titles
):
2976 plotinfo
.style
.key(sc
, 0, -0.5 * self
._symbolheight
+ title
.center
[1],
2977 self
._symbolwidth
, self
._symbolheight
)
2981 ################################################################################
2983 ################################################################################
2988 def __init__(self
, data
, style
):
2994 class graphxy(canvas
.canvas
):
3000 def __init__(self
, type, axispos
, tickdirection
):
3002 - type == 0: x-axis; type == 1: y-axis
3003 - _axispos is the y or x position of the x-axis or y-axis
3004 in postscript points, respectively
3005 - axispos is analogous to _axispos, but as a PyX length
3006 - dx and dy is the tick direction
3009 self
.axispos
= axispos
3010 self
._axispos
= unit
.topt(axispos
)
3011 self
.tickdirection
= tickdirection
3013 def clipcanvas(self
):
3014 return self
.insert(canvas
.canvas(canvas
.clip(path
._rect
(self
._xpos
, self
._ypos
, self
._width
, self
._height
))))
3016 def plot(self
, data
, style
=None):
3018 raise RuntimeError("layout setup was already performed")
3020 if helper
.issequence(data
):
3021 raise RuntimeError("list plot needs an explicit style")
3022 if self
.defaultstyle
.has_key(data
.defaultstyle
):
3023 style
= self
.defaultstyle
[data
.defaultstyle
].iterate()
3025 style
= data
.defaultstyle()
3026 self
.defaultstyle
[data
.defaultstyle
] = style
3029 for d
in helper
.ensuresequence(data
):
3031 style
= style
.iterate()
3034 d
.setstyle(self
, style
)
3035 plotinfos
.append(plotinfo(d
, style
))
3036 self
.plotinfos
.extend(plotinfos
)
3037 if helper
.issequence(data
):
3041 def addkey(self
, key
, *plotinfos
):
3043 raise RuntimeError("layout setup was already performed")
3044 self
.addkeys
.append((key
, plotinfos
))
3046 def _pos(self
, x
, y
, xaxis
=None, yaxis
=None):
3048 xaxis
= self
.axes
["x"]
3050 yaxis
= self
.axes
["y"]
3051 return self
._xpos
+xaxis
.convert(x
)*self
._width
, self
._ypos
+yaxis
.convert(y
)*self
._height
3053 def pos(self
, x
, y
, xaxis
=None, yaxis
=None):
3055 xaxis
= self
.axes
["x"]
3057 yaxis
= self
.axes
["y"]
3058 return self
.xpos
+xaxis
.convert(x
)*self
.width
, self
.ypos
+yaxis
.convert(y
)*self
.height
3060 def _vpos(self
, vx
, vy
, xaxis
=None, yaxis
=None):
3062 xaxis
= self
.axes
["x"]
3064 yaxis
= self
.axes
["y"]
3065 return self
._xpos
+vx
*self
._width
, self
._ypos
+vy
*self
._height
3067 def vpos(self
, vx
, vy
, xaxis
=None, yaxis
=None):
3069 xaxis
= self
.axes
["x"]
3071 yaxis
= self
.axes
["y"]
3072 return self
.xpos
+vx
*self
.width
, self
.ypos
+vy
*self
.height
3074 def baseline(self
, x1
=None, x2
=None, axis
=None):
3076 v1
= axis
.convert(x1
)
3080 v2
= axis
.convert(x2
)
3083 if axis
.axisposdata
.type:
3084 return path
._line
(axis
.axisposdata
._axispos
, self
._ypos
+v1
*self
._height
,
3085 axis
.axisposdata
._axispos
, self
._ypos
+v2
*self
._height
)
3087 return path
._line
(self
._xpos
+v1
*self
._width
, axis
.axisposdata
._axispos
,
3088 self
._xpos
+v2
*self
._width
, axis
.axisposdata
._axispos
)
3090 def xbaseline(self
, x1
=None, x2
=None, axis
=None):
3092 axis
= self
.axes
["x"]
3093 if axis
.axisposdata
.type != 0:
3094 raise RuntimeError("wrong axisposdata.type")
3096 v1
= axis
.convert(x1
)
3100 v2
= axis
.convert(x2
)
3103 return path
._line
(self
._xpos
+v1
*self
._width
, axis
.axisposdata
._axispos
,
3104 self
._xpos
+v2
*self
._width
, axis
.axisposdata
._axispos
)
3106 def ybaseline(self
, x1
=None, x2
=None, axis
=None):
3108 axis
= self
.axes
["y"]
3109 if axis
.axisposdata
.type != 1:
3110 raise RuntimeError("wrong axisposdata.type")
3112 v1
= axis
.convert(x1
)
3116 v2
= axis
.convert(x2
)
3119 return path
._line
(axis
.axisposdata
._axispos
, self
._ypos
+v1
*self
._height
,
3120 axis
.axisposdata
._axispos
, self
._ypos
+v2
*self
._height
)
3122 def vbaseline(self
, v1
=None, v2
=None, axis
=None):
3127 if axis
.axisposdata
.type:
3128 return path
._line
(axis
.axisposdata
._axispos
, self
._ypos
+v1
*self
._height
,
3129 axis
.axisposdata
._axispos
, self
._ypos
+v2
*self
._height
)
3131 return path
._line
(self
._xpos
+v1
*self
._width
, axis
.axisposdata
._axispos
,
3132 self
._xpos
+v2
*self
._width
, axis
.axisposdata
._axispos
)
3134 def vxbaseline(self
, v1
=None, v2
=None, axis
=None):
3136 axis
= self
.axes
["x"]
3137 if axis
.axisposdata
.type != 0:
3138 raise RuntimeError("wrong axisposdata.type")
3143 return path
._line
(self
._xpos
+v1
*self
._width
, axis
.axisposdata
._axispos
,
3144 self
._xpos
+v2
*self
._width
, axis
.axisposdata
._axispos
)
3146 def vybaseline(self
, v1
=None, v2
=None, axis
=None):
3148 axis
= self
.axes
["y"]
3149 if axis
.axisposdata
.type != 1:
3150 raise RuntimeError("wrong axisposdata.type")
3155 return path
._line
(axis
.axisposdata
._axispos
, self
._ypos
+v1
*self
._height
,
3156 axis
.axisposdata
._axispos
, self
._ypos
+v2
*self
._height
)
3158 def gridline(self
, x
, axis
=None):
3160 if axis
.axisposdata
.type:
3161 return path
._line
(self
._xpos
, self
._ypos
+v
*self
._height
,
3162 self
._xpos
+self
._width
, self
._ypos
+v
*self
._height
)
3164 return path
._line
(self
._xpos
+v
*self
._width
, self
._ypos
,
3165 self
._xpos
+v
*self
._width
, self
._ypos
+self
._height
)
3167 def xgridline(self
, x
, axis
=None):
3169 axis
= self
.axes
["x"]
3171 if axis
.axisposdata
.type != 0:
3172 raise RuntimeError("wrong axisposdata.type")
3173 return path
._line
(self
._xpos
+v
*self
._width
, self
._ypos
,
3174 self
._xpos
+v
*self
._width
, self
._ypos
+self
._height
)
3176 def ygridline(self
, x
, axis
=None):
3178 axis
= self
.axes
["y"]
3180 if axis
.axisposdata
.type != 1:
3181 raise RuntimeError("wrong axisposdata.type")
3182 return path
._line
(self
._xpos
, self
._ypos
+v
*self
._height
,
3183 self
._xpos
+self
._width
, self
._ypos
+v
*self
._height
)
3185 def vgridline(self
, v
, axis
=None):
3186 if axis
.axisposdata
.type:
3187 return path
._line
(self
._xpos
, self
._ypos
+v
*self
._height
,
3188 self
._xpos
+self
._width
, self
._ypos
+v
*self
._height
)
3190 return path
._line
(self
._xpos
+v
*self
._width
, self
._ypos
,
3191 self
._xpos
+v
*self
._width
, self
._ypos
+self
._height
)
3193 def vxgridline(self
, v
, axis
=None):
3195 axis
= self
.axes
["x"]
3196 if axis
.axisposdata
.type != 0:
3197 raise RuntimeError("wrong axisposdata.type")
3198 return path
._line
(self
._xpos
+v
*self
._width
, self
._ypos
,
3199 self
._xpos
+v
*self
._width
, self
._ypos
+self
._height
)
3201 def vygridline(self
, v
, axis
=None):
3203 axis
= self
.axes
["y"]
3204 if axis
.axisposdata
.type != 1:
3205 raise RuntimeError("wrong axisposdata.type")
3206 return path
._line
(self
._xpos
, self
._ypos
+v
*self
._height
,
3207 self
._xpos
+self
._width
, self
._ypos
+v
*self
._height
)
3209 def _tickpoint(self
, x
, axis
=None):
3210 if axis
.axisposdata
.type:
3211 return axis
.axisposdata
._axispos
, self
._ypos
+axis
.convert(x
)*self
._height
3213 return self
._xpos
+axis
.convert(x
)*self
._width
, axis
.axisposdata
._axispos
3215 def _xtickpoint(self
, x
, axis
=None):
3217 axis
= self
.axes
["x"]
3218 if axis
.axisposdata
.type != 0:
3219 raise RuntimeError("wrong axisposdata.type")
3220 return self
._xpos
+axis
.convert(x
)*self
._width
, axis
.axisposdata
._axispos
3222 def _ytickpoint(self
, x
, axis
=None):
3224 axis
= self
.axes
["y"]
3225 if axis
.axisposdata
.type != 1:
3226 raise RuntimeError("wrong axisposdata.type")
3227 return axis
.axisposdata
._axispos
, self
._ypos
+axis
.convert(x
)*self
._height
3229 def tickpoint(self
, x
, axis
=None):
3230 if axis
.axisposdata
.type:
3231 return axis
.axisposdata
.axispos
, self
.ypos
+axis
.convert(x
)*self
.height
3233 return self
.xpos
+axis
.convert(x
)*self
.width
, axis
.axisposdata
.axispos
3235 def xtickpoint(self
, x
, axis
=None):
3237 axis
= self
.axes
["x"]
3238 if axis
.axisposdata
.type != 0:
3239 raise RuntimeError("wrong axisposdata.type")
3240 return self
.xpos
+axis
.convert(x
)*self
.width
, axis
.axisposdata
.axispos
3242 def ytickpoint(self
, x
, axis
=None):
3244 axis
= self
.axes
["y"]
3245 if axis
.axisposdata
.type != 1:
3246 raise RuntimeError("wrong axisposdata.type")
3247 return axis
.axisposdata
.axispos
, self
.ypos
+axis
.convert(x
)*self
.height
3249 def _vtickpoint(self
, v
, axis
=None):
3250 if axis
.axisposdata
.type:
3251 return axis
.axisposdata
._axispos
, self
._ypos
+v
*self
._height
3253 return self
._xpos
+v
*self
._width
, axis
.axisposdata
._axispos
3255 def _vxtickpoint(self
, v
, axis
=None):
3257 axis
= self
.axes
["x"]
3258 if axis
.axisposdata
.type != 0:
3259 raise RuntimeError("wrong axisposdata.type")
3260 return self
._xpos
+v
*self
._width
, axis
.axisposdata
._axispos
3262 def _vytickpoint(self
, v
, axis
=None):
3264 axis
= self
.axes
["y"]
3265 if axis
.axisposdata
.type != 1:
3266 raise RuntimeError("wrong axisposdata.type")
3267 return axis
.axisposdata
._axispos
, self
._ypos
+v
*self
._height
3269 def vtickpoint(self
, v
, axis
=None):
3270 if axis
.axisposdata
.type:
3271 return axis
.axisposdata
.axispos
, self
.ypos
+v
*self
.height
3273 return self
.xpos
+v
*self
.width
, axis
.axisposdata
.axispos
3275 def vxtickpoint(self
, v
, axis
=None):
3277 axis
= self
.axes
["x"]
3278 if axis
.axisposdata
.type != 0:
3279 raise RuntimeError("wrong axisposdata.type")
3280 return self
.xpos
+v
*self
.width
, axis
.axisposdata
.axispos
3282 def vytickpoint(self
, v
, axis
=None):
3284 axis
= self
.axes
["y"]
3285 if axis
.axisposdata
.type != 1:
3286 raise RuntimeError("wrong axisposdata.type")
3287 return axis
.axisposdata
.axispos
, self
.ypos
+v
*self
.height
3289 def tickdirection(self
, x
, axis
=None):
3290 return axis
.axisposdata
.tickdirection
3292 def xtickdirection(self
, x
, axis
=None):
3294 axis
= self
.axes
["x"]
3295 if axis
.axisposdata
.type != 0:
3296 raise RuntimeError("wrong axisposdata.type")
3297 return axis
.axisposdata
.tickdirection
3299 def ytickdirection(self
, x
, axis
=None):
3301 axis
= self
.axes
["y"]
3302 if axis
.axisposdata
.type != 1:
3303 raise RuntimeError("wrong axisposdata.type")
3304 return axis
.axisposdata
.tickdirection
3306 def vtickdirection(self
, v
, axis
=None):
3307 return axis
.axisposdata
.tickdirection
3309 def vxtickdirection(self
, v
, axis
=None):
3311 axis
= self
.axes
["x"]
3312 if axis
.axisposdata
.type != 0:
3313 raise RuntimeError("wrong axisposdata.type")
3314 return axis
.axisposdata
.tickdirection
3316 def vytickdirection(self
, v
, axis
=None):
3318 axis
= self
.axes
["y"]
3319 if axis
.axisposdata
.type != 1:
3320 raise RuntimeError("wrong axisposdata.type")
3321 return axis
.axisposdata
.tickdirection
3323 def _addpos(self
, x
, y
, dx
, dy
):
3326 def _connect(self
, x1
, y1
, x2
, y2
):
3327 return path
._lineto
(x2
, y2
)
3329 def keynum(self
, key
):
3331 while key
[0] in string
.letters
:
3337 def gatherranges(self
):
3339 for plotinfo
in self
.plotinfos
:
3340 pdranges
= plotinfo
.data
.getranges()
3341 if pdranges
is not None:
3342 for key
in pdranges
.keys():
3343 if key
not in ranges
.keys():
3344 ranges
[key
] = pdranges
[key
]
3346 ranges
[key
] = (min(ranges
[key
][0], pdranges
[key
][0]),
3347 max(ranges
[key
][1], pdranges
[key
][1]))
3348 # known ranges are also set as ranges for the axes
3349 for key
, axis
in self
.axes
.items():
3350 if key
in ranges
.keys():
3351 axis
.setdatarange(*ranges
[key
])
3352 ranges
[key
] = axis
.getdatarange()
3353 if ranges
[key
] is None:
3357 def removedomethod(self
, method
):
3361 self
.domethods
.remove(method
)
3367 if not self
.removedomethod(self
.dolayout
): return
3369 # create list of ranges
3371 ranges
= self
.gatherranges()
3372 # 2. calculate additional ranges out of known ranges
3373 for plotinfo
in self
.plotinfos
:
3374 plotinfo
.data
.setranges(ranges
)
3375 # 3. gather ranges again
3377 # do the layout for all axes
3378 axesdist
= unit
.length(self
.axesdist_str
, default_type
="v")
3379 XPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[0])
3380 YPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[1])
3381 xaxisextents
= [0, 0]
3382 yaxisextents
= [0, 0]
3383 needxaxisdist
= [0, 0]
3384 needyaxisdist
= [0, 0]
3385 items
= list(self
.axes
.items())
3386 items
.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
3387 for key
, axis
in items
:
3388 num
= self
.keynum(key
)
3389 num2
= 1 - num
% 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
3390 num3
= 1 - 2 * (num
% 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
3391 if XPattern
.match(key
):
3392 if needxaxisdist
[num2
]:
3393 xaxisextents
[num2
] += axesdist
3394 axis
.axisposdata
= self
.axisposdata(0, self
.ypos
+ num2
*self
.height
+ num3
*xaxisextents
[num2
], (0, num3
))
3395 elif YPattern
.match(key
):
3396 if needyaxisdist
[num2
]:
3397 yaxisextents
[num2
] += axesdist
3398 axis
.axisposdata
= self
.axisposdata(1, self
.xpos
+ num2
*self
.width
+ num3
*yaxisextents
[num2
], (num3
, 0))
3400 raise ValueError("Axis key '%s' not allowed" % key
)
3401 axis
.finish(self
, self
.texrunner
)
3402 if XPattern
.match(key
):
3403 xaxisextents
[num2
] += axis
.axiscanvas
.extent
3404 needxaxisdist
[num2
] = 1
3405 if YPattern
.match(key
):
3406 yaxisextents
[num2
] += axis
.axiscanvas
.extent
3407 needyaxisdist
[num2
] = 1
3409 def dobackground(self
):
3411 if not self
.removedomethod(self
.dobackground
): return
3412 if self
.backgroundattrs
is not None:
3413 self
.draw(path
._rect
(self
._xpos
, self
._ypos
, self
._width
, self
._height
),
3414 *helper
.ensuresequence(self
.backgroundattrs
))
3418 if not self
.removedomethod(self
.doaxes
): return
3419 for axis
in self
.axes
.values():
3420 self
.insert(axis
.axiscanvas
)
3424 if not self
.removedomethod(self
.dodata
): return
3425 for plotinfo
in self
.plotinfos
:
3426 plotinfo
.data
.draw(self
)
3428 def _dokey(self
, key
, *plotinfos
):
3429 key
.setplotinfos(*plotinfos
)
3434 x
= self
._xpos
+ self
._width
- bbox
.urx
- key
._hdist
3436 x
= self
._xpos
+ self
._width
- bbox
.llx
+ key
._hdist
3439 x
= self
._xpos
- bbox
.llx
+ key
._hdist
3441 x
= self
._xpos
- bbox
.urx
- key
._hdist
3444 y
= self
._ypos
+ self
._height
- bbox
.ury
- key
._vdist
3446 y
= self
._ypos
+ self
._height
- bbox
.lly
+ key
._vdist
3449 y
= self
._ypos
- bbox
.lly
+ key
._vdist
3451 y
= self
._ypos
- bbox
.ury
- key
._vdist
3452 key
.paint(self
, x
, y
)
3456 if not self
.removedomethod(self
.dokey
): return
3457 if self
.key
is not None:
3458 self
._dokey
(self
.key
, *self
.plotinfos
)
3459 for key
, plotinfos
in self
.addkeys
:
3460 self
._dokey
(key
, *plotinfos
)
3463 while len(self
.domethods
):
3466 def initwidthheight(self
, width
, height
, ratio
):
3467 if (width
is not None) and (height
is None):
3468 self
.width
= unit
.length(width
)
3469 self
.height
= (1.0/ratio
) * self
.width
3470 elif (height
is not None) and (width
is None):
3471 self
.height
= unit
.length(height
)
3472 self
.width
= ratio
* self
.height
3474 self
.width
= unit
.length(width
)
3475 self
.height
= unit
.length(height
)
3476 self
._width
= unit
.topt(self
.width
)
3477 self
._height
= unit
.topt(self
.height
)
3478 if self
._width
<= 0: raise ValueError("width <= 0")
3479 if self
._height
<= 0: raise ValueError("height <= 0")
3481 def initaxes(self
, axes
, addlinkaxes
=0):
3482 for key
in self
.Names
:
3483 if not axes
.has_key(key
):
3484 axes
[key
] = linaxis()
3485 elif axes
[key
] is None:
3488 if not axes
.has_key(key
+ "2") and axes
.has_key(key
):
3489 axes
[key
+ "2"] = axes
[key
].createlinkaxis()
3490 elif axes
[key
+ "2"] is None:
3494 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, ratio
=goldenmean
,
3495 key
=None, backgroundattrs
=None, axesdist
="0.8 cm", **axes
):
3496 canvas
.canvas
.__init
__(self
)
3497 self
.xpos
= unit
.length(xpos
)
3498 self
.ypos
= unit
.length(ypos
)
3499 self
._xpos
= unit
.topt(self
.xpos
)
3500 self
._ypos
= unit
.topt(self
.ypos
)
3501 self
.initwidthheight(width
, height
, ratio
)
3502 self
.initaxes(axes
, 1)
3504 self
.backgroundattrs
= backgroundattrs
3505 self
.axesdist_str
= axesdist
3507 self
.domethods
= [self
.dolayout
, self
.dobackground
, self
.doaxes
, self
.dodata
, self
.dokey
]
3509 self
.defaultstyle
= {}
3514 return canvas
.canvas
.bbox(self
)
3516 def write(self
, file):
3518 canvas
.canvas
.write(self
, file)
3522 # some thoughts, but deferred right now
3524 # class graphxyz(graphxy):
3526 # Names = "x", "y", "z"
3528 # def _vxtickpoint(self, axis, v):
3529 # return self._vpos(v, axis.vypos, axis.vzpos)
3531 # def _vytickpoint(self, axis, v):
3532 # return self._vpos(axis.vxpos, v, axis.vzpos)
3534 # def _vztickpoint(self, axis, v):
3535 # return self._vpos(axis.vxpos, axis.vypos, v)
3537 # def vxtickdirection(self, axis, v):
3538 # x1, y1 = self._vpos(v, axis.vypos, axis.vzpos)
3539 # x2, y2 = self._vpos(v, 0.5, 0)
3540 # dx, dy = x1 - x2, y1 - y2
3541 # norm = math.sqrt(dx*dx + dy*dy)
3542 # return dx/norm, dy/norm
3544 # def vytickdirection(self, axis, v):
3545 # x1, y1 = self._vpos(axis.vxpos, v, axis.vzpos)
3546 # x2, y2 = self._vpos(0.5, v, 0)
3547 # dx, dy = x1 - x2, y1 - y2
3548 # norm = math.sqrt(dx*dx + dy*dy)
3549 # return dx/norm, dy/norm
3551 # def vztickdirection(self, axis, v):
3553 # x1, y1 = self._vpos(axis.vxpos, axis.vypos, v)
3554 # x2, y2 = self._vpos(0.5, 0.5, v)
3555 # dx, dy = x1 - x2, y1 - y2
3556 # norm = math.sqrt(dx*dx + dy*dy)
3557 # return dx/norm, dy/norm
3559 # def _pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
3560 # if xaxis is None: xaxis = self.axes["x"]
3561 # if yaxis is None: yaxis = self.axes["y"]
3562 # if zaxis is None: zaxis = self.axes["z"]
3563 # return self._vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
3565 # def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
3566 # if xaxis is None: xaxis = self.axes["x"]
3567 # if yaxis is None: yaxis = self.axes["y"]
3568 # if zaxis is None: zaxis = self.axes["z"]
3569 # return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
3571 # def _vpos(self, vx, vy, vz):
3572 # x, y, z = (vx - 0.5)*self._depth, (vy - 0.5)*self._width, (vz - 0.5)*self._height
3573 # d0 = float(self.a[0]*self.b[1]*(z-self.eye[2])
3574 # + self.a[2]*self.b[0]*(y-self.eye[1])
3575 # + self.a[1]*self.b[2]*(x-self.eye[0])
3576 # - self.a[2]*self.b[1]*(x-self.eye[0])
3577 # - self.a[0]*self.b[2]*(y-self.eye[1])
3578 # - self.a[1]*self.b[0]*(z-self.eye[2]))
3579 # da = (self.eye[0]*self.b[1]*(z-self.eye[2])
3580 # + self.eye[2]*self.b[0]*(y-self.eye[1])
3581 # + self.eye[1]*self.b[2]*(x-self.eye[0])
3582 # - self.eye[2]*self.b[1]*(x-self.eye[0])
3583 # - self.eye[0]*self.b[2]*(y-self.eye[1])
3584 # - self.eye[1]*self.b[0]*(z-self.eye[2]))
3585 # db = (self.a[0]*self.eye[1]*(z-self.eye[2])
3586 # + self.a[2]*self.eye[0]*(y-self.eye[1])
3587 # + self.a[1]*self.eye[2]*(x-self.eye[0])
3588 # - self.a[2]*self.eye[1]*(x-self.eye[0])
3589 # - self.a[0]*self.eye[2]*(y-self.eye[1])
3590 # - self.a[1]*self.eye[0]*(z-self.eye[2]))
3591 # return da/d0 + self._xpos, db/d0 + self._ypos
3593 # def vpos(self, vx, vy, vz):
3594 # tx, ty = self._vpos(vx, vy, vz)
3595 # return unit.t_pt(tx), unit.t_pt(ty)
3597 # def xbaseline(self, axis, x1, x2, xaxis=None):
3598 # if xaxis is None: xaxis = self.axes["x"]
3599 # return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2))
3601 # def ybaseline(self, axis, y1, y2, yaxis=None):
3602 # if yaxis is None: yaxis = self.axes["y"]
3603 # return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2))
3605 # def zbaseline(self, axis, z1, z2, zaxis=None):
3606 # if zaxis is None: zaxis = self.axes["z"]
3607 # return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2))
3609 # def vxbaseline(self, axis, v1, v2):
3610 # return (path._line(*(self._vpos(v1, 0, 0) + self._vpos(v2, 0, 0))) +
3611 # path._line(*(self._vpos(v1, 0, 1) + self._vpos(v2, 0, 1))) +
3612 # path._line(*(self._vpos(v1, 1, 1) + self._vpos(v2, 1, 1))) +
3613 # path._line(*(self._vpos(v1, 1, 0) + self._vpos(v2, 1, 0))))
3615 # def vybaseline(self, axis, v1, v2):
3616 # return (path._line(*(self._vpos(0, v1, 0) + self._vpos(0, v2, 0))) +
3617 # path._line(*(self._vpos(0, v1, 1) + self._vpos(0, v2, 1))) +
3618 # path._line(*(self._vpos(1, v1, 1) + self._vpos(1, v2, 1))) +
3619 # path._line(*(self._vpos(1, v1, 0) + self._vpos(1, v2, 0))))
3621 # def vzbaseline(self, axis, v1, v2):
3622 # return (path._line(*(self._vpos(0, 0, v1) + self._vpos(0, 0, v2))) +
3623 # path._line(*(self._vpos(0, 1, v1) + self._vpos(0, 1, v2))) +
3624 # path._line(*(self._vpos(1, 1, v1) + self._vpos(1, 1, v2))) +
3625 # path._line(*(self._vpos(1, 0, v1) + self._vpos(1, 0, v2))))
3627 # def xgridpath(self, x, xaxis=None):
3629 # if xaxis is None: xaxis = self.axes["x"]
3630 # v = xaxis.convert(x)
3631 # return path._line(self._xpos+v*self._width, self._ypos,
3632 # self._xpos+v*self._width, self._ypos+self._height)
3634 # def ygridpath(self, y, yaxis=None):
3636 # if yaxis is None: yaxis = self.axes["y"]
3637 # v = yaxis.convert(y)
3638 # return path._line(self._xpos, self._ypos+v*self._height,
3639 # self._xpos+self._width, self._ypos+v*self._height)
3641 # def zgridpath(self, z, zaxis=None):
3643 # if zaxis is None: zaxis = self.axes["z"]
3644 # v = zaxis.convert(z)
3645 # return path._line(self._xpos, self._zpos+v*self._height,
3646 # self._xpos+self._width, self._zpos+v*self._height)
3648 # def vxgridpath(self, v):
3649 # return path.path(path._moveto(*self._vpos(v, 0, 0)),
3650 # path._lineto(*self._vpos(v, 0, 1)),
3651 # path._lineto(*self._vpos(v, 1, 1)),
3652 # path._lineto(*self._vpos(v, 1, 0)),
3655 # def vygridpath(self, v):
3656 # return path.path(path._moveto(*self._vpos(0, v, 0)),
3657 # path._lineto(*self._vpos(0, v, 1)),
3658 # path._lineto(*self._vpos(1, v, 1)),
3659 # path._lineto(*self._vpos(1, v, 0)),
3662 # def vzgridpath(self, v):
3663 # return path.path(path._moveto(*self._vpos(0, 0, v)),
3664 # path._lineto(*self._vpos(0, 1, v)),
3665 # path._lineto(*self._vpos(1, 1, v)),
3666 # path._lineto(*self._vpos(1, 0, v)),
3669 # def _addpos(self, x, y, dx, dy):
3673 # def _connect(self, x1, y1, x2, y2):
3675 # return path._lineto(x2, y2)
3679 # if not self.removedomethod(self.doaxes): return
3680 # axesdist = unit.topt(unit.length(self.axesdist_str, default_type="v"))
3681 # XPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[0])
3682 # YPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[1])
3683 # ZPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[2])
3684 # items = list(self.axes.items())
3685 # items.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
3686 # for key, axis in items:
3687 # num = self.keynum(key)
3688 # num2 = 1 - num % 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
3689 # num3 = 1 - 2 * (num % 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
3690 # if XPattern.match(key):
3693 # axis._vtickpoint = self._vxtickpoint
3694 # axis.vgridpath = self.vxgridpath
3695 # axis.vbaseline = self.vxbaseline
3696 # axis.vtickdirection = self.vxtickdirection
3697 # elif YPattern.match(key):
3700 # axis._vtickpoint = self._vytickpoint
3701 # axis.vgridpath = self.vygridpath
3702 # axis.vbaseline = self.vybaseline
3703 # axis.vtickdirection = self.vytickdirection
3704 # elif ZPattern.match(key):
3707 # axis._vtickpoint = self._vztickpoint
3708 # axis.vgridpath = self.vzgridpath
3709 # axis.vbaseline = self.vzbaseline
3710 # axis.vtickdirection = self.vztickdirection
3712 # raise ValueError("Axis key '%s' not allowed" % key)
3713 # if axis.painter is not None:
3714 # axis.dopaint(self)
3715 # # if XPattern.match(key):
3716 # # self._xaxisextents[num2] += axis._extent
3717 # # needxaxisdist[num2] = 1
3718 # # if YPattern.match(key):
3719 # # self._yaxisextents[num2] += axis._extent
3720 # # needyaxisdist[num2] = 1
3722 # def __init__(self, tex, xpos=0, ypos=0, width=None, height=None, depth=None,
3723 # phi=30, theta=30, distance=1,
3724 # backgroundattrs=None, axesdist="0.8 cm", **axes):
3725 # canvas.canvas.__init__(self)
3729 # self._xpos = unit.topt(xpos)
3730 # self._ypos = unit.topt(ypos)
3731 # self._width = unit.topt(width)
3732 # self._height = unit.topt(height)
3733 # self._depth = unit.topt(depth)
3734 # self.width = width
3735 # self.height = height
3736 # self.depth = depth
3737 # if self._width <= 0: raise ValueError("width < 0")
3738 # if self._height <= 0: raise ValueError("height < 0")
3739 # if self._depth <= 0: raise ValueError("height < 0")
3740 # self._distance = distance*math.sqrt(self._width*self._width+
3741 # self._height*self._height+
3742 # self._depth*self._depth)
3743 # phi *= -math.pi/180
3744 # theta *= math.pi/180
3745 # self.a = (-math.sin(phi), math.cos(phi), 0)
3746 # self.b = (-math.cos(phi)*math.sin(theta),
3747 # -math.sin(phi)*math.sin(theta),
3749 # self.eye = (self._distance*math.cos(phi)*math.cos(theta),
3750 # self._distance*math.sin(phi)*math.cos(theta),
3751 # self._distance*math.sin(theta))
3752 # self.initaxes(axes)
3753 # self.axesdist_str = axesdist
3754 # self.backgroundattrs = backgroundattrs
3757 # self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
3758 # self.haslayout = 0
3759 # self.defaultstyle = {}
3763 # return bbox._bbox(self._xpos - 200, self._ypos - 200, self._xpos + 200, self._ypos + 200)
3766 ################################################################################
3768 ################################################################################
3771 #class _Ichangeattr:
3772 # """attribute changer
3773 # is an iterator for attributes where an attribute
3774 # is not refered by just a number (like for a sequence),
3775 # but also by the number of attributes requested
3776 # by calls of the next method (like for an color palette)
3777 # (you should ensure to call all needed next before the attr)
3779 # the attribute itself is implemented by overloading the _attr method"""
3782 # "get an attribute"
3785 # "get an attribute changer for the next attribute"
3788 class _changeattr
: pass
3791 class changeattr(_changeattr
):
3800 newindex
= self
.counter
3802 return refattr(self
, newindex
)
3805 class refattr(_changeattr
):
3807 def __init__(self
, ref
, index
):
3812 return self
.ref
.attr(self
.index
)
3815 return self
.ref
.iterate()
3818 # helper routines for a using attrs
3821 "get attr out of a attr/changeattr"
3822 if isinstance(attr
, _changeattr
):
3823 return attr
.getattr()
3827 def _getattrs(attrs
):
3828 "get attrs out of a sequence of attr/changeattr"
3829 if attrs
is not None:
3831 for attr
in helper
.ensuresequence(attrs
):
3832 if isinstance(attr
, _changeattr
):
3833 attr
= attr
.getattr()
3834 if attr
is not None:
3836 if len(result
) or not len(attrs
):
3840 def _iterateattr(attr
):
3841 "perform next to a attr/changeattr"
3842 if isinstance(attr
, _changeattr
):
3843 return attr
.iterate()
3847 def _iterateattrs(attrs
):
3848 "perform next to a sequence of attr/changeattr"
3849 if attrs
is not None:
3851 for attr
in helper
.ensuresequence(attrs
):
3852 if isinstance(attr
, _changeattr
):
3853 result
.append(attr
.iterate())
3859 class changecolor(changeattr
):
3861 def __init__(self
, palette
):
3862 changeattr
.__init
__(self
)
3863 self
.palette
= palette
3865 def attr(self
, index
):
3866 if self
.counter
!= 1:
3867 return self
.palette
.getcolor(index
/float(self
.counter
-1))
3869 return self
.palette
.getcolor(0)
3872 class _changecolorgray(changecolor
):
3874 def __init__(self
, palette
=color
.palette
.Gray
):
3875 changecolor
.__init
__(self
, palette
)
3877 _changecolorgrey
= _changecolorgray
3880 class _changecolorreversegray(changecolor
):
3882 def __init__(self
, palette
=color
.palette
.ReverseGray
):
3883 changecolor
.__init
__(self
, palette
)
3885 _changecolorreversegrey
= _changecolorreversegray
3888 class _changecolorredblack(changecolor
):
3890 def __init__(self
, palette
=color
.palette
.RedBlack
):
3891 changecolor
.__init
__(self
, palette
)
3894 class _changecolorblackred(changecolor
):
3896 def __init__(self
, palette
=color
.palette
.BlackRed
):
3897 changecolor
.__init
__(self
, palette
)
3900 class _changecolorredwhite(changecolor
):
3902 def __init__(self
, palette
=color
.palette
.RedWhite
):
3903 changecolor
.__init
__(self
, palette
)
3906 class _changecolorwhitered(changecolor
):
3908 def __init__(self
, palette
=color
.palette
.WhiteRed
):
3909 changecolor
.__init
__(self
, palette
)
3912 class _changecolorgreenblack(changecolor
):
3914 def __init__(self
, palette
=color
.palette
.GreenBlack
):
3915 changecolor
.__init
__(self
, palette
)
3918 class _changecolorblackgreen(changecolor
):
3920 def __init__(self
, palette
=color
.palette
.BlackGreen
):
3921 changecolor
.__init
__(self
, palette
)
3924 class _changecolorgreenwhite(changecolor
):
3926 def __init__(self
, palette
=color
.palette
.GreenWhite
):
3927 changecolor
.__init
__(self
, palette
)
3930 class _changecolorwhitegreen(changecolor
):
3932 def __init__(self
, palette
=color
.palette
.WhiteGreen
):
3933 changecolor
.__init
__(self
, palette
)
3936 class _changecolorblueblack(changecolor
):
3938 def __init__(self
, palette
=color
.palette
.BlueBlack
):
3939 changecolor
.__init
__(self
, palette
)
3942 class _changecolorblackblue(changecolor
):
3944 def __init__(self
, palette
=color
.palette
.BlackBlue
):
3945 changecolor
.__init
__(self
, palette
)
3948 class _changecolorbluewhite(changecolor
):
3950 def __init__(self
, palette
=color
.palette
.BlueWhite
):
3951 changecolor
.__init
__(self
, palette
)
3954 class _changecolorwhiteblue(changecolor
):
3956 def __init__(self
, palette
=color
.palette
.WhiteBlue
):
3957 changecolor
.__init
__(self
, palette
)
3960 class _changecolorredgreen(changecolor
):
3962 def __init__(self
, palette
=color
.palette
.RedGreen
):
3963 changecolor
.__init
__(self
, palette
)
3966 class _changecolorredblue(changecolor
):
3968 def __init__(self
, palette
=color
.palette
.RedBlue
):
3969 changecolor
.__init
__(self
, palette
)
3972 class _changecolorgreenred(changecolor
):
3974 def __init__(self
, palette
=color
.palette
.GreenRed
):
3975 changecolor
.__init
__(self
, palette
)
3978 class _changecolorgreenblue(changecolor
):
3980 def __init__(self
, palette
=color
.palette
.GreenBlue
):
3981 changecolor
.__init
__(self
, palette
)
3984 class _changecolorbluered(changecolor
):
3986 def __init__(self
, palette
=color
.palette
.BlueRed
):
3987 changecolor
.__init
__(self
, palette
)
3990 class _changecolorbluegreen(changecolor
):
3992 def __init__(self
, palette
=color
.palette
.BlueGreen
):
3993 changecolor
.__init
__(self
, palette
)
3996 class _changecolorrainbow(changecolor
):
3998 def __init__(self
, palette
=color
.palette
.Rainbow
):
3999 changecolor
.__init
__(self
, palette
)
4002 class _changecolorreverserainbow(changecolor
):
4004 def __init__(self
, palette
=color
.palette
.ReverseRainbow
):
4005 changecolor
.__init
__(self
, palette
)
4008 class _changecolorhue(changecolor
):
4010 def __init__(self
, palette
=color
.palette
.Hue
):
4011 changecolor
.__init
__(self
, palette
)
4014 class _changecolorreversehue(changecolor
):
4016 def __init__(self
, palette
=color
.palette
.ReverseHue
):
4017 changecolor
.__init
__(self
, palette
)
4020 changecolor
.Gray
= _changecolorgray
4021 changecolor
.Grey
= _changecolorgrey
4022 changecolor
.Reversegray
= _changecolorreversegray
4023 changecolor
.Reversegrey
= _changecolorreversegrey
4024 changecolor
.RedBlack
= _changecolorredblack
4025 changecolor
.BlackRed
= _changecolorblackred
4026 changecolor
.RedWhite
= _changecolorredwhite
4027 changecolor
.WhiteRed
= _changecolorwhitered
4028 changecolor
.GreenBlack
= _changecolorgreenblack
4029 changecolor
.BlackGreen
= _changecolorblackgreen
4030 changecolor
.GreenWhite
= _changecolorgreenwhite
4031 changecolor
.WhiteGreen
= _changecolorwhitegreen
4032 changecolor
.BlueBlack
= _changecolorblueblack
4033 changecolor
.BlackBlue
= _changecolorblackblue
4034 changecolor
.BlueWhite
= _changecolorbluewhite
4035 changecolor
.WhiteBlue
= _changecolorwhiteblue
4036 changecolor
.RedGreen
= _changecolorredgreen
4037 changecolor
.RedBlue
= _changecolorredblue
4038 changecolor
.GreenRed
= _changecolorgreenred
4039 changecolor
.GreenBlue
= _changecolorgreenblue
4040 changecolor
.BlueRed
= _changecolorbluered
4041 changecolor
.BlueGreen
= _changecolorbluegreen
4042 changecolor
.Rainbow
= _changecolorrainbow
4043 changecolor
.ReverseRainbow
= _changecolorreverserainbow
4044 changecolor
.Hue
= _changecolorhue
4045 changecolor
.ReverseHue
= _changecolorreversehue
4048 class changesequence(changeattr
):
4049 "cycles through a sequence"
4051 def __init__(self
, *sequence
):
4052 changeattr
.__init
__(self
)
4053 if not len(sequence
):
4054 sequence
= self
.defaultsequence
4055 self
.sequence
= sequence
4057 def attr(self
, index
):
4058 return self
.sequence
[index
% len(self
.sequence
)]
4061 class changelinestyle(changesequence
):
4062 defaultsequence
= (canvas
.linestyle
.solid
,
4063 canvas
.linestyle
.dashed
,
4064 canvas
.linestyle
.dotted
,
4065 canvas
.linestyle
.dashdotted
)
4068 class changestrokedfilled(changesequence
):
4069 defaultsequence
= (canvas
.stroked(), canvas
.filled())
4072 class changefilledstroked(changesequence
):
4073 defaultsequence
= (canvas
.filled(), canvas
.stroked())
4077 ################################################################################
4079 ################################################################################
4084 def cross(self
, x
, y
):
4085 return (path
._moveto
(x
-0.5*self
._size
, y
-0.5*self
._size
),
4086 path
._lineto
(x
+0.5*self
._size
, y
+0.5*self
._size
),
4087 path
._moveto
(x
-0.5*self
._size
, y
+0.5*self
._size
),
4088 path
._lineto
(x
+0.5*self
._size
, y
-0.5*self
._size
))
4090 def plus(self
, x
, y
):
4091 return (path
._moveto
(x
-0.707106781*self
._size
, y
),
4092 path
._lineto
(x
+0.707106781*self
._size
, y
),
4093 path
._moveto
(x
, y
-0.707106781*self
._size
),
4094 path
._lineto
(x
, y
+0.707106781*self
._size
))
4096 def square(self
, x
, y
):
4097 return (path
._moveto
(x
-0.5*self
._size
, y
-0.5 * self
._size
),
4098 path
._lineto
(x
+0.5*self
._size
, y
-0.5 * self
._size
),
4099 path
._lineto
(x
+0.5*self
._size
, y
+0.5 * self
._size
),
4100 path
._lineto
(x
-0.5*self
._size
, y
+0.5 * self
._size
),
4103 def triangle(self
, x
, y
):
4104 return (path
._moveto
(x
-0.759835685*self
._size
, y
-0.438691337*self
._size
),
4105 path
._lineto
(x
+0.759835685*self
._size
, y
-0.438691337*self
._size
),
4106 path
._lineto
(x
, y
+0.877382675*self
._size
),
4109 def circle(self
, x
, y
):
4110 return (path
._arc
(x
, y
, 0.564189583*self
._size
, 0, 360),
4113 def diamond(self
, x
, y
):
4114 return (path
._moveto
(x
-0.537284965*self
._size
, y
),
4115 path
._lineto
(x
, y
-0.930604859*self
._size
),
4116 path
._lineto
(x
+0.537284965*self
._size
, y
),
4117 path
._lineto
(x
, y
+0.930604859*self
._size
),
4120 def __init__(self
, symbol
=helper
.nodefault
,
4121 size
="0.2 cm", symbolattrs
=canvas
.stroked(),
4122 errorscale
=0.5, errorbarattrs
=(),
4124 self
.size_str
= size
4125 if symbol
is helper
.nodefault
:
4126 self
._symbol
= changesymbol
.cross()
4128 self
._symbol
= symbol
4129 self
._symbolattrs
= symbolattrs
4130 self
.errorscale
= errorscale
4131 self
._errorbarattrs
= errorbarattrs
4132 self
._lineattrs
= lineattrs
4134 def iteratedict(self
):
4136 result
["symbol"] = _iterateattr(self
._symbol
)
4137 result
["size"] = _iterateattr(self
.size_str
)
4138 result
["symbolattrs"] = _iterateattrs(self
._symbolattrs
)
4139 result
["errorscale"] = _iterateattr(self
.errorscale
)
4140 result
["errorbarattrs"] = _iterateattrs(self
._errorbarattrs
)
4141 result
["lineattrs"] = _iterateattrs(self
._lineattrs
)
4145 return symbol(**self
.iteratedict())
4147 def othercolumnkey(self
, key
, index
):
4148 raise ValueError("unsuitable key '%s'" % key
)
4150 def setcolumns(self
, graph
, columns
):
4151 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
4153 match
= pattern
.match(key
)
4155 if isindex
is not None: raise ValueError("multiple key specification")
4156 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
4158 iskey
= match
.groups()[0]
4160 return key
, iskey
, isindex
4162 self
.xi
= self
.xmini
= self
.xmaxi
= None
4163 self
.dxi
= self
.dxmini
= self
.dxmaxi
= None
4164 self
.yi
= self
.ymini
= self
.ymaxi
= None
4165 self
.dyi
= self
.dymini
= self
.dymaxi
= None
4166 self
.xkey
= self
.ykey
= None
4167 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
4168 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
4169 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
4170 XMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
4171 YMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
4172 XMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
4173 YMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
4174 DXPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
4175 DYPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
4176 DXMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
4177 DYMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
4178 DXMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
4179 DYMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
4180 for key
, index
in columns
.items():
4181 key
, self
.xkey
, self
.xi
= checkpattern(key
, index
, XPattern
, self
.xkey
, self
.xi
)
4182 key
, self
.ykey
, self
.yi
= checkpattern(key
, index
, YPattern
, self
.ykey
, self
.yi
)
4183 key
, self
.xkey
, self
.xmini
= checkpattern(key
, index
, XMinPattern
, self
.xkey
, self
.xmini
)
4184 key
, self
.ykey
, self
.ymini
= checkpattern(key
, index
, YMinPattern
, self
.ykey
, self
.ymini
)
4185 key
, self
.xkey
, self
.xmaxi
= checkpattern(key
, index
, XMaxPattern
, self
.xkey
, self
.xmaxi
)
4186 key
, self
.ykey
, self
.ymaxi
= checkpattern(key
, index
, YMaxPattern
, self
.ykey
, self
.ymaxi
)
4187 key
, self
.xkey
, self
.dxi
= checkpattern(key
, index
, DXPattern
, self
.xkey
, self
.dxi
)
4188 key
, self
.ykey
, self
.dyi
= checkpattern(key
, index
, DYPattern
, self
.ykey
, self
.dyi
)
4189 key
, self
.xkey
, self
.dxmini
= checkpattern(key
, index
, DXMinPattern
, self
.xkey
, self
.dxmini
)
4190 key
, self
.ykey
, self
.dymini
= checkpattern(key
, index
, DYMinPattern
, self
.ykey
, self
.dymini
)
4191 key
, self
.xkey
, self
.dxmaxi
= checkpattern(key
, index
, DXMaxPattern
, self
.xkey
, self
.dxmaxi
)
4192 key
, self
.ykey
, self
.dymaxi
= checkpattern(key
, index
, DYMaxPattern
, self
.ykey
, self
.dymaxi
)
4194 self
.othercolumnkey(key
, index
)
4195 if None in (self
.xkey
, self
.ykey
): raise ValueError("incomplete axis specification")
4196 if (len(filter(None, (self
.xmini
, self
.dxmini
, self
.dxi
))) > 1 or
4197 len(filter(None, (self
.ymini
, self
.dymini
, self
.dyi
))) > 1 or
4198 len(filter(None, (self
.xmaxi
, self
.dxmaxi
, self
.dxi
))) > 1 or
4199 len(filter(None, (self
.ymaxi
, self
.dymaxi
, self
.dyi
))) > 1):
4200 raise ValueError("multiple errorbar definition")
4201 if ((self
.xi
is None and self
.dxi
is not None) or
4202 (self
.yi
is None and self
.dyi
is not None) or
4203 (self
.xi
is None and self
.dxmini
is not None) or
4204 (self
.yi
is None and self
.dymini
is not None) or
4205 (self
.xi
is None and self
.dxmaxi
is not None) or
4206 (self
.yi
is None and self
.dymaxi
is not None)):
4207 raise ValueError("errorbar definition start value missing")
4208 self
.xaxis
= graph
.axes
[self
.xkey
]
4209 self
.yaxis
= graph
.axes
[self
.ykey
]
4211 def minmidmax(self
, point
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
4212 min = max = mid
= None
4214 mid
= point
[i
] + 0.0
4215 except (TypeError, ValueError):
4218 if di
is not None: min = point
[i
] - point
[di
]
4219 elif dmini
is not None: min = point
[i
] - point
[dmini
]
4220 elif mini
is not None: min = point
[mini
] + 0.0
4221 except (TypeError, ValueError):
4224 if di
is not None: max = point
[i
] + point
[di
]
4225 elif dmaxi
is not None: max = point
[i
] + point
[dmaxi
]
4226 elif maxi
is not None: max = point
[maxi
] + 0.0
4227 except (TypeError, ValueError):
4230 if min is not None and min > mid
: raise ValueError("minimum error in errorbar")
4231 if max is not None and max < mid
: raise ValueError("maximum error in errorbar")
4233 if min is not None and max is not None and min > max: raise ValueError("minimum/maximum error in errorbar")
4234 return min, mid
, max
4236 def keyrange(self
, points
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
4237 allmin
= allmax
= None
4238 if filter(None, (mini
, maxi
, di
, dmini
, dmaxi
)) is not None:
4239 for point
in points
:
4240 min, mid
, max = self
.minmidmax(point
, i
, mini
, maxi
, di
, dmini
, dmaxi
)
4241 if min is not None and (allmin
is None or min < allmin
): allmin
= min
4242 if mid
is not None and (allmin
is None or mid
< allmin
): allmin
= mid
4243 if mid
is not None and (allmax
is None or mid
> allmax
): allmax
= mid
4244 if max is not None and (allmax
is None or max > allmax
): allmax
= max
4246 for point
in points
:
4248 value
= point
[i
] + 0.0
4249 if allmin
is None or point
[i
] < allmin
: allmin
= point
[i
]
4250 if allmax
is None or point
[i
] > allmax
: allmax
= point
[i
]
4251 except (TypeError, ValueError):
4253 return allmin
, allmax
4255 def getranges(self
, points
):
4256 xmin
, xmax
= self
.keyrange(points
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
4257 ymin
, ymax
= self
.keyrange(points
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
4258 return {self
.xkey
: (xmin
, xmax
), self
.ykey
: (ymin
, ymax
)}
4260 def _drawerrorbar(self
, graph
, topleft
, top
, topright
,
4261 left
, center
, right
,
4262 bottomleft
, bottom
, bottomright
, point
=None):
4263 if left
is not None:
4264 if right
is not None:
4265 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
4266 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
4267 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
4268 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
4269 graph
.stroke(path
.path(path
._moveto
(*left1
),
4270 graph
._connect
(*(left1
+left2
)),
4271 path
._moveto
(*left
),
4272 graph
._connect
(*(left
+right
)),
4273 path
._moveto
(*right1
),
4274 graph
._connect
(*(right1
+right2
))),
4275 *self
.errorbarattrs
)
4276 elif center
is not None:
4277 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
4278 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
4279 graph
.stroke(path
.path(path
._moveto
(*left1
),
4280 graph
._connect
(*(left1
+left2
)),
4281 path
._moveto
(*left
),
4282 graph
._connect
(*(left
+center
))),
4283 *self
.errorbarattrs
)
4285 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
4286 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
4287 left3
= graph
._addpos
(*(left
+(self
._errorsize
, 0)))
4288 graph
.stroke(path
.path(path
._moveto
(*left1
),
4289 graph
._connect
(*(left1
+left2
)),
4290 path
._moveto
(*left
),
4291 graph
._connect
(*(left
+left3
))),
4292 *self
.errorbarattrs
)
4293 if right
is not None and left
is None:
4294 if center
is not None:
4295 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
4296 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
4297 graph
.stroke(path
.path(path
._moveto
(*right1
),
4298 graph
._connect
(*(right1
+right2
)),
4299 path
._moveto
(*right
),
4300 graph
._connect
(*(right
+center
))),
4301 *self
.errorbarattrs
)
4303 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
4304 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
4305 right3
= graph
._addpos
(*(right
+(-self
._errorsize
, 0)))
4306 graph
.stroke(path
.path(path
._moveto
(*right1
),
4307 graph
._connect
(*(right1
+right2
)),
4308 path
._moveto
(*right
),
4309 graph
._connect
(*(right
+right3
))),
4310 *self
.errorbarattrs
)
4312 if bottom
is not None:
4314 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
4315 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
4316 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
4317 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
4318 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
4319 graph
._connect
(*(bottom1
+bottom2
)),
4320 path
._moveto
(*bottom
),
4321 graph
._connect
(*(bottom
+top
)),
4322 path
._moveto
(*top1
),
4323 graph
._connect
(*(top1
+top2
))),
4324 *self
.errorbarattrs
)
4325 elif center
is not None:
4326 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
4327 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
4328 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
4329 graph
._connect
(*(bottom1
+bottom2
)),
4330 path
._moveto
(*bottom
),
4331 graph
._connect
(*(bottom
+center
))),
4332 *self
.errorbarattrs
)
4334 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
4335 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
4336 bottom3
= graph
._addpos
(*(bottom
+(0, self
._errorsize
)))
4337 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
4338 graph
._connect
(*(bottom1
+bottom2
)),
4339 path
._moveto
(*bottom
),
4340 graph
._connect
(*(bottom
+bottom3
))),
4341 *self
.errorbarattrs
)
4342 if top
is not None and bottom
is None:
4343 if center
is not None:
4344 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
4345 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
4346 graph
.stroke(path
.path(path
._moveto
(*top1
),
4347 graph
._connect
(*(top1
+top2
)),
4349 graph
._connect
(*(top
+center
))),
4350 *self
.errorbarattrs
)
4352 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
4353 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
4354 top3
= graph
._addpos
(*(top
+(0, -self
._errorsize
)))
4355 graph
.stroke(path
.path(path
._moveto
(*top1
),
4356 graph
._connect
(*(top1
+top2
)),
4358 graph
._connect
(*(top
+top3
))),
4359 *self
.errorbarattrs
)
4360 if bottomleft
is not None:
4361 if topleft
is not None and bottomright
is None:
4362 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
._errorsize
, 0)))
4363 topleft1
= graph
._addpos
(*(topleft
+(self
._errorsize
, 0)))
4364 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
4365 graph
._connect
(*(bottomleft1
+bottomleft
)),
4366 graph
._connect
(*(bottomleft
+topleft
)),
4367 graph
._connect
(*(topleft
+topleft1
))),
4368 *self
.errorbarattrs
)
4369 elif bottomright
is not None and topleft
is None:
4370 bottomleft1
= graph
._addpos
(*(bottomleft
+(0, self
._errorsize
)))
4371 bottomright1
= graph
._addpos
(*(bottomright
+(0, self
._errorsize
)))
4372 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
4373 graph
._connect
(*(bottomleft1
+bottomleft
)),
4374 graph
._connect
(*(bottomleft
+bottomright
)),
4375 graph
._connect
(*(bottomright
+bottomright1
))),
4376 *self
.errorbarattrs
)
4377 elif bottomright
is None and topleft
is None:
4378 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
._errorsize
, 0)))
4379 bottomleft2
= graph
._addpos
(*(bottomleft
+(0, self
._errorsize
)))
4380 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
4381 graph
._connect
(*(bottomleft1
+bottomleft
)),
4382 graph
._connect
(*(bottomleft
+bottomleft2
))),
4383 *self
.errorbarattrs
)
4384 if topright
is not None:
4385 if bottomright
is not None and topleft
is None:
4386 topright1
= graph
._addpos
(*(topright
+(-self
._errorsize
, 0)))
4387 bottomright1
= graph
._addpos
(*(bottomright
+(-self
._errorsize
, 0)))
4388 graph
.stroke(path
.path(path
._moveto
(*topright1
),
4389 graph
._connect
(*(topright1
+topright
)),
4390 graph
._connect
(*(topright
+bottomright
)),
4391 graph
._connect
(*(bottomright
+bottomright1
))),
4392 *self
.errorbarattrs
)
4393 elif topleft
is not None and bottomright
is None:
4394 topright1
= graph
._addpos
(*(topright
+(0, -self
._errorsize
)))
4395 topleft1
= graph
._addpos
(*(topleft
+(0, -self
._errorsize
)))
4396 graph
.stroke(path
.path(path
._moveto
(*topright1
),
4397 graph
._connect
(*(topright1
+topright
)),
4398 graph
._connect
(*(topright
+topleft
)),
4399 graph
._connect
(*(topleft
+topleft1
))),
4400 *self
.errorbarattrs
)
4401 elif topleft
is None and bottomright
is None:
4402 topright1
= graph
._addpos
(*(topright
+(-self
._errorsize
, 0)))
4403 topright2
= graph
._addpos
(*(topright
+(0, -self
._errorsize
)))
4404 graph
.stroke(path
.path(path
._moveto
(*topright1
),
4405 graph
._connect
(*(topright1
+topright
)),
4406 graph
._connect
(*(topright
+topright2
))),
4407 *self
.errorbarattrs
)
4408 if bottomright
is not None and bottomleft
is None and topright
is None:
4409 bottomright1
= graph
._addpos
(*(bottomright
+(-self
._errorsize
, 0)))
4410 bottomright2
= graph
._addpos
(*(bottomright
+(0, self
._errorsize
)))
4411 graph
.stroke(path
.path(path
._moveto
(*bottomright1
),
4412 graph
._connect
(*(bottomright1
+bottomright
)),
4413 graph
._connect
(*(bottomright
+bottomright2
))),
4414 *self
.errorbarattrs
)
4415 if topleft
is not None and bottomleft
is None and topright
is None:
4416 topleft1
= graph
._addpos
(*(topleft
+(self
._errorsize
, 0)))
4417 topleft2
= graph
._addpos
(*(topleft
+(0, -self
._errorsize
)))
4418 graph
.stroke(path
.path(path
._moveto
(*topleft1
),
4419 graph
._connect
(*(topleft1
+topleft
)),
4420 graph
._connect
(*(topleft
+topleft2
))),
4421 *self
.errorbarattrs
)
4422 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
4423 graph
.stroke(path
.path(path
._moveto
(*bottomleft
),
4424 graph
._connect
(*(bottomleft
+bottomright
)),
4425 graph
._connect
(*(bottomright
+topright
)),
4426 graph
._connect
(*(topright
+topleft
)),
4428 *self
.errorbarattrs
)
4430 def _drawsymbol(self
, canvas
, x
, y
, point
=None):
4431 canvas
.draw(path
.path(*self
.symbol(self
, x
, y
)), *self
.symbolattrs
)
4433 def drawsymbol(self
, canvas
, x
, y
, point
=None):
4434 self
._drawsymbol
(canvas
, unit
.topt(x
), unit
.topt(y
), point
)
4436 def key(self
, c
, x
, y
, width
, height
):
4437 if self
._symbolattrs
is not None:
4438 self
._drawsymbol
(c
, x
+ 0.5 * width
, y
+ 0.5 * height
)
4439 if self
._lineattrs
is not None:
4440 c
.stroke(path
._line
(x
, y
+ 0.5 * height
, x
+ width
, y
+ 0.5 * height
), *self
.lineattrs
)
4442 def drawpoints(self
, graph
, points
):
4443 xaxismin
, xaxismax
= self
.xaxis
.getdatarange()
4444 yaxismin
, yaxismax
= self
.yaxis
.getdatarange()
4445 self
.size
= unit
.length(_getattr(self
.size_str
), default_type
="v")
4446 self
._size
= unit
.topt(self
.size
)
4447 self
.symbol
= _getattr(self
._symbol
)
4448 self
.symbolattrs
= _getattrs(helper
.ensuresequence(self
._symbolattrs
))
4449 self
.errorbarattrs
= _getattrs(helper
.ensuresequence(self
._errorbarattrs
))
4450 self
._errorsize
= self
.errorscale
* self
._size
4451 self
.errorsize
= self
.errorscale
* self
.size
4452 self
.lineattrs
= _getattrs(helper
.ensuresequence(self
._lineattrs
))
4453 if self
._lineattrs
is not None:
4454 clipcanvas
= graph
.clipcanvas()
4456 haserror
= filter(None, (self
.xmini
, self
.ymini
, self
.xmaxi
, self
.ymaxi
,
4457 self
.dxi
, self
.dyi
, self
.dxmini
, self
.dymini
, self
.dxmaxi
, self
.dymaxi
)) is not None
4459 for point
in points
:
4461 xmin
, x
, xmax
= self
.minmidmax(point
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
4462 ymin
, y
, ymax
= self
.minmidmax(point
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
4463 if x
is not None and x
< xaxismin
: drawsymbol
= 0
4464 elif x
is not None and x
> xaxismax
: drawsymbol
= 0
4465 elif y
is not None and y
< yaxismin
: drawsymbol
= 0
4466 elif y
is not None and y
> yaxismax
: drawsymbol
= 0
4468 if xmin
is not None and xmin
< xaxismin
: drawsymbol
= 0
4469 elif xmax
is not None and xmax
< xaxismin
: drawsymbol
= 0
4470 elif xmax
is not None and xmax
> xaxismax
: drawsymbol
= 0
4471 elif xmin
is not None and xmin
> xaxismax
: drawsymbol
= 0
4472 elif ymin
is not None and ymin
< yaxismin
: drawsymbol
= 0
4473 elif ymax
is not None and ymax
< yaxismin
: drawsymbol
= 0
4474 elif ymax
is not None and ymax
> yaxismax
: drawsymbol
= 0
4475 elif ymin
is not None and ymin
> yaxismax
: drawsymbol
= 0
4476 xpos
=ypos
=topleft
=top
=topright
=left
=center
=right
=bottomleft
=bottom
=bottomright
=None
4477 if x
is not None and y
is not None:
4479 center
= xpos
, ypos
= graph
._pos
(x
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4480 except (ValueError, OverflowError): # XXX: exceptions???
4484 if xmin
is not None: left
= graph
._pos
(xmin
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4485 if xmax
is not None: right
= graph
._pos
(xmax
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4487 if ymax
is not None: top
= graph
._pos
(x
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4488 if ymin
is not None: bottom
= graph
._pos
(x
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4489 if x
is None or y
is None:
4490 if ymax
is not None:
4491 if xmin
is not None: topleft
= graph
._pos
(xmin
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4492 if xmax
is not None: topright
= graph
._pos
(xmax
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4493 if ymin
is not None:
4494 if xmin
is not None: bottomleft
= graph
._pos
(xmin
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4495 if xmax
is not None: bottomright
= graph
._pos
(xmax
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4497 if self
._errorbarattrs
is not None and haserror
:
4498 self
._drawerrorbar
(graph
, topleft
, top
, topright
,
4499 left
, center
, right
,
4500 bottomleft
, bottom
, bottomright
, point
)
4501 if self
._symbolattrs
is not None and xpos
is not None and ypos
is not None:
4502 self
._drawsymbol
(graph
, xpos
, ypos
, point
)
4503 if xpos
is not None and ypos
is not None:
4505 lineels
.append(path
._moveto
(xpos
, ypos
))
4508 lineels
.append(path
._lineto
(xpos
, ypos
))
4511 self
.path
= path
.path(*lineels
)
4512 if self
._lineattrs
is not None:
4513 clipcanvas
.stroke(self
.path
, *self
.lineattrs
)
4516 class changesymbol(changesequence
): pass
4519 class _changesymbolcross(changesymbol
):
4520 defaultsequence
= (symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
)
4523 class _changesymbolplus(changesymbol
):
4524 defaultsequence
= (symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
)
4527 class _changesymbolsquare(changesymbol
):
4528 defaultsequence
= (symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
)
4531 class _changesymboltriangle(changesymbol
):
4532 defaultsequence
= (symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
)
4535 class _changesymbolcircle(changesymbol
):
4536 defaultsequence
= (symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
)
4539 class _changesymboldiamond(changesymbol
):
4540 defaultsequence
= (symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
)
4543 class _changesymbolsquaretwice(changesymbol
):
4544 defaultsequence
= (symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
,
4545 symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
)
4548 class _changesymboltriangletwice(changesymbol
):
4549 defaultsequence
= (symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
,
4550 symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
)
4553 class _changesymbolcircletwice(changesymbol
):
4554 defaultsequence
= (symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
,
4555 symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
)
4558 class _changesymboldiamondtwice(changesymbol
):
4559 defaultsequence
= (symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
,
4560 symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
)
4563 changesymbol
.cross
= _changesymbolcross
4564 changesymbol
.plus
= _changesymbolplus
4565 changesymbol
.square
= _changesymbolsquare
4566 changesymbol
.triangle
= _changesymboltriangle
4567 changesymbol
.circle
= _changesymbolcircle
4568 changesymbol
.diamond
= _changesymboldiamond
4569 changesymbol
.squaretwice
= _changesymbolsquaretwice
4570 changesymbol
.triangletwice
= _changesymboltriangletwice
4571 changesymbol
.circletwice
= _changesymbolcircletwice
4572 changesymbol
.diamondtwice
= _changesymboldiamondtwice
4577 def __init__(self
, lineattrs
=helper
.nodefault
):
4578 if lineattrs
is helper
.nodefault
:
4579 lineattrs
= (changelinestyle(), canvas
.linejoin
.round)
4580 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=None, lineattrs
=lineattrs
)
4585 def __init__(self
, palette
=color
.palette
.Gray
):
4586 self
.palette
= palette
4587 self
.colorindex
= None
4588 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=(), lineattrs
=None)
4591 raise RuntimeError("style is not iterateable")
4593 def othercolumnkey(self
, key
, index
):
4595 self
.colorindex
= index
4597 symbol
.othercolumnkey(self
, key
, index
)
4599 def _drawerrorbar(self
, graph
, topleft
, top
, topright
,
4600 left
, center
, right
,
4601 bottomleft
, bottom
, bottomright
, point
=None):
4602 color
= point
[self
.colorindex
]
4603 if color
is not None:
4604 if color
!= self
.lastcolor
:
4605 self
.rectclipcanvas
.set(self
.palette
.getcolor(color
))
4606 if bottom
is not None and left
is not None:
4607 bottomleft
= left
[0], bottom
[1]
4608 if bottom
is not None and right
is not None:
4609 bottomright
= right
[0], bottom
[1]
4610 if top
is not None and right
is not None:
4611 topright
= right
[0], top
[1]
4612 if top
is not None and left
is not None:
4613 topleft
= left
[0], top
[1]
4614 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
4615 self
.rectclipcanvas
.fill(path
.path(path
._moveto
(*bottomleft
),
4616 graph
._connect
(*(bottomleft
+bottomright
)),
4617 graph
._connect
(*(bottomright
+topright
)),
4618 graph
._connect
(*(topright
+topleft
)),
4621 def drawpoints(self
, graph
, points
):
4622 if self
.colorindex
is None:
4623 raise RuntimeError("column 'color' not set")
4624 self
.lastcolor
= None
4625 self
.rectclipcanvas
= graph
.clipcanvas()
4626 symbol
.drawpoints(self
, graph
, points
)
4628 def key(self
, c
, x
, y
, width
, height
):
4629 raise RuntimeError("style doesn't yet provide a key")
4634 def __init__(self
, textdx
="0", textdy
="0.3 cm", textattrs
=textmodule
.halign
.center
, **args
):
4635 self
.textindex
= None
4636 self
.textdx_str
= textdx
4637 self
.textdy_str
= textdy
4638 self
._textattrs
= textattrs
4639 symbol
.__init
__(self
, **args
)
4641 def iteratedict(self
):
4642 result
= symbol
.iteratedict()
4643 result
["textattrs"] = _iterateattr(self
._textattrs
)
4647 return textsymbol(**self
.iteratedict())
4649 def othercolumnkey(self
, key
, index
):
4651 self
.textindex
= index
4653 symbol
.othercolumnkey(self
, key
, index
)
4655 def _drawsymbol(self
, graph
, x
, y
, point
=None):
4656 symbol
._drawsymbol
(self
, graph
, x
, y
, point
)
4657 if None not in (x
, y
, point
[self
.textindex
]) and self
._textattrs
is not None:
4658 graph
._text
(x
+ self
._textdx
, y
+ self
._textdy
, str(point
[self
.textindex
]), *helper
.ensuresequence(self
.textattrs
))
4660 def drawpoints(self
, graph
, points
):
4661 self
.textdx
= unit
.length(_getattr(self
.textdx_str
), default_type
="v")
4662 self
.textdy
= unit
.length(_getattr(self
.textdy_str
), default_type
="v")
4663 self
._textdx
= unit
.topt(self
.textdx
)
4664 self
._textdy
= unit
.topt(self
.textdy
)
4665 if self
._textattrs
is not None:
4666 self
.textattrs
= _getattr(self
._textattrs
)
4667 if self
.textindex
is None:
4668 raise RuntimeError("column 'text' not set")
4669 symbol
.drawpoints(self
, graph
, points
)
4671 def key(self
, c
, x
, y
, width
, height
):
4672 raise RuntimeError("style doesn't yet provide a key")
4675 class arrow(symbol
):
4677 def __init__(self
, linelength
="0.2 cm", arrowattrs
=(), arrowsize
="0.1 cm", arrowdict
={}, epsilon
=1e-10):
4678 self
.linelength_str
= linelength
4679 self
.arrowsize_str
= arrowsize
4680 self
.arrowattrs
= arrowattrs
4681 self
.arrowdict
= arrowdict
4682 self
.epsilon
= epsilon
4683 self
.sizeindex
= self
.angleindex
= None
4684 symbol
.__init
__(self
, symbolattrs
=(), errorbarattrs
=None, lineattrs
=None)
4687 raise RuntimeError("style is not iterateable")
4689 def othercolumnkey(self
, key
, index
):
4691 self
.sizeindex
= index
4692 elif key
== "angle":
4693 self
.angleindex
= index
4695 symbol
.othercolumnkey(self
, key
, index
)
4697 def _drawsymbol(self
, graph
, x
, y
, point
=None):
4698 if None not in (x
, y
, point
[self
.angleindex
], point
[self
.sizeindex
], self
.arrowattrs
, self
.arrowdict
):
4699 if point
[self
.sizeindex
] > self
.epsilon
:
4700 dx
, dy
= math
.cos(point
[self
.angleindex
]*math
.pi
/180.0), math
.sin(point
[self
.angleindex
]*math
.pi
/180)
4701 x1
= unit
.t_pt(x
)-0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
4702 y1
= unit
.t_pt(y
)-0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
4703 x2
= unit
.t_pt(x
)+0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
4704 y2
= unit
.t_pt(y
)+0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
4705 graph
.stroke(path
.line(x1
, y1
, x2
, y2
),
4706 canvas
.earrow(self
.arrowsize
*point
[self
.sizeindex
],
4708 *helper
.ensuresequence(self
.arrowattrs
))
4710 def drawpoints(self
, graph
, points
):
4711 self
.arrowsize
= unit
.length(_getattr(self
.arrowsize_str
), default_type
="v")
4712 self
.linelength
= unit
.length(_getattr(self
.linelength_str
), default_type
="v")
4713 self
._arrowsize
= unit
.topt(self
.arrowsize
)
4714 self
._linelength
= unit
.topt(self
.linelength
)
4715 if self
.sizeindex
is None:
4716 raise RuntimeError("column 'size' not set")
4717 if self
.angleindex
is None:
4718 raise RuntimeError("column 'angle' not set")
4719 symbol
.drawpoints(self
, graph
, points
)
4721 def key(self
, c
, x
, y
, width
, height
):
4722 raise RuntimeError("style doesn't yet provide a key")
4725 class _bariterator(changeattr
):
4727 def attr(self
, index
):
4728 return index
, self
.counter
4733 def __init__(self
, fromzero
=1, stacked
=0, skipmissing
=1, xbar
=0,
4734 barattrs
=helper
.nodefault
, _usebariterator
=helper
.nodefault
, _previousbar
=None):
4735 self
.fromzero
= fromzero
4736 self
.stacked
= stacked
4737 self
.skipmissing
= skipmissing
4739 if barattrs
is helper
.nodefault
:
4740 self
._barattrs
= (canvas
.stroked(color
.gray
.black
), changecolor
.Rainbow())
4742 self
._barattrs
= barattrs
4743 if _usebariterator
is helper
.nodefault
:
4744 self
.bariterator
= _bariterator()
4746 self
.bariterator
= _usebariterator
4747 self
.previousbar
= _previousbar
4749 def iteratedict(self
):
4751 result
["barattrs"] = _iterateattrs(self
._barattrs
)
4755 return bar(fromzero
=self
.fromzero
, stacked
=self
.stacked
, xbar
=self
.xbar
,
4756 _usebariterator
=_iterateattr(self
.bariterator
), _previousbar
=self
, **self
.iteratedict())
4758 def setcolumns(self
, graph
, columns
):
4759 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
4761 match
= pattern
.match(key
)
4763 if isindex
is not None: raise ValueError("multiple key specification")
4764 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
4766 iskey
= match
.groups()[0]
4768 return key
, iskey
, isindex
4771 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
4772 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
4773 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
4775 for key
, index
in columns
.items():
4776 key
, xkey
, xi
= checkpattern(key
, index
, XPattern
, xkey
, xi
)
4777 key
, ykey
, yi
= checkpattern(key
, index
, YPattern
, ykey
, yi
)
4779 self
.othercolumnkey(key
, index
)
4780 if None in (xkey
, ykey
): raise ValueError("incomplete axis specification")
4782 self
.nkey
, self
.ni
= ykey
, yi
4783 self
.vkey
, self
.vi
= xkey
, xi
4785 self
.nkey
, self
.ni
= xkey
, xi
4786 self
.vkey
, self
.vi
= ykey
, yi
4787 self
.naxis
, self
.vaxis
= graph
.axes
[self
.nkey
], graph
.axes
[self
.vkey
]
4789 def getranges(self
, points
):
4790 index
, count
= _getattr(self
.bariterator
)
4791 if count
!= 1 and self
.stacked
!= 1:
4792 if self
.stacked
> 1:
4793 index
= divmod(index
, self
.stacked
)[0]
4796 for point
in points
:
4797 if not self
.skipmissing
:
4798 if count
!= 1 and self
.stacked
!= 1:
4799 self
.naxis
.setname(point
[self
.ni
], index
)
4801 self
.naxis
.setname(point
[self
.ni
])
4803 v
= point
[self
.vi
] + 0.0
4804 if vmin
is None or v
< vmin
: vmin
= v
4805 if vmax
is None or v
> vmax
: vmax
= v
4806 except (TypeError, ValueError):
4809 if self
.skipmissing
:
4810 if count
!= 1 and self
.stacked
!= 1:
4811 self
.naxis
.setname(point
[self
.ni
], index
)
4813 self
.naxis
.setname(point
[self
.ni
])
4815 if vmin
> 0: vmin
= 0
4816 if vmax
< 0: vmax
= 0
4817 return {self
.vkey
: (vmin
, vmax
)}
4819 def drawpoints(self
, graph
, points
):
4820 index
, count
= _getattr(self
.bariterator
)
4821 dostacked
= (self
.stacked
!= 0 and
4822 (self
.stacked
== 1 or divmod(index
, self
.stacked
)[1]) and
4823 (self
.stacked
!= 1 or index
))
4824 if self
.stacked
> 1:
4825 index
= divmod(index
, self
.stacked
)[0]
4826 vmin
, vmax
= self
.vaxis
.getdatarange()
4827 self
.barattrs
= _getattrs(helper
.ensuresequence(self
._barattrs
))
4829 self
.stackedvalue
= {}
4830 for point
in points
:
4835 self
.stackedvalue
[n
] = v
4836 if count
!= 1 and self
.stacked
!= 1:
4837 minid
= (n
, index
, 0)
4838 maxid
= (n
, index
, 1)
4843 x1pos
, y1pos
= graph
._pos
(v
, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4844 x2pos
, y2pos
= graph
._pos
(v
, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4846 x1pos
, y1pos
= graph
._pos
(minid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4847 x2pos
, y2pos
= graph
._pos
(maxid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4850 x3pos
, y3pos
= graph
._pos
(self
.previousbar
.stackedvalue
[n
], maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4851 x4pos
, y4pos
= graph
._pos
(self
.previousbar
.stackedvalue
[n
], minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4853 x3pos
, y3pos
= graph
._pos
(maxid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4854 x4pos
, y4pos
= graph
._pos
(minid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4858 x3pos
, y3pos
= graph
._pos
(0, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4859 x4pos
, y4pos
= graph
._pos
(0, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4861 x3pos
, y3pos
= graph
._pos
(maxid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4862 x4pos
, y4pos
= graph
._pos
(minid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4864 x3pos
, y3pos
= graph
._tickpoint
(maxid
, axis
=self
.naxis
)
4865 x4pos
, y4pos
= graph
._tickpoint
(minid
, axis
=self
.naxis
)
4866 if self
.barattrs
is not None:
4867 graph
.fill(path
.path(path
._moveto
(x1pos
, y1pos
),
4868 graph
._connect
(x1pos
, y1pos
, x2pos
, y2pos
),
4869 graph
._connect
(x2pos
, y2pos
, x3pos
, y3pos
),
4870 graph
._connect
(x3pos
, y3pos
, x4pos
, y4pos
),
4871 graph
._connect
(x4pos
, y4pos
, x1pos
, y1pos
), # no closepath (might not be straight)
4872 path
.closepath()), *self
.barattrs
)
4873 except (TypeError, ValueError): pass
4875 def key(self
, c
, x
, y
, width
, height
):
4876 c
.fill(path
._rect
(x
, y
, width
, height
), *self
.barattrs
)
4881 # def setcolumns(self, graph, columns):
4882 # self.columns = columns
4884 # def getranges(self, points):
4885 # return {"x": (0, 10), "y": (0, 10), "z": (0, 1)}
4887 # def drawpoints(self, graph, points):
4892 ################################################################################
4894 ################################################################################
4899 defaultstyle
= symbol
4901 def __init__(self
, file, title
=helper
.nodefault
, context
={}, **columns
):
4903 if helper
.isstring(file):
4904 self
.data
= datamodule
.datafile(file)
4907 if title
is helper
.nodefault
:
4908 self
.title
= "(unknown)"
4912 for key
, column
in columns
.items():
4914 self
.columns
[key
] = self
.data
.getcolumnno(column
)
4915 except datamodule
.ColumnError
:
4916 self
.columns
[key
] = len(self
.data
.titles
)
4917 self
.data
.addcolumn(column
, context
=context
)
4919 def setstyle(self
, graph
, style
):
4921 self
.style
.setcolumns(graph
, self
.columns
)
4923 def getranges(self
):
4924 return self
.style
.getranges(self
.data
.data
)
4926 def setranges(self
, ranges
):
4929 def draw(self
, graph
):
4930 self
.style
.drawpoints(graph
, self
.data
.data
)
4937 def __init__(self
, expression
, title
=helper
.nodefault
, min=None, max=None, points
=100, parser
=mathtree
.parser(), context
={}):
4938 if title
is helper
.nodefault
:
4939 self
.title
= expression
4944 self
.points
= points
4945 self
.context
= context
4946 self
.result
, expression
= expression
.split("=")
4947 self
.mathtree
= parser
.parse(expression
)
4948 self
.variable
= None
4951 def setstyle(self
, graph
, style
):
4952 for variable
in self
.mathtree
.VarList():
4953 if variable
in graph
.axes
.keys():
4954 if self
.variable
is None:
4955 self
.variable
= variable
4957 raise ValueError("multiple variables found")
4958 if self
.variable
is None:
4959 raise ValueError("no variable found")
4960 self
.xaxis
= graph
.axes
[self
.variable
]
4962 self
.style
.setcolumns(graph
, {self
.variable
: 0, self
.result
: 1})
4964 def getranges(self
):
4966 return self
.style
.getranges(self
.data
)
4967 if None not in (self
.min, self
.max):
4968 return {self
.variable
: (self
.min, self
.max)}
4970 def setranges(self
, ranges
):
4971 if ranges
.has_key(self
.variable
):
4972 min, max = ranges
[self
.variable
]
4973 if self
.min is not None: min = self
.min
4974 if self
.max is not None: max = self
.max
4975 vmin
= self
.xaxis
.convert(min)
4976 vmax
= self
.xaxis
.convert(max)
4978 for i
in range(self
.points
):
4979 self
.context
[self
.variable
] = x
= self
.xaxis
.invert(vmin
+ (vmax
-vmin
)*i
/ (self
.points
-1.0))
4981 y
= self
.mathtree
.Calc(**self
.context
)
4982 except (ArithmeticError, ValueError):
4984 self
.data
.append((x
, y
))
4987 def draw(self
, graph
):
4988 self
.style
.drawpoints(graph
, self
.data
)
4991 class paramfunction
:
4995 def __init__(self
, varname
, min, max, expression
, title
=helper
.nodefault
, points
=100, parser
=mathtree
.parser(), context
={}):
4996 if title
is helper
.nodefault
:
4997 self
.title
= expression
5000 self
.varname
= varname
5003 self
.points
= points
5004 self
.expression
= {}
5006 varlist
, expressionlist
= expression
.split("=")
5007 parsestr
= mathtree
.ParseStr(expressionlist
)
5008 for key
in varlist
.split(","):
5010 if self
.mathtrees
.has_key(key
):
5011 raise ValueError("multiple assignment in tuple")
5013 self
.mathtrees
[key
] = parser
.ParseMathTree(parsestr
)
5015 except mathtree
.CommaFoundMathTreeParseError
, e
:
5016 self
.mathtrees
[key
] = e
.MathTree
5018 raise ValueError("unpack tuple of wrong size")
5019 if len(varlist
.split(",")) != len(self
.mathtrees
.keys()):
5020 raise ValueError("unpack tuple of wrong size")
5022 for i
in range(self
.points
):
5023 context
[self
.varname
] = self
.min + (self
.max-self
.min)*i
/ (self
.points
-1.0)
5025 for key
, tree
in self
.mathtrees
.items():
5026 line
.append(tree
.Calc(**context
))
5027 self
.data
.append(line
)
5029 def setstyle(self
, graph
, style
):
5032 for key
, index
in zip(self
.mathtrees
.keys(), xrange(sys
.maxint
)):
5033 columns
[key
] = index
5034 self
.style
.setcolumns(graph
, columns
)
5036 def getranges(self
):
5037 return self
.style
.getranges(self
.data
)
5039 def setranges(self
, ranges
):
5042 def draw(self
, graph
):
5043 self
.style
.drawpoints(graph
, self
.data
)