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.
39 def registerdefaultprovider(style
, keys
):
40 """sets a style as a default creator for sharedata variables '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
]
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."""
67 def __init__(self
, graph
, data
, styles
):
69 self
.title
= data
.title
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
79 defaultprovider
= getdefaultprovider(n
)
80 addstyles
.append(defaultprovider
)
81 provided
.extend(defaultprovider
.providesdata
)
82 provided
.extend(s
.providesdata
)
83 styles
= addstyles
+ 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
):
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:
153 elif len(stylesdata
) == 1:
155 raise AttributeError("access to styledata attribute '%s' failed" % attr
)
158 class graph(canvas
.canvas
):
161 canvas
.canvas
.__init
__(self
)
162 for name
in ["background", "filldata", "axes.grid", "axis.baseline", "axis.ticks", "axis.labels", "axis.title", "data", "key"]:
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
):
177 self
._calls
[method
].append((args
, kwargs
))
182 return canvas
.canvas
.bbox(self
)
185 def registerPS(self
, registry
):
187 canvas
.canvas
.registerPS(self
, registry
)
189 def registerPDF(self
, registry
):
191 canvas
.canvas
.registerPDF(self
, registry
)
193 def processPS(self
, file, writer
, context
, registry
, bbox
):
195 canvas
.canvas
.processPS(self
, file, writer
, context
, registry
, bbox
)
197 def processPDF(self
, file, writer
, context
, registry
, bbox
):
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")
205 raise RuntimeError("can't plot further data after dostyles() has been executed")
218 styles
= d
.defaultstyles
219 elif styles
!= d
.defaultstyles
:
220 raise RuntimeError("defaultstyles differ")
223 plotitems
.append(plotitem(self
, d
, styles
))
224 self
.plotitems
.extend(plotitems
)
226 for aplotitem
in plotitems
:
227 aplotitem
.makedynamicdata(self
)
234 if self
.did(self
.doranges
):
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
)
244 def doaxiscreate(self
, axisname
):
245 if self
.did(self
.doaxiscreate
, axisname
):
247 self
.doaxispositioner(axisname
)
248 self
.axes
[axisname
].create()
251 raise NotImplementedError
253 def dobackground(self
):
257 raise NotImplementedError
260 if self
.did(self
.dostyles
):
265 # count the usage of styles and perform selects
267 def stylesid(styles
):
268 return ":".join([str(id(style
)) for style
in styles
])
269 for plotitem
in self
.plotitems
:
271 styletotal
[stylesid(plotitem
.styles
)] += 1
273 styletotal
[stylesid(plotitem
.styles
)] = 1
275 for plotitem
in self
.plotitems
:
277 styleindex
[stylesid(plotitem
.styles
)] += 1
279 styleindex
[stylesid(plotitem
.styles
)] = 0
280 plotitem
.selectstyles(self
, styleindex
[stylesid(plotitem
.styles
)],
281 styletotal
[stylesid(plotitem
.styles
)])
285 def doplotitem(self
, plotitem
):
286 if self
.did(self
.doplotitem
, plotitem
):
292 for plotitem
in self
.plotitems
:
293 self
.doplotitem(plotitem
)
296 warnings
.warn("dodata() has been deprecated. Use doplot() instead.")
299 def dokeyitem(self
, plotitem
):
300 if self
.did(self
.dokeyitem
, plotitem
):
303 if plotitem
.title
is not None:
304 self
.keyitems
.append(plotitem
)
307 raise NotImplementedError
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
):
325 self
.xpos_pt
= unit
.topt(self
.xpos
)
326 self
.ypos_pt
= unit
.topt(self
.ypos
)
327 self
.xaxisat
= xaxisat
328 self
.yaxisat
= yaxisat
330 self
.backgroundattrs
= backgroundattrs
331 self
.axesdist_pt
= unit
.topt(axesdist
)
332 self
.flipped
= flipped
338 raise ValueError("specify width and/or height")
340 self
.width
= ratio
* self
.height
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
)
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
)
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
)
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
))
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):
417 xaxis
= self
.axes
["x"]
419 yaxis
= self
.axes
["y"]
420 vx
= xaxis
.convert(x
)
421 vy
= yaxis
.convert(y
)
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):
429 xaxis
= self
.axes
["x"]
431 yaxis
= self
.axes
["y"]
432 vx
= xaxis
.convert(x
)
433 vy
= yaxis
.convert(y
)
436 return (self
.xpos
+ vx
*self
.width
,
437 self
.ypos
+ vy
*self
.height
)
439 def vpos_pt(self
, vx
, vy
):
442 return (self
.xpos_pt
+ vx
*self
.width_pt
,
443 self
.ypos_pt
+ vy
*self
.height_pt
)
445 def vpos(self
, vx
, vy
):
448 return (self
.xpos
+ vx
*self
.width
,
449 self
.ypos
+ vy
*self
.height
)
451 def vzindex(self
, vx
, vy
):
454 def vangle(self
, vx1
, vy1
, vx2
, vy2
, vx3
, vy3
):
457 def vgeodesic(self
, vx1
, vy1
, vx2
, vy2
):
458 """returns a geodesic path between two points in graph coordinates"""
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"""
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"""
479 coordinate
= 1-coordinate
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
)
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
508 dependsonaxisname
= "x"
510 dependsonaxisname
= "y"
511 for axisname
in self
.axes
:
512 if axisname
[0] == dependsonaxisname
:
513 if len(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
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]:
532 t
= trafo
.translate_pt(self
.xpos_pt
+ v
*self
.width_pt
- axis
.positioner
.x1_pt
, 0)
535 t
= trafo
.translate_pt(0, self
.ypos_pt
+ v
*self
.height_pt
- axis
.positioner
.y1_pt
)
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
)
542 def doaxispositioner(self
, axisname
):
543 if self
.did(self
.doaxispositioner
, axisname
):
546 if axisname
in ["x", "x2", "y", "y2"]:
547 self
.axes
[axisname
].setpositioner(self
.axespositioners
[axisname
])
549 if axisname
[1:] == "3":
550 dependsonaxisname
= axisname
[0]
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
:
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
))
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
))
569 if self
.did(self
.dolayout
):
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
):
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
)
586 if self
.did(self
.doaxes
):
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
)
596 if self
.did(self
.dokey
):
599 for plotitem
in self
.plotitems
:
600 self
.dokeyitem(plotitem
)
601 if self
.key
is not None:
602 c
= self
.key
.paint(self
.keyitems
)
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
)
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
):
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
)
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)
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
):
658 return graphxy
.vcap_pt(self
, coordinate
, length_pt
, vx
, 0.5)
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
):
677 def __init__(self
, distance
, phi
, theta
, anglefactor
=math
.pi
/180):
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
),
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]))
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
)
726 def __init__(self
, phi
, theta
, anglefactor
=math
.pi
/180):
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
),
734 self
.c
= (-math
.cos(phi
)*math
.cos(theta
),
735 -math
.sin(phi
)*math
.cos(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,
756 self
.layer("hiddenaxes", below
=self
.layer("filldata"))
757 for name
in ["hiddenaxes.grid", "hiddenaxes.baseline", "hiddenaxes.ticks", "hiddenaxes.labels", "hiddenaxes.title"]:
763 self
.xpos_pt
= unit
.topt(xpos
)
764 self
.ypos_pt
= unit
.topt(ypos
)
765 self
.size_pt
= unit
.topt(size
)
769 self
.projector
= projector
770 self
.axesdist_pt
= unit
.topt(axesdist
)
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
)
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
)
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
)
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):
857 xaxis
= self
.axes
["x"]
859 yaxis
= self
.axes
["y"]
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):
866 xaxis
= self
.axes
["x"]
868 yaxis
= self
.axes
["y"]
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"""
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
)
919 raise ValueError("direction invalid")
921 def xvtickdirection(self
, vx
):
923 x1_pt
, y1_pt
= self
.vpos_pt(vx
, 1, 0)
924 x2_pt
, y2_pt
= self
.vpos_pt(vx
, 0, 0)
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
):
935 x1_pt
, y1_pt
= self
.vpos_pt(1, vy
, 0)
936 x2_pt
, y2_pt
= self
.vpos_pt(0, vy
, 0)
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)),
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)),
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
)),
974 def autokeygraphattrs(self
):
975 return dict(direction
="vertical", length
=self
.size
)
977 def autokeygraphtrafo(self
, keygraph
):
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
):
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),
992 self
.axes
[axisname
].hidden
= not self
.py1show
and not self
.pz0show
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),
1000 self
.axes
[axisname
].hidden
= not self
.py0show
and not self
.pz0show
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),
1008 self
.axes
[axisname
].hidden
= not self
.py1show
and not self
.pz1show
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),
1016 self
.axes
[axisname
].hidden
= not self
.py0show
and not self
.pz1show
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),
1024 self
.axes
[axisname
].hidden
= not self
.px1show
and not self
.pz0show
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),
1032 self
.axes
[axisname
].hidden
= not self
.px0show
and not self
.pz0show
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),
1040 self
.axes
[axisname
].hidden
= not self
.px1show
and not self
.pz1show
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),
1048 self
.axes
[axisname
].hidden
= not self
.px0show
and not self
.pz1show
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
),
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
),
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
),
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
),
1070 self
.axes
[axisname
].hidden
= not self
.px1show
and not self
.py1show
1072 raise NotImplementedError("4 axis per dimension supported only")
1075 if self
.did(self
.dolayout
):
1077 for axisname
in self
.axes
.keys():
1078 self
.doaxiscreate(axisname
)
1080 def dobackground(self
):
1081 if self
.did(self
.dobackground
):
1085 if self
.did(self
.doaxes
):
1089 for axis
in self
.axes
.values():
1091 self
.layer("hiddenaxes").insert(axis
.canvas
)
1093 self
.layer("axes").insert(axis
.canvas
)
1096 if self
.did(self
.dokey
):
1099 for plotitem
in self
.plotitems
:
1100 self
.dokeyitem(plotitem
)
1101 if self
.key
is not None:
1102 c
= self
.key
.paint(self
.keyitems
)
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
)
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
)])