【问题标题】:matplotlib: getting coordinates in 3D plots by a mouseeventmatplotlib:通过鼠标事件获取 3D 图中的坐标
【发布时间】:2015-06-05 19:41:30
【问题描述】:

我想通过单击等鼠标事件在 3D 图中获取坐标 (x,y,z)。 MATLAB 有这个功能,datacursormode。一张好图在下面的链接中。

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

mpldatacursor (https://github.com/joferkington/mpldatacursor) 是 matplotlib 的类似函数,但是,这似乎不适合 3D 绘图。 x 和 y 值即使可以得到也不正确。

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()

如果可能的话,我也想获得 z 值。 有什么好办法吗?

【问题讨论】:

  • 令人惊讶的是,这似乎不受支持。

标签: python matlab matplotlib 3d mouseevent


【解决方案1】:

根据您建议的链接 (https://github.com/joferkington/mpldatacursor) 中的文件“changelog.rst”,此功能已于 2015 年 7 月添加。不幸的是,它看起来像是从鼠标单击的位置提取数据点,而不是原始数据集。这导致结果有些不精确。 一种可能性是根据Get data from plot with matplotlib 中为 2D 版本提供的说明修改数据游标。希望这可以帮助。

【讨论】:

  • 虽然确实使用了光标位置而不是数据集中的最近点,但错误也来自于光标位置在 2D 中已知的事实,因此指的是无限数量的可能3D 点,光标下沿线的任何点(取决于投影)。 matplotlib 实现了一个规则来决定它显示哪个点(与 mpldatacursor 使用相同的规则)。在一般情况下,它不会在数据集中产生一个点。继续...
  • ...要做到这一点,您必须实施一些其他规则,例如在数据集中找到离光标下的线最近的点。为了使对大量点的搜索有效,将它们存储在分层数据结构中会有所帮助。
【解决方案2】:

mpldatacursor 对于我想要的来说太复杂了,即以某种方式在回调函数中接收 x,y,z 坐标。我从 mpldatacursor pick_info.py 中提取了一个辅助函数 (get_xyz_mouse_click),该函数完成了获取坐标所需的最低限度(即,没有悬停窗口,没有复杂的事件处理)。这是辅助函数:

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

这是在 3D 散点图中使用它的示例:

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)

您应该编辑 on_press() 以对 x,y,z 执行某些操作。它仍然存在使用轴网格生成点引用的另一个答案的问题(即,它不搜索原始数据以寻找最近的邻居)。我建议对原始数据模型(点、线等)进行距离变换,因为搜索表面中的补丁会变得非常复杂。

我真的希望它以 Matlab 的 datacursormode 的工作方式内置到 matplotlib 中!

【讨论】:

  • 我收到错误 ModuleNotFoundError: No module named 'simple_pick_info'。该模块似乎不存在。
  • 该解决方案没有明确说明帮助函数通常驻留在模块 simple_pick_info 中。在示例中,只需将 x,y,z = simple_pick_info.pick_info.get_xyz_mouse_click(event, ax) 更改为 x,y,z = get_xyz_mouse_click(event, ax) 它不提供鼠标悬停,但会输出鼠标坐标位置。跨度>
猜你喜欢
  • 2017-07-14
  • 2015-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-28
  • 2016-04-14
  • 1970-01-01
相关资源
最近更新 更多