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 from __future__
import nested_scopes
27 import math
, re
, string
, warnings
28 from pyx
import canvas
, path
, trafo
, unit
29 from pyx
.graph
import style
30 from pyx
.graph
.axis
import axis
, positioner
33 goldenmean
= 0.5 * (math
.sqrt(5) + 1)
37 """style data storage class
39 Instances of this class are used to store data from the styles
40 and to pass point data to the styles by instances named privatedata
41 and sharedata. sharedata is shared between all the style(s) in use
42 by a data instance, while privatedata is private to each style and
43 used as a storage place instead of self to prevent side effects when
44 using a style several times."""
50 def __init__(self
, graph
, data
, styles
):
52 self
.title
= data
.title
56 # add styles to ensure all needs of the given styles
57 provided
= [] # already provided sharedata variables
58 addstyles
= [] # a list of style instances to be added in front
62 defaultprovider
= style
.getdefaultprovider(n
)
63 addstyles
.append(defaultprovider
)
64 provided
.extend(defaultprovider
.providesdata
)
65 provided
.extend(s
.providesdata
)
66 styles
= addstyles
+ styles
69 self
.sharedata
= styledata()
70 self
.privatedatalist
= [styledata() for s
in self
.styles
]
72 # perform setcolumns to all styles
73 self
.usedcolumnnames
= []
74 for privatedata
, s
in zip(self
.privatedatalist
, self
.styles
):
75 self
.usedcolumnnames
.extend(s
.columnnames(privatedata
, self
.sharedata
, graph
, self
.data
.columnnames
))
77 def selectstyles(self
, graph
, selectindex
, selecttotal
):
78 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
79 style
.selectstyle(privatedata
, self
.sharedata
, graph
, selectindex
, selecttotal
)
81 def adjustaxesstatic(self
, graph
):
82 for columnname
, data
in self
.data
.columns
.items():
83 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
84 style
.adjustaxis(privatedata
, self
.sharedata
, graph
, columnname
, data
)
86 def makedynamicdata(self
, graph
):
87 self
.dynamiccolumns
= self
.data
.dynamiccolumns(graph
)
89 def adjustaxesdynamic(self
, graph
):
90 for columnname
, data
in self
.dynamiccolumns
.items():
91 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
92 style
.adjustaxis(privatedata
, self
.sharedata
, graph
, columnname
, data
)
94 def draw(self
, graph
):
95 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
96 style
.initdrawpoints(privatedata
, self
.sharedata
, graph
)
99 for columnname
in self
.usedcolumnnames
:
101 useitems
.append((columnname
, self
.dynamiccolumns
[columnname
]))
103 useitems
.append((columnname
, self
.data
.columns
[columnname
]))
105 raise ValueError("cannot draw empty data")
106 for i
in xrange(len(useitems
[0][1])):
107 for columnname
, data
in useitems
:
108 point
[columnname
] = data
[i
]
109 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
110 style
.drawpoint(privatedata
, self
.sharedata
, graph
, point
)
111 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
112 style
.donedrawpoints(privatedata
, self
.sharedata
, graph
)
114 def key_pt(self
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
115 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
116 style
.key_pt(privatedata
, self
.sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
)
118 def __getattr__(self
, attr
):
119 # read only access to the styles privatedata
120 stylesdata
= [getattr(styledata
, attr
)
121 for styledata
in self
.privatedatalist
122 if hasattr(styledata
, attr
)]
123 if len(stylesdata
) > 1:
125 elif len(stylesdata
) == 1:
127 raise AttributeError("access to styledata attribute '%s' failed" % attr
)
130 class graph(canvas
.canvas
):
133 canvas
.canvas
.__init
__(self
)
140 def did(self
, method
, *args
, **kwargs
):
141 if not self
._calls
.has_key(method
):
142 self
._calls
[method
] = []
143 for callargs
in self
._calls
[method
]:
144 if callargs
== (args
, kwargs
):
146 self
._calls
[method
].append((args
, kwargs
))
151 return canvas
.canvas
.bbox(self
)
153 def registerPS(self
, registry
):
155 canvas
.canvas
.registerPS(self
, registry
)
157 def registerPDF(self
, registry
):
159 canvas
.canvas
.registerPDF(self
, registry
)
161 def processPS(self
, file, writer
, context
, registry
, bbox
):
163 canvas
.canvas
.processPS(self
, file, writer
, context
, registry
, bbox
)
165 def processPDF(self
, file, writer
, context
, registry
, bbox
):
167 canvas
.canvas
.processPDF(self
, file, writer
, context
, registry
, bbox
)
169 def plot(self
, data
, styles
=None, rangewarning
=1):
170 if self
.didranges
and rangewarning
:
171 warnings
.warn("axes ranges have already been analysed; no further adjustments will be performed")
173 raise RuntimeError("can't plot further data after dostyles() has been executed")
186 styles
= d
.defaultstyles
187 elif styles
!= d
.defaultstyles
:
188 raise RuntimeError("defaultstyles differ")
191 plotitems
.append(plotitem(self
, d
, styles
))
192 self
.plotitems
.extend(plotitems
)
194 for aplotitem
in plotitems
:
195 aplotitem
.makedynamicdata(self
)
202 if self
.did(self
.doranges
):
204 for plotitem
in self
.plotitems
:
205 plotitem
.adjustaxesstatic(self
)
206 for plotitem
in self
.plotitems
:
207 plotitem
.makedynamicdata(self
)
208 for plotitem
in self
.plotitems
:
209 plotitem
.adjustaxesdynamic(self
)
212 def doaxiscreate(self
, axisname
):
213 if self
.did(self
.doaxiscreate
, axisname
):
215 self
.doaxispositioner(axisname
)
216 self
.axes
[axisname
].create()
219 raise NotImplementedError
221 def dobackground(self
):
225 raise NotImplementedError
228 if self
.did(self
.dostyles
):
233 # count the usage of styles and perform selects
235 def stylesid(styles
):
236 return ":".join([str(id(style
)) for style
in styles
])
237 for plotitem
in self
.plotitems
:
239 styletotal
[stylesid(plotitem
.styles
)] += 1
241 styletotal
[stylesid(plotitem
.styles
)] = 1
243 for plotitem
in self
.plotitems
:
245 styleindex
[stylesid(plotitem
.styles
)] += 1
247 styleindex
[stylesid(plotitem
.styles
)] = 0
248 plotitem
.selectstyles(self
, styleindex
[stylesid(plotitem
.styles
)],
249 styletotal
[stylesid(plotitem
.styles
)])
253 def doplot(self
, plotitem
):
254 if self
.did(self
.doplot
, plotitem
):
260 for plotitem
in self
.plotitems
:
261 self
.doplot(plotitem
)
264 raise NotImplementedError
273 class graphxy(graph
):
275 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, ratio
=goldenmean
,
276 key
=None, backgroundattrs
=None, axesdist
=0.8*unit
.v_cm
,
277 xaxisat
=None, yaxisat
=None, **axes
):
282 self
.xpos_pt
= unit
.topt(self
.xpos
)
283 self
.ypos_pt
= unit
.topt(self
.ypos
)
284 self
.xaxisat
= xaxisat
285 self
.yaxisat
= yaxisat
287 self
.backgroundattrs
= backgroundattrs
288 self
.axesdist_pt
= unit
.topt(axesdist
)
294 raise ValueError("specify width and/or height")
296 self
.width
= ratio
* self
.height
298 self
.height
= (1.0/ratio
) * self
.width
299 self
.width_pt
= unit
.topt(self
.width
)
300 self
.height_pt
= unit
.topt(self
.height
)
302 for axisname
, aaxis
in axes
.items():
303 if aaxis
is not None:
304 if not isinstance(aaxis
, axis
.linkedaxis
):
305 self
.axes
[axisname
] = axis
.anchoredaxis(aaxis
, self
.texrunner
, axisname
)
307 self
.axes
[axisname
] = aaxis
308 for axisname
, axisat
in [("x", xaxisat
), ("y", yaxisat
)]:
309 okey
= axisname
+ "2"
310 if not axes
.has_key(axisname
):
311 if not axes
.has_key(okey
) or axes
[okey
] is None:
312 self
.axes
[axisname
] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
313 if not axes
.has_key(okey
):
314 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
316 self
.axes
[axisname
] = axis
.linkedaxis(self
.axes
[okey
], axisname
)
317 elif not axes
.has_key(okey
) and axisat
is None:
318 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
320 if self
.axes
.has_key("x"):
321 self
.xbasepath
= self
.axes
["x"].basepath
322 self
.xvbasepath
= self
.axes
["x"].vbasepath
323 self
.xgridpath
= self
.axes
["x"].gridpath
324 self
.xtickpoint_pt
= self
.axes
["x"].tickpoint_pt
325 self
.xtickpoint
= self
.axes
["x"].tickpoint
326 self
.xvtickpoint_pt
= self
.axes
["x"].vtickpoint_pt
327 self
.xvtickpoint
= self
.axes
["x"].tickpoint
328 self
.xtickdirection
= self
.axes
["x"].tickdirection
329 self
.xvtickdirection
= self
.axes
["x"].vtickdirection
331 if self
.axes
.has_key("y"):
332 self
.ybasepath
= self
.axes
["y"].basepath
333 self
.yvbasepath
= self
.axes
["y"].vbasepath
334 self
.ygridpath
= self
.axes
["y"].gridpath
335 self
.ytickpoint_pt
= self
.axes
["y"].tickpoint_pt
336 self
.ytickpoint
= self
.axes
["y"].tickpoint
337 self
.yvtickpoint_pt
= self
.axes
["y"].vtickpoint_pt
338 self
.yvtickpoint
= self
.axes
["y"].vtickpoint
339 self
.ytickdirection
= self
.axes
["y"].tickdirection
340 self
.yvtickdirection
= self
.axes
["y"].vtickdirection
342 self
.axesnames
= ([], [])
343 for axisname
, aaxis
in self
.axes
.items():
344 if axisname
[0] not in "xy" or (len(axisname
) != 1 and (not axisname
[1:].isdigit() or
345 axisname
[1:] == "1")):
346 raise ValueError("invalid axis name")
347 if axisname
[0] == "x":
348 self
.axesnames
[0].append(axisname
)
350 self
.axesnames
[1].append(axisname
)
351 aaxis
.setcreatecall(self
.doaxiscreate
, axisname
)
354 def pos_pt(self
, x
, y
, xaxis
=None, yaxis
=None):
356 xaxis
= self
.axes
["x"]
358 yaxis
= self
.axes
["y"]
359 return (self
.xpos_pt
+ xaxis
.convert(x
)*self
.width_pt
,
360 self
.ypos_pt
+ yaxis
.convert(y
)*self
.height_pt
)
362 def pos(self
, x
, y
, xaxis
=None, yaxis
=None):
364 xaxis
= self
.axes
["x"]
366 yaxis
= self
.axes
["y"]
367 return (self
.xpos
+ xaxis
.convert(x
)*self
.width
,
368 self
.ypos
+ yaxis
.convert(y
)*self
.height
)
370 def vpos_pt(self
, vx
, vy
):
371 return (self
.xpos_pt
+ vx
*self
.width_pt
,
372 self
.ypos_pt
+ vy
*self
.height_pt
)
374 def vpos(self
, vx
, vy
):
375 return (self
.xpos
+ vx
*self
.width
,
376 self
.ypos
+ vy
*self
.height
)
378 def vzindex(self
, vx
, vy
):
381 def vangle(self
, vx1
, vy1
, vx2
, vy2
, vx3
, vy3
):
384 def vgeodesic(self
, vx1
, vy1
, vx2
, vy2
):
385 """returns a geodesic path between two points in graph coordinates"""
386 return path
.line_pt(self
.xpos_pt
+ vx1
*self
.width_pt
,
387 self
.ypos_pt
+ vy1
*self
.height_pt
,
388 self
.xpos_pt
+ vx2
*self
.width_pt
,
389 self
.ypos_pt
+ vy2
*self
.height_pt
)
391 def vgeodesic_el(self
, vx1
, vy1
, vx2
, vy2
):
392 """returns a geodesic path element between two points in graph coordinates"""
393 return path
.lineto_pt(self
.xpos_pt
+ vx2
*self
.width_pt
,
394 self
.ypos_pt
+ vy2
*self
.height_pt
)
396 def vcap_pt(self
, coordinate
, length_pt
, vx
, vy
):
397 """returns an error cap path for a given coordinate, lengths and
398 point in graph coordinates"""
400 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
- 0.5*length_pt
,
401 self
.ypos_pt
+ vy
*self
.height_pt
,
402 self
.xpos_pt
+ vx
*self
.width_pt
+ 0.5*length_pt
,
403 self
.ypos_pt
+ vy
*self
.height_pt
)
404 elif coordinate
== 1:
405 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
,
406 self
.ypos_pt
+ vy
*self
.height_pt
- 0.5*length_pt
,
407 self
.xpos_pt
+ vx
*self
.width_pt
,
408 self
.ypos_pt
+ vy
*self
.height_pt
+ 0.5*length_pt
)
410 raise ValueError("direction invalid")
412 def xvgridpath(self
, vx
):
413 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
, self
.ypos_pt
,
414 self
.xpos_pt
+ vx
*self
.width_pt
, self
.ypos_pt
+ self
.height_pt
)
416 def yvgridpath(self
, vy
):
417 return path
.line_pt(self
.xpos_pt
, self
.ypos_pt
+ vy
*self
.height_pt
,
418 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ vy
*self
.height_pt
)
420 def axistrafo(self
, axis
, t
):
421 c
= canvas
.canvas([t
])
422 c
.insert(axis
.canvas
)
425 def axisatv(self
, axis
, v
):
426 if axis
.positioner
.fixtickdirection
[0]:
428 self
.axistrafo(axis
, trafo
.translate_pt(self
.xpos_pt
+ v
*self
.width_pt
- axis
.positioner
.x1_pt
, 0))
431 self
.axistrafo(axis
, trafo
.translate_pt(0, self
.ypos_pt
+ v
*self
.height_pt
- axis
.positioner
.y1_pt
))
433 def doaxispositioner(self
, axisname
):
434 if self
.did(self
.doaxispositioner
, axisname
):
438 self
.axes
["x"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
,
439 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
,
440 (0, 1), self
.xvgridpath
))
441 elif axisname
== "x2":
442 self
.axes
["x2"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
+ self
.height_pt
,
443 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ self
.height_pt
,
444 (0, -1), self
.xvgridpath
))
445 elif axisname
== "y":
446 self
.axes
["y"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
,
447 self
.xpos_pt
, self
.ypos_pt
+ self
.height_pt
,
448 (1, 0), self
.yvgridpath
))
449 elif axisname
== "y2":
450 self
.axes
["y2"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
,
451 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ self
.height_pt
,
452 (-1, 0), self
.yvgridpath
))
454 if axisname
[1:] == "3":
455 dependsonaxisname
= axisname
[0]
457 dependsonaxisname
= "%s%d" % (axisname
[0], int(axisname
[1:]) - 2)
458 self
.doaxiscreate(dependsonaxisname
)
459 sign
= 2*(int(axisname
[1:]) % 2) - 1
460 if axisname
[0] == "x":
461 y_pt
= self
.axes
[dependsonaxisname
].positioner
.y1_pt
- sign
* (self
.axes
[dependsonaxisname
].canvas
.extent_pt
+ self
.axesdist_pt
)
462 self
.axes
[axisname
].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, y_pt
,
463 self
.xpos_pt
+ self
.width_pt
, y_pt
,
464 (0, sign
), self
.xvgridpath
))
466 x_pt
= self
.axes
[dependsonaxisname
].positioner
.x1_pt
- sign
* (self
.axes
[dependsonaxisname
].canvas
.extent_pt
+ self
.axesdist_pt
)
467 self
.axes
[axisname
].setpositioner(positioner
.lineaxispos_pt(x_pt
, self
.ypos_pt
,
468 x_pt
, self
.ypos_pt
+ self
.height_pt
,
469 (sign
, 0), self
.yvgridpath
))
472 if self
.did(self
.dolayout
):
474 for axisname
in self
.axes
.keys():
475 self
.doaxiscreate(axisname
)
476 if self
.xaxisat
is not None:
477 self
.axisatv(self
.axes
["x"], self
.axes
["y"].convert(self
.xaxisat
))
478 if self
.yaxisat
is not None:
479 self
.axisatv(self
.axes
["y"], self
.axes
["x"].convert(self
.yaxisat
))
481 def dobackground(self
):
482 if self
.did(self
.dobackground
):
484 if self
.backgroundattrs
is not None:
485 self
.draw(path
.rect_pt(self
.xpos_pt
, self
.ypos_pt
, self
.width_pt
, self
.height_pt
),
486 self
.backgroundattrs
)
489 if self
.did(self
.doaxes
):
493 for axis
in self
.axes
.values():
494 self
.insert(axis
.canvas
)
497 if self
.did(self
.dokey
):
501 if self
.key
is not None:
502 c
= self
.key
.paint(self
.plotitems
)
504 def parentchildalign(pmin
, pmax
, cmin
, cmax
, pos
, dist
, inside
):
505 ppos
= pmin
+0.5*(cmax
-cmin
)+dist
+pos
*(pmax
-pmin
-cmax
+cmin
-2*dist
)
506 cpos
= 0.5*(cmin
+cmax
)+(1-inside
)*(1-2*pos
)*(cmax
-cmin
+2*dist
)
509 x
= parentchildalign(self
.xpos_pt
, self
.xpos_pt
+self
.width_pt
,
510 bbox
.llx_pt
, bbox
.urx_pt
,
511 self
.key
.hpos
, unit
.topt(self
.key
.hdist
), self
.key
.hinside
)
512 y
= parentchildalign(self
.ypos_pt
, self
.ypos_pt
+self
.height_pt
,
513 bbox
.lly_pt
, bbox
.ury_pt
,
514 self
.key
.vpos
, unit
.topt(self
.key
.vdist
), self
.key
.vinside
)
515 self
.insert(c
, [trafo
.translate_pt(x
, y
)])
518 class graphxyz(graphxy
):
522 def __init__(self
, distance
, phi
, theta
, anglefactor
=math
.pi
/180):
525 self
.distance
= distance
527 self
.a
= (-math
.sin(phi
), math
.cos(phi
), 0)
528 self
.b
= (-math
.cos(phi
)*math
.sin(theta
),
529 -math
.sin(phi
)*math
.sin(theta
),
531 self
.eye
= (distance
*math
.cos(phi
)*math
.cos(theta
),
532 distance
*math
.sin(phi
)*math
.cos(theta
),
533 distance
*math
.sin(theta
))
535 def point(self
, x
, y
, z
):
536 d0
= (self
.a
[0]*self
.b
[1]*(z
-self
.eye
[2])
537 + self
.a
[2]*self
.b
[0]*(y
-self
.eye
[1])
538 + self
.a
[1]*self
.b
[2]*(x
-self
.eye
[0])
539 - self
.a
[2]*self
.b
[1]*(x
-self
.eye
[0])
540 - self
.a
[0]*self
.b
[2]*(y
-self
.eye
[1])
541 - self
.a
[1]*self
.b
[0]*(z
-self
.eye
[2]))
542 da
= (self
.eye
[0]*self
.b
[1]*(z
-self
.eye
[2])
543 + self
.eye
[2]*self
.b
[0]*(y
-self
.eye
[1])
544 + self
.eye
[1]*self
.b
[2]*(x
-self
.eye
[0])
545 - self
.eye
[2]*self
.b
[1]*(x
-self
.eye
[0])
546 - self
.eye
[0]*self
.b
[2]*(y
-self
.eye
[1])
547 - self
.eye
[1]*self
.b
[0]*(z
-self
.eye
[2]))
548 db
= (self
.a
[0]*self
.eye
[1]*(z
-self
.eye
[2])
549 + self
.a
[2]*self
.eye
[0]*(y
-self
.eye
[1])
550 + self
.a
[1]*self
.eye
[2]*(x
-self
.eye
[0])
551 - self
.a
[2]*self
.eye
[1]*(x
-self
.eye
[0])
552 - self
.a
[0]*self
.eye
[2]*(y
-self
.eye
[1])
553 - self
.a
[1]*self
.eye
[0]*(z
-self
.eye
[2]))
556 def zindex(self
, x
, y
, z
):
557 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
559 def angle(self
, x1
, y1
, z1
, x2
, y2
, z2
, x3
, y3
, z3
):
560 sx
= (x1
-self
.eye
[0])
561 sy
= (y1
-self
.eye
[1])
562 sz
= (z1
-self
.eye
[2])
563 nx
= (y2
-y1
)*(z3
-z1
)-(z2
-z1
)*(y3
-y1
)
564 ny
= (z2
-z1
)*(x3
-x1
)-(x2
-x1
)*(z3
-z1
)
565 nz
= (x2
-x1
)*(y3
-y1
)-(y2
-y1
)*(x3
-x1
)
566 return (sx
*nx
+sy
*ny
+sz
*nz
)/math
.sqrt(nx
*nx
+ny
*ny
+nz
*nz
)/math
.sqrt(sx
*sx
+sy
*sy
+sz
*sz
)
571 def __init__(self
, phi
, theta
, anglefactor
=math
.pi
/180):
575 self
.a
= (-math
.sin(phi
), math
.cos(phi
), 0)
576 self
.b
= (-math
.cos(phi
)*math
.sin(theta
),
577 -math
.sin(phi
)*math
.sin(theta
),
579 self
.c
= (-math
.cos(phi
)*math
.cos(theta
),
580 -math
.sin(phi
)*math
.cos(theta
),
583 def point(self
, x
, y
, z
):
584 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
586 def zindex(self
, x
, y
, z
):
587 return self
.c
[0]*x
+self
.c
[1]*y
+self
.c
[2]*z
589 def angle(self
, x1
, y1
, z1
, x2
, y2
, z2
, x3
, y3
, z3
):
590 nx
= (y2
-y1
)*(z3
-z1
)-(z2
-z1
)*(y3
-y1
)
591 ny
= (z2
-z1
)*(x3
-x1
)-(x2
-x1
)*(z3
-z1
)
592 nz
= (x2
-x1
)*(y3
-y1
)-(y2
-y1
)*(x3
-x1
)
593 return (self
.c
[0]*nx
+self
.c
[1]*ny
+self
.c
[2]*nz
)/math
.sqrt(nx
*nx
+ny
*ny
+nz
*nz
)
596 def __init__(self
, xpos
=0, ypos
=0, size
=None,
597 xscale
=1, yscale
=1, zscale
=1/goldenmean
,
598 projector
=central(10, -30, 30), key
=None,
605 self
.xpos_pt
= unit
.topt(xpos
)
606 self
.ypos_pt
= unit
.topt(ypos
)
607 self
.size_pt
= unit
.topt(size
)
611 self
.projector
= projector
614 self
.xorder
= projector
.zindex(0, -1, 0) > projector
.zindex(0, 1, 0) and 1 or 0
615 self
.yorder
= projector
.zindex(-1, 0, 0) > projector
.zindex(1, 0, 0) and 1 or 0
616 self
.zindexscale
= math
.sqrt(xscale
*xscale
+yscale
*yscale
+zscale
*zscale
)
618 for axisname
, aaxis
in axes
.items():
619 if aaxis
is not None:
620 if not isinstance(aaxis
, axis
.linkedaxis
):
621 self
.axes
[axisname
] = axis
.anchoredaxis(aaxis
, self
.texrunner
, axisname
)
623 self
.axes
[axisname
] = aaxis
624 for axisname
in ["x", "y"]:
625 okey
= axisname
+ "2"
626 if not axes
.has_key(axisname
):
627 if not axes
.has_key(okey
) or axes
[okey
] is None:
628 self
.axes
[axisname
] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
629 if not axes
.has_key(okey
):
630 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
632 self
.axes
[axisname
] = axis
.linkedaxis(self
.axes
[okey
], axisname
)
633 if not axes
.has_key("z"):
634 self
.axes
["z"] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
636 if self
.axes
.has_key("x"):
637 self
.xbasepath
= self
.axes
["x"].basepath
638 self
.xvbasepath
= self
.axes
["x"].vbasepath
639 self
.xgridpath
= self
.axes
["x"].gridpath
640 self
.xtickpoint_pt
= self
.axes
["x"].tickpoint_pt
641 self
.xtickpoint
= self
.axes
["x"].tickpoint
642 self
.xvtickpoint_pt
= self
.axes
["x"].vtickpoint_pt
643 self
.xvtickpoint
= self
.axes
["x"].tickpoint
644 self
.xtickdirection
= self
.axes
["x"].tickdirection
645 self
.xvtickdirection
= self
.axes
["x"].vtickdirection
647 if self
.axes
.has_key("y"):
648 self
.ybasepath
= self
.axes
["y"].basepath
649 self
.yvbasepath
= self
.axes
["y"].vbasepath
650 self
.ygridpath
= self
.axes
["y"].gridpath
651 self
.ytickpoint_pt
= self
.axes
["y"].tickpoint_pt
652 self
.ytickpoint
= self
.axes
["y"].tickpoint
653 self
.yvtickpoint_pt
= self
.axes
["y"].vtickpoint_pt
654 self
.yvtickpoint
= self
.axes
["y"].vtickpoint
655 self
.ytickdirection
= self
.axes
["y"].tickdirection
656 self
.yvtickdirection
= self
.axes
["y"].vtickdirection
658 if self
.axes
.has_key("z"):
659 self
.zbasepath
= self
.axes
["z"].basepath
660 self
.zvbasepath
= self
.axes
["z"].vbasepath
661 self
.zgridpath
= self
.axes
["z"].gridpath
662 self
.ztickpoint_pt
= self
.axes
["z"].tickpoint_pt
663 self
.ztickpoint
= self
.axes
["z"].tickpoint
664 self
.zvtickpoint_pt
= self
.axes
["z"].vtickpoint
665 self
.zvtickpoint
= self
.axes
["z"].vtickpoint
666 self
.ztickdirection
= self
.axes
["z"].tickdirection
667 self
.zvtickdirection
= self
.axes
["z"].vtickdirection
669 self
.axesnames
= ([], [], [])
670 for axisname
, aaxis
in self
.axes
.items():
671 if axisname
[0] not in "xyz" or (len(axisname
) != 1 and (not axisname
[1:].isdigit() or
672 axisname
[1:] == "1")):
673 raise ValueError("invalid axis name")
674 if axisname
[0] == "x":
675 self
.axesnames
[0].append(axisname
)
676 elif axisname
[0] == "y":
677 self
.axesnames
[1].append(axisname
)
679 self
.axesnames
[2].append(axisname
)
680 aaxis
.setcreatecall(self
.doaxiscreate
, axisname
)
682 def pos_pt(self
, x
, y
, z
, xaxis
=None, yaxis
=None, zaxis
=None):
684 xaxis
= self
.axes
["x"]
686 yaxis
= self
.axes
["y"]
688 zaxis
= self
.axes
["z"]
689 return self
.vpos_pt(xaxis
.convert(x
), yaxis
.convert(y
), zaxis
.convert(y
))
691 def pos(self
, x
, y
, z
, xaxis
=None, yaxis
=None, zaxis
=None):
693 xaxis
= self
.axes
["x"]
695 yaxis
= self
.axes
["y"]
697 zaxis
= self
.axes
["z"]
698 return self
.vpos(xaxis
.convert(x
), yaxis
.convert(y
), zaxis
.convert(y
))
700 def vpos_pt(self
, vx
, vy
, vz
):
701 x
, y
= self
.projector
.point(2*self
.xscale
*(vx
- 0.5),
702 2*self
.yscale
*(vy
- 0.5),
703 2*self
.zscale
*(vz
- 0.5))
704 return self
.xpos_pt
+x
*self
.size_pt
, self
.ypos_pt
+y
*self
.size_pt
706 def vpos(self
, vx
, vy
, vz
):
707 x
, y
= self
.projector
.point(2*self
.xscale
*(vx
- 0.5),
708 2*self
.yscale
*(vy
- 0.5),
709 2*self
.zscale
*(vz
- 0.5))
710 return self
.xpos
+x
*self
.size
, self
.ypos
+y
*self
.size
712 def vzindex(self
, vx
, vy
, vz
):
713 return self
.projector
.zindex(2*self
.xscale
*(vx
- 0.5),
714 2*self
.yscale
*(vy
- 0.5),
715 2*self
.zscale
*(vz
- 0.5))/self
.zindexscale
717 def vangle(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
, vx3
, vy3
, vz3
):
718 return self
.projector
.angle(2*self
.xscale
*(vx1
- 0.5),
719 2*self
.yscale
*(vy1
- 0.5),
720 2*self
.zscale
*(vz1
- 0.5),
721 2*self
.xscale
*(vx2
- 0.5),
722 2*self
.yscale
*(vy2
- 0.5),
723 2*self
.zscale
*(vz2
- 0.5),
724 2*self
.xscale
*(vx3
- 0.5),
725 2*self
.yscale
*(vy3
- 0.5),
726 2*self
.zscale
*(vz3
- 0.5))
728 def vgeodesic(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
729 """returns a geodesic path between two points in graph coordinates"""
730 return path
.line_pt(*(self
.vpos_pt(vx1
, vy1
, vz1
) + self
.vpos_pt(vx2
, vy2
, vz2
)))
732 def vgeodesic_el(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
733 """returns a geodesic path element between two points in graph coordinates"""
734 return path
.lineto_pt(*(self
.vpos_pt(vx1
, vy1
, vz1
) + self
.vpos_pt(vx2
, vy2
, vz2
)))
736 def vcap_pt(self
, coordinate
, length_pt
, vx
, vy
, vz
):
737 """returns an error cap path for a given coordinate, lengths and
738 point in graph coordinates"""
740 return self
.vgeodesic(vx
-0.5*length_pt
/self
.size_pt
, vy
, vz
, vx
+0.5*length_pt
/self
.size_pt
, vy
, vz
)
741 elif coordinate
== 1:
742 return self
.vgeodesic(vx
, vy
-0.5*length_pt
/self
.size_pt
, vz
, vx
, vy
+0.5*length_pt
/self
.size_pt
, vz
)
743 elif coordinate
== 2:
744 return self
.vgeodesic(vx
, vy
, vz
-0.5*length_pt
/self
.size_pt
, vx
, vy
, vz
+0.5*length_pt
/self
.size_pt
)
746 raise ValueError("direction invalid")
748 def xvtickdirection(self
, vx
):
750 x1_pt
, y1_pt
= self
.vpos_pt(vx
, 1, 0)
751 x2_pt
, y2_pt
= self
.vpos_pt(vx
, 0, 0)
753 x1_pt
, y1_pt
= self
.vpos_pt(vx
, 0, 0)
754 x2_pt
, y2_pt
= self
.vpos_pt(vx
, 1, 0)
755 dx_pt
= x2_pt
- x1_pt
756 dy_pt
= y2_pt
- y1_pt
757 norm
= math
.hypot(dx_pt
, dy_pt
)
758 return dx_pt
/norm
, dy_pt
/norm
760 def yvtickdirection(self
, vy
):
762 x1_pt
, y1_pt
= self
.vpos_pt(1, vy
, 0)
763 x2_pt
, y2_pt
= self
.vpos_pt(0, vy
, 0)
765 x1_pt
, y1_pt
= self
.vpos_pt(0, vy
, 0)
766 x2_pt
, y2_pt
= self
.vpos_pt(1, vy
, 0)
767 dx_pt
= x2_pt
- x1_pt
768 dy_pt
= y2_pt
- y1_pt
769 norm
= math
.hypot(dx_pt
, dy_pt
)
770 return dx_pt
/norm
, dy_pt
/norm
772 def vtickdirection(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
773 x1_pt
, y1_pt
= self
.vpos_pt(vx1
, vy1
, vz1
)
774 x2_pt
, y2_pt
= self
.vpos_pt(vx2
, vy2
, vz2
)
775 dx_pt
= x2_pt
- x1_pt
776 dy_pt
= y2_pt
- y1_pt
777 norm
= math
.hypot(dx_pt
, dy_pt
)
778 return dx_pt
/norm
, dy_pt
/norm
780 def xvgridpath(self
, vx
):
781 return path
.path(path
.moveto_pt(*self
.vpos_pt(vx
, 0, 0)),
782 path
.lineto_pt(*self
.vpos_pt(vx
, 1, 0)),
783 path
.lineto_pt(*self
.vpos_pt(vx
, 1, 1)),
784 path
.lineto_pt(*self
.vpos_pt(vx
, 0, 1)),
787 def yvgridpath(self
, vy
):
788 return path
.path(path
.moveto_pt(*self
.vpos_pt(0, vy
, 0)),
789 path
.lineto_pt(*self
.vpos_pt(1, vy
, 0)),
790 path
.lineto_pt(*self
.vpos_pt(1, vy
, 1)),
791 path
.lineto_pt(*self
.vpos_pt(0, vy
, 1)),
794 def zvgridpath(self
, vz
):
795 return path
.path(path
.moveto_pt(*self
.vpos_pt(0, 0, vz
)),
796 path
.lineto_pt(*self
.vpos_pt(1, 0, vz
)),
797 path
.lineto_pt(*self
.vpos_pt(1, 1, vz
)),
798 path
.lineto_pt(*self
.vpos_pt(0, 1, vz
)),
801 def doaxispositioner(self
, axisname
):
802 if self
.did(self
.doaxispositioner
, axisname
):
806 self
.axes
["x"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, self
.xorder
, 0),
807 lambda vx
: self
.vtickdirection(vx
, self
.xorder
, 0, vx
, 1-self
.xorder
, 0),
809 elif axisname
== "x2":
810 self
.axes
["x2"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, 1-self
.xorder
, 0),
811 lambda vx
: self
.vtickdirection(vx
, 1-self
.xorder
, 0, vx
, self
.xorder
, 0),
813 elif axisname
== "x3":
814 self
.axes
["x3"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, self
.xorder
, 1),
815 lambda vx
: self
.vtickdirection(vx
, self
.xorder
, 1, vx
, 1-self
.xorder
, 1),
817 elif axisname
== "x4":
818 self
.axes
["x4"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, 1-self
.xorder
, 1),
819 lambda vx
: self
.vtickdirection(vx
, 1-self
.xorder
, 1, vx
, self
.xorder
, 1),
821 elif axisname
== "y":
822 self
.axes
["y"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(self
.yorder
, vy
, 0),
823 lambda vy
: self
.vtickdirection(self
.yorder
, vy
, 0, 1-self
.yorder
, vy
, 0),
825 elif axisname
== "y2":
826 self
.axes
["y2"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(1-self
.yorder
, vy
, 0),
827 lambda vy
: self
.vtickdirection(1-self
.yorder
, vy
, 0, self
.yorder
, vy
, 0),
829 elif axisname
== "y3":
830 self
.axes
["y3"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(self
.yorder
, vy
, 1),
831 lambda vy
: self
.vtickdirection(self
.yorder
, vy
, 1, 1-self
.yorder
, vy
, 1),
833 elif axisname
== "y4":
834 self
.axes
["y4"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(1-self
.yorder
, vy
, 1),
835 lambda vy
: self
.vtickdirection(1-self
.yorder
, vy
, 1, self
.yorder
, vy
, 1),
837 elif axisname
== "z":
838 self
.axes
["z"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(0, 0, vz
),
839 lambda vz
: self
.vtickdirection(0, 0, vz
, 1, 1, vz
),
841 elif axisname
== "z2":
842 self
.axes
["z2"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(1, 0, vz
),
843 lambda vz
: self
.vtickdirection(1, 0, vz
, 0, 1, vz
),
845 elif axisname
== "z3":
846 self
.axes
["z3"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(0, 1, vz
),
847 lambda vz
: self
.vtickdirection(0, 1, vz
, 1, 0, vz
),
849 elif axisname
== "z4":
850 self
.axes
["z4"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(0, 0, vz
),
851 lambda vz
: self
.vtickdirection(1, 1, vz
, 0, 0, vz
),
854 raise NotImplementedError("4 axis per dimension supported only")
857 if self
.did(self
.dolayout
):
859 for axisname
in self
.axes
.keys():
860 self
.doaxiscreate(axisname
)
862 def dobackground(self
):
863 if self
.did(self
.dobackground
):
867 if self
.did(self
.doaxes
):
871 for axis
in self
.axes
.values():
872 self
.insert(axis
.canvas
)
875 if self
.did(self
.dokey
):
879 if self
.key
is not None:
880 c
= self
.key
.paint(self
.plotitems
)
882 def parentchildalign(pmin
, pmax
, cmin
, cmax
, pos
, dist
, inside
):
883 ppos
= pmin
+0.5*(cmax
-cmin
)+dist
+pos
*(pmax
-pmin
-cmax
+cmin
-2*dist
)
884 cpos
= 0.5*(cmin
+cmax
)+(1-inside
)*(1-2*pos
)*(cmax
-cmin
+2*dist
)
887 x
= parentchildalign(self
.xpos_pt
, self
.xpos_pt
+self
.size_pt
,
888 bbox
.llx_pt
, bbox
.urx_pt
,
889 self
.key
.hpos
, unit
.topt(self
.key
.hdist
), self
.key
.hinside
)
890 y
= parentchildalign(self
.ypos_pt
, self
.ypos_pt
+self
.size_pt
,
891 bbox
.lly_pt
, bbox
.ury_pt
,
892 self
.key
.vpos
, unit
.topt(self
.key
.vdist
), self
.key
.vinside
)
893 self
.insert(c
, [trafo
.translate_pt(x
, y
)])