1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2002-2012 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2012 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 x2
, y2
= axispos
.vtickpoint_pt(self
.titlepos
+0.001) # XXX: axisdirection needed
104 dx2
, dy2
= x2
-x
, y2
-y
105 if dx
*dy2
-dy
*dx2
< 0:
108 titleattrs
.append(self
.titledirection
.trafo(dy2
, -dx2
))
109 title
= canvas
.layer("title").text_pt(x
, y
, axis
.title
, titleattrs
)
110 canvas
.extent_pt
+= unit
.topt(self
.titledist
)
111 title
.linealign_pt(canvas
.extent_pt
, -dx
, -dy
)
112 canvas
.extent_pt
+= title
.extent_pt(dx
, dy
)
115 class geometricseries(attr
.changeattr
):
117 def __init__(self
, initial
, factor
):
118 self
.initial
= initial
121 def select(self
, index
, total
):
122 return self
.initial
* (self
.factor
** index
)
125 class ticklength(geometricseries
): pass
127 _base
= 0.12 * unit
.v_cm
129 ticklength
.SHORT
= ticklength(_base
/math
.sqrt(64), 1/goldenmean
)
130 ticklength
.SHORt
= ticklength(_base
/math
.sqrt(32), 1/goldenmean
)
131 ticklength
.SHOrt
= ticklength(_base
/math
.sqrt(16), 1/goldenmean
)
132 ticklength
.SHort
= ticklength(_base
/math
.sqrt(8), 1/goldenmean
)
133 ticklength
.Short
= ticklength(_base
/math
.sqrt(4), 1/goldenmean
)
134 ticklength
.short
= ticklength(_base
/math
.sqrt(2), 1/goldenmean
)
135 ticklength
.normal
= ticklength(_base
, 1/goldenmean
)
136 ticklength
.long = ticklength(_base
*math
.sqrt(2), 1/goldenmean
)
137 ticklength
.Long
= ticklength(_base
*math
.sqrt(4), 1/goldenmean
)
138 ticklength
.LOng
= ticklength(_base
*math
.sqrt(8), 1/goldenmean
)
139 ticklength
.LONg
= ticklength(_base
*math
.sqrt(16), 1/goldenmean
)
140 ticklength
.LONG
= ticklength(_base
*math
.sqrt(32), 1/goldenmean
)
143 class regular(_title
):
144 """class for painting the ticks and labels of an axis"""
146 defaulttickattrs
= []
147 defaultgridattrs
= []
148 defaultbasepathattrs
= [style
.linecap
.square
]
149 defaultlabelattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
151 def __init__(self
, innerticklength
=ticklength
.normal
,
152 outerticklength
=None,
156 labeldist
=0.3*unit
.v_cm
,
162 self
.innerticklength
= innerticklength
163 self
.outerticklength
= outerticklength
164 self
.tickattrs
= tickattrs
165 self
.gridattrs
= gridattrs
166 self
.basepathattrs
= basepathattrs
167 self
.labeldist
= labeldist
168 self
.labelattrs
= labelattrs
169 self
.labeldirection
= labeldirection
170 self
.labelhequalize
= labelhequalize
171 self
.labelvequalize
= labelvequalize
172 _title
.__init
__(self
, **kwargs
)
174 def paint(self
, canvas
, data
, axis
, axispos
):
176 t
.temp_v
= axis
.convert(data
, t
)
177 t
.temp_x_pt
, t
.temp_y_pt
= axispos
.vtickpoint_pt(t
.temp_v
)
178 t
.temp_dx
, t
.temp_dy
= axispos
.vtickdirection(t
.temp_v
)
179 maxticklevel
, maxlabellevel
= tick
.maxlevels(data
.ticks
)
180 labeldist_pt
= unit
.topt(self
.labeldist
)
182 # create & align t.temp_labelbox
184 if t
.labellevel
is not None:
185 labelattrs
= attr
.selectattrs(self
.labelattrs
, t
.labellevel
, maxlabellevel
)
186 if labelattrs
is not None:
187 labelattrs
= self
.defaultlabelattrs
+ labelattrs
188 if self
.labeldirection
is not None:
189 labelattrs
.append(self
.labeldirection
.trafo(t
.temp_dx
, t
.temp_dy
))
190 if t
.labelattrs
is not None:
191 labelattrs
.extend(t
.labelattrs
)
192 t
.temp_labelbox
= canvas
.texrunner
.text_pt(t
.temp_x_pt
, t
.temp_y_pt
, t
.label
, labelattrs
)
193 if len(data
.ticks
) > 1:
195 for t
in data
.ticks
[1:]:
196 if t
.temp_dx
!= data
.ticks
[0].temp_dx
or t
.temp_dy
!= data
.ticks
[0].temp_dy
:
200 if equaldirection
and ((not data
.ticks
[0].temp_dx
and self
.labelvequalize
) or
201 (not data
.ticks
[0].temp_dy
and self
.labelhequalize
)):
202 if self
.labelattrs
is not None:
203 box
.linealignequal_pt([t
.temp_labelbox
for t
in data
.ticks
if t
.labellevel
is not None],
204 labeldist_pt
, -data
.ticks
[0].temp_dx
, -data
.ticks
[0].temp_dy
)
207 if t
.labellevel
is not None and self
.labelattrs
is not None:
208 t
.temp_labelbox
.linealign_pt(labeldist_pt
, -t
.temp_dx
, -t
.temp_dy
)
211 if t
.ticklevel
is not None and self
.tickattrs
is not None:
212 tickattrs
= attr
.selectattrs(self
.defaulttickattrs
+ self
.tickattrs
, t
.ticklevel
, maxticklevel
)
213 if tickattrs
is not None:
214 innerticklength
= attr
.selectattr(self
.innerticklength
, t
.ticklevel
, maxticklevel
)
215 outerticklength
= attr
.selectattr(self
.outerticklength
, t
.ticklevel
, maxticklevel
)
216 if innerticklength
is not None or outerticklength
is not None:
217 if innerticklength
is None:
219 if outerticklength
is None:
221 innerticklength_pt
= unit
.topt(innerticklength
)
222 outerticklength_pt
= unit
.topt(outerticklength
)
223 x1
= t
.temp_x_pt
+ t
.temp_dx
* innerticklength_pt
224 y1
= t
.temp_y_pt
+ t
.temp_dy
* innerticklength_pt
225 x2
= t
.temp_x_pt
- t
.temp_dx
* outerticklength_pt
226 y2
= t
.temp_y_pt
- t
.temp_dy
* outerticklength_pt
227 canvas
.layer("ticks").stroke(path
.line_pt(x1
, y1
, x2
, y2
), tickattrs
)
228 if outerticklength_pt
> canvas
.extent_pt
:
229 canvas
.extent_pt
= outerticklength_pt
230 if -innerticklength_pt
> canvas
.extent_pt
:
231 canvas
.extent_pt
= -innerticklength_pt
232 if self
.gridattrs
is not None:
233 gridattrs
= attr
.selectattrs(self
.defaultgridattrs
+ self
.gridattrs
, t
.ticklevel
, maxticklevel
)
234 if gridattrs
is not None:
235 canvas
.layer("grid").stroke(axispos
.vgridpath(t
.temp_v
), gridattrs
)
236 if t
.labellevel
is not None and self
.labelattrs
is not None:
237 canvas
.layer("labels").insert(t
.temp_labelbox
)
238 canvas
.labels
.append(t
.temp_labelbox
)
239 extent_pt
= t
.temp_labelbox
.extent_pt(t
.temp_dx
, t
.temp_dy
) + labeldist_pt
240 if extent_pt
> canvas
.extent_pt
:
241 canvas
.extent_pt
= extent_pt
243 if self
.labelattrs
is None:
246 if self
.basepathattrs
is not None:
247 canvas
.layer("baseline").stroke(axispos
.vbasepath(), self
.defaultbasepathattrs
+ self
.basepathattrs
)
249 # for t in data.ticks:
250 # del t.temp_v # we've inserted those temporary variables ... and do not care any longer about them
255 # if t.labellevel is not None and self.labelattrs is not None:
256 # del t.temp_labelbox
258 _title
.paint(self
, canvas
, data
, axis
, axispos
)
261 class linked(regular
):
262 """class for painting a linked axis"""
264 def __init__(self
, labelattrs
=None, # turn off labels and title
267 regular
.__init
__(self
, labelattrs
=labelattrs
,
268 titleattrs
=titleattrs
,
273 """class for painting a baraxis"""
275 defaulttickattrs
= []
276 defaultbasepathattrs
= [style
.linecap
.square
]
277 defaultnameattrs
= [text
.halign
.center
, text
.vshift
.mathaxis
]
279 def __init__(self
, innerticklength
=None,
280 outerticklength
=None,
283 namedist
=0.3*unit
.v_cm
,
290 self
.innerticklength
= innerticklength
291 self
.outerticklength
= outerticklength
292 self
.tickattrs
= tickattrs
293 self
.basepathattrs
= basepathattrs
294 self
.namedist
= namedist
295 self
.nameattrs
= nameattrs
296 self
.namedirection
= namedirection
297 self
.namepos
= namepos
298 self
.namehequalize
= namehequalize
299 self
.namevequalize
= namevequalize
300 _title
.__init
__(self
, **args
)
302 def paint(self
, canvas
, data
, axis
, positioner
):
304 for name
in data
.names
:
305 subaxis
= data
.subaxes
[name
]
306 v
= subaxis
.vmin
+ self
.namepos
* (subaxis
.vmax
- subaxis
.vmin
)
307 x
, y
= positioner
.vtickpoint_pt(v
)
308 dx
, dy
= positioner
.vtickdirection(v
)
309 namepos
.append((v
, x
, y
, dx
, dy
))
311 if self
.nameattrs
is not None:
312 for (v
, x
, y
, dx
, dy
), name
in zip(namepos
, data
.names
):
313 nameattrs
= self
.defaultnameattrs
+ self
.nameattrs
314 if self
.namedirection
is not None:
315 nameattrs
.append(self
.namedirection
.trafo(dx
, dy
))
316 nameboxes
.append(canvas
.texrunner
.text_pt(x
, y
, str(name
), nameattrs
))
317 labeldist_pt
= canvas
.extent_pt
+ unit
.topt(self
.namedist
)
320 for np
in namepos
[1:]:
321 if np
[3] != namepos
[0][3] or np
[4] != namepos
[0][4]:
325 if equaldirection
and ((not namepos
[0][3] and self
.namevequalize
) or
326 (not namepos
[0][4] and self
.namehequalize
)):
327 box
.linealignequal_pt(nameboxes
, labeldist_pt
, -namepos
[0][3], -namepos
[0][4])
329 for namebox
, np
in zip(nameboxes
, namepos
):
330 namebox
.linealign_pt(labeldist_pt
, -np
[3], -np
[4])
331 if self
.basepathattrs
is not None:
332 p
= positioner
.vbasepath()
334 canvas
.layer("baseline").stroke(p
, self
.defaultbasepathattrs
+ self
.basepathattrs
)
335 if ( self
.tickattrs
is not None and
336 (self
.innerticklength
is not None or self
.outerticklength
is not None) ):
337 if self
.innerticklength
is not None:
338 innerticklength_pt
= unit
.topt(self
.innerticklength
)
339 if canvas
.extent_pt
< -innerticklength_pt
:
340 canvas
.extent_pt
= -innerticklength_pt
341 elif self
.outerticklength
is not None:
342 innerticklength_pt
= 0
343 if self
.outerticklength
is not None:
344 outerticklength_pt
= unit
.topt(self
.outerticklength
)
345 if canvas
.extent_pt
< outerticklength_pt
:
346 canvas
.extent_pt
= outerticklength_pt
347 elif innerticklength_pt
is not None:
348 outerticklength_pt
= 0
349 for v
in [data
.subaxes
[name
].vminover
for name
in data
.names
] + [1]:
350 x
, y
= positioner
.vtickpoint_pt(v
)
351 dx
, dy
= positioner
.vtickdirection(v
)
352 x1
= x
+ dx
* innerticklength_pt
353 y1
= y
+ dy
* innerticklength_pt
354 x2
= x
- dx
* outerticklength_pt
355 y2
= y
- dy
* outerticklength_pt
356 canvas
.layer("ticks").stroke(path
.line_pt(x1
, y1
, x2
, y2
), self
.defaulttickattrs
+ self
.tickattrs
)
357 for (v
, x
, y
, dx
, dy
), namebox
in zip(namepos
, nameboxes
):
358 newextent_pt
= namebox
.extent_pt(dx
, dy
) + labeldist_pt
359 if canvas
.extent_pt
< newextent_pt
:
360 canvas
.extent_pt
= newextent_pt
361 for namebox
in nameboxes
:
362 canvas
.layer("labels").insert(namebox
)
363 _title
.paint(self
, canvas
, data
, axis
, positioner
)
366 class linkedbar(bar
):
367 """class for painting a linked baraxis"""
369 def __init__(self
, nameattrs
=None, titleattrs
=None, **kwargs
):
370 bar
.__init
__(self
, nameattrs
=nameattrs
, titleattrs
=titleattrs
, **kwargs
)
372 def getsubaxis(self
, subaxis
, name
):
373 from pyx
.graph
.axis
import linkedaxis
374 return linkedaxis(subaxis
, name
)
378 """class for painting a splitaxis"""
380 defaultbreaklinesattrs
= []
382 def __init__(self
, breaklinesdist
=0.05*unit
.v_cm
,
383 breaklineslength
=0.5*unit
.v_cm
,
387 self
.breaklinesdist
= breaklinesdist
388 self
.breaklineslength
= breaklineslength
389 self
.breaklinesangle
= breaklinesangle
390 self
.breaklinesattrs
= breaklinesattrs
391 self
.sin
= math
.sin(self
.breaklinesangle
*math
.pi
/180.0)
392 self
.cos
= math
.cos(self
.breaklinesangle
*math
.pi
/180.0)
393 _title
.__init
__(self
, **args
)
395 def paint(self
, canvas
, data
, axis
, axispos
):
396 if self
.breaklinesattrs
is not None:
397 breaklinesdist_pt
= unit
.topt(self
.breaklinesdist
)
398 breaklineslength_pt
= unit
.topt(self
.breaklineslength
)
399 breaklinesextent_pt
= (0.5*breaklinesdist_pt
*math
.fabs(self
.cos
) +
400 0.5*breaklineslength_pt
*math
.fabs(self
.sin
))
401 if canvas
.extent_pt
< breaklinesextent_pt
:
402 canvas
.extent_pt
= breaklinesextent_pt
403 for v
in [data
.subaxes
[name
].vminover
for name
in data
.names
[1:]]:
404 # use a tangent of the basepath (this is independent of the tickdirection)
405 p
= axispos
.vbasepath(v
, None).normpath()
406 breakline
= p
.tangent(0, length
=self
.breaklineslength
)
407 widthline
= p
.tangent(0, length
=self
.breaklinesdist
).transformed(trafomodule
.rotate(self
.breaklinesangle
+90, *breakline
.atbegin()))
409 tocenter
= [0.5*(x
[0]-x
[1]) for x
in zip(breakline
.atbegin(), breakline
.atend())]
410 towidth
= [0.5*(x
[0]-x
[1]) for x
in zip(widthline
.atbegin(), widthline
.atend())]
411 breakline
= breakline
.transformed(trafomodule
.translate(*tocenter
).rotated(self
.breaklinesangle
, *breakline
.atbegin()))
412 breakline1
= breakline
.transformed(trafomodule
.translate(*towidth
))
413 breakline2
= breakline
.transformed(trafomodule
.translate(-towidth
[0], -towidth
[1]))
414 canvas
.layer("baseline").fill(path
.path(path
.moveto_pt(*breakline1
.atbegin_pt()),
415 path
.lineto_pt(*breakline1
.atend_pt()),
416 path
.lineto_pt(*breakline2
.atend_pt()),
417 path
.lineto_pt(*breakline2
.atbegin_pt()),
418 path
.closepath()), [color
.gray
.white
])
419 canvas
.layer("baseline").stroke(breakline1
, self
.defaultbreaklinesattrs
+ self
.breaklinesattrs
)
420 canvas
.layer("baseline").stroke(breakline2
, self
.defaultbreaklinesattrs
+ self
.breaklinesattrs
)
421 _title
.paint(self
, canvas
, data
, axis
, axispos
)
424 class linkedsplit(split
):
426 def __init__(self
, titleattrs
=None, **kwargs
):
427 split
.__init
__(self
, titleattrs
=titleattrs
, **kwargs
)