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
326 #ticklength.short = ticklength("%f cm" % (_base/math.sqrt(64)), 1/goldenmean)
327 ticklength
.SHORT
= ticklength(_base
/math
.sqrt(64)*unit
.v_cm
, 1/goldenmean
)
328 ticklength
.SHORt
= ticklength(_base
/math
.sqrt(32)*unit
.v_cm
, 1/goldenmean
)
329 ticklength
.SHOrt
= ticklength(_base
/math
.sqrt(16)*unit
.v_cm
, 1/goldenmean
)
330 ticklength
.SHort
= ticklength(_base
/math
.sqrt(8)*unit
.v_cm
, 1/goldenmean
)
331 ticklength
.Short
= ticklength(_base
/math
.sqrt(4)*unit
.v_cm
, 1/goldenmean
)
332 ticklength
.short
= ticklength(_base
/math
.sqrt(2)*unit
.v_cm
, 1/goldenmean
)
333 ticklength
.normal
= ticklength(_base
*unit
.v_cm
, 1/goldenmean
)
334 ticklength
.long = ticklength(_base
*math
.sqrt(2)*unit
.v_cm
, 1/goldenmean
)
335 ticklength
.Long
= ticklength(_base
*math
.sqrt(4)*unit
.v_cm
, 1/goldenmean
)
336 ticklength
.LOng
= ticklength(_base
*math
.sqrt(8)*unit
.v_cm
, 1/goldenmean
)
337 ticklength
.LONg
= ticklength(_base
*math
.sqrt(16)*unit
.v_cm
, 1/goldenmean
)
338 ticklength
.LONG
= ticklength(_base
*math
.sqrt(32)*unit
.v_cm
, 1/goldenmean
)
341 class regular(_title
):
342 """class for painting the ticks and labels of an axis
343 - the inherited _title is used to paint the title of
345 - note that the type of the elements of ticks given as an argument
346 of the paint method must be suitable for the tick position methods
349 __implements__
= _Iaxispainter
351 defaulttickattrs
= []
352 defaultgridattrs
= []
353 defaultbasepathattrs
= [style
.linecap
.square
]
354 defaultlabelattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
356 def __init__(self
, innerticklength
=ticklength
.normal
,
357 outerticklength
=None,
361 labeldist
=0.3*unit
.v_cm
,
367 """initializes the instance
368 - innerticklength and outerticklength are changable
369 visual PyX lengths for ticks, subticks, etc. plotted inside
370 and outside of the graph; None turns off ticks inside or
372 - tickattrs are a list of stroke attributes for the ticks;
374 - gridattrs are a list of lists used as stroke
375 attributes for ticks, subticks etc.; None turns off
377 - basepathattrs are a list of stroke attributes for the base line
378 of the axis; None turns off the basepath
379 - labeldist is a visual PyX length for the distance of the labels
380 from the axis basepath
381 - labelattrs is a list of attributes for a texrunners text
382 method; None turns off the labels
383 - labeldirection is an instance of rotatetext or None
384 - labelhequalize and labelvequalize (booleans) perform an equal
385 alignment for straight vertical and horizontal axes, respectively
386 - futher keyword arguments are passed to _axistitle"""
387 self
.innerticklength
= innerticklength
388 self
.outerticklength
= outerticklength
389 self
.tickattrs
= tickattrs
390 self
.gridattrs
= gridattrs
391 self
.basepathattrs
= basepathattrs
392 self
.labeldist
= labeldist
393 self
.labelattrs
= labelattrs
394 self
.labeldirection
= labeldirection
395 self
.labelhequalize
= labelhequalize
396 self
.labelvequalize
= labelvequalize
397 _title
.__init
__(self
, **kwargs
)
399 def paint(self
, axispos
, axis
, ac
=None):
403 t
.temp_v
= axis
.convert(t
)
404 t
.temp_x
, t
.temp_y
= axispos
.vtickpoint_pt(t
.temp_v
)
405 t
.temp_dx
, t
.temp_dy
= axispos
.vtickdirection(t
.temp_v
)
406 maxticklevel
, maxlabellevel
= tick
.maxlevels(axis
.ticks
)
408 # create & align t.temp_labelbox
410 if t
.labellevel
is not None:
411 labelattrs
= attr
.selectattrs(self
.labelattrs
, t
.labellevel
, maxlabellevel
)
412 if labelattrs
is not None:
413 labelattrs
= self
.defaultlabelattrs
+ labelattrs
414 if self
.labeldirection
is not None:
415 labelattrs
.append(self
.labeldirection
.trafo(t
.temp_dx
, t
.temp_dy
))
416 if t
.labelattrs
is not None:
417 labelattrs
.extend(t
.labelattrs
)
418 t
.temp_labelbox
= self
.texrunner
.text_pt(t
.temp_x
, t
.temp_y
, t
.label
, labelattrs
)
419 if len(axis
.ticks
) > 1:
421 for t
in axis
.ticks
[1:]:
422 if t
.temp_dx
!= axis
.ticks
[0].temp_dx
or t
.temp_dy
!= axis
.ticks
[0].temp_dy
:
426 if equaldirection
and ((not axis
.ticks
[0].temp_dx
and self
.labelvequalize
) or
427 (not axis
.ticks
[0].temp_dy
and self
.labelhequalize
)):
428 if self
.labelattrs
is not None:
429 box
.linealignequal([t
.temp_labelbox
for t
in axis
.ticks
if t
.labellevel
is not None],
430 self
.labeldist
, -axis
.ticks
[0].temp_dx
, -axis
.ticks
[0].temp_dy
)
433 if t
.labellevel
is not None and self
.labelattrs
is not None:
434 t
.temp_labelbox
.linealign(self
.labeldist
, -t
.temp_dx
, -t
.temp_dy
)
437 if t
.ticklevel
is not None:
438 tickattrs
= attr
.selectattrs(self
.defaulttickattrs
+ self
.tickattrs
, t
.ticklevel
, maxticklevel
)
439 if tickattrs
is not None:
440 innerticklength
= attr
.selectattr(self
.innerticklength
, t
.ticklevel
, maxticklevel
)
441 outerticklength
= attr
.selectattr(self
.outerticklength
, t
.ticklevel
, maxticklevel
)
442 if innerticklength
is not None or outerticklength
is not None:
443 if innerticklength
is None:
445 if outerticklength
is None:
447 innerticklength_pt
= unit
.topt(innerticklength
)
448 outerticklength_pt
= unit
.topt(outerticklength
)
449 x1
= t
.temp_x
+ t
.temp_dx
* innerticklength_pt
450 y1
= t
.temp_y
+ t
.temp_dy
* innerticklength_pt
451 x2
= t
.temp_x
- t
.temp_dx
* outerticklength_pt
452 y2
= t
.temp_y
- t
.temp_dy
* outerticklength_pt
453 ac
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), tickattrs
)
454 if outerticklength
is not None and outerticklength
> ac
.extent
:
455 ac
.extent
= outerticklength
456 if outerticklength
is not None and -innerticklength
> ac
.extent
:
457 ac
.extent
= -innerticklength
458 if self
.gridattrs
is not None:
459 gridattrs
= attr
.selectattrs(self
.defaultgridattrs
+ self
.gridattrs
, t
.ticklevel
, maxticklevel
)
460 if gridattrs
is not None:
461 ac
.stroke(axispos
.vgridpath(t
.temp_v
), gridattrs
)
462 if t
.labellevel
is not None and self
.labelattrs
is not None:
463 ac
.insert(t
.temp_labelbox
)
464 ac
.labels
.append(t
.temp_labelbox
)
465 extent
= t
.temp_labelbox
.extent(t
.temp_dx
, t
.temp_dy
) + self
.labeldist
466 if extent
> ac
.extent
:
468 if self
.basepathattrs
is not None:
469 ac
.stroke(axispos
.vbasepath(), self
.defaultbasepathattrs
+ self
.basepathattrs
)
471 # for t in axis.ticks:
472 # del t.temp_v # we've inserted those temporary variables ... and do not care any longer about them
477 # if t.labellevel is not None and self.labelattrs is not None:
478 # del t.temp_labelbox
480 _title
.paint(self
, axispos
, axis
, ac
=ac
)
485 class linked(regular
):
486 """class for painting a linked axis
487 - the inherited regular is used to paint the axis
488 - modifies some constructor defaults"""
490 __implements__
= _Iaxispainter
492 def __init__(self
, labelattrs
=None,
495 """initializes the instance
496 - the labelattrs default is set to None thus skipping the labels
497 - the titleattrs default is set to None thus skipping the title
498 - all keyword arguments are passed to regular"""
499 regular
.__init
__(self
, labelattrs
=labelattrs
,
500 titleattrs
=titleattrs
,
505 """implementation of the _Iaxispos interface for a subaxis"""
507 __implements__
= _Iaxispos
509 def __init__(self
, convert
, baseaxispos
, vmin
, vmax
, vminover
, vmaxover
):
510 """initializes the instance
511 - convert is the subaxis convert method
512 - baseaxispos is the axispos instance of the base axis
513 - vmin, vmax is the range covered by the subaxis in graph coordinates
514 - vminover, vmaxover is the extended range of the subaxis including
515 regions between several subaxes (for basepath drawing etc.)"""
516 self
.convert
= convert
517 self
.baseaxispos
= baseaxispos
520 self
.vminover
= vminover
521 self
.vmaxover
= vmaxover
523 def basepath(self
, x1
=None, x2
=None):
525 v1
= self
.vmin
+self
.convert(x1
)*(self
.vmax
-self
.vmin
)
529 v2
= self
.vmin
+self
.convert(x2
)*(self
.vmax
-self
.vmin
)
532 return self
.baseaxispos
.vbasepath(v1
, v2
)
534 def vbasepath(self
, v1
=None, v2
=None):
536 v1
= self
.vmin
+v1
*(self
.vmax
-self
.vmin
)
540 v2
= self
.vmin
+v2
*(self
.vmax
-self
.vmin
)
543 return self
.baseaxispos
.vbasepath(v1
, v2
)
545 def gridpath(self
, x
):
546 return self
.baseaxispos
.vgridpath(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
548 def vgridpath(self
, v
):
549 return self
.baseaxispos
.vgridpath(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
551 def tickpoint_pt(self
, x
, axis
=None):
552 return self
.baseaxispos
.vtickpoint_pt(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
554 def tickpoint(self
, x
, axis
=None):
555 return self
.baseaxispos
.vtickpoint(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
557 def vtickpoint_pt(self
, v
, axis
=None):
558 return self
.baseaxispos
.vtickpoint_pt(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
560 def vtickpoint(self
, v
, axis
=None):
561 return self
.baseaxispos
.vtickpoint(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
563 def tickdirection(self
, x
, axis
=None):
564 return self
.baseaxispos
.vtickdirection(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
566 def vtickdirection(self
, v
, axis
=None):
567 return self
.baseaxispos
.vtickdirection(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
571 """class for painting a splitaxis
572 - the inherited _title is used to paint the title of
574 - the splitaxis access the subaxes attribute of the axis"""
576 __implements__
= _Iaxispainter
578 defaultbreaklinesattrs
= []
580 def __init__(self
, breaklinesdist
=0.05*unit
.v_cm
,
581 breaklineslength
=0.5*unit
.v_cm
,
585 """initializes the instance
586 - breaklinesdist is a visual length of the distance between
587 the two lines of the axis break
588 - breaklineslength is a visual length of the length of the
589 two lines of the axis break
590 - breaklinesangle is the angle of the lines of the axis break
591 - breaklinesattrs are a list of stroke attributes for the
592 axis break lines; a single entry is allowed without being a
593 list; None turns off the break lines
594 - futher keyword arguments are passed to _title"""
595 self
.breaklinesdist
= breaklinesdist
596 self
.breaklineslength
= breaklineslength
597 self
.breaklinesangle
= breaklinesangle
598 self
.breaklinesattrs
= breaklinesattrs
599 _title
.__init
__(self
, **args
)
601 def paint(self
, axispos
, axis
, ac
=None):
604 for subaxis
in axis
.subaxes
:
605 subaxis
.finish(subaxispos(subaxis
.convert
, axispos
, subaxis
.vmin
, subaxis
.vmax
, subaxis
.vminover
, subaxis
.vmaxover
))
606 ac
.insert(subaxis
.axiscanvas
)
607 if ac
.extent
< subaxis
.axiscanvas
.extent
:
608 ac
.extent
= subaxis
.axiscanvas
.extent
609 if self
.breaklinesattrs
is not None:
610 self
.sin
= math
.sin(self
.breaklinesangle
*math
.pi
/180.0)
611 self
.cos
= math
.cos(self
.breaklinesangle
*math
.pi
/180.0)
612 breaklinesextent
= (0.5*self
.breaklinesdist
*math
.fabs(self
.cos
) +
613 0.5*self
.breaklineslength
*math
.fabs(self
.sin
))
614 if ac
.extent
< breaklinesextent
:
615 ac
.extent
= breaklinesextent
616 for subaxis1
, subaxis2
in zip(axis
.subaxes
[:-1], axis
.subaxes
[1:]):
617 # use a tangent of the basepath (this is independent of the tickdirection)
618 v
= 0.5 * (subaxis1
.vmax
+ subaxis2
.vmin
)
619 p
= path
.normpath(axispos
.vbasepath(v
, None))
620 breakline
= p
.tangent(0, length
=self
.breaklineslength
)
621 widthline
= p
.tangent(0, length
=self
.breaklinesdist
).transformed(trafomodule
.rotate(self
.breaklinesangle
+90, *breakline
.begin()))
623 tocenter
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(breakline
.begin(), breakline
.end()))
624 towidth
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(widthline
.begin(), widthline
.end()))
625 breakline
= breakline
.transformed(trafomodule
.translate(*tocenter
).rotated(self
.breaklinesangle
, *breakline
.begin()))
626 breakline1
= breakline
.transformed(trafomodule
.translate(*towidth
))
627 breakline2
= breakline
.transformed(trafomodule
.translate(-towidth
[0], -towidth
[1]))
628 ac
.fill(path
.path(path
.moveto_pt(*breakline1
.begin_pt()),
629 path
.lineto_pt(*breakline1
.end_pt()),
630 path
.lineto_pt(*breakline2
.end_pt()),
631 path
.lineto_pt(*breakline2
.begin_pt()),
632 path
.closepath()), [color
.gray
.white
])
633 ac
.stroke(breakline1
, self
.defaultbreaklinesattrs
+ self
.breaklinesattrs
)
634 ac
.stroke(breakline2
, self
.defaultbreaklinesattrs
+ self
.breaklinesattrs
)
635 _title
.paint(self
, axispos
, axis
, ac
=ac
)
639 class linkedsplit(split
):
640 """class for painting a linked splitaxis
641 - the inherited split is used to paint the axis
642 - modifies some constructor defaults"""
644 __implements__
= _Iaxispainter
646 def __init__(self
, titleattrs
=None, **kwargs
):
647 """initializes the instance
648 - the titleattrs default is set to None thus skipping the title
649 - all keyword arguments are passed to split"""
650 split
.__init
__(self
, titleattrs
=titleattrs
, **kwargs
)
654 """class for painting a baraxis
655 - the inherited _title is used to paint the title of
657 - the bar access the multisubaxis, names, and subaxis
658 relsizes attributes"""
660 __implements__
= _Iaxispainter
662 defaulttickattrs
= []
663 defaultbasepathattrs
= [style
.linecap
.square
]
664 defaultnameattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
666 def __init__(self
, innerticklength
=None,
667 outerticklength
=None,
670 namedist
=0.3*unit
.v_cm
,
677 """initializes the instance
678 - innerticklength and outerticklength are a visual length of
679 the ticks to be plotted at the axis basepath to visually
680 separate the bars; if neither innerticklength nor
681 outerticklength are set, not ticks are plotted
682 - breaklinesattrs are a list of stroke attributes for the
683 axis tick; a single entry is allowed without being a
684 list; None turns off the ticks
685 - namedist is a visual PyX length for the distance of the bar
686 names from the axis basepath
687 - nameattrs is a list of attributes for a texrunners text
688 method; a single entry is allowed without being a list;
689 None turns off the names
690 - namedirection is an instance of rotatetext or None
691 - namehequalize and namevequalize (booleans) perform an equal
692 alignment for straight vertical and horizontal axes, respectively
693 - futher keyword arguments are passed to _title"""
694 self
.innerticklength
= innerticklength
695 self
.outerticklength
= outerticklength
696 self
.tickattrs
= tickattrs
697 self
.basepathattrs
= basepathattrs
698 self
.namedist
= namedist
699 self
.nameattrs
= nameattrs
700 self
.namedirection
= namedirection
701 self
.namepos
= namepos
702 self
.namehequalize
= namehequalize
703 self
.namevequalize
= namevequalize
704 _title
.__init
__(self
, **args
)
706 def paint(self
, axispos
, axis
, ac
=None):
709 if axis
.multisubaxis
is not None:
710 for subaxis
in axis
.subaxis
:
711 subaxis
.finish(subaxispos(subaxis
.convert
, axispos
, subaxis
.vmin
, subaxis
.vmax
, None, None))
712 ac
.insert(subaxis
.axiscanvas
)
713 if ac
.extent
< subaxis
.axiscanvas
.extent
:
714 ac
.extent
= subaxis
.axiscanvas
.extent
716 for name
in axis
.names
:
717 v
= axis
.convert((name
, self
.namepos
))
718 x
, y
= axispos
.vtickpoint_pt(v
)
719 dx
, dy
= axispos
.vtickdirection(v
)
720 namepos
.append((v
, x
, y
, dx
, dy
))
722 if self
.nameattrs
is not None:
723 for (v
, x
, y
, dx
, dy
), name
in zip(namepos
, axis
.names
):
724 nameattrs
= self
.defaultnameattrs
+ self
.nameattrs
725 if self
.namedirection
is not None:
726 nameattrs
.append(self
.namedirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
727 nameboxes
.append(self
.texrunner
.text_pt(x
, y
, str(name
), nameattrs
))
728 labeldist
= ac
.extent
+ self
.namedist
731 for np
in namepos
[1:]:
732 if np
[3] != namepos
[0][3] or np
[4] != namepos
[0][4]:
736 if equaldirection
and ((not namepos
[0][3] and self
.namevequalize
) or
737 (not namepos
[0][4] and self
.namehequalize
)):
738 box
.linealignequal(nameboxes
, labeldist
, -namepos
[0][3], -namepos
[0][4])
740 for namebox
, np
in zip(nameboxes
, namepos
):
741 namebox
.linealign(labeldist
, -np
[3], -np
[4])
742 if self
.basepathattrs
is not None:
743 p
= axispos
.vbasepath()
745 ac
.stroke(p
, self
.defaultbasepathattrs
+ self
.basepathattrs
)
746 if self
.tickattrs
is not None and (self
.innerticklength
is not None or
747 self
.outerticklength
is not None):
748 if self
.innerticklength
is not None:
749 innerticklength_pt
= unit
.topt(self
.innerticklength
)
750 if ac
.extent
< -self
.innerticklength
:
751 ac
.extent
= -self
.innerticklength
752 elif self
.outerticklength
is not None:
753 innerticklength_pt
= 0
754 if self
.outerticklength
is not None:
755 outerticklength_pt
= unit
.topt(self
.outerticklength
)
756 if ac
.extent
< self
.outerticklength
:
757 ac
.extent
= self
.outerticklength
758 elif self
.innerticklength
is not None:
759 outerticklength_pt
= 0
760 for pos
in axis
.relsizes
:
761 if pos
== axis
.relsizes
[0]:
762 pos
-= axis
.firstdist
763 elif pos
!= axis
.relsizes
[-1]:
764 pos
-= 0.5 * axis
.dist
765 v
= pos
/ axis
.relsizes
[-1]
766 x
, y
= axispos
.vtickpoint_pt(v
)
767 dx
, dy
= axispos
.vtickdirection(v
)
768 x1
= x
+ dx
* innerticklength_pt
769 y1
= y
+ dy
* innerticklength_pt
770 x2
= x
- dx
* outerticklength_pt
771 y2
= y
- dy
* outerticklength_pt
772 ac
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), self
.defaulttickattrs
+ self
.tickattrs
)
773 for (v
, x
, y
, dx
, dy
), namebox
in zip(namepos
, nameboxes
):
774 newextent
= namebox
.extent(dx
, dy
) + labeldist
775 if ac
.extent
< newextent
:
776 ac
.extent
= newextent
777 for namebox
in nameboxes
:
779 _title
.paint(self
, axispos
, axis
, ac
=ac
)
783 class linkedbar(bar
):
784 """class for painting a linked baraxis
785 - the inherited bar is used to paint the axis
786 - modifies some constructor defaults"""
788 __implements__
= _Iaxispainter
790 def __init__(self
, nameattrs
=None, titleattrs
=None, **kwargs
):
791 """initializes the instance
792 - the titleattrs default is set to None thus skipping the title
793 - the nameattrs default is set to None thus skipping the names
794 - all keyword arguments are passed to bar"""
795 bar
.__init
__(self
, nameattrs
=nameattrs
, titleattrs
=titleattrs
, **kwargs
)