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

« back to all changes in this revision

Viewing changes to pyx/graph/axis/painter.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 canvas, color, attr, text, style, unit, box, path
 
28
from pyx import trafo as trafomodule
 
29
from pyx.graph.axis import tick
 
30
 
 
31
 
 
32
goldenmean = 0.5 * (math.sqrt(5) + 1)
 
33
 
 
34
 
 
35
class axiscanvas(canvas.canvas):
 
36
    """axis canvas
 
37
    - an axis canvas is a regular canvas returned by an
 
38
      axispainters painter method
 
39
    - it contains a PyX length extent to be used for the
 
40
      alignment of additional axes; the axis extent should
 
41
      be handled by the axispainters painter method; you may
 
42
      apprehend this as a size information comparable to a
 
43
      bounding box, which must be handled manually
 
44
    - it contains a list of textboxes called labels which are
 
45
      used to rate the distances between the labels if needed
 
46
      by the axis later on; the painter method has not only to
 
47
      insert the labels into this canvas, but should also fill
 
48
      this list, when a rating of the distances should be
 
49
      performed by the axis"""
 
50
 
 
51
    # __implements__ = sole implementation
 
52
 
 
53
    def __init__(self, *args, **kwargs):
 
54
        """initializes the instance
 
55
        - sets extent to zero
 
56
        - sets labels to an empty list"""
 
57
        canvas.canvas.__init__(self, *args, **kwargs)
 
58
        self.extent = 0
 
59
        self.labels = []
 
60
 
 
61
 
 
62
class rotatetext:
 
63
    """create rotations accordingly to tick directions
 
64
    - upsidedown rotations are suppressed by rotating them by another 180 degree"""
 
65
 
 
66
    # __implements__ = sole implementation
 
67
 
 
68
    def __init__(self, direction, epsilon=1e-10):
 
69
        """initializes the instance
 
70
        - direction is an angle to be used relative to the tick direction
 
71
        - epsilon is the value by which 90 degrees can be exceeded before
 
72
          an 180 degree rotation is added"""
 
73
        self.direction = direction
 
74
        self.epsilon = epsilon
 
75
 
 
76
    def trafo(self, dx, dy):
 
77
        """returns a rotation transformation accordingly to the tick direction
 
78
        - dx and dy are the direction of the tick"""
 
79
        direction = self.direction + math.atan2(dy, dx) * 180 / math.pi
 
80
        while (direction > 180 + self.epsilon):
 
81
            direction -= 360
 
82
        while (direction < -180 - self.epsilon):
 
83
            direction += 360
 
84
        while (direction > 90 + self.epsilon):
 
85
            direction -= 180
 
86
        while (direction < -90 - self.epsilon):
 
87
            direction += 180
 
88
        return trafomodule.rotate(direction)
 
89
 
 
90
 
 
91
rotatetext.parallel = rotatetext(90)
 
92
rotatetext.orthogonal = rotatetext(180)
 
93
 
 
94
 
 
95
class _Iaxispainter:
 
96
    "class for painting axes"
 
97
 
 
98
    def paint(self, axispos, axis, ac=None):
 
99
        """paint the axis into an axiscanvas
 
100
        - returns the axiscanvas
 
101
        - when no axiscanvas is provided (the typical case), a new
 
102
          axiscanvas is created. however, when extending an painter
 
103
          by inheritance, painting on the same axiscanvas is supported
 
104
          by setting the axiscanvas attribute
 
105
        - axispos is an instance, which implements _Iaxispos to
 
106
          define the tick positions
 
107
        - the axis and should not be modified (we may
 
108
          add some temporary variables like axis.ticks[i].temp_xxx,
 
109
          which might be used just temporary) -- the idea is that
 
110
          all things can be used several times
 
111
        - also do not modify the instance (self) -- even this
 
112
          instance might be used several times; thus do not modify
 
113
          attributes like self.titleattrs etc. (use local copies)
 
114
        - the method might access some additional attributes from
 
115
          the axis, e.g. the axis title -- the axis painter should
 
116
          document this behavior and rely on the availability of
 
117
          those attributes -> it becomes a question of the proper
 
118
          usage of the combination of axis & axispainter
 
119
        - the axiscanvas is a axiscanvas instance and should be
 
120
          filled with ticks, labels, title, etc.; note that the
 
121
          extent and labels instance variables should be handled
 
122
          as documented in the axiscanvas"""
 
123
 
 
124
 
 
125
class _Iaxispos:
 
126
    """interface definition of axis tick position methods
 
127
    - these methods are used for the postitioning of the ticks
 
128
      when painting an axis"""
 
129
    # TODO: should we add a local transformation (for label text etc?)
 
130
    #       (this might replace tickdirection (and even tickposition?))
 
131
 
 
132
    def basepath(self, x1=None, x2=None):
 
