further adjust some copyright dates
[PyX/mjg.git] / pyx / graph / axis / parter.py
blobd30ad5b20b27bed420c4dba614e9a4ed7d7a46b2
1 #!/usr/bin/env python
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
27 import math
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.
34 class _partdata:
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.
41 """
43 def __init__(self, **kwargs):
44 for key, value in kwargs.items():
45 setattr(self, key, value)
48 class _parter:
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.
64 """
65 # This is a (useless) empty partitioner.
66 return []
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]),)
75 else:
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]),)
79 else:
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"""
89 if extendmin:
90 min = float(dist) * math.floor(min / float(dist) + self.epsilon)
91 if extendmax:
92 max = float(dist) * math.ceil(max / float(dist) - self.epsilon)
93 return min, max
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))
102 ticks = []
103 for i in range(imin, imax + 1):
104 ticks.append(tick.tick((i*dist.num, dist.denom), ticklevel=ticklevel, labellevel=labellevel))
105 return ticks
107 def partfunction(self, data):
108 if data.first:
109 data.first = 0
110 min = data.min
111 max = data.max
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)
117 ticks = []
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))
123 return ticks
125 return None
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)]
131 lin = linear
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))
151 else:
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):
164 if data.sign == 1:
165 if data.tickindex < len(self.variants) - 1:
166 data.tickindex += 1
167 else:
168 data.tickindex = 0
169 data.base.num *= 10
170 else:
171 if data.tickindex:
172 data.tickindex -= 1
173 else:
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]()
180 autolin = autolinear
183 class preexp:
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):
193 self.pres = pres
194 self.exp = 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],)
213 else:
214 self.ticklist = helper.ensuresequence(tickpos)
216 if labelpos is None and tickpos is not None:
217 self.labellist = (helper.ensuresequence(tickpos)[0],)
218 else:
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):
225 minpower = None
226 maxpower = None
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
236 if minindex:
237 minrational = preexp.pres[minindex - 1]
238 else:
239 minrational = preexp.pres[-1]
240 minpower -= 1
241 if maxindex != len(preexp.pres) - 1:
242 maxrational = preexp.pres[maxindex + 1]
243 else:
244 maxrational = preexp.pres[0]
245 maxpower += 1
246 if extendmin:
247 min = float(minrational) * float(preexp.exp) ** minpower
248 if extendmax:
249 max = float(maxrational) * float(preexp.exp) ** maxpower
250 return min, max
252 def getticks(self, min, max, preexp, ticklevel=None, labellevel=None):
253 ticks = []
254 minimin = 0
255 maximax = 0
256 for f in preexp.pres:
257 thisticks = []
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)
266 return ticks
268 log = logarithmic
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]
303 else:
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]()
323 return None
325 autolog = autologarithmic