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?
source to share
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.
source to share
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!
source to share