dtk article sources
[PyX/mjg.git] / pyx / graph / graph.py
blobba461da481104c2a27cc3ef437d8431e6620e988
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2004 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
7 # Copyright (C) 2002-2005 André Wobst <wobsta@users.sourceforge.net>
9 # This file is part of PyX (http://pyx.sourceforge.net/).
11 # PyX is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # PyX is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with PyX; if not, write to the Free Software
23 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
26 import math, re, string
27 from pyx import canvas, path, trafo, unit
28 from pyx.graph import style
29 from pyx.graph.axis import axis, positioner
32 goldenmean = 0.5 * (math.sqrt(5) + 1)
35 class styledata:
36 """style data storage class
38 Instances of this class are used to store data from the styles
39 and to pass point data to the styles by instances named privatedata
40 and sharedata. sharedata is shared between all the style(s) in use
41 by a data instance, while privatedata is private to each style and
42 used as a storage place instead of self to prevent side effects when
43 using a style several times."""
44 pass
47 class plotitem:
49 def __init__(self, graph, data, styles):
50 self.data = data
51 self.title = data.title
53 # add styles to ensure all needs of the given styles
54 provided = [] # already provided sharedata variables
55 addstyles = [] # a list of style instances to be added in front
56 for s in styles:
57 for n in s.needsdata:
58 if n not in provided:
59 defaultprovider = style.getdefaultprovider(n)
60 addstyles.append(defaultprovider)
61 provided.extend(defaultprovider.providesdata)
62 provided.extend(s.providesdata)
64 self.styles = addstyles + styles
65 self.sharedata = styledata()
66 self.privatedatalist = [styledata() for s in self.styles]
68 # perform setcolumns to all styles
69 columnnames = self.data.columnnames(graph)
70 self.usedcolumnnames = []
71 for privatedata, s in zip(self.privatedatalist, self.styles):
72 self.usedcolumnnames.extend(s.columnnames(privatedata, self.sharedata, graph, columnnames))
74 def selectstyles(self, graph, selectindex, selecttotal):
75 for privatedata, style in zip(self.privatedatalist, self.styles):
76 style.selectstyle(privatedata, self.sharedata, graph, selectindex, selecttotal)
78 def adjustaxesstatic(self, graph):
79 for columnname, data in self.data.columns.items():
80 for privatedata, style in zip(self.privatedatalist, self.styles):
81 style.adjustaxis(privatedata, self.sharedata, graph, columnname, data)
83 def makedynamicdata(self, graph):
84 self.dynamiccolumns = self.data.dynamiccolumns(graph)
86 def adjustaxesdynamic(self, graph):
87 for columnname, data in self.dynamiccolumns.items():
88 for privatedata, style in zip(self.privatedatalist, self.styles):
89 style.adjustaxis(privatedata, self.sharedata, graph, columnname, data)
91 def draw(self, graph):
92 for privatedata, style in zip(self.privatedatalist, self.styles):
93 style.initdrawpoints(privatedata, self.sharedata, graph)
94 point = {}
95 useitems = []
96 for columnname in self.usedcolumnnames:
97 try:
98 useitems.append((columnname, self.data.columns[columnname]))
99 except KeyError:
100 useitems.append((columnname, self.dynamiccolumns[columnname]))
101 if not useitems:
102 raise ValueError("cannot draw empty data")
103 for i in xrange(len(useitems[0][1])):
104 for columnname, data in useitems:
105 point[columnname] = data[i]
106 for privatedata, style in zip(self.privatedatalist, self.styles):
107 style.drawpoint(privatedata, self.sharedata, graph, point)
108 for privatedata, style in zip(self.privatedatalist, self.styles):
109 style.donedrawpoints(privatedata, self.sharedata, graph)
111 def key_pt(self, graph, x_pt, y_pt, width_pt, height_pt):
112 for privatedata, style in zip(self.privatedatalist, self.styles):
113 style.key_pt(privatedata, self.sharedata, graph, x_pt, y_pt, width_pt, height_pt)
115 def __getattr__(self, attr):
116 # read only access to the styles privatedata
117 stylesdata = [getattr(styledata, attr)
118 for styledata in self.privatedatalist
119 if hasattr(styledata, attr)]
120 if len(stylesdata) > 1:
121 return stylesdata
122 elif len(stylesdata) == 1:
123 return stylesdata[0]
124 raise AttributeError("access to styledata attribute '%s' failed" % attr)
127 class graph(canvas.canvas):
129 def __init__(self):
130 canvas.canvas.__init__(self)
131 self.axes = {}
132 self.plotitems = []
133 self._calls = {}
134 self.didranges = 0
135 self.diddata = 0
137 def did(self, method, *args, **kwargs):
138 if not self._calls.has_key(method):
139 self._calls[method] = []
140 for callargs in self._calls[method]:
141 if callargs == (args, kwargs):
142 return 1
143 self._calls[method].append((args, kwargs))
144 return 0
146 def bbox(self):
147 self.finish()
148 return canvas.canvas.bbox(self)
150 def registerPS(self, registry):
151 self.finish()
152 canvas.canvas.registerPS(self, registry)
154 def registerPDF(self, registry):
155 self.finish()
156 canvas.canvas.registerPDF(self, registry)
158 def outputPS(self, file, writer, context):
159 self.finish()
160 canvas.canvas.outputPS(self, file, writer, context)
162 def outputPDF(self, file, writer, context):
163 self.finish()
164 canvas.canvas.outputPDF(self, file, writer, context)
166 def plot(self, data, styles=None, rangewarning=1):
167 if self.didranges and rangewarnings:
168 raise warnings.warn("axes ranges have already been analysed; no further adjustments will be performed")
169 if self.diddata:
170 raise RuntimeError("can't add further data while data has already been processed")
171 singledata = 0
172 try:
173 for d in data:
174 pass
175 except:
176 usedata = [data]
177 singledata = 1
178 else:
179 usedata = data
180 if styles is None:
181 for d in usedata:
182 if styles is None:
183 styles = d.defaultstyles
184 elif styles != d.defaultstyles:
185 raise RuntimeError("defaultstyles differ")
186 plotitems = []
187 for d in usedata:
188 plotitems.append(plotitem(self, d, styles))
189 self.plotitems.extend(plotitems)
190 if singledata:
191 return plotitems[0]
192 else:
193 return plotitems
195 def doranges(self):
196 if self.did(self.doranges):
197 return
198 for plotitem in self.plotitems:
199 plotitem.adjustaxesstatic(self)
200 for plotitem in self.plotitems:
201 plotitem.makedynamicdata(self)
202 for plotitem in self.plotitems:
203 plotitem.adjustaxesdynamic(self)
204 self.didranges = 1
206 def dodata(self):
207 if self.did(self.dodata):
208 return
209 self.dolayout()
210 self.dobackground()
212 # count the usage of styles and perform selects
213 styletotal = {}
214 def stylesid(styles):
215 return ":".join([str(id(style)) for style in styles])
216 for plotitem in self.plotitems:
217 try:
218 styletotal[stylesid(plotitem.styles)] += 1
219 except:
220 styletotal[stylesid(plotitem.styles)] = 1
221 styleindex = {}
222 for plotitem in self.plotitems:
223 try:
224 styleindex[stylesid(plotitem.styles)] += 1
225 except:
226 styleindex[stylesid(plotitem.styles)] = 0
227 plotitem.selectstyles(self, styleindex[stylesid(plotitem.styles)],
228 styletotal[stylesid(plotitem.styles)])
230 for plotitem in self.plotitems:
231 plotitem.draw(self)
233 self.diddata = 1
236 class graphxy(graph):
238 def pos_pt(self, x, y, xaxis=None, yaxis=None):
239 if xaxis is None:
240 xaxis = self.axes["x"]
241 if yaxis is None:
242 yaxis = self.axes["y"]
243 return self.xpos_pt + xaxis.convert(x)*self.width_pt, self.ypos_pt + yaxis.convert(y)*self.height_pt
245 def pos(self, x, y, xaxis=None, yaxis=None):
246 if xaxis is None:
247 xaxis = self.axes["x"]
248 if yaxis is None:
249 yaxis = self.axes["y"]
250 return self.xpos + xaxis.convert(x)*self.width, self.ypos + yaxis.convert(y)*self.height
252 def vpos_pt(self, vx, vy):
253 return self.xpos_pt + vx*self.width_pt, self.ypos_pt + vy*self.height_pt
255 def vpos(self, vx, vy):
256 return self.xpos + vx*self.width, self.ypos + vy*self.height
258 def vgeodesic(self, vx1, vy1, vx2, vy2):
259 """returns a geodesic path between two points in graph coordinates"""
260 return path.line_pt(self.xpos_pt + vx1*self.width_pt,
261 self.ypos_pt + vy1*self.height_pt,
262 self.xpos_pt + vx2*self.width_pt,
263 self.ypos_pt + vy2*self.height_pt)
265 def vgeodesic_el(self, vx1, vy1, vx2, vy2):
266 """returns a geodesic path element between two points in graph coordinates"""
267 return path.lineto_pt(self.xpos_pt + vx2*self.width_pt,
268 self.ypos_pt + vy2*self.height_pt)
270 def vcap_pt(self, coordinate, length_pt, vx, vy):
271 """returns an error cap path for a given coordinate, lengths and
272 point in graph coordinates"""
273 if coordinate == 0:
274 return path.line_pt(self.xpos_pt + vx*self.width_pt - 0.5*length_pt,
275 self.ypos_pt + vy*self.height_pt,
276 self.xpos_pt + vx*self.width_pt + 0.5*length_pt,
277 self.ypos_pt + vy*self.height_pt)
278 elif coordinate == 1:
279 return path.line_pt(self.xpos_pt + vx*self.width_pt,
280 self.ypos_pt + vy*self.height_pt - 0.5*length_pt,
281 self.xpos_pt + vx*self.width_pt,
282 self.ypos_pt + vy*self.height_pt + 0.5*length_pt)
283 else:
284 raise ValueError("direction invalid")
286 def xvgridpath(self, vx):
287 return path.line_pt(self.xpos_pt + vx*self.width_pt, self.ypos_pt,
288 self.xpos_pt + vx*self.width_pt, self.ypos_pt + self.height_pt)
290 def yvgridpath(self, vy):
291 return path.line_pt(self.xpos_pt, self.ypos_pt + vy*self.height_pt,
292 self.xpos_pt + self.width_pt, self.ypos_pt + vy*self.height_pt)
294 def axistrafo(self, axis, t):
295 c = canvas.canvas([t])
296 c.insert(axis.canvas)
297 axis.canvas = c
299 def axisatv(self, axis, v):
300 if axis.positioner.fixtickdirection[0]:
301 # it is a y-axis
302 self.axistrafo(axis, trafo.translate_pt(self.xpos_pt + v*self.width_pt - axis.positioner.x1_pt, 0))
303 else:
304 # it is an x-axis
305 self.axistrafo(axis, trafo.translate_pt(0, self.ypos_pt + v*self.height_pt - axis.positioner.y1_pt))
307 def doaxispositioner(self, axisname):
308 if self.did(self.doaxispositioner, axisname):
309 return
310 self.doranges()
311 if axisname == "x":
312 self.axes["x"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt,
313 self.xpos_pt + self.width_pt, self.ypos_pt,
314 (0, 1), self.xvgridpath))
315 elif axisname == "x2":
316 self.axes["x2"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt + self.height_pt,
317 self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt,
318 (0, -1), self.xvgridpath))
319 elif axisname == "y":
320 self.axes["y"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt,
321 self.xpos_pt, self.ypos_pt + self.height_pt,
322 (1, 0), self.yvgridpath))
323 elif axisname == "y2":
324 self.axes["y2"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt + self.width_pt, self.ypos_pt,
325 self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt,
326 (-1, 0), self.yvgridpath))
327 else:
328 if axisname[1:] == "3":
329 dependsonaxisname = axisname[0]
330 else:
331 dependsonaxisname = "%s%d" % (axisname[0], int(axisname[1:]) - 2)
332 self.doaxiscreate(dependsonaxisname)
333 sign = 2*(int(axisname[1:]) % 2) - 1
334 if axisname[0] == "x":
335 y_pt = self.axes[dependsonaxisname].positioner.y1_pt - sign * (self.axes[dependsonaxisname].canvas.extent_pt + self.axesdist_pt)
336 self.axes[axisname].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, y_pt,
337 self.xpos_pt + self.width_pt, y_pt,
338 (0, sign), self.xvgridpath))
339 else:
340 x_pt = self.axes[dependsonaxisname].positioner.x1_pt - sign * (self.axes[dependsonaxisname].canvas.extent_pt + self.axesdist_pt)
341 self.axes[axisname].setpositioner(positioner.lineaxispos_pt(x_pt, self.ypos_pt,
342 x_pt, self.ypos_pt + self.height_pt,
343 (sign, 0), self.yvgridpath))
345 def doaxiscreate(self, axisname):
346 if self.did(self.doaxiscreate, axisname):
347 return
348 self.doaxispositioner(axisname)
349 self.axes[axisname].create(self.texrunner)
351 def dolayout(self):
352 if self.did(self.dolayout):
353 return
354 for axisname in self.axes.keys():
355 self.doaxiscreate(axisname)
356 if self.xaxisat is not None:
357 self.axisatv(self.axes["x"], self.axes["y"].convert(self.xaxisat))
358 if self.yaxisat is not None:
359 self.axisatv(self.axes["y"], self.axes["x"].convert(self.yaxisat))
361 def dobackground(self):
362 if self.did(self.dobackground):
363 return
364 if self.backgroundattrs is not None:
365 self.draw(path.rect_pt(self.xpos_pt, self.ypos_pt, self.width_pt, self.height_pt),
366 self.backgroundattrs)
368 def doaxes(self):
369 if self.did(self.doaxes):
370 return
371 self.dolayout()
372 self.dobackground()
373 for axis in self.axes.values():
374 self.insert(axis.canvas)
376 def dokey(self):
377 if self.did(self.dokey):
378 return
379 self.dodata()
380 self.dobackground()
381 if self.key is not None:
382 c = self.key.paint(self.plotitems)
383 bbox = c.bbox()
384 def parentchildalign(pmin, pmax, cmin, cmax, pos, dist, inside):
385 ppos = pmin+0.5*(cmax-cmin)+dist+pos*(pmax-pmin-cmax+cmin-2*dist)
386 cpos = 0.5*(cmin+cmax)+(1-inside)*(1-2*pos)*(cmax-cmin+2*dist)
387 return ppos-cpos
388 x = parentchildalign(self.xpos_pt, self.xpos_pt+self.width_pt,
389 bbox.llx_pt, bbox.urx_pt,
390 self.key.hpos, unit.topt(self.key.hdist), self.key.hinside)
391 y = parentchildalign(self.ypos_pt, self.ypos_pt+self.height_pt,
392 bbox.lly_pt, bbox.ury_pt,
393 self.key.vpos, unit.topt(self.key.vdist), self.key.vinside)
394 self.insert(c, [trafo.translate_pt(x, y)])
396 def finish(self):
397 self.doaxes()
398 self.dodata()
399 self.dokey()
401 def __init__(self, xpos=0, ypos=0, width=None, height=None, ratio=goldenmean,
402 key=None, backgroundattrs=None, axesdist=0.8*unit.v_cm,
403 xaxisat=None, yaxisat=None, **axes):
404 graph.__init__(self)
406 self.xpos = xpos
407 self.ypos = ypos
408 self.xpos_pt = unit.topt(self.xpos)
409 self.ypos_pt = unit.topt(self.ypos)
410 self.xaxisat = xaxisat
411 self.yaxisat = yaxisat
412 self.key = key
413 self.backgroundattrs = backgroundattrs
414 self.axesdist_pt = unit.topt(axesdist)
416 self.width = width
417 self.height = height
418 if width is None:
419 if height is None:
420 raise ValueError("specify width and/or height")
421 else:
422 self.width = ratio * self.height
423 elif height is None:
424 self.height = (1.0/ratio) * self.width
425 self.width_pt = unit.topt(self.width)
426 self.height_pt = unit.topt(self.height)
428 for axisname, aaxis in axes.items():
429 if aaxis is not None:
430 if not isinstance(aaxis, axis.linkedaxis):
431 self.axes[axisname] = axis.anchoredaxis(aaxis, axisname)
432 else:
433 self.axes[axisname] = aaxis
434 for axisname, axisat in [("x", xaxisat), ("y", yaxisat)]:
435 okey = axisname + "2"
436 if not axes.has_key(axisname):
437 if not axes.has_key(okey):
438 self.axes[axisname] = axis.anchoredaxis(axis.linear(), axisname)
439 self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey)
440 else:
441 self.axes[axisname] = axis.linkedaxis(self.axes[okey], axisname)
442 elif not axes.has_key(okey) and axisat is None:
443 self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey)
445 if self.axes.has_key("x"):
446 self.xbasepath = self.axes["x"].basepath
447 self.xvbasepath = self.axes["x"].vbasepath
448 self.xgridpath = self.axes["x"].gridpath
449 self.xtickpoint_pt = self.axes["x"].tickpoint_pt
450 self.xtickpoint = self.axes["x"].tickpoint
451 self.xvtickpoint_pt = self.axes["x"].vtickpoint_pt
452 self.xvtickpoint = self.axes["x"].tickpoint
453 self.xtickdirection = self.axes["x"].tickdirection
454 self.xvtickdirection = self.axes["x"].vtickdirection
456 if self.axes.has_key("y"):
457 self.ybasepath = self.axes["y"].basepath
458 self.yvbasepath = self.axes["y"].vbasepath
459 self.ygridpath = self.axes["y"].gridpath
460 self.ytickpoint_pt = self.axes["y"].tickpoint_pt
461 self.ytickpoint = self.axes["y"].tickpoint
462 self.yvtickpoint_pt = self.axes["y"].vtickpoint_pt
463 self.yvtickpoint = self.axes["y"].tickpoint
464 self.ytickdirection = self.axes["y"].tickdirection
465 self.yvtickdirection = self.axes["y"].vtickdirection
467 self.axesnames = ([], [])
468 for axisname, aaxis in self.axes.items():
469 if axisname[0] not in "xy" or (len(axisname) != 1 and (not axisname[1:].isdigit() or
470 axisname[1:] == "1")):
471 raise ValueError("invalid axis name")
472 if axisname[0] == "x":
473 self.axesnames[0].append(axisname)
474 else:
475 self.axesnames[1].append(axisname)
476 aaxis.setcreatecall(self.doaxiscreate, axisname)
479 # some thoughts, but deferred right now
481 # class graphxyz(graphxy):
483 # axisnames = "x", "y", "z"
485 # def _vxtickpoint(self, axis, v):
486 # return self._vpos(v, axis.vypos, axis.vzpos)
488 # def _vytickpoint(self, axis, v):
489 # return self._vpos(axis.vxpos, v, axis.vzpos)
491 # def _vztickpoint(self, axis, v):
492 # return self._vpos(axis.vxpos, axis.vypos, v)
494 # def vxtickdirection(self, axis, v):
495 # x1, y1 = self._vpos(v, axis.vypos, axis.vzpos)
496 # x2, y2 = self._vpos(v, 0.5, 0)
497 # dx, dy = x1 - x2, y1 - y2
498 # norm = math.hypot(dx, dy)
499 # return dx/norm, dy/norm
501 # def vytickdirection(self, axis, v):
502 # x1, y1 = self._vpos(axis.vxpos, v, axis.vzpos)
503 # x2, y2 = self._vpos(0.5, v, 0)
504 # dx, dy = x1 - x2, y1 - y2
505 # norm = math.hypot(dx, dy)
506 # return dx/norm, dy/norm
508 # def vztickdirection(self, axis, v):
509 # return -1, 0
510 # x1, y1 = self._vpos(axis.vxpos, axis.vypos, v)
511 # x2, y2 = self._vpos(0.5, 0.5, v)
512 # dx, dy = x1 - x2, y1 - y2
513 # norm = math.hypot(dx, dy)
514 # return dx/norm, dy/norm
516 # def _pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
517 # if xaxis is None: xaxis = self.axes["x"]
518 # if yaxis is None: yaxis = self.axes["y"]
519 # if zaxis is None: zaxis = self.axes["z"]
520 # return self._vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
522 # def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
523 # if xaxis is None: xaxis = self.axes["x"]
524 # if yaxis is None: yaxis = self.axes["y"]
525 # if zaxis is None: zaxis = self.axes["z"]
526 # return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
528 # def _vpos(self, vx, vy, vz):
529 # x, y, z = (vx - 0.5)*self._depth, (vy - 0.5)*self._width, (vz - 0.5)*self._height
530 # d0 = float(self.a[0]*self.b[1]*(z-self.eye[2])
531 # + self.a[2]*self.b[0]*(y-self.eye[1])
532 # + self.a[1]*self.b[2]*(x-self.eye[0])
533 # - self.a[2]*self.b[1]*(x-self.eye[0])
534 # - self.a[0]*self.b[2]*(y-self.eye[1])
535 # - self.a[1]*self.b[0]*(z-self.eye[2]))
536 # da = (self.eye[0]*self.b[1]*(z-self.eye[2])
537 # + self.eye[2]*self.b[0]*(y-self.eye[1])
538 # + self.eye[1]*self.b[2]*(x-self.eye[0])
539 # - self.eye[2]*self.b[1]*(x-self.eye[0])
540 # - self.eye[0]*self.b[2]*(y-self.eye[1])
541 # - self.eye[1]*self.b[0]*(z-self.eye[2]))
542 # db = (self.a[0]*self.eye[1]*(z-self.eye[2])
543 # + self.a[2]*self.eye[0]*(y-self.eye[1])
544 # + self.a[1]*self.eye[2]*(x-self.eye[0])
545 # - self.a[2]*self.eye[1]*(x-self.eye[0])
546 # - self.a[0]*self.eye[2]*(y-self.eye[1])
547 # - self.a[1]*self.eye[0]*(z-self.eye[2]))
548 # return da/d0 + self._xpos, db/d0 + self._ypos
550 # def vpos(self, vx, vy, vz):
551 # tx, ty = self._vpos(vx, vy, vz)
552 # return unit.t_pt(tx), unit.t_pt(ty)
554 # def xbaseline(self, axis, x1, x2, xaxis=None):
555 # if xaxis is None: xaxis = self.axes["x"]
556 # return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2))
558 # def ybaseline(self, axis, y1, y2, yaxis=None):
559 # if yaxis is None: yaxis = self.axes["y"]
560 # return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2))
562 # def zbaseline(self, axis, z1, z2, zaxis=None):
563 # if zaxis is None: zaxis = self.axes["z"]
564 # return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2))
566 # def vxbaseline(self, axis, v1, v2):
567 # return (path._line(*(self._vpos(v1, 0, 0) + self._vpos(v2, 0, 0))) +
568 # path._line(*(self._vpos(v1, 0, 1) + self._vpos(v2, 0, 1))) +
569 # path._line(*(self._vpos(v1, 1, 1) + self._vpos(v2, 1, 1))) +
570 # path._line(*(self._vpos(v1, 1, 0) + self._vpos(v2, 1, 0))))
572 # def vybaseline(self, axis, v1, v2):
573 # return (path._line(*(self._vpos(0, v1, 0) + self._vpos(0, v2, 0))) +
574 # path._line(*(self._vpos(0, v1, 1) + self._vpos(0, v2, 1))) +
575 # path._line(*(self._vpos(1, v1, 1) + self._vpos(1, v2, 1))) +
576 # path._line(*(self._vpos(1, v1, 0) + self._vpos(1, v2, 0))))
578 # def vzbaseline(self, axis, v1, v2):
579 # return (path._line(*(self._vpos(0, 0, v1) + self._vpos(0, 0, v2))) +
580 # path._line(*(self._vpos(0, 1, v1) + self._vpos(0, 1, v2))) +
581 # path._line(*(self._vpos(1, 1, v1) + self._vpos(1, 1, v2))) +
582 # path._line(*(self._vpos(1, 0, v1) + self._vpos(1, 0, v2))))
584 # def xgridpath(self, x, xaxis=None):
585 # assert 0
586 # if xaxis is None: xaxis = self.axes["x"]
587 # v = xaxis.convert(x)
588 # return path._line(self._xpos+v*self._width, self._ypos,
589 # self._xpos+v*self._width, self._ypos+self._height)
591 # def ygridpath(self, y, yaxis=None):
592 # assert 0
593 # if yaxis is None: yaxis = self.axes["y"]
594 # v = yaxis.convert(y)
595 # return path._line(self._xpos, self._ypos+v*self._height,
596 # self._xpos+self._width, self._ypos+v*self._height)
598 # def zgridpath(self, z, zaxis=None):
599 # assert 0
600 # if zaxis is None: zaxis = self.axes["z"]
601 # v = zaxis.convert(z)
602 # return path._line(self._xpos, self._zpos+v*self._height,
603 # self._xpos+self._width, self._zpos+v*self._height)
605 # def vxgridpath(self, v):
606 # return path.path(path._moveto(*self._vpos(v, 0, 0)),
607 # path._lineto(*self._vpos(v, 0, 1)),
608 # path._lineto(*self._vpos(v, 1, 1)),
609 # path._lineto(*self._vpos(v, 1, 0)),
610 # path.closepath())
612 # def vygridpath(self, v):
613 # return path.path(path._moveto(*self._vpos(0, v, 0)),
614 # path._lineto(*self._vpos(0, v, 1)),
615 # path._lineto(*self._vpos(1, v, 1)),
616 # path._lineto(*self._vpos(1, v, 0)),
617 # path.closepath())
619 # def vzgridpath(self, v):
620 # return path.path(path._moveto(*self._vpos(0, 0, v)),
621 # path._lineto(*self._vpos(0, 1, v)),
622 # path._lineto(*self._vpos(1, 1, v)),
623 # path._lineto(*self._vpos(1, 0, v)),
624 # path.closepath())
626 # def _addpos(self, x, y, dx, dy):
627 # assert 0
628 # return x+dx, y+dy
630 # def _connect(self, x1, y1, x2, y2):
631 # assert 0
632 # return path._lineto(x2, y2)
634 # def doaxes(self):
635 # self.dolayout()
636 # if not self.removedomethod(self.doaxes): return
637 # axesdist_pt = unit.topt(self.axesdist)
638 # XPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.axisnames[0])
639 # YPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.axisnames[1])
640 # ZPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.axisnames[2])
641 # items = list(self.axes.items())
642 # items.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
643 # for key, axis in items:
644 # num = self.keynum(key)
645 # num2 = 1 - num % 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
646 # num3 = 1 - 2 * (num % 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
647 # if XPattern.match(key):
648 # axis.vypos = 0
649 # axis.vzpos = 0
650 # axis._vtickpoint = self._vxtickpoint
651 # axis.vgridpath = self.vxgridpath
652 # axis.vbaseline = self.vxbaseline
653 # axis.vtickdirection = self.vxtickdirection
654 # elif YPattern.match(key):
655 # axis.vxpos = 0
656 # axis.vzpos = 0
657 # axis._vtickpoint = self._vytickpoint
658 # axis.vgridpath = self.vygridpath
659 # axis.vbaseline = self.vybaseline
660 # axis.vtickdirection = self.vytickdirection
661 # elif ZPattern.match(key):
662 # axis.vxpos = 0
663 # axis.vypos = 0
664 # axis._vtickpoint = self._vztickpoint
665 # axis.vgridpath = self.vzgridpath
666 # axis.vbaseline = self.vzbaseline
667 # axis.vtickdirection = self.vztickdirection
668 # else:
669 # raise ValueError("Axis key '%s' not allowed" % key)
670 # if axis.painter is not None:
671 # axis.dopaint(self)
672 # # if XPattern.match(key):
673 # # self._xaxisextents[num2] += axis._extent
674 # # needxaxisdist[num2] = 1
675 # # if YPattern.match(key):
676 # # self._yaxisextents[num2] += axis._extent
677 # # needyaxisdist[num2] = 1
679 # def __init__(self, tex, xpos=0, ypos=0, width=None, height=None, depth=None,
680 # phi=30, theta=30, distance=1,
681 # backgroundattrs=None, axesdist=0.8*unit.v_cm, **axes):
682 # canvas.canvas.__init__(self)
683 # self.tex = tex
684 # self.xpos = xpos
685 # self.ypos = ypos
686 # self._xpos = unit.topt(xpos)
687 # self._ypos = unit.topt(ypos)
688 # self._width = unit.topt(width)
689 # self._height = unit.topt(height)
690 # self._depth = unit.topt(depth)
691 # self.width = width
692 # self.height = height
693 # self.depth = depth
694 # if self._width <= 0: raise ValueError("width < 0")
695 # if self._height <= 0: raise ValueError("height < 0")
696 # if self._depth <= 0: raise ValueError("height < 0")
697 # self._distance = distance*math.sqrt(self._width*self._width+
698 # self._height*self._height+
699 # self._depth*self._depth)
700 # phi *= -math.pi/180
701 # theta *= math.pi/180
702 # self.a = (-math.sin(phi), math.cos(phi), 0)
703 # self.b = (-math.cos(phi)*math.sin(theta),
704 # -math.sin(phi)*math.sin(theta),
705 # math.cos(theta))
706 # self.eye = (self._distance*math.cos(phi)*math.cos(theta),
707 # self._distance*math.sin(phi)*math.cos(theta),
708 # self._distance*math.sin(theta))
709 # self.initaxes(axes)
710 # self.axesdist = axesdist
711 # self.backgroundattrs = backgroundattrs
713 # self.data = []
714 # self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
715 # self.haslayout = 0
716 # self.defaultstyle = {}
718 # def bbox(self):
719 # self.finish()
720 # return bbox._bbox(self._xpos - 200, self._ypos - 200, self._xpos + 200, self._ypos + 200)