# A naive trial at reproducing interactive 3D scatter plots as in gnuplot

just for testing, note that there is a syntax problem in the

> line I sent (in laby=... one " is missing) .

Neat example. I cleaned it up to make more efficient and to solve
the flicker problems. In this version, all of the artists are created
once in the init function and then their data is updated on move.
This is much more efficient.

I also refactored the code to be independent of pylab and to make
Scatter3D inherit from matplotlib Axes, since since you do your own
tick lines etc. With a little work, this approach has potential for
simple 3D plots.

Thanks! I'll look over this more later.

import pylab as p
import matplotlib.numerix as nx
from matplotlib.axes import Axes

class Scatter3D(Axes):

def __init__(self, fig, rect, x, y, z,
theta=45., phi=140., labx="x", laby="y", labz="z", **kwargs):
Axes.__init__(self, fig, rect,
frameon=False, xticks=[], yticks=[], **kwargs)

self.x = x
self.y = y
self.z = z
self.labx = labx
self.laby = laby
self.labz = labz
self.theta = theta
self.phi = phi
self.startx = min(self.x)
self.starty = min(self.y)
self.startz = min(self.z)
self.endx = max(self.x)
self.endy = max(self.y)
self.endz = max(self.z)
self.rangex = self.endx - self.startx
self.rangey = self.endy - self.starty
self.rangez = self.endz - self.startz
self.axex = nx.array([1,0,0])
self.axey = nx.array([0,1,0])
self.axez = nx.array([0,0,1])
if self.rangex != 0. :
self.xn = (self.x - self.startx) / self.rangex
else :
self.xn = nx.zeros(shape(self.x), nx.Float)
if self.rangey != 0. :
self.yn = (self.y - self.starty) / self.rangey
else :
self.yn = nx.zeros(shape(self.y), nx.Float)
if self.rangez != 0. :
self.zn = (self.z - self.startz) / self.rangez
else :
self.zn = nx.zeros(shape(self.z), nx.Float)

self.w = self.figure.get_figwidth()
self.h = self.figure.get_figheight()

xp, yp, zp = self.projpoint(self.axex, self.axey, self.axez, theta=self.theta, phi=self.phi)
self.ax0line, = self.plot([0.,xp[0]],[0.,yp[0]])
self.ax1line, = self.plot([0.,xp[1]],[0.,yp[1]])
self.ax2line, = self.plot([0.,xp[2]],[0.,yp[2]])

self.eps = 0.02
eps = self.eps
self.labxt = self.text(xp[0]+eps,yp[0]+eps, self.labx)
self.labyt = self.text(xp[1]+eps,yp[1]+eps, self.laby)
self.labzt = self.text(xp[2]+eps,yp[2]+eps, self.labz)
self.endxt = self.text(xp[0],yp[0], str("%5.3f"%self.endx))
self.endyt = self.text(xp[1],yp[1], str("%5.3f"%self.endy))
self.endzt = self.text(xp[2],yp[2], str("%5.3f"%self.endz))
self.startxt = self.text(xp[0]/10.,yp[0]/10., str("%5.3f"%self.startx))
self.startyt = self.text(xp[1]/10.,yp[1]/10., str("%5.3f"%self.starty))
self.startzt = self.text(xp[2]/10.,yp[2]/10., str("%5.3f"%self.startz))

xpn, ypn, zpn = self.projpoint(self.xn, self.yn, self.zn, theta=self.theta, phi=self.phi)
self.collection = self.scatter(xpn, ypn)

self.figure.canvas.mpl_connect('motion_notify_event', self.on_move)

def plotxyz(self) :
xp, yp, zp = self.projpoint(self.axex, self.axey, self.axez, theta=self.theta, phi=self.phi)
self.ax0line.set_data([0.,xp[0]],[0.,yp[0]])
self.ax1line.set_data([0.,xp[1]],[0.,yp[1]])
self.ax2line.set_data([0.,xp[2]],[0.,yp[2]])

eps = self.eps

self.labxt.set_position((xp[0]+eps,yp[0]+eps))
self.labyt.set_position((xp[1]+eps,yp[1]+eps))
self.labzt.set_position((xp[2]+eps,yp[2]+eps))
self.endxt.set_position((xp[0],yp[0]))
self.endyt.set_position((xp[1],yp[1]))
self.endzt.set_position((xp[2],yp[2]))
self.startxt.set_position((xp[0]/10.,yp[0]/10.))
self.startyt.set_position((xp[1]/10.,yp[1]/10.))
self.startzt.set_position((xp[2]/10.,yp[2]/10.))

xpn, ypn, zpn = self.projpoint(self.xn, self.yn, self.zn, theta=self.theta, phi=self.phi)

# todo, expose the _offsets property "publicly"
self.collection._offsets = zip(xpn, ypn)
self.figure.canvas.draw()

return xpn, ypn, zpn

def on_move(self, event):
# get the x and y pixel coords
x, y = event.x, event.y
self.theta = x * 3.6 / self.w
self.phi = y * 3.6 / self.h
if event.inaxes == self:
self.plotxyz()

def rotaxe(self, x,y,rotangle=45.) :
rotangle = rotangle * nx.pi / 180.
xp = x * nx.cos(rotangle) + y * nx.sin(rotangle)
yp = - x * nx.sin(rotangle) + y * nx.cos(rotangle)
return xp, yp

def projpoint(self, x,y,z,theta=45.,phi=0.) :
xp, y1 = self.rotaxe(x,y,rotangle=phi)
yp, zp = self.rotaxe(y1,z,rotangle=theta)
return xp, yp, zp

from pylab import figure, show
fig = figure()
x,y,z = nx.mlab.rand(3,100)
ax = Scatter3D(fig, [0.1, 0.1, 0.8, 0.8],
x, y, z, labx="x", laby="y", labz="z")