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
, path
, unit
, mathtree
, color
, helper
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
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 to be filled by
1328 a 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 filled by the axispainters painter method; you may
1332 grasp this as a size information comparable to a bounding
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
, texrunner
, *args
, **kwargs
):
1344 """initializes the instance
1345 - sets the texrunner
1346 - sets extent to zero
1347 - sets labels to an empty list"""
1348 canvas
._canvas
.__init
__(self
, *args
, **kwargs
)
1349 self
.settexrunner(texrunner
)
1355 """create rotations accordingly to tick directions
1356 - upsidedown rotations are suppressed by rotating them by another 180 degree"""
1358 # __implements__ = sole implementation
1360 def __init__(self
, direction
, epsilon
=1e-10):
1361 """initializes the instance
1362 - direction is an angle to be used relative to the tick direction
1363 - epsilon is the value by which 90 degrees can be exceeded before
1364 an 180 degree rotation is added"""
1365 self
.direction
= direction
1366 self
.epsilon
= epsilon
1368 def trafo(self
, dx
, dy
):
1369 """returns a rotation transformation accordingly to the tick direction
1370 - dx and dy are the direction of the tick"""
1371 direction
= self
.direction
+ math
.atan2(dy
, dx
) * 180 / math
.pi
1372 while (direction
> 90 + self
.epsilon
):
1374 while (direction
< -90 - self
.epsilon
):
1376 return trafomodule
.rotate(direction
)
1379 rotatetext
.parallel
= rotatetext(-90)
1380 rotatetext
.orthogonal
= rotatetext(0)
1383 class _Iaxispainter
:
1384 "class for painting axes"
1386 def paint(self
, axispos
, axis
, axiscanvas
):
1387 """paint the axis into the axiscanvas
1388 - axispos is an instance, which implements _Iaxispos to
1389 define the tick positions
1390 - the axis and should not be modified (we may
1391 add some temporary variables like axis.ticks[i].temp_xxx,
1392 which might be used just temporary) -- the idea is that
1393 all things can be used several times
1394 - also do not modify the instance (self) -- even this
1395 instance might be used several times; thus do not modify
1396 attributes like self.titleattrs etc. (just use a copy of it)
1397 - the method might access some additional attributes from
1398 the axis, e.g. the axis title -- the axis painter should
1399 document this behavior and rely on the availability of
1400 those attributes -> it becomes a question of the proper
1401 usage of the combination of axis & axispainter
1402 - the axiscanvas is a axiscanvas instance and should be
1403 filled with ticks, labels, title, etc.; note that the
1404 extent and labels instance variables should be filled as
1405 documented in the axiscanvas"""
1408 class axistitlepainter
:
1409 """class for painting an axis title
1410 - the axis must have a title attribute when using this painter;
1411 this title might be None"""
1413 __implements__
= _Iaxispainter
1415 def __init__(self
, titledist
="0.3 cm",
1416 titleattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1417 titledirection
=rotatetext
.parallel
,
1419 """initialized the instance
1420 - titledist is a visual PyX length giving the distance
1421 of the title from the axis extent already there (a title might
1422 be added after labels or other things are plotted already)
1423 - labelattrs is a list of attributes for a texrunners text
1424 method; a single is allowed without being a list; None
1426 - titledirection is an instance of rotatetext or None
1427 - titlepos is the position of the title in graph coordinates"""
1428 self
.titledist_str
= titledist
1429 self
.titleattrs
= titleattrs
1430 self
.titledirection
= titledirection
1431 self
.titlepos
= titlepos
1433 def paint(self
, axispos
, axis
, axiscanvas
):
1434 if axis
.title
is not None and self
.titleattrs
is not None:
1435 titledist
= unit
.length(self
.titledist_str
, default_type
="v")
1436 x
, y
= axispos
._vtickpoint
(self
.titlepos
, axis
=axis
)
1437 dx
, dy
= axispos
.vtickdirection(self
.titlepos
, axis
=axis
)
1438 titleattrs
= helper
.ensurelist(self
.titleattrs
)
1439 if self
.titledirection
is not None:
1440 titleattrs
.append(self
.titledirection
.trafo(dx
, dy
))
1441 title
= axiscanvas
.texrunner
._text
(x
, y
, axis
.title
, *titleattrs
)
1442 axiscanvas
.extent
+= titledist
1443 title
.linealign(axiscanvas
.extent
, dx
, dy
)
1444 axiscanvas
.extent
+= title
.extent(dx
, dy
)
1445 axiscanvas
.insert(title
)
1448 class axispainter(axistitlepainter
):
1449 """class for painting the ticks and labels of an axis
1450 - the inherited titleaxispainter is used to paint the title of
1452 - note that the type of the elements of ticks given as an argument
1453 of the paint method must be suitable for the tick position methods
1456 __implements__
= _Iaxispainter
1458 defaultticklengths
= ["%0.5f cm" % (0.2*goldenmean
**(-i
)) for i
in range(10)]
1460 def __init__(self
, innerticklengths
=defaultticklengths
,
1461 outerticklengths
=None,
1465 baselineattrs
=canvas
.linecap
.square
,
1467 labelattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1468 labeldirection
=None,
1472 """initializes the instance
1473 - innerticklenths and outerticklengths are two lists of
1474 visual PyX lengths for ticks, subticks, etc. plotted inside
1475 and outside of the graph; when a single value is given, it
1476 is used for all tick levels; None turns off ticks inside or
1477 outside of the graph
1478 - tickattrs are a list of stroke attributes for the ticks;
1479 a single entry is allowed without being a list; None turns
1481 - gridlineattrs are a list of lists used as stroke
1482 attributes for ticks, subticks etc.; when a single list
1483 is given, it is used for ticks, subticks, etc.; a single
1484 entry is allowed without being a list; None turns off
1486 - zerolineattrs are a list of stroke attributes for a grid
1487 line at axis value zero; a single entry is allowed without
1488 being a list; None turns off the zeroline
1489 - baselineattrs are a list of stroke attributes for a grid
1490 line at axis value zero; a single entry is allowed without
1491 being a list; None turns off the baseline
1492 - labeldist is a visual PyX length for the distance of the labels
1493 from the axis baseline
1494 - labelattrs is a list of attributes for a texrunners text
1495 method; a single entry is allowed without being a list;
1496 None turns off the labels
1497 - titledirection is an instance of rotatetext or None
1498 - labelhequalize and labelvequalize (booleans) perform an equal
1499 alignment for straight vertical and horizontal axes, respectively
1500 - futher keyword arguments are passed to axistitlepainter"""
1501 # TODO: access to axis.divisor -- document, remove, ... ???
1502 self
.innerticklengths_str
= innerticklengths
1503 self
.outerticklengths_str
= outerticklengths
1504 self
.tickattrs
= tickattrs
1505 self
.gridattrs
= gridattrs
1506 self
.zerolineattrs
= zerolineattrs
1507 self
.baselineattrs
= baselineattrs
1508 self
.labeldist_str
= labeldist
1509 self
.labelattrs
= labelattrs
1510 self
.labeldirection
= labeldirection
1511 self
.labelhequalize
= labelhequalize
1512 self
.labelvequalize
= labelvequalize
1513 axistitlepainter
.__init
__(self
, **kwargs
)
1515 def paint(self
, axispos
, axis
, axiscanvas
):
1516 labeldist
= unit
.length(self
.labeldist_str
, default_type
="v")
1517 for tick
in axis
.ticks
:
1518 if axis
.divisor
is not None: # XXX workaround for timeaxis
1519 tick
.temp_v
= axis
.convert(float(tick
) * axis
.divisor
)
1521 tick
.temp_v
= axis
.convert(tick
)
1522 tick
.temp_x
, tick
.temp_y
= axispos
._vtickpoint
(tick
.temp_v
, axis
=axis
)
1523 tick
.temp_dx
, tick
.temp_dy
= axispos
.vtickdirection(tick
.temp_v
, axis
=axis
)
1525 # create & align tick.temp_labelbox
1526 for tick
in axis
.ticks
:
1527 if tick
.labellevel
is not None:
1528 labelattrs
= helper
.getsequenceno(self
.labelattrs
, tick
.labellevel
)
1529 if labelattrs
is not None:
1530 labelattrs
= helper
.ensurelist(labelattrs
)[:]
1531 if self
.labeldirection
is not None:
1532 labelattrs
.append(self
.labeldirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
1533 if tick
.labelattrs
is not None:
1534 labelattrs
.extend(helper
.ensurelist(tick
.labelattrs
))
1535 tick
.temp_labelbox
= axiscanvas
.texrunner
._text
(tick
.temp_x
, tick
.temp_y
, tick
.label
, *labelattrs
)
1536 if len(axis
.ticks
) > 1:
1538 for tick
in axis
.ticks
[1:]:
1539 if tick
.temp_dx
!= axis
.ticks
[0].temp_dx
or tick
.temp_dy
!= axis
.ticks
[0].temp_dy
:
1543 if equaldirection
and ((not axis
.ticks
[0].temp_dx
and self
.labelvequalize
) or
1544 (not axis
.ticks
[0].temp_dy
and self
.labelhequalize
)):
1545 if self
.labelattrs
is not None:
1546 box
.linealignequal([tick
.temp_labelbox
for tick
in axis
.ticks
if tick
.labellevel
is not None],
1547 labeldist
, axis
.ticks
[0].temp_dx
, axis
.ticks
[0].temp_dy
)
1549 for tick
in axis
.ticks
:
1550 if tick
.labellevel
is not None and self
.labelattrs
is not None:
1551 tick
.temp_labelbox
.linealign(labeldist
, tick
.temp_dx
, tick
.temp_dy
)
1554 if helper
.issequence(arg
):
1555 return [unit
.length(a
, default_type
="v") for a
in arg
]
1557 return unit
.length(arg
, default_type
="v")
1558 innerticklengths
= mkv(self
.innerticklengths_str
)
1559 outerticklengths
= mkv(self
.outerticklengths_str
)
1561 for tick
in axis
.ticks
:
1562 if tick
.ticklevel
is not None:
1563 innerticklength
= helper
.getitemno(innerticklengths
, tick
.ticklevel
)
1564 outerticklength
= helper
.getitemno(outerticklengths
, tick
.ticklevel
)
1565 if innerticklength
is not None or outerticklength
is not None:
1566 if innerticklength
is None:
1568 if outerticklength
is None:
1570 tickattrs
= helper
.getsequenceno(self
.tickattrs
, tick
.ticklevel
)
1571 if tickattrs
is not None:
1572 _innerticklength
= unit
.topt(innerticklength
)
1573 _outerticklength
= unit
.topt(outerticklength
)
1574 x1
= tick
.temp_x
- tick
.temp_dx
* _innerticklength
1575 y1
= tick
.temp_y
- tick
.temp_dy
* _innerticklength
1576 x2
= tick
.temp_x
+ tick
.temp_dx
* _outerticklength
1577 y2
= tick
.temp_y
+ tick
.temp_dy
* _outerticklength
1578 axiscanvas
.stroke(path
._line
(x1
, y1
, x2
, y2
), *helper
.ensuresequence(tickattrs
))
1579 if tick
!= frac((0, 1)) or self
.zerolineattrs
is None:
1580 gridattrs
= helper
.getsequenceno(self
.gridattrs
, tick
.ticklevel
)
1581 if gridattrs
is not None:
1582 axiscanvas
.stroke(axispos
.vgridline(tick
.temp_v
, axis
=axis
), *helper
.ensuresequence(gridattrs
))
1583 if outerticklength
is not None and unit
.topt(outerticklength
) > unit
.topt(axiscanvas
.extent
):
1584 axiscanvas
.extent
= outerticklength
1585 if outerticklength
is not None and unit
.topt(-innerticklength
) > unit
.topt(axiscanvas
.extent
):
1586 axiscanvas
.extent
= -innerticklength
1587 if tick
.labellevel
is not None and self
.labelattrs
is not None:
1588 axiscanvas
.insert(tick
.temp_labelbox
)
1589 axiscanvas
.labels
.append(tick
.temp_labelbox
)
1590 extent
= tick
.temp_labelbox
.extent(tick
.temp_dx
, tick
.temp_dy
) + labeldist
1591 if unit
.topt(extent
) > unit
.topt(axiscanvas
.extent
):
1592 axiscanvas
.extent
= extent
1593 if self
.baselineattrs
is not None:
1594 axiscanvas
.stroke(axispos
.vbaseline(axis
=axis
), *helper
.ensuresequence(self
.baselineattrs
))
1595 if self
.zerolineattrs
is not None:
1596 if len(axis
.ticks
) and axis
.ticks
[0] * axis
.ticks
[-1] < frac((0, 1)):
1597 axiscanvas
.stroke(axispos
.gridline(0, axis
=axis
), *helper
.ensuresequence(self
.zerolineattrs
))
1599 # for tick in axis.ticks:
1600 # del tick.temp_v # we've inserted those temporary variables ... and do not care any longer about them
1605 # if tick.labellevel is not None and self.labelattrs is not None:
1606 # del tick.temp_labelbox
1608 axistitlepainter
.paint(self
, axispos
, axis
, axiscanvas
)
1611 class linkaxispainter(axispainter
):
1612 """class for painting a linked axis
1613 - the inherited axispainter is used to paint the axis
1614 - modifies some constructor defaults"""
1616 __implements__
= _Iaxispainter
1618 def __init__(self
, zerolineattrs
=None,
1622 """initializes the instance
1623 - the zerolineattrs default is set to None thus skipping the zeroline
1624 - the labelattrs default is set to None thus skipping the labels
1625 - the titleattrs default is set to None thus skipping the title
1626 - all keyword arguments are passed to axispainter"""
1627 axispainter
.__init
__(self
, zerolineattrs
=zerolineattrs
,
1628 labelattrs
=labelattrs
,
1629 titleattrs
=titleattrs
,
1633 class splitaxispainter(axistitlepainter
):
1634 """class for painting a splitaxis
1635 - the inherited titleaxispainter is used to paint the title of
1637 - the splitaxispainter access the subaxes attribute of the axis"""
1639 __implements__
= _Iaxispainter
1641 def __init__(self
, breaklinesdist
="0.05 cm",
1642 breaklineslength
="0.5 cm",
1643 breaklinesangle
=-60,
1646 """initializes the instance
1647 - breaklinesdist is a visual length of the distance between
1648 the two lines of the axis break
1649 - breaklineslength is a visual length of the length of the
1650 two lines of the axis break
1651 - breaklinesangle is the angle of the lines of the axis break
1652 - breaklinesattrs are a list of stroke attributes for the
1653 axis break lines; a single entry is allowed without being a
1654 list; None turns off the break lines
1655 - futher keyword arguments are passed to axistitlepainter"""
1656 self
.breaklinesdist_str
= breaklinesdist
1657 self
.breaklineslength_str
= breaklineslength
1658 self
.breaklinesangle
= breaklinesangle
1659 self
.breaklinesattrs
= breaklinesattrs
1660 axistitlepainter
.__init
__(self
, **args
)
1662 def paint(self
, axispos
, axis
, axiscanvas
):
1663 for subaxis
in axis
.subaxes
:
1664 subaxis
.finish(axis
, axiscanvas
.texrunner
)
1665 axiscanvas
.insert(subaxis
.axiscanvas
)
1666 if unit
.topt(axiscanvas
.extent
) < unit
.topt(subaxis
.axiscanvas
.extent
):
1667 axiscanvas
.extent
= subaxis
.axiscanvas
.extent
1668 if self
.breaklinesattrs
is not None:
1669 self
.breaklinesdist
= unit
.length(self
.breaklinesdist_str
, default_type
="v")
1670 self
.breaklineslength
= unit
.length(self
.breaklineslength_str
, default_type
="v")
1671 self
.sin
= math
.sin(self
.breaklinesangle
*math
.pi
/180.0)
1672 self
.cos
= math
.cos(self
.breaklinesangle
*math
.pi
/180.0)
1673 breaklinesextent
= (0.5*self
.breaklinesdist
*math
.fabs(self
.cos
) +
1674 0.5*self
.breaklineslength
*math
.fabs(self
.sin
))
1675 if unit
.topt(axiscanvas
.extent
) < unit
.topt(breaklinesextent
):
1676 axiscanvas
.extent
= breaklinesextent
1677 for subaxis1
, subaxis2
in zip(axis
.subaxes
[:-1], axis
.subaxes
[1:]):
1678 # use a tangent of the baseline (this is independent of the tickdirection)
1679 v
= 0.5 * (subaxis1
.vmax
+ subaxis2
.vmin
)
1680 breakline
= path
.normpath(axispos
.vbaseline(v
, None, axis
=axis
)).tangent(0, self
.breaklineslength
)
1681 widthline
= path
.normpath(axispos
.vbaseline(v
, None, axis
=axis
)).tangent(0, self
.breaklinesdist
).transformed(trafomodule
.rotate(self
.breaklinesangle
+90, *breakline
.begin()))
1682 tocenter
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(breakline
.begin(), breakline
.end()))
1683 towidth
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(widthline
.begin(), widthline
.end()))
1684 breakline
= breakline
.transformed(trafomodule
.translate(*tocenter
).rotated(self
.breaklinesangle
, *breakline
.begin()))
1685 breakline1
= breakline
.transformed(trafomodule
.translate(*towidth
))
1686 breakline2
= breakline
.transformed(trafomodule
.translate(-towidth
[0], -towidth
[1]))
1687 axiscanvas
.fill(path
.path(path
.moveto(*breakline1
.begin()),
1688 path
.lineto(*breakline1
.end()),
1689 path
.lineto(*breakline2
.end()),
1690 path
.lineto(*breakline2
.begin()),
1691 path
.closepath()), color
.gray
.white
)
1692 axiscanvas
.stroke(breakline1
, *helper
.ensuresequence(self
.breaklinesattrs
))
1693 axiscanvas
.stroke(breakline2
, *helper
.ensuresequence(self
.breaklinesattrs
))
1694 axistitlepainter
.paint(self
, axispos
, axis
, axiscanvas
)
1697 class linksplitaxispainter(splitaxispainter
):
1698 """class for painting a linked splitaxis
1699 - the inherited splitaxispainter is used to paint the axis
1700 - modifies some constructor defaults"""
1702 __implements__
= _Iaxispainter
1704 def __init__(self
, titleattrs
=None, **kwargs
):
1705 """initializes the instance
1706 - the titleattrs default is set to None thus skipping the title
1707 - all keyword arguments are passed to splitaxispainter"""
1708 splitaxispainter
.__init
__(self
, titleattrs
=titleattrs
, **kwargs
)
1711 class baraxispainter(axistitlepainter
):
1712 """class for painting a baraxis
1713 - the inherited titleaxispainter is used to paint the title of
1715 - the baraxispainter access the multisubaxis, subaxis names, texts, and
1716 relsizes attributes"""
1718 __implements__
= _Iaxispainter
1720 def __init__(self
, innerticklength
=None,
1721 outerticklength
=None,
1723 baselineattrs
=canvas
.linecap
.square
,
1725 nameattrs
=(textmodule
.halign
.center
, textmodule
.vshift
.mathaxis
),
1731 """initializes the instance
1732 - innerticklength and outerticklength are a visual length of
1733 the ticks to be plotted at the axis baseline to visually
1734 separate the bars; if neither innerticklength nor
1735 outerticklength are set, not ticks are plotted
1736 - breaklinesattrs are a list of stroke attributes for the
1737 axis tick; a single entry is allowed without being a
1738 list; None turns off the ticks
1739 - namedist is a visual PyX length for the distance of the bar
1740 names from the axis baseline
1741 - nameattrs is a list of attributes for a texrunners text
1742 method; a single entry is allowed without being a list;
1743 None turns off the names
1744 - namedirection is an instance of rotatetext or None
1745 - namehequalize and namevequalize (booleans) perform an equal
1746 alignment for straight vertical and horizontal axes, respectively
1747 - futher keyword arguments are passed to axistitlepainter"""
1748 self
.innerticklength_str
= innerticklength
1749 self
.outerticklength_str
= outerticklength
1750 self
.tickattrs
= tickattrs
1751 self
.baselineattrs
= baselineattrs
1752 self
.namedist_str
= namedist
1753 self
.nameattrs
= nameattrs
1754 self
.namedirection
= namedirection
1755 self
.namepos
= namepos
1756 self
.namehequalize
= namehequalize
1757 self
.namevequalize
= namevequalize
1758 axistitlepainter
.__init
__(self
, **args
)
1760 def paint(self
, axispos
, axis
, axiscanvas
):
1761 if axis
.multisubaxis
is not None:
1762 for subaxis
in axis
.subaxis
:
1763 subaxis
.finish(axis
, axiscanvas
.texrunner
)
1764 axiscanvas
.insert(subaxis
.axiscanvas
)
1765 if unit
.topt(axiscanvas
.extent
) < unit
.topt(subaxis
.axiscanvas
.extent
):
1766 axiscanvas
.extent
= subaxis
.axiscanvas
.extent
1768 for name
in axis
.names
:
1769 v
= axis
.convert((name
, self
.namepos
))
1770 x
, y
= axispos
._vtickpoint
(v
, axis
=axis
)
1771 dx
, dy
= axispos
.vtickdirection(v
, axis
=axis
)
1772 namepos
.append((v
, x
, y
, dx
, dy
))
1774 if self
.nameattrs
is not None:
1775 for (v
, x
, y
, dx
, dy
), name
in zip(namepos
, axis
.names
):
1776 nameattrs
= helper
.ensurelist(self
.nameattrs
)[:]
1777 if self
.namedirection
is not None:
1778 nameattrs
.append(self
.namedirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
1779 if axis
.texts
.has_key(name
):
1780 nameboxes
.append(axiscanvas
.texrunner
._text
(x
, y
, str(axis
.texts
[name
]), *nameattrs
))
1781 elif axis
.texts
.has_key(str(name
)):
1782 nameboxes
.append(axiscanvas
.texrunner
._text
(x
, y
, str(axis
.texts
[str(name
)]), *nameattrs
))
1784 nameboxes
.append(axiscanvas
.texrunner
._text
(x
, y
, str(name
), *nameattrs
))
1785 labeldist
= axiscanvas
.extent
+ unit
.length(self
.namedist_str
, default_type
="v")
1786 if len(namepos
) > 1:
1788 for np
in namepos
[1:]:
1789 if np
[3] != namepos
[0][3] or np
[4] != namepos
[0][4]:
1793 if equaldirection
and ((not namepos
[0][3] and self
.namevequalize
) or
1794 (not namepos
[0][4] and self
.namehequalize
)):
1795 box
.linealignequal(nameboxes
, labeldist
, namepos
[0][3], namepos
[0][4])
1797 for namebox
, np
in zip(nameboxes
, namepos
):
1798 namebox
.linealign(labeldist
, np
[3], np
[4])
1799 if self
.innerticklength_str
is not None:
1800 innerticklength
= unit
.length(self
.innerticklength_str
, default_type
="v")
1801 _innerticklength
= unit
.topt(innerticklength
)
1802 if self
.tickattrs
is not None and unit
.topt(axiscanvas
.extent
) < -_innerticklength
:
1803 axiscanvas
.extent
= -innerticklength
1804 elif self
.outerticklength_str
is not None:
1805 innerticklength
= _innerticklength
= 0
1806 if self
.outerticklength_str
is not None:
1807 outerticklength
= unit
.length(self
.outerticklength_str
, default_type
="v")
1808 _outerticklength
= unit
.topt(outerticklength
)
1809 if self
.tickattrs
is not None and unit
.topt(axiscanvas
.extent
) < _outerticklength
:
1810 axiscanvas
.extent
= outerticklength
1811 elif self
.innerticklength_str
is not None:
1812 outerticklength
= _outerticklength
= 0
1813 for (v
, x
, y
, dx
, dy
), namebox
in zip(namepos
, nameboxes
):
1814 newextent
= namebox
.extent(dx
, dy
) + labeldist
1815 if unit
.topt(axiscanvas
.extent
) < unit
.topt(newextent
):
1816 axiscanvas
.extent
= newextent
1817 if self
.tickattrs
is not None and (self
.innerticklength_str
is not None or self
.outerticklength_str
is not None):
1818 for pos
in axis
.relsizes
:
1819 if pos
== axis
.relsizes
[0]:
1820 pos
-= axis
.firstdist
1821 elif pos
!= axis
.relsizes
[-1]:
1822 pos
-= 0.5 * axis
.dist
1823 v
= pos
/ axis
.relsizes
[-1]
1824 x
, y
= axispos
._vtickpoint
(v
, axis
=axis
)
1825 dx
, dy
= axispos
.vtickdirection(v
, axis
=axis
)
1826 x1
= x
- dx
* _innerticklength
1827 y1
= y
- dy
* _innerticklength
1828 x2
= x
+ dx
* _outerticklength
1829 y2
= y
+ dy
* _outerticklength
1830 axiscanvas
.stroke(path
._line
(x1
, y1
, x2
, y2
), *helper
.ensuresequence(self
.tickattrs
))
1831 if self
.baselineattrs
is not None:
1832 p
= axispos
.vbaseline(axis
=axis
)
1834 axiscanvas
.stroke(axispos
.vbaseline(axis
=axis
), *helper
.ensuresequence(self
.baselineattrs
))
1835 for namebox
in nameboxes
:
1836 axiscanvas
.insert(namebox
)
1837 axistitlepainter
.paint(self
, axispos
, axis
, axiscanvas
)
1840 class linkbaraxispainter(baraxispainter
):
1841 """class for painting a linked baraxis
1842 - the inherited baraxispainter is used to paint the axis
1843 - modifies some constructor defaults"""
1845 __implements__
= _Iaxispainter
1847 def __init__(self
, nameattrs
=None, titleattrs
=None, **kwargs
):
1848 """initializes the instance
1849 - the titleattrs default is set to None thus skipping the title
1850 - the nameattrs default is set to None thus skipping the names
1851 - all keyword arguments are passed to axispainter"""
1852 baraxispainter
.__init
__(self
, nameattrs
=nameattrs
, titleattrs
=titleattrs
, **kwargs
)
1855 ################################################################################
1857 ################################################################################
1861 """interface definition of a axis
1862 - data and tick range are handled separately
1863 - an axis should implement an convert and invert method like
1864 _Imap, but this is not part of this interface definition;
1865 one possibility is to mix-in a proper map class, but special
1866 purpose axes might do something else
1867 - instance variables:
1868 - axiscanvas: an axiscanvas instance, which is available after
1869 calling the finish method (the idea is, that an axis might
1870 fill different axiscanvas instances by calling the painter
1871 for different ticks and rate those ticks afterwards -- thus
1872 the axis finally can set this best canvas as its axis canvas)
1873 - relsize: relative size (width) of the axis (for use
1874 in splitaxis, baraxis etc.) -- this might not be available for
1875 all axes, which will just create an name error right now
1876 - configuration of the range handling is not subject
1877 of this interface definition"""
1878 # TODO: - add a mechanism to allow for a different range
1879 # handling of x and y ranges
1880 # - should we document instance variables this way?
1882 def convert(self
, x
):
1883 "convert a value into graph coordinates"
1885 def invert(self
, v
):
1886 "invert a graph coordinate to a axis value"
1888 def setdatarange(self
, min, max):
1889 """set the axis data range
1890 - the type of min and max must fit to the axis
1891 - min<max; the axis might be reversed, but this is
1892 expressed internally only
1893 - the axis might not apply this change of the range
1894 (e.g. when the axis range is fixed by the user)
1895 - for invalid parameters (e.g. negativ values at an
1896 logarithmic axis), an exception should be raised"""
1897 # TODO: be more specific about exceptions
1899 def settickrange(self
, min, max):
1900 """set the axis tick range
1901 - as before, but for the tick range (whatever this
1902 means for the axis)"""
1904 def getdatarange(self
):
1905 """return data range as a tuple (min, max)
1906 - min<max; the axis might be reversed, but this is
1907 expressed internally only"""
1908 # TODO: be more specific about exceptions
1910 def gettickrange(self
):
1911 """return tick range as a tuple (min, max)
1912 - as before, but for the tick range"""
1913 # TODO: be more specific about exceptions
1915 def finish(self
, axispos
, texrunner
):
1916 """finishes the axis
1917 - axispos implements _Iaxispos (usually the graph itself
1919 - the finish method creates an axiscanvas, which should be
1920 insertable into the graph to finally paint the axis
1921 - any modification of the axis range should be disabled after
1922 the finish method was called; a possible implementation
1923 would be to raise an error in these methods as soon as an
1924 axiscanvas is available"""
1925 # TODO: be more specific about exceptions
1927 def createlinkaxis(self
, **kwargs
):
1928 """create a link axis to the axis itself
1929 - typically, a link axis is a axis, which share almost
1930 all properties with the axis it is linked to
1931 - typically, the painter gets replaced by a painter
1932 which doesn't put any text to the axis"""
1936 """interface definition of axis tick position methods
1937 - these methods are used for the postitioning of the ticks
1938 when painting an axis
1939 - you may try to avoid calling of these methods from each
1940 other to gain speed"""
1942 def baseline(self
, x1
=None, x2
=None, axis
=None):
1943 """return the baseline as a path
1944 - x1 is the start position; if not set, the baseline starts
1945 from the beginning of the axis, which might imply a
1946 value outside of the graph coordinate range [0; 1]
1947 - x2 is analogous to x1, but for the end position"""
1949 def vbaseline(self
, v1
=None, v2
=None, axis
=None):
1950 """return the baseline as a path
1951 - like baseline, but for graph coordinates"""
1953 def gridline(self
, x
, axis
=None):
1954 "return the gridline as a path for a given position x"
1956 def vgridline(self
, v
, axis
=None):
1957 """return the gridline as a path for a given position v
1958 in graph coordinates"""
1960 def _tickpoint(self
, x
, axis
=None):
1961 """return the position at the baseline as a tuple (x, y) in
1962 postscript points for the position x"""
1964 def tickpoint(self
, x
, axis
=None):
1965 """return the position at the baseline as a tuple (x, y) in
1966 in PyX length for the position x"""
1968 def _vtickpoint(self
, v
, axis
=None):
1969 "like _tickpoint, but for graph coordinates"
1971 def vtickpoint(self
, v
, axis
=None):
1972 "like tickpoint, but for graph coordinates"
1974 def tickdirection(self
, x
, axis
=None):
1975 """return the direction of a tick as a tuple (dx, dy) for the
1978 def vtickdirection(self
, v
, axis
=None):
1979 """like tickposition, but for graph coordinates"""
1983 """base implementation a regular axis
1984 - typical usage is to mix-in a linmap or a logmap to
1985 complete the definition"""
1987 def __init__(self
, min=None, max=None, reverse
=0, divisor
=1,
1988 datavmin
=None, datavmax
=None, tickvmin
=0, tickvmax
=1,
1989 title
=None, painter
=axispainter(), texter
=defaulttexter(),
1990 density
=1, maxworse
=2):
1991 """initializes the instance
1992 - min and max fix the axis minimum and maximum, respectively;
1993 they are determined by the data to be plotted, when not fixed
1994 - reverse (boolean) reverses the minimum and the maximum of
1996 - numerical divisor for the axis partitioning
1997 - datavmin and datavmax are the minimal and maximal graph
1998 coordinate when adjusting the axis to the data range
1999 (likely to be changed in the future)
2000 - as before, but for the tick range
2001 - title is a string containing the axis title
2002 - axispainter is the axis painter (should implement _Ipainter)
2003 - texter is the texter (should implement _Itexter)
2004 - density is a global parameter for the axis paritioning and
2005 axis rating; its default is 1, but the range 0.5 to 2.5 should
2006 be usefull to get less or more ticks by the automatic axis
2008 - maxworse is a number of trials with worse tick rating
2009 before giving up (usually it should not be needed to increase
2010 this value; increasing the number will slow down the automatic
2011 axis partitioning considerably)
2012 - note that some methods of this class want to access a
2013 part and a rating attribute of the instance; those
2014 attributes should be initialized by the constructors
2015 of derived classes"""
2016 # TODO: improve datavmin/datavmax/tickvmin/tickvmax
2017 if None not in (min, max) and min > max:
2018 min, max, reverse
= max, min, not reverse
2019 self
.fixmin
, self
.fixmax
, self
.min, self
.max, self
.reverse
= min is not None, max is not None, min, max, reverse
2021 self
.datamin
= self
.datamax
= self
.tickmin
= self
.tickmax
= None
2022 if datavmin
is None:
2026 self
.datavmin
= 0.05
2028 self
.datavmin
= datavmin
2029 if datavmax
is None:
2033 self
.datavmax
= 0.95
2035 self
.datavmax
= datavmax
2036 self
.tickvmin
= tickvmin
2037 self
.tickvmax
= tickvmax
2039 self
.divisor
= divisor
2041 self
.painter
= painter
2042 self
.texter
= texter
2043 self
.density
= density
2044 self
.maxworse
= maxworse
2045 self
.axiscanvas
= None
2048 self
.__setinternalrange
()
2050 def __setinternalrange(self
, min=None, max=None):
2051 if not self
.fixmin
and min is not None and (self
.min is None or min < self
.min):
2053 if not self
.fixmax
and max is not None and (self
.max is None or max > self
.max):
2055 if None not in (self
.min, self
.max):
2056 min, max, vmin
, vmax
= self
.min, self
.max, 0, 1
2058 self
.setbasepoints(((min, vmin
), (max, vmax
)))
2060 if self
.datamin
is not None and self
.convert(self
.datamin
) < self
.datavmin
:
2061 min, vmin
= self
.datamin
, self
.datavmin
2062 self
.setbasepoints(((min, vmin
), (max, vmax
)))
2063 if self
.tickmin
is not None and self
.convert(self
.tickmin
) < self
.tickvmin
:
2064 min, vmin
= self
.tickmin
, self
.tickvmin
2065 self
.setbasepoints(((min, vmin
), (max, vmax
)))
2067 if self
.datamax
is not None and self
.convert(self
.datamax
) > self
.datavmax
:
2068 max, vmax
= self
.datamax
, self
.datavmax
2069 self
.setbasepoints(((min, vmin
), (max, vmax
)))
2070 if self
.tickmax
is not None and self
.convert(self
.tickmax
) > self
.tickvmax
:
2071 max, vmax
= self
.tickmax
, self
.tickvmax
2072 self
.setbasepoints(((min, vmin
), (max, vmax
)))
2074 self
.setbasepoints(((min, vmax
), (max, vmin
)))
2076 def __getinternalrange(self
):
2077 return self
.min, self
.max, self
.datamin
, self
.datamax
, self
.tickmin
, self
.tickmax
2079 def __forceinternalrange(self
, range):
2080 self
.min, self
.max, self
.datamin
, self
.datamax
, self
.tickmin
, self
.tickmax
= range
2081 self
.__setinternalrange
()
2083 def setdatarange(self
, min, max):
2084 if self
.axiscanvas
is not None:
2085 raise RuntimeError("axis was already finished")
2086 self
.datamin
, self
.datamax
= min, max
2087 self
.__setinternalrange
(min, max)
2089 def settickrange(self
, min, max):
2090 if self
.axiscanvas
is not None:
2091 raise RuntimeError("axis was already finished")
2092 self
.tickmin
, self
.tickmax
= min, max
2093 self
.__setinternalrange
(min, max)
2095 def getdatarange(self
):
2098 return self
.invert(1-self
.datavmin
), self
.invert(1-self
.datavmax
)
2100 return self
.invert(self
.datavmin
), self
.invert(self
.datavmax
)
2102 def gettickrange(self
):
2105 return self
.invert(1-self
.tickvmin
), self
.invert(1-self
.tickvmax
)
2107 return self
.invert(self
.tickvmin
), self
.invert(self
.tickvmax
)
2109 def checkfraclist(self
, fracs
):
2110 "orders a list of fracs, equal entries are not allowed"
2111 if not len(fracs
): return []
2112 sorted = list(fracs
)
2115 for item
in sorted[1:]:
2117 raise ValueError("duplicate entry found")
2121 def finish(self
, axispos
, texrunner
):
2126 min, max = self
.gettickrange()
2127 parter
= parterpos
= None
2128 if self
.part
is not None:
2129 self
.part
= helper
.ensurelist(self
.part
)
2130 for p
, i
in zip(self
.part
, xrange(sys
.maxint
)):
2131 if hasattr(p
, "defaultpart"):
2132 if parter
is not None:
2133 raise RuntimeError("only one partitioner allowed")
2137 self
.ticks
= self
.checkfraclist(self
.part
)
2139 self
.part
[:parterpos
] = self
.checkfraclist(self
.part
[:parterpos
])
2140 self
.part
[parterpos
+1:] = self
.checkfraclist(self
.part
[parterpos
+1:])
2141 if self
.divisor
is not None: # XXX workaround for timeaxis
2142 self
.ticks
= _mergeticklists(
2143 _mergeticklists(self
.part
[:parterpos
],
2144 parter
.defaultpart(min/self
.divisor
,
2148 self
.part
[parterpos
+1:])
2150 self
.ticks
= _mergeticklists(
2151 _mergeticklists(self
.part
[:parterpos
],
2152 parter
.defaultpart(min,
2156 self
.part
[parterpos
+1:])
2159 # lesspart and morepart can be called after defaultpart;
2160 # this works although some axes may share their autoparting,
2161 # because the axes are processed sequentially
2164 while worse
< self
.maxworse
:
2165 if parter
is not None:
2166 newticks
= parter
.lesspart()
2167 if parterpos
is not None and newticks
is not None:
2168 newticks
= _mergeticklists(_mergeticklists(self
.part
[:parterpos
], newticks
), self
.part
[parterpos
+1:])
2171 if newticks
is not None:
2173 bestrate
= self
.rater
.ratepart(self
, self
.ticks
, self
.density
)
2174 variants
= [[bestrate
, self
.ticks
]]
2176 newrate
= self
.rater
.ratepart(self
, newticks
, self
.density
)
2177 variants
.append([newrate
, newticks
])
2178 if newrate
< bestrate
:
2186 while worse
< self
.maxworse
:
2187 if parter
is not None:
2188 newticks
= parter
.morepart()
2189 if parterpos
is not None and newticks
is not None:
2190 newticks
= _mergeticklists(_mergeticklists(self
.part
[:parterpos
], newticks
), self
.part
[parterpos
+1:])
2193 if newticks
is not None:
2195 bestrate
= self
.rater
.ratepart(self
, self
.ticks
, self
.density
)
2196 variants
= [[bestrate
, self
.ticks
]]
2198 newrate
= self
.rater
.ratepart(self
, newticks
, self
.density
)
2199 variants
.append([newrate
, newticks
])
2200 if newrate
< bestrate
:
2210 if self
.painter
is not None:
2213 while i
< len(variants
) and (bestrate
is None or variants
[i
][0] < bestrate
):
2214 saverange
= self
.__getinternalrange
()
2215 self
.ticks
= variants
[i
][1]
2217 if self
.divisor
is not None: # XXX workaround for timeaxis
2218 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2220 self
.settickrange(self
.ticks
[0], self
.ticks
[-1])
2221 ac
= axiscanvas(texrunner
)
2222 self
.texter
.labels(self
.ticks
)
2223 self
.painter
.paint(axispos
, self
, ac
)
2224 ratelayout
= self
.rater
.ratelayout(ac
, self
.density
)
2225 if ratelayout
is not None:
2226 variants
[i
][0] += ratelayout
2227 variants
[i
].append(ac
)
2229 variants
[i
][0] = None
2230 if variants
[i
][0] is not None and (bestrate
is None or variants
[i
][0] < bestrate
):
2231 bestrate
= variants
[i
][0]
2232 self
.__forceinternalrange
(saverange
)
2234 if bestrate
is None:
2235 raise RuntimeError("no valid axis partitioning found")
2236 variants
= [variant
for variant
in variants
[:i
] if variant
[0] is not None]
2238 self
.ticks
= variants
[0][1]
2240 if self
.divisor
is not None: # XXX workaround for timeaxis
2241 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2243 self
.settickrange(self
.ticks
[0], self
.ticks
[-1])
2244 self
.axiscanvas
= variants
[0][2]
2247 if self
.divisor
is not None: # XXX workaround for timeaxis
2248 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2250 self
.settickrange(self
.ticks
[0], self
.ticks
[-1])
2251 self
.axiscanvas
= axiscanvas(texrunner
)
2254 if self
.divisor
is not None: # XXX workaround for timeaxis
2255 self
.settickrange(float(self
.ticks
[0])*self
.divisor
, float(self
.ticks
[-1])*self
.divisor
)
2257 self
.settickrange(self
.ticks
[0], self
.ticks
[-1])
2258 self
.axiscanvas
= axiscanvas(texrunner
)
2259 self
.texter
.labels(self
.ticks
)
2260 self
.painter
.paint(axispos
, self
, self
.axiscanvas
)
2262 def createlinkaxis(self
, **args
):
2263 return linkaxis(self
, **args
)
2266 class linaxis(_axis
, _linmap
):
2267 """implementation of a linear axis"""
2269 __implements__
= _Iaxis
2271 def __init__(self
, part
=autolinpart(), rater
=axisrater(), **args
):
2272 """initializes the instance
2273 - the part attribute contains a list of one partitioner
2274 (a partitioner implements _Ipart) or/and some (manually
2275 set) ticks (implementing _Itick); a single entry might
2276 be passed without wrapping it into a list; the partitioner
2277 and the tick instances must fit to the type of the axis
2278 (e.g. they should be valid parameters to the axis convert
2279 method); the ticks and the partitioner results are mixed
2281 - the rater implements _Irater and is used to rate different
2282 tick lists created by the partitioner (after merging with
2284 - futher keyword arguments are passed to _axis"""
2285 _axis
.__init
__(self
, **args
)
2286 if self
.fixmin
and self
.fixmax
:
2287 self
.relsize
= self
.max - self
.min
2292 class logaxis(_axis
, _logmap
):
2293 """implementation of a logarithmic axis"""
2295 __implements__
= _Iaxis
2297 def __init__(self
, part
=autologpart(), rater
=axisrater(ticks
=axisrater
.logticks
, labels
=axisrater
.loglabels
), **args
):
2298 """initializes the instance
2299 - the part attribute contains a list of one partitioner
2300 (a partitioner implements _Ipart) or/and some (manually
2301 set) ticks (implementing _Itick); a single entry might
2302 be passed without wrapping it into a list; the partitioner
2303 and the tick instances must fit to the type of the axis
2304 (e.g. they should be valid parameters to the axis convert
2305 method); the ticks and the partitioner results are mixed
2307 - the rater implements _Irater and is used to rate different
2308 tick lists created by the partitioner (after merging with
2310 - futher keyword arguments are passed to _axis"""
2311 _axis
.__init
__(self
, **args
)
2312 if self
.fixmin
and self
.fixmax
:
2313 self
.relsize
= math
.log(self
.max) - math
.log(self
.min)
2319 """a axis linked to an already existing regular axis
2320 - almost all properties of the axis are "copied" from the
2321 axis this axis is linked to
2322 - usually, linked axis are used to create an axis to an
2323 existing axis with different painting properties; linked
2324 axis can be used to plot an axis twice at the opposite
2325 sides of a graphxy or even to share an axis between
2326 different graphs!"""
2328 __implements__
= _Iaxis
2330 def __init__(self
, linkedaxis
, painter
=linkaxispainter()):
2331 """initializes the instance
2332 - it gets a axis this linkaxis is linked to
2333 - it gets a painter to be used for this linked axis"""
2334 self
.linkedaxis
= linkedaxis
2335 self
.painter
= painter
2338 def __getattr__(self
, attr
):
2339 """access to unkown attributes are handed over to the
2340 axis this linkaxis is linked to"""
2341 return getattr(self
.linkedaxis
, attr
)
2343 def finish(self
, axispos
, texrunner
):
2344 """finishes the axis
2345 - instead of performing the hole finish process
2346 (paritioning, rating, etc.) just a painter call
2351 self
.linkedaxis
.finish(axispos
, texrunner
)
2352 self
.axiscanvas
= axiscanvas(texrunner
)
2353 self
.painter
.paint(axispos
, self
, self
.axiscanvas
)
2357 """implementation of the _Iaxispos interface for subaxis
2358 - provides the _Iaxispos interface for axis which contain
2360 - typical usage by mix-in this class into an "main" axis and
2361 calling the painter(s) of the subaxes from the specialized
2362 painter of the "main" axis; the subaxes need to have the
2363 attributes "baseaxis" (reference to the "main" axis) and
2364 "baseaxispos" (reference to the instance implementing the
2365 _Iaxispos interface of the "main" axis)"""
2367 __implements__
= _Iaxispos
2369 def baseline(self
, x1
=None, x2
=None, axis
=None):
2371 v1
= axis
.convert(x1
)
2375 v2
= axis
.convert(x2
)
2378 return axis
.baseaxispos
.vbaseline(v1
, v2
, axis
=axis
.baseaxis
)
2380 def vbaseline(self
, v1
=None, v2
=None, axis
=None):
2384 left
= axis
.vmin
+v1
*(axis
.vmax
-axis
.vmin
)
2388 right
= axis
.vmin
+v2
*(axis
.vmax
-axis
.vmin
)
2389 return axis
.baseaxispos
.vbaseline(left
, right
, axis
=axis
.baseaxis
)
2391 def gridline(self
, x
, axis
=None):
2392 return axis
.baseaxispos
.vgridline(axis
.vmin
+axis
.convert(x
)*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2394 def vgridline(self
, v
, axis
=None):
2395 return axis
.baseaxispos
.vgridline(axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2397 def _tickpoint(self
, x
, axis
=None):
2398 return axis
.baseaxispos
._vtickpoint
(axis
.vmin
+axis
.convert(x
)*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2400 def tickpoint(self
, x
, axis
=None):
2401 return axis
.baseaxispos
.vtickpoint(axis
.vmin
+axis
.convert(x
)*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2403 def _vtickpoint(self
, v
, axis
=None):
2404 return axis
.baseaxispos
._vtickpoint
(axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2406 def vtickpoint(self
, v
, axis
=None):
2407 return axis
.baseaxispos
.vtickpoint(axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2409 def tickdirection(self
, x
, axis
=None):
2410 return axis
.baseaxispos
.vtickdirection(axis
.vmin
+axis
.convert(x
)*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2412 def vtickdirection(self
, v
, axis
=None):
2413 return axis
.baseaxispos
.vtickdirection(axis
.vmin
+v
*(axis
.vmax
-axis
.vmin
), axis
=axis
.baseaxis
)
2416 class splitaxis(_subaxispos
):
2417 """implementation of a split axis
2418 - a split axis contains several (sub-)axes with
2419 non-overlapping data ranges -- between these subaxes
2420 the axis is "splitted"
2421 - (just to get sure: a splitaxis can contain other
2422 splitaxes as its subaxes)
2423 - a splitaxis implements the _Iaxispos for its subaxes
2424 by inheritance from _subaxispos"""
2426 __implements__
= _Iaxis
, _Iaxispos
2428 def __init__(self
, subaxes
, splitlist
=0.5, splitdist
=0.1, relsizesplitdist
=1,
2429 title
=None, painter
=splitaxispainter()):
2430 """initializes the instance
2431 - subaxes is a list of subaxes
2432 - splitlist is a list of graph coordinates, where the splitting
2433 of the main axis should be performed; a single entry (splitting
2434 two axes) doesn't need to be wrapped into a list; if the list
2435 isn't long enough for the subaxes, missing entries are considered
2437 - splitdist is the size of the splitting in graph coordinates, when
2438 the associated splitlist entry is not None
2439 - relsizesplitdist: a None entry in splitlist means, that the
2440 position of the splitting should be calculated out of the
2441 relsize values of conrtibuting subaxes (the size of the
2442 splitting is relsizesplitdist in values of the relsize values
2444 - title is the title of the axis as a string
2445 - painter is the painter of the axis; it should be specialized to
2447 - the relsize of the splitaxis is the sum of the relsizes of the
2448 subaxes including the relsizesplitdist"""
2449 self
.subaxes
= subaxes
2450 self
.painter
= painter
2452 self
.splitlist
= helper
.ensurelist(splitlist
)
2453 for subaxis
in self
.subaxes
:
2456 self
.subaxes
[0].vmin
= 0
2457 self
.subaxes
[0].vminover
= None
2458 self
.subaxes
[-1].vmax
= 1
2459 self
.subaxes
[-1].vmaxover
= None
2460 for i
in xrange(len(self
.splitlist
)):
2461 if self
.splitlist
[i
] is not None:
2462 self
.subaxes
[i
].vmax
= self
.splitlist
[i
] - 0.5*splitdist
2463 self
.subaxes
[i
].vmaxover
= self
.splitlist
[i
]
2464 self
.subaxes
[i
+1].vmin
= self
.splitlist
[i
] + 0.5*splitdist
2465 self
.subaxes
[i
+1].vminover
= self
.splitlist
[i
]
2467 while i
< len(self
.subaxes
):
2468 if self
.subaxes
[i
].vmax
is None:
2469 j
= relsize
= relsize2
= 0
2470 while self
.subaxes
[i
+ j
].vmax
is None:
2471 relsize
+= self
.subaxes
[i
+ j
].relsize
+ relsizesplitdist
2473 relsize
+= self
.subaxes
[i
+ j
].relsize
2474 vleft
= self
.subaxes
[i
].vmin
2475 vright
= self
.subaxes
[i
+ j
].vmax
2476 for k
in range(i
, i
+ j
):
2477 relsize2
+= self
.subaxes
[k
].relsize
2478 self
.subaxes
[k
].vmax
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2479 relsize2
+= 0.5 * relsizesplitdist
2480 self
.subaxes
[k
].vmaxover
= self
.subaxes
[k
+ 1].vminover
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2481 relsize2
+= 0.5 * relsizesplitdist
2482 self
.subaxes
[k
+1].vmin
= vleft
+ (vright
- vleft
) * relsize2
/ float(relsize
)
2483 if i
== 0 and i
+ j
+ 1 == len(self
.subaxes
):
2484 self
.relsize
= relsize
2489 self
.fixmin
= self
.subaxes
[0].fixmin
2491 self
.min = self
.subaxes
[0].min
2492 self
.fixmax
= self
.subaxes
[-1].fixmax
2494 self
.max = self
.subaxes
[-1].max
2497 def getdatarange(self
):
2498 min = self
.subaxes
[0].getdatarange()
2499 max = self
.subaxes
[-1].getdatarange()
2501 return min[0], max[1]
2505 def setdatarange(self
, min, max):
2506 self
.subaxes
[0].setdatarange(min, None)
2507 self
.subaxes
[-1].setdatarange(None, max)
2509 def gettickrange(self
):
2510 min = self
.subaxes
[0].gettickrange()
2511 max = self
.subaxes
[-1].gettickrange()
2513 return min[0], max[1]
2517 def settickrange(self
, min, max):
2518 self
.subaxes
[0].settickrange(min, None)
2519 self
.subaxes
[-1].settickrange(None, max)
2521 def convert(self
, value
):
2522 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
2523 if value
< self
.subaxes
[0].max:
2524 return self
.subaxes
[0].vmin
+ self
.subaxes
[0].convert(value
)*(self
.subaxes
[0].vmax
-self
.subaxes
[0].vmin
)
2525 for axis
in self
.subaxes
[1:-1]:
2526 if value
> axis
.min and value
< axis
.max:
2527 return axis
.vmin
+ axis
.convert(value
)*(axis
.vmax
-axis
.vmin
)
2528 if value
> self
.subaxes
[-1].min:
2529 return self
.subaxes
[-1].vmin
+ self
.subaxes
[-1].convert(value
)*(self
.subaxes
[-1].vmax
-self
.subaxes
[-1].vmin
)
2530 raise ValueError("value couldn't be assigned to a split region")
2532 def vbaseline(self
, v1
=None, v2
=None, axis
=None):
2534 if axis
.baseaxis
.painter
.breaklinesattrs
is None: # XXX undocumented access to painter!?
2537 if axis
.vminover
is None:
2540 left
= axis
.vminover
2542 left
= axis
.vmin
+v1
*(axis
.vmax
-axis
.vmin
)
2544 if axis
.baseaxis
.painter
.breaklinesattrs
is None:
2547 if axis
.vmaxover
is None:
2550 right
= axis
.vmaxover
2552 right
= axis
.vmin
+v2
*(axis
.vmax
-axis
.vmin
)
2553 return axis
.baseaxispos
.vbaseline(left
, right
, axis
=axis
.baseaxis
)
2555 def finish(self
, axispos
, texrunner
):
2559 for subaxis
in self
.subaxes
:
2560 subaxis
.baseaxispos
= axispos
2561 subaxis
.baseaxis
= self
2562 self
.axiscanvas
= axiscanvas(texrunner
)
2563 self
.painter
.paint(axispos
, self
, self
.axiscanvas
)
2565 def createlinkaxis(self
, **args
):
2566 return linksplitaxis(self
, **args
)
2569 class linksplitaxis(linkaxis
):
2570 """a splitaxis linked to an already existing splitaxis
2571 - inherits the access to a linked axis -- as before,
2572 basically only the painter is replaced
2573 - it must take care of the creation of linked axes of
2576 __implements__
= _Iaxis
2578 def __init__(self
, linkedaxis
, painter
=linksplitaxispainter(), subaxispainter
=None):
2579 """initializes the instance
2580 - it gets a axis this linkaxis is linked to
2581 - it gets a painter to be used for this linked axis
2582 - it gets a list of painters to be used for the linkaxes
2583 of the subaxes; if None, the createlinkaxis of the subaxes
2584 are called without a painter parameter; if it is not a
2585 list, the subaxispainter is passed as the painter
2586 parameter to all createlinkaxis of the subaxes"""
2587 if subaxispainter
is not None:
2588 if helper
.issequence(subaxispainter
):
2589 if len(linkedaxis
.subaxes
) != len(subaxispainter
):
2590 raise RuntimeError("subaxes and subaxispainter lengths do not fit")
2591 self
.subaxes
= [a
.createlinkaxis(painter
=p
) for a
, p
in zip(linkedaxis
.subaxes
, subaxispainter
)]
2593 self
.subaxes
= [a
.createlinkaxis(painter
=subaxispainter
) for a
in linkedaxis
.subaxes
]
2595 self
.subaxes
= [a
.createlinkaxis() for a
in linkedaxis
.subaxes
]
2596 linkaxis
.__init
__(self
, linkedaxis
, painter
=painter
)
2598 def finish(self
, axispos
, texrunner
):
2599 for subaxis
in self
.subaxes
:
2600 subaxis
.baseaxispos
= axispos
2601 subaxis
.baseaxis
= self
2602 linkaxis
.finish(self
, axispos
, texrunner
)
2605 class baraxis(_subaxispos
):
2606 """implementation of a axis for bar graphs
2607 - a bar axes is different from a splitaxis by the way it
2608 selects its subaxes: the convert method gets a list,
2609 where the first entry is a name selecting a subaxis out
2610 of a list; instead of the term "bar" or "subaxis" the term
2611 "item" will be used here
2612 - the baraxis stores a list of names be identify the items;
2613 the names might be of any time (strings, integers, etc.);
2614 the names can be printed as the titles for the items, but
2615 alternatively the names might be transformed by the texts
2616 dictionary, which maps a name to a text to be used to label
2617 the items in the painter
2618 - usually, there is only one subaxis, which is used as
2619 the subaxis for all items
2620 - alternatively it is also possible to use another baraxis
2621 as a multisubaxis; it is copied via the createsubaxis
2622 method whenever another subaxis is needed (by that a
2623 nested bar axis with a different number of subbars at
2624 each item can be created)
2625 - any axis can be a subaxis of a baraxis; if no subaxis
2626 is specified at all, the baraxis simulates a linear
2627 subaxis with a fixed range of 0 to 1
2628 - a splitaxis implements the _Iaxispos for its subaxes
2629 by inheritance from _subaxispos when the multisubaxis
2630 feature is turned on"""
2632 def __init__(self
, subaxis
=None, multisubaxis
=None, title
=None,
2633 dist
=0.5, firstdist
=None, lastdist
=None, names
=None,
2634 texts
={}, painter
=baraxispainter()):
2635 """initialize the instance
2636 - subaxis contains a axis to be used as the subaxis
2638 - multisubaxis might contain another baraxis instance
2639 to be used to construct a new subaxis for each item;
2640 (by that a nested bar axis with a different number
2641 of subbars at each item can be created)
2642 - only one of subaxis or multisubaxis can be set; if neither
2643 of them is set, the baraxis behaves like having a linaxis
2644 as its subaxis with a fixed range 0 to 1
2645 - the title attribute contains the axis title as a string
2646 - the dist is a relsize to be used as the distance between
2648 - the firstdist and lastdist are the distance before the
2649 first and after the last item, respectively; when set
2650 to None (the default), 0.5*dist is used
2651 - names is a predefined list of names to identify the
2652 items; if set, the name list is fixed
2653 - texts is a dictionary transforming a name to a text in
2654 the painter; if a name isn't found in the dictionary
2656 - the relsize of the baraxis is the sum of the
2657 relsizes including all distances between the items"""
2659 if firstdist
is not None:
2660 self
.firstdist
= firstdist
2662 self
.firstdist
= 0.5 * dist
2663 if lastdist
is not None:
2664 self
.lastdist
= lastdist
2666 self
.lastdist
= 0.5 * dist
2667 self
.relsizes
= None
2670 for name
in helper
.ensuresequence(names
):
2672 self
.fixnames
= names
is not None
2673 self
.multisubaxis
= multisubaxis
2674 if self
.multisubaxis
is not None:
2675 if subaxis
is not None:
2676 raise RuntimeError("either use subaxis or multisubaxis")
2677 self
.subaxis
= [self
.createsubaxis() for name
in self
.names
]
2679 self
.subaxis
= subaxis
2683 self
.painter
= painter
2685 def createsubaxis(self
):
2686 return baraxis(subaxis
=self
.multisubaxis
.subaxis
,
2687 multisubaxis
=self
.multisubaxis
.multisubaxis
,
2688 title
=self
.multisubaxis
.title
,
2689 dist
=self
.multisubaxis
.dist
,
2690 firstdist
=self
.multisubaxis
.firstdist
,
2691 lastdist
=self
.multisubaxis
.lastdist
,
2692 names
=self
.multisubaxis
.names
,
2693 texts
=self
.multisubaxis
.texts
,
2694 painter
=self
.multisubaxis
.painter
)
2696 def getdatarange(self
):
2697 # TODO: we do not yet have a proper range handling for a baraxis
2700 def gettickrange(self
):
2701 # TODO: we do not yet have a proper range handling for a baraxis
2702 raise RuntimeError("range handling for a baraxis is not implemented")
2704 def setdatarange(self
):
2705 # TODO: we do not yet have a proper range handling for a baraxis
2706 raise RuntimeError("range handling for a baraxis is not implemented")
2708 def settickrange(self
):
2709 # TODO: we do not yet have a proper range handling for a baraxis
2710 raise RuntimeError("range handling for a baraxis is not implemented")
2712 def setname(self
, name
, *subnames
):
2713 """add a name to identify an item at the baraxis
2714 - by using subnames, nested name definitions are
2716 - a style (or the user itself) might use this to
2717 insert new items into a baraxis
2718 - setting self.relsizes to None forces later recalculation"""
2719 if not self
.fixnames
:
2720 if name
not in self
.names
:
2721 self
.relsizes
= None
2722 self
.names
.append(name
)
2723 if self
.multisubaxis
is not None:
2724 self
.subaxis
.append(self
.createsubaxis())
2725 if (not self
.fixnames
or name
in self
.names
) and len(subnames
):
2726 if self
.multisubaxis
is not None:
2727 if self
.subaxis
[self
.names
.index(name
)].setname(*subnames
):
2728 self
.relsizes
= None
2730 if self
.subaxis
.setname(*subnames
):
2731 self
.relsizes
= None
2732 return self
.relsizes
is not None
2734 def updaterelsizes(self
):
2735 # guess what it does: it recalculates relsize attribute
2736 self
.relsizes
= [i
*self
.dist
+ self
.firstdist
for i
in range(len(self
.names
) + 1)]
2737 self
.relsizes
[-1] += self
.lastdist
- self
.dist
2738 if self
.multisubaxis
is not None:
2740 for i
in range(1, len(self
.relsizes
)):
2741 self
.subaxis
[i
-1].updaterelsizes()
2742 subrelsize
+= self
.subaxis
[i
-1].relsizes
[-1]
2743 self
.relsizes
[i
] += subrelsize
2745 if self
.subaxis
is None:
2748 self
.subaxis
.updaterelsizes()
2749 subrelsize
= self
.subaxis
.relsizes
[-1]
2750 for i
in range(1, len(self
.relsizes
)):
2751 self
.relsizes
[i
] += i
* subrelsize
2753 def convert(self
, value
):
2754 """baraxis convert method
2755 - the value should be a list, where the first entry is
2756 a member of the names (set in the constructor or by the
2757 setname method); this first entry identifies an item in
2759 - following values are passed to the appropriate subaxis
2761 - when there is no subaxis, the convert method will behave
2762 like having a linaxis from 0 to 1 as subaxis"""
2763 # TODO: proper raising exceptions (which exceptions go thru, which are handled before?)
2764 if not self
.relsizes
:
2765 self
.updaterelsizes()
2766 pos
= self
.names
.index(value
[0])
2768 if self
.subaxis
is None:
2771 if self
.multisubaxis
is not None:
2772 subvalue
= value
[1] * self
.subaxis
[pos
].relsizes
[-1]
2774 subvalue
= value
[1] * self
.subaxis
.relsizes
[-1]
2776 if self
.multisubaxis
is not None:
2777 subvalue
= self
.subaxis
[pos
].convert(value
[1:]) * self
.subaxis
[pos
].relsizes
[-1]
2779 subvalue
= self
.subaxis
.convert(value
[1:]) * self
.subaxis
.relsizes
[-1]
2780 return (self
.relsizes
[pos
] + subvalue
) / float(self
.relsizes
[-1])
2782 def baseline(self
, x1
=None, x2
=None, axis
=None):
2783 "None is returned -> subaxis should not paint any baselines"
2784 # TODO: quick hack ?!
2787 def vbaseline(self
, v1
=None, v2
=None, axis
=None):
2788 "None is returned -> subaxis should not paint any baselines"
2789 # TODO: quick hack ?!
2792 def finish(self
, axispos
, texrunner
):
2793 if self
.multisubaxis
is not None:
2794 for name
, subaxis
in zip(self
.names
, self
.subaxis
):
2795 subaxis
.vmin
= self
.convert((name
, 0))
2796 subaxis
.vmax
= self
.convert((name
, 1))
2797 subaxis
.baseaxispos
= axispos
2798 subaxis
.baseaxis
= self
2799 self
.axiscanvas
= axiscanvas(texrunner
)
2800 self
.painter
.paint(axispos
, self
, self
.axiscanvas
)
2802 def createlinkaxis(self
, **args
):
2803 return linkbaraxis(self
, **args
)
2806 class linkbaraxis(linkaxis
):
2807 """a baraxis linked to an already existing baraxis
2808 - inherits the access to a linked axis -- as before,
2809 basically only the painter is replaced
2810 - it must take care of the creation of linked axes of
2813 __implements__
= _Iaxis
2815 def __init__(self
, linkedaxis
, painter
=linkbaraxispainter()):
2816 """initializes the instance
2817 - it gets a axis this linkaxis is linked to
2818 - it gets a painter to be used for this linked axis"""
2819 linkaxis
.__init
__(self
, linkedaxis
, painter
=painter
)
2821 def finish(self
, axispos
, texrunner
):
2822 if self
.multisubaxis
is not None:
2823 self
.subaxis
= [subaxis
.createlinkaxis() for subaxis
in self
.linkedaxis
.subaxis
]
2824 for subaxis
in self
.subaxis
:
2825 subaxis
.baseaxispos
= axispos
2826 subaxis
.baseaxis
= self
2827 elif self
.linkedaxis
.subaxis
is not None:
2828 self
.subaxis
= self
.linkedaxis
.subaxis
.createlinkaxis()
2829 self
.subaxis
.baseaxispos
= axispos
2830 self
.subaxis
.baseaxis
= self
2831 linkaxis
.finish(self
, axispos
, texrunner
)
2834 ################################################################################
2836 ################################################################################
2839 # g = graph.graphxy(key=graph.key())
2840 # g.addkey(graph.key(), ...)
2845 def __init__(self
, dist
="0.2 cm", pos
= "tr", hinside
= 1, vinside
= 1, hdist
="0.6 cm", vdist
="0.4 cm",
2846 symbolwidth
="0.5 cm", symbolheight
="0.25 cm", symbolspace
="0.2 cm",
2847 textattrs
=textmodule
.vshift
.mathaxis
):
2848 self
.dist_str
= dist
2850 self
.hinside
= hinside
2851 self
.vinside
= vinside
2852 self
.hdist_str
= hdist
2853 self
.vdist_str
= vdist
2854 self
.symbolwidth_str
= symbolwidth
2855 self
.symbolheight_str
= symbolheight
2856 self
.symbolspace_str
= symbolspace
2857 self
.textattrs
= textattrs
2858 self
.plotinfos
= None
2859 if self
.pos
in ("tr", "rt"):
2862 elif self
.pos
in ("br", "rb"):
2865 elif self
.pos
in ("tl", "lt"):
2868 elif self
.pos
in ("bl", "lb"):
2872 raise RuntimeError("invalid pos attribute")
2874 def setplotinfos(self
, *plotinfos
):
2875 """set the plotinfos to be used in the key
2876 - call it exactly once"""
2877 if self
.plotinfos
is not None:
2878 raise RuntimeError("setplotinfo is called multiple times")
2879 self
.plotinfos
= plotinfos
2881 def dolayout(self
, graph
):
2882 "creates the layout of the key"
2883 self
._dist
= unit
.topt(unit
.length(self
.dist_str
, default_type
="v"))
2884 self
._hdist
= unit
.topt(unit
.length(self
.hdist_str
, default_type
="v"))
2885 self
._vdist
= unit
.topt(unit
.length(self
.vdist_str
, default_type
="v"))
2886 self
._symbolwidth
= unit
.topt(unit
.length(self
.symbolwidth_str
, default_type
="v"))
2887 self
._symbolheight
= unit
.topt(unit
.length(self
.symbolheight_str
, default_type
="v"))
2888 self
._symbolspace
= unit
.topt(unit
.length(self
.symbolspace_str
, default_type
="v"))
2890 for plotinfo
in self
.plotinfos
:
2891 self
.titles
.append(graph
.texrunner
._text
(0, 0, plotinfo
.data
.title
, *helper
.ensuresequence(self
.textattrs
)))
2892 box
._tile
(self
.titles
, self
._dist
, 0, -1)
2893 box
._linealignequal
(self
.titles
, self
._symbolwidth
+ self
._symbolspace
, 1, 0)
2896 """return a bbox for the key
2897 method should be called after dolayout"""
2898 result
= self
.titles
[0].bbox()
2899 for title
in self
.titles
[1:]:
2900 result
= result
+ title
.bbox() + bbox
._bbox
(0, title
.center
[1] - 0.5 * self
._symbolheight
,
2901 0, title
.center
[1] + 0.5 * self
._symbolheight
)
2904 def paint(self
, c
, x
, y
):
2905 """paint the graph key into a canvas c at the position x and y (in postscript points)
2906 - method should be called after dolayout
2907 - the x, y alignment might be calculated by the graph using:
2908 - the bbox of the key as returned by the keys bbox method
2909 - the attributes _hdist, _vdist, hinside, and vinside of the key
2910 - the dimension and geometry of the graph"""
2911 sc
= c
.insert(canvas
.canvas(trafomodule
._translate
(x
, y
)))
2912 for plotinfo
, title
in zip(self
.plotinfos
, self
.titles
):
2913 plotinfo
.style
.key(sc
, 0, -0.5 * self
._symbolheight
+ title
.center
[1],
2914 self
._symbolwidth
, self
._symbolheight
)
2918 ################################################################################
2920 ################################################################################
2925 def __init__(self
, data
, style
):
2931 class graphxy(canvas
.canvas
):
2937 def __init__(self
, type, axispos
, tickdirection
):
2939 - type == 0: x-axis; type == 1: y-axis
2940 - _axispos is the y or x position of the x-axis or y-axis
2941 in postscript points, respectively
2942 - axispos is analogous to _axispos, but as a PyX length
2943 - dx and dy is the tick direction
2946 self
.axispos
= axispos
2947 self
._axispos
= unit
.topt(axispos
)
2948 self
.tickdirection
= tickdirection
2950 def clipcanvas(self
):
2951 return self
.insert(canvas
.canvas(canvas
.clip(path
._rect
(self
._xpos
, self
._ypos
, self
._width
, self
._height
))))
2953 def plot(self
, data
, style
=None):
2955 raise RuntimeError("layout setup was already performed")
2957 if helper
.issequence(data
):
2958 raise RuntimeError("list plot needs an explicit style")
2959 if self
.defaultstyle
.has_key(data
.defaultstyle
):
2960 style
= self
.defaultstyle
[data
.defaultstyle
].iterate()
2962 style
= data
.defaultstyle()
2963 self
.defaultstyle
[data
.defaultstyle
] = style
2966 for d
in helper
.ensuresequence(data
):
2968 style
= style
.iterate()
2971 d
.setstyle(self
, style
)
2972 plotinfos
.append(plotinfo(d
, style
))
2973 self
.plotinfos
.extend(plotinfos
)
2974 if helper
.issequence(data
):
2978 def addkey(self
, key
, *plotinfos
):
2980 raise RuntimeError("layout setup was already performed")
2981 self
.addkeys
.append((key
, plotinfos
))
2983 def _pos(self
, x
, y
, xaxis
=None, yaxis
=None):
2985 xaxis
= self
.axes
["x"]
2987 yaxis
= self
.axes
["y"]
2988 return self
._xpos
+xaxis
.convert(x
)*self
._width
, self
._ypos
+yaxis
.convert(y
)*self
._height
2990 def pos(self
, x
, y
, xaxis
=None, yaxis
=None):
2992 xaxis
= self
.axes
["x"]
2994 yaxis
= self
.axes
["y"]
2995 return self
.xpos
+xaxis
.convert(x
)*self
.width
, self
.ypos
+yaxis
.convert(y
)*self
.height
2997 def _vpos(self
, vx
, vy
, xaxis
=None, yaxis
=None):
2999 xaxis
= self
.axes
["x"]
3001 yaxis
= self
.axes
["y"]
3002 return self
._xpos
+vx
*self
._width
, self
._ypos
+vy
*self
._height
3004 def vpos(self
, vx
, vy
, xaxis
=None, yaxis
=None):
3006 xaxis
= self
.axes
["x"]
3008 yaxis
= self
.axes
["y"]
3009 return self
.xpos
+vx
*self
.width
, self
.ypos
+vy
*self
.height
3011 def baseline(self
, x1
=None, x2
=None, axis
=None):
3013 v1
= axis
.convert(x1
)
3017 v2
= axis
.convert(x2
)
3020 if axis
.axisposdata
.type:
3021 return path
._line
(axis
.axisposdata
._axispos
, self
._ypos
+v1
*self
._height
,
3022 axis
.axisposdata
._axispos
, self
._ypos
+v2
*self
._height
)
3024 return path
._line
(self
._xpos
+v1
*self
._width
, axis
.axisposdata
._axispos
,
3025 self
._xpos
+v2
*self
._width
, axis
.axisposdata
._axispos
)
3027 def xbaseline(self
, x1
=None, x2
=None, axis
=None):
3029 axis
= self
.axes
["x"]
3030 if axis
.axisposdata
.type != 0:
3031 raise RuntimeError("wrong axisposdata.type")
3033 v1
= axis
.convert(x1
)
3037 v2
= axis
.convert(x2
)
3040 return path
._line
(self
._xpos
+v1
*self
._width
, axis
.axisposdata
._axispos
,
3041 self
._xpos
+v2
*self
._width
, axis
.axisposdata
._axispos
)
3043 def ybaseline(self
, x1
=None, x2
=None, axis
=None):
3045 axis
= self
.axes
["y"]
3046 if axis
.axisposdata
.type != 1:
3047 raise RuntimeError("wrong axisposdata.type")
3049 v1
= axis
.convert(x1
)
3053 v2
= axis
.convert(x2
)
3056 return path
._line
(axis
.axisposdata
._axispos
, self
._ypos
+v1
*self
._height
,
3057 axis
.axisposdata
._axispos
, self
._ypos
+v2
*self
._height
)
3059 def vbaseline(self
, v1
=None, v2
=None, axis
=None):
3064 if axis
.axisposdata
.type:
3065 return path
._line
(axis
.axisposdata
._axispos
, self
._ypos
+v1
*self
._height
,
3066 axis
.axisposdata
._axispos
, self
._ypos
+v2
*self
._height
)
3068 return path
._line
(self
._xpos
+v1
*self
._width
, axis
.axisposdata
._axispos
,
3069 self
._xpos
+v2
*self
._width
, axis
.axisposdata
._axispos
)
3071 def vxbaseline(self
, v1
=None, v2
=None, axis
=None):
3073 axis
= self
.axes
["x"]
3074 if axis
.axisposdata
.type != 0:
3075 raise RuntimeError("wrong axisposdata.type")
3080 return path
._line
(self
._xpos
+v1
*self
._width
, axis
.axisposdata
._axispos
,
3081 self
._xpos
+v2
*self
._width
, axis
.axisposdata
._axispos
)
3083 def vybaseline(self
, v1
=None, v2
=None, axis
=None):
3085 axis
= self
.axes
["y"]
3086 if axis
.axisposdata
.type != 1:
3087 raise RuntimeError("wrong axisposdata.type")
3092 return path
._line
(axis
.axisposdata
._axispos
, self
._ypos
+v1
*self
._height
,
3093 axis
.axisposdata
._axispos
, self
._ypos
+v2
*self
._height
)
3095 def gridline(self
, x
, axis
=None):
3097 if axis
.axisposdata
.type:
3098 return path
._line
(self
._xpos
, self
._ypos
+v
*self
._height
,
3099 self
._xpos
+self
._width
, self
._ypos
+v
*self
._height
)
3101 return path
._line
(self
._xpos
+v
*self
._width
, self
._ypos
,
3102 self
._xpos
+v
*self
._width
, self
._ypos
+self
._height
)
3104 def xgridline(self
, x
, axis
=None):
3106 axis
= self
.axes
["x"]
3108 if axis
.axisposdata
.type != 0:
3109 raise RuntimeError("wrong axisposdata.type")
3110 return path
._line
(self
._xpos
+v
*self
._width
, self
._ypos
,
3111 self
._xpos
+v
*self
._width
, self
._ypos
+self
._height
)
3113 def ygridline(self
, x
, axis
=None):
3115 axis
= self
.axes
["y"]
3117 if axis
.axisposdata
.type != 1:
3118 raise RuntimeError("wrong axisposdata.type")
3119 return path
._line
(self
._xpos
, self
._ypos
+v
*self
._height
,
3120 self
._xpos
+self
._width
, self
._ypos
+v
*self
._height
)
3122 def vgridline(self
, v
, axis
=None):
3123 if axis
.axisposdata
.type:
3124 return path
._line
(self
._xpos
, self
._ypos
+v
*self
._height
,
3125 self
._xpos
+self
._width
, self
._ypos
+v
*self
._height
)
3127 return path
._line
(self
._xpos
+v
*self
._width
, self
._ypos
,
3128 self
._xpos
+v
*self
._width
, self
._ypos
+self
._height
)
3130 def vxgridline(self
, v
, axis
=None):
3132 axis
= self
.axes
["x"]
3133 if axis
.axisposdata
.type != 0:
3134 raise RuntimeError("wrong axisposdata.type")
3135 return path
._line
(self
._xpos
+v
*self
._width
, self
._ypos
,
3136 self
._xpos
+v
*self
._width
, self
._ypos
+self
._height
)
3138 def vygridline(self
, v
, axis
=None):
3140 axis
= self
.axes
["y"]
3141 if axis
.axisposdata
.type != 1:
3142 raise RuntimeError("wrong axisposdata.type")
3143 return path
._line
(self
._xpos
, self
._ypos
+v
*self
._height
,
3144 self
._xpos
+self
._width
, self
._ypos
+v
*self
._height
)
3146 def _tickpoint(self
, x
, axis
=None):
3147 if axis
.axisposdata
.type:
3148 return axis
.axisposdata
._axispos
, self
._ypos
+axis
.convert(x
)*self
._height
3150 return self
._xpos
+axis
.convert(x
)*self
._width
, axis
.axisposdata
._axispos
3152 def _xtickpoint(self
, x
, axis
=None):
3154 axis
= self
.axes
["x"]
3155 if axis
.axisposdata
.type != 0:
3156 raise RuntimeError("wrong axisposdata.type")
3157 return self
._xpos
+axis
.convert(x
)*self
._width
, axis
.axisposdata
._axispos
3159 def _ytickpoint(self
, x
, axis
=None):
3161 axis
= self
.axes
["y"]
3162 if axis
.axisposdata
.type != 1:
3163 raise RuntimeError("wrong axisposdata.type")
3164 return axis
.axisposdata
._axispos
, self
._ypos
+axis
.convert(x
)*self
._height
3166 def tickpoint(self
, x
, axis
=None):
3167 if axis
.axisposdata
.type:
3168 return axis
.axisposdata
.axispos
, self
.ypos
+axis
.convert(x
)*self
.height
3170 return self
.xpos
+axis
.convert(x
)*self
.width
, axis
.axisposdata
.axispos
3172 def xtickpoint(self
, x
, axis
=None):
3174 axis
= self
.axes
["x"]
3175 if axis
.axisposdata
.type != 0:
3176 raise RuntimeError("wrong axisposdata.type")
3177 return self
.xpos
+axis
.convert(x
)*self
.width
, axis
.axisposdata
.axispos
3179 def ytickpoint(self
, x
, axis
=None):
3181 axis
= self
.axes
["y"]
3182 if axis
.axisposdata
.type != 1:
3183 raise RuntimeError("wrong axisposdata.type")
3184 return axis
.axisposdata
.axispos
, self
.ypos
+axis
.convert(x
)*self
.height
3186 def _vtickpoint(self
, v
, axis
=None):
3187 if axis
.axisposdata
.type:
3188 return axis
.axisposdata
._axispos
, self
._ypos
+v
*self
._height
3190 return self
._xpos
+v
*self
._width
, axis
.axisposdata
._axispos
3192 def _vxtickpoint(self
, v
, axis
=None):
3194 axis
= self
.axes
["x"]
3195 if axis
.axisposdata
.type != 0:
3196 raise RuntimeError("wrong axisposdata.type")
3197 return self
._xpos
+v
*self
._width
, axis
.axisposdata
._axispos
3199 def _vytickpoint(self
, v
, axis
=None):
3201 axis
= self
.axes
["y"]
3202 if axis
.axisposdata
.type != 1:
3203 raise RuntimeError("wrong axisposdata.type")
3204 return axis
.axisposdata
._axispos
, self
._ypos
+v
*self
._height
3206 def vtickpoint(self
, v
, axis
=None):
3207 if axis
.axisposdata
.type:
3208 return axis
.axisposdata
.axispos
, self
.ypos
+v
*self
.height
3210 return self
.xpos
+v
*self
.width
, axis
.axisposdata
.axispos
3212 def vxtickpoint(self
, v
, axis
=None):
3214 axis
= self
.axes
["x"]
3215 if axis
.axisposdata
.type != 0:
3216 raise RuntimeError("wrong axisposdata.type")
3217 return self
.xpos
+v
*self
.width
, axis
.axisposdata
.axispos
3219 def vytickpoint(self
, v
, axis
=None):
3221 axis
= self
.axes
["y"]
3222 if axis
.axisposdata
.type != 1:
3223 raise RuntimeError("wrong axisposdata.type")
3224 return axis
.axisposdata
.axispos
, self
.ypos
+v
*self
.height
3226 def tickdirection(self
, x
, axis
=None):
3227 return axis
.axisposdata
.tickdirection
3229 def xtickdirection(self
, x
, axis
=None):
3231 axis
= self
.axes
["x"]
3232 if axis
.axisposdata
.type != 0:
3233 raise RuntimeError("wrong axisposdata.type")
3234 return axis
.axisposdata
.tickdirection
3236 def ytickdirection(self
, x
, axis
=None):
3238 axis
= self
.axes
["y"]
3239 if axis
.axisposdata
.type != 1:
3240 raise RuntimeError("wrong axisposdata.type")
3241 return axis
.axisposdata
.tickdirection
3243 def vtickdirection(self
, v
, axis
=None):
3244 return axis
.axisposdata
.tickdirection
3246 def vxtickdirection(self
, v
, axis
=None):
3248 axis
= self
.axes
["x"]
3249 if axis
.axisposdata
.type != 0:
3250 raise RuntimeError("wrong axisposdata.type")
3251 return axis
.axisposdata
.tickdirection
3253 def vytickdirection(self
, v
, axis
=None):
3255 axis
= self
.axes
["y"]
3256 if axis
.axisposdata
.type != 1:
3257 raise RuntimeError("wrong axisposdata.type")
3258 return axis
.axisposdata
.tickdirection
3260 def _addpos(self
, x
, y
, dx
, dy
):
3263 def _connect(self
, x1
, y1
, x2
, y2
):
3264 return path
._lineto
(x2
, y2
)
3266 def keynum(self
, key
):
3268 while key
[0] in string
.letters
:
3274 def gatherranges(self
):
3276 for plotinfo
in self
.plotinfos
:
3277 pdranges
= plotinfo
.data
.getranges()
3278 if pdranges
is not None:
3279 for key
in pdranges
.keys():
3280 if key
not in ranges
.keys():
3281 ranges
[key
] = pdranges
[key
]
3283 ranges
[key
] = (min(ranges
[key
][0], pdranges
[key
][0]),
3284 max(ranges
[key
][1], pdranges
[key
][1]))
3285 # known ranges are also set as ranges for the axes
3286 for key
, axis
in self
.axes
.items():
3287 if key
in ranges
.keys():
3288 axis
.setdatarange(*ranges
[key
])
3289 ranges
[key
] = axis
.getdatarange()
3290 if ranges
[key
] is None:
3294 def removedomethod(self
, method
):
3298 self
.domethods
.remove(method
)
3304 if not self
.removedomethod(self
.dolayout
): return
3306 # create list of ranges
3308 ranges
= self
.gatherranges()
3309 # 2. calculate additional ranges out of known ranges
3310 for plotinfo
in self
.plotinfos
:
3311 plotinfo
.data
.setranges(ranges
)
3312 # 3. gather ranges again
3314 # do the layout for all axes
3315 axesdist
= unit
.length(self
.axesdist_str
, default_type
="v")
3316 XPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[0])
3317 YPattern
= re
.compile(r
"%s([2-9]|[1-9][0-9]+)?$" % self
.Names
[1])
3318 xaxisextents
= [0, 0]
3319 yaxisextents
= [0, 0]
3320 needxaxisdist
= [0, 0]
3321 needyaxisdist
= [0, 0]
3322 items
= list(self
.axes
.items())
3323 items
.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
3324 for key
, axis
in items
:
3325 num
= self
.keynum(key
)
3326 num2
= 1 - num
% 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
3327 num3
= 1 - 2 * (num
% 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
3328 if XPattern
.match(key
):
3329 if needxaxisdist
[num2
]:
3330 xaxisextents
[num2
] += axesdist
3331 axis
.axisposdata
= self
.axisposdata(0, self
.ypos
+ num2
*self
.height
+ num3
*xaxisextents
[num2
], (0, num3
))
3332 elif YPattern
.match(key
):
3333 if needyaxisdist
[num2
]:
3334 yaxisextents
[num2
] += axesdist
3335 axis
.axisposdata
= self
.axisposdata(1, self
.xpos
+ num2
*self
.width
+ num3
*yaxisextents
[num2
], (num3
, 0))
3337 raise ValueError("Axis key '%s' not allowed" % key
)
3338 axis
.finish(self
, self
.texrunner
)
3339 if XPattern
.match(key
):
3340 xaxisextents
[num2
] += axis
.axiscanvas
.extent
3341 needxaxisdist
[num2
] = 1
3342 if YPattern
.match(key
):
3343 yaxisextents
[num2
] += axis
.axiscanvas
.extent
3344 needyaxisdist
[num2
] = 1
3346 def dobackground(self
):
3348 if not self
.removedomethod(self
.dobackground
): return
3349 if self
.backgroundattrs
is not None:
3350 self
.draw(path
._rect
(self
._xpos
, self
._ypos
, self
._width
, self
._height
),
3351 *helper
.ensuresequence(self
.backgroundattrs
))
3355 if not self
.removedomethod(self
.doaxes
): return
3356 for axis
in self
.axes
.values():
3357 self
.insert(axis
.axiscanvas
)
3361 if not self
.removedomethod(self
.dodata
): return
3362 for plotinfo
in self
.plotinfos
:
3363 plotinfo
.data
.draw(self
)
3365 def _dokey(self
, key
, *plotinfos
):
3366 key
.setplotinfos(*plotinfos
)
3371 x
= self
._xpos
+ self
._width
- bbox
.urx
- key
._hdist
3373 x
= self
._xpos
+ self
._width
- bbox
.llx
+ key
._hdist
3376 x
= self
._xpos
- bbox
.llx
+ key
._hdist
3378 x
= self
._xpos
- bbox
.urx
- key
._hdist
3381 y
= self
._ypos
+ self
._height
- bbox
.ury
- key
._vdist
3383 y
= self
._ypos
+ self
._height
- bbox
.lly
+ key
._vdist
3386 y
= self
._ypos
- bbox
.lly
+ key
._vdist
3388 y
= self
._ypos
- bbox
.ury
- key
._vdist
3389 key
.paint(self
, x
, y
)
3393 if not self
.removedomethod(self
.dokey
): return
3394 if self
.key
is not None:
3395 self
._dokey
(self
.key
, *self
.plotinfos
)
3396 for key
, plotinfos
in self
.addkeys
:
3397 self
._dokey
(key
, *plotinfos
)
3400 while len(self
.domethods
):
3403 def initwidthheight(self
, width
, height
, ratio
):
3404 if (width
is not None) and (height
is None):
3405 self
.width
= unit
.length(width
)
3406 self
.height
= (1.0/ratio
) * self
.width
3407 elif (height
is not None) and (width
is None):
3408 self
.height
= unit
.length(height
)
3409 self
.width
= ratio
* self
.height
3411 self
.width
= unit
.length(width
)
3412 self
.height
= unit
.length(height
)
3413 self
._width
= unit
.topt(self
.width
)
3414 self
._height
= unit
.topt(self
.height
)
3415 if self
._width
<= 0: raise ValueError("width <= 0")
3416 if self
._height
<= 0: raise ValueError("height <= 0")
3418 def initaxes(self
, axes
, addlinkaxes
=0):
3419 for key
in self
.Names
:
3420 if not axes
.has_key(key
):
3421 axes
[key
] = linaxis()
3422 elif axes
[key
] is None:
3425 if not axes
.has_key(key
+ "2") and axes
.has_key(key
):
3426 axes
[key
+ "2"] = axes
[key
].createlinkaxis()
3427 elif axes
[key
+ "2"] is None:
3431 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, ratio
=goldenmean
,
3432 key
=None, backgroundattrs
=None, axesdist
="0.8 cm", **axes
):
3433 canvas
.canvas
.__init
__(self
)
3434 self
.xpos
= unit
.length(xpos
)
3435 self
.ypos
= unit
.length(ypos
)
3436 self
._xpos
= unit
.topt(self
.xpos
)
3437 self
._ypos
= unit
.topt(self
.ypos
)
3438 self
.initwidthheight(width
, height
, ratio
)
3439 self
.initaxes(axes
, 1)
3441 self
.backgroundattrs
= backgroundattrs
3442 self
.axesdist_str
= axesdist
3444 self
.domethods
= [self
.dolayout
, self
.dobackground
, self
.doaxes
, self
.dodata
, self
.dokey
]
3446 self
.defaultstyle
= {}
3451 return canvas
.canvas
.bbox(self
)
3453 def write(self
, file):
3455 canvas
.canvas
.write(self
, file)
3459 # some thoughts, but deferred right now
3461 # class graphxyz(graphxy):
3463 # Names = "x", "y", "z"
3465 # def _vxtickpoint(self, axis, v):
3466 # return self._vpos(v, axis.vypos, axis.vzpos)
3468 # def _vytickpoint(self, axis, v):
3469 # return self._vpos(axis.vxpos, v, axis.vzpos)
3471 # def _vztickpoint(self, axis, v):
3472 # return self._vpos(axis.vxpos, axis.vypos, v)
3474 # def vxtickdirection(self, axis, v):
3475 # x1, y1 = self._vpos(v, axis.vypos, axis.vzpos)
3476 # x2, y2 = self._vpos(v, 0.5, 0)
3477 # dx, dy = x1 - x2, y1 - y2
3478 # norm = math.sqrt(dx*dx + dy*dy)
3479 # return dx/norm, dy/norm
3481 # def vytickdirection(self, axis, v):
3482 # x1, y1 = self._vpos(axis.vxpos, v, axis.vzpos)
3483 # x2, y2 = self._vpos(0.5, v, 0)
3484 # dx, dy = x1 - x2, y1 - y2
3485 # norm = math.sqrt(dx*dx + dy*dy)
3486 # return dx/norm, dy/norm
3488 # def vztickdirection(self, axis, v):
3490 # x1, y1 = self._vpos(axis.vxpos, axis.vypos, v)
3491 # x2, y2 = self._vpos(0.5, 0.5, v)
3492 # dx, dy = x1 - x2, y1 - y2
3493 # norm = math.sqrt(dx*dx + dy*dy)
3494 # return dx/norm, dy/norm
3496 # def _pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
3497 # if xaxis is None: xaxis = self.axes["x"]
3498 # if yaxis is None: yaxis = self.axes["y"]
3499 # if zaxis is None: zaxis = self.axes["z"]
3500 # return self._vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
3502 # def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
3503 # if xaxis is None: xaxis = self.axes["x"]
3504 # if yaxis is None: yaxis = self.axes["y"]
3505 # if zaxis is None: zaxis = self.axes["z"]
3506 # return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
3508 # def _vpos(self, vx, vy, vz):
3509 # x, y, z = (vx - 0.5)*self._depth, (vy - 0.5)*self._width, (vz - 0.5)*self._height
3510 # d0 = float(self.a[0]*self.b[1]*(z-self.eye[2])
3511 # + self.a[2]*self.b[0]*(y-self.eye[1])
3512 # + self.a[1]*self.b[2]*(x-self.eye[0])
3513 # - self.a[2]*self.b[1]*(x-self.eye[0])
3514 # - self.a[0]*self.b[2]*(y-self.eye[1])
3515 # - self.a[1]*self.b[0]*(z-self.eye[2]))
3516 # da = (self.eye[0]*self.b[1]*(z-self.eye[2])
3517 # + self.eye[2]*self.b[0]*(y-self.eye[1])
3518 # + self.eye[1]*self.b[2]*(x-self.eye[0])
3519 # - self.eye[2]*self.b[1]*(x-self.eye[0])
3520 # - self.eye[0]*self.b[2]*(y-self.eye[1])
3521 # - self.eye[1]*self.b[0]*(z-self.eye[2]))
3522 # db = (self.a[0]*self.eye[1]*(z-self.eye[2])
3523 # + self.a[2]*self.eye[0]*(y-self.eye[1])
3524 # + self.a[1]*self.eye[2]*(x-self.eye[0])
3525 # - self.a[2]*self.eye[1]*(x-self.eye[0])
3526 # - self.a[0]*self.eye[2]*(y-self.eye[1])
3527 # - self.a[1]*self.eye[0]*(z-self.eye[2]))
3528 # return da/d0 + self._xpos, db/d0 + self._ypos
3530 # def vpos(self, vx, vy, vz):
3531 # tx, ty = self._vpos(vx, vy, vz)
3532 # return unit.t_pt(tx), unit.t_pt(ty)
3534 # def xbaseline(self, axis, x1, x2, xaxis=None):
3535 # if xaxis is None: xaxis = self.axes["x"]
3536 # return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2))
3538 # def ybaseline(self, axis, y1, y2, yaxis=None):
3539 # if yaxis is None: yaxis = self.axes["y"]
3540 # return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2))
3542 # def zbaseline(self, axis, z1, z2, zaxis=None):
3543 # if zaxis is None: zaxis = self.axes["z"]
3544 # return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2))
3546 # def vxbaseline(self, axis, v1, v2):
3547 # return (path._line(*(self._vpos(v1, 0, 0) + self._vpos(v2, 0, 0))) +
3548 # path._line(*(self._vpos(v1, 0, 1) + self._vpos(v2, 0, 1))) +
3549 # path._line(*(self._vpos(v1, 1, 1) + self._vpos(v2, 1, 1))) +
3550 # path._line(*(self._vpos(v1, 1, 0) + self._vpos(v2, 1, 0))))
3552 # def vybaseline(self, axis, v1, v2):
3553 # return (path._line(*(self._vpos(0, v1, 0) + self._vpos(0, v2, 0))) +
3554 # path._line(*(self._vpos(0, v1, 1) + self._vpos(0, v2, 1))) +
3555 # path._line(*(self._vpos(1, v1, 1) + self._vpos(1, v2, 1))) +
3556 # path._line(*(self._vpos(1, v1, 0) + self._vpos(1, v2, 0))))
3558 # def vzbaseline(self, axis, v1, v2):
3559 # return (path._line(*(self._vpos(0, 0, v1) + self._vpos(0, 0, v2))) +
3560 # path._line(*(self._vpos(0, 1, v1) + self._vpos(0, 1, v2))) +
3561 # path._line(*(self._vpos(1, 1, v1) + self._vpos(1, 1, v2))) +
3562 # path._line(*(self._vpos(1, 0, v1) + self._vpos(1, 0, v2))))
3564 # def xgridpath(self, x, xaxis=None):
3566 # if xaxis is None: xaxis = self.axes["x"]
3567 # v = xaxis.convert(x)
3568 # return path._line(self._xpos+v*self._width, self._ypos,
3569 # self._xpos+v*self._width, self._ypos+self._height)
3571 # def ygridpath(self, y, yaxis=None):
3573 # if yaxis is None: yaxis = self.axes["y"]
3574 # v = yaxis.convert(y)
3575 # return path._line(self._xpos, self._ypos+v*self._height,
3576 # self._xpos+self._width, self._ypos+v*self._height)
3578 # def zgridpath(self, z, zaxis=None):
3580 # if zaxis is None: zaxis = self.axes["z"]
3581 # v = zaxis.convert(z)
3582 # return path._line(self._xpos, self._zpos+v*self._height,
3583 # self._xpos+self._width, self._zpos+v*self._height)
3585 # def vxgridpath(self, v):
3586 # return path.path(path._moveto(*self._vpos(v, 0, 0)),
3587 # path._lineto(*self._vpos(v, 0, 1)),
3588 # path._lineto(*self._vpos(v, 1, 1)),
3589 # path._lineto(*self._vpos(v, 1, 0)),
3592 # def vygridpath(self, v):
3593 # return path.path(path._moveto(*self._vpos(0, v, 0)),
3594 # path._lineto(*self._vpos(0, v, 1)),
3595 # path._lineto(*self._vpos(1, v, 1)),
3596 # path._lineto(*self._vpos(1, v, 0)),
3599 # def vzgridpath(self, v):
3600 # return path.path(path._moveto(*self._vpos(0, 0, v)),
3601 # path._lineto(*self._vpos(0, 1, v)),
3602 # path._lineto(*self._vpos(1, 1, v)),
3603 # path._lineto(*self._vpos(1, 0, v)),
3606 # def _addpos(self, x, y, dx, dy):
3610 # def _connect(self, x1, y1, x2, y2):
3612 # return path._lineto(x2, y2)
3616 # if not self.removedomethod(self.doaxes): return
3617 # axesdist = unit.topt(unit.length(self.axesdist_str, default_type="v"))
3618 # XPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[0])
3619 # YPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[1])
3620 # ZPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.Names[2])
3621 # items = list(self.axes.items())
3622 # items.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
3623 # for key, axis in items:
3624 # num = self.keynum(key)
3625 # num2 = 1 - num % 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
3626 # num3 = 1 - 2 * (num % 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
3627 # if XPattern.match(key):
3630 # axis._vtickpoint = self._vxtickpoint
3631 # axis.vgridpath = self.vxgridpath
3632 # axis.vbaseline = self.vxbaseline
3633 # axis.vtickdirection = self.vxtickdirection
3634 # elif YPattern.match(key):
3637 # axis._vtickpoint = self._vytickpoint
3638 # axis.vgridpath = self.vygridpath
3639 # axis.vbaseline = self.vybaseline
3640 # axis.vtickdirection = self.vytickdirection
3641 # elif ZPattern.match(key):
3644 # axis._vtickpoint = self._vztickpoint
3645 # axis.vgridpath = self.vzgridpath
3646 # axis.vbaseline = self.vzbaseline
3647 # axis.vtickdirection = self.vztickdirection
3649 # raise ValueError("Axis key '%s' not allowed" % key)
3650 # if axis.painter is not None:
3651 # axis.dopaint(self)
3652 # # if XPattern.match(key):
3653 # # self._xaxisextents[num2] += axis._extent
3654 # # needxaxisdist[num2] = 1
3655 # # if YPattern.match(key):
3656 # # self._yaxisextents[num2] += axis._extent
3657 # # needyaxisdist[num2] = 1
3659 # def __init__(self, tex, xpos=0, ypos=0, width=None, height=None, depth=None,
3660 # phi=30, theta=30, distance=1,
3661 # backgroundattrs=None, axesdist="0.8 cm", **axes):
3662 # canvas.canvas.__init__(self)
3666 # self._xpos = unit.topt(xpos)
3667 # self._ypos = unit.topt(ypos)
3668 # self._width = unit.topt(width)
3669 # self._height = unit.topt(height)
3670 # self._depth = unit.topt(depth)
3671 # self.width = width
3672 # self.height = height
3673 # self.depth = depth
3674 # if self._width <= 0: raise ValueError("width < 0")
3675 # if self._height <= 0: raise ValueError("height < 0")
3676 # if self._depth <= 0: raise ValueError("height < 0")
3677 # self._distance = distance*math.sqrt(self._width*self._width+
3678 # self._height*self._height+
3679 # self._depth*self._depth)
3680 # phi *= -math.pi/180
3681 # theta *= math.pi/180
3682 # self.a = (-math.sin(phi), math.cos(phi), 0)
3683 # self.b = (-math.cos(phi)*math.sin(theta),
3684 # -math.sin(phi)*math.sin(theta),
3686 # self.eye = (self._distance*math.cos(phi)*math.cos(theta),
3687 # self._distance*math.sin(phi)*math.cos(theta),
3688 # self._distance*math.sin(theta))
3689 # self.initaxes(axes)
3690 # self.axesdist_str = axesdist
3691 # self.backgroundattrs = backgroundattrs
3694 # self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
3695 # self.haslayout = 0
3696 # self.defaultstyle = {}
3700 # return bbox._bbox(self._xpos - 200, self._ypos - 200, self._xpos + 200, self._ypos + 200)
3703 ################################################################################
3705 ################################################################################
3708 #class _Ichangeattr:
3709 # """attribute changer
3710 # is an iterator for attributes where an attribute
3711 # is not refered by just a number (like for a sequence),
3712 # but also by the number of attributes requested
3713 # by calls of the next method (like for an color palette)
3714 # (you should ensure to call all needed next before the attr)
3716 # the attribute itself is implemented by overloading the _attr method"""
3719 # "get an attribute"
3722 # "get an attribute changer for the next attribute"
3725 class _changeattr
: pass
3728 class changeattr(_changeattr
):
3737 newindex
= self
.counter
3739 return refattr(self
, newindex
)
3742 class refattr(_changeattr
):
3744 def __init__(self
, ref
, index
):
3749 return self
.ref
.attr(self
.index
)
3752 return self
.ref
.iterate()
3755 # helper routines for a using attrs
3758 "get attr out of a attr/changeattr"
3759 if isinstance(attr
, _changeattr
):
3760 return attr
.getattr()
3764 def _getattrs(attrs
):
3765 "get attrs out of a list of attr/changeattr"
3766 if attrs
is not None:
3768 for attr
in helper
.ensuresequence(attrs
):
3769 if isinstance(attr
, _changeattr
):
3770 attr
= attr
.getattr()
3771 if attr
is not None:
3773 if len(result
) or not len(attrs
):
3777 def _iterateattr(attr
):
3778 "perform next to a attr/changeattr"
3779 if isinstance(attr
, _changeattr
):
3780 return attr
.iterate()
3784 def _iterateattrs(attrs
):
3785 "perform next to a list of attr/changeattr"
3786 if attrs
is not None:
3788 for attr
in helper
.ensuresequence(attrs
):
3789 if isinstance(attr
, _changeattr
):
3790 result
.append(attr
.iterate())
3796 class changecolor(changeattr
):
3798 def __init__(self
, palette
):
3799 changeattr
.__init
__(self
)
3800 self
.palette
= palette
3802 def attr(self
, index
):
3803 if self
.counter
!= 1:
3804 return self
.palette
.getcolor(index
/float(self
.counter
-1))
3806 return self
.palette
.getcolor(0)
3809 class _changecolorgray(changecolor
):
3811 def __init__(self
, palette
=color
.palette
.Gray
):
3812 changecolor
.__init
__(self
, palette
)
3814 _changecolorgrey
= _changecolorgray
3817 class _changecolorreversegray(changecolor
):
3819 def __init__(self
, palette
=color
.palette
.ReverseGray
):
3820 changecolor
.__init
__(self
, palette
)
3822 _changecolorreversegrey
= _changecolorreversegray
3825 class _changecolorredblack(changecolor
):
3827 def __init__(self
, palette
=color
.palette
.RedBlack
):
3828 changecolor
.__init
__(self
, palette
)
3831 class _changecolorblackred(changecolor
):
3833 def __init__(self
, palette
=color
.palette
.BlackRed
):
3834 changecolor
.__init
__(self
, palette
)
3837 class _changecolorredwhite(changecolor
):
3839 def __init__(self
, palette
=color
.palette
.RedWhite
):
3840 changecolor
.__init
__(self
, palette
)
3843 class _changecolorwhitered(changecolor
):
3845 def __init__(self
, palette
=color
.palette
.WhiteRed
):
3846 changecolor
.__init
__(self
, palette
)
3849 class _changecolorgreenblack(changecolor
):
3851 def __init__(self
, palette
=color
.palette
.GreenBlack
):
3852 changecolor
.__init
__(self
, palette
)
3855 class _changecolorblackgreen(changecolor
):
3857 def __init__(self
, palette
=color
.palette
.BlackGreen
):
3858 changecolor
.__init
__(self
, palette
)
3861 class _changecolorgreenwhite(changecolor
):
3863 def __init__(self
, palette
=color
.palette
.GreenWhite
):
3864 changecolor
.__init
__(self
, palette
)
3867 class _changecolorwhitegreen(changecolor
):
3869 def __init__(self
, palette
=color
.palette
.WhiteGreen
):
3870 changecolor
.__init
__(self
, palette
)
3873 class _changecolorblueblack(changecolor
):
3875 def __init__(self
, palette
=color
.palette
.BlueBlack
):
3876 changecolor
.__init
__(self
, palette
)
3879 class _changecolorblackblue(changecolor
):
3881 def __init__(self
, palette
=color
.palette
.BlackBlue
):
3882 changecolor
.__init
__(self
, palette
)
3885 class _changecolorbluewhite(changecolor
):
3887 def __init__(self
, palette
=color
.palette
.BlueWhite
):
3888 changecolor
.__init
__(self
, palette
)
3891 class _changecolorwhiteblue(changecolor
):
3893 def __init__(self
, palette
=color
.palette
.WhiteBlue
):
3894 changecolor
.__init
__(self
, palette
)
3897 class _changecolorredgreen(changecolor
):
3899 def __init__(self
, palette
=color
.palette
.RedGreen
):
3900 changecolor
.__init
__(self
, palette
)
3903 class _changecolorredblue(changecolor
):
3905 def __init__(self
, palette
=color
.palette
.RedBlue
):
3906 changecolor
.__init
__(self
, palette
)
3909 class _changecolorgreenred(changecolor
):
3911 def __init__(self
, palette
=color
.palette
.GreenRed
):
3912 changecolor
.__init
__(self
, palette
)
3915 class _changecolorgreenblue(changecolor
):
3917 def __init__(self
, palette
=color
.palette
.GreenBlue
):
3918 changecolor
.__init
__(self
, palette
)
3921 class _changecolorbluered(changecolor
):
3923 def __init__(self
, palette
=color
.palette
.BlueRed
):
3924 changecolor
.__init
__(self
, palette
)
3927 class _changecolorbluegreen(changecolor
):
3929 def __init__(self
, palette
=color
.palette
.BlueGreen
):
3930 changecolor
.__init
__(self
, palette
)
3933 class _changecolorrainbow(changecolor
):
3935 def __init__(self
, palette
=color
.palette
.Rainbow
):
3936 changecolor
.__init
__(self
, palette
)
3939 class _changecolorreverserainbow(changecolor
):
3941 def __init__(self
, palette
=color
.palette
.ReverseRainbow
):
3942 changecolor
.__init
__(self
, palette
)
3945 class _changecolorhue(changecolor
):
3947 def __init__(self
, palette
=color
.palette
.Hue
):
3948 changecolor
.__init
__(self
, palette
)
3951 class _changecolorreversehue(changecolor
):
3953 def __init__(self
, palette
=color
.palette
.ReverseHue
):
3954 changecolor
.__init
__(self
, palette
)
3957 changecolor
.Gray
= _changecolorgray
3958 changecolor
.Grey
= _changecolorgrey
3959 changecolor
.Reversegray
= _changecolorreversegray
3960 changecolor
.Reversegrey
= _changecolorreversegrey
3961 changecolor
.RedBlack
= _changecolorredblack
3962 changecolor
.BlackRed
= _changecolorblackred
3963 changecolor
.RedWhite
= _changecolorredwhite
3964 changecolor
.WhiteRed
= _changecolorwhitered
3965 changecolor
.GreenBlack
= _changecolorgreenblack
3966 changecolor
.BlackGreen
= _changecolorblackgreen
3967 changecolor
.GreenWhite
= _changecolorgreenwhite
3968 changecolor
.WhiteGreen
= _changecolorwhitegreen
3969 changecolor
.BlueBlack
= _changecolorblueblack
3970 changecolor
.BlackBlue
= _changecolorblackblue
3971 changecolor
.BlueWhite
= _changecolorbluewhite
3972 changecolor
.WhiteBlue
= _changecolorwhiteblue
3973 changecolor
.RedGreen
= _changecolorredgreen
3974 changecolor
.RedBlue
= _changecolorredblue
3975 changecolor
.GreenRed
= _changecolorgreenred
3976 changecolor
.GreenBlue
= _changecolorgreenblue
3977 changecolor
.BlueRed
= _changecolorbluered
3978 changecolor
.BlueGreen
= _changecolorbluegreen
3979 changecolor
.Rainbow
= _changecolorrainbow
3980 changecolor
.ReverseRainbow
= _changecolorreverserainbow
3981 changecolor
.Hue
= _changecolorhue
3982 changecolor
.ReverseHue
= _changecolorreversehue
3985 class changesequence(changeattr
):
3986 "cycles through a list"
3988 def __init__(self
, *sequence
):
3989 changeattr
.__init
__(self
)
3990 if not len(sequence
):
3991 sequence
= self
.defaultsequence
3992 self
.sequence
= sequence
3994 def attr(self
, index
):
3995 return self
.sequence
[index
% len(self
.sequence
)]
3998 class changelinestyle(changesequence
):
3999 defaultsequence
= (canvas
.linestyle
.solid
,
4000 canvas
.linestyle
.dashed
,
4001 canvas
.linestyle
.dotted
,
4002 canvas
.linestyle
.dashdotted
)
4005 class changestrokedfilled(changesequence
):
4006 defaultsequence
= (canvas
.stroked(), canvas
.filled())
4009 class changefilledstroked(changesequence
):
4010 defaultsequence
= (canvas
.filled(), canvas
.stroked())
4014 ################################################################################
4016 ################################################################################
4021 def cross(self
, x
, y
):
4022 return (path
._moveto
(x
-0.5*self
._size
, y
-0.5*self
._size
),
4023 path
._lineto
(x
+0.5*self
._size
, y
+0.5*self
._size
),
4024 path
._moveto
(x
-0.5*self
._size
, y
+0.5*self
._size
),
4025 path
._lineto
(x
+0.5*self
._size
, y
-0.5*self
._size
))
4027 def plus(self
, x
, y
):
4028 return (path
._moveto
(x
-0.707106781*self
._size
, y
),
4029 path
._lineto
(x
+0.707106781*self
._size
, y
),
4030 path
._moveto
(x
, y
-0.707106781*self
._size
),
4031 path
._lineto
(x
, y
+0.707106781*self
._size
))
4033 def square(self
, x
, y
):
4034 return (path
._moveto
(x
-0.5*self
._size
, y
-0.5 * self
._size
),
4035 path
._lineto
(x
+0.5*self
._size
, y
-0.5 * self
._size
),
4036 path
._lineto
(x
+0.5*self
._size
, y
+0.5 * self
._size
),
4037 path
._lineto
(x
-0.5*self
._size
, y
+0.5 * self
._size
),
4040 def triangle(self
, x
, y
):
4041 return (path
._moveto
(x
-0.759835685*self
._size
, y
-0.438691337*self
._size
),
4042 path
._lineto
(x
+0.759835685*self
._size
, y
-0.438691337*self
._size
),
4043 path
._lineto
(x
, y
+0.877382675*self
._size
),
4046 def circle(self
, x
, y
):
4047 return (path
._arc
(x
, y
, 0.564189583*self
._size
, 0, 360),
4050 def diamond(self
, x
, y
):
4051 return (path
._moveto
(x
-0.537284965*self
._size
, y
),
4052 path
._lineto
(x
, y
-0.930604859*self
._size
),
4053 path
._lineto
(x
+0.537284965*self
._size
, y
),
4054 path
._lineto
(x
, y
+0.930604859*self
._size
),
4057 def __init__(self
, symbol
=helper
.nodefault
,
4058 size
="0.2 cm", symbolattrs
=canvas
.stroked(),
4059 errorscale
=0.5, errorbarattrs
=(),
4061 self
.size_str
= size
4062 if symbol
is helper
.nodefault
:
4063 self
._symbol
= changesymbol
.cross()
4065 self
._symbol
= symbol
4066 self
._symbolattrs
= symbolattrs
4067 self
.errorscale
= errorscale
4068 self
._errorbarattrs
= errorbarattrs
4069 self
._lineattrs
= lineattrs
4071 def iteratedict(self
):
4073 result
["symbol"] = _iterateattr(self
._symbol
)
4074 result
["size"] = _iterateattr(self
.size_str
)
4075 result
["symbolattrs"] = _iterateattrs(self
._symbolattrs
)
4076 result
["errorscale"] = _iterateattr(self
.errorscale
)
4077 result
["errorbarattrs"] = _iterateattrs(self
._errorbarattrs
)
4078 result
["lineattrs"] = _iterateattrs(self
._lineattrs
)
4082 return symbol(**self
.iteratedict())
4084 def othercolumnkey(self
, key
, index
):
4085 raise ValueError("unsuitable key '%s'" % key
)
4087 def setcolumns(self
, graph
, columns
):
4088 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
4090 match
= pattern
.match(key
)
4092 if isindex
is not None: raise ValueError("multiple key specification")
4093 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
4095 iskey
= match
.groups()[0]
4097 return key
, iskey
, isindex
4099 self
.xi
= self
.xmini
= self
.xmaxi
= None
4100 self
.dxi
= self
.dxmini
= self
.dxmaxi
= None
4101 self
.yi
= self
.ymini
= self
.ymaxi
= None
4102 self
.dyi
= self
.dymini
= self
.dymaxi
= None
4103 self
.xkey
= self
.ykey
= None
4104 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
4105 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
4106 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
4107 XMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
4108 YMinPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
4109 XMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
4110 YMaxPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
4111 DXPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
4112 DYPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
4113 DXMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[0])
4114 DYMinPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)min$" % graph
.Names
[1])
4115 DXMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[0])
4116 DYMaxPattern
= re
.compile(r
"d(%s([2-9]|[1-9][0-9]+)?)max$" % graph
.Names
[1])
4117 for key
, index
in columns
.items():
4118 key
, self
.xkey
, self
.xi
= checkpattern(key
, index
, XPattern
, self
.xkey
, self
.xi
)
4119 key
, self
.ykey
, self
.yi
= checkpattern(key
, index
, YPattern
, self
.ykey
, self
.yi
)
4120 key
, self
.xkey
, self
.xmini
= checkpattern(key
, index
, XMinPattern
, self
.xkey
, self
.xmini
)
4121 key
, self
.ykey
, self
.ymini
= checkpattern(key
, index
, YMinPattern
, self
.ykey
, self
.ymini
)
4122 key
, self
.xkey
, self
.xmaxi
= checkpattern(key
, index
, XMaxPattern
, self
.xkey
, self
.xmaxi
)
4123 key
, self
.ykey
, self
.ymaxi
= checkpattern(key
, index
, YMaxPattern
, self
.ykey
, self
.ymaxi
)
4124 key
, self
.xkey
, self
.dxi
= checkpattern(key
, index
, DXPattern
, self
.xkey
, self
.dxi
)
4125 key
, self
.ykey
, self
.dyi
= checkpattern(key
, index
, DYPattern
, self
.ykey
, self
.dyi
)
4126 key
, self
.xkey
, self
.dxmini
= checkpattern(key
, index
, DXMinPattern
, self
.xkey
, self
.dxmini
)
4127 key
, self
.ykey
, self
.dymini
= checkpattern(key
, index
, DYMinPattern
, self
.ykey
, self
.dymini
)
4128 key
, self
.xkey
, self
.dxmaxi
= checkpattern(key
, index
, DXMaxPattern
, self
.xkey
, self
.dxmaxi
)
4129 key
, self
.ykey
, self
.dymaxi
= checkpattern(key
, index
, DYMaxPattern
, self
.ykey
, self
.dymaxi
)
4131 self
.othercolumnkey(key
, index
)
4132 if None in (self
.xkey
, self
.ykey
): raise ValueError("incomplete axis specification")
4133 if (len(filter(None, (self
.xmini
, self
.dxmini
, self
.dxi
))) > 1 or
4134 len(filter(None, (self
.ymini
, self
.dymini
, self
.dyi
))) > 1 or
4135 len(filter(None, (self
.xmaxi
, self
.dxmaxi
, self
.dxi
))) > 1 or
4136 len(filter(None, (self
.ymaxi
, self
.dymaxi
, self
.dyi
))) > 1):
4137 raise ValueError("multiple errorbar definition")
4138 if ((self
.xi
is None and self
.dxi
is not None) or
4139 (self
.yi
is None and self
.dyi
is not None) or
4140 (self
.xi
is None and self
.dxmini
is not None) or
4141 (self
.yi
is None and self
.dymini
is not None) or
4142 (self
.xi
is None and self
.dxmaxi
is not None) or
4143 (self
.yi
is None and self
.dymaxi
is not None)):
4144 raise ValueError("errorbar definition start value missing")
4145 self
.xaxis
= graph
.axes
[self
.xkey
]
4146 self
.yaxis
= graph
.axes
[self
.ykey
]
4148 def minmidmax(self
, point
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
4149 min = max = mid
= None
4151 mid
= point
[i
] + 0.0
4152 except (TypeError, ValueError):
4155 if di
is not None: min = point
[i
] - point
[di
]
4156 elif dmini
is not None: min = point
[i
] - point
[dmini
]
4157 elif mini
is not None: min = point
[mini
] + 0.0
4158 except (TypeError, ValueError):
4161 if di
is not None: max = point
[i
] + point
[di
]
4162 elif dmaxi
is not None: max = point
[i
] + point
[dmaxi
]
4163 elif maxi
is not None: max = point
[maxi
] + 0.0
4164 except (TypeError, ValueError):
4167 if min is not None and min > mid
: raise ValueError("minimum error in errorbar")
4168 if max is not None and max < mid
: raise ValueError("maximum error in errorbar")
4170 if min is not None and max is not None and min > max: raise ValueError("minimum/maximum error in errorbar")
4171 return min, mid
, max
4173 def keyrange(self
, points
, i
, mini
, maxi
, di
, dmini
, dmaxi
):
4174 allmin
= allmax
= None
4175 if filter(None, (mini
, maxi
, di
, dmini
, dmaxi
)) is not None:
4176 for point
in points
:
4177 min, mid
, max = self
.minmidmax(point
, i
, mini
, maxi
, di
, dmini
, dmaxi
)
4178 if min is not None and (allmin
is None or min < allmin
): allmin
= min
4179 if mid
is not None and (allmin
is None or mid
< allmin
): allmin
= mid
4180 if mid
is not None and (allmax
is None or mid
> allmax
): allmax
= mid
4181 if max is not None and (allmax
is None or max > allmax
): allmax
= max
4183 for point
in points
:
4185 value
= point
[i
] + 0.0
4186 if allmin
is None or point
[i
] < allmin
: allmin
= point
[i
]
4187 if allmax
is None or point
[i
] > allmax
: allmax
= point
[i
]
4188 except (TypeError, ValueError):
4190 return allmin
, allmax
4192 def getranges(self
, points
):
4193 xmin
, xmax
= self
.keyrange(points
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
4194 ymin
, ymax
= self
.keyrange(points
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
4195 return {self
.xkey
: (xmin
, xmax
), self
.ykey
: (ymin
, ymax
)}
4197 def _drawerrorbar(self
, graph
, topleft
, top
, topright
,
4198 left
, center
, right
,
4199 bottomleft
, bottom
, bottomright
, point
=None):
4200 if left
is not None:
4201 if right
is not None:
4202 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
4203 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
4204 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
4205 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
4206 graph
.stroke(path
.path(path
._moveto
(*left1
),
4207 graph
._connect
(*(left1
+left2
)),
4208 path
._moveto
(*left
),
4209 graph
._connect
(*(left
+right
)),
4210 path
._moveto
(*right1
),
4211 graph
._connect
(*(right1
+right2
))),
4212 *self
.errorbarattrs
)
4213 elif center
is not None:
4214 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
4215 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
4216 graph
.stroke(path
.path(path
._moveto
(*left1
),
4217 graph
._connect
(*(left1
+left2
)),
4218 path
._moveto
(*left
),
4219 graph
._connect
(*(left
+center
))),
4220 *self
.errorbarattrs
)
4222 left1
= graph
._addpos
(*(left
+(0, -self
._errorsize
)))
4223 left2
= graph
._addpos
(*(left
+(0, self
._errorsize
)))
4224 left3
= graph
._addpos
(*(left
+(self
._errorsize
, 0)))
4225 graph
.stroke(path
.path(path
._moveto
(*left1
),
4226 graph
._connect
(*(left1
+left2
)),
4227 path
._moveto
(*left
),
4228 graph
._connect
(*(left
+left3
))),
4229 *self
.errorbarattrs
)
4230 if right
is not None and left
is None:
4231 if center
is not None:
4232 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
4233 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
4234 graph
.stroke(path
.path(path
._moveto
(*right1
),
4235 graph
._connect
(*(right1
+right2
)),
4236 path
._moveto
(*right
),
4237 graph
._connect
(*(right
+center
))),
4238 *self
.errorbarattrs
)
4240 right1
= graph
._addpos
(*(right
+(0, -self
._errorsize
)))
4241 right2
= graph
._addpos
(*(right
+(0, self
._errorsize
)))
4242 right3
= graph
._addpos
(*(right
+(-self
._errorsize
, 0)))
4243 graph
.stroke(path
.path(path
._moveto
(*right1
),
4244 graph
._connect
(*(right1
+right2
)),
4245 path
._moveto
(*right
),
4246 graph
._connect
(*(right
+right3
))),
4247 *self
.errorbarattrs
)
4249 if bottom
is not None:
4251 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
4252 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
4253 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
4254 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
4255 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
4256 graph
._connect
(*(bottom1
+bottom2
)),
4257 path
._moveto
(*bottom
),
4258 graph
._connect
(*(bottom
+top
)),
4259 path
._moveto
(*top1
),
4260 graph
._connect
(*(top1
+top2
))),
4261 *self
.errorbarattrs
)
4262 elif center
is not None:
4263 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
4264 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
4265 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
4266 graph
._connect
(*(bottom1
+bottom2
)),
4267 path
._moveto
(*bottom
),
4268 graph
._connect
(*(bottom
+center
))),
4269 *self
.errorbarattrs
)
4271 bottom1
= graph
._addpos
(*(bottom
+(-self
._errorsize
, 0)))
4272 bottom2
= graph
._addpos
(*(bottom
+(self
._errorsize
, 0)))
4273 bottom3
= graph
._addpos
(*(bottom
+(0, self
._errorsize
)))
4274 graph
.stroke(path
.path(path
._moveto
(*bottom1
),
4275 graph
._connect
(*(bottom1
+bottom2
)),
4276 path
._moveto
(*bottom
),
4277 graph
._connect
(*(bottom
+bottom3
))),
4278 *self
.errorbarattrs
)
4279 if top
is not None and bottom
is None:
4280 if center
is not None:
4281 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
4282 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
4283 graph
.stroke(path
.path(path
._moveto
(*top1
),
4284 graph
._connect
(*(top1
+top2
)),
4286 graph
._connect
(*(top
+center
))),
4287 *self
.errorbarattrs
)
4289 top1
= graph
._addpos
(*(top
+(-self
._errorsize
, 0)))
4290 top2
= graph
._addpos
(*(top
+(self
._errorsize
, 0)))
4291 top3
= graph
._addpos
(*(top
+(0, -self
._errorsize
)))
4292 graph
.stroke(path
.path(path
._moveto
(*top1
),
4293 graph
._connect
(*(top1
+top2
)),
4295 graph
._connect
(*(top
+top3
))),
4296 *self
.errorbarattrs
)
4297 if bottomleft
is not None:
4298 if topleft
is not None and bottomright
is None:
4299 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
._errorsize
, 0)))
4300 topleft1
= graph
._addpos
(*(topleft
+(self
._errorsize
, 0)))
4301 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
4302 graph
._connect
(*(bottomleft1
+bottomleft
)),
4303 graph
._connect
(*(bottomleft
+topleft
)),
4304 graph
._connect
(*(topleft
+topleft1
))),
4305 *self
.errorbarattrs
)
4306 elif bottomright
is not None and topleft
is None:
4307 bottomleft1
= graph
._addpos
(*(bottomleft
+(0, self
._errorsize
)))
4308 bottomright1
= graph
._addpos
(*(bottomright
+(0, self
._errorsize
)))
4309 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
4310 graph
._connect
(*(bottomleft1
+bottomleft
)),
4311 graph
._connect
(*(bottomleft
+bottomright
)),
4312 graph
._connect
(*(bottomright
+bottomright1
))),
4313 *self
.errorbarattrs
)
4314 elif bottomright
is None and topleft
is None:
4315 bottomleft1
= graph
._addpos
(*(bottomleft
+(self
._errorsize
, 0)))
4316 bottomleft2
= graph
._addpos
(*(bottomleft
+(0, self
._errorsize
)))
4317 graph
.stroke(path
.path(path
._moveto
(*bottomleft1
),
4318 graph
._connect
(*(bottomleft1
+bottomleft
)),
4319 graph
._connect
(*(bottomleft
+bottomleft2
))),
4320 *self
.errorbarattrs
)
4321 if topright
is not None:
4322 if bottomright
is not None and topleft
is None:
4323 topright1
= graph
._addpos
(*(topright
+(-self
._errorsize
, 0)))
4324 bottomright1
= graph
._addpos
(*(bottomright
+(-self
._errorsize
, 0)))
4325 graph
.stroke(path
.path(path
._moveto
(*topright1
),
4326 graph
._connect
(*(topright1
+topright
)),
4327 graph
._connect
(*(topright
+bottomright
)),
4328 graph
._connect
(*(bottomright
+bottomright1
))),
4329 *self
.errorbarattrs
)
4330 elif topleft
is not None and bottomright
is None:
4331 topright1
= graph
._addpos
(*(topright
+(0, -self
._errorsize
)))
4332 topleft1
= graph
._addpos
(*(topleft
+(0, -self
._errorsize
)))
4333 graph
.stroke(path
.path(path
._moveto
(*topright1
),
4334 graph
._connect
(*(topright1
+topright
)),
4335 graph
._connect
(*(topright
+topleft
)),
4336 graph
._connect
(*(topleft
+topleft1
))),
4337 *self
.errorbarattrs
)
4338 elif topleft
is None and bottomright
is None:
4339 topright1
= graph
._addpos
(*(topright
+(-self
._errorsize
, 0)))
4340 topright2
= graph
._addpos
(*(topright
+(0, -self
._errorsize
)))
4341 graph
.stroke(path
.path(path
._moveto
(*topright1
),
4342 graph
._connect
(*(topright1
+topright
)),
4343 graph
._connect
(*(topright
+topright2
))),
4344 *self
.errorbarattrs
)
4345 if bottomright
is not None and bottomleft
is None and topright
is None:
4346 bottomright1
= graph
._addpos
(*(bottomright
+(-self
._errorsize
, 0)))
4347 bottomright2
= graph
._addpos
(*(bottomright
+(0, self
._errorsize
)))
4348 graph
.stroke(path
.path(path
._moveto
(*bottomright1
),
4349 graph
._connect
(*(bottomright1
+bottomright
)),
4350 graph
._connect
(*(bottomright
+bottomright2
))),
4351 *self
.errorbarattrs
)
4352 if topleft
is not None and bottomleft
is None and topright
is None:
4353 topleft1
= graph
._addpos
(*(topleft
+(self
._errorsize
, 0)))
4354 topleft2
= graph
._addpos
(*(topleft
+(0, -self
._errorsize
)))
4355 graph
.stroke(path
.path(path
._moveto
(*topleft1
),
4356 graph
._connect
(*(topleft1
+topleft
)),
4357 graph
._connect
(*(topleft
+topleft2
))),
4358 *self
.errorbarattrs
)
4359 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
4360 graph
.stroke(path
.path(path
._moveto
(*bottomleft
),
4361 graph
._connect
(*(bottomleft
+bottomright
)),
4362 graph
._connect
(*(bottomright
+topright
)),
4363 graph
._connect
(*(topright
+topleft
)),
4365 *self
.errorbarattrs
)
4367 def _drawsymbol(self
, canvas
, x
, y
, point
=None):
4368 canvas
.draw(path
.path(*self
.symbol(self
, x
, y
)), *self
.symbolattrs
)
4370 def drawsymbol(self
, canvas
, x
, y
, point
=None):
4371 self
._drawsymbol
(canvas
, unit
.topt(x
), unit
.topt(y
), point
)
4373 def key(self
, c
, x
, y
, width
, height
):
4374 if self
._symbolattrs
is not None:
4375 self
._drawsymbol
(c
, x
+ 0.5 * width
, y
+ 0.5 * height
)
4376 if self
._lineattrs
is not None:
4377 c
.stroke(path
._line
(x
, y
+ 0.5 * height
, x
+ width
, y
+ 0.5 * height
), *self
.lineattrs
)
4379 def drawpoints(self
, graph
, points
):
4380 xaxismin
, xaxismax
= self
.xaxis
.getdatarange()
4381 yaxismin
, yaxismax
= self
.yaxis
.getdatarange()
4382 self
.size
= unit
.length(_getattr(self
.size_str
), default_type
="v")
4383 self
._size
= unit
.topt(self
.size
)
4384 self
.symbol
= _getattr(self
._symbol
)
4385 self
.symbolattrs
= _getattrs(helper
.ensuresequence(self
._symbolattrs
))
4386 self
.errorbarattrs
= _getattrs(helper
.ensuresequence(self
._errorbarattrs
))
4387 self
._errorsize
= self
.errorscale
* self
._size
4388 self
.errorsize
= self
.errorscale
* self
.size
4389 self
.lineattrs
= _getattrs(helper
.ensuresequence(self
._lineattrs
))
4390 if self
._lineattrs
is not None:
4391 clipcanvas
= graph
.clipcanvas()
4393 haserror
= filter(None, (self
.xmini
, self
.ymini
, self
.xmaxi
, self
.ymaxi
,
4394 self
.dxi
, self
.dyi
, self
.dxmini
, self
.dymini
, self
.dxmaxi
, self
.dymaxi
)) is not None
4396 for point
in points
:
4398 xmin
, x
, xmax
= self
.minmidmax(point
, self
.xi
, self
.xmini
, self
.xmaxi
, self
.dxi
, self
.dxmini
, self
.dxmaxi
)
4399 ymin
, y
, ymax
= self
.minmidmax(point
, self
.yi
, self
.ymini
, self
.ymaxi
, self
.dyi
, self
.dymini
, self
.dymaxi
)
4400 if x
is not None and x
< xaxismin
: drawsymbol
= 0
4401 elif x
is not None and x
> xaxismax
: drawsymbol
= 0
4402 elif y
is not None and y
< yaxismin
: drawsymbol
= 0
4403 elif y
is not None and y
> yaxismax
: drawsymbol
= 0
4404 # elif haserror: # TODO: correct clipcanvas handling
4405 # if xmin is not None and xmin < xaxismin: drawsymbol = 0
4406 # elif xmax is not None and xmax < xaxismin: drawsymbol = 0
4407 # elif xmax is not None and xmax > xaxismax: drawsymbol = 0
4408 # elif xmin is not None and xmin > xaxismax: drawsymbol = 0
4409 # elif ymin is not None and ymin < yaxismin: drawsymbol = 0
4410 # elif ymax is not None and ymax < yaxismin: drawsymbol = 0
4411 # elif ymax is not None and ymax > yaxismax: drawsymbol = 0
4412 # elif ymin is not None and ymin > yaxismax: drawsymbol = 0
4413 xpos
=ypos
=topleft
=top
=topright
=left
=center
=right
=bottomleft
=bottom
=bottomright
=None
4414 if x
is not None and y
is not None:
4416 center
= xpos
, ypos
= graph
._pos
(x
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4417 except (ValueError, OverflowError): # XXX: exceptions???
4421 if xmin
is not None: left
= graph
._pos
(xmin
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4422 if xmax
is not None: right
= graph
._pos
(xmax
, y
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4424 if ymax
is not None: top
= graph
._pos
(x
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4425 if ymin
is not None: bottom
= graph
._pos
(x
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4426 if x
is None or y
is None:
4427 if ymax
is not None:
4428 if xmin
is not None: topleft
= graph
._pos
(xmin
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4429 if xmax
is not None: topright
= graph
._pos
(xmax
, ymax
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4430 if ymin
is not None:
4431 if xmin
is not None: bottomleft
= graph
._pos
(xmin
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4432 if xmax
is not None: bottomright
= graph
._pos
(xmax
, ymin
, xaxis
=self
.xaxis
, yaxis
=self
.yaxis
)
4434 if self
._errorbarattrs
is not None and haserror
:
4435 self
._drawerrorbar
(graph
, topleft
, top
, topright
,
4436 left
, center
, right
,
4437 bottomleft
, bottom
, bottomright
, point
)
4438 if self
._symbolattrs
is not None and xpos
is not None and ypos
is not None:
4439 self
._drawsymbol
(graph
, xpos
, ypos
, point
)
4440 if xpos
is not None and ypos
is not None:
4442 lineels
.append(path
._moveto
(xpos
, ypos
))
4445 lineels
.append(path
._lineto
(xpos
, ypos
))
4448 self
.path
= path
.path(*lineels
)
4449 if self
._lineattrs
is not None:
4450 clipcanvas
.stroke(self
.path
, *self
.lineattrs
)
4453 class changesymbol(changesequence
): pass
4456 class _changesymbolcross(changesymbol
):
4457 defaultsequence
= (symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
)
4460 class _changesymbolplus(changesymbol
):
4461 defaultsequence
= (symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
)
4464 class _changesymbolsquare(changesymbol
):
4465 defaultsequence
= (symbol
.square
, symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
)
4468 class _changesymboltriangle(changesymbol
):
4469 defaultsequence
= (symbol
.triangle
, symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
)
4472 class _changesymbolcircle(changesymbol
):
4473 defaultsequence
= (symbol
.circle
, symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
)
4476 class _changesymboldiamond(changesymbol
):
4477 defaultsequence
= (symbol
.diamond
, symbol
.cross
, symbol
.plus
, symbol
.square
, symbol
.triangle
, symbol
.circle
)
4480 class _changesymbolsquaretwice(changesymbol
):
4481 defaultsequence
= (symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
,
4482 symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
)
4485 class _changesymboltriangletwice(changesymbol
):
4486 defaultsequence
= (symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
,
4487 symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
)
4490 class _changesymbolcircletwice(changesymbol
):
4491 defaultsequence
= (symbol
.circle
, symbol
.circle
, symbol
.diamond
, symbol
.diamond
,
4492 symbol
.square
, symbol
.square
, symbol
.triangle
, symbol
.triangle
)
4495 class _changesymboldiamondtwice(changesymbol
):
4496 defaultsequence
= (symbol
.diamond
, symbol
.diamond
, symbol
.square
, symbol
.square
,
4497 symbol
.triangle
, symbol
.triangle
, symbol
.circle
, symbol
.circle
)
4500 changesymbol
.cross
= _changesymbolcross
4501 changesymbol
.plus
= _changesymbolplus
4502 changesymbol
.square
= _changesymbolsquare
4503 changesymbol
.triangle
= _changesymboltriangle
4504 changesymbol
.circle
= _changesymbolcircle
4505 changesymbol
.diamond
= _changesymboldiamond
4506 changesymbol
.squaretwice
= _changesymbolsquaretwice
4507 changesymbol
.triangletwice
= _changesymboltriangletwice
4508 changesymbol
.circletwice
= _changesymbolcircletwice
4509 changesymbol
.diamondtwice
= _changesymboldiamondtwice
4514 def __init__(self
, lineattrs
=helper
.nodefault
):
4515 if lineattrs
is helper
.nodefault
:
4516 lineattrs
= (changelinestyle(), canvas
.linejoin
.round)
4517 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=None, lineattrs
=lineattrs
)
4522 def __init__(self
, palette
=color
.palette
.Gray
):
4523 self
.palette
= palette
4524 self
.colorindex
= None
4525 symbol
.__init
__(self
, symbolattrs
=None, errorbarattrs
=(), lineattrs
=None)
4528 raise RuntimeError("style is not iterateable")
4530 def othercolumnkey(self
, key
, index
):
4532 self
.colorindex
= index
4534 symbol
.othercolumnkey(self
, key
, index
)
4536 def _drawerrorbar(self
, graph
, topleft
, top
, topright
,
4537 left
, center
, right
,
4538 bottomleft
, bottom
, bottomright
, point
=None):
4539 color
= point
[self
.colorindex
]
4540 if color
is not None:
4541 if color
!= self
.lastcolor
:
4542 self
.rectclipcanvas
.set(self
.palette
.getcolor(color
))
4543 if bottom
is not None and left
is not None:
4544 bottomleft
= left
[0], bottom
[1]
4545 if bottom
is not None and right
is not None:
4546 bottomright
= right
[0], bottom
[1]
4547 if top
is not None and right
is not None:
4548 topright
= right
[0], top
[1]
4549 if top
is not None and left
is not None:
4550 topleft
= left
[0], top
[1]
4551 if bottomleft
is not None and bottomright
is not None and topright
is not None and topleft
is not None:
4552 self
.rectclipcanvas
.fill(path
.path(path
._moveto
(*bottomleft
),
4553 graph
._connect
(*(bottomleft
+bottomright
)),
4554 graph
._connect
(*(bottomright
+topright
)),
4555 graph
._connect
(*(topright
+topleft
)),
4558 def drawpoints(self
, graph
, points
):
4559 if self
.colorindex
is None:
4560 raise RuntimeError("column 'color' not set")
4561 self
.lastcolor
= None
4562 self
.rectclipcanvas
= graph
.clipcanvas()
4563 symbol
.drawpoints(self
, graph
, points
)
4565 def key(self
, c
, x
, y
, width
, height
):
4566 raise RuntimeError("style doesn't yet provide a key")
4571 def __init__(self
, textdx
="0", textdy
="0.3 cm", textattrs
=textmodule
.halign
.center
, **args
):
4572 self
.textindex
= None
4573 self
.textdx_str
= textdx
4574 self
.textdy_str
= textdy
4575 self
._textattrs
= textattrs
4576 symbol
.__init
__(self
, **args
)
4578 def iteratedict(self
):
4579 result
= symbol
.iteratedict()
4580 result
["textattrs"] = _iterateattr(self
._textattrs
)
4584 return textsymbol(**self
.iteratedict())
4586 def othercolumnkey(self
, key
, index
):
4588 self
.textindex
= index
4590 symbol
.othercolumnkey(self
, key
, index
)
4592 def _drawsymbol(self
, graph
, x
, y
, point
=None):
4593 symbol
._drawsymbol
(self
, graph
, x
, y
, point
)
4594 if None not in (x
, y
, point
[self
.textindex
]) and self
._textattrs
is not None:
4595 graph
._text
(x
+ self
._textdx
, y
+ self
._textdy
, str(point
[self
.textindex
]), *helper
.ensuresequence(self
.textattrs
))
4597 def drawpoints(self
, graph
, points
):
4598 self
.textdx
= unit
.length(_getattr(self
.textdx_str
), default_type
="v")
4599 self
.textdy
= unit
.length(_getattr(self
.textdy_str
), default_type
="v")
4600 self
._textdx
= unit
.topt(self
.textdx
)
4601 self
._textdy
= unit
.topt(self
.textdy
)
4602 if self
._textattrs
is not None:
4603 self
.textattrs
= _getattr(self
._textattrs
)
4604 if self
.textindex
is None:
4605 raise RuntimeError("column 'text' not set")
4606 symbol
.drawpoints(self
, graph
, points
)
4608 def key(self
, c
, x
, y
, width
, height
):
4609 raise RuntimeError("style doesn't yet provide a key")
4612 class arrow(symbol
):
4614 def __init__(self
, linelength
="0.2 cm", arrowattrs
=(), arrowsize
="0.1 cm", arrowdict
={}, epsilon
=1e-10):
4615 self
.linelength_str
= linelength
4616 self
.arrowsize_str
= arrowsize
4617 self
.arrowattrs
= arrowattrs
4618 self
.arrowdict
= arrowdict
4619 self
.epsilon
= epsilon
4620 self
.sizeindex
= self
.angleindex
= None
4621 symbol
.__init
__(self
, symbolattrs
=(), errorbarattrs
=None, lineattrs
=None)
4624 raise RuntimeError("style is not iterateable")
4626 def othercolumnkey(self
, key
, index
):
4628 self
.sizeindex
= index
4629 elif key
== "angle":
4630 self
.angleindex
= index
4632 symbol
.othercolumnkey(self
, key
, index
)
4634 def _drawsymbol(self
, graph
, x
, y
, point
=None):
4635 if None not in (x
, y
, point
[self
.angleindex
], point
[self
.sizeindex
], self
.arrowattrs
, self
.arrowdict
):
4636 if point
[self
.sizeindex
] > self
.epsilon
:
4637 dx
, dy
= math
.cos(point
[self
.angleindex
]*math
.pi
/180.0), math
.sin(point
[self
.angleindex
]*math
.pi
/180)
4638 x1
= unit
.t_pt(x
)-0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
4639 y1
= unit
.t_pt(y
)-0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
4640 x2
= unit
.t_pt(x
)+0.5*dx
*self
.linelength
*point
[self
.sizeindex
]
4641 y2
= unit
.t_pt(y
)+0.5*dy
*self
.linelength
*point
[self
.sizeindex
]
4642 graph
.stroke(path
.line(x1
, y1
, x2
, y2
),
4643 canvas
.earrow(self
.arrowsize
*point
[self
.sizeindex
],
4645 *helper
.ensuresequence(self
.arrowattrs
))
4647 def drawpoints(self
, graph
, points
):
4648 self
.arrowsize
= unit
.length(_getattr(self
.arrowsize_str
), default_type
="v")
4649 self
.linelength
= unit
.length(_getattr(self
.linelength_str
), default_type
="v")
4650 self
._arrowsize
= unit
.topt(self
.arrowsize
)
4651 self
._linelength
= unit
.topt(self
.linelength
)
4652 if self
.sizeindex
is None:
4653 raise RuntimeError("column 'size' not set")
4654 if self
.angleindex
is None:
4655 raise RuntimeError("column 'angle' not set")
4656 symbol
.drawpoints(self
, graph
, points
)
4658 def key(self
, c
, x
, y
, width
, height
):
4659 raise RuntimeError("style doesn't yet provide a key")
4662 class _bariterator(changeattr
):
4664 def attr(self
, index
):
4665 return index
, self
.counter
4670 def __init__(self
, fromzero
=1, stacked
=0, skipmissing
=1, xbar
=0,
4671 barattrs
=helper
.nodefault
, _usebariterator
=helper
.nodefault
, _previousbar
=None):
4672 self
.fromzero
= fromzero
4673 self
.stacked
= stacked
4674 self
.skipmissing
= skipmissing
4676 if barattrs
is helper
.nodefault
:
4677 self
._barattrs
= (canvas
.stroked(color
.gray
.black
), changecolor
.Rainbow())
4679 self
._barattrs
= barattrs
4680 if _usebariterator
is helper
.nodefault
:
4681 self
.bariterator
= _bariterator()
4683 self
.bariterator
= _usebariterator
4684 self
.previousbar
= _previousbar
4686 def iteratedict(self
):
4688 result
["barattrs"] = _iterateattrs(self
._barattrs
)
4692 return bar(fromzero
=self
.fromzero
, stacked
=self
.stacked
, xbar
=self
.xbar
,
4693 _usebariterator
=_iterateattr(self
.bariterator
), _previousbar
=self
, **self
.iteratedict())
4695 def setcolumns(self
, graph
, columns
):
4696 def checkpattern(key
, index
, pattern
, iskey
, isindex
):
4698 match
= pattern
.match(key
)
4700 if isindex
is not None: raise ValueError("multiple key specification")
4701 if iskey
is not None and iskey
!= match
.groups()[0]: raise ValueError("inconsistent key names")
4703 iskey
= match
.groups()[0]
4705 return key
, iskey
, isindex
4708 if len(graph
.Names
) != 2: raise TypeError("style not applicable in graph")
4709 XPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[0])
4710 YPattern
= re
.compile(r
"(%s([2-9]|[1-9][0-9]+)?)$" % graph
.Names
[1])
4712 for key
, index
in columns
.items():
4713 key
, xkey
, xi
= checkpattern(key
, index
, XPattern
, xkey
, xi
)
4714 key
, ykey
, yi
= checkpattern(key
, index
, YPattern
, ykey
, yi
)
4716 self
.othercolumnkey(key
, index
)
4717 if None in (xkey
, ykey
): raise ValueError("incomplete axis specification")
4719 self
.nkey
, self
.ni
= ykey
, yi
4720 self
.vkey
, self
.vi
= xkey
, xi
4722 self
.nkey
, self
.ni
= xkey
, xi
4723 self
.vkey
, self
.vi
= ykey
, yi
4724 self
.naxis
, self
.vaxis
= graph
.axes
[self
.nkey
], graph
.axes
[self
.vkey
]
4726 def getranges(self
, points
):
4727 index
, count
= _getattr(self
.bariterator
)
4728 if count
!= 1 and self
.stacked
!= 1:
4729 if self
.stacked
> 1:
4730 index
= divmod(index
, self
.stacked
)[0]
4733 for point
in points
:
4734 if not self
.skipmissing
:
4735 if count
!= 1 and self
.stacked
!= 1:
4736 self
.naxis
.setname(point
[self
.ni
], index
)
4738 self
.naxis
.setname(point
[self
.ni
])
4740 v
= point
[self
.vi
] + 0.0
4741 if vmin
is None or v
< vmin
: vmin
= v
4742 if vmax
is None or v
> vmax
: vmax
= v
4743 except (TypeError, ValueError):
4746 if self
.skipmissing
:
4747 if count
!= 1 and self
.stacked
!= 1:
4748 self
.naxis
.setname(point
[self
.ni
], index
)
4750 self
.naxis
.setname(point
[self
.ni
])
4752 if vmin
> 0: vmin
= 0
4753 if vmax
< 0: vmax
= 0
4754 return {self
.vkey
: (vmin
, vmax
)}
4756 def drawpoints(self
, graph
, points
):
4757 index
, count
= _getattr(self
.bariterator
)
4758 dostacked
= (self
.stacked
!= 0 and
4759 (self
.stacked
== 1 or divmod(index
, self
.stacked
)[1]) and
4760 (self
.stacked
!= 1 or index
))
4761 if self
.stacked
> 1:
4762 index
= divmod(index
, self
.stacked
)[0]
4763 vmin
, vmax
= self
.vaxis
.getdatarange()
4764 self
.barattrs
= _getattrs(helper
.ensuresequence(self
._barattrs
))
4766 self
.stackedvalue
= {}
4767 for point
in points
:
4772 self
.stackedvalue
[n
] = v
4773 if count
!= 1 and self
.stacked
!= 1:
4774 minid
= (n
, index
, 0)
4775 maxid
= (n
, index
, 1)
4780 x1pos
, y1pos
= graph
._pos
(v
, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4781 x2pos
, y2pos
= graph
._pos
(v
, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4783 x1pos
, y1pos
= graph
._pos
(minid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4784 x2pos
, y2pos
= graph
._pos
(maxid
, v
, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4787 x3pos
, y3pos
= graph
._pos
(self
.previousbar
.stackedvalue
[n
], maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4788 x4pos
, y4pos
= graph
._pos
(self
.previousbar
.stackedvalue
[n
], minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4790 x3pos
, y3pos
= graph
._pos
(maxid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4791 x4pos
, y4pos
= graph
._pos
(minid
, self
.previousbar
.stackedvalue
[n
], xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4795 x3pos
, y3pos
= graph
._pos
(0, maxid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4796 x4pos
, y4pos
= graph
._pos
(0, minid
, xaxis
=self
.vaxis
, yaxis
=self
.naxis
)
4798 x3pos
, y3pos
= graph
._pos
(maxid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4799 x4pos
, y4pos
= graph
._pos
(minid
, 0, xaxis
=self
.naxis
, yaxis
=self
.vaxis
)
4801 x3pos
, y3pos
= graph
._tickpoint
(maxid
, axis
=self
.naxis
)
4802 x4pos
, y4pos
= graph
._tickpoint
(minid
, axis
=self
.naxis
)
4803 if self
.barattrs
is not None:
4804 graph
.fill(path
.path(path
._moveto
(x1pos
, y1pos
),
4805 graph
._connect
(x1pos
, y1pos
, x2pos
, y2pos
),
4806 graph
._connect
(x2pos
, y2pos
, x3pos
, y3pos
),
4807 graph
._connect
(x3pos
, y3pos
, x4pos
, y4pos
),
4808 graph
._connect
(x4pos
, y4pos
, x1pos
, y1pos
), # no closepath (might not be straight)
4809 path
.closepath()), *self
.barattrs
)
4810 except (TypeError, ValueError): pass
4812 def key(self
, c
, x
, y
, width
, height
):
4813 c
.fill(path
._rect
(x
, y
, width
, height
), *self
.barattrs
)
4818 # def setcolumns(self, graph, columns):
4819 # self.columns = columns
4821 # def getranges(self, points):
4822 # return {"x": (0, 10), "y": (0, 10), "z": (0, 1)}
4824 # def drawpoints(self, graph, points):
4829 ################################################################################
4831 ################################################################################
4836 defaultstyle
= symbol
4838 def __init__(self
, file, title
=helper
.nodefault
, context
={}, **columns
):
4840 if helper
.isstring(file):
4841 self
.data
= datamodule
.datafile(file)
4844 if title
is helper
.nodefault
:
4845 self
.title
= "(unknown)"
4849 for key
, column
in columns
.items():
4851 self
.columns
[key
] = self
.data
.getcolumnno(column
)
4852 except datamodule
.ColumnError
:
4853 self
.columns
[key
] = len(self
.data
.titles
)
4854 self
.data
.addcolumn(column
, context
=context
)
4856 def setstyle(self
, graph
, style
):
4858 self
.style
.setcolumns(graph
, self
.columns
)
4860 def getranges(self
):
4861 return self
.style
.getranges(self
.data
.data
)
4863 def setranges(self
, ranges
):
4866 def draw(self
, graph
):
4867 self
.style
.drawpoints(graph
, self
.data
.data
)
4874 def __init__(self
, expression
, title
=helper
.nodefault
, min=None, max=None, points
=100, parser
=mathtree
.parser(), context
={}):
4875 if title
is helper
.nodefault
:
4876 self
.title
= expression
4881 self
.points
= points
4882 self
.context
= context
4883 self
.result
, expression
= [x
.strip() for x
in expression
.split("=")]
4884 self
.mathtree
= parser
.parse(expression
)
4885 self
.variable
= None
4888 def setstyle(self
, graph
, style
):
4889 for variable
in self
.mathtree
.VarList():
4890 if variable
in graph
.axes
.keys():
4891 if self
.variable
is None:
4892 self
.variable
= variable
4894 raise ValueError("multiple variables found")
4895 if self
.variable
is None:
4896 raise ValueError("no variable found")
4897 self
.xaxis
= graph
.axes
[self
.variable
]
4899 self
.style
.setcolumns(graph
, {self
.variable
: 0, self
.result
: 1})
4901 def getranges(self
):
4903 return self
.style
.getranges(self
.data
)
4904 if None not in (self
.min, self
.max):
4905 return {self
.variable
: (self
.min, self
.max)}
4907 def setranges(self
, ranges
):
4908 if ranges
.has_key(self
.variable
):
4909 min, max = ranges
[self
.variable
]
4910 if self
.min is not None: min = self
.min
4911 if self
.max is not None: max = self
.max
4912 vmin
= self
.xaxis
.convert(min)
4913 vmax
= self
.xaxis
.convert(max)
4915 for i
in range(self
.points
):
4916 self
.context
[self
.variable
] = x
= self
.xaxis
.invert(vmin
+ (vmax
-vmin
)*i
/ (self
.points
-1.0))
4918 y
= self
.mathtree
.Calc(**self
.context
)
4919 except (ArithmeticError, ValueError):
4921 self
.data
.append((x
, y
))
4924 def draw(self
, graph
):
4925 self
.style
.drawpoints(graph
, self
.data
)
4928 class paramfunction
:
4932 def __init__(self
, varname
, min, max, expression
, title
=helper
.nodefault
, points
=100, parser
=mathtree
.parser(), context
={}):
4933 if title
is helper
.nodefault
:
4934 self
.title
= expression
4937 self
.varname
= varname
4940 self
.points
= points
4941 self
.expression
= {}
4943 varlist
, expressionlist
= expression
.split("=")
4944 parsestr
= mathtree
.ParseStr(expressionlist
)
4945 for key
in varlist
.split(","):
4947 if self
.mathtrees
.has_key(key
):
4948 raise ValueError("multiple assignment in tuple")
4950 self
.mathtrees
[key
] = parser
.ParseMathTree(parsestr
)
4952 except mathtree
.CommaFoundMathTreeParseError
, e
:
4953 self
.mathtrees
[key
] = e
.MathTree
4955 raise ValueError("unpack tuple of wrong size")
4956 if len(varlist
.split(",")) != len(self
.mathtrees
.keys()):
4957 raise ValueError("unpack tuple of wrong size")
4959 for i
in range(self
.points
):
4960 context
[self
.varname
] = self
.min + (self
.max-self
.min)*i
/ (self
.points
-1.0)
4962 for key
, tree
in self
.mathtrees
.items():
4963 line
.append(tree
.Calc(**context
))
4964 self
.data
.append(line
)
4966 def setstyle(self
, graph
, style
):
4969 for key
, index
in zip(self
.mathtrees
.keys(), xrange(sys
.maxint
)):
4970 columns
[key
] = index
4971 self
.style
.setcolumns(graph
, columns
)
4973 def getranges(self
):
4974 return self
.style
.getranges(self
.data
)
4976 def setranges(self
, ranges
):
4979 def draw(self
, graph
):
4980 self
.style
.drawpoints(graph
, self
.data
)