133
        """return the basepath as a path
 
134
        - x1 is the start position; if not set, the basepath starts
 
135
          from the beginning of the axis, which might imply a
 
136
          value outside of the graph coordinate range [0; 1]
 
137
        - x2 is analogous to x1, but for the end position"""
 
138
 
 
139
    def vbasepath(self, v1=None, v2=None):
 
140
        """return the basepath as a path
 
141
        - like basepath, but for graph coordinates"""
 
142
 
 
143
    def gridpath(self, x):
 
144
        """return the gridpath as a path for a given position x
 
145
        - might return None when no gridpath is available"""
 
146
 
 
147
    def vgridpath(self, v):
 
148
        """return the gridpath as a path for a given position v
 
149
        in graph coordinates
 
150
        - might return None when no gridpath is available"""
 
151
 
 
152
    def tickpoint_pt(self, x):
 
153
        """return the position at the basepath as a tuple (x, y) in
 
154
        postscript points for the position x"""
 
155
 
 
156
    def tickpoint(self, x):
 
157
        """return the position at the basepath as a tuple (x, y) in
 
158
        in PyX length for the position x"""
 
159
 
 
160
    def vtickpoint_pt(self, v):
 
161
        "like tickpoint_pt, but for graph coordinates"
 
162
 
 
163
    def vtickpoint(self, v):
 
164
        "like tickpoint, but for graph coordinates"
 
165
 
 
166
    def tickdirection(self, x):
 
167
        """return the direction of a tick as a tuple (dx, dy) for the
 
168
        position x (the direction points towards the graph)"""
 
169
 
 
170
    def vtickdirection(self, v):
 
171
        """like tickposition, but for graph coordinates"""
 
172
 
 
173
 
 
174
class _axispos:
 
175
    """implements those parts of _Iaxispos which can be build
 
176
    out of the axis convert method and other _Iaxispos methods
 
177
    - base _Iaxispos methods, which need to be implemented:
 
178
      - vbasepath
 
179
      - vgridpath
 
180
      - vtickpoint_pt
 
181
      - vtickdirection
 
182
    - other methods needed for _Iaxispos are build out of those
 
183
      listed above when this class is inherited"""
 
184
 
 
185
    def __init__(self, convert):
 
186
        """initializes the instance
 
187
        - convert is a convert method from an axis"""
 
188
        self.convert = convert
 
189
 
 
190
    def basepath(self, x1=None, x2=None):
 
191
        if x1 is None:
 
192
            if x2 is None:
 
193
                return self.vbasepath()
 
194
            else:
 
195
                return self.vbasepath(v2=self.convert(x2))
 
196
        else:
 
197
            if x2 is None:
 
198
                return self.vbasepath(v1=self.convert(x1))
 
199
            else:
 
200
                return self.vbasepath(v1=self.convert(x1), v2=self.convert(x2))
 
201
 
 
202
    def gridpath(self, x):
 
203
        return self.vgridpath(self.convert(x))
 
204
 
 
205
    def tickpoint_pt(self, x):
 
206
        return self.vtickpoint_pt(self.convert(x))
 
207
 
 
208
    def tickpoint(self, x):
 
209
        return self.vtickpoint(self.convert(x))
 
210
 
 
211
    def vtickpoint(self, v):
 
212
        return [x * unit.t_pt for x in self.vtickpoint(v)]
 
213
 
 
214
    def tickdirection(self, x):
 
215
        return self.vtickdirection(self.convert(x))
 
216
 
 
217
 
 
218
class pathaxispos(_axispos):
 
219
    """axis tick position methods along an arbitrary path"""
 
220
 
 
221
    __implements__ = _Iaxispos
 
222
 
 
223
    def __init__(self, p, convert, direction=1):
 
224
        self.path = p
 
225
        self.normpath = p.normpath()
 
226
        self.arclen_pt = self.normpath.arclen_pt()
 
227
        self.arclen = self.arclen_pt * unit.t_pt
 
228
        _axispos.__init__(self, convert)
 
229
        self.direction = direction
 
230
 
 
231
    def vbasepath(self, v1=None, v2=None):
 
232
        if v1 is None:
 
233
            if v2 is None:
 
234
                return self.path
 
235
            else:
 
236
                return self.normpath.split(self.normpath.arclentoparam(v2 * self.arclen))[0]
 
237
        else:
 
238
            if v2 is None:
 
239
                return self.normpath.split(self.normpath.arclentoparam(v1 * self.arclen))[1]
 
240
            else:
 
241
                return self.normpath.split(*self.normpath.arclentoparam([v1 * self.arclen, v2 * self.arclen]))[1]
 
242
 
 
243
    def vgridpath(self, v):
 
244
        return None
 
245
 
 
246
    def vtickpoint_pt(self, v):
 
247
        return self.normpath.at_pt(self.normpath.arclentoparam(v * self.arclen))
 
248
 
 
249
    def vtickdirection(self, v):
 
