remove shebang -- see comment 3 on https://bugzilla.redhat.com/bugzilla/show_bug...
[PyX/mjg.git] / pyx / graph / axis / tick.py
blob4d07c9d43f3e82e539a969a93baaa084b01da630
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
25 import sys
27 # test automatic long conversion
28 try:
29 sys.maxint+1
30 autolong = 1
31 except OverflowError:
32 autolong = 0
35 class rational:
36 """rational class performing some basic rational arithmetics
37 the axis partitioning uses rational arithmetics (with infinite accuracy)
38 basically it contains self.num and self.denom"""
40 def initfromstring(self, s):
41 "converts a string 0.123 into a rational"
42 expparts = s.strip().replace("E", "e").split("e")
43 if len(expparts) > 2:
44 raise ValueError("multiple 'e' found in '%s'" % s)
45 commaparts = expparts[0].split(".")
46 if len(commaparts) > 2:
47 raise ValueError("multiple '.' found in '%s'" % expparts[0])
48 if len(commaparts) == 1:
49 commaparts = [commaparts[0], ""]
50 self.num = 1
51 if autolong:
52 self.denom = 10 ** len(commaparts[1])
53 else:
54 self.denom = 10L ** len(commaparts[1])
55 neg = len(commaparts[0]) and commaparts[0][0] == "-"
56 if neg:
57 commaparts[0] = commaparts[0][1:]
58 elif len(commaparts[0]) and commaparts[0][0] == "+":
59 commaparts[0] = commaparts[0][1:]
60 if len(commaparts[0]):
61 if not commaparts[0].isdigit():
62 raise ValueError("unrecognized characters in '%s'" % s)
63 try:
64 x = int(commaparts[0])
65 except:
66 x = long(commaparts[0])
67 else:
68 x = 0
69 if len(commaparts[1]):
70 if not commaparts[1].isdigit():
71 raise ValueError("unrecognized characters in '%s'" % s)
72 try:
73 y = int(commaparts[1])
74 except:
75 y = long(commaparts[1])
76 else:
77 y = 0
78 self.num = x*self.denom + y
79 if neg:
80 self.num = -self.num
81 if len(expparts) == 2:
82 neg = expparts[1][0] == "-"
83 if neg:
84 expparts[1] = expparts[1][1:]
85 elif expparts[1][0] == "+":
86 expparts[1] = expparts[1][1:]
87 if not expparts[1].isdigit():
88 raise ValueError("unrecognized characters in '%s'" % s)
89 if neg:
90 if autolong:
91 self.denom *= 10 ** int(expparts[1])
92 else:
93 self.denom *= 10L ** int(expparts[1])
94 else:
95 if autolong:
96 self.num *= 10 ** int(expparts[1])
97 else:
98 self.num *= 10L ** int(expparts[1])
100 def initfromfloat(self, x, floatprecision):
101 "converts a float into a rational with finite resolution"
102 if floatprecision < 0:
103 raise RuntimeError("float resolution must be non-negative")
104 self.initfromstring(("%%.%ig" % floatprecision) % x)
106 def __init__(self, x, power=1, floatprecision=10):
107 """initializes a rational
108 - rational=(num/denom)**power
109 - x must be one of:
110 - a string (like "1.2", "1.2e3", "1.2/3.4", etc.)
111 - a float (converted using floatprecision)
112 - a sequence of two integers
113 - a rational instance"""
114 if power == 0:
115 self.num = 1
116 self.denom = 1
117 return
118 try:
119 # does x behave like a number
120 x + 0
121 except:
122 try:
123 # does x behave like a string
124 x + ""
125 except:
126 try:
127 # x might be a tuple
128 self.num, self.denom = x
129 except:
130 # otherwise it should have a num and denom
131 self.num, self.denom = x.num, x.denom
132 else:
133 # x is a string
134 fraction = x.split("/")
135 if len(fraction) > 2:
136 raise ValueError("multiple '/' found in '%s'" % x)
137 self.initfromstring(fraction[0])
138 if len(fraction) == 2:
139 self /= rational(fraction[1])
140 else:
141 # x is a number
142 self.initfromfloat(x, floatprecision)
143 if not self.denom: raise ZeroDivisionError("zero denominator")
144 if power == -1:
145 self.num, self.denom = self.denom, self.num
146 elif power < -1:
147 if autolong:
148 self.num, self.denom = self.denom ** (-power), self.num ** (-power)
149 else:
150 self.num, self.denom = long(self.denom) ** (-power), long(self.num) ** (-power)
151 elif power > 1:
152 if autolong:
153 self.num = self.num ** power
154 self.denom = self.denom ** power
155 else:
156 self.num = long(self.num) ** power
157 self.denom = long(self.denom) ** power
159 def __cmp__(self, other):
160 try:
161 return cmp(self.num * other.denom, other.num * self.denom)
162 except:
163 return cmp(float(self), other)
165 def __abs__(self):
166 return rational((abs(self.num), abs(self.denom)))
168 def __add__(self, other):
169 assert abs(other) < 1e-10
170 return float(self)
172 def __mul__(self, other):
173 return rational((self.num * other.num, self.denom * other.denom))
175 def __imul__(self, other):
176 self.num *= other.num
177 self.denom *= other.denom
178 return self
180 def __div__(self, other):
181 return rational((self.num * other.denom, self.denom * other.num))
183 def __idiv__(self, other):
184 self.num *= other.denom
185 self.denom *= other.num
186 return self
188 def __float__(self):
189 "caution: avoid final precision of floats"
190 return float(self.num) / self.denom
192 def __str__(self):
193 return "%i/%i" % (self.num, self.denom)
196 class tick(rational):
197 """tick class
198 a tick is a rational enhanced by
199 - self.ticklevel (0 = tick, 1 = subtick, etc.)
200 - self.labellevel (0 = label, 1 = sublabel, etc.)
201 - self.label (a string) and self.labelattrs (a list, defaults to [])
202 When ticklevel or labellevel is None, no tick or label is present at that value.
203 When label is None, it should be automatically created (and stored), once the
204 an axis painter needs it. Classes, which implement _Itexter do precisely that."""
206 def __init__(self, x, ticklevel=0, labellevel=0, label=None, labelattrs=[], **kwargs):
207 """initializes the instance
208 - see class description for the parameter description
209 - **kwargs are passed to the rational constructor"""
210 rational.__init__(self, x, **kwargs)
211 self.ticklevel = ticklevel
212 self.labellevel = labellevel
213 self.label = label
214 self.labelattrs = labelattrs
216 def merge(self, other):
217 """merges two ticks together:
218 - the lower ticklevel/labellevel wins
219 - the ticks should be at the same position (otherwise it doesn't make sense)
220 -> this is NOT checked"""
221 if self.ticklevel is None or (other.ticklevel is not None and other.ticklevel < self.ticklevel):
222 self.ticklevel = other.ticklevel
223 if self.labellevel is None or (other.labellevel is not None and other.labellevel < self.labellevel):
224 self.labellevel = other.labellevel
225 if self.label is None:
226 self.label = other.label
229 def mergeticklists(list1, list2, mergeequal=1):
230 """helper function to merge tick lists
231 - return a merged list of ticks out of list1 and list2
232 - CAUTION: original lists have to be ordered
233 (the returned list is also ordered)"""
234 # TODO: improve along the lines of http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/305269
236 # do not destroy original lists
237 list1 = list1[:]
238 i = 0
239 j = 0
240 try:
241 while 1: # we keep on going until we reach an index error
242 while list2[j] < list1[i]: # insert tick
243 list1.insert(i, list2[j])
244 i += 1
245 j += 1
246 if list2[j] == list1[i]: # merge tick
247 if mergeequal:
248 list1[i].merge(list2[j])
249 j += 1
250 i += 1
251 except IndexError:
252 if j < len(list2):
253 list1 += list2[j:]
254 return list1
257 def maxlevels(ticks):
258 "returns a tuple maxticklevel, maxlabellevel from a list of tick instances"
259 maxticklevel = maxlabellevel = 0
260 for tick in ticks:
261 if tick.ticklevel is not None and tick.ticklevel >= maxticklevel:
262 maxticklevel = tick.ticklevel + 1
263 if tick.labellevel is not None and tick.labellevel >= maxlabellevel:
264 maxlabellevel = tick.labellevel + 1
265 return maxticklevel, maxlabellevel