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 - CAUTION: original lists are modified and they share references to
258 # TODO: improve this using bisect?!
259 if list1
is None: return list2
260 if list2
is None: return list1
264 while 1: # we keep on going until we reach an index error
265 while list2
[j
] < list1
[i
]: # insert tick
266 list1
.insert(i
, list2
[j
])
269 if list2
[j
] == list1
[i
]: # merge tick
270 list1
[i
].merge(list2
[j
])
279 def _mergelabels(ticks
, labels
):
280 """helper function to merge labels into ticks
281 - when labels is not None, the label of all ticks with
282 labellevel different from None are set
283 - labels need to be a list of lists of strings,
284 where the first list contain the strings to be
285 used as labels for the ticks with labellevel 0,
286 the second list for labellevel 1, etc.
287 - when the maximum labellevel is 0, just a list of
288 strings might be provided as the labels argument
289 - IndexError is raised, when a list length doesn't match"""
290 if helper
.issequenceofsequences(labels
):
291 for label
, level
in zip(labels
, xrange(sys
.maxint
)):
292 usetext
= helper
.ensuresequence(label
)
295 if tick
.labellevel
== level
:
296 tick
.label
= usetext
[i
]
298 if i
!= len(usetext
):
299 raise IndexError("wrong list length of labels at level %i" % level
)
300 elif labels
is not None:
301 usetext
= helper
.ensuresequence(labels
)
304 if tick
.labellevel
== 0:
305 tick
.label
= usetext
[i
]
307 if i
!= len(usetext
):
308 raise IndexError("wrong list length of labels")
312 """interface definition of a partition scheme
313 partition schemes are used to create a list of ticks"""
315 def defaultpart(self
, min, max, extendmin
, extendmax
):
316 """create a partition
317 - returns an ordered list of ticks for the interval min to max
318 - the interval is given in float numbers, thus an appropriate
319 conversion to rational numbers has to be performed
320 - extendmin and extendmax are booleans (integers)
321 - when extendmin or extendmax is set, the ticks might
322 extend the min-max range towards lower and higher
323 ranges, respectively"""
326 """create another partition which contains less ticks
327 - this method is called several times after a call of defaultpart
328 - returns an ordered list of ticks with less ticks compared to
329 the partition returned by defaultpart and by previous calls
331 - the creation of a partition with strictly *less* ticks
332 is not to be taken serious
333 - the method might return None, when no other appropriate
334 partition can be created"""
338 """create another partition which contains more ticks
339 see lesspart, but increase the number of ticks"""
343 """linear partition scheme
344 ticks and label distances are explicitly provided to the constructor"""
346 __implements__
= _Ipart
348 def __init__(self
, tickdist
=None, labeldist
=None, labels
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
349 """configuration of the partition scheme
350 - tickdist and labeldist should be a list, where the first value
351 is the distance between ticks with ticklevel/labellevel 0,
352 the second list for ticklevel/labellevel 1, etc.;
353 a single entry is allowed without being a list
354 - tickdist and labeldist values are passed to the frac constructor
355 - when labeldist is None and tickdist is not None, the tick entries
356 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
357 - labels are applied to the resulting partition via the
358 mergelabels function (additional information available there)
359 - extendtick allows for the extension of the range given to the
360 defaultpart method to include the next tick with the specified
361 level (None turns off this feature); note, that this feature is
362 also disabled, when an axis prohibits its range extension by
363 the extendmin/extendmax variables given to the defaultpart method
364 - extendlabel is analogous to extendtick, but for labels
365 - epsilon allows for exceeding the axis range by this relative
366 value (relative to the axis range given to the defaultpart method)
367 without creating another tick specified by extendtick/extendlabel"""
368 if tickdist
is None and labeldist
is not None:
369 self
.ticklist
= (frac(helper
.ensuresequence(labeldist
)[0]),)
371 self
.ticklist
= map(frac
, helper
.ensuresequence(tickdist
))
372 if labeldist
is None and tickdist
is not None:
373 self
.labellist
= (frac(helper
.ensuresequence(tickdist
)[0]),)
375 self
.labellist
= map(frac
, helper
.ensuresequence(labeldist
))
377 self
.extendtick
= extendtick
378 self
.extendlabel
= extendlabel
379 self
.epsilon
= epsilon
381 def extendminmax(self
, min, max, frac
, extendmin
, extendmax
):
382 """return new min, max tuple extending the range min, max
383 - frac is the tick distance to be used
384 - extendmin and extendmax are booleans to allow for the extension"""
386 min = float(frac
) * math
.floor(min / float(frac
) + self
.epsilon
)
388 max = float(frac
) * math
.ceil(max / float(frac
) - self
.epsilon
)
391 def getticks(self
, min, max, frac
, ticklevel
=None, labellevel
=None):
392 """return a list of equal spaced ticks
393 - the tick distance is frac, the ticklevel is set to ticklevel and
394 the labellevel is set to labellevel
395 - min, max is the range where ticks should be placed"""
396 imin
= int(math
.ceil(min / float(frac
) - 0.5 * self
.epsilon
))
397 imax
= int(math
.floor(max / float(frac
) + 0.5 * self
.epsilon
))
399 for i
in range(imin
, imax
+ 1):
400 ticks
.append(tick((long(i
) * frac
.enum
, frac
.denom
), ticklevel
=ticklevel
, labellevel
=labellevel
))
403 def defaultpart(self
, min, max, extendmin
, extendmax
):
404 if self
.extendtick
is not None and len(self
.ticklist
) > self
.extendtick
:
405 min, max = self
.extendminmax(min, max, self
.ticklist
[self
.extendtick
], extendmin
, extendmax
)
406 if self
.extendlabel
is not None and len(self
.labellist
) > self
.extendlabel
:
407 min, max = self
.extendminmax(min, max, self
.labellist
[self
.extendlabel
], extendmin
, extendmax
)
410 for i
in range(len(self
.ticklist
)):
411 ticks
= _mergeticklists(ticks
, self
.getticks(min, max, self
.ticklist
[i
], ticklevel
= i
))
412 for i
in range(len(self
.labellist
)):
413 ticks
= _mergeticklists(ticks
, self
.getticks(min, max, self
.labellist
[i
], labellevel
= i
))
415 _mergelabels(ticks
, self
.labels
)
427 """automatic linear partition scheme
428 - possible tick distances are explicitly provided to the constructor
429 - tick distances are adjusted to the axis range by multiplication or division by 10"""
431 __implements__
= _Ipart
433 defaultvariants
= ((frac((1, 1)), frac((1, 2))),
434 (frac((2, 1)), frac((1, 1))),
435 (frac((5, 2)), frac((5, 4))),
436 (frac((5, 1)), frac((5, 2))))
438 def __init__(self
, variants
=defaultvariants
, extendtick
=0, epsilon
=1e-10):
439 """configuration of the partition scheme
440 - variants is a list of tickdist
441 - tickdist should be a list, where the first value
442 is the distance between ticks with ticklevel 0,
443 the second for ticklevel 1, etc.
444 - tickdist values are passed to the frac constructor
445 - labellevel is set to None except for those ticks in the partitions,
446 where ticklevel is zero. There labellevel is also set to zero.
447 - extendtick allows for the extension of the range given to the
448 defaultpart method to include the next tick with the specified
449 level (None turns off this feature); note, that this feature is
450 also disabled, when an axis prohibits its range extension by
451 the extendmin/extendmax variables given to the defaultpart method
452 - epsilon allows for exceeding the axis range by this relative
453 value (relative to the axis range given to the defaultpart method)
454 without creating another tick specified by extendtick"""
455 self
.variants
= variants
456 self
.extendtick
= extendtick
457 self
.epsilon
= epsilon
459 def defaultpart(self
, min, max, extendmin
, extendmax
):
460 logmm
= math
.log(max - min) / math
.log(10)
461 if logmm
< 0: # correction for rounding towards zero of the int routine
462 base
= frac((10L, 1), int(logmm
- 1))
464 base
= frac((10L, 1), int(logmm
))
465 ticks
= map(frac
, self
.variants
[0])
466 useticks
= [tick
* base
for tick
in ticks
]
467 self
.lesstickindex
= self
.moretickindex
= 0
468 self
.lessbase
= frac((base
.enum
, base
.denom
))
469 self
.morebase
= frac((base
.enum
, base
.denom
))
470 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
471 part
= linpart(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
472 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
475 if self
.lesstickindex
< len(self
.variants
) - 1:
476 self
.lesstickindex
+= 1
478 self
.lesstickindex
= 0
479 self
.lessbase
.enum
*= 10
480 ticks
= map(frac
, self
.variants
[self
.lesstickindex
])
481 useticks
= [tick
* self
.lessbase
for tick
in ticks
]
482 part
= linpart(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
483 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
486 if self
.moretickindex
:
487 self
.moretickindex
-= 1
489 self
.moretickindex
= len(self
.variants
) - 1
490 self
.morebase
.denom
*= 10
491 ticks
= map(frac
, self
.variants
[self
.moretickindex
])
492 useticks
= [tick
* self
.morebase
for tick
in ticks
]
493 part
= linpart(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
494 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
498 """storage class for the definition of logarithmic axes partitions
499 instances of this class define tick positions suitable for
500 logarithmic axes by the following instance variables:
501 - exp: integer, which defines multiplicator (usually 10)
502 - pres: list of tick positions (rational numbers, e.g. instances of frac)
503 possible positions are these tick positions and arbitrary divisions
504 and multiplications by the exp value"""
506 def __init__(self
, pres
, exp
):
507 "create a preexp instance and store its pres and exp information"
508 self
.pres
= helper
.ensuresequence(pres
)
512 class logpart(linpart
):
513 """logarithmic partition scheme
514 ticks and label positions are explicitly provided to the constructor"""
516 __implements__
= _Ipart
518 pre1exp5
= preexp(frac((1, 1)), 100000)
519 pre1exp4
= preexp(frac((1, 1)), 10000)
520 pre1exp3
= preexp(frac((1, 1)), 1000)
521 pre1exp2
= preexp(frac((1, 1)), 100)
522 pre1exp
= preexp(frac((1, 1)), 10)
523 pre125exp
= preexp((frac((1, 1)), frac((2, 1)), frac((5, 1))), 10)
524 pre1to9exp
= preexp(map(lambda x
: frac((x
, 1)), range(1, 10)), 10)
525 # ^- we always include 1 in order to get extendto(tick|label)level to work as expected
527 def __init__(self
, tickpos
=None, labelpos
=None, labels
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
528 """configuration of the partition scheme
529 - tickpos and labelpos should be a list, where the first entry
530 is a preexp instance describing ticks with ticklevel/labellevel 0,
531 the second is a preexp instance for ticklevel/labellevel 1, etc.;
532 a single entry is allowed without being a list
533 - when labelpos is None and tickpos is not None, the tick entries
534 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
535 - labels are applied to the resulting partition via the
536 mergetexts function (additional information available there)
537 - extendtick allows for the extension of the range given to the
538 defaultpart method to include the next tick with the specified
539 level (None turns off this feature); note, that this feature is
540 also disabled, when an axis prohibits its range extension by
541 the extendmin/extendmax variables given to the defaultpart method
542 - extendlabel is analogous to extendtick, but for labels
543 - epsilon allows for exceeding the axis range by this relative
544 logarithm value (relative to the logarithm axis range given
545 to the defaultpart method) without creating another tick
546 specified by extendtick/extendlabel"""
547 if tickpos
is None and labels
is not None:
548 self
.ticklist
= (helper
.ensuresequence(labelpos
)[0],)
550 self
.ticklist
= helper
.ensuresequence(tickpos
)
552 if labelpos
is None and tickpos
is not None:
553 self
.labellist
= (helper
.ensuresequence(tickpos
)[0],)
555 self
.labellist
= helper
.ensuresequence(labelpos
)
557 self
.extendtick
= extendtick
558 self
.extendlabel
= extendlabel
559 self
.epsilon
= epsilon
561 def extendminmax(self
, min, max, preexp
, extendmin
, extendmax
):
562 """return new min, max tuple extending the range min, max
563 preexp describes the allowed tick positions
564 extendmin and extendmax are booleans to allow for the extension"""
567 for i
in xrange(len(preexp
.pres
)):
568 imin
= int(math
.floor(math
.log(min / float(preexp
.pres
[i
])) /
569 math
.log(preexp
.exp
) + self
.epsilon
)) + 1
570 imax
= int(math
.ceil(math
.log(max / float(preexp
.pres
[i
])) /
571 math
.log(preexp
.exp
) - self
.epsilon
)) - 1
572 if minpower
is None or imin
< minpower
:
573 minpower
, minindex
= imin
, i
574 if maxpower
is None or imax
>= maxpower
:
575 maxpower
, maxindex
= imax
, i
577 minfrac
= preexp
.pres
[minindex
- 1]
579 minfrac
= preexp
.pres
[-1]
581 if maxindex
!= len(preexp
.pres
) - 1:
582 maxfrac
= preexp
.pres
[maxindex
+ 1]
584 maxfrac
= preexp
.pres
[0]
587 min = float(minfrac
) * float(preexp
.exp
) ** minpower
589 max = float(maxfrac
) * float(preexp
.exp
) ** maxpower
592 def getticks(self
, min, max, preexp
, ticklevel
=None, labellevel
=None):
593 """return a list of ticks
594 - preexp describes the allowed tick positions
595 - the ticklevel of the ticks is set to ticklevel and
596 the labellevel is set to labellevel
597 - min, max is the range where ticks should be placed"""
601 for f
in preexp
.pres
:
603 imin
= int(math
.ceil(math
.log(min / float(f
)) /
604 math
.log(preexp
.exp
) - 0.5 * self
.epsilon
))
605 imax
= int(math
.floor(math
.log(max / float(f
)) /
606 math
.log(preexp
.exp
) + 0.5 * self
.epsilon
))
607 for i
in range(imin
, imax
+ 1):
608 pos
= f
* frac((preexp
.exp
, 1), i
)
609 fracticks
.append(tick((pos
.enum
, pos
.denom
), ticklevel
= ticklevel
, labellevel
= labellevel
))
610 ticks
= _mergeticklists(ticks
, fracticks
)
614 class autologpart(logpart
):
615 """automatic logarithmic partition scheme
616 possible tick positions are explicitly provided to the constructor"""
618 __implements__
= _Ipart
620 defaultvariants
= (((logpart
.pre1exp
, # ticks
621 logpart
.pre1to9exp
), # subticks
622 (logpart
.pre1exp
, # labels
623 logpart
.pre125exp
)), # sublevels
625 ((logpart
.pre1exp
, # ticks
626 logpart
.pre1to9exp
), # subticks
627 None), # labels like ticks
629 ((logpart
.pre1exp2
, # ticks
630 logpart
.pre1exp
), # subticks
631 None), # labels like ticks
633 ((logpart
.pre1exp3
, # ticks
634 logpart
.pre1exp
), # subticks
635 None), # labels like ticks
637 ((logpart
.pre1exp4
, # ticks
638 logpart
.pre1exp
), # subticks
639 None), # labels like ticks
641 ((logpart
.pre1exp5
, # ticks
642 logpart
.pre1exp
), # subticks
643 None)) # labels like ticks
645 def __init__(self
, variants
=defaultvariants
, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
646 """configuration of the partition scheme
647 - variants should be a list of pairs of lists of preexp
649 - within each pair the first list contains preexp, where
650 the first preexp instance describes ticks positions with
651 ticklevel 0, the second preexp for ticklevel 1, etc.
652 - the second list within each pair describes the same as
653 before, but for labels
654 - within each pair: when the second entry (for the labels) is None
655 and the first entry (for the ticks) ticks is not None, the tick
656 entries for ticklevel 0 are used for labels and vice versa
658 - extendtick allows for the extension of the range given to the
659 defaultpart method to include the next tick with the specified
660 level (None turns off this feature); note, that this feature is
661 also disabled, when an axis prohibits its range extension by
662 the extendmin/extendmax variables given to the defaultpart method
663 - extendlabel is analogous to extendtick, but for labels
664 - epsilon allows for exceeding the axis range by this relative
665 logarithm value (relative to the logarithm axis range given
666 to the defaultpart method) without creating another tick
667 specified by extendtick/extendlabel"""
668 self
.variants
= variants
669 if len(variants
) > 2:
670 self
.variantsindex
= divmod(len(variants
), 2)[0]
672 self
.variantsindex
= 0
673 self
.extendtick
= extendtick
674 self
.extendlabel
= extendlabel
675 self
.epsilon
= epsilon
677 def defaultpart(self
, min, max, extendmin
, extendmax
):
678 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
679 self
.morevariantsindex
= self
.variantsindex
680 self
.lessvariantsindex
= self
.variantsindex
681 part
= logpart(tickpos
=self
.variants
[self
.variantsindex
][0], labelpos
=self
.variants
[self
.variantsindex
][1],
682 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
683 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
686 self
.lessvariantsindex
+= 1
687 if self
.lessvariantsindex
< len(self
.variants
):
688 part
= logpart(tickpos
=self
.variants
[self
.lessvariantsindex
][0], labelpos
=self
.variants
[self
.lessvariantsindex
][1],
689 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
690 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
693 self
.morevariantsindex
-= 1
694 if self
.morevariantsindex
>= 0:
695 part
= logpart(tickpos
=self
.variants
[self
.morevariantsindex
][0], labelpos
=self
.variants
[self
.morevariantsindex
][1],
696 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
697 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
701 ################################################################################
703 # conseptional remarks:
704 # - raters are used to calculate a rating for a realization of something
705 # - here, a rating means a positive floating point value
706 # - ratings are used to order those realizations by their suitability (lower
707 # ratings are better)
708 # - a rating of None means not suitable at all (those realizations should be
710 ################################################################################
715 - a cube rater has an optimal value, where the rate becomes zero
716 - for a left (below the optimum) and a right value (above the optimum),
717 the rating is value is set to 1 (modified by an overall weight factor
719 - the analytic form of the rating is cubic for both, the left and
720 the right side of the rater, independently"""
722 # __implements__ = sole implementation
724 def __init__(self
, opt
, left
=None, right
=None, weight
=1):
725 """initializes the rater
726 - by default, left is set to zero, right is set to 3*opt
727 - left should be smaller than opt, right should be bigger than opt
728 - weight should be positive and is a factor multiplicated to the rates"""
738 def rate(self
, value
, density
):
739 """returns a rating for a value
740 - the density lineary rescales the rater (the optimum etc.),
741 e.g. a value bigger than one increases the optimum (when it is
742 positive) and a value lower than one decreases the optimum (when
743 it is positive); the density itself should be positive"""
744 opt
= self
.opt
* density
746 other
= self
.left
* density
748 other
= self
.right
* density
751 factor
= (value
- opt
) / float(other
- opt
)
752 return self
.weight
* (factor
** 3)
756 # TODO: update docstring
757 """a distance rater (rates a list of distances)
758 - the distance rater rates a list of distances by rating each independently
759 and returning the average rate
760 - there is an optimal value, where the rate becomes zero
761 - the analytic form is linary for values above the optimal value
762 (twice the optimal value has the rating one, three times the optimal
763 value has the rating two, etc.)
764 - the analytic form is reciprocal subtracting one for values below the
765 optimal value (halve the optimal value has the rating one, one third of
766 the optimal value has the rating two, etc.)"""
768 # __implements__ = sole implementation
770 def __init__(self
, opt
, weight
=0.1):
771 """inititializes the rater
772 - opt is the optimal length (a visual PyX length)
773 - weight should be positive and is a factor multiplicated to the rates"""
777 def rate(self
, distances
, density
):
779 - the distances are a list of positive floats in PostScript points
780 - the density lineary rescales the rater (the optimum etc.),
781 e.g. a value bigger than one increases the optimum (when it is
782 positive) and a value lower than one decreases the optimum (when
783 it is positive); the density itself should be positive"""
785 opt
= unit
.topt(unit
.length(self
.opt_str
, default_type
="v")) / density
787 for distance
in distances
:
789 rate
+= self
.weight
* (opt
/ distance
- 1)
791 rate
+= self
.weight
* (distance
/ opt
- 1)
792 return rate
/ float(len(distances
))
797 - the rating of axes is splited into two separate parts:
798 - rating of the ticks in terms of the number of ticks, subticks,
800 - rating of the label distances
801 - in the end, a rate for ticks is the sum of these rates
802 - it is useful to first just rate the number of ticks etc.
803 and selecting those partitions, where this fits well -> as soon
804 as an complete rate (the sum of both parts from the list above)
805 of a first ticks is below a rate of just the number of ticks,
806 subticks labels etc. of other ticks, those other ticks will never
807 be better than the first one -> we gain speed by minimizing the
808 number of ticks, where label distances have to be taken into account)
809 - both parts of the rating are shifted into instances of raters
810 defined above --- right now, there is not yet a strict interface
811 for this delegation (should be done as soon as it is needed)"""
813 # __implements__ = sole implementation
815 linticks
= (cuberater(4), cuberater(10, weight
=0.5), )
816 linlabels
= (cuberater(4), )
817 logticks
= (cuberater(5, right
=20), cuberater(20, right
=100, weight
=0.5), )
818 loglabels
= (cuberater(5, right
=20), cuberater(5, left
=-20, right
=20, weight
=0.5), )
819 stdrange
= cuberater(1, weight
=2)
820 stddistance
= distancerater("1 cm")
822 def __init__(self
, ticks
=linticks
, labels
=linlabels
, range=stdrange
, distance
=stddistance
):
823 """initializes the axis rater
824 - ticks and labels are lists of instances of a value rater
825 - the first entry in ticks rate the number of ticks, the
826 second the number of subticks, etc.; when there are no
827 ticks of a level or there is not rater for a level, the
828 level is just ignored
829 - labels is analogous, but for labels
830 - within the rating, all ticks with a higher level are
831 considered as ticks for a given level
832 - range is a value rater instance, which rates the covering
833 of an axis range by the ticks (as a relative value of the
834 tick range vs. the axis range), ticks might cover less or
835 more than the axis range (for the standard automatic axis
836 partition schemes an extention of the axis range is normal
837 and should get some penalty)
838 - distance is an distance rater instance"""
842 self
.distance
= distance
844 def rateticks(self
, axis
, ticks
, density
):
845 """rates ticks by the number of ticks, subticks, labels etc.
846 - takes into account the number of ticks, subticks, labels
847 etc. and the coverage of the axis range by the ticks
848 - when there are no ticks of a level or there was not rater
849 given in the constructor for a level, the level is just
851 - the method returns the sum of the rating results divided
852 by the sum of the weights of the raters
853 - within the rating, all ticks with a higher level are
854 considered as ticks for a given level"""
855 maxticklevel
= maxlabellevel
= 0
857 if tick
.ticklevel
>= maxticklevel
:
858 maxticklevel
= tick
.ticklevel
+ 1
859 if tick
.labellevel
>= maxlabellevel
:
860 maxlabellevel
= tick
.labellevel
+ 1
861 numticks
= [0]*maxticklevel
862 numlabels
= [0]*maxlabellevel
864 if tick
.ticklevel
is not None:
865 for level
in range(tick
.ticklevel
, maxticklevel
):
867 if tick
.labellevel
is not None:
868 for level
in range(tick
.labellevel
, maxlabellevel
):
869 numlabels
[level
] += 1
872 for numtick
, rater
in zip(numticks
, self
.ticks
):
873 rate
+= rater
.rate(numtick
, density
)
874 weight
+= rater
.weight
875 for numlabel
, rater
in zip(numlabels
, self
.labels
):
876 rate
+= rater
.rate(numlabel
, density
)
877 weight
+= rater
.weight
880 def raterange(self
, tickrange
, datarange
):
881 """rate the range covered by the ticks compared to the range
883 - tickrange and datarange are the ranges covered by the ticks
884 and the data in graph coordinates
885 - usually, the datarange is 1 (ticks are calculated for a
887 - the ticks might cover less or more than the data range (for
888 the standard automatic axis partition schemes an extention
889 of the axis range is normal and should get some penalty)"""
890 return self
.range.rate(tickrange
, datarange
)
892 def ratelayout(self
, axiscanvas
, density
):
893 """rate distances of the labels in an axis canvas
894 - the distances should be collected as box distances of
896 - the axiscanvas provides a labels attribute for easy
897 access to the labels whose distances have to be taken
899 - the density is used within the distancerate instance"""
900 if len(axiscanvas
.labels
) > 1:
902 distances
= [axiscanvas
.labels
[i
]._boxdistance
(axiscanvas
.labels
[i
+1]) for i
in range(len(axiscanvas
.labels
) - 1)]
903 except box
.BoxCrossError
:
905 return self
.distance
.rate(distances
, density
)
910 ################################################################################
912 # texter automatically create labels for tick instances
913 ################################################################################
918 def labels(self
, ticks
):
919 """fill the label attribute of ticks
920 - ticks is a list of instances of tick
921 - for each element of ticks the value of the attribute label is set to
922 a string appropriate to the attributes enum and denom of that tick
924 - label attributes of the tick instances are just kept, whenever they
925 are not equal to None
926 - the method might extend the labelattrs attribute of the ticks"""
929 class rationaltexter
:
930 "a texter creating rational labels (e.g. 'a/b' or even 'a \over b')"
931 # XXX: we use divmod here to be more expicit
933 __implements__
= _Itexter
935 def __init__(self
, prefix
="", infix
="", suffix
="",
936 enumprefix
="", enuminfix
="", enumsuffix
="",
937 denomprefix
="", denominfix
="", denomsuffix
="",
938 plus
="", minus
="-", minuspos
=0, over
=r
"{{%s}\over{%s}}",
939 equaldenom
=0, skip1
=1, skipenum0
=1, skipenum1
=1, skipdenom1
=1,
940 labelattrs
=textmodule
.mathmode
):
941 r
"""initializes the instance
942 - prefix, infix, and suffix (strings) are added at the begin,
943 immediately after the minus, and at the end of the label,
945 - prefixenum, infixenum, and suffixenum (strings) are added
946 to the labels enumerator correspondingly
947 - prefixdenom, infixdenom, and suffixdenom (strings) are added
948 to the labels denominator correspondingly
949 - plus or minus (string) is inserted for non-negative or negative numbers
950 - minuspos is an integer, which determines the position, where the
951 plus or minus sign has to be placed; the following values are allowed:
952 1 - writes the plus or minus in front of the enumerator
953 0 - writes the plus or minus in front of the hole fraction
954 -1 - writes the plus or minus in front of the denominator
955 - over (string) is taken as a format string generating the
956 fraction bar; it has to contain exactly two string insert
957 operators "%s" -- the first for the enumerator and the second
958 for the denominator; by far the most common examples are
959 r"{{%s}\over{%s}}" and "{{%s}/{%s}}"
960 - usually the enumerator and denominator are canceled; however,
961 when equaldenom is set, the least common multiple of all
963 - skip1 (boolean) just prints the prefix, the plus or minus,
964 the infix and the suffix, when the value is plus or minus one
965 and at least one of prefix, infix and the suffix is present
966 - skipenum0 (boolean) just prints a zero instead of
967 the hole fraction, when the enumerator is zero;
968 no prefixes, infixes, and suffixes are taken into account
969 - skipenum1 (boolean) just prints the enumprefix, the plus or minus,
970 the enuminfix and the enumsuffix, when the enum value is plus or minus one
971 and at least one of enumprefix, enuminfix and the enumsuffix is present
972 - skipdenom1 (boolean) just prints the enumerator instead of
973 the hole fraction, when the denominator is one and none of the parameters
974 denomprefix, denominfix and denomsuffix are set and minuspos is not -1 or the
976 - labelattrs is a list of attributes for a texrunners text method;
977 a single is allowed without being a list; None is considered as
982 self
.enumprefix
= enumprefix
983 self
.enuminfix
= enuminfix
984 self
.enumsuffix
= enumsuffix
985 self
.denomprefix
= denomprefix
986 self
.denominfix
= denominfix
987 self
.denomsuffix
= denomsuffix
990 self
.minuspos
= minuspos
992 self
.equaldenom
= equaldenom
994 self
.skipenum0
= skipenum0
995 self
.skipenum1
= skipenum1
996 self
.skipdenom1
= skipdenom1
997 self
.labelattrs
= helper
.ensurelist(labelattrs
)
1000 """returns the greates common divisor of all elements in n
1001 - the elements of n must be non-negative integers
1002 - return None if the number of elements is zero
1003 - the greates common divisor is not affected when some
1004 of the elements are zero, but it becomes zero when
1005 all elements are zero"""
1011 i
, (dummy
, j
) = j
, divmod(i
, j
)
1016 res
= self
.gcd(res
, i
)
1020 """returns the least common multiple of all elements in n
1021 - the elements of n must be non-negative integers
1022 - return None if the number of elements is zero
1023 - the least common multiple is zero when some of the
1024 elements are zero"""
1028 res
= divmod(res
* i
, self
.gcd(res
, i
))[0]
1031 def labels(self
, ticks
):
1034 if tick
.label
is None and tick
.labellevel
is not None:
1035 labeledticks
.append(tick
)
1036 tick
.temp_fracenum
= tick
.enum
1037 tick
.temp_fracdenom
= tick
.denom
1038 tick
.temp_fracminus
= 1
1039 if tick
.temp_fracenum
< 0:
1040 tick
.temp_fracminus
= -tick
.temp_fracminus
1041 tick
.temp_fracenum
= -tick
.temp_fracenum
1042 if tick
.temp_fracdenom
< 0:
1043 tick
.temp_fracminus
= -tick
.temp_fracminus
1044 tick
.temp_fracdenom
= -tick
.temp_fracdenom
1045 gcd
= self
.gcd(tick
.temp_fracenum
, tick
.temp_fracdenom
)
1046 (tick
.temp_fracenum
, dummy1
), (tick
.temp_fracdenom
, dummy2
) = divmod(tick
.temp_fracenum
, gcd
), divmod(tick
.temp_fracdenom
, gcd
)
1048 equaldenom
= self
.lcm(*[tick
.temp_fracdenom
for tick
in ticks
if tick
.label
is None])
1049 if equaldenom
is not None:
1050 for tick
in labeledticks
:
1051 factor
, dummy
= divmod(equaldenom
, tick
.temp_fracdenom
)
1052 tick
.temp_fracenum
, tick
.temp_fracdenom
= factor
* tick
.temp_fracenum
, factor
* tick
.temp_fracdenom
1053 for tick
in labeledticks
:
1054 fracminus
= fracenumminus
= fracdenomminus
= ""
1055 if tick
.temp_fracminus
== -1:
1056 plusminus
= self
.minus
1058 plusminus
= self
.plus
1059 if self
.minuspos
== 0:
1060 fracminus
= plusminus
1061 elif self
.minuspos
== 1:
1062 fracenumminus
= plusminus
1063 elif self
.minuspos
== -1:
1064 fracdenomminus
= plusminus
1066 raise RuntimeError("invalid minuspos")
1067 if self
.skipenum0
and tick
.temp_fracenum
== 0:
1069 elif (self
.skip1
and self
.skipdenom1
and tick
.temp_fracenum
== 1 and tick
.temp_fracdenom
== 1 and
1070 (len(self
.prefix
) or len(self
.infix
) or len(self
.suffix
)) and
1071 not len(fracenumminus
) and not len(self
.enumprefix
) and not len(self
.enuminfix
) and not len(self
.enumsuffix
) and
1072 not len(fracdenomminus
) and not len(self
.denomprefix
) and not len(self
.denominfix
) and not len(self
.denomsuffix
)):
1073 tick
.label
= "%s%s%s%s" % (self
.prefix
, fracminus
, self
.infix
, self
.suffix
)
1075 if self
.skipenum1
and tick
.temp_fracenum
== 1 and (len(self
.enumprefix
) or len(self
.enuminfix
) or len(self
.enumsuffix
)):
1076 tick
.temp_fracenum
= "%s%s%s%s" % (self
.enumprefix
, fracenumminus
, self
.enuminfix
, self
.enumsuffix
)
1078 tick
.temp_fracenum
= "%s%s%s%i%s" % (self
.enumprefix
, fracenumminus
, self
.enuminfix
, tick
.temp_fracenum
, self
.enumsuffix
)
1079 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
):
1080 frac
= tick
.temp_fracenum
1082 tick
.temp_fracdenom
= "%s%s%s%i%s" % (self
.denomprefix
, fracdenomminus
, self
.denominfix
, tick
.temp_fracdenom
, self
.denomsuffix
)
1083 frac
= self
.over
% (tick
.temp_fracenum
, tick
.temp_fracdenom
)
1084 tick
.label
= "%s%s%s%s%s" % (self
.prefix
, fracminus
, self
.infix
, frac
, self
.suffix
)
1085 tick
.labelattrs
.extend(self
.labelattrs
)
1087 # del tick.temp_fracenum # we've inserted those temporary variables ... and do not care any longer about them
1088 # del tick.temp_fracdenom
1089 # del tick.temp_fracminus
1093 class decimaltexter
:
1094 "a texter creating decimal labels (e.g. '1.234' or even '0.\overline{3}')"
1096 __implements__
= _Itexter
1098 def __init__(self
, prefix
="", infix
="", suffix
="", equalprecision
=0,
1099 decimalsep
=".", thousandsep
="", thousandthpartsep
="",
1100 plus
="", minus
="-", period
=r
"\overline{%s}", labelattrs
=textmodule
.mathmode
):
1101 r
"""initializes the instance
1102 - prefix, infix, and suffix (strings) are added at the begin,
1103 immediately after the minus, and at the end of the label,
1105 - decimalsep, thousandsep, and thousandthpartsep (strings)
1106 are used as separators
1107 - plus or minus (string) is inserted for non-negative or negative numbers
1108 - period (string) is taken as a format string generating a period;
1109 it has to contain exactly one string insert operators "%s" for the
1110 period; usually it should be r"\overline{%s}"
1111 - labelattrs is a list of attributes for a texrunners text method;
1112 a single is allowed without being a list; None is considered as
1114 self
.prefix
= prefix
1116 self
.suffix
= suffix
1117 self
.equalprecision
= equalprecision
1118 self
.decimalsep
= decimalsep
1119 self
.thousandsep
= thousandsep
1120 self
.thousandthpartsep
= thousandthpartsep
1123 self
.period
= period
1124 self
.labelattrs
= helper
.ensurelist(labelattrs
)
1126 def labels(self
, ticks
):
1130 if tick
.label
is None and tick
.labellevel
is not None:
1131 labeledticks
.append(tick
)
1132 m
, n
= tick
.enum
, tick
.denom
1135 whole
, reminder
= divmod(m
, n
)
1137 if len(self
.thousandsep
):
1141 tick
.label
+= whole
[i
]
1142 if not ((l
-i
-1) % 3) and l
> i
+1:
1143 tick
.label
+= self
.thousandsep
1147 tick
.label
+= self
.decimalsep
1149 tick
.temp_decprecision
= 0
1151 tick
.temp_decprecision
+= 1
1152 if reminder
in oldreminders
:
1153 tick
.temp_decprecision
= None
1154 periodstart
= len(tick
.label
) - (len(oldreminders
) - oldreminders
.index(reminder
))
1155 tick
.label
= tick
.label
[:periodstart
] + self
.period
% tick
.label
[periodstart
:]
1157 oldreminders
+= [reminder
]
1159 whole
, reminder
= divmod(reminder
, n
)
1160 if not ((tick
.temp_decprecision
- 1) % 3) and tick
.temp_decprecision
> 1:
1161 tick
.label
+= self
.thousandthpartsep
1162 tick
.label
+= str(whole
)
1163 if maxdecprecision
< tick
.temp_decprecision
:
1164 maxdecprecision
= tick
.temp_decprecision
1165 if self
.equalprecision
:
1166 for tick
in labeledticks
:
1167 if tick
.temp_decprecision
is not None:
1168 if tick
.temp_decprecision
== 0 and maxdecprecision
> 0:
1169 tick
.label
+= self
.decimalsep
1170 for i
in range(tick
.temp_decprecision
, maxdecprecision
):
1171 if not ((i
- 1) % 3) and i
> 1:
1172 tick
.label
+= self
.thousandthpartsep
1174 for tick
in labeledticks
:
1175 if tick
.enum
* tick
.denom
< 0:
1176 plusminus
= self
.minus
1178 plusminus
= self
.plus
1179 tick
.label
= "%s%s%s%s%s" % (self
.prefix
, plusminus
, self
.infix
, tick
.label
, self
.suffix
)
1180 tick
.labelattrs
.extend(self
.labelattrs
)
1182 # del tick.temp_decprecision # we've inserted this temporary variable ... and do not care any longer about it
1185 class exponentialtexter
:
1186 "a texter creating labels with exponentials (e.g. '2\cdot10^5')"
1188 __implements__
= _Itexter
1190 def __init__(self
, plus
="", minus
="-",
1191 mantissaexp
=r
"{{%s}\cdot10^{%s}}",
1192 nomantissaexp
=r
"{10^{%s}}",
1193 minusnomantissaexp
=r
"{-10^{%s}}",
1194 mantissamin
=frac((1, 1)), mantissamax
=frac((10, 1)),
1195 skipmantissa1
=0, skipallmantissa1
=1,
1196 mantissatexter
=decimaltexter()):
1197 r
"""initializes the instance
1198 - plus or minus (string) is inserted for non-negative or negative exponents
1199 - mantissaexp (string) is taken as a format string generating the exponent;
1200 it has to contain exactly two string insert operators "%s" --
1201 the first for the mantissa and the second for the exponent;
1202 examples are r"{{%s}\cdot10^{%s}}" and r"{{%s}{\rm e}^{%s}}"
1203 - nomantissaexp (string) is taken as a format string generating the exponent
1204 when the mantissa is one and should be skipped; it has to contain
1205 exactly one string insert operators "%s" for the exponent;
1206 an examples is r"{10^{%s}}"
1207 - minusnomantissaexp (string) is taken as a format string generating the exponent
1208 when the mantissa is minus one and should be skipped; it has to contain
1209 exactly one string insert operators "%s" for the exponent; might be set to None
1210 to disallow skipping of any mantissa minus one
1211 an examples is r"{-10^{%s}}"
1212 - mantissamin and mantissamax are the minimum and maximum of the mantissa;
1213 they are frac instances greater than zero and mantissamin < mantissamax;
1214 the sign of the tick is ignored here
1215 - skipmantissa1 (boolean) turns on skipping of any mantissa equals one
1216 (and minus when minusnomantissaexp is set)
1217 - skipallmantissa1 (boolean) as above, but all mantissas must be 1
1218 - mantissatexter is the texter for the mantissa"""
1221 self
.mantissaexp
= mantissaexp
1222 self
.nomantissaexp
= nomantissaexp
1223 self
.minusnomantissaexp
= minusnomantissaexp
1224 self
.mantissamin
= mantissamin
1225 self
.mantissamax
= mantissamax
1226 self
.mantissamindivmax
= self
.mantissamin
/ self
.mantissamax
1227 self
.mantissamaxdivmin
= self
.mantissamax
/ self
.mantissamin
1228 self
.skipmantissa1
= skipmantissa1
1229 self
.skipallmantissa1
= skipallmantissa1
1230 self
.mantissatexter
= mantissatexter
1232 def labels(self
, ticks
):
1235 if tick
.label
is None and tick
.labellevel
is not None:
1236 tick
.temp_orgenum
, tick
.temp_orgdenom
= tick
.enum
, tick
.denom
1237 labeledticks
.append(tick
)
1240 while abs(tick
) >= self
.mantissamax
:
1242 x
= tick
* self
.mantissamindivmax
1243 tick
.enum
, tick
.denom
= x
.enum
, x
.denom
1244 while abs(tick
) < self
.mantissamin
:
1246 x
= tick
* self
.mantissamaxdivmin
1247 tick
.enum
, tick
.denom
= x
.enum
, x
.denom
1248 if tick
.temp_exp
< 0:
1249 tick
.temp_exp
= "%s%i" % (self
.minus
, -tick
.temp_exp
)
1251 tick
.temp_exp
= "%s%i" % (self
.plus
, tick
.temp_exp
)
1252 self
.mantissatexter
.labels(labeledticks
)
1253 if self
.minusnomantissaexp
is not None:
1254 allmantissa1
= len(labeledticks
) == len([tick
for tick
in labeledticks
if abs(tick
.enum
) == abs(tick
.denom
)])
1256 allmantissa1
= len(labeledticks
) == len([tick
for tick
in labeledticks
if tick
.enum
== tick
.denom
])
1257 for tick
in labeledticks
:
1258 if (self
.skipallmantissa1
and allmantissa1
or
1259 (self
.skipmantissa1
and (tick
.enum
== tick
.denom
or
1260 (tick
.enum
== -tick
.denom
and self
.minusnomantissaexp
is not None)))):
1261 if tick
.enum
== tick
.denom
:
1262 tick
.label
= self
.nomantissaexp
% tick
.temp_exp
1264 tick
.label
= self
.minusnomantissaexp
% tick
.temp_exp
1266 tick
.label
= self
.mantissaexp
% (tick
.label
, tick
.temp_exp
)
1267 tick
.enum
, tick
.denom
= tick
.temp_orgenum
, tick
.temp_orgdenom
1269 # del tick.temp_orgenum # we've inserted those temporary variables ... and do not care any longer about them
1270 # del tick.temp_orgdenom
1274 class defaulttexter
:
1275 "a texter creating decimal or exponential labels"
1277 __implements__
= _Itexter
1279 def __init__(self
, smallestdecimal
=frac((1, 1000)),
1280 biggestdecimal
=frac((9999, 1)),
1282 decimaltexter
=decimaltexter(),
1283 exponentialtexter
=exponentialtexter()):
1284 r
"""initializes the instance
1285 - smallestdecimal and biggestdecimal are the smallest and
1286 biggest decimal values, where the decimaltexter should be used;
1287 they are frac instances; the sign of the tick is ignored here;
1288 a tick at zero is considered for the decimaltexter as well
1289 - equaldecision (boolean) uses decimaltexter or exponentialtexter
1290 globaly (set) or for each tick separately (unset)
1291 - decimaltexter and exponentialtexter are texters to be used"""
1292 self
.smallestdecimal
= smallestdecimal
1293 self
.biggestdecimal
= biggestdecimal
1294 self
.equaldecision
= equaldecision
1295 self
.decimaltexter
= decimaltexter
1296 self
.exponentialtexter
= exponentialtexter
1298 def labels(self
, ticks
):
1302 if tick
.label
is None and tick
.labellevel
is not None:
1303 if not tick
.enum
or (abs(tick
) >= self
.smallestdecimal
and abs(tick
) <= self
.biggestdecimal
):
1304 decticks
.append(tick
)
1306 expticks
.append(tick
)
1307 if self
.equaldecision
:
1309 self
.exponentialtexter
.labels(ticks
)
1311 self
.decimaltexter
.labels(ticks
)
1313 for tick
in decticks
:
1314 self
.decimaltexter
.labels([tick
])
1315 for tick
in expticks
:
1316 self
.exponentialtexter
.labels([tick
])
1319 ################################################################################
1321 ################################################################################
1324 class axiscanvas(canvas
._canvas
):
1326 - an axis canvas is a regular canvas returned by an
1327 axispainters painter method
1328 - it contains a PyX length extent to be used for the
1329 alignment of additional axes; the axis extent should
1330 be handled by the axispainters painter method; you may
1331 apprehend this as a size information comparable to a
1332 bounding box, which must be handled manually
1333 - it contains a list of textboxes called labels which are
1334 used to rate the distances between the labels if needed
1335 by the axis later on; the painter method has not only to
1336 insert the labels into this canvas, but should also fill
1337 this list, when a rating of the distances should be
1338 performed by the axis"""
1340 # __implements__ = sole implementation
1342 def __init__(self
, *args
, **kwargs
):
1343 """initializes the instance
1344 - sets extent to zero
1345 - sets labels to an empty list"""
1346 canvas
._canvas
.__init
__(self
, *args
, **kwargs
)
1352 """create rotations accordingly to tick directions
1353 - upsidedown rotations are suppressed by rotating them by another 180 degree"""
1355 # __implements__ = sole implementation
1357 def __init__(self
, direction
, epsilon
=1e-10):
1358 """initializes the instance
1359 - direction is an angle to be used relative to the tick direction
1360 - epsilon is the value by which 90 degrees can be exceeded before
1361 an 180 degree rotation is added"""
1362 self
.direction
= direction
1363 self
.epsilon
= epsilon
1365 def trafo(self
, dx
, dy
):
1366 """returns a rotation transformation accordingly to the tick direction
1367 - dx and dy are the direction of the tick"""
1368 direction
= self
.direction
- math
.atan2(dy
, dx
) * 180 / math
.pi
1369 while (direction
> 90 + self
.epsilon
):
1371 while (direction
< -90 - self
.epsilon
):
1373 return trafomodule
.rotate(direction
)
1376 rotatetext
.parallel
= rotatetext(90)
1377 rotatetext
.orthogonal
= rotatetext(180)
1380 class _Iaxispainter
:
1381 "class for painting axes"
1383 def paint(self
, axispos
, axis
, ac
=None):
1384 """paint the axis into an axiscanvas
1385 - returns the axiscanvas
1386 - when no axiscanvas is provided (the typical case), a new
1387 axiscanvas is created. however, when extending an painter
1388 by inheritance, painting on the same axiscanvas is supported
1389 by setting the axiscanvas attribute
1390 - axispos is an instance, which implements _Iaxispos to
1391 define the tick positions
1392 - the axis and should not be modified (we may
1393 add some temporary variables like axis.ticks[i].temp_xxx,
1394 which might be used just temporary) -- the idea is that
1395 all things can be used several times
1396 - also do not modify the instance (self) -- even this
1397 instance might be used several times; thus do not modify
1398 attributes like self.titleattrs etc. (use local copies)
1399 - the method might access some additional attributes from
1400 the axis, e.g. the axis title -- the axis painter should
1401 document this behavior and rely on the availability of
1402 those attributes -> it becomes a question of the proper
1403 usage of the combination of axis & axispainter
1404 - the axiscanvas is a axiscanvas instance and should be
1405 filled with ticks, labels, title, etc.; note that the
1406 extent and labels instance variables should be handled
1407 as documented in the axiscanvas"""
1411 """interface definition of axis tick position methods
1412 - these methods are used for the postitioning of the ticks
1413 when painting an axis"""
1414 # TODO: should we add a local transformation (for label text etc?)
1415 # (this might replace tickdirection (and even tickposition?))
1417 def basepath(self
, x1
=None, x2
=None):
1418 """return the basepath as a path
1419 - x1 is the start position; if not set, the basepath starts
1420 from the beginning of the axis, which might imply a
1421 value outside of the graph coordinate range [0; 1]
1422 - x2 is analogous to x1, but for the end position"""
1424 def vbasepath(self
, v1
=None, v2
=None):
1425 """return the basepath as a path
1426 - like basepath, but for graph coordinates"""
1428 def gridpath(self
, x
):
1429 """return the gridpath as a path for a given position x
1430 - might return None when no gridpath is available"""
1432 def vgridpath(self
, v
):
1433 """return the gridpath as a path for a given position v
1434 in graph coordinates
1435 - might return None when no gridpath is available"""
1437 def tickpoint_pt(self
, x
):
1438 """return the position at the basepath as a tuple (x, y) in
1439 postscript points for the position x"""
1441 def tickpoint(self
, x
):
1442 """return the position at the basepath as a tuple (x, y) in
1443 in PyX length for the position x"""
1445 def vtickpoint_pt(self
, v
):
1446 "like tickpoint_pt, but for graph coordinates"
1448 def vtickpoint(self
, v
):
1449 "like tickpoint, but for graph coordinates"
1451 def tickdirection(self
, x
):
1452 """return the direction of a tick as a tuple (dx, dy) for the
1453 position x (the direction points towards the graph)"""
1455 def vtickdirection(self
, v
):
1456 """like tickposition, but for graph coordinates"""
1460 """implements those parts of _Iaxispos which can be build
1461 out of the axis convert method and other _Iaxispos methods
1462 - base _Iaxispos methods, which need to be implemented:
1467 - other methods needed for _Iaxispos are build out of those
1468 listed above when this class is inherited"""
1470 def __init__(self
, convert
):
1471 """initializes the instance
1472 - convert is a convert method from an axis"""
1473 self
.convert
= convert
1475 def basepath(self
, x1
=None, x2
=None):
1478 return self
.vbasepath()
1480 return self
.vbasepath(v2
=self
.convert(x2
))
1483 return self
.vbasepath(v1
=self
.convert(x1
))
1485 return self
.vbasepath(v1
=self
.convert(x1
), v2
=self
.convert(x2
))
1487 def gridpath(self
, x
):
1488 return self
.vgridpath(self
.convert(x
))
1490 def tickpoint_pt(self
, x
):
1491 return self
.vtickpoint_pt(self
.convert(x
))
1493 def tickpoint(self
, x
):
1494 return self
.vtickpoint(self
.convert(x
))
1496 def vtickpoint(self
, v
):
1497 return [unit
.t_pt(x
) for x
in self
.vtickpoint(v
)]
1499 def tickdirection(self
, x
):
1500 return self
.vtickdirection(self
.convert(x
))
1503 class pathaxispos(_axispos
):
1504 """axis tick position methods along an arbitrary path"""
1506 __implements__
= _Iaxispos
1508 def __init__(self
, p
, convert
, direction
=1):
1510 self
.normpath
= path
.normpath(p
)
1511 self
.arclength
= self
.normpath
.arclength(p
)
1512 _axispos
.__init
__(self
, convert
)
1513 self
.direction
= direction
1515 def vbasepath(self
, v1
=None, v2
=None):
1520 return self
.normpath
.split(self
.normpath
.lentopar(v2
* self
.arclength
))[0]
1523 return self
.normpath
.split(self
.normpath
.lentopar(v1
* self
.arclength
))[1]
1525 return self
.normpath
.split(*self
.normpath
.lentopar([v1
* self
.arclength
, v2
* self
.arclength
]))[1]
1527 def vgridpath(self
, v
):
1530 def tickpoint_pt(self
, v
):
1531 # XXX: path._at missing!
1532 return [unit
.topt(x
) for x
in self
.normpath
.at(self
.normpath
.lentopar(v
* self
.arclength
))]
1534 def vtickdirection(self
, v
):
1535 t
= self
.normpath
.tangent(self
.normpath
.lentopar(v
* self
.arclength
))
1536 # XXX: path._begin and path._end missing!
1537 tbegin
= [unit
.topt
[x
] for x
in t
.begin()]
1538 tend
= [unit
.topt
[x
] for x
in t
.end()]
1539 dx
= tend
[0]-tbegin
[0]
1540 dy
= tend
[1]-tbegin
[1]
1541 norm
= math
.sqrt(dx
*dx
+ dy
*dy
)
1542 if self
.direction
== 1:
1543 return dy
/norm
, -dx
/norm
1544 elif self
.direction
== -1:
1545 return -dy
/norm
, dx
/norm
1546 raise RuntimeError("unknown direction")
1549 class axistitlepainter
:
1550 """class for painting an axis title
1551 - the axis must have a title attribute when using this painter;
1552 this title might be None"""
1554 __implements__
= _Iaxispainter
1556 def __init__(self
, titledist
="0.3 cm",
1557 titleattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1558 titledirection
=rotatetext
.parallel
,
1560 texrunner
=textmodule
.defaulttexrunner
):
1561 """initialized the instance
1562 - titledist is a visual PyX length giving the distance
1563 of the title from the axis extent already there (a title might
1564 be added after labels or other things are plotted already)
1565 - labelattrs is a list of attributes for a texrunners text
1566 method; a single is allowed without being a list; None
1568 - titledirection is an instance of rotatetext or None
1569 - titlepos is the position of the title in graph coordinates
1570 - texrunner is the texrunner to be used to create text
1571 (the texrunner is available for further use in derived
1572 classes as instance variable texrunner)"""
1573 self
.titledist_str
= titledist
1574 self
.titleattrs
= titleattrs
1575 self
.titledirection
= titledirection
1576 self
.titlepos
= titlepos
1577 self
.texrunner
= texrunner
1579 def paint(self
, axispos
, axis
, ac
=None):
1582 if axis
.title
is not None and self
.titleattrs
is not None:
1583 titledist
= unit
.length(self
.titledist_str
, default_type
="v")
1584 x
, y
= axispos
.vtickpoint_pt(self
.titlepos
)
1585 dx
, dy
= axispos
.vtickdirection(self
.titlepos
)
1586 titleattrs
= helper
.ensurelist(self
.titleattrs
)
1587 if self
.titledirection
is not None:
1588 titleattrs
.append(self
.titledirection
.trafo(dx
, dy
))
1589 title
= self
.texrunner
.text_pt(x
, y
, axis
.title
, *titleattrs
)
1590 ac
.extent
+= titledist
1591 title
.linealign(ac
.extent
, -dx
, -dy
)
1592 ac
.extent
+= title
.extent(dx
, dy
)
1597 class axispainter(axistitlepainter
):
1598 """class for painting the ticks and labels of an axis
1599 - the inherited titleaxispainter is used to paint the title of
1601 - note that the type of the elements of ticks given as an argument
1602 of the paint method must be suitable for the tick position methods
1605 __implements__
= _Iaxispainter
1607 defaultticklengths
= ["%0.5f cm" % (0.2*goldenmean
**(-i
)) for i
in range(10)]
1609 def __init__(self
, innerticklengths
=defaultticklengths
,
1610 outerticklengths
=None,
1614 basepathattrs
=style
.linecap
.square
,
1616 labelattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1617 labeldirection
=None,
1621 """initializes the instance
1622 - innerticklenths and outerticklengths are two lists of
1623 visual PyX lengths for ticks, subticks, etc. plotted inside
1624 and outside of the graph; when a single value is given, it
1625 is used for all tick levels; None turns off ticks inside or
1626 outside of the graph
1627 - tickattrs are a list of stroke attributes for the ticks;
1628 a single entry is allowed without being a list; None turns
1630 - gridattrs are a list of lists used as stroke
1631 attributes for ticks, subticks etc.; when a single list
1632 is given, it is used for ticks, subticks, etc.; a single
1633 entry is allowed without being a list; None turns off
1635 - zeropathattrs are a list of stroke attributes for a grid
1636 line at axis value zero; a single entry is allowed without
1637 being a list; None turns off the zeropath
1638 - basepathattrs are a list of stroke attributes for a grid
1639 line at axis value zero; a single entry is allowed without
1640 being a list; None turns off the basepath
1641 - labeldist is a visual PyX length for the distance of the labels
1642 from the axis basepath
1643 - labelattrs is a list of attributes for a texrunners text
1644 method; a single entry is allowed without being a list;
1645 None turns off the labels
1646 - titledirection is an instance of rotatetext or None
1647 - labelhequalize and labelvequalize (booleans) perform an equal
1648 alignment for straight vertical and horizontal axes, respectively
1649 - futher keyword arguments are passed to axistitlepainter"""
1650 # TODO: access to axis.divisor -- document, remove, ... ???
1651 self
.innerticklengths_str
= innerticklengths
1652 self
.outerticklengths_str
= outerticklengths
1653 self
.tickattrs
= tickattrs
1654 self
.gridattrs
= gridattrs
1655 self
.zeropathattrs
= zeropathattrs
1656 self
.basepathattrs
= basepathattrs
1657 self
.labeldist_str
= labeldist
1658 self
.labelattrs
= labelattrs
1659 self
.labeldirection
= labeldirection
1660 self
.labelhequalize
= labelhequalize
1661 self
.labelvequalize
= labelvequalize
1662 axistitlepainter
.__init
__(self
, **kwargs
)
1664 def paint(self
, axispos
, axis
, ac
=None):
1668 raise RuntimeError("XXX") # XXX debug only
1669 labeldist
= unit
.length(self
.labeldist_str
, default_type
="v")
1670 for tick
in axis
.ticks
:
1671 tick
.temp_v
= axis
.convert(float(tick
) * axis
.divisor
)
1672 tick
.temp_x
, tick
.temp_y
= axispos
.vtickpoint_pt(tick
.temp_v
)
1673 tick
.temp_dx
, tick
.temp_dy
= axispos
.vtickdirection(tick
.temp_v
)
1675 # create & align tick.temp_labelbox
1676 for tick
in axis
.ticks
:
1677 if tick
.labellevel
is not None:
1678 labelattrs
= helper
.getsequenceno(self
.labelattrs
, tick
.labellevel
)
1679 if labelattrs
is not None:
1680 labelattrs
= helper
.ensurelist(labelattrs
)[:]
1681 if self
.labeldirection
is not None:
1682 labelattrs
.append(self
.labeldirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
1683 if tick
.labelattrs
is not None:
1684 labelattrs
.extend(helper
.ensurelist(tick
.labelattrs
))
1685 tick
.temp_labelbox
= self
.texrunner
.text_pt(tick
.temp_x
, tick
.temp_y
, tick
.label
, *labelattrs
)
1686 if len(axis
.ticks
) > 1:
1688 for tick
in axis
.ticks
[1:]:
1689 if tick
.temp_dx
!= axis
.ticks
[0].temp_dx
or tick
.temp_dy
!= axis
.ticks
[0].temp_dy
:
1693 if equaldirection
and ((not axis
.ticks
[0].temp_dx
and self
.labelvequalize
) or
1694 (not axis
.ticks
[0].temp_dy
and self
.labelhequalize
)):
1695 if self
.labelattrs
is not None:
1696 box
.linealignequal([tick
.temp_labelbox
for tick
in axis
.ticks
if tick
.labellevel
is not None],
1697 labeldist
, -axis
.ticks
[0].temp_dx
, -axis
.ticks
[0].temp_dy
)
1699 for tick
in axis
.ticks
:
1700 if tick
.labellevel
is not None and self
.labelattrs
is not None:
1701 tick
.temp_labelbox
.linealign(labeldist
, -tick
.temp_dx
, -tick
.temp_dy
)
1704 if helper
.issequence(arg
):
1705 return [unit
.length(a
, default_type
="v") for a
in arg
]
1707 return unit
.length(arg
, default_type
="v")
1708 innerticklengths
= mkv(self
.innerticklengths_str
)
1709 outerticklengths
= mkv(self
.outerticklengths_str
)
1711 for tick
in axis
.ticks
:
1712 if tick
.ticklevel
is not None:
1713 innerticklength
= helper
.getitemno(innerticklengths
, tick
.ticklevel
)
1714 outerticklength
= helper
.getitemno(outerticklengths
, tick
.ticklevel
)
1715 if innerticklength
is not None or outerticklength
is not None:
1716 if innerticklength
is None:
1718 if outerticklength
is None:
1720 tickattrs
= helper
.getsequenceno(self
.tickattrs
, tick
.ticklevel
)
1721 if tickattrs
is not None:
1722 innerticklength_pt
= unit
.topt(innerticklength
)
1723 outerticklength_pt
= unit
.topt(outerticklength
)
1724 x1
= tick
.temp_x
+ tick
.temp_dx
* innerticklength_pt
1725 y1
= tick
.temp_y
+ tick
.temp_dy
* innerticklength_pt
1726 x2
= tick
.temp_x
- tick
.temp_dx
* outerticklength_pt
1727 y2
= tick
.temp_y
- tick
.temp_dy
* outerticklength_pt
1728 ac
.stroke(path
._line
(x1
, y1
, x2
, y2
), *helper
.ensuresequence(tickattrs
))
1729 if tick
!= frac((0, 1)) or self
.zeropathattrs
is None:
1730 gridattrs
= helper
.getsequenceno(self
.gridattrs
, tick
.ticklevel
)
1731 if gridattrs
is not None:
1732 ac
.stroke(axispos
.vgridpath(tick
.temp_v
), *helper
.ensuresequence(gridattrs
))
1733 if outerticklength
is not None and unit
.topt(outerticklength
) > unit
.topt(ac
.extent
):
1734 ac
.extent
= outerticklength
1735 if outerticklength
is not None and unit
.topt(-innerticklength
) > unit
.topt(ac
.extent
):
1736 ac
.extent
= -innerticklength
1737 if tick
.labellevel
is not None and self
.labelattrs
is not None:
1738 ac
.insert(tick
.temp_labelbox
)
1739 ac
.labels
.append(tick
.temp_labelbox
)
1740 extent
= tick
.temp_labelbox
.extent(tick
.temp_dx
, tick
.temp_dy
) + labeldist
1741 if unit
.topt(extent
) > unit
.topt(ac
.extent
):
1743 if self
.basepathattrs
is not None:
1744 ac
.stroke(axispos
.vbasepath(), *helper
.ensuresequence(self
.basepathattrs
))
1745 if self
.zeropathattrs
is not None:
1746 if len(axis
.ticks
) and axis
.ticks
[0] * axis
.ticks
[-1] < frac((0, 1)):
1747 ac
.stroke(axispos
.gridpath(0), *helper
.ensuresequence(self
.zeropathattrs
))
1749 # for tick in axis.ticks:
1750 # del tick.temp_v # we've inserted those temporary variables ... and do not care any longer about them
1755 # if tick.labellevel is not None and self.labelattrs is not None:
1756 # del tick.temp_labelbox
1758 axistitlepainter
.paint(self
, axispos
, axis
, ac
=ac
)
1763 class linkaxispainter(axispainter
):
1764 """class for painting a linked axis
1765 - the inherited axispainter is used to paint the axis
1766 - modifies some constructor defaults"""
1768 __implements__
= _Iaxispainter
1770 def __init__(self
, zeropathattrs
=None,
1774 """initializes the instance
1775 - the zeropathattrs default is set to None thus skipping the zeropath
1776 - the labelattrs default is set to None thus skipping the labels
1777 - the titleattrs default is set to None thus skipping the title
1778 - all keyword arguments are passed to axispainter"""
1779 axispainter
.__init
__(self
, zeropathattrs
=zeropathattrs
,
1780 labelattrs
=labelattrs
,
1781 titleattrs
=titleattrs
,
1786 """implementation of the _Iaxispos interface for a subaxis"""
1788 __implements__
= _Iaxispos
1790 def __init__(self
, convert
, baseaxispos
, vmin
, vmax
, vminover
, vmaxover
):
1791 """initializes the instance
1792 - convert is the subaxis convert method
1793 - baseaxispos is the axispos instance of the base axis
1794 - vmin, vmax is the range covered by the subaxis in graph coordinates
1795 - vminover, vmaxover is the extended range of the subaxis including
1796 regions between several subaxes (for baseline drawing etc.)"""
1797 self
.convert
= convert
1798 self
.baseaxispos
= baseaxispos
1801 self
.vminover
= vminover
1802 self
.vmaxover
= vmaxover
1804 def basepath(self
, x1
=None, x2
=None):
1806 v1
= self
.vmin
+self
.convert(x1
)*(self
.vmax
-self
.vmin
)
1810 v2
= self
.vmin
+self
.convert(x2
)*(self
.vmax
-self
.vmin
)
1813 return self
.baseaxispos
.vbasepath(v1
, v2
)
1815 def vbasepath(self
, v1
=None, v2
=None):
1817 v1
= self
.vmin
+v1
*(self
.vmax
-self
.vmin
)
1821 v2
= self
.vmin
+v2
*(self
.vmax
-self
.vmin
)
1824 return self
.baseaxispos
.vbasepath(v1
, v2
)
1826 def gridpath(self
, x
):
1827 return self
.baseaxispos
.vgridpath(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
1829 def vgridpath(self
, v
):
1830 return self
.baseaxispos
.vgridpath(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
1832 def tickpoint_pt(self
, x
, axis
=None):
1833 return self
.baseaxispos
.vtickpoint_pt(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
1835 def tickpoint(self
, x
, axis
=None):
1836 return self
.baseaxispos
.vtickpoint(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
1838 def vtickpoint_pt(self
, v
, axis
=None):
1839 return self
.baseaxispos
.vtickpoint_pt(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
1841 def vtickpoint(self
, v
, axis
=None):
1842 return self
.baseaxispos
.vtickpoint(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
1844 def tickdirection(self
, x
, axis
=None):
1845 return self
.baseaxispos
.vtickdirection(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
1847 def vtickdirection(self
, v
, axis
=None):
1848 return self
.baseaxispos
.vtickdirection(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
1851 class splitaxispainter(axistitlepainter
):
1852 """class for painting a splitaxis
1853 - the inherited titleaxispainter is used to paint the title of
1855 - the splitaxispainter access the subaxes attribute of the axis"""
1857 __implements__
= _Iaxispainter
1859 def __init__(self
, breaklinesdist
="0.05 cm",
1860 breaklineslength
="0.5 cm",
1861 breaklinesangle
=-60,
1864 """initializes the instance
1865 - breaklinesdist is a visual length of the distance between
1866 the two lines of the axis break
1867 - breaklineslength is a visual length of the length of the
1868 two lines of the axis break
1869 - breaklinesangle is the angle of the lines of the axis break
1870 - breaklinesattrs are a list of stroke attributes for the
1871 axis break lines; a single entry is allowed without being a
1872 list; None turns off the break lines
1873 - futher keyword arguments are passed to axistitlepainter"""
1874 self
.breaklinesdist_str
= breaklinesdist
1875 self
.breaklineslength_str
= breaklineslength
1876 self
.breaklinesangle
= breaklinesangle
1877 self
.breaklinesattrs
= breaklinesattrs
1878 axistitlepainter
.__init
__(self
, **args
)
1880 def paint(self
, axispos
, axis
, ac
=None):
1884 raise RuntimeError("XXX") # XXX debug only
1885 for subaxis
in axis
.subaxes
:
1886 subac
= subaxis
.finish(subaxispos(subaxis
.convert
, axispos
, subaxis
.vmin
, subaxis
.vmax
, subaxis
.vminover
, subaxis
.vmaxover
), axis
)
1888 if unit
.topt(ac
.extent
) < unit
.topt(subac
.extent
):
1889 ac
.extent
= subac
.extent
1890 if self
.breaklinesattrs
is not None:
1891 self
.breaklinesdist
= unit
.length(self
.breaklinesdist_str
, default_type
="v")
1892 self
.breaklineslength
= unit
.length(self
.breaklineslength_str
, default_type
="v")
1893 self
.sin
= math
.sin(self
.breaklinesangle
*math
.pi
/180.0)
1894 self
.cos
= math
.cos(self
.breaklinesangle
*math
.pi
/180.0)
1895 breaklinesextent
= (0.5*self
.breaklinesdist
*math
.fabs(self
.cos
) +
1896 0.5*self
.breaklineslength
*math
.fabs(self
.sin
))
1897 if unit
.topt(ac
.extent
) < unit
.topt(breaklinesextent
):
1898 ac
.extent
= breaklinesextent
1899 for subaxis1
, subaxis2
in zip(axis
.subaxes
[:-1], axis
.subaxes
[1:]):
1900 # use a tangent of the basepath (this is independent of the tickdirection)
1901 v
= 0.5 * (subaxis1
.vmax
+ subaxis2
.vmin
)
1902 p
= path
.normpath(axispos
.vbasepath(v
, None))
1903 breakline
= p
.tangent(0, self
.breaklineslength
)
1904 widthline
= p
.tangent(0, self
.breaklinesdist
).transformed(trafomodule
.rotate(self
.breaklinesangle
+90, *breakline
.begin()))
1905 tocenter
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(breakline
.begin(), breakline
.end()))
1906 towidth
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(widthline
.begin(), widthline
.end()))
1907 breakline
= breakline
.transformed(trafomodule
.translate(*tocenter
).rotated(self
.breaklinesangle
, *breakline
.begin()))
1908 breakline1
= breakline
.transformed(trafomodule
.translate(*towidth
))
1909 breakline2
= breakline
.transformed(trafomodule
.translate(-towidth
[0], -towidth
[1]))
1910 ac
.fill(path
.path(path
.moveto(*breakline1
.begin()),
1911 path
.lineto(*breakline1
.end()),
1912 path
.lineto(*breakline2
.end()),
1913 path
.lineto(*breakline2
.begin()),
1914 path
.closepath()), color
.gray
.white
)
1915 ac
.stroke(breakline1
, *helper
.ensuresequence(self
.breaklinesattrs
))
1916 ac
.stroke(breakline2
, *helper
.ensuresequence(self
.breaklinesattrs
))
1917 axistitlepainter
.paint(self
, axispos
, axis
, ac
=ac
)
1921 class linksplitaxispainter(splitaxispainter
):
1922 """class for painting a linked splitaxis
1923 - the inherited splitaxispainter is used to paint the axis
1924 - modifies some constructor defaults"""
1926 __implements__
= _Iaxispainter
1928 def __init__(self
, titleattrs
=None, **kwargs
):
1929 """initializes the instance
1930 - the titleattrs default is set to None thus skipping the title
1931 - all keyword arguments are passed to splitaxispainter"""
1932 splitaxispainter
.__init
__(self
, titleattrs
=titleattrs
, **kwargs
)
1935 class baraxispainter(axistitlepainter
):
1936 """class for painting a baraxis
1937 - the inherited titleaxispainter is used to paint the title of
1939 - the baraxispainter access the multisubaxis, subaxis names, texts, and
1940 relsizes attributes"""
1942 __implements__
= _Iaxispainter
1944 def __init__(self
, innerticklength
=None,
1945 outerticklength
=None,
1947 basepathattrs
=style
.linecap
.square
,
1949 nameattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1955 """initializes the instance
1956 - innerticklength and outerticklength are a visual length of
1957 the ticks to be plotted at the axis basepath to visually
1958 separate the bars; if neither innerticklength nor
1959 outerticklength are set, not ticks are plotted
1960 - breaklinesattrs are a list of stroke attributes for the
1961 axis tick; a single entry is allowed without being a
1962 list; None turns off the ticks
1963 - namedist is a visual PyX length for the distance of the bar
1964 names from the axis basepath
1965 - nameattrs is a list of attributes for a texrunners text
1966 method; a single entry is allowed without being a list;
1967 None turns off the names
1968 - namedirection is an instance of rotatetext or None
1969 - namehequalize and namevequalize (booleans) perform an equal
1970 alignment for straight vertical and horizontal axes, respectively
1971 - futher keyword arguments are passed to axistitlepainter"""
1972 self
.innerticklength_str
= innerticklength
1973 self
.outerticklength_str
= outerticklength
1974 self
.tickattrs
= tickattrs
1975 self
.basepathattrs
= basepathattrs
1976 self
.namedist_str
= namedist
1977 self
.nameattrs
= nameattrs
1978 self
.namedirection
= namedirection
1979 self
.namepos
= namepos
1980 self
.namehequalize
= namehequalize
1981 self
.namevequalize
= namevequalize
1982 axistitlepainter
.__init
__(self
, **args
)
1984 def paint(self
, axispos
, axis
, ac
=None):
1988 raise RuntimeError("XXX") # XXX debug only
1989 if axis
.multisubaxis
is not None:
1990 for subaxis
in axis
.subaxis
:
1991 subac
= subaxis
.finish(subaxispos(subaxis
.convert
, axispos
, subaxis
.vmin
, subaxis
.vmax
, None, None), axis
)
1993 if unit
.topt(ac
.extent
) < unit
.topt(subac
.extent
):
1994 ac
.extent
= subac
.extent
1996 for name
in axis
.names
:
1997 v
= axis
.convert((name
, self
.namepos
))
1998 x
, y
= axispos
.vtickpoint_pt(v
)
1999 dx
, dy
= axispos
.vtickdirection(v
)
2000 namepos
.append((v
, x
, y
, dx
, dy
))
2002 if self
.nameattrs
is not None:
2003 for (v
, x
, y
, dx
, dy
), name
in zip(namepos
, axis
.names
):
2004 nameattrs
= helper
.ensurelist(self
.nameattrs
)[:]
2005 if self
.namedirection
is not None:
2006 nameattrs
.append(self
.namedirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
2007 if axis
.texts
.has_key(name
):
2008 nameboxes
.append(self
.texrunner
.text_pt(x
, y
, str(axis
.texts
[name
]), *nameattrs
))
2009 elif axis
.texts
.has_key(str(name
)):
2010 nameboxes
.append(self
.texrunner
.text_pt(x
, y
, str(axis
.texts
[str(name
)]), *nameattrs
))
2012 nameboxes
.append(self
.texrunner
.text_pt(x
, y
, str(name
), *nameattrs
))
2013 labeldist
= ac
.extent
+ unit
.length(self
.namedist_str
, default_type
="v")
2014 if len(namepos
) > 1:
2016 for np
in namepos
[1:]:
2017 if np
[3] != namepos
[0][3] or np
[4] != namepos
[0][4]:
2021 if equaldirection
and ((not namepos
[0][3] and self
.namevequalize
) or
2022 (not namepos
[0][4] and self
.namehequalize
)):
2023 box
.linealignequal(nameboxes
, labeldist
, -namepos
[0][3], -namepos
[0][4])
2025 for namebox
, np
in zip(nameboxes
, namepos
):
2026 namebox
.linealign(labeldist
, -np
[3], -np
[4])
2027 if self
.innerticklength_str
is not None:
2028 innerticklength
= unit
.length(self
.innerticklength_str
, default_type
="v")
2029 innerticklength_pt
= unit
.topt(innerticklength
)
2030 if self
.tickattrs
is not None and unit
.topt(ac
.extent
) < -innerticklength_pt
:
2031 ac
.extent
= -innerticklength
2032 elif self
.outerticklength_str
is not None:
2033 innerticklength
= innerticklength_pt
= 0
2034 if self
.outerticklength_str
is not None:
2035 outerticklength
= unit
.length(self
.outerticklength_str
, default_type
="v")
2036 outerticklength_pt
= unit
.topt(outerticklength
)
2037 if self
.tickattrs
is not None and unit
.topt(ac
.extent
) < outerticklength_pt
:
2038 ac
.extent
= outerticklength
2039 elif self
.innerticklength_str
is not None:
2040 outerticklength
= outerticklength_pt
= 0
2041 for (v
, x
, y
, dx
, dy
), namebox
in zip(namepos
, nameboxes
):
2042 newextent
= namebox
.extent(dx
, dy
) + labeldist
2043 if unit
.topt(ac
.extent
) < unit
.topt(newextent
):
2044 ac
.extent
= newextent
2045 if self
.tickattrs
is not None and (self
.innerticklength_str
is not None or self
.outerticklength_str
is not None):
2046 for pos
in axis
.relsizes
:
2047 if pos
== axis
.relsizes
[0]:
2048 pos
-= axis
.firstdist
2049 elif pos
!= axis
.relsizes
[-1]:
2050 pos
-= 0.5 * axis
.dist
2051 v
= pos
/ axis
.relsizes
[-1]
2052 x
, y
= axispos
.vtickpoint_pt(v
)
2053 dx
, dy
= axispos
.vtickdirection(v
)
2054 x1
= x
+ dx
* innerticklength_pt
2055 y1
= y
+ dy
* innerticklength_pt
2056 x2
= x
- dx
* outerticklength_pt
2057 y2
= y
- dy
* outerticklength_pt
2058 ac
.stroke(path
._line
(x1
, y1
, x2
, y2
), *helper
.ensuresequence(self
.tickattrs
))
2059 if self
.basepathattrs
is not None:
2060 p
= axispos
.vbasepath()
2062 ac
.stroke(p
, *helper
.ensuresequence(self
.basepathattrs
))
2063 for namebox
in nameboxes
:
2065 axistitlepainter
.paint(self
, axispos
, axis
, ac
=ac
)
2069 class linkbaraxispainter(baraxispainter
):
2070 """class for painting a linked baraxis
2071 - the inherited baraxispainter is used to paint the axis
2072 - modifies some constructor defaults"""
2074 __implements__
= _Iaxispainter
2076 def __init__(self
, nameattrs
=None, titleattrs
=None, **kwargs
):
2077 """initializes the instance
2078 - the titleattrs default is set to None thus skipping the title
2079 - the nameattrs default is set to None thus skipping the names
2080 - all keyword arguments are passed to axispainter"""
2081 baraxispainter
.__init
__(self
, nameattrs
=nameattrs
, titleattrs
=titleattrs
, **kwargs
)
2084 ################################################################################
2086 ################################################################################
2090 """interface definition of a axis
2091 - an axis should implement an convert and invert method like
2092 _Imap, but this is not part of this interface definition;
2093 one possibility is to mix-in a proper map class, but special
2094 purpose axes might do something else"""
2096 def convert(self
, x
):
2097 "convert a value into graph coordinates"
2099 def invert(self
, v
):
2100 "invert a graph coordinate to a axis value"
2102 def getrelsize(self
):
2103 """returns the relative size (width) of the axis
2104 - for use in splitaxis, baraxis etc.
2105 - might return None if no size is available"""
2107 def setrange(self
, min=None, max=None):
2108 """set the axis data range
2109 - the type of min and max must fit to the axis
2110 - min<max; the axis might be reversed, but this is
2111 expressed internally only (min<max all the time)
2112 - the axis might not apply the change of the range
2113 (e.g. when the axis range is fixed by the user),
2114 but usually the range is extended to contain the
2116 - for invalid parameters (e.g. negativ values at an
2117 logarithmic axis), an exception should be raised
2118 - a RuntimeError is raised, when setrange is called
2119 after the finish method"""
2122 """return data range as a tuple (min, max)
2123 - min<max; the axis might be reversed, but this is
2124 expressed internally only
2125 - a RuntimeError exception is raised when no
2126 range is available"""
2128 def finish(self
, axispos
):
2129 """finishes the axis
2130 - axispos implements _Iaxispos
2131 - the finish method returns an axiscanvas, which should be
2132 insertable into the graph to finally paint the axis
2133 - any modification of the axis range should be disabled after
2134 the finish method was called"""
2135 # TODO: be more specific about exceptions
2137 def createlinkaxis(self
, **kwargs
):
2138 """create a link axis to the axis itself
2139 - typically, a link axis is a axis, which share almost
2140 all properties with the axis it is linked to
2141 - typically, the painter gets replaced by a painter
2142 which doesn't put any text to the axis"""
2146 """base implementation a regular axis
2147 - typical usage is to mix-in a linmap or a logmap to
2148 complete the definition"""
2150 def __init__(self
, min=None, max=None, reverse
=0, divisor
=1,
2151 title
=None, painter
=axispainter(), texter
=defaulttexter(),
2152 density
=1, maxworse
=2):
2153 """initializes the instance
2154 - min and max fix the axis minimum and maximum, respectively;
2155 they are determined by the data to be plotted, when not fixed
2156 - reverse (boolean) reverses the minimum and the maximum of
2158 - numerical divisor for the axis partitioning
2159 - title is a string containing the axis title
2160 - axispainter is the axis painter (should implement _Ipainter)
2161 - texter is the texter (should implement _Itexter)
2162 - density is a global parameter for the axis paritioning and
2163 axis rating; its default is 1, but the range 0.5 to 2.5 should
2164 be usefull to get less or more ticks by the automatic axis
2166 - maxworse is a number of trials with worse tick rating
2167 before giving up (usually it should not be needed to increase
2168 this value; increasing the number will slow down the automatic
2169 axis partitioning considerably)
2170 - note that some methods of this class want to access a
2171 part and a rating attribute of the instance; those
2172 attributes should be initialized by the constructors
2173 of derived classes"""
2174 if min is not None and max is not None and min > max:
2175 min, max, reverse
= max, min, not reverse
2176 self
.fixmin
, self
.fixmax
, self
.min, self
.max, self
.reverse
= min is not None, max is not None, min, max, reverse
2177 self
.divisor
= divisor
2179 self
.painter
= painter
2180 self
.texter
= texter
2181 self
.density
= density
2182 self
.maxworse
= maxworse
2184 self
.finishac
= None
2187 def _setrange(self
, min=None, max=None):
2188 if not self
.fixmin
and min is not None and (self
.min is None or min < self
.min):
2190 if not self
.fixmax
and max is not None and (self
.max is None or max > self
.max):
2192 if None not in (self
.min, self
.max):
2195 self
.setbasepoints(((self
.min, 1), (self
.max, 0)))
2197 self
.setbasepoints(((self
.min, 0), (self
.max, 1)))
2199 def _getrange(self
):
2200 return self
.min, self
.max
2202 def _forcerange(self
, range):
2203 self
.min, self
.max = range
2206 def setrange(self
, min=None, max=None):
2207 if self
.finishac
is not None:
2208 raise RuntimeError("axis was already finished")
2209 self
._setrange
(min, max)
2212 if self
.min is not None and self
.max is not None:
2213 return self
.min, self
.max
2215 def checkfraclist(self
, fracs
):
2216 "orders a list of fracs, equal entries are not allowed"
2217 if not len(fracs
): return []
2218 sorted = list(fracs
)
2221 for item
in sorted[1:]:
2223 raise ValueError("duplicate entry found")
2227 def finish(self
, axispos
, texrunner
):
2228 if self
.finishac
is not None:
2229 return self
.finishac
2231 min, max = self
.getrange()
2232 parter
= parterpos
= None
2233 if self
.part
is not None:
2234 self
.part
= helper
.ensurelist(self
.part
)
2235 for p
, i
in zip(self
.part
, xrange(sys
.maxint
)):
2236 if hasattr(p
, "defaultpart"):
2237 if parter
is not None:
2238 raise RuntimeError("only one partitioner allowed")
2242 self
.ticks
= self
.checkfraclist(self
.part
)
2244 self
.part
[:parterpos
] = self
.checkfraclist(self
.part
[:parterpos
])
2245 self
.part
[parterpos
+1:] = self
.checkfraclist(self
.part
[parterpos
+1:])
2246 self
.ticks
= _mergeticklists(_mergeticklists(self
.part
[:parterpos
],
2247 parter
.defaultpart(min/self
.divisor
,
2251 self
.part
[parterpos
+1:])
2254 # lesspart and morepart can be called after defaultpart;
2255 # this works although some axes may share their autoparting,
2256 # because the axes are processed sequentially
2258 if parter
is not None:
2260 nextpart
= parter
.lesspart
2261 while nextpart
is not None:
2262 newticks
= nextpart()
2263 if newticks
is not None:
2264 if parterpos
is not None:
2265 newticks
= _mergeticklists(_mergeticklists(self
.part
[:parterpos
], newticks
), self
.part
[parterpos
+1:])
2267 bestrate
= self
.rater
.rateticks(self
, self
.ticks
, self
.density
)
2268 bestrate
+= self
.rater
.raterange(self
.convert(float(self
.ticks
[-1])/self
.divisor
)-
2269 self
.convert(float(self
.ticks
[0])/self
.divisor
), 1)
2270 variants
= [[bestrate
, self
.ticks
]]
2273 newrate
= self
.rater
.rateticks(self
, newticks
, self
.density
)
2274 newrate
+= self
.rater
.raterange(self
.convert(float(self
.ticks
[-1])/self
.divisor
)-
2275 self
.convert(float(self
.ticks
[0])/self
.divisor
), 1)
2276 variants
.append([newrate
, newticks
])
2277 if newrate
< bestrate
:
2284 if worse
== self
.maxworse
and nextpart
== parter
.lesspart
:
2286 nextpart
= parter
.morepart
2287 if worse
== self
.maxworse
and nextpart
== parter
.morepart
:
2291 if self
.painter
is not None:
2294 while i
< len(variants
) and (bestrate
is None or variants
[i
][0] < bestrate
):
2295 saverange
= self
._getrange
()
2296 self
.ticks
= variants
[i
][1]
2298 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2299 self
.texter
.labels(self
.ticks
)
2300 ac
= self
.painter
.paint(axispos
, self
)
2301 ratelayout
= self
.rater
.ratelayout(ac
, self
.density
)
2302 if ratelayout
is not None:
2303 variants
[i
][0] += ratelayout
2304 variants
[i
].append(ac
)
2306 variants
[i
][0] = None
2307 if variants
[i
][0] is not None and (bestrate
is None or variants
[i
][0] < bestrate
):
2308 bestrate
= variants
[i
][0]
2309 self
._forcerange
(saverange
)
2311 if bestrate
is None:
2312 raise RuntimeError("no valid axis partitioning found")
2313 variants
= [variant
for variant
in variants
[:i
] if variant
[0] is not None]
2315 self
.ticks
= variants
[0][1]
2317 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2321 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2325 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2326 self
.texter
.labels(self
.ticks
)
2327 ac
= self
.painter
.paint(axispos
, self
)
2329 return self
.finishac
2331 def createlinkaxis(self
, **args
):
2332 return linkaxis(self
, **args
)
2335 class linaxis(_axis
, _linmap
):
2336 """implementation of a linear axis"""
2338 __implements__
= _Iaxis
2340 def __init__(self
, part
=autolinpart(), rater
=axisrater(), **args
):
2341 """initializes the instance
2342 - the part attribute contains a list of one partitioner
2343 (a partitioner implements _Ipart) or/and some (manually
2344 set) ticks (implementing _Itick); a single entry might
2345 be passed without wrapping it into a list; the partitioner
2346 and the tick instances must fit to the type of the axis
2347 (e.g. they should be valid parameters to the axis convert
2348 method); the ticks and the partitioner results are mixed
2350 - the rater implements _Irater and is used to rate different
2351 tick lists created by the partitioner (after merging with
2353 - futher keyword arguments are passed to _axis"""
2354 _axis
.__init
__(self
, **args
)
2355 if self
.fixmin
and self
.fixmax
:
2356 self
.relsize
= self
.max - self
.min
2361 class logaxis(_axis
, _logmap
):
2362 """implementation of a logarithmic axis"""
2364 __implements__
= _Iaxis
2366 def __init__(self
, part
=autologpart(), rater
=axisrater(ticks
=axisrater
.logticks
, labels
=axisrater
.loglabels
), **args
):
2367 """initializes the instance
2368 - the part attribute contains a list of one partitioner
2369 (a partitioner implements _Ipart) or/and some (manually
2370 set) ticks (implementing _Itick); a single entry might
2371 be passed without wrapping it into a list; the partitioner
2372 and the tick instances must fit to the type of the axis
2373 (e.g. they should be valid parameters to the axis convert
2374 method); the ticks and the partitioner results are mixed
2376 - the rater implements _Irater and is used to rate different
2377 tick lists created by the partitioner (after merging with
2379 - futher keyword arguments are passed to _axis"""
2380 _axis
.__init
__(self
, **args
)
2381 if self
.fixmin
and self
.fixmax
:
2382 self
.relsize
= math
.log(self
.max) - math
.log(self
.min)
2388 """a axis linked to an already existing regular axis
2389 - almost all properties of the axis are "copied" from the
2390 axis this axis is linked to
2391 - usually, linked axis are used to create an axis to an
2392 existing axis with different painting properties; linked
2393 axis can be used to plot an axis twice at the opposite
2394 sides of a graphxy or even to share an axis between
2395 different graphs!"""
2397 __implements__
= _Iaxis
2399 def __init__(self
, linkedaxis
, painter
=linkaxispainter()):
2400 """initializes the instance
2401 - it gets a axis this linkaxis is linked to
2402 - it gets a painter to be used for this linked axis"""
2403 self
.linkedaxis
= linkedaxis
2404 self
.painter
= painter
2405 self
.finishac
= None
2407 def __getattr__(self
, attr
):
2408 """access to unkown attributes are handed over to the
2409 axis this linkaxis is linked to"""
2410 return getattr(self
.linkedaxis
, attr
)
2412 def finish(self
, axispos
, texrunner
):
2413 """finishes the axis
2414 - instead of performing the hole finish process
2415 (paritioning, rating, etc.) just a painter call
2417 if self
.finishac
is None:
2418 self
.linkedaxis
.finish(axispos
, texrunner
)
2419 self
.finishac
= self
.painter
.paint(axispos
, self
)
2420 return self
.finishac
2424 """implementation of a split axis
2425 - a split axis contains several (sub-)axes with
2426 non-overlapping data ranges -- between these subaxes
2427 the axis is "splitted"
2428 - (just to get sure: a splitaxis can contain other
2429 splitaxes as its subaxes)
2430 - a splitaxis implements the _Iaxispos for its subaxes
2431 by inheritance from _subaxispos"""
2433 __implements__
= _Iaxis
, _Iaxispos
2435 def __init__(self
, subaxes
, splitlist
=0.5, splitdist
=0.1, relsizesplitdist
=1,
2436 title
=None, painter
=splitaxispainter()):
2437 """initializes the instance
2438 - subaxes is a list of subaxes
2439 - splitlist is a list of graph coordinates, where the splitting
2440 of the main axis should be performed; a single entry (splitting
2441 two axes) doesn't need to be wrapped into a list; if the list
2442 isn't long enough for the subaxes, missing entries are considered
2444 - splitdist is the size of the splitting in graph coordinates, when
2445 the associated splitlist entry is not None
2446 - relsizesplitdist: a None entry in splitlist means, that the
2447 position of the splitting should be calculated out of the
2448 relsize values of conrtibuting subaxes (the size of the
2449 splitting is relsizesplitdist in values of the relsize values
2451 - title is the title of the axis as a string
2452 - painter is the painter of the axis; it should be specialized to
2454 - the relsize of the splitaxis is the sum of the relsizes of the
2455 subaxes including the relsizesplitdist"""
2456 self
.subaxes
= subaxes
2457 self
.painter
= painter
2459 self
.splitlist
= helper
.ensurelist(splitlist
)
2460 for subaxis
in self
.subaxes
:
2463 self
.subaxes
[0].vmin
= 0
2464 self
.subaxes
[0].vminover
= None
2465 self
.subaxes
[-1].vmax
= 1
2466 self
.subaxes
[-1].vmaxover
= None
2467 for i
in xrange(len(self
.splitlist
)):
2468 if self
.splitlist
[i
] is not None:
2469 self
.subaxes
[i
].vmax
= self
.splitlist
[i
] - 0.5*splitdist
2470 self
.subaxes
[i
].vmaxover
= self
.splitlist
[i
]
2471 self
.subaxes
[i
+1].vmin
= self
.splitlist
[i
] + 0.5*splitdist
2472 self
.subaxes
[i
+1].vminover
= self
.splitlist
[i
]
2474 while i
< len(self
.subaxes
):
2475 if self
.subaxes
[i
].vmax
is None:
2476 j
= relsize
= relsize2
= 0
2477 while self
.subaxes
[i
+ j
].vmax
is None:
2478 relsize
+= self
.subaxes
[i
+ j
].relsize
+ relsizesplitdist
2480 relsize
+= self
.subaxes
[i
+ j
].relsize
2481 vleft
= self
.subaxes
[i
].vmin
2482 vright
= self
.subaxes
[i
+ j
].vmax
2483 for k
in range(i
, i
+ j
):
2484 relsize2
+= self
.subaxes
[k
].relsize
2485 self
.subaxes
[k
].vmax
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2486 relsize2
+= 0.5 * relsizesplitdist
2487 self
.subaxes
[k
].vmaxover
= self
.subaxes
[k
+ 1].vminover
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2488 relsize2
+= 0.5 * relsizesplitdist
2489 self
.subaxes
[k
+1].vmin
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2490 if i
== 0 and i
+ j
+ 1 == len(self
.subaxes
):
2491 self
.relsize
= relsize
2496 self
.fixmin
= self
.subaxes
[0].fixmin
2498 self
.min = self
.subaxes
[0].min
2499 self
.fixmax
= self
.subaxes
[-1].fixmax
2501 self
.max = self
.subaxes
[-1].max
2503 self
.finishac
= None
2506 min = self
.subaxes
[0].getrange()
2507 max = self
.subaxes
[-1].getrange()
2509 return min[0], max[1]
2513 def setrange(self
, min, max):
2514 self
.subaxes
[0].setrange(min, None)
2515 self
.subaxes
[-1].setrange(None, max)
2517 def convert(self
, value
):
2518 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
2519 if value
< self
.subaxes
[0].max:
2520 return self
.subaxes
[0].vmin
+ self
.subaxes
[0].convert(value
)*(self
.subaxes
[0].vmax
-self
.subaxes
[0].vmin
)
2521 for axis
in self
.subaxes
[1:-1]:
2522 if value
> axis
.min and value
< axis
.max:
2523 return axis
.vmin
+ axis
.convert(value
)*(axis
.vmax
-axis
.vmin
)
2524 if value
> self
.subaxes
[-1].min:
2525 return self
.subaxes
[-1].vmin
+ self
.subaxes
[-1].convert(value
)*(self
.subaxes
[-1].vmax
-self
.subaxes
[-1].vmin
)
2526 raise ValueError("value couldn't be assigned to a split region")
2528 def finish(self
, axispos
, texrunner
):
2529 if self
.finishac
is None:
2530 self
.finishac
= self
.painter
.paint(axispos
, self
)
2531 return self
.finishac
2533 def createlinkaxis(self
, **args
):
2534 return linksplitaxis(self
, **args
)
2537 class linksplitaxis(linkaxis
):
2538 """a splitaxis linked to an already existing splitaxis
2539 - inherits the access to a linked axis -- as before,
2540 basically only the painter is replaced
2541 - it takes care of the creation of linked axes of
2544 __implements__
= _Iaxis
2546 def __init__(self
, linkedaxis
, painter
=linksplitaxispainter(), subaxispainter
=None):
2547 """initializes the instance
2548 - it gets a axis this linkaxis is linked to
2549 - it gets a painter to be used for this linked axis
2550 - it gets a list of painters to be used for the linkaxes
2551 of the subaxes; if None, the createlinkaxis of the subaxes
2552 are called without a painter parameter; if it is not a
2553 list, the subaxispainter is passed as the painter
2554 parameter to all createlinkaxis of the subaxes"""
2555 linkaxis
.__init
__(self
, linkedaxis
, painter
=painter
)
2556 if subaxispainter
is not None:
2557 if helper
.issequence(subaxispainter
):
2558 if len(linkedaxis
.subaxes
) != len(subaxispainter
):
2559 raise RuntimeError("subaxes and subaxispainter lengths do not fit")
2560 self
.subaxes
= [a
.createlinkaxis(painter
=p
) for a
, p
in zip(linkedaxis
.subaxes
, subaxispainter
)]
2562 self
.subaxes
= [a
.createlinkaxis(painter
=subaxispainter
) for a
in linkedaxis
.subaxes
]
2564 self
.subaxes
= [a
.createlinkaxis() for a
in linkedaxis
.subaxes
]
2568 """implementation of a axis for bar graphs
2569 - a bar axes is different from a splitaxis by the way it
2570 selects its subaxes: the convert method gets a list,
2571 where the first entry is a name selecting a subaxis out
2572 of a list; instead of the term "bar" or "subaxis" the term
2573 "item" will be used here
2574 - the baraxis stores a list of names be identify the items;
2575 the names might be of any time (strings, integers, etc.);
2576 the names can be printed as the titles for the items, but
2577 alternatively the names might be transformed by the texts
2578 dictionary, which maps a name to a text to be used to label
2579 the items in the painter
2580 - usually, there is only one subaxis, which is used as
2581 the subaxis for all items
2582 - alternatively it is also possible to use another baraxis
2583 as a multisubaxis; it is copied via the createsubaxis
2584 method whenever another subaxis is needed (by that a
2585 nested bar axis with a different number of subbars at
2586 each item can be created)
2587 - any axis can be a subaxis of a baraxis; if no subaxis
2588 is specified at all, the baraxis simulates a linear
2589 subaxis with a fixed range of 0 to 1
2590 - a splitaxis implements the _Iaxispos for its subaxes
2591 by inheritance from _subaxispos when the multisubaxis
2592 feature is turned on"""
2594 def __init__(self
, subaxis
=None, multisubaxis
=None, title
=None,
2595 dist
=0.5, firstdist
=None, lastdist
=None, names
=None,
2596 texts
={}, painter
=baraxispainter()):
2597 """initialize the instance
2598 - subaxis contains a axis to be used as the subaxis
2600 - multisubaxis might contain another baraxis instance
2601 to be used to construct a new subaxis for each item;
2602 (by that a nested bar axis with a different number
2603 of subbars at each item can be created)
2604 - only one of subaxis or multisubaxis can be set; if neither
2605 of them is set, the baraxis behaves like having a linaxis
2606 as its subaxis with a fixed range 0 to 1
2607 - the title attribute contains the axis title as a string
2608 - the dist is a relsize to be used as the distance between
2610 - the firstdist and lastdist are the distance before the
2611 first and after the last item, respectively; when set
2612 to None (the default), 0.5*dist is used
2613 - names is a predefined list of names to identify the
2614 items; if set, the name list is fixed
2615 - texts is a dictionary transforming a name to a text in
2616 the painter; if a name isn't found in the dictionary
2618 - the relsize of the baraxis is the sum of the
2619 relsizes including all distances between the items"""
2621 if firstdist
is not None:
2622 self
.firstdist
= firstdist
2624 self
.firstdist
= 0.5 * dist
2625 if lastdist
is not None:
2626 self
.lastdist
= lastdist
2628 self
.lastdist
= 0.5 * dist
2629 self
.relsizes
= None
2632 for name
in helper
.ensuresequence(names
):
2634 self
.fixnames
= names
is not None
2635 self
.multisubaxis
= multisubaxis
2636 if self
.multisubaxis
is not None:
2637 if subaxis
is not None:
2638 raise RuntimeError("either use subaxis or multisubaxis")
2639 self
.subaxis
= [self
.createsubaxis() for name
in self
.names
]
2641 self
.subaxis
= subaxis
2645 self
.painter
= painter
2647 def createsubaxis(self
):
2648 return baraxis(subaxis
=self
.multisubaxis
.subaxis
,
2649 multisubaxis
=self
.multisubaxis
.multisubaxis
,
2650 title
=self
.multisubaxis
.title
,
2651 dist
=self
.multisubaxis
.dist
,
2652 firstdist
=self
.multisubaxis
.firstdist
,
2653 lastdist
=self
.multisubaxis
.lastdist
,
2654 names
=self
.multisubaxis
.names
,
2655 texts
=self
.multisubaxis
.texts
,
2656 painter
=self
.multisubaxis
.painter
)
2659 # TODO: we do not yet have a proper range handling for a baraxis
2662 def setrange(self
, min=None, max=None):
2663 # TODO: we do not yet have a proper range handling for a baraxis
2664 raise RuntimeError("range handling for a baraxis is not implemented")
2666 def setname(self
, name
, *subnames
):
2667 """add a name to identify an item at the baraxis
2668 - by using subnames, nested name definitions are
2670 - a style (or the user itself) might use this to
2671 insert new items into a baraxis
2672 - setting self.relsizes to None forces later recalculation"""
2673 if not self
.fixnames
:
2674 if name
not in self
.names
:
2675 self
.relsizes
= None
2676 self
.names
.append(name
)
2677 if self
.multisubaxis
is not None:
2678 self
.subaxis
.append(self
.createsubaxis())
2679 if (not self
.fixnames
or name
in self
.names
) and len(subnames
):
2680 if self
.multisubaxis
is not None:
2681 if self
.subaxis
[self
.names
.index(name
)].setname(*subnames
):
2682 self
.relsizes
= None
2684 if self
.subaxis
.setname(*subnames
):
2685 self
.relsizes
= None
2686 return self
.relsizes
is not None
2688 def updaterelsizes(self
):
2689 # guess what it does: it recalculates relsize attribute
2690 self
.relsizes
= [i
*self
.dist
+ self
.firstdist
for i
in range(len(self
.names
) + 1)]
2691 self
.relsizes
[-1] += self
.lastdist
- self
.dist
2692 if self
.multisubaxis
is not None:
2694 for i
in range(1, len(self
.relsizes
)):
2695 self
.subaxis
[i
-1].updaterelsizes()
2696 subrelsize
+= self
.subaxis
[i
-1].relsizes
[-1]
2697 self
.relsizes
[i
] += subrelsize
2699 if self
.subaxis
is None:
2702 self
.subaxis
.updaterelsizes()
2703 subrelsize
= self
.subaxis
.relsizes
[-1]
2704 for i
in range(1, len(self
.relsizes
)):
2705 self
.relsizes
[i
] += i
* subrelsize
2707 def convert(self
, value
):
2708 """baraxis convert method
2709 - the value should be a list, where the first entry is
2710 a member of the names (set in the constructor or by the
2711 setname method); this first entry identifies an item in
2713 - following values are passed to the appropriate subaxis
2715 - when there is no subaxis, the convert method will behave
2716 like having a linaxis from 0 to 1 as subaxis"""
2717 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
2718 if not self
.relsizes
:
2719 self
.updaterelsizes()
2720 pos
= self
.names
.index(value
[0])
2722 if self
.subaxis
is None:
2725 if self
.multisubaxis
is not None:
2726 subvalue
= value
[1] * self
.subaxis
[pos
].relsizes
[-1]
2728 subvalue
= value
[1] * self
.subaxis
.relsizes
[-1]
2730 if self
.multisubaxis
is not None:
2731 subvalue
= self
.subaxis
[pos
].convert(value
[1:]) * self
.subaxis
[pos
].relsizes
[-1]
2733 subvalue
= self
.subaxis
.convert(value
[1:]) * self
.subaxis
.relsizes
[-1]
2734 return (self
.relsizes
[pos
] + subvalue
) / float(self
.relsizes
[-1])
2736 def finish(self
, axispos
, texrunner
):
2737 if self
.multisubaxis
is not None:
2738 for name
, subaxis
in zip(self
.names
, self
.subaxis
):
2739 subaxis
.vmin
= self
.convert((name
, 0))
2740 subaxis
.vmax
= self
.convert((name
, 1))
2741 return self
.painter
.paint(axispos
, self
)
2743 def createlinkaxis(self
, **args
):
2744 return linkbaraxis(self
, **args
)
2747 class linkbaraxis(linkaxis
):
2748 """a baraxis linked to an already existing baraxis
2749 - inherits the access to a linked axis -- as before,
2750 basically only the painter is replaced
2751 - it must take care of the creation of linked axes of
2754 __implements__
= _Iaxis
2756 def __init__(self
, linkedaxis
, painter
=linkbaraxispainter()):
2757 """initializes the instance
2758 - it gets a axis this linkaxis is linked to
2759 - it gets a painter to be used for this linked axis"""
2760 linkaxis
.__init
__(self
, linkedaxis
, painter
=painter
)
2761 if self
.multisubaxis
is not None:
2762 self
.subaxis
= [subaxis
.createlinkaxis() for subaxis
in self
.linkedaxis
.subaxis
]
2763 elif self
.subaxis
is not None:
2764 self
.subaxis
= self
.subaxis
.createlinkaxis()
2767 ################################################################################
2769 ################################################################################
2772 # g = graph.graphxy(key=graph.key())
2773 # g.addkey(graph.key(), ...)
2778 def __init__(self
, dist
="0.2 cm", pos
= "tr", hinside
= 1, vinside
= 1, hdist
="0.6 cm", vdist
="0.4 cm",
2779 symbolwidth
="0.5 cm", symbolheight
="0.25 cm", symbolspace
="0.2 cm",
2780 textattrs
=textmodule
.vshift
.mathaxis
):
2781 self
.dist_str
= dist
2783 self
.hinside
= hinside
2784 self
.vinside
= vinside
2785 self
.hdist_str
= hdist
2786 self
.vdist_str
= vdist
2787 self
.symbolwidth_str
= symbolwidth
2788 self
.symbolheight_str
= symbolheight
2789 self
.symbolspace_str
= symbolspace
2790 self
.textattrs
= textattrs
2791 self
.plotinfos
= None
2792 if self
.pos
in ("tr", "rt"):
2795 elif self
.pos
in ("br", "rb"):
2798 elif self
.pos
in ("tl", "lt"):
2801 elif self
.pos
in ("bl", "lb"):
2805 raise RuntimeError("invalid pos attribute")
2807 def setplotinfos(self
, *plotinfos
):
2808 """set the plotinfos to be used in the key
2809 - call it exactly once
2810 - plotinfo instances with title == None are ignored"""
2811 if self
.plotinfos
is not None:
2812 raise RuntimeError("setplotinfo is called multiple times")
2813 self
.plotinfos
= [plotinfo
for plotinfo
in plotinfos
if plotinfo
.data
.title
is not None]
2815 def dolayout(self
, graph
):
2816 "creates the layout of the key"
2817 self
.dist_pt
= unit
.topt(unit
.length(self
.dist_str
, default_type
="v"))
2818 self
.hdist_pt
= unit
.topt(unit
.length(self
.hdist_str
, default_type
="v"))
2819 self
.vdist_pt
= unit
.topt(unit
.length(self
.vdist_str
, default_type
="v"))
2820 self
.symbolwidth_pt
= unit
.topt(unit
.length(self
.symbolwidth_str
, default_type
="v"))
2821 self
.symbolheight_pt
= unit
.topt(unit
.length(self
.symbolheight_str
, default_type
="v"))
2822 self
.symbolspace_pt
= unit
.topt(unit
.length(self
.symbolspace_str
, default_type
="v"))
2824 for plotinfo
in self
.plotinfos
:
2825 self
.titles
.append(graph
.texrunner
.text_pt(0, 0, plotinfo
.data
.title
, *helper
.ensuresequence(self
.textattrs
)))
2826 box
._tile
(self
.titles
, self
.dist_pt
, 0, -1)
2827 box
._linealignequal
(self
.titles
, self
.symbolwidth_pt
+ self
.symbolspace_pt
, 1, 0)
2830 """return a bbox for the key
2831 method should be called after dolayout"""
2832 result
= bbox
.bbox()
2833 for title
in self
.titles
:
2834 result
= result
+ title
.bbox() + bbox
._bbox
(0, title
.center
[1] - 0.5 * self
.symbolheight_pt
,
2835 0, title
.center
[1] + 0.5 * self
.symbolheight_pt
)
2838 def paint(self
, c
, x
, y
):
2839 """paint the graph key into a canvas c at the position x and y (in postscript points)
2840 - method should be called after dolayout
2841 - the x, y alignment might be calculated by the graph using:
2842 - the bbox of the key as returned by the keys bbox method
2843 - the attributes hdist_pt, vdist_pt, hinside, and vinside of the key
2844 - the dimension and geometry of the graph"""
2845 sc
= c
.insert(canvas
.canvas(trafomodule
._translate
(x
, y
)))
2846 for plotinfo
, title
in zip(self
.plotinfos
, self
.titles
):
2847 plotinfo
.style
.key(sc
, 0, -0.5 * self
.symbolheight_pt
+ title
.center
[1],
2848 self
.symbolwidth_pt
, self
.symbolheight_pt
)
2852 ################################################################################
2854 ################################################################################
2858 """an axispos linear along a line with a fix direction for the ticks"""
2860 __implements__
= _Iaxispos
2862 def __init__(self
, convert
, x1
, y1
, x2
, y2
, fixtickdirection
):
2863 """initializes the instance
2864 - only the convert method is needed from the axis
2865 - x1, y1, x2, y2 are PyX length"""
2866 self
.convert
= convert
2871 self
.x1_pt
= unit
.topt(x1
)
2872 self
.y1_pt
= unit
.topt(y1
)
2873 self
.x2_pt
= unit
.topt(x2
)
2874 self
.y2_pt
= unit
.topt(y2
)
2875 self
.fixtickdirection
= fixtickdirection
2877 def vbasepath(self
, v1
=None, v2
=None):
2882 return path
._line
((1-v1
)*self
.x1_pt
+v1
*self
.x2_pt
,
2883 (1-v1
)*self
.y1_pt
+v1
*self
.y2_pt
,
2884 (1-v2
)*self
.x1_pt
+v2
*self
.x2_pt
,
2885 (1-v2
)*self
.y1_pt
+v2
*self
.y2_pt
)
2887 def basepath(self
, x1
=None, x2
=None):
2891 v1
= self
.convert(x1
)
2895 v2
= self
.convert(x2
)
2896 return path
._line
((1-v1
)*self
.x1_pt
+v1
*self
.x2_pt
,
2897 (1-v1
)*self
.y1_pt
+v1
*self
.y2_pt
,
2898 (1-v2
)*self
.x1_pt
+v2
*self
.x2_pt
,
2899 (1-v2
)*self
.y1_pt
+v2
*self
.y2_pt
)
2901 def gridpath(self
, x
):
2902 raise RuntimeError("gridpath not available")
2904 def vgridpath(self
, v
):
2905 raise RuntimeError("gridpath not available")
2907 def vtickpoint_pt(self
, v
):
2908 return (1-v
)*self
.x1_pt
+v
*self
.x2_pt
, (1-v
)*self
.y1_pt
+v
*self
.y2_pt
2910 def vtickpoint(self
, v
):
2911 return (1-v
)*self
.x1
+v
*self
.x2
, (1-v
)*self
.y1
+v
*self
.y2
2913 def tickpoint_pt(self
, x
):
2915 return (1-v
)*self
.x1_pt
+v
*self
.x2_pt
, (1-v
)*self
.y1_pt
+v
*self
.y2_pt
2917 def tickpoint(self
, x
):
2919 return (1-v
)*self
.x1
+v
*self
.x2
, (1-v
)*self
.y1
+v
*self
.y2
2921 def tickdirection(self
, x
):
2922 return self
.fixtickdirection
2924 def vtickdirection(self
, v
):
2925 return self
.fixtickdirection
2928 class lineaxisposlinegrid(lineaxispos
):
2929 """an axispos linear along a line with a fix direction for the ticks"""
2931 __implements__
= _Iaxispos
2933 def __init__(self
, convert
, x1
, y1
, x2
, y2
, fixtickdirection
, startgridlength
, endgridlength
):
2934 """initializes the instance
2935 - only the convert method is needed from the axis
2936 - x1, y1, x2, y2 are PyX length"""
2937 lineaxispos
.__init
__(self
, convert
, x1
, y1
, x2
, y2
, fixtickdirection
)
2938 self
.startgridlength
= startgridlength
2939 self
.endgridlength
= endgridlength
2940 self
.startgridlength_pt
= unit
.topt(self
.startgridlength
)
2941 self
.endgridlength_pt
= unit
.topt(self
.endgridlength
)
2943 def gridpath(self
, x
):
2945 return path
._line
((1-v
)*self
.x1_pt
+v
*self
.x2_pt
+self
.fixtickdirection
[0]*self
.startgridlength_pt
,
2946 (1-v
)*self
.y1_pt
+v
*self
.y2_pt
+self
.fixtickdirection
[1]*self
.startgridlength_pt
,
2947 (1-v
)*self
.x1_pt
+v
*self
.x2_pt
+self
.fixtickdirection
[0]*self
.endgridlength_pt
,
2948 (1-v
)*self
.y1_pt
+v
*self
.y2_pt
+self
.fixtickdirection
[1]*self
.endgridlength_pt
)
2950 def vgridpath(self
, v
):
2951 return path
._line
((1-v
)*self
.x1_pt
+v
*self
.x2_pt
+self
.fixtickdirection
[0]*self
.startgridlength_pt
,
2952 (1-v
)*self
.y1_pt
+v
*self
.y2_pt
+self
.fixtickdirection
[1]*self
.startgridlength_pt
,
2953 (1-v
)*self
.x1_pt
+v
*self
.x2_pt
+self
.fixtickdirection
[0]*self
.endgridlength_pt
,
2954 (1-v
)*self
.y1_pt
+v
*self
.y2_pt
+self
.fixtickdirection
[1]*self
.endgridlength_pt
)
2959 def __init__(self
, data
, style
):
2965 class graphxy(canvas
.canvas
):
2971 def __init__(self
, type, axispos
, tickdirection
):
2973 - type == 0: x-axis; type == 1: y-axis
2974 - axispos_pt is the y or x position of the x-axis or y-axis
2975 in postscript points, respectively
2976 - axispos is analogous to axispos, but as a PyX length
2977 - dx and dy is the tick direction
2980 self
.axispos
= axispos
2981 self
.axispos_pt
= unit
.topt(axispos
)
2982 self
.tickdirection
= tickdirection
2984 def clipcanvas(self
):
2985 return self
.insert(canvas
.canvas(canvas
.clip(path
._rect
(self
.xpos_pt
, self
.ypos_pt
, self
.width_pt
, self
.height_pt
))))
2987 def plot(self
, data
, style
=None):
2989 raise RuntimeError("layout setup was already performed")
2991 if helper
.issequence(data
):
2992 raise RuntimeError("list plot needs an explicit style")
2993 if self
.defaultstyle
.has_key(data
.defaultstyle
):
2994 style
= self
.defaultstyle
[data
.defaultstyle
].iterate()
2996 style
= data
.defaultstyle()
2997 self
.defaultstyle
[data
.defaultstyle
] = style
3000 for d
in helper
.ensuresequence(data
):
3002 style
= style
.iterate()
3005 d
.setstyle(self
, style
)
3006 plotinfos
.append(plotinfo(d
, style
))
3007 self
.plotinfos
.extend(plotinfos
)
3008 if helper
.issequence(data
):
3012 def addkey(self
, key
, *plotinfos
):
3014 raise RuntimeError("layout setup was already performed")
3015 self
.addkeys
.append((key
, plotinfos
))
3017 def pos_pt(self
, x
, y
, xaxis
=None, yaxis
=None):
3019 xaxis
= self
.axes
["x"]
3021 yaxis
= self
.axes
["y"]
3022 return self
.xpos_pt
+xaxis
.convert(x
)*self
.width_pt
, self
.ypos_pt
+yaxis
.convert(y
)*self
.height_pt
3024 def pos(self
, x
, y
, xaxis
=None, yaxis
=None):
3026 xaxis
= self
.axes
["x"]
3028 yaxis
= self
.axes
["y"]
3029 return self
.xpos
+xaxis
.convert(x
)*self
.width
, self
.ypos
+yaxis
.convert(y
)*self
.height
3031 def vpos_pt(self
, vx
, vy
):
3032 return self
.xpos_pt
+vx
*self
.width_pt
, self
.ypos_pt
+vy
*self
.height_pt
3034 def vpos(self
, vx
, vy
):
3035 return self
.xpos
+vx
*self
.width
, self
.ypos
+vy
*self
.height
3037 # def xbaseline(self, x1=None, x2=None, axis=None):
3039 # axis = self.axes["x"]
3040 # if x1 is not None:
3041 # v1 = axis.convert(x1)
3044 # if x2 is not None:
3045 # v2 = axis.convert(x2)
3048 # return path._line(self._xpos+v1*self._width, axis.axisposdata._axispos,
3049 # self._xpos+v2*self._width, axis.axisposdata._axispos)
3051 # def ybaseline(self, x1=None, x2=None, axis=None):
3053 # axis = self.axes["y"]
3054 # if x1 is not None:
3055 # v1 = axis.convert(x1)
3058 # if x2 is not None:
3059 # v2 = axis.convert(x2)
3062 # return path._line(axis.axisposdata._axispos, self._ypos+v1*self._height,
3063 # axis.axisposdata._axispos, self._ypos+v2*self._height)
3065 # def vxbaseline(self, v1=None, v2=None, axis=None):
3067 # axis = self.axes["x"]
3072 # return path._line(self._xpos+v1*self._width, axis.axisposdata._axispos,
3073 # self._xpos+v2*self._width, axis.axisposdata._axispos)
3075 # def vybaseline(self, v1=None, v2=None, axis=None):
3077 # axis = self.axes["y"]
3082 # return path._line(axis.axisposdata._axispos, self._ypos+v1*self._height,
3083 # axis.axisposdata._axispos, self._ypos+v2*self._height)
3085 # def xgridline(self, x, axis=None):
3087 # axis = self.axes["x"]
3088 # v = axis.convert(x)
3089 # return path._line(self._xpos+v*self._width, self._ypos,
3090 # self._xpos+v*self._width, self._ypos+self._height)
3092 # def ygridline(self, x, axis=None):
3094 # axis = self.axes["y"]
3095 # v = axis.convert(x)
3096 # return path._line(self._xpos, self._ypos+v*self._height,
3097 # self._xpos+self._width, self._ypos+v*self._height)
3099 # def vxgridline(self, v, axis=None):
3101 # axis = self.axes["x"]
3102 # return path._line(self._xpos+v*self._width, self._ypos,
3103 # self._xpos+v*self._width, self._ypos+self._height)
3105 # def vygridline(self, v, axis=None):
3107 # axis = self.axes["y"]
3108 # return path._line(self._xpos, self._ypos+v*self._height,
3109 # self._xpos+self._width, self._ypos+v*self._height)
3111 # def _xtickpoint(self, x, axis=None):
3113 # axis = self.axes["x"]
3114 # return self._xpos+axis.convert(x)*self._width, axis.axisposdata._axispos
3116 # def _ytickpoint(self, x, axis=None):
3118 # axis = self.axes["y"]
3119 # return axis.axisposdata._axispos, self._ypos+axis.convert(x)*self._height
3121 # def xtickpoint(self, x, axis=None):
3123 # axis = self.axes["x"]
3124 # return self.xpos+axis.convert(x)*self.width, axis.axisposdata.axispos
3126 # def ytickpoint(self, x, axis=None):
3128 # axis = self.axes["y"]
3129 # return axis.axisposdata.axispos, self.ypos+axis.convert(x)*self.height
3131 # def _vxtickpoint(self, v, axis=None):
3133 # axis = self.axes["x"]
3134 # return self._xpos+v*self._width, axis.axisposdata._axispos
3136 # def _vytickpoint(self, v, axis=None):
3138 # axis = self.axes["y"]
3139 # return axis.axisposdata._axispos, self._ypos+v*self._height
3141 # def vxtickpoint(self, v, axis=None):
3143 # axis = self.axes["x"]
3144 # return self.xpos+v*self.width, axis.axisposdata.axispos
3146 # def vytickpoint(self, v, axis=None):
3148 # axis = self.axes["y"]
3149 # return axis.axisposdata.axispos, self.ypos+v*self.height
3151 # def xtickdirection(self, x, axis=None):
3153 # axis = self.axes["x"]
3154 # return axis.axisposdata.tickdirection
3156 # def ytickdirection(self, x, axis=None):
3158 # axis = self.axes["y"]
3159 # return axis.axisposdata.tickdirection
3161 # def vxtickdirection(self, v, axis=None):
3163 # axis = self.axes["x"]
3164 # return axis.axisposdata.tickdirection
3166 # def vytickdirection(self, v, axis=None):
3168 # axis = self.axes["y"]
3169 # return axis.axisposdata.tickdirection
3171 def _addpos(self
, x
, y
, dx
, dy
):
3174 def _connect(self
, x1
, y1
, x2
, y2
):
3175 return path
._lineto
(x2
, y2
)
3177 def keynum(self
, key
):
3179 while key
[0] in string
.letters
:
3185 def gatherranges(self
):
3187 for plotinfo
in self
.plotinfos
:
3188 pdranges
= plotinfo
.data
.getranges()
3189 if pdranges
is not None:
3190 for key
in pdranges
.keys():
3191 if key
not in ranges
.keys():
3192 ranges
[key
] = pdranges
[key
]
3194 ranges
[key
] = (min(ranges
[key
][0], pdranges
[key
][0]),
3195 max(ranges
[key
][1], pdranges
[key
][1]))
3196 # known ranges are also set as ranges for the axes
3197 for key
, axis
in self
.axes
.items():
3198 if key
in ranges
.keys():
3199 axis
.setrange(*ranges
[key
])
3200 ranges
[key
] = axis
.getrange()
3201 if ranges
[key
] is None:
3205 def removedomethod(self
, method
):
3209 self
.domethods
.remove(method
)
3215 if not self
.removedomethod(self
.dolayout
): return
3217 # create list of ranges
3219 ranges
= self
.gatherranges()
3220 # 2. calculate additional ranges out of known ranges
3221 for plotinfo
in self
.plotinfos
:
3222 plotinfo
.data
.setranges(ranges
)
3223 # 3. gather ranges again
3225 # do the layout for all axes
3226 axesdist
= unit
.length(self
.axesdist_str
, default_type
="v")
3227 XPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[0])
3228 YPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[1])
3229 xaxisextents
= [0, 0]
3230 yaxisextents
= [0, 0]
3231 needxaxisdist
= [0, 0]
3232 needyaxisdist
= [0, 0]
3233 items
= list(self
.axes
.items())
3234 items
.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
3235 for key
, axis
in items
:
3236 num
= self
.keynum(key
)
3237 num2
= 1 - num
% 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
3238 num3
= 2 * (num
% 2) - 1 # x1 -> 1, x2 -> -1, x3 -> 1, x4 -> -1, ...
3239 if XPattern
.match(key
):
3240 if needxaxisdist
[num2
]:
3241 xaxisextents
[num2
] += axesdist
3242 self
.axespos
[key
] = lineaxisposlinegrid(self
.axes
[key
].convert
,
3244 self
.ypos
+ num2
*self
.height
- num3
*xaxisextents
[num2
],
3245 self
.xpos
+ self
.width
,
3246 self
.ypos
+ num2
*self
.height
- num3
*xaxisextents
[num2
],
3248 xaxisextents
[num2
], xaxisextents
[num2
] + self
.height
)
3250 self
.xbasepath
= self
.axespos
[key
].basepath
3251 self
.xvbasepath
= self
.axespos
[key
].vbasepath
3252 self
.xgridpath
= self
.axespos
[key
].gridpath
3253 self
.xvgridpath
= self
.axespos
[key
].vgridpath
3254 self
.xtickpoint_pt
= self
.axespos
[key
].tickpoint_pt
3255 self
.xtickpoint
= self
.axespos
[key
].tickpoint
3256 self
.xvtickpoint_pt
= self
.axespos
[key
].vtickpoint_pt
3257 self
.xvtickpoint
= self
.axespos
[key
].tickpoint
3258 self
.xtickdirection
= self
.axespos
[key
].tickdirection
3259 self
.xvtickdirection
= self
.axespos
[key
].vtickdirection
3260 elif YPattern
.match(key
):
3261 if needyaxisdist
[num2
]:
3262 yaxisextents
[num2
] += axesdist
3263 self
.axespos
[key
] = lineaxisposlinegrid(self
.axes
[key
].convert
,
3264 self
.xpos
+ num2
*self
.width
- num3
*yaxisextents
[num2
],
3266 self
.xpos
+ num2
*self
.width
- num3
*yaxisextents
[num2
],
3267 self
.ypos
+ self
.height
,
3269 yaxisextents
[num2
], yaxisextents
[num2
] + self
.width
)
3271 self
.ybasepath
= self
.axespos
[key
].basepath
3272 self
.yvbasepath
= self
.axespos
[key
].vbasepath
3273 self
.ygridpath
= self
.axespos
[key
].gridpath
3274 self
.yvgridpath
= self
.axespos
[key
].vgridpath
3275 self
.ytickpoint_pt
= self
.axespos
[key
].tickpoint_pt
3276 self
.ytickpoint
= self
.axespos
[key
].tickpoint
3277 self
.yvtickpoint_pt
= self
.axespos
[key
].vtickpoint_pt
3278 self
.yvtickpoint
= self
.axespos
[key
].tickpoint
3279 self
.ytickdirection
= self
.axespos
[key
].tickdirection
3280 self
.yvtickdirection
= self
.axespos
[key
].vtickdirection
3282 raise ValueError("Axis key '%s' not allowed" % key
)
3283 self
.axescanvas
[key
] = axis
.finish(self
.axespos
[key
], self
.texrunner
)
3284 if XPattern
.match(key
):
3285 xaxisextents
[num2
] += self
.axescanvas
[key
].extent
3286 needxaxisdist
[num2
] = 1
3287 if YPattern
.match(key
):
3288 yaxisextents
[num2
] += self
.axescanvas
[key
].extent
3289 needyaxisdist
[num2
] = 1
3291 def dobackground(self
):
3293 if not self
.removedomethod(self
.dobackground
): return
3294 if self
.backgroundattrs
is not None:
3295 self
.draw(path
._rect
(self
.xpos_pt
, self
.ypos_pt
, self
.width_pt
, self
.height_pt
),
3296 *helper
.ensuresequence(self
.backgroundattrs
))
3300 if not self
.removedomethod(self
.doaxes
): return
3301 for axiscanvas
in self
.axescanvas
.values():
3302 self
.insert(axiscanvas
)
3306 if not self
.removedomethod(self
.dodata
): return
3307 for plotinfo
in self
.plotinfos
:
3308 plotinfo
.data
.draw(self
)
3310 def _dokey(self
, key
, *plotinfos
):
3311 key
.setplotinfos(*plotinfos
)
3316 x
= self
.xpos_pt
+ self
.width_pt
- bbox
.urx
- key
.hdist_pt
3318 x
= self
.xpos_pt
+ self
.width_pt
- bbox
.llx
+ key
.hdist_pt
3321 x
= self
.xpos_pt
- bbox
.llx
+ key
.hdist_pt
3323 x
= self
.xpos_pt
- bbox
.urx
- key
.hdist_pt
3326 y
= self
.ypos_pt
+ self
.height_pt
- bbox
.ury
- key
.vdist_pt
3328 y
= self
.ypos_pt
+ self
.height_pt
- bbox
.lly
+ key
.vdist_pt
3331 y
= self
.ypos_pt
- bbox
.lly
+ key
.vdist_pt
3333 y
= self
.ypos_pt
- bbox
.ury
- key
.vdist_pt
3334 key
.paint(self
, x
, y
)
3338 if not self
.removedomethod(self
.dokey
): return
3339 if self
.key
is not None:
3340 self
._dokey
(self
.key
, *self
.plotinfos
)
3341 for key
, plotinfos
in self
.addkeys
:
3342 self
._dokey
(key
, *plotinfos
)
3345 while len(self
.domethods
):
3348 def initwidthheight(self
, width
, height
, ratio
):
3349 if (width
is not None) and (height
is None):
3350 self
.width
= unit
.length(width
)
3351 self
.height
= (1.0/ratio
) * self
.width
3352 elif (height
is not None) and (width
is None):
3353 self
.height
= unit
.length(height
)
3354 self
.width
= ratio
* self
.height
3356 self
.width
= unit
.length(width
)
3357 self
.height
= unit
.length(height
)
3358 self
.width_pt
= unit
.topt(self
.width
)
3359 self
.height_pt
= unit
.topt(self
.height
)
3360 if self
.width_pt
<= 0: raise ValueError("width <= 0")
3361 if self
.height_pt
<= 0: raise ValueError("height <= 0")
3363 def initaxes(self
, axes
, addlinkaxes
=0):
3364 for key
in self
.Names
:
3365 if not axes
.has_key(key
):
3366 axes
[key
] = linaxis()
3367 elif axes
[key
] is None:
3370 if not axes
.has_key(key
+ "2") and axes
.has_key(key
):
3371 axes
[key
+ "2"] = axes
[key
].createlinkaxis()
3372 elif axes
[key
+ "2"] is None:
3376 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, ratio
=goldenmean
,
3377 key
=None, backgroundattrs
=None, axesdist
="0.8 cm", **axes
):
3378 canvas
.canvas
.__init
__(self
)
3379 self
.xpos
= unit
.length(xpos
)
3380 self
.ypos
= unit
.length(ypos
)
3381 self
.xpos_pt
= unit
.topt(self
.xpos
)
3382 self
.ypos_pt
= unit
.topt(self
.ypos
)
3383 self
.initwidthheight(width
, height
, ratio
)
3384 self
.initaxes(axes
, 1)
3385 self
.axescanvas
= {}
3388 self
.backgroundattrs
= backgroundattrs
3389 self
.axesdist_str
= axesdist
3391 self
.domethods
= [self
.dolayout
, self
.dobackground
, self
.doaxes
, self
.dodata
, self
.dokey
]
3393 self
.defaultstyle
= {}
3398 return canvas
.canvas
.bbox(self
)
3400 def write(self
, file):
3402 canvas
.canvas
.write(self
, file)
3406 # some thoughts, but deferred right now
3408 # class graphxyz(graphxy):
3410 # Names = "x", "y", "z"
3412 # def _vxtickpoint(self, axis, v):
3413 # return self._vpos(v, axis.vypos, axis.vzpos)
3415 # def _vytickpoint(self, axis, v):
3416 # return self._vpos(axis.vxpos, v, axis.vzpos)
3418 # def _vztickpoint(self, axis, v):
3419 # return self._vpos(axis.vxpos, axis.vypos, v)
3421 # def vxtickdirection(self, axis, v):
3422 # x1, y1 = self._vpos(v, axis.vypos, axis.vzpos)
3423 # x2, y2 = self._vpos(v, 0.5, 0)
3424 # dx, dy = x1 - x2, y1 - y2
3425 # norm = math.sqrt(dx*dx + dy*dy)
3426 # return dx/norm, dy/norm
3428 # def vytickdirection(self, axis, v):
3429 # x1, y1 = self._vpos(axis.vxpos, v, axis.vzpos)
3430 # x2, y2 = self._vpos(0.5, v, 0)
3431 # dx, dy = x1 - x2, y1 - y2
3432 # norm = math.sqrt(dx*dx + dy*dy)
3433 # return dx/norm, dy/norm
3435 # def vztickdirection(self, axis, v):
3437 # x1, y1 = self._vpos(axis.vxpos, axis.vypos, v)
3438 # x2, y2 = self._vpos(0.5, 0.5, v)
3439 # dx, dy = x1 - x2, y1 - y2
3440 # norm = math.sqrt(dx*dx + dy*dy)
3441 # return dx/norm, dy/norm
3443 # def _pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
3444 # if xaxis is None: xaxis = self.axes["x"]
3445 # if yaxis is None: yaxis = self.axes["y"]
3446 # if zaxis is None: zaxis = self.axes["z"]
3447 # return self._vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
3449 # def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
3450 # if xaxis is None: xaxis = self.axes["x"]
3451 # if yaxis is None: yaxis = self.axes["y"]
3452 # if zaxis is None: zaxis = self.axes["z"]
3453 # return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
3455 # def _vpos(self, vx, vy, vz):
3456 # x, y, z = (vx - 0.5)*self._depth, (vy - 0.5)*self._width, (vz - 0.5)*self._height
3457 # d0 = float(self.a[0]*self.b[1]*(z-self.eye[2])
3458 # + self.a[2]*self.b[0]*(y-self.eye[1])
3459 # + self.a[1]*self.b[2]*(x-self.eye[0])
3460 # - self.a[2]*self.b[1]*(x-self.eye[0])
3461 # - self.a[0]*self.b[2]*(y-self.eye[1])
3462 # - self.a[1]*self.b[0]*(z-self.eye[2]))
3463 # da = (self.eye[0]*self.b[1]*(z-self.eye[2])
3464 # + self.eye[2]*self.b[0]*(y-self.eye[1])
3465 # + self.eye[1]*self.b[2]*(x-self.eye[0])
3466 # - self.eye[2]*self.b[1]*(x-self.eye[0])
3467 # - self.eye[0]*self.b[2]*(y-self.eye[1])
3468 # - self.eye[1]*self.b[0]*(z-self.eye[2]))
3469 # db = (self.a[0]*self.eye[1]*(z-self.eye[2])
3470 # + self.a[2]*self.eye[0]*(y-self.eye[1])
3471 # + self.a[1]*self.eye[2]*(x-self.eye[0])
3472 # - self.a[2]*self.eye[1]*(x-self.eye[0])
3473 # - self.a[0]*self.eye[2]*(y-self.eye[1])
3474 # - self.a[1]*self.eye[0]*(z-self.eye[2]))
3475 # return da/d0 + self._xpos, db/d0 + self._ypos
3477 # def vpos(self, vx, vy, vz):
3478 # tx, ty = self._vpos(vx, vy, vz)
3479 # return unit.t_pt(tx), unit.t_pt(ty)
3481 # def xbaseline(self, axis, x1, x2, xaxis=None):
3482 # if xaxis is None: xaxis = self.axes["x"]
3483 # return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2))
3485 # def ybaseline(self, axis, y1, y2, yaxis=None):
3486 # if yaxis is None: yaxis = self.axes["y"]
3487 # return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2))
3489 # def zbaseline(self, axis, z1, z2, zaxis=None):
3490 # if zaxis is None: zaxis = self.axes["z"]
3491 # return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2))
3493 # def vxbaseline(self, axis, v1, v2):
3494 # return (path._line(*(self._vpos(v1, 0, 0) + self._vpos(v2, 0, 0))) +
3495 # path._line(*(self._vpos(v1, 0, 1) + self._vpos(v2, 0, 1))) +
3496 # path._line(*(self._vpos(v1, 1, 1) + self._vpos(v2, 1, 1))) +
3497 # path._line(*(self._vpos(v1, 1, 0) + self._vpos(v2, 1, 0))))
3499 # def vybaseline(self, axis, v1, v2):
3500 # return (path._line(*(self._vpos(0, v1, 0) + self._vpos(0, v2, 0))) +
3501 # path._line(*(self._vpos(0, v1, 1) + self._vpos(0, v2, 1))) +
3502 # path._line(*(self._vpos(1, v1, 1) + self._vpos(1, v2, 1))) +
3503 # path._line(*(self._vpos(1, v1, 0) + self._vpos(1, v2, 0))))
3505 # def vzbaseline(self, axis, v1, v2):
3506 # return (path._line(*(self._vpos(0, 0, v1) + self._vpos(0, 0, v2))) +
3507 # path._line(*(self._vpos(0, 1, v1) + self._vpos(0, 1, v2))) +
3508 # path._line(*(self._vpos(1, 1, v1) + self._vpos(1, 1, v2))) +
3509 # path._line(*(self._vpos(1, 0, v1) + self._vpos(1, 0, v2))))
3511 # def xgridpath(self, x, xaxis=None):
3513 # if xaxis is None: xaxis = self.axes["x"]
3514 # v = xaxis.convert(x)
3515 # return path._line(self._xpos+v*self._width, self._ypos,
3516 # self._xpos+v*self._width, self._ypos+self._height)
3518 # def ygridpath(self, y, yaxis=None):
3520 # if yaxis is None: yaxis = self.axes["y"]
3521 # v = yaxis.convert(y)
3522 # return path._line(self._xpos, self._ypos+v*self._height,
3523 # self._xpos+self._width, self._ypos+v*self._height)
3525 # def zgridpath(self, z, zaxis=None):
3527 # if zaxis is None: zaxis = self.axes["z"]
3528 # v = zaxis.convert(z)
3529 # return path._line(self._xpos, self._zpos+v*self._height,
3530 # self._xpos+self._width, self._zpos+v*self._height)
3532 # def vxgridpath(self, v):
3533 # return path.path(path._moveto(*self._vpos(v, 0, 0)),
3534 # path._lineto(*self._vpos(v, 0, 1)),
3535 # path._lineto(*self._vpos(v, 1, 1)),
3536 # path._lineto(*self._vpos(v, 1, 0)),
3539 # def vygridpath(self, v):
3540 # return path.path(path._moveto(*self._vpos(0, v, 0)),
3541 # path._lineto(*self._vpos(0, v, 1)),
3542 # path._lineto(*self._vpos(1, v, 1)),
3543 # path._lineto(*self._vpos(1, v, 0)),
3546 # def vzgridpath(self, v):
3547 # return path.path(path._moveto(*self._vpos(0, 0, v)),
3548 # path._lineto(*self._vpos(0, 1, v)),
3549 # path._lineto(*self._vpos(1, 1, v)),
3550 # path._lineto(*self._vpos(1, 0, v)),
3553 # def _addpos(self, x, y, dx, dy):
3557 # def _connect(self, x1, y1, x2, y2):
3559 # return path._lineto(x2, y2)
3563 # if not self.removedomethod(self.doaxes): return
3564 # axesdist = unit.topt(unit.length(self.axesdist_str, default_type="v"))
3565 # XPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[0])
3566 # YPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[1])
3567 # ZPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[2])
3568 # items = list(self.axes.items())
3569 # items.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
3570 # for key, axis in items:
3571 # num = self.keynum(key)
3572 # num2 = 1 - num % 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
3573 # num3 = 1 - 2 * (num % 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
3574 # if XPattern.match(key):
3577 # axis._vtickpoint = self._vxtickpoint
3578 # axis.vgridpath = self.vxgridpath
3579 # axis.vbaseline = self.vxbaseline
3580 # axis.vtickdirection = self.vxtickdirection
3581 # elif YPattern.match(key):
3584 # axis._vtickpoint = self._vytickpoint
3585 # axis.vgridpath = self.vygridpath
3586 # axis.vbaseline = self.vybaseline
3587 # axis.vtickdirection = self.vytickdirection
3588 # elif ZPattern.match(key):
3591 # axis._vtickpoint = self._vztickpoint
3592 # axis.vgridpath = self.vzgridpath
3593 # axis.vbaseline = self.vzbaseline
3594 # axis.vtickdirection = self.vztickdirection
3596 # raise ValueError("Axis key '%s' not allowed" % key)
3597 # if axis.painter is not None:
3598 # axis.dopaint(self)
3599 # # if XPattern.match(key):
3600 # # self._xaxisextents[num2] += axis._extent
3601 # # needxaxisdist[num2] = 1
3602 # # if YPattern.match(key):
3603 # # self._yaxisextents[num2] += axis._extent
3604 # # needyaxisdist[num2] = 1
3606 # def __init__(self, tex, xpos=0, ypos=0, width=None, height=None, depth=None,
3607 # phi=30, theta=30, distance=1,
3608 # backgroundattrs=None, axesdist="0.8 cm", **axes):
3609 # canvas.canvas.__init__(self)
3613 # self._xpos = unit.topt(xpos)
3614 # self._ypos = unit.topt(ypos)
3615 # self._width = unit.topt(width)
3616 # self._height = unit.topt(height)
3617 # self._depth = unit.topt(depth)
3618 # self.width = width
3619 # self.height = height
3620 # self.depth = depth
3621 # if self._width <= 0: raise ValueError("width < 0")
3622 # if self._height <= 0: raise ValueError("height < 0")
3623 # if self._depth <= 0: raise ValueError("height < 0")
3624 # self._distance = distance*math.sqrt(self._width*self._width+
3625 # self._height*self._height+
3626 # self._depth*self._depth)
3627 # phi *= -math.pi/180
3628 # theta *= math.pi/180
3629 # self.a = (-math.sin(phi), math.cos(phi), 0)
3630 # self.b = (-math.cos(phi)*math.sin(theta),
3631 # -math.sin(phi)*math.sin(theta),
3633 # self.eye = (self._distance*math.cos(phi)*math.cos(theta),
3634 # self._distance*math.sin(phi)*math.cos(theta),
3635 # self._distance*math.sin(theta))
3636 # self.initaxes(axes)
3637 # self.axesdist_str = axesdist
3638 # self.backgroundattrs = backgroundattrs
3641 # self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
3642 # self.haslayout = 0
3643 # self.defaultstyle = {}
3647 # return bbox._bbox(self._xpos - 200, self._ypos - 200, self._xpos + 200, self._ypos + 200)
3650 ################################################################################
3652 ################################################################################
3655 #class _Ichangeattr:
3656 # """attribute changer
3657 # is an iterator for attributes where an attribute
3658 # is not refered by just a number (like for a sequence),
3659 # but also by the number of attributes requested
3660 # by calls of the next method (like for an color palette)
3661 # (you should ensure to call all needed next before the attr)
3663 # the attribute itself is implemented by overloading the _attr method"""
3666 # "get an attribute"
3669 # "get an attribute changer for the next attribute"
3672 class _changeattr
: pass
3675 class changeattr(_changeattr
):
3684 newindex
= self
.counter
3686 return refattr(self
, newindex
)
3689 class refattr(_changeattr
):
3691 def __init__(self
, ref
, index
):
3696 return self
.ref
.attr(self
.index
)
3699 return self
.ref
.iterate()
3702 # helper routines for a using attrs
3705 "get attr out of a attr/changeattr"
3706 if isinstance(attr
, _changeattr
):
3707 return attr
.getattr()
3711 def _getattrs(attrs
):
3712 "get attrs out of a list of attr/changeattr"
3713 if attrs
is not None:
3715 for attr
in helper
.ensuresequence(attrs
):
3716 if isinstance(attr
, _changeattr
):
3717 attr
= attr
.getattr()
3718 if attr
is not None:
3720 if len(result
) or not len(attrs
):
3724 def _iterateattr(attr
):
3725 "perform next to a attr/changeattr"
3726 if isinstance(attr
, _changeattr
):
3727 return attr
.iterate()
3731 def _iterateattrs(attrs
):
3732 "perform next to a list of attr/changeattr"
3733 if attrs
is not None:
3735 for attr
in helper
.ensuresequence(attrs
):
3736 if isinstance(attr
, _changeattr
):
3737 result
.append(attr
.iterate())
3743 class changecolor(changeattr
):
3745 def __init__(self
, palette
):
3746 changeattr
.__init
__(self
)
3747 self
.palette
= palette
3749 def attr(self
, index
):
3750 if self
.counter
!= 1:
3751 return self
.palette
.getcolor(index
/float(self
.counter
-1))
3753 return self
.palette
.getcolor(0)
3756 class _changecolorgray(changecolor
):
3758 def __init__(self
, palette
=color
.palette
.Gray
):
3759 changecolor
.__init
__(self
, palette
)
3761 _changecolorgrey
= _changecolorgray
3764 class _changecolorreversegray(changecolor
):
3766 def __init__(self
, palette
=color
.palette
.ReverseGray
):
3767 changecolor
.__init
__(self
, palette
)
3769 _changecolorreversegrey
= _changecolorreversegray
3772 class _changecolorredblack(changecolor
):
3774 def __init__(self
, palette
=color
.palette
.RedBlack
):
3775 changecolor
.__init
__(self
, palette
)
3778 class _changecolorblackred(changecolor
):
3780 def __init__(self
, palette
=color
.palette
.BlackRed
):
3781 changecolor
.__init
__(self
, palette
)
3784 class _changecolorredwhite(changecolor
):
3786 def __init__(self
, palette
=color
.palette
.RedWhite
):
3787 changecolor
.__init
__(self
, palette
)
3790 class _changecolorwhitered(changecolor
):
3792 def __init__(self
, palette
=color
.palette
.WhiteRed
):
3793 changecolor
.__init
__(self
, palette
)
3796 class _changecolorgreenblack(changecolor
):
3798 def __init__(self
, palette
=color
.palette
.GreenBlack
):
3799 changecolor
.__init
__(self
, palette
)
3802 class _changecolorblackgreen(changecolor
):
3804 def __init__(self
, palette
=color
.palette
.BlackGreen
):
3805 changecolor
.__init
__(self
, palette
)
3808 class _changecolorgreenwhite(changecolor
):
3810 def __init__(self
, palette
=color
.palette
.GreenWhite
):
3811 changecolor
.__init
__(self
, palette
)
3814 class _changecolorwhitegreen(changecolor
):
3816 def __init__(self
, palette
=color
.palette
.WhiteGreen
):
3817 changecolor
.__init
__(self
, palette
)
3820 class _changecolorblueblack(changecolor
):
3822 def __init__(self
, palette
=color
.palette
.BlueBlack
):
3823 changecolor
.__init
__(self
, palette
)
3826 class _changecolorblackblue(changecolor
):
3828 def __init__(self
, palette
=color
.palette
.BlackBlue
):
3829 changecolor
.__init
__(self
, palette
)
3832 class _changecolorbluewhite(changecolor
):
3834 def __init__(self
, palette
=color
.palette
.BlueWhite
):
3835 changecolor
.__init
__(self
, palette
)
3838 class _changecolorwhiteblue(changecolor
):
3840 def __init__(self
, palette
=color
.palette
.WhiteBlue
):
3841 changecolor
.__init
__(self
, palette
)
3844 class _changecolorredgreen(changecolor
):
3846 def __init__(self
, palette
=color
.palette
.RedGreen
):
3847 changecolor
.__init
__(self
, palette
)
3850 class _changecolorredblue(changecolor
):
3852 def __init__(self
, palette
=color
.palette
.RedBlue
):
3853 changecolor
.__init
__(self
, palette
)
3856 class _changecolorgreenred(changecolor
):
3858 def __init__(self
, palette
=color
.palette
.GreenRed
):
3859 changecolor
.__init
__(self
, palette
)
3862 class _changecolorgreenblue(changecolor
):
3864 def __init__(self
, palette
=color
.palette
.GreenBlue
):
3865 changecolor
.__init
__(self
, palette
)
3868 class _changecolorbluered(changecolor
):
3870 def __init__(self
, palette
=color
.palette
.BlueRed
):
3871 changecolor
.__init
__(self
, palette
)
3874 class _changecolorbluegreen(changecolor
):
3876 def __init__(self
, palette
=color
.palette
.BlueGreen
):
3877 changecolor
.__init
__(self
, palette
)
3880 class _changecolorrainbow(changecolor
):
3882 def __init__(self
, palette
=color
.palette
.Rainbow
):
3883 changecolor
.__init
__(self
, palette
)
3886 class _changecolorreverserainbow(changecolor
):
3888 def __init__(self
, palette
=color
.palette
.ReverseRainbow
):
3889 changecolor
.__init
__(self
, palette
)
3892 class _changecolorhue(changecolor
):
3894 def __init__(self
, palette
=color
.palette
.Hue
):
3895 changecolor
.__init
__(self
, palette
)
3898 class _changecolorreversehue(changecolor
):
3900 def __init__(self
, palette
=color
.palette
.ReverseHue
):
3901 changecolor
.__init
__(self
, palette
)
3904 changecolor
.Gray
= _changecolorgray
3905 changecolor
.Grey
= _changecolorgrey
3906 changecolor
.Reversegray
= _changecolorreversegray
3907 changecolor
.Reversegrey
= _changecolorreversegrey
3908 changecolor
.RedBlack
= _changecolorredblack
3909 changecolor
.BlackRed
= _changecolorblackred
3910 changecolor
.RedWhite
= _changecolorredwhite
3911 changecolor
.WhiteRed
= _changecolorwhitered
3912 changecolor
.GreenBlack
= _changecolorgreenblack
3913 changecolor
.BlackGreen
= _changecolorblackgreen
3914 changecolor
.GreenWhite
= _changecolorgreenwhite
3915 changecolor
.WhiteGreen
= _changecolorwhitegreen
3916 changecolor
.BlueBlack
= _changecolorblueblack
3917 changecolor
.BlackBlue
= _changecolorblackblue
3918 changecolor
.BlueWhite
= _changecolorbluewhite
3919 changecolor
.WhiteBlue
= _changecolorwhiteblue
3920 changecolor
.RedGreen
= _changecolorredgreen
3921 changecolor
.RedBlue
= _changecolorredblue
3922 changecolor
.GreenRed
= _changecolorgreenred
3923 changecolor
.GreenBlue
= _changecolorgreenblue
3924 changecolor
.BlueRed
= _changecolorbluered
3925 changecolor
.BlueGreen
= _changecolorbluegreen
3926 changecolor
.Rainbow
= _changecolorrainbow
3927 changecolor
.ReverseRainbow
= _changecolorreverserainbow
3928 changecolor
.Hue
= _changecolorhue
3929 changecolor
.ReverseHue
= _changecolorreversehue
3932 class changesequence(changeattr
):
3933 "cycles through a list"
3935 def __init__(self
, *sequence
):
3936 changeattr
.__init
__(self
)
3937 if not len(sequence
):
3938 sequence
= self
.defaultsequence
3939 self
.sequence
= sequence
3941 def attr(self
, index
):
3942 return self
.sequence
[index
% len(self
.sequence
)]
3945 class changelinestyle(changesequence
):
3946 defaultsequence
= (style
.linestyle
.solid
,
3947 style
.linestyle
.dashed
,
3948 style
.linestyle
.dotted
,
3949 style
.linestyle
.dashdotted
)
3952 class changestrokedfilled(changesequence
):
3953 defaultsequence
= (deco
.stroked(), deco
.filled())
3956 class changefilledstroked(changesequence
):
3957 defaultsequence
= (deco
.filled(), deco
.stroked())
3961 ################################################################################
3963 ################################################################################
3968 def cross(self
, x
, y
):
3969 return (path
._moveto
(x
-0.5*self
.size_pt
, y
-0.5*self
.size_pt
),
3970 path
._lineto
(x
+0.5*self
.size_pt
, y
+0.5*self
.size_pt
),
3971 path
._moveto
(x
-0.5*self
.size_pt
, y
+0.5*self
.size_pt
),
3972 path
._lineto
(x
+0.5*self
.size_pt
, y
-0.5*self
.size_pt
))
3974 def plus(self
, x
, y
):
3975 return (path
._moveto
(x
-0.707106781*self
.size_pt
, y
),
3976 path
._lineto
(x
+0.707106781*self
.size_pt
, y
),
3977 path
._moveto
(x
, y
-0.707106781*self
.size_pt
),
3978 path
._lineto
(x
, y
+0.707106781*self
.size_pt
))
3980 def square(self
, x
, y
):
3981 return (path
._moveto
(x
-0.5*self
.size_pt
, y
-0.5 * self
.size_pt
),
3982 path
._lineto
(x
+0.5*self
.size_pt
, y
-0.5 * self
.size_pt
),
3983 path
._lineto
(x
+0.5*self
.size_pt
, y
+0.5 * self
.size_pt
),
3984 path
._lineto
(x
-0.5*self
.size_pt
, y
+0.5 * self
.size_pt
),
3987 def triangle(self
, x
, y
):
3988 return (path
._moveto
(x
-0.759835685*self
.size_pt
, y
-0.438691337*self
.size_pt
),
3989 path
._lineto
(x
+0.759835685*self
.size_pt
, y
-0.438691337*self
.size_pt
),
3990 path
._lineto
(x
, y
+0.877382675*self
.size_pt
),
3993 def circle(self
, x
, y
):
3994 return (path
._arc
(x
, y
, 0.564189583*self
.size_pt
, 0, 360),
3997 def diamond(self
, x
, y
):
3998 return (path
._moveto
(x
-0.537284965*self
.size_pt
, y
),
3999 path
._lineto
(x
, y
-0.930604859*self
.size_pt
),
4000 path
._lineto
(x
+0.537284965*self
.size_pt
, y
),
4001 path
._lineto
(x
, y
+0.930604859*self
.size_pt
),
4004 def __init__(self
, symbol
=helper
.nodefault
,
4005 size
="0.2 cm", symbolattrs
=deco
.stroked(),
4006 errorscale
=0.5, errorbarattrs
=(),
4008 self
.size_str
= size
4009 if symbol
is helper
.nodefault
:
4010 self
._symbol
= changesymbol
.cross()
4012 self
._symbol
= symbol
4013 self
._symbolattrs
= symbolattrs
4014 self
.errorscale
= errorscale
4015 self
._errorbarattrs
= errorbarattrs
4016 self
._lineattrs
= lineattrs
4018 def iteratedict(self
):
4020 result
["symbol"] = _iterateattr(self
._symbol
)
4021 result
["size"] = _iterateattr(self
.size_str
)
4022 result
["symbolattrs"] = _iterateattrs(self
._symbolattrs
)
4023 result
["errorscale"] = _iterateattr(self
.errorscale
)
4024 result
["errorbarattrs"] = _iterateattrs(self
._errorbarattrs
)
4025 result
["lineattrs"] = _iterateattrs(self
._lineattrs
)
4029 return symbol(**self
.iteratedict())
4031 def othercolumnkey(self
, key
, index
):
4032 raise ValueError("unsuitable key '%s'" % key
)
4034 def setcolumns(self
, graph
, columns
):
4035 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
4037 match
= pattern
.match(key
)
4039 if isindex
is not None: raise ValueError("multiple key specification")
4040 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
4042 iskey
= match
.groups()[0]
4044 return key
, iskey
, isindex
4046 self
.xi
= self
.xmini
= self
.xmaxi
= None
4047 self
.dxi
= self
.dxmini
= self
.dxmaxi
= None
4048 self
.yi
= self
.ymini
= self
.ymaxi
= None
4049 self
.dyi
= self
.dymini
= self
.dymaxi
= None
4050 self
.xkey
= self
.ykey
= None
4051 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
4052 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
4053 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
4054 XMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
4055 YMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
4056 XMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
4057 YMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
4058 DXPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
4059 DYPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
4060 DXMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
4061 DYMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
4062 DXMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
4063 DYMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
4064 for key
, index
in columns
.items():
4065 key
, self
.xkey
, self
.xi
= checkpattern(key
, index
, XPattern
, self
.xkey
, self
.xi
)
4066 key
, self
.ykey
, self
.yi
= checkpattern(key
, index
, YPattern
, self
.ykey
, self
.yi
)
4067 key
, self
.xkey
, self
.xmini
= checkpattern(key
, index
, XMinPattern
, self
.xkey
, self
.xmini
)
4068 key
, self
.ykey
, self
.ymini
= checkpattern(key
, index
, YMinPattern
, self
.ykey
, self
.ymini
)
4069 key
, self
.xkey
, self
.xmaxi
= checkpattern(key
, index
, XMaxPattern
, self
.xkey
, self
.xmaxi
)
4070 key
, self
.ykey
, self
.ymaxi
= checkpattern(key
, index
, YMaxPattern
, self
.ykey
, self
.ymaxi
)
4071 key
, self
.xkey
, self
.dxi
= checkpattern(key
, index
, DXPattern
, self
.xkey
, self
.dxi
)
4072 key
, self
.ykey
, self
.dyi
= checkpattern(key
, index
, DYPattern
, self
.ykey
, self
.dyi
)
4073 key
, self
.xkey
, self
.dxmini
= checkpattern(key
, index
, DXMinPattern
, self
.xkey
, self
.dxmini
)
4074 key
, self
.ykey
, self
.dymini
= checkpattern(key
, index
, DYMinPattern
, self
.ykey
, self
.dymini
)
4075 key
, self
.xkey
, self
.dxmaxi
= checkpattern(key
, index
, DXMaxPattern
, self
.xkey
, self
.dxmaxi
)
4076 key
, self
.ykey
, self
.dymaxi
= checkpattern(key
, index
, DYMaxPattern
, self
.ykey
, self
.dymaxi
)
4078 self
.othercolumnkey(key
, index
)
4079 if None in (self
.xkey
, self
.ykey
): raise ValueError("incomplete axis specification")
4080 if (len(filter(None, (self
.xmini
, self
.dxmini
, self
.dxi
))) > 1 or
4081 len(filter(None, (self
.ymini
, self
.dymini
, self
.dyi
))) > 1 or
4082 len(filter(None, (self
.xmaxi
, self
.dxmaxi
, self
.dxi
))) > 1 or
4083 len(filter(None, (self
.ymaxi
, self
.dymaxi
, self
.dyi
))) > 1):
4084 raise ValueError("multiple errorbar definition")
4085 if ((self
.xi
is None and self
.dxi
is not None) or
4086 (self
.yi
is None and self
.dyi
is not None) or
4087 (self
.xi
is None and self
.dxmini
is not None) or
4088 (self
.yi
is None and self
.dymini
is not None) or
4089 (self
.xi
is None and self
.dxmaxi
is not None) or
4090 (self
.yi
is None and self
.dymaxi
is not None)):
4091 raise ValueError("errorbar definition start value missing")
4092 self
.xaxis
= graph
.axes
[self
.xkey
]
4093 self
.yaxis
= graph
.axes
[self
.ykey
]
4095 def minmidmax(self
, point
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
4096 min = max = mid
= None
4098 mid
= point
[i
] + 0.0
4099 except (TypeError, ValueError):
4102 if di
is not None: min = point
[i
] - point
[di
]
4103 elif dmini
is not None: min = point
[i
] - point
[dmini
]
4104 elif mini
is not None: min = point
[mini
] + 0.0
4105 except (TypeError, ValueError):
4108 if di
is not None: max = point
[i
] + point
[di
]
4109 elif dmaxi
is not None: max = point
[i
] + point
[dmaxi
]
4110 elif maxi
is not None: max = point
[maxi
] + 0.0
4111 except (TypeError, ValueError):
4114 if min is not None and min > mid
: raise ValueError("minimum error in errorbar")
4115 if max is not None and max < mid
: raise ValueError("maximum error in errorbar")
4117 if min is not None and max is not None and min > max: raise ValueError("minimum/maximum error in errorbar")
4118 return min, mid
, max
4120 def keyrange(self
, points
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
4121 allmin
= allmax
= None
4122 if filter(None, (mini
, maxi
, di
, dmini
, dmaxi
)) is not None:
4123 for point
in points
:
4124 min, mid
, max = self
.minmidmax(point
, i
, mini
, maxi
, di
, dmini
, dmaxi
)
4125 if min is not None and (allmin
is None or min < allmin
): allmin
= min
4126 if mid
is not None and (allmin
is None or mid
< allmin
): allmin
= mid
4127 if mid
is not None and (allmax
is None or mid
> allmax
): allmax
= mid
4128 if max is not None and (allmax
is None or max > allmax
): allmax
= max
4130 for point
in points
:
4132 value
= point
[i
] + 0.0
4133 if allmin
is None or point
[i
] < allmin
: allmin
= point
[i
]
4134 if allmax
is None or point
[i
] > allmax
: allmax
= point
[i
]
4135 except (TypeError, ValueError):
4137 return allmin
, allmax
4139 def getranges(self
, points
):
4140 xmin
, xmax
= self
.keyrange(points
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
4141 ymin
, ymax
= self
.keyrange(points
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
4142 return {self
.xkey
: (xmin
, xmax
), self
.ykey
: (ymin
, ymax
)}
4144 def drawerrorbar_pt(self
, graph
, topleft
, top
, topright
,
4145 left
, center
, right
,
4146 bottomleft
, bottom
, bottomright
, point
=None):
4147 if left
is not None:
4148 if right
is not None:
4149 left1
= graph
._addpos
(*(left
+(0, -self
.errorsize_pt
)))
4150 left2
= graph
._addpos
(*(left
+(0, self
.errorsize_pt
)))
4151 right1
= graph
._addpos
(*(right
+(0, -self
.errorsize_pt
)))
4152 right2
= graph
._addpos
(*(right
+(0, self
.errorsize_pt
)))
4153 graph
.stroke(path
.path(path
._moveto
(*left1
),
4154 graph
._connect
(*(left1
+left2
)),
4155 path
._moveto
(*left
),
4156 graph
._connect
(*(left
+right
)),
4157 path
._moveto
(*right1
),
4158 graph
._connect
(*(right1
+right2
))),
4159 *self
.errorbarattrs
)
4160 elif center
is not None:
4161 left1
= graph
._addpos
(*(left
+(0, -self
.errorsize_pt
)))
4162 left2
= graph
._addpos
(*(left
+(0, self
.errorsize_pt
)))
4163 graph
.stroke(path
.path(path
._moveto
(*left1
),
4164 graph
._connect
(*(left1
+left2
)),
4165 path
._moveto
(*left
),
4166 graph
._connect
(*(left
+center
))),
4167 *self
.errorbarattrs
)
4169 left1
= graph
._addpos
(*(left
+(0, -self
.errorsize_pt
)))
4170 left2
= graph
._addpos
(*(left
+(0, self
.errorsize_pt
)))
4171 left3
= graph
._addpos
(*(left
+(self
.errorsize_pt
, 0)))
4172 graph
.stroke(path
.path(path
._moveto
(*left1
),
4173 graph
._connect
(*(left1
+left2
)),
4174 path
._moveto
(*left
),
4175 graph
._connect
(*(left
+left3
))),
4176 *self
.errorbarattrs
)
4177 if right
is not None and left
is None:
4178 if center
is not None:
4179 right1
= graph
._addpos
(*(right
+(0, -self
.errorsize_pt
)))
4180 right2
= graph
._addpos
(*(right
+(0, self
.errorsize_pt
)))
4181 graph
.stroke(path
.path(path
._moveto
(*right1
),
4182 graph
._connect
(*(right1
+right2
)),
4183 path
._moveto
(*right
),
4184 graph
._connect
(*(right
+center
))),
4185 *self
.errorbarattrs
)
4187 right1
= graph
._addpos
(*(right
+(0, -self
.errorsize_pt
)))
4188 right2
= graph
._addpos
(*(right
+(0, self
.errorsize_pt
)))
4189 right3
= graph
._addpos
(*(right
+(-self
.errorsize_pt
, 0)))
4190 graph
.stroke(path
.path(path
._moveto
(*right1
),
4191 graph
._connect
(*(right1
+right2
)),
4192 path
._moveto
(*right
),
4193 graph
._connect
(*(right
+right3
))),
4194 *self
.errorbarattrs
)
4196 if bottom
is not None:
4198 bottom1
= graph
._addpos
(*(bottom
+(-self
.errorsize_pt
, 0)))
4199 bottom2
= graph
._addpos
(*(bottom
+(self
.errorsize_pt
, 0)))
4200 top1
= graph
._addpos
(*(top
+(-self
.errorsize_pt
, 0)))
4201 top2
= graph
._addpos
(*(top
+(self
.errorsize_pt
, 0)))
4202 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
4203 graph
._connect
(*(bottom1
+bottom2
)),
4204 path
._moveto
(*bottom
),
4205 graph
._connect
(*(bottom
+top
)),
4206 path
._moveto
(*top1
),
4207 graph
._connect
(*(top1
+top2
))),
4208 *self
.errorbarattrs
)
4209 elif center
is not None:
4210 bottom1
= graph
._addpos
(*(bottom
+(-self
.errorsize_pt
, 0)))
4211 bottom2
= graph
._addpos
(*(bottom
+(self
.errorsize_pt
, 0)))
4212 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
4213 graph
._connect
(*(bottom1
+bottom2
)),
4214 path
._moveto
(*bottom
),
4215 graph
._connect
(*(bottom
+center
))),
4216 *self
.errorbarattrs
)
4218 bottom1
= graph
._addpos
(*(bottom
+(-self
.errorsize_pt
, 0)))
4219 bottom2
= graph
._addpos
(*(bottom
+(self
.errorsize_pt
, 0)))
4220 bottom3
= graph
._addpos
(*(bottom
+(0, self
.errorsize_pt
)))
4221 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
4222 graph
._connect
(*(bottom1
+bottom2
)),
4223 path
._moveto
(*bottom
),
4224 graph
._connect
(*(bottom
+bottom3
))),
4225 *self
.errorbarattrs
)
4226 if top
is not None and bottom
is None:
4227 if center
is not None:
4228 top1
= graph
._addpos
(*(top
+(-self
.errorsize_pt
, 0)))
4229 top2
= graph
._addpos
(*(top
+(self
.errorsize_pt
, 0)))
4230 graph
.stroke(path
.path(path
._moveto
(*top1
),
4231 graph
._connect
(*(top1
+top2
)),
4233 graph
._connect
(*(top
+center
))),
4234 *self
.errorbarattrs
)
4236 top1
= graph
._addpos
(*(top
+(-self
.errorsize_pt
, 0)))
4237 top2
= graph
._addpos
(*(top
+(self
.errorsize_pt
, 0)))
4238 top3
= graph
._addpos
(*(top
+(0, -self
.errorsize_pt
)))
4239 graph
.stroke(path
.path(path
._moveto
(*top1
),
4240 graph
._connect
(*(top1
+top2
)),
4242 graph
._connect
(*(top
+top3
))),
4243 *self
.errorbarattrs
)
4244 if bottomleft
is not None:
4245 if topleft
is not None and bottomright
is None:
4246 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
.errorsize_pt
, 0)))
4247 topleft1
= graph
._addpos
(*(topleft
+(self
.errorsize_pt
, 0)))
4248 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
4249 graph
._connect
(*(bottomleft1
+bottomleft
)),
4250 graph
._connect
(*(bottomleft
+topleft
)),
4251 graph
._connect
(*(topleft
+topleft1
))),
4252 *self
.errorbarattrs
)
4253 elif bottomright
is not None and topleft
is None:
4254 bottomleft1
= graph
._addpos
(*(bottomleft
+(0, self
.errorsize_pt
)))
4255 bottomright1
= graph
._addpos
(*(bottomright
+(0, self
.errorsize_pt
)))
4256 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
4257 graph
._connect
(*(bottomleft1
+bottomleft
)),
4258 graph
._connect
(*(bottomleft
+bottomright
)),
4259 graph
._connect
(*(bottomright
+bottomright1
))),
4260 *self
.errorbarattrs
)
4261 elif bottomright
is None and topleft
is None:
4262 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
.errorsize_pt
, 0)))
4263 bottomleft2
= graph
._addpos
(*(bottomleft
+(0, self
.errorsize_pt
)))
4264 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
4265 graph
._connect
(*(bottomleft1
+bottomleft
)),
4266 graph
._connect
(*(bottomleft
+bottomleft2
))),
4267 *self
.errorbarattrs
)
4268 if topright
is not None:
4269 if bottomright
is not None and topleft
is None:
4270 topright1
= graph
._addpos
(*(topright
+(-self
.errorsize_pt
, 0)))
4271 bottomright1
= graph
._addpos
(*(bottomright
+(-self
.errorsize_pt
, 0)))
4272 graph
.stroke(path
.path(path
._moveto
(*topright1
),
4273 graph
._connect
(*(topright1
+topright
)),
4274 graph
._connect
(*(topright
+bottomright
)),
4275 graph
._connect
(*(bottomright
+bottomright1
))),
4276 *self
.errorbarattrs
)
4277 elif topleft
is not None and bottomright
is None:
4278 topright1
= graph
._addpos
(*(topright
+(0, -self
.errorsize_pt
)))
4279 topleft1
= graph
._addpos
(*(topleft
+(0, -self
.errorsize_pt
)))
4280 graph
.stroke(path
.path(path
._moveto
(*topright1
),
4281 graph
._connect
(*(topright1
+topright
)),
4282 graph
._connect
(*(topright
+topleft
)),
4283 graph
._connect
(*(topleft
+topleft1
))),
4284 *self
.errorbarattrs
)
4285 elif topleft
is None and bottomright
is None:
4286 topright1
= graph
._addpos
(*(topright
+(-self
.errorsize_pt
, 0)))
4287 topright2
= graph
._addpos
(*(topright
+(0, -self
.errorsize_pt
)))
4288 graph
.stroke(path
.path(path
._moveto
(*topright1
),
4289 graph
._connect
(*(topright1
+topright
)),
4290 graph
._connect
(*(topright
+topright2
))),
4291 *self
.errorbarattrs
)
4292 if bottomright
is not None and bottomleft
is None and topright
is None:
4293 bottomright1
= graph
._addpos
(*(bottomright
+(-self
.errorsize_pt
, 0)))
4294 bottomright2
= graph
._addpos
(*(bottomright
+(0, self
.errorsize_pt
)))
4295 graph
.stroke(path
.path(path
._moveto
(*bottomright1
),
4296 graph
._connect
(*(bottomright1
+bottomright
)),
4297 graph
._connect
(*(bottomright
+bottomright2
))),
4298 *self
.errorbarattrs
)
4299 if topleft
is not None and bottomleft
is None and topright
is None:
4300 topleft1
= graph
._addpos
(*(topleft
+(self
.errorsize_pt
, 0)))
4301 topleft2
= graph
._addpos
(*(topleft
+(0, -self
.errorsize_pt
)))
4302 graph
.stroke(path
.path(path
._moveto
(*topleft1
),
4303 graph
._connect
(*(topleft1
+topleft
)),
4304 graph
._connect
(*(topleft
+topleft2
))),
4305 *self
.errorbarattrs
)
4306 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
4307 graph
.stroke(path
.path(path
._moveto
(*bottomleft
),
4308 graph
._connect
(*(bottomleft
+bottomright
)),
4309 graph
._connect
(*(bottomright
+topright
)),
4310 graph
._connect
(*(topright
+topleft
)),
4312 *self
.errorbarattrs
)
4314 def drawsymbol_pt(self
, canvas
, x
, y
, point
=None):
4315 canvas
.draw(path
.path(*self
.symbol(self
, x
, y
)), *self
.symbolattrs
)
4317 def drawsymbol(self
, canvas
, x
, y
, point
=None):
4318 self
.drawsymbol_pt(canvas
, unit
.topt(x
), unit
.topt(y
), point
)
4320 def key(self
, c
, x
, y
, width
, height
):
4321 if self
._symbolattrs
is not None:
4322 self
.drawsymbol_pt(c
, x
+ 0.5 * width
, y
+ 0.5 * height
)
4323 if self
._lineattrs
is not None:
4324 c
.stroke(path
._line
(x
, y
+ 0.5 * height
, x
+ width
, y
+ 0.5 * height
), *self
.lineattrs
)
4326 def drawpoints(self
, graph
, points
):
4327 xaxismin
, xaxismax
= self
.xaxis
.getrange()
4328 yaxismin
, yaxismax
= self
.yaxis
.getrange()
4329 self
.size
= unit
.length(_getattr(self
.size_str
), default_type
="v")
4330 self
.size_pt
= unit
.topt(self
.size
)
4331 self
.symbol
= _getattr(self
._symbol
)
4332 self
.symbolattrs
= _getattrs(helper
.ensuresequence(self
._symbolattrs
))
4333 self
.errorbarattrs
= _getattrs(helper
.ensuresequence(self
._errorbarattrs
))
4334 self
.errorsize_pt
= self
.errorscale
* self
.size_pt
4335 self
.errorsize
= self
.errorscale
* self
.size
4336 self
.lineattrs
= _getattrs(helper
.ensuresequence(self
._lineattrs
))
4337 if self
._lineattrs
is not None:
4338 clipcanvas
= graph
.clipcanvas()
4340 haserror
= filter(None, (self
.xmini
, self
.ymini
, self
.xmaxi
, self
.ymaxi
,
4341 self
.dxi
, self
.dyi
, self
.dxmini
, self
.dymini
, self
.dxmaxi
, self
.dymaxi
)) is not None
4343 for point
in points
:
4345 xmin
, x
, xmax
= self
.minmidmax(point
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
4346 ymin
, y
, ymax
= self
.minmidmax(point
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
4347 if x
is not None and x
< xaxismin
: drawsymbol
= 0
4348 elif x
is not None and x
> xaxismax
: drawsymbol
= 0
4349 elif y
is not None and y
< yaxismin
: drawsymbol
= 0
4350 elif y
is not None and y
> yaxismax
: drawsymbol
= 0
4351 # elif haserror: # TODO: correct clipcanvas handling
4352 # if xmin is not None and xmin < xaxismin: drawsymbol = 0
4353 # elif xmax is not None and xmax < xaxismin: drawsymbol = 0
4354 # elif xmax is not None and xmax > xaxismax: drawsymbol = 0
4355 # elif xmin is not None and xmin > xaxismax: drawsymbol = 0
4356 # elif ymin is not None and ymin < yaxismin: drawsymbol = 0
4357 # elif ymax is not None and ymax < yaxismin: drawsymbol = 0
4358 # elif ymax is not None and ymax > yaxismax: drawsymbol = 0
4359 # elif ymin is not None and ymin > yaxismax: drawsymbol = 0
4360 xpos
=ypos
=topleft
=top
=topright
=left
=center
=right
=bottomleft
=bottom
=bottomright
=None
4361 if x
is not None and y
is not None:
4363 center
= xpos
, ypos
= graph
.pos_pt(x
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4364 except (ValueError, OverflowError): # XXX: exceptions???
4368 if xmin
is not None: left
= graph
.pos_pt(xmin
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4369 if xmax
is not None: right
= graph
.pos_pt(xmax
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4371 if ymax
is not None: top
= graph
.pos_pt(x
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4372 if ymin
is not None: bottom
= graph
.pos_pt(x
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4373 if x
is None or y
is None:
4374 if ymax
is not None:
4375 if xmin
is not None: topleft
= graph
.pos_pt(xmin
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4376 if xmax
is not None: topright
= graph
.pos_pt(xmax
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4377 if ymin
is not None:
4378 if xmin
is not None: bottomleft
= graph
.pos_pt(xmin
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4379 if xmax
is not None: bottomright
= graph
.pos_pt(xmax
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4381 if self
._errorbarattrs
is not None and haserror
:
4382 self
.drawerrorbar_pt(graph
, topleft
, top
, topright
,
4383 left
, center
, right
,
4384 bottomleft
, bottom
, bottomright
, point
)
4385 if self
._symbolattrs
is not None and xpos
is not None and ypos
is not None:
4386 self
.drawsymbol_pt(graph
, xpos
, ypos
, point
)
4387 if xpos
is not None and ypos
is not None:
4389 lineels
.append(path
._moveto
(xpos
, ypos
))
4392 lineels
.append(path
._lineto
(xpos
, ypos
))
4395 self
.path
= path
.path(*lineels
)
4396 if self
._lineattrs
is not None:
4397 clipcanvas
.stroke(self
.path
, *self
.lineattrs
)
4400 class changesymbol(changesequence
): pass
4403 class _changesymbolcross(changesymbol
):
4404 defaultsequence
= (symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
)
4407 class _changesymbolplus(changesymbol
):
4408 defaultsequence
= (symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
)
4411 class _changesymbolsquare(changesymbol
):
4412 defaultsequence
= (symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
)
4415 class _changesymboltriangle(changesymbol
):
4416 defaultsequence
= (symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
)
4419 class _changesymbolcircle(changesymbol
):
4420 defaultsequence
= (symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
)
4423 class _changesymboldiamond(changesymbol
):
4424 defaultsequence
= (symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
)
4427 class _changesymbolsquaretwice(changesymbol
):
4428 defaultsequence
= (symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
,
4429 symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
)
4432 class _changesymboltriangletwice(changesymbol
):
4433 defaultsequence
= (symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
,
4434 symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
)
4437 class _changesymbolcircletwice(changesymbol
):
4438 defaultsequence
= (symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
,
4439 symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
)
4442 class _changesymboldiamondtwice(changesymbol
):
4443 defaultsequence
= (symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
,
4444 symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
)
4447 changesymbol
.cross
= _changesymbolcross
4448 changesymbol
.plus
= _changesymbolplus
4449 changesymbol
.square
= _changesymbolsquare
4450 changesymbol
.triangle
= _changesymboltriangle
4451 changesymbol
.circle
= _changesymbolcircle
4452 changesymbol
.diamond
= _changesymboldiamond
4453 changesymbol
.squaretwice
= _changesymbolsquaretwice
4454 changesymbol
.triangletwice
= _changesymboltriangletwice
4455 changesymbol
.circletwice
= _changesymbolcircletwice
4456 changesymbol
.diamondtwice
= _changesymboldiamondtwice
4461 def __init__(self
, lineattrs
=helper
.nodefault
):
4462 if lineattrs
is helper
.nodefault
:
4463 lineattrs
= (changelinestyle(), style
.linejoin
.round)
4464 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=None, lineattrs
=lineattrs
)
4469 def __init__(self
, palette
=color
.palette
.Gray
):
4470 self
.palette
= palette
4471 self
.colorindex
= None
4472 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=(), lineattrs
=None)
4475 raise RuntimeError("style is not iterateable")
4477 def othercolumnkey(self
, key
, index
):
4479 self
.colorindex
= index
4481 symbol
.othercolumnkey(self
, key
, index
)
4483 def drawerrorbar_pt(self
, graph
, topleft
, top
, topright
,
4484 left
, center
, right
,
4485 bottomleft
, bottom
, bottomright
, point
=None):
4486 color
= point
[self
.colorindex
]
4487 if color
is not None:
4488 if color
!= self
.lastcolor
:
4489 self
.rectclipcanvas
.set(self
.palette
.getcolor(color
))
4490 if bottom
is not None and left
is not None:
4491 bottomleft
= left
[0], bottom
[1]
4492 if bottom
is not None and right
is not None:
4493 bottomright
= right
[0], bottom
[1]
4494 if top
is not None and right
is not None:
4495 topright
= right
[0], top
[1]
4496 if top
is not None and left
is not None:
4497 topleft
= left
[0], top
[1]
4498 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
4499 self
.rectclipcanvas
.fill(path
.path(path
._moveto
(*bottomleft
),
4500 graph
._connect
(*(bottomleft
+bottomright
)),
4501 graph
._connect
(*(bottomright
+topright
)),
4502 graph
._connect
(*(topright
+topleft
)),
4505 def drawpoints(self
, graph
, points
):
4506 if self
.colorindex
is None:
4507 raise RuntimeError("column 'color' not set")
4508 self
.lastcolor
= None
4509 self
.rectclipcanvas
= graph
.clipcanvas()
4510 symbol
.drawpoints(self
, graph
, points
)
4512 def key(self
, c
, x
, y
, width
, height
):
4513 raise RuntimeError("style doesn't yet provide a key")
4518 def __init__(self
, textdx
="0", textdy
="0.3 cm", textattrs
=textmodule
.halign
.center
, **args
):
4519 self
.textindex
= None
4520 self
.textdx_str
= textdx
4521 self
.textdy_str
= textdy
4522 self
._textattrs
= textattrs
4523 symbol
.__init
__(self
, **args
)
4525 def iteratedict(self
):
4526 result
= symbol
.iteratedict()
4527 result
["textattrs"] = _iterateattr(self
._textattrs
)
4531 return textsymbol(**self
.iteratedict())
4533 def othercolumnkey(self
, key
, index
):
4535 self
.textindex
= index
4537 symbol
.othercolumnkey(self
, key
, index
)
4539 def drawsymbol_pt(self
, graph
, x
, y
, point
=None):
4540 symbol
.drawsymbol_pt(self
, graph
, x
, y
, point
)
4541 if None not in (x
, y
, point
[self
.textindex
]) and self
._textattrs
is not None:
4542 graph
.text_pt(x
+ self
.textdx_pt
, y
+ self
.textdy_pt
, str(point
[self
.textindex
]), *helper
.ensuresequence(self
.textattrs
))
4544 def drawpoints(self
, graph
, points
):
4545 self
.textdx
= unit
.length(_getattr(self
.textdx_str
), default_type
="v")
4546 self
.textdy
= unit
.length(_getattr(self
.textdy_str
), default_type
="v")
4547 self
.textdx_pt
= unit
.topt(self
.textdx
)
4548 self
.textdy_pt
= unit
.topt(self
.textdy
)
4549 if self
._textattrs
is not None:
4550 self
.textattrs
= _getattr(self
._textattrs
)
4551 if self
.textindex
is None:
4552 raise RuntimeError("column 'text' not set")
4553 symbol
.drawpoints(self
, graph
, points
)
4555 def key(self
, c
, x
, y
, width
, height
):
4556 raise RuntimeError("style doesn't yet provide a key")
4559 class arrow(symbol
):
4561 def __init__(self
, linelength
="0.2 cm", arrowattrs
=(), arrowsize
="0.1 cm", arrowdict
={}, epsilon
=1e-10):
4562 self
.linelength_str
= linelength
4563 self
.arrowsize_str
= arrowsize
4564 self
.arrowattrs
= arrowattrs
4565 self
.arrowdict
= arrowdict
4566 self
.epsilon
= epsilon
4567 self
.sizeindex
= self
.angleindex
= None
4568 symbol
.__init
__(self
, symbolattrs
=(), errorbarattrs
=None, lineattrs
=None)
4571 raise RuntimeError("style is not iterateable")
4573 def othercolumnkey(self
, key
, index
):
4575 self
.sizeindex
= index
4576 elif key
== "angle":
4577 self
.angleindex
= index
4579 symbol
.othercolumnkey(self
, key
, index
)
4581 def drawsymbol_pt(self
, graph
, x
, y
, point
=None):
4582 if None not in (x
, y
, point
[self
.angleindex
], point
[self
.sizeindex
], self
.arrowattrs
, self
.arrowdict
):
4583 if point
[self
.sizeindex
] > self
.epsilon
:
4584 dx
, dy
= math
.cos(point
[self
.angleindex
]*math
.pi
/180.0), math
.sin(point
[self
.angleindex
]*math
.pi
/180)
4585 x1
= unit
.t_pt(x
)-0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
4586 y1
= unit
.t_pt(y
)-0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
4587 x2
= unit
.t_pt(x
)+0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
4588 y2
= unit
.t_pt(y
)+0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
4589 graph
.stroke(path
.line(x1
, y1
, x2
, y2
),
4590 deco
.earrow(self
.arrowsize
*point
[self
.sizeindex
],
4592 *helper
.ensuresequence(self
.arrowattrs
))
4594 def drawpoints(self
, graph
, points
):
4595 self
.arrowsize
= unit
.length(_getattr(self
.arrowsize_str
), default_type
="v")
4596 self
.linelength
= unit
.length(_getattr(self
.linelength_str
), default_type
="v")
4597 self
.arrowsize_pt
= unit
.topt(self
.arrowsize
)
4598 self
.linelength_pt
= unit
.topt(self
.linelength
)
4599 if self
.sizeindex
is None:
4600 raise RuntimeError("column 'size' not set")
4601 if self
.angleindex
is None:
4602 raise RuntimeError("column 'angle' not set")
4603 symbol
.drawpoints(self
, graph
, points
)
4605 def key(self
, c
, x
, y
, width
, height
):
4606 raise RuntimeError("style doesn't yet provide a key")
4609 class _bariterator(changeattr
):
4611 def attr(self
, index
):
4612 return index
, self
.counter
4617 def __init__(self
, fromzero
=1, stacked
=0, skipmissing
=1, xbar
=0,
4618 barattrs
=helper
.nodefault
, _usebariterator
=helper
.nodefault
, _previousbar
=None):
4619 self
.fromzero
= fromzero
4620 self
.stacked
= stacked
4621 self
.skipmissing
= skipmissing
4623 if barattrs
is helper
.nodefault
:
4624 self
._barattrs
= (deco
.stroked(color
.gray
.black
), changecolor
.Rainbow())
4626 self
._barattrs
= barattrs
4627 if _usebariterator
is helper
.nodefault
:
4628 self
.bariterator
= _bariterator()
4630 self
.bariterator
= _usebariterator
4631 self
.previousbar
= _previousbar
4633 def iteratedict(self
):
4635 result
["barattrs"] = _iterateattrs(self
._barattrs
)
4639 return bar(fromzero
=self
.fromzero
, stacked
=self
.stacked
, xbar
=self
.xbar
,
4640 _usebariterator
=_iterateattr(self
.bariterator
), _previousbar
=self
, **self
.iteratedict())
4642 def setcolumns(self
, graph
, columns
):
4643 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
4645 match
= pattern
.match(key
)
4647 if isindex
is not None: raise ValueError("multiple key specification")
4648 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
4650 iskey
= match
.groups()[0]
4652 return key
, iskey
, isindex
4655 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
4656 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
4657 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
4659 for key
, index
in columns
.items():
4660 key
, xkey
, xi
= checkpattern(key
, index
, XPattern
, xkey
, xi
)
4661 key
, ykey
, yi
= checkpattern(key
, index
, YPattern
, ykey
, yi
)
4663 self
.othercolumnkey(key
, index
)
4664 if None in (xkey
, ykey
): raise ValueError("incomplete axis specification")
4666 self
.nkey
, self
.ni
= ykey
, yi
4667 self
.vkey
, self
.vi
= xkey
, xi
4669 self
.nkey
, self
.ni
= xkey
, xi
4670 self
.vkey
, self
.vi
= ykey
, yi
4671 self
.naxis
, self
.vaxis
= graph
.axes
[self
.nkey
], graph
.axes
[self
.vkey
]
4673 def getranges(self
, points
):
4674 index
, count
= _getattr(self
.bariterator
)
4675 if count
!= 1 and self
.stacked
!= 1:
4676 if self
.stacked
> 1:
4677 index
= divmod(index
, self
.stacked
)[0]
4680 for point
in points
:
4681 if not self
.skipmissing
:
4682 if count
!= 1 and self
.stacked
!= 1:
4683 self
.naxis
.setname(point
[self
.ni
], index
)
4685 self
.naxis
.setname(point
[self
.ni
])
4687 v
= point
[self
.vi
] + 0.0
4688 if vmin
is None or v
< vmin
: vmin
= v
4689 if vmax
is None or v
> vmax
: vmax
= v
4690 except (TypeError, ValueError):
4693 if self
.skipmissing
:
4694 if count
!= 1 and self
.stacked
!= 1:
4695 self
.naxis
.setname(point
[self
.ni
], index
)
4697 self
.naxis
.setname(point
[self
.ni
])
4699 if vmin
> 0: vmin
= 0
4700 if vmax
< 0: vmax
= 0
4701 return {self
.vkey
: (vmin
, vmax
)}
4703 def drawpoints(self
, graph
, points
):
4704 index
, count
= _getattr(self
.bariterator
)
4705 dostacked
= (self
.stacked
!= 0 and
4706 (self
.stacked
== 1 or divmod(index
, self
.stacked
)[1]) and
4707 (self
.stacked
!= 1 or index
))
4708 if self
.stacked
> 1:
4709 index
= divmod(index
, self
.stacked
)[0]
4710 vmin
, vmax
= self
.vaxis
.getrange()
4711 self
.barattrs
= _getattrs(helper
.ensuresequence(self
._barattrs
))
4713 self
.stackedvalue
= {}
4714 for point
in points
:
4719 self
.stackedvalue
[n
] = v
4720 if count
!= 1 and self
.stacked
!= 1:
4721 minid
= (n
, index
, 0)
4722 maxid
= (n
, index
, 1)
4727 x1pos
, y1pos
= graph
.pos_pt(v
, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4728 x2pos
, y2pos
= graph
.pos_pt(v
, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4730 x1pos
, y1pos
= graph
.pos_pt(minid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4731 x2pos
, y2pos
= graph
.pos_pt(maxid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4734 x3pos
, y3pos
= graph
.pos_pt(self
.previousbar
.stackedvalue
[n
], maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4735 x4pos
, y4pos
= graph
.pos_pt(self
.previousbar
.stackedvalue
[n
], minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4737 x3pos
, y3pos
= graph
.pos_pt(maxid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4738 x4pos
, y4pos
= graph
.pos_pt(minid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4742 x3pos
, y3pos
= graph
.pos_pt(0, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4743 x4pos
, y4pos
= graph
.pos_pt(0, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4745 x3pos
, y3pos
= graph
.pos_pt(maxid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4746 x4pos
, y4pos
= graph
.pos_pt(minid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4748 #x3pos, y3pos = graph.tickpoint_pt(maxid, axis=self.naxis)
4749 #x4pos, y4pos = graph.tickpoint_pt(minid, axis=self.naxis)
4750 x3pos
, y3pos
= graph
.axespos
[self
.nkey
].tickpoint_pt(maxid
)
4751 x4pos
, y4pos
= graph
.axespos
[self
.nkey
].tickpoint_pt(minid
)
4752 if self
.barattrs
is not None:
4753 graph
.fill(path
.path(path
._moveto
(x1pos
, y1pos
),
4754 graph
._connect
(x1pos
, y1pos
, x2pos
, y2pos
),
4755 graph
._connect
(x2pos
, y2pos
, x3pos
, y3pos
),
4756 graph
._connect
(x3pos
, y3pos
, x4pos
, y4pos
),
4757 graph
._connect
(x4pos
, y4pos
, x1pos
, y1pos
), # no closepath (might not be straight)
4758 path
.closepath()), *self
.barattrs
)
4759 except (TypeError, ValueError): pass
4761 def key(self
, c
, x
, y
, width
, height
):
4762 c
.fill(path
._rect
(x
, y
, width
, height
), *self
.barattrs
)
4767 # def setcolumns(self, graph, columns):
4768 # self.columns = columns
4770 # def getranges(self, points):
4771 # return {"x": (0, 10), "y": (0, 10), "z": (0, 1)}
4773 # def drawpoints(self, graph, points):
4778 ################################################################################
4780 ################################################################################
4785 defaultstyle
= symbol
4787 def __init__(self
, file, title
=helper
.nodefault
, context
={}, **columns
):
4789 if helper
.isstring(file):
4790 self
.data
= datamodule
.datafile(file)
4793 if title
is helper
.nodefault
:
4794 self
.title
= "(unknown)"
4798 for key
, column
in columns
.items():
4800 self
.columns
[key
] = self
.data
.getcolumnno(column
)
4801 except datamodule
.ColumnError
:
4802 self
.columns
[key
] = len(self
.data
.titles
)
4803 self
.data
.addcolumn(column
, context
=context
)
4805 def setstyle(self
, graph
, style
):
4807 self
.style
.setcolumns(graph
, self
.columns
)
4809 def getranges(self
):
4810 return self
.style
.getranges(self
.data
.data
)
4812 def setranges(self
, ranges
):
4815 def draw(self
, graph
):
4816 self
.style
.drawpoints(graph
, self
.data
.data
)
4823 def __init__(self
, expression
, title
=helper
.nodefault
, min=None, max=None, points
=100, parser
=mathtree
.parser(), context
={}):
4824 if title
is helper
.nodefault
:
4825 self
.title
= expression
4830 self
.points
= points
4831 self
.context
= context
4832 self
.result
, expression
= [x
.strip() for x
in expression
.split("=")]
4833 self
.mathtree
= parser
.parse(expression
)
4834 self
.variable
= None
4837 def setstyle(self
, graph
, style
):
4838 for variable
in self
.mathtree
.VarList():
4839 if variable
in graph
.axes
.keys():
4840 if self
.variable
is None:
4841 self
.variable
= variable
4843 raise ValueError("multiple variables found")
4844 if self
.variable
is None:
4845 raise ValueError("no variable found")
4846 self
.xaxis
= graph
.axes
[self
.variable
]
4848 self
.style
.setcolumns(graph
, {self
.variable
: 0, self
.result
: 1})
4850 def getranges(self
):
4852 return self
.style
.getranges(self
.data
)
4853 if None not in (self
.min, self
.max):
4854 return {self
.variable
: (self
.min, self
.max)}
4856 def setranges(self
, ranges
):
4857 if ranges
.has_key(self
.variable
):
4858 min, max = ranges
[self
.variable
]
4859 if self
.min is not None: min = self
.min
4860 if self
.max is not None: max = self
.max
4861 vmin
= self
.xaxis
.convert(min)
4862 vmax
= self
.xaxis
.convert(max)
4864 for i
in range(self
.points
):
4865 self
.context
[self
.variable
] = x
= self
.xaxis
.invert(vmin
+ (vmax
-vmin
)*i
/ (self
.points
-1.0))
4867 y
= self
.mathtree
.Calc(**self
.context
)
4868 except (ArithmeticError, ValueError):
4870 self
.data
.append((x
, y
))
4873 def draw(self
, graph
):
4874 self
.style
.drawpoints(graph
, self
.data
)
4877 class paramfunction
:
4881 def __init__(self
, varname
, min, max, expression
, title
=helper
.nodefault
, points
=100, parser
=mathtree
.parser(), context
={}):
4882 if title
is helper
.nodefault
:
4883 self
.title
= expression
4886 self
.varname
= varname
4889 self
.points
= points
4890 self
.expression
= {}
4892 varlist
, expressionlist
= expression
.split("=")
4893 parsestr
= mathtree
.ParseStr(expressionlist
)
4894 for key
in varlist
.split(","):
4896 if self
.mathtrees
.has_key(key
):
4897 raise ValueError("multiple assignment in tuple")
4899 self
.mathtrees
[key
] = parser
.ParseMathTree(parsestr
)
4901 except mathtree
.CommaFoundMathTreeParseError
, e
:
4902 self
.mathtrees
[key
] = e
.MathTree
4904 raise ValueError("unpack tuple of wrong size")
4905 if len(varlist
.split(",")) != len(self
.mathtrees
.keys()):
4906 raise ValueError("unpack tuple of wrong size")
4908 for i
in range(self
.points
):
4909 context
[self
.varname
] = self
.min + (self
.max-self
.min)*i
/ (self
.points
-1.0)
4911 for key
, tree
in self
.mathtrees
.items():
4912 line
.append(tree
.Calc(**context
))
4913 self
.data
.append(line
)
4915 def setstyle(self
, graph
, style
):
4918 for key
, index
in zip(self
.mathtrees
.keys(), xrange(sys
.maxint
)):
4919 columns
[key
] = index
4920 self
.style
.setcolumns(graph
, columns
)
4922 def getranges(self
):
4923 return self
.style
.getranges(self
.data
)
4925 def setranges(self
, ranges
):
4928 def draw(self
, graph
):
4929 self
.style
.drawpoints(graph
, self
.data
)