Python 获取matplotlib绘图上最近数据点的坐标

Python 获取matplotlib绘图上最近数据点的坐标,python,matplotlib,Python,Matplotlib,我正在将matplotlib与NavigationToolbar2QT一起使用。工具栏显示光标的位置。但我希望光标捕捉到最近的数据点(如果足够近)或仅显示最近数据点的坐标。是否可以以某种方式排列?单击时,以下代码将打印最靠近鼠标的点的坐标 import matplotlib.pyplot as plt import numpy as np np.random.seed(19680801) N = 50 x = np.random.rand(N) y = np.random.rand(N) fig

我正在将
matplotlib
NavigationToolbar2QT
一起使用。工具栏显示光标的位置。但我希望光标捕捉到最近的数据点(如果足够近)仅显示最近数据点的坐标。是否可以以某种方式排列?

单击时,以下代码将打印最靠近鼠标的点的坐标

import matplotlib.pyplot as plt
import numpy as np
np.random.seed(19680801)
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
fig,ax = plt.subplots()
plt.scatter(x, y)
points = list(zip(x,y))
def distance(a,b):
    return(sum([(k[0]-k[1])**2 for k in zip(a,b)])**0.5)
def onclick(event):
    dists = [distance([event.xdata, event.ydata],k) for k in points]
    print(points[dists.index(min(dists))])
fig.canvas.mpl_connect('button_press_event', onclick)
plt.show()

您可以子类化
NavigationToolbar2QT
并覆盖
鼠标移动
处理程序。
xdata
ydata
属性包含打印坐标中的当前鼠标位置。在将事件传递给基类
mouse\u move
处理程序之前,可以将其捕捉到最近的数据点

完整示例,突出显示绘图中最近的点作为奖励:

import sys

import numpy as np

from matplotlib.backends.qt_compat import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvas, NavigationToolbar2QT
from matplotlib.figure import Figure


class Snapper:
    """Snaps to data points"""

    def __init__(self, data, callback):
        self.data = data
        self.callback = callback

    def snap(self, x, y):
        pos = np.array([x, y])
        distances = np.linalg.norm(self.data - pos, axis=1)
        dataidx = np.argmin(distances)
        datapos = self.data[dataidx,:]
        self.callback(datapos[0], datapos[1])
        return datapos


class SnappingNavigationToolbar(NavigationToolbar2QT):
    """Navigation toolbar with data snapping"""

    def __init__(self, canvas, parent, coordinates=True):
        super().__init__(canvas, parent, coordinates)
        self.snapper = None

    def set_snapper(self, snapper):
        self.snapper = snapper

    def mouse_move(self, event):
        if self.snapper and event.xdata and event.ydata:
            event.xdata, event.ydata = self.snapper.snap(event.xdata, event.ydata)
        super().mouse_move(event)


class Highlighter:
    def __init__(self, ax):
        self.ax = ax
        self.marker = None
        self.markerpos = None

    def draw(self, x, y):
        """draws a marker at plot position (x,y)"""
        if (x, y) != self.markerpos:
            if self.marker:
                self.marker.remove()
                del self.marker
            self.marker = self.ax.scatter(x, y, color='yellow')
            self.markerpos = (x, y)
            self.ax.figure.canvas.draw()


class ApplicationWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self._main = QtWidgets.QWidget()
        self.setCentralWidget(self._main)
        layout = QtWidgets.QVBoxLayout(self._main)
        canvas = FigureCanvas(Figure(figsize=(5,3)))
        layout.addWidget(canvas)
        toolbar = SnappingNavigationToolbar(canvas, self)
        self.addToolBar(toolbar)

        data = np.random.randn(100, 2)
        ax = canvas.figure.subplots()
        ax.scatter(data[:,0], data[:,1])

        self.highlighter = Highlighter(ax)
        snapper = Snapper(data, self.highlighter.draw)
        toolbar.set_snapper(snapper)


if __name__ == "__main__":
    qapp = QtWidgets.QApplication(sys.argv)
    app = ApplicationWindow()
    app.show()
    qapp.exec_()

如果您使用的是大量点,我建议您使用
CKDtrees

import matplotlib.pyplot as plt
import numpy as np
import scipy.spatial

