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-2006 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 pre1exp
= preexp([tick
.rational((1, 1))], 10)
203 pre125exp
= preexp([tick
.rational((1, 1)), tick
.rational((2, 1)), tick
.rational((5, 1))], 10)
204 pre1to9exp
= preexp([tick
.rational((x
, 1)) for x
in range(1, 10)], 10)
205 # ^- we always include 1 in order to get extendto(tick|label)level to work as expected
207 def __init__(self
, tickpreexps
=None, labelpreexps
=None, extendtick
=0, extendlabel
=None, epsilon
=1e-10):
208 if tickpreexps
is None and labelpreexps
is not None:
209 self
.ticklist
= [labelpreexps
[0]]
211 self
.ticklist
= tickpreexps
213 if labelpreexps
is None and tickpreexps
is not None:
214 self
.labellist
= [tickpreexps
[0]]
216 self
.labellist
= labelpreexps
217 self
.extendtick
= extendtick
218 self
.extendlabel
= extendlabel
219 self
.epsilon
= epsilon
221 def extendminmax(self
, min, max, preexp
, extendmin
, extendmax
):
224 for i
in xrange(len(preexp
.pres
)):
225 imin
= int(math
.floor(math
.log(min / float(preexp
.pres
[i
])) /
226 math
.log(preexp
.exp
) + self
.epsilon
)) + 1
227 imax
= int(math
.ceil(math
.log(max / float(preexp
.pres
[i
])) /
228 math
.log(preexp
.exp
) - self
.epsilon
)) - 1
229 if minpower
is None or imin
< minpower
:
230 minpower
, minindex
= imin
, i
231 if maxpower
is None or imax
>= maxpower
:
232 maxpower
, maxindex
= imax
, i
234 minrational
= preexp
.pres
[minindex
- 1]
236 minrational
= preexp
.pres
[-1]
238 if maxindex
!= len(preexp
.pres
) - 1:
239 maxrational
= preexp
.pres
[maxindex
+ 1]
241 maxrational
= preexp
.pres
[0]
244 min = float(minrational
) * float(preexp
.exp
) ** minpower
246 max = float(maxrational
) * float(preexp
.exp
) ** maxpower
249 def getticks(self
, min, max, preexp
, ticklevel
=None, labellevel
=None):
253 for f
in preexp
.pres
:
255 imin
= int(math
.ceil(math
.log(min / float(f
)) /
256 math
.log(preexp
.exp
) - 0.5 * self
.epsilon
))
257 imax
= int(math
.floor(math
.log(max / float(f
)) /
258 math
.log(preexp
.exp
) + 0.5 * self
.epsilon
))
259 for i
in range(imin
, imax
+ 1):
260 pos
= f
* tick
.rational((preexp
.exp
, 1), power
=i
)
261 thisticks
.append(tick
.tick((pos
.num
, pos
.denom
), ticklevel
= ticklevel
, labellevel
= labellevel
))
262 ticks
= tick
.mergeticklists(ticks
, thisticks
)
268 class autologarithmic(logarithmic
):
269 """partitioner to create several logarithmic paritions"""
271 defaultvariants
= [([logarithmic
.pre1exp
, # ticks
272 logarithmic
.pre1to9exp
], # subticks
273 [logarithmic
.pre1exp
, # labels
274 logarithmic
.pre125exp
]), # sublevels
276 ([logarithmic
.pre1exp
, # ticks
277 logarithmic
.pre1to9exp
], # subticks
278 None)] # labels like ticks
280 def __init__(self
, variants
=defaultvariants
, extendtick
=0, extendlabel
=None, autoexponent
=10, epsilon
=1e-10):
281 self
.variants
= variants
282 self
.extendtick
= extendtick
283 self
.extendlabel
= extendlabel
284 self
.autoexponent
= autoexponent
285 self
.epsilon
= epsilon
287 def partfunctions(self
, min, max, extendmin
, extendmax
):
288 return [lambda d
=_partdata(min=min, max=max, extendmin
=extendmin
, extendmax
=extendmax
,
289 variantsindex
=len(self
.variants
)):
290 self
.variantspartfunction(d
),
291 lambda d
=_partdata(min=min, max=max, extendmin
=extendmin
, extendmax
=extendmax
,
292 exponent
=self
.autoexponent
):
293 self
.autopartfunction(d
)]
295 def variantspartfunction(self
, data
):
296 data
.variantsindex
-= 1
297 if 0 <= data
.variantsindex
:
298 logarithmicparter
= logarithmic(tickpreexps
=self
.variants
[data
.variantsindex
][0], labelpreexps
=self
.variants
[data
.variantsindex
][1],
299 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
300 return logarithmicparter
.partfunctions(min=data
.min, max=data
.max, extendmin
=data
.extendmin
, extendmax
=data
.extendmax
)[0]()
303 def autopartfunction(self
, data
):
304 data
.exponent
*= self
.autoexponent
305 logarithmicparter
= logarithmic(tickpreexps
=[preexp([tick
.rational((1, 1))], data
.exponent
), logarithmic
.pre1exp
],
306 extendtick
=self
.extendtick
, extendlabel
=self
.extendlabel
, epsilon
=self
.epsilon
)
307 return logarithmicparter
.partfunctions(min=data
.min, max=data
.max, extendmin
=data
.extendmin
, extendmax
=data
.extendmax
)[0]()
309 autolog
= autologarithmic