doplot method (to be called with a plotitem) to alter the plotitem order etc.
[PyX/mjg.git] / pyx / graph / graph.py
blob6be411528418296f547d1194144383377be2a363
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)
34 class styledata:
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."""
43 pass
46 class plotitem:
48 def __init__(self, graph, data, styles):
49 self.data = data
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
55 for s in styles:
56 for n in s.needsdata:
57 if n not in provided:
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)
92 point = {}
93 useitems = []
94 for columnname in self.usedcolumnnames:
95 try:
96 useitems.append((columnname, self.dynamiccolumns[columnname]))
97 except KeyError:
98 useitems.append((columnname, self.data.columns[columnname]))
99 if not useitems:
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:
119 return stylesdata
120 elif len(stylesdata) == 1:
121 return stylesdata[0]
122 raise AttributeError("access to styledata attribute '%s' failed" % attr)
125 class graph(canvas.canvas):
127 def __init__(self):
128 canvas.canvas.__init__(self)
129 self.axes = {}
130 self.plotitems = []
131 self._calls = {}
132 self.didranges = 0
133 self.didstyles = 0
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):
140 return 1
141 self._calls[method].append((args, kwargs))
142 return 0
144 def bbox(self):
145 self.finish()
146 return canvas.canvas.bbox(self)
148 def registerPS(self, registry):
149 self.finish()
150 canvas.canvas.registerPS(self, registry)
152 def registerPDF(self, registry):
153 self.finish()
154 canvas.canvas.registerPDF(self, registry)
156 def processPS(self, file, writer, context, registry, bbox):
157 self.finish()
158 canvas.canvas.processPS(self, file, writer, context, registry, bbox)
160 def processPDF(self, file, writer, context, registry, bbox):
161 self.finish()
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")
167 if self.didstyles:
168 raise RuntimeError("can't plot further data after dostyles() has been executed")
169 singledata = 0
170 try:
171 for d in data:
172 pass
173 except:
174 usedata = [data]
175 singledata = 1
176 else:
177 usedata = data
178 if styles is None:
179 for d in usedata:
180 if styles is None:
181 styles = d.defaultstyles
182 elif styles != d.defaultstyles:
183 raise RuntimeError("defaultstyles differ")
184 plotitems = []
185 for d in usedata:
186 plotitems.append(plotitem(self, d, styles))
187 self.plotitems.extend(plotitems)
188 if self.didranges:
189 for aplotitem in plotitems:
190 aplotitem.makedynamicdata(self)
191 if singledata:
192 return plotitems[0]
193 else:
194 return plotitems
196 def doranges(self):
197 if self.did(self.doranges):
198 return
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)
205 self.didranges = 1
207 def doaxiscreate(self, axisname):
208 if self.did(self.doaxiscreate, axisname):
209 return
210 self.doaxispositioner(axisname)
211 self.axes[axisname].create()
213 def dolayout(self):
214 raise NotImplementedError
216 def dobackground(self):
217 pass
219 def doaxes(self):
220 raise NotImplementedError
222 def dostyles(self):
223 if self.did(self.dostyles):
224 return
225 self.dolayout()
226 self.dobackground()
228 # count the usage of styles and perform selects
229 styletotal = {}
230 def stylesid(styles):
231 return ":".join([str(id(style)) for style in styles])
232 for plotitem in self.plotitems:
233 try:
234 styletotal[stylesid(plotitem.styles)] += 1
235 except:
236 styletotal[stylesid(plotitem.styles)] = 1
237 styleindex = {}
238 for plotitem in self.plotitems:
239 try:
240 styleindex[stylesid(plotitem.styles)] += 1
241 except:
242 styleindex[stylesid(plotitem.styles)] = 0
243 plotitem.selectstyles(self, styleindex[stylesid(plotitem.styles)],
244 styletotal[stylesid(plotitem.styles)])
246 self.didstyles = 1
248 def doplot(self, plotitem):
249 if self.did(self.doplot, plotitem):
250 return
251 self.dostyles()
252 plotitem.draw(self)
254 def dodata(self):
255 for plotitem in self.plotitems:
256 self.doplot(plotitem)
258 def dokey(self):
259 raise NotImplementedError
261 def finish(self):
262 self.dobackground()
263 self.doaxes()
264 self.dodata()
265 self.dokey()
268 class graphxy(graph):
270 def __init__(self, xpos=0, ypos=0, width=None, height=None, ratio=goldenmean,
271 key=None, backgroundattrs=None, axesdist=0.8*unit.v_cm,
272 xaxisat=None, yaxisat=None, **axes):
273 graph.__init__(self)
275 self.xpos = xpos
276 self.ypos = ypos
277 self.xpos_pt = unit.topt(self.xpos)
278 self.ypos_pt = unit.topt(self.ypos)
279 self.xaxisat = xaxisat
280 self.yaxisat = yaxisat
281 self.key = key
282 self.backgroundattrs = backgroundattrs
283 self.axesdist_pt = unit.topt(axesdist)
285 self.width = width
286 self.height = height
287 if width is None:
288 if height is None:
289 raise ValueError("specify width and/or height")
290 else:
291 self.width = ratio * self.height
292 elif height is None:
293 self.height = (1.0/ratio) * self.width
294 self.width_pt = unit.topt(self.width)
295 self.height_pt = unit.topt(self.height)
297 for axisname, aaxis in axes.items():
298 if aaxis is not None:
299 if not isinstance(aaxis, axis.linkedaxis):
300 self.axes[axisname] = axis.anchoredaxis(aaxis, self.texrunner, axisname)
301 else:
302 self.axes[axisname] = aaxis
303 for axisname, axisat in [("x", xaxisat), ("y", yaxisat)]:
304 okey = axisname + "2"
305 if not axes.has_key(axisname):
306 if not axes.has_key(okey):
307 self.axes[axisname] = axis.anchoredaxis(axis.linear(), self.texrunner, axisname)
308 self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey)
309 else:
310 self.axes[axisname] = axis.linkedaxis(self.axes[okey], axisname)
311 elif not axes.has_key(okey) and axisat is None:
312 self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey)
314 if self.axes.has_key("x"):
315 self.xbasepath = self.axes["x"].basepath
316 self.xvbasepath = self.axes["x"].vbasepath
317 self.xgridpath = self.axes["x"].gridpath
318 self.xtickpoint_pt = self.axes["x"].tickpoint_pt
319 self.xtickpoint = self.axes["x"].tickpoint
320 self.xvtickpoint_pt = self.axes["x"].vtickpoint_pt
321 self.xvtickpoint = self.axes["x"].tickpoint
322 self.xtickdirection = self.axes["x"].tickdirection
323 self.xvtickdirection = self.axes["x"].vtickdirection
325 if self.axes.has_key("y"):
326 self.ybasepath = self.axes["y"].basepath
327 self.yvbasepath = self.axes["y"].vbasepath
328 self.ygridpath = self.axes["y"].gridpath
329 self.ytickpoint_pt = self.axes["y"].tickpoint_pt
330 self.ytickpoint = self.axes["y"].tickpoint
331 self.yvtickpoint_pt = self.axes["y"].vtickpoint_pt
332 self.yvtickpoint = self.axes["y"].tickpoint
333 self.ytickdirection = self.axes["y"].tickdirection
334 self.yvtickdirection = self.axes["y"].vtickdirection
336 self.axesnames = ([], [])
337 for axisname, aaxis in self.axes.items():
338 if axisname[0] not in "xy" or (len(axisname) != 1 and (not axisname[1:].isdigit() or
339 axisname[1:] == "1")):
340 raise ValueError("invalid axis name")
341 if axisname[0] == "x":
342 self.axesnames[0].append(axisname)
343 else:
344 self.axesnames[1].append(axisname)
345 aaxis.setcreatecall(self.doaxiscreate, axisname)
348 def pos_pt(self, x, y, xaxis=None, yaxis=None):
349 if xaxis is None:
350 xaxis = self.axes["x"]
351 if yaxis is None:
352 yaxis = self.axes["y"]
353 return (self.xpos_pt + xaxis.convert(x)*self.width_pt,
354 self.ypos_pt + yaxis.convert(y)*self.height_pt)
356 def pos(self, x, y, xaxis=None, yaxis=None):
357 if xaxis is None:
358 xaxis = self.axes["x"]
359 if yaxis is None:
360 yaxis = self.axes["y"]
361 return (self.xpos + xaxis.convert(x)*self.width,
362 self.ypos + yaxis.convert(y)*self.height)
364 def vpos_pt(self, vx, vy):
365 return (self.xpos_pt + vx*self.width_pt,
366 self.ypos_pt + vy*self.height_pt)
368 def vpos(self, vx, vy):
369 return (self.xpos + vx*self.width,
370 self.ypos + vy*self.height)
372 def vgeodesic(self, vx1, vy1, vx2, vy2):
373 """returns a geodesic path between two points in graph coordinates"""
374 return path.line_pt(self.xpos_pt + vx1*self.width_pt,
375 self.ypos_pt + vy1*self.height_pt,
376 self.xpos_pt + vx2*self.width_pt,
377 self.ypos_pt + vy2*self.height_pt)
379 def vgeodesic_el(self, vx1, vy1, vx2, vy2):
380 """returns a geodesic path element between two points in graph coordinates"""
381 return path.lineto_pt(self.xpos_pt + vx2*self.width_pt,
382 self.ypos_pt + vy2*self.height_pt)
384 def vcap_pt(self, coordinate, length_pt, vx, vy):
385 """returns an error cap path for a given coordinate, lengths and
386 point in graph coordinates"""
387 if coordinate == 0:
388 return path.line_pt(self.xpos_pt + vx*self.width_pt - 0.5*length_pt,
389 self.ypos_pt + vy*self.height_pt,
390 self.xpos_pt + vx*self.width_pt + 0.5*length_pt,
391 self.ypos_pt + vy*self.height_pt)
392 elif coordinate == 1:
393 return path.line_pt(self.xpos_pt + vx*self.width_pt,
394 self.ypos_pt + vy*self.height_pt - 0.5*length_pt,
395 self.xpos_pt + vx*self.width_pt,
396 self.ypos_pt + vy*self.height_pt + 0.5*length_pt)
397 else:
398 raise ValueError("direction invalid")
400 def xvgridpath(self, vx):
401 return path.line_pt(self.xpos_pt + vx*self.width_pt, self.ypos_pt,
402 self.xpos_pt + vx*self.width_pt, self.ypos_pt + self.height_pt)
404 def yvgridpath(self, vy):
405 return path.line_pt(self.xpos_pt, self.ypos_pt + vy*self.height_pt,
406 self.xpos_pt + self.width_pt, self.ypos_pt + vy*self.height_pt)
408 def axistrafo(self, axis, t):
409 c = canvas.canvas([t])
410 c.insert(axis.canvas)
411 axis.canvas = c
413 def axisatv(self, axis, v):
414 if axis.positioner.fixtickdirection[0]:
415 # it is a y-axis
416 self.axistrafo(axis, trafo.translate_pt(self.xpos_pt + v*self.width_pt - axis.positioner.x1_pt, 0))
417 else:
418 # it is an x-axis
419 self.axistrafo(axis, trafo.translate_pt(0, self.ypos_pt + v*self.height_pt - axis.positioner.y1_pt))
421 def doaxispositioner(self, axisname):
422 if self.did(self.doaxispositioner, axisname):
423 return
424 self.doranges()
425 if axisname == "x":
426 self.axes["x"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt,
427 self.xpos_pt + self.width_pt, self.ypos_pt,
428 (0, 1), self.xvgridpath))
429 elif axisname == "x2":
430 self.axes["x2"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt + self.height_pt,
431 self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt,
432 (0, -1), self.xvgridpath))
433 elif axisname == "y":
434 self.axes["y"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt,
435 self.xpos_pt, self.ypos_pt + self.height_pt,
436 (1, 0), self.yvgridpath))
437 elif axisname == "y2":
438 self.axes["y2"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt + self.width_pt, self.ypos_pt,
439 self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt,
440 (-1, 0), self.yvgridpath))
441 else:
442 if axisname[1:] == "3":
443 dependsonaxisname = axisname[0]
444 else:
445 dependsonaxisname = "%s%d" % (axisname[0], int(axisname[1:]) - 2)
446 self.doaxiscreate(dependsonaxisname)
447 sign = 2*(int(axisname[1:]) % 2) - 1
448 if axisname[0] == "x":
449 y_pt = self.axes[dependsonaxisname].positioner.y1_pt - sign * (self.axes[dependsonaxisname].canvas.extent_pt + self.axesdist_pt)
450 self.axes[axisname].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, y_pt,
451 self.xpos_pt + self.width_pt, y_pt,
452 (0, sign), self.xvgridpath))
453 else:
454 x_pt = self.axes[dependsonaxisname].positioner.x1_pt - sign * (self.axes[dependsonaxisname].canvas.extent_pt + self.axesdist_pt)
455 self.axes[axisname].setpositioner(positioner.lineaxispos_pt(x_pt, self.ypos_pt,
456 x_pt, self.ypos_pt + self.height_pt,
457 (sign, 0), self.yvgridpath))
459 def dolayout(self):
460 if self.did(self.dolayout):
461 return
462 for axisname in self.axes.keys():
463 self.doaxiscreate(axisname)
464 if self.xaxisat is not None:
465 self.axisatv(self.axes["x"], self.axes["y"].convert(self.xaxisat))
466 if self.yaxisat is not None:
467 self.axisatv(self.axes["y"], self.axes["x"].convert(self.yaxisat))
469 def dobackground(self):
470 if self.did(self.dobackground):
471 return
472 if self.backgroundattrs is not None:
473 self.draw(path.rect_pt(self.xpos_pt, self.ypos_pt, self.width_pt, self.height_pt),
474 self.backgroundattrs)
476 def doaxes(self):
477 if self.did(self.doaxes):
478 return
479 self.dolayout()
480 self.dobackground()
481 for axis in self.axes.values():
482 self.insert(axis.canvas)
484 def dokey(self):
485 if self.did(self.dokey):
486 return
487 self.dobackground()
488 self.dostyles()
489 if self.key is not None:
490 c = self.key.paint(self.plotitems)
491 bbox = c.bbox()
492 def parentchildalign(pmin, pmax, cmin, cmax, pos, dist, inside):
493 ppos = pmin+0.5*(cmax-cmin)+dist+pos*(pmax-pmin-cmax+cmin-2*dist)
494 cpos = 0.5*(cmin+cmax)+(1-inside)*(1-2*pos)*(cmax-cmin+2*dist)
495 return ppos-cpos
496 if bbox:
497 x = parentchildalign(self.xpos_pt, self.xpos_pt+self.width_pt,
498 bbox.llx_pt, bbox.urx_pt,
499 self.key.hpos, unit.topt(self.key.hdist), self.key.hinside)
500 y = parentchildalign(self.ypos_pt, self.ypos_pt+self.height_pt,
501 bbox.lly_pt, bbox.ury_pt,
502 self.key.vpos, unit.topt(self.key.vdist), self.key.vinside)
503 self.insert(c, [trafo.translate_pt(x, y)])
506 class graphxyz(graphxy):
508 def __init__(self, xpos=0, ypos=0, width=None, height=None, depth=None,
509 phi=30, theta=30, distance=1,
510 key=None, backgroundattrs=None, **axes):
511 graph.__init__(self)
513 self.xpos = xpos
514 self.ypos = ypos
515 self.xpos_pt = unit.topt(xpos)
516 self.ypos_pt = unit.topt(ypos)
517 self.width = width
518 self.height = height
519 self.depth = depth
520 self.width_pt = unit.topt(width)
521 self.height_pt = unit.topt(height)
522 self.depth_pt = unit.topt(depth)
523 self.key = key
524 self.backgroundattrs = backgroundattrs
526 if self.width_pt <= 0: raise ValueError("width < 0")
527 if self.height_pt <= 0: raise ValueError("height < 0")
528 if self.depth_pt <= 0: raise ValueError("height < 0")
529 self.distance_pt = distance*math.sqrt(self.width_pt*self.width_pt+
530 self.height_pt*self.height_pt+
531 self.depth_pt*self.depth_pt)
532 phi *= -math.pi/180
533 theta *= math.pi/180
534 self.a = (-math.sin(phi), math.cos(phi), 0)
535 self.b = (-math.cos(phi)*math.sin(theta),
536 -math.sin(phi)*math.sin(theta),
537 math.cos(theta))
538 self.eye = (self.distance_pt*math.cos(phi)*math.cos(theta),
539 self.distance_pt*math.sin(phi)*math.cos(theta),
540 self.distance_pt*math.sin(theta))
542 for axisname, aaxis in axes.items():
543 if aaxis is not None:
544 if not isinstance(aaxis, axis.linkedaxis):
545 self.axes[axisname] = axis.anchoredaxis(aaxis, self.texrunner, axisname)
546 else:
547 self.axes[axisname] = aaxis
549 for axisname in ["x", "y", "z"]:
550 if not axes.has_key(axisname):
551 self.axes[axisname] = axis.anchoredaxis(axis.linear(), self.texrunner, axisname)
553 if self.axes.has_key("x"):
554 self.xbasepath = self.axes["x"].basepath
555 self.xvbasepath = self.axes["x"].vbasepath
556 self.xgridpath = self.axes["x"].gridpath
557 self.xtickpoint_pt = self.axes["x"].tickpoint_pt
558 self.xtickpoint = self.axes["x"].tickpoint
559 self.xvtickpoint_pt = self.axes["x"].vtickpoint_pt
560 self.xvtickpoint = self.axes["x"].tickpoint
561 self.xtickdirection = self.axes["x"].tickdirection
562 self.xvtickdirection = self.axes["x"].vtickdirection
564 if self.axes.has_key("y"):
565 self.ybasepath = self.axes["y"].basepath
566 self.yvbasepath = self.axes["y"].vbasepath
567 self.ygridpath = self.axes["y"].gridpath
568 self.ytickpoint_pt = self.axes["y"].tickpoint_pt
569 self.ytickpoint = self.axes["y"].tickpoint
570 self.yvtickpoint_pt = self.axes["y"].vtickpoint_pt
571 self.yvtickpoint = self.axes["y"].tickpoint
572 self.ytickdirection = self.axes["y"].tickdirection
573 self.yvtickdirection = self.axes["y"].vtickdirection
575 if self.axes.has_key("z"):
576 self.zbasepath = self.axes["z"].basepath
577 self.zvbasepath = self.axes["z"].vbasepath
578 self.zgridpath = self.axes["z"].gridpath
579 self.ztickpoint_pt = self.axes["z"].tickpoint_pt
580 self.ztickpoint = self.axes["z"].tickpoint
581 self.zvtickpoint_pt = self.axes["z"].vtickpoint_pt
582 self.zvtickpoint = self.axes["z"].tickpoint
583 self.ztickdirection = self.axes["z"].tickdirection
584 self.zvtickdirection = self.axes["z"].vtickdirection
586 self.axesnames = ([], [], [])
587 for axisname, aaxis in self.axes.items():
588 if axisname[0] not in "xyz" or (len(axisname) != 1 and (not axisname[1:].isdigit() or
589 axisname[1:] == "1")):
590 raise ValueError("invalid axis name")
591 if axisname[0] == "x":
592 self.axesnames[0].append(axisname)
593 elif axisname[0] == "y":
594 self.axesnames[1].append(axisname)
595 else:
596 self.axesnames[2].append(axisname)
597 aaxis.setcreatecall(self.doaxiscreate, axisname)
599 def pos_pt(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
600 if xaxis is None:
601 xaxis = self.axes["x"]
602 if yaxis is None:
603 yaxis = self.axes["y"]
604 if zaxis is None:
605 zaxis = self.axes["z"]
606 return self.vpos_pt(xaxis.convert(x), yaxis.convert(y), zaxis.convert(y))
608 def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
609 if xaxis is None:
610 xaxis = self.axes["x"]
611 if yaxis is None:
612 yaxis = self.axes["y"]
613 if zaxis is None:
614 zaxis = self.axes["z"]
615 return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(y))
617 def vpos_pt(self, vx, vy, vz):
618 x, y, z = (vx - 0.5)*self.depth_pt, (vy - 0.5)*self.width_pt, (vz - 0.5)*self.height_pt
619 d0 = float(self.a[0]*self.b[1]*(z-self.eye[2])
620 + self.a[2]*self.b[0]*(y-self.eye[1])
621 + self.a[1]*self.b[2]*(x-self.eye[0])
622 - self.a[2]*self.b[1]*(x-self.eye[0])
623 - self.a[0]*self.b[2]*(y-self.eye[1])
624 - self.a[1]*self.b[0]*(z-self.eye[2]))
625 da = (self.eye[0]*self.b[1]*(z-self.eye[2])
626 + self.eye[2]*self.b[0]*(y-self.eye[1])
627 + self.eye[1]*self.b[2]*(x-self.eye[0])
628 - self.eye[2]*self.b[1]*(x-self.eye[0])
629 - self.eye[0]*self.b[2]*(y-self.eye[1])
630 - self.eye[1]*self.b[0]*(z-self.eye[2]))
631 db = (self.a[0]*self.eye[1]*(z-self.eye[2])
632 + self.a[2]*self.eye[0]*(y-self.eye[1])
633 + self.a[1]*self.eye[2]*(x-self.eye[0])
634 - self.a[2]*self.eye[1]*(x-self.eye[0])
635 - self.a[0]*self.eye[2]*(y-self.eye[1])
636 - self.a[1]*self.eye[0]*(z-self.eye[2]))
637 return da/d0 + self.xpos_pt, db/d0 + self.ypos_pt
639 def vpos(self, vx, vy, vz):
640 x_pt, y_pt = self.vpos_pt(vx, vy, vz)
641 return unit.t_pt(x_pt), unit.t_pt(y_pt)
643 def xbaseline(self, axis, x1, x2, xaxis=None):
644 if xaxis is None: xaxis = self.axes["x"]
645 return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2))
647 def ybaseline(self, axis, y1, y2, yaxis=None):
648 if yaxis is None: yaxis = self.axes["y"]
649 return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2))
651 def zbaseline(self, axis, z1, z2, zaxis=None):
652 if zaxis is None: zaxis = self.axes["z"]
653 return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2))
655 def vxbaseline(self, axis, v1, v2):
656 return (path.line_pt(*(self.vpos_pt(v1, 0, 0) + self.vpos_pt(v2, 0, 0))) +
657 path.line_pt(*(self.vpos_pt(v1, 0, 1) + self.vpos_pt(v2, 0, 1))) +
658 path.line_pt(*(self.vpos_pt(v1, 1, 1) + self.vpos_pt(v2, 1, 1))) +
659 path.line_pt(*(self.vpos_pt(v1, 1, 0) + self.vpos_pt(v2, 1, 0))))
661 def vybaseline(self, axis, v1, v2):
662 return (path.line_pt(*(self.vpos_pt(0, v1, 0) + self.vpos_pt(0, v2, 0))) +
663 path.line_pt(*(self.vpos_pt(0, v1, 1) + self.vpos_pt(0, v2, 1))) +
664 path.line_pt(*(self.vpos_pt(1, v1, 1) + self.vpos_pt(1, v2, 1))) +
665 path.line_pt(*(self.vpos_pt(1, v1, 0) + self.vpos_pt(1, v2, 0))))
667 def vzbaseline(self, axis, v1, v2):
668 return (path.line_pt(*(self.vpos_pt(0, 0, v1) + self.vpos_pt(0, 0, v2))) +
669 path.line_pt(*(self.vpos_pt(0, 1, v1) + self.vpos_pt(0, 1, v2))) +
670 path.line_pt(*(self.vpos_pt(1, 1, v1) + self.vpos_pt(1, 1, v2))) +
671 path.line_pt(*(self.vpos_pt(1, 0, v1) + self.vpos_pt(1, 0, v2))))
673 def vgeodesic(self, vx1, vy1, vz1, vx2, vy2, vz2):
674 """returns a geodesic path between two points in graph coordinates"""
675 return path.line_pt(*(self.vpos_pt(vx1, vy1, vz1) + self.vpos_pt(vx2, vy2, vz2)))
677 def vgeodesic_el(self, vx1, vy1, vz1, vx2, vy2, vz2):
678 """returns a geodesic path element between two points in graph coordinates"""
679 return path.lineto_pt(*(self.vpos_pt(vx1, vy1, vz1) + self.vpos_pt(vx2, vy2, vz2)))
681 def vcap_pt(self, coordinate, length_pt, vx, vy, vz):
682 """returns an error cap path for a given coordinate, lengths and
683 point in graph coordinates"""
684 raise NotImplementedError
685 if coordinate == 0:
686 return path.line_pt(self.xpos_pt + vx*self.width_pt - 0.5*length_pt,
687 self.ypos_pt + vy*self.height_pt,
688 self.xpos_pt + vx*self.width_pt + 0.5*length_pt,
689 self.ypos_pt + vy*self.height_pt)
690 elif coordinate == 1:
691 return path.line_pt(self.xpos_pt + vx*self.width_pt,
692 self.ypos_pt + vy*self.height_pt - 0.5*length_pt,
693 self.xpos_pt + vx*self.width_pt,
694 self.ypos_pt + vy*self.height_pt + 0.5*length_pt)
695 else:
696 raise ValueError("direction invalid")
698 def xvgridpath(self, vx):
699 raise NotImplementedError
701 def yvgridpath(self, vy):
702 raise NotImplementedError
704 def zvgridpath(self, vz):
705 raise NotImplementedError
707 def doaxispositioner(self, axisname):
708 if self.did(self.doaxispositioner, axisname):
709 return
710 self.doranges()
711 if axisname == "x":
712 x1_pt, y1_pt = self.vpos_pt(0, 0, 0)
713 x2_pt, y2_pt = self.vpos_pt(1, 0, 0)
714 self.axes["x"].setpositioner(positioner.lineaxispos_pt(x1_pt, y1_pt, x2_pt, y2_pt,
715 (0, 1), self.xvgridpath))
716 elif axisname == "y":
717 x1_pt, y1_pt = self.vpos_pt(0, 0, 0)
718 x2_pt, y2_pt = self.vpos_pt(0, 1, 0)
719 self.axes["y"].setpositioner(positioner.lineaxispos_pt(x1_pt, y1_pt, x2_pt, y2_pt,
720 (0, 1), self.yvgridpath))
721 elif axisname == "z":
722 x1_pt, y1_pt = self.vpos_pt(0, 0, 0)
723 x2_pt, y2_pt = self.vpos_pt(0, 0, 1)
724 self.axes["z"].setpositioner(positioner.lineaxispos_pt(x1_pt, y1_pt, x2_pt, y2_pt,
725 (1, 0), self.yvgridpath))
726 else:
727 raise NotImplementedError
729 def dolayout(self):
730 if self.did(self.dolayout):
731 return
732 for axisname in self.axes.keys():
733 self.doaxiscreate(axisname)
735 def dobackground(self):
736 if self.did(self.dobackground):
737 return
739 def doaxes(self):
740 if self.did(self.doaxes):
741 return
742 self.dolayout()
743 self.dobackground()
744 for axis in self.axes.values():
745 self.insert(axis.canvas)
747 def dokey(self):
748 if self.did(self.dokey):
749 return
750 self.dobackground()
751 self.dostyles()
752 if self.key is not None:
753 c = self.key.paint(self.plotitems)
754 bbox = c.bbox()
755 def parentchildalign(pmin, pmax, cmin, cmax, pos, dist, inside):
756 ppos = pmin+0.5*(cmax-cmin)+dist+pos*(pmax-pmin-cmax+cmin-2*dist)
757 cpos = 0.5*(cmin+cmax)+(1-inside)*(1-2*pos)*(cmax-cmin+2*dist)
758 return ppos-cpos
759 if bbox:
760 x = parentchildalign(self.xpos_pt, self.xpos_pt+self.width_pt,
761 bbox.llx_pt, bbox.urx_pt,
762 self.key.hpos, unit.topt(self.key.hdist), self.key.hinside)
763 y = parentchildalign(self.ypos_pt, self.ypos_pt+self.height_pt,
764 bbox.lly_pt, bbox.ury_pt,
765 self.key.vpos, unit.topt(self.key.vdist), self.key.vinside)
766 self.insert(c, [trafo.translate_pt(x, y)])