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]
68 def convert(self
, value
):
69 return self
.y1
+ self
.dydx
* (value
- self
.x1
)
71 def invert(self
, value
):
72 return self
.x1
+ self
.dxdy
* (value
- self
.y1
)
77 __implements__
= _Imap
79 def setbasepoints(self
, basepoints
):
80 self
.dydx
= ((basepoints
[1][1] - basepoints
[0][1]) /
81 float(math
.log(basepoints
[1][0]) - math
.log(basepoints
[0][0])))
82 self
.dxdy
= ((math
.log(basepoints
[1][0]) - math
.log(basepoints
[0][0])) /
83 float(basepoints
[1][1] - basepoints
[0][1]))
84 self
.x1
= math
.log(basepoints
[0][0])
85 self
.y1
= basepoints
[0][1]
88 def convert(self
, value
):
89 return self
.y1
+ self
.dydx
* (math
.log(value
) - self
.x1
)
91 def invert(self
, value
):
92 return math
.exp(self
.x1
+ self
.dxdy
* (value
- self
.y1
))
96 ################################################################################
98 # please note the nomenclature:
99 # - a partition is a list of tick instances; to reduce name clashes, a
100 # partition is called ticks
101 # - a partitioner is a class creating a single or several ticks
102 # - an axis has a part attribute where it stores a partitioner or/and some
103 # (manually set) ticks -> the part attribute is used to create the ticks
104 # in the axis finish method
105 ################################################################################
109 """fraction class for rational arithmetics
110 the axis partitioning uses rational arithmetics (with infinite accuracy)
111 basically it contains self.enum and self.denom"""
113 def stringfrac(self
, s
):
114 "converts a string 0.123 into a frac"
115 expparts
= s
.split("e")
116 if len(expparts
) > 2:
117 raise ValueError("multiple 'e' found in '%s'" % s
)
118 commaparts
= expparts
[0].split(".")
119 if len(commaparts
) > 2:
120 raise ValueError("multiple '.' found in '%s'" % expparts
[0])
121 if len(commaparts
) == 1:
122 commaparts
= [commaparts
[0], ""]
123 result
= frac((1, 10l), power
=len(commaparts
[1]))
124 neg
= len(commaparts
[0]) and commaparts
[0][0] == "-"
126 commaparts
[0] = commaparts
[0][1:]
127 elif len(commaparts
[0]) and commaparts
[0][0] == "+":
128 commaparts
[0] = commaparts
[0][1:]
129 if len(commaparts
[0]):
130 if not commaparts
[0].isdigit():
131 raise ValueError("unrecognized characters in '%s'" % s
)
132 x
= long(commaparts
[0])
135 if len(commaparts
[1]):
136 if not commaparts
[1].isdigit():
137 raise ValueError("unrecognized characters in '%s'" % s
)
138 y
= long(commaparts
[1])
141 result
.enum
= x
*result
.denom
+y
143 result
.enum
= -result
.enum
144 if len(expparts
) == 2:
145 neg
= expparts
[1][0] == "-"
147 expparts
[1] = expparts
[1][1:]
148 elif expparts
[1][0] == "+":
149 expparts
[1] = expparts
[1][1:]
150 if not expparts
[1].isdigit():
151 raise ValueError("unrecognized characters in '%s'" % s
)
153 result
*= frac((1, 10l), power
=long(expparts
[1]))
155 result
*= frac((10, 1l), power
=long(expparts
[1]))
158 def floatfrac(self
, x
, floatprecision
):
159 "converts a float into a frac with finite resolution"
160 if helper
.isinteger(floatprecision
) and floatprecision
< 0:
161 # this would be extremly vulnerable
162 raise RuntimeError("float resolution must be non-negative integer")
163 return self
.stringfrac(("%%.%ig" % floatprecision
) % x
)
165 def __init__(self
, x
, power
=None, floatprecision
=10):
166 "for power!=None: frac=(enum/denom)**power"
167 if helper
.isnumber(x
):
168 value
= self
.floatfrac(x
, floatprecision
)
169 enum
, denom
= value
.enum
, value
.denom
170 elif helper
.isstring(x
):
171 fraction
= x
.split("/")
172 if len(fraction
) > 2:
173 raise ValueError("multiple '/' found in '%s'" % x
)
174 value
= self
.stringfrac(fraction
[0])
175 if len(fraction
) == 2:
176 value2
= self
.stringfrac(fraction
[1])
177 value
= value
/ value2
178 enum
, denom
= value
.enum
, value
.denom
182 except (TypeError, AttributeError):
183 enum
, denom
= x
.enum
, x
.denom
184 if not helper
.isinteger(enum
) or not helper
.isinteger(denom
): raise TypeError("integer type expected")
185 if not denom
: raise ZeroDivisionError("zero denominator")
187 if not helper
.isinteger(power
): raise TypeError("integer type expected")
189 self
.enum
= long(enum
) ** power
190 self
.denom
= long(denom
) ** power
192 self
.enum
= long(denom
) ** (-power
)
193 self
.denom
= long(enum
) ** (-power
)
198 def __cmp__(self
, other
):
201 return cmp(self
.enum
* other
.denom
, other
.enum
* self
.denom
)
204 return frac((abs(self
.enum
), abs(self
.denom
)))
206 def __mul__(self
, other
):
207 return frac((self
.enum
* other
.enum
, self
.denom
* other
.denom
))
209 def __div__(self
, other
):
210 return frac((self
.enum
* other
.denom
, self
.denom
* other
.enum
))
213 "caution: avoid final precision of floats"
214 return float(self
.enum
) / self
.denom
217 return "%i/%i" % (self
.enum
, self
.denom
)
222 a tick is a frac enhanced by
223 - self.ticklevel (0 = tick, 1 = subtick, etc.)
224 - self.labellevel (0 = label, 1 = sublabel, etc.)
225 - self.label (a string) and self.labelattrs (a list, defaults to [])
226 When ticklevel or labellevel is None, no tick or label is present at that value.
227 When label is None, it should be automatically created (and stored), once the
228 an axis painter needs it. Classes, which implement _Itexter do precisely that."""
230 def __init__(self
, pos
, ticklevel
=0, labellevel
=0, label
=None, labelattrs
=[], **kwargs
):
231 """initializes the instance
232 - see class description for the parameter description
233 - **kwargs are passed to the frac constructor"""
234 frac
.__init
__(self
, pos
, **kwargs
)
235 self
.ticklevel
= ticklevel
236 self
.labellevel
= labellevel
238 self
.labelattrs
= helper
.ensurelist(labelattrs
)[:]
240 def merge(self
, other
):
241 """merges two ticks together:
242 - the lower ticklevel/labellevel wins
243 - the label is *never* taken over from other
244 - the ticks should be at the same position (otherwise it doesn't make sense)
245 -> this is NOT checked"""
246 if self
.ticklevel
is None or (other
.ticklevel
is not None and other
.ticklevel
< self
.ticklevel
):
247 self
.ticklevel
= other
.ticklevel
248 if self
.labellevel
is None or (other
.labellevel
is not None and other
.labellevel
< self
.labellevel
):
249 self
.labellevel
= other
.labellevel
252 def _mergeticklists(list1
, list2
):
253 """helper function to merge tick lists
254 - return a merged list of ticks out of list1 and list2
255 - CAUTION: original lists have to be ordered
256 (the returned list is also ordered)
257 - CAUTION: original lists are modified and they share references to
259 # TODO: improve this using bisect?!
260 if list1
is None: return list2
261 if list2
is None: return list1
265 while 1: # we keep on going until we reach an index error
266 while list2
[j
] < list1
[i
]: # insert tick
267 list1
.insert(i
, list2
[j
])
270 if list2
[j
] == list1
[i
]: # merge tick
271 list1
[i
].merge(list2
[j
])
280 def _mergelabels(ticks
, labels
):
281 """helper function to merge labels into ticks
282 - when labels is not None, the label of all ticks with
283 labellevel different from None are set
284 - labels need to be a list of lists of strings,
285 where the first list contain the strings to be
286 used as labels for the ticks with labellevel 0,
287 the second list for labellevel 1, etc.
288 - when the maximum labellevel is 0, just a list of
289 strings might be provided as the labels argument
290 - IndexError is raised, when a list length doesn't match"""
291 if helper
.issequenceofsequences(labels
):
292 for label
, level
in zip(labels
, xrange(sys
.maxint
)):
293 usetext
= helper
.ensuresequence(label
)
296 if tick
.labellevel
== level
:
297 tick
.label
= usetext
[i
]
299 if i
!= len(usetext
):
300 raise IndexError("wrong list length of labels at level %i" % level
)
301 elif labels
is not None:
302 usetext
= helper
.ensuresequence(labels
)
305 if tick
.labellevel
== 0:
306 tick
.label
= usetext
[i
]
308 if i
!= len(usetext
):
309 raise IndexError("wrong list length of labels")
313 """interface definition of a partition scheme
314 partition schemes are used to create a list of ticks"""
316 def defaultpart(self
, min, max, extendmin
, extendmax
):
317 """create a partition
318 - returns an ordered list of ticks for the interval min to max
319 - the interval is given in float numbers, thus an appropriate
320 conversion to rational numbers has to be performed
321 - extendmin and extendmax are booleans (integers)
322 - when extendmin or extendmax is set, the ticks might
323 extend the min-max range towards lower and higher
324 ranges, respectively"""
327 """create another partition which contains less ticks
328 - this method is called several times after a call of defaultpart
329 - returns an ordered list of ticks with less ticks compared to
330 the partition returned by defaultpart and by previous calls
332 - the creation of a partition with strictly *less* ticks
333 is not to be taken serious
334 - the method might return None, when no other appropriate
335 partition can be created"""
339 """create another partition which contains more ticks
340 see lesspart, but increase the number of ticks"""
344 """linear partition scheme
345 ticks and label distances are explicitly provided to the constructor"""
347 __implements__
= _Ipart
349 def __init__(self
, tickdist
=None, labeldist
=None, labels
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
350 """configuration of the partition scheme
351 - tickdist and labeldist should be a list, where the first value
352 is the distance between ticks with ticklevel/labellevel 0,
353 the second list for ticklevel/labellevel 1, etc.;
354 a single entry is allowed without being a list
355 - tickdist and labeldist values are passed to the frac constructor
356 - when labeldist is None and tickdist is not None, the tick entries
357 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
358 - labels are applied to the resulting partition via the
359 mergelabels function (additional information available there)
360 - extendtick allows for the extension of the range given to the
361 defaultpart method to include the next tick with the specified
362 level (None turns off this feature); note, that this feature is
363 also disabled, when an axis prohibits its range extension by
364 the extendmin/extendmax variables given to the defaultpart method
365 - extendlabel is analogous to extendtick, but for labels
366 - epsilon allows for exceeding the axis range by this relative
367 value (relative to the axis range given to the defaultpart method)
368 without creating another tick specified by extendtick/extendlabel"""
369 if tickdist
is None and labeldist
is not None:
370 self
.ticklist
= (frac(helper
.ensuresequence(labeldist
)[0]),)
372 self
.ticklist
= map(frac
, helper
.ensuresequence(tickdist
))
373 if labeldist
is None and tickdist
is not None:
374 self
.labellist
= (frac(helper
.ensuresequence(tickdist
)[0]),)
376 self
.labellist
= map(frac
, helper
.ensuresequence(labeldist
))
378 self
.extendtick
= extendtick
379 self
.extendlabel
= extendlabel
380 self
.epsilon
= epsilon
382 def extendminmax(self
, min, max, frac
, extendmin
, extendmax
):
383 """return new min, max tuple extending the range min, max
384 - frac is the tick distance to be used
385 - extendmin and extendmax are booleans to allow for the extension"""
387 min = float(frac
) * math
.floor(min / float(frac
) + self
.epsilon
)
389 max = float(frac
) * math
.ceil(max / float(frac
) - self
.epsilon
)
392 def getticks(self
, min, max, frac
, ticklevel
=None, labellevel
=None):
393 """return a list of equal spaced ticks
394 - the tick distance is frac, the ticklevel is set to ticklevel and
395 the labellevel is set to labellevel
396 - min, max is the range where ticks should be placed"""
397 imin
= int(math
.ceil(min / float(frac
) - 0.5 * self
.epsilon
))
398 imax
= int(math
.floor(max / float(frac
) + 0.5 * self
.epsilon
))
400 for i
in range(imin
, imax
+ 1):
401 ticks
.append(tick((long(i
) * frac
.enum
, frac
.denom
), ticklevel
=ticklevel
, labellevel
=labellevel
))
404 def defaultpart(self
, min, max, extendmin
, extendmax
):
405 if self
.extendtick
is not None and len(self
.ticklist
) > self
.extendtick
:
406 min, max = self
.extendminmax(min, max, self
.ticklist
[self
.extendtick
], extendmin
, extendmax
)
407 if self
.extendlabel
is not None and len(self
.labellist
) > self
.extendlabel
:
408 min, max = self
.extendminmax(min, max, self
.labellist
[self
.extendlabel
], extendmin
, extendmax
)
411 for i
in range(len(self
.ticklist
)):
412 ticks
= _mergeticklists(ticks
, self
.getticks(min, max, self
.ticklist
[i
], ticklevel
= i
))
413 for i
in range(len(self
.labellist
)):
414 ticks
= _mergeticklists(ticks
, self
.getticks(min, max, self
.labellist
[i
], labellevel
= i
))
416 _mergelabels(ticks
, self
.labels
)
428 """automatic linear partition scheme
429 - possible tick distances are explicitly provided to the constructor
430 - tick distances are adjusted to the axis range by multiplication or division by 10"""
432 __implements__
= _Ipart
434 defaultvariants
= ((frac((1, 1)), frac((1, 2))),
435 (frac((2, 1)), frac((1, 1))),
436 (frac((5, 2)), frac((5, 4))),
437 (frac((5, 1)), frac((5, 2))))
439 def __init__(self
, variants
=defaultvariants
, extendtick
=0, epsilon
=1e-10):
440 """configuration of the partition scheme
441 - variants is a list of tickdist
442 - tickdist should be a list, where the first value
443 is the distance between ticks with ticklevel 0,
444 the second for ticklevel 1, etc.
445 - tickdist values are passed to the frac constructor
446 - labellevel is set to None except for those ticks in the partitions,
447 where ticklevel is zero. There labellevel is also set to zero.
448 - extendtick allows for the extension of the range given to the
449 defaultpart method to include the next tick with the specified
450 level (None turns off this feature); note, that this feature is
451 also disabled, when an axis prohibits its range extension by
452 the extendmin/extendmax variables given to the defaultpart method
453 - epsilon allows for exceeding the axis range by this relative
454 value (relative to the axis range given to the defaultpart method)
455 without creating another tick specified by extendtick"""
456 self
.variants
= variants
457 self
.extendtick
= extendtick
458 self
.epsilon
= epsilon
460 def defaultpart(self
, min, max, extendmin
, extendmax
):
461 logmm
= math
.log(max - min) / math
.log(10)
462 if logmm
< 0: # correction for rounding towards zero of the int routine
463 base
= frac((10L, 1), int(logmm
- 1))
465 base
= frac((10L, 1), int(logmm
))
466 ticks
= map(frac
, self
.variants
[0])
467 useticks
= [tick
* base
for tick
in ticks
]
468 self
.lesstickindex
= self
.moretickindex
= 0
469 self
.lessbase
= frac((base
.enum
, base
.denom
))
470 self
.morebase
= frac((base
.enum
, base
.denom
))
471 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
472 part
= linpart(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
473 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
476 if self
.lesstickindex
< len(self
.variants
) - 1:
477 self
.lesstickindex
+= 1
479 self
.lesstickindex
= 0
480 self
.lessbase
.enum
*= 10
481 ticks
= map(frac
, self
.variants
[self
.lesstickindex
])
482 useticks
= [tick
* self
.lessbase
for tick
in ticks
]
483 part
= linpart(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
484 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
487 if self
.moretickindex
:
488 self
.moretickindex
-= 1
490 self
.moretickindex
= len(self
.variants
) - 1
491 self
.morebase
.denom
*= 10
492 ticks
= map(frac
, self
.variants
[self
.moretickindex
])
493 useticks
= [tick
* self
.morebase
for tick
in ticks
]
494 part
= linpart(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
495 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
499 """storage class for the definition of logarithmic axes partitions
500 instances of this class define tick positions suitable for
501 logarithmic axes by the following instance variables:
502 - exp: integer, which defines multiplicator (usually 10)
503 - pres: list of tick positions (rational numbers, e.g. instances of frac)
504 possible positions are these tick positions and arbitrary divisions
505 and multiplications by the exp value"""
507 def __init__(self
, pres
, exp
):
508 "create a preexp instance and store its pres and exp information"
509 self
.pres
= helper
.ensuresequence(pres
)
513 class logpart(linpart
):
514 """logarithmic partition scheme
515 ticks and label positions are explicitly provided to the constructor"""
517 __implements__
= _Ipart
519 pre1exp5
= preexp(frac((1, 1)), 100000)
520 pre1exp4
= preexp(frac((1, 1)), 10000)
521 pre1exp3
= preexp(frac((1, 1)), 1000)
522 pre1exp2
= preexp(frac((1, 1)), 100)
523 pre1exp
= preexp(frac((1, 1)), 10)
524 pre125exp
= preexp((frac((1, 1)), frac((2, 1)), frac((5, 1))), 10)
525 pre1to9exp
= preexp(map(lambda x
: frac((x
, 1)), range(1, 10)), 10)
526 # ^- we always include 1 in order to get extendto(tick|label)level to work as expected
528 def __init__(self
, tickpos
=None, labelpos
=None, labels
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
529 """configuration of the partition scheme
530 - tickpos and labelpos should be a list, where the first entry
531 is a preexp instance describing ticks with ticklevel/labellevel 0,
532 the second is a preexp instance for ticklevel/labellevel 1, etc.;
533 a single entry is allowed without being a list
534 - when labelpos is None and tickpos is not None, the tick entries
535 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
536 - labels are applied to the resulting partition via the
537 mergetexts function (additional information available there)
538 - extendtick allows for the extension of the range given to the
539 defaultpart method to include the next tick with the specified
540 level (None turns off this feature); note, that this feature is
541 also disabled, when an axis prohibits its range extension by
542 the extendmin/extendmax variables given to the defaultpart method
543 - extendlabel is analogous to extendtick, but for labels
544 - epsilon allows for exceeding the axis range by this relative
545 logarithm value (relative to the logarithm axis range given
546 to the defaultpart method) without creating another tick
547 specified by extendtick/extendlabel"""
548 if tickpos
is None and labels
is not None:
549 self
.ticklist
= (helper
.ensuresequence(labelpos
)[0],)
551 self
.ticklist
= helper
.ensuresequence(tickpos
)
553 if labelpos
is None and tickpos
is not None:
554 self
.labellist
= (helper
.ensuresequence(tickpos
)[0],)
556 self
.labellist
= helper
.ensuresequence(labelpos
)
558 self
.extendtick
= extendtick
559 self
.extendlabel
= extendlabel
560 self
.epsilon
= epsilon
562 def extendminmax(self
, min, max, preexp
, extendmin
, extendmax
):
563 """return new min, max tuple extending the range min, max
564 preexp describes the allowed tick positions
565 extendmin and extendmax are booleans to allow for the extension"""
568 for i
in xrange(len(preexp
.pres
)):
569 imin
= int(math
.floor(math
.log(min / float(preexp
.pres
[i
])) /
570 math
.log(preexp
.exp
) + self
.epsilon
)) + 1
571 imax
= int(math
.ceil(math
.log(max / float(preexp
.pres
[i
])) /
572 math
.log(preexp
.exp
) - self
.epsilon
)) - 1
573 if minpower
is None or imin
< minpower
:
574 minpower
, minindex
= imin
, i
575 if maxpower
is None or imax
>= maxpower
:
576 maxpower
, maxindex
= imax
, i
578 minfrac
= preexp
.pres
[minindex
- 1]
580 minfrac
= preexp
.pres
[-1]
582 if maxindex
!= len(preexp
.pres
) - 1:
583 maxfrac
= preexp
.pres
[maxindex
+ 1]
585 maxfrac
= preexp
.pres
[0]
588 min = float(minfrac
) * float(preexp
.exp
) ** minpower
590 max = float(maxfrac
) * float(preexp
.exp
) ** maxpower
593 def getticks(self
, min, max, preexp
, ticklevel
=None, labellevel
=None):
594 """return a list of ticks
595 - preexp describes the allowed tick positions
596 - the ticklevel of the ticks is set to ticklevel and
597 the labellevel is set to labellevel
598 - min, max is the range where ticks should be placed"""
602 for f
in preexp
.pres
:
604 imin
= int(math
.ceil(math
.log(min / float(f
)) /
605 math
.log(preexp
.exp
) - 0.5 * self
.epsilon
))
606 imax
= int(math
.floor(math
.log(max / float(f
)) /
607 math
.log(preexp
.exp
) + 0.5 * self
.epsilon
))
608 for i
in range(imin
, imax
+ 1):
609 pos
= f
* frac((preexp
.exp
, 1), i
)
610 fracticks
.append(tick((pos
.enum
, pos
.denom
), ticklevel
= ticklevel
, labellevel
= labellevel
))
611 ticks
= _mergeticklists(ticks
, fracticks
)
615 class autologpart(logpart
):
616 """automatic logarithmic partition scheme
617 possible tick positions are explicitly provided to the constructor"""
619 __implements__
= _Ipart
621 defaultvariants
= (((logpart
.pre1exp
, # ticks
622 logpart
.pre1to9exp
), # subticks
623 (logpart
.pre1exp
, # labels
624 logpart
.pre125exp
)), # sublevels
626 ((logpart
.pre1exp
, # ticks
627 logpart
.pre1to9exp
), # subticks
628 None), # labels like ticks
630 ((logpart
.pre1exp2
, # ticks
631 logpart
.pre1exp
), # subticks
632 None), # labels like ticks
634 ((logpart
.pre1exp3
, # ticks
635 logpart
.pre1exp
), # subticks
636 None), # labels like ticks
638 ((logpart
.pre1exp4
, # ticks
639 logpart
.pre1exp
), # subticks
640 None), # labels like ticks
642 ((logpart
.pre1exp5
, # ticks
643 logpart
.pre1exp
), # subticks
644 None)) # labels like ticks
646 def __init__(self
, variants
=defaultvariants
, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
647 """configuration of the partition scheme
648 - variants should be a list of pairs of lists of preexp
650 - within each pair the first list contains preexp, where
651 the first preexp instance describes ticks positions with
652 ticklevel 0, the second preexp for ticklevel 1, etc.
653 - the second list within each pair describes the same as
654 before, but for labels
655 - within each pair: when the second entry (for the labels) is None
656 and the first entry (for the ticks) ticks is not None, the tick
657 entries for ticklevel 0 are used for labels and vice versa
659 - extendtick allows for the extension of the range given to the
660 defaultpart method to include the next tick with the specified
661 level (None turns off this feature); note, that this feature is
662 also disabled, when an axis prohibits its range extension by
663 the extendmin/extendmax variables given to the defaultpart method
664 - extendlabel is analogous to extendtick, but for labels
665 - epsilon allows for exceeding the axis range by this relative
666 logarithm value (relative to the logarithm axis range given
667 to the defaultpart method) without creating another tick
668 specified by extendtick/extendlabel"""
669 self
.variants
= variants
670 if len(variants
) > 2:
671 self
.variantsindex
= divmod(len(variants
), 2)[0]
673 self
.variantsindex
= 0
674 self
.extendtick
= extendtick
675 self
.extendlabel
= extendlabel
676 self
.epsilon
= epsilon
678 def defaultpart(self
, min, max, extendmin
, extendmax
):
679 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
680 self
.morevariantsindex
= self
.variantsindex
681 self
.lessvariantsindex
= self
.variantsindex
682 part
= logpart(tickpos
=self
.variants
[self
.variantsindex
][0], labelpos
=self
.variants
[self
.variantsindex
][1],
683 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
684 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
687 self
.lessvariantsindex
+= 1
688 if self
.lessvariantsindex
< len(self
.variants
):
689 part
= logpart(tickpos
=self
.variants
[self
.lessvariantsindex
][0], labelpos
=self
.variants
[self
.lessvariantsindex
][1],
690 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
691 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
694 self
.morevariantsindex
-= 1
695 if self
.morevariantsindex
>= 0:
696 part
= logpart(tickpos
=self
.variants
[self
.morevariantsindex
][0], labelpos
=self
.variants
[self
.morevariantsindex
][1],
697 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
698 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
702 ################################################################################
704 # conseptional remarks:
705 # - raters are used to calculate a rating for a realization of something
706 # - here, a rating means a positive floating point value
707 # - ratings are used to order those realizations by their suitability (lower
708 # ratings are better)
709 # - a rating of None means not suitable at all (those realizations should be
711 ################################################################################
716 - a cube rater has an optimal value, where the rate becomes zero
717 - for a left (below the optimum) and a right value (above the optimum),
718 the rating is value is set to 1 (modified by an overall weight factor
720 - the analytic form of the rating is cubic for both, the left and
721 the right side of the rater, independently"""
723 # __implements__ = sole implementation
725 def __init__(self
, opt
, left
=None, right
=None, weight
=1):
726 """initializes the rater
727 - by default, left is set to zero, right is set to 3*opt
728 - left should be smaller than opt, right should be bigger than opt
729 - weight should be positive and is a factor multiplicated to the rates"""
739 def rate(self
, value
, density
):
740 """returns a rating for a value
741 - the density lineary rescales the rater (the optimum etc.),
742 e.g. a value bigger than one increases the optimum (when it is
743 positive) and a value lower than one decreases the optimum (when
744 it is positive); the density itself should be positive"""
745 opt
= self
.opt
* density
747 other
= self
.left
* density
749 other
= self
.right
* density
752 factor
= (value
- opt
) / float(other
- opt
)
753 return self
.weight
* (factor
** 3)
757 # TODO: update docstring
758 """a distance rater (rates a list of distances)
759 - the distance rater rates a list of distances by rating each independently
760 and returning the average rate
761 - there is an optimal value, where the rate becomes zero
762 - the analytic form is linary for values above the optimal value
763 (twice the optimal value has the rating one, three times the optimal
764 value has the rating two, etc.)
765 - the analytic form is reciprocal subtracting one for values below the
766 optimal value (halve the optimal value has the rating one, one third of
767 the optimal value has the rating two, etc.)"""
769 # __implements__ = sole implementation
771 def __init__(self
, opt
, weight
=0.1):
772 """inititializes the rater
773 - opt is the optimal length (a visual PyX length)
774 - weight should be positive and is a factor multiplicated to the rates"""
778 def rate(self
, distances
, density
):
780 - the distances are a list of positive floats in PostScript points
781 - the density lineary rescales the rater (the optimum etc.),
782 e.g. a value bigger than one increases the optimum (when it is
783 positive) and a value lower than one decreases the optimum (when
784 it is positive); the density itself should be positive"""
786 opt
= unit
.topt(unit
.length(self
.opt_str
, default_type
="v")) / density
788 for distance
in distances
:
790 rate
+= self
.weight
* (opt
/ distance
- 1)
792 rate
+= self
.weight
* (distance
/ opt
- 1)
793 return rate
/ float(len(distances
))
798 - the rating of axes is splited into two separate parts:
799 - rating of the ticks in terms of the number of ticks, subticks,
801 - rating of the label distances
802 - in the end, a rate for ticks is the sum of these rates
803 - it is useful to first just rate the number of ticks etc.
804 and selecting those partitions, where this fits well -> as soon
805 as an complete rate (the sum of both parts from the list above)
806 of a first ticks is below a rate of just the number of ticks,
807 subticks labels etc. of other ticks, those other ticks will never
808 be better than the first one -> we gain speed by minimizing the
809 number of ticks, where label distances have to be taken into account)
810 - both parts of the rating are shifted into instances of raters
811 defined above --- right now, there is not yet a strict interface
812 for this delegation (should be done as soon as it is needed)"""
814 # __implements__ = sole implementation
816 linticks
= (cuberater(4), cuberater(10, weight
=0.5), )
817 linlabels
= (cuberater(4), )
818 logticks
= (cuberater(5, right
=20), cuberater(20, right
=100, weight
=0.5), )
819 loglabels
= (cuberater(5, right
=20), cuberater(5, left
=-20, right
=20, weight
=0.5), )
820 stdtickrange
= cuberater(1, weight
=2)
821 stddistance
= distancerater("1 cm")
823 def __init__(self
, ticks
=linticks
, labels
=linlabels
, tickrange
=stdtickrange
, distance
=stddistance
):
824 """initializes the axis rater
825 - ticks and labels are lists of instances of a value rater
826 - the first entry in ticks rate the number of ticks, the
827 second the number of subticks, etc.; when there are no
828 ticks of a level or there is not rater for a level, the
829 level is just ignored
830 - labels is analogous, but for labels
831 - within the rating, all ticks with a higher level are
832 considered as ticks for a given level
833 - tickrange is a value rater instance, which rates the covering
834 of an axis range by the ticks (as a relative value of the
835 tick range vs. the axis range), ticks might cover less or
836 more than the axis range (for the standard automatic axis
837 partition schemes an extention of the axis range is normal
838 and should get some penalty)
839 - distance is an distance rater instance"""
840 self
.rateticks
= ticks
841 self
.ratelabels
= labels
842 self
.tickrange
= tickrange
843 self
.distance
= distance
845 def ratepart(self
, axis
, ticks
, density
):
846 """rates ticks by the number of ticks, subticks, labels etc.
847 - takes into account the number of ticks, subticks, labels
848 etc. and the coverage of the axis range by the ticks
849 - when there are no ticks of a level or there was not rater
850 given in the constructor for a level, the level is just
852 - the method returns the sum of the rating results divided
853 by the sum of the weights of the raters
854 - within the rating, all ticks with a higher level are
855 considered as ticks for a given level"""
856 maxticklevel
= maxlabellevel
= 0
858 if tick
.ticklevel
>= maxticklevel
:
859 maxticklevel
= tick
.ticklevel
+ 1
860 if tick
.labellevel
>= maxlabellevel
:
861 maxlabellevel
= tick
.labellevel
+ 1
862 numticks
= [0]*maxticklevel
863 numlabels
= [0]*maxlabellevel
865 if tick
.ticklevel
is not None:
866 for level
in range(tick
.ticklevel
, maxticklevel
):
868 if tick
.labellevel
is not None:
869 for level
in range(tick
.labellevel
, maxlabellevel
):
870 numlabels
[level
] += 1
873 for numtick
, rater
in zip(numticks
, self
.rateticks
):
874 rate
+= rater
.rate(numtick
, density
)
875 weight
+= rater
.weight
876 for numlabel
, rater
in zip(numlabels
, self
.ratelabels
):
877 rate
+= rater
.rate(numlabel
, density
)
878 weight
+= rater
.weight
880 # XXX: tickrange was not yet applied !!! TODO!!!
881 # TODO: density == 1?
882 # if axis.divisor is not None: # XXX workaround for timeaxis
883 rate
+= self
.tickrange
.rate(axis
.convert(float(ticks
[-1]) * axis
.divisor
) -
884 axis
.convert(float(ticks
[0]) * axis
.divisor
), 1)
886 # rate += self.tickrange.rate(axis.convert(ticks[-1]) -
887 # axis.convert(ticks[0]), 1)
889 rate
+= self
.tickrange
.rate(0, 1)
890 weight
+= self
.tickrange
.weight
893 def ratelayout(self
, axiscanvas
, density
):
894 """rate distances of the labels in an axis canvas
895 - the distances should be collected as box distances of
897 - the axiscanvas provides a labels attribute for easy
898 access to the labels whose distances have to be taken
900 - the density is used within the distancerate instance"""
901 if len(axiscanvas
.labels
) > 1:
903 distances
= [axiscanvas
.labels
[i
]._boxdistance
(axiscanvas
.labels
[i
+1]) for i
in range(len(axiscanvas
.labels
) - 1)]
904 except box
.BoxCrossError
:
906 return self
.distance
.rate(distances
, density
)
911 ################################################################################
913 # texter automatically create labels for tick instances
914 ################################################################################
919 def labels(self
, ticks
):
920 """fill the label attribute of ticks
921 - ticks is a list of instances of tick
922 - for each element of ticks the value of the attribute label is set to
923 a string appropriate to the attributes enum and denom of that tick
925 - label attributes of the tick instances are just kept, whenever they
926 are not equal to None
927 - the method might extend the labelattrs attribute of the ticks"""
930 class rationaltexter
:
931 "a texter creating rational labels (e.g. 'a/b' or even 'a \over b')"
932 # XXX: we use divmod here to be more expicit
934 __implements__
= _Itexter
936 def __init__(self
, prefix
="", infix
="", suffix
="",
937 enumprefix
="", enuminfix
="", enumsuffix
="",
938 denomprefix
="", denominfix
="", denomsuffix
="",
939 plus
="", minus
="-", minuspos
=0, over
=r
"{{%s}\over{%s}}",
940 equaldenom
=0, skip1
=1, skipenum0
=1, skipenum1
=1, skipdenom1
=1,
941 labelattrs
=textmodule
.mathmode
):
942 r
"""initializes the instance
943 - prefix, infix, and suffix (strings) are added at the begin,
944 immediately after the minus, and at the end of the label,
946 - prefixenum, infixenum, and suffixenum (strings) are added
947 to the labels enumerator correspondingly
948 - prefixdenom, infixdenom, and suffixdenom (strings) are added
949 to the labels denominator correspondingly
950 - plus or minus (string) is inserted for non-negative or negative numbers
951 - minuspos is an integer, which determines the position, where the
952 plus or minus sign has to be placed; the following values are allowed:
953 1 - writes the plus or minus in front of the enumerator
954 0 - writes the plus or minus in front of the hole fraction
955 -1 - writes the plus or minus in front of the denominator
956 - over (string) is taken as a format string generating the
957 fraction bar; it has to contain exactly two string insert
958 operators "%s" -- the first for the enumerator and the second
959 for the denominator; by far the most common examples are
960 r"{{%s}\over{%s}}" and "{{%s}/{%s}}"
961 - usually the enumerator and denominator are canceled; however,
962 when equaldenom is set, the least common multiple of all
964 - skip1 (boolean) just prints the prefix, the plus or minus,
965 the infix and the suffix, when the value is plus or minus one
966 and at least one of prefix, infix and the suffix is present
967 - skipenum0 (boolean) just prints a zero instead of
968 the hole fraction, when the enumerator is zero;
969 no prefixes, infixes, and suffixes are taken into account
970 - skipenum1 (boolean) just prints the enumprefix, the plus or minus,
971 the enuminfix and the enumsuffix, when the enum value is plus or minus one
972 and at least one of enumprefix, enuminfix and the enumsuffix is present
973 - skipdenom1 (boolean) just prints the enumerator instead of
974 the hole fraction, when the denominator is one and none of the parameters
975 denomprefix, denominfix and denomsuffix are set and minuspos is not -1 or the
977 - labelattrs is a list of attributes for a texrunners text method;
978 a single is allowed without being a list; None is considered as
983 self
.enumprefix
= enumprefix
984 self
.enuminfix
= enuminfix
985 self
.enumsuffix
= enumsuffix
986 self
.denomprefix
= denomprefix
987 self
.denominfix
= denominfix
988 self
.denomsuffix
= denomsuffix
991 self
.minuspos
= minuspos
993 self
.equaldenom
= equaldenom
995 self
.skipenum0
= skipenum0
996 self
.skipenum1
= skipenum1
997 self
.skipdenom1
= skipdenom1
998 self
.labelattrs
= helper
.ensurelist(labelattrs
)
1001 """returns the greates common divisor of all elements in n
1002 - the elements of n must be non-negative integers
1003 - return None if the number of elements is zero
1004 - the greates common divisor is not affected when some
1005 of the elements are zero, but it becomes zero when
1006 all elements are zero"""
1012 i
, (dummy
, j
) = j
, divmod(i
, j
)
1017 res
= self
.gcd(res
, i
)
1021 """returns the least common multiple of all elements in n
1022 - the elements of n must be non-negative integers
1023 - return None if the number of elements is zero
1024 - the least common multiple is zero when some of the
1025 elements are zero"""
1029 res
= divmod(res
* i
, self
.gcd(res
, i
))[0]
1032 def labels(self
, ticks
):
1035 if tick
.label
is None and tick
.labellevel
is not None:
1036 labeledticks
.append(tick
)
1037 tick
.temp_fracenum
= tick
.enum
1038 tick
.temp_fracdenom
= tick
.denom
1039 tick
.temp_fracminus
= 1
1040 if tick
.temp_fracenum
< 0:
1041 tick
.temp_fracminus
= -tick
.temp_fracminus
1042 tick
.temp_fracenum
= -tick
.temp_fracenum
1043 if tick
.temp_fracdenom
< 0:
1044 tick
.temp_fracminus
= -tick
.temp_fracminus
1045 tick
.temp_fracdenom
= -tick
.temp_fracdenom
1046 gcd
= self
.gcd(tick
.temp_fracenum
, tick
.temp_fracdenom
)
1047 (tick
.temp_fracenum
, dummy1
), (tick
.temp_fracdenom
, dummy2
) = divmod(tick
.temp_fracenum
, gcd
), divmod(tick
.temp_fracdenom
, gcd
)
1049 equaldenom
= self
.lcm(*[tick
.temp_fracdenom
for tick
in ticks
if tick
.label
is None])
1050 if equaldenom
is not None:
1051 for tick
in labeledticks
:
1052 factor
, dummy
= divmod(equaldenom
, tick
.temp_fracdenom
)
1053 tick
.temp_fracenum
, tick
.temp_fracdenom
= factor
* tick
.temp_fracenum
, factor
* tick
.temp_fracdenom
1054 for tick
in labeledticks
:
1055 fracminus
= fracenumminus
= fracdenomminus
= ""
1056 if tick
.temp_fracminus
== -1:
1057 plusminus
= self
.minus
1059 plusminus
= self
.plus
1060 if self
.minuspos
== 0:
1061 fracminus
= plusminus
1062 elif self
.minuspos
== 1:
1063 fracenumminus
= plusminus
1064 elif self
.minuspos
== -1:
1065 fracdenomminus
= plusminus
1067 raise RuntimeError("invalid minuspos")
1068 if self
.skipenum0
and tick
.temp_fracenum
== 0:
1070 elif (self
.skip1
and self
.skipdenom1
and tick
.temp_fracenum
== 1 and tick
.temp_fracdenom
== 1 and
1071 (len(self
.prefix
) or len(self
.infix
) or len(self
.suffix
)) and
1072 not len(fracenumminus
) and not len(self
.enumprefix
) and not len(self
.enuminfix
) and not len(self
.enumsuffix
) and
1073 not len(fracdenomminus
) and not len(self
.denomprefix
) and not len(self
.denominfix
) and not len(self
.denomsuffix
)):
1074 tick
.label
= "%s%s%s%s" % (self
.prefix
, fracminus
, self
.infix
, self
.suffix
)
1076 if self
.skipenum1
and tick
.temp_fracenum
== 1 and (len(self
.enumprefix
) or len(self
.enuminfix
) or len(self
.enumsuffix
)):
1077 tick
.temp_fracenum
= "%s%s%s%s" % (self
.enumprefix
, fracenumminus
, self
.enuminfix
, self
.enumsuffix
)
1079 tick
.temp_fracenum
= "%s%s%s%i%s" % (self
.enumprefix
, fracenumminus
, self
.enuminfix
, tick
.temp_fracenum
, self
.enumsuffix
)
1080 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
):
1081 frac
= tick
.temp_fracenum
1083 tick
.temp_fracdenom
= "%s%s%s%i%s" % (self
.denomprefix
, fracdenomminus
, self
.denominfix
, tick
.temp_fracdenom
, self
.denomsuffix
)
1084 frac
= self
.over
% (tick
.temp_fracenum
, tick
.temp_fracdenom
)
1085 tick
.label
= "%s%s%s%s%s" % (self
.prefix
, fracminus
, self
.infix
, frac
, self
.suffix
)
1086 tick
.labelattrs
.extend(self
.labelattrs
)
1088 # del tick.temp_fracenum # we've inserted those temporary variables ... and do not care any longer about them
1089 # del tick.temp_fracdenom
1090 # del tick.temp_fracminus
1094 class decimaltexter
:
1095 "a texter creating decimal labels (e.g. '1.234' or even '0.\overline{3}')"
1097 __implements__
= _Itexter
1099 def __init__(self
, prefix
="", infix
="", suffix
="", equalprecision
=0,
1100 decimalsep
=".", thousandsep
="", thousandthpartsep
="",
1101 plus
="", minus
="-", period
=r
"\overline{%s}", labelattrs
=textmodule
.mathmode
):
1102 r
"""initializes the instance
1103 - prefix, infix, and suffix (strings) are added at the begin,
1104 immediately after the minus, and at the end of the label,
1106 - decimalsep, thousandsep, and thousandthpartsep (strings)
1107 are used as separators
1108 - plus or minus (string) is inserted for non-negative or negative numbers
1109 - period (string) is taken as a format string generating a period;
1110 it has to contain exactly one string insert operators "%s" for the
1111 period; usually it should be r"\overline{%s}"
1112 - labelattrs is a list of attributes for a texrunners text method;
1113 a single is allowed without being a list; None is considered as
1115 self
.prefix
= prefix
1117 self
.suffix
= suffix
1118 self
.equalprecision
= equalprecision
1119 self
.decimalsep
= decimalsep
1120 self
.thousandsep
= thousandsep
1121 self
.thousandthpartsep
= thousandthpartsep
1124 self
.period
= period
1125 self
.labelattrs
= helper
.ensurelist(labelattrs
)
1127 def labels(self
, ticks
):
1131 if tick
.label
is None and tick
.labellevel
is not None:
1132 labeledticks
.append(tick
)
1133 m
, n
= tick
.enum
, tick
.denom
1136 whole
, reminder
= divmod(m
, n
)
1138 if len(self
.thousandsep
):
1142 tick
.label
+= whole
[i
]
1143 if not ((l
-i
-1) % 3) and l
> i
+1:
1144 tick
.label
+= self
.thousandsep
1148 tick
.label
+= self
.decimalsep
1150 tick
.temp_decprecision
= 0
1152 tick
.temp_decprecision
+= 1
1153 if reminder
in oldreminders
:
1154 tick
.temp_decprecision
= None
1155 periodstart
= len(tick
.label
) - (len(oldreminders
) - oldreminders
.index(reminder
))
1156 tick
.label
= tick
.label
[:periodstart
] + self
.period
% tick
.label
[periodstart
:]
1158 oldreminders
+= [reminder
]
1160 whole
, reminder
= divmod(reminder
, n
)
1161 if not ((tick
.temp_decprecision
- 1) % 3) and tick
.temp_decprecision
> 1:
1162 tick
.label
+= self
.thousandthpartsep
1163 tick
.label
+= str(whole
)
1164 if maxdecprecision
< tick
.temp_decprecision
:
1165 maxdecprecision
= tick
.temp_decprecision
1166 if self
.equalprecision
:
1167 for tick
in labeledticks
:
1168 if tick
.temp_decprecision
is not None:
1169 if tick
.temp_decprecision
== 0 and maxdecprecision
> 0:
1170 tick
.label
+= self
.decimalsep
1171 for i
in range(tick
.temp_decprecision
, maxdecprecision
):
1172 if not ((i
- 1) % 3) and i
> 1:
1173 tick
.label
+= self
.thousandthpartsep
1175 for tick
in labeledticks
:
1176 if tick
.enum
* tick
.denom
< 0:
1177 plusminus
= self
.minus
1179 plusminus
= self
.plus
1180 tick
.label
= "%s%s%s%s%s" % (self
.prefix
, plusminus
, self
.infix
, tick
.label
, self
.suffix
)
1181 tick
.labelattrs
.extend(self
.labelattrs
)
1183 # del tick.temp_decprecision # we've inserted this temporary variable ... and do not care any longer about it
1186 class exponentialtexter
:
1187 "a texter creating labels with exponentials (e.g. '2\cdot10^5')"
1189 __implements__
= _Itexter
1191 def __init__(self
, plus
="", minus
="-",
1192 mantissaexp
=r
"{{%s}\cdot10^{%s}}",
1193 nomantissaexp
=r
"{10^{%s}}",
1194 minusnomantissaexp
=r
"{-10^{%s}}",
1195 mantissamin
=frac((1, 1)), mantissamax
=frac((10, 1)),
1196 skipmantissa1
=0, skipallmantissa1
=1,
1197 mantissatexter
=decimaltexter()):
1198 r
"""initializes the instance
1199 - plus or minus (string) is inserted for non-negative or negative exponents
1200 - mantissaexp (string) is taken as a format string generating the exponent;
1201 it has to contain exactly two string insert operators "%s" --
1202 the first for the mantissa and the second for the exponent;
1203 examples are r"{{%s}\cdot10^{%s}}" and r"{{%s}{\rm e}^{%s}}"
1204 - nomantissaexp (string) is taken as a format string generating the exponent
1205 when the mantissa is one and should be skipped; it has to contain
1206 exactly one string insert operators "%s" for the exponent;
1207 an examples is r"{10^{%s}}"
1208 - minusnomantissaexp (string) is taken as a format string generating the exponent
1209 when the mantissa is minus one and should be skipped; it has to contain
1210 exactly one string insert operators "%s" for the exponent; might be set to None
1211 to disallow skipping of any mantissa minus one
1212 an examples is r"{-10^{%s}}"
1213 - mantissamin and mantissamax are the minimum and maximum of the mantissa;
1214 they are frac instances greater than zero and mantissamin < mantissamax;
1215 the sign of the tick is ignored here
1216 - skipmantissa1 (boolean) turns on skipping of any mantissa equals one
1217 (and minus when minusnomantissaexp is set)
1218 - skipallmantissa1 (boolean) as above, but all mantissas must be 1
1219 - mantissatexter is the texter for the mantissa"""
1222 self
.mantissaexp
= mantissaexp
1223 self
.nomantissaexp
= nomantissaexp
1224 self
.minusnomantissaexp
= minusnomantissaexp
1225 self
.mantissamin
= mantissamin
1226 self
.mantissamax
= mantissamax
1227 self
.mantissamindivmax
= self
.mantissamin
/ self
.mantissamax
1228 self
.mantissamaxdivmin
= self
.mantissamax
/ self
.mantissamin
1229 self
.skipmantissa1
= skipmantissa1
1230 self
.skipallmantissa1
= skipallmantissa1
1231 self
.mantissatexter
= mantissatexter
1233 def labels(self
, ticks
):
1236 if tick
.label
is None and tick
.labellevel
is not None:
1237 tick
.temp_orgenum
, tick
.temp_orgdenom
= tick
.enum
, tick
.denom
1238 labeledticks
.append(tick
)
1241 while abs(tick
) >= self
.mantissamax
:
1243 x
= tick
* self
.mantissamindivmax
1244 tick
.enum
, tick
.denom
= x
.enum
, x
.denom
1245 while abs(tick
) < self
.mantissamin
:
1247 x
= tick
* self
.mantissamaxdivmin
1248 tick
.enum
, tick
.denom
= x
.enum
, x
.denom
1249 if tick
.temp_exp
< 0:
1250 tick
.temp_exp
= "%s%i" % (self
.minus
, -tick
.temp_exp
)
1252 tick
.temp_exp
= "%s%i" % (self
.plus
, tick
.temp_exp
)
1253 self
.mantissatexter
.labels(labeledticks
)
1254 if self
.minusnomantissaexp
is not None:
1255 allmantissa1
= len(labeledticks
) == len([tick
for tick
in labeledticks
if abs(tick
.enum
) == abs(tick
.denom
)])
1257 allmantissa1
= len(labeledticks
) == len([tick
for tick
in labeledticks
if tick
.enum
== tick
.denom
])
1258 for tick
in labeledticks
:
1259 if (self
.skipallmantissa1
and allmantissa1
or
1260 (self
.skipmantissa1
and (tick
.enum
== tick
.denom
or
1261 (tick
.enum
== -tick
.denom
and self
.minusnomantissaexp
is not None)))):
1262 if tick
.enum
== tick
.denom
:
1263 tick
.label
= self
.nomantissaexp
% tick
.temp_exp
1265 tick
.label
= self
.minusnomantissaexp
% tick
.temp_exp
1267 tick
.label
= self
.mantissaexp
% (tick
.label
, tick
.temp_exp
)
1268 tick
.enum
, tick
.denom
= tick
.temp_orgenum
, tick
.temp_orgdenom
1270 # del tick.temp_orgenum # we've inserted those temporary variables ... and do not care any longer about them
1271 # del tick.temp_orgdenom
1275 class defaulttexter
:
1276 "a texter creating decimal or exponential labels"
1278 __implements__
= _Itexter
1280 def __init__(self
, smallestdecimal
=frac((1, 1000)),
1281 biggestdecimal
=frac((9999, 1)),
1283 decimaltexter
=decimaltexter(),
1284 exponentialtexter
=exponentialtexter()):
1285 r
"""initializes the instance
1286 - smallestdecimal and biggestdecimal are the smallest and
1287 biggest decimal values, where the decimaltexter should be used;
1288 they are frac instances; the sign of the tick is ignored here;
1289 a tick at zero is considered for the decimaltexter as well
1290 - equaldecision (boolean) uses decimaltexter or exponentialtexter
1291 globaly (set) or for each tick separately (unset)
1292 - decimaltexter and exponentialtexter are texters to be used"""
1293 self
.smallestdecimal
= smallestdecimal
1294 self
.biggestdecimal
= biggestdecimal
1295 self
.equaldecision
= equaldecision
1296 self
.decimaltexter
= decimaltexter
1297 self
.exponentialtexter
= exponentialtexter
1299 def labels(self
, ticks
):
1303 if tick
.label
is None and tick
.labellevel
is not None:
1304 if not tick
.enum
or (abs(tick
) >= self
.smallestdecimal
and abs(tick
) <= self
.biggestdecimal
):
1305 decticks
.append(tick
)
1307 expticks
.append(tick
)
1308 if self
.equaldecision
:
1310 self
.exponentialtexter
.labels(ticks
)
1312 self
.decimaltexter
.labels(ticks
)
1314 for tick
in decticks
:
1315 self
.decimaltexter
.labels([tick
])
1316 for tick
in expticks
:
1317 self
.exponentialtexter
.labels([tick
])
1320 ################################################################################
1322 ################################################################################
1325 class axiscanvas(canvas
._canvas
):
1327 - an axis canvas is a regular canvas returned by an
1328 axispainters painter method
1329 - it contains a PyX length extent to be used for the
1330 alignment of additional axes; the axis extent should
1331 be handled by the axispainters painter method; you may
1332 apprehend this as a size information comparable to a
1333 bounding box, which must be handled manually
1334 - it contains a list of textboxes called labels which are
1335 used to rate the distances between the labels if needed
1336 by the axis later on; the painter method has not only to
1337 insert the labels into this canvas, but should also fill
1338 this list, when a rating of the distances should be
1339 performed by the axis"""
1341 # __implements__ = sole implementation
1343 def __init__(self
, *args
, **kwargs
):
1344 """initializes the instance
1345 - sets extent to zero
1346 - sets labels to an empty list"""
1347 canvas
._canvas
.__init
__(self
, *args
, **kwargs
)
1353 """create rotations accordingly to tick directions
1354 - upsidedown rotations are suppressed by rotating them by another 180 degree"""
1356 # __implements__ = sole implementation
1358 def __init__(self
, direction
, epsilon
=1e-10):
1359 """initializes the instance
1360 - direction is an angle to be used relative to the tick direction
1361 - epsilon is the value by which 90 degrees can be exceeded before
1362 an 180 degree rotation is added"""
1363 self
.direction
= direction
1364 self
.epsilon
= epsilon
1366 def trafo(self
, dx
, dy
):
1367 """returns a rotation transformation accordingly to the tick direction
1368 - dx and dy are the direction of the tick"""
1369 direction
= self
.direction
+ math
.atan2(dy
, dx
) * 180 / math
.pi
1370 while (direction
> 90 + self
.epsilon
):
1372 while (direction
< -90 - self
.epsilon
):
1374 return trafomodule
.rotate(direction
)
1377 rotatetext
.parallel
= rotatetext(-90)
1378 rotatetext
.orthogonal
= rotatetext(0)
1381 class _Iaxispainter
:
1382 "class for painting axes"
1384 def paint(self
, axispos
, axis
, ac
=None):
1385 """paint the axis into an axiscanvas
1386 - returns the axiscanvas
1387 - when no axiscanvas is provided (the typical case), a new
1388 axiscanvas is created. however, when extending an painter
1389 by inheritance, painting on the same axiscanvas is supported
1390 by setting the axiscanvas attribute
1391 - axispos is an instance, which implements _Iaxispos to
1392 define the tick positions
1393 - the axis and should not be modified (we may
1394 add some temporary variables like axis.ticks[i].temp_xxx,
1395 which might be used just temporary) -- the idea is that
1396 all things can be used several times
1397 - also do not modify the instance (self) -- even this
1398 instance might be used several times; thus do not modify
1399 attributes like self.titleattrs etc. (use local copies)
1400 - the method might access some additional attributes from
1401 the axis, e.g. the axis title -- the axis painter should
1402 document this behavior and rely on the availability of
1403 those attributes -> it becomes a question of the proper
1404 usage of the combination of axis & axispainter
1405 - the axiscanvas is a axiscanvas instance and should be
1406 filled with ticks, labels, title, etc.; note that the
1407 extent and labels instance variables should be handled
1408 as documented in the axiscanvas"""
1412 """interface definition of axis tick position methods
1413 - these methods are used for the postitioning of the ticks
1414 when painting an axis"""
1415 # TODO: should we add a local transformation (for label text etc?)
1416 # (this might replace tickdirection (and even tickposition?))
1418 def baseline(self
, x1
=None, x2
=None):
1419 """return the baseline as a path
1420 - x1 is the start position; if not set, the baseline starts
1421 from the beginning of the axis, which might imply a
1422 value outside of the graph coordinate range [0; 1]
1423 - x2 is analogous to x1, but for the end position"""
1425 def vbaseline(self
, v1
=None, v2
=None):
1426 """return the baseline as a path
1427 - like baseline, but for graph coordinates"""
1429 def gridline(self
, x
):
1430 """return the gridline as a path for a given position x
1431 - might return None when no gridline is available"""
1433 def vgridline(self
, v
):
1434 """return the gridline as a path for a given position v
1435 in graph coordinates
1436 - might return None when no gridline is available"""
1438 def _tickpoint(self
, x
):
1439 """return the position at the baseline as a tuple (x, y) in
1440 postscript points for the position x"""
1442 def tickpoint(self
, x
):
1443 """return the position at the baseline as a tuple (x, y) in
1444 in PyX length for the position x"""
1446 def _vtickpoint(self
, v
):
1447 "like _tickpoint, but for graph coordinates"
1449 def vtickpoint(self
, v
):
1450 "like tickpoint, but for graph coordinates"
1452 def tickdirection(self
, x
):
1453 """return the direction of a tick as a tuple (dx, dy) for the
1456 def vtickdirection(self
, v
):
1457 """like tickposition, but for graph coordinates"""
1461 """implements those parts of _Iaxispos which can be build
1462 out of the axis convert method and other _Iaxispos methods
1463 (its designed to be inherited)"""
1465 def __init__(self
, a
):
1466 """initializes the instance
1467 - only the convert method is needed from the axis a"""
1470 def baseline(self
, x1
=None, x2
=None):
1473 return self
.vbaseline()
1475 return self
.vbaseline(v2
=self
.axis
.convert(x2
))
1478 return self
.vbaseline(v1
=self
.axis
.convert(x1
))
1480 return self
.vbaseline(v1
=self
.axis
.convert(x1
), v2
=self
.axis
.convert(x2
))
1482 def gridline(self
, x
):
1483 return self
.vgridline(self
.axis
.convert(x
))
1485 def _tickpoint(self
, x
):
1486 return self
._vtickpoint
(self
.axis
.convert(x
))
1488 def tickpoint(self
, x
):
1489 return self
.vtickpoint(self
.axis
.convert(x
))
1491 def _vtickpoint(self
, v
):
1492 return map(unit
.topt
, self
.vtickpoint(v
))
1494 def tickdirection(self
, x
):
1495 return self
.vtickdirection(self
.axis
.convert(x
))
1498 class pathaxispos(_axispos
):
1499 """axis tick position methods along an arbitrary path"""
1501 __implements__
= _Iaxispos
1503 def __init__(self
, p
, a
, direction
=1):
1505 self
.normpath
= path
.normpath(p
)
1506 self
.arclength
= self
.normpath
.arclength(p
)
1507 _axispos
.__init
__(self
, a
)
1508 self
.direction
= direction
1510 def vbaseline(self
, v1
=None, v2
=None):
1515 return self
.normpath
.split(self
.normpath
.lentopar(v2
* self
.arclength
))[0]
1518 return self
.normpath
.split(self
.normpath
.lentopar(v1
* self
.arclength
))[1]
1520 return self
.normpath
.split(*self
.normpath
.lentopar([v1
* self
.arclength
, v2
* self
.arclength
]))[1]
1522 def vgridline(self
, v
):
1525 def vtickpoint(self
, v
):
1526 return self
.normpath
.at(self
.normpath
.lentopar(v
* self
.arclength
))
1528 def vtickdirection(self
, v
):
1529 t
= self
.normpath
.tangent(self
.normpath
.lentopar(v
* self
.arclength
))
1530 # XXX: path._begin and path._end missing!
1531 tbegin
= map(unit
.topt
, t
.begin())
1532 tend
= map(unit
.topt
, t
.end())
1533 dx
= tend
[0]-tbegin
[0]
1534 dy
= tend
[1]-tbegin
[1]
1535 norm
= math
.sqrt(dx
*dx
+ dy
*dy
)
1536 if self
.direction
== 1:
1537 return dy
/norm
, -dx
/norm
1538 elif self
.direction
== -1:
1539 return -dy
/norm
, dx
/norm
1540 raise RuntimeError("unknown direction")
1543 class axistitlepainter
:
1544 """class for painting an axis title
1545 - the axis must have a title attribute when using this painter;
1546 this title might be None"""
1548 __implements__
= _Iaxispainter
1550 def __init__(self
, titledist
="0.3 cm",
1551 titleattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1552 titledirection
=rotatetext
.parallel
,
1554 texrunner
=textmodule
.defaulttexrunner
):
1555 """initialized the instance
1556 - titledist is a visual PyX length giving the distance
1557 of the title from the axis extent already there (a title might
1558 be added after labels or other things are plotted already)
1559 - labelattrs is a list of attributes for a texrunners text
1560 method; a single is allowed without being a list; None
1562 - titledirection is an instance of rotatetext or None
1563 - titlepos is the position of the title in graph coordinates
1564 - texrunner is the texrunner to be used to create text
1565 (the texrunner is available for further use in derived
1566 classes as instance variable texrunner)"""
1567 self
.titledist_str
= titledist
1568 self
.titleattrs
= titleattrs
1569 self
.titledirection
= titledirection
1570 self
.titlepos
= titlepos
1571 self
.texrunner
= texrunner
1573 def paint(self
, axispos
, axis
, ac
=None):
1576 if axis
.title
is not None and self
.titleattrs
is not None:
1577 titledist
= unit
.length(self
.titledist_str
, default_type
="v")
1578 x
, y
= axispos
._vtickpoint
(self
.titlepos
)
1579 dx
, dy
= axispos
.vtickdirection(self
.titlepos
)
1580 titleattrs
= helper
.ensurelist(self
.titleattrs
)
1581 if self
.titledirection
is not None:
1582 titleattrs
.append(self
.titledirection
.trafo(dx
, dy
))
1583 title
= self
.texrunner
._text
(x
, y
, axis
.title
, *titleattrs
)
1584 ac
.extent
+= titledist
1585 title
.linealign(ac
.extent
, dx
, dy
)
1586 ac
.extent
+= title
.extent(dx
, dy
)
1591 class axispainter(axistitlepainter
):
1592 """class for painting the ticks and labels of an axis
1593 - the inherited titleaxispainter is used to paint the title of
1595 - note that the type of the elements of ticks given as an argument
1596 of the paint method must be suitable for the tick position methods
1599 __implements__
= _Iaxispainter
1601 defaultticklengths
= ["%0.5f cm" % (0.2*goldenmean
**(-i
)) for i
in range(10)]
1603 def __init__(self
, innerticklengths
=defaultticklengths
,
1604 outerticklengths
=None,
1608 baselineattrs
=style
.linecap
.square
,
1610 labelattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1611 labeldirection
=None,
1615 """initializes the instance
1616 - innerticklenths and outerticklengths are two lists of
1617 visual PyX lengths for ticks, subticks, etc. plotted inside
1618 and outside of the graph; when a single value is given, it
1619 is used for all tick levels; None turns off ticks inside or
1620 outside of the graph
1621 - tickattrs are a list of stroke attributes for the ticks;
1622 a single entry is allowed without being a list; None turns
1624 - gridlineattrs are a list of lists used as stroke
1625 attributes for ticks, subticks etc.; when a single list
1626 is given, it is used for ticks, subticks, etc.; a single
1627 entry is allowed without being a list; None turns off
1629 - zerolineattrs are a list of stroke attributes for a grid
1630 line at axis value zero; a single entry is allowed without
1631 being a list; None turns off the zeroline
1632 - baselineattrs are a list of stroke attributes for a grid
1633 line at axis value zero; a single entry is allowed without
1634 being a list; None turns off the baseline
1635 - labeldist is a visual PyX length for the distance of the labels
1636 from the axis baseline
1637 - labelattrs is a list of attributes for a texrunners text
1638 method; a single entry is allowed without being a list;
1639 None turns off the labels
1640 - titledirection is an instance of rotatetext or None
1641 - labelhequalize and labelvequalize (booleans) perform an equal
1642 alignment for straight vertical and horizontal axes, respectively
1643 - futher keyword arguments are passed to axistitlepainter"""
1644 # TODO: access to axis.divisor -- document, remove, ... ???
1645 self
.innerticklengths_str
= innerticklengths
1646 self
.outerticklengths_str
= outerticklengths
1647 self
.tickattrs
= tickattrs
1648 self
.gridattrs
= gridattrs
1649 self
.zerolineattrs
= zerolineattrs
1650 self
.baselineattrs
= baselineattrs
1651 self
.labeldist_str
= labeldist
1652 self
.labelattrs
= labelattrs
1653 self
.labeldirection
= labeldirection
1654 self
.labelhequalize
= labelhequalize
1655 self
.labelvequalize
= labelvequalize
1656 axistitlepainter
.__init
__(self
, **kwargs
)
1658 def paint(self
, axispos
, axis
, ac
=None):
1662 raise RuntimeError("XXX") # XXX debug only
1663 labeldist
= unit
.length(self
.labeldist_str
, default_type
="v")
1664 for tick
in axis
.ticks
:
1665 if axis
.divisor
is not None: # XXX workaround for timeaxis
1666 tick
.temp_v
= axis
.convert(float(tick
) * axis
.divisor
)
1668 tick
.temp_v
= axis
.convert(tick
)
1669 tick
.temp_x
, tick
.temp_y
= axispos
._vtickpoint
(tick
.temp_v
)
1670 tick
.temp_dx
, tick
.temp_dy
= axispos
.vtickdirection(tick
.temp_v
)
1672 # create & align tick.temp_labelbox
1673 for tick
in axis
.ticks
:
1674 if tick
.labellevel
is not None:
1675 labelattrs
= helper
.getsequenceno(self
.labelattrs
, tick
.labellevel
)
1676 if labelattrs
is not None:
1677 labelattrs
= helper
.ensurelist(labelattrs
)[:]
1678 if self
.labeldirection
is not None:
1679 labelattrs
.append(self
.labeldirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
1680 if tick
.labelattrs
is not None:
1681 labelattrs
.extend(helper
.ensurelist(tick
.labelattrs
))
1682 tick
.temp_labelbox
= self
.texrunner
._text
(tick
.temp_x
, tick
.temp_y
, tick
.label
, *labelattrs
)
1683 if len(axis
.ticks
) > 1:
1685 for tick
in axis
.ticks
[1:]:
1686 if tick
.temp_dx
!= axis
.ticks
[0].temp_dx
or tick
.temp_dy
!= axis
.ticks
[0].temp_dy
:
1690 if equaldirection
and ((not axis
.ticks
[0].temp_dx
and self
.labelvequalize
) or
1691 (not axis
.ticks
[0].temp_dy
and self
.labelhequalize
)):
1692 if self
.labelattrs
is not None:
1693 box
.linealignequal([tick
.temp_labelbox
for tick
in axis
.ticks
if tick
.labellevel
is not None],
1694 labeldist
, axis
.ticks
[0].temp_dx
, axis
.ticks
[0].temp_dy
)
1696 for tick
in axis
.ticks
:
1697 if tick
.labellevel
is not None and self
.labelattrs
is not None:
1698 tick
.temp_labelbox
.linealign(labeldist
, tick
.temp_dx
, tick
.temp_dy
)
1701 if helper
.issequence(arg
):
1702 return [unit
.length(a
, default_type
="v") for a
in arg
]
1704 return unit
.length(arg
, default_type
="v")
1705 innerticklengths
= mkv(self
.innerticklengths_str
)
1706 outerticklengths
= mkv(self
.outerticklengths_str
)
1708 for tick
in axis
.ticks
:
1709 if tick
.ticklevel
is not None:
1710 innerticklength
= helper
.getitemno(innerticklengths
, tick
.ticklevel
)
1711 outerticklength
= helper
.getitemno(outerticklengths
, tick
.ticklevel
)
1712 if innerticklength
is not None or outerticklength
is not None:
1713 if innerticklength
is None:
1715 if outerticklength
is None:
1717 tickattrs
= helper
.getsequenceno(self
.tickattrs
, tick
.ticklevel
)
1718 if tickattrs
is not None:
1719 _innerticklength
= unit
.topt(innerticklength
)
1720 _outerticklength
= unit
.topt(outerticklength
)
1721 x1
= tick
.temp_x
- tick
.temp_dx
* _innerticklength
1722 y1
= tick
.temp_y
- tick
.temp_dy
* _innerticklength
1723 x2
= tick
.temp_x
+ tick
.temp_dx
* _outerticklength
1724 y2
= tick
.temp_y
+ tick
.temp_dy
* _outerticklength
1725 ac
.stroke(path
._line
(x1
, y1
, x2
, y2
), *helper
.ensuresequence(tickattrs
))
1726 if tick
!= frac((0, 1)) or self
.zerolineattrs
is None:
1727 gridattrs
= helper
.getsequenceno(self
.gridattrs
, tick
.ticklevel
)
1728 if gridattrs
is not None:
1729 self
.stroke(axispos
.vgridline(tick
.temp_v
), *helper
.ensuresequence(gridattrs
))
1730 if outerticklength
is not None and unit
.topt(outerticklength
) > unit
.topt(ac
.extent
):
1731 ac
.extent
= outerticklength
1732 if outerticklength
is not None and unit
.topt(-innerticklength
) > unit
.topt(ac
.extent
):
1733 ac
.extent
= -innerticklength
1734 if tick
.labellevel
is not None and self
.labelattrs
is not None:
1735 ac
.insert(tick
.temp_labelbox
)
1736 ac
.labels
.append(tick
.temp_labelbox
)
1737 extent
= tick
.temp_labelbox
.extent(tick
.temp_dx
, tick
.temp_dy
) + labeldist
1738 if unit
.topt(extent
) > unit
.topt(ac
.extent
):
1740 if self
.baselineattrs
is not None:
1741 ac
.stroke(axispos
.vbaseline(), *helper
.ensuresequence(self
.baselineattrs
))
1742 if self
.zerolineattrs
is not None:
1743 if len(axis
.ticks
) and axis
.ticks
[0] * axis
.ticks
[-1] < frac((0, 1)):
1744 ac
.stroke(axispos
.gridline(0), *helper
.ensuresequence(self
.zerolineattrs
))
1746 # for tick in axis.ticks:
1747 # del tick.temp_v # we've inserted those temporary variables ... and do not care any longer about them
1752 # if tick.labellevel is not None and self.labelattrs is not None:
1753 # del tick.temp_labelbox
1755 axistitlepainter
.paint(self
, axispos
, axis
, ac
=ac
)
1760 class linkaxispainter(axispainter
):
1761 """class for painting a linked axis
1762 - the inherited axispainter is used to paint the axis
1763 - modifies some constructor defaults"""
1765 __implements__
= _Iaxispainter
1767 def __init__(self
, zerolineattrs
=None,
1771 """initializes the instance
1772 - the zerolineattrs default is set to None thus skipping the zeroline
1773 - the labelattrs default is set to None thus skipping the labels
1774 - the titleattrs default is set to None thus skipping the title
1775 - all keyword arguments are passed to axispainter"""
1776 axispainter
.__init
__(self
, zerolineattrs
=zerolineattrs
,
1777 labelattrs
=labelattrs
,
1778 titleattrs
=titleattrs
,
1782 class splitaxispainter(axistitlepainter
):
1783 """class for painting a splitaxis
1784 - the inherited titleaxispainter is used to paint the title of
1786 - the splitaxispainter access the subaxes attribute of the axis"""
1788 __implements__
= _Iaxispainter
1790 def __init__(self
, breaklinesdist
="0.05 cm",
1791 breaklineslength
="0.5 cm",
1792 breaklinesangle
=-60,
1795 """initializes the instance
1796 - breaklinesdist is a visual length of the distance between
1797 the two lines of the axis break
1798 - breaklineslength is a visual length of the length of the
1799 two lines of the axis break
1800 - breaklinesangle is the angle of the lines of the axis break
1801 - breaklinesattrs are a list of stroke attributes for the
1802 axis break lines; a single entry is allowed without being a
1803 list; None turns off the break lines
1804 - futher keyword arguments are passed to axistitlepainter"""
1805 self
.breaklinesdist_str
= breaklinesdist
1806 self
.breaklineslength_str
= breaklineslength
1807 self
.breaklinesangle
= breaklinesangle
1808 self
.breaklinesattrs
= breaklinesattrs
1809 axistitlepainter
.__init
__(self
, **args
)
1811 def paint(self
, axispos
, axis
, ac
=None):
1815 raise RuntimeError("XXX") # XXX debug only
1816 for subaxis
in axis
.subaxes
:
1817 subac
= subaxis
.finish(axispos
)
1819 if unit
.topt(ac
.extent
) < unit
.topt(subac
.extent
):
1820 ac
.extent
= subac
.extent
1821 if self
.breaklinesattrs
is not None:
1822 self
.breaklinesdist
= unit
.length(self
.breaklinesdist_str
, default_type
="v")
1823 self
.breaklineslength
= unit
.length(self
.breaklineslength_str
, default_type
="v")
1824 self
.sin
= math
.sin(self
.breaklinesangle
*math
.pi
/180.0)
1825 self
.cos
= math
.cos(self
.breaklinesangle
*math
.pi
/180.0)
1826 breaklinesextent
= (0.5*self
.breaklinesdist
*math
.fabs(self
.cos
) +
1827 0.5*self
.breaklineslength
*math
.fabs(self
.sin
))
1828 if unit
.topt(ac
.extent
) < unit
.topt(breaklinesextent
):
1829 ac
.extent
= breaklinesextent
1830 for subaxis1
, subaxis2
in zip(axis
.subaxes
[:-1], axis
.subaxes
[1:]):
1831 # use a tangent of the baseline (this is independent of the tickdirection)
1832 v
= 0.5 * (subaxis1
.vmax
+ subaxis2
.vmin
)
1833 p
= path
.normpath(axispos
.vbaseline(v
, None))
1834 breakline
= p
.tangent(0, self
.breaklineslength
)
1835 widthline
= p
.tangent(0, self
.breaklinesdist
).transformed(trafomodule
.rotate(self
.breaklinesangle
+90, *breakline
.begin()))
1836 tocenter
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(breakline
.begin(), breakline
.end()))
1837 towidth
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(widthline
.begin(), widthline
.end()))
1838 breakline
= breakline
.transformed(trafomodule
.translate(*tocenter
).rotated(self
.breaklinesangle
, *breakline
.begin()))
1839 breakline1
= breakline
.transformed(trafomodule
.translate(*towidth
))
1840 breakline2
= breakline
.transformed(trafomodule
.translate(-towidth
[0], -towidth
[1]))
1841 ac
.fill(path
.path(path
.moveto(*breakline1
.begin()),
1842 path
.lineto(*breakline1
.end()),
1843 path
.lineto(*breakline2
.end()),
1844 path
.lineto(*breakline2
.begin()),
1845 path
.closepath()), color
.gray
.white
)
1846 ac
.stroke(breakline1
, *helper
.ensuresequence(self
.breaklinesattrs
))
1847 ac
.stroke(breakline2
, *helper
.ensuresequence(self
.breaklinesattrs
))
1848 axistitlepainter
.paint(self
, axispos
, axis
, ac
=ac
)
1852 class linksplitaxispainter(splitaxispainter
):
1853 """class for painting a linked splitaxis
1854 - the inherited splitaxispainter is used to paint the axis
1855 - modifies some constructor defaults"""
1857 __implements__
= _Iaxispainter
1859 def __init__(self
, titleattrs
=None, **kwargs
):
1860 """initializes the instance
1861 - the titleattrs default is set to None thus skipping the title
1862 - all keyword arguments are passed to splitaxispainter"""
1863 splitaxispainter
.__init
__(self
, titleattrs
=titleattrs
, **kwargs
)
1866 class baraxispainter(axistitlepainter
):
1867 """class for painting a baraxis
1868 - the inherited titleaxispainter is used to paint the title of
1870 - the baraxispainter access the multisubaxis, subaxis names, texts, and
1871 relsizes attributes"""
1873 __implements__
= _Iaxispainter
1875 def __init__(self
, innerticklength
=None,
1876 outerticklength
=None,
1878 baselineattrs
=style
.linecap
.square
,
1880 nameattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1886 """initializes the instance
1887 - innerticklength and outerticklength are a visual length of
1888 the ticks to be plotted at the axis baseline to visually
1889 separate the bars; if neither innerticklength nor
1890 outerticklength are set, not ticks are plotted
1891 - breaklinesattrs are a list of stroke attributes for the
1892 axis tick; a single entry is allowed without being a
1893 list; None turns off the ticks
1894 - namedist is a visual PyX length for the distance of the bar
1895 names from the axis baseline
1896 - nameattrs is a list of attributes for a texrunners text
1897 method; a single entry is allowed without being a list;
1898 None turns off the names
1899 - namedirection is an instance of rotatetext or None
1900 - namehequalize and namevequalize (booleans) perform an equal
1901 alignment for straight vertical and horizontal axes, respectively
1902 - futher keyword arguments are passed to axistitlepainter"""
1903 self
.innerticklength_str
= innerticklength
1904 self
.outerticklength_str
= outerticklength
1905 self
.tickattrs
= tickattrs
1906 self
.baselineattrs
= baselineattrs
1907 self
.namedist_str
= namedist
1908 self
.nameattrs
= nameattrs
1909 self
.namedirection
= namedirection
1910 self
.namepos
= namepos
1911 self
.namehequalize
= namehequalize
1912 self
.namevequalize
= namevequalize
1913 axistitlepainter
.__init
__(self
, **args
)
1915 def paint(self
, axispos
, axis
, ac
=None):
1919 raise RuntimeError("XXX") # XXX debug only
1920 if axis
.multisubaxis
is not None:
1921 for subaxis
in axis
.subaxis
:
1922 subac
= subaxis
.finish(axis
, self
.texrunner
)
1924 if unit
.topt(ac
.extent
) < unit
.topt(subac
.extent
):
1925 ac
.extent
= subac
.extent
1927 for name
in axis
.names
:
1928 v
= axis
.convert((name
, self
.namepos
))
1929 x
, y
= axispos
._vtickpoint
(v
)
1930 dx
, dy
= axispos
.vtickdirection(v
)
1931 namepos
.append((v
, x
, y
, dx
, dy
))
1933 if self
.nameattrs
is not None:
1934 for (v
, x
, y
, dx
, dy
), name
in zip(namepos
, axis
.names
):
1935 nameattrs
= helper
.ensurelist(self
.nameattrs
)[:]
1936 if self
.namedirection
is not None:
1937 nameattrs
.append(self
.namedirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
1938 if axis
.texts
.has_key(name
):
1939 nameboxes
.append(self
.texrunner
._text
(x
, y
, str(axis
.texts
[name
]), *nameattrs
))
1940 elif axis
.texts
.has_key(str(name
)):
1941 nameboxes
.append(self
.texrunner
._text
(x
, y
, str(axis
.texts
[str(name
)]), *nameattrs
))
1943 nameboxes
.append(self
.texrunner
._text
(x
, y
, str(name
), *nameattrs
))
1944 labeldist
= ac
.extent
+ unit
.length(self
.namedist_str
, default_type
="v")
1945 if len(namepos
) > 1:
1947 for np
in namepos
[1:]:
1948 if np
[3] != namepos
[0][3] or np
[4] != namepos
[0][4]:
1952 if equaldirection
and ((not namepos
[0][3] and self
.namevequalize
) or
1953 (not namepos
[0][4] and self
.namehequalize
)):
1954 box
.linealignequal(nameboxes
, labeldist
, namepos
[0][3], namepos
[0][4])
1956 for namebox
, np
in zip(nameboxes
, namepos
):
1957 namebox
.linealign(labeldist
, np
[3], np
[4])
1958 if self
.innerticklength_str
is not None:
1959 innerticklength
= unit
.length(self
.innerticklength_str
, default_type
="v")
1960 _innerticklength
= unit
.topt(innerticklength
)
1961 if self
.tickattrs
is not None and unit
.topt(ac
.extent
) < -_innerticklength
:
1962 ac
.extent
= -innerticklength
1963 elif self
.outerticklength_str
is not None:
1964 innerticklength
= _innerticklength
= 0
1965 if self
.outerticklength_str
is not None:
1966 outerticklength
= unit
.length(self
.outerticklength_str
, default_type
="v")
1967 _outerticklength
= unit
.topt(outerticklength
)
1968 if self
.tickattrs
is not None and unit
.topt(ac
.extent
) < _outerticklength
:
1969 ac
.extent
= outerticklength
1970 elif self
.innerticklength_str
is not None:
1971 outerticklength
= _outerticklength
= 0
1972 for (v
, x
, y
, dx
, dy
), namebox
in zip(namepos
, nameboxes
):
1973 newextent
= namebox
.extent(dx
, dy
) + labeldist
1974 if unit
.topt(ac
.extent
) < unit
.topt(newextent
):
1975 ac
.extent
= newextent
1976 if self
.tickattrs
is not None and (self
.innerticklength_str
is not None or self
.outerticklength_str
is not None):
1977 for pos
in axis
.relsizes
:
1978 if pos
== axis
.relsizes
[0]:
1979 pos
-= axis
.firstdist
1980 elif pos
!= axis
.relsizes
[-1]:
1981 pos
-= 0.5 * axis
.dist
1982 v
= pos
/ axis
.relsizes
[-1]
1983 x
, y
= axispos
._vtickpoint
(v
)
1984 dx
, dy
= axispos
.vtickdirection(v
)
1985 x1
= x
- dx
* _innerticklength
1986 y1
= y
- dy
* _innerticklength
1987 x2
= x
+ dx
* _outerticklength
1988 y2
= y
+ dy
* _outerticklength
1989 ac
.stroke(path
._line
(x1
, y1
, x2
, y2
), *helper
.ensuresequence(self
.tickattrs
))
1990 if self
.baselineattrs
is not None:
1991 p
= axispos
.vbaseline()
1993 ac
.stroke(p
, *helper
.ensuresequence(self
.baselineattrs
))
1994 for namebox
in nameboxes
:
1996 axistitlepainter
.paint(self
, axispos
, axis
, ac
=ac
)
1999 class linkbaraxispainter(baraxispainter
):
2000 """class for painting a linked baraxis
2001 - the inherited baraxispainter is used to paint the axis
2002 - modifies some constructor defaults"""
2004 __implements__
= _Iaxispainter
2006 def __init__(self
, nameattrs
=None, titleattrs
=None, **kwargs
):
2007 """initializes the instance
2008 - the titleattrs default is set to None thus skipping the title
2009 - the nameattrs default is set to None thus skipping the names
2010 - all keyword arguments are passed to axispainter"""
2011 baraxispainter
.__init
__(self
, nameattrs
=nameattrs
, titleattrs
=titleattrs
, **kwargs
)
2014 ################################################################################
2016 ################################################################################
2020 """interface definition of a axis
2021 - an axis should implement an convert and invert method like
2022 _Imap, but this is not part of this interface definition;
2023 one possibility is to mix-in a proper map class, but special
2024 purpose axes might do something else"""
2026 def convert(self
, x
):
2027 "convert a value into graph coordinates"
2029 def invert(self
, v
):
2030 "invert a graph coordinate to a axis value"
2032 def getrelsize(self
):
2033 """returns the relative size (width) of the axis
2034 - for use in splitaxis, baraxis etc.
2035 - might return None if no size is available"""
2037 def setrange(self
, min=None, max=None):
2038 """set the axis data range
2039 - the type of min and max must fit to the axis
2040 - min<max; the axis might be reversed, but this is
2041 expressed internally only (min<max all the time)
2042 - the axis might not apply the change of the range
2043 (e.g. when the axis range is fixed by the user),
2044 but usually the range is extended to contain the
2046 - for invalid parameters (e.g. negativ values at an
2047 logarithmic axis), an exception should be raised
2048 - a RuntimeError is raised, when setrange is called
2049 after the finish method"""
2052 """return data range as a tuple (min, max)
2053 - min<max; the axis might be reversed, but this is
2054 expressed internally only
2055 - a RuntimeError exception is raised when no
2056 range is available"""
2058 def finish(self
, axispos
):
2059 """finishes the axis
2060 - axispos implements _Iaxispos
2061 - the finish method returns an axiscanvas, which should be
2062 insertable into the graph to finally paint the axis
2063 - any modification of the axis range should be disabled after
2064 the finish method was called"""
2065 # TODO: be more specific about exceptions
2067 def createlinkaxis(self
, **kwargs
):
2068 """create a link axis to the axis itself
2069 - typically, a link axis is a axis, which share almost
2070 all properties with the axis it is linked to
2071 - typically, the painter gets replaced by a painter
2072 which doesn't put any text to the axis"""
2076 """base implementation a regular axis
2077 - typical usage is to mix-in a linmap or a logmap to
2078 complete the definition"""
2080 def __init__(self
, min=None, max=None, reverse
=0, divisor
=1,
2081 title
=None, painter
=axispainter(), texter
=defaulttexter(),
2082 density
=1, maxworse
=2):
2083 """initializes the instance
2084 - min and max fix the axis minimum and maximum, respectively;
2085 they are determined by the data to be plotted, when not fixed
2086 - reverse (boolean) reverses the minimum and the maximum of
2088 - numerical divisor for the axis partitioning
2089 - title is a string containing the axis title
2090 - axispainter is the axis painter (should implement _Ipainter)
2091 - texter is the texter (should implement _Itexter)
2092 - density is a global parameter for the axis paritioning and
2093 axis rating; its default is 1, but the range 0.5 to 2.5 should
2094 be usefull to get less or more ticks by the automatic axis
2096 - maxworse is a number of trials with worse tick rating
2097 before giving up (usually it should not be needed to increase
2098 this value; increasing the number will slow down the automatic
2099 axis partitioning considerably)
2100 - note that some methods of this class want to access a
2101 part and a rating attribute of the instance; those
2102 attributes should be initialized by the constructors
2103 of derived classes"""
2104 if None not in (min, max) and min > max:
2105 min, max, reverse
= max, min, not reverse
2106 self
.fixmin
, self
.fixmax
, self
.min, self
.max, self
.reverse
= min is not None, max is not None, min, max, reverse
2107 self
.divisor
= divisor
2109 self
.painter
= painter
2110 self
.texter
= texter
2111 self
.density
= density
2112 self
.maxworse
= maxworse
2115 self
._setinternalrange
()
2117 def _setinternalrange(self
, min=None, max=None):
2118 if not self
.fixmin
and min is not None and (self
.min is None or min < self
.min):
2120 if not self
.fixmax
and max is not None and (self
.max is None or max > self
.max):
2122 if None not in (self
.min, self
.max):
2124 if not self
.reverse
:
2125 self
.setbasepoints(((self
.min, 1), (self
.max, 0)))
2127 self
.setbasepoints(((self
.min, 0), (self
.max, 1)))
2129 def _getinternalrange(self
):
2130 return self
.min, self
.max
2132 def _forceinternalrange(self
, range):
2133 self
.min, self
.max = range
2134 self
._setinternalrange
()
2136 def setrange(self
, min=None, max=None):
2138 raise RuntimeError("axis was already finished")
2139 self
._setinternalrange
(min, max)
2142 if self
.min is not None and self
.max is not None:
2143 return self
.min, self
.max
2145 def checkfraclist(self
, fracs
):
2146 "orders a list of fracs, equal entries are not allowed"
2147 if not len(fracs
): return []
2148 sorted = list(fracs
)
2151 for item
in sorted[1:]:
2153 raise ValueError("duplicate entry found")
2157 def finish(self
, axispos
, texrunner
):
2161 min, max = self
.getrange()
2162 parter
= parterpos
= None
2163 if self
.part
is not None:
2164 self
.part
= helper
.ensurelist(self
.part
)
2165 for p
, i
in zip(self
.part
, xrange(sys
.maxint
)):
2166 if hasattr(p
, "defaultpart"):
2167 if parter
is not None:
2168 raise RuntimeError("only one partitioner allowed")
2172 self
.ticks
= self
.checkfraclist(self
.part
)
2174 self
.part
[:parterpos
] = self
.checkfraclist(self
.part
[:parterpos
])
2175 self
.part
[parterpos
+1:] = self
.checkfraclist(self
.part
[parterpos
+1:])
2176 # if self.divisor is not None: # XXX workaround for timeaxis
2177 self
.ticks
= _mergeticklists(
2178 _mergeticklists(self
.part
[:parterpos
],
2179 parter
.defaultpart(min/self
.divisor
,
2183 self
.part
[parterpos
+1:])
2185 # self.ticks = _mergeticklists(
2186 # _mergeticklists(self.part[:parterpos],
2187 # parter.defaultpart(min,
2190 # not self.fixmax)),
2191 # self.part[parterpos+1:])
2194 # lesspart and morepart can be called after defaultpart;
2195 # this works although some axes may share their autoparting,
2196 # because the axes are processed sequentially
2199 while worse
< self
.maxworse
:
2200 if parter
is not None:
2201 newticks
= parter
.lesspart()
2202 if parterpos
is not None and newticks
is not None:
2203 newticks
= _mergeticklists(_mergeticklists(self
.part
[:parterpos
], newticks
), self
.part
[parterpos
+1:])
2206 if newticks
is not None:
2208 bestrate
= self
.rater
.ratepart(self
, self
.ticks
, self
.density
)
2209 variants
= [[bestrate
, self
.ticks
]]
2211 newrate
= self
.rater
.ratepart(self
, newticks
, self
.density
)
2212 variants
.append([newrate
, newticks
])
2213 if newrate
< bestrate
:
2221 while worse
< self
.maxworse
:
2222 if parter
is not None:
2223 newticks
= parter
.morepart()
2224 if parterpos
is not None and newticks
is not None:
2225 newticks
= _mergeticklists(_mergeticklists(self
.part
[:parterpos
], newticks
), self
.part
[parterpos
+1:])
2228 if newticks
is not None:
2230 bestrate
= self
.rater
.ratepart(self
, self
.ticks
, self
.density
)
2231 variants
= [[bestrate
, self
.ticks
]]
2233 newrate
= self
.rater
.ratepart(self
, newticks
, self
.density
)
2234 variants
.append([newrate
, newticks
])
2235 if newrate
< bestrate
:
2245 if self
.painter
is not None:
2248 while i
< len(variants
) and (bestrate
is None or variants
[i
][0] < bestrate
):
2249 saverange
= self
._getinternalrange
()
2250 self
.ticks
= variants
[i
][1]
2252 # if self.divisor is not None: # XXX workaround for timeaxis
2253 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2255 # self.setrange(self.ticks[0], self.ticks[-1])
2256 self
.texter
.labels(self
.ticks
)
2257 ac
= self
.painter
.paint(axispos
, self
)
2258 ratelayout
= self
.rater
.ratelayout(ac
, self
.density
)
2259 if ratelayout
is not None:
2260 variants
[i
][0] += ratelayout
2261 variants
[i
].append(ac
)
2263 variants
[i
][0] = None
2264 if variants
[i
][0] is not None and (bestrate
is None or variants
[i
][0] < bestrate
):
2265 bestrate
= variants
[i
][0]
2266 self
._forceinternalrange
(saverange
)
2268 if bestrate
is None:
2269 raise RuntimeError("no valid axis partitioning found")
2270 variants
= [variant
for variant
in variants
[:i
] if variant
[0] is not None]
2272 self
.ticks
= variants
[0][1]
2274 # if self.divisor is not None: # XXX workaround for timeaxis
2275 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2277 # self.setrange(self.ticks[0], self.ticks[-1])
2281 # if self.divisor is not None: # XXX workaround for timeaxis
2282 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2284 # self.setrange(self.ticks[0], self.ticks[-1])
2288 # if self.divisor is not None: # XXX workaround for timeaxis
2289 self
.setrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2291 # self.setrange(self.ticks[0], self.ticks[-1])
2292 self
.texter
.labels(self
.ticks
)
2293 ac
= self
.painter
.paint(axispos
, self
)
2297 def createlinkaxis(self
, **args
):
2298 return linkaxis(self
, **args
)
2301 class linaxis(_axis
, _linmap
):
2302 """implementation of a linear axis"""
2304 __implements__
= _Iaxis
2306 def __init__(self
, part
=autolinpart(), rater
=axisrater(), **args
):
2307 """initializes the instance
2308 - the part attribute contains a list of one partitioner
2309 (a partitioner implements _Ipart) or/and some (manually
2310 set) ticks (implementing _Itick); a single entry might
2311 be passed without wrapping it into a list; the partitioner
2312 and the tick instances must fit to the type of the axis
2313 (e.g. they should be valid parameters to the axis convert
2314 method); the ticks and the partitioner results are mixed
2316 - the rater implements _Irater and is used to rate different
2317 tick lists created by the partitioner (after merging with
2319 - futher keyword arguments are passed to _axis"""
2320 _axis
.__init
__(self
, **args
)
2321 if self
.fixmin
and self
.fixmax
:
2322 self
.relsize
= self
.max - self
.min
2327 class logaxis(_axis
, _logmap
):
2328 """implementation of a logarithmic axis"""
2330 __implements__
= _Iaxis
2332 def __init__(self
, part
=autologpart(), rater
=axisrater(ticks
=axisrater
.logticks
, labels
=axisrater
.loglabels
), **args
):
2333 """initializes the instance
2334 - the part attribute contains a list of one partitioner
2335 (a partitioner implements _Ipart) or/and some (manually
2336 set) ticks (implementing _Itick); a single entry might
2337 be passed without wrapping it into a list; the partitioner
2338 and the tick instances must fit to the type of the axis
2339 (e.g. they should be valid parameters to the axis convert
2340 method); the ticks and the partitioner results are mixed
2342 - the rater implements _Irater and is used to rate different
2343 tick lists created by the partitioner (after merging with
2345 - futher keyword arguments are passed to _axis"""
2346 _axis
.__init
__(self
, **args
)
2347 if self
.fixmin
and self
.fixmax
:
2348 self
.relsize
= math
.log(self
.max) - math
.log(self
.min)
2354 """a axis linked to an already existing regular axis
2355 - almost all properties of the axis are "copied" from the
2356 axis this axis is linked to
2357 - usually, linked axis are used to create an axis to an
2358 existing axis with different painting properties; linked
2359 axis can be used to plot an axis twice at the opposite
2360 sides of a graphxy or even to share an axis between
2361 different graphs!"""
2363 __implements__
= _Iaxis
2365 def __init__(self
, linkedaxis
, painter
=linkaxispainter()):
2366 """initializes the instance
2367 - it gets a axis this linkaxis is linked to
2368 - it gets a painter to be used for this linked axis"""
2369 self
.linkedaxis
= linkedaxis
2370 self
.painter
= painter
2373 def __getattr__(self
, attr
):
2374 """access to unkown attributes are handed over to the
2375 axis this linkaxis is linked to"""
2376 return getattr(self
.linkedaxis
, attr
)
2378 def finish(self
, axispos
, texrunner
):
2379 """finishes the axis
2380 - instead of performing the hole finish process
2381 (paritioning, rating, etc.) just a painter call
2386 self
.linkedaxis
.finish(axispos
, texrunner
) # multiple calls to finish!
2387 return self
.painter
.paint(axispos
, self
)
2391 """implementation of the _Iaxispos interface for subaxis
2392 - provides the _Iaxispos interface for axis which contain
2394 - typical usage by mix-in this class into an "main" axis and
2395 calling the painter(s) of the subaxes from the specialized
2396 painter of the "main" axis; the subaxes need to have the
2397 attributes "baseaxis" (reference to the "main" axis) and
2398 "baseaxispos" (reference to the instance implementing the
2399 _Iaxispos interface of the "main" axis)"""
2401 __implements__
= _Iaxispos
2403 def baseline(self
, x1
=None, x2
=None, axis
=None):
2405 v1
= axis
.convert(x1
)
2409 v2
= axis
.convert(x2
)
2412 return axis
.baseaxispos
.vbaseline(v1
, v2
, axis
=axis
.baseaxis
)
2414 def vbaseline(self
, v1
=None, v2
=None, axis
=None):
2418 left
= axis
.vmin
+v1
*(axis
.vmax
-axis
.vmin
)
2422 right
= axis
.vmin
+v2
*(axis
.vmax
-axis
.vmin
)
2423 return axis
.baseaxispos
.vbaseline(left
, right
, axis
=axis
.baseaxis
)
2425 def gridline(self
, x
, axis
=None):
2426 return axis
.baseaxispos
.vgridline(axis
.vmin
+axis
.convert(x
)*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2428 def vgridline(self
, v
, axis
=None):
2429 return axis
.baseaxispos
.vgridline(axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2431 def _tickpoint(self
, x
, axis
=None):
2432 return axis
.baseaxispos
._vtickpoint
(axis
.vmin
+axis
.convert(x
)*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2434 def tickpoint(self
, x
, axis
=None):
2435 return axis
.baseaxispos
.vtickpoint(axis
.vmin
+axis
.convert(x
)*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2437 def _vtickpoint(self
, v
, axis
=None):
2438 return axis
.baseaxispos
._vtickpoint
(axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2440 def vtickpoint(self
, v
, axis
=None):
2441 return axis
.baseaxispos
.vtickpoint(axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2443 def tickdirection(self
, x
, axis
=None):
2444 return axis
.baseaxispos
.vtickdirection(axis
.vmin
+axis
.convert(x
)*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2446 def vtickdirection(self
, v
, axis
=None):
2447 return axis
.baseaxispos
.vtickdirection(axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2450 class splitaxis(_subaxispos
):
2451 """implementation of a split axis
2452 - a split axis contains several (sub-)axes with
2453 non-overlapping data ranges -- between these subaxes
2454 the axis is "splitted"
2455 - (just to get sure: a splitaxis can contain other
2456 splitaxes as its subaxes)
2457 - a splitaxis implements the _Iaxispos for its subaxes
2458 by inheritance from _subaxispos"""
2460 __implements__
= _Iaxis
, _Iaxispos
2462 def __init__(self
, subaxes
, splitlist
=0.5, splitdist
=0.1, relsizesplitdist
=1,
2463 title
=None, painter
=splitaxispainter()):
2464 """initializes the instance
2465 - subaxes is a list of subaxes
2466 - splitlist is a list of graph coordinates, where the splitting
2467 of the main axis should be performed; a single entry (splitting
2468 two axes) doesn't need to be wrapped into a list; if the list
2469 isn't long enough for the subaxes, missing entries are considered
2471 - splitdist is the size of the splitting in graph coordinates, when
2472 the associated splitlist entry is not None
2473 - relsizesplitdist: a None entry in splitlist means, that the
2474 position of the splitting should be calculated out of the
2475 relsize values of conrtibuting subaxes (the size of the
2476 splitting is relsizesplitdist in values of the relsize values
2478 - title is the title of the axis as a string
2479 - painter is the painter of the axis; it should be specialized to
2481 - the relsize of the splitaxis is the sum of the relsizes of the
2482 subaxes including the relsizesplitdist"""
2483 self
.subaxes
= subaxes
2484 self
.painter
= painter
2486 self
.splitlist
= helper
.ensurelist(splitlist
)
2487 for subaxis
in self
.subaxes
:
2490 self
.subaxes
[0].vmin
= 0
2491 self
.subaxes
[0].vminover
= None
2492 self
.subaxes
[-1].vmax
= 1
2493 self
.subaxes
[-1].vmaxover
= None
2494 for i
in xrange(len(self
.splitlist
)):
2495 if self
.splitlist
[i
] is not None:
2496 self
.subaxes
[i
].vmax
= self
.splitlist
[i
] - 0.5*splitdist
2497 self
.subaxes
[i
].vmaxover
= self
.splitlist
[i
]
2498 self
.subaxes
[i
+1].vmin
= self
.splitlist
[i
] + 0.5*splitdist
2499 self
.subaxes
[i
+1].vminover
= self
.splitlist
[i
]
2501 while i
< len(self
.subaxes
):
2502 if self
.subaxes
[i
].vmax
is None:
2503 j
= relsize
= relsize2
= 0
2504 while self
.subaxes
[i
+ j
].vmax
is None:
2505 relsize
+= self
.subaxes
[i
+ j
].relsize
+ relsizesplitdist
2507 relsize
+= self
.subaxes
[i
+ j
].relsize
2508 vleft
= self
.subaxes
[i
].vmin
2509 vright
= self
.subaxes
[i
+ j
].vmax
2510 for k
in range(i
, i
+ j
):
2511 relsize2
+= self
.subaxes
[k
].relsize
2512 self
.subaxes
[k
].vmax
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2513 relsize2
+= 0.5 * relsizesplitdist
2514 self
.subaxes
[k
].vmaxover
= self
.subaxes
[k
+ 1].vminover
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2515 relsize2
+= 0.5 * relsizesplitdist
2516 self
.subaxes
[k
+1].vmin
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2517 if i
== 0 and i
+ j
+ 1 == len(self
.subaxes
):
2518 self
.relsize
= relsize
2523 self
.fixmin
= self
.subaxes
[0].fixmin
2525 self
.min = self
.subaxes
[0].min
2526 self
.fixmax
= self
.subaxes
[-1].fixmax
2528 self
.max = self
.subaxes
[-1].max
2532 min = self
.subaxes
[0].getrange()
2533 max = self
.subaxes
[-1].getrange()
2535 return min[0], max[1]
2539 def setdatarange(self
, min, max):
2540 self
.subaxes
[0].setdatarange(min, None)
2541 self
.subaxes
[-1].setdatarange(None, max)
2543 def gettickrange(self
):
2544 min = self
.subaxes
[0].gettickrange()
2545 max = self
.subaxes
[-1].gettickrange()
2547 return min[0], max[1]
2551 def settickrange(self
, min, max):
2552 self
.subaxes
[0].settickrange(min, None)
2553 self
.subaxes
[-1].settickrange(None, max)
2555 def convert(self
, value
):
2556 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
2557 if value
< self
.subaxes
[0].max:
2558 return self
.subaxes
[0].vmin
+ self
.subaxes
[0].convert(value
)*(self
.subaxes
[0].vmax
-self
.subaxes
[0].vmin
)
2559 for axis
in self
.subaxes
[1:-1]:
2560 if value
> axis
.min and value
< axis
.max:
2561 return axis
.vmin
+ axis
.convert(value
)*(axis
.vmax
-axis
.vmin
)
2562 if value
> self
.subaxes
[-1].min:
2563 return self
.subaxes
[-1].vmin
+ self
.subaxes
[-1].convert(value
)*(self
.subaxes
[-1].vmax
-self
.subaxes
[-1].vmin
)
2564 raise ValueError("value couldn't be assigned to a split region")
2566 def vbaseline(self
, v1
=None, v2
=None, axis
=None):
2568 if axis
.baseaxis
.painter
.breaklinesattrs
is None: # XXX undocumented access to painter!?
2571 if axis
.vminover
is None:
2574 left
= axis
.vminover
2576 left
= axis
.vmin
+v1
*(axis
.vmax
-axis
.vmin
)
2578 if axis
.baseaxis
.painter
.breaklinesattrs
is None:
2581 if axis
.vmaxover
is None:
2584 right
= axis
.vmaxover
2586 right
= axis
.vmin
+v2
*(axis
.vmax
-axis
.vmin
)
2587 return axis
.baseaxispos
.vbaseline(left
, right
, axis
=axis
.baseaxis
)
2589 def finish(self
, axispos
, texrunner
):
2593 for subaxis
in self
.subaxes
:
2594 subaxis
.baseaxispos
= axispos
2595 subaxis
.baseaxis
= self
2596 return self
.painter
.paint(axispos
, self
)
2598 def createlinkaxis(self
, **args
):
2599 return linksplitaxis(self
, **args
)
2602 class linksplitaxis(linkaxis
):
2603 """a splitaxis linked to an already existing splitaxis
2604 - inherits the access to a linked axis -- as before,
2605 basically only the painter is replaced
2606 - it must take care of the creation of linked axes of
2609 __implements__
= _Iaxis
2611 def __init__(self
, linkedaxis
, painter
=linksplitaxispainter(), subaxispainter
=None):
2612 """initializes the instance
2613 - it gets a axis this linkaxis is linked to
2614 - it gets a painter to be used for this linked axis
2615 - it gets a list of painters to be used for the linkaxes
2616 of the subaxes; if None, the createlinkaxis of the subaxes
2617 are called without a painter parameter; if it is not a
2618 list, the subaxispainter is passed as the painter
2619 parameter to all createlinkaxis of the subaxes"""
2620 if subaxispainter
is not None:
2621 if helper
.issequence(subaxispainter
):
2622 if len(linkedaxis
.subaxes
) != len(subaxispainter
):
2623 raise RuntimeError("subaxes and subaxispainter lengths do not fit")
2624 self
.subaxes
= [a
.createlinkaxis(painter
=p
) for a
, p
in zip(linkedaxis
.subaxes
, subaxispainter
)]
2626 self
.subaxes
= [a
.createlinkaxis(painter
=subaxispainter
) for a
in linkedaxis
.subaxes
]
2628 self
.subaxes
= [a
.createlinkaxis() for a
in linkedaxis
.subaxes
]
2629 linkaxis
.__init
__(self
, linkedaxis
, painter
=painter
)
2631 def finish(self
, axispos
, texrunner
):
2632 for subaxis
in self
.subaxes
:
2633 subaxis
.baseaxispos
= axispos
2634 subaxis
.baseaxis
= self
2635 linkaxis
.finish(self
, axispos
, texrunner
)
2638 class baraxis(_subaxispos
):
2639 """implementation of a axis for bar graphs
2640 - a bar axes is different from a splitaxis by the way it
2641 selects its subaxes: the convert method gets a list,
2642 where the first entry is a name selecting a subaxis out
2643 of a list; instead of the term "bar" or "subaxis" the term
2644 "item" will be used here
2645 - the baraxis stores a list of names be identify the items;
2646 the names might be of any time (strings, integers, etc.);
2647 the names can be printed as the titles for the items, but
2648 alternatively the names might be transformed by the texts
2649 dictionary, which maps a name to a text to be used to label
2650 the items in the painter
2651 - usually, there is only one subaxis, which is used as
2652 the subaxis for all items
2653 - alternatively it is also possible to use another baraxis
2654 as a multisubaxis; it is copied via the createsubaxis
2655 method whenever another subaxis is needed (by that a
2656 nested bar axis with a different number of subbars at
2657 each item can be created)
2658 - any axis can be a subaxis of a baraxis; if no subaxis
2659 is specified at all, the baraxis simulates a linear
2660 subaxis with a fixed range of 0 to 1
2661 - a splitaxis implements the _Iaxispos for its subaxes
2662 by inheritance from _subaxispos when the multisubaxis
2663 feature is turned on"""
2665 def __init__(self
, subaxis
=None, multisubaxis
=None, title
=None,
2666 dist
=0.5, firstdist
=None, lastdist
=None, names
=None,
2667 texts
={}, painter
=baraxispainter()):
2668 """initialize the instance
2669 - subaxis contains a axis to be used as the subaxis
2671 - multisubaxis might contain another baraxis instance
2672 to be used to construct a new subaxis for each item;
2673 (by that a nested bar axis with a different number
2674 of subbars at each item can be created)
2675 - only one of subaxis or multisubaxis can be set; if neither
2676 of them is set, the baraxis behaves like having a linaxis
2677 as its subaxis with a fixed range 0 to 1
2678 - the title attribute contains the axis title as a string
2679 - the dist is a relsize to be used as the distance between
2681 - the firstdist and lastdist are the distance before the
2682 first and after the last item, respectively; when set
2683 to None (the default), 0.5*dist is used
2684 - names is a predefined list of names to identify the
2685 items; if set, the name list is fixed
2686 - texts is a dictionary transforming a name to a text in
2687 the painter; if a name isn't found in the dictionary
2689 - the relsize of the baraxis is the sum of the
2690 relsizes including all distances between the items"""
2692 if firstdist
is not None:
2693 self
.firstdist
= firstdist
2695 self
.firstdist
= 0.5 * dist
2696 if lastdist
is not None:
2697 self
.lastdist
= lastdist
2699 self
.lastdist
= 0.5 * dist
2700 self
.relsizes
= None
2703 for name
in helper
.ensuresequence(names
):
2705 self
.fixnames
= names
is not None
2706 self
.multisubaxis
= multisubaxis
2707 if self
.multisubaxis
is not None:
2708 if subaxis
is not None:
2709 raise RuntimeError("either use subaxis or multisubaxis")
2710 self
.subaxis
= [self
.createsubaxis() for name
in self
.names
]
2712 self
.subaxis
= subaxis
2716 self
.painter
= painter
2718 def createsubaxis(self
):
2719 return baraxis(subaxis
=self
.multisubaxis
.subaxis
,
2720 multisubaxis
=self
.multisubaxis
.multisubaxis
,
2721 title
=self
.multisubaxis
.title
,
2722 dist
=self
.multisubaxis
.dist
,
2723 firstdist
=self
.multisubaxis
.firstdist
,
2724 lastdist
=self
.multisubaxis
.lastdist
,
2725 names
=self
.multisubaxis
.names
,
2726 texts
=self
.multisubaxis
.texts
,
2727 painter
=self
.multisubaxis
.painter
)
2730 # TODO: we do not yet have a proper range handling for a baraxis
2733 def setrange(self
, min=None, max=None):
2734 # TODO: we do not yet have a proper range handling for a baraxis
2735 raise RuntimeError("range handling for a baraxis is not implemented")
2737 def setname(self
, name
, *subnames
):
2738 """add a name to identify an item at the baraxis
2739 - by using subnames, nested name definitions are
2741 - a style (or the user itself) might use this to
2742 insert new items into a baraxis
2743 - setting self.relsizes to None forces later recalculation"""
2744 if not self
.fixnames
:
2745 if name
not in self
.names
:
2746 self
.relsizes
= None
2747 self
.names
.append(name
)
2748 if self
.multisubaxis
is not None:
2749 self
.subaxis
.append(self
.createsubaxis())
2750 if (not self
.fixnames
or name
in self
.names
) and len(subnames
):
2751 if self
.multisubaxis
is not None:
2752 if self
.subaxis
[self
.names
.index(name
)].setname(*subnames
):
2753 self
.relsizes
= None
2755 if self
.subaxis
.setname(*subnames
):
2756 self
.relsizes
= None
2757 return self
.relsizes
is not None
2759 def updaterelsizes(self
):
2760 # guess what it does: it recalculates relsize attribute
2761 self
.relsizes
= [i
*self
.dist
+ self
.firstdist
for i
in range(len(self
.names
) + 1)]
2762 self
.relsizes
[-1] += self
.lastdist
- self
.dist
2763 if self
.multisubaxis
is not None:
2765 for i
in range(1, len(self
.relsizes
)):
2766 self
.subaxis
[i
-1].updaterelsizes()
2767 subrelsize
+= self
.subaxis
[i
-1].relsizes
[-1]
2768 self
.relsizes
[i
] += subrelsize
2770 if self
.subaxis
is None:
2773 self
.subaxis
.updaterelsizes()
2774 subrelsize
= self
.subaxis
.relsizes
[-1]
2775 for i
in range(1, len(self
.relsizes
)):
2776 self
.relsizes
[i
] += i
* subrelsize
2778 def convert(self
, value
):
2779 """baraxis convert method
2780 - the value should be a list, where the first entry is
2781 a member of the names (set in the constructor or by the
2782 setname method); this first entry identifies an item in
2784 - following values are passed to the appropriate subaxis
2786 - when there is no subaxis, the convert method will behave
2787 like having a linaxis from 0 to 1 as subaxis"""
2788 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
2789 if not self
.relsizes
:
2790 self
.updaterelsizes()
2791 pos
= self
.names
.index(value
[0])
2793 if self
.subaxis
is None:
2796 if self
.multisubaxis
is not None:
2797 subvalue
= value
[1] * self
.subaxis
[pos
].relsizes
[-1]
2799 subvalue
= value
[1] * self
.subaxis
.relsizes
[-1]
2801 if self
.multisubaxis
is not None:
2802 subvalue
= self
.subaxis
[pos
].convert(value
[1:]) * self
.subaxis
[pos
].relsizes
[-1]
2804 subvalue
= self
.subaxis
.convert(value
[1:]) * self
.subaxis
.relsizes
[-1]
2805 return (self
.relsizes
[pos
] + subvalue
) / float(self
.relsizes
[-1])
2807 def baseline(self
, x1
=None, x2
=None, axis
=None):
2808 "None is returned -> subaxis should not paint any baselines"
2809 # TODO: quick hack ?!
2812 def vbaseline(self
, v1
=None, v2
=None, axis
=None):
2813 "None is returned -> subaxis should not paint any baselines"
2814 # TODO: quick hack ?!
2817 def finish(self
, axispos
, texrunner
):
2818 if self
.multisubaxis
is not None:
2819 for name
, subaxis
in zip(self
.names
, self
.subaxis
):
2820 subaxis
.vmin
= self
.convert((name
, 0))
2821 subaxis
.vmax
= self
.convert((name
, 1))
2822 subaxis
.baseaxispos
= axispos
2823 subaxis
.baseaxis
= self
2824 return self
.painter
.paint(axispos
, self
)
2826 def createlinkaxis(self
, **args
):
2827 return linkbaraxis(self
, **args
)
2830 class linkbaraxis(linkaxis
):
2831 """a baraxis linked to an already existing baraxis
2832 - inherits the access to a linked axis -- as before,
2833 basically only the painter is replaced
2834 - it must take care of the creation of linked axes of
2837 __implements__
= _Iaxis
2839 def __init__(self
, linkedaxis
, painter
=linkbaraxispainter()):
2840 """initializes the instance
2841 - it gets a axis this linkaxis is linked to
2842 - it gets a painter to be used for this linked axis"""
2843 linkaxis
.__init
__(self
, linkedaxis
, painter
=painter
)
2845 def finish(self
, axispos
, texrunner
):
2846 if self
.multisubaxis
is not None:
2847 self
.subaxis
= [subaxis
.createlinkaxis() for subaxis
in self
.linkedaxis
.subaxis
]
2848 for subaxis
in self
.subaxis
:
2849 subaxis
.baseaxispos
= axispos
2850 subaxis
.baseaxis
= self
2851 elif self
.linkedaxis
.subaxis
is not None:
2852 self
.subaxis
= self
.linkedaxis
.subaxis
.createlinkaxis()
2853 self
.subaxis
.baseaxispos
= axispos
2854 self
.subaxis
.baseaxis
= self
2855 linkaxis
.finish(self
, axispos
, texrunner
)
2858 ################################################################################
2860 ################################################################################
2863 # g = graph.graphxy(key=graph.key())
2864 # g.addkey(graph.key(), ...)
2869 def __init__(self
, dist
="0.2 cm", pos
= "tr", hinside
= 1, vinside
= 1, hdist
="0.6 cm", vdist
="0.4 cm",
2870 symbolwidth
="0.5 cm", symbolheight
="0.25 cm", symbolspace
="0.2 cm",
2871 textattrs
=textmodule
.vshift
.mathaxis
):
2872 self
.dist_str
= dist
2874 self
.hinside
= hinside
2875 self
.vinside
= vinside
2876 self
.hdist_str
= hdist
2877 self
.vdist_str
= vdist
2878 self
.symbolwidth_str
= symbolwidth
2879 self
.symbolheight_str
= symbolheight
2880 self
.symbolspace_str
= symbolspace
2881 self
.textattrs
= textattrs
2882 self
.plotinfos
= None
2883 if self
.pos
in ("tr", "rt"):
2886 elif self
.pos
in ("br", "rb"):
2889 elif self
.pos
in ("tl", "lt"):
2892 elif self
.pos
in ("bl", "lb"):
2896 raise RuntimeError("invalid pos attribute")
2898 def setplotinfos(self
, *plotinfos
):
2899 """set the plotinfos to be used in the key
2900 - call it exactly once
2901 - plotinfo instances with title == None are ignored"""
2902 if self
.plotinfos
is not None:
2903 raise RuntimeError("setplotinfo is called multiple times")
2904 self
.plotinfos
= [plotinfo
for plotinfo
in plotinfos
if plotinfo
.data
.title
is not None]
2906 def dolayout(self
, graph
):
2907 "creates the layout of the key"
2908 self
._dist
= unit
.topt(unit
.length(self
.dist_str
, default_type
="v"))
2909 self
._hdist
= unit
.topt(unit
.length(self
.hdist_str
, default_type
="v"))
2910 self
._vdist
= unit
.topt(unit
.length(self
.vdist_str
, default_type
="v"))
2911 self
._symbolwidth
= unit
.topt(unit
.length(self
.symbolwidth_str
, default_type
="v"))
2912 self
._symbolheight
= unit
.topt(unit
.length(self
.symbolheight_str
, default_type
="v"))
2913 self
._symbolspace
= unit
.topt(unit
.length(self
.symbolspace_str
, default_type
="v"))
2915 for plotinfo
in self
.plotinfos
:
2916 self
.titles
.append(graph
.texrunner
._text
(0, 0, plotinfo
.data
.title
, *helper
.ensuresequence(self
.textattrs
)))
2917 box
._tile
(self
.titles
, self
._dist
, 0, -1)
2918 box
._linealignequal
(self
.titles
, self
._symbolwidth
+ self
._symbolspace
, 1, 0)
2921 """return a bbox for the key
2922 method should be called after dolayout"""
2923 result
= bbox
.bbox()
2924 for title
in self
.titles
:
2925 result
= result
+ title
.bbox() + bbox
._bbox
(0, title
.center
[1] - 0.5 * self
._symbolheight
,
2926 0, title
.center
[1] + 0.5 * self
._symbolheight
)
2929 def paint(self
, c
, x
, y
):
2930 """paint the graph key into a canvas c at the position x and y (in postscript points)
2931 - method should be called after dolayout
2932 - the x, y alignment might be calculated by the graph using:
2933 - the bbox of the key as returned by the keys bbox method
2934 - the attributes _hdist, _vdist, hinside, and vinside of the key
2935 - the dimension and geometry of the graph"""
2936 sc
= c
.insert(canvas
.canvas(trafomodule
._translate
(x
, y
)))
2937 for plotinfo
, title
in zip(self
.plotinfos
, self
.titles
):
2938 plotinfo
.style
.key(sc
, 0, -0.5 * self
._symbolheight
+ title
.center
[1],
2939 self
._symbolwidth
, self
._symbolheight
)
2943 ################################################################################
2945 ################################################################################
2948 class lineaxispos(_axispos
): # XXX: optimize by full implementation
2950 __implements__
= _Iaxispos
2952 def __init__(self
, axis
, x1
, y1
, x2
, y2
, fixtickdirection
):
2953 """initializes the instance
2954 - only the convert method is needed from the axis a"""
2955 _axispos
.__init
__(self
, axis
)
2960 self
.fixtickdirection
= fixtickdirection
2962 def vbaseline(self
, v1
=None, v2
=None):
2964 v1
= 0 # XXX reversed?
2967 return path
.line((1-v1
)*self
.x1
+v1
*self
.x2
,
2968 (1-v1
)*self
.y1
+v1
*self
.y2
,
2969 (1-v2
)*self
.x1
+v2
*self
.x2
,
2970 (1-v2
)*self
.y1
+v2
*self
.y2
)
2972 def vgridline(self
, v
):
2974 raise RuntimeError("gridline implementation missing")
2976 def vtickpoint(self
, v
):
2977 return (1-v
)*self
.x1
+v
*self
.x2
, (1-v
)*self
.y1
+v
*self
.y2
2979 def vtickdirection(self
, v
):
2980 return self
.fixtickdirection
2985 def __init__(self
, data
, style
):
2991 class graphxy(canvas
.canvas
):
2997 def __init__(self
, type, axispos
, tickdirection
):
2999 - type == 0: x-axis; type == 1: y-axis
3000 - _axispos is the y or x position of the x-axis or y-axis
3001 in postscript points, respectively
3002 - axispos is analogous to _axispos, but as a PyX length
3003 - dx and dy is the tick direction
3006 self
.axispos
= axispos
3007 self
._axispos
= unit
.topt(axispos
)
3008 self
.tickdirection
= tickdirection
3010 def clipcanvas(self
):
3011 return self
.insert(canvas
.canvas(canvas
.clip(path
._rect
(self
._xpos
, self
._ypos
, self
._width
, self
._height
))))
3013 def plot(self
, data
, style
=None):
3015 raise RuntimeError("layout setup was already performed")
3017 if helper
.issequence(data
):
3018 raise RuntimeError("list plot needs an explicit style")
3019 if self
.defaultstyle
.has_key(data
.defaultstyle
):
3020 style
= self
.defaultstyle
[data
.defaultstyle
].iterate()
3022 style
= data
.defaultstyle()
3023 self
.defaultstyle
[data
.defaultstyle
] = style
3026 for d
in helper
.ensuresequence(data
):
3028 style
= style
.iterate()
3031 d
.setstyle(self
, style
)
3032 plotinfos
.append(plotinfo(d
, style
))
3033 self
.plotinfos
.extend(plotinfos
)
3034 if helper
.issequence(data
):
3038 def addkey(self
, key
, *plotinfos
):
3040 raise RuntimeError("layout setup was already performed")
3041 self
.addkeys
.append((key
, plotinfos
))
3043 def _pos(self
, x
, y
, xaxis
=None, yaxis
=None):
3045 xaxis
= self
.axes
["x"]
3047 yaxis
= self
.axes
["y"]
3048 return self
._xpos
+xaxis
.convert(x
)*self
._width
, self
._ypos
+yaxis
.convert(y
)*self
._height
3050 def pos(self
, x
, y
, xaxis
=None, yaxis
=None):
3052 xaxis
= self
.axes
["x"]
3054 yaxis
= self
.axes
["y"]
3055 return self
.xpos
+xaxis
.convert(x
)*self
.width
, self
.ypos
+yaxis
.convert(y
)*self
.height
3057 def _vpos(self
, vx
, vy
):
3058 return self
._xpos
+vx
*self
._width
, self
._ypos
+vy
*self
._height
3060 def vpos(self
, vx
, vy
):
3061 return self
.xpos
+vx
*self
.width
, self
.ypos
+vy
*self
.height
3063 # def xbaseline(self, x1=None, x2=None, axis=None):
3065 # axis = self.axes["x"]
3066 # if x1 is not None:
3067 # v1 = axis.convert(x1)
3070 # if x2 is not None:
3071 # v2 = axis.convert(x2)
3074 # return path._line(self._xpos+v1*self._width, axis.axisposdata._axispos,
3075 # self._xpos+v2*self._width, axis.axisposdata._axispos)
3077 # def ybaseline(self, x1=None, x2=None, axis=None):
3079 # axis = self.axes["y"]
3080 # if x1 is not None:
3081 # v1 = axis.convert(x1)
3084 # if x2 is not None:
3085 # v2 = axis.convert(x2)
3088 # return path._line(axis.axisposdata._axispos, self._ypos+v1*self._height,
3089 # axis.axisposdata._axispos, self._ypos+v2*self._height)
3091 # def vxbaseline(self, v1=None, v2=None, axis=None):
3093 # axis = self.axes["x"]
3098 # return path._line(self._xpos+v1*self._width, axis.axisposdata._axispos,
3099 # self._xpos+v2*self._width, axis.axisposdata._axispos)
3101 # def vybaseline(self, v1=None, v2=None, axis=None):
3103 # axis = self.axes["y"]
3108 # return path._line(axis.axisposdata._axispos, self._ypos+v1*self._height,
3109 # axis.axisposdata._axispos, self._ypos+v2*self._height)
3111 # def xgridline(self, x, axis=None):
3113 # axis = self.axes["x"]
3114 # v = axis.convert(x)
3115 # return path._line(self._xpos+v*self._width, self._ypos,
3116 # self._xpos+v*self._width, self._ypos+self._height)
3118 # def ygridline(self, x, axis=None):
3120 # axis = self.axes["y"]
3121 # v = axis.convert(x)
3122 # return path._line(self._xpos, self._ypos+v*self._height,
3123 # self._xpos+self._width, self._ypos+v*self._height)
3125 # def vxgridline(self, v, axis=None):
3127 # axis = self.axes["x"]
3128 # return path._line(self._xpos+v*self._width, self._ypos,
3129 # self._xpos+v*self._width, self._ypos+self._height)
3131 # def vygridline(self, v, axis=None):
3133 # axis = self.axes["y"]
3134 # return path._line(self._xpos, self._ypos+v*self._height,
3135 # self._xpos+self._width, self._ypos+v*self._height)
3137 # def _xtickpoint(self, x, axis=None):
3139 # axis = self.axes["x"]
3140 # return self._xpos+axis.convert(x)*self._width, axis.axisposdata._axispos
3142 # def _ytickpoint(self, x, axis=None):
3144 # axis = self.axes["y"]
3145 # return axis.axisposdata._axispos, self._ypos+axis.convert(x)*self._height
3147 # def xtickpoint(self, x, axis=None):
3149 # axis = self.axes["x"]
3150 # return self.xpos+axis.convert(x)*self.width, axis.axisposdata.axispos
3152 # def ytickpoint(self, x, axis=None):
3154 # axis = self.axes["y"]
3155 # return axis.axisposdata.axispos, self.ypos+axis.convert(x)*self.height
3157 # def _vxtickpoint(self, v, axis=None):
3159 # axis = self.axes["x"]
3160 # return self._xpos+v*self._width, axis.axisposdata._axispos
3162 # def _vytickpoint(self, v, axis=None):
3164 # axis = self.axes["y"]
3165 # return axis.axisposdata._axispos, self._ypos+v*self._height
3167 # def vxtickpoint(self, v, axis=None):
3169 # axis = self.axes["x"]
3170 # return self.xpos+v*self.width, axis.axisposdata.axispos
3172 # def vytickpoint(self, v, axis=None):
3174 # axis = self.axes["y"]
3175 # return axis.axisposdata.axispos, self.ypos+v*self.height
3177 # def xtickdirection(self, x, axis=None):
3179 # axis = self.axes["x"]
3180 # return axis.axisposdata.tickdirection
3182 # def ytickdirection(self, x, axis=None):
3184 # axis = self.axes["y"]
3185 # return axis.axisposdata.tickdirection
3187 # def vxtickdirection(self, v, axis=None):
3189 # axis = self.axes["x"]
3190 # return axis.axisposdata.tickdirection
3192 # def vytickdirection(self, v, axis=None):
3194 # axis = self.axes["y"]
3195 # return axis.axisposdata.tickdirection
3197 def _addpos(self
, x
, y
, dx
, dy
):
3200 def _connect(self
, x1
, y1
, x2
, y2
):
3201 return path
._lineto
(x2
, y2
)
3203 def keynum(self
, key
):
3205 while key
[0] in string
.letters
:
3211 def gatherranges(self
):
3213 for plotinfo
in self
.plotinfos
:
3214 pdranges
= plotinfo
.data
.getranges()
3215 if pdranges
is not None:
3216 for key
in pdranges
.keys():
3217 if key
not in ranges
.keys():
3218 ranges
[key
] = pdranges
[key
]
3220 ranges
[key
] = (min(ranges
[key
][0], pdranges
[key
][0]),
3221 max(ranges
[key
][1], pdranges
[key
][1]))
3222 # known ranges are also set as ranges for the axes
3223 for key
, axis
in self
.axes
.items():
3224 if key
in ranges
.keys():
3225 axis
.setrange(*ranges
[key
])
3226 ranges
[key
] = axis
.getrange()
3227 if ranges
[key
] is None:
3231 def removedomethod(self
, method
):
3235 self
.domethods
.remove(method
)
3241 if not self
.removedomethod(self
.dolayout
): return
3243 # create list of ranges
3245 ranges
= self
.gatherranges()
3246 # 2. calculate additional ranges out of known ranges
3247 for plotinfo
in self
.plotinfos
:
3248 plotinfo
.data
.setranges(ranges
)
3249 # 3. gather ranges again
3251 # do the layout for all axes
3252 axesdist
= unit
.length(self
.axesdist_str
, default_type
="v")
3253 XPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[0])
3254 YPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[1])
3255 xaxisextents
= [0, 0]
3256 yaxisextents
= [0, 0]
3257 needxaxisdist
= [0, 0]
3258 needyaxisdist
= [0, 0]
3259 items
= list(self
.axes
.items())
3260 items
.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
3261 for key
, axis
in items
:
3262 num
= self
.keynum(key
)
3263 num2
= 1 - num
% 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
3264 num3
= 1 - 2 * (num
% 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
3265 if XPattern
.match(key
):
3266 if needxaxisdist
[num2
]:
3267 xaxisextents
[num2
] += axesdist
3268 self
.axespos
[key
] = lineaxispos(self
.axes
[key
],
3270 self
.ypos
+ num2
*self
.height
+ num3
*xaxisextents
[num2
],
3271 self
.xpos
+ self
.width
,
3272 self
.ypos
+ num2
*self
.height
+ num3
*xaxisextents
[num2
],
3274 elif YPattern
.match(key
):
3275 if needyaxisdist
[num2
]:
3276 yaxisextents
[num2
] += axesdist
3277 self
.axespos
[key
] = lineaxispos(self
.axes
[key
],
3278 self
.xpos
+ num2
*self
.width
+ num3
*yaxisextents
[num2
],
3280 self
.xpos
+ num2
*self
.width
+ num3
*yaxisextents
[num2
],
3281 self
.ypos
+ self
.height
,
3284 raise ValueError("Axis key '%s' not allowed" % key
)
3285 self
.axescanvas
[key
] = axis
.finish(self
.axespos
[key
], self
.texrunner
)
3286 if XPattern
.match(key
):
3287 xaxisextents
[num2
] += self
.axescanvas
[key
].extent
3288 needxaxisdist
[num2
] = 1
3289 if YPattern
.match(key
):
3290 yaxisextents
[num2
] += self
.axescanvas
[key
].extent
3291 needyaxisdist
[num2
] = 1
3293 def dobackground(self
):
3295 if not self
.removedomethod(self
.dobackground
): return
3296 if self
.backgroundattrs
is not None:
3297 self
.draw(path
._rect
(self
._xpos
, self
._ypos
, self
._width
, self
._height
),
3298 *helper
.ensuresequence(self
.backgroundattrs
))
3302 if not self
.removedomethod(self
.doaxes
): return
3303 for axiscanvas
in self
.axescanvas
.values():
3304 self
.insert(axiscanvas
)
3308 if not self
.removedomethod(self
.dodata
): return
3309 for plotinfo
in self
.plotinfos
:
3310 plotinfo
.data
.draw(self
)
3312 def _dokey(self
, key
, *plotinfos
):
3313 key
.setplotinfos(*plotinfos
)
3318 x
= self
._xpos
+ self
._width
- bbox
.urx
- key
._hdist
3320 x
= self
._xpos
+ self
._width
- bbox
.llx
+ key
._hdist
3323 x
= self
._xpos
- bbox
.llx
+ key
._hdist
3325 x
= self
._xpos
- bbox
.urx
- key
._hdist
3328 y
= self
._ypos
+ self
._height
- bbox
.ury
- key
._vdist
3330 y
= self
._ypos
+ self
._height
- bbox
.lly
+ key
._vdist
3333 y
= self
._ypos
- bbox
.lly
+ key
._vdist
3335 y
= self
._ypos
- bbox
.ury
- key
._vdist
3336 key
.paint(self
, x
, y
)
3340 if not self
.removedomethod(self
.dokey
): return
3341 if self
.key
is not None:
3342 self
._dokey
(self
.key
, *self
.plotinfos
)
3343 for key
, plotinfos
in self
.addkeys
:
3344 self
._dokey
(key
, *plotinfos
)
3347 while len(self
.domethods
):
3350 def initwidthheight(self
, width
, height
, ratio
):
3351 if (width
is not None) and (height
is None):
3352 self
.width
= unit
.length(width
)
3353 self
.height
= (1.0/ratio
) * self
.width
3354 elif (height
is not None) and (width
is None):
3355 self
.height
= unit
.length(height
)
3356 self
.width
= ratio
* self
.height
3358 self
.width
= unit
.length(width
)
3359 self
.height
= unit
.length(height
)
3360 self
._width
= unit
.topt(self
.width
)
3361 self
._height
= unit
.topt(self
.height
)
3362 if self
._width
<= 0: raise ValueError("width <= 0")
3363 if self
._height
<= 0: raise ValueError("height <= 0")
3365 def initaxes(self
, axes
, addlinkaxes
=0):
3366 for key
in self
.Names
:
3367 if not axes
.has_key(key
):
3368 axes
[key
] = linaxis()
3369 elif axes
[key
] is None:
3372 if not axes
.has_key(key
+ "2") and axes
.has_key(key
):
3373 axes
[key
+ "2"] = axes
[key
].createlinkaxis()
3374 elif axes
[key
+ "2"] is None:
3378 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, ratio
=goldenmean
,
3379 key
=None, backgroundattrs
=None, axesdist
="0.8 cm", **axes
):
3380 canvas
.canvas
.__init
__(self
)
3381 self
.xpos
= unit
.length(xpos
)
3382 self
.ypos
= unit
.length(ypos
)
3383 self
._xpos
= unit
.topt(self
.xpos
)
3384 self
._ypos
= unit
.topt(self
.ypos
)
3385 self
.initwidthheight(width
, height
, ratio
)
3386 self
.initaxes(axes
, 1)
3387 self
.axescanvas
= {}
3390 self
.backgroundattrs
= backgroundattrs
3391 self
.axesdist_str
= axesdist
3393 self
.domethods
= [self
.dolayout
, self
.dobackground
, self
.doaxes
, self
.dodata
, self
.dokey
]
3395 self
.defaultstyle
= {}
3400 return canvas
.canvas
.bbox(self
)
3402 def write(self
, file):
3404 canvas
.canvas
.write(self
, file)
3408 # some thoughts, but deferred right now
3410 # class graphxyz(graphxy):
3412 # Names = "x", "y", "z"
3414 # def _vxtickpoint(self, axis, v):
3415 # return self._vpos(v, axis.vypos, axis.vzpos)
3417 # def _vytickpoint(self, axis, v):
3418 # return self._vpos(axis.vxpos, v, axis.vzpos)
3420 # def _vztickpoint(self, axis, v):
3421 # return self._vpos(axis.vxpos, axis.vypos, v)
3423 # def vxtickdirection(self, axis, v):
3424 # x1, y1 = self._vpos(v, axis.vypos, axis.vzpos)
3425 # x2, y2 = self._vpos(v, 0.5, 0)
3426 # dx, dy = x1 - x2, y1 - y2
3427 # norm = math.sqrt(dx*dx + dy*dy)
3428 # return dx/norm, dy/norm
3430 # def vytickdirection(self, axis, v):
3431 # x1, y1 = self._vpos(axis.vxpos, v, axis.vzpos)
3432 # x2, y2 = self._vpos(0.5, v, 0)
3433 # dx, dy = x1 - x2, y1 - y2
3434 # norm = math.sqrt(dx*dx + dy*dy)
3435 # return dx/norm, dy/norm
3437 # def vztickdirection(self, axis, v):
3439 # x1, y1 = self._vpos(axis.vxpos, axis.vypos, v)
3440 # x2, y2 = self._vpos(0.5, 0.5, v)
3441 # dx, dy = x1 - x2, y1 - y2
3442 # norm = math.sqrt(dx*dx + dy*dy)
3443 # return dx/norm, dy/norm
3445 # def _pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
3446 # if xaxis is None: xaxis = self.axes["x"]
3447 # if yaxis is None: yaxis = self.axes["y"]
3448 # if zaxis is None: zaxis = self.axes["z"]
3449 # return self._vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
3451 # def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
3452 # if xaxis is None: xaxis = self.axes["x"]
3453 # if yaxis is None: yaxis = self.axes["y"]
3454 # if zaxis is None: zaxis = self.axes["z"]
3455 # return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
3457 # def _vpos(self, vx, vy, vz):
3458 # x, y, z = (vx - 0.5)*self._depth, (vy - 0.5)*self._width, (vz - 0.5)*self._height
3459 # d0 = float(self.a[0]*self.b[1]*(z-self.eye[2])
3460 # + self.a[2]*self.b[0]*(y-self.eye[1])
3461 # + self.a[1]*self.b[2]*(x-self.eye[0])
3462 # - self.a[2]*self.b[1]*(x-self.eye[0])
3463 # - self.a[0]*self.b[2]*(y-self.eye[1])
3464 # - self.a[1]*self.b[0]*(z-self.eye[2]))
3465 # da = (self.eye[0]*self.b[1]*(z-self.eye[2])
3466 # + self.eye[2]*self.b[0]*(y-self.eye[1])
3467 # + self.eye[1]*self.b[2]*(x-self.eye[0])
3468 # - self.eye[2]*self.b[1]*(x-self.eye[0])
3469 # - self.eye[0]*self.b[2]*(y-self.eye[1])
3470 # - self.eye[1]*self.b[0]*(z-self.eye[2]))
3471 # db = (self.a[0]*self.eye[1]*(z-self.eye[2])
3472 # + self.a[2]*self.eye[0]*(y-self.eye[1])
3473 # + self.a[1]*self.eye[2]*(x-self.eye[0])
3474 # - self.a[2]*self.eye[1]*(x-self.eye[0])
3475 # - self.a[0]*self.eye[2]*(y-self.eye[1])
3476 # - self.a[1]*self.eye[0]*(z-self.eye[2]))
3477 # return da/d0 + self._xpos, db/d0 + self._ypos
3479 # def vpos(self, vx, vy, vz):
3480 # tx, ty = self._vpos(vx, vy, vz)
3481 # return unit.t_pt(tx), unit.t_pt(ty)
3483 # def xbaseline(self, axis, x1, x2, xaxis=None):
3484 # if xaxis is None: xaxis = self.axes["x"]
3485 # return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2))
3487 # def ybaseline(self, axis, y1, y2, yaxis=None):
3488 # if yaxis is None: yaxis = self.axes["y"]
3489 # return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2))
3491 # def zbaseline(self, axis, z1, z2, zaxis=None):
3492 # if zaxis is None: zaxis = self.axes["z"]
3493 # return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2))
3495 # def vxbaseline(self, axis, v1, v2):
3496 # return (path._line(*(self._vpos(v1, 0, 0) + self._vpos(v2, 0, 0))) +
3497 # path._line(*(self._vpos(v1, 0, 1) + self._vpos(v2, 0, 1))) +
3498 # path._line(*(self._vpos(v1, 1, 1) + self._vpos(v2, 1, 1))) +
3499 # path._line(*(self._vpos(v1, 1, 0) + self._vpos(v2, 1, 0))))
3501 # def vybaseline(self, axis, v1, v2):
3502 # return (path._line(*(self._vpos(0, v1, 0) + self._vpos(0, v2, 0))) +
3503 # path._line(*(self._vpos(0, v1, 1) + self._vpos(0, v2, 1))) +
3504 # path._line(*(self._vpos(1, v1, 1) + self._vpos(1, v2, 1))) +
3505 # path._line(*(self._vpos(1, v1, 0) + self._vpos(1, v2, 0))))
3507 # def vzbaseline(self, axis, v1, v2):
3508 # return (path._line(*(self._vpos(0, 0, v1) + self._vpos(0, 0, v2))) +
3509 # path._line(*(self._vpos(0, 1, v1) + self._vpos(0, 1, v2))) +
3510 # path._line(*(self._vpos(1, 1, v1) + self._vpos(1, 1, v2))) +
3511 # path._line(*(self._vpos(1, 0, v1) + self._vpos(1, 0, v2))))
3513 # def xgridpath(self, x, xaxis=None):
3515 # if xaxis is None: xaxis = self.axes["x"]
3516 # v = xaxis.convert(x)
3517 # return path._line(self._xpos+v*self._width, self._ypos,
3518 # self._xpos+v*self._width, self._ypos+self._height)
3520 # def ygridpath(self, y, yaxis=None):
3522 # if yaxis is None: yaxis = self.axes["y"]
3523 # v = yaxis.convert(y)
3524 # return path._line(self._xpos, self._ypos+v*self._height,
3525 # self._xpos+self._width, self._ypos+v*self._height)
3527 # def zgridpath(self, z, zaxis=None):
3529 # if zaxis is None: zaxis = self.axes["z"]
3530 # v = zaxis.convert(z)
3531 # return path._line(self._xpos, self._zpos+v*self._height,
3532 # self._xpos+self._width, self._zpos+v*self._height)
3534 # def vxgridpath(self, v):
3535 # return path.path(path._moveto(*self._vpos(v, 0, 0)),
3536 # path._lineto(*self._vpos(v, 0, 1)),
3537 # path._lineto(*self._vpos(v, 1, 1)),
3538 # path._lineto(*self._vpos(v, 1, 0)),
3541 # def vygridpath(self, v):
3542 # return path.path(path._moveto(*self._vpos(0, v, 0)),
3543 # path._lineto(*self._vpos(0, v, 1)),
3544 # path._lineto(*self._vpos(1, v, 1)),
3545 # path._lineto(*self._vpos(1, v, 0)),
3548 # def vzgridpath(self, v):
3549 # return path.path(path._moveto(*self._vpos(0, 0, v)),
3550 # path._lineto(*self._vpos(0, 1, v)),
3551 # path._lineto(*self._vpos(1, 1, v)),
3552 # path._lineto(*self._vpos(1, 0, v)),
3555 # def _addpos(self, x, y, dx, dy):
3559 # def _connect(self, x1, y1, x2, y2):
3561 # return path._lineto(x2, y2)
3565 # if not self.removedomethod(self.doaxes): return
3566 # axesdist = unit.topt(unit.length(self.axesdist_str, default_type="v"))
3567 # XPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[0])
3568 # YPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[1])
3569 # ZPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[2])
3570 # items = list(self.axes.items())
3571 # items.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
3572 # for key, axis in items:
3573 # num = self.keynum(key)
3574 # num2 = 1 - num % 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
3575 # num3 = 1 - 2 * (num % 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
3576 # if XPattern.match(key):
3579 # axis._vtickpoint = self._vxtickpoint
3580 # axis.vgridpath = self.vxgridpath
3581 # axis.vbaseline = self.vxbaseline
3582 # axis.vtickdirection = self.vxtickdirection
3583 # elif YPattern.match(key):
3586 # axis._vtickpoint = self._vytickpoint
3587 # axis.vgridpath = self.vygridpath
3588 # axis.vbaseline = self.vybaseline
3589 # axis.vtickdirection = self.vytickdirection
3590 # elif ZPattern.match(key):
3593 # axis._vtickpoint = self._vztickpoint
3594 # axis.vgridpath = self.vzgridpath
3595 # axis.vbaseline = self.vzbaseline
3596 # axis.vtickdirection = self.vztickdirection
3598 # raise ValueError("Axis key '%s' not allowed" % key)
3599 # if axis.painter is not None:
3600 # axis.dopaint(self)
3601 # # if XPattern.match(key):
3602 # # self._xaxisextents[num2] += axis._extent
3603 # # needxaxisdist[num2] = 1
3604 # # if YPattern.match(key):
3605 # # self._yaxisextents[num2] += axis._extent
3606 # # needyaxisdist[num2] = 1
3608 # def __init__(self, tex, xpos=0, ypos=0, width=None, height=None, depth=None,
3609 # phi=30, theta=30, distance=1,
3610 # backgroundattrs=None, axesdist="0.8 cm", **axes):
3611 # canvas.canvas.__init__(self)
3615 # self._xpos = unit.topt(xpos)
3616 # self._ypos = unit.topt(ypos)
3617 # self._width = unit.topt(width)
3618 # self._height = unit.topt(height)
3619 # self._depth = unit.topt(depth)
3620 # self.width = width
3621 # self.height = height
3622 # self.depth = depth
3623 # if self._width <= 0: raise ValueError("width < 0")
3624 # if self._height <= 0: raise ValueError("height < 0")
3625 # if self._depth <= 0: raise ValueError("height < 0")
3626 # self._distance = distance*math.sqrt(self._width*self._width+
3627 # self._height*self._height+
3628 # self._depth*self._depth)
3629 # phi *= -math.pi/180
3630 # theta *= math.pi/180
3631 # self.a = (-math.sin(phi), math.cos(phi), 0)
3632 # self.b = (-math.cos(phi)*math.sin(theta),
3633 # -math.sin(phi)*math.sin(theta),
3635 # self.eye = (self._distance*math.cos(phi)*math.cos(theta),
3636 # self._distance*math.sin(phi)*math.cos(theta),
3637 # self._distance*math.sin(theta))
3638 # self.initaxes(axes)
3639 # self.axesdist_str = axesdist
3640 # self.backgroundattrs = backgroundattrs
3643 # self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
3644 # self.haslayout = 0
3645 # self.defaultstyle = {}
3649 # return bbox._bbox(self._xpos - 200, self._ypos - 200, self._xpos + 200, self._ypos + 200)
3652 ################################################################################
3654 ################################################################################
3657 #class _Ichangeattr:
3658 # """attribute changer
3659 # is an iterator for attributes where an attribute
3660 # is not refered by just a number (like for a sequence),
3661 # but also by the number of attributes requested
3662 # by calls of the next method (like for an color palette)
3663 # (you should ensure to call all needed next before the attr)
3665 # the attribute itself is implemented by overloading the _attr method"""
3668 # "get an attribute"
3671 # "get an attribute changer for the next attribute"
3674 class _changeattr
: pass
3677 class changeattr(_changeattr
):
3686 newindex
= self
.counter
3688 return refattr(self
, newindex
)
3691 class refattr(_changeattr
):
3693 def __init__(self
, ref
, index
):
3698 return self
.ref
.attr(self
.index
)
3701 return self
.ref
.iterate()
3704 # helper routines for a using attrs
3707 "get attr out of a attr/changeattr"
3708 if isinstance(attr
, _changeattr
):
3709 return attr
.getattr()
3713 def _getattrs(attrs
):
3714 "get attrs out of a list of attr/changeattr"
3715 if attrs
is not None:
3717 for attr
in helper
.ensuresequence(attrs
):
3718 if isinstance(attr
, _changeattr
):
3719 attr
= attr
.getattr()
3720 if attr
is not None:
3722 if len(result
) or not len(attrs
):
3726 def _iterateattr(attr
):
3727 "perform next to a attr/changeattr"
3728 if isinstance(attr
, _changeattr
):
3729 return attr
.iterate()
3733 def _iterateattrs(attrs
):
3734 "perform next to a list of attr/changeattr"
3735 if attrs
is not None:
3737 for attr
in helper
.ensuresequence(attrs
):
3738 if isinstance(attr
, _changeattr
):
3739 result
.append(attr
.iterate())
3745 class changecolor(changeattr
):
3747 def __init__(self
, palette
):
3748 changeattr
.__init
__(self
)
3749 self
.palette
= palette
3751 def attr(self
, index
):
3752 if self
.counter
!= 1:
3753 return self
.palette
.getcolor(index
/float(self
.counter
-1))
3755 return self
.palette
.getcolor(0)
3758 class _changecolorgray(changecolor
):
3760 def __init__(self
, palette
=color
.palette
.Gray
):
3761 changecolor
.__init
__(self
, palette
)
3763 _changecolorgrey
= _changecolorgray
3766 class _changecolorreversegray(changecolor
):
3768 def __init__(self
, palette
=color
.palette
.ReverseGray
):
3769 changecolor
.__init
__(self
, palette
)
3771 _changecolorreversegrey
= _changecolorreversegray
3774 class _changecolorredblack(changecolor
):
3776 def __init__(self
, palette
=color
.palette
.RedBlack
):
3777 changecolor
.__init
__(self
, palette
)
3780 class _changecolorblackred(changecolor
):
3782 def __init__(self
, palette
=color
.palette
.BlackRed
):
3783 changecolor
.__init
__(self
, palette
)
3786 class _changecolorredwhite(changecolor
):
3788 def __init__(self
, palette
=color
.palette
.RedWhite
):
3789 changecolor
.__init
__(self
, palette
)
3792 class _changecolorwhitered(changecolor
):
3794 def __init__(self
, palette
=color
.palette
.WhiteRed
):
3795 changecolor
.__init
__(self
, palette
)
3798 class _changecolorgreenblack(changecolor
):
3800 def __init__(self
, palette
=color
.palette
.GreenBlack
):
3801 changecolor
.__init
__(self
, palette
)
3804 class _changecolorblackgreen(changecolor
):
3806 def __init__(self
, palette
=color
.palette
.BlackGreen
):
3807 changecolor
.__init
__(self
, palette
)
3810 class _changecolorgreenwhite(changecolor
):
3812 def __init__(self
, palette
=color
.palette
.GreenWhite
):
3813 changecolor
.__init
__(self
, palette
)
3816 class _changecolorwhitegreen(changecolor
):
3818 def __init__(self
, palette
=color
.palette
.WhiteGreen
):
3819 changecolor
.__init
__(self
, palette
)
3822 class _changecolorblueblack(changecolor
):
3824 def __init__(self
, palette
=color
.palette
.BlueBlack
):
3825 changecolor
.__init
__(self
, palette
)
3828 class _changecolorblackblue(changecolor
):
3830 def __init__(self
, palette
=color
.palette
.BlackBlue
):
3831 changecolor
.__init
__(self
, palette
)
3834 class _changecolorbluewhite(changecolor
):
3836 def __init__(self
, palette
=color
.palette
.BlueWhite
):
3837 changecolor
.__init
__(self
, palette
)
3840 class _changecolorwhiteblue(changecolor
):
3842 def __init__(self
, palette
=color
.palette
.WhiteBlue
):
3843 changecolor
.__init
__(self
, palette
)
3846 class _changecolorredgreen(changecolor
):
3848 def __init__(self
, palette
=color
.palette
.RedGreen
):
3849 changecolor
.__init
__(self
, palette
)
3852 class _changecolorredblue(changecolor
):
3854 def __init__(self
, palette
=color
.palette
.RedBlue
):
3855 changecolor
.__init
__(self
, palette
)
3858 class _changecolorgreenred(changecolor
):
3860 def __init__(self
, palette
=color
.palette
.GreenRed
):
3861 changecolor
.__init
__(self
, palette
)
3864 class _changecolorgreenblue(changecolor
):
3866 def __init__(self
, palette
=color
.palette
.GreenBlue
):
3867 changecolor
.__init
__(self
, palette
)
3870 class _changecolorbluered(changecolor
):
3872 def __init__(self
, palette
=color
.palette
.BlueRed
):
3873 changecolor
.__init
__(self
, palette
)
3876 class _changecolorbluegreen(changecolor
):
3878 def __init__(self
, palette
=color
.palette
.BlueGreen
):
3879 changecolor
.__init
__(self
, palette
)
3882 class _changecolorrainbow(changecolor
):
3884 def __init__(self
, palette
=color
.palette
.Rainbow
):
3885 changecolor
.__init
__(self
, palette
)
3888 class _changecolorreverserainbow(changecolor
):
3890 def __init__(self
, palette
=color
.palette
.ReverseRainbow
):
3891 changecolor
.__init
__(self
, palette
)
3894 class _changecolorhue(changecolor
):
3896 def __init__(self
, palette
=color
.palette
.Hue
):
3897 changecolor
.__init
__(self
, palette
)
3900 class _changecolorreversehue(changecolor
):
3902 def __init__(self
, palette
=color
.palette
.ReverseHue
):
3903 changecolor
.__init
__(self
, palette
)
3906 changecolor
.Gray
= _changecolorgray
3907 changecolor
.Grey
= _changecolorgrey
3908 changecolor
.Reversegray
= _changecolorreversegray
3909 changecolor
.Reversegrey
= _changecolorreversegrey
3910 changecolor
.RedBlack
= _changecolorredblack
3911 changecolor
.BlackRed
= _changecolorblackred
3912 changecolor
.RedWhite
= _changecolorredwhite
3913 changecolor
.WhiteRed
= _changecolorwhitered
3914 changecolor
.GreenBlack
= _changecolorgreenblack
3915 changecolor
.BlackGreen
= _changecolorblackgreen
3916 changecolor
.GreenWhite
= _changecolorgreenwhite
3917 changecolor
.WhiteGreen
= _changecolorwhitegreen
3918 changecolor
.BlueBlack
= _changecolorblueblack
3919 changecolor
.BlackBlue
= _changecolorblackblue
3920 changecolor
.BlueWhite
= _changecolorbluewhite
3921 changecolor
.WhiteBlue
= _changecolorwhiteblue
3922 changecolor
.RedGreen
= _changecolorredgreen
3923 changecolor
.RedBlue
= _changecolorredblue
3924 changecolor
.GreenRed
= _changecolorgreenred
3925 changecolor
.GreenBlue
= _changecolorgreenblue
3926 changecolor
.BlueRed
= _changecolorbluered
3927 changecolor
.BlueGreen
= _changecolorbluegreen
3928 changecolor
.Rainbow
= _changecolorrainbow
3929 changecolor
.ReverseRainbow
= _changecolorreverserainbow
3930 changecolor
.Hue
= _changecolorhue
3931 changecolor
.ReverseHue
= _changecolorreversehue
3934 class changesequence(changeattr
):
3935 "cycles through a list"
3937 def __init__(self
, *sequence
):
3938 changeattr
.__init
__(self
)
3939 if not len(sequence
):
3940 sequence
= self
.defaultsequence
3941 self
.sequence
= sequence
3943 def attr(self
, index
):
3944 return self
.sequence
[index
% len(self
.sequence
)]
3947 class changelinestyle(changesequence
):
3948 defaultsequence
= (style
.linestyle
.solid
,
3949 style
.linestyle
.dashed
,
3950 style
.linestyle
.dotted
,
3951 style
.linestyle
.dashdotted
)
3954 class changestrokedfilled(changesequence
):
3955 defaultsequence
= (deco
.stroked(), deco
.filled())
3958 class changefilledstroked(changesequence
):
3959 defaultsequence
= (deco
.filled(), deco
.stroked())
3963 ################################################################################
3965 ################################################################################
3970 def cross(self
, x
, y
):
3971 return (path
._moveto
(x
-0.5*self
._size
, y
-0.5*self
._size
),
3972 path
._lineto
(x
+0.5*self
._size
, y
+0.5*self
._size
),
3973 path
._moveto
(x
-0.5*self
._size
, y
+0.5*self
._size
),
3974 path
._lineto
(x
+0.5*self
._size
, y
-0.5*self
._size
))
3976 def plus(self
, x
, y
):
3977 return (path
._moveto
(x
-0.707106781*self
._size
, y
),
3978 path
._lineto
(x
+0.707106781*self
._size
, y
),
3979 path
._moveto
(x
, y
-0.707106781*self
._size
),
3980 path
._lineto
(x
, y
+0.707106781*self
._size
))
3982 def square(self
, x
, y
):
3983 return (path
._moveto
(x
-0.5*self
._size
, y
-0.5 * self
._size
),
3984 path
._lineto
(x
+0.5*self
._size
, y
-0.5 * self
._size
),
3985 path
._lineto
(x
+0.5*self
._size
, y
+0.5 * self
._size
),
3986 path
._lineto
(x
-0.5*self
._size
, y
+0.5 * self
._size
),
3989 def triangle(self
, x
, y
):
3990 return (path
._moveto
(x
-0.759835685*self
._size
, y
-0.438691337*self
._size
),
3991 path
._lineto
(x
+0.759835685*self
._size
, y
-0.438691337*self
._size
),
3992 path
._lineto
(x
, y
+0.877382675*self
._size
),
3995 def circle(self
, x
, y
):
3996 return (path
._arc
(x
, y
, 0.564189583*self
._size
, 0, 360),
3999 def diamond(self
, x
, y
):
4000 return (path
._moveto
(x
-0.537284965*self
._size
, y
),
4001 path
._lineto
(x
, y
-0.930604859*self
._size
),
4002 path
._lineto
(x
+0.537284965*self
._size
, y
),
4003 path
._lineto
(x
, y
+0.930604859*self
._size
),
4006 def __init__(self
, symbol
=helper
.nodefault
,
4007 size
="0.2 cm", symbolattrs
=deco
.stroked(),
4008 errorscale
=0.5, errorbarattrs
=(),
4010 self
.size_str
= size
4011 if symbol
is helper
.nodefault
:
4012 self
._symbol
= changesymbol
.cross()
4014 self
._symbol
= symbol
4015 self
._symbolattrs
= symbolattrs
4016 self
.errorscale
= errorscale
4017 self
._errorbarattrs
= errorbarattrs
4018 self
._lineattrs
= lineattrs
4020 def iteratedict(self
):
4022 result
["symbol"] = _iterateattr(self
._symbol
)
4023 result
["size"] = _iterateattr(self
.size_str
)
4024 result
["symbolattrs"] = _iterateattrs(self
._symbolattrs
)
4025 result
["errorscale"] = _iterateattr(self
.errorscale
)
4026 result
["errorbarattrs"] = _iterateattrs(self
._errorbarattrs
)
4027 result
["lineattrs"] = _iterateattrs(self
._lineattrs
)
4031 return symbol(**self
.iteratedict())
4033 def othercolumnkey(self
, key
, index
):
4034 raise ValueError("unsuitable key '%s'" % key
)
4036 def setcolumns(self
, graph
, columns
):
4037 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
4039 match
= pattern
.match(key
)
4041 if isindex
is not None: raise ValueError("multiple key specification")
4042 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
4044 iskey
= match
.groups()[0]
4046 return key
, iskey
, isindex
4048 self
.xi
= self
.xmini
= self
.xmaxi
= None
4049 self
.dxi
= self
.dxmini
= self
.dxmaxi
= None
4050 self
.yi
= self
.ymini
= self
.ymaxi
= None
4051 self
.dyi
= self
.dymini
= self
.dymaxi
= None
4052 self
.xkey
= self
.ykey
= None
4053 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
4054 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
4055 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
4056 XMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
4057 YMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
4058 XMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
4059 YMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
4060 DXPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
4061 DYPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
4062 DXMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
4063 DYMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
4064 DXMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
4065 DYMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
4066 for key
, index
in columns
.items():
4067 key
, self
.xkey
, self
.xi
= checkpattern(key
, index
, XPattern
, self
.xkey
, self
.xi
)
4068 key
, self
.ykey
, self
.yi
= checkpattern(key
, index
, YPattern
, self
.ykey
, self
.yi
)
4069 key
, self
.xkey
, self
.xmini
= checkpattern(key
, index
, XMinPattern
, self
.xkey
, self
.xmini
)
4070 key
, self
.ykey
, self
.ymini
= checkpattern(key
, index
, YMinPattern
, self
.ykey
, self
.ymini
)
4071 key
, self
.xkey
, self
.xmaxi
= checkpattern(key
, index
, XMaxPattern
, self
.xkey
, self
.xmaxi
)
4072 key
, self
.ykey
, self
.ymaxi
= checkpattern(key
, index
, YMaxPattern
, self
.ykey
, self
.ymaxi
)
4073 key
, self
.xkey
, self
.dxi
= checkpattern(key
, index
, DXPattern
, self
.xkey
, self
.dxi
)
4074 key
, self
.ykey
, self
.dyi
= checkpattern(key
, index
, DYPattern
, self
.ykey
, self
.dyi
)
4075 key
, self
.xkey
, self
.dxmini
= checkpattern(key
, index
, DXMinPattern
, self
.xkey
, self
.dxmini
)
4076 key
, self
.ykey
, self
.dymini
= checkpattern(key
, index
, DYMinPattern
, self
.ykey
, self
.dymini
)
4077 key
, self
.xkey
, self
.dxmaxi
= checkpattern(key
, index
, DXMaxPattern
, self
.xkey
, self
.dxmaxi
)
4078 key
, self
.ykey
, self
.dymaxi
= checkpattern(key
, index
, DYMaxPattern
, self
.ykey
, self
.dymaxi
)
4080 self
.othercolumnkey(key
, index
)
4081 if None in (self
.xkey
, self
.ykey
): raise ValueError("incomplete axis specification")
4082 if (len(filter(None, (self
.xmini
, self
.dxmini
, self
.dxi
))) > 1 or
4083 len(filter(None, (self
.ymini
, self
.dymini
, self
.dyi
))) > 1 or
4084 len(filter(None, (self
.xmaxi
, self
.dxmaxi
, self
.dxi
))) > 1 or
4085 len(filter(None, (self
.ymaxi
, self
.dymaxi
, self
.dyi
))) > 1):
4086 raise ValueError("multiple errorbar definition")
4087 if ((self
.xi
is None and self
.dxi
is not None) or
4088 (self
.yi
is None and self
.dyi
is not None) or
4089 (self
.xi
is None and self
.dxmini
is not None) or
4090 (self
.yi
is None and self
.dymini
is not None) or
4091 (self
.xi
is None and self
.dxmaxi
is not None) or
4092 (self
.yi
is None and self
.dymaxi
is not None)):
4093 raise ValueError("errorbar definition start value missing")
4094 self
.xaxis
= graph
.axes
[self
.xkey
]
4095 self
.yaxis
= graph
.axes
[self
.ykey
]
4097 def minmidmax(self
, point
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
4098 min = max = mid
= None
4100 mid
= point
[i
] + 0.0
4101 except (TypeError, ValueError):
4104 if di
is not None: min = point
[i
] - point
[di
]
4105 elif dmini
is not None: min = point
[i
] - point
[dmini
]
4106 elif mini
is not None: min = point
[mini
] + 0.0
4107 except (TypeError, ValueError):
4110 if di
is not None: max = point
[i
] + point
[di
]
4111 elif dmaxi
is not None: max = point
[i
] + point
[dmaxi
]
4112 elif maxi
is not None: max = point
[maxi
] + 0.0
4113 except (TypeError, ValueError):
4116 if min is not None and min > mid
: raise ValueError("minimum error in errorbar")
4117 if max is not None and max < mid
: raise ValueError("maximum error in errorbar")
4119 if min is not None and max is not None and min > max: raise ValueError("minimum/maximum error in errorbar")
4120 return min, mid
, max
4122 def keyrange(self
, points
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
4123 allmin
= allmax
= None
4124 if filter(None, (mini
, maxi
, di
, dmini
, dmaxi
)) is not None:
4125 for point
in points
:
4126 min, mid
, max = self
.minmidmax(point
, i
, mini
, maxi
, di
, dmini
, dmaxi
)
4127 if min is not None and (allmin
is None or min < allmin
): allmin
= min
4128 if mid
is not None and (allmin
is None or mid
< allmin
): allmin
= mid
4129 if mid
is not None and (allmax
is None or mid
> allmax
): allmax
= mid
4130 if max is not None and (allmax
is None or max > allmax
): allmax
= max
4132 for point
in points
:
4134 value
= point
[i
] + 0.0
4135 if allmin
is None or point
[i
] < allmin
: allmin
= point
[i
]
4136 if allmax
is None or point
[i
] > allmax
: allmax
= point
[i
]
4137 except (TypeError, ValueError):
4139 return allmin
, allmax
4141 def getranges(self
, points
):
4142 xmin
, xmax
= self
.keyrange(points
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
4143 ymin
, ymax
= self
.keyrange(points
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
4144 return {self
.xkey
: (xmin
, xmax
), self
.ykey
: (ymin
, ymax
)}
4146 def _drawerrorbar(self
, graph
, topleft
, top
, topright
,
4147 left
, center
, right
,
4148 bottomleft
, bottom
, bottomright
, point
=None):
4149 if left
is not None:
4150 if right
is not None:
4151 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
4152 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
4153 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
4154 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
4155 graph
.stroke(path
.path(path
._moveto
(*left1
),
4156 graph
._connect
(*(left1
+left2
)),
4157 path
._moveto
(*left
),
4158 graph
._connect
(*(left
+right
)),
4159 path
._moveto
(*right1
),
4160 graph
._connect
(*(right1
+right2
))),
4161 *self
.errorbarattrs
)
4162 elif center
is not None:
4163 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
4164 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
4165 graph
.stroke(path
.path(path
._moveto
(*left1
),
4166 graph
._connect
(*(left1
+left2
)),
4167 path
._moveto
(*left
),
4168 graph
._connect
(*(left
+center
))),
4169 *self
.errorbarattrs
)
4171 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
4172 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
4173 left3
= graph
._addpos
(*(left
+(self
._errorsize
, 0)))
4174 graph
.stroke(path
.path(path
._moveto
(*left1
),
4175 graph
._connect
(*(left1
+left2
)),
4176 path
._moveto
(*left
),
4177 graph
._connect
(*(left
+left3
))),
4178 *self
.errorbarattrs
)
4179 if right
is not None and left
is None:
4180 if center
is not None:
4181 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
4182 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
4183 graph
.stroke(path
.path(path
._moveto
(*right1
),
4184 graph
._connect
(*(right1
+right2
)),
4185 path
._moveto
(*right
),
4186 graph
._connect
(*(right
+center
))),
4187 *self
.errorbarattrs
)
4189 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
4190 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
4191 right3
= graph
._addpos
(*(right
+(-self
._errorsize
, 0)))
4192 graph
.stroke(path
.path(path
._moveto
(*right1
),
4193 graph
._connect
(*(right1
+right2
)),
4194 path
._moveto
(*right
),
4195 graph
._connect
(*(right
+right3
))),
4196 *self
.errorbarattrs
)
4198 if bottom
is not None:
4200 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
4201 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
4202 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
4203 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
4204 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
4205 graph
._connect
(*(bottom1
+bottom2
)),
4206 path
._moveto
(*bottom
),
4207 graph
._connect
(*(bottom
+top
)),
4208 path
._moveto
(*top1
),
4209 graph
._connect
(*(top1
+top2
))),
4210 *self
.errorbarattrs
)
4211 elif center
is not None:
4212 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
4213 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
4214 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
4215 graph
._connect
(*(bottom1
+bottom2
)),
4216 path
._moveto
(*bottom
),
4217 graph
._connect
(*(bottom
+center
))),
4218 *self
.errorbarattrs
)
4220 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
4221 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
4222 bottom3
= graph
._addpos
(*(bottom
+(0, self
._errorsize
)))
4223 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
4224 graph
._connect
(*(bottom1
+bottom2
)),
4225 path
._moveto
(*bottom
),
4226 graph
._connect
(*(bottom
+bottom3
))),
4227 *self
.errorbarattrs
)
4228 if top
is not None and bottom
is None:
4229 if center
is not None:
4230 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
4231 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
4232 graph
.stroke(path
.path(path
._moveto
(*top1
),
4233 graph
._connect
(*(top1
+top2
)),
4235 graph
._connect
(*(top
+center
))),
4236 *self
.errorbarattrs
)
4238 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
4239 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
4240 top3
= graph
._addpos
(*(top
+(0, -self
._errorsize
)))
4241 graph
.stroke(path
.path(path
._moveto
(*top1
),
4242 graph
._connect
(*(top1
+top2
)),
4244 graph
._connect
(*(top
+top3
))),
4245 *self
.errorbarattrs
)
4246 if bottomleft
is not None:
4247 if topleft
is not None and bottomright
is None:
4248 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
._errorsize
, 0)))
4249 topleft1
= graph
._addpos
(*(topleft
+(self
._errorsize
, 0)))
4250 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
4251 graph
._connect
(*(bottomleft1
+bottomleft
)),
4252 graph
._connect
(*(bottomleft
+topleft
)),
4253 graph
._connect
(*(topleft
+topleft1
))),
4254 *self
.errorbarattrs
)
4255 elif bottomright
is not None and topleft
is None:
4256 bottomleft1
= graph
._addpos
(*(bottomleft
+(0, self
._errorsize
)))
4257 bottomright1
= graph
._addpos
(*(bottomright
+(0, self
._errorsize
)))
4258 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
4259 graph
._connect
(*(bottomleft1
+bottomleft
)),
4260 graph
._connect
(*(bottomleft
+bottomright
)),
4261 graph
._connect
(*(bottomright
+bottomright1
))),
4262 *self
.errorbarattrs
)
4263 elif bottomright
is None and topleft
is None:
4264 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
._errorsize
, 0)))
4265 bottomleft2
= graph
._addpos
(*(bottomleft
+(0, self
._errorsize
)))
4266 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
4267 graph
._connect
(*(bottomleft1
+bottomleft
)),
4268 graph
._connect
(*(bottomleft
+bottomleft2
))),
4269 *self
.errorbarattrs
)
4270 if topright
is not None:
4271 if bottomright
is not None and topleft
is None:
4272 topright1
= graph
._addpos
(*(topright
+(-self
._errorsize
, 0)))
4273 bottomright1
= graph
._addpos
(*(bottomright
+(-self
._errorsize
, 0)))
4274 graph
.stroke(path
.path(path
._moveto
(*topright1
),
4275 graph
._connect
(*(topright1
+topright
)),
4276 graph
._connect
(*(topright
+bottomright
)),
4277 graph
._connect
(*(bottomright
+bottomright1
))),
4278 *self
.errorbarattrs
)
4279 elif topleft
is not None and bottomright
is None:
4280 topright1
= graph
._addpos
(*(topright
+(0, -self
._errorsize
)))
4281 topleft1
= graph
._addpos
(*(topleft
+(0, -self
._errorsize
)))
4282 graph
.stroke(path
.path(path
._moveto
(*topright1
),
4283 graph
._connect
(*(topright1
+topright
)),
4284 graph
._connect
(*(topright
+topleft
)),
4285 graph
._connect
(*(topleft
+topleft1
))),
4286 *self
.errorbarattrs
)
4287 elif topleft
is None and bottomright
is None:
4288 topright1
= graph
._addpos
(*(topright
+(-self
._errorsize
, 0)))
4289 topright2
= graph
._addpos
(*(topright
+(0, -self
._errorsize
)))
4290 graph
.stroke(path
.path(path
._moveto
(*topright1
),
4291 graph
._connect
(*(topright1
+topright
)),
4292 graph
._connect
(*(topright
+topright2
))),
4293 *self
.errorbarattrs
)
4294 if bottomright
is not None and bottomleft
is None and topright
is None:
4295 bottomright1
= graph
._addpos
(*(bottomright
+(-self
._errorsize
, 0)))
4296 bottomright2
= graph
._addpos
(*(bottomright
+(0, self
._errorsize
)))
4297 graph
.stroke(path
.path(path
._moveto
(*bottomright1
),
4298 graph
._connect
(*(bottomright1
+bottomright
)),
4299 graph
._connect
(*(bottomright
+bottomright2
))),
4300 *self
.errorbarattrs
)
4301 if topleft
is not None and bottomleft
is None and topright
is None:
4302 topleft1
= graph
._addpos
(*(topleft
+(self
._errorsize
, 0)))
4303 topleft2
= graph
._addpos
(*(topleft
+(0, -self
._errorsize
)))
4304 graph
.stroke(path
.path(path
._moveto
(*topleft1
),
4305 graph
._connect
(*(topleft1
+topleft
)),
4306 graph
._connect
(*(topleft
+topleft2
))),
4307 *self
.errorbarattrs
)
4308 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
4309 graph
.stroke(path
.path(path
._moveto
(*bottomleft
),
4310 graph
._connect
(*(bottomleft
+bottomright
)),
4311 graph
._connect
(*(bottomright
+topright
)),
4312 graph
._connect
(*(topright
+topleft
)),
4314 *self
.errorbarattrs
)
4316 def _drawsymbol(self
, canvas
, x
, y
, point
=None):
4317 canvas
.draw(path
.path(*self
.symbol(self
, x
, y
)), *self
.symbolattrs
)
4319 def drawsymbol(self
, canvas
, x
, y
, point
=None):
4320 self
._drawsymbol
(canvas
, unit
.topt(x
), unit
.topt(y
), point
)
4322 def key(self
, c
, x
, y
, width
, height
):
4323 if self
._symbolattrs
is not None:
4324 self
._drawsymbol
(c
, x
+ 0.5 * width
, y
+ 0.5 * height
)
4325 if self
._lineattrs
is not None:
4326 c
.stroke(path
._line
(x
, y
+ 0.5 * height
, x
+ width
, y
+ 0.5 * height
), *self
.lineattrs
)
4328 def drawpoints(self
, graph
, points
):
4329 xaxismin
, xaxismax
= self
.xaxis
.getrange()
4330 yaxismin
, yaxismax
= self
.yaxis
.getrange()
4331 self
.size
= unit
.length(_getattr(self
.size_str
), default_type
="v")
4332 self
._size
= unit
.topt(self
.size
)
4333 self
.symbol
= _getattr(self
._symbol
)
4334 self
.symbolattrs
= _getattrs(helper
.ensuresequence(self
._symbolattrs
))
4335 self
.errorbarattrs
= _getattrs(helper
.ensuresequence(self
._errorbarattrs
))
4336 self
._errorsize
= self
.errorscale
* self
._size
4337 self
.errorsize
= self
.errorscale
* self
.size
4338 self
.lineattrs
= _getattrs(helper
.ensuresequence(self
._lineattrs
))
4339 if self
._lineattrs
is not None:
4340 clipcanvas
= graph
.clipcanvas()
4342 haserror
= filter(None, (self
.xmini
, self
.ymini
, self
.xmaxi
, self
.ymaxi
,
4343 self
.dxi
, self
.dyi
, self
.dxmini
, self
.dymini
, self
.dxmaxi
, self
.dymaxi
)) is not None
4345 for point
in points
:
4347 xmin
, x
, xmax
= self
.minmidmax(point
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
4348 ymin
, y
, ymax
= self
.minmidmax(point
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
4349 if x
is not None and x
< xaxismin
: drawsymbol
= 0
4350 elif x
is not None and x
> xaxismax
: drawsymbol
= 0
4351 elif y
is not None and y
< yaxismin
: drawsymbol
= 0
4352 elif y
is not None and y
> yaxismax
: drawsymbol
= 0
4353 # elif haserror: # TODO: correct clipcanvas handling
4354 # if xmin is not None and xmin < xaxismin: drawsymbol = 0
4355 # elif xmax is not None and xmax < xaxismin: drawsymbol = 0
4356 # elif xmax is not None and xmax > xaxismax: drawsymbol = 0
4357 # elif xmin is not None and xmin > xaxismax: drawsymbol = 0
4358 # elif ymin is not None and ymin < yaxismin: drawsymbol = 0
4359 # elif ymax is not None and ymax < yaxismin: drawsymbol = 0
4360 # elif ymax is not None and ymax > yaxismax: drawsymbol = 0
4361 # elif ymin is not None and ymin > yaxismax: drawsymbol = 0
4362 xpos
=ypos
=topleft
=top
=topright
=left
=center
=right
=bottomleft
=bottom
=bottomright
=None
4363 if x
is not None and y
is not None:
4365 center
= xpos
, ypos
= graph
._pos
(x
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4366 except (ValueError, OverflowError): # XXX: exceptions???
4370 if xmin
is not None: left
= graph
._pos
(xmin
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4371 if xmax
is not None: right
= graph
._pos
(xmax
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4373 if ymax
is not None: top
= graph
._pos
(x
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4374 if ymin
is not None: bottom
= graph
._pos
(x
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4375 if x
is None or y
is None:
4376 if ymax
is not None:
4377 if xmin
is not None: topleft
= graph
._pos
(xmin
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4378 if xmax
is not None: topright
= graph
._pos
(xmax
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4379 if ymin
is not None:
4380 if xmin
is not None: bottomleft
= graph
._pos
(xmin
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4381 if xmax
is not None: bottomright
= graph
._pos
(xmax
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4383 if self
._errorbarattrs
is not None and haserror
:
4384 self
._drawerrorbar
(graph
, topleft
, top
, topright
,
4385 left
, center
, right
,
4386 bottomleft
, bottom
, bottomright
, point
)
4387 if self
._symbolattrs
is not None and xpos
is not None and ypos
is not None:
4388 self
._drawsymbol
(graph
, xpos
, ypos
, point
)
4389 if xpos
is not None and ypos
is not None:
4391 lineels
.append(path
._moveto
(xpos
, ypos
))
4394 lineels
.append(path
._lineto
(xpos
, ypos
))
4397 self
.path
= path
.path(*lineels
)
4398 if self
._lineattrs
is not None:
4399 clipcanvas
.stroke(self
.path
, *self
.lineattrs
)
4402 class changesymbol(changesequence
): pass
4405 class _changesymbolcross(changesymbol
):
4406 defaultsequence
= (symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
)
4409 class _changesymbolplus(changesymbol
):
4410 defaultsequence
= (symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
)
4413 class _changesymbolsquare(changesymbol
):
4414 defaultsequence
= (symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
)
4417 class _changesymboltriangle(changesymbol
):
4418 defaultsequence
= (symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
)
4421 class _changesymbolcircle(changesymbol
):
4422 defaultsequence
= (symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
)
4425 class _changesymboldiamond(changesymbol
):
4426 defaultsequence
= (symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
)
4429 class _changesymbolsquaretwice(changesymbol
):
4430 defaultsequence
= (symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
,
4431 symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
)
4434 class _changesymboltriangletwice(changesymbol
):
4435 defaultsequence
= (symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
,
4436 symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
)
4439 class _changesymbolcircletwice(changesymbol
):
4440 defaultsequence
= (symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
,
4441 symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
)
4444 class _changesymboldiamondtwice(changesymbol
):
4445 defaultsequence
= (symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
,
4446 symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
)
4449 changesymbol
.cross
= _changesymbolcross
4450 changesymbol
.plus
= _changesymbolplus
4451 changesymbol
.square
= _changesymbolsquare
4452 changesymbol
.triangle
= _changesymboltriangle
4453 changesymbol
.circle
= _changesymbolcircle
4454 changesymbol
.diamond
= _changesymboldiamond
4455 changesymbol
.squaretwice
= _changesymbolsquaretwice
4456 changesymbol
.triangletwice
= _changesymboltriangletwice
4457 changesymbol
.circletwice
= _changesymbolcircletwice
4458 changesymbol
.diamondtwice
= _changesymboldiamondtwice
4463 def __init__(self
, lineattrs
=helper
.nodefault
):
4464 if lineattrs
is helper
.nodefault
:
4465 lineattrs
= (changelinestyle(), style
.linejoin
.round)
4466 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=None, lineattrs
=lineattrs
)
4471 def __init__(self
, palette
=color
.palette
.Gray
):
4472 self
.palette
= palette
4473 self
.colorindex
= None
4474 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=(), lineattrs
=None)
4477 raise RuntimeError("style is not iterateable")
4479 def othercolumnkey(self
, key
, index
):
4481 self
.colorindex
= index
4483 symbol
.othercolumnkey(self
, key
, index
)
4485 def _drawerrorbar(self
, graph
, topleft
, top
, topright
,
4486 left
, center
, right
,
4487 bottomleft
, bottom
, bottomright
, point
=None):
4488 color
= point
[self
.colorindex
]
4489 if color
is not None:
4490 if color
!= self
.lastcolor
:
4491 self
.rectclipcanvas
.set(self
.palette
.getcolor(color
))
4492 if bottom
is not None and left
is not None:
4493 bottomleft
= left
[0], bottom
[1]
4494 if bottom
is not None and right
is not None:
4495 bottomright
= right
[0], bottom
[1]
4496 if top
is not None and right
is not None:
4497 topright
= right
[0], top
[1]
4498 if top
is not None and left
is not None:
4499 topleft
= left
[0], top
[1]
4500 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
4501 self
.rectclipcanvas
.fill(path
.path(path
._moveto
(*bottomleft
),
4502 graph
._connect
(*(bottomleft
+bottomright
)),
4503 graph
._connect
(*(bottomright
+topright
)),
4504 graph
._connect
(*(topright
+topleft
)),
4507 def drawpoints(self
, graph
, points
):
4508 if self
.colorindex
is None:
4509 raise RuntimeError("column 'color' not set")
4510 self
.lastcolor
= None
4511 self
.rectclipcanvas
= graph
.clipcanvas()
4512 symbol
.drawpoints(self
, graph
, points
)
4514 def key(self
, c
, x
, y
, width
, height
):
4515 raise RuntimeError("style doesn't yet provide a key")
4520 def __init__(self
, textdx
="0", textdy
="0.3 cm", textattrs
=textmodule
.halign
.center
, **args
):
4521 self
.textindex
= None
4522 self
.textdx_str
= textdx
4523 self
.textdy_str
= textdy
4524 self
._textattrs
= textattrs
4525 symbol
.__init
__(self
, **args
)
4527 def iteratedict(self
):
4528 result
= symbol
.iteratedict()
4529 result
["textattrs"] = _iterateattr(self
._textattrs
)
4533 return textsymbol(**self
.iteratedict())
4535 def othercolumnkey(self
, key
, index
):
4537 self
.textindex
= index
4539 symbol
.othercolumnkey(self
, key
, index
)
4541 def _drawsymbol(self
, graph
, x
, y
, point
=None):
4542 symbol
._drawsymbol
(self
, graph
, x
, y
, point
)
4543 if None not in (x
, y
, point
[self
.textindex
]) and self
._textattrs
is not None:
4544 graph
._text
(x
+ self
._textdx
, y
+ self
._textdy
, str(point
[self
.textindex
]), *helper
.ensuresequence(self
.textattrs
))
4546 def drawpoints(self
, graph
, points
):
4547 self
.textdx
= unit
.length(_getattr(self
.textdx_str
), default_type
="v")
4548 self
.textdy
= unit
.length(_getattr(self
.textdy_str
), default_type
="v")
4549 self
._textdx
= unit
.topt(self
.textdx
)
4550 self
._textdy
= unit
.topt(self
.textdy
)
4551 if self
._textattrs
is not None:
4552 self
.textattrs
= _getattr(self
._textattrs
)
4553 if self
.textindex
is None:
4554 raise RuntimeError("column 'text' not set")
4555 symbol
.drawpoints(self
, graph
, points
)
4557 def key(self
, c
, x
, y
, width
, height
):
4558 raise RuntimeError("style doesn't yet provide a key")
4561 class arrow(symbol
):
4563 def __init__(self
, linelength
="0.2 cm", arrowattrs
=(), arrowsize
="0.1 cm", arrowdict
={}, epsilon
=1e-10):
4564 self
.linelength_str
= linelength
4565 self
.arrowsize_str
= arrowsize
4566 self
.arrowattrs
= arrowattrs
4567 self
.arrowdict
= arrowdict
4568 self
.epsilon
= epsilon
4569 self
.sizeindex
= self
.angleindex
= None
4570 symbol
.__init
__(self
, symbolattrs
=(), errorbarattrs
=None, lineattrs
=None)
4573 raise RuntimeError("style is not iterateable")
4575 def othercolumnkey(self
, key
, index
):
4577 self
.sizeindex
= index
4578 elif key
== "angle":
4579 self
.angleindex
= index
4581 symbol
.othercolumnkey(self
, key
, index
)
4583 def _drawsymbol(self
, graph
, x
, y
, point
=None):
4584 if None not in (x
, y
, point
[self
.angleindex
], point
[self
.sizeindex
], self
.arrowattrs
, self
.arrowdict
):
4585 if point
[self
.sizeindex
] > self
.epsilon
:
4586 dx
, dy
= math
.cos(point
[self
.angleindex
]*math
.pi
/180.0), math
.sin(point
[self
.angleindex
]*math
.pi
/180)
4587 x1
= unit
.t_pt(x
)-0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
4588 y1
= unit
.t_pt(y
)-0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
4589 x2
= unit
.t_pt(x
)+0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
4590 y2
= unit
.t_pt(y
)+0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
4591 graph
.stroke(path
.line(x1
, y1
, x2
, y2
),
4592 deco
.earrow(self
.arrowsize
*point
[self
.sizeindex
],
4594 *helper
.ensuresequence(self
.arrowattrs
))
4596 def drawpoints(self
, graph
, points
):
4597 self
.arrowsize
= unit
.length(_getattr(self
.arrowsize_str
), default_type
="v")
4598 self
.linelength
= unit
.length(_getattr(self
.linelength_str
), default_type
="v")
4599 self
._arrowsize
= unit
.topt(self
.arrowsize
)
4600 self
._linelength
= unit
.topt(self
.linelength
)
4601 if self
.sizeindex
is None:
4602 raise RuntimeError("column 'size' not set")
4603 if self
.angleindex
is None:
4604 raise RuntimeError("column 'angle' not set")
4605 symbol
.drawpoints(self
, graph
, points
)
4607 def key(self
, c
, x
, y
, width
, height
):
4608 raise RuntimeError("style doesn't yet provide a key")
4611 class _bariterator(changeattr
):
4613 def attr(self
, index
):
4614 return index
, self
.counter
4619 def __init__(self
, fromzero
=1, stacked
=0, skipmissing
=1, xbar
=0,
4620 barattrs
=helper
.nodefault
, _usebariterator
=helper
.nodefault
, _previousbar
=None):
4621 self
.fromzero
= fromzero
4622 self
.stacked
= stacked
4623 self
.skipmissing
= skipmissing
4625 if barattrs
is helper
.nodefault
:
4626 self
._barattrs
= (deco
.stroked(color
.gray
.black
), changecolor
.Rainbow())
4628 self
._barattrs
= barattrs
4629 if _usebariterator
is helper
.nodefault
:
4630 self
.bariterator
= _bariterator()
4632 self
.bariterator
= _usebariterator
4633 self
.previousbar
= _previousbar
4635 def iteratedict(self
):
4637 result
["barattrs"] = _iterateattrs(self
._barattrs
)
4641 return bar(fromzero
=self
.fromzero
, stacked
=self
.stacked
, xbar
=self
.xbar
,
4642 _usebariterator
=_iterateattr(self
.bariterator
), _previousbar
=self
, **self
.iteratedict())
4644 def setcolumns(self
, graph
, columns
):
4645 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
4647 match
= pattern
.match(key
)
4649 if isindex
is not None: raise ValueError("multiple key specification")
4650 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
4652 iskey
= match
.groups()[0]
4654 return key
, iskey
, isindex
4657 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
4658 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
4659 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
4661 for key
, index
in columns
.items():
4662 key
, xkey
, xi
= checkpattern(key
, index
, XPattern
, xkey
, xi
)
4663 key
, ykey
, yi
= checkpattern(key
, index
, YPattern
, ykey
, yi
)
4665 self
.othercolumnkey(key
, index
)
4666 if None in (xkey
, ykey
): raise ValueError("incomplete axis specification")
4668 self
.nkey
, self
.ni
= ykey
, yi
4669 self
.vkey
, self
.vi
= xkey
, xi
4671 self
.nkey
, self
.ni
= xkey
, xi
4672 self
.vkey
, self
.vi
= ykey
, yi
4673 self
.naxis
, self
.vaxis
= graph
.axes
[self
.nkey
], graph
.axes
[self
.vkey
]
4675 def getranges(self
, points
):
4676 index
, count
= _getattr(self
.bariterator
)
4677 if count
!= 1 and self
.stacked
!= 1:
4678 if self
.stacked
> 1:
4679 index
= divmod(index
, self
.stacked
)[0]
4682 for point
in points
:
4683 if not self
.skipmissing
:
4684 if count
!= 1 and self
.stacked
!= 1:
4685 self
.naxis
.setname(point
[self
.ni
], index
)
4687 self
.naxis
.setname(point
[self
.ni
])
4689 v
= point
[self
.vi
] + 0.0
4690 if vmin
is None or v
< vmin
: vmin
= v
4691 if vmax
is None or v
> vmax
: vmax
= v
4692 except (TypeError, ValueError):
4695 if self
.skipmissing
:
4696 if count
!= 1 and self
.stacked
!= 1:
4697 self
.naxis
.setname(point
[self
.ni
], index
)
4699 self
.naxis
.setname(point
[self
.ni
])
4701 if vmin
> 0: vmin
= 0
4702 if vmax
< 0: vmax
= 0
4703 return {self
.vkey
: (vmin
, vmax
)}
4705 def drawpoints(self
, graph
, points
):
4706 index
, count
= _getattr(self
.bariterator
)
4707 dostacked
= (self
.stacked
!= 0 and
4708 (self
.stacked
== 1 or divmod(index
, self
.stacked
)[1]) and
4709 (self
.stacked
!= 1 or index
))
4710 if self
.stacked
> 1:
4711 index
= divmod(index
, self
.stacked
)[0]
4712 vmin
, vmax
= self
.vaxis
.getrange()
4713 self
.barattrs
= _getattrs(helper
.ensuresequence(self
._barattrs
))
4715 self
.stackedvalue
= {}
4716 for point
in points
:
4721 self
.stackedvalue
[n
] = v
4722 if count
!= 1 and self
.stacked
!= 1:
4723 minid
= (n
, index
, 0)
4724 maxid
= (n
, index
, 1)
4729 x1pos
, y1pos
= graph
._pos
(v
, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4730 x2pos
, y2pos
= graph
._pos
(v
, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4732 x1pos
, y1pos
= graph
._pos
(minid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4733 x2pos
, y2pos
= graph
._pos
(maxid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4736 x3pos
, y3pos
= graph
._pos
(self
.previousbar
.stackedvalue
[n
], maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4737 x4pos
, y4pos
= graph
._pos
(self
.previousbar
.stackedvalue
[n
], minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4739 x3pos
, y3pos
= graph
._pos
(maxid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4740 x4pos
, y4pos
= graph
._pos
(minid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4744 x3pos
, y3pos
= graph
._pos
(0, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4745 x4pos
, y4pos
= graph
._pos
(0, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4747 x3pos
, y3pos
= graph
._pos
(maxid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4748 x4pos
, y4pos
= graph
._pos
(minid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4750 x3pos
, y3pos
= graph
._tickpoint
(maxid
, axis
=self
.naxis
)
4751 x4pos
, y4pos
= graph
._tickpoint
(minid
, axis
=self
.naxis
)
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
)