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 [unit
.t_pt(x
) 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
= unit
.t_pt(self
.arclen_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 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_str
= 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 titledist
= unit
.length(self
.titledist_str
, default_type
="v")
300 x
, y
= axispos
.vtickpoint_pt(self
.titlepos
)
301 dx
, dy
= axispos
.vtickdirection(self
.titlepos
)
302 titleattrs
= self
.defaulttitleattrs
+ self
.titleattrs
303 if self
.titledirection
is not None:
304 titleattrs
.append(self
.titledirection
.trafo(dx
, dy
))
305 title
= self
.texrunner
.text_pt(x
, y
, axis
.title
, titleattrs
)
306 ac
.extent
+= titledist
307 title
.linealign(ac
.extent
, -dx
, -dy
)
308 ac
.extent
+= title
.extent(dx
, dy
)
313 class geometricseries(attr
.changeattr
):
315 def __init__(self
, initial
, factor
):
316 self
.initial
= initial
319 def select(self
, index
, total
):
320 return self
.initial
* (self
.factor
** index
)
323 class ticklength(geometricseries
): pass
327 #ticklength.short = ticklength("%f cm" % (_base/math.sqrt(64)), 1/goldenmean)
328 ticklength
.SHORT
= ticklength(_base
/math
.sqrt(64), 1/goldenmean
)
329 ticklength
.SHORt
= ticklength(_base
/math
.sqrt(32), 1/goldenmean
)
330 ticklength
.SHOrt
= ticklength(_base
/math
.sqrt(16), 1/goldenmean
)
331 ticklength
.SHort
= ticklength(_base
/math
.sqrt(8), 1/goldenmean
)
332 ticklength
.Short
= ticklength(_base
/math
.sqrt(4), 1/goldenmean
)
333 ticklength
.short
= ticklength(_base
/math
.sqrt(2), 1/goldenmean
)
334 ticklength
.normal
= ticklength(_base
, 1/goldenmean
)
335 ticklength
.long = ticklength(_base
*math
.sqrt(2), 1/goldenmean
)
336 ticklength
.Long
= ticklength(_base
*math
.sqrt(4), 1/goldenmean
)
337 ticklength
.LOng
= ticklength(_base
*math
.sqrt(8), 1/goldenmean
)
338 ticklength
.LONg
= ticklength(_base
*math
.sqrt(16), 1/goldenmean
)
339 ticklength
.LONG
= ticklength(_base
*math
.sqrt(32), 1/goldenmean
)
343 """class for painting the ticks and labels of an axis
344 - the inherited _title is used to paint the title of
346 - note that the type of the elements of ticks given as an argument
347 of the paint method must be suitable for the tick position methods
350 __implements__
= _Iaxispainter
352 defaulttickattrs
= []
353 defaultgridattrs
= []
354 defaultbasepathattrs
= [style
.linecap
.square
]
355 defaultlabelattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
357 def __init__(self
, innerticklength
=ticklength
.normal
,
358 outerticklength
=None,
368 """initializes the instance
369 - innerticklength and outerticklength are changable
370 visual PyX lengths for ticks, subticks, etc. plotted inside
371 and outside of the graph; None turns off ticks inside or
373 - tickattrs are a list of stroke attributes for the ticks;
375 - gridattrs are a list of lists used as stroke
376 attributes for ticks, subticks etc.; None turns off
378 - basepathattrs are a list of stroke attributes for the base line
379 of the axis; None turns off the basepath
380 - labeldist is a visual PyX length for the distance of the labels
381 from the axis basepath
382 - labelattrs is a list of attributes for a texrunners text
383 method; None turns off the labels
384 - labeldirection is an instance of rotatetext or None
385 - labelhequalize and labelvequalize (booleans) perform an equal
386 alignment for straight vertical and horizontal axes, respectively
387 - futher keyword arguments are passed to _axistitle"""
388 self
.innerticklength_str
= innerticklength
389 self
.outerticklength_str
= outerticklength
390 self
.tickattrs
= tickattrs
391 self
.gridattrs
= gridattrs
392 self
.basepathattrs
= basepathattrs
393 self
.labeldist_str
= labeldist
394 self
.labelattrs
= labelattrs
395 self
.labeldirection
= labeldirection
396 self
.labelhequalize
= labelhequalize
397 self
.labelvequalize
= labelvequalize
398 _title
.__init
__(self
, **kwargs
)
400 def paint(self
, axispos
, axis
, ac
=None):
403 labeldist
= unit
.length(self
.labeldist_str
, default_type
="v")
405 t
.temp_v
= axis
.convert(t
)
406 t
.temp_x
, t
.temp_y
= axispos
.vtickpoint_pt(t
.temp_v
)
407 t
.temp_dx
, t
.temp_dy
= axispos
.vtickdirection(t
.temp_v
)
408 maxticklevel
, maxlabellevel
= tick
.maxlevels(axis
.ticks
)
410 # create & align t.temp_labelbox
412 if t
.labellevel
is not None:
413 labelattrs
= attr
.selectattrs(self
.labelattrs
, t
.labellevel
, maxlabellevel
)
414 if labelattrs
is not None:
415 labelattrs
= self
.defaultlabelattrs
+ labelattrs
416 if self
.labeldirection
is not None:
417 labelattrs
.append(self
.labeldirection
.trafo(t
.temp_dx
, t
.temp_dy
))
418 if t
.labelattrs
is not None:
419 labelattrs
.extend(t
.labelattrs
)
420 t
.temp_labelbox
= self
.texrunner
.text_pt(t
.temp_x
, t
.temp_y
, t
.label
, labelattrs
)
421 if len(axis
.ticks
) > 1:
423 for t
in axis
.ticks
[1:]:
424 if t
.temp_dx
!= axis
.ticks
[0].temp_dx
or t
.temp_dy
!= axis
.ticks
[0].temp_dy
:
428 if equaldirection
and ((not axis
.ticks
[0].temp_dx
and self
.labelvequalize
) or
429 (not axis
.ticks
[0].temp_dy
and self
.labelhequalize
)):
430 if self
.labelattrs
is not None:
431 box
.linealignequal([t
.temp_labelbox
for t
in axis
.ticks
if t
.labellevel
is not None],
432 labeldist
, -axis
.ticks
[0].temp_dx
, -axis
.ticks
[0].temp_dy
)
435 if t
.labellevel
is not None and self
.labelattrs
is not None:
436 t
.temp_labelbox
.linealign(labeldist
, -t
.temp_dx
, -t
.temp_dy
)
439 if t
.ticklevel
is not None:
440 innerticklength
= attr
.selectattr(self
.innerticklength_str
, t
.ticklevel
, maxticklevel
)
441 outerticklength
= attr
.selectattr(self
.outerticklength_str
, t
.ticklevel
, maxticklevel
)
442 if innerticklength
is not None or outerticklength
is not None:
443 if innerticklength
is None:
446 innerticklength
= unit
.length(innerticklength
, default_type
="v")
447 if outerticklength
is None:
450 outerticklength
= unit
.length(outerticklength
, default_type
="v")
451 tickattrs
= attr
.selectattrs(self
.defaulttickattrs
+ self
.tickattrs
, t
.ticklevel
, maxticklevel
)
452 if tickattrs
is not None:
453 innerticklength_pt
= unit
.topt(innerticklength
)
454 outerticklength_pt
= unit
.topt(outerticklength
)
455 x1
= t
.temp_x
+ t
.temp_dx
* innerticklength_pt
456 y1
= t
.temp_y
+ t
.temp_dy
* innerticklength_pt
457 x2
= t
.temp_x
- t
.temp_dx
* outerticklength_pt
458 y2
= t
.temp_y
- t
.temp_dy
* outerticklength_pt
459 ac
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), tickattrs
)
460 if outerticklength
is not None and outerticklength
> ac
.extent
:
461 ac
.extent
= outerticklength
462 if outerticklength
is not None and -innerticklength
> ac
.extent
:
463 ac
.extent
= -innerticklength
464 if self
.gridattrs
is not None:
465 gridattrs
= attr
.selectattrs(self
.defaultgridattrs
+ self
.gridattrs
, t
.ticklevel
, maxticklevel
)
466 ac
.stroke(axispos
.vgridpath(t
.temp_v
), gridattrs
)
467 if t
.labellevel
is not None and self
.labelattrs
is not None:
468 ac
.insert(t
.temp_labelbox
)
469 ac
.labels
.append(t
.temp_labelbox
)
470 extent
= t
.temp_labelbox
.extent(t
.temp_dx
, t
.temp_dy
) + labeldist
471 if extent
> ac
.extent
:
473 if self
.basepathattrs
is not None:
474 ac
.stroke(axispos
.vbasepath(), self
.defaultbasepathattrs
+ self
.basepathattrs
)
476 # for t in axis.ticks:
477 # del t.temp_v # we've inserted those temporary variables ... and do not care any longer about them
482 # if t.labellevel is not None and self.labelattrs is not None:
483 # del t.temp_labelbox
485 _title
.paint(self
, axispos
, axis
, ac
=ac
)
491 """class for painting a linked axis
492 - the inherited plain is used to paint the axis
493 - modifies some constructor defaults"""
495 __implements__
= _Iaxispainter
497 def __init__(self
, labelattrs
=None,
500 """initializes the instance
501 - the labelattrs default is set to None thus skipping the labels
502 - the titleattrs default is set to None thus skipping the title
503 - all keyword arguments are passed to plain"""
504 plain
.__init
__(self
, labelattrs
=labelattrs
,
505 titleattrs
=titleattrs
,
510 """implementation of the _Iaxispos interface for a subaxis"""
512 __implements__
= _Iaxispos
514 def __init__(self
, convert
, baseaxispos
, vmin
, vmax
, vminover
, vmaxover
):
515 """initializes the instance
516 - convert is the subaxis convert method
517 - baseaxispos is the axispos instance of the base axis
518 - vmin, vmax is the range covered by the subaxis in graph coordinates
519 - vminover, vmaxover is the extended range of the subaxis including
520 regions between several subaxes (for basepath drawing etc.)"""
521 self
.convert
= convert
522 self
.baseaxispos
= baseaxispos
525 self
.vminover
= vminover
526 self
.vmaxover
= vmaxover
528 def basepath(self
, x1
=None, x2
=None):
530 v1
= self
.vmin
+self
.convert(x1
)*(self
.vmax
-self
.vmin
)
534 v2
= self
.vmin
+self
.convert(x2
)*(self
.vmax
-self
.vmin
)
537 return self
.baseaxispos
.vbasepath(v1
, v2
)
539 def vbasepath(self
, v1
=None, v2
=None):
541 v1
= self
.vmin
+v1
*(self
.vmax
-self
.vmin
)
545 v2
= self
.vmin
+v2
*(self
.vmax
-self
.vmin
)
548 return self
.baseaxispos
.vbasepath(v1
, v2
)
550 def gridpath(self
, x
):
551 return self
.baseaxispos
.vgridpath(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
553 def vgridpath(self
, v
):
554 return self
.baseaxispos
.vgridpath(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
556 def tickpoint_pt(self
, x
, axis
=None):
557 return self
.baseaxispos
.vtickpoint_pt(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
559 def tickpoint(self
, x
, axis
=None):
560 return self
.baseaxispos
.vtickpoint(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
562 def vtickpoint_pt(self
, v
, axis
=None):
563 return self
.baseaxispos
.vtickpoint_pt(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
565 def vtickpoint(self
, v
, axis
=None):
566 return self
.baseaxispos
.vtickpoint(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
568 def tickdirection(self
, x
, axis
=None):
569 return self
.baseaxispos
.vtickdirection(self
.vmin
+self
.convert(x
)*(self
.vmax
-self
.vmin
))
571 def vtickdirection(self
, v
, axis
=None):
572 return self
.baseaxispos
.vtickdirection(self
.vmin
+v
*(self
.vmax
-self
.vmin
))
576 """class for painting a splitaxis
577 - the inherited _title is used to paint the title of
579 - the splitaxis access the subaxes attribute of the axis"""
581 __implements__
= _Iaxispainter
583 defaultbreaklinesattrs
= []
585 def __init__(self
, breaklinesdist
="0.05 cm",
586 breaklineslength
="0.5 cm",
590 """initializes the instance
591 - breaklinesdist is a visual length of the distance between
592 the two lines of the axis break
593 - breaklineslength is a visual length of the length of the
594 two lines of the axis break
595 - breaklinesangle is the angle of the lines of the axis break
596 - breaklinesattrs are a list of stroke attributes for the
597 axis break lines; a single entry is allowed without being a
598 list; None turns off the break lines
599 - futher keyword arguments are passed to _title"""
600 self
.breaklinesdist_str
= breaklinesdist
601 self
.breaklineslength_str
= breaklineslength
602 self
.breaklinesangle
= breaklinesangle
603 self
.breaklinesattrs
= breaklinesattrs
604 _title
.__init
__(self
, **args
)
606 def paint(self
, axispos
, axis
, ac
=None):
609 for subaxis
in axis
.subaxes
:
610 subaxis
.finish(subaxispos(subaxis
.convert
, axispos
, subaxis
.vmin
, subaxis
.vmax
, subaxis
.vminover
, subaxis
.vmaxover
))
611 ac
.insert(subaxis
.axiscanvas
)
612 if ac
.extent
< subaxis
.axiscanvas
.extent
:
613 ac
.extent
= subaxis
.axiscanvas
.extent
614 if self
.breaklinesattrs
is not None:
615 self
.breaklinesdist
= unit
.length(self
.breaklinesdist_str
, default_type
="v")
616 self
.breaklineslength
= unit
.length(self
.breaklineslength_str
, default_type
="v")
617 self
.sin
= math
.sin(self
.breaklinesangle
*math
.pi
/180.0)
618 self
.cos
= math
.cos(self
.breaklinesangle
*math
.pi
/180.0)
619 breaklinesextent
= (0.5*self
.breaklinesdist
*math
.fabs(self
.cos
) +
620 0.5*self
.breaklineslength
*math
.fabs(self
.sin
))
621 if ac
.extent
< breaklinesextent
:
622 ac
.extent
= breaklinesextent
623 for subaxis1
, subaxis2
in zip(axis
.subaxes
[:-1], axis
.subaxes
[1:]):
624 # use a tangent of the basepath (this is independent of the tickdirection)
625 v
= 0.5 * (subaxis1
.vmax
+ subaxis2
.vmin
)
626 p
= path
.normpath(axispos
.vbasepath(v
, None))
627 breakline
= p
.tangent(0, length
=self
.breaklineslength
)
628 widthline
= p
.tangent(0, length
=self
.breaklinesdist
).transformed(trafomodule
.rotate(self
.breaklinesangle
+90, *breakline
.begin()))
630 tocenter
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(breakline
.begin(), breakline
.end()))
631 towidth
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(widthline
.begin(), widthline
.end()))
632 breakline
= breakline
.transformed(trafomodule
.translate(*tocenter
).rotated(self
.breaklinesangle
, *breakline
.begin()))
633 breakline1
= breakline
.transformed(trafomodule
.translate(*towidth
))
634 breakline2
= breakline
.transformed(trafomodule
.translate(-towidth
[0], -towidth
[1]))
635 ac
.fill(path
.path(path
.moveto_pt(*breakline1
.begin_pt()),
636 path
.lineto_pt(*breakline1
.end_pt()),
637 path
.lineto_pt(*breakline2
.end_pt()),
638 path
.lineto_pt(*breakline2
.begin_pt()),
639 path
.closepath()), [color
.gray
.white
])
640 ac
.stroke(breakline1
, self
.defaultbreaklinesattrs
+ self
.breaklinesattrs
)
641 ac
.stroke(breakline2
, self
.defaultbreaklinesattrs
+ self
.breaklinesattrs
)
642 _title
.paint(self
, axispos
, axis
, ac
=ac
)
646 class linkedsplit(split
):
647 """class for painting a linked splitaxis
648 - the inherited split is used to paint the axis
649 - modifies some constructor defaults"""
651 __implements__
= _Iaxispainter
653 def __init__(self
, titleattrs
=None, **kwargs
):
654 """initializes the instance
655 - the titleattrs default is set to None thus skipping the title
656 - all keyword arguments are passed to split"""
657 split
.__init
__(self
, titleattrs
=titleattrs
, **kwargs
)
661 """class for painting a baraxis
662 - the inherited _title is used to paint the title of
664 - the bar access the multisubaxis, names, and subaxis
665 relsizes attributes"""
667 __implements__
= _Iaxispainter
669 defaulttickattrs
= []
670 defaultbasepathattrs
= [style
.linecap
.square
]
671 defaultnameattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
673 def __init__(self
, innerticklength
=None,
674 outerticklength
=None,
684 """initializes the instance
685 - innerticklength and outerticklength are a visual length of
686 the ticks to be plotted at the axis basepath to visually
687 separate the bars; if neither innerticklength nor
688 outerticklength are set, not ticks are plotted
689 - breaklinesattrs are a list of stroke attributes for the
690 axis tick; a single entry is allowed without being a
691 list; None turns off the ticks
692 - namedist is a visual PyX length for the distance of the bar
693 names from the axis basepath
694 - nameattrs is a list of attributes for a texrunners text
695 method; a single entry is allowed without being a list;
696 None turns off the names
697 - namedirection is an instance of rotatetext or None
698 - namehequalize and namevequalize (booleans) perform an equal
699 alignment for straight vertical and horizontal axes, respectively
700 - futher keyword arguments are passed to _title"""
701 self
.innerticklength_str
= innerticklength
702 self
.outerticklength_str
= outerticklength
703 self
.tickattrs
= tickattrs
704 self
.basepathattrs
= basepathattrs
705 self
.namedist_str
= namedist
706 self
.nameattrs
= nameattrs
707 self
.namedirection
= namedirection
708 self
.namepos
= namepos
709 self
.namehequalize
= namehequalize
710 self
.namevequalize
= namevequalize
711 _title
.__init
__(self
, **args
)
713 def paint(self
, axispos
, axis
, ac
=None):
716 if axis
.multisubaxis
is not None:
717 for subaxis
in axis
.subaxis
:
718 subaxis
.finish(subaxispos(subaxis
.convert
, axispos
, subaxis
.vmin
, subaxis
.vmax
, None, None))
719 ac
.insert(subaxis
.axiscanvas
)
720 if ac
.extent
< subaxis
.axiscanvas
.extent
:
721 ac
.extent
= subaxis
.axiscanvas
.extent
723 for name
in axis
.names
:
724 v
= axis
.convert((name
, self
.namepos
))
725 x
, y
= axispos
.vtickpoint_pt(v
)
726 dx
, dy
= axispos
.vtickdirection(v
)
727 namepos
.append((v
, x
, y
, dx
, dy
))
729 if self
.nameattrs
is not None:
730 for (v
, x
, y
, dx
, dy
), name
in zip(namepos
, axis
.names
):
731 nameattrs
= self
.defaultnameattrs
+ self
.nameattrs
732 if self
.namedirection
is not None:
733 nameattrs
.append(self
.namedirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
734 nameboxes
.append(self
.texrunner
.text_pt(x
, y
, str(name
), nameattrs
))
735 labeldist
= ac
.extent
+ unit
.length(self
.namedist_str
, default_type
="v")
738 for np
in namepos
[1:]:
739 if np
[3] != namepos
[0][3] or np
[4] != namepos
[0][4]:
743 if equaldirection
and ((not namepos
[0][3] and self
.namevequalize
) or
744 (not namepos
[0][4] and self
.namehequalize
)):
745 box
.linealignequal(nameboxes
, labeldist
, -namepos
[0][3], -namepos
[0][4])
747 for namebox
, np
in zip(nameboxes
, namepos
):
748 namebox
.linealign(labeldist
, -np
[3], -np
[4])
749 if self
.basepathattrs
is not None:
750 p
= axispos
.vbasepath()
752 ac
.stroke(p
, self
.defaultbasepathattrs
+ self
.basepathattrs
)
753 if self
.tickattrs
is not None and (self
.innerticklength_str
is not None or
754 self
.outerticklength_str
is not None):
755 if self
.innerticklength_str
is not None:
756 innerticklength
= unit
.length(self
.innerticklength_str
, default_type
="v")
757 innerticklength_pt
= unit
.topt(innerticklength
)
758 if ac
.extent
< -innerticklength
:
759 ac
.extent
= -innerticklength
760 elif self
.outerticklength_str
is not None:
761 innerticklength
= innerticklength_pt
= 0
762 if self
.outerticklength_str
is not None:
763 outerticklength
= unit
.length(self
.outerticklength_str
, default_type
="v")
764 outerticklength_pt
= unit
.topt(outerticklength
)
765 if ac
.extent
< outerticklength
:
766 ac
.extent
= outerticklength
767 elif self
.innerticklength_str
is not None:
768 outerticklength
= outerticklength_pt
= 0
769 for pos
in axis
.relsizes
:
770 if pos
== axis
.relsizes
[0]:
771 pos
-= axis
.firstdist
772 elif pos
!= axis
.relsizes
[-1]:
773 pos
-= 0.5 * axis
.dist
774 v
= pos
/ axis
.relsizes
[-1]
775 x
, y
= axispos
.vtickpoint_pt(v
)
776 dx
, dy
= axispos
.vtickdirection(v
)
777 x1
= x
+ dx
* innerticklength_pt
778 y1
= y
+ dy
* innerticklength_pt
779 x2
= x
- dx
* outerticklength_pt
780 y2
= y
- dy
* outerticklength_pt
781 ac
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), self
.defaulttickattrs
+ self
.tickattrs
)
782 for (v
, x
, y
, dx
, dy
), namebox
in zip(namepos
, nameboxes
):
783 newextent
= namebox
.extent(dx
, dy
) + labeldist
784 if ac
.extent
< newextent
:
785 ac
.extent
= newextent
786 for namebox
in nameboxes
:
788 _title
.paint(self
, axispos
, axis
, ac
=ac
)
792 class linkedbar(bar
):
793 """class for painting a linked baraxis
794 - the inherited bar is used to paint the axis
795 - modifies some constructor defaults"""
797 __implements__
= _Iaxispainter
799 def __init__(self
, nameattrs
=None, titleattrs
=None, **kwargs
):
800 """initializes the instance
801 - the titleattrs default is set to None thus skipping the title
802 - the nameattrs default is set to None thus skipping the names
803 - all keyword arguments are passed to bar"""
804 bar
.__init
__(self
, nameattrs
=nameattrs
, titleattrs
=titleattrs
, **kwargs
)