250
        t= self.normpath.tangent(self.normpath.arclentoparam(v * self.arclen))
 
251
        tbegin = t.begin_pt()
 
252
        tend = t.end_pt()
 
253
        dx = tend[0]-tbegin[0]
 
254
        dy = tend[1]-tbegin[1]
 
255
        norm = math.hypot(dx, dy)
 
256
        if self.direction == 1:
 
257
            return -dy/norm, dx/norm
 
258
        elif self.direction == -1:
 
259
            return dy/norm, -dx/norm
 
260
        raise RuntimeError("unknown direction")
 
261
 
 
262
 
 
263
class _title:
 
264
    """class for painting an axis title
 
265
    - the axis must have a title attribute when using this painter;
 
266
      this title might be None"""
 
267
 
 
268
    __implements__ = _Iaxispainter
 
269
 
 
270
    defaulttitleattrs = [text.halign.center, text.vshift.mathaxis]
 
271
 
 
272
    def __init__(self, titledist=0.3*unit.v_cm,
 
273
                       titleattrs=[],
 
274
                       titledirection=rotatetext.parallel,
 
275
                       titlepos=0.5,
 
276
                       texrunner=text.defaulttexrunner):
 
277
        """initialized the instance
 
278
        - titledist is a visual PyX length giving the distance
 
279
          of the title from the axis extent already there (a title might
 
280
          be added after labels or other things are plotted already)
 
281
        - titleattrs is a list of attributes for a texrunners text
 
282
          method; a single is allowed without being a list; None
 
283
          turns off the title
 
284
        - titledirection is an instance of rotatetext or None
 
285
        - titlepos is the position of the title in graph coordinates
 
286
        - texrunner is the texrunner to be used to create text
 
287
          (the texrunner is available for further use in derived
 
288
          classes as instance variable texrunner)"""
 
289
        self.titledist = titledist
 
290
        self.titleattrs = titleattrs
 
291
        self.titledirection = titledirection
 
292
        self.titlepos = titlepos
 
293
        self.texrunner = texrunner
 
294
 
 
295
    def paint(self, axispos, axis, ac=None):
 
296
        if ac is None:
 
297
            ac = axiscanvas()
 
298
        if axis.title is not None and self.titleattrs is not None:
 
299
            x, y = axispos.vtickpoint_pt(self.titlepos)
 
300
            dx, dy = axispos.vtickdirection(self.titlepos)
 
301
            titleattrs = self.defaulttitleattrs + self.titleattrs
 
302
            if self.titledirection is not None:
 
303
                titleattrs.append(self.titledirection.trafo(dx, dy))
 
304
            title = self.texrunner.text_pt(x, y, axis.title, titleattrs)
 
305
            ac.extent += self.titledist
 
306
            title.linealign(ac.extent, -dx, -dy)
 
307
            ac.extent += title.extent(dx, dy)
 
308
            ac.insert(title)
 
309
        return ac
 
310
 
 
311
 
 
312
class geometricseries(attr.changeattr):
 
313
 
 
314
    def __init__(self, initial, factor):
 
315
        self.initial = initial
 
316
        self.factor = factor
 
317
 
 
318
    def select(self, index, total):
 
319
        return self.initial * (self.factor ** index)
 
320
 
 
321
 
 
322
class ticklength(geometricseries): pass
 
323
 
 
324
_base = 0.12 * unit.v_cm
 
325
 
 
326
ticklength.SHORT = ticklength(_base/math.sqrt(64), 1/goldenmean)
 
327
ticklength.SHORt = ticklength(_base/math.sqrt(32), 1/goldenmean)
 
328
ticklength.SHOrt = ticklength(_base/math.sqrt(16), 1/goldenmean)
 
329
ticklength.SHort = ticklength(_base/math.sqrt(8), 1/goldenmean)
 
330
ticklength.Short = ticklength(_base/math.sqrt(4), 1/goldenmean)
 
331
ticklength.short = ticklength(_base/math.sqrt(2), 1/goldenmean)
 
332
ticklength.normal = ticklength(_base, 1/goldenmean)
 
333
ticklength.long = ticklength(_base*math.sqrt(2), 1/goldenmean)
 
334
ticklength.Long = ticklength(_base*math.sqrt(4), 1/goldenmean)
 
335
ticklength.LOng = ticklength(_base*math.sqrt(8), 1/goldenmean)
 
336
ticklength.LONg = ticklength(_base*math.sqrt(16), 1/goldenmean)
 
337
ticklength.LONG = ticklength(_base*math.sqrt(32), 1/goldenmean)
 
338
 
 
339
 
 
340
class regular(_title):
 
341
    """class for painting the ticks and labels of an axis
 
342
    - the inherited _title is used to paint the title of
 
343
      the axis
 
344
    - note that the type of the elements of ticks given as an argument
 
345
      of the paint method must be suitable for the tick position methods
 
346
      of the axis"""
 
347
 
 
348
    __implements__ = _Iaxispainter
 
