remove shebang -- see comment 3 on https://bugzilla.redhat.com/bugzilla/show_bug...
[PyX/mjg.git] / pyx / graph / axis / parter.py
blob1c90a4dd07c6a83479d08a221816785944587694
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
26 import math
27 from pyx.graph.axis import tick
30 # Note: A partition is a list of ticks.
32 class _partdata:
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.
39 """
41 def __init__(self, **kwargs):
42 for key, value in kwargs.items():
43 setattr(self, key, value)
46 class _parter:
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.
62 """
63 # This is a (useless) empty partitioner.
64 return []
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])]
73 else:
74 self.ticklist = map(tick.rational, tickdists)
75 if labeldists is None and tickdists is not None:
76 self.labellist = [tick.rational(tickdists[0])]
77 else:
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"""
87 if extendmin:
88 min = float(dist) * math.floor(min / float(dist) + self.epsilon)
89 if extendmax:
90 max = float(dist) * math.ceil(max / float(dist) - self.epsilon)
91 return min, max
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))
100 ticks = []
101 for i in range(imin, imax + 1):
102 ticks.append(tick.tick((i*dist.num, dist.denom), ticklevel=ticklevel, labellevel=labellevel))
103 return ticks
105 def partfunction(self, data):
106 if data.first:
107 data.first = 0
108 min = data.min
109 max = data.max
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)
115 ticks = []
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))
121 return ticks
123 return None
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)]
129 lin = linear
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):
146 try:
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))
152 else:
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):
165 if data.sign == 1:
166 if data.tickindex < len(self.variants) - 1:
167 data.tickindex += 1
168 else:
169 data.tickindex = 0
170 data.base.num *= 10
171 else:
172 if data.tickindex:
173 data.tickindex -= 1
174 else:
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]()
181 autolin = autolinear
184 class preexp:
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):
194 self.pres = pres
195 self.exp = 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]]
214 else:
215 self.ticklist = tickpreexps
217 if labelpreexps is None and tickpreexps is not None:
218 self.labellist = [tickpreexps[0]]
219 else:
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):
226 minpower = None
227 maxpower = None
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
237 if minindex:
238 minrational = preexp.pres[minindex - 1]
239 else:
240 minrational = preexp.pres[-1]
241 minpower -= 1
242 if maxindex != len(preexp.pres) - 1:
243 maxrational = preexp.pres[maxindex + 1]
244 else:
245 maxrational = preexp.pres[0]
246 maxpower += 1
247 if extendmin:
248 min = float(minrational) * float(preexp.exp) ** minpower
249 if extendmax:
250 max = float(maxrational) * float(preexp.exp) ** maxpower
251 return min, max
253 def getticks(self, min, max, preexp, ticklevel=None, labellevel=None):
254 ticks = []
255 minimin = 0
256 maximax = 0
257 for f in preexp.pres:
258 thisticks = []
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)
267 return ticks
269 log = logarithmic
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]
304 else:
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]()
324 return None
326 autolog = autologarithmic