- path module
[PyX/mjg.git] / pyx / graph / graph.py
blob0fc116db58638a8f4824ec81a1589031dd57fb0c
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, warnings
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 self.usedcolumnnames = []
70 for privatedata, s in zip(self.privatedatalist, self.styles):
71 self.usedcolumnnames.extend(s.columnnames(privatedata, self.sharedata, graph, self.data.columnnames))
73 def selectstyles(self, graph, selectindex, selecttotal):
74 for privatedata, style in zip(self.privatedatalist, self.styles):
75 style.selectstyle(privatedata, self.sharedata, graph, selectindex, selecttotal)
77 def adjustaxesstatic(self, graph):
78 for columnname, data in self.data.columns.items():
79 for privatedata, style in zip(self.privatedatalist, self.styles):
80 style.adjustaxis(privatedata, self.sharedata, graph, columnname, data)
82 def makedynamicdata(self, graph):
83 self.dynamiccolumns = self.data.dynamiccolumns(graph)
85 def adjustaxesdynamic(self, graph):
86 for columnname, data in self.dynamiccolumns.items():
87 for privatedata, style in zip(self.privatedatalist, self.styles):
88 style.adjustaxis(privatedata, self.sharedata, graph, columnname, data)
90 def draw(self, graph):
91 for privatedata, style in zip(self.privatedatalist, self.styles):
92 style.initdrawpoints(privatedata, self.sharedata, graph)
93 point = {}
94 useitems = []
95 for columnname in self.usedcolumnnames:
96 try:
97 useitems.append((columnname, self.dynamiccolumns[columnname]))
98 except KeyError:
99 useitems.append((columnname, self.data.columns[columnname]))
100 if not useitems:
101 raise ValueError("cannot draw empty data")
102 for i in xrange(len(useitems[0][1])):
103 for columnname, data in useitems:
104 point[columnname] = data[i]
105 for privatedata, style in zip(self.privatedatalist, self.styles):
106 style.drawpoint(privatedata, self.sharedata, graph, point)
107 for privatedata, style in zip(self.privatedatalist, self.styles):
108 style.donedrawpoints(privatedata, self.sharedata, graph)
110 def key_pt(self, graph, x_pt, y_pt, width_pt, height_pt):
111 for privatedata, style in zip(self.privatedatalist, self.styles):
112 style.key_pt(privatedata, self.sharedata, graph, x_pt, y_pt, width_pt, height_pt)
114 def __getattr__(self, attr):
115 # read only access to the styles privatedata
116 stylesdata = [getattr(styledata, attr)
117 for styledata in self.privatedatalist
118 if hasattr(styledata, attr)]
119 if len(stylesdata) > 1:
120 return stylesdata
121 elif len(stylesdata) == 1:
122 return stylesdata[0]
123 raise AttributeError("access to styledata attribute '%s' failed" % attr)
126 class graph(canvas.canvas):
128 def __init__(self):
129 canvas.canvas.__init__(self)
130 self.axes = {}
131 self.plotitems = []
132 self._calls = {}
133 self.didranges = 0
134 self.diddata = 0
136 def did(self, method, *args, **kwargs):
137 if not self._calls.has_key(method):
138 self._calls[method] = []
139 for callargs in self._calls[method]:
140 if callargs == (args, kwargs):
141 return 1
142 self._calls[method].append((args, kwargs))
143 return 0
145 def bbox(self):
146 self.finish()
147 return canvas.canvas.bbox(self)
149 def registerPS(self, registry):
150 self.finish()
151 canvas.canvas.registerPS(self, registry)
153 def registerPDF(self, registry):
154 self.finish()
155 canvas.canvas.registerPDF(self, registry)
157 def processPS(self, file, writer, context, registry, bbox):
158 self.finish()
159 canvas.canvas.processPS(self, file, writer, context, registry, bbox)
161 def processPDF(self, file, writer, context, registry, bbox):
162 self.finish()
163 canvas.canvas.processPDF(self, file, writer, context, registry, bbox)
165 def plot(self, data, styles=None, rangewarning=1):
166 if self.didranges and rangewarning:
167 warnings.warn("axes ranges have already been analysed; no further adjustments will be performed")
168 if self.diddata:
169 raise RuntimeError("can't add further data while data has already been processed")
170 singledata = 0
171 try:
172 for d in data:
173 pass
174 except:
175 usedata = [data]
176 singledata = 1
177 else:
178 usedata = data
179 if styles is None:
180 for d in usedata:
181 if styles is None:
182 styles = d.defaultstyles
183 elif styles != d.defaultstyles:
184 raise RuntimeError("defaultstyles differ")
185 plotitems = []
186 for d in usedata:
187 plotitems.append(plotitem(self, d, styles))
188 self.plotitems.extend(plotitems)
189 if self.didranges:
190 for aplotitem in plotitems:
191 aplotitem.makedynamicdata(self)
192 if singledata:
193 return plotitems[0]
194 else:
195 return plotitems
197 def doranges(self):
198 if self.did(self.doranges):
199 return
200 for plotitem in self.plotitems:
201 plotitem.adjustaxesstatic(self)
202 for plotitem in self.plotitems:
203 plotitem.makedynamicdata(self)
204 for plotitem in self.plotitems:
205 plotitem.adjustaxesdynamic(self)
206 self.didranges = 1
208 def dolayout(self):
209 raise NotImplementedError
211 def dobackground(self):
212 raise NotImplementedError
214 def doaxes(self):
215 raise NotImplementedError
217 def dodata(self):
218 if self.did(self.dodata):
219 return
220 self.dolayout()
221 self.dobackground()
223 # count the usage of styles and perform selects
224 styletotal = {}
225 def stylesid(styles):
226 return ":".join([str(id(style)) for style in styles])
227 for plotitem in self.plotitems:
228 try:
229 styletotal[stylesid(plotitem.styles)] += 1
230 except:
231 styletotal[stylesid(plotitem.styles)] = 1
232 styleindex = {}
233 for plotitem in self.plotitems:
234 try:
235 styleindex[stylesid(plotitem.styles)] += 1
236 except:
237 styleindex[stylesid(plotitem.styles)] = 0
238 plotitem.selectstyles(self, styleindex[stylesid(plotitem.styles)],
239 styletotal[stylesid(plotitem.styles)])
241 for plotitem in self.plotitems:
242 plotitem.draw(self)
244 self.diddata = 1
246 def dokey(self):
247 raise NotImplementedError
249 def finish(self):
250 self.dobackground()
251 self.doaxes()
252 self.dodata()
253 self.dokey()
256 class graphxy(graph):
258 def __init__(self, xpos=0, ypos=0, width=None, height=None, ratio=goldenmean,
259 key=None, backgroundattrs=None, axesdist=0.8*unit.v_cm,
260 xaxisat=None, yaxisat=None, **axes):
261 graph.__init__(self)
263 self.xpos = xpos
264 self.ypos = ypos
265 self.xpos_pt = unit.topt(self.xpos)
266 self.ypos_pt = unit.topt(self.ypos)
267 self.xaxisat = xaxisat
268 self.yaxisat = yaxisat
269 self.key = key
270 self.backgroundattrs = backgroundattrs
271 self.axesdist_pt = unit.topt(axesdist)
273 self.width = width
274 self.height = height
275 if width is None:
276 if height is None:
277 raise ValueError("specify width and/or height")
278 else:
279 self.width = ratio * self.height
280 elif height is None:
281 self.height = (1.0/ratio) * self.width
282 self.width_pt = unit.topt(self.width)
283 self.height_pt = unit.topt(self.height)
285 for axisname, aaxis in axes.items():
286 if aaxis is not None:
287 if not isinstance(aaxis, axis.linkedaxis):
288 self.axes[axisname] = axis.anchoredaxis(aaxis, self.texrunner, axisname)
289 else:
290 self.axes[axisname] = aaxis
291 for axisname, axisat in [("x", xaxisat), ("y", yaxisat)]:
292 okey = axisname + "2"
293 if not axes.has_key(axisname):
294 if not axes.has_key(okey):
295 self.axes[axisname] = axis.anchoredaxis(axis.linear(), self.texrunner, axisname)
296 self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey)
297 else:
298 self.axes[axisname] = axis.linkedaxis(self.axes[okey], axisname)
299 elif not axes.has_key(okey) and axisat is None:
300 self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey)
302 if self.axes.has_key("x"):
303 self.xbasepath = self.axes["x"].basepath
304 self.xvbasepath = self.axes["x"].vbasepath
305 self.xgridpath = self.axes["x"].gridpath
306 self.xtickpoint_pt = self.axes["x"].tickpoint_pt
307 self.xtickpoint = self.axes["x"].tickpoint
308 self.xvtickpoint_pt = self.axes["x"].vtickpoint_pt
309 self.xvtickpoint = self.axes["x"].tickpoint
310 self.xtickdirection = self.axes["x"].tickdirection
311 self.xvtickdirection = self.axes["x"].vtickdirection
313 if self.axes.has_key("y"):
314 self.ybasepath = self.axes["y"].basepath
315 self.yvbasepath = self.axes["y"].vbasepath
316 self.ygridpath = self.axes["y"].gridpath
317 self.ytickpoint_pt = self.axes["y"].tickpoint_pt
318 self.ytickpoint = self.axes["y"].tickpoint
319 self.yvtickpoint_pt = self.axes["y"].vtickpoint_pt
320 self.yvtickpoint = self.axes["y"].tickpoint
321 self.ytickdirection = self.axes["y"].tickdirection
322 self.yvtickdirection = self.axes["y"].vtickdirection
324 self.axesnames = ([], [])
325 for axisname, aaxis in self.axes.items():
326 if axisname[0] not in "xy" or (len(axisname) != 1 and (not axisname[1:].isdigit() or
327 axisname[1:] == "1")):
328 raise ValueError("invalid axis name")
329 if axisname[0] == "x":
330 self.axesnames[0].append(axisname)
331 else:
332 self.axesnames[1].append(axisname)
333 aaxis.setcreatecall(self.doaxiscreate, axisname)
336 def pos_pt(self, x, y, xaxis=None, yaxis=None):
337 if xaxis is None:
338 xaxis = self.axes["x"]
339 if yaxis is None:
340 yaxis = self.axes["y"]
341 return (self.xpos_pt + xaxis.convert(x)*self.width_pt,
342 self.ypos_pt + yaxis.convert(y)*self.height_pt)
344 def pos(self, x, y, xaxis=None, yaxis=None):
345 if xaxis is None:
346 xaxis = self.axes["x"]
347 if yaxis is None:
348 yaxis = self.axes["y"]
349 return (self.xpos + xaxis.convert(x)*self.width,
350 self.ypos + yaxis.convert(y)*self.height)
352 def vpos_pt(self, vx, vy):
353 return (self.xpos_pt + vx*self.width_pt,
354 self.ypos_pt + vy*self.height_pt)
356 def vpos(self, vx, vy):
357 return (self.xpos + vx*self.width,
358 self.ypos + vy*self.height)
360 def vgeodesic(self, vx1, vy1, vx2, vy2):
361 """returns a geodesic path between two points in graph coordinates"""
362 return path.line_pt(self.xpos_pt + vx1*self.width_pt,
363 self.ypos_pt + vy1*self.height_pt,
364 self.xpos_pt + vx2*self.width_pt,
365 self.ypos_pt + vy2*self.height_pt)
367 def vgeodesic_el(self, vx1, vy1, vx2, vy2):
368 """returns a geodesic path element between two points in graph coordinates"""
369 return path.lineto_pt(self.xpos_pt + vx2*self.width_pt,
370 self.ypos_pt + vy2*self.height_pt)
372 def vcap_pt(self, coordinate, length_pt, vx, vy):
373 """returns an error cap path for a given coordinate, lengths and
374 point in graph coordinates"""
375 if coordinate == 0:
376 return path.line_pt(self.xpos_pt + vx*self.width_pt - 0.5*length_pt,
377 self.ypos_pt + vy*self.height_pt,
378 self.xpos_pt + vx*self.width_pt + 0.5*length_pt,
379 self.ypos_pt + vy*self.height_pt)
380 elif coordinate == 1:
381 return path.line_pt(self.xpos_pt + vx*self.width_pt,
382 self.ypos_pt + vy*self.height_pt - 0.5*length_pt,
383 self.xpos_pt + vx*self.width_pt,
384 self.ypos_pt + vy*self.height_pt + 0.5*length_pt)
385 else:
386 raise ValueError("direction invalid")
388 def xvgridpath(self, vx):
389 return path.line_pt(self.xpos_pt + vx*self.width_pt, self.ypos_pt,
390 self.xpos_pt + vx*self.width_pt, self.ypos_pt + self.height_pt)
392 def yvgridpath(self, vy):
393 return path.line_pt(self.xpos_pt, self.ypos_pt + vy*self.height_pt,
394 self.xpos_pt + self.width_pt, self.ypos_pt + vy*self.height_pt)
396 def axistrafo(self, axis, t):
397 c = canvas.canvas([t])
398 c.insert(axis.canvas)
399 axis.canvas = c
401 def axisatv(self, axis, v):
402 if axis.positioner.fixtickdirection[0]:
403 # it is a y-axis
404 self.axistrafo(axis, trafo.translate_pt(self.xpos_pt + v*self.width_pt - axis.positioner.x1_pt, 0))
405 else:
406 # it is an x-axis
407 self.axistrafo(axis, trafo.translate_pt(0, self.ypos_pt + v*self.height_pt - axis.positioner.y1_pt))
409 def doaxispositioner(self, axisname):
410 if self.did(self.doaxispositioner, axisname):
411 return
412 self.doranges()
413 if axisname == "x":
414 self.axes["x"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt,
415 self.xpos_pt + self.width_pt, self.ypos_pt,
416 (0, 1), self.xvgridpath))
417 elif axisname == "x2":
418 self.axes["x2"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt + self.height_pt,
419 self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt,
420 (0, -1), self.xvgridpath))
421 elif axisname == "y":
422 self.axes["y"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt,
423 self.xpos_pt, self.ypos_pt + self.height_pt,
424 (1, 0), self.yvgridpath))
425 elif axisname == "y2":
426 self.axes["y2"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt + self.width_pt, self.ypos_pt,
427 self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt,
428 (-1, 0), self.yvgridpath))
429 else:
430 if axisname[1:] == "3":
431 dependsonaxisname = axisname[0]
432 else:
433 dependsonaxisname = "%s%d" % (axisname[0], int(axisname[1:]) - 2)
434 self.doaxiscreate(dependsonaxisname)
435 sign = 2*(int(axisname[1:]) % 2) - 1
436 if axisname[0] == "x":
437 y_pt = self.axes[dependsonaxisname].positioner.y1_pt - sign * (self.axes[dependsonaxisname].canvas.extent_pt + self.axesdist_pt)
438 self.axes[axisname].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, y_pt,
439 self.xpos_pt + self.width_pt, y_pt,
440 (0, sign), self.xvgridpath))
441 else:
442 x_pt = self.axes[dependsonaxisname].positioner.x1_pt - sign * (self.axes[dependsonaxisname].canvas.extent_pt + self.axesdist_pt)
443 self.axes[axisname].setpositioner(positioner.lineaxispos_pt(x_pt, self.ypos_pt,
444 x_pt, self.ypos_pt + self.height_pt,
445 (sign, 0), self.yvgridpath))
447 def doaxiscreate(self, axisname):
448 if self.did(self.doaxiscreate, axisname):
449 return
450 self.doaxispositioner(axisname)
451 self.axes[axisname].create()
453 def dolayout(self):
454 if self.did(self.dolayout):
455 return
456 for axisname in self.axes.keys():
457 self.doaxiscreate(axisname)
458 if self.xaxisat is not None:
459 self.axisatv(self.axes["x"], self.axes["y"].convert(self.xaxisat))
460 if self.yaxisat is not None:
461 self.axisatv(self.axes["y"], self.axes["x"].convert(self.yaxisat))
463 def dobackground(self):
464 if self.did(self.dobackground):
465 return
466 if self.backgroundattrs is not None:
467 self.draw(path.rect_pt(self.xpos_pt, self.ypos_pt, self.width_pt, self.height_pt),
468 self.backgroundattrs)
470 def doaxes(self):
471 if self.did(self.doaxes):
472 return
473 self.dolayout()
474 self.dobackground()
475 for axis in self.axes.values():
476 self.insert(axis.canvas)
478 def dokey(self):
479 if self.did(self.dokey):
480 return
481 self.dobackground()
482 self.dodata()
483 if self.key is not None:
484 c = self.key.paint(self.plotitems)
485 bbox = c.bbox()
486 def parentchildalign(pmin, pmax, cmin, cmax, pos, dist, inside):
487 ppos = pmin+0.5*(cmax-cmin)+dist+pos*(pmax-pmin-cmax+cmin-2*dist)
488 cpos = 0.5*(cmin+cmax)+(1-inside)*(1-2*pos)*(cmax-cmin+2*dist)
489 return ppos-cpos
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 # some thoughts, but deferred right now
501 # class graphxyz(graphxy):
503 # axisnames = "x", "y", "z"
505 # def _vxtickpoint(self, axis, v):
506 # return self._vpos(v, axis.vypos, axis.vzpos)
508 # def _vytickpoint(self, axis, v):
509 # return self._vpos(axis.vxpos, v, axis.vzpos)
511 # def _vztickpoint(self, axis, v):
512 # return self._vpos(axis.vxpos, axis.vypos, v)
514 # def vxtickdirection(self, axis, v):
515 # x1, y1 = self._vpos(v, axis.vypos, axis.vzpos)
516 # x2, y2 = self._vpos(v, 0.5, 0)
517 # dx, dy = x1 - x2, y1 - y2
518 # norm = math.hypot(dx, dy)
519 # return dx/norm, dy/norm
521 # def vytickdirection(self, axis, v):
522 # x1, y1 = self._vpos(axis.vxpos, v, axis.vzpos)
523 # x2, y2 = self._vpos(0.5, v, 0)
524 # dx, dy = x1 - x2, y1 - y2
525 # norm = math.hypot(dx, dy)
526 # return dx/norm, dy/norm
528 # def vztickdirection(self, axis, v):
529 # return -1, 0
530 # x1, y1 = self._vpos(axis.vxpos, axis.vypos, v)
531 # x2, y2 = self._vpos(0.5, 0.5, v)
532 # dx, dy = x1 - x2, y1 - y2
533 # norm = math.hypot(dx, dy)
534 # return dx/norm, dy/norm
536 # def _pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
537 # if xaxis is None: xaxis = self.axes["x"]
538 # if yaxis is None: yaxis = self.axes["y"]
539 # if zaxis is None: zaxis = self.axes["z"]
540 # return self._vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
542 # def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
543 # if xaxis is None: xaxis = self.axes["x"]
544 # if yaxis is None: yaxis = self.axes["y"]
545 # if zaxis is None: zaxis = self.axes["z"]
546 # return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
548 # def _vpos(self, vx, vy, vz):
549 # x, y, z = (vx - 0.5)*self._depth, (vy - 0.5)*self._width, (vz - 0.5)*self._height
550 # d0 = float(self.a[0]*self.b[1]*(z-self.eye[2])
551 # + self.a[2]*self.b[0]*(y-self.eye[1])
552 # + self.a[1]*self.b[2]*(x-self.eye[0])
553 # - self.a[2]*self.b[1]*(x-self.eye[0])
554 # - self.a[0]*self.b[2]*(y-self.eye[1])
555 # - self.a[1]*self.b[0]*(z-self.eye[2]))
556 # da = (self.eye[0]*self.b[1]*(z-self.eye[2])
557 # + self.eye[2]*self.b[0]*(y-self.eye[1])
558 # + self.eye[1]*self.b[2]*(x-self.eye[0])
559 # - self.eye[2]*self.b[1]*(x-self.eye[0])
560 # - self.eye[0]*self.b[2]*(y-self.eye[1])
561 # - self.eye[1]*self.b[0]*(z-self.eye[2]))
562 # db = (self.a[0]*self.eye[1]*(z-self.eye[2])
563 # + self.a[2]*self.eye[0]*(y-self.eye[1])
564 # + self.a[1]*self.eye[2]*(x-self.eye[0])
565 # - self.a[2]*self.eye[1]*(x-self.eye[0])
566 # - self.a[0]*self.eye[2]*(y-self.eye[1])
567 # - self.a[1]*self.eye[0]*(z-self.eye[2]))
568 # return da/d0 + self._xpos, db/d0 + self._ypos
570 # def vpos(self, vx, vy, vz):
571 # tx, ty = self._vpos(vx, vy, vz)
572 # return unit.t_pt(tx), unit.t_pt(ty)
574 # def xbaseline(self, axis, x1, x2, xaxis=None):
575 # if xaxis is None: xaxis = self.axes["x"]
576 # return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2))
578 # def ybaseline(self, axis, y1, y2, yaxis=None):
579 # if yaxis is None: yaxis = self.axes["y"]
580 # return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2))
582 # def zbaseline(self, axis, z1, z2, zaxis=None):
583 # if zaxis is None: zaxis = self.axes["z"]
584 # return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2))
586 # def vxbaseline(self, axis, v1, v2):
587 # return (path._line(*(self._vpos(v1, 0, 0) + self._vpos(v2, 0, 0))) +
588 # path._line(*(self._vpos(v1, 0, 1) + self._vpos(v2, 0, 1))) +
589 # path._line(*(self._vpos(v1, 1, 1) + self._vpos(v2, 1, 1))) +
590 # path._line(*(self._vpos(v1, 1, 0) + self._vpos(v2, 1, 0))))
592 # def vybaseline(self, axis, v1, v2):
593 # return (path._line(*(self._vpos(0, v1, 0) + self._vpos(0, v2, 0))) +
594 # path._line(*(self._vpos(0, v1, 1) + self._vpos(0, v2, 1))) +
595 # path._line(*(self._vpos(1, v1, 1) + self._vpos(1, v2, 1))) +
596 # path._line(*(self._vpos(1, v1, 0) + self._vpos(1, v2, 0))))
598 # def vzbaseline(self, axis, v1, v2):
599 # return (path._line(*(self._vpos(0, 0, v1) + self._vpos(0, 0, v2))) +
600 # path._line(*(self._vpos(0, 1, v1) + self._vpos(0, 1, v2))) +
601 # path._line(*(self._vpos(1, 1, v1) + self._vpos(1, 1, v2))) +
602 # path._line(*(self._vpos(1, 0, v1) + self._vpos(1, 0, v2))))
604 # def xgridpath(self, x, xaxis=None):
605 # assert 0
606 # if xaxis is None: xaxis = self.axes["x"]
607 # v = xaxis.convert(x)
608 # return path._line(self._xpos+v*self._width, self._ypos,
609 # self._xpos+v*self._width, self._ypos+self._height)
611 # def ygridpath(self, y, yaxis=None):
612 # assert 0
613 # if yaxis is None: yaxis = self.axes["y"]
614 # v = yaxis.convert(y)
615 # return path._line(self._xpos, self._ypos+v*self._height,
616 # self._xpos+self._width, self._ypos+v*self._height)
618 # def zgridpath(self, z, zaxis=None):
619 # assert 0
620 # if zaxis is None: zaxis = self.axes["z"]
621 # v = zaxis.convert(z)
622 # return path._line(self._xpos, self._zpos+v*self._height,
623 # self._xpos+self._width, self._zpos+v*self._height)
625 # def vxgridpath(self, v):
626 # return path.path(path._moveto(*self._vpos(v, 0, 0)),
627 # path._lineto(*self._vpos(v, 0, 1)),
628 # path._lineto(*self._vpos(v, 1, 1)),
629 # path._lineto(*self._vpos(v, 1, 0)),
630 # path.closepath())
632 # def vygridpath(self, v):
633 # return path.path(path._moveto(*self._vpos(0, v, 0)),
634 # path._lineto(*self._vpos(0, v, 1)),
635 # path._lineto(*self._vpos(1, v, 1)),
636 # path._lineto(*self._vpos(1, v, 0)),
637 # path.closepath())
639 # def vzgridpath(self, v):
640 # return path.path(path._moveto(*self._vpos(0, 0, v)),
641 # path._lineto(*self._vpos(0, 1, v)),
642 # path._lineto(*self._vpos(1, 1, v)),
643 # path._lineto(*self._vpos(1, 0, v)),
644 # path.closepath())
646 # def _addpos(self, x, y, dx, dy):
647 # assert 0
648 # return x+dx, y+dy
650 # def _connect(self, x1, y1, x2, y2):
651 # assert 0
652 # return path._lineto(x2, y2)
654 # def doaxes(self):
655 # self.dolayout()
656 # if not self.removedomethod(self.doaxes): return
657 # axesdist_pt = unit.topt(self.axesdist)
658 # XPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.axisnames[0])
659 # YPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.axisnames[1])
660 # ZPattern = re.compile(r"%s([2-9]|[1-9][0-9]+)?$" % self.axisnames[2])
661 # items = list(self.axes.items())
662 # items.sort() #TODO: alphabetical sorting breaks for axis numbers bigger than 9
663 # for key, axis in items:
664 # num = self.keynum(key)
665 # num2 = 1 - num % 2 # x1 -> 0, x2 -> 1, x3 -> 0, x4 -> 1, ...
666 # num3 = 1 - 2 * (num % 2) # x1 -> -1, x2 -> 1, x3 -> -1, x4 -> 1, ...
667 # if XPattern.match(key):
668 # axis.vypos = 0
669 # axis.vzpos = 0
670 # axis._vtickpoint = self._vxtickpoint
671 # axis.vgridpath = self.vxgridpath
672 # axis.vbaseline = self.vxbaseline
673 # axis.vtickdirection = self.vxtickdirection
674 # elif YPattern.match(key):
675 # axis.vxpos = 0
676 # axis.vzpos = 0
677 # axis._vtickpoint = self._vytickpoint
678 # axis.vgridpath = self.vygridpath
679 # axis.vbaseline = self.vybaseline
680 # axis.vtickdirection = self.vytickdirection
681 # elif ZPattern.match(key):
682 # axis.vxpos = 0
683 # axis.vypos = 0
684 # axis._vtickpoint = self._vztickpoint
685 # axis.vgridpath = self.vzgridpath
686 # axis.vbaseline = self.vzbaseline
687 # axis.vtickdirection = self.vztickdirection
688 # else:
689 # raise ValueError("Axis key '%s' not allowed" % key)
690 # if axis.painter is not None:
691 # axis.dopaint(self)
692 # # if XPattern.match(key):
693 # # self._xaxisextents[num2] += axis._extent
694 # # needxaxisdist[num2] = 1
695 # # if YPattern.match(key):
696 # # self._yaxisextents[num2] += axis._extent
697 # # needyaxisdist[num2] = 1
699 # def __init__(self, tex, xpos=0, ypos=0, width=None, height=None, depth=None,
700 # phi=30, theta=30, distance=1,
701 # backgroundattrs=None, axesdist=0.8*unit.v_cm, **axes):
702 # canvas.canvas.__init__(self)
703 # self.tex = tex
704 # self.xpos = xpos
705 # self.ypos = ypos
706 # self._xpos = unit.topt(xpos)
707 # self._ypos = unit.topt(ypos)
708 # self._width = unit.topt(width)
709 # self._height = unit.topt(height)
710 # self._depth = unit.topt(depth)
711 # self.width = width
712 # self.height = height
713 # self.depth = depth
714 # if self._width <= 0: raise ValueError("width < 0")
715 # if self._height <= 0: raise ValueError("height < 0")
716 # if self._depth <= 0: raise ValueError("height < 0")
717 # self._distance = distance*math.sqrt(self._width*self._width+
718 # self._height*self._height+
719 # self._depth*self._depth)
720 # phi *= -math.pi/180
721 # theta *= math.pi/180
722 # self.a = (-math.sin(phi), math.cos(phi), 0)
723 # self.b = (-math.cos(phi)*math.sin(theta),
724 # -math.sin(phi)*math.sin(theta),
725 # math.cos(theta))
726 # self.eye = (self._distance*math.cos(phi)*math.cos(theta),
727 # self._distance*math.sin(phi)*math.cos(theta),
728 # self._distance*math.sin(theta))
729 # self.initaxes(axes)
730 # self.axesdist = axesdist
731 # self.backgroundattrs = backgroundattrs
733 # self.data = []
734 # self.domethods = [self.dolayout, self.dobackground, self.doaxes, self.dodata]
735 # self.haslayout = 0
736 # self.defaultstyle = {}
738 # def bbox(self):
739 # self.finish()
740 # return bbox._bbox(self._xpos - 200, self._ypos - 200, self._xpos + 200, self._ypos + 200)