bring back some first preliminary graphxyz code
[PyX/mjg.git] / pyx / graph / graph.py
blob780bdbc69bdb05d5829cfbccee76df6fa7d3b38f
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.diddata = 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.diddata:
168 raise RuntimeError("can't plot further data after dodata() 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 dodata(self):
223 if self.did(self.dodata):
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 for plotitem in self.plotitems:
247 plotitem.draw(self)
249 self.diddata = 1
251 def dokey(self):
252 raise NotImplementedError
254 def finish(self):
255 self.dobackground()
256 self.doaxes()
257 self.dodata()
258 self.dokey()
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):
266 graph.__init__(self)
268 self.xpos = xpos
269 self.ypos = ypos
270 self.xpos_pt = unit.topt(self.xpos)
271 self.ypos_pt = unit.topt(self.ypos)
272 self.xaxisat = xaxisat
273 self.yaxisat = yaxisat
274 self.key = key
275 self.backgroundattrs = backgroundattrs
276 self.axesdist_pt = unit.topt(axesdist)
278 self.width = width
279 self.height = height
280 if width is None:
281 if height is None:
282 raise ValueError("specify width and/or height")
283 else:
284 self.width = ratio * self.height
285 elif height is None:
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)
294 else:
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)
302 else:
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)
336 else:
337 self.axesnames[1].append(axisname)
338 aaxis.setcreatecall(self.doaxiscreate, axisname)
341 def pos_pt(self, x, y, xaxis=None, yaxis=None):
342 if xaxis is None:
343 xaxis = self.axes["x"]
344 if yaxis is None:
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):
350 if xaxis is None:
351 xaxis = self.axes["x"]
352 if yaxis is None:
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"""
380 if coordinate == 0:
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)
390 else:
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)
404 axis.canvas = c
406 def axisatv(self, axis, v):
407 if axis.positioner.fixtickdirection[0]:
408 # it is a y-axis
409 self.axistrafo(axis, trafo.translate_pt(self.xpos_pt + v*self.width_pt - axis.positioner.x1_pt, 0))
410 else:
411 # it is an x-axis
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):
416 return
417 self.doranges()
418 if axisname == "x":
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))
434 else:
435 if axisname[1:] == "3":
436 dependsonaxisname = axisname[0]
437 else:
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))
446 else:
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))
452 def dolayout(self):
453 if self.did(self.dolayout):
454 return
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):
464 return
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)
469 def doaxes(self):
470 if self.did(self.doaxes):
471 return
472 self.dolayout()
473 self.dobackground()
474 for axis in self.axes.values():
475 self.insert(axis.canvas)
477 def dokey(self):
478 if self.did(self.dokey):
479 return
480 self.dobackground()
481 self.dodata()
482 if self.key is not None:
483 c = self.key.paint(self.plotitems)
484 bbox = c.bbox()
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)
488 return ppos-cpos
489 if bbox:
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):
504 graph.__init__(self)
506 self.xpos = xpos
507 self.ypos = ypos
508 self.xpos_pt = unit.topt(xpos)
509 self.ypos_pt = unit.topt(ypos)
510 self.width = width
511 self.height = height
512 self.depth = depth
513 self.width_pt = unit.topt(width)
514 self.height_pt = unit.topt(height)
515 self.depth_pt = unit.topt(depth)
516 self.key = key
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)
525 phi *= -math.pi/180
526 theta *= math.pi/180
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),
530 math.cos(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)
539 else:
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)
588 else:
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):
593 if xaxis is None:
594 xaxis = self.axes["x"]
595 if yaxis is None:
596 yaxis = self.axes["y"]
597 if zaxis is None:
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):
602 if xaxis is None:
603 xaxis = self.axes["x"]
604 if yaxis is None:
605 yaxis = self.axes["y"]
606 if zaxis is None:
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
678 if coordinate == 0:
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)
688 else:
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):
702 return
703 self.doranges()
704 if axisname == "x":
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))
719 else:
720 raise NotImplementedError
722 def dolayout(self):
723 if self.did(self.dolayout):
724 return
725 for axisname in self.axes.keys():
726 self.doaxiscreate(axisname)
728 def dobackground(self):
729 if self.did(self.dobackground):
730 return
732 def doaxes(self):
733 if self.did(self.doaxes):
734 return
735 self.dolayout()
736 self.dobackground()
737 for axis in self.axes.values():
738 self.insert(axis.canvas)
740 def dokey(self):
741 if self.did(self.dokey):
742 return
743 self.dobackground()
744 self.dodata()
745 if self.key is not None:
746 c = self.key.paint(self.plotitems)
747 bbox = c.bbox()
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)
751 return ppos-cpos
752 if bbox:
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)])