Matplotlib: getting coordinates in 3D plots with mouse

I want to get coordinates (x, y, z) in 3D plots using a mouse event like click. MATLAB has this function datacursormode

. A good image is in the following link.

http://www.mathworks.com/help/matlab/ref/datacursormode.html

mpldatacursor

( https://github.com/joferkington/mpldatacursor ) is a similar function for matplotlib, however this seems unusable for 3D plots. x and y are not correct even though they can be obtained.

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from mpldatacursor import datacursor

x = np.arange(-3, 3, 0.25)
y = np.arange(-3, 3, 0.25)
X, Y = np.meshgrid(x, y)
Z = np.sin(X)+ np.cos(Y)

fig = plt.figure()
ax = Axes3D(fig)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1)

datacursor(surf)

plt.show()

      

I also want to get the z value if possible. Is there a good way?

+4


source to share


3 answers


As per the "changelog.rst" file at the link you suggested ( https://github.com/joferkington/mpldatacursor ) this feature was added in July 2015 Unfortunately it looks like it is fetching data points from where the mouse clicks, not the original dataset. This leads to some inaccuracy in the result. It is possible to modify the datacursor according to the instructions provided for the 2D version in Get data from a plot using matplotlib . Hope this helps. enter image description here



0


source


I need to know exactly how the get_xyz_mouse function works. thank



0


source


mpldatacursor was too complicated for what I wanted to get the x, y, z coordinates in the callback function. I extracted a helper function (get_xyz_mouse_click) from mpldatacursor's pick_info.py that does the minimum required to get coordinates (i.e. no hover window, no complicated event handling). Here's a helper function:

import numpy as np
import matplotlib.transforms as mtransforms
from mpl_toolkits import mplot3d

def get_xyz_mouse_click(event, ax):
    """
    Get coordinates clicked by user
    """
    if ax.M is None:
        return {}

    xd, yd = event.xdata, event.ydata
    p = (xd, yd)
    edges = ax.tunit_edges()
    ldists = [(mplot3d.proj3d.line2d_seg_dist(p0, p1, p), i) for \
                i, (p0, p1) in enumerate(edges)]
    ldists.sort()

    # nearest edge
    edgei = ldists[0][1]

    p0, p1 = edges[edgei]

    # scale the z value to match
    x0, y0, z0 = p0
    x1, y1, z1 = p1
    d0 = np.hypot(x0-xd, y0-yd)
    d1 = np.hypot(x1-xd, y1-yd)
    dt = d0+d1
    z = d1/dt * z0 + d0/dt * z1

    x, y, z = mplot3d.proj3d.inv_transform(xd, yd, z, ax.M)
    return x, y, z

      

Here's an example of using it in a 3D scatter plot:

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np

import simple_pick_info.pick_info

# Fixing random state for reproducibility
np.random.seed(19680801)


def randrange(n, vmin, vmax):
    '''
    Helper function to make an array of random numbers having shape (n, )
    with each number distributed Uniform(vmin, vmax).
    '''
    return (vmax - vmin)*np.random.rand(n) + vmin

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

n = 100

# For each set of style and range settings, plot n random points in the box
# defined by x in [23, 32], y in [0, 100], z in [zlow, zhigh].
for c, m, zlow, zhigh in [('r', 'o', -50, -25), ('b', '^', -30, -5)]:
    xs = randrange(n, 23, 32)
    ys = randrange(n, 0, 100)
    zs = randrange(n, zlow, zhigh)
    ax.scatter(xs, ys, zs, c=c, marker=m)

ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')

plt.show()


def on_press(event):
    x,y,z = simple_pick_info.pick_info.get_xyz_mouse_click(event, ax)    
    print(f'Clicked at: x={x}, y={y}, z={z}')

cid = fig.canvas.mpl_connect('button_press_event', on_press)

      

You have to edit on_press()

to do something with x, y, z. There is still a problem that another answer is quoted using an axis grid to get a point (i.e. it does not look for the nearest neighbor in the original data). I recommend doing this by converting the distance to your original data model (points, lines, etc.), because it will be very difficult to search for areas on the surface.

I really would like this to be embedded in matplotlib the way Matlab datacursormode works!

-1


source







All Articles