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")
fig.add_axes(ax)
show()