1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 from __future__
import nested_scopes
27 from pyx
.graph
.axis
import tick
30 # Note: A partition is a list of ticks.
33 """state storage class for a partfunction
35 partdata is used to keep local data and a current state to emulate
36 generators. In the future we might use yield statements within a
37 partfunction. Currently we add partdata by a lambda construct and
38 do inplace modifications within partdata to keep track of the state.
41 def __init__(self
, **kwargs
):
42 for key
, value
in kwargs
.items():
43 setattr(self
, key
, value
)
47 """interface of a partitioner"""
49 def partfunctions(self
, min, max, extendmin
, extendmax
):
50 """returns a list of partfunctions
52 A partfunction can be called without further arguments and
53 it will return a new partition each time, or None. Several
54 partfunctions are used to walk in different "directions"
55 (like more and less partitions).
57 Note that we do not alternate walking in different directions
58 (i.e. alternate the partfunction calls). Instead we first walk
59 into one direction (which should give less and less ticks) until
60 the rating becomes bad and when try more ticks. We want to keep
61 the number of ticks small compared to a simple alternate search.
63 # This is a (useless) empty partitioner.
67 class linear(_parter
):
68 """partitioner to create a single linear parition"""
70 def __init__(self
, tickdists
=None, labeldists
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
71 if tickdists
is None and labeldists
is not None:
72 self
.ticklist
= [tick
.rational(labeldists
[0])]
74 self
.ticklist
= map(tick
.rational
, tickdists
)
75 if labeldists
is None and tickdists
is not None:
76 self
.labellist
= [tick
.rational(tickdists
[0])]
78 self
.labellist
= map(tick
.rational
, labeldists
)
79 self
.extendtick
= extendtick
80 self
.extendlabel
= extendlabel
81 self
.epsilon
= epsilon
83 def extendminmax(self
, min, max, dist
, extendmin
, extendmax
):
84 """return new min, max tuple extending the range min, max
85 - dist is the tick distance to be used
86 - extendmin and extendmax are booleans to allow for the extension"""
88 min = float(dist
) * math
.floor(min / float(dist
) + self
.epsilon
)
90 max = float(dist
) * math
.ceil(max / float(dist
) - self
.epsilon
)
93 def getticks(self
, min, max, dist
, ticklevel
=None, labellevel
=None):
94 """return a list of equal spaced ticks
95 - the tick distance is dist, the ticklevel is set to ticklevel and
96 the labellevel is set to labellevel
97 - min, max is the range where ticks should be placed"""
98 imin
= int(math
.ceil(min/float(dist
) - 0.5*self
.epsilon
))
99 imax
= int(math
.floor(max/float(dist
) + 0.5*self
.epsilon
))
101 for i
in range(imin
, imax
+ 1):
102 ticks
.append(tick
.tick((i
*dist
.num
, dist
.denom
), ticklevel
=ticklevel
, labellevel
=labellevel
))
105 def partfunction(self
, data
):
110 if self
.extendtick
is not None and len(self
.ticklist
) > self
.extendtick
:
111 min, max = self
.extendminmax(min, max, self
.ticklist
[self
.extendtick
], data
.extendmin
, data
.extendmax
)
112 if self
.extendlabel
is not None and len(self
.labellist
) > self
.extendlabel
:
113 min, max = self
.extendminmax(min, max, self
.labellist
[self
.extendlabel
], data
.extendmin
, data
.extendmax
)
116 for i
in range(len(self
.ticklist
)):
117 ticks
= tick
.mergeticklists(ticks
, self
.getticks(min, max, self
.ticklist
[i
], ticklevel
=i
))
118 for i
in range(len(self
.labellist
)):
119 ticks
= tick
.mergeticklists(ticks
, self
.getticks(min, max, self
.labellist
[i
], labellevel
=i
))
125 def partfunctions(self
, min, max, extendmin
, extendmax
):
126 return [lambda d
=_partdata(first
=1, min=min, max=max, extendmin
=extendmin
, extendmax
=extendmax
):
127 self
.partfunction(d
)]
132 class autolinear(_parter
):
133 """partitioner to create an arbitrary number of linear paritions"""
135 defaultvariants
= [[tick
.rational((1, 1)), tick
.rational((1, 2))],
136 [tick
.rational((2, 1)), tick
.rational((1, 1))],
137 [tick
.rational((5, 2)), tick
.rational((5, 4))],
138 [tick
.rational((5, 1)), tick
.rational((5, 2))]]
140 def __init__(self
, variants
=defaultvariants
, extendtick
=0, epsilon
=1e-10):
141 self
.variants
= variants
142 self
.extendtick
= extendtick
143 self
.epsilon
= epsilon
145 def partfunctions(self
, min, max, extendmin
, extendmax
):
147 logmm
= math
.log(max - min) / math
.log(10)
148 except ArithmeticError:
149 raise RuntimeError("partitioning failed due to empty or invalid axis range")
150 if logmm
< 0: # correction for rounding towards zero of the int routine
151 base
= tick
.rational((10, 1), power
=int(logmm
-1))
153 base
= tick
.rational((10, 1), power
=int(logmm
))
154 ticks
= map(tick
.rational
, self
.variants
[0])
155 useticks
= [t
* base
for t
in ticks
]
157 return [lambda d
=_partdata(min=min, max=max, extendmin
=extendmin
, extendmax
=extendmax
,
158 sign
=1, tickindex
=-1, base
=tick
.rational(base
)):
159 self
.partfunction(d
),
160 lambda d
=_partdata(min=min, max=max, extendmin
=extendmin
, extendmax
=extendmax
,
161 sign
=-1, tickindex
=0, base
=tick
.rational(base
)):
162 self
.partfunction(d
)]
164 def partfunction(self
, data
):
166 if data
.tickindex
< len(self
.variants
) - 1:
175 data
.tickindex
= len(self
.variants
) - 1
176 data
.base
.denom
*= 10
177 tickdists
= [tick
.rational(t
) * data
.base
for t
in self
.variants
[data
.tickindex
]]
178 linearparter
= linear(tickdists
=tickdists
, extendtick
=self
.extendtick
, epsilon
=self
.epsilon
)
179 return linearparter
.partfunctions(min=data
.min, max=data
.max, extendmin
=data
.extendmin
, extendmax
=data
.extendmax
)[0]()
185 """definition of a logarithmic partition
187 exp is an integer, which defines multiplicator (usually 10).
188 pres are a list of tick positions (rational numbers, e.g.
189 instances of rational). possible positions are the tick
190 positions and arbitrary divisions and multiplications of
191 the tick positions by exp."""
193 def __init__(self
, pres
, exp
):
198 class logarithmic(linear
):
199 """partitioner to create a single logarithmic parition"""
201 # define some useful constants
202 pre1exp5
= preexp([tick
.rational((1, 1))], 100000)
203 pre1exp4
= preexp([tick
.rational((1, 1))], 10000)
204 pre1exp3
= preexp([tick
.rational((1, 1))], 1000)
205 pre1exp2
= preexp([tick
.rational((1, 1))], 100)
206 pre1exp
= preexp([tick
.rational((1, 1))], 10)
207 pre125exp
= preexp([tick
.rational((1, 1)), tick
.rational((2, 1)), tick
.rational((5, 1))], 10)
208 pre1to9exp
= preexp([tick
.rational((x
, 1)) for x
in range(1, 10)], 10)
209 # ^- we always include 1 in order to get extendto(tick|label)level to work as expected
211 def __init__(self
, tickpreexps
=None, labelpreexps
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
212 if tickpreexps
is None and labelpreexps
is not None:
213 self
.ticklist
= [labelpreexps
[0]]
215 self
.ticklist
= tickpreexps
217 if labelpreexps
is None and tickpreexps
is not None:
218 self
.labellist
= [tickpreexps
[0]]
220 self
.labellist
= labelpreexps
221 self
.extendtick
= extendtick
222 self
.extendlabel
= extendlabel
223 self
.epsilon
= epsilon
225 def extendminmax(self
, min, max, preexp
, extendmin
, extendmax
):
228 for i
in xrange(len(preexp
.pres
)):
229 imin
= int(math
.floor(math
.log(min / float(preexp
.pres
[i
])) /
230 math
.log(preexp
.exp
) + self
.epsilon
)) + 1
231 imax
= int(math
.ceil(math
.log(max / float(preexp
.pres
[i
])) /
232 math
.log(preexp
.exp
) - self
.epsilon
)) - 1
233 if minpower
is None or imin
< minpower
:
234 minpower
, minindex
= imin
, i
235 if maxpower
is None or imax
>= maxpower
:
236 maxpower
, maxindex
= imax
, i
238 minrational
= preexp
.pres
[minindex
- 1]
240 minrational
= preexp
.pres
[-1]
242 if maxindex
!= len(preexp
.pres
) - 1:
243 maxrational
= preexp
.pres
[maxindex
+ 1]
245 maxrational
= preexp
.pres
[0]
248 min = float(minrational
) * float(preexp
.exp
) ** minpower
250 max = float(maxrational
) * float(preexp
.exp
) ** maxpower
253 def getticks(self
, min, max, preexp
, ticklevel
=None, labellevel
=None):
257 for f
in preexp
.pres
:
259 imin
= int(math
.ceil(math
.log(min / float(f
)) /
260 math
.log(preexp
.exp
) - 0.5 * self
.epsilon
))
261 imax
= int(math
.floor(math
.log(max / float(f
)) /
262 math
.log(preexp
.exp
) + 0.5 * self
.epsilon
))
263 for i
in range(imin
, imax
+ 1):
264 pos
= f
* tick
.rational((preexp
.exp
, 1), power
=i
)
265 thisticks
.append(tick
.tick((pos
.num
, pos
.denom
), ticklevel
= ticklevel
, labellevel
= labellevel
))
266 ticks
= tick
.mergeticklists(ticks
, thisticks
)
272 class autologarithmic(logarithmic
):
273 """partitioner to create several logarithmic paritions"""
275 defaultvariants
= [([logarithmic
.pre1exp
, # ticks
276 logarithmic
.pre1to9exp
], # subticks
277 [logarithmic
.pre1exp
, # labels
278 logarithmic
.pre125exp
]), # sublevels
280 ([logarithmic
.pre1exp
, # ticks
281 logarithmic
.pre1to9exp
], # subticks
282 None), # labels like ticks
284 ([logarithmic
.pre1exp2
, # ticks
285 logarithmic
.pre1exp
], # subticks
286 None), # labels like ticks
288 ([logarithmic
.pre1exp3
, # ticks
289 logarithmic
.pre1exp
], # subticks
290 None), # labels like ticks
292 ([logarithmic
.pre1exp4
, # ticks
293 logarithmic
.pre1exp
], # subticks
294 None), # labels like ticks
296 ([logarithmic
.pre1exp5
, # ticks
297 logarithmic
.pre1exp
], # subticks
298 None)] # labels like ticks
300 def __init__(self
, variants
=defaultvariants
, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
301 self
.variants
= variants
302 if len(variants
) > 2:
303 self
.variantsindex
= divmod(len(variants
), 2)[0]
305 self
.variantsindex
= 0
306 self
.extendtick
= extendtick
307 self
.extendlabel
= extendlabel
308 self
.epsilon
= epsilon
310 def partfunctions(self
, min, max, extendmin
, extendmax
):
311 return [lambda d
=_partdata(min=min, max=max, extendmin
=extendmin
, extendmax
=extendmax
,
312 sign
=1, variantsindex
=self
.variantsindex
-1):
313 self
.partfunction(d
),
314 lambda d
=_partdata(min=min, max=max, extendmin
=extendmin
, extendmax
=extendmax
,
315 sign
=-1, variantsindex
=self
.variantsindex
):
316 self
.partfunction(d
)]
318 def partfunction(self
, data
):
319 data
.variantsindex
+= data
.sign
320 if 0 <= data
.variantsindex
< len(self
.variants
):
321 logarithmicparter
= logarithmic(tickpreexps
=self
.variants
[data
.variantsindex
][0], labelpreexps
=self
.variants
[data
.variantsindex
][1],
322 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
323 return logarithmicparter
.partfunctions(min=data
.min, max=data
.max, extendmin
=data
.extendmin
, extendmax
=data
.extendmax
)[0]()
326 autolog
= autologarithmic