Lấy tọa độ của điểm dữ liệu gần nhất trên biểu đồ matplotlib


9

Tôi đang sử dụng matplotlibvới NavigationToolbar2QT. Thanh công cụ đang hiển thị vị trí của con trỏ. Nhưng tôi muốn con trỏ chộp vào điểm dữ liệu gần nhất (khi đủ gần) hoặc chỉ đơn giản là hiển thị tọa độ của điểm dữ liệu gần nhất. Điều đó có thể được sắp xếp bằng cách nào đó?


Vui lòng kiểm tra liên kết dưới đây và xem nếu nó giải quyết vấn đề của bạn. Liên kết cung cấp một hàm snaptocthon trông giống như những gì bạn đang tìm kiếm. matplotlib.org/3.1.1/gallery/misc/cthon_demo_sgskip.html
Anupam Chaplot

@AnupamChaplot "Nó sử dụng Matplotlib để vẽ con trỏ và có thể chậm vì điều này đòi hỏi phải vẽ lại hình với mỗi lần di chuyển chuột." Tôi có khoảng 16 ô với 10000 điểm MACHI trên biểu đồ, vì vậy với việc vẽ lại điều này sẽ khá chậm.
Pygmalion

Nếu bạn không muốn vẽ lại bất cứ điều gì theo bề ngoài (tại sao hỏi cho rằng sau đó?), Bạn có thể thao tác những gì được hiển thị trên thanh công cụ như trong matplotlib.org/3.1.1/gallery/images_contours_and_fields/...
ImportanceOfBeingErnest

@ImportanceOfByingErnest Tôi không hiểu đề xuất của bạn. Nhưng hãy tưởng tượng điều này: bạn có 16 ô vuông và mỗi ô có một đỉnh riêng biệt. Bạn muốn biết tọa độ chính xác của đỉnh của một âm mưu mà không nhìn trộm vào dữ liệu. Bạn không bao giờ có thể đặt con trỏ chính xác vào điểm, vì vậy điều này rất thiếu chính xác. Vì vậy, các chương trình như Origin có một tùy chọn để hiển thị tọa độ chính xác của điểm gần nhất với vị trí con trỏ hiện tại.
Pygmalion

1
Vâng, đó là những gì con trỏ_demo_sgskip làm. Nhưng nếu bạn không muốn vẽ con trỏ, bạn có thể sử dụng các phép tính từ ví dụ đó và thay vào đó hiển thị số kết quả trên thanh công cụ, như được hiển thị trong image_zcoord
ImportanceOfByingErnest

Câu trả lời:


6

Nếu bạn đang làm việc với các nhóm điểm lớn, tôi khuyên bạn nên sử dụng 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)

Tôi tái cấu trúc kpie'scâu trả lời ở đây một chút. Khi ckdtreeđược tạo, bạn có thể xác định các điểm gần nhất ngay lập tức và nhiều loại thông tin khác nhau về chúng với một chút nỗ lực:

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

Hiển thị tương tác vị trí con trỏ. Nếu bạn muốn tọa độ của điểm gần nhất được hiển thị trên Thanh công cụ Điều hướng:

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

Sử dụng các sự kiện. Nếu bạn muốn một loại tương tác khác, bạn có thể sử dụng các sự kiện:

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

Điều này tất nhiên sẽ hoạt động hoàn hảo chỉ khi tỷ lệ hình ảnh x: y bằng 1. Có ý tưởng nào về phần này của vấn đề, ngoại trừ việc thu nhỏ lại pointsmỗi lần cốt truyện được phóng to?
Pygmalion

Thay đổi tỷ lệ khung hình yêu cầu thay đổi số liệu về cách đo khoảng cách bằng ckdtrees. Có vẻ như việc sử dụng các số liệu tùy chỉnh trên ckdtrees không được hỗ trợ. Do đó, bạn phải giữ ckdtree.datađiểm thực tế với tỷ lệ = 1. Bạn pointscó thể được định cỡ lại và không có vấn đề gì nếu bạn chỉ cần truy cập vào các chỉ số của họ.
mathfux

Cảm ơn. Bạn có biết, trong mọi trường hợp, nếu có một cách dễ dàng truy cập tỷ lệ quy mô reuw cho các trục trong matplotlib? Những gì tôi tìm thấy trên web là vô cùng phức tạp.
Pygmalion

IMHO giải pháp tốt nhất cho vấn đề của tôi là đưa tùy chọn đó vào matplotlibthư viện. Rốt cuộc, thư viện đã giải quyết các vị trí điểm ở đâu đó - sau tất cả, nó đang vẽ chúng lên trong cốt truyện!
Pygmalion

Bạn có thể muốn thử set_aspect: matplotlib.org/3.1.3/api/_as_gen/
Kẻ

0

Đoạn mã sau sẽ in tọa độ của dấu chấm gần chuột nhất khi bạn nhấp.

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

Tôi có thể có thể điều chỉnh mã theo tình huống của mình (16 ô với 10000 điểm mỗi điểm), nhưng ý tưởng là tọa độ của dấu chấm được in trên, ví dụ, thanh công cụ điều hướng. Điều đó có thể không?
Pygmalion

0

Bạn có thể phân lớp NavigationToolbar2QTvà ghi đè mouse_movetrình xử lý. Các thuộc tính xdataydatachứa vị trí chuột hiện tại trong tọa độ cốt truyện. Bạn có thể chụp nó đến điểm dữ liệu gần nhất trước khi chuyển sự kiện đến mouse_movetrình xử lý lớp cơ sở .

Ví dụ đầy đủ, với việc làm nổi bật điểm gần nhất trong cốt truyện dưới dạng phần thưởng:

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_()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.