2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 import re
, math
, string
, sys
26 import bbox
, box
, canvas
, color
, deco
, helper
, path
, style
, unit
, mathtree
27 import text
as textmodule
28 import data
as datamodule
29 import trafo
as trafomodule
32 goldenmean
= 0.5 * (math
.sqrt(5) + 1)
35 ################################################################################
37 ################################################################################
40 """interface definition of a map
41 maps convert a value into another value by bijective transformation f"""
47 "returns f^-1(y) where f^-1 is the inverse transformation (x=f^-1(f(x)) for all x)"
49 def setbasepoints(self
, basepoints
):
50 """set basepoints for the convertions
51 basepoints are tuples (x, y) with y == f(x) and x == f^-1(y)
52 the number of basepoints needed might depend on the transformation
53 usually two pairs are needed like for linear maps, logarithmic maps, etc."""
59 __implements__
= _Imap
61 def setbasepoints(self
, basepoints
):
62 self
.dydx
= (basepoints
[1][1] - basepoints
[0][1]) / float(basepoints
[1][0] - basepoints
[0][0])
63 self
.dxdy
= (basepoints
[1][0] - basepoints
[0][0]) / float(basepoints
[1][1] - basepoints
[0][1])
64 self
.x1
= basepoints
[0][0]
65 self
.y1
= basepoints
[0][1]
67 def convert(self
, value
):
68 return self
.y1
+ self
.dydx
* (value
- self
.x1
)
70 def invert(self
, value
):
71 return self
.x1
+ self
.dxdy
* (value
- self
.y1
)
76 __implements__
= _Imap
78 def setbasepoints(self
, basepoints
):
79 self
.dydx
= ((basepoints
[1][1] - basepoints
[0][1]) /
80 float(math
.log(basepoints
[1][0]) - math
.log(basepoints
[0][0])))
81 self
.dxdy
= ((math
.log(basepoints
[1][0]) - math
.log(basepoints
[0][0])) /
82 float(basepoints
[1][1] - basepoints
[0][1]))
83 self
.x1
= math
.log(basepoints
[0][0])
84 self
.y1
= basepoints
[0][1]
87 def convert(self
, value
):
88 return self
.y1
+ self
.dydx
* (math
.log(value
) - self
.x1
)
90 def invert(self
, value
):
91 return math
.exp(self
.x1
+ self
.dxdy
* (value
- self
.y1
))
95 ################################################################################
97 # please note the nomenclature:
98 # - a partition is a list of tick instances; to reduce name clashes, a
99 # partition is called ticks
100 # - a partitioner is a class creating a single or several ticks
101 # - an axis has a part attribute where it stores a partitioner or/and some
102 # (manually set) ticks -> the part attribute is used to create the ticks
103 # in the axis finish method
104 ################################################################################
108 """fraction class for rational arithmetics
109 the axis partitioning uses rational arithmetics (with infinite accuracy)
110 basically it contains self.enum and self.denom"""
112 def stringfrac(self
, s
):
113 "converts a string 0.123 into a frac"
114 expparts
= s
.split("e")
115 if len(expparts
) > 2:
116 raise ValueError("multiple 'e' found in '%s'" % s
)
117 commaparts
= expparts
[0].split(".")
118 if len(commaparts
) > 2:
119 raise ValueError("multiple '.' found in '%s'" % expparts
[0])
120 if len(commaparts
) == 1:
121 commaparts
= [commaparts
[0], ""]
122 result
= frac((1, 10l), power
=len(commaparts
[1]))
123 neg
= len(commaparts
[0]) and commaparts
[0][0] == "-"
125 commaparts
[0] = commaparts
[0][1:]
126 elif len(commaparts
[0]) and commaparts
[0][0] == "+":
127 commaparts
[0] = commaparts
[0][1:]
128 if len(commaparts
[0]):
129 if not commaparts
[0].isdigit():
130 raise ValueError("unrecognized characters in '%s'" % s
)
131 x
= long(commaparts
[0])
134 if len(commaparts
[1]):
135 if not commaparts
[1].isdigit():
136 raise ValueError("unrecognized characters in '%s'" % s
)
137 y
= long(commaparts
[1])
140 result
.enum
= x
*result
.denom
+y
142 result
.enum
= -result
.enum
143 if len(expparts
) == 2:
144 neg
= expparts
[1][0] == "-"
146 expparts
[1] = expparts
[1][1:]
147 elif expparts
[1][0] == "+":
148 expparts
[1] = expparts
[1][1:]
149 if not expparts
[1].isdigit():
150 raise ValueError("unrecognized characters in '%s'" % s
)
152 result
*= frac((1, 10l), power
=long(expparts
[1]))
154 result
*= frac((10, 1l), power
=long(expparts
[1]))
157 def floatfrac(self
, x
, floatprecision
):
158 "converts a float into a frac with finite resolution"
159 if helper
.isinteger(floatprecision
) and floatprecision
< 0:
160 # this would be extremly vulnerable
161 raise RuntimeError("float resolution must be non-negative integer")
162 return self
.stringfrac(("%%.%ig" % floatprecision
) % x
)
164 def __init__(self
, x
, power
=None, floatprecision
=10):
165 "for power!=None: frac=(enum/denom)**power"
166 if helper
.isnumber(x
):
167 value
= self
.floatfrac(x
, floatprecision
)
168 enum
, denom
= value
.enum
, value
.denom
169 elif helper
.isstring(x
):
170 fraction
= x
.split("/")
171 if len(fraction
) > 2:
172 raise ValueError("multiple '/' found in '%s'" % x
)
173 value
= self
.stringfrac(fraction
[0])
174 if len(fraction
) == 2:
175 value2
= self
.stringfrac(fraction
[1])
176 value
= value
/ value2
177 enum
, denom
= value
.enum
, value
.denom
181 except (TypeError, AttributeError):
182 enum
, denom
= x
.enum
, x
.denom
183 if not helper
.isinteger(enum
) or not helper
.isinteger(denom
): raise TypeError("integer type expected")
184 if not denom
: raise ZeroDivisionError("zero denominator")
186 if not helper
.isinteger(power
): raise TypeError("integer type expected")
188 self
.enum
= long(enum
) ** power
189 self
.denom
= long(denom
) ** power
191 self
.enum
= long(denom
) ** (-power
)
192 self
.denom
= long(enum
) ** (-power
)
197 def __cmp__(self
, other
):
200 return cmp(self
.enum
* other
.denom
, other
.enum
* self
.denom
)
203 return frac((abs(self
.enum
), abs(self
.denom
)))
205 def __mul__(self
, other
):
206 return frac((self
.enum
* other
.enum
, self
.denom
* other
.denom
))
208 def __div__(self
, other
):
209 return frac((self
.enum
* other
.denom
, self
.denom
* other
.enum
))
212 "caution: avoid final precision of floats"
213 return float(self
.enum
) / self
.denom
216 return "%i/%i" % (self
.enum
, self
.denom
)
221 a tick is a frac enhanced by
222 - self.ticklevel (0 = tick, 1 = subtick, etc.)
223 - self.labellevel (0 = label, 1 = sublabel, etc.)
224 - self.label (a string) and self.labelattrs (a list, defaults to [])
225 When ticklevel or labellevel is None, no tick or label is present at that value.
226 When label is None, it should be automatically created (and stored), once the
227 an axis painter needs it. Classes, which implement _Itexter do precisely that."""
229 def __init__(self
, pos
, ticklevel
=0, labellevel
=0, label
=None, labelattrs
=[], **kwargs
):
230 """initializes the instance
231 - see class description for the parameter description
232 - **kwargs are passed to the frac constructor"""
233 frac
.__init
__(self
, pos
, **kwargs
)
234 self
.ticklevel
= ticklevel
235 self
.labellevel
= labellevel
237 self
.labelattrs
= helper
.ensurelist(labelattrs
)[:]
239 def merge(self
, other
):
240 """merges two ticks together:
241 - the lower ticklevel/labellevel wins
242 - the label is *never* taken over from other
243 - the ticks should be at the same position (otherwise it doesn't make sense)
244 -> this is NOT checked"""
245 if self
.ticklevel
is None or (other
.ticklevel
is not None and other
.ticklevel
< self
.ticklevel
):
246 self
.ticklevel
= other
.ticklevel
247 if self
.labellevel
is None or (other
.labellevel
is not None and other
.labellevel
< self
.labellevel
):
248 self
.labellevel
= other
.labellevel
251 def _mergeticklists(list1
, list2
):
252 """helper function to merge tick lists
253 - return a merged list of ticks out of list1 and list2
254 - CAUTION: original lists have to be ordered
255 (the returned list is also ordered)"""
256 # TODO: improve this using bisect?!
258 # XXX do not the original lists
263 while 1: # we keep on going until we reach an index error
264 while list2
[j
] < list1
[i
]: # insert tick
265 list1
.insert(i
, list2
[j
])
268 if list2
[j
] == list1
[i
]: # merge tick
269 list1
[i
].merge(list2
[j
])
278 def _mergelabels(ticks
, labels
):
279 """helper function to merge labels into ticks
280 - when labels is not None, the label of all ticks with
281 labellevel different from None are set
282 - labels need to be a list of lists of strings,
283 where the first list contain the strings to be
284 used as labels for the ticks with labellevel 0,
285 the second list for labellevel 1, etc.
286 - when the maximum labellevel is 0, just a list of
287 strings might be provided as the labels argument
288 - IndexError is raised, when a list length doesn't match"""
289 if helper
.issequenceofsequences(labels
):
290 for label
, level
in zip(labels
, xrange(sys
.maxint
)):
291 usetext
= helper
.ensuresequence(label
)
294 if tick
.labellevel
== level
:
295 tick
.label
= usetext
[i
]
297 if i
!= len(usetext
):
298 raise IndexError("wrong list length of labels at level %i" % level
)
299 elif labels
is not None:
300 usetext
= helper
.ensuresequence(labels
)
303 if tick
.labellevel
== 0:
304 tick
.label
= usetext
[i
]
306 if i
!= len(usetext
):
307 raise IndexError("wrong list length of labels")
311 """interface definition of a partition scheme
312 partition schemes are used to create a list of ticks"""
314 def defaultpart(self
, min, max, extendmin
, extendmax
):
315 """create a partition
316 - returns an ordered list of ticks for the interval min to max
317 - the interval is given in float numbers, thus an appropriate
318 conversion to rational numbers has to be performed
319 - extendmin and extendmax are booleans (integers)
320 - when extendmin or extendmax is set, the ticks might
321 extend the min-max range towards lower and higher
322 ranges, respectively"""
325 """create another partition which contains less ticks
326 - this method is called several times after a call of defaultpart
327 - returns an ordered list of ticks with less ticks compared to
328 the partition returned by defaultpart and by previous calls
330 - the creation of a partition with strictly *less* ticks
331 is not to be taken serious
332 - the method might return None, when no other appropriate
333 partition can be created"""
337 """create another partition which contains more ticks
338 see lesspart, but increase the number of ticks"""
342 """linear partition scheme
343 ticks and label distances are explicitly provided to the constructor"""
345 __implements__
= _Iparter
347 def __init__(self
, tickdist
=None, labeldist
=None, labels
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
348 """configuration of the partition scheme
349 - tickdist and labeldist should be a list, where the first value
350 is the distance between ticks with ticklevel/labellevel 0,
351 the second list for ticklevel/labellevel 1, etc.;
352 a single entry is allowed without being a list
353 - tickdist and labeldist values are passed to the frac constructor
354 - when labeldist is None and tickdist is not None, the tick entries
355 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
356 - labels are applied to the resulting partition via the
357 mergelabels function (additional information available there)
358 - extendtick allows for the extension of the range given to the
359 defaultpart method to include the next tick with the specified
360 level (None turns off this feature); note, that this feature is
361 also disabled, when an axis prohibits its range extension by
362 the extendmin/extendmax variables given to the defaultpart method
363 - extendlabel is analogous to extendtick, but for labels
364 - epsilon allows for exceeding the axis range by this relative
365 value (relative to the axis range given to the defaultpart method)
366 without creating another tick specified by extendtick/extendlabel"""
367 if tickdist
is None and labeldist
is not None:
368 self
.ticklist
= (frac(helper
.ensuresequence(labeldist
)[0]),)
370 self
.ticklist
= map(frac
, helper
.ensuresequence(tickdist
))
371 if labeldist
is None and tickdist
is not None:
372 self
.labellist
= (frac(helper
.ensuresequence(tickdist
)[0]),)
374 self
.labellist
= map(frac
, helper
.ensuresequence(labeldist
))
376 self
.extendtick
= extendtick
377 self
.extendlabel
= extendlabel
378 self
.epsilon
= epsilon
380 def extendminmax(self
, min, max, frac
, extendmin
, extendmax
):
381 """return new min, max tuple extending the range min, max
382 - frac is the tick distance to be used
383 - extendmin and extendmax are booleans to allow for the extension"""
385 min = float(frac
) * math
.floor(min / float(frac
) + self
.epsilon
)
387 max = float(frac
) * math
.ceil(max / float(frac
) - self
.epsilon
)
390 def getticks(self
, min, max, frac
, ticklevel
=None, labellevel
=None):
391 """return a list of equal spaced ticks
392 - the tick distance is frac, the ticklevel is set to ticklevel and
393 the labellevel is set to labellevel
394 - min, max is the range where ticks should be placed"""
395 imin
= int(math
.ceil(min / float(frac
) - 0.5 * self
.epsilon
))
396 imax
= int(math
.floor(max / float(frac
) + 0.5 * self
.epsilon
))
398 for i
in range(imin
, imax
+ 1):
399 ticks
.append(tick((long(i
) * frac
.enum
, frac
.denom
), ticklevel
=ticklevel
, labellevel
=labellevel
))
402 def defaultpart(self
, min, max, extendmin
, extendmax
):
403 if self
.extendtick
is not None and len(self
.ticklist
) > self
.extendtick
:
404 min, max = self
.extendminmax(min, max, self
.ticklist
[self
.extendtick
], extendmin
, extendmax
)
405 if self
.extendlabel
is not None and len(self
.labellist
) > self
.extendlabel
:
406 min, max = self
.extendminmax(min, max, self
.labellist
[self
.extendlabel
], extendmin
, extendmax
)
409 for i
in range(len(self
.ticklist
)):
410 ticks
= _mergeticklists(ticks
, self
.getticks(min, max, self
.ticklist
[i
], ticklevel
= i
))
411 for i
in range(len(self
.labellist
)):
412 ticks
= _mergeticklists(ticks
, self
.getticks(min, max, self
.labellist
[i
], labellevel
= i
))
414 _mergelabels(ticks
, self
.labels
)
426 """automatic linear partition scheme
427 - possible tick distances are explicitly provided to the constructor
428 - tick distances are adjusted to the axis range by multiplication or division by 10"""
430 __implements__
= _Iparter
432 defaultvariants
= ((frac((1, 1)), frac((1, 2))),
433 (frac((2, 1)), frac((1, 1))),
434 (frac((5, 2)), frac((5, 4))),
435 (frac((5, 1)), frac((5, 2))))
437 def __init__(self
, variants
=defaultvariants
, extendtick
=0, epsilon
=1e-10):
438 """configuration of the partition scheme
439 - variants is a list of tickdist
440 - tickdist should be a list, where the first value
441 is the distance between ticks with ticklevel 0,
442 the second for ticklevel 1, etc.
443 - tickdist values are passed to the frac constructor
444 - labellevel is set to None except for those ticks in the partitions,
445 where ticklevel is zero. There labellevel is also set to zero.
446 - extendtick allows for the extension of the range given to the
447 defaultpart method to include the next tick with the specified
448 level (None turns off this feature); note, that this feature is
449 also disabled, when an axis prohibits its range extension by
450 the extendmin/extendmax variables given to the defaultpart method
451 - epsilon allows for exceeding the axis range by this relative
452 value (relative to the axis range given to the defaultpart method)
453 without creating another tick specified by extendtick"""
454 self
.variants
= variants
455 self
.extendtick
= extendtick
456 self
.epsilon
= epsilon
458 def defaultpart(self
, min, max, extendmin
, extendmax
):
459 logmm
= math
.log(max - min) / math
.log(10)
460 if logmm
< 0: # correction for rounding towards zero of the int routine
461 base
= frac((10L, 1), int(logmm
- 1))
463 base
= frac((10L, 1), int(logmm
))
464 ticks
= map(frac
, self
.variants
[0])
465 useticks
= [tick
* base
for tick
in ticks
]
466 self
.lesstickindex
= self
.moretickindex
= 0
467 self
.lessbase
= frac((base
.enum
, base
.denom
))
468 self
.morebase
= frac((base
.enum
, base
.denom
))
469 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
470 part
= linparter(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
471 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
474 if self
.lesstickindex
< len(self
.variants
) - 1:
475 self
.lesstickindex
+= 1
477 self
.lesstickindex
= 0
478 self
.lessbase
.enum
*= 10
479 ticks
= map(frac
, self
.variants
[self
.lesstickindex
])
480 useticks
= [tick
* self
.lessbase
for tick
in ticks
]
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
.moretickindex
:
486 self
.moretickindex
-= 1
488 self
.moretickindex
= len(self
.variants
) - 1
489 self
.morebase
.denom
*= 10
490 ticks
= map(frac
, self
.variants
[self
.moretickindex
])
491 useticks
= [tick
* self
.morebase
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
)
497 """storage class for the definition of logarithmic axes partitions
498 instances of this class define tick positions suitable for
499 logarithmic axes by the following instance variables:
500 - exp: integer, which defines multiplicator (usually 10)
501 - pres: list of tick positions (rational numbers, e.g. instances of frac)
502 possible positions are these tick positions and arbitrary divisions
503 and multiplications by the exp value"""
505 def __init__(self
, pres
, exp
):
506 "create a preexp instance and store its pres and exp information"
507 self
.pres
= helper
.ensuresequence(pres
)
511 class logparter(linparter
):
512 """logarithmic partition scheme
513 ticks and label positions are explicitly provided to the constructor"""
515 __implements__
= _Iparter
517 pre1exp5
= preexp(frac((1, 1)), 100000)
518 pre1exp4
= preexp(frac((1, 1)), 10000)
519 pre1exp3
= preexp(frac((1, 1)), 1000)
520 pre1exp2
= preexp(frac((1, 1)), 100)
521 pre1exp
= preexp(frac((1, 1)), 10)
522 pre125exp
= preexp((frac((1, 1)), frac((2, 1)), frac((5, 1))), 10)
523 pre1to9exp
= preexp(map(lambda x
: frac((x
, 1)), range(1, 10)), 10)
524 # ^- we always include 1 in order to get extendto(tick|label)level to work as expected
526 def __init__(self
, tickpos
=None, labelpos
=None, labels
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
527 """configuration of the partition scheme
528 - tickpos and labelpos should be a list, where the first entry
529 is a preexp instance describing ticks with ticklevel/labellevel 0,
530 the second is a preexp instance for ticklevel/labellevel 1, etc.;
531 a single entry is allowed without being a list
532 - when labelpos is None and tickpos is not None, the tick entries
533 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
534 - labels are applied to the resulting partition via the
535 mergetexts function (additional information available there)
536 - extendtick allows for the extension of the range given to the
537 defaultpart method to include the next tick with the specified
538 level (None turns off this feature); note, that this feature is
539 also disabled, when an axis prohibits its range extension by
540 the extendmin/extendmax variables given to the defaultpart method
541 - extendlabel is analogous to extendtick, but for labels
542 - epsilon allows for exceeding the axis range by this relative
543 logarithm value (relative to the logarithm axis range given
544 to the defaultpart method) without creating another tick
545 specified by extendtick/extendlabel"""
546 if tickpos
is None and labels
is not None:
547 self
.ticklist
= (helper
.ensuresequence(labelpos
)[0],)
549 self
.ticklist
= helper
.ensuresequence(tickpos
)
551 if labelpos
is None and tickpos
is not None:
552 self
.labellist
= (helper
.ensuresequence(tickpos
)[0],)
554 self
.labellist
= helper
.ensuresequence(labelpos
)
556 self
.extendtick
= extendtick
557 self
.extendlabel
= extendlabel
558 self
.epsilon
= epsilon
560 def extendminmax(self
, min, max, preexp
, extendmin
, extendmax
):
561 """return new min, max tuple extending the range min, max
562 preexp describes the allowed tick positions
563 extendmin and extendmax are booleans to allow for the extension"""
566 for i
in xrange(len(preexp
.pres
)):
567 imin
= int(math
.floor(math
.log(min / float(preexp
.pres
[i
])) /
568 math
.log(preexp
.exp
) + self
.epsilon
)) + 1
569 imax
= int(math
.ceil(math
.log(max / float(preexp
.pres
[i
])) /
570 math
.log(preexp
.exp
) - self
.epsilon
)) - 1
571 if minpower
is None or imin
< minpower
:
572 minpower
, minindex
= imin
, i
573 if maxpower
is None or imax
>= maxpower
:
574 maxpower
, maxindex
= imax
, i
576 minfrac
= preexp
.pres
[minindex
- 1]
578 minfrac
= preexp
.pres
[-1]
580 if maxindex
!= len(preexp
.pres
) - 1:
581 maxfrac
= preexp
.pres
[maxindex
+ 1]
583 maxfrac
= preexp
.pres
[0]
586 min = float(minfrac
) * float(preexp
.exp
) ** minpower
588 max = float(maxfrac
) * float(preexp
.exp
) ** maxpower
591 def getticks(self
, min, max, preexp
, ticklevel
=None, labellevel
=None):
592 """return a list of ticks
593 - preexp describes the allowed tick positions
594 - the ticklevel of the ticks is set to ticklevel and
595 the labellevel is set to labellevel
596 - min, max is the range where ticks should be placed"""
600 for f
in preexp
.pres
:
602 imin
= int(math
.ceil(math
.log(min / float(f
)) /
603 math
.log(preexp
.exp
) - 0.5 * self
.epsilon
))
604 imax
= int(math
.floor(math
.log(max / float(f
)) /
605 math
.log(preexp
.exp
) + 0.5 * self
.epsilon
))
606 for i
in range(imin
, imax
+ 1):
607 pos
= f
* frac((preexp
.exp
, 1), i
)
608 fracticks
.append(tick((pos
.enum
, pos
.denom
), ticklevel
= ticklevel
, labellevel
= labellevel
))
609 ticks
= _mergeticklists(ticks
, fracticks
)
613 class autologparter(logparter
):
614 """automatic logarithmic partition scheme
615 possible tick positions are explicitly provided to the constructor"""
617 __implements__
= _Iparter
619 defaultvariants
= (((logparter
.pre1exp
, # ticks
620 logparter
.pre1to9exp
), # subticks
621 (logparter
.pre1exp
, # labels
622 logparter
.pre125exp
)), # sublevels
624 ((logparter
.pre1exp
, # ticks
625 logparter
.pre1to9exp
), # subticks
626 None), # labels like ticks
628 ((logparter
.pre1exp2
, # ticks
629 logparter
.pre1exp
), # subticks
630 None), # labels like ticks
632 ((logparter
.pre1exp3
, # ticks
633 logparter
.pre1exp
), # subticks
634 None), # labels like ticks
636 ((logparter
.pre1exp4
, # ticks
637 logparter
.pre1exp
), # subticks
638 None), # labels like ticks
640 ((logparter
.pre1exp5
, # ticks
641 logparter
.pre1exp
), # subticks
642 None)) # labels like ticks
644 def __init__(self
, variants
=defaultvariants
, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
645 """configuration of the partition scheme
646 - variants should be a list of pairs of lists of preexp
648 - within each pair the first list contains preexp, where
649 the first preexp instance describes ticks positions with
650 ticklevel 0, the second preexp for ticklevel 1, etc.
651 - the second list within each pair describes the same as
652 before, but for labels
653 - within each pair: when the second entry (for the labels) is None
654 and the first entry (for the ticks) ticks is not None, the tick
655 entries for ticklevel 0 are used for labels and vice versa
657 - extendtick allows for the extension of the range given to the
658 defaultpart method to include the next tick with the specified
659 level (None turns off this feature); note, that this feature is
660 also disabled, when an axis prohibits its range extension by
661 the extendmin/extendmax variables given to the defaultpart method
662 - extendlabel is analogous to extendtick, but for labels
663 - epsilon allows for exceeding the axis range by this relative
664 logarithm value (relative to the logarithm axis range given
665 to the defaultpart method) without creating another tick
666 specified by extendtick/extendlabel"""
667 self
.variants
= variants
668 if len(variants
) > 2:
669 self
.variantsindex
= divmod(len(variants
), 2)[0]
671 self
.variantsindex
= 0
672 self
.extendtick
= extendtick
673 self
.extendlabel
= extendlabel
674 self
.epsilon
= epsilon
676 def defaultpart(self
, min, max, extendmin
, extendmax
):
677 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
678 self
.morevariantsindex
= self
.variantsindex
679 self
.lessvariantsindex
= self
.variantsindex
680 part
= logparter(tickpos
=self
.variants
[self
.variantsindex
][0], labelpos
=self
.variants
[self
.variantsindex
][1],
681 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
682 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
685 self
.lessvariantsindex
+= 1
686 if self
.lessvariantsindex
< len(self
.variants
):
687 part
= logparter(tickpos
=self
.variants
[self
.lessvariantsindex
][0], labelpos
=self
.variants
[self
.lessvariantsindex
][1],
688 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
689 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
692 self
.morevariantsindex
-= 1
693 if self
.morevariantsindex
>= 0:
694 part
= logparter(tickpos
=self
.variants
[self
.morevariantsindex
][0], labelpos
=self
.variants
[self
.morevariantsindex
][1],
695 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
696 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
700 ################################################################################
702 # conseptional remarks:
703 # - raters are used to calculate a rating for a realization of something
704 # - here, a rating means a positive floating point value
705 # - ratings are used to order those realizations by their suitability (lower
706 # ratings are better)
707 # - a rating of None means not suitable at all (those realizations should be
709 ################################################################################
714 - a cube rater has an optimal value, where the rate becomes zero
715 - for a left (below the optimum) and a right value (above the optimum),
716 the rating is value is set to 1 (modified by an overall weight factor
718 - the analytic form of the rating is cubic for both, the left and
719 the right side of the rater, independently"""
721 # __implements__ = sole implementation
723 def __init__(self
, opt
, left
=None, right
=None, weight
=1):
724 """initializes the rater
725 - by default, left is set to zero, right is set to 3*opt
726 - left should be smaller than opt, right should be bigger than opt
727 - weight should be positive and is a factor multiplicated to the rates"""
737 def rate(self
, value
, density
):
738 """returns a rating for a value
739 - the density lineary rescales the rater (the optimum etc.),
740 e.g. a value bigger than one increases the optimum (when it is
741 positive) and a value lower than one decreases the optimum (when
742 it is positive); the density itself should be positive"""
743 opt
= self
.opt
* density
745 other
= self
.left
* density
747 other
= self
.right
* density
750 factor
= (value
- opt
) / float(other
- opt
)
751 return self
.weight
* (factor
** 3)
755 # TODO: update docstring
756 """a distance rater (rates a list of distances)
757 - the distance rater rates a list of distances by rating each independently
758 and returning the average rate
759 - there is an optimal value, where the rate becomes zero
760 - the analytic form is linary for values above the optimal value
761 (twice the optimal value has the rating one, three times the optimal
762 value has the rating two, etc.)
763 - the analytic form is reciprocal subtracting one for values below the
764 optimal value (halve the optimal value has the rating one, one third of
765 the optimal value has the rating two, etc.)"""
767 # __implements__ = sole implementation
769 def __init__(self
, opt
, weight
=0.1):
770 """inititializes the rater
771 - opt is the optimal length (a visual PyX length)
772 - weight should be positive and is a factor multiplicated to the rates"""
776 def rate(self
, distances
, density
):
778 - the distances are a list of positive floats in PostScript points
779 - the density lineary rescales the rater (the optimum etc.),
780 e.g. a value bigger than one increases the optimum (when it is
781 positive) and a value lower than one decreases the optimum (when
782 it is positive); the density itself should be positive"""
784 opt
= unit
.topt(unit
.length(self
.opt_str
, default_type
="v")) / density
786 for distance
in distances
:
788 rate
+= self
.weight
* (opt
/ distance
- 1)
790 rate
+= self
.weight
* (distance
/ opt
- 1)
791 return rate
/ float(len(distances
))
796 - the rating of axes is splited into two separate parts:
797 - rating of the ticks in terms of the number of ticks, subticks,
799 - rating of the label distances
800 - in the end, a rate for ticks is the sum of these rates
801 - it is useful to first just rate the number of ticks etc.
802 and selecting those partitions, where this fits well -> as soon
803 as an complete rate (the sum of both parts from the list above)
804 of a first ticks is below a rate of just the number of ticks,
805 subticks labels etc. of other ticks, those other ticks will never
806 be better than the first one -> we gain speed by minimizing the
807 number of ticks, where label distances have to be taken into account)
808 - both parts of the rating are shifted into instances of raters
809 defined above --- right now, there is not yet a strict interface
810 for this delegation (should be done as soon as it is needed)"""
812 # __implements__ = sole implementation
814 linticks
= (cuberater(4), cuberater(10, weight
=0.5), )
815 linlabels
= (cuberater(4), )
816 logticks
= (cuberater(5, right
=20), cuberater(20, right
=100, weight
=0.5), )
817 loglabels
= (cuberater(5, right
=20), cuberater(5, left
=-20, right
=20, weight
=0.5), )
818 stdrange
= cuberater(1, weight
=2)
819 stddistance
= distancerater("1 cm")
821 def __init__(self
, ticks
=linticks
, labels
=linlabels
, range=stdrange
, distance
=stddistance
):
822 """initializes the axis rater
823 - ticks and labels are lists of instances of a value rater
824 - the first entry in ticks rate the number of ticks, the
825 second the number of subticks, etc.; when there are no
826 ticks of a level or there is not rater for a level, the
827 level is just ignored
828 - labels is analogous, but for labels
829 - within the rating, all ticks with a higher level are
830 considered as ticks for a given level
831 - range is a value rater instance, which rates the covering
832 of an axis range by the ticks (as a relative value of the
833 tick range vs. the axis range), ticks might cover less or
834 more than the axis range (for the standard automatic axis
835 partition schemes an extention of the axis range is normal
836 and should get some penalty)
837 - distance is an distance rater instance"""
841 self
.distance
= distance
843 def rateticks(self
, axis
, ticks
, density
):
844 """rates ticks by the number of ticks, subticks, labels etc.
845 - takes into account the number of ticks, subticks, labels
846 etc. and the coverage of the axis range by the ticks
847 - when there are no ticks of a level or there was not rater
848 given in the constructor for a level, the level is just
850 - the method returns the sum of the rating results divided
851 by the sum of the weights of the raters
852 - within the rating, all ticks with a higher level are
853 considered as ticks for a given level"""
854 maxticklevel
= maxlabellevel
= 0
856 if tick
.ticklevel
is not None and tick
.ticklevel
>= maxticklevel
:
857 maxticklevel
= tick
.ticklevel
+ 1
858 if tick
.labellevel
is not None and tick
.labellevel
>= maxlabellevel
:
859 maxlabellevel
= tick
.labellevel
+ 1
860 numticks
= [0]*maxticklevel
861 numlabels
= [0]*maxlabellevel
863 if tick
.ticklevel
is not None:
864 for level
in range(tick
.ticklevel
, maxticklevel
):
866 if tick
.labellevel
is not None:
867 for level
in range(tick
.labellevel
, maxlabellevel
):
868 numlabels
[level
] += 1
871 for numtick
, rater
in zip(numticks
, self
.ticks
):
872 rate
+= rater
.rate(numtick
, density
)
873 weight
+= rater
.weight
874 for numlabel
, rater
in zip(numlabels
, self
.labels
):
875 rate
+= rater
.rate(numlabel
, density
)
876 weight
+= rater
.weight
879 def raterange(self
, tickrange
, datarange
):
880 """rate the range covered by the ticks compared to the range
882 - tickrange and datarange are the ranges covered by the ticks
883 and the data in graph coordinates
884 - usually, the datarange is 1 (ticks are calculated for a
886 - the ticks might cover less or more than the data range (for
887 the standard automatic axis partition schemes an extention
888 of the axis range is normal and should get some penalty)"""
889 return self
.range.rate(tickrange
, datarange
)
891 def ratelayout(self
, axiscanvas
, density
):
892 """rate distances of the labels in an axis canvas
893 - the distances should be collected as box distances of
895 - the axiscanvas provides a labels attribute for easy
896 access to the labels whose distances have to be taken
898 - the density is used within the distancerate instance"""
899 if len(axiscanvas
.labels
) > 1:
901 distances
= [axiscanvas
.labels
[i
].boxdistance_pt(axiscanvas
.labels
[i
+1]) for i
in range(len(axiscanvas
.labels
) - 1)]
902 except box
.BoxCrossError
:
904 return self
.distance
.rate(distances
, density
)
909 ################################################################################
911 # texter automatically create labels for tick instances
912 ################################################################################
917 def labels(self
, ticks
):
918 """fill the label attribute of ticks
919 - ticks is a list of instances of tick
920 - for each element of ticks the value of the attribute label is set to
921 a string appropriate to the attributes enum and denom of that tick
923 - label attributes of the tick instances are just kept, whenever they
924 are not equal to None
925 - the method might extend the labelattrs attribute of the ticks"""
928 class rationaltexter
:
929 "a texter creating rational labels (e.g. 'a/b' or even 'a \over b')"
930 # XXX: we use divmod here to be more expicit
932 __implements__
= _Itexter
934 def __init__(self
, prefix
="", infix
="", suffix
="",
935 enumprefix
="", enuminfix
="", enumsuffix
="",
936 denomprefix
="", denominfix
="", denomsuffix
="",
937 plus
="", minus
="-", minuspos
=0, over
=r
"{{%s}\over{%s}}",
938 equaldenom
=0, skip1
=1, skipenum0
=1, skipenum1
=1, skipdenom1
=1,
939 labelattrs
=textmodule
.mathmode
):
940 r
"""initializes the instance
941 - prefix, infix, and suffix (strings) are added at the begin,
942 immediately after the minus, and at the end of the label,
944 - prefixenum, infixenum, and suffixenum (strings) are added
945 to the labels enumerator correspondingly
946 - prefixdenom, infixdenom, and suffixdenom (strings) are added
947 to the labels denominator correspondingly
948 - plus or minus (string) is inserted for non-negative or negative numbers
949 - minuspos is an integer, which determines the position, where the
950 plus or minus sign has to be placed; the following values are allowed:
951 1 - writes the plus or minus in front of the enumerator
952 0 - writes the plus or minus in front of the hole fraction
953 -1 - writes the plus or minus in front of the denominator
954 - over (string) is taken as a format string generating the
955 fraction bar; it has to contain exactly two string insert
956 operators "%s" -- the first for the enumerator and the second
957 for the denominator; by far the most common examples are
958 r"{{%s}\over{%s}}" and "{{%s}/{%s}}"
959 - usually the enumerator and denominator are canceled; however,
960 when equaldenom is set, the least common multiple of all
962 - skip1 (boolean) just prints the prefix, the plus or minus,
963 the infix and the suffix, when the value is plus or minus one
964 and at least one of prefix, infix and the suffix is present
965 - skipenum0 (boolean) just prints a zero instead of
966 the hole fraction, when the enumerator is zero;
967 no prefixes, infixes, and suffixes are taken into account
968 - skipenum1 (boolean) just prints the enumprefix, the plus or minus,
969 the enuminfix and the enumsuffix, when the enum value is plus or minus one
970 and at least one of enumprefix, enuminfix and the enumsuffix is present
971 - skipdenom1 (boolean) just prints the enumerator instead of
972 the hole fraction, when the denominator is one and none of the parameters
973 denomprefix, denominfix and denomsuffix are set and minuspos is not -1 or the
975 - labelattrs is a list of attributes for a texrunners text method;
976 a single is allowed without being a list; None is considered as
981 self
.enumprefix
= enumprefix
982 self
.enuminfix
= enuminfix
983 self
.enumsuffix
= enumsuffix
984 self
.denomprefix
= denomprefix
985 self
.denominfix
= denominfix
986 self
.denomsuffix
= denomsuffix
989 self
.minuspos
= minuspos
991 self
.equaldenom
= equaldenom
993 self
.skipenum0
= skipenum0
994 self
.skipenum1
= skipenum1
995 self
.skipdenom1
= skipdenom1
996 self
.labelattrs
= helper
.ensurelist(labelattrs
)
999 """returns the greates common divisor of all elements in n
1000 - the elements of n must be non-negative integers
1001 - return None if the number of elements is zero
1002 - the greates common divisor is not affected when some
1003 of the elements are zero, but it becomes zero when
1004 all elements are zero"""
1010 i
, (dummy
, j
) = j
, divmod(i
, j
)
1015 res
= self
.gcd(res
, i
)
1019 """returns the least common multiple of all elements in n
1020 - the elements of n must be non-negative integers
1021 - return None if the number of elements is zero
1022 - the least common multiple is zero when some of the
1023 elements are zero"""
1027 res
= divmod(res
* i
, self
.gcd(res
, i
))[0]
1030 def labels(self
, ticks
):
1033 if tick
.label
is None and tick
.labellevel
is not None:
1034 labeledticks
.append(tick
)
1035 tick
.temp_fracenum
= tick
.enum
1036 tick
.temp_fracdenom
= tick
.denom
1037 tick
.temp_fracminus
= 1
1038 if tick
.temp_fracenum
< 0:
1039 tick
.temp_fracminus
= -tick
.temp_fracminus
1040 tick
.temp_fracenum
= -tick
.temp_fracenum
1041 if tick
.temp_fracdenom
< 0:
1042 tick
.temp_fracminus
= -tick
.temp_fracminus
1043 tick
.temp_fracdenom
= -tick
.temp_fracdenom
1044 gcd
= self
.gcd(tick
.temp_fracenum
, tick
.temp_fracdenom
)
1045 (tick
.temp_fracenum
, dummy1
), (tick
.temp_fracdenom
, dummy2
) = divmod(tick
.temp_fracenum
, gcd
), divmod(tick
.temp_fracdenom
, gcd
)
1047 equaldenom
= self
.lcm(*[tick
.temp_fracdenom
for tick
in ticks
if tick
.label
is None])
1048 if equaldenom
is not None:
1049 for tick
in labeledticks
:
1050 factor
, dummy
= divmod(equaldenom
, tick
.temp_fracdenom
)
1051 tick
.temp_fracenum
, tick
.temp_fracdenom
= factor
* tick
.temp_fracenum
, factor
* tick
.temp_fracdenom
1052 for tick
in labeledticks
:
1053 fracminus
= fracenumminus
= fracdenomminus
= ""
1054 if tick
.temp_fracminus
== -1:
1055 plusminus
= self
.minus
1057 plusminus
= self
.plus
1058 if self
.minuspos
== 0:
1059 fracminus
= plusminus
1060 elif self
.minuspos
== 1:
1061 fracenumminus
= plusminus
1062 elif self
.minuspos
== -1:
1063 fracdenomminus
= plusminus
1065 raise RuntimeError("invalid minuspos")
1066 if self
.skipenum0
and tick
.temp_fracenum
== 0:
1068 elif (self
.skip1
and self
.skipdenom1
and tick
.temp_fracenum
== 1 and tick
.temp_fracdenom
== 1 and
1069 (len(self
.prefix
) or len(self
.infix
) or len(self
.suffix
)) and
1070 not len(fracenumminus
) and not len(self
.enumprefix
) and not len(self
.enuminfix
) and not len(self
.enumsuffix
) and
1071 not len(fracdenomminus
) and not len(self
.denomprefix
) and not len(self
.denominfix
) and not len(self
.denomsuffix
)):
1072 tick
.label
= "%s%s%s%s" % (self
.prefix
, fracminus
, self
.infix
, self
.suffix
)
1074 if self
.skipenum1
and tick
.temp_fracenum
== 1 and (len(self
.enumprefix
) or len(self
.enuminfix
) or len(self
.enumsuffix
)):
1075 tick
.temp_fracenum
= "%s%s%s%s" % (self
.enumprefix
, fracenumminus
, self
.enuminfix
, self
.enumsuffix
)
1077 tick
.temp_fracenum
= "%s%s%s%i%s" % (self
.enumprefix
, fracenumminus
, self
.enuminfix
, tick
.temp_fracenum
, self
.enumsuffix
)
1078 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
):
1079 frac
= tick
.temp_fracenum
1081 tick
.temp_fracdenom
= "%s%s%s%i%s" % (self
.denomprefix
, fracdenomminus
, self
.denominfix
, tick
.temp_fracdenom
, self
.denomsuffix
)
1082 frac
= self
.over
% (tick
.temp_fracenum
, tick
.temp_fracdenom
)
1083 tick
.label
= "%s%s%s%s%s" % (self
.prefix
, fracminus
, self
.infix
, frac
, self
.suffix
)
1084 tick
.labelattrs
.extend(self
.labelattrs
)
1086 # del tick.temp_fracenum # we've inserted those temporary variables ... and do not care any longer about them
1087 # del tick.temp_fracdenom
1088 # del tick.temp_fracminus
1092 class decimaltexter
:
1093 "a texter creating decimal labels (e.g. '1.234' or even '0.\overline{3}')"
1095 __implements__
= _Itexter
1097 def __init__(self
, prefix
="", infix
="", suffix
="", equalprecision
=0,
1098 decimalsep
=".", thousandsep
="", thousandthpartsep
="",
1099 plus
="", minus
="-", period
=r
"\overline{%s}", labelattrs
=textmodule
.mathmode
):
1100 r
"""initializes the instance
1101 - prefix, infix, and suffix (strings) are added at the begin,
1102 immediately after the minus, and at the end of the label,
1104 - decimalsep, thousandsep, and thousandthpartsep (strings)
1105 are used as separators
1106 - plus or minus (string) is inserted for non-negative or negative numbers
1107 - period (string) is taken as a format string generating a period;
1108 it has to contain exactly one string insert operators "%s" for the
1109 period; usually it should be r"\overline{%s}"
1110 - labelattrs is a list of attributes for a texrunners text method;
1111 a single is allowed without being a list; None is considered as
1113 self
.prefix
= prefix
1115 self
.suffix
= suffix
1116 self
.equalprecision
= equalprecision
1117 self
.decimalsep
= decimalsep
1118 self
.thousandsep
= thousandsep
1119 self
.thousandthpartsep
= thousandthpartsep
1122 self
.period
= period
1123 self
.labelattrs
= helper
.ensurelist(labelattrs
)
1125 def labels(self
, ticks
):
1129 if tick
.label
is None and tick
.labellevel
is not None:
1130 labeledticks
.append(tick
)
1131 m
, n
= tick
.enum
, tick
.denom
1134 whole
, reminder
= divmod(m
, n
)
1136 if len(self
.thousandsep
):
1140 tick
.label
+= whole
[i
]
1141 if not ((l
-i
-1) % 3) and l
> i
+1:
1142 tick
.label
+= self
.thousandsep
1146 tick
.label
+= self
.decimalsep
1148 tick
.temp_decprecision
= 0
1150 tick
.temp_decprecision
+= 1
1151 if reminder
in oldreminders
:
1152 tick
.temp_decprecision
= None
1153 periodstart
= len(tick
.label
) - (len(oldreminders
) - oldreminders
.index(reminder
))
1154 tick
.label
= tick
.label
[:periodstart
] + self
.period
% tick
.label
[periodstart
:]
1156 oldreminders
+= [reminder
]
1158 whole
, reminder
= divmod(reminder
, n
)
1159 if not ((tick
.temp_decprecision
- 1) % 3) and tick
.temp_decprecision
> 1:
1160 tick
.label
+= self
.thousandthpartsep
1161 tick
.label
+= str(whole
)
1162 if maxdecprecision
< tick
.temp_decprecision
:
1163 maxdecprecision
= tick
.temp_decprecision
1164 if self
.equalprecision
:
1165 for tick
in labeledticks
:
1166 if tick
.temp_decprecision
is not None:
1167 if tick
.temp_decprecision
== 0 and maxdecprecision
> 0:
1168 tick
.label
+= self
.decimalsep
1169 for i
in range(tick
.temp_decprecision
, maxdecprecision
):
1170 if not ((i
- 1) % 3) and i
> 1:
1171 tick
.label
+= self
.thousandthpartsep
1173 for tick
in labeledticks
:
1174 if tick
.enum
* tick
.denom
< 0:
1175 plusminus
= self
.minus
1177 plusminus
= self
.plus
1178 tick
.label
= "%s%s%s%s%s" % (self
.prefix
, plusminus
, self
.infix
, tick
.label
, self
.suffix
)
1179 tick
.labelattrs
.extend(self
.labelattrs
)
1181 # del tick.temp_decprecision # we've inserted this temporary variable ... and do not care any longer about it
1184 class exponentialtexter
:
1185 "a texter creating labels with exponentials (e.g. '2\cdot10^5')"
1187 __implements__
= _Itexter
1189 def __init__(self
, plus
="", minus
="-",
1190 mantissaexp
=r
"{{%s}\cdot10^{%s}}",
1193 nomantissaexp
=r
"{10^{%s}}",
1194 minusnomantissaexp
=r
"{-10^{%s}}",
1195 mantissamin
=frac((1, 1)), mantissamax
=frac((10, 1)),
1196 skipmantissa1
=0, skipallmantissa1
=1,
1197 mantissatexter
=decimaltexter()):
1198 r
"""initializes the instance
1199 - plus or minus (string) is inserted for non-negative or negative exponents
1200 - mantissaexp (string) is taken as a format string generating the exponent;
1201 it has to contain exactly two string insert operators "%s" --
1202 the first for the mantissa and the second for the exponent;
1203 examples are r"{{%s}\cdot10^{%s}}" and r"{{%s}{\rm e}{%s}}"
1204 - skipexp0 (string) is taken as a format string used for exponent 0;
1205 exactly one string insert operators "%s" for the mantissa;
1206 None turns off the special handling of exponent 0;
1207 an example is r"{%s}"
1208 - skipexp1 (string) is taken as a format string used for exponent 1;
1209 exactly one string insert operators "%s" for the mantissa;
1210 None turns off the special handling of exponent 1;
1211 an example is r"{{%s}\cdot10}"
1212 - nomantissaexp (string) is taken as a format string generating the exponent
1213 when the mantissa is one and should be skipped; it has to contain
1214 exactly one string insert operators "%s" for the exponent;
1215 an examples is r"{10^{%s}}"
1216 - minusnomantissaexp (string) is taken as a format string generating the exponent
1217 when the mantissa is minus one and should be skipped; it has to contain
1218 exactly one string insert operators "%s" for the exponent;
1219 None turns off the special handling of mantissa -1;
1220 an examples is r"{-10^{%s}}"
1221 - mantissamin and mantissamax are the minimum and maximum of the mantissa;
1222 they are frac instances greater than zero and mantissamin < mantissamax;
1223 the sign of the tick is ignored here
1224 - skipmantissa1 (boolean) turns on skipping of any mantissa equals one
1225 (and minus when minusnomantissaexp is set)
1226 - skipallmantissa1 (boolean) as above, but all mantissas must be 1 (or -1)
1227 - mantissatexter is the texter for the mantissa
1228 - the skipping of a mantissa is stronger than the skipping of an exponent"""
1231 self
.mantissaexp
= mantissaexp
1232 self
.skipexp0
= skipexp0
1233 self
.skipexp1
= skipexp1
1234 self
.nomantissaexp
= nomantissaexp
1235 self
.minusnomantissaexp
= minusnomantissaexp
1236 self
.mantissamin
= mantissamin
1237 self
.mantissamax
= mantissamax
1238 self
.mantissamindivmax
= self
.mantissamin
/ self
.mantissamax
1239 self
.mantissamaxdivmin
= self
.mantissamax
/ self
.mantissamin
1240 self
.skipmantissa1
= skipmantissa1
1241 self
.skipallmantissa1
= skipallmantissa1
1242 self
.mantissatexter
= mantissatexter
1244 def labels(self
, ticks
):
1247 if tick
.label
is None and tick
.labellevel
is not None:
1248 tick
.temp_orgenum
, tick
.temp_orgdenom
= tick
.enum
, tick
.denom
1249 labeledticks
.append(tick
)
1252 while abs(tick
) >= self
.mantissamax
:
1254 x
= tick
* self
.mantissamindivmax
1255 tick
.enum
, tick
.denom
= x
.enum
, x
.denom
1256 while abs(tick
) < self
.mantissamin
:
1258 x
= tick
* self
.mantissamaxdivmin
1259 tick
.enum
, tick
.denom
= x
.enum
, x
.denom
1260 if tick
.temp_exp
< 0:
1261 tick
.temp_exp
= "%s%i" % (self
.minus
, -tick
.temp_exp
)
1263 tick
.temp_exp
= "%s%i" % (self
.plus
, tick
.temp_exp
)
1264 self
.mantissatexter
.labels(labeledticks
)
1265 if self
.minusnomantissaexp
is not None:
1266 allmantissa1
= len(labeledticks
) == len([tick
for tick
in labeledticks
if abs(tick
.enum
) == abs(tick
.denom
)])
1268 allmantissa1
= len(labeledticks
) == len([tick
for tick
in labeledticks
if tick
.enum
== tick
.denom
])
1269 for tick
in labeledticks
:
1270 if (self
.skipallmantissa1
and allmantissa1
or
1271 (self
.skipmantissa1
and (tick
.enum
== tick
.denom
or
1272 (tick
.enum
== -tick
.denom
and self
.minusnomantissaexp
is not None)))):
1273 if tick
.enum
== tick
.denom
:
1274 tick
.label
= self
.nomantissaexp
% tick
.temp_exp
1276 tick
.label
= self
.minusnomantissaexp
% tick
.temp_exp
1278 if tick
.temp_exp
== "0" and self
.skipexp0
is not None:
1279 tick
.label
= self
.skipexp0
% tick
.label
1280 elif tick
.temp_exp
== "1" and self
.skipexp1
is not None:
1281 tick
.label
= self
.skipexp1
% tick
.label
1283 tick
.label
= self
.mantissaexp
% (tick
.label
, tick
.temp_exp
)
1284 tick
.enum
, tick
.denom
= tick
.temp_orgenum
, tick
.temp_orgdenom
1286 # del tick.temp_orgenum # we've inserted those temporary variables ... and do not care any longer about them
1287 # del tick.temp_orgdenom
1291 class defaulttexter
:
1292 "a texter creating decimal or exponential labels"
1294 __implements__
= _Itexter
1296 def __init__(self
, smallestdecimal
=frac((1, 1000)),
1297 biggestdecimal
=frac((9999, 1)),
1299 decimaltexter
=decimaltexter(),
1300 exponentialtexter
=exponentialtexter()):
1301 r
"""initializes the instance
1302 - smallestdecimal and biggestdecimal are the smallest and
1303 biggest decimal values, where the decimaltexter should be used;
1304 they are frac instances; the sign of the tick is ignored here;
1305 a tick at zero is considered for the decimaltexter as well
1306 - equaldecision (boolean) uses decimaltexter or exponentialtexter
1307 globaly (set) or for each tick separately (unset)
1308 - decimaltexter and exponentialtexter are texters to be used"""
1309 self
.smallestdecimal
= smallestdecimal
1310 self
.biggestdecimal
= biggestdecimal
1311 self
.equaldecision
= equaldecision
1312 self
.decimaltexter
= decimaltexter
1313 self
.exponentialtexter
= exponentialtexter
1315 def labels(self
, ticks
):
1319 if tick
.label
is None and tick
.labellevel
is not None:
1320 if not tick
.enum
or (abs(tick
) >= self
.smallestdecimal
and abs(tick
) <= self
.biggestdecimal
):
1321 decticks
.append(tick
)
1323 expticks
.append(tick
)
1324 if self
.equaldecision
:
1326 self
.exponentialtexter
.labels(ticks
)
1328 self
.decimaltexter
.labels(ticks
)
1330 for tick
in decticks
:
1331 self
.decimaltexter
.labels([tick
])
1332 for tick
in expticks
:
1333 self
.exponentialtexter
.labels([tick
])
1336 ################################################################################
1338 ################################################################################
1341 class axiscanvas(canvas
._canvas
):
1343 - an axis canvas is a regular canvas returned by an
1344 axispainters painter method
1345 - it contains a PyX length extent to be used for the
1346 alignment of additional axes; the axis extent should
1347 be handled by the axispainters painter method; you may
1348 apprehend this as a size information comparable to a
1349 bounding box, which must be handled manually
1350 - it contains a list of textboxes called labels which are
1351 used to rate the distances between the labels if needed
1352 by the axis later on; the painter method has not only to
1353 insert the labels into this canvas, but should also fill
1354 this list, when a rating of the distances should be
1355 performed by the axis"""
1357 # __implements__ = sole implementation
1359 def __init__(self
, *args
, **kwargs
):
1360 """initializes the instance
1361 - sets extent to zero
1362 - sets labels to an empty list"""
1363 canvas
._canvas
.__init
__(self
, *args
, **kwargs
)
1369 """create rotations accordingly to tick directions
1370 - upsidedown rotations are suppressed by rotating them by another 180 degree"""
1372 # __implements__ = sole implementation
1374 def __init__(self
, direction
, epsilon
=1e-10):
1375 """initializes the instance
1376 - direction is an angle to be used relative to the tick direction
1377 - epsilon is the value by which 90 degrees can be exceeded before
1378 an 180 degree rotation is added"""
1379 self
.direction
= direction
1380 self
.epsilon
= epsilon
1382 def trafo(self
, dx
, dy
):
1383 """returns a rotation transformation accordingly to the tick direction
1384 - dx and dy are the direction of the tick"""
1385 direction
= self
.direction
+ math
.atan2(dy
, dx
) * 180 / math
.pi
1386 while (direction
> 180 + self
.epsilon
):
1388 while (direction
< -180 - self
.epsilon
):
1390 while (direction
> 90 + self
.epsilon
):
1392 while (direction
< -90 - self
.epsilon
):
1394 return trafomodule
.rotate(direction
)
1397 rotatetext
.parallel
= rotatetext(90)
1398 rotatetext
.orthogonal
= rotatetext(180)
1401 class _Iaxispainter
:
1402 "class for painting axes"
1404 def paint(self
, axispos
, axis
, ac
=None):
1405 """paint the axis into an axiscanvas
1406 - returns the axiscanvas
1407 - when no axiscanvas is provided (the typical case), a new
1408 axiscanvas is created. however, when extending an painter
1409 by inheritance, painting on the same axiscanvas is supported
1410 by setting the axiscanvas attribute
1411 - axispos is an instance, which implements _Iaxispos to
1412 define the tick positions
1413 - the axis and should not be modified (we may
1414 add some temporary variables like axis.ticks[i].temp_xxx,
1415 which might be used just temporary) -- the idea is that
1416 all things can be used several times
1417 - also do not modify the instance (self) -- even this
1418 instance might be used several times; thus do not modify
1419 attributes like self.titleattrs etc. (use local copies)
1420 - the method might access some additional attributes from
1421 the axis, e.g. the axis title -- the axis painter should
1422 document this behavior and rely on the availability of
1423 those attributes -> it becomes a question of the proper
1424 usage of the combination of axis & axispainter
1425 - the axiscanvas is a axiscanvas instance and should be
1426 filled with ticks, labels, title, etc.; note that the
1427 extent and labels instance variables should be handled
1428 as documented in the axiscanvas"""
1432 """interface definition of axis tick position methods
1433 - these methods are used for the postitioning of the ticks
1434 when painting an axis"""
1435 # TODO: should we add a local transformation (for label text etc?)
1436 # (this might replace tickdirection (and even tickposition?))
1438 def basepath(self
, x1
=None, x2
=None):
1439 """return the basepath as a path
1440 - x1 is the start position; if not set, the basepath starts
1441 from the beginning of the axis, which might imply a
1442 value outside of the graph coordinate range [0; 1]
1443 - x2 is analogous to x1, but for the end position"""
1445 def vbasepath(self
, v1
=None, v2
=None):
1446 """return the basepath as a path
1447 - like basepath, but for graph coordinates"""
1449 def gridpath(self
, x
):
1450 """return the gridpath as a path for a given position x
1451 - might return None when no gridpath is available"""
1453 def vgridpath(self
, v
):
1454 """return the gridpath as a path for a given position v
1455 in graph coordinates
1456 - might return None when no gridpath is available"""
1458 def tickpoint_pt(self
, x
):
1459 """return the position at the basepath as a tuple (x, y) in
1460 postscript points for the position x"""
1462 def tickpoint(self
, x
):
1463 """return the position at the basepath as a tuple (x, y) in
1464 in PyX length for the position x"""
1466 def vtickpoint_pt(self
, v
):
1467 "like tickpoint_pt, but for graph coordinates"
1469 def vtickpoint(self
, v
):
1470 "like tickpoint, but for graph coordinates"
1472 def tickdirection(self
, x
):
1473 """return the direction of a tick as a tuple (dx, dy) for the
1474 position x (the direction points towards the graph)"""
1476 def vtickdirection(self
, v
):
1477 """like tickposition, but for graph coordinates"""
1481 """implements those parts of _Iaxispos which can be build
1482 out of the axis convert method and other _Iaxispos methods
1483 - base _Iaxispos methods, which need to be implemented:
1488 - other methods needed for _Iaxispos are build out of those
1489 listed above when this class is inherited"""
1491 def __init__(self
, convert
):
1492 """initializes the instance
1493 - convert is a convert method from an axis"""
1494 self
.convert
= convert
1496 def basepath(self
, x1
=None, x2
=None):
1499 return self
.vbasepath()
1501 return self
.vbasepath(v2
=self
.convert(x2
))
1504 return self
.vbasepath(v1
=self
.convert(x1
))
1506 return self
.vbasepath(v1
=self
.convert(x1
), v2
=self
.convert(x2
))
1508 def gridpath(self
, x
):
1509 return self
.vgridpath(self
.convert(x
))
1511 def tickpoint_pt(self
, x
):
1512 return self
.vtickpoint_pt(self
.convert(x
))
1514 def tickpoint(self
, x
):
1515 return self
.vtickpoint(self
.convert(x
))
1517 def vtickpoint(self
, v
):
1518 return [unit
.t_pt(x
) for x
in self
.vtickpoint(v
)]
1520 def tickdirection(self
, x
):
1521 return self
.vtickdirection(self
.convert(x
))
1524 class pathaxispos(_axispos
):
1525 """axis tick position methods along an arbitrary path"""
1527 __implements__
= _Iaxispos
1529 def __init__(self
, p
, convert
, direction
=1):
1531 self
.normpath
= path
.normpath(p
)
1532 self
.arclength
= self
.normpath
.arclength(p
)
1533 _axispos
.__init
__(self
, convert
)
1534 self
.direction
= direction
1536 def vbasepath(self
, v1
=None, v2
=None):
1541 return self
.normpath
.split(self
.normpath
.lentopar(v2
* self
.arclength
))[0]
1544 return self
.normpath
.split(self
.normpath
.lentopar(v1
* self
.arclength
))[1]
1546 return self
.normpath
.split(*self
.normpath
.lentopar([v1
* self
.arclength
, v2
* self
.arclength
]))[1]
1548 def vgridpath(self
, v
):
1551 def vtickpoint_pt(self
, v
):
1552 # XXX: path._at missing!
1553 return [unit
.topt(x
) for x
in self
.normpath
.at(self
.normpath
.lentopar(v
* self
.arclength
))]
1555 def vtickdirection(self
, v
):
1556 t
= self
.normpath
.tangent(self
.normpath
.lentopar(v
* self
.arclength
))
1557 # XXX: path._begin and path._end missing!
1558 tbegin
= [unit
.topt(x
) for x
in t
.begin()]
1559 tend
= [unit
.topt(x
) for x
in t
.end()]
1560 dx
= tend
[0]-tbegin
[0]
1561 dy
= tend
[1]-tbegin
[1]
1562 norm
= math
.sqrt(dx
*dx
+ dy
*dy
)
1563 if self
.direction
== 1:
1564 return -dy
/norm
, dx
/norm
1565 elif self
.direction
== -1:
1566 return dy
/norm
, -dx
/norm
1567 raise RuntimeError("unknown direction")
1570 class axistitlepainter
:
1571 """class for painting an axis title
1572 - the axis must have a title attribute when using this painter;
1573 this title might be None"""
1575 __implements__
= _Iaxispainter
1577 def __init__(self
, titledist
="0.3 cm",
1578 titleattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1579 titledirection
=rotatetext
.parallel
,
1581 texrunner
=textmodule
.defaulttexrunner
):
1582 """initialized the instance
1583 - titledist is a visual PyX length giving the distance
1584 of the title from the axis extent already there (a title might
1585 be added after labels or other things are plotted already)
1586 - titleattrs is a list of attributes for a texrunners text
1587 method; a single is allowed without being a list; None
1589 - titledirection is an instance of rotatetext or None
1590 - titlepos is the position of the title in graph coordinates
1591 - texrunner is the texrunner to be used to create text
1592 (the texrunner is available for further use in derived
1593 classes as instance variable texrunner)"""
1594 self
.titledist_str
= titledist
1595 self
.titleattrs
= titleattrs
1596 self
.titledirection
= titledirection
1597 self
.titlepos
= titlepos
1598 self
.texrunner
= texrunner
1600 def paint(self
, axispos
, axis
, ac
=None):
1603 if axis
.title
is not None and self
.titleattrs
is not None:
1604 titledist
= unit
.length(self
.titledist_str
, default_type
="v")
1605 x
, y
= axispos
.vtickpoint_pt(self
.titlepos
)
1606 dx
, dy
= axispos
.vtickdirection(self
.titlepos
)
1607 titleattrs
= helper
.ensurelist(self
.titleattrs
)
1608 if self
.titledirection
is not None:
1609 titleattrs
.append(self
.titledirection
.trafo(dx
, dy
))
1610 title
= self
.texrunner
.text_pt(x
, y
, axis
.title
, titleattrs
)
1611 ac
.extent
+= titledist
1612 title
.linealign(ac
.extent
, -dx
, -dy
)
1613 ac
.extent
+= title
.extent(dx
, dy
)
1618 class axispainter(axistitlepainter
):
1619 """class for painting the ticks and labels of an axis
1620 - the inherited titleaxispainter is used to paint the title of
1622 - note that the type of the elements of ticks given as an argument
1623 of the paint method must be suitable for the tick position methods
1626 __implements__
= _Iaxispainter
1628 defaultticklengths
= ["%0.5f cm" % (0.2*goldenmean
**(-i
)) for i
in range(10)]
1630 def __init__(self
, innerticklengths
=defaultticklengths
,
1631 outerticklengths
=None,
1635 basepathattrs
=style
.linecap
.square
,
1637 labelattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1638 labeldirection
=None,
1642 """initializes the instance
1643 - innerticklenths and outerticklengths are two lists of
1644 visual PyX lengths for ticks, subticks, etc. plotted inside
1645 and outside of the graph; when a single value is given, it
1646 is used for all tick levels; None turns off ticks inside or
1647 outside of the graph
1648 - tickattrs are a list of stroke attributes for the ticks;
1649 a single entry is allowed without being a list; None turns
1651 - gridattrs are a list of lists used as stroke
1652 attributes for ticks, subticks etc.; when a single list
1653 is given, it is used for ticks, subticks, etc.; a single
1654 entry is allowed without being a list; None turns off
1656 - zeropathattrs are a list of stroke attributes for a grid
1657 line at axis value zero; a single entry is allowed without
1658 being a list; None turns off the zeropath
1659 - basepathattrs are a list of stroke attributes for a grid
1660 line at axis value zero; a single entry is allowed without
1661 being a list; None turns off the basepath
1662 - labeldist is a visual PyX length for the distance of the labels
1663 from the axis basepath
1664 - labelattrs is a list of attributes for a texrunners text
1665 method; a single entry is allowed without being a list;
1666 None turns off the labels
1667 - titledirection is an instance of rotatetext or None
1668 - labelhequalize and labelvequalize (booleans) perform an equal
1669 alignment for straight vertical and horizontal axes, respectively
1670 - futher keyword arguments are passed to axistitlepainter"""
1671 # TODO: access to axis.divisor -- document, remove, ... ???
1672 self
.innerticklengths_str
= innerticklengths
1673 self
.outerticklengths_str
= outerticklengths
1674 self
.tickattrs
= tickattrs
1675 self
.gridattrs
= gridattrs
1676 self
.zeropathattrs
= zeropathattrs
1677 self
.basepathattrs
= basepathattrs
1678 self
.labeldist_str
= labeldist
1679 self
.labelattrs
= labelattrs
1680 self
.labeldirection
= labeldirection
1681 self
.labelhequalize
= labelhequalize
1682 self
.labelvequalize
= labelvequalize
1683 axistitlepainter
.__init
__(self
, **kwargs
)
1685 def paint(self
, axispos
, axis
, ac
=None):
1689 raise RuntimeError("XXX") # XXX debug only
1690 labeldist
= unit
.length(self
.labeldist_str
, default_type
="v")
1691 for tick
in axis
.ticks
:
1692 tick
.temp_v
= axis
.convert(float(tick
) * axis
.divisor
)
1693 tick
.temp_x
, tick
.temp_y
= axispos
.vtickpoint_pt(tick
.temp_v
)
1694 tick
.temp_dx
, tick
.temp_dy
= axispos
.vtickdirection(tick
.temp_v
)
1696 # create & align tick.temp_labelbox
1697 for tick
in axis
.ticks
:
1698 if tick
.labellevel
is not None:
1699 labelattrs
= helper
.getsequenceno(self
.labelattrs
, tick
.labellevel
)
1700 if labelattrs
is not None:
1701 labelattrs
= helper
.ensurelist(labelattrs
)[:]
1702 if self
.labeldirection
is not None:
1703 labelattrs
.append(self
.labeldirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
1704 if tick
.labelattrs
is not None:
1705 labelattrs
.extend(helper
.ensurelist(tick
.labelattrs
))
1706 tick
.temp_labelbox
= self
.texrunner
.text_pt(tick
.temp_x
, tick
.temp_y
, tick
.label
, labelattrs
)
1707 if len(axis
.ticks
) > 1:
1709 for tick
in axis
.ticks
[1:]:
1710 if tick
.temp_dx
!= axis
.ticks
[0].temp_dx
or tick
.temp_dy
!= axis
.ticks
[0].temp_dy
:
1714 if equaldirection
and ((not axis
.ticks
[0].temp_dx
and self
.labelvequalize
) or
1715 (not axis
.ticks
[0].temp_dy
and self
.labelhequalize
)):
1716 if self
.labelattrs
is not None:
1717 box
.linealignequal([tick
.temp_labelbox
for tick
in axis
.ticks
if tick
.labellevel
is not None],
1718 labeldist
, -axis
.ticks
[0].temp_dx
, -axis
.ticks
[0].temp_dy
)
1720 for tick
in axis
.ticks
:
1721 if tick
.labellevel
is not None and self
.labelattrs
is not None:
1722 tick
.temp_labelbox
.linealign(labeldist
, -tick
.temp_dx
, -tick
.temp_dy
)
1725 if helper
.issequence(arg
):
1726 return [unit
.length(a
, default_type
="v") for a
in arg
]
1728 return unit
.length(arg
, default_type
="v")
1729 innerticklengths
= mkv(self
.innerticklengths_str
)
1730 outerticklengths
= mkv(self
.outerticklengths_str
)
1732 for tick
in axis
.ticks
:
1733 if tick
.ticklevel
is not None:
1734 innerticklength
= helper
.getitemno(innerticklengths
, tick
.ticklevel
)
1735 outerticklength
= helper
.getitemno(outerticklengths
, tick
.ticklevel
)
1736 if innerticklength
is not None or outerticklength
is not None:
1737 if innerticklength
is None:
1739 if outerticklength
is None:
1741 tickattrs
= helper
.getsequenceno(self
.tickattrs
, tick
.ticklevel
)
1742 if tickattrs
is not None:
1743 innerticklength_pt
= unit
.topt(innerticklength
)
1744 outerticklength_pt
= unit
.topt(outerticklength
)
1745 x1
= tick
.temp_x
+ tick
.temp_dx
* innerticklength_pt
1746 y1
= tick
.temp_y
+ tick
.temp_dy
* innerticklength_pt
1747 x2
= tick
.temp_x
- tick
.temp_dx
* outerticklength_pt
1748 y2
= tick
.temp_y
- tick
.temp_dy
* outerticklength_pt
1749 ac
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), helper
.ensurelist(tickattrs
))
1750 if tick
!= frac((0, 1)) or self
.zeropathattrs
is None:
1751 gridattrs
= helper
.getsequenceno(self
.gridattrs
, tick
.ticklevel
)
1752 if gridattrs
is not None:
1753 ac
.stroke(axispos
.vgridpath(tick
.temp_v
), helper
.ensurelist(gridattrs
))
1754 if outerticklength
is not None and unit
.topt(outerticklength
) > unit
.topt(ac
.extent
):
1755 ac
.extent
= outerticklength
1756 if outerticklength
is not None and unit
.topt(-innerticklength
) > unit
.topt(ac
.extent
):
1757 ac
.extent
= -innerticklength
1758 if tick
.labellevel
is not None and self
.labelattrs
is not None:
1759 ac
.insert(tick
.temp_labelbox
)
1760 ac
.labels
.append(tick
.temp_labelbox
)
1761 extent
= tick
.temp_labelbox
.extent(tick
.temp_dx
, tick
.temp_dy
) + labeldist
1762 if unit
.topt(extent
) > unit
.topt(ac
.extent
):
1764 if self
.basepathattrs
is not None:
1765 ac
.stroke(axispos
.vbasepath(), helper
.ensurelist(self
.basepathattrs
))
1766 if self
.zeropathattrs
is not None:
1767 if len(axis
.ticks
) and axis
.ticks
[0] * axis
.ticks
[-1] < frac((0, 1)):
1768 ac
.stroke(axispos
.gridpath(0), helper
.ensurelist(self
.zeropathattrs
))
1770 # for tick in axis.ticks:
1771 # del tick.temp_v # we've inserted those temporary variables ... and do not care any longer about them
1776 # if tick.labellevel is not None and self.labelattrs is not None:
1777 # del tick.temp_labelbox
1779 axistitlepainter
.paint(self
, axispos
, axis
, ac
=ac
)
1784 class linkaxispainter(axispainter
):
1785 """class for painting a linked axis
1786 - the inherited axispainter is used to paint the axis
1787 - modifies some constructor defaults"""
1789 __implements__
= _Iaxispainter
1791 def __init__(self
, zeropathattrs
=None,
1795 """initializes the instance
1796 - the zeropathattrs default is set to None thus skipping the zeropath
1797 - the labelattrs default is set to None thus skipping the labels
1798 - the titleattrs default is set to None thus skipping the title
1799 - all keyword arguments are passed to axispainter"""
1800 axispainter
.__init
__(self
, zeropathattrs
=zeropathattrs
,
1801 labelattrs
=labelattrs
,
1802 titleattrs
=titleattrs
,
1807 """implementation of the _Iaxispos interface for a subaxis"""
1809 __implements__
= _Iaxispos
1811 def __init__(self
, convert
, baseaxispos
, vmin
, vmax
, vminover
, vmaxover
):
1812 """initializes the instance
1813 - convert is the subaxis convert method
1814 - baseaxispos is the axispos instance of the base axis
1815 - vmin, vmax is the range covered by the subaxis in graph coordinates
1816 - vminover, vmaxover is the extended range of the subaxis including
1817 regions between several subaxes (for baseline drawing etc.)"""
1818 self
.convert
= convert
1819 self
.baseaxispos
= baseaxispos
1822 self
.vminover
= vminover
1823 self
.vmaxover
= vmaxover
1825 def basepath(self
, x1
=None, x2
=None):
1827 v1
= self
.vmin
+self
.convert(x1
)*(self
.vmax
-self
.vmin
)
1831 v2
= self
.vmin
+self
.convert(x2
)*(self
.vmax
-self
.vmin
)
1834 return self
.baseaxispos
.vbasepath(v1
, v2
)
1836 def vbasepath(self
, v1
=None, v2
=None):
1838 v1
= self
.vmin
+v1
*(self
.vmax
-self
.vmin
)
1842 v2
= self
.vmin
+v2
*(self
.vmax
-self
.vmin
)
1845 return self
.baseaxispos
.vbasepath(v1
, v2
)
1847 def gridpath(self
, x
):
1848 return self
.baseaxispos
.vgridpath(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
1850 def vgridpath(self
, v
):
1851 return self
.baseaxispos
.vgridpath(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
1853 def tickpoint_pt(self
, x
, axis
=None):
1854 return self
.baseaxispos
.vtickpoint_pt(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
1856 def tickpoint(self
, x
, axis
=None):
1857 return self
.baseaxispos
.vtickpoint(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
1859 def vtickpoint_pt(self
, v
, axis
=None):
1860 return self
.baseaxispos
.vtickpoint_pt(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
1862 def vtickpoint(self
, v
, axis
=None):
1863 return self
.baseaxispos
.vtickpoint(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
1865 def tickdirection(self
, x
, axis
=None):
1866 return self
.baseaxispos
.vtickdirection(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
1868 def vtickdirection(self
, v
, axis
=None):
1869 return self
.baseaxispos
.vtickdirection(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
1872 class splitaxispainter(axistitlepainter
):
1873 """class for painting a splitaxis
1874 - the inherited titleaxispainter is used to paint the title of
1876 - the splitaxispainter access the subaxes attribute of the axis"""
1878 __implements__
= _Iaxispainter
1880 def __init__(self
, breaklinesdist
="0.05 cm",
1881 breaklineslength
="0.5 cm",
1882 breaklinesangle
=-60,
1885 """initializes the instance
1886 - breaklinesdist is a visual length of the distance between
1887 the two lines of the axis break
1888 - breaklineslength is a visual length of the length of the
1889 two lines of the axis break
1890 - breaklinesangle is the angle of the lines of the axis break
1891 - breaklinesattrs are a list of stroke attributes for the
1892 axis break lines; a single entry is allowed without being a
1893 list; None turns off the break lines
1894 - futher keyword arguments are passed to axistitlepainter"""
1895 self
.breaklinesdist_str
= breaklinesdist
1896 self
.breaklineslength_str
= breaklineslength
1897 self
.breaklinesangle
= breaklinesangle
1898 self
.breaklinesattrs
= breaklinesattrs
1899 axistitlepainter
.__init
__(self
, **args
)
1901 def paint(self
, axispos
, axis
, ac
=None):
1905 raise RuntimeError("XXX") # XXX debug only
1906 for subaxis
in axis
.subaxes
:
1907 subaxis
.finish(subaxispos(subaxis
.convert
, axispos
, subaxis
.vmin
, subaxis
.vmax
, subaxis
.vminover
, subaxis
.vmaxover
))
1908 ac
.insert(subaxis
.axiscanvas
)
1909 if unit
.topt(ac
.extent
) < unit
.topt(subaxis
.axiscanvas
.extent
):
1910 ac
.extent
= subaxis
.axiscanvas
.extent
1911 if self
.breaklinesattrs
is not None:
1912 self
.breaklinesdist
= unit
.length(self
.breaklinesdist_str
, default_type
="v")
1913 self
.breaklineslength
= unit
.length(self
.breaklineslength_str
, default_type
="v")
1914 self
.sin
= math
.sin(self
.breaklinesangle
*math
.pi
/180.0)
1915 self
.cos
= math
.cos(self
.breaklinesangle
*math
.pi
/180.0)
1916 breaklinesextent
= (0.5*self
.breaklinesdist
*math
.fabs(self
.cos
) +
1917 0.5*self
.breaklineslength
*math
.fabs(self
.sin
))
1918 if unit
.topt(ac
.extent
) < unit
.topt(breaklinesextent
):
1919 ac
.extent
= breaklinesextent
1920 for subaxis1
, subaxis2
in zip(axis
.subaxes
[:-1], axis
.subaxes
[1:]):
1921 # use a tangent of the basepath (this is independent of the tickdirection)
1922 v
= 0.5 * (subaxis1
.vmax
+ subaxis2
.vmin
)
1923 p
= path
.normpath(axispos
.vbasepath(v
, None))
1924 breakline
= p
.tangent(0, self
.breaklineslength
)
1925 widthline
= p
.tangent(0, self
.breaklinesdist
).transformed(trafomodule
.rotate(self
.breaklinesangle
+90, *breakline
.begin()))
1926 tocenter
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(breakline
.begin(), breakline
.end()))
1927 towidth
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(widthline
.begin(), widthline
.end()))
1928 breakline
= breakline
.transformed(trafomodule
.translate(*tocenter
).rotated(self
.breaklinesangle
, *breakline
.begin()))
1929 breakline1
= breakline
.transformed(trafomodule
.translate(*towidth
))
1930 breakline2
= breakline
.transformed(trafomodule
.translate(-towidth
[0], -towidth
[1]))
1931 ac
.fill(path
.path(path
.moveto(*breakline1
.begin()),
1932 path
.lineto(*breakline1
.end()),
1933 path
.lineto(*breakline2
.end()),
1934 path
.lineto(*breakline2
.begin()),
1935 path
.closepath()), [color
.gray
.white
])
1936 ac
.stroke(breakline1
, helper
.ensurelist(self
.breaklinesattrs
))
1937 ac
.stroke(breakline2
, helper
.ensurelist(self
.breaklinesattrs
))
1938 axistitlepainter
.paint(self
, axispos
, axis
, ac
=ac
)
1942 class linksplitaxispainter(splitaxispainter
):
1943 """class for painting a linked splitaxis
1944 - the inherited splitaxispainter is used to paint the axis
1945 - modifies some constructor defaults"""
1947 __implements__
= _Iaxispainter
1949 def __init__(self
, titleattrs
=None, **kwargs
):
1950 """initializes the instance
1951 - the titleattrs default is set to None thus skipping the title
1952 - all keyword arguments are passed to splitaxispainter"""
1953 splitaxispainter
.__init
__(self
, titleattrs
=titleattrs
, **kwargs
)
1956 class baraxispainter(axistitlepainter
):
1957 """class for painting a baraxis
1958 - the inherited titleaxispainter is used to paint the title of
1960 - the baraxispainter access the multisubaxis, subaxis names, texts, and
1961 relsizes attributes"""
1963 __implements__
= _Iaxispainter
1965 def __init__(self
, innerticklength
=None,
1966 outerticklength
=None,
1968 basepathattrs
=style
.linecap
.square
,
1970 nameattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1976 """initializes the instance
1977 - innerticklength and outerticklength are a visual length of
1978 the ticks to be plotted at the axis basepath to visually
1979 separate the bars; if neither innerticklength nor
1980 outerticklength are set, not ticks are plotted
1981 - breaklinesattrs are a list of stroke attributes for the
1982 axis tick; a single entry is allowed without being a
1983 list; None turns off the ticks
1984 - namedist is a visual PyX length for the distance of the bar
1985 names from the axis basepath
1986 - nameattrs is a list of attributes for a texrunners text
1987 method; a single entry is allowed without being a list;
1988 None turns off the names
1989 - namedirection is an instance of rotatetext or None
1990 - namehequalize and namevequalize (booleans) perform an equal
1991 alignment for straight vertical and horizontal axes, respectively
1992 - futher keyword arguments are passed to axistitlepainter"""
1993 self
.innerticklength_str
= innerticklength
1994 self
.outerticklength_str
= outerticklength
1995 self
.tickattrs
= tickattrs
1996 self
.basepathattrs
= basepathattrs
1997 self
.namedist_str
= namedist
1998 self
.nameattrs
= nameattrs
1999 self
.namedirection
= namedirection
2000 self
.namepos
= namepos
2001 self
.namehequalize
= namehequalize
2002 self
.namevequalize
= namevequalize
2003 axistitlepainter
.__init
__(self
, **args
)
2005 def paint(self
, axispos
, axis
, ac
=None):
2009 raise RuntimeError("XXX") # XXX debug only
2010 if axis
.multisubaxis
is not None:
2011 for subaxis
in axis
.subaxis
:
2012 subaxis
.finish(subaxispos(subaxis
.convert
, axispos
, subaxis
.vmin
, subaxis
.vmax
, None, None))
2013 ac
.insert(subaxis
.axiscanvas
)
2014 if unit
.topt(ac
.extent
) < unit
.topt(subaxis
.axiscanvas
.extent
):
2015 ac
.extent
= subaxis
.axiscanvas
.extent
2017 for name
in axis
.names
:
2018 v
= axis
.convert((name
, self
.namepos
))
2019 x
, y
= axispos
.vtickpoint_pt(v
)
2020 dx
, dy
= axispos
.vtickdirection(v
)
2021 namepos
.append((v
, x
, y
, dx
, dy
))
2023 if self
.nameattrs
is not None:
2024 for (v
, x
, y
, dx
, dy
), name
in zip(namepos
, axis
.names
):
2025 nameattrs
= helper
.ensurelist(self
.nameattrs
)[:]
2026 if self
.namedirection
is not None:
2027 nameattrs
.append(self
.namedirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
2028 if axis
.texts
.has_key(name
):
2029 nameboxes
.append(self
.texrunner
.text_pt(x
, y
, str(axis
.texts
[name
]), nameattrs
))
2030 elif axis
.texts
.has_key(str(name
)):
2031 nameboxes
.append(self
.texrunner
.text_pt(x
, y
, str(axis
.texts
[str(name
)]), nameattrs
))
2033 nameboxes
.append(self
.texrunner
.text_pt(x
, y
, str(name
), nameattrs
))
2034 labeldist
= ac
.extent
+ unit
.length(self
.namedist_str
, default_type
="v")
2035 if len(namepos
) > 1:
2037 for np
in namepos
[1:]:
2038 if np
[3] != namepos
[0][3] or np
[4] != namepos
[0][4]:
2042 if equaldirection
and ((not namepos
[0][3] and self
.namevequalize
) or
2043 (not namepos
[0][4] and self
.namehequalize
)):
2044 box
.linealignequal(nameboxes
, labeldist
, -namepos
[0][3], -namepos
[0][4])
2046 for namebox
, np
in zip(nameboxes
, namepos
):
2047 namebox
.linealign(labeldist
, -np
[3], -np
[4])
2048 if self
.innerticklength_str
is not None:
2049 innerticklength
= unit
.length(self
.innerticklength_str
, default_type
="v")
2050 innerticklength_pt
= unit
.topt(innerticklength
)
2051 if self
.tickattrs
is not None and unit
.topt(ac
.extent
) < -innerticklength_pt
:
2052 ac
.extent
= -innerticklength
2053 elif self
.outerticklength_str
is not None:
2054 innerticklength
= innerticklength_pt
= 0
2055 if self
.outerticklength_str
is not None:
2056 outerticklength
= unit
.length(self
.outerticklength_str
, default_type
="v")
2057 outerticklength_pt
= unit
.topt(outerticklength
)
2058 if self
.tickattrs
is not None and unit
.topt(ac
.extent
) < outerticklength_pt
:
2059 ac
.extent
= outerticklength
2060 elif self
.innerticklength_str
is not None:
2061 outerticklength
= outerticklength_pt
= 0
2062 for (v
, x
, y
, dx
, dy
), namebox
in zip(namepos
, nameboxes
):
2063 newextent
= namebox
.extent(dx
, dy
) + labeldist
2064 if unit
.topt(ac
.extent
) < unit
.topt(newextent
):
2065 ac
.extent
= newextent
2066 if self
.tickattrs
is not None and (self
.innerticklength_str
is not None or self
.outerticklength_str
is not None):
2067 for pos
in axis
.relsizes
:
2068 if pos
== axis
.relsizes
[0]:
2069 pos
-= axis
.firstdist
2070 elif pos
!= axis
.relsizes
[-1]:
2071 pos
-= 0.5 * axis
.dist
2072 v
= pos
/ axis
.relsizes
[-1]
2073 x
, y
= axispos
.vtickpoint_pt(v
)
2074 dx
, dy
= axispos
.vtickdirection(v
)
2075 x1
= x
+ dx
* innerticklength_pt
2076 y1
= y
+ dy
* innerticklength_pt
2077 x2
= x
- dx
* outerticklength_pt
2078 y2
= y
- dy
* outerticklength_pt
2079 ac
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), helper
.ensurelist(self
.tickattrs
))
2080 if self
.basepathattrs
is not None:
2081 p
= axispos
.vbasepath()
2083 ac
.stroke(p
, helper
.ensurelist(self
.basepathattrs
))
2084 for namebox
in nameboxes
:
2086 axistitlepainter
.paint(self
, axispos
, axis
, ac
=ac
)
2090 class linkbaraxispainter(baraxispainter
):
2091 """class for painting a linked baraxis
2092 - the inherited baraxispainter is used to paint the axis
2093 - modifies some constructor defaults"""
2095 __implements__
= _Iaxispainter
2097 def __init__(self
, nameattrs
=None, titleattrs
=None, **kwargs
):
2098 """initializes the instance
2099 - the titleattrs default is set to None thus skipping the title
2100 - the nameattrs default is set to None thus skipping the names
2101 - all keyword arguments are passed to axispainter"""
2102 baraxispainter
.__init
__(self
, nameattrs
=nameattrs
, titleattrs
=titleattrs
, **kwargs
)
2105 ################################################################################
2107 ################################################################################
2111 """interface definition of a axis
2112 - an axis should implement an convert and invert method like
2113 _Imap, but this is not part of this interface definition;
2114 one possibility is to mix-in a proper map class, but special
2115 purpose axes might do something else
2116 - an axis has the instance variable axiscanvas after the finish
2118 - an axis might have further instance variables (title, ticks)
2119 to be used in combination with appropriate axispainters"""
2121 def convert(self
, x
):
2122 "convert a value into graph coordinates"
2124 def invert(self
, v
):
2125 "invert a graph coordinate to a axis value"
2127 def getrelsize(self
):
2128 """returns the relative size (width) of the axis
2129 - for use in splitaxis, baraxis etc.
2130 - might return None if no size is available"""
2132 def setrange(self
, min=None, max=None):
2133 """set the axis data range
2134 - the type of min and max must fit to the axis
2135 - min<max; the axis might be reversed, but this is
2136 expressed internally only (min<max all the time)
2137 - the axis might not apply the change of the range
2138 (e.g. when the axis range is fixed by the user),
2139 but usually the range is extended to contain the
2141 - for invalid parameters (e.g. negativ values at an
2142 logarithmic axis), an exception should be raised
2143 - a RuntimeError is raised, when setrange is called
2144 after the finish method"""
2147 """return data range as a tuple (min, max)
2148 - min<max; the axis might be reversed, but this is
2149 expressed internally only
2150 - a RuntimeError exception is raised when no
2151 range is available"""
2153 def finish(self
, axispos
):
2154 """finishes the axis
2155 - axispos implements _Iaxispos
2156 - sets the instance axiscanvas, which is insertable into the
2157 graph to finally paint the axis
2158 - any modification of the axis range should be disabled after
2159 the finish method was called"""
2160 # TODO: be more specific about exceptions
2162 def createlinkaxis(self
, **kwargs
):
2163 """create a link axis to the axis itself
2164 - typically, a link axis is a axis, which share almost
2165 all properties with the axis it is linked to
2166 - typically, the painter gets replaced by a painter
2167 which doesn't put any text to the axis"""
2171 """base implementation a regular axis
2172 - typical usage is to mix-in a linmap or a logmap to
2173 complete the axis interface
2174 - note that some methods of this class want to access a
2175 parter and a rater; those attributes implementing _Iparter
2176 and _Irater should be initialized by the constructors
2177 of derived classes"""
2179 def __init__(self
, min=None, max=None, reverse
=0, divisor
=1,
2180 title
=None, painter
=axispainter(), texter
=defaulttexter(),
2181 density
=1, maxworse
=2, manualticks
=[]):
2182 """initializes the instance
2183 - min and max fix the axis minimum and maximum, respectively;
2184 they are determined by the data to be plotted, when not fixed
2185 - reverse (boolean) reverses the minimum and the maximum of
2187 - numerical divisor for the axis partitioning
2188 - title is a string containing the axis title
2189 - axispainter is the axis painter (should implement _Ipainter)
2190 - texter is the texter (should implement _Itexter)
2191 - density is a global parameter for the axis paritioning and
2192 axis rating; its default is 1, but the range 0.5 to 2.5 should
2193 be usefull to get less or more ticks by the automatic axis
2195 - maxworse is a number of trials with worse tick rating
2196 before giving up (usually it should not be needed to increase
2197 this value; increasing the number will slow down the automatic
2198 axis partitioning considerably)
2199 - manualticks and the partitioner results are mixed
2201 - note that some methods of this class want to access a
2202 parter and a rater; those attributes implementing _Iparter
2203 and _Irater should be initialized by the constructors
2204 of derived classes"""
2205 if min is not None and max is not None and min > max:
2206 min, max, reverse
= max, min, not reverse
2207 self
.fixmin
, self
.fixmax
, self
.min, self
.max, self
.reverse
= min is not None, max is not None, min, max, reverse
2208 self
.divisor
= divisor
2210 self
.painter
= painter
2211 self
.texter
= texter
2212 self
.density
= density
2213 self
.maxworse
= maxworse
2214 self
.manualticks
= self
.checkfraclist(manualticks
)
2216 self
.axiscanvas
= None
2219 def _setrange(self
, min=None, max=None):
2220 if not self
.fixmin
and min is not None and (self
.min is None or min < self
.min):
2222 if not self
.fixmax
and max is not None and (self
.max is None or max > self
.max):
2224 if None not in (self
.min, self
.max):
2227 self
.setbasepoints(((self
.min, 1), (self
.max, 0)))
2229 self
.setbasepoints(((self
.min, 0), (self
.max, 1)))
2231 def _getrange(self
):
2232 return self
.min, self
.max
2234 def _forcerange(self
, range):
2235 self
.min, self
.max = range
2238 def setrange(self
, min=None, max=None):
2239 if self
.axiscanvas
is not None:
2240 raise RuntimeError("axis was already finished")
2241 self
._setrange
(min, max)
2244 if self
.min is not None and self
.max is not None:
2245 return self
.min, self
.max
2247 def checkfraclist(self
, fracs
):
2248 "orders a list of fracs, equal entries are not allowed"
2249 if not len(fracs
): return []
2250 sorted = list(fracs
)
2253 for item
in sorted[1:]:
2255 raise ValueError("duplicate entry found")
2259 def finish(self
, axispos
):
2260 if self
.axiscanvas
is not None: return
2262 # lesspart and morepart can be called after defaultpart;
2263 # this works although some axes may share their autoparting,
2264 # because the axes are processed sequentially
2266 if self
.parter
is not None:
2267 min, max = self
.getrange()
2268 self
.ticks
= _mergeticklists(self
.manualticks
,
2269 self
.parter
.defaultpart(min/self
.divisor
,
2274 nextpart
= self
.parter
.lesspart
2275 while nextpart
is not None:
2276 newticks
= nextpart()
2277 if newticks
is not None:
2278 newticks
= _mergeticklists(self
.manualticks
, newticks
)
2280 bestrate
= self
.rater
.rateticks(self
, self
.ticks
, self
.density
)
2281 bestrate
+= self
.rater
.raterange(self
.convert(float(self
.ticks
[-1])/self
.divisor
)-
2282 self
.convert(float(self
.ticks
[0])/self
.divisor
), 1)
2283 variants
= [[bestrate
, self
.ticks
]]
2285 newrate
= self
.rater
.rateticks(self
, newticks
, self
.density
)
2286 newrate
+= self
.rater
.raterange(self
.convert(float(newticks
[-1])/self
.divisor
)-
2287 self
.convert(float(newticks
[0])/self
.divisor
), 1)
2288 variants
.append([newrate
, newticks
])
2289 if newrate
< bestrate
:
2296 if worse
== self
.maxworse
and nextpart
== self
.parter
.lesspart
:
2298 nextpart
= self
.parter
.morepart
2299 if worse
== self
.maxworse
and nextpart
== self
.parter
.morepart
:
2302 self
.ticks
=self
.manualticks
2304 # rating, when several choises are available
2307 if self
.painter
is not None:
2310 while i
< len(variants
) and (bestrate
is None or variants
[i
][0] < bestrate
):
2311 saverange
= self
._getrange
()
2312 self
.ticks
= variants
[i
][1]
2314 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2315 self
.texter
.labels(self
.ticks
)
2316 ac
= self
.painter
.paint(axispos
, self
)
2317 ratelayout
= self
.rater
.ratelayout(ac
, self
.density
)
2318 if ratelayout
is not None:
2319 variants
[i
][0] += ratelayout
2320 variants
[i
].append(ac
)
2322 variants
[i
][0] = None
2323 if variants
[i
][0] is not None and (bestrate
is None or variants
[i
][0] < bestrate
):
2324 bestrate
= variants
[i
][0]
2325 self
._forcerange
(saverange
)
2327 if bestrate
is None:
2328 raise RuntimeError("no valid axis partitioning found")
2329 variants
= [variant
for variant
in variants
[:i
] if variant
[0] is not None]
2331 self
.ticks
= variants
[0][1]
2333 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2334 self
.axiscanvas
= variants
[0][2]
2336 self
.ticks
= variants
[0][1]
2337 self
.texter
.labels(self
.ticks
)
2339 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2340 self
.axiscanvas
= axiscanvas()
2343 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2344 self
.texter
.labels(self
.ticks
)
2345 if self
.painter
is not None:
2346 self
.axiscanvas
= self
.painter
.paint(axispos
, self
)
2348 self
.axiscanvas
= axiscanvas()
2350 def createlinkaxis(self
, **args
):
2351 return linkaxis(self
, **args
)
2354 class linaxis(_axis
, _linmap
):
2355 """implementation of a linear axis"""
2357 __implements__
= _Iaxis
2359 def __init__(self
, parter
=autolinparter(), rater
=axisrater(), **args
):
2360 """initializes the instance
2361 - the parter attribute implements _Iparter
2362 - manualticks and the partitioner results are mixed
2364 - the rater implements _Irater and is used to rate different
2365 tick lists created by the partitioner (after merging with
2367 - futher keyword arguments are passed to _axis"""
2368 _axis
.__init
__(self
, **args
)
2369 if self
.fixmin
and self
.fixmax
:
2370 self
.relsize
= self
.max - self
.min
2371 self
.parter
= parter
2375 class logaxis(_axis
, _logmap
):
2376 """implementation of a logarithmic axis"""
2378 __implements__
= _Iaxis
2380 def __init__(self
, parter
=autologparter(), rater
=axisrater(ticks
=axisrater
.logticks
, labels
=axisrater
.loglabels
), **args
):
2381 """initializes the instance
2382 - the parter attribute implements _Iparter
2383 - manualticks and the partitioner results are mixed
2385 - the rater implements _Irater and is used to rate different
2386 tick lists created by the partitioner (after merging with
2388 - futher keyword arguments are passed to _axis"""
2389 _axis
.__init
__(self
, **args
)
2390 if self
.fixmin
and self
.fixmax
:
2391 self
.relsize
= math
.log(self
.max) - math
.log(self
.min)
2392 self
.parter
= parter
2397 """a axis linked to an already existing regular axis
2398 - almost all properties of the axis are "copied" from the
2399 axis this axis is linked to
2400 - usually, linked axis are used to create an axis to an
2401 existing axis with different painting properties; linked
2402 axis can be used to plot an axis twice at the opposite
2403 sides of a graphxy or even to share an axis between
2404 different graphs!"""
2406 __implements__
= _Iaxis
2408 def __init__(self
, linkedaxis
, painter
=linkaxispainter()):
2409 """initializes the instance
2410 - it gets a axis this linkaxis is linked to
2411 - it gets a painter to be used for this linked axis"""
2412 self
.linkedaxis
= linkedaxis
2413 self
.painter
= painter
2414 self
.axiscanvas
= None
2416 def __getattr__(self
, attr
):
2417 """access to unkown attributes are handed over to the
2418 axis this linkaxis is linked to"""
2419 return getattr(self
.linkedaxis
, attr
)
2421 def finish(self
, axispos
):
2422 """finishes the axis
2423 - instead of performing the hole finish process
2424 (paritioning, rating, etc.) just a painter call
2426 if self
.axiscanvas
is None:
2427 self
.linkedaxis
.finish(axispos
)
2428 self
.axiscanvas
= self
.painter
.paint(axispos
, self
)
2432 """implementation of a split axis
2433 - a split axis contains several (sub-)axes with
2434 non-overlapping data ranges -- between these subaxes
2435 the axis is "splitted"
2436 - (just to get sure: a splitaxis can contain other
2437 splitaxes as its subaxes)
2438 - a splitaxis implements the _Iaxispos for its subaxes
2439 by inheritance from _subaxispos"""
2441 __implements__
= _Iaxis
, _Iaxispos
2443 def __init__(self
, subaxes
, splitlist
=0.5, splitdist
=0.1, relsizesplitdist
=1,
2444 title
=None, painter
=splitaxispainter()):
2445 """initializes the instance
2446 - subaxes is a list of subaxes
2447 - splitlist is a list of graph coordinates, where the splitting
2448 of the main axis should be performed; a single entry (splitting
2449 two axes) doesn't need to be wrapped into a list; if the list
2450 isn't long enough for the subaxes, missing entries are considered
2452 - splitdist is the size of the splitting in graph coordinates, when
2453 the associated splitlist entry is not None
2454 - relsizesplitdist: a None entry in splitlist means, that the
2455 position of the splitting should be calculated out of the
2456 relsize values of conrtibuting subaxes (the size of the
2457 splitting is relsizesplitdist in values of the relsize values
2459 - title is the title of the axis as a string
2460 - painter is the painter of the axis; it should be specialized to
2462 - the relsize of the splitaxis is the sum of the relsizes of the
2463 subaxes including the relsizesplitdist"""
2464 self
.subaxes
= subaxes
2465 self
.painter
= painter
2467 self
.splitlist
= helper
.ensurelist(splitlist
)
2468 for subaxis
in self
.subaxes
:
2471 self
.subaxes
[0].vmin
= 0
2472 self
.subaxes
[0].vminover
= None
2473 self
.subaxes
[-1].vmax
= 1
2474 self
.subaxes
[-1].vmaxover
= None
2475 for i
in xrange(len(self
.splitlist
)):
2476 if self
.splitlist
[i
] is not None:
2477 self
.subaxes
[i
].vmax
= self
.splitlist
[i
] - 0.5*splitdist
2478 self
.subaxes
[i
].vmaxover
= self
.splitlist
[i
]
2479 self
.subaxes
[i
+1].vmin
= self
.splitlist
[i
] + 0.5*splitdist
2480 self
.subaxes
[i
+1].vminover
= self
.splitlist
[i
]
2482 while i
< len(self
.subaxes
):
2483 if self
.subaxes
[i
].vmax
is None:
2484 j
= relsize
= relsize2
= 0
2485 while self
.subaxes
[i
+ j
].vmax
is None:
2486 relsize
+= self
.subaxes
[i
+ j
].relsize
+ relsizesplitdist
2488 relsize
+= self
.subaxes
[i
+ j
].relsize
2489 vleft
= self
.subaxes
[i
].vmin
2490 vright
= self
.subaxes
[i
+ j
].vmax
2491 for k
in range(i
, i
+ j
):
2492 relsize2
+= self
.subaxes
[k
].relsize
2493 self
.subaxes
[k
].vmax
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2494 relsize2
+= 0.5 * relsizesplitdist
2495 self
.subaxes
[k
].vmaxover
= self
.subaxes
[k
+ 1].vminover
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2496 relsize2
+= 0.5 * relsizesplitdist
2497 self
.subaxes
[k
+1].vmin
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2498 if i
== 0 and i
+ j
+ 1 == len(self
.subaxes
):
2499 self
.relsize
= relsize
2504 self
.fixmin
= self
.subaxes
[0].fixmin
2506 self
.min = self
.subaxes
[0].min
2507 self
.fixmax
= self
.subaxes
[-1].fixmax
2509 self
.max = self
.subaxes
[-1].max
2511 self
.axiscanvas
= None
2514 min = self
.subaxes
[0].getrange()
2515 max = self
.subaxes
[-1].getrange()
2517 return min[0], max[1]
2521 def setrange(self
, min, max):
2522 self
.subaxes
[0].setrange(min, None)
2523 self
.subaxes
[-1].setrange(None, max)
2525 def convert(self
, value
):
2526 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
2527 if value
< self
.subaxes
[0].max:
2528 return self
.subaxes
[0].vmin
+ self
.subaxes
[0].convert(value
)*(self
.subaxes
[0].vmax
-self
.subaxes
[0].vmin
)
2529 for axis
in self
.subaxes
[1:-1]:
2530 if value
> axis
.min and value
< axis
.max:
2531 return axis
.vmin
+ axis
.convert(value
)*(axis
.vmax
-axis
.vmin
)
2532 if value
> self
.subaxes
[-1].min:
2533 return self
.subaxes
[-1].vmin
+ self
.subaxes
[-1].convert(value
)*(self
.subaxes
[-1].vmax
-self
.subaxes
[-1].vmin
)
2534 raise ValueError("value couldn't be assigned to a split region")
2536 def finish(self
, axispos
):
2537 if self
.axiscanvas
is None:
2538 self
.axiscanvas
= self
.painter
.paint(axispos
, self
)
2540 def createlinkaxis(self
, **args
):
2541 return linksplitaxis(self
, **args
)
2544 class linksplitaxis(linkaxis
):
2545 """a splitaxis linked to an already existing splitaxis
2546 - inherits the access to a linked axis -- as before,
2547 basically only the painter is replaced
2548 - it takes care of the creation of linked axes of
2551 __implements__
= _Iaxis
2553 def __init__(self
, linkedaxis
, painter
=linksplitaxispainter(), subaxispainter
=None):
2554 """initializes the instance
2555 - it gets a axis this linkaxis is linked to
2556 - it gets a painter to be used for this linked axis
2557 - it gets a list of painters to be used for the linkaxes
2558 of the subaxes; if None, the createlinkaxis of the subaxes
2559 are called without a painter parameter; if it is not a
2560 list, the subaxispainter is passed as the painter
2561 parameter to all createlinkaxis of the subaxes"""
2562 linkaxis
.__init
__(self
, linkedaxis
, painter
=painter
)
2563 if subaxispainter
is not None:
2564 if helper
.issequence(subaxispainter
):
2565 if len(linkedaxis
.subaxes
) != len(subaxispainter
):
2566 raise RuntimeError("subaxes and subaxispainter lengths do not fit")
2567 self
.subaxes
= [a
.createlinkaxis(painter
=p
) for a
, p
in zip(linkedaxis
.subaxes
, subaxispainter
)]
2569 self
.subaxes
= [a
.createlinkaxis(painter
=subaxispainter
) for a
in linkedaxis
.subaxes
]
2571 self
.subaxes
= [a
.createlinkaxis() for a
in linkedaxis
.subaxes
]
2575 """implementation of a axis for bar graphs
2576 - a bar axes is different from a splitaxis by the way it
2577 selects its subaxes: the convert method gets a list,
2578 where the first entry is a name selecting a subaxis out
2579 of a list; instead of the term "bar" or "subaxis" the term
2580 "item" will be used here
2581 - the baraxis stores a list of names be identify the items;
2582 the names might be of any time (strings, integers, etc.);
2583 the names can be printed as the titles for the items, but
2584 alternatively the names might be transformed by the texts
2585 dictionary, which maps a name to a text to be used to label
2586 the items in the painter
2587 - usually, there is only one subaxis, which is used as
2588 the subaxis for all items
2589 - alternatively it is also possible to use another baraxis
2590 as a multisubaxis; it is copied via the createsubaxis
2591 method whenever another subaxis is needed (by that a
2592 nested bar axis with a different number of subbars at
2593 each item can be created)
2594 - any axis can be a subaxis of a baraxis; if no subaxis
2595 is specified at all, the baraxis simulates a linear
2596 subaxis with a fixed range of 0 to 1
2597 - a splitaxis implements the _Iaxispos for its subaxes
2598 by inheritance from _subaxispos when the multisubaxis
2599 feature is turned on"""
2601 def __init__(self
, subaxis
=None, multisubaxis
=None, title
=None,
2602 dist
=0.5, firstdist
=None, lastdist
=None, names
=None,
2603 texts
={}, painter
=baraxispainter()):
2604 """initialize the instance
2605 - subaxis contains a axis to be used as the subaxis
2607 - multisubaxis might contain another baraxis instance
2608 to be used to construct a new subaxis for each item;
2609 (by that a nested bar axis with a different number
2610 of subbars at each item can be created)
2611 - only one of subaxis or multisubaxis can be set; if neither
2612 of them is set, the baraxis behaves like having a linaxis
2613 as its subaxis with a fixed range 0 to 1
2614 - the title attribute contains the axis title as a string
2615 - the dist is a relsize to be used as the distance between
2617 - the firstdist and lastdist are the distance before the
2618 first and after the last item, respectively; when set
2619 to None (the default), 0.5*dist is used
2620 - names is a predefined list of names to identify the
2621 items; if set, the name list is fixed
2622 - texts is a dictionary transforming a name to a text in
2623 the painter; if a name isn't found in the dictionary
2625 - the relsize of the baraxis is the sum of the
2626 relsizes including all distances between the items"""
2628 if firstdist
is not None:
2629 self
.firstdist
= firstdist
2631 self
.firstdist
= 0.5 * dist
2632 if lastdist
is not None:
2633 self
.lastdist
= lastdist
2635 self
.lastdist
= 0.5 * dist
2636 self
.relsizes
= None
2639 for name
in helper
.ensuresequence(names
):
2641 self
.fixnames
= names
is not None
2642 self
.multisubaxis
= multisubaxis
2643 if self
.multisubaxis
is not None:
2644 if subaxis
is not None:
2645 raise RuntimeError("either use subaxis or multisubaxis")
2646 self
.subaxis
= [self
.createsubaxis() for name
in self
.names
]
2648 self
.subaxis
= subaxis
2652 self
.painter
= painter
2653 self
.axiscanvas
= None
2655 def createsubaxis(self
):
2656 return baraxis(subaxis
=self
.multisubaxis
.subaxis
,
2657 multisubaxis
=self
.multisubaxis
.multisubaxis
,
2658 title
=self
.multisubaxis
.title
,
2659 dist
=self
.multisubaxis
.dist
,
2660 firstdist
=self
.multisubaxis
.firstdist
,
2661 lastdist
=self
.multisubaxis
.lastdist
,
2662 names
=self
.multisubaxis
.names
,
2663 texts
=self
.multisubaxis
.texts
,
2664 painter
=self
.multisubaxis
.painter
)
2667 # TODO: we do not yet have a proper range handling for a baraxis
2670 def setrange(self
, min=None, max=None):
2671 # TODO: we do not yet have a proper range handling for a baraxis
2672 raise RuntimeError("range handling for a baraxis is not implemented")
2674 def setname(self
, name
, *subnames
):
2675 """add a name to identify an item at the baraxis
2676 - by using subnames, nested name definitions are
2678 - a style (or the user itself) might use this to
2679 insert new items into a baraxis
2680 - setting self.relsizes to None forces later recalculation"""
2681 if not self
.fixnames
:
2682 if name
not in self
.names
:
2683 self
.relsizes
= None
2684 self
.names
.append(name
)
2685 if self
.multisubaxis
is not None:
2686 self
.subaxis
.append(self
.createsubaxis())
2687 if (not self
.fixnames
or name
in self
.names
) and len(subnames
):
2688 if self
.multisubaxis
is not None:
2689 if self
.subaxis
[self
.names
.index(name
)].setname(*subnames
):
2690 self
.relsizes
= None
2692 if self
.subaxis
.setname(*subnames
):
2693 self
.relsizes
= None
2694 return self
.relsizes
is not None
2696 def updaterelsizes(self
):
2697 # guess what it does: it recalculates relsize attribute
2698 self
.relsizes
= [i
*self
.dist
+ self
.firstdist
for i
in range(len(self
.names
) + 1)]
2699 self
.relsizes
[-1] += self
.lastdist
- self
.dist
2700 if self
.multisubaxis
is not None:
2702 for i
in range(1, len(self
.relsizes
)):
2703 self
.subaxis
[i
-1].updaterelsizes()
2704 subrelsize
+= self
.subaxis
[i
-1].relsizes
[-1]
2705 self
.relsizes
[i
] += subrelsize
2707 if self
.subaxis
is None:
2710 self
.subaxis
.updaterelsizes()
2711 subrelsize
= self
.subaxis
.relsizes
[-1]
2712 for i
in range(1, len(self
.relsizes
)):
2713 self
.relsizes
[i
] += i
* subrelsize
2715 def convert(self
, value
):
2716 """baraxis convert method
2717 - the value should be a list, where the first entry is
2718 a member of the names (set in the constructor or by the
2719 setname method); this first entry identifies an item in
2721 - following values are passed to the appropriate subaxis
2723 - when there is no subaxis, the convert method will behave
2724 like having a linaxis from 0 to 1 as subaxis"""
2725 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
2726 if not self
.relsizes
:
2727 self
.updaterelsizes()
2728 pos
= self
.names
.index(value
[0])
2730 if self
.subaxis
is None:
2733 if self
.multisubaxis
is not None:
2734 subvalue
= value
[1] * self
.subaxis
[pos
].relsizes
[-1]
2736 subvalue
= value
[1] * self
.subaxis
.relsizes
[-1]
2738 if self
.multisubaxis
is not None:
2739 subvalue
= self
.subaxis
[pos
].convert(value
[1:]) * self
.subaxis
[pos
].relsizes
[-1]
2741 subvalue
= self
.subaxis
.convert(value
[1:]) * self
.subaxis
.relsizes
[-1]
2742 return (self
.relsizes
[pos
] + subvalue
) / float(self
.relsizes
[-1])
2744 def finish(self
, axispos
):
2745 if self
.axiscanvas
is None:
2746 if self
.multisubaxis
is not None:
2747 for name
, subaxis
in zip(self
.names
, self
.subaxis
):
2748 subaxis
.vmin
= self
.convert((name
, 0))
2749 subaxis
.vmax
= self
.convert((name
, 1))
2750 self
.axiscanvas
= self
.painter
.paint(axispos
, self
)
2752 def createlinkaxis(self
, **args
):
2753 return linkbaraxis(self
, **args
)
2756 class linkbaraxis(linkaxis
):
2757 """a baraxis linked to an already existing baraxis
2758 - inherits the access to a linked axis -- as before,
2759 basically only the painter is replaced
2760 - it must take care of the creation of linked axes of
2763 __implements__
= _Iaxis
2765 def __init__(self
, linkedaxis
, painter
=linkbaraxispainter()):
2766 """initializes the instance
2767 - it gets a axis this linkaxis is linked to
2768 - it gets a painter to be used for this linked axis"""
2769 linkaxis
.__init
__(self
, linkedaxis
, painter
=painter
)
2770 if self
.multisubaxis
is not None:
2771 self
.subaxis
= [subaxis
.createlinkaxis() for subaxis
in self
.linkedaxis
.subaxis
]
2772 elif self
.subaxis
is not None:
2773 self
.subaxis
= self
.subaxis
.createlinkaxis()
2776 def pathaxis(path
, axis
, **kwargs
):
2777 """creates an axiscanvas for an axis along a path"""
2778 mypathaxispos
= pathaxispos(path
, axis
.convert
, **kwargs
)
2779 axis
.finish(mypathaxispos
)
2780 return axis
.axiscanvas
2782 ################################################################################
2784 ################################################################################
2787 # g = graph.graphxy(key=graph.key())
2788 # g.addkey(graph.key(), ...)
2793 def __init__(self
, dist
="0.2 cm", pos
= "tr", hinside
= 1, vinside
= 1, hdist
="0.6 cm", vdist
="0.4 cm",
2794 symbolwidth
="0.5 cm", symbolheight
="0.25 cm", symbolspace
="0.2 cm",
2795 textattrs
=textmodule
.vshift
.mathaxis
):
2796 self
.dist_str
= dist
2798 self
.hinside
= hinside
2799 self
.vinside
= vinside
2800 self
.hdist_str
= hdist
2801 self
.vdist_str
= vdist
2802 self
.symbolwidth_str
= symbolwidth
2803 self
.symbolheight_str
= symbolheight
2804 self
.symbolspace_str
= symbolspace
2805 self
.textattrs
= textattrs
2806 self
.plotinfos
= None
2807 if self
.pos
in ("tr", "rt"):
2810 elif self
.pos
in ("br", "rb"):
2813 elif self
.pos
in ("tl", "lt"):
2816 elif self
.pos
in ("bl", "lb"):
2820 raise RuntimeError("invalid pos attribute")
2822 def setplotinfos(self
, *plotinfos
):
2823 """set the plotinfos to be used in the key
2824 - call it exactly once
2825 - plotinfo instances with title == None are ignored"""
2826 if self
.plotinfos
is not None:
2827 raise RuntimeError("setplotinfo is called multiple times")
2828 self
.plotinfos
= [plotinfo
for plotinfo
in plotinfos
if plotinfo
.data
.title
is not None]
2830 def dolayout(self
, graph
):
2831 "creates the layout of the key"
2832 self
.dist_pt
= unit
.topt(unit
.length(self
.dist_str
, default_type
="v"))
2833 self
.hdist_pt
= unit
.topt(unit
.length(self
.hdist_str
, default_type
="v"))
2834 self
.vdist_pt
= unit
.topt(unit
.length(self
.vdist_str
, default_type
="v"))
2835 self
.symbolwidth_pt
= unit
.topt(unit
.length(self
.symbolwidth_str
, default_type
="v"))
2836 self
.symbolheight_pt
= unit
.topt(unit
.length(self
.symbolheight_str
, default_type
="v"))
2837 self
.symbolspace_pt
= unit
.topt(unit
.length(self
.symbolspace_str
, default_type
="v"))
2839 for plotinfo
in self
.plotinfos
:
2840 self
.titles
.append(graph
.texrunner
.text_pt(0, 0, plotinfo
.data
.title
, helper
.ensuresequence(self
.textattrs
)))
2841 box
.tile_pt(self
.titles
, self
.dist_pt
, 0, -1)
2842 box
.linealignequal_pt(self
.titles
, self
.symbolwidth_pt
+ self
.symbolspace_pt
, 1, 0)
2845 """return a bbox for the key
2846 method should be called after dolayout"""
2847 result
= bbox
.bbox()
2848 for title
in self
.titles
:
2849 result
= result
+ title
.bbox() + bbox
._bbox
(0, title
.center
[1] - 0.5 * self
.symbolheight_pt
,
2850 0, title
.center
[1] + 0.5 * self
.symbolheight_pt
)
2853 def paint(self
, c
, x
, y
):
2854 """paint the graph key into a canvas c at the position x and y (in postscript points)
2855 - method should be called after dolayout
2856 - the x, y alignment might be calculated by the graph using:
2857 - the bbox of the key as returned by the keys bbox method
2858 - the attributes hdist_pt, vdist_pt, hinside, and vinside of the key
2859 - the dimension and geometry of the graph"""
2860 sc
= c
.insert(canvas
.canvas(trafomodule
.translate_pt(x
, y
)))
2861 for plotinfo
, title
in zip(self
.plotinfos
, self
.titles
):
2862 plotinfo
.style
.key(sc
, 0, -0.5 * self
.symbolheight_pt
+ title
.center
[1],
2863 self
.symbolwidth_pt
, self
.symbolheight_pt
)
2867 ################################################################################
2869 ################################################################################
2873 """an axispos linear along a line with a fix direction for the ticks"""
2875 __implements__
= _Iaxispos
2877 def __init__(self
, convert
, x1
, y1
, x2
, y2
, fixtickdirection
):
2878 """initializes the instance
2879 - only the convert method is needed from the axis
2880 - x1, y1, x2, y2 are PyX length"""
2881 self
.convert
= convert
2886 self
.x1_pt
= unit
.topt(x1
)
2887 self
.y1_pt
= unit
.topt(y1
)
2888 self
.x2_pt
= unit
.topt(x2
)
2889 self
.y2_pt
= unit
.topt(y2
)
2890 self
.fixtickdirection
= fixtickdirection
2892 def vbasepath(self
, v1
=None, v2
=None):
2897 return path
.line_pt((1-v1
)*self
.x1_pt
+v1
*self
.x2_pt
,
2898 (1-v1
)*self
.y1_pt
+v1
*self
.y2_pt
,
2899 (1-v2
)*self
.x1_pt
+v2
*self
.x2_pt
,
2900 (1-v2
)*self
.y1_pt
+v2
*self
.y2_pt
)
2902 def basepath(self
, x1
=None, x2
=None):
2906 v1
= self
.convert(x1
)
2910 v2
= self
.convert(x2
)
2911 return path
.line_pt((1-v1
)*self
.x1_pt
+v1
*self
.x2_pt
,
2912 (1-v1
)*self
.y1_pt
+v1
*self
.y2_pt
,
2913 (1-v2
)*self
.x1_pt
+v2
*self
.x2_pt
,
2914 (1-v2
)*self
.y1_pt
+v2
*self
.y2_pt
)
2916 def gridpath(self
, x
):
2917 raise RuntimeError("gridpath not available")
2919 def vgridpath(self
, v
):
2920 raise RuntimeError("gridpath not available")
2922 def vtickpoint_pt(self
, v
):
2923 return (1-v
)*self
.x1_pt
+v
*self
.x2_pt
, (1-v
)*self
.y1_pt
+v
*self
.y2_pt
2925 def vtickpoint(self
, v
):
2926 return (1-v
)*self
.x1
+v
*self
.x2
, (1-v
)*self
.y1
+v
*self
.y2
2928 def tickpoint_pt(self
, x
):
2930 return (1-v
)*self
.x1_pt
+v
*self
.x2_pt
, (1-v
)*self
.y1_pt
+v
*self
.y2_pt
2932 def tickpoint(self
, x
):
2934 return (1-v
)*self
.x1
+v
*self
.x2
, (1-v
)*self
.y1
+v
*self
.y2
2936 def tickdirection(self
, x
):
2937 return self
.fixtickdirection
2939 def vtickdirection(self
, v
):
2940 return self
.fixtickdirection
2943 class lineaxisposlinegrid(lineaxispos
):
2944 """an axispos linear along a line with a fix direction for the ticks"""
2946 __implements__
= _Iaxispos
2948 def __init__(self
, convert
, x1
, y1
, x2
, y2
, fixtickdirection
, startgridlength
, endgridlength
):
2949 """initializes the instance
2950 - only the convert method is needed from the axis
2951 - x1, y1, x2, y2 are PyX length"""
2952 lineaxispos
.__init
__(self
, convert
, x1
, y1
, x2
, y2
, fixtickdirection
)
2953 self
.startgridlength
= startgridlength
2954 self
.endgridlength
= endgridlength
2955 self
.startgridlength_pt
= unit
.topt(self
.startgridlength
)
2956 self
.endgridlength_pt
= unit
.topt(self
.endgridlength
)
2958 def gridpath(self
, x
):
2960 return path
.line_pt((1-v
)*self
.x1_pt
+v
*self
.x2_pt
+self
.fixtickdirection
[0]*self
.startgridlength_pt
,
2961 (1-v
)*self
.y1_pt
+v
*self
.y2_pt
+self
.fixtickdirection
[1]*self
.startgridlength_pt
,
2962 (1-v
)*self
.x1_pt
+v
*self
.x2_pt
+self
.fixtickdirection
[0]*self
.endgridlength_pt
,
2963 (1-v
)*self
.y1_pt
+v
*self
.y2_pt
+self
.fixtickdirection
[1]*self
.endgridlength_pt
)
2965 def vgridpath(self
, v
):
2966 return path
.line_pt((1-v
)*self
.x1_pt
+v
*self
.x2_pt
+self
.fixtickdirection
[0]*self
.startgridlength_pt
,
2967 (1-v
)*self
.y1_pt
+v
*self
.y2_pt
+self
.fixtickdirection
[1]*self
.startgridlength_pt
,
2968 (1-v
)*self
.x1_pt
+v
*self
.x2_pt
+self
.fixtickdirection
[0]*self
.endgridlength_pt
,
2969 (1-v
)*self
.y1_pt
+v
*self
.y2_pt
+self
.fixtickdirection
[1]*self
.endgridlength_pt
)
2974 def __init__(self
, data
, style
):
2980 class graphxy(canvas
.canvas
):
2986 def __init__(self
, type, axispos
, tickdirection
):
2988 - type == 0: x-axis; type == 1: y-axis
2989 - axispos_pt is the y or x position of the x-axis or y-axis
2990 in postscript points, respectively
2991 - axispos is analogous to axispos, but as a PyX length
2992 - dx and dy is the tick direction
2995 self
.axispos
= axispos
2996 self
.axispos_pt
= unit
.topt(axispos
)
2997 self
.tickdirection
= tickdirection
2999 def clipcanvas(self
):
3000 return self
.insert(canvas
.canvas(canvas
.clip(path
.rect_pt(self
.xpos_pt
, self
.ypos_pt
, self
.width_pt
, self
.height_pt
))))
3002 def plot(self
, data
, style
=None):
3004 raise RuntimeError("layout setup was already performed")
3006 if helper
.issequence(data
):
3007 raise RuntimeError("list plot needs an explicit style")
3008 if self
.defaultstyle
.has_key(data
.defaultstyle
):
3009 style
= self
.defaultstyle
[data
.defaultstyle
].iterate()
3011 style
= data
.defaultstyle()
3012 self
.defaultstyle
[data
.defaultstyle
] = style
3015 for d
in helper
.ensuresequence(data
):
3017 style
= style
.iterate()
3020 d
.setstyle(self
, style
)
3021 plotinfos
.append(plotinfo(d
, style
))
3022 self
.plotinfos
.extend(plotinfos
)
3023 if helper
.issequence(data
):
3027 def addkey(self
, key
, *plotinfos
):
3029 raise RuntimeError("layout setup was already performed")
3030 self
.addkeys
.append((key
, plotinfos
))
3032 def pos_pt(self
, x
, y
, xaxis
=None, yaxis
=None):
3034 xaxis
= self
.axes
["x"]
3036 yaxis
= self
.axes
["y"]
3037 return self
.xpos_pt
+xaxis
.convert(x
)*self
.width_pt
, self
.ypos_pt
+yaxis
.convert(y
)*self
.height_pt
3039 def pos(self
, x
, y
, xaxis
=None, yaxis
=None):
3041 xaxis
= self
.axes
["x"]
3043 yaxis
= self
.axes
["y"]
3044 return self
.xpos
+xaxis
.convert(x
)*self
.width
, self
.ypos
+yaxis
.convert(y
)*self
.height
3046 def vpos_pt(self
, vx
, vy
):
3047 return self
.xpos_pt
+vx
*self
.width_pt
, self
.ypos_pt
+vy
*self
.height_pt
3049 def vpos(self
, vx
, vy
):
3050 return self
.xpos
+vx
*self
.width
, self
.ypos
+vy
*self
.height
3052 def _addpos(self
, x
, y
, dx
, dy
):
3055 def _connect(self
, x1
, y1
, x2
, y2
):
3056 return path
.lineto_pt(x2
, y2
)
3058 def keynum(self
, key
):
3060 while key
[0] in string
.letters
:
3066 def gatherranges(self
):
3068 for plotinfo
in self
.plotinfos
:
3069 pdranges
= plotinfo
.data
.getranges()
3070 if pdranges
is not None:
3071 for key
in pdranges
.keys():
3072 if key
not in ranges
.keys():
3073 ranges
[key
] = pdranges
[key
]
3075 ranges
[key
] = (min(ranges
[key
][0], pdranges
[key
][0]),
3076 max(ranges
[key
][1], pdranges
[key
][1]))
3077 # known ranges are also set as ranges for the axes
3078 for key
, axis
in self
.axes
.items():
3079 if key
in ranges
.keys():
3080 axis
.setrange(*ranges
[key
])
3081 ranges
[key
] = axis
.getrange()
3082 if ranges
[key
] is None:
3086 def removedomethod(self
, method
):
3090 self
.domethods
.remove(method
)
3096 if not self
.removedomethod(self
.dolayout
): return
3098 # create list of ranges
3100 ranges
= self
.gatherranges()
3101 # 2. calculate additional ranges out of known ranges
3102 for plotinfo
in self
.plotinfos
:
3103 plotinfo
.data
.setranges(ranges
)
3104 # 3. gather ranges again
3106 # do the layout for all axes
3107 axesdist
= unit
.length(self
.axesdist_str
, default_type
="v")
3108 XPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[0])
3109 YPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[1])
3110 xaxisextents
= [0, 0]
3111 yaxisextents
= [0, 0]
3112 needxaxisdist
= [0, 0]
3113 needyaxisdist
= [0, 0]
3114 items
= list(self
.axes
.items())
3115 items
.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
3116 for key
, axis
in items
:
3117 num
= self
.keynum(key
)
3118 num2
= 1 - num
% 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
3119 num3
= 2 * (num
% 2) - 1 # x1 -> 1, x2 -> -1, x3 -> 1, x4 -> -1, ...
3120 if XPattern
.match(key
):
3121 if needxaxisdist
[num2
]:
3122 xaxisextents
[num2
] += axesdist
3123 self
.axespos
[key
] = lineaxisposlinegrid(self
.axes
[key
].convert
,
3125 self
.ypos
+ num2
*self
.height
- num3
*xaxisextents
[num2
],
3126 self
.xpos
+ self
.width
,
3127 self
.ypos
+ num2
*self
.height
- num3
*xaxisextents
[num2
],
3129 xaxisextents
[num2
], xaxisextents
[num2
] + self
.height
)
3131 self
.xbasepath
= self
.axespos
[key
].basepath
3132 self
.xvbasepath
= self
.axespos
[key
].vbasepath
3133 self
.xgridpath
= self
.axespos
[key
].gridpath
3134 self
.xvgridpath
= self
.axespos
[key
].vgridpath
3135 self
.xtickpoint_pt
= self
.axespos
[key
].tickpoint_pt
3136 self
.xtickpoint
= self
.axespos
[key
].tickpoint
3137 self
.xvtickpoint_pt
= self
.axespos
[key
].vtickpoint_pt
3138 self
.xvtickpoint
= self
.axespos
[key
].tickpoint
3139 self
.xtickdirection
= self
.axespos
[key
].tickdirection
3140 self
.xvtickdirection
= self
.axespos
[key
].vtickdirection
3141 elif YPattern
.match(key
):
3142 if needyaxisdist
[num2
]:
3143 yaxisextents
[num2
] += axesdist
3144 self
.axespos
[key
] = lineaxisposlinegrid(self
.axes
[key
].convert
,
3145 self
.xpos
+ num2
*self
.width
- num3
*yaxisextents
[num2
],
3147 self
.xpos
+ num2
*self
.width
- num3
*yaxisextents
[num2
],
3148 self
.ypos
+ self
.height
,
3150 yaxisextents
[num2
], yaxisextents
[num2
] + self
.width
)
3152 self
.ybasepath
= self
.axespos
[key
].basepath
3153 self
.yvbasepath
= self
.axespos
[key
].vbasepath
3154 self
.ygridpath
= self
.axespos
[key
].gridpath
3155 self
.yvgridpath
= self
.axespos
[key
].vgridpath
3156 self
.ytickpoint_pt
= self
.axespos
[key
].tickpoint_pt
3157 self
.ytickpoint
= self
.axespos
[key
].tickpoint
3158 self
.yvtickpoint_pt
= self
.axespos
[key
].vtickpoint_pt
3159 self
.yvtickpoint
= self
.axespos
[key
].tickpoint
3160 self
.ytickdirection
= self
.axespos
[key
].tickdirection
3161 self
.yvtickdirection
= self
.axespos
[key
].vtickdirection
3163 raise ValueError("Axis key '%s' not allowed" % key
)
3164 axis
.finish(self
.axespos
[key
])
3165 if XPattern
.match(key
):
3166 xaxisextents
[num2
] += axis
.axiscanvas
.extent
3167 needxaxisdist
[num2
] = 1
3168 if YPattern
.match(key
):
3169 yaxisextents
[num2
] += axis
.axiscanvas
.extent
3170 needyaxisdist
[num2
] = 1
3172 def dobackground(self
):
3174 if not self
.removedomethod(self
.dobackground
): return
3175 if self
.backgroundattrs
is not None:
3176 self
.draw(path
.rect_pt(self
.xpos_pt
, self
.ypos_pt
, self
.width_pt
, self
.height_pt
),
3177 helper
.ensurelist(self
.backgroundattrs
))
3181 if not self
.removedomethod(self
.doaxes
): return
3182 for axis
in self
.axes
.values():
3183 self
.insert(axis
.axiscanvas
)
3187 if not self
.removedomethod(self
.dodata
): return
3188 for plotinfo
in self
.plotinfos
:
3189 plotinfo
.data
.draw(self
)
3191 def _dokey(self
, key
, *plotinfos
):
3192 key
.setplotinfos(*plotinfos
)
3197 x
= self
.xpos_pt
+ self
.width_pt
- bbox
.urx
- key
.hdist_pt
3199 x
= self
.xpos_pt
+ self
.width_pt
- bbox
.llx
+ key
.hdist_pt
3202 x
= self
.xpos_pt
- bbox
.llx
+ key
.hdist_pt
3204 x
= self
.xpos_pt
- bbox
.urx
- key
.hdist_pt
3207 y
= self
.ypos_pt
+ self
.height_pt
- bbox
.ury
- key
.vdist_pt
3209 y
= self
.ypos_pt
+ self
.height_pt
- bbox
.lly
+ key
.vdist_pt
3212 y
= self
.ypos_pt
- bbox
.lly
+ key
.vdist_pt
3214 y
= self
.ypos_pt
- bbox
.ury
- key
.vdist_pt
3215 key
.paint(self
, x
, y
)
3219 if not self
.removedomethod(self
.dokey
): return
3220 if self
.key
is not None:
3221 self
._dokey
(self
.key
, *self
.plotinfos
)
3222 for key
, plotinfos
in self
.addkeys
:
3223 self
._dokey
(key
, *plotinfos
)
3226 while len(self
.domethods
):
3229 def initwidthheight(self
, width
, height
, ratio
):
3230 if (width
is not None) and (height
is None):
3231 self
.width
= unit
.length(width
)
3232 self
.height
= (1.0/ratio
) * self
.width
3233 elif (height
is not None) and (width
is None):
3234 self
.height
= unit
.length(height
)
3235 self
.width
= ratio
* self
.height
3237 self
.width
= unit
.length(width
)
3238 self
.height
= unit
.length(height
)
3239 self
.width_pt
= unit
.topt(self
.width
)
3240 self
.height_pt
= unit
.topt(self
.height
)
3241 if self
.width_pt
<= 0: raise ValueError("width <= 0")
3242 if self
.height_pt
<= 0: raise ValueError("height <= 0")
3244 def initaxes(self
, axes
, addlinkaxes
=0):
3245 for key
in self
.Names
:
3246 if not axes
.has_key(key
):
3247 axes
[key
] = linaxis()
3248 elif axes
[key
] is None:
3251 if not axes
.has_key(key
+ "2") and axes
.has_key(key
):
3252 axes
[key
+ "2"] = axes
[key
].createlinkaxis()
3253 elif axes
[key
+ "2"] is None:
3257 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, ratio
=goldenmean
,
3258 key
=None, backgroundattrs
=None, axesdist
="0.8 cm", **axes
):
3259 canvas
.canvas
.__init
__(self
)
3260 self
.xpos
= unit
.length(xpos
)
3261 self
.ypos
= unit
.length(ypos
)
3262 self
.xpos_pt
= unit
.topt(self
.xpos
)
3263 self
.ypos_pt
= unit
.topt(self
.ypos
)
3264 self
.initwidthheight(width
, height
, ratio
)
3265 self
.initaxes(axes
, 1)
3266 self
.axescanvas
= {}
3269 self
.backgroundattrs
= backgroundattrs
3270 self
.axesdist_str
= axesdist
3272 self
.domethods
= [self
.dolayout
, self
.dobackground
, self
.doaxes
, self
.dodata
, self
.dokey
]
3274 self
.defaultstyle
= {}
3279 return canvas
.canvas
.bbox(self
)
3281 def write(self
, file):
3283 canvas
.canvas
.write(self
, file)
3287 # some thoughts, but deferred right now
3289 # class graphxyz(graphxy):
3291 # Names = "x", "y", "z"
3293 # def _vxtickpoint(self, axis, v):
3294 # return self._vpos(v, axis.vypos, axis.vzpos)
3296 # def _vytickpoint(self, axis, v):
3297 # return self._vpos(axis.vxpos, v, axis.vzpos)
3299 # def _vztickpoint(self, axis, v):
3300 # return self._vpos(axis.vxpos, axis.vypos, v)
3302 # def vxtickdirection(self, axis, v):
3303 # x1, y1 = self._vpos(v, axis.vypos, axis.vzpos)
3304 # x2, y2 = self._vpos(v, 0.5, 0)
3305 # dx, dy = x1 - x2, y1 - y2
3306 # norm = math.sqrt(dx*dx + dy*dy)
3307 # return dx/norm, dy/norm
3309 # def vytickdirection(self, axis, v):
3310 # x1, y1 = self._vpos(axis.vxpos, v, axis.vzpos)
3311 # x2, y2 = self._vpos(0.5, v, 0)
3312 # dx, dy = x1 - x2, y1 - y2
3313 # norm = math.sqrt(dx*dx + dy*dy)
3314 # return dx/norm, dy/norm
3316 # def vztickdirection(self, axis, v):
3318 # x1, y1 = self._vpos(axis.vxpos, axis.vypos, v)
3319 # x2, y2 = self._vpos(0.5, 0.5, v)
3320 # dx, dy = x1 - x2, y1 - y2
3321 # norm = math.sqrt(dx*dx + dy*dy)
3322 # return dx/norm, dy/norm
3324 # def _pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
3325 # if xaxis is None: xaxis = self.axes["x"]
3326 # if yaxis is None: yaxis = self.axes["y"]
3327 # if zaxis is None: zaxis = self.axes["z"]
3328 # return self._vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
3330 # def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
3331 # if xaxis is None: xaxis = self.axes["x"]
3332 # if yaxis is None: yaxis = self.axes["y"]
3333 # if zaxis is None: zaxis = self.axes["z"]
3334 # return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
3336 # def _vpos(self, vx, vy, vz):
3337 # x, y, z = (vx - 0.5)*self._depth, (vy - 0.5)*self._width, (vz - 0.5)*self._height
3338 # d0 = float(self.a[0]*self.b[1]*(z-self.eye[2])
3339 # + self.a[2]*self.b[0]*(y-self.eye[1])
3340 # + self.a[1]*self.b[2]*(x-self.eye[0])
3341 # - self.a[2]*self.b[1]*(x-self.eye[0])
3342 # - self.a[0]*self.b[2]*(y-self.eye[1])
3343 # - self.a[1]*self.b[0]*(z-self.eye[2]))
3344 # da = (self.eye[0]*self.b[1]*(z-self.eye[2])
3345 # + self.eye[2]*self.b[0]*(y-self.eye[1])
3346 # + self.eye[1]*self.b[2]*(x-self.eye[0])
3347 # - self.eye[2]*self.b[1]*(x-self.eye[0])
3348 # - self.eye[0]*self.b[2]*(y-self.eye[1])
3349 # - self.eye[1]*self.b[0]*(z-self.eye[2]))
3350 # db = (self.a[0]*self.eye[1]*(z-self.eye[2])
3351 # + self.a[2]*self.eye[0]*(y-self.eye[1])
3352 # + self.a[1]*self.eye[2]*(x-self.eye[0])
3353 # - self.a[2]*self.eye[1]*(x-self.eye[0])
3354 # - self.a[0]*self.eye[2]*(y-self.eye[1])
3355 # - self.a[1]*self.eye[0]*(z-self.eye[2]))
3356 # return da/d0 + self._xpos, db/d0 + self._ypos
3358 # def vpos(self, vx, vy, vz):
3359 # tx, ty = self._vpos(vx, vy, vz)
3360 # return unit.t_pt(tx), unit.t_pt(ty)
3362 # def xbaseline(self, axis, x1, x2, xaxis=None):
3363 # if xaxis is None: xaxis = self.axes["x"]
3364 # return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2))
3366 # def ybaseline(self, axis, y1, y2, yaxis=None):
3367 # if yaxis is None: yaxis = self.axes["y"]
3368 # return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2))
3370 # def zbaseline(self, axis, z1, z2, zaxis=None):
3371 # if zaxis is None: zaxis = self.axes["z"]
3372 # return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2))
3374 # def vxbaseline(self, axis, v1, v2):
3375 # return (path._line(*(self._vpos(v1, 0, 0) + self._vpos(v2, 0, 0))) +
3376 # path._line(*(self._vpos(v1, 0, 1) + self._vpos(v2, 0, 1))) +
3377 # path._line(*(self._vpos(v1, 1, 1) + self._vpos(v2, 1, 1))) +
3378 # path._line(*(self._vpos(v1, 1, 0) + self._vpos(v2, 1, 0))))
3380 # def vybaseline(self, axis, v1, v2):
3381 # return (path._line(*(self._vpos(0, v1, 0) + self._vpos(0, v2, 0))) +
3382 # path._line(*(self._vpos(0, v1, 1) + self._vpos(0, v2, 1))) +
3383 # path._line(*(self._vpos(1, v1, 1) + self._vpos(1, v2, 1))) +
3384 # path._line(*(self._vpos(1, v1, 0) + self._vpos(1, v2, 0))))
3386 # def vzbaseline(self, axis, v1, v2):
3387 # return (path._line(*(self._vpos(0, 0, v1) + self._vpos(0, 0, v2))) +
3388 # path._line(*(self._vpos(0, 1, v1) + self._vpos(0, 1, v2))) +
3389 # path._line(*(self._vpos(1, 1, v1) + self._vpos(1, 1, v2))) +
3390 # path._line(*(self._vpos(1, 0, v1) + self._vpos(1, 0, v2))))
3392 # def xgridpath(self, x, xaxis=None):
3394 # if xaxis is None: xaxis = self.axes["x"]
3395 # v = xaxis.convert(x)
3396 # return path._line(self._xpos+v*self._width, self._ypos,
3397 # self._xpos+v*self._width, self._ypos+self._height)
3399 # def ygridpath(self, y, yaxis=None):
3401 # if yaxis is None: yaxis = self.axes["y"]
3402 # v = yaxis.convert(y)
3403 # return path._line(self._xpos, self._ypos+v*self._height,
3404 # self._xpos+self._width, self._ypos+v*self._height)
3406 # def zgridpath(self, z, zaxis=None):
3408 # if zaxis is None: zaxis = self.axes["z"]
3409 # v = zaxis.convert(z)
3410 # return path._line(self._xpos, self._zpos+v*self._height,
3411 # self._xpos+self._width, self._zpos+v*self._height)
3413 # def vxgridpath(self, v):
3414 # return path.path(path._moveto(*self._vpos(v, 0, 0)),
3415 # path._lineto(*self._vpos(v, 0, 1)),
3416 # path._lineto(*self._vpos(v, 1, 1)),
3417 # path._lineto(*self._vpos(v, 1, 0)),
3420 # def vygridpath(self, v):
3421 # return path.path(path._moveto(*self._vpos(0, v, 0)),
3422 # path._lineto(*self._vpos(0, v, 1)),
3423 # path._lineto(*self._vpos(1, v, 1)),
3424 # path._lineto(*self._vpos(1, v, 0)),
3427 # def vzgridpath(self, v):
3428 # return path.path(path._moveto(*self._vpos(0, 0, v)),
3429 # path._lineto(*self._vpos(0, 1, v)),
3430 # path._lineto(*self._vpos(1, 1, v)),
3431 # path._lineto(*self._vpos(1, 0, v)),
3434 # def _addpos(self, x, y, dx, dy):
3438 # def _connect(self, x1, y1, x2, y2):
3440 # return path._lineto(x2, y2)
3444 # if not self.removedomethod(self.doaxes): return
3445 # axesdist = unit.topt(unit.length(self.axesdist_str, default_type="v"))
3446 # XPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[0])
3447 # YPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[1])
3448 # ZPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[2])
3449 # items = list(self.axes.items())
3450 # items.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
3451 # for key, axis in items:
3452 # num = self.keynum(key)
3453 # num2 = 1 - num % 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
3454 # num3 = 1 - 2 * (num % 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
3455 # if XPattern.match(key):
3458 # axis._vtickpoint = self._vxtickpoint
3459 # axis.vgridpath = self.vxgridpath
3460 # axis.vbaseline = self.vxbaseline
3461 # axis.vtickdirection = self.vxtickdirection
3462 # elif YPattern.match(key):
3465 # axis._vtickpoint = self._vytickpoint
3466 # axis.vgridpath = self.vygridpath
3467 # axis.vbaseline = self.vybaseline
3468 # axis.vtickdirection = self.vytickdirection
3469 # elif ZPattern.match(key):
3472 # axis._vtickpoint = self._vztickpoint
3473 # axis.vgridpath = self.vzgridpath
3474 # axis.vbaseline = self.vzbaseline
3475 # axis.vtickdirection = self.vztickdirection
3477 # raise ValueError("Axis key '%s' not allowed" % key)
3478 # if axis.painter is not None:
3479 # axis.dopaint(self)
3480 # # if XPattern.match(key):
3481 # # self._xaxisextents[num2] += axis._extent
3482 # # needxaxisdist[num2] = 1
3483 # # if YPattern.match(key):
3484 # # self._yaxisextents[num2] += axis._extent
3485 # # needyaxisdist[num2] = 1
3487 # def __init__(self, tex, xpos=0, ypos=0, width=None, height=None, depth=None,
3488 # phi=30, theta=30, distance=1,
3489 # backgroundattrs=None, axesdist="0.8 cm", **axes):
3490 # canvas.canvas.__init__(self)
3494 # self._xpos = unit.topt(xpos)
3495 # self._ypos = unit.topt(ypos)
3496 # self._width = unit.topt(width)
3497 # self._height = unit.topt(height)
3498 # self._depth = unit.topt(depth)
3499 # self.width = width
3500 # self.height = height
3501 # self.depth = depth
3502 # if self._width <= 0: raise ValueError("width < 0")
3503 # if self._height <= 0: raise ValueError("height < 0")
3504 # if self._depth <= 0: raise ValueError("height < 0")
3505 # self._distance = distance*math.sqrt(self._width*self._width+
3506 # self._height*self._height+
3507 # self._depth*self._depth)
3508 # phi *= -math.pi/180
3509 # theta *= math.pi/180
3510 # self.a = (-math.sin(phi), math.cos(phi), 0)
3511 # self.b = (-math.cos(phi)*math.sin(theta),
3512 # -math.sin(phi)*math.sin(theta),
3514 # self.eye = (self._distance*math.cos(phi)*math.cos(theta),
3515 # self._distance*math.sin(phi)*math.cos(theta),
3516 # self._distance*math.sin(theta))
3517 # self.initaxes(axes)
3518 # self.axesdist_str = axesdist
3519 # self.backgroundattrs = backgroundattrs
3522 # self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
3523 # self.haslayout = 0
3524 # self.defaultstyle = {}
3528 # return bbox._bbox(self._xpos - 200, self._ypos - 200, self._xpos + 200, self._ypos + 200)
3531 ################################################################################
3533 ################################################################################
3536 #class _Ichangeattr:
3537 # """attribute changer
3538 # is an iterator for attributes where an attribute
3539 # is not refered by just a number (like for a sequence),
3540 # but also by the number of attributes requested
3541 # by calls of the next method (like for an color palette)
3542 # (you should ensure to call all needed next before the attr)
3544 # the attribute itself is implemented by overloading the _attr method"""
3547 # "get an attribute"
3550 # "get an attribute changer for the next attribute"
3553 class _changeattr
: pass
3556 class changeattr(_changeattr
):
3565 newindex
= self
.counter
3567 return refattr(self
, newindex
)
3570 class refattr(_changeattr
):
3572 def __init__(self
, ref
, index
):
3577 return self
.ref
.attr(self
.index
)
3580 return self
.ref
.iterate()
3583 # helper routines for a using attrs
3586 "get attr out of a attr/changeattr"
3587 if isinstance(attr
, _changeattr
):
3588 return attr
.getattr()
3592 def _getattrs(attrs
):
3593 "get attrs out of a list of attr/changeattr"
3594 if attrs
is not None:
3596 for attr
in helper
.ensuresequence(attrs
):
3597 if isinstance(attr
, _changeattr
):
3598 attr
= attr
.getattr()
3599 if attr
is not None:
3601 if len(result
) or not len(attrs
):
3605 def _iterateattr(attr
):
3606 "perform next to a attr/changeattr"
3607 if isinstance(attr
, _changeattr
):
3608 return attr
.iterate()
3612 def _iterateattrs(attrs
):
3613 "perform next to a list of attr/changeattr"
3614 if attrs
is not None:
3616 for attr
in helper
.ensuresequence(attrs
):
3617 if isinstance(attr
, _changeattr
):
3618 result
.append(attr
.iterate())
3624 class changecolor(changeattr
):
3626 def __init__(self
, palette
):
3627 changeattr
.__init
__(self
)
3628 self
.palette
= palette
3630 def attr(self
, index
):
3631 if self
.counter
!= 1:
3632 return self
.palette
.getcolor(index
/float(self
.counter
-1))
3634 return self
.palette
.getcolor(0)
3637 class _changecolorgray(changecolor
):
3639 def __init__(self
, palette
=color
.palette
.Gray
):
3640 changecolor
.__init
__(self
, palette
)
3642 _changecolorgrey
= _changecolorgray
3645 class _changecolorreversegray(changecolor
):
3647 def __init__(self
, palette
=color
.palette
.ReverseGray
):
3648 changecolor
.__init
__(self
, palette
)
3650 _changecolorreversegrey
= _changecolorreversegray
3653 class _changecolorredblack(changecolor
):
3655 def __init__(self
, palette
=color
.palette
.RedBlack
):
3656 changecolor
.__init
__(self
, palette
)
3659 class _changecolorblackred(changecolor
):
3661 def __init__(self
, palette
=color
.palette
.BlackRed
):
3662 changecolor
.__init
__(self
, palette
)
3665 class _changecolorredwhite(changecolor
):
3667 def __init__(self
, palette
=color
.palette
.RedWhite
):
3668 changecolor
.__init
__(self
, palette
)
3671 class _changecolorwhitered(changecolor
):
3673 def __init__(self
, palette
=color
.palette
.WhiteRed
):
3674 changecolor
.__init
__(self
, palette
)
3677 class _changecolorgreenblack(changecolor
):
3679 def __init__(self
, palette
=color
.palette
.GreenBlack
):
3680 changecolor
.__init
__(self
, palette
)
3683 class _changecolorblackgreen(changecolor
):
3685 def __init__(self
, palette
=color
.palette
.BlackGreen
):
3686 changecolor
.__init
__(self
, palette
)
3689 class _changecolorgreenwhite(changecolor
):
3691 def __init__(self
, palette
=color
.palette
.GreenWhite
):
3692 changecolor
.__init
__(self
, palette
)
3695 class _changecolorwhitegreen(changecolor
):
3697 def __init__(self
, palette
=color
.palette
.WhiteGreen
):
3698 changecolor
.__init
__(self
, palette
)
3701 class _changecolorblueblack(changecolor
):
3703 def __init__(self
, palette
=color
.palette
.BlueBlack
):
3704 changecolor
.__init
__(self
, palette
)
3707 class _changecolorblackblue(changecolor
):
3709 def __init__(self
, palette
=color
.palette
.BlackBlue
):
3710 changecolor
.__init
__(self
, palette
)
3713 class _changecolorbluewhite(changecolor
):
3715 def __init__(self
, palette
=color
.palette
.BlueWhite
):
3716 changecolor
.__init
__(self
, palette
)
3719 class _changecolorwhiteblue(changecolor
):
3721 def __init__(self
, palette
=color
.palette
.WhiteBlue
):
3722 changecolor
.__init
__(self
, palette
)
3725 class _changecolorredgreen(changecolor
):
3727 def __init__(self
, palette
=color
.palette
.RedGreen
):
3728 changecolor
.__init
__(self
, palette
)
3731 class _changecolorredblue(changecolor
):
3733 def __init__(self
, palette
=color
.palette
.RedBlue
):
3734 changecolor
.__init
__(self
, palette
)
3737 class _changecolorgreenred(changecolor
):
3739 def __init__(self
, palette
=color
.palette
.GreenRed
):
3740 changecolor
.__init
__(self
, palette
)
3743 class _changecolorgreenblue(changecolor
):
3745 def __init__(self
, palette
=color
.palette
.GreenBlue
):
3746 changecolor
.__init
__(self
, palette
)
3749 class _changecolorbluered(changecolor
):
3751 def __init__(self
, palette
=color
.palette
.BlueRed
):
3752 changecolor
.__init
__(self
, palette
)
3755 class _changecolorbluegreen(changecolor
):
3757 def __init__(self
, palette
=color
.palette
.BlueGreen
):
3758 changecolor
.__init
__(self
, palette
)
3761 class _changecolorrainbow(changecolor
):
3763 def __init__(self
, palette
=color
.palette
.Rainbow
):
3764 changecolor
.__init
__(self
, palette
)
3767 class _changecolorreverserainbow(changecolor
):
3769 def __init__(self
, palette
=color
.palette
.ReverseRainbow
):
3770 changecolor
.__init
__(self
, palette
)
3773 class _changecolorhue(changecolor
):
3775 def __init__(self
, palette
=color
.palette
.Hue
):
3776 changecolor
.__init
__(self
, palette
)
3779 class _changecolorreversehue(changecolor
):
3781 def __init__(self
, palette
=color
.palette
.ReverseHue
):
3782 changecolor
.__init
__(self
, palette
)
3785 changecolor
.Gray
= _changecolorgray
3786 changecolor
.Grey
= _changecolorgrey
3787 changecolor
.Reversegray
= _changecolorreversegray
3788 changecolor
.Reversegrey
= _changecolorreversegrey
3789 changecolor
.RedBlack
= _changecolorredblack
3790 changecolor
.BlackRed
= _changecolorblackred
3791 changecolor
.RedWhite
= _changecolorredwhite
3792 changecolor
.WhiteRed
= _changecolorwhitered
3793 changecolor
.GreenBlack
= _changecolorgreenblack
3794 changecolor
.BlackGreen
= _changecolorblackgreen
3795 changecolor
.GreenWhite
= _changecolorgreenwhite
3796 changecolor
.WhiteGreen
= _changecolorwhitegreen
3797 changecolor
.BlueBlack
= _changecolorblueblack
3798 changecolor
.BlackBlue
= _changecolorblackblue
3799 changecolor
.BlueWhite
= _changecolorbluewhite
3800 changecolor
.WhiteBlue
= _changecolorwhiteblue
3801 changecolor
.RedGreen
= _changecolorredgreen
3802 changecolor
.RedBlue
= _changecolorredblue
3803 changecolor
.GreenRed
= _changecolorgreenred
3804 changecolor
.GreenBlue
= _changecolorgreenblue
3805 changecolor
.BlueRed
= _changecolorbluered
3806 changecolor
.BlueGreen
= _changecolorbluegreen
3807 changecolor
.Rainbow
= _changecolorrainbow
3808 changecolor
.ReverseRainbow
= _changecolorreverserainbow
3809 changecolor
.Hue
= _changecolorhue
3810 changecolor
.ReverseHue
= _changecolorreversehue
3813 class changesequence(changeattr
):
3814 "cycles through a list"
3816 def __init__(self
, *sequence
):
3817 changeattr
.__init
__(self
)
3818 if not len(sequence
):
3819 sequence
= self
.defaultsequence
3820 self
.sequence
= sequence
3822 def attr(self
, index
):
3823 return self
.sequence
[index
% len(self
.sequence
)]
3826 class changelinestyle(changesequence
):
3827 defaultsequence
= (style
.linestyle
.solid
,
3828 style
.linestyle
.dashed
,
3829 style
.linestyle
.dotted
,
3830 style
.linestyle
.dashdotted
)
3833 class changestrokedfilled(changesequence
):
3834 defaultsequence
= (deco
.stroked
, deco
.filled
)
3837 class changefilledstroked(changesequence
):
3838 defaultsequence
= (deco
.filled
, deco
.stroked
)
3842 ################################################################################
3844 ################################################################################
3849 def cross(self
, x
, y
):
3850 return (path
.moveto_pt(x
-0.5*self
.size_pt
, y
-0.5*self
.size_pt
),
3851 path
.lineto_pt(x
+0.5*self
.size_pt
, y
+0.5*self
.size_pt
),
3852 path
.moveto_pt(x
-0.5*self
.size_pt
, y
+0.5*self
.size_pt
),
3853 path
.lineto_pt(x
+0.5*self
.size_pt
, y
-0.5*self
.size_pt
))
3855 def plus(self
, x
, y
):
3856 return (path
.moveto_pt(x
-0.707106781*self
.size_pt
, y
),
3857 path
.lineto_pt(x
+0.707106781*self
.size_pt
, y
),
3858 path
.moveto_pt(x
, y
-0.707106781*self
.size_pt
),
3859 path
.lineto_pt(x
, y
+0.707106781*self
.size_pt
))
3861 def square(self
, x
, y
):
3862 return (path
.moveto_pt(x
-0.5*self
.size_pt
, y
-0.5 * self
.size_pt
),
3863 path
.lineto_pt(x
+0.5*self
.size_pt
, y
-0.5 * self
.size_pt
),
3864 path
.lineto_pt(x
+0.5*self
.size_pt
, y
+0.5 * self
.size_pt
),
3865 path
.lineto_pt(x
-0.5*self
.size_pt
, y
+0.5 * self
.size_pt
),
3868 def triangle(self
, x
, y
):
3869 return (path
.moveto_pt(x
-0.759835685*self
.size_pt
, y
-0.438691337*self
.size_pt
),
3870 path
.lineto_pt(x
+0.759835685*self
.size_pt
, y
-0.438691337*self
.size_pt
),
3871 path
.lineto_pt(x
, y
+0.877382675*self
.size_pt
),
3874 def circle(self
, x
, y
):
3875 return (path
.arc_pt(x
, y
, 0.564189583*self
.size_pt
, 0, 360),
3878 def diamond(self
, x
, y
):
3879 return (path
.moveto_pt(x
-0.537284965*self
.size_pt
, y
),
3880 path
.lineto_pt(x
, y
-0.930604859*self
.size_pt
),
3881 path
.lineto_pt(x
+0.537284965*self
.size_pt
, y
),
3882 path
.lineto_pt(x
, y
+0.930604859*self
.size_pt
),
3885 def __init__(self
, symbol
=helper
.nodefault
,
3886 size
="0.2 cm", symbolattrs
=deco
.stroked
,
3887 errorscale
=0.5, errorbarattrs
=(),
3889 self
.size_str
= size
3890 if symbol
is helper
.nodefault
:
3891 self
._symbol
= changesymbol
.cross()
3893 self
._symbol
= symbol
3894 self
._symbolattrs
= symbolattrs
3895 self
.errorscale
= errorscale
3896 self
._errorbarattrs
= errorbarattrs
3897 self
._lineattrs
= lineattrs
3899 def iteratedict(self
):
3901 result
["symbol"] = _iterateattr(self
._symbol
)
3902 result
["size"] = _iterateattr(self
.size_str
)
3903 result
["symbolattrs"] = _iterateattrs(self
._symbolattrs
)
3904 result
["errorscale"] = _iterateattr(self
.errorscale
)
3905 result
["errorbarattrs"] = _iterateattrs(self
._errorbarattrs
)
3906 result
["lineattrs"] = _iterateattrs(self
._lineattrs
)
3910 return symbol(**self
.iteratedict())
3912 def othercolumnkey(self
, key
, index
):
3913 raise ValueError("unsuitable key '%s'" % key
)
3915 def setcolumns(self
, graph
, columns
):
3916 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
3918 match
= pattern
.match(key
)
3920 if isindex
is not None: raise ValueError("multiple key specification")
3921 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
3923 iskey
= match
.groups()[0]
3925 return key
, iskey
, isindex
3927 self
.xi
= self
.xmini
= self
.xmaxi
= None
3928 self
.dxi
= self
.dxmini
= self
.dxmaxi
= None
3929 self
.yi
= self
.ymini
= self
.ymaxi
= None
3930 self
.dyi
= self
.dymini
= self
.dymaxi
= None
3931 self
.xkey
= self
.ykey
= None
3932 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
3933 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
3934 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
3935 XMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
3936 YMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
3937 XMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
3938 YMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
3939 DXPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
3940 DYPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
3941 DXMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
3942 DYMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
3943 DXMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
3944 DYMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
3945 for key
, index
in columns
.items():
3946 key
, self
.xkey
, self
.xi
= checkpattern(key
, index
, XPattern
, self
.xkey
, self
.xi
)
3947 key
, self
.ykey
, self
.yi
= checkpattern(key
, index
, YPattern
, self
.ykey
, self
.yi
)
3948 key
, self
.xkey
, self
.xmini
= checkpattern(key
, index
, XMinPattern
, self
.xkey
, self
.xmini
)
3949 key
, self
.ykey
, self
.ymini
= checkpattern(key
, index
, YMinPattern
, self
.ykey
, self
.ymini
)
3950 key
, self
.xkey
, self
.xmaxi
= checkpattern(key
, index
, XMaxPattern
, self
.xkey
, self
.xmaxi
)
3951 key
, self
.ykey
, self
.ymaxi
= checkpattern(key
, index
, YMaxPattern
, self
.ykey
, self
.ymaxi
)
3952 key
, self
.xkey
, self
.dxi
= checkpattern(key
, index
, DXPattern
, self
.xkey
, self
.dxi
)
3953 key
, self
.ykey
, self
.dyi
= checkpattern(key
, index
, DYPattern
, self
.ykey
, self
.dyi
)
3954 key
, self
.xkey
, self
.dxmini
= checkpattern(key
, index
, DXMinPattern
, self
.xkey
, self
.dxmini
)
3955 key
, self
.ykey
, self
.dymini
= checkpattern(key
, index
, DYMinPattern
, self
.ykey
, self
.dymini
)
3956 key
, self
.xkey
, self
.dxmaxi
= checkpattern(key
, index
, DXMaxPattern
, self
.xkey
, self
.dxmaxi
)
3957 key
, self
.ykey
, self
.dymaxi
= checkpattern(key
, index
, DYMaxPattern
, self
.ykey
, self
.dymaxi
)
3959 self
.othercolumnkey(key
, index
)
3960 if None in (self
.xkey
, self
.ykey
): raise ValueError("incomplete axis specification")
3961 if (len(filter(None, (self
.xmini
, self
.dxmini
, self
.dxi
))) > 1 or
3962 len(filter(None, (self
.ymini
, self
.dymini
, self
.dyi
))) > 1 or
3963 len(filter(None, (self
.xmaxi
, self
.dxmaxi
, self
.dxi
))) > 1 or
3964 len(filter(None, (self
.ymaxi
, self
.dymaxi
, self
.dyi
))) > 1):
3965 raise ValueError("multiple errorbar definition")
3966 if ((self
.xi
is None and self
.dxi
is not None) or
3967 (self
.yi
is None and self
.dyi
is not None) or
3968 (self
.xi
is None and self
.dxmini
is not None) or
3969 (self
.yi
is None and self
.dymini
is not None) or
3970 (self
.xi
is None and self
.dxmaxi
is not None) or
3971 (self
.yi
is None and self
.dymaxi
is not None)):
3972 raise ValueError("errorbar definition start value missing")
3973 self
.xaxis
= graph
.axes
[self
.xkey
]
3974 self
.yaxis
= graph
.axes
[self
.ykey
]
3976 def minmidmax(self
, point
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
3977 min = max = mid
= None
3979 mid
= point
[i
] + 0.0
3980 except (TypeError, ValueError):
3983 if di
is not None: min = point
[i
] - point
[di
]
3984 elif dmini
is not None: min = point
[i
] - point
[dmini
]
3985 elif mini
is not None: min = point
[mini
] + 0.0
3986 except (TypeError, ValueError):
3989 if di
is not None: max = point
[i
] + point
[di
]
3990 elif dmaxi
is not None: max = point
[i
] + point
[dmaxi
]
3991 elif maxi
is not None: max = point
[maxi
] + 0.0
3992 except (TypeError, ValueError):
3995 if min is not None and min > mid
: raise ValueError("minimum error in errorbar")
3996 if max is not None and max < mid
: raise ValueError("maximum error in errorbar")
3998 if min is not None and max is not None and min > max: raise ValueError("minimum/maximum error in errorbar")
3999 return min, mid
, max
4001 def keyrange(self
, points
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
4002 allmin
= allmax
= None
4003 if filter(None, (mini
, maxi
, di
, dmini
, dmaxi
)) is not None:
4004 for point
in points
:
4005 min, mid
, max = self
.minmidmax(point
, i
, mini
, maxi
, di
, dmini
, dmaxi
)
4006 if min is not None and (allmin
is None or min < allmin
): allmin
= min
4007 if mid
is not None and (allmin
is None or mid
< allmin
): allmin
= mid
4008 if mid
is not None and (allmax
is None or mid
> allmax
): allmax
= mid
4009 if max is not None and (allmax
is None or max > allmax
): allmax
= max
4011 for point
in points
:
4013 value
= point
[i
] + 0.0
4014 if allmin
is None or point
[i
] < allmin
: allmin
= point
[i
]
4015 if allmax
is None or point
[i
] > allmax
: allmax
= point
[i
]
4016 except (TypeError, ValueError):
4018 return allmin
, allmax
4020 def getranges(self
, points
):
4021 xmin
, xmax
= self
.keyrange(points
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
4022 ymin
, ymax
= self
.keyrange(points
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
4023 return {self
.xkey
: (xmin
, xmax
), self
.ykey
: (ymin
, ymax
)}
4025 def drawerrorbar_pt(self
, graph
, topleft
, top
, topright
,
4026 left
, center
, right
,
4027 bottomleft
, bottom
, bottomright
, point
=None):
4028 if left
is not None:
4029 if right
is not None:
4030 left1
= graph
._addpos
(*(left
+(0, -self
.errorsize_pt
)))
4031 left2
= graph
._addpos
(*(left
+(0, self
.errorsize_pt
)))
4032 right1
= graph
._addpos
(*(right
+(0, -self
.errorsize_pt
)))
4033 right2
= graph
._addpos
(*(right
+(0, self
.errorsize_pt
)))
4034 graph
.stroke(path
.path(path
.moveto_pt(*left1
),
4035 graph
._connect
(*(left1
+left2
)),
4036 path
.moveto_pt(*left
),
4037 graph
._connect
(*(left
+right
)),
4038 path
.moveto_pt(*right1
),
4039 graph
._connect
(*(right1
+right2
))),
4041 elif center
is not None:
4042 left1
= graph
._addpos
(*(left
+(0, -self
.errorsize_pt
)))
4043 left2
= graph
._addpos
(*(left
+(0, self
.errorsize_pt
)))
4044 graph
.stroke(path
.path(path
.moveto_pt(*left1
),
4045 graph
._connect
(*(left1
+left2
)),
4046 path
.moveto_pt(*left
),
4047 graph
._connect
(*(left
+center
))),
4050 left1
= graph
._addpos
(*(left
+(0, -self
.errorsize_pt
)))
4051 left2
= graph
._addpos
(*(left
+(0, self
.errorsize_pt
)))
4052 left3
= graph
._addpos
(*(left
+(self
.errorsize_pt
, 0)))
4053 graph
.stroke(path
.path(path
.moveto_pt(*left1
),
4054 graph
._connect
(*(left1
+left2
)),
4055 path
.moveto_pt(*left
),
4056 graph
._connect
(*(left
+left3
))),
4058 if right
is not None and left
is None:
4059 if center
is not None:
4060 right1
= graph
._addpos
(*(right
+(0, -self
.errorsize_pt
)))
4061 right2
= graph
._addpos
(*(right
+(0, self
.errorsize_pt
)))
4062 graph
.stroke(path
.path(path
.moveto_pt(*right1
),
4063 graph
._connect
(*(right1
+right2
)),
4064 path
.moveto_pt(*right
),
4065 graph
._connect
(*(right
+center
))),
4068 right1
= graph
._addpos
(*(right
+(0, -self
.errorsize_pt
)))
4069 right2
= graph
._addpos
(*(right
+(0, self
.errorsize_pt
)))
4070 right3
= graph
._addpos
(*(right
+(-self
.errorsize_pt
, 0)))
4071 graph
.stroke(path
.path(path
.moveto_pt(*right1
),
4072 graph
._connect
(*(right1
+right2
)),
4073 path
.moveto_pt(*right
),
4074 graph
._connect
(*(right
+right3
))),
4077 if bottom
is not None:
4079 bottom1
= graph
._addpos
(*(bottom
+(-self
.errorsize_pt
, 0)))
4080 bottom2
= graph
._addpos
(*(bottom
+(self
.errorsize_pt
, 0)))
4081 top1
= graph
._addpos
(*(top
+(-self
.errorsize_pt
, 0)))
4082 top2
= graph
._addpos
(*(top
+(self
.errorsize_pt
, 0)))
4083 graph
.stroke(path
.path(path
.moveto_pt(*bottom1
),
4084 graph
._connect
(*(bottom1
+bottom2
)),
4085 path
.moveto_pt(*bottom
),
4086 graph
._connect
(*(bottom
+top
)),
4087 path
.moveto_pt(*top1
),
4088 graph
._connect
(*(top1
+top2
))),
4090 elif center
is not None:
4091 bottom1
= graph
._addpos
(*(bottom
+(-self
.errorsize_pt
, 0)))
4092 bottom2
= graph
._addpos
(*(bottom
+(self
.errorsize_pt
, 0)))
4093 graph
.stroke(path
.path(path
.moveto_pt(*bottom1
),
4094 graph
._connect
(*(bottom1
+bottom2
)),
4095 path
.moveto_pt(*bottom
),
4096 graph
._connect
(*(bottom
+center
))),
4099 bottom1
= graph
._addpos
(*(bottom
+(-self
.errorsize_pt
, 0)))
4100 bottom2
= graph
._addpos
(*(bottom
+(self
.errorsize_pt
, 0)))
4101 bottom3
= graph
._addpos
(*(bottom
+(0, self
.errorsize_pt
)))
4102 graph
.stroke(path
.path(path
.moveto_pt(*bottom1
),
4103 graph
._connect
(*(bottom1
+bottom2
)),
4104 path
.moveto_pt(*bottom
),
4105 graph
._connect
(*(bottom
+bottom3
))),
4107 if top
is not None and bottom
is None:
4108 if center
is not None:
4109 top1
= graph
._addpos
(*(top
+(-self
.errorsize_pt
, 0)))
4110 top2
= graph
._addpos
(*(top
+(self
.errorsize_pt
, 0)))
4111 graph
.stroke(path
.path(path
.moveto_pt(*top1
),
4112 graph
._connect
(*(top1
+top2
)),
4113 path
.moveto_pt(*top
),
4114 graph
._connect
(*(top
+center
))),
4117 top1
= graph
._addpos
(*(top
+(-self
.errorsize_pt
, 0)))
4118 top2
= graph
._addpos
(*(top
+(self
.errorsize_pt
, 0)))
4119 top3
= graph
._addpos
(*(top
+(0, -self
.errorsize_pt
)))
4120 graph
.stroke(path
.path(path
.moveto_pt(*top1
),
4121 graph
._connect
(*(top1
+top2
)),
4122 path
.moveto_pt(*top
),
4123 graph
._connect
(*(top
+top3
))),
4125 if bottomleft
is not None:
4126 if topleft
is not None and bottomright
is None:
4127 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
.errorsize_pt
, 0)))
4128 topleft1
= graph
._addpos
(*(topleft
+(self
.errorsize_pt
, 0)))
4129 graph
.stroke(path
.path(path
.moveto_pt(*bottomleft1
),
4130 graph
._connect
(*(bottomleft1
+bottomleft
)),
4131 graph
._connect
(*(bottomleft
+topleft
)),
4132 graph
._connect
(*(topleft
+topleft1
))),
4134 elif bottomright
is not None and topleft
is None:
4135 bottomleft1
= graph
._addpos
(*(bottomleft
+(0, self
.errorsize_pt
)))
4136 bottomright1
= graph
._addpos
(*(bottomright
+(0, self
.errorsize_pt
)))
4137 graph
.stroke(path
.path(path
.moveto_pt(*bottomleft1
),
4138 graph
._connect
(*(bottomleft1
+bottomleft
)),
4139 graph
._connect
(*(bottomleft
+bottomright
)),
4140 graph
._connect
(*(bottomright
+bottomright1
))),
4142 elif bottomright
is None and topleft
is None:
4143 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
.errorsize_pt
, 0)))
4144 bottomleft2
= graph
._addpos
(*(bottomleft
+(0, self
.errorsize_pt
)))
4145 graph
.stroke(path
.path(path
.moveto_pt(*bottomleft1
),
4146 graph
._connect
(*(bottomleft1
+bottomleft
)),
4147 graph
._connect
(*(bottomleft
+bottomleft2
))),
4149 if topright
is not None:
4150 if bottomright
is not None and topleft
is None:
4151 topright1
= graph
._addpos
(*(topright
+(-self
.errorsize_pt
, 0)))
4152 bottomright1
= graph
._addpos
(*(bottomright
+(-self
.errorsize_pt
, 0)))
4153 graph
.stroke(path
.path(path
.moveto_pt(*topright1
),
4154 graph
._connect
(*(topright1
+topright
)),
4155 graph
._connect
(*(topright
+bottomright
)),
4156 graph
._connect
(*(bottomright
+bottomright1
))),
4158 elif topleft
is not None and bottomright
is None:
4159 topright1
= graph
._addpos
(*(topright
+(0, -self
.errorsize_pt
)))
4160 topleft1
= graph
._addpos
(*(topleft
+(0, -self
.errorsize_pt
)))
4161 graph
.stroke(path
.path(path
.moveto_pt(*topright1
),
4162 graph
._connect
(*(topright1
+topright
)),
4163 graph
._connect
(*(topright
+topleft
)),
4164 graph
._connect
(*(topleft
+topleft1
))),
4166 elif topleft
is None and bottomright
is None:
4167 topright1
= graph
._addpos
(*(topright
+(-self
.errorsize_pt
, 0)))
4168 topright2
= graph
._addpos
(*(topright
+(0, -self
.errorsize_pt
)))
4169 graph
.stroke(path
.path(path
.moveto_pt(*topright1
),
4170 graph
._connect
(*(topright1
+topright
)),
4171 graph
._connect
(*(topright
+topright2
))),
4173 if bottomright
is not None and bottomleft
is None and topright
is None:
4174 bottomright1
= graph
._addpos
(*(bottomright
+(-self
.errorsize_pt
, 0)))
4175 bottomright2
= graph
._addpos
(*(bottomright
+(0, self
.errorsize_pt
)))
4176 graph
.stroke(path
.path(path
.moveto_pt(*bottomright1
),
4177 graph
._connect
(*(bottomright1
+bottomright
)),
4178 graph
._connect
(*(bottomright
+bottomright2
))),
4180 if topleft
is not None and bottomleft
is None and topright
is None:
4181 topleft1
= graph
._addpos
(*(topleft
+(self
.errorsize_pt
, 0)))
4182 topleft2
= graph
._addpos
(*(topleft
+(0, -self
.errorsize_pt
)))
4183 graph
.stroke(path
.path(path
.moveto_pt(*topleft1
),
4184 graph
._connect
(*(topleft1
+topleft
)),
4185 graph
._connect
(*(topleft
+topleft2
))),
4187 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
4188 graph
.stroke(path
.path(path
.moveto_pt(*bottomleft
),
4189 graph
._connect
(*(bottomleft
+bottomright
)),
4190 graph
._connect
(*(bottomright
+topright
)),
4191 graph
._connect
(*(topright
+topleft
)),
4195 def drawsymbol_pt(self
, canvas
, x
, y
, point
=None):
4196 canvas
.draw(path
.path(*self
.symbol(self
, x
, y
)), self
.symbolattrs
)
4198 def drawsymbol(self
, canvas
, x
, y
, point
=None):
4199 self
.drawsymbol_pt(canvas
, unit
.topt(x
), unit
.topt(y
), point
)
4201 def key(self
, c
, x
, y
, width
, height
):
4202 if self
._symbolattrs
is not None:
4203 self
.drawsymbol_pt(c
, x
+ 0.5 * width
, y
+ 0.5 * height
)
4204 if self
._lineattrs
is not None:
4205 c
.stroke(path
.line_pt(x
, y
+ 0.5 * height
, x
+ width
, y
+ 0.5 * height
), self
.lineattrs
)
4207 def drawpoints(self
, graph
, points
):
4208 xaxismin
, xaxismax
= self
.xaxis
.getrange()
4209 yaxismin
, yaxismax
= self
.yaxis
.getrange()
4210 self
.size
= unit
.length(_getattr(self
.size_str
), default_type
="v")
4211 self
.size_pt
= unit
.topt(self
.size
)
4212 self
.symbol
= _getattr(self
._symbol
)
4213 self
.symbolattrs
= _getattrs(helper
.ensuresequence(self
._symbolattrs
))
4214 self
.errorbarattrs
= _getattrs(helper
.ensuresequence(self
._errorbarattrs
))
4215 self
.errorsize_pt
= self
.errorscale
* self
.size_pt
4216 self
.errorsize
= self
.errorscale
* self
.size
4217 self
.lineattrs
= _getattrs(helper
.ensuresequence(self
._lineattrs
))
4218 if self
._lineattrs
is not None:
4219 clipcanvas
= graph
.clipcanvas()
4221 haserror
= filter(None, (self
.xmini
, self
.ymini
, self
.xmaxi
, self
.ymaxi
,
4222 self
.dxi
, self
.dyi
, self
.dxmini
, self
.dymini
, self
.dxmaxi
, self
.dymaxi
)) is not None
4224 for point
in points
:
4226 xmin
, x
, xmax
= self
.minmidmax(point
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
4227 ymin
, y
, ymax
= self
.minmidmax(point
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
4228 if x
is not None and x
< xaxismin
: drawsymbol
= 0
4229 elif x
is not None and x
> xaxismax
: drawsymbol
= 0
4230 elif y
is not None and y
< yaxismin
: drawsymbol
= 0
4231 elif y
is not None and y
> yaxismax
: drawsymbol
= 0
4232 # elif haserror: # TODO: correct clipcanvas handling
4233 # if xmin is not None and xmin < xaxismin: drawsymbol = 0
4234 # elif xmax is not None and xmax < xaxismin: drawsymbol = 0
4235 # elif xmax is not None and xmax > xaxismax: drawsymbol = 0
4236 # elif xmin is not None and xmin > xaxismax: drawsymbol = 0
4237 # elif ymin is not None and ymin < yaxismin: drawsymbol = 0
4238 # elif ymax is not None and ymax < yaxismin: drawsymbol = 0
4239 # elif ymax is not None and ymax > yaxismax: drawsymbol = 0
4240 # elif ymin is not None and ymin > yaxismax: drawsymbol = 0
4241 xpos
=ypos
=topleft
=top
=topright
=left
=center
=right
=bottomleft
=bottom
=bottomright
=None
4242 if x
is not None and y
is not None:
4244 center
= xpos
, ypos
= graph
.pos_pt(x
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4245 except (ValueError, OverflowError): # XXX: exceptions???
4249 if xmin
is not None: left
= graph
.pos_pt(xmin
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4250 if xmax
is not None: right
= graph
.pos_pt(xmax
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4252 if ymax
is not None: top
= graph
.pos_pt(x
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4253 if ymin
is not None: bottom
= graph
.pos_pt(x
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4254 if x
is None or y
is None:
4255 if ymax
is not None:
4256 if xmin
is not None: topleft
= graph
.pos_pt(xmin
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4257 if xmax
is not None: topright
= graph
.pos_pt(xmax
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4258 if ymin
is not None:
4259 if xmin
is not None: bottomleft
= graph
.pos_pt(xmin
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4260 if xmax
is not None: bottomright
= graph
.pos_pt(xmax
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4262 if self
._errorbarattrs
is not None and haserror
:
4263 self
.drawerrorbar_pt(graph
, topleft
, top
, topright
,
4264 left
, center
, right
,
4265 bottomleft
, bottom
, bottomright
, point
)
4266 if self
._symbolattrs
is not None and xpos
is not None and ypos
is not None:
4267 self
.drawsymbol_pt(graph
, xpos
, ypos
, point
)
4268 if xpos
is not None and ypos
is not None:
4270 lineels
.append(path
.moveto_pt(xpos
, ypos
))
4273 lineels
.append(path
.lineto_pt(xpos
, ypos
))
4276 self
.path
= path
.path(*lineels
)
4277 if self
._lineattrs
is not None:
4278 clipcanvas
.stroke(self
.path
, self
.lineattrs
)
4281 class changesymbol(changesequence
): pass
4284 class _changesymbolcross(changesymbol
):
4285 defaultsequence
= (symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
)
4288 class _changesymbolplus(changesymbol
):
4289 defaultsequence
= (symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
)
4292 class _changesymbolsquare(changesymbol
):
4293 defaultsequence
= (symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
)
4296 class _changesymboltriangle(changesymbol
):
4297 defaultsequence
= (symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
)
4300 class _changesymbolcircle(changesymbol
):
4301 defaultsequence
= (symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
)
4304 class _changesymboldiamond(changesymbol
):
4305 defaultsequence
= (symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
)
4308 class _changesymbolsquaretwice(changesymbol
):
4309 defaultsequence
= (symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
,
4310 symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
)
4313 class _changesymboltriangletwice(changesymbol
):
4314 defaultsequence
= (symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
,
4315 symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
)
4318 class _changesymbolcircletwice(changesymbol
):
4319 defaultsequence
= (symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
,
4320 symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
)
4323 class _changesymboldiamondtwice(changesymbol
):
4324 defaultsequence
= (symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
,
4325 symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
)
4328 changesymbol
.cross
= _changesymbolcross
4329 changesymbol
.plus
= _changesymbolplus
4330 changesymbol
.square
= _changesymbolsquare
4331 changesymbol
.triangle
= _changesymboltriangle
4332 changesymbol
.circle
= _changesymbolcircle
4333 changesymbol
.diamond
= _changesymboldiamond
4334 changesymbol
.squaretwice
= _changesymbolsquaretwice
4335 changesymbol
.triangletwice
= _changesymboltriangletwice
4336 changesymbol
.circletwice
= _changesymbolcircletwice
4337 changesymbol
.diamondtwice
= _changesymboldiamondtwice
4342 def __init__(self
, lineattrs
=helper
.nodefault
):
4343 if lineattrs
is helper
.nodefault
:
4344 lineattrs
= (changelinestyle(), style
.linejoin
.round)
4345 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=None, lineattrs
=lineattrs
)
4350 def __init__(self
, palette
=color
.palette
.Gray
):
4351 self
.palette
= palette
4352 self
.colorindex
= None
4353 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=(), lineattrs
=None)
4356 raise RuntimeError("style is not iterateable")
4358 def othercolumnkey(self
, key
, index
):
4360 self
.colorindex
= index
4362 symbol
.othercolumnkey(self
, key
, index
)
4364 def drawerrorbar_pt(self
, graph
, topleft
, top
, topright
,
4365 left
, center
, right
,
4366 bottomleft
, bottom
, bottomright
, point
=None):
4367 color
= point
[self
.colorindex
]
4368 if color
is not None:
4369 if color
!= self
.lastcolor
:
4370 self
.rectclipcanvas
.set([self
.palette
.getcolor(color
)])
4371 if bottom
is not None and left
is not None:
4372 bottomleft
= left
[0], bottom
[1]
4373 if bottom
is not None and right
is not None:
4374 bottomright
= right
[0], bottom
[1]
4375 if top
is not None and right
is not None:
4376 topright
= right
[0], top
[1]
4377 if top
is not None and left
is not None:
4378 topleft
= left
[0], top
[1]
4379 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
4380 self
.rectclipcanvas
.fill(path
.path(path
.moveto_pt(*bottomleft
),
4381 graph
._connect
(*(bottomleft
+bottomright
)),
4382 graph
._connect
(*(bottomright
+topright
)),
4383 graph
._connect
(*(topright
+topleft
)),
4386 def drawpoints(self
, graph
, points
):
4387 if self
.colorindex
is None:
4388 raise RuntimeError("column 'color' not set")
4389 self
.lastcolor
= None
4390 self
.rectclipcanvas
= graph
.clipcanvas()
4391 symbol
.drawpoints(self
, graph
, points
)
4393 def key(self
, c
, x
, y
, width
, height
):
4394 raise RuntimeError("style doesn't yet provide a key")
4399 def __init__(self
, textdx
="0", textdy
="0.3 cm", textattrs
=textmodule
.halign
.center
, **args
):
4400 self
.textindex
= None
4401 self
.textdx_str
= textdx
4402 self
.textdy_str
= textdy
4403 self
._textattrs
= textattrs
4404 symbol
.__init
__(self
, **args
)
4406 def iteratedict(self
):
4407 result
= symbol
.iteratedict()
4408 result
["textattrs"] = _iterateattr(self
._textattrs
)
4412 return textsymbol(**self
.iteratedict())
4414 def othercolumnkey(self
, key
, index
):
4416 self
.textindex
= index
4418 symbol
.othercolumnkey(self
, key
, index
)
4420 def drawsymbol_pt(self
, graph
, x
, y
, point
=None):
4421 symbol
.drawsymbol_pt(self
, graph
, x
, y
, point
)
4422 if None not in (x
, y
, point
[self
.textindex
]) and self
._textattrs
is not None:
4423 graph
.text_pt(x
+ self
.textdx_pt
, y
+ self
.textdy_pt
, str(point
[self
.textindex
]), helper
.ensuresequence(self
.textattrs
))
4425 def drawpoints(self
, graph
, points
):
4426 self
.textdx
= unit
.length(_getattr(self
.textdx_str
), default_type
="v")
4427 self
.textdy
= unit
.length(_getattr(self
.textdy_str
), default_type
="v")
4428 self
.textdx_pt
= unit
.topt(self
.textdx
)
4429 self
.textdy_pt
= unit
.topt(self
.textdy
)
4430 if self
._textattrs
is not None:
4431 self
.textattrs
= _getattr(self
._textattrs
)
4432 if self
.textindex
is None:
4433 raise RuntimeError("column 'text' not set")
4434 symbol
.drawpoints(self
, graph
, points
)
4436 def key(self
, c
, x
, y
, width
, height
):
4437 raise RuntimeError("style doesn't yet provide a key")
4440 class arrow(symbol
):
4442 def __init__(self
, linelength
="0.2 cm", arrowattrs
=(), arrowsize
="0.1 cm", arrowdict
={}, epsilon
=1e-10):
4443 self
.linelength_str
= linelength
4444 self
.arrowsize_str
= arrowsize
4445 self
.arrowattrs
= arrowattrs
4446 self
.arrowdict
= arrowdict
4447 self
.epsilon
= epsilon
4448 self
.sizeindex
= self
.angleindex
= None
4449 symbol
.__init
__(self
, symbolattrs
=(), errorbarattrs
=None, lineattrs
=None)
4452 raise RuntimeError("style is not iterateable")
4454 def othercolumnkey(self
, key
, index
):
4456 self
.sizeindex
= index
4457 elif key
== "angle":
4458 self
.angleindex
= index
4460 symbol
.othercolumnkey(self
, key
, index
)
4462 def drawsymbol_pt(self
, graph
, x
, y
, point
=None):
4463 if None not in (x
, y
, point
[self
.angleindex
], point
[self
.sizeindex
], self
.arrowattrs
, self
.arrowdict
):
4464 if point
[self
.sizeindex
] > self
.epsilon
:
4465 dx
, dy
= math
.cos(point
[self
.angleindex
]*math
.pi
/180.0), math
.sin(point
[self
.angleindex
]*math
.pi
/180)
4466 x1
= unit
.t_pt(x
)-0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
4467 y1
= unit
.t_pt(y
)-0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
4468 x2
= unit
.t_pt(x
)+0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
4469 y2
= unit
.t_pt(y
)+0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
4470 graph
.stroke(path
.line(x1
, y1
, x2
, y2
),
4471 [deco
.earrow(size
=self
.arrowsize
*point
[self
.sizeindex
],
4472 **self
.arrowdict
)]+helper
.ensurelist(self
.arrowattrs
))
4474 def drawpoints(self
, graph
, points
):
4475 self
.arrowsize
= unit
.length(_getattr(self
.arrowsize_str
), default_type
="v")
4476 self
.linelength
= unit
.length(_getattr(self
.linelength_str
), default_type
="v")
4477 self
.arrowsize_pt
= unit
.topt(self
.arrowsize
)
4478 self
.linelength_pt
= unit
.topt(self
.linelength
)
4479 if self
.sizeindex
is None:
4480 raise RuntimeError("column 'size' not set")
4481 if self
.angleindex
is None:
4482 raise RuntimeError("column 'angle' not set")
4483 symbol
.drawpoints(self
, graph
, points
)
4485 def key(self
, c
, x
, y
, width
, height
):
4486 raise RuntimeError("style doesn't yet provide a key")
4489 class _bariterator(changeattr
):
4491 def attr(self
, index
):
4492 return index
, self
.counter
4497 def __init__(self
, fromzero
=1, stacked
=0, skipmissing
=1, xbar
=0,
4498 barattrs
=helper
.nodefault
, _usebariterator
=helper
.nodefault
, _previousbar
=None):
4499 self
.fromzero
= fromzero
4500 self
.stacked
= stacked
4501 self
.skipmissing
= skipmissing
4503 if barattrs
is helper
.nodefault
:
4504 self
._barattrs
= [deco
.stroked([color
.gray
.black
]), changecolor
.Rainbow()]
4506 self
._barattrs
= barattrs
4507 if _usebariterator
is helper
.nodefault
:
4508 self
.bariterator
= _bariterator()
4510 self
.bariterator
= _usebariterator
4511 self
.previousbar
= _previousbar
4513 def iteratedict(self
):
4515 result
["barattrs"] = _iterateattrs(self
._barattrs
)
4519 return bar(fromzero
=self
.fromzero
, stacked
=self
.stacked
, xbar
=self
.xbar
,
4520 _usebariterator
=_iterateattr(self
.bariterator
), _previousbar
=self
, **self
.iteratedict())
4522 def setcolumns(self
, graph
, columns
):
4523 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
4525 match
= pattern
.match(key
)
4527 if isindex
is not None: raise ValueError("multiple key specification")
4528 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
4530 iskey
= match
.groups()[0]
4532 return key
, iskey
, isindex
4535 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
4536 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
4537 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
4539 for key
, index
in columns
.items():
4540 key
, xkey
, xi
= checkpattern(key
, index
, XPattern
, xkey
, xi
)
4541 key
, ykey
, yi
= checkpattern(key
, index
, YPattern
, ykey
, yi
)
4543 self
.othercolumnkey(key
, index
)
4544 if None in (xkey
, ykey
): raise ValueError("incomplete axis specification")
4546 self
.nkey
, self
.ni
= ykey
, yi
4547 self
.vkey
, self
.vi
= xkey
, xi
4549 self
.nkey
, self
.ni
= xkey
, xi
4550 self
.vkey
, self
.vi
= ykey
, yi
4551 self
.naxis
, self
.vaxis
= graph
.axes
[self
.nkey
], graph
.axes
[self
.vkey
]
4553 def getranges(self
, points
):
4554 index
, count
= _getattr(self
.bariterator
)
4555 if count
!= 1 and self
.stacked
!= 1:
4556 if self
.stacked
> 1:
4557 index
= divmod(index
, self
.stacked
)[0]
4560 for point
in points
:
4561 if not self
.skipmissing
:
4562 if count
!= 1 and self
.stacked
!= 1:
4563 self
.naxis
.setname(point
[self
.ni
], index
)
4565 self
.naxis
.setname(point
[self
.ni
])
4567 v
= point
[self
.vi
] + 0.0
4568 if vmin
is None or v
< vmin
: vmin
= v
4569 if vmax
is None or v
> vmax
: vmax
= v
4570 except (TypeError, ValueError):
4573 if self
.skipmissing
:
4574 if count
!= 1 and self
.stacked
!= 1:
4575 self
.naxis
.setname(point
[self
.ni
], index
)
4577 self
.naxis
.setname(point
[self
.ni
])
4579 if vmin
> 0: vmin
= 0
4580 if vmax
< 0: vmax
= 0
4581 return {self
.vkey
: (vmin
, vmax
)}
4583 def drawpoints(self
, graph
, points
):
4584 index
, count
= _getattr(self
.bariterator
)
4585 dostacked
= (self
.stacked
!= 0 and
4586 (self
.stacked
== 1 or divmod(index
, self
.stacked
)[1]) and
4587 (self
.stacked
!= 1 or index
))
4588 if self
.stacked
> 1:
4589 index
= divmod(index
, self
.stacked
)[0]
4590 vmin
, vmax
= self
.vaxis
.getrange()
4591 self
.barattrs
= _getattrs(helper
.ensuresequence(self
._barattrs
))
4593 self
.stackedvalue
= {}
4594 for point
in points
:
4599 self
.stackedvalue
[n
] = v
4600 if count
!= 1 and self
.stacked
!= 1:
4601 minid
= (n
, index
, 0)
4602 maxid
= (n
, index
, 1)
4607 x1pos
, y1pos
= graph
.pos_pt(v
, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4608 x2pos
, y2pos
= graph
.pos_pt(v
, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4610 x1pos
, y1pos
= graph
.pos_pt(minid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4611 x2pos
, y2pos
= graph
.pos_pt(maxid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4614 x3pos
, y3pos
= graph
.pos_pt(self
.previousbar
.stackedvalue
[n
], maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4615 x4pos
, y4pos
= graph
.pos_pt(self
.previousbar
.stackedvalue
[n
], minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4617 x3pos
, y3pos
= graph
.pos_pt(maxid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4618 x4pos
, y4pos
= graph
.pos_pt(minid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4622 x3pos
, y3pos
= graph
.pos_pt(0, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4623 x4pos
, y4pos
= graph
.pos_pt(0, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4625 x3pos
, y3pos
= graph
.pos_pt(maxid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4626 x4pos
, y4pos
= graph
.pos_pt(minid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4628 #x3pos, y3pos = graph.tickpoint_pt(maxid, axis=self.naxis)
4629 #x4pos, y4pos = graph.tickpoint_pt(minid, axis=self.naxis)
4630 x3pos
, y3pos
= graph
.axespos
[self
.nkey
].tickpoint_pt(maxid
)
4631 x4pos
, y4pos
= graph
.axespos
[self
.nkey
].tickpoint_pt(minid
)
4632 if self
.barattrs
is not None:
4633 graph
.fill(path
.path(path
.moveto_pt(x1pos
, y1pos
),
4634 graph
._connect
(x1pos
, y1pos
, x2pos
, y2pos
),
4635 graph
._connect
(x2pos
, y2pos
, x3pos
, y3pos
),
4636 graph
._connect
(x3pos
, y3pos
, x4pos
, y4pos
),
4637 graph
._connect
(x4pos
, y4pos
, x1pos
, y1pos
), # no closepath (might not be straight)
4638 path
.closepath()), self
.barattrs
)
4639 except (TypeError, ValueError): pass
4641 def key(self
, c
, x
, y
, width
, height
):
4642 c
.fill(path
.rect_pt(x
, y
, width
, height
), self
.barattrs
)
4647 # def setcolumns(self, graph, columns):
4648 # self.columns = columns
4650 # def getranges(self, points):
4651 # return {"x": (0, 10), "y": (0, 10), "z": (0, 1)}
4653 # def drawpoints(self, graph, points):
4658 ################################################################################
4660 ################################################################################
4665 defaultstyle
= symbol
4667 def __init__(self
, file, title
=helper
.nodefault
, context
={}, **columns
):
4669 if helper
.isstring(file):
4670 self
.data
= datamodule
.datafile(file)
4673 if title
is helper
.nodefault
:
4674 self
.title
= "(unknown)"
4678 for key
, column
in columns
.items():
4680 self
.columns
[key
] = self
.data
.getcolumnno(column
)
4681 except datamodule
.ColumnError
:
4682 self
.columns
[key
] = len(self
.data
.titles
)
4683 self
.data
.addcolumn(column
, context
=context
)
4685 def setstyle(self
, graph
, style
):
4687 self
.style
.setcolumns(graph
, self
.columns
)
4689 def getranges(self
):
4690 return self
.style
.getranges(self
.data
.data
)
4692 def setranges(self
, ranges
):
4695 def draw(self
, graph
):
4696 self
.style
.drawpoints(graph
, self
.data
.data
)
4703 def __init__(self
, expression
, title
=helper
.nodefault
, min=None, max=None, points
=100, parser
=mathtree
.parser(), context
={}):
4704 if title
is helper
.nodefault
:
4705 self
.title
= expression
4710 self
.points
= points
4711 self
.context
= context
4712 self
.result
, expression
= [x
.strip() for x
in expression
.split("=")]
4713 self
.mathtree
= parser
.parse(expression
)
4714 self
.variable
= None
4717 def setstyle(self
, graph
, style
):
4718 for variable
in self
.mathtree
.VarList():
4719 if variable
in graph
.axes
.keys():
4720 if self
.variable
is None:
4721 self
.variable
= variable
4723 raise ValueError("multiple variables found")
4724 if self
.variable
is None:
4725 raise ValueError("no variable found")
4726 self
.xaxis
= graph
.axes
[self
.variable
]
4728 self
.style
.setcolumns(graph
, {self
.variable
: 0, self
.result
: 1})
4730 def getranges(self
):
4732 return self
.style
.getranges(self
.data
)
4733 if None not in (self
.min, self
.max):
4734 return {self
.variable
: (self
.min, self
.max)}
4736 def setranges(self
, ranges
):
4737 if ranges
.has_key(self
.variable
):
4738 min, max = ranges
[self
.variable
]
4739 if self
.min is not None: min = self
.min
4740 if self
.max is not None: max = self
.max
4741 vmin
= self
.xaxis
.convert(min)
4742 vmax
= self
.xaxis
.convert(max)
4744 for i
in range(self
.points
):
4745 self
.context
[self
.variable
] = x
= self
.xaxis
.invert(vmin
+ (vmax
-vmin
)*i
/ (self
.points
-1.0))
4747 y
= self
.mathtree
.Calc(**self
.context
)
4748 except (ArithmeticError, ValueError):
4750 self
.data
.append((x
, y
))
4753 def draw(self
, graph
):
4754 self
.style
.drawpoints(graph
, self
.data
)
4757 class paramfunction
:
4761 def __init__(self
, varname
, min, max, expression
, title
=helper
.nodefault
, points
=100, parser
=mathtree
.parser(), context
={}):
4762 if title
is helper
.nodefault
:
4763 self
.title
= expression
4766 self
.varname
= varname
4769 self
.points
= points
4770 self
.expression
= {}
4772 varlist
, expressionlist
= expression
.split("=")
4773 if mathtree
.__useparser
__ == mathtree
.__newparser
__: # XXX: switch between mathtree-parsers
4774 keys
= varlist
.split(",")
4775 mtrees
= helper
.ensurelist(parser
.parse(expressionlist
))
4776 if len(keys
) != len(mtrees
):
4777 raise ValueError("unpack tuple of wrong size")
4778 for i
in range(len(keys
)):
4779 key
= keys
[i
].strip()
4780 if self
.mathtrees
.has_key(key
):
4781 raise ValueError("multiple assignment in tuple")
4782 self
.mathtrees
[key
] = mtrees
[i
]
4783 if len(keys
) != len(self
.mathtrees
.keys()):
4784 raise ValueError("unpack tuple of wrong size")
4786 parsestr
= mathtree
.ParseStr(expressionlist
)
4787 for key
in varlist
.split(","):
4789 if self
.mathtrees
.has_key(key
):
4790 raise ValueError("multiple assignment in tuple")
4792 self
.mathtrees
[key
] = parser
.ParseMathTree(parsestr
)
4794 except mathtree
.CommaFoundMathTreeParseError
, e
:
4795 self
.mathtrees
[key
] = e
.MathTree
4797 raise ValueError("unpack tuple of wrong size")
4798 if len(varlist
.split(",")) != len(self
.mathtrees
.keys()):
4799 raise ValueError("unpack tuple of wrong size")
4801 for i
in range(self
.points
):
4802 context
[self
.varname
] = self
.min + (self
.max-self
.min)*i
/ (self
.points
-1.0)
4804 for key
, tree
in self
.mathtrees
.items():
4805 line
.append(tree
.Calc(**context
))
4806 self
.data
.append(line
)
4808 def setstyle(self
, graph
, style
):
4811 for key
, index
in zip(self
.mathtrees
.keys(), xrange(sys
.maxint
)):
4812 columns
[key
] = index
4813 self
.style
.setcolumns(graph
, columns
)
4815 def getranges(self
):
4816 return self
.style
.getranges(self
.data
)
4818 def setranges(self
, ranges
):
4821 def draw(self
, graph
):
4822 self
.style
.drawpoints(graph
, self
.data
)