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
54 # add styles to ensure all needs of the given styles
55 provided
= [] # already provided sharedata variables
56 addstyles
= [] # a list of style instances to be added in front
60 defaultprovider
= style
.getdefaultprovider(n
)
61 addstyles
.append(defaultprovider
)
62 provided
.extend(defaultprovider
.providesdata
)
63 provided
.extend(s
.providesdata
)
64 styles
= addstyles
+ styles
67 self
.sharedata
= styledata()
68 self
.privatedatalist
= [styledata() for s
in self
.styles
]
70 # perform setcolumns to all styles
71 self
.usedcolumnnames
= []
72 for privatedata
, s
in zip(self
.privatedatalist
, self
.styles
):
73 self
.usedcolumnnames
.extend(s
.columnnames(privatedata
, self
.sharedata
, graph
, self
.data
.columnnames
))
75 def selectstyles(self
, graph
, selectindex
, selecttotal
):
76 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
77 style
.selectstyle(privatedata
, self
.sharedata
, graph
, selectindex
, selecttotal
)
79 def adjustaxesstatic(self
, graph
):
80 for columnname
, data
in self
.data
.columns
.items():
81 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
82 style
.adjustaxis(privatedata
, self
.sharedata
, graph
, columnname
, data
)
84 def makedynamicdata(self
, graph
):
85 self
.dynamiccolumns
= self
.data
.dynamiccolumns(graph
)
87 def adjustaxesdynamic(self
, graph
):
88 for columnname
, data
in self
.dynamiccolumns
.items():
89 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
90 style
.adjustaxis(privatedata
, self
.sharedata
, graph
, columnname
, data
)
92 def draw(self
, graph
):
93 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
94 style
.initdrawpoints(privatedata
, self
.sharedata
, graph
)
97 for columnname
in self
.usedcolumnnames
:
99 useitems
.append((columnname
, self
.dynamiccolumns
[columnname
]))
101 useitems
.append((columnname
, self
.data
.columns
[columnname
]))
103 raise ValueError("cannot draw empty data")
104 for i
in xrange(len(useitems
[0][1])):
105 for columnname
, data
in useitems
:
106 point
[columnname
] = data
[i
]
107 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
108 style
.drawpoint(privatedata
, self
.sharedata
, graph
, point
)
109 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
110 style
.donedrawpoints(privatedata
, self
.sharedata
, graph
)
112 def key_pt(self
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
113 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
114 style
.key_pt(privatedata
, self
.sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
)
116 def __getattr__(self
, attr
):
117 # read only access to the styles privatedata
118 stylesdata
= [getattr(styledata
, attr
)
119 for styledata
in self
.privatedatalist
120 if hasattr(styledata
, attr
)]
121 if len(stylesdata
) > 1:
123 elif len(stylesdata
) == 1:
125 raise AttributeError("access to styledata attribute '%s' failed" % attr
)
128 class graph(canvas
.canvas
):
131 canvas
.canvas
.__init
__(self
)
138 def did(self
, method
, *args
, **kwargs
):
139 if not self
._calls
.has_key(method
):
140 self
._calls
[method
] = []
141 for callargs
in self
._calls
[method
]:
142 if callargs
== (args
, kwargs
):
144 self
._calls
[method
].append((args
, kwargs
))
149 return canvas
.canvas
.bbox(self
)
151 def registerPS(self
, registry
):
153 canvas
.canvas
.registerPS(self
, registry
)
155 def registerPDF(self
, registry
):
157 canvas
.canvas
.registerPDF(self
, registry
)
159 def processPS(self
, file, writer
, context
, registry
, bbox
):
161 canvas
.canvas
.processPS(self
, file, writer
, context
, registry
, bbox
)
163 def processPDF(self
, file, writer
, context
, registry
, bbox
):
165 canvas
.canvas
.processPDF(self
, file, writer
, context
, registry
, bbox
)
167 def plot(self
, data
, styles
=None, rangewarning
=1):
168 if self
.didranges
and rangewarning
:
169 warnings
.warn("axes ranges have already been analysed; no further adjustments will be performed")
171 raise RuntimeError("can't plot further data after dostyles() has been executed")
184 styles
= d
.defaultstyles
185 elif styles
!= d
.defaultstyles
:
186 raise RuntimeError("defaultstyles differ")
189 plotitems
.append(plotitem(self
, d
, styles
))
190 self
.plotitems
.extend(plotitems
)
192 for aplotitem
in plotitems
:
193 aplotitem
.makedynamicdata(self
)
200 if self
.did(self
.doranges
):
202 for plotitem
in self
.plotitems
:
203 plotitem
.adjustaxesstatic(self
)
204 for plotitem
in self
.plotitems
:
205 plotitem
.makedynamicdata(self
)
206 for plotitem
in self
.plotitems
:
207 plotitem
.adjustaxesdynamic(self
)
210 def doaxiscreate(self
, axisname
):
211 if self
.did(self
.doaxiscreate
, axisname
):
213 self
.doaxispositioner(axisname
)
214 self
.axes
[axisname
].create()
217 raise NotImplementedError
219 def dobackground(self
):
223 raise NotImplementedError
226 if self
.did(self
.dostyles
):
231 # count the usage of styles and perform selects
233 def stylesid(styles
):
234 return ":".join([str(id(style
)) for style
in styles
])
235 for plotitem
in self
.plotitems
:
237 styletotal
[stylesid(plotitem
.styles
)] += 1
239 styletotal
[stylesid(plotitem
.styles
)] = 1
241 for plotitem
in self
.plotitems
:
243 styleindex
[stylesid(plotitem
.styles
)] += 1
245 styleindex
[stylesid(plotitem
.styles
)] = 0
246 plotitem
.selectstyles(self
, styleindex
[stylesid(plotitem
.styles
)],
247 styletotal
[stylesid(plotitem
.styles
)])
251 def doplot(self
, plotitem
):
252 if self
.did(self
.doplot
, plotitem
):
258 for plotitem
in self
.plotitems
:
259 self
.doplot(plotitem
)
262 raise NotImplementedError
271 class graphxy(graph
):
273 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, ratio
=goldenmean
,
274 key
=None, backgroundattrs
=None, axesdist
=0.8*unit
.v_cm
,
275 xaxisat
=None, yaxisat
=None, **axes
):
280 self
.xpos_pt
= unit
.topt(self
.xpos
)
281 self
.ypos_pt
= unit
.topt(self
.ypos
)
282 self
.xaxisat
= xaxisat
283 self
.yaxisat
= yaxisat
285 self
.backgroundattrs
= backgroundattrs
286 self
.axesdist_pt
= unit
.topt(axesdist
)
292 raise ValueError("specify width and/or height")
294 self
.width
= ratio
* self
.height
296 self
.height
= (1.0/ratio
) * self
.width
297 self
.width_pt
= unit
.topt(self
.width
)
298 self
.height_pt
= unit
.topt(self
.height
)
300 for axisname
, aaxis
in axes
.items():
301 if aaxis
is not None:
302 if not isinstance(aaxis
, axis
.linkedaxis
):
303 self
.axes
[axisname
] = axis
.anchoredaxis(aaxis
, self
.texrunner
, axisname
)
305 self
.axes
[axisname
] = aaxis
306 for axisname
, axisat
in [("x", xaxisat
), ("y", yaxisat
)]:
307 okey
= axisname
+ "2"
308 if not axes
.has_key(axisname
):
309 if not axes
.has_key(okey
):
310 self
.axes
[axisname
] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
311 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
313 self
.axes
[axisname
] = axis
.linkedaxis(self
.axes
[okey
], axisname
)
314 elif not axes
.has_key(okey
) and axisat
is None:
315 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
317 if self
.axes
.has_key("x"):
318 self
.xbasepath
= self
.axes
["x"].basepath
319 self
.xvbasepath
= self
.axes
["x"].vbasepath
320 self
.xgridpath
= self
.axes
["x"].gridpath
321 self
.xtickpoint_pt
= self
.axes
["x"].tickpoint_pt
322 self
.xtickpoint
= self
.axes
["x"].tickpoint
323 self
.xvtickpoint_pt
= self
.axes
["x"].vtickpoint_pt
324 self
.xvtickpoint
= self
.axes
["x"].tickpoint
325 self
.xtickdirection
= self
.axes
["x"].tickdirection
326 self
.xvtickdirection
= self
.axes
["x"].vtickdirection
328 if self
.axes
.has_key("y"):
329 self
.ybasepath
= self
.axes
["y"].basepath
330 self
.yvbasepath
= self
.axes
["y"].vbasepath
331 self
.ygridpath
= self
.axes
["y"].gridpath
332 self
.ytickpoint_pt
= self
.axes
["y"].tickpoint_pt
333 self
.ytickpoint
= self
.axes
["y"].tickpoint
334 self
.yvtickpoint_pt
= self
.axes
["y"].vtickpoint_pt
335 self
.yvtickpoint
= self
.axes
["y"].vtickpoint
336 self
.ytickdirection
= self
.axes
["y"].tickdirection
337 self
.yvtickdirection
= self
.axes
["y"].vtickdirection
339 self
.axesnames
= ([], [])
340 for axisname
, aaxis
in self
.axes
.items():
341 if axisname
[0] not in "xy" or (len(axisname
) != 1 and (not axisname
[1:].isdigit() or
342 axisname
[1:] == "1")):
343 raise ValueError("invalid axis name")
344 if axisname
[0] == "x":
345 self
.axesnames
[0].append(axisname
)
347 self
.axesnames
[1].append(axisname
)
348 aaxis
.setcreatecall(self
.doaxiscreate
, axisname
)
351 def pos_pt(self
, x
, y
, xaxis
=None, yaxis
=None):
353 xaxis
= self
.axes
["x"]
355 yaxis
= self
.axes
["y"]
356 return (self
.xpos_pt
+ xaxis
.convert(x
)*self
.width_pt
,
357 self
.ypos_pt
+ yaxis
.convert(y
)*self
.height_pt
)
359 def pos(self
, x
, y
, xaxis
=None, yaxis
=None):
361 xaxis
= self
.axes
["x"]
363 yaxis
= self
.axes
["y"]
364 return (self
.xpos
+ xaxis
.convert(x
)*self
.width
,
365 self
.ypos
+ yaxis
.convert(y
)*self
.height
)
367 def vpos_pt(self
, vx
, vy
):
368 return (self
.xpos_pt
+ vx
*self
.width_pt
,
369 self
.ypos_pt
+ vy
*self
.height_pt
)
371 def vpos(self
, vx
, vy
):
372 return (self
.xpos
+ vx
*self
.width
,
373 self
.ypos
+ vy
*self
.height
)
375 def vzindex(self
, vx
, vy
):
378 def vangle(self
, vx1
, vy1
, vx2
, vy2
, vx3
, vy3
):
381 def vgeodesic(self
, vx1
, vy1
, vx2
, vy2
):
382 """returns a geodesic path between two points in graph coordinates"""
383 return path
.line_pt(self
.xpos_pt
+ vx1
*self
.width_pt
,
384 self
.ypos_pt
+ vy1
*self
.height_pt
,
385 self
.xpos_pt
+ vx2
*self
.width_pt
,
386 self
.ypos_pt
+ vy2
*self
.height_pt
)
388 def vgeodesic_el(self
, vx1
, vy1
, vx2
, vy2
):
389 """returns a geodesic path element between two points in graph coordinates"""
390 return path
.lineto_pt(self
.xpos_pt
+ vx2
*self
.width_pt
,
391 self
.ypos_pt
+ vy2
*self
.height_pt
)
393 def vcap_pt(self
, coordinate
, length_pt
, vx
, vy
):
394 """returns an error cap path for a given coordinate, lengths and
395 point in graph coordinates"""
397 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
- 0.5*length_pt
,
398 self
.ypos_pt
+ vy
*self
.height_pt
,
399 self
.xpos_pt
+ vx
*self
.width_pt
+ 0.5*length_pt
,
400 self
.ypos_pt
+ vy
*self
.height_pt
)
401 elif coordinate
== 1:
402 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
,
403 self
.ypos_pt
+ vy
*self
.height_pt
- 0.5*length_pt
,
404 self
.xpos_pt
+ vx
*self
.width_pt
,
405 self
.ypos_pt
+ vy
*self
.height_pt
+ 0.5*length_pt
)
407 raise ValueError("direction invalid")
409 def xvgridpath(self
, vx
):
410 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
, self
.ypos_pt
,
411 self
.xpos_pt
+ vx
*self
.width_pt
, self
.ypos_pt
+ self
.height_pt
)
413 def yvgridpath(self
, vy
):
414 return path
.line_pt(self
.xpos_pt
, self
.ypos_pt
+ vy
*self
.height_pt
,
415 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ vy
*self
.height_pt
)
417 def axistrafo(self
, axis
, t
):
418 c
= canvas
.canvas([t
])
419 c
.insert(axis
.canvas
)
422 def axisatv(self
, axis
, v
):
423 if axis
.positioner
.fixtickdirection
[0]:
425 self
.axistrafo(axis
, trafo
.translate_pt(self
.xpos_pt
+ v
*self
.width_pt
- axis
.positioner
.x1_pt
, 0))
428 self
.axistrafo(axis
, trafo
.translate_pt(0, self
.ypos_pt
+ v
*self
.height_pt
- axis
.positioner
.y1_pt
))
430 def doaxispositioner(self
, axisname
):
431 if self
.did(self
.doaxispositioner
, axisname
):
435 self
.axes
["x"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
,
436 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
,
437 (0, 1), self
.xvgridpath
))
438 elif axisname
== "x2":
439 self
.axes
["x2"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
+ self
.height_pt
,
440 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ self
.height_pt
,
441 (0, -1), self
.xvgridpath
))
442 elif axisname
== "y":
443 self
.axes
["y"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
,
444 self
.xpos_pt
, self
.ypos_pt
+ self
.height_pt
,
445 (1, 0), self
.yvgridpath
))
446 elif axisname
== "y2":
447 self
.axes
["y2"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
,
448 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ self
.height_pt
,
449 (-1, 0), self
.yvgridpath
))
451 if axisname
[1:] == "3":
452 dependsonaxisname
= axisname
[0]
454 dependsonaxisname
= "%s%d" % (axisname
[0], int(axisname
[1:]) - 2)
455 self
.doaxiscreate(dependsonaxisname
)
456 sign
= 2*(int(axisname
[1:]) % 2) - 1
457 if axisname
[0] == "x":
458 y_pt
= self
.axes
[dependsonaxisname
].positioner
.y1_pt
- sign
* (self
.axes
[dependsonaxisname
].canvas
.extent_pt
+ self
.axesdist_pt
)
459 self
.axes
[axisname
].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, y_pt
,
460 self
.xpos_pt
+ self
.width_pt
, y_pt
,
461 (0, sign
), self
.xvgridpath
))
463 x_pt
= self
.axes
[dependsonaxisname
].positioner
.x1_pt
- sign
* (self
.axes
[dependsonaxisname
].canvas
.extent_pt
+ self
.axesdist_pt
)
464 self
.axes
[axisname
].setpositioner(positioner
.lineaxispos_pt(x_pt
, self
.ypos_pt
,
465 x_pt
, self
.ypos_pt
+ self
.height_pt
,
466 (sign
, 0), self
.yvgridpath
))
469 if self
.did(self
.dolayout
):
471 for axisname
in self
.axes
.keys():
472 self
.doaxiscreate(axisname
)
473 if self
.xaxisat
is not None:
474 self
.axisatv(self
.axes
["x"], self
.axes
["y"].convert(self
.xaxisat
))
475 if self
.yaxisat
is not None:
476 self
.axisatv(self
.axes
["y"], self
.axes
["x"].convert(self
.yaxisat
))
478 def dobackground(self
):
479 if self
.did(self
.dobackground
):
481 if self
.backgroundattrs
is not None:
482 self
.draw(path
.rect_pt(self
.xpos_pt
, self
.ypos_pt
, self
.width_pt
, self
.height_pt
),
483 self
.backgroundattrs
)
486 if self
.did(self
.doaxes
):
490 for axis
in self
.axes
.values():
491 self
.insert(axis
.canvas
)
494 if self
.did(self
.dokey
):
498 if self
.key
is not None:
499 c
= self
.key
.paint(self
.plotitems
)
501 def parentchildalign(pmin
, pmax
, cmin
, cmax
, pos
, dist
, inside
):
502 ppos
= pmin
+0.5*(cmax
-cmin
)+dist
+pos
*(pmax
-pmin
-cmax
+cmin
-2*dist
)
503 cpos
= 0.5*(cmin
+cmax
)+(1-inside
)*(1-2*pos
)*(cmax
-cmin
+2*dist
)
506 x
= parentchildalign(self
.xpos_pt
, self
.xpos_pt
+self
.width_pt
,
507 bbox
.llx_pt
, bbox
.urx_pt
,
508 self
.key
.hpos
, unit
.topt(self
.key
.hdist
), self
.key
.hinside
)
509 y
= parentchildalign(self
.ypos_pt
, self
.ypos_pt
+self
.height_pt
,
510 bbox
.lly_pt
, bbox
.ury_pt
,
511 self
.key
.vpos
, unit
.topt(self
.key
.vdist
), self
.key
.vinside
)
512 self
.insert(c
, [trafo
.translate_pt(x
, y
)])
515 class graphxyz(graphxy
):
519 def __init__(self
, distance
, phi
, theta
, anglefactor
=math
.pi
/180):
522 self
.distance
= distance
524 self
.a
= (-math
.sin(phi
), math
.cos(phi
), 0)
525 self
.b
= (-math
.cos(phi
)*math
.sin(theta
),
526 -math
.sin(phi
)*math
.sin(theta
),
528 self
.eye
= (distance
*math
.cos(phi
)*math
.cos(theta
),
529 distance
*math
.sin(phi
)*math
.cos(theta
),
530 distance
*math
.sin(theta
))
532 def point(self
, x
, y
, z
):
533 d0
= (self
.a
[0]*self
.b
[1]*(z
-self
.eye
[2])
534 + self
.a
[2]*self
.b
[0]*(y
-self
.eye
[1])
535 + self
.a
[1]*self
.b
[2]*(x
-self
.eye
[0])
536 - self
.a
[2]*self
.b
[1]*(x
-self
.eye
[0])
537 - self
.a
[0]*self
.b
[2]*(y
-self
.eye
[1])
538 - self
.a
[1]*self
.b
[0]*(z
-self
.eye
[2]))
539 da
= (self
.eye
[0]*self
.b
[1]*(z
-self
.eye
[2])
540 + self
.eye
[2]*self
.b
[0]*(y
-self
.eye
[1])
541 + self
.eye
[1]*self
.b
[2]*(x
-self
.eye
[0])
542 - self
.eye
[2]*self
.b
[1]*(x
-self
.eye
[0])
543 - self
.eye
[0]*self
.b
[2]*(y
-self
.eye
[1])
544 - self
.eye
[1]*self
.b
[0]*(z
-self
.eye
[2]))
545 db
= (self
.a
[0]*self
.eye
[1]*(z
-self
.eye
[2])
546 + self
.a
[2]*self
.eye
[0]*(y
-self
.eye
[1])
547 + self
.a
[1]*self
.eye
[2]*(x
-self
.eye
[0])
548 - self
.a
[2]*self
.eye
[1]*(x
-self
.eye
[0])
549 - self
.a
[0]*self
.eye
[2]*(y
-self
.eye
[1])
550 - self
.a
[1]*self
.eye
[0]*(z
-self
.eye
[2]))
553 def zindex(self
, x
, y
, z
):
554 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
556 def angle(self
, x1
, y1
, z1
, x2
, y2
, z2
, x3
, y3
, z3
):
557 sx
= (x1
-self
.eye
[0])
558 sy
= (y1
-self
.eye
[1])
559 sz
= (z1
-self
.eye
[2])
560 nx
= (y2
-y1
)*(z3
-z1
)-(z2
-z1
)*(y3
-y1
)
561 ny
= (z2
-z1
)*(x3
-x1
)-(x2
-x1
)*(z3
-z1
)
562 nz
= (x2
-x1
)*(y3
-y1
)-(y2
-y1
)*(x3
-x1
)
563 return (sx
*nx
+sy
*ny
+sz
*nz
)/math
.sqrt(nx
*nx
+ny
*ny
+nz
*nz
)/math
.sqrt(sx
*sx
+sy
*sy
+sz
*sz
)
568 def __init__(self
, phi
, theta
, anglefactor
=math
.pi
/180):
572 self
.a
= (-math
.sin(phi
), math
.cos(phi
), 0)
573 self
.b
= (-math
.cos(phi
)*math
.sin(theta
),
574 -math
.sin(phi
)*math
.sin(theta
),
576 self
.c
= (-math
.cos(phi
)*math
.cos(theta
),
577 -math
.sin(phi
)*math
.cos(theta
),
580 def point(self
, x
, y
, z
):
581 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
583 def zindex(self
, x
, y
, z
):
584 return self
.c
[0]*x
+self
.c
[1]*y
+self
.c
[2]*z
586 def angle(self
, x1
, y1
, z1
, x2
, y2
, z2
, x3
, y3
, z3
):
587 nx
= (y2
-y1
)*(z3
-z1
)-(z2
-z1
)*(y3
-y1
)
588 ny
= (z2
-z1
)*(x3
-x1
)-(x2
-x1
)*(z3
-z1
)
589 nz
= (x2
-x1
)*(y3
-y1
)-(y2
-y1
)*(x3
-x1
)
590 return (self
.c
[0]*nx
+self
.c
[1]*ny
+self
.c
[2]*nz
)/math
.sqrt(nx
*nx
+ny
*ny
+nz
*nz
)
593 def __init__(self
, xpos
=0, ypos
=0, size
=None,
594 xscale
=1, yscale
=1, zscale
=1/goldenmean
,
595 projector
=central(10, -30, 30), key
=None,
602 self
.xpos_pt
= unit
.topt(xpos
)
603 self
.ypos_pt
= unit
.topt(ypos
)
604 self
.size_pt
= unit
.topt(size
)
608 self
.projector
= projector
611 self
.xorder
= projector
.zindex(0, -1, 0) > projector
.zindex(0, 1, 0) and 1 or 0
612 self
.yorder
= projector
.zindex(-1, 0, 0) > projector
.zindex(1, 0, 0) and 1 or 0
613 self
.zindexscale
= math
.sqrt(xscale
*xscale
+yscale
*yscale
+zscale
*zscale
)
615 for axisname
, aaxis
in axes
.items():
616 if aaxis
is not None:
617 if not isinstance(aaxis
, axis
.linkedaxis
):
618 self
.axes
[axisname
] = axis
.anchoredaxis(aaxis
, self
.texrunner
, axisname
)
620 self
.axes
[axisname
] = aaxis
621 for axisname
in ["x", "y"]:
622 okey
= axisname
+ "2"
623 if not axes
.has_key(axisname
):
624 if not axes
.has_key(okey
):
625 self
.axes
[axisname
] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
626 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
628 self
.axes
[axisname
] = axis
.linkedaxis(self
.axes
[okey
], axisname
)
629 if not axes
.has_key("z"):
630 self
.axes
["z"] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
632 if self
.axes
.has_key("x"):
633 self
.xbasepath
= self
.axes
["x"].basepath
634 self
.xvbasepath
= self
.axes
["x"].vbasepath
635 self
.xgridpath
= self
.axes
["x"].gridpath
636 self
.xtickpoint_pt
= self
.axes
["x"].tickpoint_pt
637 self
.xtickpoint
= self
.axes
["x"].tickpoint
638 self
.xvtickpoint_pt
= self
.axes
["x"].vtickpoint_pt
639 self
.xvtickpoint
= self
.axes
["x"].tickpoint
640 self
.xtickdirection
= self
.axes
["x"].tickdirection
641 self
.xvtickdirection
= self
.axes
["x"].vtickdirection
643 if self
.axes
.has_key("y"):
644 self
.ybasepath
= self
.axes
["y"].basepath
645 self
.yvbasepath
= self
.axes
["y"].vbasepath
646 self
.ygridpath
= self
.axes
["y"].gridpath
647 self
.ytickpoint_pt
= self
.axes
["y"].tickpoint_pt
648 self
.ytickpoint
= self
.axes
["y"].tickpoint
649 self
.yvtickpoint_pt
= self
.axes
["y"].vtickpoint_pt
650 self
.yvtickpoint
= self
.axes
["y"].vtickpoint
651 self
.ytickdirection
= self
.axes
["y"].tickdirection
652 self
.yvtickdirection
= self
.axes
["y"].vtickdirection
654 if self
.axes
.has_key("z"):
655 self
.zbasepath
= self
.axes
["z"].basepath
656 self
.zvbasepath
= self
.axes
["z"].vbasepath
657 self
.zgridpath
= self
.axes
["z"].gridpath
658 self
.ztickpoint_pt
= self
.axes
["z"].tickpoint_pt
659 self
.ztickpoint
= self
.axes
["z"].tickpoint
660 self
.zvtickpoint_pt
= self
.axes
["z"].vtickpoint
661 self
.zvtickpoint
= self
.axes
["z"].vtickpoint
662 self
.ztickdirection
= self
.axes
["z"].tickdirection
663 self
.zvtickdirection
= self
.axes
["z"].vtickdirection
665 self
.axesnames
= ([], [], [])
666 for axisname
, aaxis
in self
.axes
.items():
667 if axisname
[0] not in "xyz" or (len(axisname
) != 1 and (not axisname
[1:].isdigit() or
668 axisname
[1:] == "1")):
669 raise ValueError("invalid axis name")
670 if axisname
[0] == "x":
671 self
.axesnames
[0].append(axisname
)
672 elif axisname
[0] == "y":
673 self
.axesnames
[1].append(axisname
)
675 self
.axesnames
[2].append(axisname
)
676 aaxis
.setcreatecall(self
.doaxiscreate
, axisname
)
678 def pos_pt(self
, x
, y
, z
, xaxis
=None, yaxis
=None, zaxis
=None):
680 xaxis
= self
.axes
["x"]
682 yaxis
= self
.axes
["y"]
684 zaxis
= self
.axes
["z"]
685 return self
.vpos_pt(xaxis
.convert(x
), yaxis
.convert(y
), zaxis
.convert(y
))
687 def pos(self
, x
, y
, z
, xaxis
=None, yaxis
=None, zaxis
=None):
689 xaxis
= self
.axes
["x"]
691 yaxis
= self
.axes
["y"]
693 zaxis
= self
.axes
["z"]
694 return self
.vpos(xaxis
.convert(x
), yaxis
.convert(y
), zaxis
.convert(y
))
696 def vpos_pt(self
, vx
, vy
, vz
):
697 x
, y
= self
.projector
.point(2*self
.xscale
*(vx
- 0.5),
698 2*self
.yscale
*(vy
- 0.5),
699 2*self
.zscale
*(vz
- 0.5))
700 return self
.xpos_pt
+x
*self
.size_pt
, self
.ypos_pt
+y
*self
.size_pt
702 def vpos(self
, vx
, vy
, vz
):
703 x
, y
= self
.projector
.point(2*self
.xscale
*(vx
- 0.5),
704 2*self
.yscale
*(vy
- 0.5),
705 2*self
.zscale
*(vz
- 0.5))
706 return self
.xpos
+x
*self
.size
, self
.ypos
+y
*self
.size
708 def vzindex(self
, vx
, vy
, vz
):
709 return self
.projector
.zindex(2*self
.xscale
*(vx
- 0.5),
710 2*self
.yscale
*(vy
- 0.5),
711 2*self
.zscale
*(vz
- 0.5))/self
.zindexscale
713 def vangle(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
, vx3
, vy3
, vz3
):
714 return self
.projector
.angle(2*self
.xscale
*(vx1
- 0.5),
715 2*self
.yscale
*(vy1
- 0.5),
716 2*self
.zscale
*(vz1
- 0.5),
717 2*self
.xscale
*(vx2
- 0.5),
718 2*self
.yscale
*(vy2
- 0.5),
719 2*self
.zscale
*(vz2
- 0.5),
720 2*self
.xscale
*(vx3
- 0.5),
721 2*self
.yscale
*(vy3
- 0.5),
722 2*self
.zscale
*(vz3
- 0.5))
724 def vgeodesic(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
725 """returns a geodesic path between two points in graph coordinates"""
726 return path
.line_pt(*(self
.vpos_pt(vx1
, vy1
, vz1
) + self
.vpos_pt(vx2
, vy2
, vz2
)))
728 def vgeodesic_el(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
729 """returns a geodesic path element between two points in graph coordinates"""
730 return path
.lineto_pt(*(self
.vpos_pt(vx1
, vy1
, vz1
) + self
.vpos_pt(vx2
, vy2
, vz2
)))
732 def vcap_pt(self
, coordinate
, length_pt
, vx
, vy
, vz
):
733 """returns an error cap path for a given coordinate, lengths and
734 point in graph coordinates"""
736 return self
.vgeodesic(vx
-0.5*length_pt
/self
.size_pt
, vy
, vz
, vx
+0.5*length_pt
/self
.size_pt
, vy
, vz
)
737 elif coordinate
== 1:
738 return self
.vgeodesic(vx
, vy
-0.5*length_pt
/self
.size_pt
, vz
, vx
, vy
+0.5*length_pt
/self
.size_pt
, vz
)
739 elif coordinate
== 2:
740 return self
.vgeodesic(vx
, vy
, vz
-0.5*length_pt
/self
.size_pt
, vx
, vy
, vz
+0.5*length_pt
/self
.size_pt
)
742 raise ValueError("direction invalid")
744 def xvtickdirection(self
, vx
):
746 x1_pt
, y1_pt
= self
.vpos_pt(vx
, 1, 0)
747 x2_pt
, y2_pt
= self
.vpos_pt(vx
, 0, 0)
749 x1_pt
, y1_pt
= self
.vpos_pt(vx
, 0, 0)
750 x2_pt
, y2_pt
= self
.vpos_pt(vx
, 1, 0)
751 dx_pt
= x2_pt
- x1_pt
752 dy_pt
= y2_pt
- y1_pt
753 norm
= math
.hypot(dx_pt
, dy_pt
)
754 return dx_pt
/norm
, dy_pt
/norm
756 def yvtickdirection(self
, vy
):
758 x1_pt
, y1_pt
= self
.vpos_pt(1, vy
, 0)
759 x2_pt
, y2_pt
= self
.vpos_pt(0, vy
, 0)
761 x1_pt
, y1_pt
= self
.vpos_pt(0, vy
, 0)
762 x2_pt
, y2_pt
= self
.vpos_pt(1, vy
, 0)
763 dx_pt
= x2_pt
- x1_pt
764 dy_pt
= y2_pt
- y1_pt
765 norm
= math
.hypot(dx_pt
, dy_pt
)
766 return dx_pt
/norm
, dy_pt
/norm
768 def vtickdirection(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
769 x1_pt
, y1_pt
= self
.vpos_pt(vx1
, vy1
, vz1
)
770 x2_pt
, y2_pt
= self
.vpos_pt(vx2
, vy2
, vz2
)
771 dx_pt
= x2_pt
- x1_pt
772 dy_pt
= y2_pt
- y1_pt
773 norm
= math
.hypot(dx_pt
, dy_pt
)
774 return dx_pt
/norm
, dy_pt
/norm
776 def xvgridpath(self
, vx
):
777 return path
.path(path
.moveto_pt(*self
.vpos_pt(vx
, 0, 0)),
778 path
.lineto_pt(*self
.vpos_pt(vx
, 1, 0)),
779 path
.lineto_pt(*self
.vpos_pt(vx
, 1, 1)),
780 path
.lineto_pt(*self
.vpos_pt(vx
, 0, 1)),
783 def yvgridpath(self
, vy
):
784 return path
.path(path
.moveto_pt(*self
.vpos_pt(0, vy
, 0)),
785 path
.lineto_pt(*self
.vpos_pt(1, vy
, 0)),
786 path
.lineto_pt(*self
.vpos_pt(1, vy
, 1)),
787 path
.lineto_pt(*self
.vpos_pt(0, vy
, 1)),
790 def zvgridpath(self
, vz
):
791 return path
.path(path
.moveto_pt(*self
.vpos_pt(0, 0, vz
)),
792 path
.lineto_pt(*self
.vpos_pt(1, 0, vz
)),
793 path
.lineto_pt(*self
.vpos_pt(1, 1, vz
)),
794 path
.lineto_pt(*self
.vpos_pt(0, 1, vz
)),
797 def doaxispositioner(self
, axisname
):
798 if self
.did(self
.doaxispositioner
, axisname
):
802 self
.axes
["x"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, self
.xorder
, 0),
803 lambda vx
: self
.vtickdirection(vx
, self
.xorder
, 0, vx
, 1-self
.xorder
, 0),
805 elif axisname
== "x2":
806 self
.axes
["x2"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, 1-self
.xorder
, 0),
807 lambda vx
: self
.vtickdirection(vx
, 1-self
.xorder
, 0, vx
, self
.xorder
, 0),
809 elif axisname
== "x3":
810 self
.axes
["x3"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, self
.xorder
, 1),
811 lambda vx
: self
.vtickdirection(vx
, self
.xorder
, 1, vx
, 1-self
.xorder
, 1),
813 elif axisname
== "x4":
814 self
.axes
["x4"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, 1-self
.xorder
, 1),
815 lambda vx
: self
.vtickdirection(vx
, 1-self
.xorder
, 1, vx
, self
.xorder
, 1),
817 elif axisname
== "y":
818 self
.axes
["y"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(self
.yorder
, vy
, 0),
819 lambda vy
: self
.vtickdirection(self
.yorder
, vy
, 0, 1-self
.yorder
, vy
, 0),
821 elif axisname
== "y2":
822 self
.axes
["y2"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(1-self
.yorder
, vy
, 0),
823 lambda vy
: self
.vtickdirection(1-self
.yorder
, vy
, 0, self
.yorder
, vy
, 0),
825 elif axisname
== "y3":
826 self
.axes
["y3"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(self
.yorder
, vy
, 1),
827 lambda vy
: self
.vtickdirection(self
.yorder
, vy
, 1, 1-self
.yorder
, vy
, 1),
829 elif axisname
== "y4":
830 self
.axes
["y4"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(1-self
.yorder
, vy
, 1),
831 lambda vy
: self
.vtickdirection(1-self
.yorder
, vy
, 1, self
.yorder
, vy
, 1),
833 elif axisname
== "z":
834 self
.axes
["z"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(0, 0, vz
),
835 lambda vz
: self
.vtickdirection(0, 0, vz
, 1, 1, vz
),
837 elif axisname
== "z2":
838 self
.axes
["z2"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(1, 0, vz
),
839 lambda vz
: self
.vtickdirection(1, 0, vz
, 0, 1, vz
),
841 elif axisname
== "z3":
842 self
.axes
["z3"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(0, 1, vz
),
843 lambda vz
: self
.vtickdirection(0, 1, vz
, 1, 0, vz
),
845 elif axisname
== "z4":
846 self
.axes
["z4"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(0, 0, vz
),
847 lambda vz
: self
.vtickdirection(1, 1, vz
, 0, 0, vz
),
850 raise NotImplementedError("4 axis per dimension supported only")
853 if self
.did(self
.dolayout
):
855 for axisname
in self
.axes
.keys():
856 self
.doaxiscreate(axisname
)
858 def dobackground(self
):
859 if self
.did(self
.dobackground
):
863 if self
.did(self
.doaxes
):
867 for axis
in self
.axes
.values():
868 self
.insert(axis
.canvas
)
871 if self
.did(self
.dokey
):
875 if self
.key
is not None:
876 c
= self
.key
.paint(self
.plotitems
)
878 def parentchildalign(pmin
, pmax
, cmin
, cmax
, pos
, dist
, inside
):
879 ppos
= pmin
+0.5*(cmax
-cmin
)+dist
+pos
*(pmax
-pmin
-cmax
+cmin
-2*dist
)
880 cpos
= 0.5*(cmin
+cmax
)+(1-inside
)*(1-2*pos
)*(cmax
-cmin
+2*dist
)
883 x
= parentchildalign(self
.xpos_pt
, self
.xpos_pt
+self
.size_pt
,
884 bbox
.llx_pt
, bbox
.urx_pt
,
885 self
.key
.hpos
, unit
.topt(self
.key
.hdist
), self
.key
.hinside
)
886 y
= parentchildalign(self
.ypos_pt
, self
.ypos_pt
+self
.size_pt
,
887 bbox
.lly_pt
, bbox
.ury_pt
,
888 self
.key
.vpos
, unit
.topt(self
.key
.vdist
), self
.key
.vinside
)
889 self
.insert(c
, [trafo
.translate_pt(x
, y
)])