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-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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
):
38 def __init__(self
, painter
, graphtexrunner
):
39 """initializes the instance
41 - sets labels to an empty list"""
42 canvas
._canvas
.__init
__(self
)
45 if isinstance(painter
, _text
) and painter
.texrunner
:
46 self
.settexrunner(painter
.texrunner
)
48 self
.settexrunner(graphtexrunner
)
52 """create rotations accordingly to tick directions"""
54 def __init__(self
, direction
, epsilon
=1e-10):
55 self
.direction
= direction
56 self
.epsilon
= epsilon
58 def trafo(self
, dx
, dy
):
59 direction
= self
.direction
+ math
.atan2(dy
, dx
) * 180 / math
.pi
60 while (direction
> 180 + self
.epsilon
):
62 while (direction
< -180 - self
.epsilon
):
64 while (direction
> 90 + self
.epsilon
):
66 while (direction
< -90 - self
.epsilon
):
68 return trafomodule
.rotate(direction
)
71 rotatetext
.parallel
= rotatetext(90)
72 rotatetext
.orthogonal
= rotatetext(180)
76 """a painter with a texrunner"""
78 def __init__(self
, texrunner
=None):
79 self
.texrunner
= texrunner
83 """class for painting an axis title"""
85 defaulttitleattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
87 def __init__(self
, titledist
=0.3*unit
.v_cm
,
89 titledirection
=rotatetext
.parallel
,
92 self
.titledist
= titledist
93 self
.titleattrs
= titleattrs
94 self
.titledirection
= titledirection
95 self
.titlepos
= titlepos
96 _text
.__init
__(self
, **kwargs
)
98 def paint(self
, canvas
, data
, axis
, axispos
):
99 if axis
.title
is not None and self
.titleattrs
is not None:
100 x
, y
= axispos
.vtickpoint_pt(self
.titlepos
)
101 dx
, dy
= axispos
.vtickdirection(self
.titlepos
)
102 titleattrs
= self
.defaulttitleattrs
+ self
.titleattrs
103 if self
.titledirection
is not None:
104 titleattrs
.append(self
.titledirection
.trafo(dx
, dy
))
105 title
= canvas
.text_pt(x
, y
, axis
.title
, titleattrs
)
106 canvas
.extent_pt
+= unit
.topt(self
.titledist
)
107 title
.linealign_pt(canvas
.extent_pt
, -dx
, -dy
)
108 canvas
.extent_pt
+= title
.extent_pt(dx
, dy
)
112 class geometricseries(attr
.changeattr
):
114 def __init__(self
, initial
, factor
):
115 self
.initial
= initial
118 def select(self
, index
, total
):
119 return self
.initial
* (self
.factor
** index
)
122 class ticklength(geometricseries
): pass
124 _base
= 0.12 * unit
.v_cm
126 ticklength
.SHORT
= ticklength(_base
/math
.sqrt(64), 1/goldenmean
)
127 ticklength
.SHORt
= ticklength(_base
/math
.sqrt(32), 1/goldenmean
)
128 ticklength
.SHOrt
= ticklength(_base
/math
.sqrt(16), 1/goldenmean
)
129 ticklength
.SHort
= ticklength(_base
/math
.sqrt(8), 1/goldenmean
)
130 ticklength
.Short
= ticklength(_base
/math
.sqrt(4), 1/goldenmean
)
131 ticklength
.short
= ticklength(_base
/math
.sqrt(2), 1/goldenmean
)
132 ticklength
.normal
= ticklength(_base
, 1/goldenmean
)
133 ticklength
.long = ticklength(_base
*math
.sqrt(2), 1/goldenmean
)
134 ticklength
.Long
= ticklength(_base
*math
.sqrt(4), 1/goldenmean
)
135 ticklength
.LOng
= ticklength(_base
*math
.sqrt(8), 1/goldenmean
)
136 ticklength
.LONg
= ticklength(_base
*math
.sqrt(16), 1/goldenmean
)
137 ticklength
.LONG
= ticklength(_base
*math
.sqrt(32), 1/goldenmean
)
140 class regular(_title
):
141 """class for painting the ticks and labels of an axis"""
143 defaulttickattrs
= []
144 defaultgridattrs
= []
145 defaultbasepathattrs
= [style
.linecap
.square
]
146 defaultlabelattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
148 def __init__(self
, innerticklength
=ticklength
.normal
,
149 outerticklength
=None,
153 labeldist
=0.3*unit
.v_cm
,
159 self
.innerticklength
= innerticklength
160 self
.outerticklength
= outerticklength
161 self
.tickattrs
= tickattrs
162 self
.gridattrs
= gridattrs
163 self
.basepathattrs
= basepathattrs
164 self
.labeldist
= labeldist
165 self
.labelattrs
= labelattrs
166 self
.labeldirection
= labeldirection
167 self
.labelhequalize
= labelhequalize
168 self
.labelvequalize
= labelvequalize
169 _title
.__init
__(self
, **kwargs
)
171 def paint(self
, canvas
, data
, axis
, axispos
):
173 t
.temp_v
= axis
.convert(data
, t
)
174 t
.temp_x_pt
, t
.temp_y_pt
= axispos
.vtickpoint_pt(t
.temp_v
)
175 t
.temp_dx
, t
.temp_dy
= axispos
.vtickdirection(t
.temp_v
)
176 maxticklevel
, maxlabellevel
= tick
.maxlevels(data
.ticks
)
177 labeldist_pt
= unit
.topt(self
.labeldist
)
179 # create & align t.temp_labelbox
181 if t
.labellevel
is not None:
182 labelattrs
= attr
.selectattrs(self
.labelattrs
, t
.labellevel
, maxlabellevel
)
183 if labelattrs
is not None:
184 labelattrs
= self
.defaultlabelattrs
+ labelattrs
185 if self
.labeldirection
is not None:
186 labelattrs
.append(self
.labeldirection
.trafo(t
.temp_dx
, t
.temp_dy
))
187 if t
.labelattrs
is not None:
188 labelattrs
.extend(t
.labelattrs
)
189 t
.temp_labelbox
= canvas
.texrunner
.text_pt(t
.temp_x_pt
, t
.temp_y_pt
, t
.label
, labelattrs
)
190 if len(data
.ticks
) > 1:
192 for t
in data
.ticks
[1:]:
193 if t
.temp_dx
!= data
.ticks
[0].temp_dx
or t
.temp_dy
!= data
.ticks
[0].temp_dy
:
197 if equaldirection
and ((not data
.ticks
[0].temp_dx
and self
.labelvequalize
) or
198 (not data
.ticks
[0].temp_dy
and self
.labelhequalize
)):
199 if self
.labelattrs
is not None:
200 box
.linealignequal_pt([t
.temp_labelbox
for t
in data
.ticks
if t
.labellevel
is not None],
201 labeldist_pt
, -data
.ticks
[0].temp_dx
, -data
.ticks
[0].temp_dy
)
204 if t
.labellevel
is not None and self
.labelattrs
is not None:
205 t
.temp_labelbox
.linealign_pt(labeldist_pt
, -t
.temp_dx
, -t
.temp_dy
)
208 if t
.ticklevel
is not None and self
.tickattrs
is not None:
209 tickattrs
= attr
.selectattrs(self
.defaulttickattrs
+ self
.tickattrs
, t
.ticklevel
, maxticklevel
)
210 if tickattrs
is not None:
211 innerticklength
= attr
.selectattr(self
.innerticklength
, t
.ticklevel
, maxticklevel
)
212 outerticklength
= attr
.selectattr(self
.outerticklength
, t
.ticklevel
, maxticklevel
)
213 if innerticklength
is not None or outerticklength
is not None:
214 if innerticklength
is None:
216 if outerticklength
is None:
218 innerticklength_pt
= unit
.topt(innerticklength
)
219 outerticklength_pt
= unit
.topt(outerticklength
)
220 x1
= t
.temp_x_pt
+ t
.temp_dx
* innerticklength_pt
221 y1
= t
.temp_y_pt
+ t
.temp_dy
* innerticklength_pt
222 x2
= t
.temp_x_pt
- t
.temp_dx
* outerticklength_pt
223 y2
= t
.temp_y_pt
- t
.temp_dy
* outerticklength_pt
224 canvas
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), tickattrs
)
225 if outerticklength_pt
> canvas
.extent_pt
:
226 canvas
.extent_pt
= outerticklength_pt
227 if -innerticklength_pt
> canvas
.extent_pt
:
228 canvas
.extent_pt
= -innerticklength_pt
229 if self
.gridattrs
is not None:
230 gridattrs
= attr
.selectattrs(self
.defaultgridattrs
+ self
.gridattrs
, t
.ticklevel
, maxticklevel
)
231 if gridattrs
is not None:
232 canvas
.stroke(axispos
.vgridpath(t
.temp_v
), gridattrs
)
233 if t
.labellevel
is not None and self
.labelattrs
is not None:
234 canvas
.insert(t
.temp_labelbox
)
235 canvas
.labels
.append(t
.temp_labelbox
)
236 extent_pt
= t
.temp_labelbox
.extent_pt(t
.temp_dx
, t
.temp_dy
) + labeldist_pt
237 if extent_pt
> canvas
.extent_pt
:
238 canvas
.extent_pt
= extent_pt
240 if self
.labelattrs
is None:
243 if self
.basepathattrs
is not None:
244 canvas
.stroke(axispos
.vbasepath(), self
.defaultbasepathattrs
+ self
.basepathattrs
)
246 # for t in data.ticks:
247 # del t.temp_v # we've inserted those temporary variables ... and do not care any longer about them
252 # if t.labellevel is not None and self.labelattrs is not None:
253 # del t.temp_labelbox
255 _title
.paint(self
, canvas
, data
, axis
, axispos
)
258 class linked(regular
):
259 """class for painting a linked axis"""
261 def __init__(self
, labelattrs
=None, # turn off labels and title
264 regular
.__init
__(self
, labelattrs
=labelattrs
,
265 titleattrs
=titleattrs
,
270 """class for painting a baraxis"""
272 defaulttickattrs
= []
273 defaultbasepathattrs
= [style
.linecap
.square
]
274 defaultnameattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
276 def __init__(self
, innerticklength
=None,
277 outerticklength
=None,
280 namedist
=0.3*unit
.v_cm
,
287 self
.innerticklength
= innerticklength
288 self
.outerticklength
= outerticklength
289 self
.tickattrs
= tickattrs
290 self
.basepathattrs
= basepathattrs
291 self
.namedist
= namedist
292 self
.nameattrs
= nameattrs
293 self
.namedirection
= namedirection
294 self
.namepos
= namepos
295 self
.namehequalize
= namehequalize
296 self
.namevequalize
= namevequalize
297 _title
.__init
__(self
, **args
)
299 def paint(self
, canvas
, data
, axis
, positioner
):
301 for name
in data
.names
:
302 subaxis
= data
.subaxes
[name
]
303 v
= subaxis
.vmin
+ self
.namepos
* (subaxis
.vmax
- subaxis
.vmin
)
304 x
, y
= positioner
.vtickpoint_pt(v
)
305 dx
, dy
= positioner
.vtickdirection(v
)
306 namepos
.append((v
, x
, y
, dx
, dy
))
308 if self
.nameattrs
is not None:
309 for (v
, x
, y
, dx
, dy
), name
in zip(namepos
, data
.names
):
310 nameattrs
= self
.defaultnameattrs
+ self
.nameattrs
311 if self
.namedirection
is not None:
312 nameattrs
.append(self
.namedirection
.trafo(tick
.temp_dx
, tick
.temp_dy
))
313 nameboxes
.append(canvas
.texrunner
.text_pt(x
, y
, str(name
), nameattrs
))
314 labeldist_pt
= canvas
.extent_pt
+ unit
.topt(self
.namedist
)
317 for np
in namepos
[1:]:
318 if np
[3] != namepos
[0][3] or np
[4] != namepos
[0][4]:
322 if equaldirection
and ((not namepos
[0][3] and self
.namevequalize
) or
323 (not namepos
[0][4] and self
.namehequalize
)):
324 box
.linealignequal_pt(nameboxes
, labeldist_pt
, -namepos
[0][3], -namepos
[0][4])
326 for namebox
, np
in zip(nameboxes
, namepos
):
327 namebox
.linealign_pt(labeldist_pt
, -np
[3], -np
[4])
328 if self
.basepathattrs
is not None:
329 p
= positioner
.vbasepath()
331 canvas
.stroke(p
, self
.defaultbasepathattrs
+ self
.basepathattrs
)
332 if ( self
.tickattrs
is not None and
333 (self
.innerticklength
is not None or self
.outerticklength
is not None) ):
334 if self
.innerticklength
is not None:
335 innerticklength_pt
= unit
.topt(self
.innerticklength
)
336 if canvas
.extent_pt
< -innerticklength_pt
:
337 canvas
.extent_pt
= -innerticklength_pt
338 elif self
.outerticklength
is not None:
339 innerticklength_pt
= 0
340 if self
.outerticklength
is not None:
341 outerticklength_pt
= unit
.topt(self
.outerticklength
)
342 if canvas
.extent_pt
< outerticklength_pt
:
343 canvas
.extent_pt
= outerticklength_pt
344 elif innerticklength_pt
is not None:
345 outerticklength_pt
= 0
346 for v
in [data
.subaxes
[name
].vminover
for name
in data
.names
] + [1]:
347 x
, y
= positioner
.vtickpoint_pt(v
)
348 dx
, dy
= positioner
.vtickdirection(v
)
349 x1
= x
+ dx
* innerticklength_pt
350 y1
= y
+ dy
* innerticklength_pt
351 x2
= x
- dx
* outerticklength_pt
352 y2
= y
- dy
* outerticklength_pt
353 canvas
.stroke(path
.line_pt(x1
, y1
, x2
, y2
), self
.defaulttickattrs
+ self
.tickattrs
)
354 for (v
, x
, y
, dx
, dy
), namebox
in zip(namepos
, nameboxes
):
355 newextent_pt
= namebox
.extent_pt(dx
, dy
) + labeldist_pt
356 if canvas
.extent_pt
< newextent_pt
:
357 canvas
.extent_pt
= newextent_pt
358 for namebox
in nameboxes
:
359 canvas
.insert(namebox
)
360 _title
.paint(self
, canvas
, data
, axis
, positioner
)
363 class linkedbar(bar
):
364 """class for painting a linked baraxis"""
366 def __init__(self
, nameattrs
=None, titleattrs
=None, **kwargs
):
367 bar
.__init
__(self
, nameattrs
=nameattrs
, titleattrs
=titleattrs
, **kwargs
)
369 def getsubaxis(self
, subaxis
, name
):
370 from pyx
.graph
.axis
import linkedaxis
371 return linkedaxis(subaxis
, name
)
375 """class for painting a splitaxis"""
377 defaultbreaklinesattrs
= []
379 def __init__(self
, breaklinesdist
=0.05*unit
.v_cm
,
380 breaklineslength
=0.5*unit
.v_cm
,
384 self
.breaklinesdist
= breaklinesdist
385 self
.breaklineslength
= breaklineslength
386 self
.breaklinesangle
= breaklinesangle
387 self
.breaklinesattrs
= breaklinesattrs
388 self
.sin
= math
.sin(self
.breaklinesangle
*math
.pi
/180.0)
389 self
.cos
= math
.cos(self
.breaklinesangle
*math
.pi
/180.0)
390 _title
.__init
__(self
, **args
)
392 def paint(self
, canvas
, data
, axis
, axispos
):
393 if self
.breaklinesattrs
is not None:
394 breaklinesdist_pt
= unit
.topt(self
.breaklinesdist
)
395 breaklineslength_pt
= unit
.topt(self
.breaklineslength
)
396 breaklinesextent_pt
= (0.5*breaklinesdist_pt
*math
.fabs(self
.cos
) +
397 0.5*breaklineslength_pt
*math
.fabs(self
.sin
))
398 if canvas
.extent_pt
< breaklinesextent_pt
:
399 canvas
.extent_pt
= breaklinesextent_pt
400 for v
in [data
.subaxes
[name
].vminover
for name
in data
.names
[1:]]:
401 # use a tangent of the basepath (this is independent of the tickdirection)
402 p
= axispos
.vbasepath(v
, None).normpath()
403 breakline
= p
.tangent(0, length
=self
.breaklineslength
)
404 widthline
= p
.tangent(0, length
=self
.breaklinesdist
).transformed(trafomodule
.rotate(self
.breaklinesangle
+90, *breakline
.atbegin()))
406 tocenter
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(breakline
.atbegin(), breakline
.atend()))
407 towidth
= map(lambda x
: 0.5*(x
[0]-x
[1]), zip(widthline
.atbegin(), widthline
.atend()))
408 breakline
= breakline
.transformed(trafomodule
.translate(*tocenter
).rotated(self
.breaklinesangle
, *breakline
.atbegin()))
409 breakline1
= breakline
.transformed(trafomodule
.translate(*towidth
))
410 breakline2
= breakline
.transformed(trafomodule
.translate(-towidth
[0], -towidth
[1]))
411 canvas
.fill(path
.path(path
.moveto_pt(*breakline1
.atbegin_pt()),
412 path
.lineto_pt(*breakline1
.atend_pt()),
413 path
.lineto_pt(*breakline2
.atend_pt()),
414 path
.lineto_pt(*breakline2
.atbegin_pt()),
415 path
.closepath()), [color
.gray
.white
])
416 canvas
.stroke(breakline1
, self
.defaultbreaklinesattrs
+ self
.breaklinesattrs
)
417 canvas
.stroke(breakline2
, self
.defaultbreaklinesattrs
+ self
.breaklinesattrs
)
418 _title
.paint(self
, canvas
, data
, axis
, axispos
)
421 class linkedsplit(split
):
423 def __init__(self
, titleattrs
=None, **kwargs
):
424 split
.__init
__(self
, titleattrs
=titleattrs
, **kwargs
)