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"].vtickpoint
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 vzindex(self
, vx
, vy
):
375 def vangle(self
, vx1
, vy1
, vx2
, vy2
, vx3
, vy3
):
378 def vgeodesic(self
, vx1
, vy1
, vx2
, vy2
):
379 """returns a geodesic path between two points in graph coordinates"""
380 return path
.line_pt(self
.xpos_pt
+ vx1
*self
.width_pt
,
381 self
.ypos_pt
+ vy1
*self
.height_pt
,
382 self
.xpos_pt
+ vx2
*self
.width_pt
,
383 self
.ypos_pt
+ vy2
*self
.height_pt
)
385 def vgeodesic_el(self
, vx1
, vy1
, vx2
, vy2
):
386 """returns a geodesic path element between two points in graph coordinates"""
387 return path
.lineto_pt(self
.xpos_pt
+ vx2
*self
.width_pt
,
388 self
.ypos_pt
+ vy2
*self
.height_pt
)
390 def vcap_pt(self
, coordinate
, length_pt
, vx
, vy
):
391 """returns an error cap path for a given coordinate, lengths and
392 point in graph coordinates"""
394 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
- 0.5*length_pt
,
395 self
.ypos_pt
+ vy
*self
.height_pt
,
396 self
.xpos_pt
+ vx
*self
.width_pt
+ 0.5*length_pt
,
397 self
.ypos_pt
+ vy
*self
.height_pt
)
398 elif coordinate
== 1:
399 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
,
400 self
.ypos_pt
+ vy
*self
.height_pt
- 0.5*length_pt
,
401 self
.xpos_pt
+ vx
*self
.width_pt
,
402 self
.ypos_pt
+ vy
*self
.height_pt
+ 0.5*length_pt
)
404 raise ValueError("direction invalid")
406 def xvgridpath(self
, vx
):
407 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
, self
.ypos_pt
,
408 self
.xpos_pt
+ vx
*self
.width_pt
, self
.ypos_pt
+ self
.height_pt
)
410 def yvgridpath(self
, vy
):
411 return path
.line_pt(self
.xpos_pt
, self
.ypos_pt
+ vy
*self
.height_pt
,
412 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ vy
*self
.height_pt
)
414 def axistrafo(self
, axis
, t
):
415 c
= canvas
.canvas([t
])
416 c
.insert(axis
.canvas
)
419 def axisatv(self
, axis
, v
):
420 if axis
.positioner
.fixtickdirection
[0]:
422 self
.axistrafo(axis
, trafo
.translate_pt(self
.xpos_pt
+ v
*self
.width_pt
- axis
.positioner
.x1_pt
, 0))
425 self
.axistrafo(axis
, trafo
.translate_pt(0, self
.ypos_pt
+ v
*self
.height_pt
- axis
.positioner
.y1_pt
))
427 def doaxispositioner(self
, axisname
):
428 if self
.did(self
.doaxispositioner
, axisname
):
432 self
.axes
["x"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
,
433 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
,
434 (0, 1), self
.xvgridpath
))
435 elif axisname
== "x2":
436 self
.axes
["x2"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
+ self
.height_pt
,
437 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ self
.height_pt
,
438 (0, -1), self
.xvgridpath
))
439 elif axisname
== "y":
440 self
.axes
["y"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
,
441 self
.xpos_pt
, self
.ypos_pt
+ self
.height_pt
,
442 (1, 0), self
.yvgridpath
))
443 elif axisname
== "y2":
444 self
.axes
["y2"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
,
445 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ self
.height_pt
,
446 (-1, 0), self
.yvgridpath
))
448 if axisname
[1:] == "3":
449 dependsonaxisname
= axisname
[0]
451 dependsonaxisname
= "%s%d" % (axisname
[0], int(axisname
[1:]) - 2)
452 self
.doaxiscreate(dependsonaxisname
)
453 sign
= 2*(int(axisname
[1:]) % 2) - 1
454 if axisname
[0] == "x":
455 y_pt
= self
.axes
[dependsonaxisname
].positioner
.y1_pt
- sign
* (self
.axes
[dependsonaxisname
].canvas
.extent_pt
+ self
.axesdist_pt
)
456 self
.axes
[axisname
].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, y_pt
,
457 self
.xpos_pt
+ self
.width_pt
, y_pt
,
458 (0, sign
), self
.xvgridpath
))
460 x_pt
= self
.axes
[dependsonaxisname
].positioner
.x1_pt
- sign
* (self
.axes
[dependsonaxisname
].canvas
.extent_pt
+ self
.axesdist_pt
)
461 self
.axes
[axisname
].setpositioner(positioner
.lineaxispos_pt(x_pt
, self
.ypos_pt
,
462 x_pt
, self
.ypos_pt
+ self
.height_pt
,
463 (sign
, 0), self
.yvgridpath
))
466 if self
.did(self
.dolayout
):
468 for axisname
in self
.axes
.keys():
469 self
.doaxiscreate(axisname
)
470 if self
.xaxisat
is not None:
471 self
.axisatv(self
.axes
["x"], self
.axes
["y"].convert(self
.xaxisat
))
472 if self
.yaxisat
is not None:
473 self
.axisatv(self
.axes
["y"], self
.axes
["x"].convert(self
.yaxisat
))
475 def dobackground(self
):
476 if self
.did(self
.dobackground
):
478 if self
.backgroundattrs
is not None:
479 self
.draw(path
.rect_pt(self
.xpos_pt
, self
.ypos_pt
, self
.width_pt
, self
.height_pt
),
480 self
.backgroundattrs
)
483 if self
.did(self
.doaxes
):
487 for axis
in self
.axes
.values():
488 self
.insert(axis
.canvas
)
491 if self
.did(self
.dokey
):
495 if self
.key
is not None:
496 c
= self
.key
.paint(self
.plotitems
)
498 def parentchildalign(pmin
, pmax
, cmin
, cmax
, pos
, dist
, inside
):
499 ppos
= pmin
+0.5*(cmax
-cmin
)+dist
+pos
*(pmax
-pmin
-cmax
+cmin
-2*dist
)
500 cpos
= 0.5*(cmin
+cmax
)+(1-inside
)*(1-2*pos
)*(cmax
-cmin
+2*dist
)
503 x
= parentchildalign(self
.xpos_pt
, self
.xpos_pt
+self
.width_pt
,
504 bbox
.llx_pt
, bbox
.urx_pt
,
505 self
.key
.hpos
, unit
.topt(self
.key
.hdist
), self
.key
.hinside
)
506 y
= parentchildalign(self
.ypos_pt
, self
.ypos_pt
+self
.height_pt
,
507 bbox
.lly_pt
, bbox
.ury_pt
,
508 self
.key
.vpos
, unit
.topt(self
.key
.vdist
), self
.key
.vinside
)
509 self
.insert(c
, [trafo
.translate_pt(x
, y
)])
512 class graphxyz(graphxy
):
516 def __init__(self
, distance
, phi
, theta
, anglefactor
=math
.pi
/180):
519 self
.distance
= distance
521 self
.a
= (-math
.sin(phi
), math
.cos(phi
), 0)
522 self
.b
= (-math
.cos(phi
)*math
.sin(theta
),
523 -math
.sin(phi
)*math
.sin(theta
),
525 self
.eye
= (distance
*math
.cos(phi
)*math
.cos(theta
),
526 distance
*math
.sin(phi
)*math
.cos(theta
),
527 distance
*math
.sin(theta
))
529 def point(self
, x
, y
, z
):
530 d0
= (self
.a
[0]*self
.b
[1]*(z
-self
.eye
[2])
531 + self
.a
[2]*self
.b
[0]*(y
-self
.eye
[1])
532 + self
.a
[1]*self
.b
[2]*(x
-self
.eye
[0])
533 - self
.a
[2]*self
.b
[1]*(x
-self
.eye
[0])
534 - self
.a
[0]*self
.b
[2]*(y
-self
.eye
[1])
535 - self
.a
[1]*self
.b
[0]*(z
-self
.eye
[2]))
536 da
= (self
.eye
[0]*self
.b
[1]*(z
-self
.eye
[2])
537 + self
.eye
[2]*self
.b
[0]*(y
-self
.eye
[1])
538 + self
.eye
[1]*self
.b
[2]*(x
-self
.eye
[0])
539 - self
.eye
[2]*self
.b
[1]*(x
-self
.eye
[0])
540 - self
.eye
[0]*self
.b
[2]*(y
-self
.eye
[1])
541 - self
.eye
[1]*self
.b
[0]*(z
-self
.eye
[2]))
542 db
= (self
.a
[0]*self
.eye
[1]*(z
-self
.eye
[2])
543 + self
.a
[2]*self
.eye
[0]*(y
-self
.eye
[1])
544 + self
.a
[1]*self
.eye
[2]*(x
-self
.eye
[0])
545 - self
.a
[2]*self
.eye
[1]*(x
-self
.eye
[0])
546 - self
.a
[0]*self
.eye
[2]*(y
-self
.eye
[1])
547 - self
.a
[1]*self
.eye
[0]*(z
-self
.eye
[2]))
550 def zindex(self
, x
, y
, z
):
551 return math
.sqrt((x
-self
.eye
[0])*(x
-self
.eye
[0])+(y
-self
.eye
[1])*(y
-self
.eye
[1])+(z
-self
.eye
[2])*(z
-self
.eye
[2]))-self
.distance
553 def angle(self
, x1
, y1
, z1
, x2
, y2
, z2
, x3
, y3
, z3
):
554 sx
= (x1
-self
.eye
[0])
555 sy
= (y1
-self
.eye
[1])
556 sz
= (z1
-self
.eye
[2])
557 nx
= (y2
-y1
)*(z3
-z1
)-(z2
-z1
)*(y3
-y1
)
558 ny
= (z2
-z1
)*(x3
-x1
)-(x2
-x1
)*(z3
-z1
)
559 nz
= (x2
-x1
)*(y3
-y1
)-(y2
-y1
)*(x3
-x1
)
560 return (sx
*nx
+sy
*ny
+sz
*nz
)/math
.sqrt(nx
*nx
+ny
*ny
+nz
*nz
)/math
.sqrt(sx
*sx
+sy
*sy
+sz
*sz
)
565 def __init__(self
, phi
, theta
, anglefactor
=math
.pi
/180):
569 self
.a
= (-math
.sin(phi
), math
.cos(phi
), 0)
570 self
.b
= (-math
.cos(phi
)*math
.sin(theta
),
571 -math
.sin(phi
)*math
.sin(theta
),
573 self
.c
= (-math
.cos(phi
)*math
.cos(theta
),
574 -math
.sin(phi
)*math
.cos(theta
),
577 def point(self
, x
, y
, z
):
578 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
580 def zindex(self
, x
, y
, z
):
581 return self
.c
[0]*x
+self
.c
[1]*y
+self
.c
[2]*z
583 def angle(self
, x1
, y1
, z1
, x2
, y2
, z2
, x3
, y3
, z3
):
584 nx
= (y2
-y1
)*(z3
-z1
)-(z2
-z1
)*(y3
-y1
)
585 ny
= (z2
-z1
)*(x3
-x1
)-(x2
-x1
)*(z3
-z1
)
586 nz
= (x2
-x1
)*(y3
-y1
)-(y2
-y1
)*(x3
-x1
)
587 return (self
.c
[0]*nx
+self
.c
[1]*ny
+self
.c
[2]*nz
)/math
.sqrt(nx
*nx
+ny
*ny
+nz
*nz
)
590 def __init__(self
, xpos
=0, ypos
=0, size
=None,
591 xscale
=1, yscale
=1, zscale
=1/goldenmean
,
592 projector
=central(10, -30, 30), key
=None,
599 self
.xpos_pt
= unit
.topt(xpos
)
600 self
.ypos_pt
= unit
.topt(ypos
)
601 self
.size_pt
= unit
.topt(size
)
605 self
.projector
= projector
608 self
.xorder
= projector
.zindex(0, -1, 0) > projector
.zindex(0, 1, 0) and 1 or 0
609 self
.yorder
= projector
.zindex(-1, 0, 0) > projector
.zindex(1, 0, 0) and 1 or 0
610 self
.zindexscale
= math
.sqrt(xscale
*xscale
+yscale
*yscale
+zscale
*zscale
)
612 for axisname
, aaxis
in axes
.items():
613 if aaxis
is not None:
614 if not isinstance(aaxis
, axis
.linkedaxis
):
615 self
.axes
[axisname
] = axis
.anchoredaxis(aaxis
, self
.texrunner
, axisname
)
617 self
.axes
[axisname
] = aaxis
618 for axisname
in ["x", "y"]:
619 okey
= axisname
+ "2"
620 if not axes
.has_key(axisname
):
621 if not axes
.has_key(okey
):
622 self
.axes
[axisname
] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
623 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
625 self
.axes
[axisname
] = axis
.linkedaxis(self
.axes
[okey
], axisname
)
626 if not axes
.has_key("z"):
627 self
.axes
["z"] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
629 if self
.axes
.has_key("x"):
630 self
.xbasepath
= self
.axes
["x"].basepath
631 self
.xvbasepath
= self
.axes
["x"].vbasepath
632 self
.xgridpath
= self
.axes
["x"].gridpath
633 self
.xtickpoint_pt
= self
.axes
["x"].tickpoint_pt
634 self
.xtickpoint
= self
.axes
["x"].tickpoint
635 self
.xvtickpoint_pt
= self
.axes
["x"].vtickpoint_pt
636 self
.xvtickpoint
= self
.axes
["x"].tickpoint
637 self
.xtickdirection
= self
.axes
["x"].tickdirection
638 self
.xvtickdirection
= self
.axes
["x"].vtickdirection
640 if self
.axes
.has_key("y"):
641 self
.ybasepath
= self
.axes
["y"].basepath
642 self
.yvbasepath
= self
.axes
["y"].vbasepath
643 self
.ygridpath
= self
.axes
["y"].gridpath
644 self
.ytickpoint_pt
= self
.axes
["y"].tickpoint_pt
645 self
.ytickpoint
= self
.axes
["y"].tickpoint
646 self
.yvtickpoint_pt
= self
.axes
["y"].vtickpoint_pt
647 self
.yvtickpoint
= self
.axes
["y"].vtickpoint
648 self
.ytickdirection
= self
.axes
["y"].tickdirection
649 self
.yvtickdirection
= self
.axes
["y"].vtickdirection
651 if self
.axes
.has_key("z"):
652 self
.zbasepath
= self
.axes
["z"].basepath
653 self
.zvbasepath
= self
.axes
["z"].vbasepath
654 self
.zgridpath
= self
.axes
["z"].gridpath
655 self
.ztickpoint_pt
= self
.axes
["z"].tickpoint_pt
656 self
.ztickpoint
= self
.axes
["z"].tickpoint
657 self
.zvtickpoint_pt
= self
.axes
["z"].vtickpoint
658 self
.zvtickpoint
= self
.axes
["z"].vtickpoint
659 self
.ztickdirection
= self
.axes
["z"].tickdirection
660 self
.zvtickdirection
= self
.axes
["z"].vtickdirection
662 self
.axesnames
= ([], [], [])
663 for axisname
, aaxis
in self
.axes
.items():
664 if axisname
[0] not in "xyz" or (len(axisname
) != 1 and (not axisname
[1:].isdigit() or
665 axisname
[1:] == "1")):
666 raise ValueError("invalid axis name")
667 if axisname
[0] == "x":
668 self
.axesnames
[0].append(axisname
)
669 elif axisname
[0] == "y":
670 self
.axesnames
[1].append(axisname
)
672 self
.axesnames
[2].append(axisname
)
673 aaxis
.setcreatecall(self
.doaxiscreate
, axisname
)
675 def pos_pt(self
, x
, y
, z
, xaxis
=None, yaxis
=None, zaxis
=None):
677 xaxis
= self
.axes
["x"]
679 yaxis
= self
.axes
["y"]
681 zaxis
= self
.axes
["z"]
682 return self
.vpos_pt(xaxis
.convert(x
), yaxis
.convert(y
), zaxis
.convert(y
))
684 def pos(self
, x
, y
, z
, xaxis
=None, yaxis
=None, zaxis
=None):
686 xaxis
= self
.axes
["x"]
688 yaxis
= self
.axes
["y"]
690 zaxis
= self
.axes
["z"]
691 return self
.vpos(xaxis
.convert(x
), yaxis
.convert(y
), zaxis
.convert(y
))
693 def vpos_pt(self
, vx
, vy
, vz
):
694 x
, y
= self
.projector
.point(2*self
.xscale
*(vx
- 0.5),
695 2*self
.yscale
*(vy
- 0.5),
696 2*self
.zscale
*(vz
- 0.5))
697 return self
.xpos_pt
+x
*self
.size_pt
, self
.ypos_pt
+y
*self
.size_pt
699 def vpos(self
, vx
, vy
, vz
):
700 x
, y
= self
.projector
.point(2*self
.xscale
*(vx
- 0.5),
701 2*self
.yscale
*(vy
- 0.5),
702 2*self
.zscale
*(vz
- 0.5))
703 return self
.xpos
+x
*self
.size
, self
.ypos
+y
*self
.size
705 def vzindex(self
, vx
, vy
, vz
):
706 return self
.projector
.zindex(2*self
.xscale
*(vx
- 0.5),
707 2*self
.yscale
*(vy
- 0.5),
708 2*self
.zscale
*(vz
- 0.5))/self
.zindexscale
710 def vangle(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
, vx3
, vy3
, vz3
):
711 return self
.projector
.angle(2*self
.xscale
*(vx1
- 0.5),
712 2*self
.yscale
*(vy1
- 0.5),
713 2*self
.zscale
*(vz1
- 0.5),
714 2*self
.xscale
*(vx2
- 0.5),
715 2*self
.yscale
*(vy2
- 0.5),
716 2*self
.zscale
*(vz2
- 0.5),
717 2*self
.xscale
*(vx3
- 0.5),
718 2*self
.yscale
*(vy3
- 0.5),
719 2*self
.zscale
*(vz3
- 0.5))
721 def vgeodesic(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
722 """returns a geodesic path between two points in graph coordinates"""
723 return path
.line_pt(*(self
.vpos_pt(vx1
, vy1
, vz1
) + self
.vpos_pt(vx2
, vy2
, vz2
)))
725 def vgeodesic_el(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
726 """returns a geodesic path element between two points in graph coordinates"""
727 return path
.lineto_pt(*(self
.vpos_pt(vx1
, vy1
, vz1
) + self
.vpos_pt(vx2
, vy2
, vz2
)))
729 def vcap_pt(self
, coordinate
, length_pt
, vx
, vy
, vz
):
730 """returns an error cap path for a given coordinate, lengths and
731 point in graph coordinates"""
733 return self
.vgeodesic(vx
-0.5*length_pt
/self
.size_pt
, vy
, vz
, vx
+0.5*length_pt
/self
.size_pt
, vy
, vz
)
734 elif coordinate
== 1:
735 return self
.vgeodesic(vx
, vy
-0.5*length_pt
/self
.size_pt
, vz
, vx
, vy
+0.5*length_pt
/self
.size_pt
, vz
)
736 elif coordinate
== 2:
737 return self
.vgeodesic(vx
, vy
, vz
-0.5*length_pt
/self
.size_pt
, vx
, vy
, vz
+0.5*length_pt
/self
.size_pt
)
739 raise ValueError("direction invalid")
741 def xvtickdirection(self
, vx
):
743 x1_pt
, y1_pt
= self
.vpos_pt(vx
, 1, 0)
744 x2_pt
, y2_pt
= self
.vpos_pt(vx
, 0, 0)
746 x1_pt
, y1_pt
= self
.vpos_pt(vx
, 0, 0)
747 x2_pt
, y2_pt
= self
.vpos_pt(vx
, 1, 0)
748 dx_pt
= x2_pt
- x1_pt
749 dy_pt
= y2_pt
- y1_pt
750 norm
= math
.hypot(dx_pt
, dy_pt
)
751 return dx_pt
/norm
, dy_pt
/norm
753 def yvtickdirection(self
, vy
):
755 x1_pt
, y1_pt
= self
.vpos_pt(1, vy
, 0)
756 x2_pt
, y2_pt
= self
.vpos_pt(0, vy
, 0)
758 x1_pt
, y1_pt
= self
.vpos_pt(0, vy
, 0)
759 x2_pt
, y2_pt
= self
.vpos_pt(1, vy
, 0)
760 dx_pt
= x2_pt
- x1_pt
761 dy_pt
= y2_pt
- y1_pt
762 norm
= math
.hypot(dx_pt
, dy_pt
)
763 return dx_pt
/norm
, dy_pt
/norm
765 def vtickdirection(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
766 x1_pt
, y1_pt
= self
.vpos_pt(vx1
, vy1
, vz1
)
767 x2_pt
, y2_pt
= self
.vpos_pt(vx2
, vy2
, vz2
)
768 dx_pt
= x2_pt
- x1_pt
769 dy_pt
= y2_pt
- y1_pt
770 norm
= math
.hypot(dx_pt
, dy_pt
)
771 return dx_pt
/norm
, dy_pt
/norm
773 def xvgridpath(self
, vx
):
774 return path
.path(path
.moveto_pt(*self
.vpos_pt(vx
, 0, 0)),
775 path
.lineto_pt(*self
.vpos_pt(vx
, 1, 0)),
776 path
.lineto_pt(*self
.vpos_pt(vx
, 1, 1)),
777 path
.lineto_pt(*self
.vpos_pt(vx
, 0, 1)),
780 def yvgridpath(self
, vy
):
781 return path
.path(path
.moveto_pt(*self
.vpos_pt(0, vy
, 0)),
782 path
.lineto_pt(*self
.vpos_pt(1, vy
, 0)),
783 path
.lineto_pt(*self
.vpos_pt(1, vy
, 1)),
784 path
.lineto_pt(*self
.vpos_pt(0, vy
, 1)),
787 def zvgridpath(self
, vz
):
788 return path
.path(path
.moveto_pt(*self
.vpos_pt(0, 0, vz
)),
789 path
.lineto_pt(*self
.vpos_pt(1, 0, vz
)),
790 path
.lineto_pt(*self
.vpos_pt(1, 1, vz
)),
791 path
.lineto_pt(*self
.vpos_pt(0, 1, vz
)),
794 def doaxispositioner(self
, axisname
):
795 if self
.did(self
.doaxispositioner
, axisname
):
799 self
.axes
["x"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, self
.xorder
, 0),
800 lambda vx
: self
.vtickdirection(vx
, self
.xorder
, 0, vx
, 1-self
.xorder
, 0),
802 elif axisname
== "x2":
803 self
.axes
["x2"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, 1-self
.xorder
, 0),
804 lambda vx
: self
.vtickdirection(vx
, 1-self
.xorder
, 0, vx
, self
.xorder
, 0),
806 elif axisname
== "x3":
807 self
.axes
["x3"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, self
.xorder
, 1),
808 lambda vx
: self
.vtickdirection(vx
, self
.xorder
, 1, vx
, 1-self
.xorder
, 1),
810 elif axisname
== "x4":
811 self
.axes
["x4"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, 1-self
.xorder
, 1),
812 lambda vx
: self
.vtickdirection(vx
, 1-self
.xorder
, 1, vx
, self
.xorder
, 1),
814 elif axisname
== "y":
815 self
.axes
["y"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(self
.yorder
, vy
, 0),
816 lambda vy
: self
.vtickdirection(self
.yorder
, vy
, 0, 1-self
.yorder
, vy
, 0),
818 elif axisname
== "y2":
819 self
.axes
["y2"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(1-self
.yorder
, vy
, 0),
820 lambda vy
: self
.vtickdirection(1-self
.yorder
, vy
, 0, self
.yorder
, vy
, 0),
822 elif axisname
== "y3":
823 self
.axes
["y3"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(self
.yorder
, vy
, 1),
824 lambda vy
: self
.vtickdirection(self
.yorder
, vy
, 1, 1-self
.yorder
, vy
, 1),
826 elif axisname
== "y4":
827 self
.axes
["y4"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(1-self
.yorder
, vy
, 1),
828 lambda vy
: self
.vtickdirection(1-self
.yorder
, vy
, 1, self
.yorder
, vy
, 1),
830 elif axisname
== "z":
831 self
.axes
["z"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(0, 0, vz
),
832 lambda vz
: self
.vtickdirection(0, 0, vz
, 1, 1, vz
),
834 elif axisname
== "z2":
835 self
.axes
["z2"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(1, 0, vz
),
836 lambda vz
: self
.vtickdirection(1, 0, vz
, 0, 1, vz
),
838 elif axisname
== "z3":
839 self
.axes
["z3"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(0, 1, vz
),
840 lambda vz
: self
.vtickdirection(0, 1, vz
, 1, 0, vz
),
842 elif axisname
== "z4":
843 self
.axes
["z4"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(0, 0, vz
),
844 lambda vz
: self
.vtickdirection(1, 1, vz
, 0, 0, vz
),
847 raise NotImplementedError("4 axis per dimension supported only")
850 if self
.did(self
.dolayout
):
852 for axisname
in self
.axes
.keys():
853 self
.doaxiscreate(axisname
)
855 def dobackground(self
):
856 if self
.did(self
.dobackground
):
860 if self
.did(self
.doaxes
):
864 for axis
in self
.axes
.values():
865 self
.insert(axis
.canvas
)
868 if self
.did(self
.dokey
):
872 if self
.key
is not None:
873 c
= self
.key
.paint(self
.plotitems
)
875 def parentchildalign(pmin
, pmax
, cmin
, cmax
, pos
, dist
, inside
):
876 ppos
= pmin
+0.5*(cmax
-cmin
)+dist
+pos
*(pmax
-pmin
-cmax
+cmin
-2*dist
)
877 cpos
= 0.5*(cmin
+cmax
)+(1-inside
)*(1-2*pos
)*(cmax
-cmin
+2*dist
)
880 x
= parentchildalign(self
.xpos_pt
, self
.xpos_pt
+self
.size_pt
,
881 bbox
.llx_pt
, bbox
.urx_pt
,
882 self
.key
.hpos
, unit
.topt(self
.key
.hdist
), self
.key
.hinside
)
883 y
= parentchildalign(self
.ypos_pt
, self
.ypos_pt
+self
.size_pt
,
884 bbox
.lly_pt
, bbox
.ury_pt
,
885 self
.key
.vpos
, unit
.topt(self
.key
.vdist
), self
.key
.vinside
)
886 self
.insert(c
, [trafo
.translate_pt(x
, y
)])