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.num, 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.num, base.denom))
192
self.morebase = tick.rational((base.num, 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.num *= 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.num, 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