349
 
 
350
    defaulttickattrs = []
 
351
    defaultgridattrs = []
 
352
    defaultbasepathattrs = [style.linecap.square]
 
353
    defaultlabelattrs = [text.halign.center, text.vshift.mathaxis]
 
354
 
 
355
    def __init__(self, innerticklength=ticklength.normal,
 
356
                       outerticklength=None,
 
357
                       tickattrs=[],
 
358
                       gridattrs=None,
 
359
                       basepathattrs=[],
 
360
                       labeldist=0.3*unit.v_cm,
 
361
                       labelattrs=[],
 
362
                       labeldirection=None,
 
363
                       labelhequalize=0,
 
364
                       labelvequalize=1,
 
365
                       **kwargs):
 
366
        """initializes the instance
 
367
        - innerticklength and outerticklength are changable
 
368
          visual PyX lengths for ticks, subticks, etc. plotted inside
 
369
          and outside of the graph; None turns off ticks inside or
 
370
          outside of the graph
 
371
        - tickattrs are a list of stroke attributes for the ticks;
 
372
          None turns off ticks
 
373
        - gridattrs are a list of lists used as stroke
 
374
          attributes for ticks, subticks etc.; None turns off
 
375
          the grid
 
376
        - basepathattrs are a list of stroke attributes for the base line
 
377
          of the axis; None turns off the basepath
 
378
        - labeldist is a visual PyX length for the distance of the labels
 
379
          from the axis basepath
 
380
        - labelattrs is a list of attributes for a texrunners text
 
381
          method; None turns off the labels
 
382
        - labeldirection is an instance of rotatetext or None
 
383
        - labelhequalize and labelvequalize (booleans) perform an equal
 
384
          alignment for straight vertical and horizontal axes, respectively
 
385
        - futher keyword arguments are passed to _axistitle"""
 
386
        self.innerticklength = innerticklength
 
387
        self.outerticklength = outerticklength
 
388
        self.tickattrs = tickattrs
 
389
        self.gridattrs = gridattrs
 
390
        self.basepathattrs = basepathattrs
 
391
        self.labeldist = labeldist
 
392
        self.labelattrs = labelattrs
 
393
        self.labeldirection = labeldirection
 
394
        self.labelhequalize = labelhequalize
 
395
        self.labelvequalize = labelvequalize
 
396
        _title.__init__(self, **kwargs)
 
397
 
 
398
    def paint(self, axispos, axis, ac=None):
 
399
        if ac is None:
 
400
            ac = axiscanvas()
 
401
        for t in axis.ticks:
 
402
            t.temp_v = axis.convert(t)
 
403
            t.temp_x, t.temp_y = axispos.vtickpoint_pt(t.temp_v)
 
404
            t.temp_dx, t.temp_dy = axispos.vtickdirection(t.temp_v)
 
405
        maxticklevel, maxlabellevel = tick.maxlevels(axis.ticks)
 
406
 
 
407
        # create & align t.temp_labelbox
 
408
        for t in axis.ticks:
 
409
            if t.labellevel is not None:
 
410
                labelattrs = attr.selectattrs(self.labelattrs, t.labellevel, maxlabellevel)
 
411
                if labelattrs is not None:
 
412
                    labelattrs = self.defaultlabelattrs + labelattrs
 
413
                    if self.labeldirection is not None:
 
414
                        labelattrs.append(self.labeldirection.trafo(t.temp_dx, t.temp_dy))
 
415
                    if t.labelattrs is not None:
 
416
                        labelattrs.extend(t.labelattrs)
 
417
                    t.temp_labelbox = self.texrunner.text_pt(t.temp_x, t.temp_y, t.label, labelattrs)
 
418
        if len(axis.ticks) > 1:
 
419
            equaldirection = 1
 
420
            for t in axis.ticks[1:]:
 
421
                if t.temp_dx != axis.ticks[0].temp_dx or t.temp_dy != axis.ticks[0].temp_dy:
 
422
                    equaldirection = 0
 
423
        else:
 
424
            equaldirection = 0
 
425
        if equaldirection and ((not axis.ticks[0].temp_dx and self.labelvequalize) or
 
426
                               (not axis.ticks[0].temp_dy and self.labelhequalize)):
 
427
            if self.labelattrs is not None:
 
428
                box.linealignequal([t.temp_labelbox for t in axis.ticks if t.labellevel is not None],
 
429
                                   self.labeldist, -axis.ticks[0].temp_dx, -axis.ticks[0].temp_dy)
 
430
        else:
 
431
            for t in axis.ticks:
 
432
                if t.labellevel is not None and self.labelattrs is not None:
 
433
                    t.temp_labelbox.linealign(self.labeldist, -t.temp_dx, -t.temp_dy)
 
434
 
 
435
        for t in axis.ticks:
 
436
            if t.ticklevel is not None:
 
