update upload data
[PyX.git] / pyx / graph / axis / parter.py
blob03788aa3069a36d476f68af53a1e243279aefa06
1 # -*- encoding: utf-8 -*-
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 import math
25 from pyx.graph.axis import tick
28 # Note: A partition is a list of ticks.
30 class _partdata:
31 """state storage class for a partfunction
33 partdata is used to keep local data and a current state to emulate
34 generators. In the future we might use yield statements within a
35 partfunction. Currently we add partdata by a lambda construct and
36 do inplace modifications within partdata to keep track of the state.
37 """
39 def __init__(self, **kwargs):
40 for key, value in kwargs.items():
41 setattr(self, key, value)
44 class _parter:
45 """interface of a partitioner"""
47 def partfunctions(self, min, max, extendmin, extendmax):
48 """returns a list of partfunctions
50 A partfunction can be called without further arguments and
51 it will return a new partition each time, or None. Several
52 partfunctions are used to walk in different "directions"
53 (like more and less partitions).
55 Note that we do not alternate walking in different directions
56 (i.e. alternate the partfunction calls). Instead we first walk
57 into one direction (which should give less and less ticks) until
58 the rating becomes bad and when try more ticks. We want to keep
59 the number of ticks small compared to a simple alternate search.
60 """
61 # This is a (useless) empty partitioner.
62 return []
65 class linear(_parter):
66 """partitioner to create a single linear partition"""
68 def __init__(self, tickdists=None, labeldists=None, extendtick=0, extendlabel=None, epsilon=1e-10):
69 if tickdists is None and labeldists is not None:
70 self.ticklist = [tick.rational(labeldists[0])]
71 else:
72 self.ticklist = map(tick.rational, tickdists)
73 if labeldists is None and tickdists is not None:
74 self.labellist = [tick.rational(tickdists[0])]
75 else:
76 self.labellist = map(tick.rational, labeldists)
77 self.extendtick = extendtick
78 self.extendlabel = extendlabel
79 self.epsilon = epsilon
81 def extendminmax(self, min, max, dist, extendmin, extendmax):
82 """return new min, max tuple extending the range min, max
83 - dist is the tick distance to be used
84 - extendmin and extendmax are booleans to allow for the extension"""
85 if extendmin:
86 min = float(dist) * math.floor(min / float(dist) + self.epsilon)
87 if extendmax:
88 max = float(dist) * math.ceil(max / float(dist) - self.epsilon)
89 return min, max
91 def getticks(self, min, max, dist, ticklevel=None, labellevel=None):
92 """return a list of equal spaced ticks
93 - the tick distance is dist, the ticklevel is set to ticklevel and
94 the labellevel is set to labellevel
95 - min, max is the range where ticks should be placed"""
96 imin = int(math.ceil(min/float(dist) - 0.5*self.epsilon))
97 imax = int(math.floor(max/float(dist) + 0.5*self.epsilon))
98 ticks = []
99 for i in range(imin, imax + 1):
100 ticks.append(tick.tick((i*dist.num, dist.denom), ticklevel=ticklevel, labellevel=labellevel))
101 return ticks
103 def partfunction(self, data):
104 if data.first:
105 data.first = 0
106 min = data.min
107 max = data.max
108 if self.extendtick is not None and len(self.ticklist) > self.extendtick:
109 min, max = self.extendminmax(min, max, self.ticklist[self.extendtick], data.extendmin, data.extendmax)
110 if self.extendlabel is not None and len(self.labellist) > self.extendlabel:
111 min, max = self.extendminmax(min, max, self.labellist[self.extendlabel], data.extendmin, data.extendmax)
113 ticks = []
114 for i in range(len(self.ticklist)):
115 ticks = tick.mergeticklists(ticks, self.getticks(min, max, self.ticklist[i], ticklevel=i))
116 for i in range(len(self.labellist)):
117 ticks = tick.mergeticklists(ticks, self.getticks(min, max, self.labellist[i], labellevel=i))
119 return ticks
121 return None
123 def partfunctions(self, min, max, extendmin, extendmax):
124 return [lambda d=_partdata(first=1, min=min, max=max, extendmin=extendmin, extendmax=extendmax):
125 self.partfunction(d)]
127 lin = linear
130 class autolinear(_parter):
131 """partitioner to create an arbitrary number of linear partitions"""
133 defaultvariants = [[tick.rational((1, 1)), tick.rational((1, 2))],
134 [tick.rational((2, 1)), tick.rational((1, 1))],
135 [tick.rational((5, 2)), tick.rational((5, 4))],
136 [tick.rational((5, 1)), tick.rational((5, 2))]]
138 def __init__(self, variants=defaultvariants, extendtick=0, epsilon=1e-10):
139 self.variants = variants
140 self.extendtick = extendtick
141 self.epsilon = epsilon
143 def partfunctions(self, min, max, extendmin, extendmax):
144 try:
145 logmm = math.log(max - min) / math.log(10)
146 except ArithmeticError:
147 raise RuntimeError("partitioning failed due to empty or invalid axis range")
148 if logmm < 0: # correction for rounding towards zero of the int routine
149 base = tick.rational((10, 1), power=int(logmm-1))
150 else:
151 base = tick.rational((10, 1), power=int(logmm))
152 ticks = map(tick.rational, self.variants[0])
153 useticks = [t * base for t in ticks]
155 return [lambda d=_partdata(min=min, max=max, extendmin=extendmin, extendmax=extendmax,
156 sign=1, tickindex=-1, base=tick.rational(base)):
157 self.partfunction(d),
158 lambda d=_partdata(min=min, max=max, extendmin=extendmin, extendmax=extendmax,
159 sign=-1, tickindex=0, base=tick.rational(base)):
160 self.partfunction(d)]
162 def partfunction(self, data):
163 if data.sign == 1:
164 if data.tickindex < len(self.variants) - 1:
165 data.tickindex += 1
166 else:
167 data.tickindex = 0
168 data.base.num *= 10
169 else:
170 if data.tickindex:
171 data.tickindex -= 1
172 else:
173 data.tickindex = len(self.variants) - 1
174 data.base.denom *= 10
175 tickdists = [tick.rational(t) * data.base for t in self.variants[data.tickindex]]
176 linearparter = linear(tickdists=tickdists, extendtick=self.extendtick, epsilon=self.epsilon)
177 return linearparter.partfunctions(min=data.min, max=data.max, extendmin=data.extendmin, extendmax=data.extendmax)[0]()
179 autolin = autolinear
182 class preexp:
183 """definition of a logarithmic partition
185 exp is an integer, which defines multiplicator (usually 10).
186 pres are a list of tick positions (rational numbers, e.g.
187 instances of rational). possible positions are the tick
188 positions and arbitrary divisions and multiplications of
189 the tick positions by exp."""
191 def __init__(self, pres, exp):
192 self.pres = pres
193 self.exp = exp
196 class logarithmic(linear):
197 """partitioner to create a single logarithmic partition"""
199 # define some useful constants
200 pre1exp = preexp([tick.rational((1, 1))], 10)
201 pre125exp = preexp([tick.rational((1, 1)), tick.rational((2, 1)), tick.rational((5, 1))], 10)
202 pre1to9exp = preexp([tick.rational((x, 1)) for x in range(1, 10)], 10)
203 # ^- we always include 1 in order to get extendto(tick|label)level to work as expected
205 def __init__(self, tickpreexps=None, labelpreexps=None, extendtick=0, extendlabel=None, epsilon=1e-10):
206 if tickpreexps is None and labelpreexps is not None:
207 self.ticklist = [labelpreexps[0]]
208 else:
209 self.ticklist = tickpreexps
211 if labelpreexps is None and tickpreexps is not None:
212 self.labellist = [tickpreexps[0]]
213 else:
214 self.labellist = labelpreexps
215 self.extendtick = extendtick
216 self.extendlabel = extendlabel
217 self.epsilon = epsilon
219 def extendminmax(self, min, max, preexp, extendmin, extendmax):
220 minpower = None
221 maxpower = None
222 for i in xrange(len(preexp.pres)):
223 imin = int(math.floor(math.log(min / float(preexp.pres[i])) /
224 math.log(preexp.exp) + self.epsilon)) + 1
225 imax = int(math.ceil(math.log(max / float(preexp.pres[i])) /
226 math.log(preexp.exp) - self.epsilon)) - 1
227 if minpower is None or imin < minpower:
228 minpower, minindex = imin, i
229 if maxpower is None or imax >= maxpower:
230 maxpower, maxindex = imax, i
231 if minindex:
232 minrational = preexp.pres[minindex - 1]
233 else:
234 minrational = preexp.pres[-1]
235 minpower -= 1
236 if maxindex != len(preexp.pres) - 1:
237 maxrational = preexp.pres[maxindex + 1]
238 else:
239 maxrational = preexp.pres[0]
240 maxpower += 1
241 if extendmin:
242 min = float(minrational) * float(preexp.exp) ** minpower
243 if extendmax:
244 max = float(maxrational) * float(preexp.exp) ** maxpower
245 return min, max
247 def getticks(self, min, max, preexp, ticklevel=None, labellevel=None):
248 ticks = []
249 minimin = 0
250 maximax = 0
251 for f in preexp.pres:
252 thisticks = []
253 imin = int(math.ceil(math.log(min / float(f)) /
254 math.log(preexp.exp) - 0.5 * self.epsilon))
255 imax = int(math.floor(math.log(max / float(f)) /
256 math.log(preexp.exp) + 0.5 * self.epsilon))
257 for i in range(imin, imax + 1):
258 pos = f * tick.rational((preexp.exp, 1), power=i)
259 thisticks.append(tick.tick((pos.num, pos.denom), ticklevel = ticklevel, labellevel = labellevel))
260 ticks = tick.mergeticklists(ticks, thisticks)
261 return ticks
263 log = logarithmic
266 class autologarithmic(logarithmic):
267 """partitioner to create several logarithmic partitions"""
269 defaultvariants = [([logarithmic.pre1exp, # ticks
270 logarithmic.pre1to9exp], # subticks
271 [logarithmic.pre1exp, # labels
272 logarithmic.pre125exp]), # sublevels
274 ([logarithmic.pre1exp, # ticks
275 logarithmic.pre1to9exp], # subticks
276 None)] # labels like ticks
278 def __init__(self, variants=defaultvariants, extendtick=0, extendlabel=None, autoexponent=10, epsilon=1e-10):
279 self.variants = variants
280 self.extendtick = extendtick
281 self.extendlabel = extendlabel
282 self.autoexponent = autoexponent
283 self.epsilon = epsilon
285 def partfunctions(self, min, max, extendmin, extendmax):
286 return [lambda d=_partdata(min=min, max=max, extendmin=extendmin, extendmax=extendmax,
287 variantsindex=len(self.variants)):
288 self.variantspartfunction(d),
289 lambda d=_partdata(min=min, max=max, extendmin=extendmin, extendmax=extendmax,
290 exponent=self.autoexponent):
291 self.autopartfunction(d)]
293 def variantspartfunction(self, data):
294 data.variantsindex -= 1
295 if 0 <= data.variantsindex:
296 logarithmicparter= logarithmic(tickpreexps=self.variants[data.variantsindex][0], labelpreexps=self.variants[data.variantsindex][1],
297 extendtick=self.extendtick, extendlabel=self.extendlabel, epsilon=self.epsilon)
298 return logarithmicparter.partfunctions(min=data.min, max=data.max, extendmin=data.extendmin, extendmax=data.extendmax)[0]()
299 return None
301 def autopartfunction(self, data):
302 data.exponent *= self.autoexponent
303 logarithmicparter= logarithmic(tickpreexps=[preexp([tick.rational((1, 1))], data.exponent), logarithmic.pre1exp],
304 extendtick=self.extendtick, extendlabel=self.extendlabel, epsilon=self.epsilon)
305 return logarithmicparter.partfunctions(min=data.min, max=data.max, extendmin=data.extendmin, extendmax=data.extendmax)[0]()
307 autolog = autologarithmic