build fixes (faq was converted to sphinx)
[PyX.git] / pyx / graph / graph.py
blobb7eb8d164dc2234547b9e9a96134cb54a369df61
1 # -*- encoding: utf-8 -*-
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-2011 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, pycompat, trafo, unit
27 from pyx.graph.axis import axis, positioner
30 goldenmean = 0.5 * (math.sqrt(5) + 1)
33 # The following two methods are used to register and get a default provider
34 # for keys. A key is a variable name in sharedata. A provider is a style
35 # which creates variables in sharedata.
37 _defaultprovider = {}
39 def registerdefaultprovider(style, keys):
40 """sets a style as a default creator for sharedata variables 'keys'"""
41 for key in keys:
42 assert key in style.providesdata, "key not provided by style"
43 # we might allow for overwriting the defaults, i.e. the following is not checked:
44 # assert key in _defaultprovider.keys(), "default provider already registered for key"
45 _defaultprovider[key] = style
47 def getdefaultprovider(key):
48 """returns a style, which acts as a default creator for the
49 sharedata variable 'key'"""
50 return _defaultprovider[key]
53 class styledata:
54 """style data storage class
56 Instances of this class are used to store data from the styles
57 and to pass point data to the styles by instances named privatedata
58 and sharedata. sharedata is shared between all the style(s) in use
59 by a data instance, while privatedata is private to each style and
60 used as a storage place instead of self to prevent side effects when
61 using a style several times."""
62 pass
65 class plotitem:
67 def __init__(self, graph, data, styles):
68 self.data = data
69 self.title = data.title
71 addstyles = [None]
72 while addstyles:
73 # add styles to ensure all needs of the given styles
74 provided = [] # already provided sharedata variables
75 addstyles = [] # a list of style instances to be added in front
76 for s in styles:
77 for n in s.needsdata:
78 if n not in provided:
79 defaultprovider = getdefaultprovider(n)
80 addstyles.append(defaultprovider)
81 provided.extend(defaultprovider.providesdata)
82 provided.extend(s.providesdata)
83 styles = addstyles + styles
85 self.styles = styles
86 self.sharedata = styledata()
87 self.dataaxisnames = {}
88 self.privatedatalist = [styledata() for s in self.styles]
90 # perform setcolumns to all styles
91 self.usedcolumnnames = pycompat.set()
92 for privatedata, s in zip(self.privatedatalist, self.styles):
93 self.usedcolumnnames.update(pycompat.set(s.columnnames(privatedata, self.sharedata, graph, self.data.columnnames, self.dataaxisnames)))
95 def selectstyles(self, graph, selectindex, selecttotal):
96 for privatedata, style in zip(self.privatedatalist, self.styles):
97 style.selectstyle(privatedata, self.sharedata, graph, selectindex, selecttotal)
99 def adjustaxesstatic(self, graph):
100 for columnname, data in self.data.columns.items():
101 for privatedata, style in zip(self.privatedatalist, self.styles):
102 style.adjustaxis(privatedata, self.sharedata, graph, self, columnname, data)
104 def makedynamicdata(self, graph):
105 self.dynamiccolumns = self.data.dynamiccolumns(graph, self.dataaxisnames)
107 def adjustaxesdynamic(self, graph):
108 for columnname, data in self.dynamiccolumns.items():
109 for privatedata, style in zip(self.privatedatalist, self.styles):
110 style.adjustaxis(privatedata, self.sharedata, graph, self, columnname, data)
112 def draw(self, graph):
113 for privatedata, style in zip(self.privatedatalist, self.styles):
114 style.initdrawpoints(privatedata, self.sharedata, graph)
116 point = dict([(columnname, None) for columnname in self.usedcolumnnames])
117 # fill point with (static) column data first
118 columns = self.data.columns.keys()
119 for values in zip(*self.data.columns.values()):
120 for column, value in zip(columns, values):
121 point[column] = value
122 for privatedata, style in zip(self.privatedatalist, self.styles):
123 style.drawpoint(privatedata, self.sharedata, graph, point)
125 point = dict([(columnname, None) for columnname in self.usedcolumnnames])
126 # insert an empty point
127 if self.data.columns and self.dynamiccolumns:
128 for privatedata, style in zip(self.privatedatalist, self.styles):
129 style.drawpoint(privatedata, self.sharedata, graph, point)
130 # fill point with dynamic column data
131 columns = self.dynamiccolumns.keys()
132 for values in zip(*self.dynamiccolumns.values()):
133 for key, value in zip(columns, values):
134 point[key] = value
135 for privatedata, style in zip(self.privatedatalist, self.styles):
136 style.drawpoint(privatedata, self.sharedata, graph, point)
137 for privatedata, style in zip(self.privatedatalist, self.styles):
138 style.donedrawpoints(privatedata, self.sharedata, graph)
140 def key_pt(self, graph, x_pt, y_pt, width_pt, height_pt):
141 for privatedata, style in zip(self.privatedatalist, self.styles):
142 style.key_pt(privatedata, self.sharedata, graph, x_pt, y_pt, width_pt, height_pt)
144 def __getattr__(self, attr):
145 # read only access to the styles privatedata
146 # this is just a convenience method
147 # use case: access the path of a the line style
148 stylesdata = [getattr(styledata, attr)
149 for styledata in self.privatedatalist
150 if hasattr(styledata, attr)]
151 if len(stylesdata) > 1:
152 return stylesdata
153 elif len(stylesdata) == 1:
154 return stylesdata[0]
155 raise AttributeError("access to styledata attribute '%s' failed" % attr)
158 class graph(canvas.canvas):
160 def __init__(self):
161 canvas.canvas.__init__(self)
162 for name in ["background", "filldata", "axes.grid", "axis.baseline", "axis.ticks", "axis.labels", "axis.title", "data", "key"]:
163 self.layer(name)
164 self.axes = {}
165 self.plotitems = []
166 self.keyitems = []
167 self._calls = {}
168 self.didranges = 0
169 self.didstyles = 0
171 def did(self, method, *args, **kwargs):
172 if not self._calls.has_key(method):
173 self._calls[method] = []
174 for callargs in self._calls[method]:
175 if callargs == (args, kwargs):
176 return 1
177 self._calls[method].append((args, kwargs))
178 return 0
180 def bbox(self):
181 self.finish()
182 return canvas.canvas.bbox(self)
185 def registerPS(self, registry):
186 self.finish()
187 canvas.canvas.registerPS(self, registry)
189 def registerPDF(self, registry):
190 self.finish()
191 canvas.canvas.registerPDF(self, registry)
193 def processPS(self, file, writer, context, registry, bbox):
194 self.finish()
195 canvas.canvas.processPS(self, file, writer, context, registry, bbox)
197 def processPDF(self, file, writer, context, registry, bbox):
198 self.finish()
199 canvas.canvas.processPDF(self, file, writer, context, registry, bbox)
201 def plot(self, data, styles=None, rangewarning=1):
202 if self.didranges and rangewarning:
203 warnings.warn("axes ranges have already been analysed; no further adjustments will be performed")
204 if self.didstyles:
205 raise RuntimeError("can't plot further data after dostyles() has been executed")
206 singledata = 0
207 try:
208 for d in data:
209 pass
210 except:
211 usedata = [data]
212 singledata = 1
213 else:
214 usedata = data
215 if styles is None:
216 for d in usedata:
217 if styles is None:
218 styles = d.defaultstyles
219 elif styles != d.defaultstyles:
220 raise RuntimeError("defaultstyles differ")
221 plotitems = []
222 for d in usedata:
223 plotitems.append(plotitem(self, d, styles))
224 self.plotitems.extend(plotitems)
225 if self.didranges:
226 for aplotitem in plotitems:
227 aplotitem.makedynamicdata(self)
228 if singledata:
229 return plotitems[0]
230 else:
231 return plotitems
233 def doranges(self):
234 if self.did(self.doranges):
235 return
236 for plotitem in self.plotitems:
237 plotitem.adjustaxesstatic(self)
238 for plotitem in self.plotitems:
239 plotitem.makedynamicdata(self)
240 for plotitem in self.plotitems:
241 plotitem.adjustaxesdynamic(self)
242 self.didranges = 1
244 def doaxiscreate(self, axisname):
245 if self.did(self.doaxiscreate, axisname):
246 return
247 self.doaxispositioner(axisname)
248 self.axes[axisname].create()
250 def dolayout(self):
251 raise NotImplementedError
253 def dobackground(self):
254 pass
256 def doaxes(self):
257 raise NotImplementedError
259 def dostyles(self):
260 if self.did(self.dostyles):
261 return
262 self.dolayout()
263 self.dobackground()
265 # count the usage of styles and perform selects
266 styletotal = {}
267 def stylesid(styles):
268 return ":".join([str(id(style)) for style in styles])
269 for plotitem in self.plotitems:
270 try:
271 styletotal[stylesid(plotitem.styles)] += 1
272 except:
273 styletotal[stylesid(plotitem.styles)] = 1
274 styleindex = {}
275 for plotitem in self.plotitems:
276 try:
277 styleindex[stylesid(plotitem.styles)] += 1
278 except:
279 styleindex[stylesid(plotitem.styles)] = 0
280 plotitem.selectstyles(self, styleindex[stylesid(plotitem.styles)],
281 styletotal[stylesid(plotitem.styles)])
283 self.didstyles = 1
285 def doplotitem(self, plotitem):
286 if self.did(self.doplotitem, plotitem):
287 return
288 self.dostyles()
289 plotitem.draw(self)
291 def doplot(self):
292 for plotitem in self.plotitems:
293 self.doplotitem(plotitem)
295 def dodata(self):
296 warnings.warn("dodata() has been deprecated. Use doplot() instead.")
297 self.doplot()
299 def dokeyitem(self, plotitem):
300 if self.did(self.dokeyitem, plotitem):
301 return
302 self.dostyles()
303 if plotitem.title is not None:
304 self.keyitems.append(plotitem)
306 def dokey(self):
307 raise NotImplementedError
309 def finish(self):
310 self.dobackground()
311 self.doaxes()
312 self.doplot()
313 self.dokey()
316 class graphxy(graph):
318 def __init__(self, xpos=0, ypos=0, width=None, height=None, ratio=goldenmean,
319 key=None, backgroundattrs=None, axesdist=0.8*unit.v_cm, flipped=False,
320 xaxisat=None, yaxisat=None, **axes):
321 graph.__init__(self)
323 self.xpos = xpos
324 self.ypos = ypos
325 self.xpos_pt = unit.topt(self.xpos)
326 self.ypos_pt = unit.topt(self.ypos)
327 self.xaxisat = xaxisat
328 self.yaxisat = yaxisat
329 self.key = key
330 self.backgroundattrs = backgroundattrs
331 self.axesdist_pt = unit.topt(axesdist)
332 self.flipped = flipped
334 self.width = width
335 self.height = height
336 if width is None:
337 if height is None:
338 raise ValueError("specify width and/or height")
339 else:
340 self.width = ratio * self.height
341 elif height is None:
342 self.height = (1.0/ratio) * self.width
343 self.width_pt = unit.topt(self.width)
344 self.height_pt = unit.topt(self.height)
346 for axisname, aaxis in axes.items():
347 if aaxis is not None:
348 if not isinstance(aaxis, axis.linkedaxis):
349 self.axes[axisname] = axis.anchoredaxis(aaxis, self.texrunner, axisname)
350 else:
351 self.axes[axisname] = aaxis
352 for axisname, axisat in [("x", xaxisat), ("y", yaxisat)]:
353 okey = axisname + "2"
354 if not axes.has_key(axisname):
355 if not axes.has_key(okey) or axes[okey] is None:
356 self.axes[axisname] = axis.anchoredaxis(axis.linear(), self.texrunner, axisname)
357 if not axes.has_key(okey):
358 self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey)
359 else:
360 self.axes[axisname] = axis.linkedaxis(self.axes[okey], axisname)
361 elif not axes.has_key(okey) and axisat is None:
362 self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey)
364 if self.axes.has_key("x"):
365 self.xbasepath = self.axes["x"].basepath
366 self.xvbasepath = self.axes["x"].vbasepath
367 self.xgridpath = self.axes["x"].gridpath
368 self.xtickpoint_pt = self.axes["x"].tickpoint_pt
369 self.xtickpoint = self.axes["x"].tickpoint
370 self.xvtickpoint_pt = self.axes["x"].vtickpoint_pt
371 self.xvtickpoint = self.axes["x"].tickpoint
372 self.xtickdirection = self.axes["x"].tickdirection
373 self.xvtickdirection = self.axes["x"].vtickdirection
375 if self.axes.has_key("y"):
376 self.ybasepath = self.axes["y"].basepath
377 self.yvbasepath = self.axes["y"].vbasepath
378 self.ygridpath = self.axes["y"].gridpath
379 self.ytickpoint_pt = self.axes["y"].tickpoint_pt
380 self.ytickpoint = self.axes["y"].tickpoint
381 self.yvtickpoint_pt = self.axes["y"].vtickpoint_pt
382 self.yvtickpoint = self.axes["y"].vtickpoint
383 self.ytickdirection = self.axes["y"].tickdirection
384 self.yvtickdirection = self.axes["y"].vtickdirection
386 self.axesnames = ([], [])
387 for axisname, aaxis in self.axes.items():
388 if axisname[0] not in "xy" or (len(axisname) != 1 and (not axisname[1:].isdigit() or
389 axisname[1:] == "1")):
390 raise ValueError("invalid axis name")
391 if axisname[0] == "x":
392 self.axesnames[0].append(axisname)
393 else:
394 self.axesnames[1].append(axisname)
395 aaxis.setcreatecall(self.doaxiscreate, axisname)
397 self.axespositioners = dict(x=positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt,
398 self.xpos_pt + self.width_pt, self.ypos_pt,
399 (0, 1), self.xvgridpath),
400 x2=positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt + self.height_pt,
401 self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt,
402 (0, -1), self.xvgridpath),
403 y=positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt,
404 self.xpos_pt, self.ypos_pt + self.height_pt,
405 (1, 0), self.yvgridpath),
406 y2=positioner.lineaxispos_pt(self.xpos_pt + self.width_pt, self.ypos_pt,
407 self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt,
408 (-1, 0), self.yvgridpath))
409 if self.flipped:
410 self.axespositioners = dict(x=self.axespositioners["y2"],
411 y2=self.axespositioners["x2"],
412 y=self.axespositioners["x"],
413 x2=self.axespositioners["y"])
415 def pos_pt(self, x, y, xaxis=None, yaxis=None):
416 if xaxis is None:
417 xaxis = self.axes["x"]
418 if yaxis is None:
419 yaxis = self.axes["y"]
420 vx = xaxis.convert(x)
421 vy = yaxis.convert(y)
422 if self.flipped:
423 vx, vy = vy, vx
424 return (self.xpos_pt + vx*self.width_pt,
425 self.ypos_pt + vy*self.height_pt)
427 def pos(self, x, y, xaxis=None, yaxis=None):
428 if xaxis is None:
429 xaxis = self.axes["x"]
430 if yaxis is None:
431 yaxis = self.axes["y"]
432 vx = xaxis.convert(x)
433 vy = yaxis.convert(y)
434 if self.flipped:
435 vx, vy = vy, vx
436 return (self.xpos + vx*self.width,
437 self.ypos + vy*self.height)
439 def vpos_pt(self, vx, vy):
440 if self.flipped:
441 vx, vy = vy, vx
442 return (self.xpos_pt + vx*self.width_pt,
443 self.ypos_pt + vy*self.height_pt)
445 def vpos(self, vx, vy):
446 if self.flipped:
447 vx, vy = vy, vx
448 return (self.xpos + vx*self.width,
449 self.ypos + vy*self.height)
451 def vzindex(self, vx, vy):
452 return 0
454 def vangle(self, vx1, vy1, vx2, vy2, vx3, vy3):
455 return 1
457 def vgeodesic(self, vx1, vy1, vx2, vy2):
458 """returns a geodesic path between two points in graph coordinates"""
459 if self.flipped:
460 vx1, vy1 = vy1, vx1
461 vx2, vy2 = vy2, vx2
462 return path.line_pt(self.xpos_pt + vx1*self.width_pt,
463 self.ypos_pt + vy1*self.height_pt,
464 self.xpos_pt + vx2*self.width_pt,
465 self.ypos_pt + vy2*self.height_pt)
467 def vgeodesic_el(self, vx1, vy1, vx2, vy2):
468 """returns a geodesic path element between two points in graph coordinates"""
469 if self.flipped:
470 vx1, vy1 = vy1, vx1
471 vx2, vy2 = vy2, vx2
472 return path.lineto_pt(self.xpos_pt + vx2*self.width_pt,
473 self.ypos_pt + vy2*self.height_pt)
475 def vcap_pt(self, coordinate, length_pt, vx, vy):
476 """returns an error cap path for a given coordinate, lengths and
477 point in graph coordinates"""
478 if self.flipped:
479 coordinate = 1-coordinate
480 vx, vy = vy, vx
481 if coordinate == 0:
482 return path.line_pt(self.xpos_pt + vx*self.width_pt - 0.5*length_pt,
483 self.ypos_pt + vy*self.height_pt,
484 self.xpos_pt + vx*self.width_pt + 0.5*length_pt,
485 self.ypos_pt + vy*self.height_pt)
486 elif coordinate == 1:
487 return path.line_pt(self.xpos_pt + vx*self.width_pt,
488 self.ypos_pt + vy*self.height_pt - 0.5*length_pt,
489 self.xpos_pt + vx*self.width_pt,
490 self.ypos_pt + vy*self.height_pt + 0.5*length_pt)
491 else:
492 raise ValueError("direction invalid")
494 def xvgridpath(self, vx):
495 return path.line_pt(self.xpos_pt + vx*self.width_pt, self.ypos_pt,
496 self.xpos_pt + vx*self.width_pt, self.ypos_pt + self.height_pt)
498 def yvgridpath(self, vy):
499 return path.line_pt(self.xpos_pt, self.ypos_pt + vy*self.height_pt,
500 self.xpos_pt + self.width_pt, self.ypos_pt + vy*self.height_pt)
502 def autokeygraphattrs(self):
503 return dict(direction="vertical", length=self.height)
505 def autokeygraphtrafo(self, keygraph):
506 dependsonaxisnumber = None
507 if self.flipped:
508 dependsonaxisname = "x"
509 else:
510 dependsonaxisname = "y"
511 for axisname in self.axes:
512 if axisname[0] == dependsonaxisname:
513 if len(axisname) == 1:
514 axisname += "1"
515 axisnumber = int(axisname[1:])
516 if not (axisnumber % 2) and not self.flipped or (axisnumber % 2) and self.flipped:
517 if dependsonaxisnumber is None or dependsonaxisnumber < axisnumber:
518 dependsonaxisnumber = axisnumber
519 if dependsonaxisnumber is None:
520 x_pt = self.xpos_pt + self.width_pt
521 else:
522 if dependsonaxisnumber > 1:
523 dependsonaxisname += str(dependsonaxisnumber)
524 self.doaxiscreate(dependsonaxisname)
525 x_pt = self.axes[dependsonaxisname].positioner.x1_pt + self.axes[dependsonaxisname].canvas.extent_pt
526 x_pt += self.axesdist_pt
527 return trafo.translate_pt(x_pt, self.ypos_pt)
529 def axisatv(self, axis, v):
530 if axis.positioner.fixtickdirection[0]:
531 # it is a y-axis
532 t = trafo.translate_pt(self.xpos_pt + v*self.width_pt - axis.positioner.x1_pt, 0)
533 else:
534 # it is an x-axis
535 t = trafo.translate_pt(0, self.ypos_pt + v*self.height_pt - axis.positioner.y1_pt)
536 c = canvas.canvas()
537 for layer, subcanvas in axis.canvas.layers.items():
538 c.layer(layer).insert(subcanvas, [t])
539 assert len(axis.canvas.layers) == len(axis.canvas.items), str(axis.canvas.items)
540 axis.canvas = c
542 def doaxispositioner(self, axisname):
543 if self.did(self.doaxispositioner, axisname):
544 return
545 self.doranges()
546 if axisname in ["x", "x2", "y", "y2"]:
547 self.axes[axisname].setpositioner(self.axespositioners[axisname])
548 else:
549 if axisname[1:] == "3":
550 dependsonaxisname = axisname[0]
551 else:
552 dependsonaxisname = "%s%d" % (axisname[0], int(axisname[1:]) - 2)
553 self.doaxiscreate(dependsonaxisname)
554 sign = 2*(int(axisname[1:]) % 2) - 1
555 if axisname[0] == "x" and self.flipped:
556 sign = -sign
557 if axisname[0] == "x" and not self.flipped or axisname[0] == "y" and self.flipped:
558 y_pt = self.axes[dependsonaxisname].positioner.y1_pt - sign * (self.axes[dependsonaxisname].canvas.extent_pt + self.axesdist_pt)
559 self.axes[axisname].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, y_pt,
560 self.xpos_pt + self.width_pt, y_pt,
561 (0, sign), self.xvgridpath))
562 else:
563 x_pt = self.axes[dependsonaxisname].positioner.x1_pt - sign * (self.axes[dependsonaxisname].canvas.extent_pt + self.axesdist_pt)
564 self.axes[axisname].setpositioner(positioner.lineaxispos_pt(x_pt, self.ypos_pt,
565 x_pt, self.ypos_pt + self.height_pt,
566 (sign, 0), self.yvgridpath))
568 def dolayout(self):
569 if self.did(self.dolayout):
570 return
571 for axisname in self.axes.keys():
572 self.doaxiscreate(axisname)
573 if self.xaxisat is not None:
574 self.axisatv(self.axes["x"], self.axes["y"].convert(self.xaxisat))
575 if self.yaxisat is not None:
576 self.axisatv(self.axes["y"], self.axes["x"].convert(self.yaxisat))
578 def dobackground(self):
579 if self.did(self.dobackground):
580 return
581 if self.backgroundattrs is not None:
582 self.layer("background").draw(path.rect_pt(self.xpos_pt, self.ypos_pt, self.width_pt, self.height_pt),
583 self.backgroundattrs)
585 def doaxes(self):
586 if self.did(self.doaxes):
587 return
588 self.dolayout()
589 self.dobackground()
590 for axis in self.axes.values():
591 for layer, canvas in axis.canvas.layers.items():
592 self.layer("axes.%s" % layer).insert(canvas)
593 assert len(axis.canvas.layers) == len(axis.canvas.items), str(axis.canvas.items)
595 def dokey(self):
596 if self.did(self.dokey):
597 return
598 self.dobackground()
599 for plotitem in self.plotitems:
600 self.dokeyitem(plotitem)
601 if self.key is not None:
602 c = self.key.paint(self.keyitems)
603 bbox = c.bbox()
604 def parentchildalign(pmin, pmax, cmin, cmax, pos, dist, inside):
605 ppos = pmin+0.5*(cmax-cmin)+dist+pos*(pmax-pmin-cmax+cmin-2*dist)
606 cpos = 0.5*(cmin+cmax)+(1-inside)*(1-2*pos)*(cmax-cmin+2*dist)
607 return ppos-cpos
608 if bbox:
609 x = parentchildalign(self.xpos_pt, self.xpos_pt+self.width_pt,
610 bbox.llx_pt, bbox.urx_pt,
611 self.key.hpos, unit.topt(self.key.hdist), self.key.hinside)
612 y = parentchildalign(self.ypos_pt, self.ypos_pt+self.height_pt,
613 bbox.lly_pt, bbox.ury_pt,
614 self.key.vpos, unit.topt(self.key.vdist), self.key.vinside)
615 self.layer("key").insert(c, [trafo.translate_pt(x, y)])
619 class graphx(graphxy):
621 def __init__(self, xpos=0, ypos=0, length=None, size=0.5*unit.v_cm, direction="vertical",
622 key=None, backgroundattrs=None, axesdist=0.8*unit.v_cm, **axes):
623 for name in axes:
624 if not name.startswith("x"):
625 raise ValueError("Only x axes are allowed")
626 self.direction = direction
627 if self.direction == "vertical":
628 kwargsxy = dict(width=size, height=length, flipped=True)
629 elif self.direction == "horizontal":
630 kwargsxy = dict(width=length, height=size)
631 else:
632 raise ValueError("vertical or horizontal direction required")
633 kwargsxy.update(**axes)
635 graphxy.__init__(self, xpos=xpos, ypos=ypos, ratio=None, key=key, y=axis.lin(min=0, max=1, parter=None),
636 backgroundattrs=backgroundattrs, axesdist=axesdist, **kwargsxy)
638 def pos_pt(self, x, xaxis=None):
639 return graphxy.pos_pt(self, x, 0.5, xaxis)
641 def pos(self, x, xaxis=None):
642 return graphxy.pos(self, x, 0.5, xaxis)
644 def vpos_pt(self, vx):
645 return graphxy.vpos_pt(self, vx, 0.5)
647 def vpos(self, vx):
648 return graphxy.vpos(self, vx, 0.5)
650 def vgeodesic(self, vx1, vx2):
651 return graphxy.vgeodesic(self, vx1, 0.5, vx2, 0.5)
653 def vgeodesic_el(self, vx1, vy1, vx2, vy2):
654 return graphxy.vgeodesic_el(self, vx1, 0.5, vx2, 0.5)
656 def vcap_pt(self, coordinate, length_pt, vx):
657 if coordinate == 0:
658 return graphxy.vcap_pt(self, coordinate, length_pt, vx, 0.5)
659 else:
660 raise ValueError("direction invalid")
662 def xvgridpath(self, vx):
663 return graphxy.xvgridpath(self, vx)
665 def yvgridpath(self, vy):
666 raise Exception("This method does not exist on a one dimensional graph.")
668 def axisatv(self, axis, v):
669 raise Exception("This method does not exist on a one dimensional graph.")
673 class graphxyz(graph):
675 class central:
677 def __init__(self, distance, phi, theta, anglefactor=math.pi/180):
678 phi *= anglefactor
679 theta *= anglefactor
680 self.distance = distance
682 self.a = (-math.sin(phi), math.cos(phi), 0)
683 self.b = (-math.cos(phi)*math.sin(theta),
684 -math.sin(phi)*math.sin(theta),
685 math.cos(theta))
686 self.eye = (distance*math.cos(phi)*math.cos(theta),
687 distance*math.sin(phi)*math.cos(theta),
688 distance*math.sin(theta))
690 def point(self, x, y, z):
691 d0 = (self.a[0]*self.b[1]*(z-self.eye[2])
692 + self.a[2]*self.b[0]*(y-self.eye[1])
693 + self.a[1]*self.b[2]*(x-self.eye[0])
694 - self.a[2]*self.b[1]*(x-self.eye[0])
695 - self.a[0]*self.b[2]*(y-self.eye[1])
696 - self.a[1]*self.b[0]*(z-self.eye[2]))
697 da = (self.eye[0]*self.b[1]*(z-self.eye[2])
698 + self.eye[2]*self.b[0]*(y-self.eye[1])
699 + self.eye[1]*self.b[2]*(x-self.eye[0])
700 - self.eye[2]*self.b[1]*(x-self.eye[0])
701 - self.eye[0]*self.b[2]*(y-self.eye[1])
702 - self.eye[1]*self.b[0]*(z-self.eye[2]))
703 db = (self.a[0]*self.eye[1]*(z-self.eye[2])
704 + self.a[2]*self.eye[0]*(y-self.eye[1])
705 + self.a[1]*self.eye[2]*(x-self.eye[0])
706 - self.a[2]*self.eye[1]*(x-self.eye[0])
707 - self.a[0]*self.eye[2]*(y-self.eye[1])
708 - self.a[1]*self.eye[0]*(z-self.eye[2]))
709 return da/d0, db/d0
711 def zindex(self, x, y, z):
712 return math.sqrt((x-self.eye[0])*(x-self.eye[0])+(y-self.eye[1])*(y-self.eye[1])+(z-self.eye[2])*(z-self.eye[2]))-self.distance
714 def angle(self, x1, y1, z1, x2, y2, z2, x3, y3, z3):
715 sx = (x1-self.eye[0])
716 sy = (y1-self.eye[1])
717 sz = (z1-self.eye[2])
718 nx = (y2-y1)*(z3-z1)-(z2-z1)*(y3-y1)
719 ny = (z2-z1)*(x3-x1)-(x2-x1)*(z3-z1)
720 nz = (x2-x1)*(y3-y1)-(y2-y1)*(x3-x1)
721 return (sx*nx+sy*ny+sz*nz)/math.sqrt(nx*nx+ny*ny+nz*nz)/math.sqrt(sx*sx+sy*sy+sz*sz)
724 class parallel:
726 def __init__(self, phi, theta, anglefactor=math.pi/180):
727 phi *= anglefactor
728 theta *= anglefactor
730 self.a = (-math.sin(phi), math.cos(phi), 0)
731 self.b = (-math.cos(phi)*math.sin(theta),
732 -math.sin(phi)*math.sin(theta),
733 math.cos(theta))
734 self.c = (-math.cos(phi)*math.cos(theta),
735 -math.sin(phi)*math.cos(theta),
736 -math.sin(theta))
738 def point(self, x, y, z):
739 return self.a[0]*x+self.a[1]*y+self.a[2]*z, self.b[0]*x+self.b[1]*y+self.b[2]*z
741 def zindex(self, x, y, z):
742 return self.c[0]*x+self.c[1]*y+self.c[2]*z
744 def angle(self, x1, y1, z1, x2, y2, z2, x3, y3, z3):
745 nx = (y2-y1)*(z3-z1)-(z2-z1)*(y3-y1)
746 ny = (z2-z1)*(x3-x1)-(x2-x1)*(z3-z1)
747 nz = (x2-x1)*(y3-y1)-(y2-y1)*(x3-x1)
748 return (self.c[0]*nx+self.c[1]*ny+self.c[2]*nz)/math.sqrt(nx*nx+ny*ny+nz*nz)
751 def __init__(self, xpos=0, ypos=0, size=None,
752 xscale=1, yscale=1, zscale=1/goldenmean,
753 projector=central(10, -30, 30), axesdist=0.8*unit.v_cm, key=None,
754 **axes):
755 graph.__init__(self)
756 self.layer("hiddenaxes", below=self.layer("filldata"))
757 for name in ["hiddenaxes.grid", "hiddenaxes.baseline", "hiddenaxes.ticks", "hiddenaxes.labels", "hiddenaxes.title"]:
758 self.layer(name)
760 self.xpos = xpos
761 self.ypos = ypos
762 self.size = size
763 self.xpos_pt = unit.topt(xpos)
764 self.ypos_pt = unit.topt(ypos)
765 self.size_pt = unit.topt(size)
766 self.xscale = xscale
767 self.yscale = yscale
768 self.zscale = zscale
769 self.projector = projector
770 self.axesdist_pt = unit.topt(axesdist)
771 self.key = key
773 self.xorder = projector.zindex(0, -1, 0) > projector.zindex(0, 1, 0) and 1 or 0
774 self.yorder = projector.zindex(-1, 0, 0) > projector.zindex(1, 0, 0) and 1 or 0
775 self.zindexscale = math.sqrt(xscale*xscale+yscale*yscale+zscale*zscale)
777 # the pXYshow attributes are booleans stating whether plane perpendicular to axis X
778 # at the virtual graph coordinate Y will be hidden by data or not. An axis is considered
779 # to be visible if one of the two planes it is part of is visible. Other axes are drawn
780 # in the hiddenaxes layer (i.e. layer group).
781 # TODO: Tick and grid visibility is treated like the axis visibility at the moment.
782 self.pz0show = self.vangle(0, 0, 0, 1, 0, 0, 1, 1, 0) > 0
783 self.pz1show = self.vangle(0, 0, 1, 0, 1, 1, 1, 1, 1) > 0
784 self.py0show = self.vangle(0, 0, 0, 0, 0, 1, 1, 0, 1) > 0
785 self.py1show = self.vangle(0, 1, 0, 1, 1, 0, 1, 1, 1) > 0
786 self.px0show = self.vangle(0, 0, 0, 0, 1, 0, 0, 1, 1) > 0
787 self.px1show = self.vangle(1, 0, 0, 1, 0, 1, 1, 1, 1) > 0
789 for axisname, aaxis in axes.items():
790 if aaxis is not None:
791 if not isinstance(aaxis, axis.linkedaxis):
792 self.axes[axisname] = axis.anchoredaxis(aaxis, self.texrunner, axisname)
793 else:
794 self.axes[axisname] = aaxis
795 for axisname in ["x", "y"]:
796 okey = axisname + "2"
797 if not axes.has_key(axisname):
798 if not axes.has_key(okey) or axes[okey] is None:
799 self.axes[axisname] = axis.anchoredaxis(axis.linear(), self.texrunner, axisname)
800 if not axes.has_key(okey):
801 self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey)
802 else:
803 self.axes[axisname] = axis.linkedaxis(self.axes[okey], axisname)
804 elif not axes.has_key(okey):
805 self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey)
806 if not axes.has_key("z"):
807 self.axes["z"] = axis.anchoredaxis(axis.linear(), self.texrunner, "z")
809 if self.axes.has_key("x"):
810 self.xbasepath = self.axes["x"].basepath
811 self.xvbasepath = self.axes["x"].vbasepath
812 self.xgridpath = self.axes["x"].gridpath
813 self.xtickpoint_pt = self.axes["x"].tickpoint_pt
814 self.xtickpoint = self.axes["x"].tickpoint
815 self.xvtickpoint_pt = self.axes["x"].vtickpoint_pt
816 self.xvtickpoint = self.axes["x"].tickpoint
817 self.xtickdirection = self.axes["x"].tickdirection
818 self.xvtickdirection = self.axes["x"].vtickdirection
820 if self.axes.has_key("y"):
821 self.ybasepath = self.axes["y"].basepath
822 self.yvbasepath = self.axes["y"].vbasepath
823 self.ygridpath = self.axes["y"].gridpath
824 self.ytickpoint_pt = self.axes["y"].tickpoint_pt
825 self.ytickpoint = self.axes["y"].tickpoint
826 self.yvtickpoint_pt = self.axes["y"].vtickpoint_pt
827 self.yvtickpoint = self.axes["y"].vtickpoint
828 self.ytickdirection = self.axes["y"].tickdirection
829 self.yvtickdirection = self.axes["y"].vtickdirection
831 if self.axes.has_key("z"):
832 self.zbasepath = self.axes["z"].basepath
833 self.zvbasepath = self.axes["z"].vbasepath
834 self.zgridpath = self.axes["z"].gridpath
835 self.ztickpoint_pt = self.axes["z"].tickpoint_pt
836 self.ztickpoint = self.axes["z"].tickpoint
837 self.zvtickpoint_pt = self.axes["z"].vtickpoint
838 self.zvtickpoint = self.axes["z"].vtickpoint
839 self.ztickdirection = self.axes["z"].tickdirection
840 self.zvtickdirection = self.axes["z"].vtickdirection
842 self.axesnames = ([], [], [])
843 for axisname, aaxis in self.axes.items():
844 if axisname[0] not in "xyz" or (len(axisname) != 1 and (not axisname[1:].isdigit() or
845 axisname[1:] == "1")):
846 raise ValueError("invalid axis name")
847 if axisname[0] == "x":
848 self.axesnames[0].append(axisname)
849 elif axisname[0] == "y":
850 self.axesnames[1].append(axisname)
851 else:
852 self.axesnames[2].append(axisname)
853 aaxis.setcreatecall(self.doaxiscreate, axisname)
855 def pos_pt(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
856 if xaxis is None:
857 xaxis = self.axes["x"]
858 if yaxis is None:
859 yaxis = self.axes["y"]
860 if zaxis is None:
861 zaxis = self.axes["z"]
862 return self.vpos_pt(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
864 def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
865 if xaxis is None:
866 xaxis = self.axes["x"]
867 if yaxis is None:
868 yaxis = self.axes["y"]
869 if zaxis is None:
870 zaxis = self.axes["z"]
871 return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(z))
873 def vpos_pt(self, vx, vy, vz):
874 x, y = self.projector.point(2*self.xscale*(vx - 0.5),
875 2*self.yscale*(vy - 0.5),
876 2*self.zscale*(vz - 0.5))
877 return self.xpos_pt+x*self.size_pt, self.ypos_pt+y*self.size_pt
879 def vpos(self, vx, vy, vz):
880 x, y = self.projector.point(2*self.xscale*(vx - 0.5),
881 2*self.yscale*(vy - 0.5),
882 2*self.zscale*(vz - 0.5))
883 return self.xpos+x*self.size, self.ypos+y*self.size
885 def vzindex(self, vx, vy, vz):
886 return self.projector.zindex(2*self.xscale*(vx - 0.5),
887 2*self.yscale*(vy - 0.5),
888 2*self.zscale*(vz - 0.5))/self.zindexscale
890 def vangle(self, vx1, vy1, vz1, vx2, vy2, vz2, vx3, vy3, vz3):
891 return self.projector.angle(2*self.xscale*(vx1 - 0.5),
892 2*self.yscale*(vy1 - 0.5),
893 2*self.zscale*(vz1 - 0.5),
894 2*self.xscale*(vx2 - 0.5),
895 2*self.yscale*(vy2 - 0.5),
896 2*self.zscale*(vz2 - 0.5),
897 2*self.xscale*(vx3 - 0.5),
898 2*self.yscale*(vy3 - 0.5),
899 2*self.zscale*(vz3 - 0.5))
901 def vgeodesic(self, vx1, vy1, vz1, vx2, vy2, vz2):
902 """returns a geodesic path between two points in graph coordinates"""
903 return path.line_pt(*(self.vpos_pt(vx1, vy1, vz1) + self.vpos_pt(vx2, vy2, vz2)))
905 def vgeodesic_el(self, vx1, vy1, vz1, vx2, vy2, vz2):
906 """returns a geodesic path element between two points in graph coordinates"""
907 return path.lineto_pt(*self.vpos_pt(vx2, vy2, vz2))
909 def vcap_pt(self, coordinate, length_pt, vx, vy, vz):
910 """returns an error cap path for a given coordinate, lengths and
911 point in graph coordinates"""
912 if coordinate == 0:
913 return self.vgeodesic(vx-0.5*length_pt/self.size_pt, vy, vz, vx+0.5*length_pt/self.size_pt, vy, vz)
914 elif coordinate == 1:
915 return self.vgeodesic(vx, vy-0.5*length_pt/self.size_pt, vz, vx, vy+0.5*length_pt/self.size_pt, vz)
916 elif coordinate == 2:
917 return self.vgeodesic(vx, vy, vz-0.5*length_pt/self.size_pt, vx, vy, vz+0.5*length_pt/self.size_pt)
918 else:
919 raise ValueError("direction invalid")
921 def xvtickdirection(self, vx):
922 if self.xorder:
923 x1_pt, y1_pt = self.vpos_pt(vx, 1, 0)
924 x2_pt, y2_pt = self.vpos_pt(vx, 0, 0)
925 else:
926 x1_pt, y1_pt = self.vpos_pt(vx, 0, 0)
927 x2_pt, y2_pt = self.vpos_pt(vx, 1, 0)
928 dx_pt = x2_pt - x1_pt
929 dy_pt = y2_pt - y1_pt
930 norm = math.hypot(dx_pt, dy_pt)
931 return dx_pt/norm, dy_pt/norm
933 def yvtickdirection(self, vy):
934 if self.yorder:
935 x1_pt, y1_pt = self.vpos_pt(1, vy, 0)
936 x2_pt, y2_pt = self.vpos_pt(0, vy, 0)
937 else:
938 x1_pt, y1_pt = self.vpos_pt(0, vy, 0)
939 x2_pt, y2_pt = self.vpos_pt(1, vy, 0)
940 dx_pt = x2_pt - x1_pt
941 dy_pt = y2_pt - y1_pt
942 norm = math.hypot(dx_pt, dy_pt)
943 return dx_pt/norm, dy_pt/norm
945 def vtickdirection(self, vx1, vy1, vz1, vx2, vy2, vz2):
946 x1_pt, y1_pt = self.vpos_pt(vx1, vy1, vz1)
947 x2_pt, y2_pt = self.vpos_pt(vx2, vy2, vz2)
948 dx_pt = x2_pt - x1_pt
949 dy_pt = y2_pt - y1_pt
950 norm = math.hypot(dx_pt, dy_pt)
951 return dx_pt/norm, dy_pt/norm
953 def xvgridpath(self, vx):
954 return path.path(path.moveto_pt(*self.vpos_pt(vx, 0, 0)),
955 path.lineto_pt(*self.vpos_pt(vx, 1, 0)),
956 path.lineto_pt(*self.vpos_pt(vx, 1, 1)),
957 path.lineto_pt(*self.vpos_pt(vx, 0, 1)),
958 path.closepath())
960 def yvgridpath(self, vy):
961 return path.path(path.moveto_pt(*self.vpos_pt(0, vy, 0)),
962 path.lineto_pt(*self.vpos_pt(1, vy, 0)),
963 path.lineto_pt(*self.vpos_pt(1, vy, 1)),
964 path.lineto_pt(*self.vpos_pt(0, vy, 1)),
965 path.closepath())
967 def zvgridpath(self, vz):
968 return path.path(path.moveto_pt(*self.vpos_pt(0, 0, vz)),
969 path.lineto_pt(*self.vpos_pt(1, 0, vz)),
970 path.lineto_pt(*self.vpos_pt(1, 1, vz)),
971 path.lineto_pt(*self.vpos_pt(0, 1, vz)),
972 path.closepath())
974 def autokeygraphattrs(self):
975 return dict(direction="vertical", length=self.size)
977 def autokeygraphtrafo(self, keygraph):
978 self.doaxes()
979 x_pt = self.layer("axes").bbox().right_pt() + self.axesdist_pt
980 y_pt = 0.5*(self.layer("axes").bbox().top_pt() + self.layer("axes").bbox().bottom_pt() - self.size_pt)
981 return trafo.translate_pt(x_pt, y_pt)
983 def doaxispositioner(self, axisname):
984 if self.did(self.doaxispositioner, axisname):
985 return
986 self.doranges()
987 if axisname == "x":
988 self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, self.xorder, 0),
989 lambda vx: self.vtickdirection(vx, self.xorder, 0, vx, 1-self.xorder, 0),
990 self.xvgridpath))
991 if self.xorder:
992 self.axes[axisname].hidden = not self.py1show and not self.pz0show
993 else:
994 self.axes[axisname].hidden = not self.py0show and not self.pz0show
995 elif axisname == "x2":
996 self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, 1-self.xorder, 0),
997 lambda vx: self.vtickdirection(vx, 1-self.xorder, 0, vx, self.xorder, 0),
998 self.xvgridpath))
999 if self.xorder:
1000 self.axes[axisname].hidden = not self.py0show and not self.pz0show
1001 else:
1002 self.axes[axisname].hidden = not self.py1show and not self.pz0show
1003 elif axisname == "x3":
1004 self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, self.xorder, 1),
1005 lambda vx: self.vtickdirection(vx, self.xorder, 1, vx, 1-self.xorder, 1),
1006 self.xvgridpath))
1007 if self.xorder:
1008 self.axes[axisname].hidden = not self.py1show and not self.pz1show
1009 else:
1010 self.axes[axisname].hidden = not self.py0show and not self.pz1show
1011 elif axisname == "x4":
1012 self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, 1-self.xorder, 1),
1013 lambda vx: self.vtickdirection(vx, 1-self.xorder, 1, vx, self.xorder, 1),
1014 self.xvgridpath))
1015 if self.xorder:
1016 self.axes[axisname].hidden = not self.py0show and not self.pz1show
1017 else:
1018 self.axes[axisname].hidden = not self.py1show and not self.pz1show
1019 elif axisname == "y":
1020 self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(self.yorder, vy, 0),
1021 lambda vy: self.vtickdirection(self.yorder, vy, 0, 1-self.yorder, vy, 0),
1022 self.yvgridpath))
1023 if self.yorder:
1024 self.axes[axisname].hidden = not self.px1show and not self.pz0show
1025 else:
1026 self.axes[axisname].hidden = not self.px0show and not self.pz0show
1027 elif axisname == "y2":
1028 self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(1-self.yorder, vy, 0),
1029 lambda vy: self.vtickdirection(1-self.yorder, vy, 0, self.yorder, vy, 0),
1030 self.yvgridpath))
1031 if self.yorder:
1032 self.axes[axisname].hidden = not self.px0show and not self.pz0show
1033 else:
1034 self.axes[axisname].hidden = not self.px1show and not self.pz0show
1035 elif axisname == "y3":
1036 self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(self.yorder, vy, 1),
1037 lambda vy: self.vtickdirection(self.yorder, vy, 1, 1-self.yorder, vy, 1),
1038 self.yvgridpath))
1039 if self.yorder:
1040 self.axes[axisname].hidden = not self.px1show and not self.pz1show
1041 else:
1042 self.axes[axisname].hidden = not self.px0show and not self.pz1show
1043 elif axisname == "y4":
1044 self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(1-self.yorder, vy, 1),
1045 lambda vy: self.vtickdirection(1-self.yorder, vy, 1, self.yorder, vy, 1),
1046 self.yvgridpath))
1047 if self.yorder:
1048 self.axes[axisname].hidden = not self.px0show and not self.pz1show
1049 else:
1050 self.axes[axisname].hidden = not self.px1show and not self.pz1show
1051 elif axisname == "z":
1052 self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(0, 0, vz),
1053 lambda vz: self.vtickdirection(0, 0, vz, 1, 1, vz),
1054 self.zvgridpath))
1055 self.axes[axisname].hidden = not self.px0show and not self.py0show
1056 elif axisname == "z2":
1057 self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(1, 0, vz),
1058 lambda vz: self.vtickdirection(1, 0, vz, 0, 1, vz),
1059 self.zvgridpath))
1060 self.axes[axisname].hidden = not self.px1show and not self.py0show
1061 elif axisname == "z3":
1062 self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(0, 1, vz),
1063 lambda vz: self.vtickdirection(0, 1, vz, 1, 0, vz),
1064 self.zvgridpath))
1065 self.axes[axisname].hidden = not self.px0show and not self.py1show
1066 elif axisname == "z4":
1067 self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(1, 1, vz),
1068 lambda vz: self.vtickdirection(1, 1, vz, 0, 0, vz),
1069 self.zvgridpath))
1070 self.axes[axisname].hidden = not self.px1show and not self.py1show
1071 else:
1072 raise NotImplementedError("4 axis per dimension supported only")
1074 def dolayout(self):
1075 if self.did(self.dolayout):
1076 return
1077 for axisname in self.axes.keys():
1078 self.doaxiscreate(axisname)
1080 def dobackground(self):
1081 if self.did(self.dobackground):
1082 return
1084 def doaxes(self):
1085 if self.did(self.doaxes):
1086 return
1087 self.dolayout()
1088 self.dobackground()
1089 for axis in self.axes.values():
1090 if axis.hidden:
1091 self.layer("hiddenaxes").insert(axis.canvas)
1092 else:
1093 self.layer("axes").insert(axis.canvas)
1095 def dokey(self):
1096 if self.did(self.dokey):
1097 return
1098 self.dobackground()
1099 for plotitem in self.plotitems:
1100 self.dokeyitem(plotitem)
1101 if self.key is not None:
1102 c = self.key.paint(self.keyitems)
1103 bbox = c.bbox()
1104 def parentchildalign(pmin, pmax, cmin, cmax, pos, dist, inside):
1105 ppos = pmin+0.5*(cmax-cmin)+dist+pos*(pmax-pmin-cmax+cmin-2*dist)
1106 cpos = 0.5*(cmin+cmax)+(1-inside)*(1-2*pos)*(cmax-cmin+2*dist)
1107 return ppos-cpos
1108 if bbox:
1109 x = parentchildalign(self.xpos_pt, self.xpos_pt+self.size_pt,
1110 bbox.llx_pt, bbox.urx_pt,
1111 self.key.hpos, unit.topt(self.key.hdist), self.key.hinside)
1112 y = parentchildalign(self.ypos_pt, self.ypos_pt+self.size_pt,
1113 bbox.lly_pt, bbox.ury_pt,
1114 self.key.vpos, unit.topt(self.key.vdist), self.key.vinside)
1115 self.insert(c, [trafo.translate_pt(x, y)])