points = np.column_stack([np.random.rand(50), np.random.rand(50)])
fig, ax = plt.subplots()
coll = ax.scatter(points[:,0], points[:,1])
ckdtree = scipy.spatial.cKDTree(points)
我重构了kpie的答案。一旦创建了
ckdtree
,您就可以立即识别最近的点以及有关这些点的各种信息:

def closest_point_distance(ckdtree, x, y):
    #returns distance to closest point
    return ckdtree.query([x, y])[0]

def closest_point_id(ckdtree, x, y):
    #returns index of closest point
    return ckdtree.query([x, y])[1]

def closest_point_coords(ckdtree, x, y):
    # returns coordinates of closest point
    return ckdtree.data[closest_point_id(ckdtree, x, y)]
    # ckdtree.data is the same as points
光标位置的交互式显示。 如果要在导航工具栏上显示最近点的坐标:

def val_shower(ckdtree):
    #formatter of coordinates displayed on Navigation Bar
    return lambda x, y: '[x = {}, y = {}]'.format(*closest_point_coords(ckdtree, x, y))

plt.gca().format_coord = val_shower(ckdtree)
plt.show()
使用事件。 如果需要另一种交互方式,可以使用事件:

def onclick(event):
    if event.inaxes is not None:
        print(closest_point_coords(ckdtree, event.xdata, event.ydata))

fig.canvas.mpl_connect('motion_notify_event', onclick)
plt.show()

另一种可能性是使用已经具有的拾取支撑轴。请参阅事件处理文档中的


Jim

请检查下面的链接,看看它是否解决了您的问题。该链接提供了一个函数snaptocursor,它看起来与您正在寻找的类似@AnupamChaplot“它使用Matplotlib来绘制光标,可能会很慢,因为这需要每次鼠标移动都重新绘制图形。”我在图形上有大约16个图形,每个图形上有10000个点,因此重新绘制会很慢。如果你不想在视觉上重新绘制任何东西(那么为什么要求这样做呢?),您可以操作工具栏中显示的内容,如@ImportanceOfBeingErnest所示。我不理解您的建议。但是想象一下:你有16条直线图,每一条都有一个明显的峰值。您希望知道一个绘图峰值的精确坐标,而不必查看数据。您永远无法将光标精确地放置在点上,因此这是非常不精确的。所以像Origin这样的程序有一个选项,可以显示距离当前光标位置最近点的精确坐标。是的,这就是它的功能。但是,如果您不想绘制光标,可以使用该示例中的计算结果,而是在工具栏中显示生成的数字,如中所示。我可能能够根据自己的情况调整代码(16个绘图,每个绘图10000个点),但想法是,点的坐标打印在导航工具栏上。这有可能吗?当然,只有当x:y视觉比例等于1时,这才会完美地工作。除了每次缩放绘图时重新缩放
点之外,对问题的这一部分有什么想法吗?更改纵横比需要更改CKD树中测量距离的度量。似乎不支持在ckdtrees上使用自定义度量。因此,您必须将
ckdtree.data
保留为比例为1的真实点。您的
点可以重新缩放,如果您只需要访问它们的索引,则不会有任何问题。谢谢。您是否知道,在
matplotlib
中,是否有一种方法可以轻松访问轴的REEW比例?我在网上发现的东西非常复杂。我想解决我的问题的最好办法是将它作为一个选项包含在
matplotlib
库中。毕竟,库已经在某个地方重新调用了点位置——毕竟,它正在绘图中绘制它们!您可能想试试
set\u aspect
:这是一个非常可靠的解决方案,非常感谢!我不理解Snapper类中的轴=1部分,
距离=np.linalg.norm(self.data-pos,axis=1)
(self.data-pos)是一个存储n个二维位置向量的n x 2矩阵。axis=1参数告诉numpy计算矩阵每行的范数,返回这些位置向量的欧几里德长度。axis=0将逐列运行,axis=None将返回矩阵范数。有一个很好的视觉解释看看。啊,我明白了,谢谢你的链接。因此
轴=1
意味着
距离
现在是一个长度为n的数组,包含
[sqrt(dx0²+dy0²)、sqrt(dx1²+dy1²)、sqrt(dx2²+dy2²),…]
轴=0
将创建一个长度为2的无意义对象,其中包含
[sqrt(dx0²+dx1+dx2+dx2+dx2+dx2²+dx2+)]、[sqrt(dy0²+dy2²+)]