more restructuring of the unit system
[PyX/mjg.git] / pyx / graph / axis / painter.py
blobf480ea6d7f8571805b4e4ebb618ce4251742236a
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 [x * unit.t_pt 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 = self.arclen_pt * unit.t_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*unit.v_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 = 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 x, y = axispos.vtickpoint_pt(self.titlepos)
300 dx, dy = axispos.vtickdirection(self.titlepos)
301 titleattrs = self.defaulttitleattrs + self.titleattrs
302 if self.titledirection is not None:
303 titleattrs.append(self.titledirection.trafo(dx, dy))
304 title = self.texrunner.text_pt(x, y, axis.title, titleattrs)
305 ac.extent += self.titledist
306 title.linealign(ac.extent, -dx, -dy)
307 ac.extent += title.extent(dx, dy)
308 ac.insert(title)
309 return ac
312 class geometricseries(attr.changeattr):
314 def __init__(self, initial, factor):
315 self.initial = initial
316 self.factor = factor
318 def select(self, index, total):
319 return self.initial * (self.factor ** index)
322 class ticklength(geometricseries): pass
324 _base = 0.12 * unit.v_cm
326 ticklength.SHORT = ticklength(_base/math.sqrt(64), 1/goldenmean)
327 ticklength.SHORt = ticklength(_base/math.sqrt(32), 1/goldenmean)
328 ticklength.SHOrt = ticklength(_base/math.sqrt(16), 1/goldenmean)
329 ticklength.SHort = ticklength(_base/math.sqrt(8), 1/goldenmean)
330 ticklength.Short = ticklength(_base/math.sqrt(4), 1/goldenmean)
331 ticklength.short = ticklength(_base/math.sqrt(2), 1/goldenmean)
332 ticklength.normal = ticklength(_base, 1/goldenmean)
333 ticklength.long = ticklength(_base*math.sqrt(2), 1/goldenmean)
334 ticklength.Long = ticklength(_base*math.sqrt(4), 1/goldenmean)
335 ticklength.LOng = ticklength(_base*math.sqrt(8), 1/goldenmean)
336 ticklength.LONg = ticklength(_base*math.sqrt(16), 1/goldenmean)
337 ticklength.LONG = ticklength(_base*math.sqrt(32), 1/goldenmean)
340 class regular(_title):
341 """class for painting the ticks and labels of an axis
342 - the inherited _title is used to paint the title of
343 the axis
344 - note that the type of the elements of ticks given as an argument
345 of the paint method must be suitable for the tick position methods
346 of the axis"""
348 __implements__ = _Iaxispainter
350 defaulttickattrs = []
351 defaultgridattrs = []
352 defaultbasepathattrs = [style.linecap.square]
353 defaultlabelattrs = [text.halign.center, text.vshift.mathaxis]
355 def __init__(self, innerticklength=ticklength.normal,
356 outerticklength=None,
357 tickattrs=[],
358 gridattrs=None,
359 basepathattrs=[],
360 labeldist=0.3*unit.v_cm,
361 labelattrs=[],
362 labeldirection=None,
363 labelhequalize=0,
364 labelvequalize=1,
365 **kwargs):
366 """initializes the instance
367 - innerticklength and outerticklength are changable
368 visual PyX lengths for ticks, subticks, etc. plotted inside
369 and outside of the graph; None turns off ticks inside or
370 outside of the graph
371 - tickattrs are a list of stroke attributes for the ticks;
372 None turns off ticks
373 - gridattrs are a list of lists used as stroke
374 attributes for ticks, subticks etc.; None turns off
375 the grid
376 - basepathattrs are a list of stroke attributes for the base line
377 of the axis; None turns off the basepath
378 - labeldist is a visual PyX length for the distance of the labels
379 from the axis basepath
380 - labelattrs is a list of attributes for a texrunners text
381 method; None turns off the labels
382 - labeldirection is an instance of rotatetext or None
383 - labelhequalize and labelvequalize (booleans) perform an equal
384 alignment for straight vertical and horizontal axes, respectively
385 - futher keyword arguments are passed to _axistitle"""
386 self.innerticklength = innerticklength
387 self.outerticklength = outerticklength
388 self.tickattrs = tickattrs
389 self.gridattrs = gridattrs
390 self.basepathattrs = basepathattrs
391 self.labeldist = labeldist
392 self.labelattrs = labelattrs
393 self.labeldirection = labeldirection
394 self.labelhequalize = labelhequalize
395 self.labelvequalize = labelvequalize
396 _title.__init__(self, **kwargs)
398 def paint(self, axispos, axis, ac=None):
399 if ac is None:
400 ac = axiscanvas()
401 for t in axis.ticks:
402 t.temp_v = axis.convert(t)
403 t.temp_x, t.temp_y = axispos.vtickpoint_pt(t.temp_v)
404 t.temp_dx, t.temp_dy = axispos.vtickdirection(t.temp_v)
405 maxticklevel, maxlabellevel = tick.maxlevels(axis.ticks)
407 # create & align t.temp_labelbox
408 for t in axis.ticks:
409 if t.labellevel is not None:
410 labelattrs = attr.selectattrs(self.labelattrs, t.labellevel, maxlabellevel)
411 if labelattrs is not None:
412 labelattrs = self.defaultlabelattrs + labelattrs
413 if self.labeldirection is not None:
414 labelattrs.append(self.labeldirection.trafo(t.temp_dx, t.temp_dy))
415 if t.labelattrs is not None:
416 labelattrs.extend(t.labelattrs)
417 t.temp_labelbox = self.texrunner.text_pt(t.temp_x, t.temp_y, t.label, labelattrs)
418 if len(axis.ticks) > 1:
419 equaldirection = 1
420 for t in axis.ticks[1:]:
421 if t.temp_dx != axis.ticks[0].temp_dx or t.temp_dy != axis.ticks[0].temp_dy:
422 equaldirection = 0
423 else:
424 equaldirection = 0
425 if equaldirection and ((not axis.ticks[0].temp_dx and self.labelvequalize) or
426 (not axis.ticks[0].temp_dy and self.labelhequalize)):
427 if self.labelattrs is not None:
428 box.linealignequal([t.temp_labelbox for t in axis.ticks if t.labellevel is not None],
429 self.labeldist, -axis.ticks[0].temp_dx, -axis.ticks[0].temp_dy)
430 else:
431 for t in axis.ticks:
432 if t.labellevel is not None and self.labelattrs is not None:
433 t.temp_labelbox.linealign(self.labeldist, -t.temp_dx, -t.temp_dy)
435 for t in axis.ticks:
436 if t.ticklevel is not None:
437 tickattrs = attr.selectattrs(self.defaulttickattrs + self.tickattrs, t.ticklevel, maxticklevel)
438 if tickattrs is not None:
439 innerticklength = attr.selectattr(self.innerticklength, t.ticklevel, maxticklevel)
440 outerticklength = attr.selectattr(self.outerticklength, t.ticklevel, maxticklevel)
441 if innerticklength is not None or outerticklength is not None:
442 if innerticklength is None:
443 innerticklength = 0
444 if outerticklength is None:
445 outerticklength = 0
446 innerticklength_pt = unit.topt(innerticklength)
447 outerticklength_pt = unit.topt(outerticklength)
448 x1 = t.temp_x + t.temp_dx * innerticklength_pt
449 y1 = t.temp_y + t.temp_dy * innerticklength_pt
450 x2 = t.temp_x - t.temp_dx * outerticklength_pt
451 y2 = t.temp_y - t.temp_dy * outerticklength_pt
452 ac.stroke(path.line_pt(x1, y1, x2, y2), tickattrs)
453 if outerticklength is not None and outerticklength > ac.extent:
454 ac.extent = outerticklength
455 if outerticklength is not None and -innerticklength > ac.extent:
456 ac.extent = -innerticklength
457 if self.gridattrs is not None:
458 gridattrs = attr.selectattrs(self.defaultgridattrs + self.gridattrs, t.ticklevel, maxticklevel)
459 if gridattrs is not None:
460 ac.stroke(axispos.vgridpath(t.temp_v), gridattrs)
461 if t.labellevel is not None and self.labelattrs is not None:
462 ac.insert(t.temp_labelbox)
463 ac.labels.append(t.temp_labelbox)
464 extent = t.temp_labelbox.extent(t.temp_dx, t.temp_dy) + self.labeldist
465 if extent > ac.extent:
466 ac.extent = extent
467 if self.basepathattrs is not None:
468 ac.stroke(axispos.vbasepath(), self.defaultbasepathattrs + self.basepathattrs)
470 # for t in axis.ticks:
471 # del t.temp_v # we've inserted those temporary variables ... and do not care any longer about them
472 # del t.temp_x
473 # del t.temp_y
474 # del t.temp_dx
475 # del t.temp_dy
476 # if t.labellevel is not None and self.labelattrs is not None:
477 # del t.temp_labelbox
479 _title.paint(self, axispos, axis, ac=ac)
481 return ac
484 class linked(regular):
485 """class for painting a linked axis
486 - the inherited regular is used to paint the axis
487 - modifies some constructor defaults"""
489 __implements__ = _Iaxispainter
491 def __init__(self, labelattrs=None,
492 titleattrs=None,
493 **kwargs):
494 """initializes the instance
495 - the labelattrs default is set to None thus skipping the labels
496 - the titleattrs default is set to None thus skipping the title
497 - all keyword arguments are passed to regular"""
498 regular.__init__(self, labelattrs=labelattrs,
499 titleattrs=titleattrs,
500 **kwargs)
503 class subaxispos:
504 """implementation of the _Iaxispos interface for a subaxis"""
506 __implements__ = _Iaxispos
508 def __init__(self, convert, baseaxispos, vmin, vmax, vminover, vmaxover):
509 """initializes the instance
510 - convert is the subaxis convert method
511 - baseaxispos is the axispos instance of the base axis
512 - vmin, vmax is the range covered by the subaxis in graph coordinates
513 - vminover, vmaxover is the extended range of the subaxis including
514 regions between several subaxes (for basepath drawing etc.)"""
515 self.convert = convert
516 self.baseaxispos = baseaxispos
517 self.vmin = vmin
518 self.vmax = vmax
519 self.vminover = vminover
520 self.vmaxover = vmaxover
522 def basepath(self, x1=None, x2=None):
523 if x1 is not None:
524 v1 = self.vmin+self.convert(x1)*(self.vmax-self.vmin)
525 else:
526 v1 = self.vminover
527 if x2 is not None:
528 v2 = self.vmin+self.convert(x2)*(self.vmax-self.vmin)
529 else:
530 v2 = self.vmaxover
531 return self.baseaxispos.vbasepath(v1, v2)
533 def vbasepath(self, v1=None, v2=None):
534 if v1 is not None:
535 v1 = self.vmin+v1*(self.vmax-self.vmin)
536 else:
537 v1 = self.vminover
538 if v2 is not None:
539 v2 = self.vmin+v2*(self.vmax-self.vmin)
540 else:
541 v2 = self.vmaxover
542 return self.baseaxispos.vbasepath(v1, v2)
544 def gridpath(self, x):
545 return self.baseaxispos.vgridpath(self.vmin+self.convert(x)*(self.vmax-self.vmin))
547 def vgridpath(self, v):
548 return self.baseaxispos.vgridpath(self.vmin+v*(self.vmax-self.vmin))
550 def tickpoint_pt(self, x, axis=None):
551 return self.baseaxispos.vtickpoint_pt(self.vmin+self.convert(x)*(self.vmax-self.vmin))
553 def tickpoint(self, x, axis=None):
554 return self.baseaxispos.vtickpoint(self.vmin+self.convert(x)*(self.vmax-self.vmin))
556 def vtickpoint_pt(self, v, axis=None):
557 return self.baseaxispos.vtickpoint_pt(self.vmin+v*(self.vmax-self.vmin))
559 def vtickpoint(self, v, axis=None):
560 return self.baseaxispos.vtickpoint(self.vmin+v*(self.vmax-self.vmin))
562 def tickdirection(self, x, axis=None):
563 return self.baseaxispos.vtickdirection(self.vmin+self.convert(x)*(self.vmax-self.vmin))
565 def vtickdirection(self, v, axis=None):
566 return self.baseaxispos.vtickdirection(self.vmin+v*(self.vmax-self.vmin))
569 class split(_title):
570 """class for painting a splitaxis
571 - the inherited _title is used to paint the title of
572 the axis
573 - the splitaxis access the subaxes attribute of the axis"""
575 __implements__ = _Iaxispainter
577 defaultbreaklinesattrs = []
579 def __init__(self, breaklinesdist=0.05*unit.v_cm,
580 breaklineslength=0.5*unit.v_cm,
581 breaklinesangle=-60,
582 breaklinesattrs=[],
583 **args):
584 """initializes the instance
585 - breaklinesdist is a visual length of the distance between
586 the two lines of the axis break
587 - breaklineslength is a visual length of the length of the
588 two lines of the axis break
589 - breaklinesangle is the angle of the lines of the axis break
590 - breaklinesattrs are a list of stroke attributes for the
591 axis break lines; a single entry is allowed without being a
592 list; None turns off the break lines
593 - futher keyword arguments are passed to _title"""
594 self.breaklinesdist = breaklinesdist
595 self.breaklineslength = breaklineslength
596 self.breaklinesangle = breaklinesangle
597 self.breaklinesattrs = breaklinesattrs
598 _title.__init__(self, **args)
600 def paint(self, axispos, axis, ac=None):
601 if ac is None:
602 ac = axiscanvas()
603 for subaxis in axis.subaxes:
604 subaxis.finish(subaxispos(subaxis.convert, axispos, subaxis.vmin, subaxis.vmax, subaxis.vminover, subaxis.vmaxover))
605 ac.insert(subaxis.axiscanvas)
606 if ac.extent < subaxis.axiscanvas.extent:
607 ac.extent = subaxis.axiscanvas.extent
608 if self.breaklinesattrs is not None:
609 self.sin = math.sin(self.breaklinesangle*math.pi/180.0)
610 self.cos = math.cos(self.breaklinesangle*math.pi/180.0)
611 breaklinesextent = (0.5*self.breaklinesdist*math.fabs(self.cos) +
612 0.5*self.breaklineslength*math.fabs(self.sin))
613 if ac.extent < breaklinesextent:
614 ac.extent = breaklinesextent
615 for subaxis1, subaxis2 in zip(axis.subaxes[:-1], axis.subaxes[1:]):
616 # use a tangent of the basepath (this is independent of the tickdirection)
617 v = 0.5 * (subaxis1.vmax + subaxis2.vmin)
618 p = path.normpath(axispos.vbasepath(v, None))
619 breakline = p.tangent(0, length=self.breaklineslength)
620 widthline = p.tangent(0, length=self.breaklinesdist).transformed(trafomodule.rotate(self.breaklinesangle+90, *breakline.begin()))
621 # XXX Uiiii
622 tocenter = map(lambda x: 0.5*(x[0]-x[1]), zip(breakline.begin(), breakline.end()))
623 towidth = map(lambda x: 0.5*(x[0]-x[1]), zip(widthline.begin(), widthline.end()))
624 breakline = breakline.transformed(trafomodule.translate(*tocenter).rotated(self.breaklinesangle, *breakline.begin()))
625 breakline1 = breakline.transformed(trafomodule.translate(*towidth))
626 breakline2 = breakline.transformed(trafomodule.translate(-towidth[0], -towidth[1]))
627 ac.fill(path.path(path.moveto_pt(*breakline1.begin_pt()),
628 path.lineto_pt(*breakline1.end_pt()),
629 path.lineto_pt(*breakline2.end_pt()),
630 path.lineto_pt(*breakline2.begin_pt()),
631 path.closepath()), [color.gray.white])
632 ac.stroke(breakline1, self.defaultbreaklinesattrs + self.breaklinesattrs)
633 ac.stroke(breakline2, self.defaultbreaklinesattrs + self.breaklinesattrs)
634 _title.paint(self, axispos, axis, ac=ac)
635 return ac
638 class linkedsplit(split):
639 """class for painting a linked splitaxis
640 - the inherited split is used to paint the axis
641 - modifies some constructor defaults"""
643 __implements__ = _Iaxispainter
645 def __init__(self, titleattrs=None, **kwargs):
646 """initializes the instance
647 - the titleattrs default is set to None thus skipping the title
648 - all keyword arguments are passed to split"""
649 split.__init__(self, titleattrs=titleattrs, **kwargs)
652 class bar(_title):
653 """class for painting a baraxis
654 - the inherited _title is used to paint the title of
655 the axis
656 - the bar access the multisubaxis, names, and subaxis
657 relsizes attributes"""
659 __implements__ = _Iaxispainter
661 defaulttickattrs = []
662 defaultbasepathattrs = [style.linecap.square]
663 defaultnameattrs = [text.halign.center, text.vshift.mathaxis]
665 def __init__(self, innerticklength=None,
666 outerticklength=None,
667 tickattrs=[],
668 basepathattrs=[],
669 namedist=0.3*unit.v_cm,
670 nameattrs=[],
671 namedirection=None,
672 namepos=0.5,
673 namehequalize=0,
674 namevequalize=1,
675 **args):
676 """initializes the instance
677 - innerticklength and outerticklength are a visual length of
678 the ticks to be plotted at the axis basepath to visually
679 separate the bars; if neither innerticklength nor
680 outerticklength are set, not ticks are plotted
681 - breaklinesattrs are a list of stroke attributes for the
682 axis tick; a single entry is allowed without being a
683 list; None turns off the ticks
684 - namedist is a visual PyX length for the distance of the bar
685 names from the axis basepath
686 - nameattrs is a list of attributes for a texrunners text
687 method; a single entry is allowed without being a list;
688 None turns off the names
689 - namedirection is an instance of rotatetext or None
690 - namehequalize and namevequalize (booleans) perform an equal
691 alignment for straight vertical and horizontal axes, respectively
692 - futher keyword arguments are passed to _title"""
693 self.innerticklength = innerticklength
694 self.outerticklength = outerticklength
695 self.tickattrs = tickattrs
696 self.basepathattrs = basepathattrs
697 self.namedist = namedist
698 self.nameattrs = nameattrs
699 self.namedirection = namedirection
700 self.namepos = namepos
701 self.namehequalize = namehequalize
702 self.namevequalize = namevequalize
703 _title.__init__(self, **args)
705 def paint(self, axispos, axis, ac=None):
706 if ac is None:
707 ac = axiscanvas()
708 if axis.multisubaxis is not None:
709 for subaxis in axis.subaxis:
710 subaxis.finish(subaxispos(subaxis.convert, axispos, subaxis.vmin, subaxis.vmax, None, None))
711 ac.insert(subaxis.axiscanvas)
712 if ac.extent < subaxis.axiscanvas.extent:
713 ac.extent = subaxis.axiscanvas.extent
714 namepos = []
715 for name in axis.names:
716 v = axis.convert((name, self.namepos))
717 x, y = axispos.vtickpoint_pt(v)
718 dx, dy = axispos.vtickdirection(v)
719 namepos.append((v, x, y, dx, dy))
720 nameboxes = []
721 if self.nameattrs is not None:
722 for (v, x, y, dx, dy), name in zip(namepos, axis.names):
723 nameattrs = self.defaultnameattrs + self.nameattrs
724 if self.namedirection is not None:
725 nameattrs.append(self.namedirection.trafo(tick.temp_dx, tick.temp_dy))
726 nameboxes.append(self.texrunner.text_pt(x, y, str(name), nameattrs))
727 labeldist = ac.extent + self.namedist
728 if len(namepos) > 1:
729 equaldirection = 1
730 for np in namepos[1:]:
731 if np[3] != namepos[0][3] or np[4] != namepos[0][4]:
732 equaldirection = 0
733 else:
734 equaldirection = 0
735 if equaldirection and ((not namepos[0][3] and self.namevequalize) or
736 (not namepos[0][4] and self.namehequalize)):
737 box.linealignequal(nameboxes, labeldist, -namepos[0][3], -namepos[0][4])
738 else:
739 for namebox, np in zip(nameboxes, namepos):
740 namebox.linealign(labeldist, -np[3], -np[4])
741 if self.basepathattrs is not None:
742 p = axispos.vbasepath()
743 if p is not None:
744 ac.stroke(p, self.defaultbasepathattrs + self.basepathattrs)
745 if self.tickattrs is not None and (self.innerticklength is not None or
746 self.outerticklength is not None):
747 if self.innerticklength is not None:
748 innerticklength_pt = unit.topt(self.innerticklength)
749 if ac.extent < -self.innerticklength:
750 ac.extent = -self.innerticklength
751 elif self.outerticklength is not None:
752 innerticklength_pt = 0
753 if self.outerticklength is not None:
754 outerticklength_pt = unit.topt(self.outerticklength)
755 if ac.extent < self.outerticklength:
756 ac.extent = self.outerticklength
757 elif self.innerticklength is not None:
758 outerticklength_pt = 0
759 for pos in axis.relsizes:
760 if pos == axis.relsizes[0]:
761 pos -= axis.firstdist
762 elif pos != axis.relsizes[-1]:
763 pos -= 0.5 * axis.dist
764 v = pos / axis.relsizes[-1]
765 x, y = axispos.vtickpoint_pt(v)
766 dx, dy = axispos.vtickdirection(v)
767 x1 = x + dx * innerticklength_pt
768 y1 = y + dy * innerticklength_pt
769 x2 = x - dx * outerticklength_pt
770 y2 = y - dy * outerticklength_pt
771 ac.stroke(path.line_pt(x1, y1, x2, y2), self.defaulttickattrs + self.tickattrs)
772 for (v, x, y, dx, dy), namebox in zip(namepos, nameboxes):
773 newextent = namebox.extent(dx, dy) + labeldist
774 if ac.extent < newextent:
775 ac.extent = newextent
776 for namebox in nameboxes:
777 ac.insert(namebox)
778 _title.paint(self, axispos, axis, ac=ac)
779 return ac
782 class linkedbar(bar):
783 """class for painting a linked baraxis
784 - the inherited bar is used to paint the axis
785 - modifies some constructor defaults"""
787 __implements__ = _Iaxispainter
789 def __init__(self, nameattrs=None, titleattrs=None, **kwargs):
790 """initializes the instance
791 - the titleattrs default is set to None thus skipping the title
792 - the nameattrs default is set to None thus skipping the names
793 - all keyword arguments are passed to bar"""
794 bar.__init__(self, nameattrs=nameattrs, titleattrs=titleattrs, **kwargs)