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