~ubuntu-branches/ubuntu/trusty/pyx/trusty

« back to all changes in this revision

Viewing changes to pyx/graph/axis/parter.py

  • Committer: Bazaar Package Importer
  • Author(s): Graham Wilson
  • Date: 2004-12-25 06:42:57 UTC
  • Revision ID: james.westby@ubuntu.com-20041225064257-31469ij5uysqq302
Tags: upstream-0.7.1
Import upstream version 0.7.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: ISO-8859-1 -*-
 
3
#
 
4
#
 
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-2004 Andr� Wobst <wobsta@users.sourceforge.net>
 
8
#
 
9
# This file is part of PyX (http://pyx.sourceforge.net/).
 
10
#
 
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.
 
15
#
 
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.
 
20
#
 
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
24
 
 
25
 
 
26
import math
 
27
from pyx import helper
 
28
from pyx.graph.axis import tick
 
29
 
 
30
 
 
31
# partitioner (parter)
 
32
# please note the nomenclature:
 
33
# - a part (partition) is a list of tick instances; thus ticks `==' part
 
34
# - a parter (partitioner) is a class creating ticks
 
35
 
 
36
 
 
37
class _Iparter:
 
38
    """interface definition of a partition scheme
 
39
    partition schemes are used to create a list of ticks"""
 
40
 
 
41
    def defaultpart(self, min, max, extendmin, extendmax):
 
42
        """create a partition
 
43
        - returns an ordered list of ticks for the interval min to max
 
44
        - the interval is given in float numbers, thus an appropriate
 
45
          conversion to rational numbers has to be performed
 
46
        - extendmin and extendmax are booleans (integers)
 
47
        - when extendmin or extendmax is set, the ticks might
 
48
          extend the min-max range towards lower and higher
 
49
          ranges, respectively"""
 
50
 
 
51
    def lesspart(self):
 
52
        """create another partition which contains less ticks
 
53
        - this method is called several times after a call of defaultpart
 
54
        - returns an ordered list of ticks with less ticks compared to
 
55
          the partition returned by defaultpart and by previous calls
 
56
          of lesspart
 
57
        - the creation of a partition with strictly *less* ticks
 
58
          is not to be taken serious
 
59
        - the method might return None, when no other appropriate
 
60
          partition can be created"""
 
61
 
 
62
 
 
63
    def morepart(self):
 
64
        """create another partition which contains more ticks
 
65
        see lesspart, but increase the number of ticks"""
 
66
 
 
67
 
 
68
class linear:
 
69
    """linear partition scheme
 
70
    ticks and label distances are explicitly provided to the constructor"""
 
71
 
 
72
    __implements__ = _Iparter
 
73
 
 
74
    def __init__(self, tickdist=None, labeldist=None, extendtick=0, extendlabel=None, epsilon=1e-10):
 
75
        """configuration of the partition scheme
 
76
        - tickdist and labeldist should be a list, where the first value
 
77
          is the distance between ticks with ticklevel/labellevel 0,
 
78
          the second list for ticklevel/labellevel 1, etc.;
 
79
          a single entry is allowed without being a list
 
80
        - tickdist and labeldist values are passed to the rational constructor
 
81
        - when labeldist is None and tickdist is not None, the tick entries
 
82
          for ticklevel 0 are used for labels and vice versa (ticks<->labels)
 
83
        - extendtick allows for the extension of the range given to the
 
84
          defaultpart method to include the next tick with the specified
 
85
          level (None turns off this feature); note, that this feature is
 
86
          also disabled, when an axis prohibits its range extension by
 
87
          the extendmin/extendmax variables given to the defaultpart method
 
88
        - extendlabel is analogous to extendtick, but for labels
 
89
        - epsilon allows for exceeding the axis range by this relative
 
90
          value (relative to the axis range given to the defaultpart method)
 
91
          without creating another tick specified by extendtick/extendlabel"""
 
92
        if tickdist is None and labeldist is not None:
 
93
            self.ticklist = (tick.rational(helper.ensuresequence(labeldist)[0]),)
 
94
        else:
 
95
            self.ticklist = map(tick.rational, helper.ensuresequence(tickdist))
 
96
        if labeldist is None and tickdist is not None:
 
97
            self.labellist = (tick.rational(helper.ensuresequence(tickdist)[0]),)
 
98
        else:
 
99
            self.labellist = map(tick.rational, helper.ensuresequence(labeldist))
 
