1 # -*- encoding: utf-8 -*-
4 # Copyright (C) 2002-2012 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2012 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 logging
, math
, re
, string
26 from pyx
import canvas
, path
, trafo
, unit
27 from pyx
.graph
.axis
import axis
, positioner
29 logger
= logging
.getLogger("pyx")
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
= set()
92 for privatedata
, s
in zip(self
.privatedatalist
, self
.styles
):
93 self
.usedcolumnnames
.update(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 list(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 list(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
= list(self
.data
.columns
.keys())
119 for values
in zip(*list(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
= list(self
.dynamiccolumns
.keys())
132 for values
in zip(*list(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", "axes.baseline", "axes.ticks", "axes.labels", "axes.title", "data", "key"]:
171 def did(self
, method
, *args
, **kwargs
):
172 if method
not in self
._calls
:
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 processPS(self
, file, writer
, context
, registry
, bbox
):
187 canvas
.canvas
.processPS(self
, file, writer
, context
, registry
, bbox
)
189 def processPDF(self
, file, writer
, context
, registry
, bbox
):
191 canvas
.canvas
.processPDF(self
, file, writer
, context
, registry
, bbox
)
193 def plot(self
, data
, styles
=None, rangewarning
=1):
194 if self
.didranges
and rangewarning
:
195 logger
.warning("axes ranges have already been analysed; no further adjustments will be performed")
197 raise RuntimeError("can't plot further data after dostyles() has been executed")
210 styles
= d
.defaultstyles
211 elif styles
!= d
.defaultstyles
:
212 raise RuntimeError("defaultstyles differ")
215 plotitems
.append(plotitem(self
, d
, styles
))
216 self
.plotitems
.extend(plotitems
)
218 for aplotitem
in plotitems
:
219 aplotitem
.makedynamicdata(self
)
226 if self
.did(self
.doranges
):
228 for plotitem
in self
.plotitems
:
229 plotitem
.adjustaxesstatic(self
)
230 for plotitem
in self
.plotitems
:
231 plotitem
.makedynamicdata(self
)
232 for plotitem
in self
.plotitems
:
233 plotitem
.adjustaxesdynamic(self
)
236 def doaxiscreate(self
, axisname
):
237 if self
.did(self
.doaxiscreate
, axisname
):
239 self
.doaxispositioner(axisname
)
240 self
.axes
[axisname
].create()
243 raise NotImplementedError
245 def dobackground(self
):
249 raise NotImplementedError
252 if self
.did(self
.dostyles
):
257 # count the usage of styles and perform selects
259 def stylesid(styles
):
260 return ":".join([str(id(style
)) for style
in styles
])
261 for plotitem
in self
.plotitems
:
263 styletotal
[stylesid(plotitem
.styles
)] += 1
265 styletotal
[stylesid(plotitem
.styles
)] = 1
267 for plotitem
in self
.plotitems
:
269 styleindex
[stylesid(plotitem
.styles
)] += 1
271 styleindex
[stylesid(plotitem
.styles
)] = 0
272 plotitem
.selectstyles(self
, styleindex
[stylesid(plotitem
.styles
)],
273 styletotal
[stylesid(plotitem
.styles
)])
277 def doplotitem(self
, plotitem
):
278 if self
.did(self
.doplotitem
, plotitem
):
284 for plotitem
in self
.plotitems
:
285 self
.doplotitem(plotitem
)
288 logger
.warning("dodata() has been deprecated. Use doplot() instead.")
291 def dokeyitem(self
, plotitem
):
292 if self
.did(self
.dokeyitem
, plotitem
):
295 if plotitem
.title
is not None:
296 self
.keyitems
.append(plotitem
)
299 raise NotImplementedError
308 class graphxy(graph
):
310 def __init__(self
, xpos
=0, ypos
=0, width
=None, height
=None, ratio
=goldenmean
,
311 key
=None, backgroundattrs
=None, axesdist
=0.8*unit
.v_cm
, flipped
=False,
312 xaxisat
=None, yaxisat
=None, **axes
):
317 self
.xpos_pt
= unit
.topt(self
.xpos
)
318 self
.ypos_pt
= unit
.topt(self
.ypos
)
319 self
.xaxisat
= xaxisat
320 self
.yaxisat
= yaxisat
322 self
.backgroundattrs
= backgroundattrs
323 self
.axesdist_pt
= unit
.topt(axesdist
)
324 self
.flipped
= flipped
330 raise ValueError("specify width and/or height")
332 self
.width
= ratio
* self
.height
334 self
.height
= (1.0/ratio
) * self
.width
335 self
.width_pt
= unit
.topt(self
.width
)
336 self
.height_pt
= unit
.topt(self
.height
)
338 for axisname
, aaxis
in list(axes
.items()):
339 if aaxis
is not None:
340 if not isinstance(aaxis
, axis
.linkedaxis
):
341 self
.axes
[axisname
] = axis
.anchoredaxis(aaxis
, self
.texrunner
, axisname
)
343 self
.axes
[axisname
] = aaxis
344 for axisname
, axisat
in [("x", xaxisat
), ("y", yaxisat
)]:
345 okey
= axisname
+ "2"
346 if axisname
not in axes
:
347 if okey
not in axes
or axes
[okey
] is None:
348 self
.axes
[axisname
] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
350 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
352 self
.axes
[axisname
] = axis
.linkedaxis(self
.axes
[okey
], axisname
)
353 elif okey
not in axes
and axisat
is None:
354 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
357 self
.xbasepath
= self
.axes
["x"].basepath
358 self
.xvbasepath
= self
.axes
["x"].vbasepath
359 self
.xgridpath
= self
.axes
["x"].gridpath
360 self
.xtickpoint_pt
= self
.axes
["x"].tickpoint_pt
361 self
.xtickpoint
= self
.axes
["x"].tickpoint
362 self
.xvtickpoint_pt
= self
.axes
["x"].vtickpoint_pt
363 self
.xvtickpoint
= self
.axes
["x"].tickpoint
364 self
.xtickdirection
= self
.axes
["x"].tickdirection
365 self
.xvtickdirection
= self
.axes
["x"].vtickdirection
368 self
.ybasepath
= self
.axes
["y"].basepath
369 self
.yvbasepath
= self
.axes
["y"].vbasepath
370 self
.ygridpath
= self
.axes
["y"].gridpath
371 self
.ytickpoint_pt
= self
.axes
["y"].tickpoint_pt
372 self
.ytickpoint
= self
.axes
["y"].tickpoint
373 self
.yvtickpoint_pt
= self
.axes
["y"].vtickpoint_pt
374 self
.yvtickpoint
= self
.axes
["y"].vtickpoint
375 self
.ytickdirection
= self
.axes
["y"].tickdirection
376 self
.yvtickdirection
= self
.axes
["y"].vtickdirection
378 self
.axesnames
= ([], [])
379 for axisname
, aaxis
in list(self
.axes
.items()):
380 if axisname
[0] not in "xy" or (len(axisname
) != 1 and (not axisname
[1:].isdigit() or
381 axisname
[1:] == "1")):
382 raise ValueError("invalid axis name")
383 if axisname
[0] == "x":
384 self
.axesnames
[0].append(axisname
)
386 self
.axesnames
[1].append(axisname
)
387 aaxis
.setcreatecall(self
.doaxiscreate
, axisname
)
389 self
.axespositioners
= dict(x
=positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
,
390 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
,
391 (0, 1), self
.xvgridpath
),
392 x2
=positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
+ self
.height_pt
,
393 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ self
.height_pt
,
394 (0, -1), self
.xvgridpath
),
395 y
=positioner
.lineaxispos_pt(self
.xpos_pt
, self
.ypos_pt
,
396 self
.xpos_pt
, self
.ypos_pt
+ self
.height_pt
,
397 (1, 0), self
.yvgridpath
),
398 y2
=positioner
.lineaxispos_pt(self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
,
399 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ self
.height_pt
,
400 (-1, 0), self
.yvgridpath
))
402 self
.axespositioners
= dict(x
=self
.axespositioners
["y2"],
403 y2
=self
.axespositioners
["x2"],
404 y
=self
.axespositioners
["x"],
405 x2
=self
.axespositioners
["y"])
407 def pos_pt(self
, x
, y
, xaxis
=None, yaxis
=None):
409 xaxis
= self
.axes
["x"]
411 yaxis
= self
.axes
["y"]
412 vx
= xaxis
.convert(x
)
413 vy
= yaxis
.convert(y
)
416 return (self
.xpos_pt
+ vx
*self
.width_pt
,
417 self
.ypos_pt
+ vy
*self
.height_pt
)
419 def pos(self
, x
, y
, xaxis
=None, yaxis
=None):
421 xaxis
= self
.axes
["x"]
423 yaxis
= self
.axes
["y"]
424 vx
= xaxis
.convert(x
)
425 vy
= yaxis
.convert(y
)
428 return (self
.xpos
+ vx
*self
.width
,
429 self
.ypos
+ vy
*self
.height
)
431 def vpos_pt(self
, vx
, vy
):
434 return (self
.xpos_pt
+ vx
*self
.width_pt
,
435 self
.ypos_pt
+ vy
*self
.height_pt
)
437 def vpos(self
, vx
, vy
):
440 return (self
.xpos
+ vx
*self
.width
,
441 self
.ypos
+ vy
*self
.height
)
443 def vzindex(self
, vx
, vy
):
446 def vangle(self
, vx1
, vy1
, vx2
, vy2
, vx3
, vy3
):
449 def vgeodesic(self
, vx1
, vy1
, vx2
, vy2
):
450 """returns a geodesic path between two points in graph coordinates"""
454 return path
.line_pt(self
.xpos_pt
+ vx1
*self
.width_pt
,
455 self
.ypos_pt
+ vy1
*self
.height_pt
,
456 self
.xpos_pt
+ vx2
*self
.width_pt
,
457 self
.ypos_pt
+ vy2
*self
.height_pt
)
459 def vgeodesic_el(self
, vx1
, vy1
, vx2
, vy2
):
460 """returns a geodesic path element between two points in graph coordinates"""
464 return path
.lineto_pt(self
.xpos_pt
+ vx2
*self
.width_pt
,
465 self
.ypos_pt
+ vy2
*self
.height_pt
)
467 def vcap_pt(self
, coordinate
, length_pt
, vx
, vy
):
468 """returns an error cap path for a given coordinate, lengths and
469 point in graph coordinates"""
471 coordinate
= 1-coordinate
474 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
- 0.5*length_pt
,
475 self
.ypos_pt
+ vy
*self
.height_pt
,
476 self
.xpos_pt
+ vx
*self
.width_pt
+ 0.5*length_pt
,
477 self
.ypos_pt
+ vy
*self
.height_pt
)
478 elif coordinate
== 1:
479 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
,
480 self
.ypos_pt
+ vy
*self
.height_pt
- 0.5*length_pt
,
481 self
.xpos_pt
+ vx
*self
.width_pt
,
482 self
.ypos_pt
+ vy
*self
.height_pt
+ 0.5*length_pt
)
484 raise ValueError("direction invalid")
486 def xvgridpath(self
, vx
):
487 return path
.line_pt(self
.xpos_pt
+ vx
*self
.width_pt
, self
.ypos_pt
,
488 self
.xpos_pt
+ vx
*self
.width_pt
, self
.ypos_pt
+ self
.height_pt
)
490 def yvgridpath(self
, vy
):
491 return path
.line_pt(self
.xpos_pt
, self
.ypos_pt
+ vy
*self
.height_pt
,
492 self
.xpos_pt
+ self
.width_pt
, self
.ypos_pt
+ vy
*self
.height_pt
)
494 def autokeygraphattrs(self
):
495 return dict(direction
="vertical", length
=self
.height
)
497 def autokeygraphtrafo(self
, keygraph
):
498 dependsonaxisnumber
= None
500 dependsonaxisname
= "x"
502 dependsonaxisname
= "y"
503 for axisname
in self
.axes
:
504 if axisname
[0] == dependsonaxisname
:
505 if len(axisname
) == 1:
507 axisnumber
= int(axisname
[1:])
508 if not (axisnumber
% 2) and not self
.flipped
or (axisnumber
% 2) and self
.flipped
:
509 if dependsonaxisnumber
is None or dependsonaxisnumber
< axisnumber
:
510 dependsonaxisnumber
= axisnumber
511 if dependsonaxisnumber
is None:
512 x_pt
= self
.xpos_pt
+ self
.width_pt
514 if dependsonaxisnumber
> 1:
515 dependsonaxisname
+= str(dependsonaxisnumber
)
516 self
.doaxiscreate(dependsonaxisname
)
517 x_pt
= self
.axes
[dependsonaxisname
].positioner
.x1_pt
+ self
.axes
[dependsonaxisname
].canvas
.extent_pt
518 x_pt
+= self
.axesdist_pt
519 return trafo
.translate_pt(x_pt
, self
.ypos_pt
)
521 def axisatv(self
, axis
, v
):
522 if axis
.positioner
.fixtickdirection
[0]:
524 t
= trafo
.translate_pt(self
.xpos_pt
+ v
*self
.width_pt
- axis
.positioner
.x1_pt
, 0)
527 t
= trafo
.translate_pt(0, self
.ypos_pt
+ v
*self
.height_pt
- axis
.positioner
.y1_pt
)
529 for layer
, subcanvas
in list(axis
.canvas
.layers
.items()):
530 c
.layer(layer
).insert(subcanvas
, [t
])
531 assert len(axis
.canvas
.layers
) == len(axis
.canvas
.items
), str(axis
.canvas
.items
)
534 def doaxispositioner(self
, axisname
):
535 if self
.did(self
.doaxispositioner
, axisname
):
538 if axisname
in ["x", "x2", "y", "y2"]:
539 self
.axes
[axisname
].setpositioner(self
.axespositioners
[axisname
])
541 if axisname
[1:] == "3":
542 dependsonaxisname
= axisname
[0]
544 dependsonaxisname
= "%s%d" % (axisname
[0], int(axisname
[1:]) - 2)
545 self
.doaxiscreate(dependsonaxisname
)
546 sign
= 2*(int(axisname
[1:]) % 2) - 1
547 if axisname
[0] == "x" and self
.flipped
:
549 if axisname
[0] == "x" and not self
.flipped
or axisname
[0] == "y" and self
.flipped
:
550 y_pt
= self
.axes
[dependsonaxisname
].positioner
.y1_pt
- sign
* (self
.axes
[dependsonaxisname
].canvas
.extent_pt
+ self
.axesdist_pt
)
551 self
.axes
[axisname
].setpositioner(positioner
.lineaxispos_pt(self
.xpos_pt
, y_pt
,
552 self
.xpos_pt
+ self
.width_pt
, y_pt
,
553 (0, sign
), self
.xvgridpath
))
555 x_pt
= self
.axes
[dependsonaxisname
].positioner
.x1_pt
- sign
* (self
.axes
[dependsonaxisname
].canvas
.extent_pt
+ self
.axesdist_pt
)
556 self
.axes
[axisname
].setpositioner(positioner
.lineaxispos_pt(x_pt
, self
.ypos_pt
,
557 x_pt
, self
.ypos_pt
+ self
.height_pt
,
558 (sign
, 0), self
.yvgridpath
))
561 if self
.did(self
.dolayout
):
563 for axisname
in list(self
.axes
.keys()):
564 self
.doaxiscreate(axisname
)
565 if self
.xaxisat
is not None:
566 self
.axisatv(self
.axes
["x"], self
.axes
["y"].convert(self
.xaxisat
))
567 if self
.yaxisat
is not None:
568 self
.axisatv(self
.axes
["y"], self
.axes
["x"].convert(self
.yaxisat
))
570 def dobackground(self
):
571 if self
.did(self
.dobackground
):
573 if self
.backgroundattrs
is not None:
574 self
.layer("background").draw(path
.rect_pt(self
.xpos_pt
, self
.ypos_pt
, self
.width_pt
, self
.height_pt
),
575 self
.backgroundattrs
)
578 if self
.did(self
.doaxes
):
582 for axis
in list(self
.axes
.values()):
583 for layer
, canvas
in list(axis
.canvas
.layers
.items()):
584 self
.layer("axes.%s" % layer
).insert(canvas
)
585 assert len(axis
.canvas
.layers
) == len(axis
.canvas
.items
), str(axis
.canvas
.items
)
588 if self
.did(self
.dokey
):
591 for plotitem
in self
.plotitems
:
592 self
.dokeyitem(plotitem
)
593 if self
.key
is not None:
594 c
= self
.key
.paint(self
.keyitems
)
596 def parentchildalign(pmin
, pmax
, cmin
, cmax
, pos
, dist
, inside
):
597 ppos
= pmin
+0.5*(cmax
-cmin
)+dist
+pos
*(pmax
-pmin
-cmax
+cmin
-2*dist
)
598 cpos
= 0.5*(cmin
+cmax
)+(1-inside
)*(1-2*pos
)*(cmax
-cmin
+2*dist
)
601 x
= parentchildalign(self
.xpos_pt
, self
.xpos_pt
+self
.width_pt
,
602 bbox
.llx_pt
, bbox
.urx_pt
,
603 self
.key
.hpos
, unit
.topt(self
.key
.hdist
), self
.key
.hinside
)
604 y
= parentchildalign(self
.ypos_pt
, self
.ypos_pt
+self
.height_pt
,
605 bbox
.lly_pt
, bbox
.ury_pt
,
606 self
.key
.vpos
, unit
.topt(self
.key
.vdist
), self
.key
.vinside
)
607 self
.layer("key").insert(c
, [trafo
.translate_pt(x
, y
)])
611 class graphx(graphxy
):
613 def __init__(self
, xpos
=0, ypos
=0, length
=None, size
=0.5*unit
.v_cm
, direction
="vertical",
614 key
=None, backgroundattrs
=None, axesdist
=0.8*unit
.v_cm
, **axes
):
616 if not name
.startswith("x"):
617 raise ValueError("Only x axes are allowed")
618 self
.direction
= direction
619 if self
.direction
== "vertical":
620 kwargsxy
= dict(width
=size
, height
=length
, flipped
=True)
621 elif self
.direction
== "horizontal":
622 kwargsxy
= dict(width
=length
, height
=size
)
624 raise ValueError("vertical or horizontal direction required")
625 kwargsxy
.update(**axes
)
627 graphxy
.__init
__(self
, xpos
=xpos
, ypos
=ypos
, ratio
=None, key
=key
, y
=axis
.lin(min=0, max=1, parter
=None),
628 backgroundattrs
=backgroundattrs
, axesdist
=axesdist
, **kwargsxy
)
630 def pos_pt(self
, x
, xaxis
=None):
631 return graphxy
.pos_pt(self
, x
, 0.5, xaxis
)
633 def pos(self
, x
, xaxis
=None):
634 return graphxy
.pos(self
, x
, 0.5, xaxis
)
636 def vpos_pt(self
, vx
):
637 return graphxy
.vpos_pt(self
, vx
, 0.5)
640 return graphxy
.vpos(self
, vx
, 0.5)
642 def vgeodesic(self
, vx1
, vx2
):
643 return graphxy
.vgeodesic(self
, vx1
, 0.5, vx2
, 0.5)
645 def vgeodesic_el(self
, vx1
, vy1
, vx2
, vy2
):
646 return graphxy
.vgeodesic_el(self
, vx1
, 0.5, vx2
, 0.5)
648 def vcap_pt(self
, coordinate
, length_pt
, vx
):
650 return graphxy
.vcap_pt(self
, coordinate
, length_pt
, vx
, 0.5)
652 raise ValueError("direction invalid")
654 def xvgridpath(self
, vx
):
655 return graphxy
.xvgridpath(self
, vx
)
657 def yvgridpath(self
, vy
):
658 raise Exception("This method does not exist on a one dimensional graph.")
660 def axisatv(self
, axis
, v
):
661 raise Exception("This method does not exist on a one dimensional graph.")
665 class graphxyz(graph
):
669 def __init__(self
, distance
, phi
, theta
, anglefactor
=math
.pi
/180):
672 self
.distance
= distance
674 self
.a
= (-math
.sin(phi
), math
.cos(phi
), 0)
675 self
.b
= (-math
.cos(phi
)*math
.sin(theta
),
676 -math
.sin(phi
)*math
.sin(theta
),
678 self
.eye
= (distance
*math
.cos(phi
)*math
.cos(theta
),
679 distance
*math
.sin(phi
)*math
.cos(theta
),
680 distance
*math
.sin(theta
))
682 def point(self
, x
, y
, z
):
683 d0
= (self
.a
[0]*self
.b
[1]*(z
-self
.eye
[2])
684 + self
.a
[2]*self
.b
[0]*(y
-self
.eye
[1])
685 + self
.a
[1]*self
.b
[2]*(x
-self
.eye
[0])
686 - self
.a
[2]*self
.b
[1]*(x
-self
.eye
[0])
687 - self
.a
[0]*self
.b
[2]*(y
-self
.eye
[1])
688 - self
.a
[1]*self
.b
[0]*(z
-self
.eye
[2]))
689 da
= (self
.eye
[0]*self
.b
[1]*(z
-self
.eye
[2])
690 + self
.eye
[2]*self
.b
[0]*(y
-self
.eye
[1])
691 + self
.eye
[1]*self
.b
[2]*(x
-self
.eye
[0])
692 - self
.eye
[2]*self
.b
[1]*(x
-self
.eye
[0])
693 - self
.eye
[0]*self
.b
[2]*(y
-self
.eye
[1])
694 - self
.eye
[1]*self
.b
[0]*(z
-self
.eye
[2]))
695 db
= (self
.a
[0]*self
.eye
[1]*(z
-self
.eye
[2])
696 + self
.a
[2]*self
.eye
[0]*(y
-self
.eye
[1])
697 + self
.a
[1]*self
.eye
[2]*(x
-self
.eye
[0])
698 - self
.a
[2]*self
.eye
[1]*(x
-self
.eye
[0])
699 - self
.a
[0]*self
.eye
[2]*(y
-self
.eye
[1])
700 - self
.a
[1]*self
.eye
[0]*(z
-self
.eye
[2]))
703 def zindex(self
, x
, y
, z
):
704 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
706 def angle(self
, x1
, y1
, z1
, x2
, y2
, z2
, x3
, y3
, z3
):
707 sx
= (x1
-self
.eye
[0])
708 sy
= (y1
-self
.eye
[1])
709 sz
= (z1
-self
.eye
[2])
710 nx
= (y2
-y1
)*(z3
-z1
)-(z2
-z1
)*(y3
-y1
)
711 ny
= (z2
-z1
)*(x3
-x1
)-(x2
-x1
)*(z3
-z1
)
712 nz
= (x2
-x1
)*(y3
-y1
)-(y2
-y1
)*(x3
-x1
)
713 return (sx
*nx
+sy
*ny
+sz
*nz
)/math
.sqrt(nx
*nx
+ny
*ny
+nz
*nz
)/math
.sqrt(sx
*sx
+sy
*sy
+sz
*sz
)
718 def __init__(self
, phi
, theta
, anglefactor
=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
),
726 self
.c
= (-math
.cos(phi
)*math
.cos(theta
),
727 -math
.sin(phi
)*math
.cos(theta
),
730 def point(self
, x
, y
, z
):
731 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
733 def zindex(self
, x
, y
, z
):
734 return self
.c
[0]*x
+self
.c
[1]*y
+self
.c
[2]*z
736 def angle(self
, x1
, y1
, z1
, x2
, y2
, z2
, x3
, y3
, z3
):
737 nx
= (y2
-y1
)*(z3
-z1
)-(z2
-z1
)*(y3
-y1
)
738 ny
= (z2
-z1
)*(x3
-x1
)-(x2
-x1
)*(z3
-z1
)
739 nz
= (x2
-x1
)*(y3
-y1
)-(y2
-y1
)*(x3
-x1
)
740 return (self
.c
[0]*nx
+self
.c
[1]*ny
+self
.c
[2]*nz
)/math
.sqrt(nx
*nx
+ny
*ny
+nz
*nz
)
743 def __init__(self
, xpos
=0, ypos
=0, size
=None,
744 xscale
=1, yscale
=1, zscale
=1/goldenmean
, xy12axesat
=None, xy12axesatname
="z",
745 projector
=central(10, -30, 30), axesdist
=0.8*unit
.v_cm
, key
=None,
748 for name
in ["hiddenaxes.grid", "hiddenaxes.baseline", "hiddenaxes.ticks", "hiddenaxes.labels", "hiddenaxes.title"]:
750 self
.layer("hiddenaxes", below
="filldata")
755 self
.xpos_pt
= unit
.topt(xpos
)
756 self
.ypos_pt
= unit
.topt(ypos
)
757 self
.size_pt
= unit
.topt(size
)
761 self
.xy12axesat
= xy12axesat
762 self
.xy12axesatname
= xy12axesatname
763 self
.projector
= projector
764 self
.axesdist_pt
= unit
.topt(axesdist
)
767 self
.xorder
= projector
.zindex(0, -1, 0) > projector
.zindex(0, 1, 0) and 1 or 0
768 self
.yorder
= projector
.zindex(-1, 0, 0) > projector
.zindex(1, 0, 0) and 1 or 0
769 self
.zindexscale
= math
.sqrt(xscale
*xscale
+yscale
*yscale
+zscale
*zscale
)
771 # the pXYshow attributes are booleans stating whether plane perpendicular to axis X
772 # at the virtual graph coordinate Y will be hidden by data or not. An axis is considered
773 # to be visible if one of the two planes it is part of is visible. Other axes are drawn
774 # in the hiddenaxes layer (i.e. layer group).
775 # TODO: Tick and grid visibility is treated like the axis visibility at the moment.
776 self
.pz0show
= self
.vangle(0, 0, 0, 1, 0, 0, 1, 1, 0) > 0
777 self
.pz1show
= self
.vangle(0, 0, 1, 0, 1, 1, 1, 1, 1) > 0
778 self
.py0show
= self
.vangle(0, 0, 0, 0, 0, 1, 1, 0, 1) > 0
779 self
.py1show
= self
.vangle(0, 1, 0, 1, 1, 0, 1, 1, 1) > 0
780 self
.px0show
= self
.vangle(0, 0, 0, 0, 1, 0, 0, 1, 1) > 0
781 self
.px1show
= self
.vangle(1, 0, 0, 1, 0, 1, 1, 1, 1) > 0
783 for axisname
, aaxis
in list(axes
.items()):
784 if aaxis
is not None:
785 if not isinstance(aaxis
, axis
.linkedaxis
):
786 self
.axes
[axisname
] = axis
.anchoredaxis(aaxis
, self
.texrunner
, axisname
)
788 self
.axes
[axisname
] = aaxis
789 for axisname
in ["x", "y"]:
790 okey
= axisname
+ "2"
791 if axisname
not in axes
:
792 if okey
not in axes
or axes
[okey
] is None:
793 self
.axes
[axisname
] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, axisname
)
795 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
797 self
.axes
[axisname
] = axis
.linkedaxis(self
.axes
[okey
], axisname
)
798 elif okey
not in axes
:
799 self
.axes
[okey
] = axis
.linkedaxis(self
.axes
[axisname
], okey
)
801 self
.axes
["z"] = axis
.anchoredaxis(axis
.linear(), self
.texrunner
, "z")
804 self
.xbasepath
= self
.axes
["x"].basepath
805 self
.xvbasepath
= self
.axes
["x"].vbasepath
806 self
.xgridpath
= self
.axes
["x"].gridpath
807 self
.xtickpoint_pt
= self
.axes
["x"].tickpoint_pt
808 self
.xtickpoint
= self
.axes
["x"].tickpoint
809 self
.xvtickpoint_pt
= self
.axes
["x"].vtickpoint_pt
810 self
.xvtickpoint
= self
.axes
["x"].tickpoint
811 self
.xtickdirection
= self
.axes
["x"].tickdirection
812 self
.xvtickdirection
= self
.axes
["x"].vtickdirection
815 self
.ybasepath
= self
.axes
["y"].basepath
816 self
.yvbasepath
= self
.axes
["y"].vbasepath
817 self
.ygridpath
= self
.axes
["y"].gridpath
818 self
.ytickpoint_pt
= self
.axes
["y"].tickpoint_pt
819 self
.ytickpoint
= self
.axes
["y"].tickpoint
820 self
.yvtickpoint_pt
= self
.axes
["y"].vtickpoint_pt
821 self
.yvtickpoint
= self
.axes
["y"].vtickpoint
822 self
.ytickdirection
= self
.axes
["y"].tickdirection
823 self
.yvtickdirection
= self
.axes
["y"].vtickdirection
826 self
.zbasepath
= self
.axes
["z"].basepath
827 self
.zvbasepath
= self
.axes
["z"].vbasepath
828 self
.zgridpath
= self
.axes
["z"].gridpath
829 self
.ztickpoint_pt
= self
.axes
["z"].tickpoint_pt
830 self
.ztickpoint
= self
.axes
["z"].tickpoint
831 self
.zvtickpoint_pt
= self
.axes
["z"].vtickpoint
832 self
.zvtickpoint
= self
.axes
["z"].vtickpoint
833 self
.ztickdirection
= self
.axes
["z"].tickdirection
834 self
.zvtickdirection
= self
.axes
["z"].vtickdirection
836 self
.axesnames
= ([], [], [])
837 for axisname
, aaxis
in list(self
.axes
.items()):
838 if axisname
[0] not in "xyz" or (len(axisname
) != 1 and (not axisname
[1:].isdigit() or
839 axisname
[1:] == "1")):
840 raise ValueError("invalid axis name")
841 if axisname
[0] == "x":
842 self
.axesnames
[0].append(axisname
)
843 elif axisname
[0] == "y":
844 self
.axesnames
[1].append(axisname
)
846 self
.axesnames
[2].append(axisname
)
847 aaxis
.setcreatecall(self
.doaxiscreate
, axisname
)
849 def pos_pt(self
, x
, y
, z
, xaxis
=None, yaxis
=None, zaxis
=None):
851 xaxis
= self
.axes
["x"]
853 yaxis
= self
.axes
["y"]
855 zaxis
= self
.axes
["z"]
856 return self
.vpos_pt(xaxis
.convert(x
), yaxis
.convert(y
), zaxis
.convert(z
))
858 def pos(self
, x
, y
, z
, xaxis
=None, yaxis
=None, zaxis
=None):
860 xaxis
= self
.axes
["x"]
862 yaxis
= self
.axes
["y"]
864 zaxis
= self
.axes
["z"]
865 return self
.vpos(xaxis
.convert(x
), yaxis
.convert(y
), zaxis
.convert(z
))
867 def vpos_pt(self
, vx
, vy
, vz
):
868 x
, y
= self
.projector
.point(2*self
.xscale
*(vx
- 0.5),
869 2*self
.yscale
*(vy
- 0.5),
870 2*self
.zscale
*(vz
- 0.5))
871 return self
.xpos_pt
+x
*self
.size_pt
, self
.ypos_pt
+y
*self
.size_pt
873 def vpos(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
+x
*self
.size
, self
.ypos
+y
*self
.size
879 def vzindex(self
, vx
, vy
, vz
):
880 return self
.projector
.zindex(2*self
.xscale
*(vx
- 0.5),
881 2*self
.yscale
*(vy
- 0.5),
882 2*self
.zscale
*(vz
- 0.5))/self
.zindexscale
884 def vangle(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
, vx3
, vy3
, vz3
):
885 return self
.projector
.angle(2*self
.xscale
*(vx1
- 0.5),
886 2*self
.yscale
*(vy1
- 0.5),
887 2*self
.zscale
*(vz1
- 0.5),
888 2*self
.xscale
*(vx2
- 0.5),
889 2*self
.yscale
*(vy2
- 0.5),
890 2*self
.zscale
*(vz2
- 0.5),
891 2*self
.xscale
*(vx3
- 0.5),
892 2*self
.yscale
*(vy3
- 0.5),
893 2*self
.zscale
*(vz3
- 0.5))
895 def vgeodesic(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
896 """returns a geodesic path between two points in graph coordinates"""
897 return path
.line_pt(*(self
.vpos_pt(vx1
, vy1
, vz1
) + self
.vpos_pt(vx2
, vy2
, vz2
)))
899 def vgeodesic_el(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
900 """returns a geodesic path element between two points in graph coordinates"""
901 return path
.lineto_pt(*self
.vpos_pt(vx2
, vy2
, vz2
))
903 def vcap_pt(self
, coordinate
, length_pt
, vx
, vy
, vz
):
904 """returns an error cap path for a given coordinate, lengths and
905 point in graph coordinates"""
907 return self
.vgeodesic(vx
-0.5*length_pt
/self
.size_pt
, vy
, vz
, vx
+0.5*length_pt
/self
.size_pt
, vy
, vz
)
908 elif coordinate
== 1:
909 return self
.vgeodesic(vx
, vy
-0.5*length_pt
/self
.size_pt
, vz
, vx
, vy
+0.5*length_pt
/self
.size_pt
, vz
)
910 elif coordinate
== 2:
911 return self
.vgeodesic(vx
, vy
, vz
-0.5*length_pt
/self
.size_pt
, vx
, vy
, vz
+0.5*length_pt
/self
.size_pt
)
913 raise ValueError("direction invalid")
915 def xvtickdirection(self
, vx
):
917 x1_pt
, y1_pt
= self
.vpos_pt(vx
, 1, 0)
918 x2_pt
, y2_pt
= self
.vpos_pt(vx
, 0, 0)
920 x1_pt
, y1_pt
= self
.vpos_pt(vx
, 0, 0)
921 x2_pt
, y2_pt
= self
.vpos_pt(vx
, 1, 0)
922 dx_pt
= x2_pt
- x1_pt
923 dy_pt
= y2_pt
- y1_pt
924 norm
= math
.hypot(dx_pt
, dy_pt
)
925 return dx_pt
/norm
, dy_pt
/norm
927 def yvtickdirection(self
, vy
):
929 x1_pt
, y1_pt
= self
.vpos_pt(1, vy
, 0)
930 x2_pt
, y2_pt
= self
.vpos_pt(0, vy
, 0)
932 x1_pt
, y1_pt
= self
.vpos_pt(0, vy
, 0)
933 x2_pt
, y2_pt
= self
.vpos_pt(1, vy
, 0)
934 dx_pt
= x2_pt
- x1_pt
935 dy_pt
= y2_pt
- y1_pt
936 norm
= math
.hypot(dx_pt
, dy_pt
)
937 return dx_pt
/norm
, dy_pt
/norm
939 def vtickdirection(self
, vx1
, vy1
, vz1
, vx2
, vy2
, vz2
):
940 x1_pt
, y1_pt
= self
.vpos_pt(vx1
, vy1
, vz1
)
941 x2_pt
, y2_pt
= self
.vpos_pt(vx2
, vy2
, vz2
)
942 dx_pt
= x2_pt
- x1_pt
943 dy_pt
= y2_pt
- y1_pt
944 norm
= math
.hypot(dx_pt
, dy_pt
)
945 return dx_pt
/norm
, dy_pt
/norm
947 def xvgridpath(self
, vx
):
948 return path
.path(path
.moveto_pt(*self
.vpos_pt(vx
, 0, 0)),
949 path
.lineto_pt(*self
.vpos_pt(vx
, 1, 0)),
950 path
.lineto_pt(*self
.vpos_pt(vx
, 1, 1)),
951 path
.lineto_pt(*self
.vpos_pt(vx
, 0, 1)),
954 def yvgridpath(self
, vy
):
955 return path
.path(path
.moveto_pt(*self
.vpos_pt(0, vy
, 0)),
956 path
.lineto_pt(*self
.vpos_pt(1, vy
, 0)),
957 path
.lineto_pt(*self
.vpos_pt(1, vy
, 1)),
958 path
.lineto_pt(*self
.vpos_pt(0, vy
, 1)),
961 def zvgridpath(self
, vz
):
962 return path
.path(path
.moveto_pt(*self
.vpos_pt(0, 0, vz
)),
963 path
.lineto_pt(*self
.vpos_pt(1, 0, vz
)),
964 path
.lineto_pt(*self
.vpos_pt(1, 1, vz
)),
965 path
.lineto_pt(*self
.vpos_pt(0, 1, vz
)),
968 def autokeygraphattrs(self
):
969 return dict(direction
="vertical", length
=self
.size
)
971 def autokeygraphtrafo(self
, keygraph
):
973 x_pt
= self
.layer("axes").bbox().right_pt() + self
.axesdist_pt
974 y_pt
= 0.5*(self
.layer("axes").bbox().top_pt() + self
.layer("axes").bbox().bottom_pt() - self
.size_pt
)
975 return trafo
.translate_pt(x_pt
, y_pt
)
977 def doaxispositioner(self
, axisname
):
978 if self
.did(self
.doaxispositioner
, axisname
):
981 if self
.xy12axesat
is not None:
982 self
.doaxiscreate(self
.xy12axesatname
)
983 self
.doaxispositioner(self
.xy12axesatname
)
984 xy12axesatv
= self
.axes
[self
.xy12axesatname
].convert(self
.xy12axesat
)
988 self
.axes
[axisname
].setpositioner(positioner
.flexlineaxispos_pt(lambda vx
: self
.vpos_pt(vx
, self
.xorder
, xy12axesatv
),
989 lambda vx
: self
.vtickdirection(vx
, self
.xorder
, 0, vx
, 1-self
.xorder
, xy12axesatv
),
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
, xy12axesatv
),
997 lambda vx
: self
.vtickdirection(vx
, 1-self
.xorder
, 0, vx
, self
.xorder
, xy12axesatv
),
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
, xy12axesatv
),
1021 lambda vy
: self
.vtickdirection(self
.yorder
, vy
, 0, 1-self
.yorder
, vy
, xy12axesatv
),
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
, xy12axesatv
),
1029 lambda vy
: self
.vtickdirection(1-self
.yorder
, vy
, 0, self
.yorder
, vy
, xy12axesatv
),
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 list(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 list(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
)])