1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2006 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 import math
, re
, string
, warnings
26 from pyx
import canvas
, path
, trafo
, unit
27 from pyx
.graph
import style
28 from pyx
.graph
.axis
import axis
, positioner
31 goldenmean
= 0.5 * (math
.sqrt(5) + 1)
35 """style data storage class
37 Instances of this class are used to store data from the styles
38 and to pass point data to the styles by instances named privatedata
39 and sharedata. sharedata is shared between all the style(s) in use
40 by a data instance, while privatedata is private to each style and
41 used as a storage place instead of self to prevent side effects when
42 using a style several times."""
48 def __init__(self
, graph
, data
, styles
):
50 self
.title
= data
.title
52 # add styles to ensure all needs of the given styles
53 provided
= [] # already provided sharedata variables
54 addstyles
= [] # a list of style instances to be added in front
58 defaultprovider
= style
.getdefaultprovider(n
)
59 addstyles
.append(defaultprovider
)
60 provided
.extend(defaultprovider
.providesdata
)
61 provided
.extend(s
.providesdata
)
63 self
.styles
= addstyles
+ styles
64 self
.sharedata
= styledata()
65 self
.privatedatalist
= [styledata() for s
in self
.styles
]
67 # perform setcolumns to all styles
68 self
.usedcolumnnames
= []
69 for privatedata
, s
in zip(self
.privatedatalist
, self
.styles
):
70 self
.usedcolumnnames
.extend(s
.columnnames(privatedata
, self
.sharedata
, graph
, self
.data
.columnnames
))
72 def selectstyles(self
, graph
, selectindex
, selecttotal
):
73 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
74 style
.selectstyle(privatedata
, self
.sharedata
, graph
, selectindex
, selecttotal
)
76 def adjustaxesstatic(self
, graph
):
77 for columnname
, data
in self
.data
.columns
.items():
78 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
79 style
.adjustaxis(privatedata
, self
.sharedata
, graph
, columnname
, data
)
81 def makedynamicdata(self
, graph
):
82 self
.dynamiccolumns
= self
.data
.dynamiccolumns(graph
)
84 def adjustaxesdynamic(self
, graph
):
85 for columnname
, data
in self
.dynamiccolumns
.items():
86 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
87 style
.adjustaxis(privatedata
, self
.sharedata
, graph
, columnname
, data
)
89 def draw(self
, graph
):
90 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
91 style
.initdrawpoints(privatedata
, self
.sharedata
, graph
)
94 for columnname
in self
.usedcolumnnames
:
96 useitems
.append((columnname
, self
.dynamiccolumns
[columnname
]))
98 useitems
.append((columnname
, self
.data
.columns
[columnname
]))
100 raise ValueError("cannot draw empty data")
101 for i
in xrange(len(useitems
[0][1])):
102 for columnname
, data
in useitems
:
103 point
[columnname
] = data
[i
]
104 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
105 style
.drawpoint(privatedata
, self
.sharedata
, graph
, point
)
106 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
107 style
.donedrawpoints(privatedata
, self
.sharedata
, graph
)
109 def key_pt(self
, graph
, x_pt
, y_pt
, width_pt
, height_pt
):
110 for privatedata
, style
in zip(self
.privatedatalist
, self
.styles
):
111 style
.key_pt(privatedata
, self
.sharedata
, graph
, x_pt
, y_pt
, width_pt
, height_pt
)
113 def __getattr__(self
, attr
):
114 # read only access to the styles privatedata
115 stylesdata
= [getattr(styledata
, attr
)
116 for styledata
in self
.privatedatalist
117 if hasattr(styledata
, attr
)]
118 if len(stylesdata
) > 1:
120 elif len(stylesdata
) == 1:
122 raise AttributeError("access to styledata attribute '%s' failed" % attr
)
125 class graph(canvas
.canvas
):
128 canvas
.canvas
.__init
__(self
)
135 def did(self
, method
, *args
, **kwargs
):
136 if not self
._calls
.has_key(method
):
137 self
._calls
[method
] = []
138 for callargs
in self
._calls
[method
]:
139 if callargs
== (args
, kwargs
):
141 self
._calls
[method
].append((args
, kwargs
))
146 return canvas
.canvas
.bbox(self
)
148 def registerPS(self
, registry
):
150 canvas
.canvas
.registerPS(self
, registry
)
152 def registerPDF(self
, registry
):
154 canvas
.canvas
.registerPDF(self
, registry
)
156 def processPS(self
, file, writer
, context
, registry
, bbox
):
158 canvas
.canvas
.processPS(self
, file, writer
, context
, registry
, bbox
)
160 def processPDF(self
, file, writer
, context
, registry
, bbox
):
162 canvas
.canvas
.processPDF(self
, file, writer
, context
, registry
, bbox
)
164 def plot(self
, data
, styles
=None, rangewarning
=1):
165 if self
.didranges
and rangewarning
:
166 warnings
.warn("axes ranges have already been analysed; no further adjustments will be performed")
168 raise RuntimeError("can't plot further data after dodata() has been executed")
181 styles
= d
.defaultstyles
182 elif styles
!= d
.defaultstyles
:
183 raise RuntimeError("defaultstyles differ")
186 plotitems
.append(plotitem(self
, d
, styles
))
187 self
.plotitems
.extend(plotitems
)
189 for aplotitem
in plotitems
:
190 aplotitem
.makedynamicdata(self
)
197 if self
.did(self
.doranges
):
199 for plotitem
in self
.plotitems
:
200 plotitem
.adjustaxesstatic(self
)
201 for plotitem
in self
.plotitems
:
202 plotitem
.makedynamicdata(self
)
203 for plotitem
in self
.plotitems
:
204 plotitem
.adjustaxesdynamic(self
)
207 def doaxiscreate(self
, axisname
):
208 if self
.did(self
.doaxiscreate
, axisname
):
210 self
.doaxispositioner(axisname
)
211 self
.axes
[axisname
].create()
214 raise NotImplementedError
216 def dobackground(self
):
220 raise NotImplementedError
223 if self
.did(self
.dodata
):
228 # count the usage of styles and perform selects
230 def stylesid(styles
):
231 return ":".join([str(id(style
)) for style
in styles
])
232 for plotitem
in self
.plotitems
:
234 styletotal
[stylesid(plotitem
.styles
)] += 1
236 styletotal
[stylesid(plotitem
.styles
)] = 1
238 for plotitem
in self
.plotitems
:
240 styleindex
[stylesid(plotitem
.styles
)] += 1
242 styleindex
[stylesid(plotitem
.styles
)] = 0
243 plotitem
.selectstyles(self
, styleindex
[stylesid(plotitem
.styles
)],
244 styletotal
[stylesid(plotitem
.styles
)])
246 for plotitem
in self
.plotitems
:
252 raise NotImplementedError
261 class graphxy(graph
):
263 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, ratio
=goldenmean
,
264 key
=None, backgroundattrs
=None, axesdist
=0.8*unit
.v_cm
,
265 xaxisat
=None, yaxisat
=None, **axes
):
270 self
.xpos_pt
= unit
.topt(self
.xpos
)
271 self
.ypos_pt
= unit
.topt(self
.ypos
)
272 self
.xaxisat
= xaxisat
273 self
.yaxisat
= yaxisat
275 self
.backgroundattrs
= backgroundattrs
276 self
.axesdist_pt
= unit
.topt(axesdist
)
282 raise ValueError("specify width and/or height")
284 self
.width
= ratio
* self
.height
286 self
.height
= (1.0/ratio
) * self
.width
287 self
.width_pt
= unit
.topt(self
.width
)
288 self
.height_pt
= unit
.topt(self
.height
)
290 for axisname
, aaxis
in axes
.items():
291 if aaxis
is not None:
292 if not isinstance(aaxis
, axis
.linkedaxis
):
293 self
.axes
[axisname
] = axis
.anchoredaxis(aaxis
, self
.texrunner
, axisname
)
295 self
.axes
[axisname
] = aaxis
296 for axisname
, axisat
in [("x", xaxisat
), ("y", yaxisat
)]:
297 okey
= axisname
+ "2"
298 if not axes
.has_key(axisname
):
299 if not axes
.has_key(okey
):
300 self
.axes
[axisname
] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
301 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
303 self
.axes
[axisname
] = axis
.linkedaxis(self
.axes
[okey
], axisname
)
304 elif not axes
.has_key(okey
) and axisat
is None:
305 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
307 if self
.axes
.has_key("x"):
308 self
.xbasepath
= self
.axes
["x"].basepath
309 self
.xvbasepath
= self
.axes
["x"].vbasepath
310 self
.xgridpath
= self
.axes
["x"].gridpath
311 self
.xtickpoint_pt
= self
.axes
["x"].tickpoint_pt
312 self
.xtickpoint
= self
.axes
["x"].tickpoint
313 self
.xvtickpoint_pt
= self
.axes
["x"].vtickpoint_pt
314 self
.xvtickpoint
= self
.axes
["x"].tickpoint
315 self
.xtickdirection
= self
.axes
["x"].tickdirection
316 self
.xvtickdirection
= self
.axes
["x"].vtickdirection
318 if self
.axes
.has_key("y"):
319 self
.ybasepath
= self
.axes
["y"].basepath
320 self
.yvbasepath
= self
.axes
["y"].vbasepath
321 self
.ygridpath
= self
.axes
["y"].gridpath
322 self
.ytickpoint_pt
= self
.axes
["y"].tickpoint_pt
323 self
.ytickpoint
= self
.axes
["y"].tickpoint
324 self
.yvtickpoint_pt
= self
.axes
["y"].vtickpoint_pt
325 self
.yvtickpoint
= self
.axes
["y"].tickpoint
326 self
.ytickdirection
= self
.axes
["y"].tickdirection
327 self
.yvtickdirection
= self
.axes
["y"].vtickdirection
329 self
.axesnames
= ([], [])
330 for axisname
, aaxis
in self
.axes
.items():
331 if axisname
[0] not in "xy" or (len(axisname
) != 1 and (not axisname
[1:].isdigit() or
332 axisname
[1:] == "1")):
333 raise ValueError("invalid axis name")
334 if axisname
[0] == "x":
335 self
.axesnames
[0].append(axisname
)
337 self
.axesnames
[1].append(axisname
)
338 aaxis
.setcreatecall(self
.doaxiscreate
, axisname
)
341 def pos_pt(self
, x
, y
, xaxis
=None, yaxis
=None):
343 xaxis
= self
.axes
["x"]
345 yaxis
= self
.axes
["y"]
346 return (self
.xpos_pt
+ xaxis
.convert(x
)*self
.width_pt
,
347 self
.ypos_pt
+ yaxis
.convert(y
)*self
.height_pt
)
349 def pos(self
, x
, y
, xaxis
=None, yaxis
=None):
351 xaxis
= self
.axes
["x"]
353 yaxis
= self
.axes
["y"]
354 return (self
.xpos
+ xaxis
.convert(x
)*self
.width
,
355 self
.ypos
+ yaxis
.convert(y
)*self
.height
)
357 def vpos_pt(self
, vx
, vy
):
358 return (self
.xpos_pt
+ vx
*self
.width_pt
,
359 self
.ypos_pt
+ vy
*self
.height_pt
)
361 def vpos(self
, vx
, vy
):
362 return (self
.xpos
+ vx
*self
.width
,
363 self
.ypos
+ vy
*self
.height
)
365 def vgeodesic(self
, vx1
, vy1
, vx2
, vy2
):
366 """returns a geodesic path between two points in graph coordinates"""
367 return path
.line_pt(self
.xpos_pt
+ vx1
*self
.width_pt
,
368 self
.ypos_pt
+ vy1
*self
.height_pt
,
369 self
.xpos_pt
+ vx2
*self
.width_pt
,
370 self
.ypos_pt
+ vy2
*self
.height_pt
)
372 def vgeodesic_el(self
, vx1
, vy1
, vx2
, vy2
):
373 """returns a geodesic path element between two points in graph coordinates"""
374 return path
.lineto_pt(self
.xpos_pt
+ vx2
*self
.width_pt
,
375 self
.ypos_pt
+ vy2
*self
.height_pt
)
377 def vcap_pt(self
, coordinate
, length_pt
, vx
, vy
):
378 """returns an error cap path for a given coordinate, lengths and
379 point in graph coordinates"""
381 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
- 0.5*length_pt
,
382 self
.ypos_pt
+ vy
*self
.height_pt
,
383 self
.xpos_pt
+ vx
*self
.width_pt
+ 0.5*length_pt
,
384 self
.ypos_pt
+ vy
*self
.height_pt
)
385 elif coordinate
== 1:
386 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
,
387 self
.ypos_pt
+ vy
*self
.height_pt
- 0.5*length_pt
,
388 self
.xpos_pt
+ vx
*self
.width_pt
,
389 self
.ypos_pt
+ vy
*self
.height_pt
+ 0.5*length_pt
)
391 raise ValueError("direction invalid")
393 def xvgridpath(self
, vx
):
394 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
, self
.ypos_pt
,
395 self
.xpos_pt
+ vx
*self
.width_pt
, self
.ypos_pt
+ self
.height_pt
)
397 def yvgridpath(self
, vy
):
398 return path
.line_pt(self
.xpos_pt
, self
.ypos_pt
+ vy
*self
.height_pt
,
399 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ vy
*self
.height_pt
)
401 def axistrafo(self
, axis
, t
):
402 c
= canvas
.canvas([t
])
403 c
.insert(axis
.canvas
)
406 def axisatv(self
, axis
, v
):
407 if axis
.positioner
.fixtickdirection
[0]:
409 self
.axistrafo(axis
, trafo
.translate_pt(self
.xpos_pt
+ v
*self
.width_pt
- axis
.positioner
.x1_pt
, 0))
412 self
.axistrafo(axis
, trafo
.translate_pt(0, self
.ypos_pt
+ v
*self
.height_pt
- axis
.positioner
.y1_pt
))
414 def doaxispositioner(self
, axisname
):
415 if self
.did(self
.doaxispositioner
, axisname
):
419 self
.axes
["x"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
,
420 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
,
421 (0, 1), self
.xvgridpath
))
422 elif axisname
== "x2":
423 self
.axes
["x2"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
+ self
.height_pt
,
424 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ self
.height_pt
,
425 (0, -1), self
.xvgridpath
))
426 elif axisname
== "y":
427 self
.axes
["y"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
,
428 self
.xpos_pt
, self
.ypos_pt
+ self
.height_pt
,
429 (1, 0), self
.yvgridpath
))
430 elif axisname
== "y2":
431 self
.axes
["y2"].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
,
432 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ self
.height_pt
,
433 (-1, 0), self
.yvgridpath
))
435 if axisname
[1:] == "3":
436 dependsonaxisname
= axisname
[0]
438 dependsonaxisname
= "%s%d" % (axisname
[0], int(axisname
[1:]) - 2)
439 self
.doaxiscreate(dependsonaxisname
)
440 sign
= 2*(int(axisname
[1:]) % 2) - 1
441 if axisname
[0] == "x":
442 y_pt
= self
.axes
[dependsonaxisname
].positioner
.y1_pt
- sign
* (self
.axes
[dependsonaxisname
].canvas
.extent_pt
+ self
.axesdist_pt
)
443 self
.axes
[axisname
].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, y_pt
,
444 self
.xpos_pt
+ self
.width_pt
, y_pt
,
445 (0, sign
), self
.xvgridpath
))
447 x_pt
= self
.axes
[dependsonaxisname
].positioner
.x1_pt
- sign
* (self
.axes
[dependsonaxisname
].canvas
.extent_pt
+ self
.axesdist_pt
)
448 self
.axes
[axisname
].setpositioner(positioner
.lineaxispos_pt(x_pt
, self
.ypos_pt
,
449 x_pt
, self
.ypos_pt
+ self
.height_pt
,
450 (sign
, 0), self
.yvgridpath
))
453 if self
.did(self
.dolayout
):
455 for axisname
in self
.axes
.keys():
456 self
.doaxiscreate(axisname
)
457 if self
.xaxisat
is not None:
458 self
.axisatv(self
.axes
["x"], self
.axes
["y"].convert(self
.xaxisat
))
459 if self
.yaxisat
is not None:
460 self
.axisatv(self
.axes
["y"], self
.axes
["x"].convert(self
.yaxisat
))
462 def dobackground(self
):
463 if self
.did(self
.dobackground
):
465 if self
.backgroundattrs
is not None:
466 self
.draw(path
.rect_pt(self
.xpos_pt
, self
.ypos_pt
, self
.width_pt
, self
.height_pt
),
467 self
.backgroundattrs
)
470 if self
.did(self
.doaxes
):
474 for axis
in self
.axes
.values():
475 self
.insert(axis
.canvas
)
478 if self
.did(self
.dokey
):
482 if self
.key
is not None:
483 c
= self
.key
.paint(self
.plotitems
)
485 def parentchildalign(pmin
, pmax
, cmin
, cmax
, pos
, dist
, inside
):
486 ppos
= pmin
+0.5*(cmax
-cmin
)+dist
+pos
*(pmax
-pmin
-cmax
+cmin
-2*dist
)
487 cpos
= 0.5*(cmin
+cmax
)+(1-inside
)*(1-2*pos
)*(cmax
-cmin
+2*dist
)
490 x
= parentchildalign(self
.xpos_pt
, self
.xpos_pt
+self
.width_pt
,
491 bbox
.llx_pt
, bbox
.urx_pt
,
492 self
.key
.hpos
, unit
.topt(self
.key
.hdist
), self
.key
.hinside
)
493 y
= parentchildalign(self
.ypos_pt
, self
.ypos_pt
+self
.height_pt
,
494 bbox
.lly_pt
, bbox
.ury_pt
,
495 self
.key
.vpos
, unit
.topt(self
.key
.vdist
), self
.key
.vinside
)
496 self
.insert(c
, [trafo
.translate_pt(x
, y
)])
499 class graphxyz(graphxy
):
501 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, depth
=None,
502 phi
=30, theta
=30, distance
=1,
503 key
=None, backgroundattrs
=None, **axes
):
508 self
.xpos_pt
= unit
.topt(xpos
)
509 self
.ypos_pt
= unit
.topt(ypos
)
513 self
.width_pt
= unit
.topt(width
)
514 self
.height_pt
= unit
.topt(height
)
515 self
.depth_pt
= unit
.topt(depth
)
517 self
.backgroundattrs
= backgroundattrs
519 if self
.width_pt
<= 0: raise ValueError("width < 0")
520 if self
.height_pt
<= 0: raise ValueError("height < 0")
521 if self
.depth_pt
<= 0: raise ValueError("height < 0")
522 self
.distance_pt
= distance
*math
.sqrt(self
.width_pt
*self
.width_pt
+
523 self
.height_pt
*self
.height_pt
+
524 self
.depth_pt
*self
.depth_pt
)
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
= (self
.distance_pt
*math
.cos(phi
)*math
.cos(theta
),
532 self
.distance_pt
*math
.sin(phi
)*math
.cos(theta
),
533 self
.distance_pt
*math
.sin(theta
))
535 for axisname
, aaxis
in axes
.items():
536 if aaxis
is not None:
537 if not isinstance(aaxis
, axis
.linkedaxis
):
538 self
.axes
[axisname
] = axis
.anchoredaxis(aaxis
, self
.texrunner
, axisname
)
540 self
.axes
[axisname
] = aaxis
542 for axisname
in ["x", "y", "z"]:
543 if not axes
.has_key(axisname
):
544 self
.axes
[axisname
] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
546 if self
.axes
.has_key("x"):
547 self
.xbasepath
= self
.axes
["x"].basepath
548 self
.xvbasepath
= self
.axes
["x"].vbasepath
549 self
.xgridpath
= self
.axes
["x"].gridpath
550 self
.xtickpoint_pt
= self
.axes
["x"].tickpoint_pt
551 self
.xtickpoint
= self
.axes
["x"].tickpoint
552 self
.xvtickpoint_pt
= self
.axes
["x"].vtickpoint_pt
553 self
.xvtickpoint
= self
.axes
["x"].tickpoint
554 self
.xtickdirection
= self
.axes
["x"].tickdirection
555 self
.xvtickdirection
= self
.axes
["x"].vtickdirection
557 if self
.axes
.has_key("y"):
558 self
.ybasepath
= self
.axes
["y"].basepath
559 self
.yvbasepath
= self
.axes
["y"].vbasepath
560 self
.ygridpath
= self
.axes
["y"].gridpath
561 self
.ytickpoint_pt
= self
.axes
["y"].tickpoint_pt
562 self
.ytickpoint
= self
.axes
["y"].tickpoint
563 self
.yvtickpoint_pt
= self
.axes
["y"].vtickpoint_pt
564 self
.yvtickpoint
= self
.axes
["y"].tickpoint
565 self
.ytickdirection
= self
.axes
["y"].tickdirection
566 self
.yvtickdirection
= self
.axes
["y"].vtickdirection
568 if self
.axes
.has_key("z"):
569 self
.zbasepath
= self
.axes
["z"].basepath
570 self
.zvbasepath
= self
.axes
["z"].vbasepath
571 self
.zgridpath
= self
.axes
["z"].gridpath
572 self
.ztickpoint_pt
= self
.axes
["z"].tickpoint_pt
573 self
.ztickpoint
= self
.axes
["z"].tickpoint
574 self
.zvtickpoint_pt
= self
.axes
["z"].vtickpoint_pt
575 self
.zvtickpoint
= self
.axes
["z"].tickpoint
576 self
.ztickdirection
= self
.axes
["z"].tickdirection
577 self
.zvtickdirection
= self
.axes
["z"].vtickdirection
579 self
.axesnames
= ([], [], [])
580 for axisname
, aaxis
in self
.axes
.items():
581 if axisname
[0] not in "xyz" or (len(axisname
) != 1 and (not axisname
[1:].isdigit() or
582 axisname
[1:] == "1")):
583 raise ValueError("invalid axis name")
584 if axisname
[0] == "x":
585 self
.axesnames
[0].append(axisname
)
586 elif axisname
[0] == "y":
587 self
.axesnames
[1].append(axisname
)
589 self
.axesnames
[2].append(axisname
)
590 aaxis
.setcreatecall(self
.doaxiscreate
, axisname
)
592 def pos_pt(self
, x
, y
, z
, xaxis
=None, yaxis
=None, zaxis
=None):
594 xaxis
= self
.axes
["x"]
596 yaxis
= self
.axes
["y"]
598 zaxis
= self
.axes
["z"]
599 return self
.vpos_pt(xaxis
.convert(x
), yaxis
.convert(y
), zaxis
.convert(y
))
601 def pos(self
, x
, y
, z
, xaxis
=None, yaxis
=None, zaxis
=None):
603 xaxis
= self
.axes
["x"]
605 yaxis
= self
.axes
["y"]
607 zaxis
= self
.axes
["z"]
608 return self
.vpos(xaxis
.convert(x
), yaxis
.convert(y
), zaxis
.convert(y
))
610 def vpos_pt(self
, vx
, vy
, vz
):
611 x
, y
, z
= (vx
- 0.5)*self
.depth_pt
, (vy
- 0.5)*self
.width_pt
, (vz
- 0.5)*self
.height_pt
612 d0
= float(self
.a
[0]*self
.b
[1]*(z
-self
.eye
[2])
613 + self
.a
[2]*self
.b
[0]*(y
-self
.eye
[1])
614 + self
.a
[1]*self
.b
[2]*(x
-self
.eye
[0])
615 - self
.a
[2]*self
.b
[1]*(x
-self
.eye
[0])
616 - self
.a
[0]*self
.b
[2]*(y
-self
.eye
[1])
617 - self
.a
[1]*self
.b
[0]*(z
-self
.eye
[2]))
618 da
= (self
.eye
[0]*self
.b
[1]*(z
-self
.eye
[2])
619 + self
.eye
[2]*self
.b
[0]*(y
-self
.eye
[1])
620 + self
.eye
[1]*self
.b
[2]*(x
-self
.eye
[0])
621 - self
.eye
[2]*self
.b
[1]*(x
-self
.eye
[0])
622 - self
.eye
[0]*self
.b
[2]*(y
-self
.eye
[1])
623 - self
.eye
[1]*self
.b
[0]*(z
-self
.eye
[2]))
624 db
= (self
.a
[0]*self
.eye
[1]*(z
-self
.eye
[2])
625 + self
.a
[2]*self
.eye
[0]*(y
-self
.eye
[1])
626 + self
.a
[1]*self
.eye
[2]*(x
-self
.eye
[0])
627 - self
.a
[2]*self
.eye
[1]*(x
-self
.eye
[0])
628 - self
.a
[0]*self
.eye
[2]*(y
-self
.eye
[1])
629 - self
.a
[1]*self
.eye
[0]*(z
-self
.eye
[2]))
630 return da
/d0
+ self
.xpos_pt
, db
/d0
+ self
.ypos_pt
632 def vpos(self
, vx
, vy
, vz
):
633 x_pt
, y_pt
= self
.vpos_pt(vx
, vy
, vz
)
634 return unit
.t_pt(x_pt
), unit
.t_pt(y_pt
)
636 def xbaseline(self
, axis
, x1
, x2
, xaxis
=None):
637 if xaxis
is None: xaxis
= self
.axes
["x"]
638 return self
.vxbaseline(axis
, xaxis
.convert(x1
), xaxis
.convert(x2
))
640 def ybaseline(self
, axis
, y1
, y2
, yaxis
=None):
641 if yaxis
is None: yaxis
= self
.axes
["y"]
642 return self
.vybaseline(axis
, yaxis
.convert(y1
), yaxis
.convert(y2
))
644 def zbaseline(self
, axis
, z1
, z2
, zaxis
=None):
645 if zaxis
is None: zaxis
= self
.axes
["z"]
646 return self
.vzbaseline(axis
, zaxis
.convert(z1
), zaxis
.convert(z2
))
648 def vxbaseline(self
, axis
, v1
, v2
):
649 return (path
.line_pt(*(self
.vpos_pt(v1
, 0, 0) + self
.vpos_pt(v2
, 0, 0))) +
650 path
.line_pt(*(self
.vpos_pt(v1
, 0, 1) + self
.vpos_pt(v2
, 0, 1))) +
651 path
.line_pt(*(self
.vpos_pt(v1
, 1, 1) + self
.vpos_pt(v2
, 1, 1))) +
652 path
.line_pt(*(self
.vpos_pt(v1
, 1, 0) + self
.vpos_pt(v2
, 1, 0))))
654 def vybaseline(self
, axis
, v1
, v2
):
655 return (path
.line_pt(*(self
.vpos_pt(0, v1
, 0) + self
.vpos_pt(0, v2
, 0))) +
656 path
.line_pt(*(self
.vpos_pt(0, v1
, 1) + self
.vpos_pt(0, v2
, 1))) +
657 path
.line_pt(*(self
.vpos_pt(1, v1
, 1) + self
.vpos_pt(1, v2
, 1))) +
658 path
.line_pt(*(self
.vpos_pt(1, v1
, 0) + self
.vpos_pt(1, v2
, 0))))
660 def vzbaseline(self
, axis
, v1
, v2
):
661 return (path
.line_pt(*(self
.vpos_pt(0, 0, v1
) + self
.vpos_pt(0, 0, v2
))) +
662 path
.line_pt(*(self
.vpos_pt(0, 1, v1
) + self
.vpos_pt(0, 1, v2
))) +
663 path
.line_pt(*(self
.vpos_pt(1, 1, v1
) + self
.vpos_pt(1, 1, v2
))) +
664 path
.line_pt(*(self
.vpos_pt(1, 0, v1
) + self
.vpos_pt(1, 0, v2
))))
666 def vgeodesic(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
667 """returns a geodesic path between two points in graph coordinates"""
668 return path
.line_pt(*(self
.vpos_pt(vx1
, vy1
, vz1
) + self
.vpos_pt(vx2
, vy2
, vz2
)))
670 def vgeodesic_el(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
671 """returns a geodesic path element between two points in graph coordinates"""
672 return path
.lineto_pt(*(self
.vpos_pt(vx1
, vy1
, vz1
) + self
.vpos_pt(vx2
, vy2
, vz2
)))
674 def vcap_pt(self
, coordinate
, length_pt
, vx
, vy
, vz
):
675 """returns an error cap path for a given coordinate, lengths and
676 point in graph coordinates"""
677 raise NotImplementedError
679 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
- 0.5*length_pt
,
680 self
.ypos_pt
+ vy
*self
.height_pt
,
681 self
.xpos_pt
+ vx
*self
.width_pt
+ 0.5*length_pt
,
682 self
.ypos_pt
+ vy
*self
.height_pt
)
683 elif coordinate
== 1:
684 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
,
685 self
.ypos_pt
+ vy
*self
.height_pt
- 0.5*length_pt
,
686 self
.xpos_pt
+ vx
*self
.width_pt
,
687 self
.ypos_pt
+ vy
*self
.height_pt
+ 0.5*length_pt
)
689 raise ValueError("direction invalid")
691 def xvgridpath(self
, vx
):
692 raise NotImplementedError
694 def yvgridpath(self
, vy
):
695 raise NotImplementedError
697 def zvgridpath(self
, vz
):
698 raise NotImplementedError
700 def doaxispositioner(self
, axisname
):
701 if self
.did(self
.doaxispositioner
, axisname
):
705 x1_pt
, y1_pt
= self
.vpos_pt(0, 0, 0)
706 x2_pt
, y2_pt
= self
.vpos_pt(1, 0, 0)
707 self
.axes
["x"].setpositioner(positioner
.lineaxispos_pt(x1_pt
, y1_pt
, x2_pt
, y2_pt
,
708 (0, 1), self
.xvgridpath
))
709 elif axisname
== "y":
710 x1_pt
, y1_pt
= self
.vpos_pt(0, 0, 0)
711 x2_pt
, y2_pt
= self
.vpos_pt(0, 1, 0)
712 self
.axes
["y"].setpositioner(positioner
.lineaxispos_pt(x1_pt
, y1_pt
, x2_pt
, y2_pt
,
713 (0, 1), self
.yvgridpath
))
714 elif axisname
== "z":
715 x1_pt
, y1_pt
= self
.vpos_pt(0, 0, 0)
716 x2_pt
, y2_pt
= self
.vpos_pt(0, 0, 1)
717 self
.axes
["z"].setpositioner(positioner
.lineaxispos_pt(x1_pt
, y1_pt
, x2_pt
, y2_pt
,
718 (1, 0), self
.yvgridpath
))
720 raise NotImplementedError
723 if self
.did(self
.dolayout
):
725 for axisname
in self
.axes
.keys():
726 self
.doaxiscreate(axisname
)
728 def dobackground(self
):
729 if self
.did(self
.dobackground
):
733 if self
.did(self
.doaxes
):
737 for axis
in self
.axes
.values():
738 self
.insert(axis
.canvas
)
741 if self
.did(self
.dokey
):
745 if self
.key
is not None:
746 c
= self
.key
.paint(self
.plotitems
)
748 def parentchildalign(pmin
, pmax
, cmin
, cmax
, pos
, dist
, inside
):
749 ppos
= pmin
+0.5*(cmax
-cmin
)+dist
+pos
*(pmax
-pmin
-cmax
+cmin
-2*dist
)
750 cpos
= 0.5*(cmin
+cmax
)+(1-inside
)*(1-2*pos
)*(cmax
-cmin
+2*dist
)
753 x
= parentchildalign(self
.xpos_pt
, self
.xpos_pt
+self
.width_pt
,
754 bbox
.llx_pt
, bbox
.urx_pt
,
755 self
.key
.hpos
, unit
.topt(self
.key
.hdist
), self
.key
.hinside
)
756 y
= parentchildalign(self
.ypos_pt
, self
.ypos_pt
+self
.height_pt
,
757 bbox
.lly_pt
, bbox
.ury_pt
,
758 self
.key
.vpos
, unit
.topt(self
.key
.vdist
), self
.key
.vinside
)
759 self
.insert(c
, [trafo
.translate_pt(x
, y
)])