437
                tickattrs = attr.selectattrs(self.defaulttickattrs + self.tickattrs, t.ticklevel, maxticklevel)
 
438
                if tickattrs is not None:
 
439
                    innerticklength = attr.selectattr(self.innerticklength, t.ticklevel, maxticklevel)
 
440
                    outerticklength = attr.selectattr(self.outerticklength, t.ticklevel, maxticklevel)
 
441
                    if innerticklength is not None or outerticklength is not None:
 
442
                        if innerticklength is None:
 
443
                            innerticklength = 0
 
444
                        if outerticklength is None:
 
445
                            outerticklength = 0
 
446
                        innerticklength_pt = unit.topt(innerticklength)
 
447
                        outerticklength_pt = unit.topt(outerticklength)
 
448
                        x1 = t.temp_x + t.temp_dx * innerticklength_pt
 
449
                        y1 = t.temp_y + t.temp_dy * innerticklength_pt
 
450
                        x2 = t.temp_x - t.temp_dx * outerticklength_pt
 
451
                        y2 = t.temp_y - t.temp_dy * outerticklength_pt
 
452
                        ac.stroke(path.line_pt(x1, y1, x2, y2), tickattrs)
 
453
                        if outerticklength is not None and outerticklength > ac.extent:
 
454
                            ac.extent = outerticklength
 
455
                        if outerticklength is not None and -innerticklength > ac.extent:
 
456
                            ac.extent = -innerticklength
 
457
            if self.gridattrs is not None:
 
458
                gridattrs = attr.selectattrs(self.defaultgridattrs + self.gridattrs, t.ticklevel, maxticklevel)
 
459
                if gridattrs is not None:
 
460
                    ac.stroke(axispos.vgridpath(t.temp_v), gridattrs)
 
461
            if t.labellevel is not None and self.labelattrs is not None:
 
462
                ac.insert(t.temp_labelbox)
 
463
                ac.labels.append(t.temp_labelbox)
 
464
                extent = t.temp_labelbox.extent(t.temp_dx, t.temp_dy) + self.labeldist
 
465
                if extent > ac.extent:
 
466
                    ac.extent = extent
 
467
        if self.basepathattrs is not None:
 
468
            ac.stroke(axispos.vbasepath(), self.defaultbasepathattrs + self.basepathattrs)
 
469
 
 
470
        # for t in axis.ticks:
 
471
        #     del t.temp_v    # we've inserted those temporary variables ... and do not care any longer about them
 
472
        #     del t.temp_x
 
473
        #     del t.temp_y
 
474
        #     del t.temp_dx
 
475
        #     del t.temp_dy
 
476
        #     if t.labellevel is not None and self.labelattrs is not None:
 
477
        #         del t.temp_labelbox
 
478
 
 
479
        _title.paint(self, axispos, axis, ac=ac)
 
480
 
 
481
        return ac
 
482
 
 
483
 
 
484
class linked(regular):
 
485
    """class for painting a linked axis
 
486
    - the inherited regular is used to paint the axis
 
487
    - modifies some constructor defaults"""
 
488
 
 
489
    __implements__ = _Iaxispainter
 
490
 
 
491
    def __init__(self, labelattrs=None,
 
492
                       titleattrs=None,
 
493
                       **kwargs):
 
494
        """initializes the instance
 
495
        - the labelattrs default is set to None thus skipping the labels
 
496
        - the titleattrs default is set to None thus skipping the title
 
497
        - all keyword arguments are passed to regular"""
 
498
        regular.__init__(self, labelattrs=labelattrs,
 
499
                               titleattrs=titleattrs,
 
500
                               **kwargs)
 
501
 
 
502
 
 
503
class subaxispos:
 
504
    """implementation of the _Iaxispos interface for a subaxis"""
 
505
 
 
506
    __implements__ = _Iaxispos
 
507
 
 
508
    def __init__(self, convert, baseaxispos, vmin, vmax, vminover, vmaxover):
 
509
        """initializes the instance
 
510
        - convert is the subaxis convert method
 
511
        - baseaxispos is the axispos instance of the base axis
 
512
        - vmin, vmax is the range covered by the subaxis in graph coordinates
 
513
        - vminover, vmaxover is the extended range of the subaxis including
 
514
          regions between several subaxes (for basepath drawing etc.)"""
 
515
        self.convert = convert
 
516
        self.baseaxispos = baseaxispos
 
517
        self.vmin = vmin
 
518
        self.vmax = vmax
 
519
        self.vminover = vminover
 
520
        self.vmaxover = vmaxover
 
521
 
 
522
    def basepath(self, x1=None, x2=None):
 
523
        if x1 is not None:
 
524
            v1 = self.vmin+self.convert(x1)*(self.vmax-self.vmin)
 
525
        else:
 
526
            v1 = self.vminover
 
527
        if x2 is not None:
 
528
            v2 = self.vmin+self.convert(x2)*(self.vmax-self.vmin)
 
