1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2005 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
26 from pyx
import canvas
, color
, attr
, text
, style
, unit
, box
, path
27 from pyx
import trafo
as trafomodule
28 from pyx
.graph
.axis
import tick
31 goldenmean
= 0.5 * (math
.sqrt(5) + 1)
34 class axiscanvas(canvas
.canvas
):
37 def __init__(self
, painter
, graphtexrunner
):
38 """initializes the instance
40 - sets labels to an empty list"""
41 canvas
._canvas
.__init
__(self
)
44 if isinstance(painter
, _text
) and painter
.texrunner
:
45 self
.settexrunner(painter
.texrunner
)
47 self
.settexrunner(graphtexrunner
)
51 """create rotations accordingly to tick directions"""
53 def __init__(self
, direction
, epsilon
=1e-10):
54 self
.direction
= direction
55 self
.epsilon
= epsilon
57 def trafo(self
, dx
, dy
):
58 direction
= self
.direction
+ math
.atan2(dy
, dx
) * 180 / math
.pi
59 while (direction
> 180 + self
.epsilon
):
61 while (direction
< -180 - self
.epsilon
):
63 while (direction
> 90 + self
.epsilon
):
65 while (direction
< -90 - self
.epsilon
):
67 return trafomodule
.rotate(direction
)
70 rotatetext
.parallel
= rotatetext(90)
71 rotatetext
.orthogonal
= rotatetext(180)
75 """a painter with a texrunner"""
77 def __init__(self
, texrunner
=None):
78 self
.texrunner
= texrunner
82 """class for painting an axis title"""
84 defaulttitleattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
86 def __init__(self
, titledist
=0.3*unit
.v_cm
,
88 titledirection
=rotatetext
.parallel
,
91 self
.titledist
= titledist
92 self
.titleattrs
= titleattrs
93 self
.titledirection
= titledirection
94 self
.titlepos
= titlepos
95 _text
.__init
__(self
, **kwargs
)
97 def paint(self
, canvas
, data
, axis
, axispos
):
98 if axis
.title
is not None and self
.titleattrs
is not None:
99 x
, y
= axispos
.vtickpoint_pt(self
.titlepos
)
100 dx
, dy
= axispos
.vtickdirection(self
.titlepos
)
101 titleattrs
= self
.defaulttitleattrs
+ self
.titleattrs
102 if self
.titledirection
is not None:
103 titleattrs
.append(self
.titledirection
.trafo(dx
, dy
))
104 title
= canvas
.text_pt(x
, y
, axis
.title
, titleattrs
)
105 canvas
.extent_pt
+= unit
.topt(self
.titledist
)
106 title
.linealign_pt(canvas
.extent_pt
, -dx
, -dy
)
107 canvas
.extent_pt
+= title
.extent_pt(dx
, dy
)
111 class geometricseries(attr
.changeattr
):
113 def __init__(self
, initial
, factor
):
114 self
.initial
= initial
117 def select(self
, index
, total
):
118 return self
.initial
* (self
.factor
** index
)
121 class ticklength(geometricseries
): pass
123 _base
= 0.12 * unit
.v_cm
125 ticklength
.SHORT
= ticklength(_base
/math
.sqrt(64), 1/goldenmean
)
126 ticklength
.SHORt
= ticklength(_base
/math
.sqrt(32), 1/goldenmean
)
127 ticklength
.SHOrt
= ticklength(_base
/math
.sqrt(16), 1/goldenmean
)
128 ticklength
.SHort
= ticklength(_base
/math
.sqrt(8), 1/goldenmean
)
129 ticklength
.Short
= ticklength(_base
/math
.sqrt(4), 1/goldenmean
)
130 ticklength
.short
= ticklength(_base
/math
.sqrt(2), 1/goldenmean
)
131 ticklength
.normal
= ticklength(_base
, 1/goldenmean
)
132 ticklength
.long = ticklength(_base
*math
.sqrt(2), 1/goldenmean
)
133 ticklength
.Long
= ticklength(_base
*math
.sqrt(4), 1/goldenmean
)
134 ticklength
.LOng
= ticklength(_base
*math
.sqrt(8), 1/goldenmean
)
135 ticklength
.LONg
= ticklength(_base
*math
.sqrt(16), 1/goldenmean
)
136 ticklength
.LONG
= ticklength(_base
*math
.sqrt(32), 1/goldenmean
)
139 class regular(_title
):
140 """class for painting the ticks and labels of an axis"""
142 defaulttickattrs
= []
143 defaultgridattrs
= []
144 defaultbasepathattrs
= [style
.linecap
.square
]
145 defaultlabelattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
147 def __init__(self
, innerticklength
=ticklength
.normal
,
148 outerticklength
=None,
152 labeldist
=0.3*unit
.v_cm
,
158 self
.innerticklength
= innerticklength
159 self
.outerticklength
= outerticklength
160 self
.tickattrs
= tickattrs
161 self
.gridattrs
= gridattrs
162 self
.basepathattrs
= basepathattrs
163 self
.labeldist
= labeldist
164 self
.labelattrs
= labelattrs
165 self
.labeldirection
= labeldirection
166 self
.labelhequalize
= labelhequalize
167 self
.labelvequalize
= labelvequalize
168 _title
.__init
__(self
, **kwargs
)
170 def paint(self
, canvas
, data
, axis
, axispos
):
172 t
.temp_v
= axis
.convert(data
, t
)
173 t
.temp_x_pt
, t
.temp_y_pt
= axispos
.vtickpoint_pt(t
.temp_v
)
174 t
.temp_dx
, t
.temp_dy
= axispos
.vtickdirection(t
.temp_v
)
175 maxticklevel
, maxlabellevel
= tick
.maxlevels(data
.ticks
)
176 labeldist_pt
= unit
.topt(self
.labeldist
)
178 # create & align t.temp_labelbox
180 if t
.labellevel
is not None:
181 labelattrs
= attr
.selectattrs(self
.labelattrs
, t
.labellevel
, maxlabellevel
)
182 if labelattrs
is not None:
183 labelattrs
= self
.defaultlabelattrs
+ labelattrs
184 if self
.labeldirection
is not None:
185 labelattrs
.append(self
.labeldirection
.trafo(t
.temp_dx
, t
.temp_dy
))
186 if t
.labelattrs
is not None:
187 labelattrs
.extend(t
.labelattrs
)
188 t
.temp_labelbox
= canvas
.texrunner
.text_pt(t
.temp_x_pt
, t
.temp_y_pt
, t
.label
, labelattrs
)
189 if len(data
.ticks
) > 1:
191 for t
in data
.ticks
[1:]:
192 if t
.temp_dx
!= data
.ticks
[0].temp_dx
or t
.temp_dy
!= data
.ticks
[0].temp_dy
:
196 if equaldirection
and ((not data
.ticks
[0].temp_dx
and self
.labelvequalize
) or
197 (not data
.ticks
[0].temp_dy
and self
.labelhequalize
)):
198 if self
.labelattrs
is not None:
199 box
.linealignequal_pt([t
.temp_labelbox
for t
in data
.ticks
if t
.labellevel
is not None],
200 labeldist_pt
, -data
.ticks
[0].temp_dx
, -data
.ticks
[0].temp_dy
)
203 if t
.labellevel
is not None and self
.labelattrs
is not None:
204 t
.temp_labelbox
.linealign_pt(labeldist_pt
, -t
.temp_dx
, -t
.temp_dy
)
207 if t
.ticklevel
is not None and self
.tickattrs
is not None:
208 tickattrs
= attr
.selectattrs(self
.defaulttickattrs
+ self
.tickattrs
, t
.ticklevel
, maxticklevel
)
209 if tickattrs
is not None:
210 innerticklength
= attr
.selectattr(self
.innerticklength
, t
.ticklevel
, maxticklevel
)
211 outerticklength
= attr
.selectattr(self
.outerticklength
, t
.ticklevel
, maxticklevel
)
212 if innerticklength
is not None or outerticklength
is not None:
213 if innerticklength
is None:
215 if outerticklength
is None:
217 innerticklength_pt
= unit
.topt(innerticklength
)
218 outerticklength_pt
= unit
.topt(outerticklength
)
219 x1
= t
.temp_x_pt
+ t
.temp_dx
* innerticklength_pt
220 y1
= t
.temp_y_pt
+ t
.temp_dy
* innerticklength_pt
221 x2
= t
.temp_x_pt
- t
.temp_dx
* outerticklength_pt
222 y2
= t
.temp_y_pt
- t
.temp_dy
* outerticklength_pt
223 canvas
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), tickattrs
)
224 if outerticklength_pt
> canvas
.extent_pt
:
225 canvas
.extent_pt
= outerticklength_pt
226 if -innerticklength_pt
> canvas
.extent_pt
:
227 canvas
.extent_pt
= -innerticklength_pt
228 if self
.gridattrs
is not None:
229 gridattrs
= attr
.selectattrs(self
.defaultgridattrs
+ self
.gridattrs
, t
.ticklevel
, maxticklevel
)
230 if gridattrs
is not None:
231 canvas
.stroke(axispos
.vgridpath(t
.temp_v
), gridattrs
)
232 if t
.labellevel
is not None and self
.labelattrs
is not None:
233 canvas
.insert(t
.temp_labelbox
)
234 canvas
.labels
.append(t
.temp_labelbox
)
235 extent_pt
= t
.temp_labelbox
.extent_pt(t
.temp_dx
, t
.temp_dy
) + labeldist_pt
236 if extent_pt
> canvas
.extent_pt
:
237 canvas
.extent_pt
= extent_pt
239 if self
.labelattrs
is None:
242 if self
.basepathattrs
is not None:
243 canvas
.stroke(axispos
.vbasepath(), self
.defaultbasepathattrs
+ self
.basepathattrs
)
245 # for t in data.ticks:
246 # del t.temp_v # we've inserted those temporary variables ... and do not care any longer about them
251 # if t.labellevel is not None and self.labelattrs is not None:
252 # del t.temp_labelbox
254 _title
.paint(self
, canvas
, data
, axis
, axispos
)
257 class linked(regular
):
258 """class for painting a linked axis"""
260 def __init__(self
, labelattrs
=None, # turn off labels and title
263 regular
.__init
__(self
, labelattrs
=labelattrs
,
264 titleattrs
=titleattrs
,
269 """class for painting a baraxis"""
271 defaulttickattrs
= []
272 defaultbasepathattrs
= [style
.linecap
.square
]
273 defaultnameattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
275 def __init__(self
, innerticklength
=None,
276 outerticklength
=None,
279 namedist
=0.3*unit
.v_cm
,
286 self
.innerticklength
= innerticklength
287 self
.outerticklength
= outerticklength
288 self
.tickattrs
= tickattrs
289 self
.basepathattrs
= basepathattrs
290 self
.namedist
= namedist
291 self
.nameattrs
= nameattrs
292 self
.namedirection
= namedirection
293 self
.namepos
= namepos
294 self
.namehequalize
= namehequalize
295 self
.namevequalize
= namevequalize
296 _title
.__init
__(self
, **args
)
298 def paint(self
, canvas
, data
, axis
, positioner
):
300 for name
in data
.names
:
301 subaxis
= data
.subaxes
[name
]
302 v
= subaxis
.vmin
+ self
.namepos
* (subaxis
.vmax
- subaxis
.vmin
)
303 x
, y
= positioner
.vtickpoint_pt(v
)
304 dx
, dy
= positioner
.vtickdirection(v
)
305 namepos
.append((v
, x
, y
, dx
, dy
))
307 if self
.nameattrs
is not None:
308 for (v
, x
, y
, dx
, dy
), name
in zip(namepos
, data
.names
):
309 nameattrs
= self
.defaultnameattrs
+ self
.nameattrs
310 if self
.namedirection
is not None:
311 nameattrs
.append(self
.namedirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
312 nameboxes
.append(canvas
.texrunner
.text_pt(x
, y
, str(name
), nameattrs
))
313 labeldist_pt
= canvas
.extent_pt
+ unit
.topt(self
.namedist
)
316 for np
in namepos
[1:]:
317 if np
[3] != namepos
[0][3] or np
[4] != namepos
[0][4]:
321 if equaldirection
and ((not namepos
[0][3] and self
.namevequalize
) or
322 (not namepos
[0][4] and self
.namehequalize
)):
323 box
.linealignequal_pt(nameboxes
, labeldist_pt
, -namepos
[0][3], -namepos
[0][4])
325 for namebox
, np
in zip(nameboxes
, namepos
):
326 namebox
.linealign_pt(labeldist_pt
, -np
[3], -np
[4])
327 if self
.basepathattrs
is not None:
328 p
= positioner
.vbasepath()
330 canvas
.stroke(p
, self
.defaultbasepathattrs
+ self
.basepathattrs
)
331 if ( self
.tickattrs
is not None and
332 (self
.innerticklength
is not None or self
.outerticklength
is not None) ):
333 if self
.innerticklength
is not None:
334 innerticklength_pt
= unit
.topt(self
.innerticklength
)
335 if canvas
.extent_pt
< -innerticklength_pt
:
336 canvas
.extent_pt
= -innerticklength_pt
337 elif self
.outerticklength
is not None:
338 innerticklength_pt
= 0
339 if self
.outerticklength
is not None:
340 outerticklength_pt
= unit
.topt(self
.outerticklength
)
341 if canvas
.extent_pt
< outerticklength_pt
:
342 canvas
.extent_pt
= outerticklength_pt
343 elif innerticklength_pt
is not None:
344 outerticklength_pt
= 0
345 for v
in [data
.subaxes
[name
].vminover
for name
in data
.names
] + [1]:
346 x
, y
= positioner
.vtickpoint_pt(v
)
347 dx
, dy
= positioner
.vtickdirection(v
)
348 x1
= x
+ dx
* innerticklength_pt
349 y1
= y
+ dy
* innerticklength_pt
350 x2
= x
- dx
* outerticklength_pt
351 y2
= y
- dy
* outerticklength_pt
352 canvas
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), self
.defaulttickattrs
+ self
.tickattrs
)
353 for (v
, x
, y
, dx
, dy
), namebox
in zip(namepos
, nameboxes
):
354 newextent_pt
= namebox
.extent_pt(dx
, dy
) + labeldist_pt
355 if canvas
.extent_pt
< newextent_pt
:
356 canvas
.extent_pt
= newextent_pt
357 for namebox
in nameboxes
:
358 canvas
.insert(namebox
)
359 _title
.paint(self
, canvas
, data
, axis
, positioner
)
362 class linkedbar(bar
):
363 """class for painting a linked baraxis"""
365 def __init__(self
, nameattrs
=None, titleattrs
=None, **kwargs
):
366 bar
.__init
__(self
, nameattrs
=nameattrs
, titleattrs
=titleattrs
, **kwargs
)
368 def getsubaxis(self
, subaxis
, name
):
369 from pyx
.graph
.axis
import linkedaxis
370 return linkedaxis(subaxis
, name
)
374 """class for painting a splitaxis"""
376 defaultbreaklinesattrs
= []
378 def __init__(self
, breaklinesdist
=0.05*unit
.v_cm
,
379 breaklineslength
=0.5*unit
.v_cm
,
383 self
.breaklinesdist
= breaklinesdist
384 self
.breaklineslength
= breaklineslength
385 self
.breaklinesangle
= breaklinesangle
386 self
.breaklinesattrs
= breaklinesattrs
387 self
.sin
= math
.sin(self
.breaklinesangle
*math
.pi
/180.0)
388 self
.cos
= math
.cos(self
.breaklinesangle
*math
.pi
/180.0)
389 _title
.__init
__(self
, **args
)
391 def paint(self
, canvas
, data
, axis
, axispos
):
392 if self
.breaklinesattrs
is not None:
393 breaklinesdist_pt
= unit
.topt(self
.breaklinesdist
)
394 breaklineslength_pt
= unit
.topt(self
.breaklineslength
)
395 breaklinesextent_pt
= (0.5*breaklinesdist_pt
*math
.fabs(self
.cos
) +
396 0.5*breaklineslength_pt
*math
.fabs(self
.sin
))
397 if canvas
.extent_pt
< breaklinesextent_pt
:
398 canvas
.extent_pt
= breaklinesextent_pt
399 for v
in [data
.subaxes
[name
].vminover
for name
in data
.names
[1:]]:
400 # use a tangent of the basepath (this is independent of the tickdirection)
401 p
= axispos
.vbasepath(v
, None).normpath()
402 breakline
= p
.tangent(0, length
=self
.breaklineslength
)
403 widthline
= p
.tangent(0, length
=self
.breaklinesdist
).transformed(trafomodule
.rotate(self
.breaklinesangle
+90, *breakline
.atbegin()))
405 tocenter
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(breakline
.atbegin(), breakline
.atend()))
406 towidth
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(widthline
.atbegin(), widthline
.atend()))
407 breakline
= breakline
.transformed(trafomodule
.translate(*tocenter
).rotated(self
.breaklinesangle
, *breakline
.atbegin()))
408 breakline1
= breakline
.transformed(trafomodule
.translate(*towidth
))
409 breakline2
= breakline
.transformed(trafomodule
.translate(-towidth
[0], -towidth
[1]))
410 canvas
.fill(path
.path(path
.moveto_pt(*breakline1
.atbegin_pt()),
411 path
.lineto_pt(*breakline1
.atend_pt()),
412 path
.lineto_pt(*breakline2
.atend_pt()),
413 path
.lineto_pt(*breakline2
.atbegin_pt()),
414 path
.closepath()), [color
.gray
.white
])
415 canvas
.stroke(breakline1
, self
.defaultbreaklinesattrs
+ self
.breaklinesattrs
)
416 canvas
.stroke(breakline2
, self
.defaultbreaklinesattrs
+ self
.breaklinesattrs
)
417 _title
.paint(self
, canvas
, data
, axis
, axispos
)
420 class linkedsplit(split
):
422 def __init__(self
, titleattrs
=None, **kwargs
):
423 split
.__init
__(self
, titleattrs
=titleattrs
, **kwargs
)