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-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 from __future__
import nested_scopes
28 from pyx
import helper
# TODO to be removed
29 from pyx
.graph
.axis
import tick
32 # Note: A partition is a list of ticks.
35 """state storage class for a partfunction
37 partdata is used to keep local data and a current state to emulate
38 generators. In the future we might use yield statements within a
39 partfunction. Currently we add partdata by a lambda construct and
40 do inplace modifications within partdata to keep track of the state.
43 def __init__(self
, **kwargs
):
44 for key
, value
in kwargs
.items():
45 setattr(self
, key
, value
)
49 """interface of a partitioner"""
51 def partfunctions(self
, min, max, extendmin
, extendmax
):
52 """returns a list of partfunctions
54 A partfunction can be called without further arguments and
55 it will return a new partition each time, or None. Several
56 partfunctions are used to walk in different "directions"
57 (like more and less partitions).
59 Note that we do not alternate walking in different directions
60 (i.e. alternate the partfunction calls). Instead we first walk
61 into one direction (which should give less and less ticks) until
62 the rating becomes bad and when try more ticks. We want to keep
63 the number of ticks small compared to a simple alternate search.
65 # This is a (useless) empty partitioner.
69 class linear(_parter
):
70 """partitioner to create a single linear parition"""
72 def __init__(self
, tickdist
=None, labeldist
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
73 if tickdist
is None and labeldist
is not None:
74 self
.ticklist
= (tick
.rational(helper
.ensuresequence(labeldist
)[0]),)
76 self
.ticklist
= map(tick
.rational
, helper
.ensuresequence(tickdist
))
77 if labeldist
is None and tickdist
is not None:
78 self
.labellist
= (tick
.rational(helper
.ensuresequence(tickdist
)[0]),)
80 self
.labellist
= map(tick
.rational
, helper
.ensuresequence(labeldist
))
81 self
.extendtick
= extendtick
82 self
.extendlabel
= extendlabel
83 self
.epsilon
= epsilon
85 def extendminmax(self
, min, max, dist
, extendmin
, extendmax
):
86 """return new min, max tuple extending the range min, max
87 - dist is the tick distance to be used
88 - extendmin and extendmax are booleans to allow for the extension"""
90 min = float(dist
) * math
.floor(min / float(dist
) + self
.epsilon
)
92 max = float(dist
) * math
.ceil(max / float(dist
) - self
.epsilon
)
95 def getticks(self
, min, max, dist
, ticklevel
=None, labellevel
=None):
96 """return a list of equal spaced ticks
97 - the tick distance is dist, the ticklevel is set to ticklevel and
98 the labellevel is set to labellevel
99 - min, max is the range where ticks should be placed"""
100 imin
= int(math
.ceil(min/float(dist
) - 0.5*self
.epsilon
))
101 imax
= int(math
.floor(max/float(dist
) + 0.5*self
.epsilon
))
103 for i
in range(imin
, imax
+ 1):
104 ticks
.append(tick
.tick((i
*dist
.num
, dist
.denom
), ticklevel
=ticklevel
, labellevel
=labellevel
))
107 def partfunction(self
, data
):
112 if self
.extendtick
is not None and len(self
.ticklist
) > self
.extendtick
:
113 min, max = self
.extendminmax(min, max, self
.ticklist
[self
.extendtick
], data
.extendmin
, data
.extendmax
)
114 if self
.extendlabel
is not None and len(self
.labellist
) > self
.extendlabel
:
115 min, max = self
.extendminmax(min, max, self
.labellist
[self
.extendlabel
], data
.extendmin
, data
.extendmax
)
118 for i
in range(len(self
.ticklist
)):
119 ticks
= tick
.mergeticklists(ticks
, self
.getticks(min, max, self
.ticklist
[i
], ticklevel
=i
))
120 for i
in range(len(self
.labellist
)):
121 ticks
= tick
.mergeticklists(ticks
, self
.getticks(min, max, self
.labellist
[i
], labellevel
=i
))
127 def partfunctions(self
, min, max, extendmin
, extendmax
):
128 return [lambda d
=_partdata(first
=1, min=min, max=max, extendmin
=extendmin
, extendmax
=extendmax
):
129 self
.partfunction(d
)]
134 class autolinear(_parter
):
135 """partitioner to create an arbitrary number of linear paritions"""
137 defaultvariants
= [[tick
.rational((1, 1)), tick
.rational((1, 2))],
138 [tick
.rational((2, 1)), tick
.rational((1, 1))],
139 [tick
.rational((5, 2)), tick
.rational((5, 4))],
140 [tick
.rational((5, 1)), tick
.rational((5, 2))]]
142 def __init__(self
, variants
=defaultvariants
, extendtick
=0, epsilon
=1e-10):
143 self
.variants
= variants
144 self
.extendtick
= extendtick
145 self
.epsilon
= epsilon
147 def partfunctions(self
, min, max, extendmin
, extendmax
):
148 logmm
= math
.log(max - min) / math
.log(10)
149 if logmm
< 0: # correction for rounding towards zero of the int routine
150 base
= tick
.rational((10, 1), power
=int(logmm
-1))
152 base
= tick
.rational((10, 1), power
=int(logmm
))
153 ticks
= map(tick
.rational
, self
.variants
[0])
154 useticks
= [t
* base
for t
in ticks
]
156 return [lambda d
=_partdata(min=min, max=max, extendmin
=extendmin
, extendmax
=extendmax
,
157 sign
=1, tickindex
=-1, base
=tick
.rational(base
)):
158 self
.partfunction(d
),
159 lambda d
=_partdata(min=min, max=max, extendmin
=extendmin
, extendmax
=extendmax
,
160 sign
=-1, tickindex
=0, base
=tick
.rational(base
)):
161 self
.partfunction(d
)]
163 def partfunction(self
, data
):
165 if data
.tickindex
< len(self
.variants
) - 1:
174 data
.tickindex
= len(self
.variants
) - 1
175 data
.base
.denom
*= 10
176 tickdist
= [tick
.rational(t
) * data
.base
for t
in self
.variants
[data
.tickindex
]]
177 linearparter
= linear(tickdist
=tickdist
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
178 return linearparter
.partfunctions(min=data
.min, max=data
.max, extendmin
=data
.extendmin
, extendmax
=data
.extendmax
)[0]()
184 """definition of a logarithmic partition
186 exp is an integer, which defines multiplicator (usually 10).
187 pres are a list of tick positions (rational numbers, e.g.
188 instances of rational). possible positions are the tick
189 positions and arbitrary divisions and multiplications of
190 the tick positions by exp."""
192 def __init__(self
, pres
, exp
):
197 class logarithmic(linear
):
198 """partitioner to create a single logarithmic parition"""
200 # define some useful constants
201 pre1exp5
= preexp([tick
.rational((1, 1))], 100000)
202 pre1exp4
= preexp([tick
.rational((1, 1))], 10000)
203 pre1exp3
= preexp([tick
.rational((1, 1))], 1000)
204 pre1exp2
= preexp([tick
.rational((1, 1))], 100)
205 pre1exp
= preexp([tick
.rational((1, 1))], 10)
206 pre125exp
= preexp([tick
.rational((1, 1)), tick
.rational((2, 1)), tick
.rational((5, 1))], 10)
207 pre1to9exp
= preexp([tick
.rational((x
, 1)) for x
in range(1, 10)], 10)
208 # ^- we always include 1 in order to get extendto(tick|label)level to work as expected
210 def __init__(self
, tickpos
=None, labelpos
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
211 if tickpos
is None and labelpos
is not None:
212 self
.ticklist
= (helper
.ensuresequence(labelpos
)[0],)
214 self
.ticklist
= helper
.ensuresequence(tickpos
)
216 if labelpos
is None and tickpos
is not None:
217 self
.labellist
= (helper
.ensuresequence(tickpos
)[0],)
219 self
.labellist
= helper
.ensuresequence(labelpos
)
220 self
.extendtick
= extendtick
221 self
.extendlabel
= extendlabel
222 self
.epsilon
= epsilon
224 def extendminmax(self
, min, max, preexp
, extendmin
, extendmax
):
227 for i
in xrange(len(preexp
.pres
)):
228 imin
= int(math
.floor(math
.log(min / float(preexp
.pres
[i
])) /
229 math
.log(preexp
.exp
) + self
.epsilon
)) + 1
230 imax
= int(math
.ceil(math
.log(max / float(preexp
.pres
[i
])) /
231 math
.log(preexp
.exp
) - self
.epsilon
)) - 1
232 if minpower
is None or imin
< minpower
:
233 minpower
, minindex
= imin
, i
234 if maxpower
is None or imax
>= maxpower
:
235 maxpower
, maxindex
= imax
, i
237 minrational
= preexp
.pres
[minindex
- 1]
239 minrational
= preexp
.pres
[-1]
241 if maxindex
!= len(preexp
.pres
) - 1:
242 maxrational
= preexp
.pres
[maxindex
+ 1]
244 maxrational
= preexp
.pres
[0]
247 min = float(minrational
) * float(preexp
.exp
) ** minpower
249 max = float(maxrational
) * float(preexp
.exp
) ** maxpower
252 def getticks(self
, min, max, preexp
, ticklevel
=None, labellevel
=None):
256 for f
in preexp
.pres
:
258 imin
= int(math
.ceil(math
.log(min / float(f
)) /
259 math
.log(preexp
.exp
) - 0.5 * self
.epsilon
))
260 imax
= int(math
.floor(math
.log(max / float(f
)) /
261 math
.log(preexp
.exp
) + 0.5 * self
.epsilon
))
262 for i
in range(imin
, imax
+ 1):
263 pos
= f
* tick
.rational((preexp
.exp
, 1), power
=i
)
264 thisticks
.append(tick
.tick((pos
.num
, pos
.denom
), ticklevel
= ticklevel
, labellevel
= labellevel
))
265 ticks
= tick
.mergeticklists(ticks
, thisticks
)
271 class autologarithmic(logarithmic
):
272 """partitioner to create several logarithmic paritions"""
274 defaultvariants
= [([logarithmic
.pre1exp
, # ticks
275 logarithmic
.pre1to9exp
], # subticks
276 [logarithmic
.pre1exp
, # labels
277 logarithmic
.pre125exp
]), # sublevels
279 ([logarithmic
.pre1exp
, # ticks
280 logarithmic
.pre1to9exp
], # subticks
281 None), # labels like ticks
283 ([logarithmic
.pre1exp2
, # ticks
284 logarithmic
.pre1exp
], # subticks
285 None), # labels like ticks
287 ([logarithmic
.pre1exp3
, # ticks
288 logarithmic
.pre1exp
], # subticks
289 None), # labels like ticks
291 ([logarithmic
.pre1exp4
, # ticks
292 logarithmic
.pre1exp
], # subticks
293 None), # labels like ticks
295 ([logarithmic
.pre1exp5
, # ticks
296 logarithmic
.pre1exp
], # subticks
297 None)] # labels like ticks
299 def __init__(self
, variants
=defaultvariants
, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
300 self
.variants
= variants
301 if len(variants
) > 2:
302 self
.variantsindex
= divmod(len(variants
), 2)[0]
304 self
.variantsindex
= 0
305 self
.extendtick
= extendtick
306 self
.extendlabel
= extendlabel
307 self
.epsilon
= epsilon
309 def partfunctions(self
, min, max, extendmin
, extendmax
):
310 return [lambda d
=_partdata(min=min, max=max, extendmin
=extendmin
, extendmax
=extendmax
,
311 sign
=1, variantsindex
=self
.variantsindex
-1):
312 self
.partfunction(d
),
313 lambda d
=_partdata(min=min, max=max, extendmin
=extendmin
, extendmax
=extendmax
,
314 sign
=-1, variantsindex
=self
.variantsindex
):
315 self
.partfunction(d
)]
317 def partfunction(self
, data
):
318 data
.variantsindex
+= data
.sign
319 if 0 <= data
.variantsindex
< len(self
.variants
):
320 logarithmicparter
= logarithmic(tickpos
=self
.variants
[data
.variantsindex
][0], labelpos
=self
.variants
[data
.variantsindex
][1],
321 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
322 return logarithmicparter
.partfunctions(min=data
.min, max=data
.max, extendmin
=data
.extendmin
, extendmax
=data
.extendmax
)[0]()
325 autolog
= autologarithmic