100
        self.extendtick = extendtick
 
101
        self.extendlabel = extendlabel
 
102
        self.epsilon = epsilon
 
103
 
 
104
    def extendminmax(self, min, max, dist, extendmin, extendmax):
 
105
        """return new min, max tuple extending the range min, max
 
106
        - dist is the tick distance to be used
 
107
        - extendmin and extendmax are booleans to allow for the extension"""
 
108
        if extendmin:
 
109
            min = float(dist) * math.floor(min / float(dist) + self.epsilon)
 
110
        if extendmax:
 
111
            max = float(dist) * math.ceil(max / float(dist) - self.epsilon)
 
112
        return min, max
 
113
 
 
114
    def getticks(self, min, max, dist, ticklevel=None, labellevel=None):
 
115
        """return a list of equal spaced ticks
 
116
        - the tick distance is dist, the ticklevel is set to ticklevel and
 
117
          the labellevel is set to labellevel
 
118
        - min, max is the range where ticks should be placed"""
 
119
        imin = int(math.ceil(min/float(dist) - 0.5*self.epsilon))
 
120
        imax = int(math.floor(max/float(dist) + 0.5*self.epsilon))
 
121
        ticks = []
 
122
        for i in range(imin, imax + 1):
 
123
            ticks.append(tick.tick((i*dist.num, dist.denom), ticklevel=ticklevel, labellevel=labellevel))
 
124
        return ticks
 
125
 
 
126
    def defaultpart(self, min, max, extendmin, extendmax):
 
127
        if self.extendtick is not None and len(self.ticklist) > self.extendtick:
 
128
            min, max = self.extendminmax(min, max, self.ticklist[self.extendtick], extendmin, extendmax)
 
129
        if self.extendlabel is not None and len(self.labellist) > self.extendlabel:
 
130
            min, max = self.extendminmax(min, max, self.labellist[self.extendlabel], extendmin, extendmax)
 
131
 
 
132
        ticks = []
 
133
        for i in range(len(self.ticklist)):
 
134
            ticks = tick.mergeticklists(ticks, self.getticks(min, max, self.ticklist[i], ticklevel = i))
 
135
        for i in range(len(self.labellist)):
 
136
            ticks = tick.mergeticklists(ticks, self.getticks(min, max, self.labellist[i], labellevel = i))
 
137
 
 
138
        return ticks
 
139
 
 
140
    def lesspart(self):
 
141
        return None
 
142
 
 
143
    def morepart(self):
 
144
        return None
 
145
 
 
146
lin = linear
 
147
 
 
148
 
 
149
class autolinear:
 
150
    """automatic linear partition scheme
 
151
    - possible tick distances are explicitly provided to the constructor
 
152
    - tick distances are adjusted to the axis range by multiplication or division by 10"""
 
153
 
 
154
    __implements__ = _Iparter
 
155
 
 
156
    defaultvariants = [[tick.rational((1, 1)), tick.rational((1, 2))],
 
157
                       [tick.rational((2, 1)), tick.rational((1, 1))],
 
158
                       [tick.rational((5, 2)), tick.rational((5, 4))],
 
159
                       [tick.rational((5, 1)), tick.rational((5, 2))]]
 
160
 
 
161
    def __init__(self, variants=defaultvariants, extendtick=0, epsilon=1e-10):
 
162
        """configuration of the partition scheme
 
163
        - variants is a list of tickdist
 
164
        - tickdist should be a list, where the first value
 
165
          is the distance between ticks with ticklevel 0,
 
166
          the second for ticklevel 1, etc.
 
167
        - tickdist values are passed to the rational constructor
 
168
        - labellevel is set to None except for those ticks in the partitions,
 
169
          where ticklevel is zero. There labellevel is also set to zero.
 
170
        - extendtick allows for the extension of the range given to the
 
171
          defaultpart method to include the next tick with the specified
 
172
          level (None turns off this feature); note, that this feature is
 
173
          also disabled, when an axis prohibits its range extension by
 
174
          the extendmin/extendmax variables given to the defaultpart method
 
175
        - epsilon allows for exceeding the axis range by this relative
 
176
          value (relative to the axis range given to the defaultpart method)
 
177
          without creating another tick specified by extendtick"""
 
178
        self.variants = variants
 
179
        self.extendtick = extendtick
 
180
        self.epsilon = epsilon
 
