# -*- coding: ISO-8859-1 -*-
#
#
# Copyright (C) 2002-2004 Jrg Lehmann <joergl@users.sourceforge.net>
# Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
# Copyright (C) 2002-2006 Andr Wobst <wobsta@users.sourceforge.net>
#
# This file is part of PyX (http://pyx.sourceforge.net/).
#
# PyX is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# PyX is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PyX; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
from __future__ import nested_scopes
import math, re, string, warnings
from pyx import canvas,path,trafo,unit
from pyx.graph import style
from pyx.graph.axis import axis,positioner
goldenmean = 0.5 * (math.sqrt(5) + 1)
class styledata:
"""style data storage class
Instances of this class are used to store data from the styles
and to pass point data to the styles by instances named privatedata
and sharedata. sharedata is shared between all the style(s) in use
by a data instance, while privatedata is private to each style and
used as a storage place instead of self to prevent side effects when
using a style several times."""
pass
class plotitem:
def __init__(self, graph, data, styles):
self.data = data
self.title = data.title
addstyles = [None]
while addstyles:
# add styles to ensure all needs of the given styles
provided = [] # already provided sharedata variables
addstyles = [] # a list of style instances to be added in front
for s in styles:
for n in s.needsdata:
if n not in provided:
defaultprovider = style.getdefaultprovider(n)
addstyles.append(defaultprovider)
provided.extend(defaultprovider.providesdata)
provided.extend(s.providesdata)
styles = addstyles + styles
self.styles = styles
self.sharedata = styledata()
self.privatedatalist = [styledata() for s in self.styles]
# perform setcolumns to all styles
self.usedcolumnnames = []
for privatedata, s in zip(self.privatedatalist, self.styles):
self.usedcolumnnames.extend(s.columnnames(privatedata, self.sharedata, graph, self.data.columnnames))
def selectstyles(self, graph, selectindex, selecttotal):
for privatedata, style in zip(self.privatedatalist, self.styles):
style.selectstyle(privatedata, self.sharedata, graph, selectindex, selecttotal)
def adjustaxesstatic(self, graph):
for columnname, data in self.data.columns.items():
for privatedata, style in zip(self.privatedatalist, self.styles):
style.adjustaxis(privatedata, self.sharedata, graph, columnname, data)
def makedynamicdata(self, graph):
self.dynamiccolumns = self.data.dynamiccolumns(graph)
def adjustaxesdynamic(self, graph):
for columnname, data in self.dynamiccolumns.items():
for privatedata, style in zip(self.privatedatalist, self.styles):
style.adjustaxis(privatedata, self.sharedata, graph, columnname, data)
def draw(self, graph):
for privatedata, style in zip(self.privatedatalist, self.styles):
style.initdrawpoints(privatedata, self.sharedata, graph)
point = {}
useitems = []
for columnname in self.usedcolumnnames:
try:
useitems.append((columnname, self.dynamiccolumns[columnname]))
except KeyError:
useitems.append((columnname, self.data.columns[columnname]))
if not useitems:
raise ValueError("cannot draw empty data")
for i in xrange(len(useitems[0][1])):
for columnname, data in useitems:
point[columnname] = data[i]
for privatedata, style in zip(self.privatedatalist, self.styles):
style.drawpoint(privatedata, self.sharedata, graph, point)
for privatedata, style in zip(self.privatedatalist, self.styles):
style.donedrawpoints(privatedata, self.sharedata, graph)
def key_pt(self, graph, x_pt, y_pt, width_pt, height_pt):
for privatedata, style in zip(self.privatedatalist, self.styles):
style.key_pt(privatedata, self.sharedata, graph, x_pt, y_pt, width_pt, height_pt)
def __getattr__(self, attr):
# read only access to the styles privatedata
stylesdata = [getattr(styledata, attr)
for styledata in self.privatedatalist
if hasattr(styledata, attr)]
if len(stylesdata) > 1:
return stylesdata
elif len(stylesdata) == 1:
return stylesdata[0]
raise AttributeError("access to styledata attribute '%s' failed" % attr)
class graph(canvas.canvas):
def __init__(self):
canvas.canvas.__init__(self)
self.axes = {}
self.plotitems = []
self._calls = {}
self.didranges = 0
self.didstyles = 0
def did(self, method, *args, **kwargs):
if not self._calls.has_key(method):
self._calls[method] = []
for callargs in self._calls[method]:
if callargs == (args, kwargs):
return 1
self._calls[method].append((args, kwargs))
return 0
def bbox(self):
self.finish()
return canvas.canvas.bbox(self)
def registerPS(self, registry):
self.finish()
canvas.canvas.registerPS(self, registry)
def registerPDF(self, registry):
self.finish()
canvas.canvas.registerPDF(self, registry)
def processPS(self, file, writer, context, registry, bbox):
self.finish()
canvas.canvas.processPS(self, file, writer, context, registry, bbox)
def processPDF(self, file, writer, context, registry, bbox):
self.finish()
canvas.canvas.processPDF(self, file, writer, context, registry, bbox)
def plot(self, data, styles=None, rangewarning=1):
if self.didranges and rangewarning:
warnings.warn("axes ranges have already been analysed; no further adjustments will be performed")
if self.didstyles:
raise RuntimeError("can't plot further data after dostyles() has been executed")
singledata = 0
try:
for d in data:
pass
except:
usedata = [data]
singledata = 1
else:
usedata = data
if styles is None:
for d in usedata:
if styles is None:
styles = d.defaultstyles
elif styles != d.defaultstyles:
raise RuntimeError("defaultstyles differ")
plotitems = []
for d in usedata:
plotitems.append(plotitem(self, d, styles))
self.plotitems.extend(plotitems)
if self.didranges:
for aplotitem in plotitems:
aplotitem.makedynamicdata(self)
if singledata:
return plotitems[0]
else:
return plotitems
def doranges(self):
if self.did(self.doranges):
return
for plotitem in self.plotitems:
plotitem.adjustaxesstatic(self)
for plotitem in self.plotitems:
plotitem.makedynamicdata(self)
for plotitem in self.plotitems:
plotitem.adjustaxesdynamic(self)
self.didranges = 1
def doaxiscreate(self, axisname):
if self.did(self.doaxiscreate, axisname):
return
self.doaxispositioner(axisname)
self.axes[axisname].create()
def dolayout(self):
raise NotImplementedError
def dobackground(self):
pass
def doaxes(self):
raise NotImplementedError
def dostyles(self):
if self.did(self.dostyles):
return
self.dolayout()
self.dobackground()
# count the usage of styles and perform selects
styletotal = {}
def stylesid(styles):
return ":".join([str(id(style)) for style in styles])
for plotitem in self.plotitems:
try:
styletotal[stylesid(plotitem.styles)] += 1
except:
styletotal[stylesid(plotitem.styles)] = 1
styleindex = {}
for plotitem in self.plotitems:
try:
styleindex[stylesid(plotitem.styles)] += 1
except:
styleindex[stylesid(plotitem.styles)] = 0
plotitem.selectstyles(self, styleindex[stylesid(plotitem.styles)],
styletotal[stylesid(plotitem.styles)])
self.didstyles = 1
def doplot(self, plotitem):
if self.did(self.doplot, plotitem):
return
self.dostyles()
plotitem.draw(self)
def dodata(self):
for plotitem in self.plotitems:
self.doplot(plotitem)
def dokey(self):
raise NotImplementedError
def finish(self):
self.dobackground()
self.doaxes()
self.dodata()
self.dokey()
class graphxy(graph):
def __init__(self, xpos=0, ypos=0, width=None, height=None, ratio=goldenmean,
key=None, backgroundattrs=None, axesdist=0.8*unit.v_cm,
xaxisat=None, yaxisat=None, **axes):
graph.__init__(self)
self.xpos = xpos
self.ypos = ypos
self.xpos_pt = unit.topt(self.xpos)
self.ypos_pt = unit.topt(self.ypos)
self.xaxisat = xaxisat
self.yaxisat = yaxisat
self.key = key
self.backgroundattrs = backgroundattrs
self.axesdist_pt = unit.topt(axesdist)
self.width = width
self.height = height
if width is None:
if height is None:
raise ValueError("specify width and/or height")
else:
self.width = ratio * self.height
elif height is None:
self.height = (1.0/ratio) * self.width
self.width_pt = unit.topt(self.width)
self.height_pt = unit.topt(self.height)
for axisname, aaxis in axes.items():
if aaxis is not None:
if not isinstance(aaxis, axis.linkedaxis):
self.axes[axisname] = axis.anchoredaxis(aaxis, self.texrunner, axisname)
else:
self.axes[axisname] = aaxis
for axisname, axisat in [("x", xaxisat), ("y", yaxisat)]:
okey = axisname + "2"
if not axes.has_key(axisname):
if not axes.has_key(okey) or axes[okey] is None:
self.axes[axisname] = axis.anchoredaxis(axis.linear(), self.texrunner, axisname)
if not axes.has_key(okey):
self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey)
else:
self.axes[axisname] = axis.linkedaxis(self.axes[okey], axisname)
elif not axes.has_key(okey) and axisat is None:
self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey)
if self.axes.has_key("x"):
self.xbasepath = self.axes["x"].basepath
self.xvbasepath = self.axes["x"].vbasepath
self.xgridpath = self.axes["x"].gridpath
self.xtickpoint_pt = self.axes["x"].tickpoint_pt
self.xtickpoint = self.axes["x"].tickpoint
self.xvtickpoint_pt = self.axes["x"].vtickpoint_pt
self.xvtickpoint = self.axes["x"].tickpoint
self.xtickdirection = self.axes["x"].tickdirection
self.xvtickdirection = self.axes["x"].vtickdirection
if self.axes.has_key("y"):
self.ybasepath = self.axes["y"].basepath
self.yvbasepath = self.axes["y"].vbasepath
self.ygridpath = self.axes["y"].gridpath
self.ytickpoint_pt = self.axes["y"].tickpoint_pt
self.ytickpoint = self.axes["y"].tickpoint
self.yvtickpoint_pt = self.axes["y"].vtickpoint_pt
self.yvtickpoint = self.axes["y"].vtickpoint
self.ytickdirection = self.axes["y"].tickdirection
self.yvtickdirection = self.axes["y"].vtickdirection
self.axesnames = ([], [])
for axisname, aaxis in self.axes.items():
if axisname[0] not in "xy" or (len(axisname) != 1 and (not axisname[1:].isdigit() or
axisname[1:] == "1")):
raise ValueError("invalid axis name")
if axisname[0] == "x":
self.axesnames[0].append(axisname)
else:
self.axesnames[1].append(axisname)
aaxis.setcreatecall(self.doaxiscreate, axisname)
def pos_pt(self, x, y, xaxis=None, yaxis=None):
if xaxis is None:
xaxis = self.axes["x"]
if yaxis is None:
yaxis = self.axes["y"]
return (self.xpos_pt + xaxis.convert(x)*self.width_pt,
self.ypos_pt + yaxis.convert(y)*self.height_pt)
def pos(self, x, y, xaxis=None, yaxis=None):
if xaxis is None:
xaxis = self.axes["x"]
if yaxis is None:
yaxis = self.axes["y"]
return (self.xpos + xaxis.convert(x)*self.width,
self.ypos + yaxis.convert(y)*self.height)
def vpos_pt(self, vx, vy):
return (self.xpos_pt + vx*self.width_pt,
self.ypos_pt + vy*self.height_pt)
def vpos(self, vx, vy):
return (self.xpos + vx*self.width,
self.ypos + vy*self.height)
def vzindex(self, vx, vy):
return 0
def vangle(self, vx1, vy1, vx2, vy2, vx3, vy3):
return 1
def vgeodesic(self, vx1, vy1, vx2, vy2):
"""returns a geodesic path between two points in graph coordinates"""
return path.line_pt(self.xpos_pt + vx1*self.width_pt,
self.ypos_pt + vy1*self.height_pt,
self.xpos_pt + vx2*self.width_pt,
self.ypos_pt + vy2*self.height_pt)
def vgeodesic_el(self, vx1, vy1, vx2, vy2):
"""returns a geodesic path element between two points in graph coordinates"""
return path.lineto_pt(self.xpos_pt + vx2*self.width_pt,
self.ypos_pt + vy2*self.height_pt)
def vcap_pt(self, coordinate, length_pt, vx, vy):
"""returns an error cap path for a given coordinate, lengths and
point in graph coordinates"""
if coordinate == 0:
return path.line_pt(self.xpos_pt + vx*self.width_pt - 0.5*length_pt,
self.ypos_pt + vy*self.height_pt,
self.xpos_pt + vx*self.width_pt + 0.5*length_pt,
self.ypos_pt + vy*self.height_pt)
elif coordinate == 1:
return path.line_pt(self.xpos_pt + vx*self.width_pt,
self.ypos_pt + vy*self.height_pt - 0.5*length_pt,
self.xpos_pt + vx*self.width_pt,
self.ypos_pt + vy*self.height_pt + 0.5*length_pt)
else:
raise ValueError("direction invalid")
def xvgridpath(self, vx):
return path.line_pt(self.xpos_pt + vx*self.width_pt, self.ypos_pt,
self.xpos_pt + vx*self.width_pt, self.ypos_pt + self.height_pt)
def yvgridpath(self, vy):
return path.line_pt(self.xpos_pt, self.ypos_pt + vy*self.height_pt,
self.xpos_pt + self.width_pt, self.ypos_pt + vy*self.height_pt)
def axistrafo(self, axis, t):
c = canvas.canvas([t])
c.insert(axis.canvas)
axis.canvas = c
def axisatv(self, axis, v):
if axis.positioner.fixtickdirection[0]:
# it is a y-axis
self.axistrafo(axis, trafo.translate_pt(self.xpos_pt + v*self.width_pt - axis.positioner.x1_pt, 0))
else:
# it is an x-axis
self.axistrafo(axis, trafo.translate_pt(0, self.ypos_pt + v*self.height_pt - axis.positioner.y1_pt))
def doaxispositioner(self, axisname):
if self.did(self.doaxispositioner, axisname):
return
self.doranges()
if axisname == "x":
self.axes["x"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt,
self.xpos_pt + self.width_pt, self.ypos_pt,
(0, 1), self.xvgridpath))
elif axisname == "x2":
self.axes["x2"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt + self.height_pt,
self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt,
(0, -1), self.xvgridpath))
elif axisname == "y":
self.axes["y"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt,
self.xpos_pt, self.ypos_pt + self.height_pt,
(1, 0), self.yvgridpath))
elif axisname == "y2":
self.axes["y2"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt + self.width_pt, self.ypos_pt,
self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt,
(-1, 0), self.yvgridpath))
else:
if axisname[1:] == "3":
dependsonaxisname = axisname[0]
else:
dependsonaxisname = "%s%d" % (axisname[0], int(axisname[1:]) - 2)
self.doaxiscreate(dependsonaxisname)
sign = 2*(int(axisname[1:]) % 2) - 1
if axisname[0] == "x":
y_pt = self.axes[dependsonaxisname].positioner.y1_pt - sign * (self.axes[dependsonaxisname].canvas.extent_pt + self.axesdist_pt)
self.axes[axisname].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, y_pt,
self.xpos_pt + self.width_pt, y_pt,
(0, sign), self.xvgridpath))
else:
x_pt = self.axes[dependsonaxisname].positioner.x1_pt - sign * (self.axes[dependsonaxisname].canvas.extent_pt + self.axesdist_pt)
self.axes[axisname].setpositioner(positioner.lineaxispos_pt(x_pt, self.ypos_pt,
x_pt, self.ypos_pt + self.height_pt,
(sign, 0), self.yvgridpath))
def dolayout(self):
if self.did(self.dolayout):
return
for axisname in self.axes.keys():
self.doaxiscreate(axisname)
if self.xaxisat is not None:
self.axisatv(self.axes["x"], self.axes["y"].convert(self.xaxisat))
if self.yaxisat is not None:
self.axisatv(self.axes["y"], self.axes["x"].convert(self.yaxisat))
def dobackground(self):
if self.did(self.dobackground):
return
if self.backgroundattrs is not None:
self.draw(path.rect_pt(self.xpos_pt, self.ypos_pt, self.width_pt, self.height_pt),
self.backgroundattrs)
def doaxes(self):
if self.did(self.doaxes):
return
self.dolayout()
self.dobackground()
for axis in self.axes.values():
self.insert(axis.canvas)
def dokey(self):
if self.did(self.dokey):
return
self.dobackground()
self.dostyles()
if self.key is not None:
c = self.key.paint(self.plotitems)
bbox = c.bbox()
def parentchildalign(pmin, pmax, cmin, cmax, pos, dist, inside):
ppos = pmin+0.5*(cmax-cmin)+dist+pos*(pmax-pmin-cmax+cmin-2*dist)
cpos = 0.5*(cmin+cmax)+(1-inside)*(1-2*pos)*(cmax-cmin+2*dist)
return ppos-cpos
if bbox:
x = parentchildalign(self.xpos_pt, self.xpos_pt+self.width_pt,
bbox.llx_pt, bbox.urx_pt,
self.key.hpos, unit.topt(self.key.hdist), self.key.hinside)
y = parentchildalign(self.ypos_pt, self.ypos_pt+self.height_pt,
bbox.lly_pt, bbox.ury_pt,
self.key.vpos, unit.topt(self.key.vdist), self.key.vinside)
self.insert(c, [trafo.translate_pt(x, y)])
class graphxyz(graphxy):
class central:
def __init__(self, distance, phi, theta, anglefactor=math.pi/180):
phi *= anglefactor
theta *= anglefactor
self.distance = distance
self.a = (-math.sin(phi), math.cos(phi), 0)
self.b = (-math.cos(phi)*math.sin(theta),
-math.sin(phi)*math.sin(theta),
math.cos(theta))
self.eye = (distance*math.cos(phi)*math.cos(theta),
distance*math.sin(phi)*math.cos(theta),
distance*math.sin(theta))
def point(self, x, y, z):
d0 = (self.a[0]*self.b[1]*(z-self.eye[2])
+ self.a[2]*self.b[0]*(y-self.eye[1])
+ self.a[1]*self.b[2]*(x-self.eye[0])
- self.a[2]*self.b[1]*(x-self.eye[0])
- self.a[0]*self.b[2]*(y-self.eye[1])
- self.a[1]*self.b[0]*(z-self.eye[2]))
da = (self.eye[0]*self.b[1]*(z-self.eye[2])
+ self.eye[2]*self.b[0]*(y-self.eye[1])
+ self.eye[1]*self.b[2]*(x-self.eye[0])
- self.eye[2]*self.b[1]*(x-self.eye[0])
- self.eye[0]*self.b[2]*(y-self.eye[1])
- self.eye[1]*self.b[0]*(z-self.eye[2]))
db = (self.a[0]*self.eye[1]*(z-self.eye[2])
+ self.a[2]*self.eye[0]*(y-self.eye[1])
+ self.a[1]*self.eye[2]*(x-self.eye[0])
- self.a[2]*self.eye[1]*(x-self.eye[0])
- self.a[0]*self.eye[2]*(y-self.eye[1])
- self.a[1]*self.eye[0]*(z-self.eye[2]))
return da/d0, db/d0
def zindex(self, x, y, z):
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
def angle(self, x1, y1, z1, x2, y2, z2, x3, y3, z3):
sx = (x1-self.eye[0])
sy = (y1-self.eye[1])
sz = (z1-self.eye[2])
nx = (y2-y1)*(z3-z1)-(z2-z1)*(y3-y1)
ny = (z2-z1)*(x3-x1)-(x2-x1)*(z3-z1)
nz = (x2-x1)*(y3-y1)-(y2-y1)*(x3-x1)
return (sx*nx+sy*ny+sz*nz)/math.sqrt(nx*nx+ny*ny+nz*nz)/math.sqrt(sx*sx+sy*sy+sz*sz)
class parallel:
def __init__(self, phi, theta, anglefactor=math.pi/180):
phi *= anglefactor
theta *= anglefactor
self.a = (-math.sin(phi), math.cos(phi), 0)
self.b = (-math.cos(phi)*math.sin(theta),
-math.sin(phi)*math.sin(theta),
math.cos(theta))
self.c = (-math.cos(phi)*math.cos(theta),
-math.sin(phi)*math.cos(theta),
-math.sin(theta))
def point(self, x, y, z):
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
def zindex(self, x, y, z):
return self.c[0]*x+self.c[1]*y+self.c[2]*z
def angle(self, x1, y1, z1, x2, y2, z2, x3, y3, z3):
nx = (y2-y1)*(z3-z1)-(z2-z1)*(y3-y1)
ny = (z2-z1)*(x3-x1)-(x2-x1)*(z3-z1)
nz = (x2-x1)*(y3-y1)-(y2-y1)*(x3-x1)
return (self.c[0]*nx+self.c[1]*ny+self.c[2]*nz)/math.sqrt(nx*nx+ny*ny+nz*nz)
def __init__(self, xpos=0, ypos=0, size=None,
xscale=1, yscale=1, zscale=1/goldenmean,
projector=central(10, -30, 30), key=None,
**axes):
graph.__init__(self)
self.xpos = xpos
self.ypos = ypos
self.size = size
self.xpos_pt = unit.topt(xpos)
self.ypos_pt = unit.topt(ypos)
self.size_pt = unit.topt(size)
self.xscale = xscale
self.yscale = yscale
self.zscale = zscale
self.projector = projector
self.key = key
self.xorder = projector.zindex(0, -1, 0) > projector.zindex(0, 1, 0) and 1 or 0
self.yorder = projector.zindex(-1, 0, 0) > projector.zindex(1, 0, 0) and 1 or 0
self.zindexscale = math.sqrt(xscale*xscale+yscale*yscale+zscale*zscale)
for axisname, aaxis in axes.items():
if aaxis is not None:
if not isinstance(aaxis, axis.linkedaxis):
self.axes[axisname] = axis.anchoredaxis(aaxis, self.texrunner, axisname)
else:
self.axes[axisname] = aaxis
for axisname in ["x", "y"]:
okey = axisname + "2"
if not axes.has_key(axisname):
if not axes.has_key(okey) or axes[okey] is None:
self.axes[axisname] = axis.anchoredaxis(axis.linear(), self.texrunner, axisname)
if not axes.has_key(okey):
self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey)
else:
self.axes[axisname] = axis.linkedaxis(self.axes[okey], axisname)
if not axes.has_key("z"):
self.axes["z"] = axis.anchoredaxis(axis.linear(), self.texrunner, axisname)
if self.axes.has_key("x"):
self.xbasepath = self.axes["x"].basepath
self.xvbasepath = self.axes["x"].vbasepath
self.xgridpath = self.axes["x"].gridpath
self.xtickpoint_pt = self.axes["x"].tickpoint_pt
self.xtickpoint = self.axes["x"].tickpoint
self.xvtickpoint_pt = self.axes["x"].vtickpoint_pt
self.xvtickpoint = self.axes["x"].tickpoint
self.xtickdirection = self.axes["x"].tickdirection
self.xvtickdirection = self.axes["x"].vtickdirection
if self.axes.has_key("y"):
self.ybasepath = self.axes["y"].basepath
self.yvbasepath = self.axes["y"].vbasepath
self.ygridpath = self.axes["y"].gridpath
self.ytickpoint_pt = self.axes["y"].tickpoint_pt
self.ytickpoint = self.axes["y"].tickpoint
self.yvtickpoint_pt = self.axes["y"].vtickpoint_pt
self.yvtickpoint = self.axes["y"].vtickpoint
self.ytickdirection = self.axes["y"].tickdirection
self.yvtickdirection = self.axes["y"].vtickdirection
if self.axes.has_key("z"):
self.zbasepath = self.axes["z"].basepath
self.zvbasepath = self.axes["z"].vbasepath
self.zgridpath = self.axes["z"].gridpath
self.ztickpoint_pt = self.axes["z"].tickpoint_pt
self.ztickpoint = self.axes["z"].tickpoint
self.zvtickpoint_pt = self.axes["z"].vtickpoint
self.zvtickpoint = self.axes["z"].vtickpoint
self.ztickdirection = self.axes["z"].tickdirection
self.zvtickdirection = self.axes["z"].vtickdirection
self.axesnames = ([], [], [])
for axisname, aaxis in self.axes.items():
if axisname[0] not in "xyz" or (len(axisname) != 1 and (not axisname[1:].isdigit() or
axisname[1:] == "1")):
raise ValueError("invalid axis name")
if axisname[0] == "x":
self.axesnames[0].append(axisname)
elif axisname[0] == "y":
self.axesnames[1].append(axisname)
else:
self.axesnames[2].append(axisname)
aaxis.setcreatecall(self.doaxiscreate, axisname)
def pos_pt(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
if xaxis is None:
xaxis = self.axes["x"]
if yaxis is None:
yaxis = self.axes["y"]
if zaxis is None:
zaxis = self.axes["z"]
return self.vpos_pt(xaxis.convert(x), yaxis.convert(y), zaxis.convert(y))
def pos(self, x, y, z, xaxis=None, yaxis=None, zaxis=None):
if xaxis is None:
xaxis = self.axes["x"]
if yaxis is None:
yaxis = self.axes["y"]
if zaxis is None:
zaxis = self.axes["z"]
return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(y))
def vpos_pt(self, vx, vy, vz):
x, y = self.projector.point(2*self.xscale*(vx - 0.5),
2*self.yscale*(vy - 0.5),
2*self.zscale*(vz - 0.5))
return self.xpos_pt+x*self.size_pt, self.ypos_pt+y*self.size_pt
def vpos(self, vx, vy, vz):
x, y = self.projector.point(2*self.xscale*(vx - 0.5),
2*self.yscale*(vy - 0.5),
2*self.zscale*(vz - 0.5))
return self.xpos+x*self.size, self.ypos+y*self.size
def vzindex(self, vx, vy, vz):
return self.projector.zindex(2*self.xscale*(vx - 0.5),
2*self.yscale*(vy - 0.5),
2*self.zscale*(vz - 0.5))/self.zindexscale
def vangle(self, vx1, vy1, vz1, vx2, vy2, vz2, vx3, vy3, vz3):
return self.projector.angle(2*self.xscale*(vx1 - 0.5),
2*self.yscale*(vy1 - 0.5),
2*self.zscale*(vz1 - 0.5),
2*self.xscale*(vx2 - 0.5),
2*self.yscale*(vy2 - 0.5),
2*self.zscale*(vz2 - 0.5),
2*self.xscale*(vx3 - 0.5),
2*self.yscale*(vy3 - 0.5),
2*self.zscale*(vz3 - 0.5))
def vgeodesic(self, vx1, vy1, vz1, vx2, vy2, vz2):
"""returns a geodesic path between two points in graph coordinates"""
return path.line_pt(*(self.vpos_pt(vx1, vy1, vz1) + self.vpos_pt(vx2, vy2, vz2)))
def vgeodesic_el(self, vx1, vy1, vz1, vx2, vy2, vz2):
"""returns a geodesic path element between two points in graph coordinates"""
return path.lineto_pt(*(self.vpos_pt(vx1, vy1, vz1) + self.vpos_pt(vx2, vy2, vz2)))
def vcap_pt(self, coordinate, length_pt, vx, vy, vz):
"""returns an error cap path for a given coordinate, lengths and
point in graph coordinates"""
if coordinate == 0:
return self.vgeodesic(vx-0.5*length_pt/self.size_pt, vy, vz, vx+0.5*length_pt/self.size_pt, vy, vz)
elif coordinate == 1:
return self.vgeodesic(vx, vy-0.5*length_pt/self.size_pt, vz, vx, vy+0.5*length_pt/self.size_pt, vz)
elif coordinate == 2:
return self.vgeodesic(vx, vy, vz-0.5*length_pt/self.size_pt, vx, vy, vz+0.5*length_pt/self.size_pt)
else:
raise ValueError("direction invalid")
def xvtickdirection(self, vx):
if self.xorder:
x1_pt, y1_pt = self.vpos_pt(vx, 1, 0)
x2_pt, y2_pt = self.vpos_pt(vx, 0, 0)
else:
x1_pt, y1_pt = self.vpos_pt(vx, 0, 0)
x2_pt, y2_pt = self.vpos_pt(vx, 1, 0)
dx_pt = x2_pt - x1_pt
dy_pt = y2_pt - y1_pt
norm = math.hypot(dx_pt, dy_pt)
return dx_pt/norm, dy_pt/norm
def yvtickdirection(self, vy):
if self.yorder:
x1_pt, y1_pt = self.vpos_pt(1, vy, 0)
x2_pt, y2_pt = self.vpos_pt(0, vy, 0)
else:
x1_pt, y1_pt = self.vpos_pt(0, vy, 0)
x2_pt, y2_pt = self.vpos_pt(1, vy, 0)
dx_pt = x2_pt - x1_pt
dy_pt = y2_pt - y1_pt
norm = math.hypot(dx_pt, dy_pt)
return dx_pt/norm, dy_pt/norm
def vtickdirection(self, vx1, vy1, vz1, vx2, vy2, vz2):
x1_pt, y1_pt = self.vpos_pt(vx1, vy1, vz1)
x2_pt, y2_pt = self.vpos_pt(vx2, vy2, vz2)
dx_pt = x2_pt - x1_pt
dy_pt = y2_pt - y1_pt
norm = math.hypot(dx_pt, dy_pt)
return dx_pt/norm, dy_pt/norm
def xvgridpath(self, vx):
return path.path(path.moveto_pt(*self.vpos_pt(vx, 0, 0)),
path.lineto_pt(*self.vpos_pt(vx, 1, 0)),
path.lineto_pt(*self.vpos_pt(vx, 1, 1)),
path.lineto_pt(*self.vpos_pt(vx, 0, 1)),
path.closepath())
def yvgridpath(self, vy):
return path.path(path.moveto_pt(*self.vpos_pt(0, vy, 0)),
path.lineto_pt(*self.vpos_pt(1, vy, 0)),
path.lineto_pt(*self.vpos_pt(1, vy, 1)),
path.lineto_pt(*self.vpos_pt(0, vy, 1)),
path.closepath())
def zvgridpath(self, vz):
return path.path(path.moveto_pt(*self.vpos_pt(0, 0, vz)),
path.lineto_pt(*self.vpos_pt(1, 0, vz)),
path.lineto_pt(*self.vpos_pt(1, 1, vz)),
path.lineto_pt(*self.vpos_pt(0, 1, vz)),
path.closepath())
def doaxispositioner(self, axisname):
if self.did(self.doaxispositioner, axisname):
return
self.doranges()
if axisname == "x":
self.axes["x"].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, self.xorder, 0),
lambda vx: self.vtickdirection(vx, self.xorder, 0, vx, 1-self.xorder, 0),
self.xvgridpath))
elif axisname == "x2":
self.axes["x2"].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, 1-self.xorder, 0),
lambda vx: self.vtickdirection(vx, 1-self.xorder, 0, vx, self.xorder, 0),
self.xvgridpath))
elif axisname == "x3":
self.axes["x3"].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, self.xorder, 1),
lambda vx: self.vtickdirection(vx, self.xorder, 1, vx, 1-self.xorder, 1),
self.xvgridpath))
elif axisname == "x4":
self.axes["x4"].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, 1-self.xorder, 1),
lambda vx: self.vtickdirection(vx, 1-self.xorder, 1, vx, self.xorder, 1),
self.xvgridpath))
elif axisname == "y":
self.axes["y"].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(self.yorder, vy, 0),
lambda vy: self.vtickdirection(self.yorder, vy, 0, 1-self.yorder, vy, 0),
self.yvgridpath))
elif axisname == "y2":
self.axes["y2"].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(1-self.yorder, vy, 0),
lambda vy: self.vtickdirection(1-self.yorder, vy, 0, self.yorder, vy, 0),
self.yvgridpath))
elif axisname == "y3":
self.axes["y3"].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(self.yorder, vy, 1),
lambda vy: self.vtickdirection(self.yorder, vy, 1, 1-self.yorder, vy, 1),
self.yvgridpath))
elif axisname == "y4":
self.axes["y4"].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(1-self.yorder, vy, 1),
lambda vy: self.vtickdirection(1-self.yorder, vy, 1, self.yorder, vy, 1),
self.yvgridpath))
elif axisname == "z":
self.axes["z"].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(0, 0, vz),
lambda vz: self.vtickdirection(0, 0, vz, 1, 1, vz),
self.zvgridpath))
elif axisname == "z2":
self.axes["z2"].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(1, 0, vz),
lambda vz: self.vtickdirection(1, 0, vz, 0, 1, vz),
self.zvgridpath))
elif axisname == "z3":
self.axes["z3"].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(0, 1, vz),
lambda vz: self.vtickdirection(0, 1, vz, 1, 0, vz),
self.zvgridpath))
elif axisname == "z4":
self.axes["z4"].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(0, 0, vz),
lambda vz: self.vtickdirection(1, 1, vz, 0, 0, vz),
self.zvgridpath))
else:
raise NotImplementedError("4 axis per dimension supported only")
def dolayout(self):
if self.did(self.dolayout):
return
for axisname in self.axes.keys():
self.doaxiscreate(axisname)
def dobackground(self):
if self.did(self.dobackground):
return
def doaxes(self):
if self.did(self.doaxes):
return
self.dolayout()
self.dobackground()
for axis in self.axes.values():
self.insert(axis.canvas)
def dokey(self):
if self.did(self.dokey):
return
self.dobackground()
self.dostyles()
if self.key is not None:
c = self.key.paint(self.plotitems)
bbox = c.bbox()
def parentchildalign(pmin, pmax, cmin, cmax, pos, dist, inside):
ppos = pmin+0.5*(cmax-cmin)+dist+pos*(pmax-pmin-cmax+cmin-2*dist)
cpos = 0.5*(cmin+cmax)+(1-inside)*(1-2*pos)*(cmax-cmin+2*dist)
return ppos-cpos
if bbox:
x = parentchildalign(self.xpos_pt, self.xpos_pt+self.size_pt,
bbox.llx_pt, bbox.urx_pt,
self.key.hpos, unit.topt(self.key.hdist), self.key.hinside)
y = parentchildalign(self.ypos_pt, self.ypos_pt+self.size_pt,
bbox.lly_pt, bbox.ury_pt,
self.key.vpos, unit.topt(self.key.vdist), self.key.vinside)
self.insert(c, [trafo.translate_pt(x, y)])
|