From b554c52f0ef8fb8235353c2ec0bc8ef74aa473c2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Andr=C3=A9=20Wobst?= Date: Fri, 28 Sep 2007 16:03:40 +0000 Subject: [PATCH] implement central and parallel projection in 3d graphs; correct axis (tick direction and non-linearity in central projection) git-svn-id: https://pyx.svn.sourceforge.net/svnroot/pyx/trunk/pyx@2887 069f4177-920e-0410-937b-c2a4a81bcd90 --- pyx/color.py | 2 +- pyx/graph/axis/positioner.py | 24 +++- pyx/graph/graph.py | 237 ++++++++++++++++++++++++---------------- pyx/graph/style.py | 5 +- pyx/mesh.py | 10 +- test/functional/test_graph3d.py | 6 +- 6 files changed, 172 insertions(+), 112 deletions(-) diff --git a/pyx/color.py b/pyx/color.py index 52605721..29cd1a44 100644 --- a/pyx/color.py +++ b/pyx/color.py @@ -164,7 +164,7 @@ class rgb(color): def colorspacestring(self): return "/DeviceRGB" - def to8bitstring(self): + def tostring8bit(self): return struct.pack("BBB", int(self.color["r"]*255), int(self.color["g"]*255), int(self.color["b"]*255)) def tohexstring(self, cssstrip=1, addhash=1): diff --git a/pyx/graph/axis/positioner.py b/pyx/graph/axis/positioner.py index c4a74ad4..bdee12db 100644 --- a/pyx/graph/axis/positioner.py +++ b/pyx/graph/axis/positioner.py @@ -79,13 +79,13 @@ class pathpositioner(_positioner): class lineaxispos_pt: """an axispos linear along a line with a fix direction for the ticks""" - def __init__(self, x1_pt, y1_pt, x2_pt, y2_pt, fixtickdirection, vgridpathfunction): + def __init__(self, x1_pt, y1_pt, x2_pt, y2_pt, fixtickdirection, vgridpath): self.x1_pt = x1_pt self.y1_pt = y1_pt self.x2_pt = x2_pt self.y2_pt = y2_pt self.fixtickdirection = fixtickdirection - self.vgridpathfunction = vgridpathfunction + self.vgridpath = vgridpath def vbasepath(self, v1=None, v2=None): if v1 is None: @@ -97,12 +97,26 @@ class lineaxispos_pt: (1-v2)*self.x1_pt+v2*self.x2_pt, (1-v2)*self.y1_pt+v2*self.y2_pt) - def vgridpath(self, v): - return self.vgridpathfunction(v) - def vtickpoint_pt(self, v): return (1-v)*self.x1_pt+v*self.x2_pt, (1-v)*self.y1_pt+v*self.y2_pt def vtickdirection(self, v): return self.fixtickdirection + +class flexlineaxispos_pt(lineaxispos_pt): + """an axispos linear along a line with flexible direction for the ticks""" + + def __init__(self, vtickpoint_pt, vtickdirection, vgridpath): + self.vtickpoint_pt = vtickpoint_pt + self.vtickdirection = vtickdirection + self.vgridpath = vgridpath + + def vbasepath(self, v1=None, v2=None): + if v1 is None: + v1 = 0 + if v2 is None: + v2 = 1 + x1_pt, y1_pt = self.vtickpoint_pt(v1) + x2_pt, y2_pt = self.vtickpoint_pt(v2) + return path.line_pt(x1_pt, y1_pt, x2_pt, y2_pt) diff --git a/pyx/graph/graph.py b/pyx/graph/graph.py index 6be41152..0efdee15 100644 --- a/pyx/graph/graph.py +++ b/pyx/graph/graph.py @@ -505,40 +505,77 @@ class graphxy(graph): class graphxyz(graphxy): - def __init__(self, xpos=0, ypos=0, width=None, height=None, depth=None, - phi=30, theta=30, distance=1, - key=None, backgroundattrs=None, **axes): + class central: + + def __init__(self, distance, 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.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 + + + 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)) + + 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 __init__(self, xpos=0, ypos=0, size=None, + xscale=1, yscale=1, zscale=1/goldenmean, + projector=central(10, -30, 30), + key=None, backgroundattrs=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.width = width - self.height = height - self.depth = depth - self.width_pt = unit.topt(width) - self.height_pt = unit.topt(height) - self.depth_pt = unit.topt(depth) + self.size_pt = unit.topt(size) + self.xscale = xscale + self.yscale = yscale + self.zscale = zscale + self.projector = projector self.key = key self.backgroundattrs = backgroundattrs - if self.width_pt <= 0: raise ValueError("width < 0") - if self.height_pt <= 0: raise ValueError("height < 0") - if self.depth_pt <= 0: raise ValueError("height < 0") - self.distance_pt = distance*math.sqrt(self.width_pt*self.width_pt+ - self.height_pt*self.height_pt+ - self.depth_pt*self.depth_pt) - phi *= -math.pi/180 - theta *= math.pi/180 - 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 = (self.distance_pt*math.cos(phi)*math.cos(theta), - self.distance_pt*math.sin(phi)*math.cos(theta), - self.distance_pt*math.sin(theta)) - for axisname, aaxis in axes.items(): if aaxis is not None: if not isinstance(aaxis, axis.linkedaxis): @@ -556,10 +593,8 @@ class graphxyz(graphxy): 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 @@ -567,10 +602,8 @@ class graphxyz(graphxy): 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"].tickpoint self.ytickdirection = self.axes["y"].tickdirection - self.yvtickdirection = self.axes["y"].vtickdirection if self.axes.has_key("z"): self.zbasepath = self.axes["z"].basepath @@ -578,10 +611,8 @@ class graphxyz(graphxy): 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_pt self.zvtickpoint = self.axes["z"].tickpoint self.ztickdirection = self.axes["z"].tickdirection - self.zvtickdirection = self.axes["z"].vtickdirection self.axesnames = ([], [], []) for axisname, aaxis in self.axes.items(): @@ -615,60 +646,46 @@ class graphxyz(graphxy): return self.vpos(xaxis.convert(x), yaxis.convert(y), zaxis.convert(y)) def vpos_pt(self, vx, vy, vz): - x, y, z = (vx - 0.5)*self.depth_pt, (vy - 0.5)*self.width_pt, (vz - 0.5)*self.height_pt - d0 = float(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 + self.xpos_pt, db/d0 + self.ypos_pt + 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_pt, y_pt = self.vpos_pt(vx, vy, vz) - return unit.t_pt(x_pt), unit.t_pt(y_pt) - - def xbaseline(self, axis, x1, x2, xaxis=None): - if xaxis is None: xaxis = self.axes["x"] - return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2)) - - def ybaseline(self, axis, y1, y2, yaxis=None): - if yaxis is None: yaxis = self.axes["y"] - return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2)) - - def zbaseline(self, axis, z1, z2, zaxis=None): - if zaxis is None: zaxis = self.axes["z"] - return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2)) - - def vxbaseline(self, axis, v1, v2): - return (path.line_pt(*(self.vpos_pt(v1, 0, 0) + self.vpos_pt(v2, 0, 0))) + - path.line_pt(*(self.vpos_pt(v1, 0, 1) + self.vpos_pt(v2, 0, 1))) + - path.line_pt(*(self.vpos_pt(v1, 1, 1) + self.vpos_pt(v2, 1, 1))) + - path.line_pt(*(self.vpos_pt(v1, 1, 0) + self.vpos_pt(v2, 1, 0)))) - - def vybaseline(self, axis, v1, v2): - return (path.line_pt(*(self.vpos_pt(0, v1, 0) + self.vpos_pt(0, v2, 0))) + - path.line_pt(*(self.vpos_pt(0, v1, 1) + self.vpos_pt(0, v2, 1))) + - path.line_pt(*(self.vpos_pt(1, v1, 1) + self.vpos_pt(1, v2, 1))) + - path.line_pt(*(self.vpos_pt(1, v1, 0) + self.vpos_pt(1, v2, 0)))) - - def vzbaseline(self, axis, v1, v2): - return (path.line_pt(*(self.vpos_pt(0, 0, v1) + self.vpos_pt(0, 0, v2))) + - path.line_pt(*(self.vpos_pt(0, 1, v1) + self.vpos_pt(0, 1, v2))) + - path.line_pt(*(self.vpos_pt(1, 1, v1) + self.vpos_pt(1, 1, v2))) + - path.line_pt(*(self.vpos_pt(1, 0, v1) + self.vpos_pt(1, 0, v2)))) + 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 xbaseline(self, axis, x1, x2, xaxis=None): +# if xaxis is None: xaxis = self.axes["x"] +# return self.vxbaseline(axis, xaxis.convert(x1), xaxis.convert(x2)) +# +# def ybaseline(self, axis, y1, y2, yaxis=None): +# if yaxis is None: yaxis = self.axes["y"] +# return self.vybaseline(axis, yaxis.convert(y1), yaxis.convert(y2)) +# +# def zbaseline(self, axis, z1, z2, zaxis=None): +# if zaxis is None: zaxis = self.axes["z"] +# return self.vzbaseline(axis, zaxis.convert(z1), zaxis.convert(z2)) +# +# def vxbaseline(self, axis, v1, v2): +# return (path.line_pt(*(self.vpos_pt(v1, 0, 0) + self.vpos_pt(v2, 0, 0))) + +# path.line_pt(*(self.vpos_pt(v1, 0, 1) + self.vpos_pt(v2, 0, 1))) + +# path.line_pt(*(self.vpos_pt(v1, 1, 1) + self.vpos_pt(v2, 1, 1))) + +# path.line_pt(*(self.vpos_pt(v1, 1, 0) + self.vpos_pt(v2, 1, 0)))) +# +# def vybaseline(self, axis, v1, v2): +# return (path.line_pt(*(self.vpos_pt(0, v1, 0) + self.vpos_pt(0, v2, 0))) + +# path.line_pt(*(self.vpos_pt(0, v1, 1) + self.vpos_pt(0, v2, 1))) + +# path.line_pt(*(self.vpos_pt(1, v1, 1) + self.vpos_pt(1, v2, 1))) + +# path.line_pt(*(self.vpos_pt(1, v1, 0) + self.vpos_pt(1, v2, 0)))) +# +# def vzbaseline(self, axis, v1, v2): +# return (path.line_pt(*(self.vpos_pt(0, 0, v1) + self.vpos_pt(0, 0, v2))) + +# path.line_pt(*(self.vpos_pt(0, 1, v1) + self.vpos_pt(0, 1, v2))) + +# path.line_pt(*(self.vpos_pt(1, 1, v1) + self.vpos_pt(1, 1, v2))) + +# path.line_pt(*(self.vpos_pt(1, 0, v1) + self.vpos_pt(1, 0, v2)))) def vgeodesic(self, vx1, vy1, vz1, vx2, vy2, vz2): """returns a geodesic path between two points in graph coordinates""" @@ -695,6 +712,45 @@ class graphxyz(graphxy): else: raise ValueError("direction invalid") + def xvtickpoint_pt(self, vx): + return self.vpos_pt(vx, 0, 0) + + def yvtickpoint_pt(self, vy): + return self.vpos_pt(1, vy, 0) + + def zvtickpoint_pt(self, vz): + return self.vpos_pt(0, 0, vz) + + def xvtickdirection(self, vx): + 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): + x1_pt, y1_pt = self.vpos_pt(1, vy, 0) + x2_pt, y2_pt = self.vpos_pt(0, 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 zvtickdirection(self, vz): + x1_pt, y1_pt = self.vpos_pt(0, 0, vz) + x2_pt, y2_pt = self.vpos_pt(1, 1, vz) + 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 yvgridpath(self, vy): + raise NotImplementedError + + def zvgridpath(self, vz): + raise NotImplementedError + def xvgridpath(self, vx): raise NotImplementedError @@ -711,20 +767,17 @@ class graphxyz(graphxy): if axisname == "x": x1_pt, y1_pt = self.vpos_pt(0, 0, 0) x2_pt, y2_pt = self.vpos_pt(1, 0, 0) - self.axes["x"].setpositioner(positioner.lineaxispos_pt(x1_pt, y1_pt, x2_pt, y2_pt, - (0, 1), self.xvgridpath)) + self.axes["x"].setpositioner(positioner.flexlineaxispos_pt(self.xvtickpoint_pt, self.xvtickdirection, self.xvgridpath)) elif axisname == "y": - x1_pt, y1_pt = self.vpos_pt(0, 0, 0) - x2_pt, y2_pt = self.vpos_pt(0, 1, 0) - self.axes["y"].setpositioner(positioner.lineaxispos_pt(x1_pt, y1_pt, x2_pt, y2_pt, - (0, 1), self.yvgridpath)) + x1_pt, y1_pt = self.vpos_pt(1, 0, 0) + x2_pt, y2_pt = self.vpos_pt(1, 1, 0) + self.axes["y"].setpositioner(positioner.flexlineaxispos_pt(self.yvtickpoint_pt, self.yvtickdirection, self.yvgridpath)) elif axisname == "z": x1_pt, y1_pt = self.vpos_pt(0, 0, 0) x2_pt, y2_pt = self.vpos_pt(0, 0, 1) - self.axes["z"].setpositioner(positioner.lineaxispos_pt(x1_pt, y1_pt, x2_pt, y2_pt, - (1, 0), self.yvgridpath)) + self.axes["z"].setpositioner(positioner.flexlineaxispos_pt(self.zvtickpoint_pt, self.zvtickdirection, self.yvgridpath)) else: - raise NotImplementedError + raise NotImplementedError("multiple axes not yet supported") def dolayout(self): if self.did(self.dolayout): diff --git a/pyx/graph/style.py b/pyx/graph/style.py index 461e76fe..2b369818 100644 --- a/pyx/graph/style.py +++ b/pyx/graph/style.py @@ -23,7 +23,7 @@ import math, warnings -from pyx import attr, deco, style, color, unit, canvas, path +from pyx import attr, deco, style, color, unit, canvas, path, mesh from pyx import text as textmodule builtinrange = range @@ -1675,7 +1675,6 @@ class surface(_line): if len(p): graph.stroke(p, privatedata.gridattrs) if privatedata.colorize: - from pyx import mesh nodes = [] elements = [] for value1a, value1b in zip(values1[:-1], values1[1:]): @@ -1720,7 +1719,7 @@ class surface(_line): e4 = mesh.element((n3, n4, n5)) nodes.extend([n1, n2, n3, n4, n5]) elements.extend([e1, e2, e3, e4]) - m = mesh.canvasmesh(elements, nodes) + m = mesh.mesh(elements, nodes) graph.insert(m) def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt): diff --git a/pyx/mesh.py b/pyx/mesh.py index 91d38448..c94ac80b 100644 --- a/pyx/mesh.py +++ b/pyx/mesh.py @@ -40,12 +40,6 @@ import struct, binascii, zlib import bbox, canvas, color, pdfwriter, unit -class mesh: - - def __init__(self, elements): - self.elements = elements - - class node_pt: def __init__(self, coords_pt, value): @@ -80,10 +74,10 @@ class PDFGenericResource(pdfwriter.PDFobject): file.write(self.content) -class canvasmesh(mesh, canvas.canvasitem): +class mesh(canvas.canvasitem): def __init__(self, elements, check=1): - mesh.__init__(self, elements) + self.elements = elements if check: colorspacestring = "" for element in elements: diff --git a/test/functional/test_graph3d.py b/test/functional/test_graph3d.py index a99d0d85..21ccad4b 100755 --- a/test/functional/test_graph3d.py +++ b/test/functional/test_graph3d.py @@ -7,15 +7,15 @@ from pyx import * text.set(mode="latex") def test_minimal(c, x, y): - g = c.insert(graph.graphxyz(x, y, width=5, height=5, depth=5)) + g = c.insert(graph.graphxyz(x, y, size=5)) g.plot(graph.data.file("data/husimi_small.dat", x=1, y=2, z=3)) def test_line(c, x, y): - g = c.insert(graph.graphxyz(x, y, width=5, height=5, depth=5)) + g = c.insert(graph.graphxyz(x, y, size=5)) g.plot(graph.data.file("data/husimi_small.dat", x=1, y=2, z=3), [graph.style.line()]) def test_surface(c, x, y): - g = c.insert(graph.graphxyz(x, y, width=5, height=5, depth=5)) + g = c.insert(graph.graphxyz(x, y, size=5)) g.plot(graph.data.file("data/husimi_small.dat", x=1, y=2, z=3, color=3), [graph.style.surface(strokelines1=0)]) def test_surface2d(c, x, y): -- 2.11.4.GIT