0

I am new to programming in Python. I am working on a project that involves 3D surface plots, I want to get the coordinates of a point on the 3D surface in the event of a mouse click. I have tried checking online for something similar and I have found a neat way to do this for 2D plots. (data tip for 2D plots)

My question is can this be extended for 3D plots? My intuition is that I can obtain the x, y coordinates from the surface plot similar to the solution given in the link, which depending on the current view can be mapped to a 3D coordinate point (does it sound right?). But, I am not able to find a way to do it.

Community
  • 1
  • 1
Naren
  • 23
  • 6

1 Answers1

0

OK, I figured out the solution! might be useful for someone so posting it here. It is combination of two solutions from stack Overflow.

Solution 1 Solution 2

I have used ax.format_coord(x,y) to obtain the z value. With ax.format_coord(mouseevent.xdata,mouseevent.ydata) you get the x, y, z values in a string ('x=0.222, y=0.452, z=0.826') from which you can extract the values.(2)

def getz(x,y,ax):
    s = ax.format_coord(x,y)
    out = ""
    for i in range(s.find('z')+2,len(s)-1):
        out = out+s[i]
    return float(out)

class DataCursor(object):
    """A simple data cursor widget that displays the x,y location of a
    matplotlib artist when it is selected."""
    def __init__(self, artists, tolerance=5, offsets=(-20, 20), 
                 template='z: %0.2f', display_all=False):
    """Create the data cursor and connect it to the relevant figure.
    "artists" is the matplotlib artist or sequence of artists that will be 
        selected. 
    "tolerance" is the radius (in points) that the mouse click must be
        within to select the artist.
    "offsets" is a tuple of (x,y) offsets in points from the selected
        point to the displayed annotation box
    "template" is the format string to be used. Note: For compatibility
        with older versions of python, this uses the old-style (%) 
        formatting specification.
    "display_all" controls whether more than one annotation box will
        be shown if there are multiple axes.  Only one will be shown
        per-axis, regardless. 
    """
    self.template = template
    self.offsets = offsets
    self.display_all = display_all
    if not cbook.iterable(artists):
        artists = [artists]
    self.artists = artists
    self.axes = tuple(set(art.axes for art in self.artists))
    self.figures = tuple(set(ax.figure for ax in self.axes))

    self.annotations = {}
    for ax in self.axes:
        self.annotations[ax] = self.annotate(ax)

    for artist in self.artists:
        artist.set_picker(tolerance)
    for fig in self.figures:
        fig.canvas.mpl_connect('pick_event', self)

def annotate(self, ax):
    """Draws and hides the annotation box for the given axis "ax"."""
    annotation = ax.annotate(self.template, xy=(0, 0), ha='right',
            xytext=self.offsets, textcoords='offset points', va='bottom',
            bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
            arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0')
            )
    annotation.set_visible(False)
    return annotation

def __call__(self, event):
    """Intended to be called through "mpl_connect"."""
    # Rather than trying to interpolate, just display the clicked coords
    # This will only be called if it's within "tolerance", anyway.
    x, y = event.mouseevent.xdata, event.mouseevent.ydata
    z = getz(x,y,self.axes[0]) # Just a small change here.
    # print str(x) + ' ' + str(y) + ' ' + str(z)
    annotation = self.annotations[event.artist.axes]
    if x is not None:
        if not self.display_all:
            # Hide any other annotation boxes...
            for ann in self.annotations.values():
                ann.set_visible(False)
        # Update the annotation in the current axis..
        annotation.xy = x, y
        annotation.set_text(self.template % (z))
        annotation.set_visible(True)
        event.canvas.draw()


if __name__ == '__main__':
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D

    plt.figure()
    ax = Axes3D(fig)
    X = np.arange(-5, 5, 0.25)
    Y = np.arange(-5, 5, 0.25)
    X, Y = np.meshgrid(X, Y)
    R = np.sqrt(X**2 + Y**2)
    Z = np.sin(R)
    surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1)
    DataCursor([surf])

    plt.show()

Hope this helps. Thanks!

Community
  • 1
  • 1
Naren
  • 23
  • 6
  • I think you have some indentation problems, your code doesn't work for me. – leb Sep 18 '15 at 14:42
  • Thanks for the comment. I am no longer working on this project, I won't be able to test it. Thanks! – Naren Sep 18 '15 at 21:20