181
 
 
182
    def defaultpart(self, min, max, extendmin, extendmax):
 
183
        logmm = math.log(max - min) / math.log(10)
 
184
        if logmm < 0: # correction for rounding towards zero of the int routine
 
185
            base = tick.rational((10, 1), power=int(logmm-1))
 
186
        else:
 
187
            base = tick.rational((10, 1), power=int(logmm))
 
188
        ticks = map(tick.rational, self.variants[0])
 
189
        useticks = [t * base for t in ticks]
 
190
        self.lesstickindex = self.moretickindex = 0
 
191
        self.lessbase = tick.rational((base.num, base.denom))
 
192
        self.morebase = tick.rational((base.num, base.denom))
 
193
        self.min, self.max, self.extendmin, self.extendmax = min, max, extendmin, extendmax
 
194
        part = linear(tickdist=useticks, extendtick=self.extendtick, epsilon=self.epsilon)
 
195
        return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
 
196
 
 
197
    def lesspart(self):
 
198
        if self.lesstickindex < len(self.variants) - 1:
 
199
            self.lesstickindex += 1
 
200
        else:
 
201
            self.lesstickindex = 0
 
202
            self.lessbase.num *= 10
 
203
        ticks = map(tick.rational, self.variants[self.lesstickindex])
 
204
        useticks = [t * self.lessbase for t in ticks]
 
205
        part = linear(tickdist=useticks, extendtick=self.extendtick, epsilon=self.epsilon)
 
206
        return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
 
207
 
 
208
    def morepart(self):
 
209
        if self.moretickindex:
 
210
            self.moretickindex -= 1
 
211
        else:
 
212
            self.moretickindex = len(self.variants) - 1
 
213
            self.morebase.denom *= 10
 
214
        ticks = map(tick.rational, self.variants[self.moretickindex])
 
215
        useticks = [t * self.morebase for t in ticks]
 
216
        part = linear(tickdist=useticks, extendtick=self.extendtick, epsilon=self.epsilon)
 
217
        return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
 
218
 
 
219
autolin = autolinear
 
220
 
 
221
 
 
222
class preexp:
 
223
    """storage class for the definition of logarithmic axes partitions
 
224
    instances of this class define tick positions suitable for
 
225
    logarithmic axes by the following instance variables:
 
226
    - exp: integer, which defines multiplicator (usually 10)
 
227
    - pres: list of tick positions (rational numbers, e.g. instances of rational)
 
228
    possible positions are these tick positions and arbitrary divisions
 
229
    and multiplications by the exp value"""
 
230
 
 
231
    def __init__(self, pres, exp):
 
232
         "create a preexp instance and store its pres and exp information"
 
233
         self.pres = pres
 
234
         self.exp = exp
 
235
 
 
236
 
 
237
class logarithmic(linear):
 
238
    """logarithmic partition scheme
 
239
    ticks and label positions are explicitly provided to the constructor"""
 
240
 
 
241
    __implements__ = _Iparter
 
242
 
 
243
    pre1exp5   = preexp([tick.rational((1, 1))], 100000)
 
244
    pre1exp4   = preexp([tick.rational((1, 1))], 10000)
 
245
    pre1exp3   = preexp([tick.rational((1, 1))], 1000)
 
246
    pre1exp2   = preexp([tick.rational((1, 1))], 100)
 
247
    pre1exp    = preexp([tick.rational((1, 1))], 10)
 
248
    pre125exp  = preexp([tick.rational((1, 1)), tick.rational((2, 1)), tick.rational((5, 1))], 10)
 
249
    pre1to9exp = preexp([tick.rational((x, 1)) for x in range(1, 10)], 10)
 
250
    #  ^- we always include 1 in order to get extendto(tick|label)level to work as expected
 
251
 
 
252
    def __init__(self, tickpos=None, labelpos=None, extendtick=0, extendlabel=None, epsilon=1e-10):
 
