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
)
141 def did(self
, method
, *args
, **kwargs
):
142 if not self
._calls
.has_key(method
):
143 self
._calls
[method
] = []
144 for callargs
in self
._calls
[method
]:
145 if callargs
== (args
, kwargs
):
147 self
._calls
[method
].append((args
, kwargs
))
152 return canvas
.canvas
.bbox(self
)
154 def registerPS(self
, registry
):
156 canvas
.canvas
.registerPS(self
, registry
)
158 def registerPDF(self
, registry
):
160 canvas
.canvas
.registerPDF(self
, registry
)
162 def processPS(self
, file, writer
, context
, registry
, bbox
):
164 canvas
.canvas
.processPS(self
, file, writer
, context
, registry
, bbox
)
166 def processPDF(self
, file, writer
, context
, registry
, bbox
):
168 canvas
.canvas
.processPDF(self
, file, writer
, context
, registry
, bbox
)
170 def plot(self
, data
, styles
=None, rangewarning
=1):
171 if self
.didranges
and rangewarning
:
172 warnings
.warn("axes ranges have already been analysed; no further adjustments will be performed")
174 raise RuntimeError("can't plot further data after dostyles() has been executed")
187 styles
= d
.defaultstyles
188 elif styles
!= d
.defaultstyles
:
189 raise RuntimeError("defaultstyles differ")
192 plotitems
.append(plotitem(self
, d
, styles
))
193 self
.plotitems
.extend(plotitems
)
195 for aplotitem
in plotitems
:
196 aplotitem
.makedynamicdata(self
)
203 if self
.did(self
.doranges
):
205 for plotitem
in self
.plotitems
:
206 plotitem
.adjustaxesstatic(self
)
207 for plotitem
in self
.plotitems
:
208 plotitem
.makedynamicdata(self
)
209 for plotitem
in self
.plotitems
:
210 plotitem
.adjustaxesdynamic(self
)
213 def doaxiscreate(self
, axisname
):
214 if self
.did(self
.doaxiscreate
, axisname
):
216 self
.doaxispositioner(axisname
)
217 self
.axes
[axisname
].create()
220 raise NotImplementedError
222 def dobackground(self
):
226 raise NotImplementedError
229 if self
.did(self
.dostyles
):
234 # count the usage of styles and perform selects
236 def stylesid(styles
):
237 return ":".join([str(id(style
)) for style
in styles
])
238 for plotitem
in self
.plotitems
:
240 styletotal
[stylesid(plotitem
.styles
)] += 1
242 styletotal
[stylesid(plotitem
.styles
)] = 1
244 for plotitem
in self
.plotitems
:
246 styleindex
[stylesid(plotitem
.styles
)] += 1
248 styleindex
[stylesid(plotitem
.styles
)] = 0
249 plotitem
.selectstyles(self
, styleindex
[stylesid(plotitem
.styles
)],
250 styletotal
[stylesid(plotitem
.styles
)])
254 def doplotitem(self
, plotitem
):
255 if self
.did(self
.doplotitem
, plotitem
):
261 for plotitem
in self
.plotitems
:
262 self
.doplotitem(plotitem
)
265 warnings
.warn("dodata() has been deprecated. Use doplot() instead.")
268 def dokeyitem(self
, plotitem
):
269 if self
.did(self
.dokeyitem
, plotitem
):
272 if plotitem
.title
is not None:
273 self
.keyitems
.append(plotitem
)
276 raise NotImplementedError
285 class graphxy(graph
):
287 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, ratio
=goldenmean
,
288 key
=None, backgroundattrs
=None, axesdist
=0.8*unit
.v_cm
,
289 xaxisat
=None, yaxisat
=None, **axes
):
294 self
.xpos_pt
= unit
.topt(self
.xpos
)
295 self
.ypos_pt
= unit
.topt(self
.ypos
)
296 self
.xaxisat
= xaxisat
297 self
.yaxisat
= yaxisat
299 self
.backgroundattrs
= backgroundattrs
300 self
.axesdist_pt
= unit
.topt(axesdist
)
306 raise ValueError("specify width and/or height")
308 self
.width
= ratio
* self
.height
310 self
.height
= (1.0/ratio
) * self
.width
311 self
.width_pt
= unit
.topt(self
.width
)
312 self
.height_pt
= unit
.topt(self
.height
)
314 for axisname
, aaxis
in axes
.items():
315 if aaxis
is not None:
316 if not isinstance(aaxis
, axis
.linkedaxis
):
317 self
.axes
[axisname
] = axis
.anchoredaxis(aaxis
, self
.texrunner
, axisname
)
319 self
.axes
[axisname
] = aaxis
320 for axisname
, axisat
in [("x", xaxisat
), ("y", yaxisat
)]:
321 okey
= axisname
+ "2"
322 if not axes
.has_key(axisname
):
323 if not axes
.has_key(okey
) or axes
[okey
] is None:
324 self
.axes
[axisname
] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
325 if not axes
.has_key(okey
):
326 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
328 self
.axes
[axisname
] = axis
.linkedaxis(self
.axes
[okey
], axisname
)
329 elif not axes
.has_key(okey
) and axisat
is None:
330 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
332 if self
.axes
.has_key("x"):
333 self
.xbasepath
= self
.axes
["x"].basepath
334 self
.xvbasepath
= self
.axes
["x"].vbasepath
335 self
.xgridpath
= self
.axes
["x"].gridpath
336 self
.xtickpoint_pt
= self
.axes
["x"].tickpoint_pt
337 self
.xtickpoint
= self
.axes
["x"].tickpoint
338 self
.xvtickpoint_pt
= self
.axes
["x"].vtickpoint_pt
339 self
.xvtickpoint
= self
.axes
["x"].tickpoint
340 self
.xtickdirection
= self
.axes
["x"].tickdirection
341 self
.xvtickdirection
= self
.axes
["x"].vtickdirection
343 if self
.axes
.has_key("y"):
344 self
.ybasepath
= self
.axes
["y"].basepath
345 self
.yvbasepath
= self
.axes
["y"].vbasepath
346 self
.ygridpath
= self
.axes
["y"].gridpath
347 self
.ytickpoint_pt
= self
.axes
["y"].tickpoint_pt
348 self
.ytickpoint
= self
.axes
["y"].tickpoint
349 self
.yvtickpoint_pt
= self
.axes
["y"].vtickpoint_pt
350 self
.yvtickpoint
= self
.axes
["y"].vtickpoint
351 self
.ytickdirection
= self
.axes
["y"].tickdirection
352 self
.yvtickdirection
= self
.axes
["y"].vtickdirection
354 self
.axesnames
= ([], [])
355 for axisname
, aaxis
in self
.axes
.items():
356 if axisname
[0] not in "xy" or (len(axisname
) != 1 and (not axisname
[1:].isdigit() or
357 axisname
[1:] == "1")):
358 raise ValueError("invalid axis name")
359 if axisname
[0] == "x":
360 self
.axesnames
[0].append(axisname
)
362 self
.axesnames
[1].append(axisname
)
363 aaxis
.setcreatecall(self
.doaxiscreate
, axisname
)
366 def pos_pt(self
, x
, y
, xaxis
=None, yaxis
=None):
368 xaxis
= self
.axes
["x"]
370 yaxis
= self
.axes
["y"]
371 return (self
.xpos_pt
+ xaxis
.convert(x
)*self
.width_pt
,
372 self
.ypos_pt
+ yaxis
.convert(y
)*self
.height_pt
)
374 def pos(self
, x
, y
, xaxis
=None, yaxis
=None):
376 xaxis
= self
.axes
["x"]
378 yaxis
= self
.axes
["y"]
379 return (self
.xpos
+ xaxis
.convert(x
)*self
.width
,
380 self
.ypos
+ yaxis
.convert(y
)*self
.height
)
382 def vpos_pt(self
, vx
, vy
):
383 return (self
.xpos_pt
+ vx
*self
.width_pt
,
384 self
.ypos_pt
+ vy
*self
.height_pt
)
386 def vpos(self
, vx
, vy
):
387 return (self
.xpos
+ vx
*self
.width
,
388 self
.ypos
+ vy
*self
.height
)
390 def vzindex(self
, vx
, vy
):
393 def vangle(self
, vx1
, vy1
, vx2
, vy2
, vx3
, vy3
):
396 def vgeodesic(self
, vx1
, vy1
, vx2
, vy2
):
397 """returns a geodesic path between two points in graph coordinates"""
398 return path
.line_pt(self
.xpos_pt
+ vx1
*self
.width_pt
,
399 self
.ypos_pt
+ vy1
*self
.height_pt
,
400 self
.xpos_pt
+ vx2
*self
.width_pt
,
401 self
.ypos_pt
+ vy2
*self
.height_pt
)
403 def vgeodesic_el(self
, vx1
, vy1
, vx2
, vy2
):
404 """returns a geodesic path element between two points in graph coordinates"""
405 return path
.lineto_pt(self
.xpos_pt
+ vx2
*self
.width_pt
,
406 self
.ypos_pt
+ vy2
*self
.height_pt
)
408 def vcap_pt(self
, coordinate
, length_pt
, vx
, vy
):
409 """returns an error cap path for a given coordinate, lengths and
410 point in graph coordinates"""
412 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
- 0.5*length_pt
,
413 self
.ypos_pt
+ vy
*self
.height_pt
,
414 self
.xpos_pt
+ vx
*self
.width_pt
+ 0.5*length_pt
,
415 self
.ypos_pt
+ vy
*self
.height_pt
)
416 elif coordinate
== 1:
417 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
,
418 self
.ypos_pt
+ vy
*self
.height_pt
- 0.5*length_pt
,
419 self
.xpos_pt
+ vx
*self
.width_pt
,
420 self
.ypos_pt
+ vy
*self
.height_pt
+ 0.5*length_pt
)
422 raise ValueError("direction invalid")
424 def xvgridpath(self
, vx
):
425 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
, self
.ypos_pt
,
426 self
.xpos_pt
+ vx
*self
.width_pt
, self
.ypos_pt
+ self
.height_pt
)
428 def yvgridpath(self
, vy
):
429 return path
.line_pt(self
.xpos_pt
, self
.ypos_pt
+ vy
*self
.height_pt
,
430 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ vy
*self
.height_pt
)
432 def axistrafo(self
, axis
, t
):
433 c
= canvas
.canvas([t
])
434 c
.insert(axis
.canvas
)
437 def axisatv(self
, axis
, v
):
438 if axis
.positioner
.fixtickdirection
[0]:
440 self
.axistrafo(axis
, trafo
.translate_pt(self
.xpos_pt
+ v
*self
.width_pt
- axis
.positioner
.x1_pt
, 0))
443 self
.axistrafo(axis
, trafo
.translate_pt(0, self
.ypos_pt
+ v
*self
.height_pt
- axis
.positioner
.y1_pt
))
445 def doaxispositioner(self
, axisname
):
446 if self
.did(self
.doaxispositioner
, axisname
):
450 self
.axes
["x"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
,
451 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
,
452 (0, 1), self
.xvgridpath
))
453 elif axisname
== "x2":
454 self
.axes
["x2"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
+ self
.height_pt
,
455 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ self
.height_pt
,
456 (0, -1), self
.xvgridpath
))
457 elif axisname
== "y":
458 self
.axes
["y"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
,
459 self
.xpos_pt
, self
.ypos_pt
+ self
.height_pt
,
460 (1, 0), self
.yvgridpath
))
461 elif axisname
== "y2":
462 self
.axes
["y2"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
,
463 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ self
.height_pt
,
464 (-1, 0), self
.yvgridpath
))
466 if axisname
[1:] == "3":
467 dependsonaxisname
= axisname
[0]
469 dependsonaxisname
= "%s%d" % (axisname
[0], int(axisname
[1:]) - 2)
470 self
.doaxiscreate(dependsonaxisname
)
471 sign
= 2*(int(axisname
[1:]) % 2) - 1
472 if axisname
[0] == "x":
473 y_pt
= self
.axes
[dependsonaxisname
].positioner
.y1_pt
- sign
* (self
.axes
[dependsonaxisname
].canvas
.extent_pt
+ self
.axesdist_pt
)
474 self
.axes
[axisname
].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, y_pt
,
475 self
.xpos_pt
+ self
.width_pt
, y_pt
,
476 (0, sign
), self
.xvgridpath
))
478 x_pt
= self
.axes
[dependsonaxisname
].positioner
.x1_pt
- sign
* (self
.axes
[dependsonaxisname
].canvas
.extent_pt
+ self
.axesdist_pt
)
479 self
.axes
[axisname
].setpositioner(positioner
.lineaxispos_pt(x_pt
, self
.ypos_pt
,
480 x_pt
, self
.ypos_pt
+ self
.height_pt
,
481 (sign
, 0), self
.yvgridpath
))
484 if self
.did(self
.dolayout
):
486 for axisname
in self
.axes
.keys():
487 self
.doaxiscreate(axisname
)
488 if self
.xaxisat
is not None:
489 self
.axisatv(self
.axes
["x"], self
.axes
["y"].convert(self
.xaxisat
))
490 if self
.yaxisat
is not None:
491 self
.axisatv(self
.axes
["y"], self
.axes
["x"].convert(self
.yaxisat
))
493 def dobackground(self
):
494 if self
.did(self
.dobackground
):
496 if self
.backgroundattrs
is not None:
497 self
.draw(path
.rect_pt(self
.xpos_pt
, self
.ypos_pt
, self
.width_pt
, self
.height_pt
),
498 self
.backgroundattrs
)
501 if self
.did(self
.doaxes
):
505 for axis
in self
.axes
.values():
506 self
.insert(axis
.canvas
)
509 if self
.did(self
.dokey
):
512 for plotitem
in self
.plotitems
:
513 self
.dokeyitem(plotitem
)
514 if self
.key
is not None:
515 c
= self
.key
.paint(self
.keyitems
)
517 def parentchildalign(pmin
, pmax
, cmin
, cmax
, pos
, dist
, inside
):
518 ppos
= pmin
+0.5*(cmax
-cmin
)+dist
+pos
*(pmax
-pmin
-cmax
+cmin
-2*dist
)
519 cpos
= 0.5*(cmin
+cmax
)+(1-inside
)*(1-2*pos
)*(cmax
-cmin
+2*dist
)
522 x
= parentchildalign(self
.xpos_pt
, self
.xpos_pt
+self
.width_pt
,
523 bbox
.llx_pt
, bbox
.urx_pt
,
524 self
.key
.hpos
, unit
.topt(self
.key
.hdist
), self
.key
.hinside
)
525 y
= parentchildalign(self
.ypos_pt
, self
.ypos_pt
+self
.height_pt
,
526 bbox
.lly_pt
, bbox
.ury_pt
,
527 self
.key
.vpos
, unit
.topt(self
.key
.vdist
), self
.key
.vinside
)
528 self
.insert(c
, [trafo
.translate_pt(x
, y
)])
531 class graphxyz(graphxy
):
535 def __init__(self
, distance
, phi
, theta
, anglefactor
=math
.pi
/180):
538 self
.distance
= distance
540 self
.a
= (-math
.sin(phi
), math
.cos(phi
), 0)
541 self
.b
= (-math
.cos(phi
)*math
.sin(theta
),
542 -math
.sin(phi
)*math
.sin(theta
),
544 self
.eye
= (distance
*math
.cos(phi
)*math
.cos(theta
),
545 distance
*math
.sin(phi
)*math
.cos(theta
),
546 distance
*math
.sin(theta
))
548 def point(self
, x
, y
, z
):
549 d0
= (self
.a
[0]*self
.b
[1]*(z
-self
.eye
[2])
550 + self
.a
[2]*self
.b
[0]*(y
-self
.eye
[1])
551 + self
.a
[1]*self
.b
[2]*(x
-self
.eye
[0])
552 - self
.a
[2]*self
.b
[1]*(x
-self
.eye
[0])
553 - self
.a
[0]*self
.b
[2]*(y
-self
.eye
[1])
554 - self
.a
[1]*self
.b
[0]*(z
-self
.eye
[2]))
555 da
= (self
.eye
[0]*self
.b
[1]*(z
-self
.eye
[2])
556 + self
.eye
[2]*self
.b
[0]*(y
-self
.eye
[1])
557 + self
.eye
[1]*self
.b
[2]*(x
-self
.eye
[0])
558 - self
.eye
[2]*self
.b
[1]*(x
-self
.eye
[0])
559 - self
.eye
[0]*self
.b
[2]*(y
-self
.eye
[1])
560 - self
.eye
[1]*self
.b
[0]*(z
-self
.eye
[2]))
561 db
= (self
.a
[0]*self
.eye
[1]*(z
-self
.eye
[2])
562 + self
.a
[2]*self
.eye
[0]*(y
-self
.eye
[1])
563 + self
.a
[1]*self
.eye
[2]*(x
-self
.eye
[0])
564 - self
.a
[2]*self
.eye
[1]*(x
-self
.eye
[0])
565 - self
.a
[0]*self
.eye
[2]*(y
-self
.eye
[1])
566 - self
.a
[1]*self
.eye
[0]*(z
-self
.eye
[2]))
569 def zindex(self
, x
, y
, z
):
570 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
572 def angle(self
, x1
, y1
, z1
, x2
, y2
, z2
, x3
, y3
, z3
):
573 sx
= (x1
-self
.eye
[0])
574 sy
= (y1
-self
.eye
[1])
575 sz
= (z1
-self
.eye
[2])
576 nx
= (y2
-y1
)*(z3
-z1
)-(z2
-z1
)*(y3
-y1
)
577 ny
= (z2
-z1
)*(x3
-x1
)-(x2
-x1
)*(z3
-z1
)
578 nz
= (x2
-x1
)*(y3
-y1
)-(y2
-y1
)*(x3
-x1
)
579 return (sx
*nx
+sy
*ny
+sz
*nz
)/math
.sqrt(nx
*nx
+ny
*ny
+nz
*nz
)/math
.sqrt(sx
*sx
+sy
*sy
+sz
*sz
)
584 def __init__(self
, phi
, theta
, anglefactor
=math
.pi
/180):
588 self
.a
= (-math
.sin(phi
), math
.cos(phi
), 0)
589 self
.b
= (-math
.cos(phi
)*math
.sin(theta
),
590 -math
.sin(phi
)*math
.sin(theta
),
592 self
.c
= (-math
.cos(phi
)*math
.cos(theta
),
593 -math
.sin(phi
)*math
.cos(theta
),
596 def point(self
, x
, y
, z
):
597 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
599 def zindex(self
, x
, y
, z
):
600 return self
.c
[0]*x
+self
.c
[1]*y
+self
.c
[2]*z
602 def angle(self
, x1
, y1
, z1
, x2
, y2
, z2
, x3
, y3
, z3
):
603 nx
= (y2
-y1
)*(z3
-z1
)-(z2
-z1
)*(y3
-y1
)
604 ny
= (z2
-z1
)*(x3
-x1
)-(x2
-x1
)*(z3
-z1
)
605 nz
= (x2
-x1
)*(y3
-y1
)-(y2
-y1
)*(x3
-x1
)
606 return (self
.c
[0]*nx
+self
.c
[1]*ny
+self
.c
[2]*nz
)/math
.sqrt(nx
*nx
+ny
*ny
+nz
*nz
)
609 def __init__(self
, xpos
=0, ypos
=0, size
=None,
610 xscale
=1, yscale
=1, zscale
=1/goldenmean
,
611 projector
=central(10, -30, 30), key
=None,
618 self
.xpos_pt
= unit
.topt(xpos
)
619 self
.ypos_pt
= unit
.topt(ypos
)
620 self
.size_pt
= unit
.topt(size
)
624 self
.projector
= projector
627 self
.xorder
= projector
.zindex(0, -1, 0) > projector
.zindex(0, 1, 0) and 1 or 0
628 self
.yorder
= projector
.zindex(-1, 0, 0) > projector
.zindex(1, 0, 0) and 1 or 0
629 self
.zindexscale
= math
.sqrt(xscale
*xscale
+yscale
*yscale
+zscale
*zscale
)
631 for axisname
, aaxis
in axes
.items():
632 if aaxis
is not None:
633 if not isinstance(aaxis
, axis
.linkedaxis
):
634 self
.axes
[axisname
] = axis
.anchoredaxis(aaxis
, self
.texrunner
, axisname
)
636 self
.axes
[axisname
] = aaxis
637 for axisname
in ["x", "y"]:
638 okey
= axisname
+ "2"
639 if not axes
.has_key(axisname
):
640 if not axes
.has_key(okey
) or axes
[okey
] is None:
641 self
.axes
[axisname
] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
642 if not axes
.has_key(okey
):
643 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
645 self
.axes
[axisname
] = axis
.linkedaxis(self
.axes
[okey
], axisname
)
646 if not axes
.has_key("z"):
647 self
.axes
["z"] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, "z")
649 if self
.axes
.has_key("x"):
650 self
.xbasepath
= self
.axes
["x"].basepath
651 self
.xvbasepath
= self
.axes
["x"].vbasepath
652 self
.xgridpath
= self
.axes
["x"].gridpath
653 self
.xtickpoint_pt
= self
.axes
["x"].tickpoint_pt
654 self
.xtickpoint
= self
.axes
["x"].tickpoint
655 self
.xvtickpoint_pt
= self
.axes
["x"].vtickpoint_pt
656 self
.xvtickpoint
= self
.axes
["x"].tickpoint
657 self
.xtickdirection
= self
.axes
["x"].tickdirection
658 self
.xvtickdirection
= self
.axes
["x"].vtickdirection
660 if self
.axes
.has_key("y"):
661 self
.ybasepath
= self
.axes
["y"].basepath
662 self
.yvbasepath
= self
.axes
["y"].vbasepath
663 self
.ygridpath
= self
.axes
["y"].gridpath
664 self
.ytickpoint_pt
= self
.axes
["y"].tickpoint_pt
665 self
.ytickpoint
= self
.axes
["y"].tickpoint
666 self
.yvtickpoint_pt
= self
.axes
["y"].vtickpoint_pt
667 self
.yvtickpoint
= self
.axes
["y"].vtickpoint
668 self
.ytickdirection
= self
.axes
["y"].tickdirection
669 self
.yvtickdirection
= self
.axes
["y"].vtickdirection
671 if self
.axes
.has_key("z"):
672 self
.zbasepath
= self
.axes
["z"].basepath
673 self
.zvbasepath
= self
.axes
["z"].vbasepath
674 self
.zgridpath
= self
.axes
["z"].gridpath
675 self
.ztickpoint_pt
= self
.axes
["z"].tickpoint_pt
676 self
.ztickpoint
= self
.axes
["z"].tickpoint
677 self
.zvtickpoint_pt
= self
.axes
["z"].vtickpoint
678 self
.zvtickpoint
= self
.axes
["z"].vtickpoint
679 self
.ztickdirection
= self
.axes
["z"].tickdirection
680 self
.zvtickdirection
= self
.axes
["z"].vtickdirection
682 self
.axesnames
= ([], [], [])
683 for axisname
, aaxis
in self
.axes
.items():
684 if axisname
[0] not in "xyz" or (len(axisname
) != 1 and (not axisname
[1:].isdigit() or
685 axisname
[1:] == "1")):
686 raise ValueError("invalid axis name")
687 if axisname
[0] == "x":
688 self
.axesnames
[0].append(axisname
)
689 elif axisname
[0] == "y":
690 self
.axesnames
[1].append(axisname
)
692 self
.axesnames
[2].append(axisname
)
693 aaxis
.setcreatecall(self
.doaxiscreate
, axisname
)
695 def pos_pt(self
, x
, y
, z
, xaxis
=None, yaxis
=None, zaxis
=None):
697 xaxis
= self
.axes
["x"]
699 yaxis
= self
.axes
["y"]
701 zaxis
= self
.axes
["z"]
702 return self
.vpos_pt(xaxis
.convert(x
), yaxis
.convert(y
), zaxis
.convert(z
))
704 def pos(self
, x
, y
, z
, xaxis
=None, yaxis
=None, zaxis
=None):
706 xaxis
= self
.axes
["x"]
708 yaxis
= self
.axes
["y"]
710 zaxis
= self
.axes
["z"]
711 return self
.vpos(xaxis
.convert(x
), yaxis
.convert(y
), zaxis
.convert(z
))
713 def vpos_pt(self
, vx
, vy
, vz
):
714 x
, y
= self
.projector
.point(2*self
.xscale
*(vx
- 0.5),
715 2*self
.yscale
*(vy
- 0.5),
716 2*self
.zscale
*(vz
- 0.5))
717 return self
.xpos_pt
+x
*self
.size_pt
, self
.ypos_pt
+y
*self
.size_pt
719 def vpos(self
, vx
, vy
, vz
):
720 x
, y
= self
.projector
.point(2*self
.xscale
*(vx
- 0.5),
721 2*self
.yscale
*(vy
- 0.5),
722 2*self
.zscale
*(vz
- 0.5))
723 return self
.xpos
+x
*self
.size
, self
.ypos
+y
*self
.size
725 def vzindex(self
, vx
, vy
, vz
):
726 return self
.projector
.zindex(2*self
.xscale
*(vx
- 0.5),
727 2*self
.yscale
*(vy
- 0.5),
728 2*self
.zscale
*(vz
- 0.5))/self
.zindexscale
730 def vangle(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
, vx3
, vy3
, vz3
):
731 return self
.projector
.angle(2*self
.xscale
*(vx1
- 0.5),
732 2*self
.yscale
*(vy1
- 0.5),
733 2*self
.zscale
*(vz1
- 0.5),
734 2*self
.xscale
*(vx2
- 0.5),
735 2*self
.yscale
*(vy2
- 0.5),
736 2*self
.zscale
*(vz2
- 0.5),
737 2*self
.xscale
*(vx3
- 0.5),
738 2*self
.yscale
*(vy3
- 0.5),
739 2*self
.zscale
*(vz3
- 0.5))
741 def vgeodesic(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
742 """returns a geodesic path between two points in graph coordinates"""
743 return path
.line_pt(*(self
.vpos_pt(vx1
, vy1
, vz1
) + self
.vpos_pt(vx2
, vy2
, vz2
)))
745 def vgeodesic_el(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
746 """returns a geodesic path element between two points in graph coordinates"""
747 return path
.lineto_pt(*self
.vpos_pt(vx2
, vy2
, vz2
))
749 def vcap_pt(self
, coordinate
, length_pt
, vx
, vy
, vz
):
750 """returns an error cap path for a given coordinate, lengths and
751 point in graph coordinates"""
753 return self
.vgeodesic(vx
-0.5*length_pt
/self
.size_pt
, vy
, vz
, vx
+0.5*length_pt
/self
.size_pt
, vy
, vz
)
754 elif coordinate
== 1:
755 return self
.vgeodesic(vx
, vy
-0.5*length_pt
/self
.size_pt
, vz
, vx
, vy
+0.5*length_pt
/self
.size_pt
, vz
)
756 elif coordinate
== 2:
757 return self
.vgeodesic(vx
, vy
, vz
-0.5*length_pt
/self
.size_pt
, vx
, vy
, vz
+0.5*length_pt
/self
.size_pt
)
759 raise ValueError("direction invalid")
761 def xvtickdirection(self
, vx
):
763 x1_pt
, y1_pt
= self
.vpos_pt(vx
, 1, 0)
764 x2_pt
, y2_pt
= self
.vpos_pt(vx
, 0, 0)
766 x1_pt
, y1_pt
= self
.vpos_pt(vx
, 0, 0)
767 x2_pt
, y2_pt
= self
.vpos_pt(vx
, 1, 0)
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 yvtickdirection(self
, vy
):
775 x1_pt
, y1_pt
= self
.vpos_pt(1, vy
, 0)
776 x2_pt
, y2_pt
= self
.vpos_pt(0, vy
, 0)
778 x1_pt
, y1_pt
= self
.vpos_pt(0, vy
, 0)
779 x2_pt
, y2_pt
= self
.vpos_pt(1, vy
, 0)
780 dx_pt
= x2_pt
- x1_pt
781 dy_pt
= y2_pt
- y1_pt
782 norm
= math
.hypot(dx_pt
, dy_pt
)
783 return dx_pt
/norm
, dy_pt
/norm
785 def vtickdirection(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
786 x1_pt
, y1_pt
= self
.vpos_pt(vx1
, vy1
, vz1
)
787 x2_pt
, y2_pt
= self
.vpos_pt(vx2
, vy2
, vz2
)
788 dx_pt
= x2_pt
- x1_pt
789 dy_pt
= y2_pt
- y1_pt
790 norm
= math
.hypot(dx_pt
, dy_pt
)
791 return dx_pt
/norm
, dy_pt
/norm
793 def xvgridpath(self
, vx
):
794 return path
.path(path
.moveto_pt(*self
.vpos_pt(vx
, 0, 0)),
795 path
.lineto_pt(*self
.vpos_pt(vx
, 1, 0)),
796 path
.lineto_pt(*self
.vpos_pt(vx
, 1, 1)),
797 path
.lineto_pt(*self
.vpos_pt(vx
, 0, 1)),
800 def yvgridpath(self
, vy
):
801 return path
.path(path
.moveto_pt(*self
.vpos_pt(0, vy
, 0)),
802 path
.lineto_pt(*self
.vpos_pt(1, vy
, 0)),
803 path
.lineto_pt(*self
.vpos_pt(1, vy
, 1)),
804 path
.lineto_pt(*self
.vpos_pt(0, vy
, 1)),
807 def zvgridpath(self
, vz
):
808 return path
.path(path
.moveto_pt(*self
.vpos_pt(0, 0, vz
)),
809 path
.lineto_pt(*self
.vpos_pt(1, 0, vz
)),
810 path
.lineto_pt(*self
.vpos_pt(1, 1, vz
)),
811 path
.lineto_pt(*self
.vpos_pt(0, 1, vz
)),
814 def doaxispositioner(self
, axisname
):
815 if self
.did(self
.doaxispositioner
, axisname
):
819 self
.axes
["x"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, self
.xorder
, 0),
820 lambda vx
: self
.vtickdirection(vx
, self
.xorder
, 0, vx
, 1-self
.xorder
, 0),
822 elif axisname
== "x2":
823 self
.axes
["x2"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, 1-self
.xorder
, 0),
824 lambda vx
: self
.vtickdirection(vx
, 1-self
.xorder
, 0, vx
, self
.xorder
, 0),
826 elif axisname
== "x3":
827 self
.axes
["x3"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, self
.xorder
, 1),
828 lambda vx
: self
.vtickdirection(vx
, self
.xorder
, 1, vx
, 1-self
.xorder
, 1),
830 elif axisname
== "x4":
831 self
.axes
["x4"].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, 1-self
.xorder
, 1),
832 lambda vx
: self
.vtickdirection(vx
, 1-self
.xorder
, 1, vx
, self
.xorder
, 1),
834 elif axisname
== "y":
835 self
.axes
["y"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(self
.yorder
, vy
, 0),
836 lambda vy
: self
.vtickdirection(self
.yorder
, vy
, 0, 1-self
.yorder
, vy
, 0),
838 elif axisname
== "y2":
839 self
.axes
["y2"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(1-self
.yorder
, vy
, 0),
840 lambda vy
: self
.vtickdirection(1-self
.yorder
, vy
, 0, self
.yorder
, vy
, 0),
842 elif axisname
== "y3":
843 self
.axes
["y3"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(self
.yorder
, vy
, 1),
844 lambda vy
: self
.vtickdirection(self
.yorder
, vy
, 1, 1-self
.yorder
, vy
, 1),
846 elif axisname
== "y4":
847 self
.axes
["y4"].setpositioner(positioner
.flexlineaxispos_pt(lambda vy
: self
.vpos_pt(1-self
.yorder
, vy
, 1),
848 lambda vy
: self
.vtickdirection(1-self
.yorder
, vy
, 1, self
.yorder
, vy
, 1),
850 elif axisname
== "z":
851 self
.axes
["z"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(0, 0, vz
),
852 lambda vz
: self
.vtickdirection(0, 0, vz
, 1, 1, vz
),
854 elif axisname
== "z2":
855 self
.axes
["z2"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(1, 0, vz
),
856 lambda vz
: self
.vtickdirection(1, 0, vz
, 0, 1, vz
),
858 elif axisname
== "z3":
859 self
.axes
["z3"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(0, 1, vz
),
860 lambda vz
: self
.vtickdirection(0, 1, vz
, 1, 0, vz
),
862 elif axisname
== "z4":
863 self
.axes
["z4"].setpositioner(positioner
.flexlineaxispos_pt(lambda vz
: self
.vpos_pt(1, 1, vz
),
864 lambda vz
: self
.vtickdirection(1, 1, vz
, 0, 0, vz
),
867 raise NotImplementedError("4 axis per dimension supported only")
870 if self
.did(self
.dolayout
):
872 for axisname
in self
.axes
.keys():
873 self
.doaxiscreate(axisname
)
875 def dobackground(self
):
876 if self
.did(self
.dobackground
):
880 if self
.did(self
.doaxes
):
884 for axis
in self
.axes
.values():
885 self
.insert(axis
.canvas
)
888 if self
.did(self
.dokey
):
891 for plotitem
in self
.plotitems
:
892 self
.dokeyitem(plotitem
)
893 if self
.key
is not None:
894 c
= self
.key
.paint(self
.keyitems
)
896 def parentchildalign(pmin
, pmax
, cmin
, cmax
, pos
, dist
, inside
):
897 ppos
= pmin
+0.5*(cmax
-cmin
)+dist
+pos
*(pmax
-pmin
-cmax
+cmin
-2*dist
)
898 cpos
= 0.5*(cmin
+cmax
)+(1-inside
)*(1-2*pos
)*(cmax
-cmin
+2*dist
)
901 x
= parentchildalign(self
.xpos_pt
, self
.xpos_pt
+self
.size_pt
,
902 bbox
.llx_pt
, bbox
.urx_pt
,
903 self
.key
.hpos
, unit
.topt(self
.key
.hdist
), self
.key
.hinside
)
904 y
= parentchildalign(self
.ypos_pt
, self
.ypos_pt
+self
.size_pt
,
905 bbox
.lly_pt
, bbox
.ury_pt
,
906 self
.key
.vpos
, unit
.topt(self
.key
.vdist
), self
.key
.vinside
)
907 self
.insert(c
, [trafo
.translate_pt(x
, y
)])