1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2006 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 import math
, re
, string
, warnings
26 from pyx
import canvas
, path
, trafo
, unit
27 from pyx
.graph
import style
28 from pyx
.graph
.axis
import axis
, positioner
31 goldenmean
= 0.5 * (math
.sqrt(5) + 1)
35 """style data storage class
37 Instances of this class are used to store data from the styles
38 and to pass point data to the styles by instances named privatedata
39 and sharedata. sharedata is shared between all the style(s) in use
40 by a data instance, while privatedata is private to each style and
41 used as a storage place instead of self to prevent side effects when
42 using a style several times."""
48 def __init__(self
, graph
, data
, styles
):
50 self
.title
= data
.title
52 # add styles to ensure all needs of the given styles
53 provided
= [] # already provided sharedata variables
54 addstyles
= [] # a list of style instances to be added in front
58 defaultprovider
= style
.getdefaultprovider(n
)
59 addstyles
.append(defaultprovider
)
60 provided
.extend(defaultprovider
.providesdata
)
61 provided
.extend(s
.providesdata
)
63 self
.styles
= addstyles
+ styles
64 self
.sharedata
= styledata()
65 self
.privatedatalist
= [styledata() for s
in self
.styles
]
67 # perform setcolumns to all styles
68 self
.usedcolumnnames
= []
69 for privatedata
, s
in zip(self
.privatedatalist
, self
.styles
):
70 self
.usedcolumnnames
.extend(s
.columnnames(privatedata
, self
.sharedata
, graph
, self
.data
.columnnames
))
72 def selectstyles(self
, graph
, selectindex
, selecttotal
):
73 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
74 style
.selectstyle(privatedata
, self
.sharedata
, graph
, selectindex
, selecttotal
)
76 def adjustaxesstatic(self
, graph
):
77 for columnname
, data
in self
.data
.columns
.items():
78 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
79 style
.adjustaxis(privatedata
, self
.sharedata
, graph
, columnname
, data
)
81 def makedynamicdata(self
, graph
):
82 self
.dynamiccolumns
= self
.data
.dynamiccolumns(graph
)
84 def adjustaxesdynamic(self
, graph
):
85 for columnname
, data
in self
.dynamiccolumns
.items():
86 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
87 style
.adjustaxis(privatedata
, self
.sharedata
, graph
, columnname
, data
)
89 def draw(self
, graph
):
90 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
91 style
.initdrawpoints(privatedata
, self
.sharedata
, graph
)
94 for columnname
in self
.usedcolumnnames
:
96 useitems
.append((columnname
, self
.dynamiccolumns
[columnname
]))
98 useitems
.append((columnname
, self
.data
.columns
[columnname
]))
100 raise ValueError("cannot draw empty data")
101 for i
in xrange(len(useitems
[0][1])):
102 for columnname
, data
in useitems
:
103 point
[columnname
] = data
[i
]
104 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
105 style
.drawpoint(privatedata
, self
.sharedata
, graph
, point
)
106 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
107 style
.donedrawpoints(privatedata
, self
.sharedata
, graph
)
109 def key_pt(self
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
110 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
111 style
.key_pt(privatedata
, self
.sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
)
113 def __getattr__(self
, attr
):
114 # read only access to the styles privatedata
115 stylesdata
= [getattr(styledata
, attr
)
116 for styledata
in self
.privatedatalist
117 if hasattr(styledata
, attr
)]
118 if len(stylesdata
) > 1:
120 elif len(stylesdata
) == 1:
122 raise AttributeError("access to styledata attribute '%s' failed" % attr
)
125 class graph(canvas
.canvas
):
128 canvas
.canvas
.__init
__(self
)
135 def did(self
, method
, *args
, **kwargs
):
136 if not self
._calls
.has_key(method
):
137 self
._calls
[method
] = []
138 for callargs
in self
._calls
[method
]:
139 if callargs
== (args
, kwargs
):
141 self
._calls
[method
].append((args
, kwargs
))
146 return canvas
.canvas
.bbox(self
)
148 def registerPS(self
, registry
):
150 canvas
.canvas
.registerPS(self
, registry
)
152 def registerPDF(self
, registry
):
154 canvas
.canvas
.registerPDF(self
, registry
)
156 def processPS(self
, file, writer
, context
, registry
, bbox
):
158 canvas
.canvas
.processPS(self
, file, writer
, context
, registry
, bbox
)
160 def processPDF(self
, file, writer
, context
, registry
, bbox
):
162 canvas
.canvas
.processPDF(self
, file, writer
, context
, registry
, bbox
)
164 def plot(self
, data
, styles
=None, rangewarning
=1):
165 if self
.didranges
and rangewarning
:
166 warnings
.warn("axes ranges have already been analysed; no further adjustments will be performed")
168 raise RuntimeError("can't plot further data after dostyles() has been executed")
181 styles
= d
.defaultstyles
182 elif styles
!= d
.defaultstyles
:
183 raise RuntimeError("defaultstyles differ")
186 plotitems
.append(plotitem(self
, d
, styles
))
187 self
.plotitems
.extend(plotitems
)
189 for aplotitem
in plotitems
:
190 aplotitem
.makedynamicdata(self
)
197 if self
.did(self
.doranges
):
199 for plotitem
in self
.plotitems
:
200 plotitem
.adjustaxesstatic(self
)
201 for plotitem
in self
.plotitems
:
202 plotitem
.makedynamicdata(self
)
203 for plotitem
in self
.plotitems
:
204 plotitem
.adjustaxesdynamic(self
)
207 def doaxiscreate(self
, axisname
):
208 if self
.did(self
.doaxiscreate
, axisname
):
210 self
.doaxispositioner(axisname
)
211 self
.axes
[axisname
].create()
214 raise NotImplementedError
216 def dobackground(self
):
220 raise NotImplementedError
223 if self
.did(self
.dostyles
):
228 # count the usage of styles and perform selects
230 def stylesid(styles
):
231 return ":".join([str(id(style
)) for style
in styles
])
232 for plotitem
in self
.plotitems
:
234 styletotal
[stylesid(plotitem
.styles
)] += 1
236 styletotal
[stylesid(plotitem
.styles
)] = 1
238 for plotitem
in self
.plotitems
:
240 styleindex
[stylesid(plotitem
.styles
)] += 1
242 styleindex
[stylesid(plotitem
.styles
)] = 0
243 plotitem
.selectstyles(self
, styleindex
[stylesid(plotitem
.styles
)],
244 styletotal
[stylesid(plotitem
.styles
)])
248 def doplot(self
, plotitem
):
249 if self
.did(self
.doplot
, plotitem
):
255 for plotitem
in self
.plotitems
:
256 self
.doplot(plotitem
)
259 raise NotImplementedError
268 class graphxy(graph
):
270 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, ratio
=goldenmean
,
271 key
=None, backgroundattrs
=None, axesdist
=0.8*unit
.v_cm
,
272 xaxisat
=None, yaxisat
=None, **axes
):
277 self
.xpos_pt
= unit
.topt(self
.xpos
)
278 self
.ypos_pt
= unit
.topt(self
.ypos
)
279 self
.xaxisat
= xaxisat
280 self
.yaxisat
= yaxisat
282 self
.backgroundattrs
= backgroundattrs
283 self
.axesdist_pt
= unit
.topt(axesdist
)
289 raise ValueError("specify width and/or height")
291 self
.width
= ratio
* self
.height
293 self
.height
= (1.0/ratio
) * self
.width
294 self
.width_pt
= unit
.topt(self
.width
)
295 self
.height_pt
= unit
.topt(self
.height
)
297 for axisname
, aaxis
in axes
.items():
298 if aaxis
is not None:
299 if not isinstance(aaxis
, axis
.linkedaxis
):
300 self
.axes
[axisname
] = axis
.anchoredaxis(aaxis
, self
.texrunner
, axisname
)
302 self
.axes
[axisname
] = aaxis
303 for axisname
, axisat
in [("x", xaxisat
), ("y", yaxisat
)]:
304 okey
= axisname
+ "2"
305 if not axes
.has_key(axisname
):
306 if not axes
.has_key(okey
):
307 self
.axes
[axisname
] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
308 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
310 self
.axes
[axisname
] = axis
.linkedaxis(self
.axes
[okey
], axisname
)
311 elif not axes
.has_key(okey
) and axisat
is None:
312 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
314 if self
.axes
.has_key("x"):
315 self
.xbasepath
= self
.axes
["x"].basepath
316 self
.xvbasepath
= self
.axes
["x"].vbasepath
317 self
.xgridpath
= self
.axes
["x"].gridpath
318 self
.xtickpoint_pt
= self
.axes
["x"].tickpoint_pt
319 self
.xtickpoint
= self
.axes
["x"].tickpoint
320 self
.xvtickpoint_pt
= self
.axes
["x"].vtickpoint_pt
321 self
.xvtickpoint
= self
.axes
["x"].tickpoint
322 self
.xtickdirection
= self
.axes
["x"].tickdirection
323 self
.xvtickdirection
= self
.axes
["x"].vtickdirection
325 if self
.axes
.has_key("y"):
326 self
.ybasepath
= self
.axes
["y"].basepath
327 self
.yvbasepath
= self
.axes
["y"].vbasepath
328 self
.ygridpath
= self
.axes
["y"].gridpath
329 self
.ytickpoint_pt
= self
.axes
["y"].tickpoint_pt
330 self
.ytickpoint
= self
.axes
["y"].tickpoint
331 self
.yvtickpoint_pt
= self
.axes
["y"].vtickpoint_pt
332 self
.yvtickpoint
= self
.axes
["y"].tickpoint
333 self
.ytickdirection
= self
.axes
["y"].tickdirection
334 self
.yvtickdirection
= self
.axes
["y"].vtickdirection
336 self
.axesnames
= ([], [])
337 for axisname
, aaxis
in self
.axes
.items():
338 if axisname
[0] not in "xy" or (len(axisname
) != 1 and (not axisname
[1:].isdigit() or
339 axisname
[1:] == "1")):
340 raise ValueError("invalid axis name")
341 if axisname
[0] == "x":
342 self
.axesnames
[0].append(axisname
)
344 self
.axesnames
[1].append(axisname
)
345 aaxis
.setcreatecall(self
.doaxiscreate
, axisname
)
348 def pos_pt(self
, x
, y
, xaxis
=None, yaxis
=None):
350 xaxis
= self
.axes
["x"]
352 yaxis
= self
.axes
["y"]
353 return (self
.xpos_pt
+ xaxis
.convert(x
)*self
.width_pt
,
354 self
.ypos_pt
+ yaxis
.convert(y
)*self
.height_pt
)
356 def pos(self
, x
, y
, xaxis
=None, yaxis
=None):
358 xaxis
= self
.axes
["x"]
360 yaxis
= self
.axes
["y"]
361 return (self
.xpos
+ xaxis
.convert(x
)*self
.width
,
362 self
.ypos
+ yaxis
.convert(y
)*self
.height
)
364 def vpos_pt(self
, vx
, vy
):
365 return (self
.xpos_pt
+ vx
*self
.width_pt
,
366 self
.ypos_pt
+ vy
*self
.height_pt
)
368 def vpos(self
, vx
, vy
):
369 return (self
.xpos
+ vx
*self
.width
,
370 self
.ypos
+ vy
*self
.height
)
372 def vgeodesic(self
, vx1
, vy1
, vx2
, vy2
):
373 """returns a geodesic path between two points in graph coordinates"""
374 return path
.line_pt(self
.xpos_pt
+ vx1
*self
.width_pt
,
375 self
.ypos_pt
+ vy1
*self
.height_pt
,
376 self
.xpos_pt
+ vx2
*self
.width_pt
,
377 self
.ypos_pt
+ vy2
*self
.height_pt
)
379 def vgeodesic_el(self
, vx1
, vy1
, vx2
, vy2
):
380 """returns a geodesic path element between two points in graph coordinates"""
381 return path
.lineto_pt(self
.xpos_pt
+ vx2
*self
.width_pt
,
382 self
.ypos_pt
+ vy2
*self
.height_pt
)
384 def vcap_pt(self
, coordinate
, length_pt
, vx
, vy
):
385 """returns an error cap path for a given coordinate, lengths and
386 point in graph coordinates"""
388 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
- 0.5*length_pt
,
389 self
.ypos_pt
+ vy
*self
.height_pt
,
390 self
.xpos_pt
+ vx
*self
.width_pt
+ 0.5*length_pt
,
391 self
.ypos_pt
+ vy
*self
.height_pt
)
392 elif coordinate
== 1:
393 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
,
394 self
.ypos_pt
+ vy
*self
.height_pt
- 0.5*length_pt
,
395 self
.xpos_pt
+ vx
*self
.width_pt
,
396 self
.ypos_pt
+ vy
*self
.height_pt
+ 0.5*length_pt
)
398 raise ValueError("direction invalid")
400 def xvgridpath(self
, vx
):
401 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
, self
.ypos_pt
,
402 self
.xpos_pt
+ vx
*self
.width_pt
, self
.ypos_pt
+ self
.height_pt
)
404 def yvgridpath(self
, vy
):
405 return path
.line_pt(self
.xpos_pt
, self
.ypos_pt
+ vy
*self
.height_pt
,
406 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ vy
*self
.height_pt
)
408 def axistrafo(self
, axis
, t
):
409 c
= canvas
.canvas([t
])
410 c
.insert(axis
.canvas
)
413 def axisatv(self
, axis
, v
):
414 if axis
.positioner
.fixtickdirection
[0]:
416 self
.axistrafo(axis
, trafo
.translate_pt(self
.xpos_pt
+ v
*self
.width_pt
- axis
.positioner
.x1_pt
, 0))
419 self
.axistrafo(axis
, trafo
.translate_pt(0, self
.ypos_pt
+ v
*self
.height_pt
- axis
.positioner
.y1_pt
))
421 def doaxispositioner(self
, axisname
):
422 if self
.did(self
.doaxispositioner
, axisname
):
426 self
.axes
["x"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
,
427 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
,
428 (0, 1), self
.xvgridpath
))
429 elif axisname
== "x2":
430 self
.axes
["x2"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
+ self
.height_pt
,
431 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ self
.height_pt
,
432 (0, -1), self
.xvgridpath
))
433 elif axisname
== "y":
434 self
.axes
["y"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
,
435 self
.xpos_pt
, self
.ypos_pt
+ self
.height_pt
,
436 (1, 0), self
.yvgridpath
))
437 elif axisname
== "y2":
438 self
.axes
["y2"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
,
439 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ self
.height_pt
,
440 (-1, 0), self
.yvgridpath
))
442 if axisname
[1:] == "3":
443 dependsonaxisname
= axisname
[0]
445 dependsonaxisname
= "%s%d" % (axisname
[0], int(axisname
[1:]) - 2)
446 self
.doaxiscreate(dependsonaxisname
)
447 sign
= 2*(int(axisname
[1:]) % 2) - 1
448 if axisname
[0] == "x":
449 y_pt
= self
.axes
[dependsonaxisname
].positioner
.y1_pt
- sign
* (self
.axes
[dependsonaxisname
].canvas
.extent_pt
+ self
.axesdist_pt
)
450 self
.axes
[axisname
].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, y_pt
,
451 self
.xpos_pt
+ self
.width_pt
, y_pt
,
452 (0, sign
), self
.xvgridpath
))
454 x_pt
= self
.axes
[dependsonaxisname
].positioner
.x1_pt
- sign
* (self
.axes
[dependsonaxisname
].canvas
.extent_pt
+ self
.axesdist_pt
)
455 self
.axes
[axisname
].setpositioner(positioner
.lineaxispos_pt(x_pt
, self
.ypos_pt
,
456 x_pt
, self
.ypos_pt
+ self
.height_pt
,
457 (sign
, 0), self
.yvgridpath
))
460 if self
.did(self
.dolayout
):
462 for axisname
in self
.axes
.keys():
463 self
.doaxiscreate(axisname
)
464 if self
.xaxisat
is not None:
465 self
.axisatv(self
.axes
["x"], self
.axes
["y"].convert(self
.xaxisat
))
466 if self
.yaxisat
is not None:
467 self
.axisatv(self
.axes
["y"], self
.axes
["x"].convert(self
.yaxisat
))
469 def dobackground(self
):
470 if self
.did(self
.dobackground
):
472 if self
.backgroundattrs
is not None:
473 self
.draw(path
.rect_pt(self
.xpos_pt
, self
.ypos_pt
, self
.width_pt
, self
.height_pt
),
474 self
.backgroundattrs
)
477 if self
.did(self
.doaxes
):
481 for axis
in self
.axes
.values():
482 self
.insert(axis
.canvas
)
485 if self
.did(self
.dokey
):
489 if self
.key
is not None:
490 c
= self
.key
.paint(self
.plotitems
)
492 def parentchildalign(pmin
, pmax
, cmin
, cmax
, pos
, dist
, inside
):
493 ppos
= pmin
+0.5*(cmax
-cmin
)+dist
+pos
*(pmax
-pmin
-cmax
+cmin
-2*dist
)
494 cpos
= 0.5*(cmin
+cmax
)+(1-inside
)*(1-2*pos
)*(cmax
-cmin
+2*dist
)
497 x
= parentchildalign(self
.xpos_pt
, self
.xpos_pt
+self
.width_pt
,
498 bbox
.llx_pt
, bbox
.urx_pt
,
499 self
.key
.hpos
, unit
.topt(self
.key
.hdist
), self
.key
.hinside
)
500 y
= parentchildalign(self
.ypos_pt
, self
.ypos_pt
+self
.height_pt
,
501 bbox
.lly_pt
, bbox
.ury_pt
,
502 self
.key
.vpos
, unit
.topt(self
.key
.vdist
), self
.key
.vinside
)
503 self
.insert(c
, [trafo
.translate_pt(x
, y
)])
506 class graphxyz(graphxy
):
510 def __init__(self
, distance
, phi
, theta
, anglefactor
=math
.pi
/180):
514 self
.a
= (-math
.sin(phi
), math
.cos(phi
), 0)
515 self
.b
= (-math
.cos(phi
)*math
.sin(theta
),
516 -math
.sin(phi
)*math
.sin(theta
),
518 self
.eye
= (distance
*math
.cos(phi
)*math
.cos(theta
),
519 distance
*math
.sin(phi
)*math
.cos(theta
),
520 distance
*math
.sin(theta
))
522 def point(self
, x
, y
, z
):
523 d0
= (self
.a
[0]*self
.b
[1]*(z
-self
.eye
[2])
524 + self
.a
[2]*self
.b
[0]*(y
-self
.eye
[1])
525 + self
.a
[1]*self
.b
[2]*(x
-self
.eye
[0])
526 - self
.a
[2]*self
.b
[1]*(x
-self
.eye
[0])
527 - self
.a
[0]*self
.b
[2]*(y
-self
.eye
[1])
528 - self
.a
[1]*self
.b
[0]*(z
-self
.eye
[2]))
529 da
= (self
.eye
[0]*self
.b
[1]*(z
-self
.eye
[2])
530 + self
.eye
[2]*self
.b
[0]*(y
-self
.eye
[1])
531 + self
.eye
[1]*self
.b
[2]*(x
-self
.eye
[0])
532 - self
.eye
[2]*self
.b
[1]*(x
-self
.eye
[0])
533 - self
.eye
[0]*self
.b
[2]*(y
-self
.eye
[1])
534 - self
.eye
[1]*self
.b
[0]*(z
-self
.eye
[2]))
535 db
= (self
.a
[0]*self
.eye
[1]*(z
-self
.eye
[2])
536 + self
.a
[2]*self
.eye
[0]*(y
-self
.eye
[1])
537 + self
.a
[1]*self
.eye
[2]*(x
-self
.eye
[0])
538 - self
.a
[2]*self
.eye
[1]*(x
-self
.eye
[0])
539 - self
.a
[0]*self
.eye
[2]*(y
-self
.eye
[1])
540 - self
.a
[1]*self
.eye
[0]*(z
-self
.eye
[2]))
546 def __init__(self
, phi
, theta
, anglefactor
=math
.pi
/180):
550 self
.a
= (-math
.sin(phi
), math
.cos(phi
), 0)
551 self
.b
= (-math
.cos(phi
)*math
.sin(theta
),
552 -math
.sin(phi
)*math
.sin(theta
),
555 def point(self
, x
, y
, z
):
556 return self
.a
[0]*x
+self
.a
[1]*y
+self
.a
[2]*z
, self
.b
[0]*x
+self
.b
[1]*y
+self
.b
[2]*z
559 def __init__(self
, xpos
=0, ypos
=0, size
=None,
560 xscale
=1, yscale
=1, zscale
=1/goldenmean
,
561 projector
=central(10, -30, 30),
562 key
=None, backgroundattrs
=None,
569 self
.xpos_pt
= unit
.topt(xpos
)
570 self
.ypos_pt
= unit
.topt(ypos
)
571 self
.size_pt
= unit
.topt(size
)
575 self
.projector
= projector
577 self
.backgroundattrs
= backgroundattrs
579 for axisname
, aaxis
in axes
.items():
580 if aaxis
is not None:
581 if not isinstance(aaxis
, axis
.linkedaxis
):
582 self
.axes
[axisname
] = axis
.anchoredaxis(aaxis
, self
.texrunner
, axisname
)
584 self
.axes
[axisname
] = aaxis
586 for axisname
in ["x", "y", "z"]:
587 if not axes
.has_key(axisname
):
588 self
.axes
[axisname
] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
590 if self
.axes
.has_key("x"):
591 self
.xbasepath
= self
.axes
["x"].basepath
592 self
.xvbasepath
= self
.axes
["x"].vbasepath
593 self
.xgridpath
= self
.axes
["x"].gridpath
594 self
.xtickpoint_pt
= self
.axes
["x"].tickpoint_pt
595 self
.xtickpoint
= self
.axes
["x"].tickpoint
596 self
.xvtickpoint
= self
.axes
["x"].tickpoint
597 self
.xtickdirection
= self
.axes
["x"].tickdirection
599 if self
.axes
.has_key("y"):
600 self
.ybasepath
= self
.axes
["y"].basepath
601 self
.yvbasepath
= self
.axes
["y"].vbasepath
602 self
.ygridpath
= self
.axes
["y"].gridpath
603 self
.ytickpoint_pt
= self
.axes
["y"].tickpoint_pt
604 self
.ytickpoint
= self
.axes
["y"].tickpoint
605 self
.yvtickpoint
= self
.axes
["y"].tickpoint
606 self
.ytickdirection
= self
.axes
["y"].tickdirection
608 if self
.axes
.has_key("z"):
609 self
.zbasepath
= self
.axes
["z"].basepath
610 self
.zvbasepath
= self
.axes
["z"].vbasepath
611 self
.zgridpath
= self
.axes
["z"].gridpath
612 self
.ztickpoint_pt
= self
.axes
["z"].tickpoint_pt
613 self
.ztickpoint
= self
.axes
["z"].tickpoint
614 self
.zvtickpoint
= self
.axes
["z"].tickpoint
615 self
.ztickdirection
= self
.axes
["z"].tickdirection
617 self
.axesnames
= ([], [], [])
618 for axisname
, aaxis
in self
.axes
.items():
619 if axisname
[0] not in "xyz" or (len(axisname
) != 1 and (not axisname
[1:].isdigit() or
620 axisname
[1:] == "1")):
621 raise ValueError("invalid axis name")
622 if axisname
[0] == "x":
623 self
.axesnames
[0].append(axisname
)
624 elif axisname
[0] == "y":
625 self
.axesnames
[1].append(axisname
)
627 self
.axesnames
[2].append(axisname
)
628 aaxis
.setcreatecall(self
.doaxiscreate
, axisname
)
630 def pos_pt(self
, x
, y
, z
, xaxis
=None, yaxis
=None, zaxis
=None):
632 xaxis
= self
.axes
["x"]
634 yaxis
= self
.axes
["y"]
636 zaxis
= self
.axes
["z"]
637 return self
.vpos_pt(xaxis
.convert(x
), yaxis
.convert(y
), zaxis
.convert(y
))
639 def pos(self
, x
, y
, z
, xaxis
=None, yaxis
=None, zaxis
=None):
641 xaxis
= self
.axes
["x"]
643 yaxis
= self
.axes
["y"]
645 zaxis
= self
.axes
["z"]
646 return self
.vpos(xaxis
.convert(x
), yaxis
.convert(y
), zaxis
.convert(y
))
648 def vpos_pt(self
, vx
, vy
, vz
):
649 x
, y
= self
.projector
.point(2*self
.xscale
*(vx
- 0.5),
650 2*self
.yscale
*(vy
- 0.5),
651 2*self
.zscale
*(vz
- 0.5))
652 return self
.xpos_pt
+x
*self
.size_pt
, self
.ypos_pt
+y
*self
.size_pt
654 def vpos(self
, vx
, vy
, vz
):
655 x
, y
= self
.projector
.point(2*self
.xscale
*(vx
- 0.5),
656 2*self
.yscale
*(vy
- 0.5),
657 2*self
.zscale
*(vz
- 0.5))
658 return self
.xpos
+x
*self
.size
, self
.ypos
+y
*self
.size
660 # def xbaseline(self, axis, x1, x2, xaxis=None):
661 # if xaxis is None: xaxis = self.axes["x"]
662 # return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2))
664 # def ybaseline(self, axis, y1, y2, yaxis=None):
665 # if yaxis is None: yaxis = self.axes["y"]
666 # return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2))
668 # def zbaseline(self, axis, z1, z2, zaxis=None):
669 # if zaxis is None: zaxis = self.axes["z"]
670 # return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2))
672 # def vxbaseline(self, axis, v1, v2):
673 # return (path.line_pt(*(self.vpos_pt(v1, 0, 0) + self.vpos_pt(v2, 0, 0))) +
674 # path.line_pt(*(self.vpos_pt(v1, 0, 1) + self.vpos_pt(v2, 0, 1))) +
675 # path.line_pt(*(self.vpos_pt(v1, 1, 1) + self.vpos_pt(v2, 1, 1))) +
676 # path.line_pt(*(self.vpos_pt(v1, 1, 0) + self.vpos_pt(v2, 1, 0))))
678 # def vybaseline(self, axis, v1, v2):
679 # return (path.line_pt(*(self.vpos_pt(0, v1, 0) + self.vpos_pt(0, v2, 0))) +
680 # path.line_pt(*(self.vpos_pt(0, v1, 1) + self.vpos_pt(0, v2, 1))) +
681 # path.line_pt(*(self.vpos_pt(1, v1, 1) + self.vpos_pt(1, v2, 1))) +
682 # path.line_pt(*(self.vpos_pt(1, v1, 0) + self.vpos_pt(1, v2, 0))))
684 # def vzbaseline(self, axis, v1, v2):
685 # return (path.line_pt(*(self.vpos_pt(0, 0, v1) + self.vpos_pt(0, 0, v2))) +
686 # path.line_pt(*(self.vpos_pt(0, 1, v1) + self.vpos_pt(0, 1, v2))) +
687 # path.line_pt(*(self.vpos_pt(1, 1, v1) + self.vpos_pt(1, 1, v2))) +
688 # path.line_pt(*(self.vpos_pt(1, 0, v1) + self.vpos_pt(1, 0, v2))))
690 def vgeodesic(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
691 """returns a geodesic path between two points in graph coordinates"""
692 return path
.line_pt(*(self
.vpos_pt(vx1
, vy1
, vz1
) + self
.vpos_pt(vx2
, vy2
, vz2
)))
694 def vgeodesic_el(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
695 """returns a geodesic path element between two points in graph coordinates"""
696 return path
.lineto_pt(*(self
.vpos_pt(vx1
, vy1
, vz1
) + self
.vpos_pt(vx2
, vy2
, vz2
)))
698 def vcap_pt(self
, coordinate
, length_pt
, vx
, vy
, vz
):
699 """returns an error cap path for a given coordinate, lengths and
700 point in graph coordinates"""
701 raise NotImplementedError
703 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
- 0.5*length_pt
,
704 self
.ypos_pt
+ vy
*self
.height_pt
,
705 self
.xpos_pt
+ vx
*self
.width_pt
+ 0.5*length_pt
,
706 self
.ypos_pt
+ vy
*self
.height_pt
)
707 elif coordinate
== 1:
708 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
,
709 self
.ypos_pt
+ vy
*self
.height_pt
- 0.5*length_pt
,
710 self
.xpos_pt
+ vx
*self
.width_pt
,
711 self
.ypos_pt
+ vy
*self
.height_pt
+ 0.5*length_pt
)
713 raise ValueError("direction invalid")
715 def xvtickpoint_pt(self
, vx
):
716 return self
.vpos_pt(vx
, 0, 0)
718 def yvtickpoint_pt(self
, vy
):
719 return self
.vpos_pt(1, vy
, 0)
721 def zvtickpoint_pt(self
, vz
):
722 return self
.vpos_pt(0, 0, vz
)
724 def xvtickdirection(self
, vx
):
725 x1_pt
, y1_pt
= self
.vpos_pt(vx
, 0, 0)
726 x2_pt
, y2_pt
= self
.vpos_pt(vx
, 1, 0)
727 dx_pt
= x2_pt
- x1_pt
728 dy_pt
= y2_pt
- y1_pt
729 norm
= math
.hypot(dx_pt
, dy_pt
)
730 return dx_pt
/norm
, dy_pt
/norm
732 def yvtickdirection(self
, vy
):
733 x1_pt
, y1_pt
= self
.vpos_pt(1, vy
, 0)
734 x2_pt
, y2_pt
= self
.vpos_pt(0, vy
, 0)
735 dx_pt
= x2_pt
- x1_pt
736 dy_pt
= y2_pt
- y1_pt
737 norm
= math
.hypot(dx_pt
, dy_pt
)
738 return dx_pt
/norm
, dy_pt
/norm
740 def zvtickdirection(self
, vz
):
741 x1_pt
, y1_pt
= self
.vpos_pt(0, 0, vz
)
742 x2_pt
, y2_pt
= self
.vpos_pt(1, 1, vz
)
743 dx_pt
= x2_pt
- x1_pt
744 dy_pt
= y2_pt
- y1_pt
745 norm
= math
.hypot(dx_pt
, dy_pt
)
746 return dx_pt
/norm
, dy_pt
/norm
748 def yvgridpath(self
, vy
):
749 raise NotImplementedError
751 def zvgridpath(self
, vz
):
752 raise NotImplementedError
754 def xvgridpath(self
, vx
):
755 raise NotImplementedError
757 def yvgridpath(self
, vy
):
758 raise NotImplementedError
760 def zvgridpath(self
, vz
):
761 raise NotImplementedError
763 def doaxispositioner(self
, axisname
):
764 if self
.did(self
.doaxispositioner
, axisname
):
768 x1_pt
, y1_pt
= self
.vpos_pt(0, 0, 0)
769 x2_pt
, y2_pt
= self
.vpos_pt(1, 0, 0)
770 self
.axes
["x"].setpositioner(positioner
.flexlineaxispos_pt(self
.xvtickpoint_pt
, self
.xvtickdirection
, self
.xvgridpath
))
771 elif axisname
== "y":
772 x1_pt
, y1_pt
= self
.vpos_pt(1, 0, 0)
773 x2_pt
, y2_pt
= self
.vpos_pt(1, 1, 0)
774 self
.axes
["y"].setpositioner(positioner
.flexlineaxispos_pt(self
.yvtickpoint_pt
, self
.yvtickdirection
, self
.yvgridpath
))
775 elif axisname
== "z":
776 x1_pt
, y1_pt
= self
.vpos_pt(0, 0, 0)
777 x2_pt
, y2_pt
= self
.vpos_pt(0, 0, 1)
778 self
.axes
["z"].setpositioner(positioner
.flexlineaxispos_pt(self
.zvtickpoint_pt
, self
.zvtickdirection
, self
.yvgridpath
))
780 raise NotImplementedError("multiple axes not yet supported")
783 if self
.did(self
.dolayout
):
785 for axisname
in self
.axes
.keys():
786 self
.doaxiscreate(axisname
)
788 def dobackground(self
):
789 if self
.did(self
.dobackground
):
793 if self
.did(self
.doaxes
):
797 for axis
in self
.axes
.values():
798 self
.insert(axis
.canvas
)
801 if self
.did(self
.dokey
):
805 if self
.key
is not None:
806 c
= self
.key
.paint(self
.plotitems
)
808 def parentchildalign(pmin
, pmax
, cmin
, cmax
, pos
, dist
, inside
):
809 ppos
= pmin
+0.5*(cmax
-cmin
)+dist
+pos
*(pmax
-pmin
-cmax
+cmin
-2*dist
)
810 cpos
= 0.5*(cmin
+cmax
)+(1-inside
)*(1-2*pos
)*(cmax
-cmin
+2*dist
)
813 x
= parentchildalign(self
.xpos_pt
, self
.xpos_pt
+self
.width_pt
,
814 bbox
.llx_pt
, bbox
.urx_pt
,
815 self
.key
.hpos
, unit
.topt(self
.key
.hdist
), self
.key
.hinside
)
816 y
= parentchildalign(self
.ypos_pt
, self
.ypos_pt
+self
.height_pt
,
817 bbox
.lly_pt
, bbox
.ury_pt
,
818 self
.key
.vpos
, unit
.topt(self
.key
.vdist
), self
.key
.vinside
)
819 self
.insert(c
, [trafo
.translate_pt(x
, y
)])