253
        """configuration of the partition scheme
 
254
        - tickpos and labelpos should be a list, where the first entry
 
255
          is a preexp instance describing ticks with ticklevel/labellevel 0,
 
256
          the second is a preexp instance for ticklevel/labellevel 1, etc.;
 
257
          a single entry is allowed without being a list
 
258
        - when labelpos is None and tickpos is not None, the tick entries
 
259
          for ticklevel 0 are used for labels and vice versa (ticks<->labels)
 
260
        - extendtick allows for the extension of the range given to the
 
261
          defaultpart method to include the next tick with the specified
 
262
          level (None turns off this feature); note, that this feature is
 
263
          also disabled, when an axis prohibits its range extension by
 
264
          the extendmin/extendmax variables given to the defaultpart method
 
265
        - extendlabel is analogous to extendtick, but for labels
 
266
        - epsilon allows for exceeding the axis range by this relative
 
267
          logarithm value (relative to the logarithm axis range given
 
268
          to the defaultpart method) without creating another tick
 
269
          specified by extendtick/extendlabel"""
 
270
        if tickpos is None and labelpos is not None:
 
271
            self.ticklist = (helper.ensuresequence(labelpos)[0],)
 
272
        else:
 
273
            self.ticklist = helper.ensuresequence(tickpos)
 
274
 
 
275
        if labelpos is None and tickpos is not None:
 
276
            self.labellist = (helper.ensuresequence(tickpos)[0],)
 
277
        else:
 
278
            self.labellist = helper.ensuresequence(labelpos)
 
279
        self.extendtick = extendtick
 
280
        self.extendlabel = extendlabel
 
281
        self.epsilon = epsilon
 
282
 
 
283
    def extendminmax(self, min, max, preexp, extendmin, extendmax):
 
284
        """return new min, max tuple extending the range min, max
 
285
        preexp describes the allowed tick positions
 
286
        extendmin and extendmax are booleans to allow for the extension"""
 
287
        minpower = None
 
288
        maxpower = None
 
289
        for i in xrange(len(preexp.pres)):
 
290
            imin = int(math.floor(math.log(min / float(preexp.pres[i])) /
 
291
                                  math.log(preexp.exp) + self.epsilon)) + 1
 
292
            imax = int(math.ceil(math.log(max / float(preexp.pres[i])) /
 
293
                                 math.log(preexp.exp) - self.epsilon)) - 1
 
294
            if minpower is None or imin < minpower:
 
295
                minpower, minindex = imin, i
 
296
            if maxpower is None or imax >= maxpower:
 
297
                maxpower, maxindex = imax, i
 
298
        if minindex:
 
299
            minrational = preexp.pres[minindex - 1]
 
300
        else:
 
301
            minrational = preexp.pres[-1]
 
302
            minpower -= 1
 
303
        if maxindex != len(preexp.pres) - 1:
 
304
            maxrational = preexp.pres[maxindex + 1]
 
305
        else:
 
306
            maxrational = preexp.pres[0]
 
307
            maxpower += 1
 
308
        if extendmin:
 
309
            min = float(minrational) * float(preexp.exp) ** minpower
 
310
        if extendmax:
 
311
            max = float(maxrational) * float(preexp.exp) ** maxpower
 
312
        return min, max
 
313
 
 
314
    def getticks(self, min, max, preexp, ticklevel=None, labellevel=None):
 
315
        """return a list of ticks
 
316
        - preexp describes the allowed tick positions
 
317
        - the ticklevel of the ticks is set to ticklevel and
 
318
          the labellevel is set to labellevel
 
319
        -  min, max is the range where ticks should be placed"""
 
320
        ticks = []
 
321
        minimin = 0
 
322
        maximax = 0
 
323
        for f in preexp.pres:
 
324
            thisticks = []
 
325
            imin = int(math.ceil(math.log(min / float(f)) /
 
326
                                 math.log(preexp.exp) - 0.5 * self.epsilon))
 
327
            imax = int(math.floor(math.log(max / float(f)) /
 
328
                                  math.log(preexp.exp) + 0.5 * self.epsilon))
 
329
            for i in range(imin, imax + 1):
 
330
                pos = f * tick.rational((preexp.exp, 1), power=i)
 
331
                thisticks.append(tick.tick((pos.num, pos.denom), ticklevel = ticklevel, labellevel = labellevel))
 
332
            ticks = tick.mergeticklists(ticks, thisticks)
 
333
        return ticks
 
334
 
 
335
log = logarithmic
 
336
 
 
337
 
 
338
class autologarithmic(logarithmic):
 
339
    """automatic logarithmic partition scheme
 
340
    possible tick positions are explicitly provided to the constructor"""
 
341
 
 
342
    __implements__ = _Iparter
 
