获取Matplotlib图上最近的数据点的坐标


9

我使用matplotlibNavigationToolbar2QT。工具栏显示光标的位置。但是我希望光标捕捉到最近的数据点(足够接近时),或者只是显示最近的数据点的坐标。能以某种方式安排吗?


请检查下面的链接,看看它是否可以解决您的问题。该链接提供了一个功能与自己所需的外观相似的snaptocursor。matplotlib.org/3.1.1/gallery/misc/cursor_demo_sgskip.html
Anupam Chaplot

@AnupamChaplot“它使用Matplotlib绘制光标,可能会很慢,因为这需要在每次移动鼠标时都重新绘制图形。” 我在图形上大约有16个图,每个图具有10000点,因此重绘会比较慢。
皮格马利翁

如果你不想在视觉上重绘什么(为什么要求这个呢?),你可以操纵什么工具栏中显示如图matplotlib.org/3.1.1/gallery/images_contours_and_fields/...
ImportanceOfBeingErnest

@ImportanceOfBeingErnest我不明白您的建议。但请想象一下:您有16个线图,每个图都有一个不同的峰值。您想知道一个图的峰的确切坐标,而无需查看数据。您永远不能将光标精确地指向该点,因此这是非常不精确的。因此,像Origin这样的程序可以选择显示最接近当前光标位置的精确坐标。
皮格马利翁

1
是的,这就是cursor_demo_sgskip的功能。但是,如果您不想绘制光标,则可以使用该示例中的计算结果,而是在工具栏中显示结果数字,如image_zcoord
ImportanceOfBeingErnest

Answers:


6

如果您要处理大量要点,建议您使用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's在这里重构了一点答案。一旦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()

当然,只有在x:y视觉比例等于1的情况下,这才可以完美地工作。除了points每次缩放图时重新缩放外,对这部分问题有任何想法吗?
皮格马利翁

更改宽高比要求更改以ckdtrees为单位测量距离的度量。似乎不支持在ckdtrees上使用自定义指标。因此,您必须保留ckdtree.data比例= 1的实际点。您points可以重新缩放比例,如果只需要访问它们的索引就没有问题。
mathfux

谢谢。您是否知道是否有一种方法可以轻松访问in轴的反比例比例matplotlib?我在网上发现的内容非常复杂。
皮格马利翁

恕我直言,针对我的问题的最佳解决方案是将其作为选项包括在matplotlib库中。毕竟,库已在某个位置重新调用了点位置-毕竟,它是在绘图中绘制它们!
皮格马利翁

你可能会想尝试set_aspectmatplotlib.org/3.1.3/api/_as_gen/...
mathfux

0

当您单击时,以下代码将打印最接近鼠标的点的坐标。

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

我可能可以使代码适应我的情况(每个16个绘图,每个10000个点),但是想法是将点的坐标打印在例如导航工具栏上。那可能吗?
皮格马利翁

0

您可以继承NavigationToolbar2QT并覆盖mouse_move处理程序。在xdataydata属性包含在情节坐标当前鼠标的位置。您可以在将事件传递给基类mouse_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_()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.