disable distancerating by labels being None in the axiscanvas
[PyX/mjg.git] / pyx / graph / axis / painter.py
blob51ec66e022353e3809459a2cbc6a662f74c7efc1
1 #!/usr/bin/env python
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
26 import math
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):
36 """axis canvas"""
38 def __init__(self, painter, graphtexrunner):
39 """initializes the instance
40 - sets extent to zero
41 - sets labels to an empty list"""
42 canvas._canvas.__init__(self)
43 self.extent_pt = 0
44 self.labels = []
45 if isinstance(painter, _text) and painter.texrunner:
46 self.settexrunner(painter.texrunner)
47 else:
48 self.settexrunner(graphtexrunner)
51 class rotatetext:
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):
61 direction -= 360
62 while (direction < -180 - self.epsilon):
63 direction += 360
64 while (direction > 90 + self.epsilon):
65 direction -= 180
66 while (direction < -90 - self.epsilon):
67 direction += 180
68 return trafomodule.rotate(direction)
71 rotatetext.parallel = rotatetext(90)
72 rotatetext.orthogonal = rotatetext(180)
75 class _text:
76 """a painter with a texrunner"""
78 def __init__(self, texrunner=None):
79 self.texrunner = texrunner
82 class _title(_text):
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,
88 titleattrs=[],
89 titledirection=rotatetext.parallel,
90 titlepos=0.5,
91 **kwargs):
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)
109 canvas.insert(title)
112 class geometricseries(attr.changeattr):
114 def __init__(self, initial, factor):
115 self.initial = initial
116 self.factor = factor
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,
150 tickattrs=[],
151 gridattrs=None,
152 basepathattrs=[],
153 labeldist=0.3*unit.v_cm,
154 labelattrs=[],
155 labeldirection=None,
156 labelhequalize=0,
157 labelvequalize=1,
158 **kwargs):
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):
172 for t in data.ticks:
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
180 for t in data.ticks:
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:
191 equaldirection = 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:
194 equaldirection = 0
195 else:
196 equaldirection = 0
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)
202 else:
203 for t in data.ticks:
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)
207 for t in data.ticks:
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:
215 innerticklength = 0
216 if outerticklength is None:
217 outerticklength = 0
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:
241 canvas.labels = 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
248 # del t.temp_x_pt
249 # del t.temp_y_pt
250 # del t.temp_dx
251 # del t.temp_dy
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
262 titleattrs=None,
263 **kwargs):
264 regular.__init__(self, labelattrs=labelattrs,
265 titleattrs=titleattrs,
266 **kwargs)
269 class bar(_title):
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,
278 tickattrs=[],
279 basepathattrs=[],
280 namedist=0.3*unit.v_cm,
281 nameattrs=[],
282 namedirection=None,
283 namepos=0.5,
284 namehequalize=0,
285 namevequalize=1,
286 **args):
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):
300 namepos = []
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))
307 nameboxes = []
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)
315 if len(namepos) > 1:
316 equaldirection = 1
317 for np in namepos[1:]:
318 if np[3] != namepos[0][3] or np[4] != namepos[0][4]:
319 equaldirection = 0
320 else:
321 equaldirection = 0
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])
325 else:
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()
330 if p is not None:
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)
374 class split(_title):
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,
381 breaklinesangle=-60,
382 breaklinesattrs=[],
383 **args):
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()))
405 # XXX Uiiii
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)