343
 
 
344
    defaultvariants = [([logarithmic.pre1exp,      # ticks
 
345
                         logarithmic.pre1to9exp],  # subticks
 
346
                        [logarithmic.pre1exp,      # labels
 
347
                         logarithmic.pre125exp]),  # sublevels
 
348
 
 
349
                       ([logarithmic.pre1exp,      # ticks
 
350
                         logarithmic.pre1to9exp],  # subticks
 
351
                        None),                     # labels like ticks
 
352
 
 
353
                       ([logarithmic.pre1exp2,     # ticks
 
354
                         logarithmic.pre1exp],     # subticks
 
355
                        None),                     # labels like ticks
 
356
 
 
357
                       ([logarithmic.pre1exp3,     # ticks
 
358
                         logarithmic.pre1exp],     # subticks
 
359
                        None),                     # labels like ticks
 
360
 
 
361
                       ([logarithmic.pre1exp4,     # ticks
 
362
                         logarithmic.pre1exp],     # subticks
 
363
                        None),                     # labels like ticks
 
364
 
 
365
                       ([logarithmic.pre1exp5,     # ticks
 
366
                         logarithmic.pre1exp],     # subticks
 
367
                        None)]                     # labels like ticks
 
368
 
 
369
    def __init__(self, variants=defaultvariants, extendtick=0, extendlabel=None, epsilon=1e-10):
 
370
        """configuration of the partition scheme
 
371
        - variants should be a list of pairs of lists of preexp
 
372
          instances
 
373
        - within each pair the first list contains preexp, where
 
374
          the first preexp instance describes ticks positions with
 
375
          ticklevel 0, the second preexp for ticklevel 1, etc.
 
376
        - the second list within each pair describes the same as
 
377
          before, but for labels
 
378
        - within each pair: when the second entry (for the labels) is None
 
379
          and the first entry (for the ticks) ticks is not None, the tick
 
380
          entries for ticklevel 0 are used for labels and vice versa
 
381
          (ticks<->labels)
 
382
        - extendtick allows for the extension of the range given to the
 
383
          defaultpart method to include the next tick with the specified
 
384
          level (None turns off this feature); note, that this feature is
 
385
          also disabled, when an axis prohibits its range extension by
 
386
          the extendmin/extendmax variables given to the defaultpart method
 
387
        - extendlabel is analogous to extendtick, but for labels
 
388
        - epsilon allows for exceeding the axis range by this relative
 
389
          logarithm value (relative to the logarithm axis range given
 
390
          to the defaultpart method) without creating another tick
 
391
          specified by extendtick/extendlabel"""
 
392
        self.variants = variants
 
393
        if len(variants) > 2:
 
394
            self.variantsindex = divmod(len(variants), 2)[0]
 
395
        else:
 
396
            self.variantsindex = 0
 
397
        self.extendtick = extendtick
 
398
        self.extendlabel = extendlabel
 
399
        self.epsilon = epsilon
 
400
 
 
401
    def defaultpart(self, min, max, extendmin, extendmax):
 
402
        self.min, self.max, self.extendmin, self.extendmax = min, max, extendmin, extendmax
 
403
        self.morevariantsindex = self.variantsindex
 
404
        self.lessvariantsindex = self.variantsindex
 
405
        part = logarithmic(tickpos=self.variants[self.variantsindex][0], labelpos=self.variants[self.variantsindex][1],
 
406
                           extendtick=self.extendtick, extendlabel=self.extendlabel, epsilon=self.epsilon)
 
407
        return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
 
408
 
 
409
    def lesspart(self):
 
410
        self.lessvariantsindex += 1
 
411
        if self.lessvariantsindex < len(self.variants):
 
412
            part = logarithmic(tickpos=self.variants[self.lessvariantsindex][0], labelpos=self.variants[self.lessvariantsindex][1],
 
413
                               extendtick=self.extendtick, extendlabel=self.extendlabel, epsilon=self.epsilon)
 
414
            return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
 
415
 
 
416
    def morepart(self):
 
417
        self.morevariantsindex -= 1
 
418
        if self.morevariantsindex >= 0:
 
419
            part = logarithmic(tickpos=self.variants[self.morevariantsindex][0], labelpos=self.variants[self.morevariantsindex][1],
 
420
                               extendtick=self.extendtick, extendlabel=self.extendlabel, epsilon=self.epsilon)
 
421
            return part.defaultpart(self.min, self.max, self.extendmin, self.extendmax)
 
422
 
 
423
autolog = autologarithmic