529
        else:
 
530
            v2 = self.vmaxover
 
531
        return self.baseaxispos.vbasepath(v1, v2)
 
532
 
 
533
    def vbasepath(self, v1=None, v2=None):
 
534
        if v1 is not None:
 
535
            v1 = self.vmin+v1*(self.vmax-self.vmin)
 
536
        else:
 
537
            v1 = self.vminover
 
538
        if v2 is not None:
 
539
            v2 = self.vmin+v2*(self.vmax-self.vmin)
 
540
        else:
 
541
            v2 = self.vmaxover
 
542
        return self.baseaxispos.vbasepath(v1, v2)
 
543
 
 
544
    def gridpath(self, x):
 
545
        return self.baseaxispos.vgridpath(self.vmin+self.convert(x)*(self.vmax-self.vmin))
 
546
 
 
547
    def vgridpath(self, v):
 
548
        return self.baseaxispos.vgridpath(self.vmin+v*(self.vmax-self.vmin))
 
549
 
 
550
    def tickpoint_pt(self, x, axis=None):
 
551
        return self.baseaxispos.vtickpoint_pt(self.vmin+self.convert(x)*(self.vmax-self.vmin))
 
552
 
 
553
    def tickpoint(self, x, axis=None):
 
554
        return self.baseaxispos.vtickpoint(self.vmin+self.convert(x)*(self.vmax-self.vmin))
 
555
 
 
556
    def vtickpoint_pt(self, v, axis=None):
 
557
        return self.baseaxispos.vtickpoint_pt(self.vmin+v*(self.vmax-self.vmin))
 
558
 
 
559
    def vtickpoint(self, v, axis=None):
 
560
        return self.baseaxispos.vtickpoint(self.vmin+v*(self.vmax-self.vmin))
 
561
 
 
562
    def tickdirection(self, x, axis=None):
 
563
        return self.baseaxispos.vtickdirection(self.vmin+self.convert(x)*(self.vmax-self.vmin))
 
564
 
 
565
    def vtickdirection(self, v, axis=None):
 
566
        return self.baseaxispos.vtickdirection(self.vmin+v*(self.vmax-self.vmin))
 
567
 
 
568
 
 
569
class split(_title):
 
570
    """class for painting a splitaxis
 
571
    - the inherited _title is used to paint the title of
 
572
      the axis
 
573
    - the splitaxis access the subaxes attribute of the axis"""
 
574
 
 
575
    __implements__ = _Iaxispainter
 
576
 
 
577
    defaultbreaklinesattrs = []
 
578
 
 
579
    def __init__(self, breaklinesdist=0.05*unit.v_cm,
 
580
                       breaklineslength=0.5*unit.v_cm,
 
581
                       breaklinesangle=-60,
 
582
                       breaklinesattrs=[],
 
583
                       **args):
 
584
        """initializes the instance
 
585
        - breaklinesdist is a visual length of the distance between
 
586
          the two lines of the axis break
 
587
        - breaklineslength is a visual length of the length of the
 
588
          two lines of the axis break
 
589
        - breaklinesangle is the angle of the lines of the axis break
 
590
        - breaklinesattrs are a list of stroke attributes for the
 
591
          axis break lines; a single entry is allowed without being a
 
592
          list; None turns off the break lines
 
593
        - futher keyword arguments are passed to _title"""
 
594
        self.breaklinesdist = breaklinesdist
 
595
        self.breaklineslength = breaklineslength
 
596
        self.breaklinesangle = breaklinesangle
 
597
        self.breaklinesattrs = breaklinesattrs
 
598
        _title.__init__(self, **args)
 
599
 
 
600
    def paint(self, axispos, axis, ac=None):
 
601
        if ac is None:
 
602
            ac = axiscanvas()
 
603
        for subaxis in axis.subaxes:
 
604
            subaxis.finish(subaxispos(subaxis.convert, axispos, subaxis.vmin, subaxis.vmax, subaxis.vminover, subaxis.vmaxover))
 
605
            ac.insert(subaxis.axiscanvas)
 
606
            if ac.extent < subaxis.axiscanvas.extent:
 
607
                ac.extent = subaxis.axiscanvas.extent
 
608
        if self.breaklinesattrs is not None:
 
609
            self.sin = math.sin(self.breaklinesangle*math.pi/180.0)
 
610
            self.cos = math.cos(self.breaklinesangle*math.pi/180.0)
 
611
            breaklinesextent = (0.5*self.breaklinesdist*math.fabs(self.cos) +
 
612
                                0.5*self.breaklineslength*math.fabs(self.sin))
 
613
            if ac.extent < breaklinesextent:
 
614
                ac.extent = breaklinesextent
 
615
            for subaxis1, subaxis2 in zip(axis.subaxes[:-1], axis.subaxes[1:]):
 
616
                # use a tangent of the basepath (this is independent of the tickdirection)
 
