2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2004 André Wobst <wobsta@users.sourceforge.net>
9 # This file is part of PyX (http://pyx.sourceforge.net/).
11 # PyX is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # PyX is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with PyX; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 import re
, math
, string
, sys
27 import attr
, bbox
, box
, canvas
, color
, deco
, helper
, path
, style
, unit
, mathtree
28 import text
as textmodule
29 import data
as datamodule
30 import trafo
as trafomodule
33 goldenmean
= 0.5 * (math
.sqrt(5) + 1)
36 ################################################################################
38 ################################################################################
41 """interface definition of a map
42 maps convert a value into another value by bijective transformation f"""
48 "returns f^-1(y) where f^-1 is the inverse transformation (x=f^-1(f(x)) for all x)"
50 def setbasepoints(self
, basepoints
):
51 """set basepoints for the convertions
52 basepoints are tuples (x, y) with y == f(x) and x == f^-1(y)
53 the number of basepoints needed might depend on the transformation
54 usually two pairs are needed like for linear maps, logarithmic maps, etc."""
60 __implements__
= _Imap
62 def setbasepoints(self
, basepoints
):
63 self
.dydx
= (basepoints
[1][1] - basepoints
[0][1]) / float(basepoints
[1][0] - basepoints
[0][0])
64 self
.dxdy
= (basepoints
[1][0] - basepoints
[0][0]) / float(basepoints
[1][1] - basepoints
[0][1])
65 self
.x1
= basepoints
[0][0]
66 self
.y1
= basepoints
[0][1]
68 def convert(self
, value
):
69 return self
.y1
+ self
.dydx
* (value
- self
.x1
)
71 def invert(self
, value
):
72 return self
.x1
+ self
.dxdy
* (value
- self
.y1
)
77 __implements__
= _Imap
79 def setbasepoints(self
, basepoints
):
80 self
.dydx
= ((basepoints
[1][1] - basepoints
[0][1]) /
81 float(math
.log(basepoints
[1][0]) - math
.log(basepoints
[0][0])))
82 self
.dxdy
= ((math
.log(basepoints
[1][0]) - math
.log(basepoints
[0][0])) /
83 float(basepoints
[1][1] - basepoints
[0][1]))
84 self
.x1
= math
.log(basepoints
[0][0])
85 self
.y1
= basepoints
[0][1]
88 def convert(self
, value
):
89 return self
.y1
+ self
.dydx
* (math
.log(value
) - self
.x1
)
91 def invert(self
, value
):
92 return math
.exp(self
.x1
+ self
.dxdy
* (value
- self
.y1
))
96 ################################################################################
98 # please note the nomenclature:
99 # - a partition is a list of tick instances; to reduce name clashes, a
100 # partition is called ticks
101 # - a partitioner is a class creating a single or several ticks
102 # - an axis has a part attribute where it stores a partitioner or/and some
103 # (manually set) ticks -> the part attribute is used to create the ticks
104 # in the axis finish method
105 ################################################################################
109 """fraction class for rational arithmetics
110 the axis partitioning uses rational arithmetics (with infinite accuracy)
111 basically it contains self.enum and self.denom"""
113 def stringfrac(self
, s
):
114 "converts a string 0.123 into a frac"
115 expparts
= s
.split("e")
116 if len(expparts
) > 2:
117 raise ValueError("multiple 'e' found in '%s'" % s
)
118 commaparts
= expparts
[0].split(".")
119 if len(commaparts
) > 2:
120 raise ValueError("multiple '.' found in '%s'" % expparts
[0])
121 if len(commaparts
) == 1:
122 commaparts
= [commaparts
[0], ""]
123 result
= frac((1, 10l), power
=len(commaparts
[1]))
124 neg
= len(commaparts
[0]) and commaparts
[0][0] == "-"
126 commaparts
[0] = commaparts
[0][1:]
127 elif len(commaparts
[0]) and commaparts
[0][0] == "+":
128 commaparts
[0] = commaparts
[0][1:]
129 if len(commaparts
[0]):
130 if not commaparts
[0].isdigit():
131 raise ValueError("unrecognized characters in '%s'" % s
)
132 x
= long(commaparts
[0])
135 if len(commaparts
[1]):
136 if not commaparts
[1].isdigit():
137 raise ValueError("unrecognized characters in '%s'" % s
)
138 y
= long(commaparts
[1])
141 result
.enum
= x
*result
.denom
+y
143 result
.enum
= -result
.enum
144 if len(expparts
) == 2:
145 neg
= expparts
[1][0] == "-"
147 expparts
[1] = expparts
[1][1:]
148 elif expparts
[1][0] == "+":
149 expparts
[1] = expparts
[1][1:]
150 if not expparts
[1].isdigit():
151 raise ValueError("unrecognized characters in '%s'" % s
)
153 result
*= frac((1, 10l), power
=long(expparts
[1]))
155 result
*= frac((10, 1l), power
=long(expparts
[1]))
158 def floatfrac(self
, x
, floatprecision
):
159 "converts a float into a frac with finite resolution"
160 if helper
.isinteger(floatprecision
) and floatprecision
< 0:
161 # this would be extremly vulnerable
162 raise RuntimeError("float resolution must be non-negative integer")
163 return self
.stringfrac(("%%.%ig" % floatprecision
) % x
)
165 def __init__(self
, x
, power
=None, floatprecision
=10):
166 "for power!=None: frac=(enum/denom)**power"
167 if helper
.isnumber(x
):
168 value
= self
.floatfrac(x
, floatprecision
)
169 enum
, denom
= value
.enum
, value
.denom
170 elif helper
.isstring(x
):
171 fraction
= x
.split("/")
172 if len(fraction
) > 2:
173 raise ValueError("multiple '/' found in '%s'" % x
)
174 value
= self
.stringfrac(fraction
[0])
175 if len(fraction
) == 2:
176 value2
= self
.stringfrac(fraction
[1])
177 value
= value
/ value2
178 enum
, denom
= value
.enum
, value
.denom
182 except (TypeError, AttributeError):
183 enum
, denom
= x
.enum
, x
.denom
184 if not helper
.isinteger(enum
) or not helper
.isinteger(denom
): raise TypeError("integer type expected")
185 if not denom
: raise ZeroDivisionError("zero denominator")
187 if not helper
.isinteger(power
): raise TypeError("integer type expected")
189 self
.enum
= long(enum
) ** power
190 self
.denom
= long(denom
) ** power
192 self
.enum
= long(denom
) ** (-power
)
193 self
.denom
= long(enum
) ** (-power
)
198 def __cmp__(self
, other
):
201 return cmp(self
.enum
* other
.denom
, other
.enum
* self
.denom
)
204 return frac((abs(self
.enum
), abs(self
.denom
)))
206 def __mul__(self
, other
):
207 return frac((self
.enum
* other
.enum
, self
.denom
* other
.denom
))
209 def __div__(self
, other
):
210 return frac((self
.enum
* other
.denom
, self
.denom
* other
.enum
))
213 "caution: avoid final precision of floats"
214 return float(self
.enum
) / self
.denom
217 return "%i/%i" % (self
.enum
, self
.denom
)
222 a tick is a frac enhanced by
223 - self.ticklevel (0 = tick, 1 = subtick, etc.)
224 - self.labellevel (0 = label, 1 = sublabel, etc.)
225 - self.label (a string) and self.labelattrs (a list, defaults to [])
226 When ticklevel or labellevel is None, no tick or label is present at that value.
227 When label is None, it should be automatically created (and stored), once the
228 an axis painter needs it. Classes, which implement _Itexter do precisely that."""
230 def __init__(self
, pos
, ticklevel
=0, labellevel
=0, label
=None, labelattrs
=[], **kwargs
):
231 """initializes the instance
232 - see class description for the parameter description
233 - **kwargs are passed to the frac constructor"""
234 frac
.__init
__(self
, pos
, **kwargs
)
235 self
.ticklevel
= ticklevel
236 self
.labellevel
= labellevel
238 self
.labelattrs
= labelattrs
240 def merge(self
, other
):
241 """merges two ticks together:
242 - the lower ticklevel/labellevel wins
243 - the label is *never* taken over from other
244 - the ticks should be at the same position (otherwise it doesn't make sense)
245 -> this is NOT checked"""
246 if self
.ticklevel
is None or (other
.ticklevel
is not None and other
.ticklevel
< self
.ticklevel
):
247 self
.ticklevel
= other
.ticklevel
248 if self
.labellevel
is None or (other
.labellevel
is not None and other
.labellevel
< self
.labellevel
):
249 self
.labellevel
= other
.labellevel
252 def _mergeticklists(list1
, list2
):
253 """helper function to merge tick lists
254 - return a merged list of ticks out of list1 and list2
255 - CAUTION: original lists have to be ordered
256 (the returned list is also ordered)"""
257 # TODO: improve this using bisect?!
259 # XXX do not the original lists
264 while 1: # we keep on going until we reach an index error
265 while list2
[j
] < list1
[i
]: # insert tick
266 list1
.insert(i
, list2
[j
])
269 if list2
[j
] == list1
[i
]: # merge tick
270 list1
[i
].merge(list2
[j
])
279 def _mergelabels(ticks
, labels
):
280 """helper function to merge labels into ticks
281 - when labels is not None, the label of all ticks with
282 labellevel different from None are set
283 - labels need to be a list of lists of strings,
284 where the first list contain the strings to be
285 used as labels for the ticks with labellevel 0,
286 the second list for labellevel 1, etc.
287 - when the maximum labellevel is 0, just a list of
288 strings might be provided as the labels argument
289 - IndexError is raised, when a list length doesn't match"""
290 if helper
.issequenceofsequences(labels
):
291 for label
, level
in zip(labels
, xrange(sys
.maxint
)):
292 usetext
= helper
.ensuresequence(label
)
295 if tick
.labellevel
== level
:
296 tick
.label
= usetext
[i
]
298 if i
!= len(usetext
):
299 raise IndexError("wrong list length of labels at level %i" % level
)
300 elif labels
is not None:
301 usetext
= helper
.ensuresequence(labels
)
304 if tick
.labellevel
== 0:
305 tick
.label
= usetext
[i
]
307 if i
!= len(usetext
):
308 raise IndexError("wrong list length of labels")
310 def _maxlevels(ticks
):
311 "returns a tuple maxticklist, maxlabellevel from a list of tick instances"
312 maxticklevel
= maxlabellevel
= 0
314 if tick
.ticklevel
is not None and tick
.ticklevel
>= maxticklevel
:
315 maxticklevel
= tick
.ticklevel
+ 1
316 if tick
.labellevel
is not None and tick
.labellevel
>= maxlabellevel
:
317 maxlabellevel
= tick
.labellevel
+ 1
318 return maxticklevel
, maxlabellevel
322 """interface definition of a partition scheme
323 partition schemes are used to create a list of ticks"""
325 def defaultpart(self
, min, max, extendmin
, extendmax
):
326 """create a partition
327 - returns an ordered list of ticks for the interval min to max
328 - the interval is given in float numbers, thus an appropriate
329 conversion to rational numbers has to be performed
330 - extendmin and extendmax are booleans (integers)
331 - when extendmin or extendmax is set, the ticks might
332 extend the min-max range towards lower and higher
333 ranges, respectively"""
336 """create another partition which contains less ticks
337 - this method is called several times after a call of defaultpart
338 - returns an ordered list of ticks with less ticks compared to
339 the partition returned by defaultpart and by previous calls
341 - the creation of a partition with strictly *less* ticks
342 is not to be taken serious
343 - the method might return None, when no other appropriate
344 partition can be created"""
348 """create another partition which contains more ticks
349 see lesspart, but increase the number of ticks"""
353 """linear partition scheme
354 ticks and label distances are explicitly provided to the constructor"""
356 __implements__
= _Iparter
358 def __init__(self
, tickdist
=None, labeldist
=None, labels
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
359 """configuration of the partition scheme
360 - tickdist and labeldist should be a list, where the first value
361 is the distance between ticks with ticklevel/labellevel 0,
362 the second list for ticklevel/labellevel 1, etc.;
363 a single entry is allowed without being a list
364 - tickdist and labeldist values are passed to the frac constructor
365 - when labeldist is None and tickdist is not None, the tick entries
366 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
367 - labels are applied to the resulting partition via the
368 mergelabels function (additional information available there)
369 - extendtick allows for the extension of the range given to the
370 defaultpart method to include the next tick with the specified
371 level (None turns off this feature); note, that this feature is
372 also disabled, when an axis prohibits its range extension by
373 the extendmin/extendmax variables given to the defaultpart method
374 - extendlabel is analogous to extendtick, but for labels
375 - epsilon allows for exceeding the axis range by this relative
376 value (relative to the axis range given to the defaultpart method)
377 without creating another tick specified by extendtick/extendlabel"""
378 if tickdist
is None and labeldist
is not None:
379 self
.ticklist
= (frac(helper
.ensuresequence(labeldist
)[0]),)
381 self
.ticklist
= map(frac
, helper
.ensuresequence(tickdist
))
382 if labeldist
is None and tickdist
is not None:
383 self
.labellist
= (frac(helper
.ensuresequence(tickdist
)[0]),)
385 self
.labellist
= map(frac
, helper
.ensuresequence(labeldist
))
387 self
.extendtick
= extendtick
388 self
.extendlabel
= extendlabel
389 self
.epsilon
= epsilon
391 def extendminmax(self
, min, max, frac
, extendmin
, extendmax
):
392 """return new min, max tuple extending the range min, max
393 - frac is the tick distance to be used
394 - extendmin and extendmax are booleans to allow for the extension"""
396 min = float(frac
) * math
.floor(min / float(frac
) + self
.epsilon
)
398 max = float(frac
) * math
.ceil(max / float(frac
) - self
.epsilon
)
401 def getticks(self
, min, max, frac
, ticklevel
=None, labellevel
=None):
402 """return a list of equal spaced ticks
403 - the tick distance is frac, the ticklevel is set to ticklevel and
404 the labellevel is set to labellevel
405 - min, max is the range where ticks should be placed"""
406 imin
= int(math
.ceil(min / float(frac
) - 0.5 * self
.epsilon
))
407 imax
= int(math
.floor(max / float(frac
) + 0.5 * self
.epsilon
))
409 for i
in range(imin
, imax
+ 1):
410 ticks
.append(tick((long(i
) * frac
.enum
, frac
.denom
), ticklevel
=ticklevel
, labellevel
=labellevel
))
413 def defaultpart(self
, min, max, extendmin
, extendmax
):
414 if self
.extendtick
is not None and len(self
.ticklist
) > self
.extendtick
:
415 min, max = self
.extendminmax(min, max, self
.ticklist
[self
.extendtick
], extendmin
, extendmax
)
416 if self
.extendlabel
is not None and len(self
.labellist
) > self
.extendlabel
:
417 min, max = self
.extendminmax(min, max, self
.labellist
[self
.extendlabel
], extendmin
, extendmax
)
420 for i
in range(len(self
.ticklist
)):
421 ticks
= _mergeticklists(ticks
, self
.getticks(min, max, self
.ticklist
[i
], ticklevel
= i
))
422 for i
in range(len(self
.labellist
)):
423 ticks
= _mergeticklists(ticks
, self
.getticks(min, max, self
.labellist
[i
], labellevel
= i
))
425 _mergelabels(ticks
, self
.labels
)
437 """automatic linear partition scheme
438 - possible tick distances are explicitly provided to the constructor
439 - tick distances are adjusted to the axis range by multiplication or division by 10"""
441 __implements__
= _Iparter
443 defaultvariants
= ((frac((1, 1)), frac((1, 2))),
444 (frac((2, 1)), frac((1, 1))),
445 (frac((5, 2)), frac((5, 4))),
446 (frac((5, 1)), frac((5, 2))))
448 def __init__(self
, variants
=defaultvariants
, extendtick
=0, epsilon
=1e-10):
449 """configuration of the partition scheme
450 - variants is a list of tickdist
451 - tickdist should be a list, where the first value
452 is the distance between ticks with ticklevel 0,
453 the second for ticklevel 1, etc.
454 - tickdist values are passed to the frac constructor
455 - labellevel is set to None except for those ticks in the partitions,
456 where ticklevel is zero. There labellevel is also set to zero.
457 - extendtick allows for the extension of the range given to the
458 defaultpart method to include the next tick with the specified
459 level (None turns off this feature); note, that this feature is
460 also disabled, when an axis prohibits its range extension by
461 the extendmin/extendmax variables given to the defaultpart method
462 - epsilon allows for exceeding the axis range by this relative
463 value (relative to the axis range given to the defaultpart method)
464 without creating another tick specified by extendtick"""
465 self
.variants
= variants
466 self
.extendtick
= extendtick
467 self
.epsilon
= epsilon
469 def defaultpart(self
, min, max, extendmin
, extendmax
):
470 logmm
= math
.log(max - min) / math
.log(10)
471 if logmm
< 0: # correction for rounding towards zero of the int routine
472 base
= frac((10L, 1), int(logmm
- 1))
474 base
= frac((10L, 1), int(logmm
))
475 ticks
= map(frac
, self
.variants
[0])
476 useticks
= [tick
* base
for tick
in ticks
]
477 self
.lesstickindex
= self
.moretickindex
= 0
478 self
.lessbase
= frac((base
.enum
, base
.denom
))
479 self
.morebase
= frac((base
.enum
, base
.denom
))
480 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
481 part
= linparter(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
482 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
485 if self
.lesstickindex
< len(self
.variants
) - 1:
486 self
.lesstickindex
+= 1
488 self
.lesstickindex
= 0
489 self
.lessbase
.enum
*= 10
490 ticks
= map(frac
, self
.variants
[self
.lesstickindex
])
491 useticks
= [tick
* self
.lessbase
for tick
in ticks
]
492 part
= linparter(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
493 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
496 if self
.moretickindex
:
497 self
.moretickindex
-= 1
499 self
.moretickindex
= len(self
.variants
) - 1
500 self
.morebase
.denom
*= 10
501 ticks
= map(frac
, self
.variants
[self
.moretickindex
])
502 useticks
= [tick
* self
.morebase
for tick
in ticks
]
503 part
= linparter(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
504 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
508 """storage class for the definition of logarithmic axes partitions
509 instances of this class define tick positions suitable for
510 logarithmic axes by the following instance variables:
511 - exp: integer, which defines multiplicator (usually 10)
512 - pres: list of tick positions (rational numbers, e.g. instances of frac)
513 possible positions are these tick positions and arbitrary divisions
514 and multiplications by the exp value"""
516 def __init__(self
, pres
, exp
):
517 "create a preexp instance and store its pres and exp information"
518 self
.pres
= helper
.ensuresequence(pres
)
522 class logparter(linparter
):
523 """logarithmic partition scheme
524 ticks and label positions are explicitly provided to the constructor"""
526 __implements__
= _Iparter
528 pre1exp5
= preexp(frac((1, 1)), 100000)
529 pre1exp4
= preexp(frac((1, 1)), 10000)
530 pre1exp3
= preexp(frac((1, 1)), 1000)
531 pre1exp2
= preexp(frac((1, 1)), 100)
532 pre1exp
= preexp(frac((1, 1)), 10)
533 pre125exp
= preexp((frac((1, 1)), frac((2, 1)), frac((5, 1))), 10)
534 pre1to9exp
= preexp(map(lambda x
: frac((x
, 1)), range(1, 10)), 10)
535 # ^- we always include 1 in order to get extendto(tick|label)level to work as expected
537 def __init__(self
, tickpos
=None, labelpos
=None, labels
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
538 """configuration of the partition scheme
539 - tickpos and labelpos should be a list, where the first entry
540 is a preexp instance describing ticks with ticklevel/labellevel 0,
541 the second is a preexp instance for ticklevel/labellevel 1, etc.;
542 a single entry is allowed without being a list
543 - when labelpos is None and tickpos is not None, the tick entries
544 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
545 - labels are applied to the resulting partition via the
546 mergetexts function (additional information available there)
547 - extendtick allows for the extension of the range given to the
548 defaultpart method to include the next tick with the specified
549 level (None turns off this feature); note, that this feature is
550 also disabled, when an axis prohibits its range extension by
551 the extendmin/extendmax variables given to the defaultpart method
552 - extendlabel is analogous to extendtick, but for labels
553 - epsilon allows for exceeding the axis range by this relative
554 logarithm value (relative to the logarithm axis range given
555 to the defaultpart method) without creating another tick
556 specified by extendtick/extendlabel"""
557 if tickpos
is None and labels
is not None:
558 self
.ticklist
= (helper
.ensuresequence(labelpos
)[0],)
560 self
.ticklist
= helper
.ensuresequence(tickpos
)
562 if labelpos
is None and tickpos
is not None:
563 self
.labellist
= (helper
.ensuresequence(tickpos
)[0],)
565 self
.labellist
= helper
.ensuresequence(labelpos
)
567 self
.extendtick
= extendtick
568 self
.extendlabel
= extendlabel
569 self
.epsilon
= epsilon
571 def extendminmax(self
, min, max, preexp
, extendmin
, extendmax
):
572 """return new min, max tuple extending the range min, max
573 preexp describes the allowed tick positions
574 extendmin and extendmax are booleans to allow for the extension"""
577 for i
in xrange(len(preexp
.pres
)):
578 imin
= int(math
.floor(math
.log(min / float(preexp
.pres
[i
])) /
579 math
.log(preexp
.exp
) + self
.epsilon
)) + 1
580 imax
= int(math
.ceil(math
.log(max / float(preexp
.pres
[i
])) /
581 math
.log(preexp
.exp
) - self
.epsilon
)) - 1
582 if minpower
is None or imin
< minpower
:
583 minpower
, minindex
= imin
, i
584 if maxpower
is None or imax
>= maxpower
:
585 maxpower
, maxindex
= imax
, i
587 minfrac
= preexp
.pres
[minindex
- 1]
589 minfrac
= preexp
.pres
[-1]
591 if maxindex
!= len(preexp
.pres
) - 1:
592 maxfrac
= preexp
.pres
[maxindex
+ 1]
594 maxfrac
= preexp
.pres
[0]
597 min = float(minfrac
) * float(preexp
.exp
) ** minpower
599 max = float(maxfrac
) * float(preexp
.exp
) ** maxpower
602 def getticks(self
, min, max, preexp
, ticklevel
=None, labellevel
=None):
603 """return a list of ticks
604 - preexp describes the allowed tick positions
605 - the ticklevel of the ticks is set to ticklevel and
606 the labellevel is set to labellevel
607 - min, max is the range where ticks should be placed"""
611 for f
in preexp
.pres
:
613 imin
= int(math
.ceil(math
.log(min / float(f
)) /
614 math
.log(preexp
.exp
) - 0.5 * self
.epsilon
))
615 imax
= int(math
.floor(math
.log(max / float(f
)) /
616 math
.log(preexp
.exp
) + 0.5 * self
.epsilon
))
617 for i
in range(imin
, imax
+ 1):
618 pos
= f
* frac((preexp
.exp
, 1), i
)
619 fracticks
.append(tick((pos
.enum
, pos
.denom
), ticklevel
= ticklevel
, labellevel
= labellevel
))
620 ticks
= _mergeticklists(ticks
, fracticks
)
624 class autologparter(logparter
):
625 """automatic logarithmic partition scheme
626 possible tick positions are explicitly provided to the constructor"""
628 __implements__
= _Iparter
630 defaultvariants
= (((logparter
.pre1exp
, # ticks
631 logparter
.pre1to9exp
), # subticks
632 (logparter
.pre1exp
, # labels
633 logparter
.pre125exp
)), # sublevels
635 ((logparter
.pre1exp
, # ticks
636 logparter
.pre1to9exp
), # subticks
637 None), # labels like ticks
639 ((logparter
.pre1exp2
, # ticks
640 logparter
.pre1exp
), # subticks
641 None), # labels like ticks
643 ((logparter
.pre1exp3
, # ticks
644 logparter
.pre1exp
), # subticks
645 None), # labels like ticks
647 ((logparter
.pre1exp4
, # ticks
648 logparter
.pre1exp
), # subticks
649 None), # labels like ticks
651 ((logparter
.pre1exp5
, # ticks
652 logparter
.pre1exp
), # subticks
653 None)) # labels like ticks
655 def __init__(self
, variants
=defaultvariants
, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
656 """configuration of the partition scheme
657 - variants should be a list of pairs of lists of preexp
659 - within each pair the first list contains preexp, where
660 the first preexp instance describes ticks positions with
661 ticklevel 0, the second preexp for ticklevel 1, etc.
662 - the second list within each pair describes the same as
663 before, but for labels
664 - within each pair: when the second entry (for the labels) is None
665 and the first entry (for the ticks) ticks is not None, the tick
666 entries for ticklevel 0 are used for labels and vice versa
668 - extendtick allows for the extension of the range given to the
669 defaultpart method to include the next tick with the specified
670 level (None turns off this feature); note, that this feature is
671 also disabled, when an axis prohibits its range extension by
672 the extendmin/extendmax variables given to the defaultpart method
673 - extendlabel is analogous to extendtick, but for labels
674 - epsilon allows for exceeding the axis range by this relative
675 logarithm value (relative to the logarithm axis range given
676 to the defaultpart method) without creating another tick
677 specified by extendtick/extendlabel"""
678 self
.variants
= variants
679 if len(variants
) > 2:
680 self
.variantsindex
= divmod(len(variants
), 2)[0]
682 self
.variantsindex
= 0
683 self
.extendtick
= extendtick
684 self
.extendlabel
= extendlabel
685 self
.epsilon
= epsilon
687 def defaultpart(self
, min, max, extendmin
, extendmax
):
688 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
689 self
.morevariantsindex
= self
.variantsindex
690 self
.lessvariantsindex
= self
.variantsindex
691 part
= logparter(tickpos
=self
.variants
[self
.variantsindex
][0], labelpos
=self
.variants
[self
.variantsindex
][1],
692 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
693 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
696 self
.lessvariantsindex
+= 1
697 if self
.lessvariantsindex
< len(self
.variants
):
698 part
= logparter(tickpos
=self
.variants
[self
.lessvariantsindex
][0], labelpos
=self
.variants
[self
.lessvariantsindex
][1],
699 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
700 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
703 self
.morevariantsindex
-= 1
704 if self
.morevariantsindex
>= 0:
705 part
= logparter(tickpos
=self
.variants
[self
.morevariantsindex
][0], labelpos
=self
.variants
[self
.morevariantsindex
][1],
706 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
707 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
711 ################################################################################
713 # conseptional remarks:
714 # - raters are used to calculate a rating for a realization of something
715 # - here, a rating means a positive floating point value
716 # - ratings are used to order those realizations by their suitability (lower
717 # ratings are better)
718 # - a rating of None means not suitable at all (those realizations should be
720 ################################################################################
725 - a cube rater has an optimal value, where the rate becomes zero
726 - for a left (below the optimum) and a right value (above the optimum),
727 the rating is value is set to 1 (modified by an overall weight factor
729 - the analytic form of the rating is cubic for both, the left and
730 the right side of the rater, independently"""
732 # __implements__ = sole implementation
734 def __init__(self
, opt
, left
=None, right
=None, weight
=1):
735 """initializes the rater
736 - by default, left is set to zero, right is set to 3*opt
737 - left should be smaller than opt, right should be bigger than opt
738 - weight should be positive and is a factor multiplicated to the rates"""
748 def rate(self
, value
, density
):
749 """returns a rating for a value
750 - the density lineary rescales the rater (the optimum etc.),
751 e.g. a value bigger than one increases the optimum (when it is
752 positive) and a value lower than one decreases the optimum (when
753 it is positive); the density itself should be positive"""
754 opt
= self
.opt
* density
756 other
= self
.left
* density
758 other
= self
.right
* density
761 factor
= (value
- opt
) / float(other
- opt
)
762 return self
.weight
* (factor
** 3)
766 # TODO: update docstring
767 """a distance rater (rates a list of distances)
768 - the distance rater rates a list of distances by rating each independently
769 and returning the average rate
770 - there is an optimal value, where the rate becomes zero
771 - the analytic form is linary for values above the optimal value
772 (twice the optimal value has the rating one, three times the optimal
773 value has the rating two, etc.)
774 - the analytic form is reciprocal subtracting one for values below the
775 optimal value (halve the optimal value has the rating one, one third of
776 the optimal value has the rating two, etc.)"""
778 # __implements__ = sole implementation
780 def __init__(self
, opt
, weight
=0.1):
781 """inititializes the rater
782 - opt is the optimal length (a visual PyX length)
783 - weight should be positive and is a factor multiplicated to the rates"""
787 def rate(self
, distances
, density
):
789 - the distances are a list of positive floats in PostScript points
790 - the density lineary rescales the rater (the optimum etc.),
791 e.g. a value bigger than one increases the optimum (when it is
792 positive) and a value lower than one decreases the optimum (when
793 it is positive); the density itself should be positive"""
795 opt
= unit
.topt(unit
.length(self
.opt_str
, default_type
="v")) / density
797 for distance
in distances
:
799 rate
+= self
.weight
* (opt
/ distance
- 1)
801 rate
+= self
.weight
* (distance
/ opt
- 1)
802 return rate
/ float(len(distances
))
807 - the rating of axes is splited into two separate parts:
808 - rating of the ticks in terms of the number of ticks, subticks,
810 - rating of the label distances
811 - in the end, a rate for ticks is the sum of these rates
812 - it is useful to first just rate the number of ticks etc.
813 and selecting those partitions, where this fits well -> as soon
814 as an complete rate (the sum of both parts from the list above)
815 of a first ticks is below a rate of just the number of ticks,
816 subticks labels etc. of other ticks, those other ticks will never
817 be better than the first one -> we gain speed by minimizing the
818 number of ticks, where label distances have to be taken into account)
819 - both parts of the rating are shifted into instances of raters
820 defined above --- right now, there is not yet a strict interface
821 for this delegation (should be done as soon as it is needed)"""
823 # __implements__ = sole implementation
825 linticks
= (cuberater(4), cuberater(10, weight
=0.5), )
826 linlabels
= (cuberater(4), )
827 logticks
= (cuberater(5, right
=20), cuberater(20, right
=100, weight
=0.5), )
828 loglabels
= (cuberater(5, right
=20), cuberater(5, left
=-20, right
=20, weight
=0.5), )
829 stdrange
= cuberater(1, weight
=2)
830 stddistance
= distancerater("1 cm")
832 def __init__(self
, ticks
=linticks
, labels
=linlabels
, range=stdrange
, distance
=stddistance
):
833 """initializes the axis rater
834 - ticks and labels are lists of instances of a value rater
835 - the first entry in ticks rate the number of ticks, the
836 second the number of subticks, etc.; when there are no
837 ticks of a level or there is not rater for a level, the
838 level is just ignored
839 - labels is analogous, but for labels
840 - within the rating, all ticks with a higher level are
841 considered as ticks for a given level
842 - range is a value rater instance, which rates the covering
843 of an axis range by the ticks (as a relative value of the
844 tick range vs. the axis range), ticks might cover less or
845 more than the axis range (for the standard automatic axis
846 partition schemes an extention of the axis range is normal
847 and should get some penalty)
848 - distance is an distance rater instance"""
852 self
.distance
= distance
854 def rateticks(self
, axis
, ticks
, density
):
855 """rates ticks by the number of ticks, subticks, labels etc.
856 - takes into account the number of ticks, subticks, labels
857 etc. and the coverage of the axis range by the ticks
858 - when there are no ticks of a level or there was not rater
859 given in the constructor for a level, the level is just
861 - the method returns the sum of the rating results divided
862 by the sum of the weights of the raters
863 - within the rating, all ticks with a higher level are
864 considered as ticks for a given level"""
865 maxticklevel
, maxlabellevel
= _maxlevels(ticks
)
866 numticks
= [0]*maxticklevel
867 numlabels
= [0]*maxlabellevel
869 if tick
.ticklevel
is not None:
870 for level
in range(tick
.ticklevel
, maxticklevel
):
872 if tick
.labellevel
is not None:
873 for level
in range(tick
.labellevel
, maxlabellevel
):
874 numlabels
[level
] += 1
877 for numtick
, rater
in zip(numticks
, self
.ticks
):
878 rate
+= rater
.rate(numtick
, density
)
879 weight
+= rater
.weight
880 for numlabel
, rater
in zip(numlabels
, self
.labels
):
881 rate
+= rater
.rate(numlabel
, density
)
882 weight
+= rater
.weight
885 def raterange(self
, tickrange
, datarange
):
886 """rate the range covered by the ticks compared to the range
888 - tickrange and datarange are the ranges covered by the ticks
889 and the data in graph coordinates
890 - usually, the datarange is 1 (ticks are calculated for a
892 - the ticks might cover less or more than the data range (for
893 the standard automatic axis partition schemes an extention
894 of the axis range is normal and should get some penalty)"""
895 return self
.range.rate(tickrange
, datarange
)
897 def ratelayout(self
, axiscanvas
, density
):
898 """rate distances of the labels in an axis canvas
899 - the distances should be collected as box distances of
901 - the axiscanvas provides a labels attribute for easy
902 access to the labels whose distances have to be taken
904 - the density is used within the distancerate instance"""
905 if len(axiscanvas
.labels
) > 1:
907 distances
= [axiscanvas
.labels
[i
].boxdistance_pt(axiscanvas
.labels
[i
+1]) for i
in range(len(axiscanvas
.labels
) - 1)]
908 except box
.BoxCrossError
:
910 return self
.distance
.rate(distances
, density
)
915 ################################################################################
917 # texter automatically create labels for tick instances
918 ################################################################################
923 def labels(self
, ticks
):
924 """fill the label attribute of ticks
925 - ticks is a list of instances of tick
926 - for each element of ticks the value of the attribute label is set to
927 a string appropriate to the attributes enum and denom of that tick
929 - label attributes of the tick instances are just kept, whenever they
930 are not equal to None
931 - the method might modify the labelattrs attribute of the ticks; be sure
932 to not modify it in-place!"""
935 class rationaltexter
:
936 "a texter creating rational labels (e.g. 'a/b' or even 'a \over b')"
937 # XXX: we use divmod here to be more expicit
939 __implements__
= _Itexter
941 def __init__(self
, prefix
="", infix
="", suffix
="",
942 enumprefix
="", enuminfix
="", enumsuffix
="",
943 denomprefix
="", denominfix
="", denomsuffix
="",
944 plus
="", minus
="-", minuspos
=0, over
=r
"{{%s}\over{%s}}",
945 equaldenom
=0, skip1
=1, skipenum0
=1, skipenum1
=1, skipdenom1
=1,
946 labelattrs
=[textmodule
.mathmode
]):
947 r
"""initializes the instance
948 - prefix, infix, and suffix (strings) are added at the begin,
949 immediately after the minus, and at the end of the label,
951 - prefixenum, infixenum, and suffixenum (strings) are added
952 to the labels enumerator correspondingly
953 - prefixdenom, infixdenom, and suffixdenom (strings) are added
954 to the labels denominator correspondingly
955 - plus or minus (string) is inserted for non-negative or negative numbers
956 - minuspos is an integer, which determines the position, where the
957 plus or minus sign has to be placed; the following values are allowed:
958 1 - writes the plus or minus in front of the enumerator
959 0 - writes the plus or minus in front of the hole fraction
960 -1 - writes the plus or minus in front of the denominator
961 - over (string) is taken as a format string generating the
962 fraction bar; it has to contain exactly two string insert
963 operators "%s" -- the first for the enumerator and the second
964 for the denominator; by far the most common examples are
965 r"{{%s}\over{%s}}" and "{{%s}/{%s}}"
966 - usually the enumerator and denominator are canceled; however,
967 when equaldenom is set, the least common multiple of all
969 - skip1 (boolean) just prints the prefix, the plus or minus,
970 the infix and the suffix, when the value is plus or minus one
971 and at least one of prefix, infix and the suffix is present
972 - skipenum0 (boolean) just prints a zero instead of
973 the hole fraction, when the enumerator is zero;
974 no prefixes, infixes, and suffixes are taken into account
975 - skipenum1 (boolean) just prints the enumprefix, the plus or minus,
976 the enuminfix and the enumsuffix, when the enum value is plus or minus one
977 and at least one of enumprefix, enuminfix and the enumsuffix is present
978 - skipdenom1 (boolean) just prints the enumerator instead of
979 the hole fraction, when the denominator is one and none of the parameters
980 denomprefix, denominfix and denomsuffix are set and minuspos is not -1 or the
982 - labelattrs is a list of attributes for a texrunners text method;
983 None is considered as an empty list; labelattrs might be changed
984 in the painter as well"""
988 self
.enumprefix
= enumprefix
989 self
.enuminfix
= enuminfix
990 self
.enumsuffix
= enumsuffix
991 self
.denomprefix
= denomprefix
992 self
.denominfix
= denominfix
993 self
.denomsuffix
= denomsuffix
996 self
.minuspos
= minuspos
998 self
.equaldenom
= equaldenom
1000 self
.skipenum0
= skipenum0
1001 self
.skipenum1
= skipenum1
1002 self
.skipdenom1
= skipdenom1
1003 self
.labelattrs
= labelattrs
1006 """returns the greates common divisor of all elements in n
1007 - the elements of n must be non-negative integers
1008 - return None if the number of elements is zero
1009 - the greates common divisor is not affected when some
1010 of the elements are zero, but it becomes zero when
1011 all elements are zero"""
1017 i
, (dummy
, j
) = j
, divmod(i
, j
)
1022 res
= self
.gcd(res
, i
)
1026 """returns the least common multiple of all elements in n
1027 - the elements of n must be non-negative integers
1028 - return None if the number of elements is zero
1029 - the least common multiple is zero when some of the
1030 elements are zero"""
1034 res
= divmod(res
* i
, self
.gcd(res
, i
))[0]
1037 def labels(self
, ticks
):
1040 if tick
.label
is None and tick
.labellevel
is not None:
1041 labeledticks
.append(tick
)
1042 tick
.temp_fracenum
= tick
.enum
1043 tick
.temp_fracdenom
= tick
.denom
1044 tick
.temp_fracminus
= 1
1045 if tick
.temp_fracenum
< 0:
1046 tick
.temp_fracminus
= -tick
.temp_fracminus
1047 tick
.temp_fracenum
= -tick
.temp_fracenum
1048 if tick
.temp_fracdenom
< 0:
1049 tick
.temp_fracminus
= -tick
.temp_fracminus
1050 tick
.temp_fracdenom
= -tick
.temp_fracdenom
1051 gcd
= self
.gcd(tick
.temp_fracenum
, tick
.temp_fracdenom
)
1052 (tick
.temp_fracenum
, dummy1
), (tick
.temp_fracdenom
, dummy2
) = divmod(tick
.temp_fracenum
, gcd
), divmod(tick
.temp_fracdenom
, gcd
)
1054 equaldenom
= self
.lcm(*[tick
.temp_fracdenom
for tick
in ticks
if tick
.label
is None])
1055 if equaldenom
is not None:
1056 for tick
in labeledticks
:
1057 factor
, dummy
= divmod(equaldenom
, tick
.temp_fracdenom
)
1058 tick
.temp_fracenum
, tick
.temp_fracdenom
= factor
* tick
.temp_fracenum
, factor
* tick
.temp_fracdenom
1059 for tick
in labeledticks
:
1060 fracminus
= fracenumminus
= fracdenomminus
= ""
1061 if tick
.temp_fracminus
== -1:
1062 plusminus
= self
.minus
1064 plusminus
= self
.plus
1065 if self
.minuspos
== 0:
1066 fracminus
= plusminus
1067 elif self
.minuspos
== 1:
1068 fracenumminus
= plusminus
1069 elif self
.minuspos
== -1:
1070 fracdenomminus
= plusminus
1072 raise RuntimeError("invalid minuspos")
1073 if self
.skipenum0
and tick
.temp_fracenum
== 0:
1075 elif (self
.skip1
and self
.skipdenom1
and tick
.temp_fracenum
== 1 and tick
.temp_fracdenom
== 1 and
1076 (len(self
.prefix
) or len(self
.infix
) or len(self
.suffix
)) and
1077 not len(fracenumminus
) and not len(self
.enumprefix
) and not len(self
.enuminfix
) and not len(self
.enumsuffix
) and
1078 not len(fracdenomminus
) and not len(self
.denomprefix
) and not len(self
.denominfix
) and not len(self
.denomsuffix
)):
1079 tick
.label
= "%s%s%s%s" % (self
.prefix
, fracminus
, self
.infix
, self
.suffix
)
1081 if self
.skipenum1
and tick
.temp_fracenum
== 1 and (len(self
.enumprefix
) or len(self
.enuminfix
) or len(self
.enumsuffix
)):
1082 tick
.temp_fracenum
= "%s%s%s%s" % (self
.enumprefix
, fracenumminus
, self
.enuminfix
, self
.enumsuffix
)
1084 tick
.temp_fracenum
= "%s%s%s%i%s" % (self
.enumprefix
, fracenumminus
, self
.enuminfix
, tick
.temp_fracenum
, self
.enumsuffix
)
1085 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
):
1086 frac
= tick
.temp_fracenum
1088 tick
.temp_fracdenom
= "%s%s%s%i%s" % (self
.denomprefix
, fracdenomminus
, self
.denominfix
, tick
.temp_fracdenom
, self
.denomsuffix
)
1089 frac
= self
.over
% (tick
.temp_fracenum
, tick
.temp_fracdenom
)
1090 tick
.label
= "%s%s%s%s%s" % (self
.prefix
, fracminus
, self
.infix
, frac
, self
.suffix
)
1091 tick
.labelattrs
= tick
.labelattrs
+ self
.labelattrs
1093 # del tick.temp_fracenum # we've inserted those temporary variables ... and do not care any longer about them
1094 # del tick.temp_fracdenom
1095 # del tick.temp_fracminus
1099 class decimaltexter
:
1100 "a texter creating decimal labels (e.g. '1.234' or even '0.\overline{3}')"
1102 __implements__
= _Itexter
1104 def __init__(self
, prefix
="", infix
="", suffix
="", equalprecision
=0,
1105 decimalsep
=".", thousandsep
="", thousandthpartsep
="",
1106 plus
="", minus
="-", period
=r
"\overline{%s}",
1107 labelattrs
=[textmodule
.mathmode
]):
1108 r
"""initializes the instance
1109 - prefix, infix, and suffix (strings) are added at the begin,
1110 immediately after the minus, and at the end of the label,
1112 - decimalsep, thousandsep, and thousandthpartsep (strings)
1113 are used as separators
1114 - plus or minus (string) is inserted for non-negative or negative numbers
1115 - period (string) is taken as a format string generating a period;
1116 it has to contain exactly one string insert operators "%s" for the
1117 period; usually it should be r"\overline{%s}"
1118 - labelattrs is a list of attributes for a texrunners text method;
1119 a single is allowed without being a list; None is considered as
1120 an empty list; labelattrs might be changed in the painter as well"""
1121 self
.prefix
= prefix
1123 self
.suffix
= suffix
1124 self
.equalprecision
= equalprecision
1125 self
.decimalsep
= decimalsep
1126 self
.thousandsep
= thousandsep
1127 self
.thousandthpartsep
= thousandthpartsep
1130 self
.period
= period
1131 self
.labelattrs
= labelattrs
1133 def labels(self
, ticks
):
1137 if tick
.label
is None and tick
.labellevel
is not None:
1138 labeledticks
.append(tick
)
1139 m
, n
= tick
.enum
, tick
.denom
1142 quotient
, remainder
= divmod(m
, n
)
1143 quotient
= str(quotient
)
1144 if len(self
.thousandsep
):
1148 tick
.label
+= quotient
[i
]
1149 if not ((l
-i
-1) % 3) and l
> i
+1:
1150 tick
.label
+= self
.thousandsep
1152 tick
.label
= quotient
1154 tick
.label
+= self
.decimalsep
1156 tick
.temp_decprecision
= 0
1158 tick
.temp_decprecision
+= 1
1159 if remainder
in oldremainders
:
1160 tick
.temp_decprecision
= None
1161 periodstart
= len(tick
.label
) - (len(oldremainders
) - oldremainders
.index(remainder
))
1162 tick
.label
= tick
.label
[:periodstart
] + self
.period
% tick
.label
[periodstart
:]
1164 oldremainders
+= [remainder
]
1166 quotient
, remainder
= divmod(remainder
, n
)
1167 if not ((tick
.temp_decprecision
- 1) % 3) and tick
.temp_decprecision
> 1:
1168 tick
.label
+= self
.thousandthpartsep
1169 tick
.label
+= str(quotient
)
1170 if maxdecprecision
< tick
.temp_decprecision
:
1171 maxdecprecision
= tick
.temp_decprecision
1172 if self
.equalprecision
:
1173 for tick
in labeledticks
:
1174 if tick
.temp_decprecision
is not None:
1175 if tick
.temp_decprecision
== 0 and maxdecprecision
> 0:
1176 tick
.label
+= self
.decimalsep
1177 for i
in range(tick
.temp_decprecision
, maxdecprecision
):
1178 if not ((i
- 1) % 3) and i
> 1:
1179 tick
.label
+= self
.thousandthpartsep
1181 for tick
in labeledticks
:
1182 if tick
.enum
* tick
.denom
< 0:
1183 plusminus
= self
.minus
1185 plusminus
= self
.plus
1186 tick
.label
= "%s%s%s%s%s" % (self
.prefix
, plusminus
, self
.infix
, tick
.label
, self
.suffix
)
1187 tick
.labelattrs
= tick
.labelattrs
+ self
.labelattrs
1189 # del tick.temp_decprecision # we've inserted this temporary variable ... and do not care any longer about it
1192 class exponentialtexter
:
1193 "a texter creating labels with exponentials (e.g. '2\cdot10^5')"
1195 __implements__
= _Itexter
1197 def __init__(self
, plus
="", minus
="-",
1198 mantissaexp
=r
"{{%s}\cdot10^{%s}}",
1201 nomantissaexp
=r
"{10^{%s}}",
1202 minusnomantissaexp
=r
"{-10^{%s}}",
1203 mantissamin
=frac((1, 1)), mantissamax
=frac((10, 1)),
1204 skipmantissa1
=0, skipallmantissa1
=1,
1205 mantissatexter
=decimaltexter()):
1206 r
"""initializes the instance
1207 - plus or minus (string) is inserted for non-negative or negative exponents
1208 - mantissaexp (string) is taken as a format string generating the exponent;
1209 it has to contain exactly two string insert operators "%s" --
1210 the first for the mantissa and the second for the exponent;
1211 examples are r"{{%s}\cdot10^{%s}}" and r"{{%s}{\rm e}{%s}}"
1212 - skipexp0 (string) is taken as a format string used for exponent 0;
1213 exactly one string insert operators "%s" for the mantissa;
1214 None turns off the special handling of exponent 0;
1215 an example is r"{%s}"
1216 - skipexp1 (string) is taken as a format string used for exponent 1;
1217 exactly one string insert operators "%s" for the mantissa;
1218 None turns off the special handling of exponent 1;
1219 an example is r"{{%s}\cdot10}"
1220 - nomantissaexp (string) is taken as a format string generating the exponent
1221 when the mantissa is one and should be skipped; it has to contain
1222 exactly one string insert operators "%s" for the exponent;
1223 an examples is r"{10^{%s}}"
1224 - minusnomantissaexp (string) is taken as a format string generating the exponent
1225 when the mantissa is minus one and should be skipped; it has to contain
1226 exactly one string insert operators "%s" for the exponent;
1227 None turns off the special handling of mantissa -1;
1228 an examples is r"{-10^{%s}}"
1229 - mantissamin and mantissamax are the minimum and maximum of the mantissa;
1230 they are frac instances greater than zero and mantissamin < mantissamax;
1231 the sign of the tick is ignored here
1232 - skipmantissa1 (boolean) turns on skipping of any mantissa equals one
1233 (and minus when minusnomantissaexp is set)
1234 - skipallmantissa1 (boolean) as above, but all mantissas must be 1 (or -1)
1235 - mantissatexter is the texter for the mantissa
1236 - the skipping of a mantissa is stronger than the skipping of an exponent"""
1239 self
.mantissaexp
= mantissaexp
1240 self
.skipexp0
= skipexp0
1241 self
.skipexp1
= skipexp1
1242 self
.nomantissaexp
= nomantissaexp
1243 self
.minusnomantissaexp
= minusnomantissaexp
1244 self
.mantissamin
= mantissamin
1245 self
.mantissamax
= mantissamax
1246 self
.mantissamindivmax
= self
.mantissamin
/ self
.mantissamax
1247 self
.mantissamaxdivmin
= self
.mantissamax
/ self
.mantissamin
1248 self
.skipmantissa1
= skipmantissa1
1249 self
.skipallmantissa1
= skipallmantissa1
1250 self
.mantissatexter
= mantissatexter
1252 def labels(self
, ticks
):
1255 if tick
.label
is None and tick
.labellevel
is not None:
1256 tick
.temp_orgenum
, tick
.temp_orgdenom
= tick
.enum
, tick
.denom
1257 labeledticks
.append(tick
)
1260 while abs(tick
) >= self
.mantissamax
:
1262 x
= tick
* self
.mantissamindivmax
1263 tick
.enum
, tick
.denom
= x
.enum
, x
.denom
1264 while abs(tick
) < self
.mantissamin
:
1266 x
= tick
* self
.mantissamaxdivmin
1267 tick
.enum
, tick
.denom
= x
.enum
, x
.denom
1268 if tick
.temp_exp
< 0:
1269 tick
.temp_exp
= "%s%i" % (self
.minus
, -tick
.temp_exp
)
1271 tick
.temp_exp
= "%s%i" % (self
.plus
, tick
.temp_exp
)
1272 self
.mantissatexter
.labels(labeledticks
)
1273 if self
.minusnomantissaexp
is not None:
1274 allmantissa1
= len(labeledticks
) == len([tick
for tick
in labeledticks
if abs(tick
.enum
) == abs(tick
.denom
)])
1276 allmantissa1
= len(labeledticks
) == len([tick
for tick
in labeledticks
if tick
.enum
== tick
.denom
])
1277 for tick
in labeledticks
:
1278 if (self
.skipallmantissa1
and allmantissa1
or
1279 (self
.skipmantissa1
and (tick
.enum
== tick
.denom
or
1280 (tick
.enum
== -tick
.denom
and self
.minusnomantissaexp
is not None)))):
1281 if tick
.enum
== tick
.denom
:
1282 tick
.label
= self
.nomantissaexp
% tick
.temp_exp
1284 tick
.label
= self
.minusnomantissaexp
% tick
.temp_exp
1286 if tick
.temp_exp
== "0" and self
.skipexp0
is not None:
1287 tick
.label
= self
.skipexp0
% tick
.label
1288 elif tick
.temp_exp
== "1" and self
.skipexp1
is not None:
1289 tick
.label
= self
.skipexp1
% tick
.label
1291 tick
.label
= self
.mantissaexp
% (tick
.label
, tick
.temp_exp
)
1292 tick
.enum
, tick
.denom
= tick
.temp_orgenum
, tick
.temp_orgdenom
1294 # del tick.temp_orgenum # we've inserted those temporary variables ... and do not care any longer about them
1295 # del tick.temp_orgdenom
1299 class defaulttexter
:
1300 "a texter creating decimal or exponential labels"
1302 __implements__
= _Itexter
1304 def __init__(self
, smallestdecimal
=frac((1, 1000)),
1305 biggestdecimal
=frac((9999, 1)),
1307 decimaltexter
=decimaltexter(),
1308 exponentialtexter
=exponentialtexter()):
1309 """initializes the instance
1310 - smallestdecimal and biggestdecimal are the smallest and
1311 biggest decimal values, where the decimaltexter should be used;
1312 they are frac instances; the sign of the tick is ignored here;
1313 a tick at zero is considered for the decimaltexter as well
1314 - equaldecision (boolean) uses decimaltexter or exponentialtexter
1315 globaly (set) or for each tick separately (unset)
1316 - decimaltexter and exponentialtexter are texters to be used"""
1317 self
.smallestdecimal
= smallestdecimal
1318 self
.biggestdecimal
= biggestdecimal
1319 self
.equaldecision
= equaldecision
1320 self
.decimaltexter
= decimaltexter
1321 self
.exponentialtexter
= exponentialtexter
1323 def labels(self
, ticks
):
1327 if tick
.label
is None and tick
.labellevel
is not None:
1328 if not tick
.enum
or (abs(tick
) >= self
.smallestdecimal
and abs(tick
) <= self
.biggestdecimal
):
1329 decticks
.append(tick
)
1331 expticks
.append(tick
)
1332 if self
.equaldecision
:
1334 self
.exponentialtexter
.labels(ticks
)
1336 self
.decimaltexter
.labels(ticks
)
1338 for tick
in decticks
:
1339 self
.decimaltexter
.labels([tick
])
1340 for tick
in expticks
:
1341 self
.exponentialtexter
.labels([tick
])
1344 ################################################################################
1346 ################################################################################
1349 class axiscanvas(canvas
._canvas
):
1351 - an axis canvas is a regular canvas returned by an
1352 axispainters painter method
1353 - it contains a PyX length extent to be used for the
1354 alignment of additional axes; the axis extent should
1355 be handled by the axispainters painter method; you may
1356 apprehend this as a size information comparable to a
1357 bounding box, which must be handled manually
1358 - it contains a list of textboxes called labels which are
1359 used to rate the distances between the labels if needed
1360 by the axis later on; the painter method has not only to
1361 insert the labels into this canvas, but should also fill
1362 this list, when a rating of the distances should be
1363 performed by the axis"""
1365 # __implements__ = sole implementation
1367 def __init__(self
, *args
, **kwargs
):
1368 """initializes the instance
1369 - sets extent to zero
1370 - sets labels to an empty list"""
1371 canvas
._canvas
.__init
__(self
, *args
, **kwargs
)
1377 """create rotations accordingly to tick directions
1378 - upsidedown rotations are suppressed by rotating them by another 180 degree"""
1380 # __implements__ = sole implementation
1382 def __init__(self
, direction
, epsilon
=1e-10):
1383 """initializes the instance
1384 - direction is an angle to be used relative to the tick direction
1385 - epsilon is the value by which 90 degrees can be exceeded before
1386 an 180 degree rotation is added"""
1387 self
.direction
= direction
1388 self
.epsilon
= epsilon
1390 def trafo(self
, dx
, dy
):
1391 """returns a rotation transformation accordingly to the tick direction
1392 - dx and dy are the direction of the tick"""
1393 direction
= self
.direction
+ math
.atan2(dy
, dx
) * 180 / math
.pi
1394 while (direction
> 180 + self
.epsilon
):
1396 while (direction
< -180 - self
.epsilon
):
1398 while (direction
> 90 + self
.epsilon
):
1400 while (direction
< -90 - self
.epsilon
):
1402 return trafomodule
.rotate(direction
)
1405 rotatetext
.parallel
= rotatetext(90)
1406 rotatetext
.orthogonal
= rotatetext(180)
1409 class _Iaxispainter
:
1410 "class for painting axes"
1412 def paint(self
, axispos
, axis
, ac
=None):
1413 """paint the axis into an axiscanvas
1414 - returns the axiscanvas
1415 - when no axiscanvas is provided (the typical case), a new
1416 axiscanvas is created. however, when extending an painter
1417 by inheritance, painting on the same axiscanvas is supported
1418 by setting the axiscanvas attribute
1419 - axispos is an instance, which implements _Iaxispos to
1420 define the tick positions
1421 - the axis and should not be modified (we may
1422 add some temporary variables like axis.ticks[i].temp_xxx,
1423 which might be used just temporary) -- the idea is that
1424 all things can be used several times
1425 - also do not modify the instance (self) -- even this
1426 instance might be used several times; thus do not modify
1427 attributes like self.titleattrs etc. (use local copies)
1428 - the method might access some additional attributes from
1429 the axis, e.g. the axis title -- the axis painter should
1430 document this behavior and rely on the availability of
1431 those attributes -> it becomes a question of the proper
1432 usage of the combination of axis & axispainter
1433 - the axiscanvas is a axiscanvas instance and should be
1434 filled with ticks, labels, title, etc.; note that the
1435 extent and labels instance variables should be handled
1436 as documented in the axiscanvas"""
1440 """interface definition of axis tick position methods
1441 - these methods are used for the postitioning of the ticks
1442 when painting an axis"""
1443 # TODO: should we add a local transformation (for label text etc?)
1444 # (this might replace tickdirection (and even tickposition?))
1446 def basepath(self
, x1
=None, x2
=None):
1447 """return the basepath as a path
1448 - x1 is the start position; if not set, the basepath starts
1449 from the beginning of the axis, which might imply a
1450 value outside of the graph coordinate range [0; 1]
1451 - x2 is analogous to x1, but for the end position"""
1453 def vbasepath(self
, v1
=None, v2
=None):
1454 """return the basepath as a path
1455 - like basepath, but for graph coordinates"""
1457 def gridpath(self
, x
):
1458 """return the gridpath as a path for a given position x
1459 - might return None when no gridpath is available"""
1461 def vgridpath(self
, v
):
1462 """return the gridpath as a path for a given position v
1463 in graph coordinates
1464 - might return None when no gridpath is available"""
1466 def tickpoint_pt(self
, x
):
1467 """return the position at the basepath as a tuple (x, y) in
1468 postscript points for the position x"""
1470 def tickpoint(self
, x
):
1471 """return the position at the basepath as a tuple (x, y) in
1472 in PyX length for the position x"""
1474 def vtickpoint_pt(self
, v
):
1475 "like tickpoint_pt, but for graph coordinates"
1477 def vtickpoint(self
, v
):
1478 "like tickpoint, but for graph coordinates"
1480 def tickdirection(self
, x
):
1481 """return the direction of a tick as a tuple (dx, dy) for the
1482 position x (the direction points towards the graph)"""
1484 def vtickdirection(self
, v
):
1485 """like tickposition, but for graph coordinates"""
1489 """implements those parts of _Iaxispos which can be build
1490 out of the axis convert method and other _Iaxispos methods
1491 - base _Iaxispos methods, which need to be implemented:
1496 - other methods needed for _Iaxispos are build out of those
1497 listed above when this class is inherited"""
1499 def __init__(self
, convert
):
1500 """initializes the instance
1501 - convert is a convert method from an axis"""
1502 self
.convert
= convert
1504 def basepath(self
, x1
=None, x2
=None):
1507 return self
.vbasepath()
1509 return self
.vbasepath(v2
=self
.convert(x2
))
1512 return self
.vbasepath(v1
=self
.convert(x1
))
1514 return self
.vbasepath(v1
=self
.convert(x1
), v2
=self
.convert(x2
))
1516 def gridpath(self
, x
):
1517 return self
.vgridpath(self
.convert(x
))
1519 def tickpoint_pt(self
, x
):
1520 return self
.vtickpoint_pt(self
.convert(x
))
1522 def tickpoint(self
, x
):
1523 return self
.vtickpoint(self
.convert(x
))
1525 def vtickpoint(self
, v
):
1526 return [unit
.t_pt(x
) for x
in self
.vtickpoint(v
)]
1528 def tickdirection(self
, x
):
1529 return self
.vtickdirection(self
.convert(x
))
1532 class pathaxispos(_axispos
):
1533 """axis tick position methods along an arbitrary path"""
1535 __implements__
= _Iaxispos
1537 def __init__(self
, p
, convert
, direction
=1):
1539 self
.normpath
= path
.normpath(p
)
1540 self
.arclength
= self
.normpath
.arclength()
1541 _axispos
.__init
__(self
, convert
)
1542 self
.direction
= direction
1544 def vbasepath(self
, v1
=None, v2
=None):
1549 return self
.normpath
.split(self
.normpath
.lentopar(v2
* self
.arclength
))[0]
1552 return self
.normpath
.split(self
.normpath
.lentopar(v1
* self
.arclength
))[1]
1554 return self
.normpath
.split(*self
.normpath
.lentopar([v1
* self
.arclength
, v2
* self
.arclength
]))[1]
1556 def vgridpath(self
, v
):
1559 def vtickpoint_pt(self
, v
):
1560 # XXX: path._at missing!
1561 return [unit
.topt(x
) for x
in self
.normpath
.at(self
.normpath
.lentopar(v
* self
.arclength
))]
1563 def vtickdirection(self
, v
):
1564 t
= self
.normpath
.tangent(self
.normpath
.lentopar(v
* self
.arclength
))
1565 # XXX: path._begin and path._end missing!
1566 tbegin
= [unit
.topt(x
) for x
in t
.begin()]
1567 tend
= [unit
.topt(x
) for x
in t
.end()]
1568 dx
= tend
[0]-tbegin
[0]
1569 dy
= tend
[1]-tbegin
[1]
1570 norm
= math
.sqrt(dx
*dx
+ dy
*dy
)
1571 if self
.direction
== 1:
1572 return -dy
/norm
, dx
/norm
1573 elif self
.direction
== -1:
1574 return dy
/norm
, -dx
/norm
1575 raise RuntimeError("unknown direction")
1578 class axistitlepainter
:
1579 """class for painting an axis title
1580 - the axis must have a title attribute when using this painter;
1581 this title might be None"""
1583 __implements__
= _Iaxispainter
1585 defaulttitleattrs
= [textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
]
1587 def __init__(self
, titledist
="0.3 cm",
1589 titledirection
=rotatetext
.parallel
,
1591 texrunner
=textmodule
.defaulttexrunner
):
1592 """initialized the instance
1593 - titledist is a visual PyX length giving the distance
1594 of the title from the axis extent already there (a title might
1595 be added after labels or other things are plotted already)
1596 - titleattrs is a list of attributes for a texrunners text
1597 method; a single is allowed without being a list; None
1599 - titledirection is an instance of rotatetext or None
1600 - titlepos is the position of the title in graph coordinates
1601 - texrunner is the texrunner to be used to create text
1602 (the texrunner is available for further use in derived
1603 classes as instance variable texrunner)"""
1604 self
.titledist_str
= titledist
1605 self
.titleattrs
= titleattrs
1606 self
.titledirection
= titledirection
1607 self
.titlepos
= titlepos
1608 self
.texrunner
= texrunner
1610 def paint(self
, axispos
, axis
, ac
=None):
1613 if axis
.title
is not None and self
.titleattrs
is not None:
1614 titledist
= unit
.length(self
.titledist_str
, default_type
="v")
1615 x
, y
= axispos
.vtickpoint_pt(self
.titlepos
)
1616 dx
, dy
= axispos
.vtickdirection(self
.titlepos
)
1617 titleattrs
= self
.defaulttitleattrs
+ self
.titleattrs
1618 if self
.titledirection
is not None:
1619 titleattrs
.append(self
.titledirection
.trafo(dx
, dy
))
1620 title
= self
.texrunner
.text_pt(x
, y
, axis
.title
, titleattrs
)
1621 ac
.extent
+= titledist
1622 title
.linealign(ac
.extent
, -dx
, -dy
)
1623 ac
.extent
+= title
.extent(dx
, dy
)
1628 class geometricseries(attr
.changeattr
):
1630 def __init__(self
, initial
, factor
):
1631 self
.initial
= initial
1632 self
.factor
= factor
1634 def select(self
, index
, total
):
1635 return self
.initial
* (self
.factor
** index
)
1638 class ticklength(geometricseries
): pass
1642 #ticklength.short = ticklength("%f cm" % (_base/math.sqrt(64)), 1/goldenmean)
1643 ticklength
.short
= ticklength(_base
/math
.sqrt(64), 1/goldenmean
)
1644 ticklength
.short
= ticklength(_base
/math
.sqrt(32), 1/goldenmean
)
1645 ticklength
.short
= ticklength(_base
/math
.sqrt(16), 1/goldenmean
)
1646 ticklength
.short
= ticklength(_base
/math
.sqrt(8), 1/goldenmean
)
1647 ticklength
.short
= ticklength(_base
/math
.sqrt(4), 1/goldenmean
)
1648 ticklength
.short
= ticklength(_base
/math
.sqrt(2), 1/goldenmean
)
1649 ticklength
.normal
= ticklength(_base
, 1/goldenmean
)
1650 ticklength
.long = ticklength(_base
*math
.sqrt(2), 1/goldenmean
)
1651 ticklength
.long = ticklength(_base
*math
.sqrt(4), 1/goldenmean
)
1652 ticklength
.long = ticklength(_base
*math
.sqrt(8), 1/goldenmean
)
1653 ticklength
.long = ticklength(_base
*math
.sqrt(16), 1/goldenmean
)
1654 ticklength
.long = ticklength(_base
*math
.sqrt(32), 1/goldenmean
)
1657 class axispainter(axistitlepainter
):
1658 """class for painting the ticks and labels of an axis
1659 - the inherited titleaxispainter is used to paint the title of
1661 - note that the type of the elements of ticks given as an argument
1662 of the paint method must be suitable for the tick position methods
1665 __implements__
= _Iaxispainter
1667 defaulttickattrs
= []
1668 defaultgridattrs
= []
1669 defaultbasepathattrs
= [style
.linecap
.square
]
1670 defaultlabelattrs
= [textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
]
1672 def __init__(self
, innerticklength
=ticklength
.short
,
1673 outerticklength
=None,
1679 labeldirection
=None,
1683 """initializes the instance
1684 - innerticklength and outerticklength are two lists of
1685 visual PyX lengths for ticks, subticks, etc. plotted inside
1686 and outside of the graph; when a single value is given, it
1687 is used for all tick levels; None turns off ticks inside or
1688 outside of the graph
1689 - tickattrs are a list of stroke attributes for the ticks;
1690 a single entry is allowed without being a list; None turns
1692 - gridattrs are a list of lists used as stroke
1693 attributes for ticks, subticks etc.; when a single list
1694 is given, it is used for ticks, subticks, etc.; a single
1695 entry is allowed without being a list; None turns off
1697 - basepathattrs are a list of stroke attributes for a grid
1698 line at axis value zero; a single entry is allowed without
1699 being a list; None turns off the basepath
1700 - labeldist is a visual PyX length for the distance of the labels
1701 from the axis basepath
1702 - labelattrs is a list of attributes for a texrunners text
1703 method; a single entry is allowed without being a list;
1704 None turns off the labels
1705 - titledirection is an instance of rotatetext or None
1706 - labelhequalize and labelvequalize (booleans) perform an equal
1707 alignment for straight vertical and horizontal axes, respectively
1708 - futher keyword arguments are passed to axistitlepainter"""
1709 # TODO: access to axis.divisor -- document, remove, ... ???
1710 self
.innerticklength_str
= innerticklength
1711 self
.outerticklength_str
= outerticklength
1712 self
.tickattrs
= tickattrs
1713 self
.gridattrs
= gridattrs
1714 self
.basepathattrs
= basepathattrs
1715 self
.labeldist_str
= labeldist
1716 self
.labelattrs
= labelattrs
1717 self
.labeldirection
= labeldirection
1718 self
.labelhequalize
= labelhequalize
1719 self
.labelvequalize
= labelvequalize
1720 axistitlepainter
.__init
__(self
, **kwargs
)
1722 def paint(self
, axispos
, axis
, ac
=None):
1726 raise RuntimeError("XXX") # XXX debug only
1727 labeldist
= unit
.length(self
.labeldist_str
, default_type
="v")
1728 for tick
in axis
.ticks
:
1729 tick
.temp_v
= axis
.convert(float(tick
) * axis
.divisor
)
1730 tick
.temp_x
, tick
.temp_y
= axispos
.vtickpoint_pt(tick
.temp_v
)
1731 tick
.temp_dx
, tick
.temp_dy
= axispos
.vtickdirection(tick
.temp_v
)
1732 maxticklevel
, maxlabellevel
= _maxlevels(axis
.ticks
)
1734 # create & align tick.temp_labelbox
1735 for tick
in axis
.ticks
:
1736 if tick
.labellevel
is not None:
1737 labelattrs
= attr
.selectattrs(self
.labelattrs
, tick
.labellevel
, maxlabellevel
)
1738 if labelattrs
is not None:
1739 labelattrs
= self
.defaultlabelattrs
+ labelattrs
1740 if self
.labeldirection
is not None:
1741 labelattrs
.append(self
.labeldirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
1742 if tick
.labelattrs
is not None:
1743 labelattrs
.extend(tick
.labelattrs
)
1744 tick
.temp_labelbox
= self
.texrunner
.text_pt(tick
.temp_x
, tick
.temp_y
, tick
.label
, labelattrs
)
1745 if len(axis
.ticks
) > 1:
1747 for tick
in axis
.ticks
[1:]:
1748 if tick
.temp_dx
!= axis
.ticks
[0].temp_dx
or tick
.temp_dy
!= axis
.ticks
[0].temp_dy
:
1752 if equaldirection
and ((not axis
.ticks
[0].temp_dx
and self
.labelvequalize
) or
1753 (not axis
.ticks
[0].temp_dy
and self
.labelhequalize
)):
1754 if self
.labelattrs
is not None:
1755 box
.linealignequal([tick
.temp_labelbox
for tick
in axis
.ticks
if tick
.labellevel
is not None],
1756 labeldist
, -axis
.ticks
[0].temp_dx
, -axis
.ticks
[0].temp_dy
)
1758 for tick
in axis
.ticks
:
1759 if tick
.labellevel
is not None and self
.labelattrs
is not None:
1760 tick
.temp_labelbox
.linealign(labeldist
, -tick
.temp_dx
, -tick
.temp_dy
)
1762 for tick
in axis
.ticks
:
1763 if tick
.ticklevel
is not None:
1764 innerticklength
= attr
.selectattr(self
.innerticklength_str
, tick
.ticklevel
, maxticklevel
)
1765 outerticklength
= attr
.selectattr(self
.outerticklength_str
, tick
.ticklevel
, maxticklevel
)
1766 if innerticklength
is not None or outerticklength
is not None:
1767 if innerticklength
is None:
1770 innerticklength
= unit
.length(innerticklength
, default_type
="v")
1771 if outerticklength
is None:
1774 outerticklength
= unit
.length(outerticklength
, default_type
="v")
1775 tickattrs
= attr
.selectattrs(self
.defaulttickattrs
+ self
.tickattrs
, tick
.ticklevel
, maxticklevel
)
1776 if tickattrs
is not None:
1777 innerticklength_pt
= unit
.topt(innerticklength
)
1778 outerticklength_pt
= unit
.topt(outerticklength
)
1779 x1
= tick
.temp_x
+ tick
.temp_dx
* innerticklength_pt
1780 y1
= tick
.temp_y
+ tick
.temp_dy
* innerticklength_pt
1781 x2
= tick
.temp_x
- tick
.temp_dx
* outerticklength_pt
1782 y2
= tick
.temp_y
- tick
.temp_dy
* outerticklength_pt
1783 ac
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), tickattrs
)
1784 if outerticklength
is not None and unit
.topt(outerticklength
) > unit
.topt(ac
.extent
):
1785 ac
.extent
= outerticklength
1786 if outerticklength
is not None and unit
.topt(-innerticklength
) > unit
.topt(ac
.extent
):
1787 ac
.extent
= -innerticklength
1788 if self
.gridattrs
is not None:
1789 gridattrs
= attr
.selectattrs(self
.defaultgridattrs
+ self
.gridattrs
, tick
.ticklevel
, maxticklevel
)
1790 ac
.stroke(axispos
.vgridpath(tick
.temp_v
), gridattrs
)
1791 if tick
.labellevel
is not None and self
.labelattrs
is not None:
1792 ac
.insert(tick
.temp_labelbox
)
1793 ac
.labels
.append(tick
.temp_labelbox
)
1794 extent
= tick
.temp_labelbox
.extent(tick
.temp_dx
, tick
.temp_dy
) + labeldist
1795 if unit
.topt(extent
) > unit
.topt(ac
.extent
):
1797 if self
.basepathattrs
is not None:
1798 ac
.stroke(axispos
.vbasepath(), self
.defaultbasepathattrs
+ self
.basepathattrs
)
1800 # for tick in axis.ticks:
1801 # del tick.temp_v # we've inserted those temporary variables ... and do not care any longer about them
1806 # if tick.labellevel is not None and self.labelattrs is not None:
1807 # del tick.temp_labelbox
1809 axistitlepainter
.paint(self
, axispos
, axis
, ac
=ac
)
1814 class linkaxispainter(axispainter
):
1815 """class for painting a linked axis
1816 - the inherited axispainter is used to paint the axis
1817 - modifies some constructor defaults"""
1819 __implements__
= _Iaxispainter
1821 def __init__(self
, labelattrs
=None,
1824 """initializes the instance
1825 - the labelattrs default is set to None thus skipping the labels
1826 - the titleattrs default is set to None thus skipping the title
1827 - all keyword arguments are passed to axispainter"""
1828 axispainter
.__init
__(self
, labelattrs
=labelattrs
,
1829 titleattrs
=titleattrs
,
1834 """implementation of the _Iaxispos interface for a subaxis"""
1836 __implements__
= _Iaxispos
1838 def __init__(self
, convert
, baseaxispos
, vmin
, vmax
, vminover
, vmaxover
):
1839 """initializes the instance
1840 - convert is the subaxis convert method
1841 - baseaxispos is the axispos instance of the base axis
1842 - vmin, vmax is the range covered by the subaxis in graph coordinates
1843 - vminover, vmaxover is the extended range of the subaxis including
1844 regions between several subaxes (for basepath drawing etc.)"""
1845 self
.convert
= convert
1846 self
.baseaxispos
= baseaxispos
1849 self
.vminover
= vminover
1850 self
.vmaxover
= vmaxover
1852 def basepath(self
, x1
=None, x2
=None):
1854 v1
= self
.vmin
+self
.convert(x1
)*(self
.vmax
-self
.vmin
)
1858 v2
= self
.vmin
+self
.convert(x2
)*(self
.vmax
-self
.vmin
)
1861 return self
.baseaxispos
.vbasepath(v1
, v2
)
1863 def vbasepath(self
, v1
=None, v2
=None):
1865 v1
= self
.vmin
+v1
*(self
.vmax
-self
.vmin
)
1869 v2
= self
.vmin
+v2
*(self
.vmax
-self
.vmin
)
1872 return self
.baseaxispos
.vbasepath(v1
, v2
)
1874 def gridpath(self
, x
):
1875 return self
.baseaxispos
.vgridpath(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
1877 def vgridpath(self
, v
):
1878 return self
.baseaxispos
.vgridpath(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
1880 def tickpoint_pt(self
, x
, axis
=None):
1881 return self
.baseaxispos
.vtickpoint_pt(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
1883 def tickpoint(self
, x
, axis
=None):
1884 return self
.baseaxispos
.vtickpoint(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
1886 def vtickpoint_pt(self
, v
, axis
=None):
1887 return self
.baseaxispos
.vtickpoint_pt(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
1889 def vtickpoint(self
, v
, axis
=None):
1890 return self
.baseaxispos
.vtickpoint(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
1892 def tickdirection(self
, x
, axis
=None):
1893 return self
.baseaxispos
.vtickdirection(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
1895 def vtickdirection(self
, v
, axis
=None):
1896 return self
.baseaxispos
.vtickdirection(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
1899 class splitaxispainter(axistitlepainter
):
1900 """class for painting a splitaxis
1901 - the inherited titleaxispainter is used to paint the title of
1903 - the splitaxispainter access the subaxes attribute of the axis"""
1905 __implements__
= _Iaxispainter
1907 defaultbreaklinesattrs
= []
1909 def __init__(self
, breaklinesdist
="0.05 cm",
1910 breaklineslength
="0.5 cm",
1911 breaklinesangle
=-60,
1914 """initializes the instance
1915 - breaklinesdist is a visual length of the distance between
1916 the two lines of the axis break
1917 - breaklineslength is a visual length of the length of the
1918 two lines of the axis break
1919 - breaklinesangle is the angle of the lines of the axis break
1920 - breaklinesattrs are a list of stroke attributes for the
1921 axis break lines; a single entry is allowed without being a
1922 list; None turns off the break lines
1923 - futher keyword arguments are passed to axistitlepainter"""
1924 self
.breaklinesdist_str
= breaklinesdist
1925 self
.breaklineslength_str
= breaklineslength
1926 self
.breaklinesangle
= breaklinesangle
1927 self
.breaklinesattrs
= breaklinesattrs
1928 axistitlepainter
.__init
__(self
, **args
)
1930 def paint(self
, axispos
, axis
, ac
=None):
1934 raise RuntimeError("XXX") # XXX debug only
1935 for subaxis
in axis
.subaxes
:
1936 subaxis
.finish(subaxispos(subaxis
.convert
, axispos
, subaxis
.vmin
, subaxis
.vmax
, subaxis
.vminover
, subaxis
.vmaxover
))
1937 ac
.insert(subaxis
.axiscanvas
)
1938 if unit
.topt(ac
.extent
) < unit
.topt(subaxis
.axiscanvas
.extent
):
1939 ac
.extent
= subaxis
.axiscanvas
.extent
1940 if self
.breaklinesattrs
is not None:
1941 self
.breaklinesdist
= unit
.length(self
.breaklinesdist_str
, default_type
="v")
1942 self
.breaklineslength
= unit
.length(self
.breaklineslength_str
, default_type
="v")
1943 self
.sin
= math
.sin(self
.breaklinesangle
*math
.pi
/180.0)
1944 self
.cos
= math
.cos(self
.breaklinesangle
*math
.pi
/180.0)
1945 breaklinesextent
= (0.5*self
.breaklinesdist
*math
.fabs(self
.cos
) +
1946 0.5*self
.breaklineslength
*math
.fabs(self
.sin
))
1947 if unit
.topt(ac
.extent
) < unit
.topt(breaklinesextent
):
1948 ac
.extent
= breaklinesextent
1949 for subaxis1
, subaxis2
in zip(axis
.subaxes
[:-1], axis
.subaxes
[1:]):
1950 # use a tangent of the basepath (this is independent of the tickdirection)
1951 v
= 0.5 * (subaxis1
.vmax
+ subaxis2
.vmin
)
1952 p
= path
.normpath(axispos
.vbasepath(v
, None))
1953 breakline
= p
.tangent(0, self
.breaklineslength
)
1954 widthline
= p
.tangent(0, self
.breaklinesdist
).transformed(trafomodule
.rotate(self
.breaklinesangle
+90, *breakline
.begin()))
1955 tocenter
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(breakline
.begin(), breakline
.end()))
1956 towidth
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(widthline
.begin(), widthline
.end()))
1957 breakline
= breakline
.transformed(trafomodule
.translate(*tocenter
).rotated(self
.breaklinesangle
, *breakline
.begin()))
1958 breakline1
= breakline
.transformed(trafomodule
.translate(*towidth
))
1959 breakline2
= breakline
.transformed(trafomodule
.translate(-towidth
[0], -towidth
[1]))
1960 ac
.fill(path
.path(path
.moveto(*breakline1
.begin()),
1961 path
.lineto(*breakline1
.end()),
1962 path
.lineto(*breakline2
.end()),
1963 path
.lineto(*breakline2
.begin()),
1964 path
.closepath()), [color
.gray
.white
])
1965 ac
.stroke(breakline1
, self
.defaultbreaklinesattrs
+ self
.breaklinesattrs
)
1966 ac
.stroke(breakline2
, self
.defaultbreaklinesattrs
+ self
.breaklinesattrs
)
1967 axistitlepainter
.paint(self
, axispos
, axis
, ac
=ac
)
1971 class linksplitaxispainter(splitaxispainter
):
1972 """class for painting a linked splitaxis
1973 - the inherited splitaxispainter is used to paint the axis
1974 - modifies some constructor defaults"""
1976 __implements__
= _Iaxispainter
1978 def __init__(self
, titleattrs
=None, **kwargs
):
1979 """initializes the instance
1980 - the titleattrs default is set to None thus skipping the title
1981 - all keyword arguments are passed to splitaxispainter"""
1982 splitaxispainter
.__init
__(self
, titleattrs
=titleattrs
, **kwargs
)
1985 class baraxispainter(axistitlepainter
):
1986 """class for painting a baraxis
1987 - the inherited titleaxispainter is used to paint the title of
1989 - the baraxispainter access the multisubaxis, subaxis names, texts, and
1990 relsizes attributes"""
1992 __implements__
= _Iaxispainter
1994 defaulttickattrs
= []
1995 defaultbasepathattrs
= [style
.linecap
.square
]
1996 defaultnameattrs
= [textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
]
1998 def __init__(self
, innerticklength
=None,
1999 outerticklength
=None,
2009 """initializes the instance
2010 - innerticklength and outerticklength are a visual length of
2011 the ticks to be plotted at the axis basepath to visually
2012 separate the bars; if neither innerticklength nor
2013 outerticklength are set, not ticks are plotted
2014 - breaklinesattrs are a list of stroke attributes for the
2015 axis tick; a single entry is allowed without being a
2016 list; None turns off the ticks
2017 - namedist is a visual PyX length for the distance of the bar
2018 names from the axis basepath
2019 - nameattrs is a list of attributes for a texrunners text
2020 method; a single entry is allowed without being a list;
2021 None turns off the names
2022 - namedirection is an instance of rotatetext or None
2023 - namehequalize and namevequalize (booleans) perform an equal
2024 alignment for straight vertical and horizontal axes, respectively
2025 - futher keyword arguments are passed to axistitlepainter"""
2026 self
.innerticklength_str
= innerticklength
2027 self
.outerticklength_str
= outerticklength
2028 self
.tickattrs
= tickattrs
2029 self
.basepathattrs
= basepathattrs
2030 self
.namedist_str
= namedist
2031 self
.nameattrs
= nameattrs
2032 self
.namedirection
= namedirection
2033 self
.namepos
= namepos
2034 self
.namehequalize
= namehequalize
2035 self
.namevequalize
= namevequalize
2036 axistitlepainter
.__init
__(self
, **args
)
2038 def paint(self
, axispos
, axis
, ac
=None):
2042 raise RuntimeError("XXX") # XXX debug only
2043 if axis
.multisubaxis
is not None:
2044 for subaxis
in axis
.subaxis
:
2045 subaxis
.finish(subaxispos(subaxis
.convert
, axispos
, subaxis
.vmin
, subaxis
.vmax
, None, None))
2046 ac
.insert(subaxis
.axiscanvas
)
2047 if unit
.topt(ac
.extent
) < unit
.topt(subaxis
.axiscanvas
.extent
):
2048 ac
.extent
= subaxis
.axiscanvas
.extent
2050 for name
in axis
.names
:
2051 v
= axis
.convert((name
, self
.namepos
))
2052 x
, y
= axispos
.vtickpoint_pt(v
)
2053 dx
, dy
= axispos
.vtickdirection(v
)
2054 namepos
.append((v
, x
, y
, dx
, dy
))
2056 if self
.nameattrs
is not None:
2057 for (v
, x
, y
, dx
, dy
), name
in zip(namepos
, axis
.names
):
2058 nameattrs
= self
.defaultnameattrs
+ self
.nameattrs
2059 if self
.namedirection
is not None:
2060 nameattrs
.append(self
.namedirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
2061 if axis
.texts
.has_key(name
):
2062 nameboxes
.append(self
.texrunner
.text_pt(x
, y
, str(axis
.texts
[name
]), nameattrs
))
2063 elif axis
.texts
.has_key(str(name
)):
2064 nameboxes
.append(self
.texrunner
.text_pt(x
, y
, str(axis
.texts
[str(name
)]), nameattrs
))
2066 nameboxes
.append(self
.texrunner
.text_pt(x
, y
, str(name
), nameattrs
))
2067 labeldist
= ac
.extent
+ unit
.length(self
.namedist_str
, default_type
="v")
2068 if len(namepos
) > 1:
2070 for np
in namepos
[1:]:
2071 if np
[3] != namepos
[0][3] or np
[4] != namepos
[0][4]:
2075 if equaldirection
and ((not namepos
[0][3] and self
.namevequalize
) or
2076 (not namepos
[0][4] and self
.namehequalize
)):
2077 box
.linealignequal(nameboxes
, labeldist
, -namepos
[0][3], -namepos
[0][4])
2079 for namebox
, np
in zip(nameboxes
, namepos
):
2080 namebox
.linealign(labeldist
, -np
[3], -np
[4])
2081 if self
.basepathattrs
is not None:
2082 p
= axispos
.vbasepath()
2084 ac
.stroke(p
, self
.defaultbasepathattrs
+ self
.basepathattrs
)
2085 if self
.tickattrs
is not None and (self
.innerticklength_str
is not None or
2086 self
.outerticklength_str
is not None):
2087 if self
.innerticklength_str
is not None:
2088 innerticklength
= unit
.length(self
.innerticklength_str
, default_type
="v")
2089 innerticklength_pt
= unit
.topt(innerticklength
)
2090 if unit
.topt(ac
.extent
) < -innerticklength_pt
:
2091 ac
.extent
= -innerticklength
2092 elif self
.outerticklength_str
is not None:
2093 innerticklength
= innerticklength_pt
= 0
2094 if self
.outerticklength_str
is not None:
2095 outerticklength
= unit
.length(self
.outerticklength_str
, default_type
="v")
2096 outerticklength_pt
= unit
.topt(outerticklength
)
2097 if unit
.topt(ac
.extent
) < outerticklength_pt
:
2098 ac
.extent
= outerticklength
2099 elif self
.innerticklength_str
is not None:
2100 outerticklength
= outerticklength_pt
= 0
2101 for pos
in axis
.relsizes
:
2102 if pos
== axis
.relsizes
[0]:
2103 pos
-= axis
.firstdist
2104 elif pos
!= axis
.relsizes
[-1]:
2105 pos
-= 0.5 * axis
.dist
2106 v
= pos
/ axis
.relsizes
[-1]
2107 x
, y
= axispos
.vtickpoint_pt(v
)
2108 dx
, dy
= axispos
.vtickdirection(v
)
2109 x1
= x
+ dx
* innerticklength_pt
2110 y1
= y
+ dy
* innerticklength_pt
2111 x2
= x
- dx
* outerticklength_pt
2112 y2
= y
- dy
* outerticklength_pt
2113 ac
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), self
.defaulttickattrs
+ self
.tickattrs
)
2114 for (v
, x
, y
, dx
, dy
), namebox
in zip(namepos
, nameboxes
):
2115 newextent
= namebox
.extent(dx
, dy
) + labeldist
2116 if unit
.topt(ac
.extent
) < unit
.topt(newextent
):
2117 ac
.extent
= newextent
2118 for namebox
in nameboxes
:
2120 axistitlepainter
.paint(self
, axispos
, axis
, ac
=ac
)
2124 class linkbaraxispainter(baraxispainter
):
2125 """class for painting a linked baraxis
2126 - the inherited baraxispainter is used to paint the axis
2127 - modifies some constructor defaults"""
2129 __implements__
= _Iaxispainter
2131 def __init__(self
, nameattrs
=None, titleattrs
=None, **kwargs
):
2132 """initializes the instance
2133 - the titleattrs default is set to None thus skipping the title
2134 - the nameattrs default is set to None thus skipping the names
2135 - all keyword arguments are passed to axispainter"""
2136 baraxispainter
.__init
__(self
, nameattrs
=nameattrs
, titleattrs
=titleattrs
, **kwargs
)
2139 ################################################################################
2141 ################################################################################
2145 """interface definition of a axis
2146 - an axis should implement an convert and invert method like
2147 _Imap, but this is not part of this interface definition;
2148 one possibility is to mix-in a proper map class, but special
2149 purpose axes might do something else
2150 - an axis has the instance variable axiscanvas after the finish
2152 - an axis might have further instance variables (title, ticks)
2153 to be used in combination with appropriate axispainters"""
2155 def convert(self
, x
):
2156 "convert a value into graph coordinates"
2158 def invert(self
, v
):
2159 "invert a graph coordinate to a axis value"
2161 def getrelsize(self
):
2162 """returns the relative size (width) of the axis
2163 - for use in splitaxis, baraxis etc.
2164 - might return None if no size is available"""
2166 def setrange(self
, min=None, max=None):
2167 """set the axis data range
2168 - the type of min and max must fit to the axis
2169 - min<max; the axis might be reversed, but this is
2170 expressed internally only (min<max all the time)
2171 - the axis might not apply the change of the range
2172 (e.g. when the axis range is fixed by the user),
2173 but usually the range is extended to contain the
2175 - for invalid parameters (e.g. negativ values at an
2176 logarithmic axis), an exception should be raised
2177 - a RuntimeError is raised, when setrange is called
2178 after the finish method"""
2181 """return data range as a tuple (min, max)
2182 - min<max; the axis might be reversed, but this is
2183 expressed internally only
2184 - a RuntimeError exception is raised when no
2185 range is available"""
2187 def finish(self
, axispos
):
2188 """finishes the axis
2189 - axispos implements _Iaxispos
2190 - sets the instance axiscanvas, which is insertable into the
2191 graph to finally paint the axis
2192 - any modification of the axis range should be disabled after
2193 the finish method was called"""
2194 # TODO: be more specific about exceptions
2196 def createlinkaxis(self
, **kwargs
):
2197 """create a link axis to the axis itself
2198 - typically, a link axis is a axis, which share almost
2199 all properties with the axis it is linked to
2200 - typically, the painter gets replaced by a painter
2201 which doesn't put any text to the axis"""
2205 """base implementation a regular axis
2206 - typical usage is to mix-in a linmap or a logmap to
2207 complete the axis interface
2208 - note that some methods of this class want to access a
2209 parter and a rater; those attributes implementing _Iparter
2210 and _Irater should be initialized by the constructors
2211 of derived classes"""
2213 def __init__(self
, min=None, max=None, reverse
=0, divisor
=1,
2214 title
=None, painter
=axispainter(), texter
=defaulttexter(),
2215 density
=1, maxworse
=2, manualticks
=[]):
2216 """initializes the instance
2217 - min and max fix the axis minimum and maximum, respectively;
2218 they are determined by the data to be plotted, when not fixed
2219 - reverse (boolean) reverses the minimum and the maximum of
2221 - numerical divisor for the axis partitioning
2222 - title is a string containing the axis title
2223 - axispainter is the axis painter (should implement _Ipainter)
2224 - texter is the texter (should implement _Itexter)
2225 - density is a global parameter for the axis paritioning and
2226 axis rating; its default is 1, but the range 0.5 to 2.5 should
2227 be usefull to get less or more ticks by the automatic axis
2229 - maxworse is a number of trials with worse tick rating
2230 before giving up (usually it should not be needed to increase
2231 this value; increasing the number will slow down the automatic
2232 axis partitioning considerably)
2233 - manualticks and the partitioner results are mixed
2235 - note that some methods of this class want to access a
2236 parter and a rater; those attributes implementing _Iparter
2237 and _Irater should be initialized by the constructors
2238 of derived classes"""
2239 if min is not None and max is not None and min > max:
2240 min, max, reverse
= max, min, not reverse
2241 self
.fixmin
, self
.fixmax
, self
.min, self
.max, self
.reverse
= min is not None, max is not None, min, max, reverse
2242 self
.divisor
= divisor
2244 self
.painter
= painter
2245 self
.texter
= texter
2246 self
.density
= density
2247 self
.maxworse
= maxworse
2248 self
.manualticks
= self
.checkfraclist(manualticks
)
2250 self
.axiscanvas
= None
2253 def _setrange(self
, min=None, max=None):
2254 if not self
.fixmin
and min is not None and (self
.min is None or min < self
.min):
2256 if not self
.fixmax
and max is not None and (self
.max is None or max > self
.max):
2258 if None not in (self
.min, self
.max):
2261 self
.setbasepoints(((self
.min, 1), (self
.max, 0)))
2263 self
.setbasepoints(((self
.min, 0), (self
.max, 1)))
2265 def _getrange(self
):
2266 return self
.min, self
.max
2268 def _forcerange(self
, range):
2269 self
.min, self
.max = range
2272 def setrange(self
, min=None, max=None):
2273 oldmin
, oldmax
= self
.min, self
.max
2274 self
._setrange
(min, max)
2275 if self
.axiscanvas
is not None and ((oldmin
!= self
.min) or (oldmax
!= self
.max)):
2276 raise RuntimeError("range modification while axis was already finished")
2279 if self
.min is not None and self
.max is not None:
2280 return self
.min, self
.max
2282 def checkfraclist(self
, fracs
):
2283 "orders a list of fracs, equal entries are not allowed"
2284 if not len(fracs
): return []
2285 sorted = list(fracs
)
2288 for item
in sorted[1:]:
2290 raise ValueError("duplicate entry found")
2294 def finish(self
, axispos
):
2295 if self
.axiscanvas
is not None: return
2297 # lesspart and morepart can be called after defaultpart;
2298 # this works although some axes may share their autoparting,
2299 # because the axes are processed sequentially
2301 if self
.parter
is not None:
2302 min, max = self
.getrange()
2303 self
.ticks
= _mergeticklists(self
.manualticks
,
2304 self
.parter
.defaultpart(min/self
.divisor
,
2309 nextpart
= self
.parter
.lesspart
2310 while nextpart
is not None:
2311 newticks
= nextpart()
2312 if newticks
is not None:
2313 newticks
= _mergeticklists(self
.manualticks
, newticks
)
2315 bestrate
= self
.rater
.rateticks(self
, self
.ticks
, self
.density
)
2316 bestrate
+= self
.rater
.raterange(self
.convert(float(self
.ticks
[-1])/self
.divisor
)-
2317 self
.convert(float(self
.ticks
[0])/self
.divisor
), 1)
2318 variants
= [[bestrate
, self
.ticks
]]
2320 newrate
= self
.rater
.rateticks(self
, newticks
, self
.density
)
2321 newrate
+= self
.rater
.raterange(self
.convert(float(newticks
[-1])/self
.divisor
)-
2322 self
.convert(float(newticks
[0])/self
.divisor
), 1)
2323 variants
.append([newrate
, newticks
])
2324 if newrate
< bestrate
:
2331 if worse
== self
.maxworse
and nextpart
== self
.parter
.lesspart
:
2333 nextpart
= self
.parter
.morepart
2334 if worse
== self
.maxworse
and nextpart
== self
.parter
.morepart
:
2337 self
.ticks
=self
.manualticks
2339 # rating, when several choises are available
2342 if self
.painter
is not None:
2345 while i
< len(variants
) and (bestrate
is None or variants
[i
][0] < bestrate
):
2346 saverange
= self
._getrange
()
2347 self
.ticks
= variants
[i
][1]
2349 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2350 self
.texter
.labels(self
.ticks
)
2351 ac
= self
.painter
.paint(axispos
, self
)
2352 ratelayout
= self
.rater
.ratelayout(ac
, self
.density
)
2353 if ratelayout
is not None:
2354 variants
[i
][0] += ratelayout
2355 variants
[i
].append(ac
)
2357 variants
[i
][0] = None
2358 if variants
[i
][0] is not None and (bestrate
is None or variants
[i
][0] < bestrate
):
2359 bestrate
= variants
[i
][0]
2360 self
._forcerange
(saverange
)
2362 if bestrate
is None:
2363 raise RuntimeError("no valid axis partitioning found")
2364 variants
= [variant
for variant
in variants
[:i
] if variant
[0] is not None]
2366 self
.ticks
= variants
[0][1]
2368 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2369 self
.axiscanvas
= variants
[0][2]
2371 self
.ticks
= variants
[0][1]
2372 self
.texter
.labels(self
.ticks
)
2374 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2375 self
.axiscanvas
= axiscanvas()
2378 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2379 self
.texter
.labels(self
.ticks
)
2380 if self
.painter
is not None:
2381 self
.axiscanvas
= self
.painter
.paint(axispos
, self
)
2383 self
.axiscanvas
= axiscanvas()
2385 def createlinkaxis(self
, **args
):
2386 return linkaxis(self
, **args
)
2389 class linaxis(_axis
, _linmap
):
2390 """implementation of a linear axis"""
2392 __implements__
= _Iaxis
2394 def __init__(self
, parter
=autolinparter(), rater
=axisrater(), **args
):
2395 """initializes the instance
2396 - the parter attribute implements _Iparter
2397 - manualticks and the partitioner results are mixed
2399 - the rater implements _Irater and is used to rate different
2400 tick lists created by the partitioner (after merging with
2402 - futher keyword arguments are passed to _axis"""
2403 _axis
.__init
__(self
, **args
)
2404 if self
.fixmin
and self
.fixmax
:
2405 self
.relsize
= self
.max - self
.min
2406 self
.parter
= parter
2410 class logaxis(_axis
, _logmap
):
2411 """implementation of a logarithmic axis"""
2413 __implements__
= _Iaxis
2415 def __init__(self
, parter
=autologparter(), rater
=axisrater(ticks
=axisrater
.logticks
, labels
=axisrater
.loglabels
), **args
):
2416 """initializes the instance
2417 - the parter attribute implements _Iparter
2418 - manualticks and the partitioner results are mixed
2420 - the rater implements _Irater and is used to rate different
2421 tick lists created by the partitioner (after merging with
2423 - futher keyword arguments are passed to _axis"""
2424 _axis
.__init
__(self
, **args
)
2425 if self
.fixmin
and self
.fixmax
:
2426 self
.relsize
= math
.log(self
.max) - math
.log(self
.min)
2427 self
.parter
= parter
2432 """a axis linked to an already existing regular axis
2433 - almost all properties of the axis are "copied" from the
2434 axis this axis is linked to
2435 - usually, linked axis are used to create an axis to an
2436 existing axis with different painting properties; linked
2437 axis can be used to plot an axis twice at the opposite
2438 sides of a graphxy or even to share an axis between
2439 different graphs!"""
2441 __implements__
= _Iaxis
2443 def __init__(self
, linkedaxis
, painter
=linkaxispainter()):
2444 """initializes the instance
2445 - it gets a axis this linkaxis is linked to
2446 - it gets a painter to be used for this linked axis"""
2447 self
.linkedaxis
= linkedaxis
2448 self
.painter
= painter
2449 self
.axiscanvas
= None
2451 def __getattr__(self
, attr
):
2452 """access to unkown attributes are handed over to the
2453 axis this linkaxis is linked to"""
2454 return getattr(self
.linkedaxis
, attr
)
2456 def finish(self
, axispos
):
2457 """finishes the axis
2458 - instead of performing the hole finish process
2459 (paritioning, rating, etc.) just a painter call
2461 if self
.axiscanvas
is None:
2462 if self
.linkedaxis
.axiscanvas
is None:
2463 raise RuntimeError("link axis finish method called before the finish method of the original axis")
2464 self
.axiscanvas
= self
.painter
.paint(axispos
, self
)
2468 """implementation of a split axis
2469 - a split axis contains several (sub-)axes with
2470 non-overlapping data ranges -- between these subaxes
2471 the axis is "splitted"
2472 - (just to get sure: a splitaxis can contain other
2473 splitaxes as its subaxes)
2474 - a splitaxis implements the _Iaxispos for its subaxes
2475 by inheritance from _subaxispos"""
2477 __implements__
= _Iaxis
, _Iaxispos
2479 def __init__(self
, subaxes
, splitlist
=[0.5], splitdist
=0.1, relsizesplitdist
=1,
2480 title
=None, painter
=splitaxispainter()):
2481 """initializes the instance
2482 - subaxes is a list of subaxes
2483 - splitlist is a list of graph coordinates, where the splitting
2484 of the main axis should be performed; if the list isn't long enough
2485 for the subaxes, missing entries are considered to be None
2486 - splitdist is the size of the splitting in graph coordinates, when
2487 the associated splitlist entry is not None
2488 - relsizesplitdist: a None entry in splitlist means, that the
2489 position of the splitting should be calculated out of the
2490 relsize values of conrtibuting subaxes (the size of the
2491 splitting is relsizesplitdist in values of the relsize values
2493 - title is the title of the axis as a string
2494 - painter is the painter of the axis; it should be specialized to
2496 - the relsize of the splitaxis is the sum of the relsizes of the
2497 subaxes including the relsizesplitdist"""
2498 self
.subaxes
= subaxes
2499 self
.painter
= painter
2501 self
.splitlist
= splitlist
2502 for subaxis
in self
.subaxes
:
2505 self
.subaxes
[0].vmin
= 0
2506 self
.subaxes
[0].vminover
= None
2507 self
.subaxes
[-1].vmax
= 1
2508 self
.subaxes
[-1].vmaxover
= None
2509 for i
in xrange(len(self
.splitlist
)):
2510 if self
.splitlist
[i
] is not None:
2511 self
.subaxes
[i
].vmax
= self
.splitlist
[i
] - 0.5*splitdist
2512 self
.subaxes
[i
].vmaxover
= self
.splitlist
[i
]
2513 self
.subaxes
[i
+1].vmin
= self
.splitlist
[i
] + 0.5*splitdist
2514 self
.subaxes
[i
+1].vminover
= self
.splitlist
[i
]
2516 while i
< len(self
.subaxes
):
2517 if self
.subaxes
[i
].vmax
is None:
2518 j
= relsize
= relsize2
= 0
2519 while self
.subaxes
[i
+ j
].vmax
is None:
2520 relsize
+= self
.subaxes
[i
+ j
].relsize
+ relsizesplitdist
2522 relsize
+= self
.subaxes
[i
+ j
].relsize
2523 vleft
= self
.subaxes
[i
].vmin
2524 vright
= self
.subaxes
[i
+ j
].vmax
2525 for k
in range(i
, i
+ j
):
2526 relsize2
+= self
.subaxes
[k
].relsize
2527 self
.subaxes
[k
].vmax
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2528 relsize2
+= 0.5 * relsizesplitdist
2529 self
.subaxes
[k
].vmaxover
= self
.subaxes
[k
+ 1].vminover
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2530 relsize2
+= 0.5 * relsizesplitdist
2531 self
.subaxes
[k
+1].vmin
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2532 if i
== 0 and i
+ j
+ 1 == len(self
.subaxes
):
2533 self
.relsize
= relsize
2538 self
.fixmin
= self
.subaxes
[0].fixmin
2540 self
.min = self
.subaxes
[0].min
2541 self
.fixmax
= self
.subaxes
[-1].fixmax
2543 self
.max = self
.subaxes
[-1].max
2545 self
.axiscanvas
= None
2548 min = self
.subaxes
[0].getrange()
2549 max = self
.subaxes
[-1].getrange()
2551 return min[0], max[1]
2555 def setrange(self
, min, max):
2556 self
.subaxes
[0].setrange(min, None)
2557 self
.subaxes
[-1].setrange(None, max)
2559 def convert(self
, value
):
2560 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
2561 if value
< self
.subaxes
[0].max:
2562 return self
.subaxes
[0].vmin
+ self
.subaxes
[0].convert(value
)*(self
.subaxes
[0].vmax
-self
.subaxes
[0].vmin
)
2563 for axis
in self
.subaxes
[1:-1]:
2564 if value
> axis
.min and value
< axis
.max:
2565 return axis
.vmin
+ axis
.convert(value
)*(axis
.vmax
-axis
.vmin
)
2566 if value
> self
.subaxes
[-1].min:
2567 return self
.subaxes
[-1].vmin
+ self
.subaxes
[-1].convert(value
)*(self
.subaxes
[-1].vmax
-self
.subaxes
[-1].vmin
)
2568 raise ValueError("value couldn't be assigned to a split region")
2570 def finish(self
, axispos
):
2571 if self
.axiscanvas
is None:
2572 self
.axiscanvas
= self
.painter
.paint(axispos
, self
)
2574 def createlinkaxis(self
, **args
):
2575 return linksplitaxis(self
, **args
)
2578 class omitsubaxispainter
: pass
2580 class linksplitaxis(linkaxis
):
2581 """a splitaxis linked to an already existing splitaxis
2582 - inherits the access to a linked axis -- as before,
2583 basically only the painter is replaced
2584 - it takes care of the creation of linked axes of
2587 __implements__
= _Iaxis
2589 def __init__(self
, linkedaxis
, painter
=linksplitaxispainter(), subaxispainter
=omitsubaxispainter
):
2590 """initializes the instance
2591 - linkedaxis is the axis this axis becomes linked to
2592 - painter is axispainter instance for this linked axis
2593 - subaxispainter is a changeable painter to be used for linked
2594 subaxes; if omitsubaxispainter the createlinkaxis method of
2595 the subaxis are called without a painter parameter"""
2596 linkaxis
.__init
__(self
, linkedaxis
, painter
=painter
)
2598 for subaxis
in linkedaxis
.subaxes
:
2599 painter
= attr
.selectattr(subaxispainter
, len(self
.subaxes
), len(linkedaxis
.subaxes
))
2600 if painter
is omitsubaxispainter
:
2601 self
.subaxes
.append(subaxis
.createlinkaxis())
2603 self
.subaxes
.append(subaxis
.createlinkaxis(painter
=painter
))
2607 """implementation of a axis for bar graphs
2608 - a bar axes is different from a splitaxis by the way it
2609 selects its subaxes: the convert method gets a list,
2610 where the first entry is a name selecting a subaxis out
2611 of a list; instead of the term "bar" or "subaxis" the term
2612 "item" will be used here
2613 - the baraxis stores a list of names be identify the items;
2614 the names might be of any time (strings, integers, etc.);
2615 the names can be printed as the titles for the items, but
2616 alternatively the names might be transformed by the texts
2617 dictionary, which maps a name to a text to be used to label
2618 the items in the painter
2619 - usually, there is only one subaxis, which is used as
2620 the subaxis for all items
2621 - alternatively it is also possible to use another baraxis
2622 as a multisubaxis; it is copied via the createsubaxis
2623 method whenever another subaxis is needed (by that a
2624 nested bar axis with a different number of subbars at
2625 each item can be created)
2626 - any axis can be a subaxis of a baraxis; if no subaxis
2627 is specified at all, the baraxis simulates a linear
2628 subaxis with a fixed range of 0 to 1
2629 - a splitaxis implements the _Iaxispos for its subaxes
2630 by inheritance from _subaxispos when the multisubaxis
2631 feature is turned on"""
2633 def __init__(self
, subaxis
=None, multisubaxis
=None, title
=None,
2634 dist
=0.5, firstdist
=None, lastdist
=None, names
=None,
2635 texts
={}, painter
=baraxispainter()):
2636 """initialize the instance
2637 - subaxis contains a axis to be used as the subaxis
2639 - multisubaxis might contain another baraxis instance
2640 to be used to construct a new subaxis for each item;
2641 (by that a nested bar axis with a different number
2642 of subbars at each item can be created)
2643 - only one of subaxis or multisubaxis can be set; if neither
2644 of them is set, the baraxis behaves like having a linaxis
2645 as its subaxis with a fixed range 0 to 1
2646 - the title attribute contains the axis title as a string
2647 - the dist is a relsize to be used as the distance between
2649 - the firstdist and lastdist are the distance before the
2650 first and after the last item, respectively; when set
2651 to None (the default), 0.5*dist is used
2652 - names is a predefined list of names to identify the
2653 items; if set, the name list is fixed
2654 - texts is a dictionary transforming a name to a text in
2655 the painter; if a name isn't found in the dictionary
2657 - the relsize of the baraxis is the sum of the
2658 relsizes including all distances between the items"""
2660 if firstdist
is not None:
2661 self
.firstdist
= firstdist
2663 self
.firstdist
= 0.5 * dist
2664 if lastdist
is not None:
2665 self
.lastdist
= lastdist
2667 self
.lastdist
= 0.5 * dist
2668 self
.relsizes
= None
2671 for name
in helper
.ensuresequence(names
):
2673 self
.fixnames
= names
is not None
2674 self
.multisubaxis
= multisubaxis
2675 if self
.multisubaxis
is not None:
2676 if subaxis
is not None:
2677 raise RuntimeError("either use subaxis or multisubaxis")
2678 self
.subaxis
= [self
.createsubaxis() for name
in self
.names
]
2680 self
.subaxis
= subaxis
2684 self
.painter
= painter
2685 self
.axiscanvas
= None
2687 def createsubaxis(self
):
2688 return baraxis(subaxis
=self
.multisubaxis
.subaxis
,
2689 multisubaxis
=self
.multisubaxis
.multisubaxis
,
2690 title
=self
.multisubaxis
.title
,
2691 dist
=self
.multisubaxis
.dist
,
2692 firstdist
=self
.multisubaxis
.firstdist
,
2693 lastdist
=self
.multisubaxis
.lastdist
,
2694 names
=self
.multisubaxis
.names
,
2695 texts
=self
.multisubaxis
.texts
,
2696 painter
=self
.multisubaxis
.painter
)
2699 # TODO: we do not yet have a proper range handling for a baraxis
2702 def setrange(self
, min=None, max=None):
2703 # TODO: we do not yet have a proper range handling for a baraxis
2704 raise RuntimeError("range handling for a baraxis is not implemented")
2706 def setname(self
, name
, *subnames
):
2707 """add a name to identify an item at the baraxis
2708 - by using subnames, nested name definitions are
2710 - a style (or the user itself) might use this to
2711 insert new items into a baraxis
2712 - setting self.relsizes to None forces later recalculation"""
2713 if not self
.fixnames
:
2714 if name
not in self
.names
:
2715 self
.relsizes
= None
2716 self
.names
.append(name
)
2717 if self
.multisubaxis
is not None:
2718 self
.subaxis
.append(self
.createsubaxis())
2719 if (not self
.fixnames
or name
in self
.names
) and len(subnames
):
2720 if self
.multisubaxis
is not None:
2721 if self
.subaxis
[self
.names
.index(name
)].setname(*subnames
):
2722 self
.relsizes
= None
2724 if self
.subaxis
.setname(*subnames
):
2725 self
.relsizes
= None
2726 return self
.relsizes
is not None
2728 def updaterelsizes(self
):
2729 # guess what it does: it recalculates relsize attribute
2730 self
.relsizes
= [i
*self
.dist
+ self
.firstdist
for i
in range(len(self
.names
) + 1)]
2731 self
.relsizes
[-1] += self
.lastdist
- self
.dist
2732 if self
.multisubaxis
is not None:
2734 for i
in range(1, len(self
.relsizes
)):
2735 self
.subaxis
[i
-1].updaterelsizes()
2736 subrelsize
+= self
.subaxis
[i
-1].relsizes
[-1]
2737 self
.relsizes
[i
] += subrelsize
2739 if self
.subaxis
is None:
2742 self
.subaxis
.updaterelsizes()
2743 subrelsize
= self
.subaxis
.relsizes
[-1]
2744 for i
in range(1, len(self
.relsizes
)):
2745 self
.relsizes
[i
] += i
* subrelsize
2747 def convert(self
, value
):
2748 """baraxis convert method
2749 - the value should be a list, where the first entry is
2750 a member of the names (set in the constructor or by the
2751 setname method); this first entry identifies an item in
2753 - following values are passed to the appropriate subaxis
2755 - when there is no subaxis, the convert method will behave
2756 like having a linaxis from 0 to 1 as subaxis"""
2757 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
2758 if not self
.relsizes
:
2759 self
.updaterelsizes()
2760 pos
= self
.names
.index(value
[0])
2762 if self
.subaxis
is None:
2765 if self
.multisubaxis
is not None:
2766 subvalue
= value
[1] * self
.subaxis
[pos
].relsizes
[-1]
2768 subvalue
= value
[1] * self
.subaxis
.relsizes
[-1]
2770 if self
.multisubaxis
is not None:
2771 subvalue
= self
.subaxis
[pos
].convert(value
[1:]) * self
.subaxis
[pos
].relsizes
[-1]
2773 subvalue
= self
.subaxis
.convert(value
[1:]) * self
.subaxis
.relsizes
[-1]
2774 return (self
.relsizes
[pos
] + subvalue
) / float(self
.relsizes
[-1])
2776 def finish(self
, axispos
):
2777 if self
.axiscanvas
is None:
2778 if self
.multisubaxis
is not None:
2779 for name
, subaxis
in zip(self
.names
, self
.subaxis
):
2780 subaxis
.vmin
= self
.convert((name
, 0))
2781 subaxis
.vmax
= self
.convert((name
, 1))
2782 self
.axiscanvas
= self
.painter
.paint(axispos
, self
)
2784 def createlinkaxis(self
, **args
):
2785 return linkbaraxis(self
, **args
)
2788 class linkbaraxis(linkaxis
):
2789 """a baraxis linked to an already existing baraxis
2790 - inherits the access to a linked axis -- as before,
2791 basically only the painter is replaced
2792 - it must take care of the creation of linked axes of
2795 __implements__
= _Iaxis
2797 def __init__(self
, linkedaxis
, painter
=linkbaraxispainter()):
2798 """initializes the instance
2799 - it gets a axis this linkaxis is linked to
2800 - it gets a painter to be used for this linked axis"""
2801 linkaxis
.__init
__(self
, linkedaxis
, painter
=painter
)
2802 if self
.multisubaxis
is not None:
2803 self
.subaxis
= [subaxis
.createlinkaxis() for subaxis
in self
.linkedaxis
.subaxis
]
2804 elif self
.subaxis
is not None:
2805 self
.subaxis
= self
.subaxis
.createlinkaxis()
2808 def pathaxis(path
, axis
, **kwargs
):
2809 """creates an axiscanvas for an axis along a path"""
2810 mypathaxispos
= pathaxispos(path
, axis
.convert
, **kwargs
)
2811 axis
.finish(mypathaxispos
)
2812 return axis
.axiscanvas
2814 ################################################################################
2816 ################################################################################
2819 # g = graph.graphxy(key=graph.key())
2820 # g.addkey(graph.key(), ...)
2825 def __init__(self
, dist
="0.2 cm", pos
= "tr", hinside
= 1, vinside
= 1, hdist
="0.6 cm", vdist
="0.4 cm",
2826 symbolwidth
="0.5 cm", symbolheight
="0.25 cm", symbolspace
="0.2 cm",
2827 textattrs
=textmodule
.vshift
.mathaxis
):
2828 self
.dist_str
= dist
2830 self
.hinside
= hinside
2831 self
.vinside
= vinside
2832 self
.hdist_str
= hdist
2833 self
.vdist_str
= vdist
2834 self
.symbolwidth_str
= symbolwidth
2835 self
.symbolheight_str
= symbolheight
2836 self
.symbolspace_str
= symbolspace
2837 self
.textattrs
= textattrs
2838 self
.plotinfos
= None
2839 if self
.pos
in ("tr", "rt"):
2842 elif self
.pos
in ("br", "rb"):
2845 elif self
.pos
in ("tl", "lt"):
2848 elif self
.pos
in ("bl", "lb"):
2852 raise RuntimeError("invalid pos attribute")
2854 def setplotinfos(self
, *plotinfos
):
2855 """set the plotinfos to be used in the key
2856 - call it exactly once
2857 - plotinfo instances with title == None are ignored"""
2858 if self
.plotinfos
is not None:
2859 raise RuntimeError("setplotinfo is called multiple times")
2860 self
.plotinfos
= [plotinfo
for plotinfo
in plotinfos
if plotinfo
.data
.title
is not None]
2862 def dolayout(self
, graph
):
2863 "creates the layout of the key"
2864 self
.dist_pt
= unit
.topt(unit
.length(self
.dist_str
, default_type
="v"))
2865 self
.hdist_pt
= unit
.topt(unit
.length(self
.hdist_str
, default_type
="v"))
2866 self
.vdist_pt
= unit
.topt(unit
.length(self
.vdist_str
, default_type
="v"))
2867 self
.symbolwidth_pt
= unit
.topt(unit
.length(self
.symbolwidth_str
, default_type
="v"))
2868 self
.symbolheight_pt
= unit
.topt(unit
.length(self
.symbolheight_str
, default_type
="v"))
2869 self
.symbolspace_pt
= unit
.topt(unit
.length(self
.symbolspace_str
, default_type
="v"))
2871 for plotinfo
in self
.plotinfos
:
2872 self
.titles
.append(graph
.texrunner
.text_pt(0, 0, plotinfo
.data
.title
, helper
.ensuresequence(self
.textattrs
)))
2873 box
.tile_pt(self
.titles
, self
.dist_pt
, 0, -1)
2874 box
.linealignequal_pt(self
.titles
, self
.symbolwidth_pt
+ self
.symbolspace_pt
, 1, 0)
2877 """return a bbox for the key
2878 method should be called after dolayout"""
2879 result
= bbox
.bbox()
2880 for title
in self
.titles
:
2881 result
+= title
.bbox() + bbox
._bbox
(0, title
.center
[1] - 0.5 * self
.symbolheight_pt
,
2882 0, title
.center
[1] + 0.5 * self
.symbolheight_pt
)
2885 def paint(self
, c
, x
, y
):
2886 """paint the graph key into a canvas c at the position x and y (in postscript points)
2887 - method should be called after dolayout
2888 - the x, y alignment might be calculated by the graph using:
2889 - the bbox of the key as returned by the keys bbox method
2890 - the attributes hdist_pt, vdist_pt, hinside, and vinside of the key
2891 - the dimension and geometry of the graph"""
2892 sc
= c
.insert(canvas
.canvas(trafomodule
.translate_pt(x
, y
)))
2893 for plotinfo
, title
in zip(self
.plotinfos
, self
.titles
):
2894 plotinfo
.style
.key(sc
, 0, -0.5 * self
.symbolheight_pt
+ title
.center
[1],
2895 self
.symbolwidth_pt
, self
.symbolheight_pt
)
2899 ################################################################################
2901 ################################################################################
2905 """an axispos linear along a line with a fix direction for the ticks"""
2907 __implements__
= _Iaxispos
2909 def __init__(self
, convert
, x1
, y1
, x2
, y2
, fixtickdirection
):
2910 """initializes the instance
2911 - only the convert method is needed from the axis
2912 - x1, y1, x2, y2 are PyX length"""
2913 self
.convert
= convert
2918 self
.x1_pt
= unit
.topt(x1
)
2919 self
.y1_pt
= unit
.topt(y1
)
2920 self
.x2_pt
= unit
.topt(x2
)
2921 self
.y2_pt
= unit
.topt(y2
)
2922 self
.fixtickdirection
= fixtickdirection
2924 def vbasepath(self
, v1
=None, v2
=None):
2929 return path
.line_pt((1-v1
)*self
.x1_pt
+v1
*self
.x2_pt
,
2930 (1-v1
)*self
.y1_pt
+v1
*self
.y2_pt
,
2931 (1-v2
)*self
.x1_pt
+v2
*self
.x2_pt
,
2932 (1-v2
)*self
.y1_pt
+v2
*self
.y2_pt
)
2934 def basepath(self
, x1
=None, x2
=None):
2938 v1
= self
.convert(x1
)
2942 v2
= self
.convert(x2
)
2943 return path
.line_pt((1-v1
)*self
.x1_pt
+v1
*self
.x2_pt
,
2944 (1-v1
)*self
.y1_pt
+v1
*self
.y2_pt
,
2945 (1-v2
)*self
.x1_pt
+v2
*self
.x2_pt
,
2946 (1-v2
)*self
.y1_pt
+v2
*self
.y2_pt
)
2948 def gridpath(self
, x
):
2949 raise RuntimeError("gridpath not available")
2951 def vgridpath(self
, v
):
2952 raise RuntimeError("gridpath not available")
2954 def vtickpoint_pt(self
, v
):
2955 return (1-v
)*self
.x1_pt
+v
*self
.x2_pt
, (1-v
)*self
.y1_pt
+v
*self
.y2_pt
2957 def vtickpoint(self
, v
):
2958 return (1-v
)*self
.x1
+v
*self
.x2
, (1-v
)*self
.y1
+v
*self
.y2
2960 def tickpoint_pt(self
, x
):
2962 return (1-v
)*self
.x1_pt
+v
*self
.x2_pt
, (1-v
)*self
.y1_pt
+v
*self
.y2_pt
2964 def tickpoint(self
, x
):
2966 return (1-v
)*self
.x1
+v
*self
.x2
, (1-v
)*self
.y1
+v
*self
.y2
2968 def tickdirection(self
, x
):
2969 return self
.fixtickdirection
2971 def vtickdirection(self
, v
):
2972 return self
.fixtickdirection
2975 class lineaxisposlinegrid(lineaxispos
):
2976 """an axispos linear along a line with a fix direction for the ticks"""
2978 __implements__
= _Iaxispos
2980 def __init__(self
, convert
, x1
, y1
, x2
, y2
, fixtickdirection
, startgridlength
, endgridlength
):
2981 """initializes the instance
2982 - only the convert method is needed from the axis
2983 - x1, y1, x2, y2 are PyX length"""
2984 lineaxispos
.__init
__(self
, convert
, x1
, y1
, x2
, y2
, fixtickdirection
)
2985 self
.startgridlength
= startgridlength
2986 self
.endgridlength
= endgridlength
2987 self
.startgridlength_pt
= unit
.topt(self
.startgridlength
)
2988 self
.endgridlength_pt
= unit
.topt(self
.endgridlength
)
2990 def gridpath(self
, x
):
2992 return path
.line_pt((1-v
)*self
.x1_pt
+v
*self
.x2_pt
+self
.fixtickdirection
[0]*self
.startgridlength_pt
,
2993 (1-v
)*self
.y1_pt
+v
*self
.y2_pt
+self
.fixtickdirection
[1]*self
.startgridlength_pt
,
2994 (1-v
)*self
.x1_pt
+v
*self
.x2_pt
+self
.fixtickdirection
[0]*self
.endgridlength_pt
,
2995 (1-v
)*self
.y1_pt
+v
*self
.y2_pt
+self
.fixtickdirection
[1]*self
.endgridlength_pt
)
2997 def vgridpath(self
, v
):
2998 return path
.line_pt((1-v
)*self
.x1_pt
+v
*self
.x2_pt
+self
.fixtickdirection
[0]*self
.startgridlength_pt
,
2999 (1-v
)*self
.y1_pt
+v
*self
.y2_pt
+self
.fixtickdirection
[1]*self
.startgridlength_pt
,
3000 (1-v
)*self
.x1_pt
+v
*self
.x2_pt
+self
.fixtickdirection
[0]*self
.endgridlength_pt
,
3001 (1-v
)*self
.y1_pt
+v
*self
.y2_pt
+self
.fixtickdirection
[1]*self
.endgridlength_pt
)
3006 def __init__(self
, data
, style
):
3012 class graphxy(canvas
.canvas
):
3018 def __init__(self
, type, axispos
, tickdirection
):
3020 - type == 0: x-axis; type == 1: y-axis
3021 - axispos_pt is the y or x position of the x-axis or y-axis
3022 in postscript points, respectively
3023 - axispos is analogous to axispos, but as a PyX length
3024 - dx and dy is the tick direction
3027 self
.axispos
= axispos
3028 self
.axispos_pt
= unit
.topt(axispos
)
3029 self
.tickdirection
= tickdirection
3031 def clipcanvas(self
):
3032 return self
.insert(canvas
.canvas(canvas
.clip(path
.rect_pt(self
.xpos_pt
, self
.ypos_pt
, self
.width_pt
, self
.height_pt
))))
3034 def plot(self
, data
, style
=None):
3036 raise RuntimeError("layout setup was already performed")
3038 if helper
.issequence(data
):
3039 raise RuntimeError("list plot needs an explicit style")
3040 if self
.defaultstyle
.has_key(data
.defaultstyle
):
3041 style
= self
.defaultstyle
[data
.defaultstyle
].iterate()
3043 style
= data
.defaultstyle()
3044 self
.defaultstyle
[data
.defaultstyle
] = style
3047 for d
in helper
.ensuresequence(data
):
3049 style
= style
.iterate()
3052 d
.setstyle(self
, style
)
3053 plotinfos
.append(plotinfo(d
, style
))
3054 self
.plotinfos
.extend(plotinfos
)
3055 if helper
.issequence(data
):
3059 def addkey(self
, key
, *plotinfos
):
3061 raise RuntimeError("layout setup was already performed")
3062 self
.addkeys
.append((key
, plotinfos
))
3064 def pos_pt(self
, x
, y
, xaxis
=None, yaxis
=None):
3066 xaxis
= self
.axes
["x"]
3068 yaxis
= self
.axes
["y"]
3069 return self
.xpos_pt
+xaxis
.convert(x
)*self
.width_pt
, self
.ypos_pt
+yaxis
.convert(y
)*self
.height_pt
3071 def pos(self
, x
, y
, xaxis
=None, yaxis
=None):
3073 xaxis
= self
.axes
["x"]
3075 yaxis
= self
.axes
["y"]
3076 return self
.xpos
+xaxis
.convert(x
)*self
.width
, self
.ypos
+yaxis
.convert(y
)*self
.height
3078 def vpos_pt(self
, vx
, vy
):
3079 return self
.xpos_pt
+vx
*self
.width_pt
, self
.ypos_pt
+vy
*self
.height_pt
3081 def vpos(self
, vx
, vy
):
3082 return self
.xpos
+vx
*self
.width
, self
.ypos
+vy
*self
.height
3084 def _addpos(self
, x
, y
, dx
, dy
):
3087 def _connect(self
, x1
, y1
, x2
, y2
):
3088 return path
.lineto_pt(x2
, y2
)
3090 def keynum(self
, key
):
3092 while key
[0] in string
.letters
:
3098 def gatherranges(self
):
3100 for plotinfo
in self
.plotinfos
:
3101 pdranges
= plotinfo
.data
.getranges()
3102 if pdranges
is not None:
3103 for key
in pdranges
.keys():
3104 if key
not in ranges
.keys():
3105 ranges
[key
] = pdranges
[key
]
3107 ranges
[key
] = (min(ranges
[key
][0], pdranges
[key
][0]),
3108 max(ranges
[key
][1], pdranges
[key
][1]))
3109 # known ranges are also set as ranges for the axes
3110 for key
, axis
in self
.axes
.items():
3111 if key
in ranges
.keys():
3112 axis
.setrange(*ranges
[key
])
3113 ranges
[key
] = axis
.getrange()
3114 if ranges
[key
] is None:
3118 def removedomethod(self
, method
):
3122 self
.domethods
.remove(method
)
3128 if not self
.removedomethod(self
.dolayout
): return
3130 # create list of ranges
3132 ranges
= self
.gatherranges()
3133 # 2. calculate additional ranges out of known ranges
3134 for plotinfo
in self
.plotinfos
:
3135 plotinfo
.data
.setranges(ranges
)
3136 # 3. gather ranges again
3138 # do the layout for all axes
3139 axesdist
= unit
.length(self
.axesdist_str
, default_type
="v")
3140 XPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[0])
3141 YPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[1])
3142 xaxisextents
= [0, 0]
3143 yaxisextents
= [0, 0]
3144 needxaxisdist
= [0, 0]
3145 needyaxisdist
= [0, 0]
3146 items
= list(self
.axes
.items())
3147 items
.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
3148 for key
, axis
in items
:
3149 num
= self
.keynum(key
)
3150 num2
= 1 - num
% 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
3151 num3
= 2 * (num
% 2) - 1 # x1 -> 1, x2 -> -1, x3 -> 1, x4 -> -1, ...
3152 if XPattern
.match(key
):
3153 if needxaxisdist
[num2
]:
3154 xaxisextents
[num2
] += axesdist
3155 self
.axespos
[key
] = lineaxisposlinegrid(self
.axes
[key
].convert
,
3157 self
.ypos
+ num2
*self
.height
- num3
*xaxisextents
[num2
],
3158 self
.xpos
+ self
.width
,
3159 self
.ypos
+ num2
*self
.height
- num3
*xaxisextents
[num2
],
3161 xaxisextents
[num2
], xaxisextents
[num2
] + self
.height
)
3163 self
.xbasepath
= self
.axespos
[key
].basepath
3164 self
.xvbasepath
= self
.axespos
[key
].vbasepath
3165 self
.xgridpath
= self
.axespos
[key
].gridpath
3166 self
.xvgridpath
= self
.axespos
[key
].vgridpath
3167 self
.xtickpoint_pt
= self
.axespos
[key
].tickpoint_pt
3168 self
.xtickpoint
= self
.axespos
[key
].tickpoint
3169 self
.xvtickpoint_pt
= self
.axespos
[key
].vtickpoint_pt
3170 self
.xvtickpoint
= self
.axespos
[key
].tickpoint
3171 self
.xtickdirection
= self
.axespos
[key
].tickdirection
3172 self
.xvtickdirection
= self
.axespos
[key
].vtickdirection
3173 elif YPattern
.match(key
):
3174 if needyaxisdist
[num2
]:
3175 yaxisextents
[num2
] += axesdist
3176 self
.axespos
[key
] = lineaxisposlinegrid(self
.axes
[key
].convert
,
3177 self
.xpos
+ num2
*self
.width
- num3
*yaxisextents
[num2
],
3179 self
.xpos
+ num2
*self
.width
- num3
*yaxisextents
[num2
],
3180 self
.ypos
+ self
.height
,
3182 yaxisextents
[num2
], yaxisextents
[num2
] + self
.width
)
3184 self
.ybasepath
= self
.axespos
[key
].basepath
3185 self
.yvbasepath
= self
.axespos
[key
].vbasepath
3186 self
.ygridpath
= self
.axespos
[key
].gridpath
3187 self
.yvgridpath
= self
.axespos
[key
].vgridpath
3188 self
.ytickpoint_pt
= self
.axespos
[key
].tickpoint_pt
3189 self
.ytickpoint
= self
.axespos
[key
].tickpoint
3190 self
.yvtickpoint_pt
= self
.axespos
[key
].vtickpoint_pt
3191 self
.yvtickpoint
= self
.axespos
[key
].tickpoint
3192 self
.ytickdirection
= self
.axespos
[key
].tickdirection
3193 self
.yvtickdirection
= self
.axespos
[key
].vtickdirection
3195 raise ValueError("Axis key '%s' not allowed" % key
)
3196 axis
.finish(self
.axespos
[key
])
3197 if XPattern
.match(key
):
3198 xaxisextents
[num2
] += axis
.axiscanvas
.extent
3199 needxaxisdist
[num2
] = 1
3200 if YPattern
.match(key
):
3201 yaxisextents
[num2
] += axis
.axiscanvas
.extent
3202 needyaxisdist
[num2
] = 1
3204 def dobackground(self
):
3206 if not self
.removedomethod(self
.dobackground
): return
3207 if self
.backgroundattrs
is not None:
3208 self
.draw(path
.rect_pt(self
.xpos_pt
, self
.ypos_pt
, self
.width_pt
, self
.height_pt
),
3209 helper
.ensurelist(self
.backgroundattrs
))
3213 if not self
.removedomethod(self
.doaxes
): return
3214 for axis
in self
.axes
.values():
3215 self
.insert(axis
.axiscanvas
)
3219 if not self
.removedomethod(self
.dodata
): return
3220 for plotinfo
in self
.plotinfos
:
3221 plotinfo
.data
.draw(self
)
3223 def _dokey(self
, key
, *plotinfos
):
3224 key
.setplotinfos(*plotinfos
)
3229 x
= self
.xpos_pt
+ self
.width_pt
- bbox
.urx
- key
.hdist_pt
3231 x
= self
.xpos_pt
+ self
.width_pt
- bbox
.llx
+ key
.hdist_pt
3234 x
= self
.xpos_pt
- bbox
.llx
+ key
.hdist_pt
3236 x
= self
.xpos_pt
- bbox
.urx
- key
.hdist_pt
3239 y
= self
.ypos_pt
+ self
.height_pt
- bbox
.ury
- key
.vdist_pt
3241 y
= self
.ypos_pt
+ self
.height_pt
- bbox
.lly
+ key
.vdist_pt
3244 y
= self
.ypos_pt
- bbox
.lly
+ key
.vdist_pt
3246 y
= self
.ypos_pt
- bbox
.ury
- key
.vdist_pt
3247 key
.paint(self
, x
, y
)
3251 if not self
.removedomethod(self
.dokey
): return
3252 if self
.key
is not None:
3253 self
._dokey
(self
.key
, *self
.plotinfos
)
3254 for key
, plotinfos
in self
.addkeys
:
3255 self
._dokey
(key
, *plotinfos
)
3258 while len(self
.domethods
):
3261 def initwidthheight(self
, width
, height
, ratio
):
3262 if (width
is not None) and (height
is None):
3263 self
.width
= unit
.length(width
)
3264 self
.height
= (1.0/ratio
) * self
.width
3265 elif (height
is not None) and (width
is None):
3266 self
.height
= unit
.length(height
)
3267 self
.width
= ratio
* self
.height
3269 self
.width
= unit
.length(width
)
3270 self
.height
= unit
.length(height
)
3271 self
.width_pt
= unit
.topt(self
.width
)
3272 self
.height_pt
= unit
.topt(self
.height
)
3273 if self
.width_pt
<= 0: raise ValueError("width <= 0")
3274 if self
.height_pt
<= 0: raise ValueError("height <= 0")
3276 def initaxes(self
, axes
, addlinkaxes
=0):
3277 for key
in self
.Names
:
3278 if not axes
.has_key(key
):
3279 axes
[key
] = linaxis()
3280 elif axes
[key
] is None:
3283 if not axes
.has_key(key
+ "2") and axes
.has_key(key
):
3284 axes
[key
+ "2"] = axes
[key
].createlinkaxis()
3285 elif axes
[key
+ "2"] is None:
3289 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, ratio
=goldenmean
,
3290 key
=None, backgroundattrs
=None, axesdist
="0.8 cm", **axes
):
3291 canvas
.canvas
.__init
__(self
)
3292 self
.xpos
= unit
.length(xpos
)
3293 self
.ypos
= unit
.length(ypos
)
3294 self
.xpos_pt
= unit
.topt(self
.xpos
)
3295 self
.ypos_pt
= unit
.topt(self
.ypos
)
3296 self
.initwidthheight(width
, height
, ratio
)
3297 self
.initaxes(axes
, 1)
3298 self
.axescanvas
= {}
3301 self
.backgroundattrs
= backgroundattrs
3302 self
.axesdist_str
= axesdist
3304 self
.domethods
= [self
.dolayout
, self
.dobackground
, self
.doaxes
, self
.dodata
, self
.dokey
]
3306 self
.defaultstyle
= {}
3311 return canvas
.canvas
.bbox(self
)
3313 def write(self
, file):
3315 canvas
.canvas
.write(self
, file)
3319 # some thoughts, but deferred right now
3321 # class graphxyz(graphxy):
3323 # Names = "x", "y", "z"
3325 # def _vxtickpoint(self, axis, v):
3326 # return self._vpos(v, axis.vypos, axis.vzpos)
3328 # def _vytickpoint(self, axis, v):
3329 # return self._vpos(axis.vxpos, v, axis.vzpos)
3331 # def _vztickpoint(self, axis, v):
3332 # return self._vpos(axis.vxpos, axis.vypos, v)
3334 # def vxtickdirection(self, axis, v):
3335 # x1, y1 = self._vpos(v, axis.vypos, axis.vzpos)
3336 # x2, y2 = self._vpos(v, 0.5, 0)
3337 # dx, dy = x1 - x2, y1 - y2
3338 # norm = math.sqrt(dx*dx + dy*dy)
3339 # return dx/norm, dy/norm
3341 # def vytickdirection(self, axis, v):
3342 # x1, y1 = self._vpos(axis.vxpos, v, axis.vzpos)
3343 # x2, y2 = self._vpos(0.5, v, 0)
3344 # dx, dy = x1 - x2, y1 - y2
3345 # norm = math.sqrt(dx*dx + dy*dy)
3346 # return dx/norm, dy/norm
3348 # def vztickdirection(self, axis, v):
3350 # x1, y1 = self._vpos(axis.vxpos, axis.vypos, v)
3351 # x2, y2 = self._vpos(0.5, 0.5, v)
3352 # dx, dy = x1 - x2, y1 - y2
3353 # norm = math.sqrt(dx*dx + dy*dy)
3354 # return dx/norm, dy/norm
3356 # def _pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
3357 # if xaxis is None: xaxis = self.axes["x"]
3358 # if yaxis is None: yaxis = self.axes["y"]
3359 # if zaxis is None: zaxis = self.axes["z"]
3360 # return self._vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
3362 # def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
3363 # if xaxis is None: xaxis = self.axes["x"]
3364 # if yaxis is None: yaxis = self.axes["y"]
3365 # if zaxis is None: zaxis = self.axes["z"]
3366 # return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
3368 # def _vpos(self, vx, vy, vz):
3369 # x, y, z = (vx - 0.5)*self._depth, (vy - 0.5)*self._width, (vz - 0.5)*self._height
3370 # d0 = float(self.a[0]*self.b[1]*(z-self.eye[2])
3371 # + self.a[2]*self.b[0]*(y-self.eye[1])
3372 # + self.a[1]*self.b[2]*(x-self.eye[0])
3373 # - self.a[2]*self.b[1]*(x-self.eye[0])
3374 # - self.a[0]*self.b[2]*(y-self.eye[1])
3375 # - self.a[1]*self.b[0]*(z-self.eye[2]))
3376 # da = (self.eye[0]*self.b[1]*(z-self.eye[2])
3377 # + self.eye[2]*self.b[0]*(y-self.eye[1])
3378 # + self.eye[1]*self.b[2]*(x-self.eye[0])
3379 # - self.eye[2]*self.b[1]*(x-self.eye[0])
3380 # - self.eye[0]*self.b[2]*(y-self.eye[1])
3381 # - self.eye[1]*self.b[0]*(z-self.eye[2]))
3382 # db = (self.a[0]*self.eye[1]*(z-self.eye[2])
3383 # + self.a[2]*self.eye[0]*(y-self.eye[1])
3384 # + self.a[1]*self.eye[2]*(x-self.eye[0])
3385 # - self.a[2]*self.eye[1]*(x-self.eye[0])
3386 # - self.a[0]*self.eye[2]*(y-self.eye[1])
3387 # - self.a[1]*self.eye[0]*(z-self.eye[2]))
3388 # return da/d0 + self._xpos, db/d0 + self._ypos
3390 # def vpos(self, vx, vy, vz):
3391 # tx, ty = self._vpos(vx, vy, vz)
3392 # return unit.t_pt(tx), unit.t_pt(ty)
3394 # def xbaseline(self, axis, x1, x2, xaxis=None):
3395 # if xaxis is None: xaxis = self.axes["x"]
3396 # return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2))
3398 # def ybaseline(self, axis, y1, y2, yaxis=None):
3399 # if yaxis is None: yaxis = self.axes["y"]
3400 # return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2))
3402 # def zbaseline(self, axis, z1, z2, zaxis=None):
3403 # if zaxis is None: zaxis = self.axes["z"]
3404 # return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2))
3406 # def vxbaseline(self, axis, v1, v2):
3407 # return (path._line(*(self._vpos(v1, 0, 0) + self._vpos(v2, 0, 0))) +
3408 # path._line(*(self._vpos(v1, 0, 1) + self._vpos(v2, 0, 1))) +
3409 # path._line(*(self._vpos(v1, 1, 1) + self._vpos(v2, 1, 1))) +
3410 # path._line(*(self._vpos(v1, 1, 0) + self._vpos(v2, 1, 0))))
3412 # def vybaseline(self, axis, v1, v2):
3413 # return (path._line(*(self._vpos(0, v1, 0) + self._vpos(0, v2, 0))) +
3414 # path._line(*(self._vpos(0, v1, 1) + self._vpos(0, v2, 1))) +
3415 # path._line(*(self._vpos(1, v1, 1) + self._vpos(1, v2, 1))) +
3416 # path._line(*(self._vpos(1, v1, 0) + self._vpos(1, v2, 0))))
3418 # def vzbaseline(self, axis, v1, v2):
3419 # return (path._line(*(self._vpos(0, 0, v1) + self._vpos(0, 0, v2))) +
3420 # path._line(*(self._vpos(0, 1, v1) + self._vpos(0, 1, v2))) +
3421 # path._line(*(self._vpos(1, 1, v1) + self._vpos(1, 1, v2))) +
3422 # path._line(*(self._vpos(1, 0, v1) + self._vpos(1, 0, v2))))
3424 # def xgridpath(self, x, xaxis=None):
3426 # if xaxis is None: xaxis = self.axes["x"]
3427 # v = xaxis.convert(x)
3428 # return path._line(self._xpos+v*self._width, self._ypos,
3429 # self._xpos+v*self._width, self._ypos+self._height)
3431 # def ygridpath(self, y, yaxis=None):
3433 # if yaxis is None: yaxis = self.axes["y"]
3434 # v = yaxis.convert(y)
3435 # return path._line(self._xpos, self._ypos+v*self._height,
3436 # self._xpos+self._width, self._ypos+v*self._height)
3438 # def zgridpath(self, z, zaxis=None):
3440 # if zaxis is None: zaxis = self.axes["z"]
3441 # v = zaxis.convert(z)
3442 # return path._line(self._xpos, self._zpos+v*self._height,
3443 # self._xpos+self._width, self._zpos+v*self._height)
3445 # def vxgridpath(self, v):
3446 # return path.path(path._moveto(*self._vpos(v, 0, 0)),
3447 # path._lineto(*self._vpos(v, 0, 1)),
3448 # path._lineto(*self._vpos(v, 1, 1)),
3449 # path._lineto(*self._vpos(v, 1, 0)),
3452 # def vygridpath(self, v):
3453 # return path.path(path._moveto(*self._vpos(0, v, 0)),
3454 # path._lineto(*self._vpos(0, v, 1)),
3455 # path._lineto(*self._vpos(1, v, 1)),
3456 # path._lineto(*self._vpos(1, v, 0)),
3459 # def vzgridpath(self, v):
3460 # return path.path(path._moveto(*self._vpos(0, 0, v)),
3461 # path._lineto(*self._vpos(0, 1, v)),
3462 # path._lineto(*self._vpos(1, 1, v)),
3463 # path._lineto(*self._vpos(1, 0, v)),
3466 # def _addpos(self, x, y, dx, dy):
3470 # def _connect(self, x1, y1, x2, y2):
3472 # return path._lineto(x2, y2)
3476 # if not self.removedomethod(self.doaxes): return
3477 # axesdist = unit.topt(unit.length(self.axesdist_str, default_type="v"))
3478 # XPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[0])
3479 # YPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[1])
3480 # ZPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[2])
3481 # items = list(self.axes.items())
3482 # items.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
3483 # for key, axis in items:
3484 # num = self.keynum(key)
3485 # num2 = 1 - num % 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
3486 # num3 = 1 - 2 * (num % 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
3487 # if XPattern.match(key):
3490 # axis._vtickpoint = self._vxtickpoint
3491 # axis.vgridpath = self.vxgridpath
3492 # axis.vbaseline = self.vxbaseline
3493 # axis.vtickdirection = self.vxtickdirection
3494 # elif YPattern.match(key):
3497 # axis._vtickpoint = self._vytickpoint
3498 # axis.vgridpath = self.vygridpath
3499 # axis.vbaseline = self.vybaseline
3500 # axis.vtickdirection = self.vytickdirection
3501 # elif ZPattern.match(key):
3504 # axis._vtickpoint = self._vztickpoint
3505 # axis.vgridpath = self.vzgridpath
3506 # axis.vbaseline = self.vzbaseline
3507 # axis.vtickdirection = self.vztickdirection
3509 # raise ValueError("Axis key '%s' not allowed" % key)
3510 # if axis.painter is not None:
3511 # axis.dopaint(self)
3512 # # if XPattern.match(key):
3513 # # self._xaxisextents[num2] += axis._extent
3514 # # needxaxisdist[num2] = 1
3515 # # if YPattern.match(key):
3516 # # self._yaxisextents[num2] += axis._extent
3517 # # needyaxisdist[num2] = 1
3519 # def __init__(self, tex, xpos=0, ypos=0, width=None, height=None, depth=None,
3520 # phi=30, theta=30, distance=1,
3521 # backgroundattrs=None, axesdist="0.8 cm", **axes):
3522 # canvas.canvas.__init__(self)
3526 # self._xpos = unit.topt(xpos)
3527 # self._ypos = unit.topt(ypos)
3528 # self._width = unit.topt(width)
3529 # self._height = unit.topt(height)
3530 # self._depth = unit.topt(depth)
3531 # self.width = width
3532 # self.height = height
3533 # self.depth = depth
3534 # if self._width <= 0: raise ValueError("width < 0")
3535 # if self._height <= 0: raise ValueError("height < 0")
3536 # if self._depth <= 0: raise ValueError("height < 0")
3537 # self._distance = distance*math.sqrt(self._width*self._width+
3538 # self._height*self._height+
3539 # self._depth*self._depth)
3540 # phi *= -math.pi/180
3541 # theta *= math.pi/180
3542 # self.a = (-math.sin(phi), math.cos(phi), 0)
3543 # self.b = (-math.cos(phi)*math.sin(theta),
3544 # -math.sin(phi)*math.sin(theta),
3546 # self.eye = (self._distance*math.cos(phi)*math.cos(theta),
3547 # self._distance*math.sin(phi)*math.cos(theta),
3548 # self._distance*math.sin(theta))
3549 # self.initaxes(axes)
3550 # self.axesdist_str = axesdist
3551 # self.backgroundattrs = backgroundattrs
3554 # self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
3555 # self.haslayout = 0
3556 # self.defaultstyle = {}
3560 # return bbox._bbox(self._xpos - 200, self._ypos - 200, self._xpos + 200, self._ypos + 200)
3563 ################################################################################
3565 ################################################################################
3568 #class _Ichangeattr:
3569 # """attribute changer
3570 # is an iterator for attributes where an attribute
3571 # is not refered by just a number (like for a sequence),
3572 # but also by the number of attributes requested
3573 # by calls of the next method (like for an color palette)
3574 # (you should ensure to call all needed next before the attr)
3576 # the attribute itself is implemented by overloading the _attr method"""
3579 # "get an attribute"
3582 # "get an attribute changer for the next attribute"
3585 class _changeattr
: pass
3588 class changeattr(_changeattr
):
3597 newindex
= self
.counter
3599 return refattr(self
, newindex
)
3602 class refattr(_changeattr
):
3604 def __init__(self
, ref
, index
):
3609 return self
.ref
.attr(self
.index
)
3612 return self
.ref
.iterate()
3615 # helper routines for a using attrs
3618 "get attr out of a attr/changeattr"
3619 if isinstance(attr
, _changeattr
):
3620 return attr
.getattr()
3624 def _getattrs(attrs
):
3625 "get attrs out of a list of attr/changeattr"
3626 if attrs
is not None:
3628 for attr
in helper
.ensuresequence(attrs
):
3629 if isinstance(attr
, _changeattr
):
3630 attr
= attr
.getattr()
3631 if attr
is not None:
3633 if len(result
) or not len(attrs
):
3637 def _iterateattr(attr
):
3638 "perform next to a attr/changeattr"
3639 if isinstance(attr
, _changeattr
):
3640 return attr
.iterate()
3644 def _iterateattrs(attrs
):
3645 "perform next to a list of attr/changeattr"
3646 if attrs
is not None:
3648 for attr
in helper
.ensuresequence(attrs
):
3649 if isinstance(attr
, _changeattr
):
3650 result
.append(attr
.iterate())
3656 class changecolor(changeattr
):
3658 def __init__(self
, palette
):
3659 changeattr
.__init
__(self
)
3660 self
.palette
= palette
3662 def attr(self
, index
):
3663 if self
.counter
!= 1:
3664 return self
.palette
.getcolor(index
/float(self
.counter
-1))
3666 return self
.palette
.getcolor(0)
3669 class _changecolorgray(changecolor
):
3671 def __init__(self
, palette
=color
.palette
.Gray
):
3672 changecolor
.__init
__(self
, palette
)
3674 _changecolorgrey
= _changecolorgray
3677 class _changecolorreversegray(changecolor
):
3679 def __init__(self
, palette
=color
.palette
.ReverseGray
):
3680 changecolor
.__init
__(self
, palette
)
3682 _changecolorreversegrey
= _changecolorreversegray
3685 class _changecolorredblack(changecolor
):
3687 def __init__(self
, palette
=color
.palette
.RedBlack
):
3688 changecolor
.__init
__(self
, palette
)
3691 class _changecolorblackred(changecolor
):
3693 def __init__(self
, palette
=color
.palette
.BlackRed
):
3694 changecolor
.__init
__(self
, palette
)
3697 class _changecolorredwhite(changecolor
):
3699 def __init__(self
, palette
=color
.palette
.RedWhite
):
3700 changecolor
.__init
__(self
, palette
)
3703 class _changecolorwhitered(changecolor
):
3705 def __init__(self
, palette
=color
.palette
.WhiteRed
):
3706 changecolor
.__init
__(self
, palette
)
3709 class _changecolorgreenblack(changecolor
):
3711 def __init__(self
, palette
=color
.palette
.GreenBlack
):
3712 changecolor
.__init
__(self
, palette
)
3715 class _changecolorblackgreen(changecolor
):
3717 def __init__(self
, palette
=color
.palette
.BlackGreen
):
3718 changecolor
.__init
__(self
, palette
)
3721 class _changecolorgreenwhite(changecolor
):
3723 def __init__(self
, palette
=color
.palette
.GreenWhite
):
3724 changecolor
.__init
__(self
, palette
)
3727 class _changecolorwhitegreen(changecolor
):
3729 def __init__(self
, palette
=color
.palette
.WhiteGreen
):
3730 changecolor
.__init
__(self
, palette
)
3733 class _changecolorblueblack(changecolor
):
3735 def __init__(self
, palette
=color
.palette
.BlueBlack
):
3736 changecolor
.__init
__(self
, palette
)
3739 class _changecolorblackblue(changecolor
):
3741 def __init__(self
, palette
=color
.palette
.BlackBlue
):
3742 changecolor
.__init
__(self
, palette
)
3745 class _changecolorbluewhite(changecolor
):
3747 def __init__(self
, palette
=color
.palette
.BlueWhite
):
3748 changecolor
.__init
__(self
, palette
)
3751 class _changecolorwhiteblue(changecolor
):
3753 def __init__(self
, palette
=color
.palette
.WhiteBlue
):
3754 changecolor
.__init
__(self
, palette
)
3757 class _changecolorredgreen(changecolor
):
3759 def __init__(self
, palette
=color
.palette
.RedGreen
):
3760 changecolor
.__init
__(self
, palette
)
3763 class _changecolorredblue(changecolor
):
3765 def __init__(self
, palette
=color
.palette
.RedBlue
):
3766 changecolor
.__init
__(self
, palette
)
3769 class _changecolorgreenred(changecolor
):
3771 def __init__(self
, palette
=color
.palette
.GreenRed
):
3772 changecolor
.__init
__(self
, palette
)
3775 class _changecolorgreenblue(changecolor
):
3777 def __init__(self
, palette
=color
.palette
.GreenBlue
):
3778 changecolor
.__init
__(self
, palette
)
3781 class _changecolorbluered(changecolor
):
3783 def __init__(self
, palette
=color
.palette
.BlueRed
):
3784 changecolor
.__init
__(self
, palette
)
3787 class _changecolorbluegreen(changecolor
):
3789 def __init__(self
, palette
=color
.palette
.BlueGreen
):
3790 changecolor
.__init
__(self
, palette
)
3793 class _changecolorrainbow(changecolor
):
3795 def __init__(self
, palette
=color
.palette
.Rainbow
):
3796 changecolor
.__init
__(self
, palette
)
3799 class _changecolorreverserainbow(changecolor
):
3801 def __init__(self
, palette
=color
.palette
.ReverseRainbow
):
3802 changecolor
.__init
__(self
, palette
)
3805 class _changecolorhue(changecolor
):
3807 def __init__(self
, palette
=color
.palette
.Hue
):
3808 changecolor
.__init
__(self
, palette
)
3811 class _changecolorreversehue(changecolor
):
3813 def __init__(self
, palette
=color
.palette
.ReverseHue
):
3814 changecolor
.__init
__(self
, palette
)
3817 changecolor
.Gray
= _changecolorgray
3818 changecolor
.Grey
= _changecolorgrey
3819 changecolor
.Reversegray
= _changecolorreversegray
3820 changecolor
.Reversegrey
= _changecolorreversegrey
3821 changecolor
.RedBlack
= _changecolorredblack
3822 changecolor
.BlackRed
= _changecolorblackred
3823 changecolor
.RedWhite
= _changecolorredwhite
3824 changecolor
.WhiteRed
= _changecolorwhitered
3825 changecolor
.GreenBlack
= _changecolorgreenblack
3826 changecolor
.BlackGreen
= _changecolorblackgreen
3827 changecolor
.GreenWhite
= _changecolorgreenwhite
3828 changecolor
.WhiteGreen
= _changecolorwhitegreen
3829 changecolor
.BlueBlack
= _changecolorblueblack
3830 changecolor
.BlackBlue
= _changecolorblackblue
3831 changecolor
.BlueWhite
= _changecolorbluewhite
3832 changecolor
.WhiteBlue
= _changecolorwhiteblue
3833 changecolor
.RedGreen
= _changecolorredgreen
3834 changecolor
.RedBlue
= _changecolorredblue
3835 changecolor
.GreenRed
= _changecolorgreenred
3836 changecolor
.GreenBlue
= _changecolorgreenblue
3837 changecolor
.BlueRed
= _changecolorbluered
3838 changecolor
.BlueGreen
= _changecolorbluegreen
3839 changecolor
.Rainbow
= _changecolorrainbow
3840 changecolor
.ReverseRainbow
= _changecolorreverserainbow
3841 changecolor
.Hue
= _changecolorhue
3842 changecolor
.ReverseHue
= _changecolorreversehue
3845 class changesequence(changeattr
):
3846 "cycles through a list"
3848 def __init__(self
, *sequence
):
3849 changeattr
.__init
__(self
)
3850 if not len(sequence
):
3851 sequence
= self
.defaultsequence
3852 self
.sequence
= sequence
3854 def attr(self
, index
):
3855 return self
.sequence
[index
% len(self
.sequence
)]
3858 class changelinestyle(changesequence
):
3859 defaultsequence
= (style
.linestyle
.solid
,
3860 style
.linestyle
.dashed
,
3861 style
.linestyle
.dotted
,
3862 style
.linestyle
.dashdotted
)
3865 class changestrokedfilled(changesequence
):
3866 defaultsequence
= (deco
.stroked
, deco
.filled
)
3869 class changefilledstroked(changesequence
):
3870 defaultsequence
= (deco
.filled
, deco
.stroked
)
3874 ################################################################################
3876 ################################################################################
3881 def cross(self
, x
, y
):
3882 return (path
.moveto_pt(x
-0.5*self
.size_pt
, y
-0.5*self
.size_pt
),
3883 path
.lineto_pt(x
+0.5*self
.size_pt
, y
+0.5*self
.size_pt
),
3884 path
.moveto_pt(x
-0.5*self
.size_pt
, y
+0.5*self
.size_pt
),
3885 path
.lineto_pt(x
+0.5*self
.size_pt
, y
-0.5*self
.size_pt
))
3887 def plus(self
, x
, y
):
3888 return (path
.moveto_pt(x
-0.707106781*self
.size_pt
, y
),
3889 path
.lineto_pt(x
+0.707106781*self
.size_pt
, y
),
3890 path
.moveto_pt(x
, y
-0.707106781*self
.size_pt
),
3891 path
.lineto_pt(x
, y
+0.707106781*self
.size_pt
))
3893 def square(self
, x
, y
):
3894 return (path
.moveto_pt(x
-0.5*self
.size_pt
, y
-0.5 * self
.size_pt
),
3895 path
.lineto_pt(x
+0.5*self
.size_pt
, y
-0.5 * self
.size_pt
),
3896 path
.lineto_pt(x
+0.5*self
.size_pt
, y
+0.5 * self
.size_pt
),
3897 path
.lineto_pt(x
-0.5*self
.size_pt
, y
+0.5 * self
.size_pt
),
3900 def triangle(self
, x
, y
):
3901 return (path
.moveto_pt(x
-0.759835685*self
.size_pt
, y
-0.438691337*self
.size_pt
),
3902 path
.lineto_pt(x
+0.759835685*self
.size_pt
, y
-0.438691337*self
.size_pt
),
3903 path
.lineto_pt(x
, y
+0.877382675*self
.size_pt
),
3906 def circle(self
, x
, y
):
3907 return (path
.arc_pt(x
, y
, 0.564189583*self
.size_pt
, 0, 360),
3910 def diamond(self
, x
, y
):
3911 return (path
.moveto_pt(x
-0.537284965*self
.size_pt
, y
),
3912 path
.lineto_pt(x
, y
-0.930604859*self
.size_pt
),
3913 path
.lineto_pt(x
+0.537284965*self
.size_pt
, y
),
3914 path
.lineto_pt(x
, y
+0.930604859*self
.size_pt
),
3917 def __init__(self
, symbol
=helper
.nodefault
,
3918 size
="0.2 cm", symbolattrs
=deco
.stroked
,
3919 errorscale
=0.5, errorbarattrs
=(),
3921 self
.size_str
= size
3922 if symbol
is helper
.nodefault
:
3923 self
._symbol
= changesymbol
.cross()
3925 self
._symbol
= symbol
3926 self
._symbolattrs
= symbolattrs
3927 self
.errorscale
= errorscale
3928 self
._errorbarattrs
= errorbarattrs
3929 self
._lineattrs
= lineattrs
3931 def iteratedict(self
):
3933 result
["symbol"] = _iterateattr(self
._symbol
)
3934 result
["size"] = _iterateattr(self
.size_str
)
3935 result
["symbolattrs"] = _iterateattrs(self
._symbolattrs
)
3936 result
["errorscale"] = _iterateattr(self
.errorscale
)
3937 result
["errorbarattrs"] = _iterateattrs(self
._errorbarattrs
)
3938 result
["lineattrs"] = _iterateattrs(self
._lineattrs
)
3942 return symbol(**self
.iteratedict())
3944 def othercolumnkey(self
, key
, index
):
3945 raise ValueError("unsuitable key '%s'" % key
)
3947 def setcolumns(self
, graph
, columns
):
3948 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
3950 match
= pattern
.match(key
)
3952 if isindex
is not None: raise ValueError("multiple key specification")
3953 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
3955 iskey
= match
.groups()[0]
3957 return key
, iskey
, isindex
3959 self
.xi
= self
.xmini
= self
.xmaxi
= None
3960 self
.dxi
= self
.dxmini
= self
.dxmaxi
= None
3961 self
.yi
= self
.ymini
= self
.ymaxi
= None
3962 self
.dyi
= self
.dymini
= self
.dymaxi
= None
3963 self
.xkey
= self
.ykey
= None
3964 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
3965 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
3966 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
3967 XMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
3968 YMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
3969 XMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
3970 YMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
3971 DXPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
3972 DYPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
3973 DXMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
3974 DYMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
3975 DXMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
3976 DYMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
3977 for key
, index
in columns
.items():
3978 key
, self
.xkey
, self
.xi
= checkpattern(key
, index
, XPattern
, self
.xkey
, self
.xi
)
3979 key
, self
.ykey
, self
.yi
= checkpattern(key
, index
, YPattern
, self
.ykey
, self
.yi
)
3980 key
, self
.xkey
, self
.xmini
= checkpattern(key
, index
, XMinPattern
, self
.xkey
, self
.xmini
)
3981 key
, self
.ykey
, self
.ymini
= checkpattern(key
, index
, YMinPattern
, self
.ykey
, self
.ymini
)
3982 key
, self
.xkey
, self
.xmaxi
= checkpattern(key
, index
, XMaxPattern
, self
.xkey
, self
.xmaxi
)
3983 key
, self
.ykey
, self
.ymaxi
= checkpattern(key
, index
, YMaxPattern
, self
.ykey
, self
.ymaxi
)
3984 key
, self
.xkey
, self
.dxi
= checkpattern(key
, index
, DXPattern
, self
.xkey
, self
.dxi
)
3985 key
, self
.ykey
, self
.dyi
= checkpattern(key
, index
, DYPattern
, self
.ykey
, self
.dyi
)
3986 key
, self
.xkey
, self
.dxmini
= checkpattern(key
, index
, DXMinPattern
, self
.xkey
, self
.dxmini
)
3987 key
, self
.ykey
, self
.dymini
= checkpattern(key
, index
, DYMinPattern
, self
.ykey
, self
.dymini
)
3988 key
, self
.xkey
, self
.dxmaxi
= checkpattern(key
, index
, DXMaxPattern
, self
.xkey
, self
.dxmaxi
)
3989 key
, self
.ykey
, self
.dymaxi
= checkpattern(key
, index
, DYMaxPattern
, self
.ykey
, self
.dymaxi
)
3991 self
.othercolumnkey(key
, index
)
3992 if None in (self
.xkey
, self
.ykey
): raise ValueError("incomplete axis specification")
3993 if (len(filter(None, (self
.xmini
, self
.dxmini
, self
.dxi
))) > 1 or
3994 len(filter(None, (self
.ymini
, self
.dymini
, self
.dyi
))) > 1 or
3995 len(filter(None, (self
.xmaxi
, self
.dxmaxi
, self
.dxi
))) > 1 or
3996 len(filter(None, (self
.ymaxi
, self
.dymaxi
, self
.dyi
))) > 1):
3997 raise ValueError("multiple errorbar definition")
3998 if ((self
.xi
is None and self
.dxi
is not None) or
3999 (self
.yi
is None and self
.dyi
is not None) or
4000 (self
.xi
is None and self
.dxmini
is not None) or
4001 (self
.yi
is None and self
.dymini
is not None) or
4002 (self
.xi
is None and self
.dxmaxi
is not None) or
4003 (self
.yi
is None and self
.dymaxi
is not None)):
4004 raise ValueError("errorbar definition start value missing")
4005 self
.xaxis
= graph
.axes
[self
.xkey
]
4006 self
.yaxis
= graph
.axes
[self
.ykey
]
4008 def minmidmax(self
, point
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
4009 min = max = mid
= None
4011 mid
= point
[i
] + 0.0
4012 except (TypeError, ValueError):
4015 if di
is not None: min = point
[i
] - point
[di
]
4016 elif dmini
is not None: min = point
[i
] - point
[dmini
]
4017 elif mini
is not None: min = point
[mini
] + 0.0
4018 except (TypeError, ValueError):
4021 if di
is not None: max = point
[i
] + point
[di
]
4022 elif dmaxi
is not None: max = point
[i
] + point
[dmaxi
]
4023 elif maxi
is not None: max = point
[maxi
] + 0.0
4024 except (TypeError, ValueError):
4027 if min is not None and min > mid
: raise ValueError("minimum error in errorbar")
4028 if max is not None and max < mid
: raise ValueError("maximum error in errorbar")
4030 if min is not None and max is not None and min > max: raise ValueError("minimum/maximum error in errorbar")
4031 return min, mid
, max
4033 def keyrange(self
, points
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
4034 allmin
= allmax
= None
4035 if filter(None, (mini
, maxi
, di
, dmini
, dmaxi
)) is not None:
4036 for point
in points
:
4037 min, mid
, max = self
.minmidmax(point
, i
, mini
, maxi
, di
, dmini
, dmaxi
)
4038 if min is not None and (allmin
is None or min < allmin
): allmin
= min
4039 if mid
is not None and (allmin
is None or mid
< allmin
): allmin
= mid
4040 if mid
is not None and (allmax
is None or mid
> allmax
): allmax
= mid
4041 if max is not None and (allmax
is None or max > allmax
): allmax
= max
4043 for point
in points
:
4045 value
= point
[i
] + 0.0
4046 if allmin
is None or point
[i
] < allmin
: allmin
= point
[i
]
4047 if allmax
is None or point
[i
] > allmax
: allmax
= point
[i
]
4048 except (TypeError, ValueError):
4050 return allmin
, allmax
4052 def getranges(self
, points
):
4053 xmin
, xmax
= self
.keyrange(points
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
4054 ymin
, ymax
= self
.keyrange(points
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
4055 return {self
.xkey
: (xmin
, xmax
), self
.ykey
: (ymin
, ymax
)}
4057 def drawerrorbar_pt(self
, graph
, topleft
, top
, topright
,
4058 left
, center
, right
,
4059 bottomleft
, bottom
, bottomright
, point
=None):
4060 if left
is not None:
4061 if right
is not None:
4062 left1
= graph
._addpos
(*(left
+(0, -self
.errorsize_pt
)))
4063 left2
= graph
._addpos
(*(left
+(0, self
.errorsize_pt
)))
4064 right1
= graph
._addpos
(*(right
+(0, -self
.errorsize_pt
)))
4065 right2
= graph
._addpos
(*(right
+(0, self
.errorsize_pt
)))
4066 graph
.stroke(path
.path(path
.moveto_pt(*left1
),
4067 graph
._connect
(*(left1
+left2
)),
4068 path
.moveto_pt(*left
),
4069 graph
._connect
(*(left
+right
)),
4070 path
.moveto_pt(*right1
),
4071 graph
._connect
(*(right1
+right2
))),
4073 elif center
is not None:
4074 left1
= graph
._addpos
(*(left
+(0, -self
.errorsize_pt
)))
4075 left2
= graph
._addpos
(*(left
+(0, self
.errorsize_pt
)))
4076 graph
.stroke(path
.path(path
.moveto_pt(*left1
),
4077 graph
._connect
(*(left1
+left2
)),
4078 path
.moveto_pt(*left
),
4079 graph
._connect
(*(left
+center
))),
4082 left1
= graph
._addpos
(*(left
+(0, -self
.errorsize_pt
)))
4083 left2
= graph
._addpos
(*(left
+(0, self
.errorsize_pt
)))
4084 left3
= graph
._addpos
(*(left
+(self
.errorsize_pt
, 0)))
4085 graph
.stroke(path
.path(path
.moveto_pt(*left1
),
4086 graph
._connect
(*(left1
+left2
)),
4087 path
.moveto_pt(*left
),
4088 graph
._connect
(*(left
+left3
))),
4090 if right
is not None and left
is None:
4091 if center
is not None:
4092 right1
= graph
._addpos
(*(right
+(0, -self
.errorsize_pt
)))
4093 right2
= graph
._addpos
(*(right
+(0, self
.errorsize_pt
)))
4094 graph
.stroke(path
.path(path
.moveto_pt(*right1
),
4095 graph
._connect
(*(right1
+right2
)),
4096 path
.moveto_pt(*right
),
4097 graph
._connect
(*(right
+center
))),
4100 right1
= graph
._addpos
(*(right
+(0, -self
.errorsize_pt
)))
4101 right2
= graph
._addpos
(*(right
+(0, self
.errorsize_pt
)))
4102 right3
= graph
._addpos
(*(right
+(-self
.errorsize_pt
, 0)))
4103 graph
.stroke(path
.path(path
.moveto_pt(*right1
),
4104 graph
._connect
(*(right1
+right2
)),
4105 path
.moveto_pt(*right
),
4106 graph
._connect
(*(right
+right3
))),
4109 if bottom
is not None:
4111 bottom1
= graph
._addpos
(*(bottom
+(-self
.errorsize_pt
, 0)))
4112 bottom2
= graph
._addpos
(*(bottom
+(self
.errorsize_pt
, 0)))
4113 top1
= graph
._addpos
(*(top
+(-self
.errorsize_pt
, 0)))
4114 top2
= graph
._addpos
(*(top
+(self
.errorsize_pt
, 0)))
4115 graph
.stroke(path
.path(path
.moveto_pt(*bottom1
),
4116 graph
._connect
(*(bottom1
+bottom2
)),
4117 path
.moveto_pt(*bottom
),
4118 graph
._connect
(*(bottom
+top
)),
4119 path
.moveto_pt(*top1
),
4120 graph
._connect
(*(top1
+top2
))),
4122 elif center
is not None:
4123 bottom1
= graph
._addpos
(*(bottom
+(-self
.errorsize_pt
, 0)))
4124 bottom2
= graph
._addpos
(*(bottom
+(self
.errorsize_pt
, 0)))
4125 graph
.stroke(path
.path(path
.moveto_pt(*bottom1
),
4126 graph
._connect
(*(bottom1
+bottom2
)),
4127 path
.moveto_pt(*bottom
),
4128 graph
._connect
(*(bottom
+center
))),
4131 bottom1
= graph
._addpos
(*(bottom
+(-self
.errorsize_pt
, 0)))
4132 bottom2
= graph
._addpos
(*(bottom
+(self
.errorsize_pt
, 0)))
4133 bottom3
= graph
._addpos
(*(bottom
+(0, self
.errorsize_pt
)))
4134 graph
.stroke(path
.path(path
.moveto_pt(*bottom1
),
4135 graph
._connect
(*(bottom1
+bottom2
)),
4136 path
.moveto_pt(*bottom
),
4137 graph
._connect
(*(bottom
+bottom3
))),
4139 if top
is not None and bottom
is None:
4140 if center
is not None:
4141 top1
= graph
._addpos
(*(top
+(-self
.errorsize_pt
, 0)))
4142 top2
= graph
._addpos
(*(top
+(self
.errorsize_pt
, 0)))
4143 graph
.stroke(path
.path(path
.moveto_pt(*top1
),
4144 graph
._connect
(*(top1
+top2
)),
4145 path
.moveto_pt(*top
),
4146 graph
._connect
(*(top
+center
))),
4149 top1
= graph
._addpos
(*(top
+(-self
.errorsize_pt
, 0)))
4150 top2
= graph
._addpos
(*(top
+(self
.errorsize_pt
, 0)))
4151 top3
= graph
._addpos
(*(top
+(0, -self
.errorsize_pt
)))
4152 graph
.stroke(path
.path(path
.moveto_pt(*top1
),
4153 graph
._connect
(*(top1
+top2
)),
4154 path
.moveto_pt(*top
),
4155 graph
._connect
(*(top
+top3
))),
4157 if bottomleft
is not None:
4158 if topleft
is not None and bottomright
is None:
4159 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
.errorsize_pt
, 0)))
4160 topleft1
= graph
._addpos
(*(topleft
+(self
.errorsize_pt
, 0)))
4161 graph
.stroke(path
.path(path
.moveto_pt(*bottomleft1
),
4162 graph
._connect
(*(bottomleft1
+bottomleft
)),
4163 graph
._connect
(*(bottomleft
+topleft
)),
4164 graph
._connect
(*(topleft
+topleft1
))),
4166 elif bottomright
is not None and topleft
is None:
4167 bottomleft1
= graph
._addpos
(*(bottomleft
+(0, self
.errorsize_pt
)))
4168 bottomright1
= graph
._addpos
(*(bottomright
+(0, self
.errorsize_pt
)))
4169 graph
.stroke(path
.path(path
.moveto_pt(*bottomleft1
),
4170 graph
._connect
(*(bottomleft1
+bottomleft
)),
4171 graph
._connect
(*(bottomleft
+bottomright
)),
4172 graph
._connect
(*(bottomright
+bottomright1
))),
4174 elif bottomright
is None and topleft
is None:
4175 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
.errorsize_pt
, 0)))
4176 bottomleft2
= graph
._addpos
(*(bottomleft
+(0, self
.errorsize_pt
)))
4177 graph
.stroke(path
.path(path
.moveto_pt(*bottomleft1
),
4178 graph
._connect
(*(bottomleft1
+bottomleft
)),
4179 graph
._connect
(*(bottomleft
+bottomleft2
))),
4181 if topright
is not None:
4182 if bottomright
is not None and topleft
is None:
4183 topright1
= graph
._addpos
(*(topright
+(-self
.errorsize_pt
, 0)))
4184 bottomright1
= graph
._addpos
(*(bottomright
+(-self
.errorsize_pt
, 0)))
4185 graph
.stroke(path
.path(path
.moveto_pt(*topright1
),
4186 graph
._connect
(*(topright1
+topright
)),
4187 graph
._connect
(*(topright
+bottomright
)),
4188 graph
._connect
(*(bottomright
+bottomright1
))),
4190 elif topleft
is not None and bottomright
is None:
4191 topright1
= graph
._addpos
(*(topright
+(0, -self
.errorsize_pt
)))
4192 topleft1
= graph
._addpos
(*(topleft
+(0, -self
.errorsize_pt
)))
4193 graph
.stroke(path
.path(path
.moveto_pt(*topright1
),
4194 graph
._connect
(*(topright1
+topright
)),
4195 graph
._connect
(*(topright
+topleft
)),
4196 graph
._connect
(*(topleft
+topleft1
))),
4198 elif topleft
is None and bottomright
is None:
4199 topright1
= graph
._addpos
(*(topright
+(-self
.errorsize_pt
, 0)))
4200 topright2
= graph
._addpos
(*(topright
+(0, -self
.errorsize_pt
)))
4201 graph
.stroke(path
.path(path
.moveto_pt(*topright1
),
4202 graph
._connect
(*(topright1
+topright
)),
4203 graph
._connect
(*(topright
+topright2
))),
4205 if bottomright
is not None and bottomleft
is None and topright
is None:
4206 bottomright1
= graph
._addpos
(*(bottomright
+(-self
.errorsize_pt
, 0)))
4207 bottomright2
= graph
._addpos
(*(bottomright
+(0, self
.errorsize_pt
)))
4208 graph
.stroke(path
.path(path
.moveto_pt(*bottomright1
),
4209 graph
._connect
(*(bottomright1
+bottomright
)),
4210 graph
._connect
(*(bottomright
+bottomright2
))),
4212 if topleft
is not None and bottomleft
is None and topright
is None:
4213 topleft1
= graph
._addpos
(*(topleft
+(self
.errorsize_pt
, 0)))
4214 topleft2
= graph
._addpos
(*(topleft
+(0, -self
.errorsize_pt
)))
4215 graph
.stroke(path
.path(path
.moveto_pt(*topleft1
),
4216 graph
._connect
(*(topleft1
+topleft
)),
4217 graph
._connect
(*(topleft
+topleft2
))),
4219 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
4220 graph
.stroke(path
.path(path
.moveto_pt(*bottomleft
),
4221 graph
._connect
(*(bottomleft
+bottomright
)),
4222 graph
._connect
(*(bottomright
+topright
)),
4223 graph
._connect
(*(topright
+topleft
)),
4227 def drawsymbol_pt(self
, canvas
, x
, y
, point
=None):
4228 canvas
.draw(path
.path(*self
.symbol(self
, x
, y
)), self
.symbolattrs
)
4230 def drawsymbol(self
, canvas
, x
, y
, point
=None):
4231 self
.drawsymbol_pt(canvas
, unit
.topt(x
), unit
.topt(y
), point
)
4233 def key(self
, c
, x
, y
, width
, height
):
4234 if self
._symbolattrs
is not None:
4235 self
.drawsymbol_pt(c
, x
+ 0.5 * width
, y
+ 0.5 * height
)
4236 if self
._lineattrs
is not None:
4237 c
.stroke(path
.line_pt(x
, y
+ 0.5 * height
, x
+ width
, y
+ 0.5 * height
), self
.lineattrs
)
4239 def drawpoints(self
, graph
, points
):
4240 xaxismin
, xaxismax
= self
.xaxis
.getrange()
4241 yaxismin
, yaxismax
= self
.yaxis
.getrange()
4242 self
.size
= unit
.length(_getattr(self
.size_str
), default_type
="v")
4243 self
.size_pt
= unit
.topt(self
.size
)
4244 self
.symbol
= _getattr(self
._symbol
)
4245 self
.symbolattrs
= _getattrs(helper
.ensuresequence(self
._symbolattrs
))
4246 self
.errorbarattrs
= _getattrs(helper
.ensuresequence(self
._errorbarattrs
))
4247 self
.errorsize_pt
= self
.errorscale
* self
.size_pt
4248 self
.errorsize
= self
.errorscale
* self
.size
4249 self
.lineattrs
= _getattrs(helper
.ensuresequence(self
._lineattrs
))
4250 if self
._lineattrs
is not None:
4251 clipcanvas
= graph
.clipcanvas()
4253 haserror
= filter(None, (self
.xmini
, self
.ymini
, self
.xmaxi
, self
.ymaxi
,
4254 self
.dxi
, self
.dyi
, self
.dxmini
, self
.dymini
, self
.dxmaxi
, self
.dymaxi
)) is not None
4256 for point
in points
:
4258 xmin
, x
, xmax
= self
.minmidmax(point
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
4259 ymin
, y
, ymax
= self
.minmidmax(point
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
4260 if x
is not None and x
< xaxismin
: drawsymbol
= 0
4261 elif x
is not None and x
> xaxismax
: drawsymbol
= 0
4262 elif y
is not None and y
< yaxismin
: drawsymbol
= 0
4263 elif y
is not None and y
> yaxismax
: drawsymbol
= 0
4264 # elif haserror: # TODO: correct clipcanvas handling
4265 # if xmin is not None and xmin < xaxismin: drawsymbol = 0
4266 # elif xmax is not None and xmax < xaxismin: drawsymbol = 0
4267 # elif xmax is not None and xmax > xaxismax: drawsymbol = 0
4268 # elif xmin is not None and xmin > xaxismax: drawsymbol = 0
4269 # elif ymin is not None and ymin < yaxismin: drawsymbol = 0
4270 # elif ymax is not None and ymax < yaxismin: drawsymbol = 0
4271 # elif ymax is not None and ymax > yaxismax: drawsymbol = 0
4272 # elif ymin is not None and ymin > yaxismax: drawsymbol = 0
4273 xpos
=ypos
=topleft
=top
=topright
=left
=center
=right
=bottomleft
=bottom
=bottomright
=None
4274 if x
is not None and y
is not None:
4276 center
= xpos
, ypos
= graph
.pos_pt(x
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4277 except (ValueError, OverflowError): # XXX: exceptions???
4281 if xmin
is not None: left
= graph
.pos_pt(xmin
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4282 if xmax
is not None: right
= graph
.pos_pt(xmax
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4284 if ymax
is not None: top
= graph
.pos_pt(x
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4285 if ymin
is not None: bottom
= graph
.pos_pt(x
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4286 if x
is None or y
is None:
4287 if ymax
is not None:
4288 if xmin
is not None: topleft
= graph
.pos_pt(xmin
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4289 if xmax
is not None: topright
= graph
.pos_pt(xmax
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4290 if ymin
is not None:
4291 if xmin
is not None: bottomleft
= graph
.pos_pt(xmin
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4292 if xmax
is not None: bottomright
= graph
.pos_pt(xmax
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4294 if self
._errorbarattrs
is not None and haserror
:
4295 self
.drawerrorbar_pt(graph
, topleft
, top
, topright
,
4296 left
, center
, right
,
4297 bottomleft
, bottom
, bottomright
, point
)
4298 if self
._symbolattrs
is not None and xpos
is not None and ypos
is not None:
4299 self
.drawsymbol_pt(graph
, xpos
, ypos
, point
)
4300 if xpos
is not None and ypos
is not None:
4302 lineels
.append(path
.moveto_pt(xpos
, ypos
))
4305 lineels
.append(path
.lineto_pt(xpos
, ypos
))
4308 self
.path
= path
.path(*lineels
)
4309 if self
._lineattrs
is not None:
4310 clipcanvas
.stroke(self
.path
, self
.lineattrs
)
4313 class changesymbol(changesequence
): pass
4316 class _changesymbolcross(changesymbol
):
4317 defaultsequence
= (symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
)
4320 class _changesymbolplus(changesymbol
):
4321 defaultsequence
= (symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
)
4324 class _changesymbolsquare(changesymbol
):
4325 defaultsequence
= (symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
)
4328 class _changesymboltriangle(changesymbol
):
4329 defaultsequence
= (symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
)
4332 class _changesymbolcircle(changesymbol
):
4333 defaultsequence
= (symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
)
4336 class _changesymboldiamond(changesymbol
):
4337 defaultsequence
= (symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
)
4340 class _changesymbolsquaretwice(changesymbol
):
4341 defaultsequence
= (symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
,
4342 symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
)
4345 class _changesymboltriangletwice(changesymbol
):
4346 defaultsequence
= (symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
,
4347 symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
)
4350 class _changesymbolcircletwice(changesymbol
):
4351 defaultsequence
= (symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
,
4352 symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
)
4355 class _changesymboldiamondtwice(changesymbol
):
4356 defaultsequence
= (symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
,
4357 symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
)
4360 changesymbol
.cross
= _changesymbolcross
4361 changesymbol
.plus
= _changesymbolplus
4362 changesymbol
.square
= _changesymbolsquare
4363 changesymbol
.triangle
= _changesymboltriangle
4364 changesymbol
.circle
= _changesymbolcircle
4365 changesymbol
.diamond
= _changesymboldiamond
4366 changesymbol
.squaretwice
= _changesymbolsquaretwice
4367 changesymbol
.triangletwice
= _changesymboltriangletwice
4368 changesymbol
.circletwice
= _changesymbolcircletwice
4369 changesymbol
.diamondtwice
= _changesymboldiamondtwice
4374 def __init__(self
, lineattrs
=helper
.nodefault
):
4375 if lineattrs
is helper
.nodefault
:
4376 lineattrs
= (changelinestyle(), style
.linejoin
.round)
4377 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=None, lineattrs
=lineattrs
)
4382 def __init__(self
, palette
=color
.palette
.Gray
):
4383 self
.palette
= palette
4384 self
.colorindex
= None
4385 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=(), lineattrs
=None)
4388 raise RuntimeError("style is not iterateable")
4390 def othercolumnkey(self
, key
, index
):
4392 self
.colorindex
= index
4394 symbol
.othercolumnkey(self
, key
, index
)
4396 def drawerrorbar_pt(self
, graph
, topleft
, top
, topright
,
4397 left
, center
, right
,
4398 bottomleft
, bottom
, bottomright
, point
=None):
4399 color
= point
[self
.colorindex
]
4400 if color
is not None:
4401 if color
!= self
.lastcolor
:
4402 self
.rectclipcanvas
.set([self
.palette
.getcolor(color
)])
4403 if bottom
is not None and left
is not None:
4404 bottomleft
= left
[0], bottom
[1]
4405 if bottom
is not None and right
is not None:
4406 bottomright
= right
[0], bottom
[1]
4407 if top
is not None and right
is not None:
4408 topright
= right
[0], top
[1]
4409 if top
is not None and left
is not None:
4410 topleft
= left
[0], top
[1]
4411 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
4412 self
.rectclipcanvas
.fill(path
.path(path
.moveto_pt(*bottomleft
),
4413 graph
._connect
(*(bottomleft
+bottomright
)),
4414 graph
._connect
(*(bottomright
+topright
)),
4415 graph
._connect
(*(topright
+topleft
)),
4418 def drawpoints(self
, graph
, points
):
4419 if self
.colorindex
is None:
4420 raise RuntimeError("column 'color' not set")
4421 self
.lastcolor
= None
4422 self
.rectclipcanvas
= graph
.clipcanvas()
4423 symbol
.drawpoints(self
, graph
, points
)
4425 def key(self
, c
, x
, y
, width
, height
):
4426 raise RuntimeError("style doesn't yet provide a key")
4431 def __init__(self
, textdx
="0", textdy
="0.3 cm", textattrs
=textmodule
.halign
.center
, **args
):
4432 self
.textindex
= None
4433 self
.textdx_str
= textdx
4434 self
.textdy_str
= textdy
4435 self
._textattrs
= textattrs
4436 symbol
.__init
__(self
, **args
)
4438 def iteratedict(self
):
4439 result
= symbol
.iteratedict()
4440 result
["textattrs"] = _iterateattr(self
._textattrs
)
4444 return textsymbol(**self
.iteratedict())
4446 def othercolumnkey(self
, key
, index
):
4448 self
.textindex
= index
4450 symbol
.othercolumnkey(self
, key
, index
)
4452 def drawsymbol_pt(self
, graph
, x
, y
, point
=None):
4453 symbol
.drawsymbol_pt(self
, graph
, x
, y
, point
)
4454 if None not in (x
, y
, point
[self
.textindex
]) and self
._textattrs
is not None:
4455 graph
.text_pt(x
+ self
.textdx_pt
, y
+ self
.textdy_pt
, str(point
[self
.textindex
]), helper
.ensuresequence(self
.textattrs
))
4457 def drawpoints(self
, graph
, points
):
4458 self
.textdx
= unit
.length(_getattr(self
.textdx_str
), default_type
="v")
4459 self
.textdy
= unit
.length(_getattr(self
.textdy_str
), default_type
="v")
4460 self
.textdx_pt
= unit
.topt(self
.textdx
)
4461 self
.textdy_pt
= unit
.topt(self
.textdy
)
4462 if self
._textattrs
is not None:
4463 self
.textattrs
= _getattr(self
._textattrs
)
4464 if self
.textindex
is None:
4465 raise RuntimeError("column 'text' not set")
4466 symbol
.drawpoints(self
, graph
, points
)
4468 def key(self
, c
, x
, y
, width
, height
):
4469 raise RuntimeError("style doesn't yet provide a key")
4472 class arrow(symbol
):
4474 def __init__(self
, linelength
="0.2 cm", arrowattrs
=(), arrowsize
="0.1 cm", arrowdict
={}, epsilon
=1e-10):
4475 self
.linelength_str
= linelength
4476 self
.arrowsize_str
= arrowsize
4477 self
.arrowattrs
= arrowattrs
4478 self
.arrowdict
= arrowdict
4479 self
.epsilon
= epsilon
4480 self
.sizeindex
= self
.angleindex
= None
4481 symbol
.__init
__(self
, symbolattrs
=(), errorbarattrs
=None, lineattrs
=None)
4484 raise RuntimeError("style is not iterateable")
4486 def othercolumnkey(self
, key
, index
):
4488 self
.sizeindex
= index
4489 elif key
== "angle":
4490 self
.angleindex
= index
4492 symbol
.othercolumnkey(self
, key
, index
)
4494 def drawsymbol_pt(self
, graph
, x
, y
, point
=None):
4495 if None not in (x
, y
, point
[self
.angleindex
], point
[self
.sizeindex
], self
.arrowattrs
, self
.arrowdict
):
4496 if point
[self
.sizeindex
] > self
.epsilon
:
4497 dx
, dy
= math
.cos(point
[self
.angleindex
]*math
.pi
/180.0), math
.sin(point
[self
.angleindex
]*math
.pi
/180)
4498 x1
= unit
.t_pt(x
)-0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
4499 y1
= unit
.t_pt(y
)-0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
4500 x2
= unit
.t_pt(x
)+0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
4501 y2
= unit
.t_pt(y
)+0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
4502 graph
.stroke(path
.line(x1
, y1
, x2
, y2
),
4503 [deco
.earrow(size
=self
.arrowsize
*point
[self
.sizeindex
],
4504 **self
.arrowdict
)]+helper
.ensurelist(self
.arrowattrs
))
4506 def drawpoints(self
, graph
, points
):
4507 self
.arrowsize
= unit
.length(_getattr(self
.arrowsize_str
), default_type
="v")
4508 self
.linelength
= unit
.length(_getattr(self
.linelength_str
), default_type
="v")
4509 self
.arrowsize_pt
= unit
.topt(self
.arrowsize
)
4510 self
.linelength_pt
= unit
.topt(self
.linelength
)
4511 if self
.sizeindex
is None:
4512 raise RuntimeError("column 'size' not set")
4513 if self
.angleindex
is None:
4514 raise RuntimeError("column 'angle' not set")
4515 symbol
.drawpoints(self
, graph
, points
)
4517 def key(self
, c
, x
, y
, width
, height
):
4518 raise RuntimeError("style doesn't yet provide a key")
4521 class _bariterator(changeattr
):
4523 def attr(self
, index
):
4524 return index
, self
.counter
4529 def __init__(self
, fromzero
=1, stacked
=0, skipmissing
=1, xbar
=0,
4530 barattrs
=helper
.nodefault
, _usebariterator
=helper
.nodefault
, _previousbar
=None):
4531 self
.fromzero
= fromzero
4532 self
.stacked
= stacked
4533 self
.skipmissing
= skipmissing
4535 if barattrs
is helper
.nodefault
:
4536 self
._barattrs
= [deco
.stroked([color
.gray
.black
]), changecolor
.Rainbow()]
4538 self
._barattrs
= barattrs
4539 if _usebariterator
is helper
.nodefault
:
4540 self
.bariterator
= _bariterator()
4542 self
.bariterator
= _usebariterator
4543 self
.previousbar
= _previousbar
4545 def iteratedict(self
):
4547 result
["barattrs"] = _iterateattrs(self
._barattrs
)
4551 return bar(fromzero
=self
.fromzero
, stacked
=self
.stacked
, xbar
=self
.xbar
,
4552 _usebariterator
=_iterateattr(self
.bariterator
), _previousbar
=self
, **self
.iteratedict())
4554 def setcolumns(self
, graph
, columns
):
4555 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
4557 match
= pattern
.match(key
)
4559 if isindex
is not None: raise ValueError("multiple key specification")
4560 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
4562 iskey
= match
.groups()[0]
4564 return key
, iskey
, isindex
4567 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
4568 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
4569 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
4571 for key
, index
in columns
.items():
4572 key
, xkey
, xi
= checkpattern(key
, index
, XPattern
, xkey
, xi
)
4573 key
, ykey
, yi
= checkpattern(key
, index
, YPattern
, ykey
, yi
)
4575 self
.othercolumnkey(key
, index
)
4576 if None in (xkey
, ykey
): raise ValueError("incomplete axis specification")
4578 self
.nkey
, self
.ni
= ykey
, yi
4579 self
.vkey
, self
.vi
= xkey
, xi
4581 self
.nkey
, self
.ni
= xkey
, xi
4582 self
.vkey
, self
.vi
= ykey
, yi
4583 self
.naxis
, self
.vaxis
= graph
.axes
[self
.nkey
], graph
.axes
[self
.vkey
]
4585 def getranges(self
, points
):
4586 index
, count
= _getattr(self
.bariterator
)
4587 if count
!= 1 and self
.stacked
!= 1:
4588 if self
.stacked
> 1:
4589 index
= divmod(index
, self
.stacked
)[0]
4592 for point
in points
:
4593 if not self
.skipmissing
:
4594 if count
!= 1 and self
.stacked
!= 1:
4595 self
.naxis
.setname(point
[self
.ni
], index
)
4597 self
.naxis
.setname(point
[self
.ni
])
4599 v
= point
[self
.vi
] + 0.0
4600 if vmin
is None or v
< vmin
: vmin
= v
4601 if vmax
is None or v
> vmax
: vmax
= v
4602 except (TypeError, ValueError):
4605 if self
.skipmissing
:
4606 if count
!= 1 and self
.stacked
!= 1:
4607 self
.naxis
.setname(point
[self
.ni
], index
)
4609 self
.naxis
.setname(point
[self
.ni
])
4611 if vmin
> 0: vmin
= 0
4612 if vmax
< 0: vmax
= 0
4613 return {self
.vkey
: (vmin
, vmax
)}
4615 def drawpoints(self
, graph
, points
):
4616 index
, count
= _getattr(self
.bariterator
)
4617 dostacked
= (self
.stacked
!= 0 and
4618 (self
.stacked
== 1 or divmod(index
, self
.stacked
)[1]) and
4619 (self
.stacked
!= 1 or index
))
4620 if self
.stacked
> 1:
4621 index
= divmod(index
, self
.stacked
)[0]
4622 vmin
, vmax
= self
.vaxis
.getrange()
4623 self
.barattrs
= _getattrs(helper
.ensuresequence(self
._barattrs
))
4625 self
.stackedvalue
= {}
4626 for point
in points
:
4631 self
.stackedvalue
[n
] = v
4632 if count
!= 1 and self
.stacked
!= 1:
4633 minid
= (n
, index
, 0)
4634 maxid
= (n
, index
, 1)
4639 x1pos
, y1pos
= graph
.pos_pt(v
, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4640 x2pos
, y2pos
= graph
.pos_pt(v
, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4642 x1pos
, y1pos
= graph
.pos_pt(minid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4643 x2pos
, y2pos
= graph
.pos_pt(maxid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4646 x3pos
, y3pos
= graph
.pos_pt(self
.previousbar
.stackedvalue
[n
], maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4647 x4pos
, y4pos
= graph
.pos_pt(self
.previousbar
.stackedvalue
[n
], minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4649 x3pos
, y3pos
= graph
.pos_pt(maxid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4650 x4pos
, y4pos
= graph
.pos_pt(minid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4654 x3pos
, y3pos
= graph
.pos_pt(0, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4655 x4pos
, y4pos
= graph
.pos_pt(0, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4657 x3pos
, y3pos
= graph
.pos_pt(maxid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4658 x4pos
, y4pos
= graph
.pos_pt(minid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4660 #x3pos, y3pos = graph.tickpoint_pt(maxid, axis=self.naxis)
4661 #x4pos, y4pos = graph.tickpoint_pt(minid, axis=self.naxis)
4662 x3pos
, y3pos
= graph
.axespos
[self
.nkey
].tickpoint_pt(maxid
)
4663 x4pos
, y4pos
= graph
.axespos
[self
.nkey
].tickpoint_pt(minid
)
4664 if self
.barattrs
is not None:
4665 graph
.fill(path
.path(path
.moveto_pt(x1pos
, y1pos
),
4666 graph
._connect
(x1pos
, y1pos
, x2pos
, y2pos
),
4667 graph
._connect
(x2pos
, y2pos
, x3pos
, y3pos
),
4668 graph
._connect
(x3pos
, y3pos
, x4pos
, y4pos
),
4669 graph
._connect
(x4pos
, y4pos
, x1pos
, y1pos
), # no closepath (might not be straight)
4670 path
.closepath()), self
.barattrs
)
4671 except (TypeError, ValueError): pass
4673 def key(self
, c
, x
, y
, width
, height
):
4674 c
.fill(path
.rect_pt(x
, y
, width
, height
), self
.barattrs
)
4679 # def setcolumns(self, graph, columns):
4680 # self.columns = columns
4682 # def getranges(self, points):
4683 # return {"x": (0, 10), "y": (0, 10), "z": (0, 1)}
4685 # def drawpoints(self, graph, points):
4690 ################################################################################
4692 ################################################################################
4697 defaultstyle
= symbol
4699 def __init__(self
, file, title
=helper
.nodefault
, context
={}, **columns
):
4701 if helper
.isstring(file):
4702 self
.data
= datamodule
.datafile(file)
4705 if title
is helper
.nodefault
:
4706 self
.title
= "(unknown)"
4710 for key
, column
in columns
.items():
4712 self
.columns
[key
] = self
.data
.getcolumnno(column
)
4713 except datamodule
.ColumnError
:
4714 self
.columns
[key
] = len(self
.data
.titles
)
4715 self
.data
.addcolumn(column
, context
=context
)
4717 def setstyle(self
, graph
, style
):
4719 self
.style
.setcolumns(graph
, self
.columns
)
4721 def getranges(self
):
4722 return self
.style
.getranges(self
.data
.data
)
4724 def setranges(self
, ranges
):
4727 def draw(self
, graph
):
4728 self
.style
.drawpoints(graph
, self
.data
.data
)
4735 def __init__(self
, expression
, title
=helper
.nodefault
, min=None, max=None, points
=100, parser
=mathtree
.parser(), context
={}):
4736 if title
is helper
.nodefault
:
4737 self
.title
= expression
4742 self
.points
= points
4743 self
.context
= context
4744 self
.result
, expression
= [x
.strip() for x
in expression
.split("=")]
4745 self
.mathtree
= parser
.parse(expression
)
4746 self
.variable
= None
4749 def setstyle(self
, graph
, style
):
4750 for variable
in self
.mathtree
.VarList():
4751 if variable
in graph
.axes
.keys():
4752 if self
.variable
is None:
4753 self
.variable
= variable
4755 raise ValueError("multiple variables found")
4756 if self
.variable
is None:
4757 raise ValueError("no variable found")
4758 self
.xaxis
= graph
.axes
[self
.variable
]
4760 self
.style
.setcolumns(graph
, {self
.variable
: 0, self
.result
: 1})
4762 def getranges(self
):
4764 return self
.style
.getranges(self
.data
)
4765 if None not in (self
.min, self
.max):
4766 return {self
.variable
: (self
.min, self
.max)}
4768 def setranges(self
, ranges
):
4769 if ranges
.has_key(self
.variable
):
4770 min, max = ranges
[self
.variable
]
4771 if self
.min is not None: min = self
.min
4772 if self
.max is not None: max = self
.max
4773 vmin
= self
.xaxis
.convert(min)
4774 vmax
= self
.xaxis
.convert(max)
4776 for i
in range(self
.points
):
4777 self
.context
[self
.variable
] = x
= self
.xaxis
.invert(vmin
+ (vmax
-vmin
)*i
/ (self
.points
-1.0))
4779 y
= self
.mathtree
.Calc(**self
.context
)
4780 except (ArithmeticError, ValueError):
4782 self
.data
.append((x
, y
))
4785 def draw(self
, graph
):
4786 self
.style
.drawpoints(graph
, self
.data
)
4789 class paramfunction
:
4793 def __init__(self
, varname
, min, max, expression
, title
=helper
.nodefault
, points
=100, parser
=mathtree
.parser(), context
={}):
4794 if title
is helper
.nodefault
:
4795 self
.title
= expression
4798 self
.varname
= varname
4801 self
.points
= points
4802 self
.expression
= {}
4804 varlist
, expressionlist
= expression
.split("=")
4805 if mathtree
.__useparser
__ == mathtree
.__newparser
__: # XXX: switch between mathtree-parsers
4806 keys
= varlist
.split(",")
4807 mtrees
= helper
.ensurelist(parser
.parse(expressionlist
))
4808 if len(keys
) != len(mtrees
):
4809 raise ValueError("unpack tuple of wrong size")
4810 for i
in range(len(keys
)):
4811 key
= keys
[i
].strip()
4812 if self
.mathtrees
.has_key(key
):
4813 raise ValueError("multiple assignment in tuple")
4814 self
.mathtrees
[key
] = mtrees
[i
]
4815 if len(keys
) != len(self
.mathtrees
.keys()):
4816 raise ValueError("unpack tuple of wrong size")
4818 parsestr
= mathtree
.ParseStr(expressionlist
)
4819 for key
in varlist
.split(","):
4821 if self
.mathtrees
.has_key(key
):
4822 raise ValueError("multiple assignment in tuple")
4824 self
.mathtrees
[key
] = parser
.ParseMathTree(parsestr
)
4826 except mathtree
.CommaFoundMathTreeParseError
, e
:
4827 self
.mathtrees
[key
] = e
.MathTree
4829 raise ValueError("unpack tuple of wrong size")
4830 if len(varlist
.split(",")) != len(self
.mathtrees
.keys()):
4831 raise ValueError("unpack tuple of wrong size")
4833 for i
in range(self
.points
):
4834 context
[self
.varname
] = self
.min + (self
.max-self
.min)*i
/ (self
.points
-1.0)
4836 for key
, tree
in self
.mathtrees
.items():
4837 line
.append(tree
.Calc(**context
))
4838 self
.data
.append(line
)
4840 def setstyle(self
, graph
, style
):
4843 for key
, index
in zip(self
.mathtrees
.keys(), xrange(sys
.maxint
)):
4844 columns
[key
] = index
4845 self
.style
.setcolumns(graph
, columns
)
4847 def getranges(self
):
4848 return self
.style
.getranges(self
.data
)
4850 def setranges(self
, ranges
):
4853 def draw(self
, graph
):
4854 self
.style
.drawpoints(graph
, self
.data
)