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-2004 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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
32 goldenmean
= 0.5 * (math
.sqrt(5) + 1)
35 class axiscanvas(canvas
.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"""
51 # __implements__ = sole implementation
53 def __init__(self
, *args
, **kwargs
):
54 """initializes the instance
56 - sets labels to an empty list"""
57 canvas
.canvas
.__init
__(self
, *args
, **kwargs
)
63 """create rotations accordingly to tick directions
64 - upsidedown rotations are suppressed by rotating them by another 180 degree"""
66 # __implements__ = sole implementation
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
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
):
82 while (direction
< -180 - self
.epsilon
):
84 while (direction
> 90 + self
.epsilon
):
86 while (direction
< -90 - self
.epsilon
):
88 return trafomodule
.rotate(direction
)
91 rotatetext
.parallel
= rotatetext(90)
92 rotatetext
.orthogonal
= rotatetext(180)
96 "class for painting axes"
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"""
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?))
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"""
139 def vbasepath(self
, v1
=None, v2
=None):
140 """return the basepath as a path
141 - like basepath, but for graph coordinates"""
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"""
147 def vgridpath(self
, v
):
148 """return the gridpath as a path for a given position v
150 - might return None when no gridpath is available"""
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"""
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"""
160 def vtickpoint_pt(self
, v
):
161 "like tickpoint_pt, but for graph coordinates"
163 def vtickpoint(self
, v
):
164 "like tickpoint, but for graph coordinates"
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)"""
170 def vtickdirection(self
, v
):
171 """like tickposition, but for graph coordinates"""
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:
182 - other methods needed for _Iaxispos are build out of those
183 listed above when this class is inherited"""
185 def __init__(self
, convert
):
186 """initializes the instance
187 - convert is a convert method from an axis"""
188 self
.convert
= convert
190 def basepath(self
, x1
=None, x2
=None):
193 return self
.vbasepath()
195 return self
.vbasepath(v2
=self
.convert(x2
))
198 return self
.vbasepath(v1
=self
.convert(x1
))
200 return self
.vbasepath(v1
=self
.convert(x1
), v2
=self
.convert(x2
))
202 def gridpath(self
, x
):
203 return self
.vgridpath(self
.convert(x
))
205 def tickpoint_pt(self
, x
):
206 return self
.vtickpoint_pt(self
.convert(x
))
208 def tickpoint(self
, x
):
209 return self
.vtickpoint(self
.convert(x
))
211 def vtickpoint(self
, v
):
212 return [x
* unit
.t_pt
for x
in self
.vtickpoint(v
)]
214 def tickdirection(self
, x
):
215 return self
.vtickdirection(self
.convert(x
))
218 class pathaxispos(_axispos
):
219 """axis tick position methods along an arbitrary path"""
221 __implements__
= _Iaxispos
223 def __init__(self
, p
, convert
, direction
=1):
225 self
.normpath
= path
.normpath(p
)
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
231 def vbasepath(self
, v1
=None, v2
=None):
236 return self
.normpath
.split(self
.normpath
.arclentoparam(v2
* self
.arclen
))[0]
239 return self
.normpath
.split(self
.normpath
.arclentoparam(v1
* self
.arclen
))[1]
241 return self
.normpath
.split(*self
.normpath
.arclentoparam([v1
* self
.arclen
, v2
* self
.arclen
]))[1]
243 def vgridpath(self
, v
):
246 def vtickpoint_pt(self
, v
):
247 return self
.normpath
.at_pt(self
.normpath
.arclentoparam(v
* self
.arclen
))
249 def vtickdirection(self
, v
):
250 t
= self
.normpath
.tangent(self
.normpath
.arclentoparam(v
* self
.arclen
))
251 tbegin
= t
.begin_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")
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"""
268 __implements__
= _Iaxispainter
270 defaulttitleattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
272 def __init__(self
, titledist
=0.3*unit
.v_cm
,
274 titledirection
=rotatetext
.parallel
,
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
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
295 def paint(self
, axispos
, axis
, ac
=None):
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
)
312 class geometricseries(attr
.changeattr
):
314 def __init__(self
, initial
, factor
):
315 self
.initial
= initial
318 def select(self
, index
, total
):
319 return self
.initial
* (self
.factor
** index
)
322 class ticklength(geometricseries
): pass
324 _base
= 0.12 * unit
.v_cm
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
)
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
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
348 __implements__
= _Iaxispainter
350 defaulttickattrs
= []
351 defaultgridattrs
= []
352 defaultbasepathattrs
= [style
.linecap
.square
]
353 defaultlabelattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
355 def __init__(self
, innerticklength
=ticklength
.normal
,
356 outerticklength
=None,
360 labeldist
=0.3*unit
.v_cm
,
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
371 - tickattrs are a list of stroke attributes for the ticks;
373 - gridattrs are a list of lists used as stroke
374 attributes for ticks, subticks etc.; None turns off
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
)
398 def paint(self
, axispos
, axis
, ac
=None):
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
)
407 # create & align t.temp_labelbox
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:
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
:
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
)
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
)
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:
444 if outerticklength
is None:
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
:
467 if self
.basepathattrs
is not None:
468 ac
.stroke(axispos
.vbasepath(), self
.defaultbasepathattrs
+ self
.basepathattrs
)
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
476 # if t.labellevel is not None and self.labelattrs is not None:
477 # del t.temp_labelbox
479 _title
.paint(self
, axispos
, axis
, ac
=ac
)
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"""
489 __implements__
= _Iaxispainter
491 def __init__(self
, labelattrs
=None,
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
,
504 """implementation of the _Iaxispos interface for a subaxis"""
506 __implements__
= _Iaxispos
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
519 self
.vminover
= vminover
520 self
.vmaxover
= vmaxover
522 def basepath(self
, x1
=None, x2
=None):
524 v1
= self
.vmin
+self
.convert(x1
)*(self
.vmax
-self
.vmin
)
528 v2
= self
.vmin
+self
.convert(x2
)*(self
.vmax
-self
.vmin
)
531 return self
.baseaxispos
.vbasepath(v1
, v2
)
533 def vbasepath(self
, v1
=None, v2
=None):
535 v1
= self
.vmin
+v1
*(self
.vmax
-self
.vmin
)
539 v2
= self
.vmin
+v2
*(self
.vmax
-self
.vmin
)
542 return self
.baseaxispos
.vbasepath(v1
, v2
)
544 def gridpath(self
, x
):
545 return self
.baseaxispos
.vgridpath(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
547 def vgridpath(self
, v
):
548 return self
.baseaxispos
.vgridpath(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
550 def tickpoint_pt(self
, x
, axis
=None):
551 return self
.baseaxispos
.vtickpoint_pt(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
553 def tickpoint(self
, x
, axis
=None):
554 return self
.baseaxispos
.vtickpoint(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
556 def vtickpoint_pt(self
, v
, axis
=None):
557 return self
.baseaxispos
.vtickpoint_pt(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
559 def vtickpoint(self
, v
, axis
=None):
560 return self
.baseaxispos
.vtickpoint(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
562 def tickdirection(self
, x
, axis
=None):
563 return self
.baseaxispos
.vtickdirection(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
565 def vtickdirection(self
, v
, axis
=None):
566 return self
.baseaxispos
.vtickdirection(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
570 """class for painting a splitaxis
571 - the inherited _title is used to paint the title of
573 - the splitaxis access the subaxes attribute of the axis"""
575 __implements__
= _Iaxispainter
577 defaultbreaklinesattrs
= []
579 def __init__(self
, breaklinesdist
=0.05*unit
.v_cm
,
580 breaklineslength
=0.5*unit
.v_cm
,
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
)
600 def paint(self
, axispos
, axis
, ac
=None):
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
= path
.normpath(axispos
.vbasepath(v
, None))
619 breakline
= p
.tangent(0, length
=self
.breaklineslength
)
620 widthline
= p
.tangent(0, length
=self
.breaklinesdist
).transformed(trafomodule
.rotate(self
.breaklinesangle
+90, *breakline
.begin()))
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
)
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"""
643 __implements__
= _Iaxispainter
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
)
653 """class for painting a baraxis
654 - the inherited _title is used to paint the title of
656 - the bar access the multisubaxis, names, and subaxis
657 relsizes attributes"""
659 __implements__
= _Iaxispainter
661 defaulttickattrs
= []
662 defaultbasepathattrs
= [style
.linecap
.square
]
663 defaultnameattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
665 def __init__(self
, innerticklength
=None,
666 outerticklength
=None,
669 namedist
=0.3*unit
.v_cm
,
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
)
705 def paint(self
, axispos
, axis
, ac
=None):
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
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
))
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
730 for np
in namepos
[1:]:
731 if np
[3] != namepos
[0][3] or np
[4] != namepos
[0][4]:
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])
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()
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
:
778 _title
.paint(self
, axispos
, axis
, ac
=ac
)
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"""
787 __implements__
= _Iaxispainter
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
)