617
                v = 0.5 * (subaxis1.vmax + subaxis2.vmin)
 
618
                p = axispos.vbasepath(v, None).normpath()
 
619
                breakline = p.tangent(0, length=self.breaklineslength)
 
620
                widthline = p.tangent(0, length=self.breaklinesdist).transformed(trafomodule.rotate(self.breaklinesangle+90, *breakline.begin()))
 
621
                # XXX Uiiii
 
622
                tocenter = map(lambda x: 0.5*(x[0]-x[1]), zip(breakline.begin(), breakline.end()))
 
623
                towidth = map(lambda x: 0.5*(x[0]-x[1]), zip(widthline.begin(), widthline.end()))
 
624
                breakline = breakline.transformed(trafomodule.translate(*tocenter).rotated(self.breaklinesangle, *breakline.begin()))
 
625
                breakline1 = breakline.transformed(trafomodule.translate(*towidth))
 
626
                breakline2 = breakline.transformed(trafomodule.translate(-towidth[0], -towidth[1]))
 
627
                ac.fill(path.path(path.moveto_pt(*breakline1.begin_pt()),
 
628
                                  path.lineto_pt(*breakline1.end_pt()),
 
629
                                  path.lineto_pt(*breakline2.end_pt()),
 
630
                                  path.lineto_pt(*breakline2.begin_pt()),
 
631
                                  path.closepath()), [color.gray.white])
 
632
                ac.stroke(breakline1, self.defaultbreaklinesattrs + self.breaklinesattrs)
 
633
                ac.stroke(breakline2, self.defaultbreaklinesattrs + self.breaklinesattrs)
 
634
        _title.paint(self, axispos, axis, ac=ac)
 
635
        return ac
 
636
 
 
637
 
 
638
class linkedsplit(split):
 
639
    """class for painting a linked splitaxis
 
640
    - the inherited split is used to paint the axis
 
641
    - modifies some constructor defaults"""
 
642
 
 
643
    __implements__ = _Iaxispainter
 
644
 
 
645
    def __init__(self, titleattrs=None, **kwargs):
 
646
        """initializes the instance
 
647
        - the titleattrs default is set to None thus skipping the title
 
648
        - all keyword arguments are passed to split"""
 
649
        split.__init__(self, titleattrs=titleattrs, **kwargs)
 
650
 
 
651
 
 
652
class bar(_title):
 
653
    """class for painting a baraxis
 
654
    - the inherited _title is used to paint the title of
 
655
      the axis
 
656
    - the bar access the multisubaxis, names, and subaxis
 
657
      relsizes attributes"""
 
658
 
 
659
    __implements__ = _Iaxispainter
 
660
 
 
661
    defaulttickattrs = []
 
662
    defaultbasepathattrs = [style.linecap.square]
 
663
    defaultnameattrs = [text.halign.center, text.vshift.mathaxis]
 
664
 
 
665
    def __init__(self, innerticklength=None,
 
666
                       outerticklength=None,
 
667
                       tickattrs=[],
 
668
                       basepathattrs=[],
 
669
                       namedist=0.3*unit.v_cm,
 
670
                       nameattrs=[],
 
671
                       namedirection=None,
 
672
                       namepos=0.5,
 
673
                       namehequalize=0,
 
674
                       namevequalize=1,
 
675
                       **args):
 
676
        """initializes the instance
 
677
        - innerticklength and outerticklength are a visual length of
 
678
          the ticks to be plotted at the axis basepath to visually
 
679
          separate the bars; if neither innerticklength nor
 
680
          outerticklength are set, not ticks are plotted
 
681
        - breaklinesattrs are a list of stroke attributes for the
 
682
          axis tick; a single entry is allowed without being a
 
683
          list; None turns off the ticks
 
684
        - namedist is a visual PyX length for the distance of the bar
 
685
          names from the axis basepath
 
686
        - nameattrs is a list of attributes for a texrunners text
 
687
          method; a single entry is allowed without being a list;
 
688
          None turns off the names
 
689
        - namedirection is an instance of rotatetext or None
 
690
        - namehequalize and namevequalize (booleans) perform an equal
 
691
          alignment for straight vertical and horizontal axes, respectively
 
692
        - futher keyword arguments are passed to _title"""
 
693
        self.innerticklength = innerticklength
 
694
        self.outerticklength = outerticklength
 
695
        self.tickattrs = tickattrs
 
696
        self.basepathattrs = basepathattrs
 
697
        self.namedist = namedist
 
698
        self.nameattrs = nameattrs
 
699
        self.namedirection = namedirection
 
700
        self.namepos = namepos
 
701
        self.namehequalize = namehequalize
 
702
        self.namevequalize = namevequalize
 
703
        _title.__init__(self, **args)
 
704
 
 
705
    def paint(self, axispos, axis, ac=None):
 
706
        if ac is None:
 
707
            ac = axiscanvas()
 
