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-2004 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 fractions
import Fraction
26 from pyx
import text
, utils
27 from pyx
.graph
.axis
.tick
import tick
as Tick
31 def labels(self
, ticks
):
32 """fill the label attribute of ticks
33 - ticks is a list of instances of tick
34 - for each element of ticks the value of the attribute label is set to
35 a string or MultiEngineText instance appropriate to the attributes
36 num and denom of that tick instance
37 - label attributes of the tick instances are just kept, whenever they
39 - the method might modify the labelattrs attribute of the ticks; be sure
40 to not modify it in-place!"""
41 raise NotImplementedError
44 class decimal(_texter
):
45 "a texter creating decimal labels (e.g. '1.234' or even '0.\overline{3}')"
47 def __init__(self
, prefix
="", infix
="", suffix
="", equalprecision
=False,
48 decimalsep
=".", thousandsep
="", thousandthpartsep
="",
49 plus
="", minus
="-", period
=r
"\overline{%s}",
50 labelattrs
=[text
.mathmode
]):
51 r
"""initializes the instance
52 - prefix, infix, and suffix (strings) are added at the begin,
53 immediately after the minus, and at the end of the label,
55 - equalprecision forces the same number of digits after decimalsep,
56 even when the tailing digits are zero
57 - decimalsep, thousandsep, and thousandthpartsep (strings)
58 are used as separators
59 - plus or minus (string) is inserted for non-negative or negative numbers
60 - period (string) is taken as a format string generating a period;
61 it has to contain exactly one string insert operators "%s" for the
62 period; usually it should be r"\overline{%s}"
63 - labelattrs is a list of attributes to be added to the label attributes
64 given in the painter"""
68 self
.equalprecision
= equalprecision
69 self
.decimalsep
= decimalsep
70 self
.thousandsep
= thousandsep
71 self
.thousandthpartsep
= thousandthpartsep
75 self
.labelattrs
= labelattrs
77 def labels(self
, ticks
):
81 if tick
.label
is None and tick
.labellevel
is not None:
82 labeledticks
.append(tick
)
83 m
, n
= tick
.num
, tick
.denom
86 quotient
, remainder
= divmod(m
, n
)
87 quotient
= str(quotient
)
88 if len(self
.thousandsep
):
92 tick
.label
+= quotient
[i
]
93 if not ((l
-i
-1) % 3) and l
> i
+1:
94 tick
.label
+= self
.thousandsep
98 tick
.label
+= self
.decimalsep
100 tick
.temp_decprecision
= 0
102 tick
.temp_decprecision
+= 1
103 if remainder
in oldremainders
:
104 tick
.temp_decprecision
= None
105 periodstart
= len(tick
.label
) - (len(oldremainders
) - oldremainders
.index(remainder
))
106 tick
.label
= tick
.label
[:periodstart
] + self
.period
% tick
.label
[periodstart
:]
108 oldremainders
+= [remainder
]
110 quotient
, remainder
= divmod(remainder
, n
)
111 if not ((tick
.temp_decprecision
- 1) % 3) and tick
.temp_decprecision
> 1:
112 tick
.label
+= self
.thousandthpartsep
113 tick
.label
+= str(quotient
)
115 if maxdecprecision
< tick
.temp_decprecision
:
116 maxdecprecision
= tick
.temp_decprecision
117 if self
.equalprecision
:
118 for tick
in labeledticks
:
119 if tick
.temp_decprecision
is not None:
120 if tick
.temp_decprecision
== 0 and maxdecprecision
> 0:
121 tick
.label
+= self
.decimalsep
122 for i
in range(tick
.temp_decprecision
, maxdecprecision
):
123 if not ((i
- 1) % 3) and i
> 1:
124 tick
.label
+= self
.thousandthpartsep
126 for tick
in labeledticks
:
127 if tick
.num
* tick
.denom
< 0:
128 plusminus
= self
.minus
130 plusminus
= self
.plus
131 tick
.label
= "%s%s%s%s%s" % (self
.prefix
, plusminus
, self
.infix
, tick
.label
, self
.suffix
)
132 tick
.labelattrs
= tick
.labelattrs
+ self
.labelattrs
134 # del tick.temp_decprecision # we've inserted this temporary variable ... and do not care any longer about it
138 class skipmantissaunity
:
141 skipmantissaunity
.never
= 0
142 skipmantissaunity
.each
= 1
143 skipmantissaunity
.all
= 2
146 class default(_texter
):
148 "a texter creating regular (e.g. '2') and exponential (e.g. '2\cdot10^5') labels"
150 def __init__(self
, multiplication_tex
=r
"\cdot{}", multiplication_unicode
="·", base
=Fraction(10),
151 skipmantissaunity
=skipmantissaunity
.all
, minusunity
="-",
152 minexponent
=4, minnegexponent
=None, uniformexponent
=True,
153 mantissatexter
=decimal(), basetexter
=decimal(), exponenttexter
=decimal(),
154 labelattrs
=[text
.mathmode
]):
155 # , **kwargs): # future
156 r
"""initializes the instance
157 - multiplication_tex and multiplication_unicode are the strings to
158 indicate the multiplication between the mantissa and the base
159 number for the TexEngine and the UnicodeEngine, respecitvely
160 - base is the number of the base of the exponent
161 - skipmantissaunity is either skipmantissaunity.never (never skip the
162 unity mantissa), skipmantissaunity.each (skip the unity mantissa
163 whenever it occurs for each label separately), or skipmantissaunity.all
164 (skip the unity mantissa whenever if all labels happen to be
165 mantissafixed with unity)
166 - minusunity is used as the output of -unity for the mantissa
167 - minexponent is the minimal positive exponent value to be printed by
169 - minnegexponent is the minimal negative exponent value to be printed by
170 exponential notation, for None it is considered to be equal to minexponent
171 - uniformexponent forces all numbers to be written in exponential notation
172 when at least one label excets the limits for non-exponential
174 - mantissatexter, basetexter, and exponenttexter generate the texts
175 for the mantissa, basetexter, and exponenttexter
176 - labelattrs is a list of attributes to be added to the label attributes
177 given in the painter"""
178 self
.multiplication_tex
= multiplication_tex
179 self
.multiplication_unicode
= multiplication_unicode
181 self
.skipmantissaunity
= skipmantissaunity
182 self
.minusunity
= minusunity
183 self
.minexponent
= minexponent
184 self
.minnegexponent
= minnegexponent
if minnegexponent
is not None else minexponent
185 self
.uniformexponent
= uniformexponent
186 self
.mantissatexter
= mantissatexter
187 self
.basetexter
= basetexter
188 self
.exponenttexter
= exponenttexter
189 self
.labelattrs
= labelattrs
192 # kwargs = utils.kwsplit(kwargs, ['mantissatexter', 'basetexter', 'exponenttexter'])
193 # self.mantissatexter = mantissatexter(a=1, **kwargs['mantissatexter'])
194 # self.basetexter = basetexter(**kwargs['basetexter'])
195 # self.exponenttexter = exponenttexter(**kwargs['exponenttexter'])
197 def labels(self
, ticks
):
200 if tick
.label
is None and tick
.labellevel
is not None:
201 labeledticks
.append(tick
)
203 tick
.labelattrs
= tick
.labelattrs
+ self
.labelattrs
206 # express tick = tick.temp_sign * tick.temp_mantissa * self.base ** tick.temp_exponent with 1 <= temp_mantissa < self.base
207 # and decide whether a tick is to be written in exponential notation
208 tick
.temp_sign
= 1 if tick
>= 0 else -1
209 tick
.temp_mantissa
= abs(Fraction(tick
.num
, tick
.denom
))
210 tick
.temp_exponent
= 0
211 while tick
.temp_mantissa
>= self
.base
:
212 tick
.temp_exponent
+= 1
213 tick
.temp_mantissa
/= self
.base
214 while tick
.temp_mantissa
< 1:
215 tick
.temp_exponent
-= 1
216 tick
.temp_mantissa
*= self
.base
217 tick
.temp_wantexponent
= not (-self
.minnegexponent
< tick
.temp_exponent
< self
.minexponent
)
219 tick
.temp_mantissa
= tick
.temp_exponent
= 0
221 tick
.temp_wantexponent
= not (-self
.minnegexponent
< 0 < self
.minexponent
)
223 # make decision on exponential notation uniform if requested
224 if self
.uniformexponent
and any(tick
.temp_wantexponent
for tick
in labeledticks
):
225 for tick
in labeledticks
:
227 tick
.temp_wantexponent
= True
229 # mark mantissa == 1 to be not labeled
230 if self
.skipmantissaunity
== skipmantissaunity
.each
:
231 for tick
in labeledticks
:
232 if tick
.temp_wantexponent
and tick
.temp_mantissa
== 1:
233 tick
.temp_mantissa
= None
234 elif self
.skipmantissaunity
== skipmantissaunity
.all
and all(tick
.temp_mantissa
== 1 for tick
in labeledticks
if tick
.temp_wantexponent
):
235 for tick
in labeledticks
:
236 if tick
.temp_wantexponent
:
237 tick
.temp_mantissa
= None
240 basetick
= Tick(self
.base
, labellevel
=0)
241 self
.basetexter
.labels([basetick
])
242 for tick
in labeledticks
:
243 if tick
.temp_wantexponent
:
244 if tick
.temp_mantissa
is not None:
245 tick
.temp_mantissatick
= Tick(tick
.temp_sign
* tick
.temp_mantissa
, labellevel
=0)
246 tick
.temp_exponenttick
= Tick(tick
.temp_exponent
, labellevel
=0)
248 tick
.temp_mantissatick
= tick
250 self
.mantissatexter
.labels([tick
.temp_mantissatick
for tick
in labeledticks
if tick
.temp_mantissa
is not None])
251 self
.exponenttexter
.labels([tick
.temp_exponenttick
for tick
in labeledticks
if tick
.temp_wantexponent
])
252 for tick
in labeledticks
:
253 if tick
.temp_wantexponent
:
254 if tick
.temp_mantissa
is not None:
255 mantissalabel_tex
= tick
.temp_mantissatick
.label
+ self
.multiplication_tex
256 mantissalabel_unicode
= tick
.temp_mantissatick
.label
+ self
.multiplication_unicode
258 mantissalabel_tex
= self
.minusunity
if tick
.temp_sign
== -1 else ""
259 mantissalabel_unicode
= self
.minusunity
if tick
.temp_sign
== -1 else ""
260 tick
.label
= text
.MultiEngineText("%s%s^{%s}" % (mantissalabel_tex
, basetick
.label
, tick
.temp_exponenttick
.label
), [mantissalabel_unicode
+ basetick
.label
, text
.Text(tick
.temp_exponenttick
.label
, scale
=0.8, shift
=0.5)])
263 class rational(_texter
):
264 "a texter creating rational labels (e.g. 'a/b' or even 'a \over b')"
265 # we use divmod here to be more explicit
267 def __init__(self
, prefix
="", infix
="", suffix
="",
268 numprefix
="", numinfix
="", numsuffix
="",
269 denomprefix
="", denominfix
="", denomsuffix
="",
270 plus
="", minus
="-", minuspos
=0, over
=r
"{{%s}\over{%s}}",
271 equaldenom
=False, skip1
=True, skipnum0
=True, skipnum1
=True, skipdenom1
=True,
272 labelattrs
=[text
.mathmode
]):
273 r
"""initializes the instance
274 - prefix, infix, and suffix (strings) are added at the begin,
275 immediately after the minus, and at the end of the label,
277 - prefixnum, infixnum, and suffixnum (strings) are added
278 to the labels numerator correspondingly
279 - prefixdenom, infixdenom, and suffixdenom (strings) are added
280 to the labels denominator correspondingly
281 - plus or minus (string) is inserted for non-negative or negative numbers
282 - minuspos is an integer, which determines the position, where the
283 plus or minus sign has to be placed; the following values are allowed:
284 1 - writes the plus or minus in front of the numerator
285 0 - writes the plus or minus in front of the hole fraction
286 -1 - writes the plus or minus in front of the denominator
287 - over (string) is taken as a format string generating the
288 fraction bar; it has to contain exactly two string insert
289 operators "%s" -- the first for the numerator and the second
290 for the denominator; by far the most common examples are
291 r"{{%s}\over{%s}}" and "{{%s}/{%s}}"
292 - usually the numerator and denominator are canceled; however,
293 when equaldenom is set, the least common multiple of all
295 - skip1 (boolean) just prints the prefix, the plus or minus,
296 the infix and the suffix, when the value is plus or minus one
297 and at least one of prefix, infix and the suffix is present
298 - skipnum0 (boolean) just prints a zero instead of
299 the hole fraction, when the numerator is zero;
300 no prefixes, infixes, and suffixes are taken into account
301 - skipnum1 (boolean) just prints the numprefix, the plus or minus,
302 the numinfix and the numsuffix, when the num value is plus or minus one
303 and at least one of numprefix, numinfix and the numsuffix is present
304 - skipdenom1 (boolean) just prints the numerator instead of
305 the hole fraction, when the denominator is one and none of the parameters
306 denomprefix, denominfix and denomsuffix are set and minuspos is not -1 or the
308 - labelattrs is a list of attributes for a texrunners text method;
309 None is considered as an empty list; labelattrs might be changed
310 in the painter as well"""
314 self
.numprefix
= numprefix
315 self
.numinfix
= numinfix
316 self
.numsuffix
= numsuffix
317 self
.denomprefix
= denomprefix
318 self
.denominfix
= denominfix
319 self
.denomsuffix
= denomsuffix
322 self
.minuspos
= minuspos
324 self
.equaldenom
= equaldenom
326 self
.skipnum0
= skipnum0
327 self
.skipnum1
= skipnum1
328 self
.skipdenom1
= skipdenom1
329 self
.labelattrs
= labelattrs
332 """returns the greates common divisor of all elements in n
333 - the elements of n must be non-negative integers
334 - return None if the number of elements is zero
335 - the greates common divisor is not affected when some
336 of the elements are zero, but it becomes zero when
337 all elements are zero"""
343 i
, (dummy
, j
) = j
, divmod(i
, j
)
348 res
= self
.gcd(res
, i
)
352 """returns the least common multiple of all elements in n
353 - the elements of n must be non-negative integers
354 - return None if the number of elements is zero
355 - the least common multiple is zero when some of the
360 res
= divmod(res
* i
, self
.gcd(res
, i
))[0]
363 def labels(self
, ticks
):
366 if tick
.label
is None and tick
.labellevel
is not None:
367 labeledticks
.append(tick
)
368 tick
.temp_rationalnum
= tick
.num
369 tick
.temp_rationaldenom
= tick
.denom
370 tick
.temp_rationalminus
= 1
371 if tick
.temp_rationalnum
< 0:
372 tick
.temp_rationalminus
= -tick
.temp_rationalminus
373 tick
.temp_rationalnum
= -tick
.temp_rationalnum
374 if tick
.temp_rationaldenom
< 0:
375 tick
.temp_rationalminus
= -tick
.temp_rationalminus
376 tick
.temp_rationaldenom
= -tick
.temp_rationaldenom
377 gcd
= self
.gcd(tick
.temp_rationalnum
, tick
.temp_rationaldenom
)
378 (tick
.temp_rationalnum
, dummy1
), (tick
.temp_rationaldenom
, dummy2
) = divmod(tick
.temp_rationalnum
, gcd
), divmod(tick
.temp_rationaldenom
, gcd
)
380 equaldenom
= self
.lcm(*[tick
.temp_rationaldenom
for tick
in ticks
if tick
.label
is None])
381 if equaldenom
is not None:
382 for tick
in labeledticks
:
383 factor
, dummy
= divmod(equaldenom
, tick
.temp_rationaldenom
)
384 tick
.temp_rationalnum
, tick
.temp_rationaldenom
= factor
* tick
.temp_rationalnum
, factor
* tick
.temp_rationaldenom
385 for tick
in labeledticks
:
386 rationalminus
= rationalnumminus
= rationaldenomminus
= ""
387 if tick
.temp_rationalminus
== -1:
388 plusminus
= self
.minus
390 plusminus
= self
.plus
391 if self
.minuspos
== 0:
392 rationalminus
= plusminus
393 elif self
.minuspos
== 1:
394 rationalnumminus
= plusminus
395 elif self
.minuspos
== -1:
396 rationaldenomminus
= plusminus
398 raise RuntimeError("invalid minuspos")
399 if self
.skipnum0
and tick
.temp_rationalnum
== 0:
401 elif (self
.skip1
and self
.skipdenom1
and tick
.temp_rationalnum
== 1 and tick
.temp_rationaldenom
== 1 and
402 (len(self
.prefix
) or len(self
.infix
) or len(self
.suffix
)) and
403 not len(rationalnumminus
) and not len(self
.numprefix
) and not len(self
.numinfix
) and not len(self
.numsuffix
) and
404 not len(rationaldenomminus
) and not len(self
.denomprefix
) and not len(self
.denominfix
) and not len(self
.denomsuffix
)):
405 tick
.label
= "%s%s%s%s" % (self
.prefix
, rationalminus
, self
.infix
, self
.suffix
)
407 if self
.skipnum1
and tick
.temp_rationalnum
== 1 and (len(self
.numprefix
) or len(self
.numinfix
) or len(self
.numsuffix
)):
408 tick
.temp_rationalnum
= "%s%s%s%s" % (self
.numprefix
, rationalnumminus
, self
.numinfix
, self
.numsuffix
)
410 tick
.temp_rationalnum
= "%s%s%s%i%s" % (self
.numprefix
, rationalnumminus
, self
.numinfix
, tick
.temp_rationalnum
, self
.numsuffix
)
411 if self
.skipdenom1
and tick
.temp_rationaldenom
== 1 and not len(rationaldenomminus
) and not len(self
.denomprefix
) and not len(self
.denominfix
) and not len(self
.denomsuffix
):
412 tick
.label
= "%s%s%s%s%s" % (self
.prefix
, rationalminus
, self
.infix
, tick
.temp_rationalnum
, self
.suffix
)
414 tick
.temp_rationaldenom
= "%s%s%s%i%s" % (self
.denomprefix
, rationaldenomminus
, self
.denominfix
, tick
.temp_rationaldenom
, self
.denomsuffix
)
415 tick
.label
= text
.MultiEngineText("%s%s%s%s%s" % (self
.prefix
, rationalminus
, self
.infix
, self
.over
% (tick
.temp_rationalnum
, tick
.temp_rationaldenom
), self
.suffix
),
416 ["%s%s%s" % (self
.prefix
, rationalminus
, self
.infix
)] + [text
.StackedText([text
.Text(tick
.temp_rationalnum
, shift
=0.3), text
.Text(tick
.temp_rationaldenom
, shift
=-0.9)], frac
=True, align
=0.5)] + [self
.suffix
])
417 tick
.labelattrs
= tick
.labelattrs
+ self
.labelattrs
419 # del tick.temp_rationalnum # we've inserted those temporary variables ... and do not care any longer about them
420 # del tick.temp_rationaldenom
421 # del tick.temp_rationalminus