graph manual finished for 0.6 and small cleanups
[PyX/mjg.git] / pyx / graph / axis / painter.py
blob837d03bad25b60f2b8198725804a75f2ee11fe0d
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-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
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
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
55 - sets extent to zero
56 - sets labels to an empty list"""
57 canvas.canvas.__init__(self, *args, **kwargs)
58 self.extent = 0
59 self.labels = []
62 class rotatetext:
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):
81 direction -= 360
82 while (direction < -180 - self.epsilon):
83 direction += 360
84 while (direction > 90 + self.epsilon):
85 direction -= 180
86 while (direction < -90 - self.epsilon):
87 direction += 180
88 return trafomodule.rotate(direction)
91 rotatetext.parallel = rotatetext(90)
92 rotatetext.orthogonal = rotatetext(180)
95 class _Iaxispainter:
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"""
125 class _Iaxispos:
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
149 in graph coordinates
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"""
174 class _axispos:
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:
178 - vbasepath
179 - vgridpath
180 - vtickpoint_pt
181 - vtickdirection
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):
191 if x1 is None:
192 if x2 is None:
193 return self.vbasepath()
194 else:
195 return self.vbasepath(v2=self.convert(x2))
196 else:
197 if x2 is None:
198 return self.vbasepath(v1=self.convert(x1))
199 else:
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):
224 self.path = p
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):
232 if v1 is None:
233 if v2 is None:
234 return self.path
235 else:
236 return self.normpath.split(self.normpath.arclentoparam(v2 * self.arclen))[0]
237 else:
238 if v2 is None:
239 return self.normpath.split(self.normpath.arclentoparam(v1 * self.arclen))[1]
240 else:
241 return self.normpath.split(*self.normpath.arclentoparam([v1 * self.arclen, v2 * self.arclen]))[1]
243 def vgridpath(self, v):
244 return None
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()
252 tend = t.end_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")
263 class _title:
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",
273 titleattrs=[],
274 titledirection=rotatetext.parallel,
275 titlepos=0.5,
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
283 turns off the title
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):
296 if ac is None:
297 ac = axiscanvas()
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)
309 ac.insert(title)
310 return ac
313 class geometricseries(attr.changeattr):
315 def __init__(self, initial, factor):
316 self.initial = initial
317 self.factor = factor
319 def select(self, index, total):
320 return self.initial * (self.factor ** index)
323 class ticklength(geometricseries): pass
325 _base = 0.12
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)
342 class plain(_title):
343 """class for painting the ticks and labels of an axis
344 - the inherited _title is used to paint the title of
345 the axis
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
348 of the axis"""
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,
359 tickattrs=[],
360 gridattrs=None,
361 basepathattrs=[],
362 labeldist="0.3 cm",
363 labelattrs=[],
364 labeldirection=None,
365 labelhequalize=0,
366 labelvequalize=1,
367 **kwargs):
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
372 outside of the graph
373 - tickattrs are a list of stroke attributes for the ticks;
374 None turns off ticks
375 - gridattrs are a list of lists used as stroke
376 attributes for ticks, subticks etc.; None turns off
377 the grid
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):
401 if ac is None:
402 ac = axiscanvas()
403 labeldist = unit.length(self.labeldist_str, default_type="v")
404 for t in axis.ticks:
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
411 for t in axis.ticks:
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:
422 equaldirection = 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:
425 equaldirection = 0
426 else:
427 equaldirection = 0
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)
433 else:
434 for t in axis.ticks:
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)
438 for t in axis.ticks:
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:
444 innerticklength = 0
445 else:
446 innerticklength = unit.length(innerticklength, default_type="v")
447 if outerticklength is None:
448 outerticklength = 0
449 else:
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:
472 ac.extent = 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
478 # del t.temp_x
479 # del t.temp_y
480 # del t.temp_dx
481 # del t.temp_dy
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)
487 return ac
490 class linked(plain):
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,
498 titleattrs=None,
499 **kwargs):
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,
506 **kwargs)
509 class subaxispos:
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
523 self.vmin = vmin
524 self.vmax = vmax
525 self.vminover = vminover
526 self.vmaxover = vmaxover
528 def basepath(self, x1=None, x2=None):
529 if x1 is not None:
530 v1 = self.vmin+self.convert(x1)*(self.vmax-self.vmin)
531 else:
532 v1 = self.vminover
533 if x2 is not None:
534 v2 = self.vmin+self.convert(x2)*(self.vmax-self.vmin)
535 else:
536 v2 = self.vmaxover
537 return self.baseaxispos.vbasepath(v1, v2)
539 def vbasepath(self, v1=None, v2=None):
540 if v1 is not None:
541 v1 = self.vmin+v1*(self.vmax-self.vmin)
542 else:
543 v1 = self.vminover
544 if v2 is not None:
545 v2 = self.vmin+v2*(self.vmax-self.vmin)
546 else:
547 v2 = self.vmaxover
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))
575 class split(_title):
576 """class for painting a splitaxis
577 - the inherited _title is used to paint the title of
578 the axis
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",
587 breaklinesangle=-60,
588 breaklinesattrs=[],
589 **args):
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):
607 if ac is None:
608 ac = axiscanvas()
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()))
629 # XXX Uiiii
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)
643 return 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)
660 class bar(_title):
661 """class for painting a baraxis
662 - the inherited _title is used to paint the title of
663 the axis
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,
675 tickattrs=[],
676 basepathattrs=[],
677 namedist="0.3 cm",
678 nameattrs=[],
679 namedirection=None,
680 namepos=0.5,
681 namehequalize=0,
682 namevequalize=1,
683 **args):
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):
714 if ac is None:
715 ac = axiscanvas()
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
722 namepos = []
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))
728 nameboxes = []
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")
736 if len(namepos) > 1:
737 equaldirection = 1
738 for np in namepos[1:]:
739 if np[3] != namepos[0][3] or np[4] != namepos[0][4]:
740 equaldirection = 0
741 else:
742 equaldirection = 0
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])
746 else:
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()
751 if p is not None:
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:
787 ac.insert(namebox)
788 _title.paint(self, axispos, axis, ac=ac)
789 return 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)