708
        if axis.multisubaxis is not None:
 
709
            for subaxis in axis.subaxis:
 
710
                subaxis.finish(subaxispos(subaxis.convert, axispos, subaxis.vmin, subaxis.vmax, None, None))
 
711
                ac.insert(subaxis.axiscanvas)
 
712
                if ac.extent < subaxis.axiscanvas.extent:
 
713
                    ac.extent = subaxis.axiscanvas.extent
 
714
        namepos = []
 
715
        for name in axis.names:
 
716
            v = axis.convert((name, self.namepos))
 
717
            x, y = axispos.vtickpoint_pt(v)
 
718
            dx, dy = axispos.vtickdirection(v)
 
719
            namepos.append((v, x, y, dx, dy))
 
720
        nameboxes = []
 
721
        if self.nameattrs is not None:
 
722
            for (v, x, y, dx, dy), name in zip(namepos, axis.names):
 
723
                nameattrs = self.defaultnameattrs + self.nameattrs
 
724
                if self.namedirection is not None:
 
725
                    nameattrs.append(self.namedirection.trafo(tick.temp_dx, tick.temp_dy))
 
726
                nameboxes.append(self.texrunner.text_pt(x, y, str(name), nameattrs))
 
727
        labeldist = ac.extent + self.namedist
 
728
        if len(namepos) > 1:
 
729
            equaldirection = 1
 
730
            for np in namepos[1:]:
 
731
                if np[3] != namepos[0][3] or np[4] != namepos[0][4]:
 
732
                    equaldirection = 0
 
733
        else:
 
734
            equaldirection = 0
 
735
        if equaldirection and ((not namepos[0][3] and self.namevequalize) or
 
736
                               (not namepos[0][4] and self.namehequalize)):
 
737
            box.linealignequal(nameboxes, labeldist, -namepos[0][3], -namepos[0][4])
 
738
        else:
 
739
            for namebox, np in zip(nameboxes, namepos):
 
740
                namebox.linealign(labeldist, -np[3], -np[4])
 
741
        if self.basepathattrs is not None:
 
742
            p = axispos.vbasepath()
 
743
            if p is not None:
 
744
                ac.stroke(p, self.defaultbasepathattrs + self.basepathattrs)
 
745
        if self.tickattrs is not None and (self.innerticklength is not None or
 
746
                                           self.outerticklength is not None):
 
747
            if self.innerticklength is not None:
 
748
                innerticklength_pt = unit.topt(self.innerticklength)
 
749
                if ac.extent < -self.innerticklength:
 
750
                    ac.extent = -self.innerticklength
 
751
            elif self.outerticklength is not None:
 
752
                innerticklength_pt = 0
 
753
            if self.outerticklength is not None:
 
754
                outerticklength_pt = unit.topt(self.outerticklength)
 
755
                if ac.extent < self.outerticklength:
 
756
                    ac.extent = self.outerticklength
 
757
            elif self.innerticklength is not None:
 
758
                outerticklength_pt = 0
 
759
            for pos in axis.relsizes:
 
760
                if pos == axis.relsizes[0]:
 
761
                    pos -= axis.firstdist
 
762
                elif pos != axis.relsizes[-1]:
 
763
                    pos -= 0.5 * axis.dist
 
764
                v = pos / axis.relsizes[-1]
 
765
                x, y = axispos.vtickpoint_pt(v)
 
766
                dx, dy = axispos.vtickdirection(v)
 
767
                x1 = x + dx * innerticklength_pt
 
768
                y1 = y + dy * innerticklength_pt
 
769
                x2 = x - dx * outerticklength_pt
 
770
                y2 = y - dy * outerticklength_pt
 
771
                ac.stroke(path.line_pt(x1, y1, x2, y2), self.defaulttickattrs + self.tickattrs)
 
772
        for (v, x, y, dx, dy), namebox in zip(namepos, nameboxes):
 
773
            newextent = namebox.extent(dx, dy) + labeldist
 
774
            if ac.extent < newextent:
 
775
                ac.extent = newextent
 
776
        for namebox in nameboxes:
 
777
            ac.insert(namebox)
 
778
        _title.paint(self, axispos, axis, ac=ac)
 
779
        return ac
 
780
 
 
781
 
 
782
class linkedbar(bar):
 
783
    """class for painting a linked baraxis
 
784
    - the inherited bar is used to paint the axis
 
785
    - modifies some constructor defaults"""
 
786
 
 
787
    __implements__ = _Iaxispainter
 
788
 
 
789
    def __init__(self, nameattrs=None, titleattrs=None, **kwargs):
 
790
        """initializes the instance
 
791
        - the titleattrs default is set to None thus skipping the title
 
792
        - the nameattrs default is set to None thus skipping the names
 
793
        - all keyword arguments are passed to bar"""
 
794
        bar.__init__(self, nameattrs=nameattrs, titleattrs=titleattrs, **kwargs)