2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2004 André Wobst <wobsta@users.sourceforge.net>
9 # This file is part of PyX (http://pyx.sourceforge.net/).
11 # PyX is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # PyX is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with PyX; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 from pyx
import helper
28 from pyx
.graph
.axis
import tick
31 # partitioner (parter)
32 # please note the nomenclature:
33 # - a part (partition) is a list of tick instances; thus ticks `==' part
34 # - a parter (partitioner) is a class creating ticks
38 """interface definition of a partition scheme
39 partition schemes are used to create a list of ticks"""
41 def defaultpart(self
, min, max, extendmin
, extendmax
):
43 - returns an ordered list of ticks for the interval min to max
44 - the interval is given in float numbers, thus an appropriate
45 conversion to rational numbers has to be performed
46 - extendmin and extendmax are booleans (integers)
47 - when extendmin or extendmax is set, the ticks might
48 extend the min-max range towards lower and higher
49 ranges, respectively"""
52 """create another partition which contains less ticks
53 - this method is called several times after a call of defaultpart
54 - returns an ordered list of ticks with less ticks compared to
55 the partition returned by defaultpart and by previous calls
57 - the creation of a partition with strictly *less* ticks
58 is not to be taken serious
59 - the method might return None, when no other appropriate
60 partition can be created"""
64 """create another partition which contains more ticks
65 see lesspart, but increase the number of ticks"""
69 """linear partition scheme
70 ticks and label distances are explicitly provided to the constructor"""
72 __implements__
= _Iparter
74 def __init__(self
, tickdist
=None, labeldist
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
75 """configuration of the partition scheme
76 - tickdist and labeldist should be a list, where the first value
77 is the distance between ticks with ticklevel/labellevel 0,
78 the second list for ticklevel/labellevel 1, etc.;
79 a single entry is allowed without being a list
80 - tickdist and labeldist values are passed to the rational constructor
81 - when labeldist is None and tickdist is not None, the tick entries
82 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
83 - extendtick allows for the extension of the range given to the
84 defaultpart method to include the next tick with the specified
85 level (None turns off this feature); note, that this feature is
86 also disabled, when an axis prohibits its range extension by
87 the extendmin/extendmax variables given to the defaultpart method
88 - extendlabel is analogous to extendtick, but for labels
89 - epsilon allows for exceeding the axis range by this relative
90 value (relative to the axis range given to the defaultpart method)
91 without creating another tick specified by extendtick/extendlabel"""
92 if tickdist
is None and labeldist
is not None:
93 self
.ticklist
= (tick
.rational(helper
.ensuresequence(labeldist
)[0]),)
95 self
.ticklist
= map(tick
.rational
, helper
.ensuresequence(tickdist
))
96 if labeldist
is None and tickdist
is not None:
97 self
.labellist
= (tick
.rational(helper
.ensuresequence(tickdist
)[0]),)
99 self
.labellist
= map(tick
.rational
, helper
.ensuresequence(labeldist
))
100 self
.extendtick
= extendtick
101 self
.extendlabel
= extendlabel
102 self
.epsilon
= epsilon
104 def extendminmax(self
, min, max, dist
, extendmin
, extendmax
):
105 """return new min, max tuple extending the range min, max
106 - dist is the tick distance to be used
107 - extendmin and extendmax are booleans to allow for the extension"""
109 min = float(dist
) * math
.floor(min / float(dist
) + self
.epsilon
)
111 max = float(dist
) * math
.ceil(max / float(dist
) - self
.epsilon
)
114 def getticks(self
, min, max, dist
, ticklevel
=None, labellevel
=None):
115 """return a list of equal spaced ticks
116 - the tick distance is dist, the ticklevel is set to ticklevel and
117 the labellevel is set to labellevel
118 - min, max is the range where ticks should be placed"""
119 imin
= int(math
.ceil(min/float(dist
) - 0.5*self
.epsilon
))
120 imax
= int(math
.floor(max/float(dist
) + 0.5*self
.epsilon
))
122 for i
in range(imin
, imax
+ 1):
123 ticks
.append(tick
.tick((i
*dist
.enum
, dist
.denom
), ticklevel
=ticklevel
, labellevel
=labellevel
))
126 def defaultpart(self
, min, max, extendmin
, extendmax
):
127 if self
.extendtick
is not None and len(self
.ticklist
) > self
.extendtick
:
128 min, max = self
.extendminmax(min, max, self
.ticklist
[self
.extendtick
], extendmin
, extendmax
)
129 if self
.extendlabel
is not None and len(self
.labellist
) > self
.extendlabel
:
130 min, max = self
.extendminmax(min, max, self
.labellist
[self
.extendlabel
], extendmin
, extendmax
)
133 for i
in range(len(self
.ticklist
)):
134 ticks
= tick
.mergeticklists(ticks
, self
.getticks(min, max, self
.ticklist
[i
], ticklevel
= i
))
135 for i
in range(len(self
.labellist
)):
136 ticks
= tick
.mergeticklists(ticks
, self
.getticks(min, max, self
.labellist
[i
], labellevel
= i
))
150 """automatic linear partition scheme
151 - possible tick distances are explicitly provided to the constructor
152 - tick distances are adjusted to the axis range by multiplication or division by 10"""
154 __implements__
= _Iparter
156 defaultvariants
= [[tick
.rational((1, 1)), tick
.rational((1, 2))],
157 [tick
.rational((2, 1)), tick
.rational((1, 1))],
158 [tick
.rational((5, 2)), tick
.rational((5, 4))],
159 [tick
.rational((5, 1)), tick
.rational((5, 2))]]
161 def __init__(self
, variants
=defaultvariants
, extendtick
=0, epsilon
=1e-10):
162 """configuration of the partition scheme
163 - variants is a list of tickdist
164 - tickdist should be a list, where the first value
165 is the distance between ticks with ticklevel 0,
166 the second for ticklevel 1, etc.
167 - tickdist values are passed to the rational constructor
168 - labellevel is set to None except for those ticks in the partitions,
169 where ticklevel is zero. There labellevel is also set to zero.
170 - extendtick allows for the extension of the range given to the
171 defaultpart method to include the next tick with the specified
172 level (None turns off this feature); note, that this feature is
173 also disabled, when an axis prohibits its range extension by
174 the extendmin/extendmax variables given to the defaultpart method
175 - epsilon allows for exceeding the axis range by this relative
176 value (relative to the axis range given to the defaultpart method)
177 without creating another tick specified by extendtick"""
178 self
.variants
= variants
179 self
.extendtick
= extendtick
180 self
.epsilon
= epsilon
182 def defaultpart(self
, min, max, extendmin
, extendmax
):
183 logmm
= math
.log(max - min) / math
.log(10)
184 if logmm
< 0: # correction for rounding towards zero of the int routine
185 base
= tick
.rational((10, 1), power
=int(logmm
-1))
187 base
= tick
.rational((10, 1), power
=int(logmm
))
188 ticks
= map(tick
.rational
, self
.variants
[0])
189 useticks
= [t
* base
for t
in ticks
]
190 self
.lesstickindex
= self
.moretickindex
= 0
191 self
.lessbase
= tick
.rational((base
.enum
, base
.denom
))
192 self
.morebase
= tick
.rational((base
.enum
, base
.denom
))
193 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
194 part
= linear(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
195 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
198 if self
.lesstickindex
< len(self
.variants
) - 1:
199 self
.lesstickindex
+= 1
201 self
.lesstickindex
= 0
202 self
.lessbase
.enum
*= 10
203 ticks
= map(tick
.rational
, self
.variants
[self
.lesstickindex
])
204 useticks
= [t
* self
.lessbase
for t
in ticks
]
205 part
= linear(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
206 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
209 if self
.moretickindex
:
210 self
.moretickindex
-= 1
212 self
.moretickindex
= len(self
.variants
) - 1
213 self
.morebase
.denom
*= 10
214 ticks
= map(tick
.rational
, self
.variants
[self
.moretickindex
])
215 useticks
= [t
* self
.morebase
for t
in ticks
]
216 part
= linear(tickdist
=useticks
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
217 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
223 """storage class for the definition of logarithmic axes partitions
224 instances of this class define tick positions suitable for
225 logarithmic axes by the following instance variables:
226 - exp: integer, which defines multiplicator (usually 10)
227 - pres: list of tick positions (rational numbers, e.g. instances of rational)
228 possible positions are these tick positions and arbitrary divisions
229 and multiplications by the exp value"""
231 def __init__(self
, pres
, exp
):
232 "create a preexp instance and store its pres and exp information"
237 class logarithmic(linear
):
238 """logarithmic partition scheme
239 ticks and label positions are explicitly provided to the constructor"""
241 __implements__
= _Iparter
243 pre1exp5
= preexp([tick
.rational((1, 1))], 100000)
244 pre1exp4
= preexp([tick
.rational((1, 1))], 10000)
245 pre1exp3
= preexp([tick
.rational((1, 1))], 1000)
246 pre1exp2
= preexp([tick
.rational((1, 1))], 100)
247 pre1exp
= preexp([tick
.rational((1, 1))], 10)
248 pre125exp
= preexp([tick
.rational((1, 1)), tick
.rational((2, 1)), tick
.rational((5, 1))], 10)
249 pre1to9exp
= preexp([tick
.rational((x
, 1)) for x
in range(1, 10)], 10)
250 # ^- we always include 1 in order to get extendto(tick|label)level to work as expected
252 def __init__(self
, tickpos
=None, labelpos
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
253 """configuration of the partition scheme
254 - tickpos and labelpos should be a list, where the first entry
255 is a preexp instance describing ticks with ticklevel/labellevel 0,
256 the second is a preexp instance for ticklevel/labellevel 1, etc.;
257 a single entry is allowed without being a list
258 - when labelpos is None and tickpos is not None, the tick entries
259 for ticklevel 0 are used for labels and vice versa (ticks<->labels)
260 - extendtick allows for the extension of the range given to the
261 defaultpart method to include the next tick with the specified
262 level (None turns off this feature); note, that this feature is
263 also disabled, when an axis prohibits its range extension by
264 the extendmin/extendmax variables given to the defaultpart method
265 - extendlabel is analogous to extendtick, but for labels
266 - epsilon allows for exceeding the axis range by this relative
267 logarithm value (relative to the logarithm axis range given
268 to the defaultpart method) without creating another tick
269 specified by extendtick/extendlabel"""
270 if tickpos
is None and labelpos
is not None:
271 self
.ticklist
= (helper
.ensuresequence(labelpos
)[0],)
273 self
.ticklist
= helper
.ensuresequence(tickpos
)
275 if labelpos
is None and tickpos
is not None:
276 self
.labellist
= (helper
.ensuresequence(tickpos
)[0],)
278 self
.labellist
= helper
.ensuresequence(labelpos
)
279 self
.extendtick
= extendtick
280 self
.extendlabel
= extendlabel
281 self
.epsilon
= epsilon
283 def extendminmax(self
, min, max, preexp
, extendmin
, extendmax
):
284 """return new min, max tuple extending the range min, max
285 preexp describes the allowed tick positions
286 extendmin and extendmax are booleans to allow for the extension"""
289 for i
in xrange(len(preexp
.pres
)):
290 imin
= int(math
.floor(math
.log(min / float(preexp
.pres
[i
])) /
291 math
.log(preexp
.exp
) + self
.epsilon
)) + 1
292 imax
= int(math
.ceil(math
.log(max / float(preexp
.pres
[i
])) /
293 math
.log(preexp
.exp
) - self
.epsilon
)) - 1
294 if minpower
is None or imin
< minpower
:
295 minpower
, minindex
= imin
, i
296 if maxpower
is None or imax
>= maxpower
:
297 maxpower
, maxindex
= imax
, i
299 minrational
= preexp
.pres
[minindex
- 1]
301 minrational
= preexp
.pres
[-1]
303 if maxindex
!= len(preexp
.pres
) - 1:
304 maxrational
= preexp
.pres
[maxindex
+ 1]
306 maxrational
= preexp
.pres
[0]
309 min = float(minrational
) * float(preexp
.exp
) ** minpower
311 max = float(maxrational
) * float(preexp
.exp
) ** maxpower
314 def getticks(self
, min, max, preexp
, ticklevel
=None, labellevel
=None):
315 """return a list of ticks
316 - preexp describes the allowed tick positions
317 - the ticklevel of the ticks is set to ticklevel and
318 the labellevel is set to labellevel
319 - min, max is the range where ticks should be placed"""
323 for f
in preexp
.pres
:
325 imin
= int(math
.ceil(math
.log(min / float(f
)) /
326 math
.log(preexp
.exp
) - 0.5 * self
.epsilon
))
327 imax
= int(math
.floor(math
.log(max / float(f
)) /
328 math
.log(preexp
.exp
) + 0.5 * self
.epsilon
))
329 for i
in range(imin
, imax
+ 1):
330 pos
= f
* tick
.rational((preexp
.exp
, 1), power
=i
)
331 thisticks
.append(tick
.tick((pos
.enum
, pos
.denom
), ticklevel
= ticklevel
, labellevel
= labellevel
))
332 ticks
= tick
.mergeticklists(ticks
, thisticks
)
338 class autologarithmic(logarithmic
):
339 """automatic logarithmic partition scheme
340 possible tick positions are explicitly provided to the constructor"""
342 __implements__
= _Iparter
344 defaultvariants
= [([logarithmic
.pre1exp
, # ticks
345 logarithmic
.pre1to9exp
], # subticks
346 [logarithmic
.pre1exp
, # labels
347 logarithmic
.pre125exp
]), # sublevels
349 ([logarithmic
.pre1exp
, # ticks
350 logarithmic
.pre1to9exp
], # subticks
351 None), # labels like ticks
353 ([logarithmic
.pre1exp2
, # ticks
354 logarithmic
.pre1exp
], # subticks
355 None), # labels like ticks
357 ([logarithmic
.pre1exp3
, # ticks
358 logarithmic
.pre1exp
], # subticks
359 None), # labels like ticks
361 ([logarithmic
.pre1exp4
, # ticks
362 logarithmic
.pre1exp
], # subticks
363 None), # labels like ticks
365 ([logarithmic
.pre1exp5
, # ticks
366 logarithmic
.pre1exp
], # subticks
367 None)] # labels like ticks
369 def __init__(self
, variants
=defaultvariants
, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
370 """configuration of the partition scheme
371 - variants should be a list of pairs of lists of preexp
373 - within each pair the first list contains preexp, where
374 the first preexp instance describes ticks positions with
375 ticklevel 0, the second preexp for ticklevel 1, etc.
376 - the second list within each pair describes the same as
377 before, but for labels
378 - within each pair: when the second entry (for the labels) is None
379 and the first entry (for the ticks) ticks is not None, the tick
380 entries for ticklevel 0 are used for labels and vice versa
382 - extendtick allows for the extension of the range given to the
383 defaultpart method to include the next tick with the specified
384 level (None turns off this feature); note, that this feature is
385 also disabled, when an axis prohibits its range extension by
386 the extendmin/extendmax variables given to the defaultpart method
387 - extendlabel is analogous to extendtick, but for labels
388 - epsilon allows for exceeding the axis range by this relative
389 logarithm value (relative to the logarithm axis range given
390 to the defaultpart method) without creating another tick
391 specified by extendtick/extendlabel"""
392 self
.variants
= variants
393 if len(variants
) > 2:
394 self
.variantsindex
= divmod(len(variants
), 2)[0]
396 self
.variantsindex
= 0
397 self
.extendtick
= extendtick
398 self
.extendlabel
= extendlabel
399 self
.epsilon
= epsilon
401 def defaultpart(self
, min, max, extendmin
, extendmax
):
402 self
.min, self
.max, self
.extendmin
, self
.extendmax
= min, max, extendmin
, extendmax
403 self
.morevariantsindex
= self
.variantsindex
404 self
.lessvariantsindex
= self
.variantsindex
405 part
= logarithmic(tickpos
=self
.variants
[self
.variantsindex
][0], labelpos
=self
.variants
[self
.variantsindex
][1],
406 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
407 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
410 self
.lessvariantsindex
+= 1
411 if self
.lessvariantsindex
< len(self
.variants
):
412 part
= logarithmic(tickpos
=self
.variants
[self
.lessvariantsindex
][0], labelpos
=self
.variants
[self
.lessvariantsindex
][1],
413 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
414 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
417 self
.morevariantsindex
-= 1
418 if self
.morevariantsindex
>= 0:
419 part
= logarithmic(tickpos
=self
.variants
[self
.morevariantsindex
][0], labelpos
=self
.variants
[self
.morevariantsindex
][1],
420 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
421 return part
.defaultpart(self
.min, self
.max, self
.extendmin
, self
.extendmax
)
423 autolog
= autologarithmic