มีวิธีใน matplotlib ในการตรวจสอบว่าศิลปินคนใดที่อยู่ในพื้นที่ที่แสดงอยู่ในปัจจุบันของแกน?


9

ฉันมีโปรแกรมที่มีรูปแบบอินเทอร์แอคทีฟซึ่งศิลปินหลายคนถูกวาดเป็นครั้งคราว ในรูปนี้คุณยังสามารถซูมและกวาดโดยใช้เมาส์ อย่างไรก็ตามการแสดงในระหว่างการซูมปรากฎว่าไม่ค่อยดีนักเพราะศิลปินทุกคนวาดขึ้นมาใหม่เสมอ มีวิธีการตรวจสอบว่าศิลปินคนไหนที่อยู่ในพื้นที่แสดงในปัจจุบันและวาดใหม่เท่านั้น? (ในตัวอย่างด้านล่างน้ำหอมยังคงค่อนข้างดี แต่มันสามารถทำให้แย่ลงโดยพลการโดยใช้ศิลปินที่ซับซ้อนมากขึ้น)

ฉันมีปัญหาเกี่ยวกับ performace ที่คล้ายกันกับhoverวิธีที่เมื่อใดก็ตามที่มันถูกเรียกว่ามันวิ่งcanvas.draw()ในตอนท้าย แต่อย่างที่คุณเห็นฉันพบวิธีแก้ปัญหาที่เรียบร้อยด้วยการใช้แคชและเรียกคืนพื้นหลังของแกน (ตามนี้ ) สิ่งนี้ปรับปรุงการแสดงได้อย่างมีนัยสำคัญและตอนนี้ถึงแม้จะมีศิลปินหลายคนมันก็ราบรื่นดี อาจจะมีวิธีที่คล้ายกันในการทำเช่นนี้ แต่สำหรับpanและzoomวิธีการ?

ขออภัยสำหรับตัวอย่างโค้ดแบบยาวส่วนใหญ่ไม่เกี่ยวข้องโดยตรงกับคำถาม แต่จำเป็นสำหรับตัวอย่างการทำงานเพื่อเน้นปัญหา

แก้ไข

ฉันอัปเดต MWE เป็นสิ่งที่เป็นตัวแทนของรหัสจริงของฉัน

import numpy as np
import numpy as np
import sys
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import \
    FigureCanvasQTAgg
import matplotlib.patheffects as PathEffects
from matplotlib.text import Annotation
from matplotlib.collections import LineCollection

from PyQt5.QtWidgets import QApplication, QVBoxLayout, QDialog


def check_limits(base_xlim, base_ylim, new_xlim, new_ylim):
    if new_xlim[0] < base_xlim[0]:
        overlap = base_xlim[0] - new_xlim[0]
        new_xlim[0] = base_xlim[0]
        if new_xlim[1] + overlap > base_xlim[1]:
            new_xlim[1] = base_xlim[1]
        else:
            new_xlim[1] += overlap
    if new_xlim[1] > base_xlim[1]:
        overlap = new_xlim[1] - base_xlim[1]
        new_xlim[1] = base_xlim[1]
        if new_xlim[0] - overlap < base_xlim[0]:
            new_xlim[0] = base_xlim[0]
        else:
            new_xlim[0] -= overlap
    if new_ylim[1] < base_ylim[1]:
        overlap = base_ylim[1] - new_ylim[1]
        new_ylim[1] = base_ylim[1]
        if new_ylim[0] + overlap > base_ylim[0]:
            new_ylim[0] = base_ylim[0]
        else:
            new_ylim[0] += overlap
    if new_ylim[0] > base_ylim[0]:
        overlap = new_ylim[0] - base_ylim[0]
        new_ylim[0] = base_ylim[0]
        if new_ylim[1] - overlap < base_ylim[1]:
            new_ylim[1] = base_ylim[1]
        else:
            new_ylim[1] -= overlap

    return new_xlim, new_ylim


class FigureCanvas(FigureCanvasQTAgg):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.bg_cache = None

    def draw(self):
        ax = self.figure.axes[0]
        hid_annotation = False
        if ax.annot.get_visible():
            ax.annot.set_visible(False)
            hid_annotation = True
        hid_highlight = False
        if ax.last_artist:
            ax.last_artist.set_path_effects([PathEffects.Normal()])
            hid_highlight = True
        super().draw()
        self.bg_cache = self.copy_from_bbox(self.figure.bbox)
        if hid_highlight:
            ax.last_artist.set_path_effects(
                [PathEffects.withStroke(
                    linewidth=7, foreground="c", alpha=0.4
                )]
            )
            ax.draw_artist(ax.last_artist)
        if hid_annotation:
            ax.annot.set_visible(True)
            ax.draw_artist(ax.annot)

        if hid_highlight:
            self.update()


def position(t_, coeff, var=0.1):
    x_ = np.random.normal(np.polyval(coeff[:, 0], t_), var)
    y_ = np.random.normal(np.polyval(coeff[:, 1], t_), var)

    return x_, y_


class Data:
    def __init__(self, times):
        self.length = np.random.randint(1, 20)
        self.t = np.sort(
            np.random.choice(times, size=self.length, replace=False)
        )
        self.vel = [np.random.uniform(-2, 2), np.random.uniform(-2, 2)]
        self.accel = [np.random.uniform(-0.01, 0.01), np.random.uniform(-0.01,
                                                                      0.01)]
        x0, y0 = np.random.uniform(0, 1000, 2)
        self.x, self.y = position(
            self.t, np.array([self.accel, self.vel, [x0, y0]])
        )


class Test(QDialog):
    def __init__(self):
        super().__init__()
        self.fig, self.ax = plt.subplots()
        self.canvas = FigureCanvas(self.fig)
        self.artists = []
        self.zoom_factor = 1.5
        self.x_press = None
        self.y_press = None
        self.annot = Annotation(
            "", xy=(0, 0), xytext=(-20, 20), textcoords="offset points",
            bbox=dict(boxstyle="round", fc="w", alpha=0.7), color='black',
            arrowprops=dict(arrowstyle="->"), zorder=6, visible=False,
            annotation_clip=False, in_layout=False,
        )
        self.annot.set_clip_on(False)
        setattr(self.ax, 'annot', self.annot)
        self.ax.add_artist(self.annot)
        self.last_artist = None
        setattr(self.ax, 'last_artist', self.last_artist)

        self.image = np.random.uniform(0, 100, 1000000).reshape((1000, 1000))
        self.ax.imshow(self.image, cmap='gray', interpolation='nearest')
        self.times = np.linspace(0, 20)
        for i in range(1000):
            data = Data(self.times)
            points = np.array([data.x, data.y]).T.reshape(-1, 1, 2)
            segments = np.concatenate([points[:-1], points[1:]], axis=1)
            z = np.linspace(0, 1, data.length)
            norm = plt.Normalize(z.min(), z.max())
            lc = LineCollection(
                segments, cmap='autumn', norm=norm, alpha=1,
                linewidths=2, picker=8, capstyle='round',
                joinstyle='round'
            )
            setattr(lc, 'data_id', i)
            lc.set_array(z)
            self.ax.add_artist(lc)
            self.artists.append(lc)
        self.default_xlim = self.ax.get_xlim()
        self.default_ylim = self.ax.get_ylim()

        self.canvas.draw()

        self.cid_motion = self.fig.canvas.mpl_connect(
            'motion_notify_event', self.motion_event
        )
        self.cid_button = self.fig.canvas.mpl_connect(
            'button_press_event', self.pan_press
        )
        self.cid_zoom = self.fig.canvas.mpl_connect(
            'scroll_event', self.zoom
        )

        layout = QVBoxLayout()
        layout.addWidget(self.canvas)
        self.setLayout(layout)

    def zoom(self, event):
        if event.inaxes == self.ax:
            scale_factor = np.power(self.zoom_factor, -event.step)
            xdata = event.xdata
            ydata = event.ydata
            cur_xlim = self.ax.get_xlim()
            cur_ylim = self.ax.get_ylim()
            x_left = xdata - cur_xlim[0]
            x_right = cur_xlim[1] - xdata
            y_top = ydata - cur_ylim[0]
            y_bottom = cur_ylim[1] - ydata

            new_xlim = [
                xdata - x_left * scale_factor, xdata + x_right * scale_factor
            ]
            new_ylim = [
                ydata - y_top * scale_factor, ydata + y_bottom * scale_factor
            ]
            # intercept new plot parameters if they are out of bounds
            new_xlim, new_ylim = check_limits(
                self.default_xlim, self.default_ylim, new_xlim, new_ylim
            )

            if cur_xlim != tuple(new_xlim) or cur_ylim != tuple(new_ylim):
                self.ax.set_xlim(new_xlim)
                self.ax.set_ylim(new_ylim)

                self.canvas.draw_idle()

    def motion_event(self, event):
        if event.button == 1:
            self.pan_move(event)
        else:
            self.hover(event)

    def pan_press(self, event):
        if event.inaxes == self.ax:
            self.x_press = event.xdata
            self.y_press = event.ydata

    def pan_move(self, event):
        if event.inaxes == self.ax:
            xdata = event.xdata
            ydata = event.ydata
            cur_xlim = self.ax.get_xlim()
            cur_ylim = self.ax.get_ylim()
            dx = xdata - self.x_press
            dy = ydata - self.y_press
            new_xlim = [cur_xlim[0] - dx, cur_xlim[1] - dx]
            new_ylim = [cur_ylim[0] - dy, cur_ylim[1] - dy]

            # intercept new plot parameters that are out of bound
            new_xlim, new_ylim = check_limits(
                self.default_xlim, self.default_ylim, new_xlim, new_ylim
            )

            if cur_xlim != tuple(new_xlim) or cur_ylim != tuple(new_ylim):
                self.ax.set_xlim(new_xlim)
                self.ax.set_ylim(new_ylim)

                self.canvas.draw_idle()

    def update_annot(self, event, artist):
        self.ax.annot.xy = (event.xdata, event.ydata)
        text = f'Data #{artist.data_id}'
        self.ax.annot.set_text(text)
        self.ax.annot.set_visible(True)
        self.ax.draw_artist(self.ax.annot)

    def hover(self, event):
        vis = self.ax.annot.get_visible()
        if event.inaxes == self.ax:
            ind = 0
            cont = None
            while (
                ind in range(len(self.artists))
                and not cont
            ):
                artist = self.artists[ind]
                cont, _ = artist.contains(event)
                if cont and artist is not self.ax.last_artist:
                    if self.ax.last_artist is not None:
                        self.canvas.restore_region(self.canvas.bg_cache)
                        self.ax.last_artist.set_path_effects(
                            [PathEffects.Normal()]
                        )
                        self.ax.last_artist = None
                    artist.set_path_effects(
                        [PathEffects.withStroke(
                            linewidth=7, foreground="c", alpha=0.4
                        )]
                    )
                    self.ax.last_artist = artist
                    self.ax.draw_artist(self.ax.last_artist)
                    self.update_annot(event, self.ax.last_artist)
                ind += 1

            if vis and not cont and self.ax.last_artist:
                self.canvas.restore_region(self.canvas.bg_cache)
                self.ax.last_artist.set_path_effects([PathEffects.Normal()])
                self.ax.last_artist = None
                self.ax.annot.set_visible(False)
        elif vis:
            self.canvas.restore_region(self.canvas.bg_cache)
            self.ax.last_artist.set_path_effects([PathEffects.Normal()])
            self.ax.last_artist = None
            self.ax.annot.set_visible(False)
        self.canvas.update()
        self.canvas.flush_events()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    test = Test()
    test.show()
    sys.exit(app.exec_())

ฉันไม่เข้าใจปัญหา เนื่องจากศิลปินที่อยู่นอกแนวแกนนั้นไม่ได้ดึงดูดพวกเขาพวกเขาจะไม่ชะลอตัวลงเลย
ImportanceOfBeingErnest

ดังนั้นคุณกำลังบอกว่ามีกิจวัตรประจำวันที่จะตรวจสอบว่าศิลปินคนใดบ้างที่สามารถมองเห็นได้ บางทีรูทีนนี้อาจมีค่าใช้จ่ายสูง เนื่องจากคุณสามารถเห็นความแตกต่างของประสิทธิภาพการทำงานได้อย่างง่ายดายหากคุณลองทำสิ่งต่อไปนี้เช่นกับศิลปิน WME 1,000 คนของฉันด้านบนซูมเข้าศิลปินเดี่ยวแล้วเลื่อนไปรอบ ๆ คุณจะสังเกตเห็นความล่าช้าอย่างมีนัยสำคัญ ทีนี้ทำแบบเดียวกัน แต่พล็อตเพียง 1 (หรือ 100) ศิลปินและคุณจะเห็นว่าแทบจะไม่ล่าช้า
mapf

คำถามคือคุณสามารถเขียนรูทีนที่มีประสิทธิภาพมากขึ้นได้หรือไม่? ในกรณีที่เรียบง่ายอาจจะ ดังนั้นคุณสามารถตรวจสอบศิลปินที่อยู่ในขอบเขตการดูและตั้งค่าอื่น ๆ ที่มองไม่เห็น หากการตรวจสอบเปรียบเทียบพิกัดกึ่งกลางของจุดนั้นจะเร็วขึ้น แต่นั่นจะทำให้คุณคลายจุดถ้าศูนย์กลางของมันอยู่ด้านนอก แต่น้อยกว่าครึ่งหนึ่งของจุดนั้นจะยังคงอยู่ในมุมมอง ที่ถูกกล่าวว่าปัญหาหลักที่นี่คือว่ามีศิลปิน 1,000 คนในแกน หากคุณใช้เพียงจุดเดียวในplotทุกจุดแทนปัญหาจะไม่เกิดขึ้น
ImportanceOfBeingErnest

ใช่จริงอย่างแน่นอน เป็นเพียงว่าหลักฐานของฉันผิด ฉันคิดว่าเหตุผลของการแสดงที่ไม่ดีคือศิลปินทุกคนมีความเป็นอิสระไม่ว่าพวกเขาจะเห็นหรือไม่ก็ตาม ดังนั้นฉันจึงคิดว่ารูทีนที่ชาญฉลาดที่ดึงดูดศิลปินที่จะได้เห็นเท่านั้นจะปรับปรุงการแสดง แต่ดูเหมือนว่ากิจวัตรดังกล่าวจะเกิดขึ้นแล้วดังนั้นฉันคิดว่ามีไม่มากที่สามารถทำได้ที่นี่ ฉันค่อนข้างมั่นใจว่าฉันจะไม่สามารถเขียนรูทีนที่มีประสิทธิภาพมากขึ้นได้อย่างน้อยในกรณีทั่วไป
mapf

ในกรณีของฉันฉันกำลังจัดการกับ linecollections (บวกภาพในพื้นหลัง) และตามที่คุณพูดไปแล้วแม้ว่ามันจะเป็นเพียงแค่จุดใน MWE ของฉันเพียงแค่ตรวจสอบว่าพิกัดอยู่ภายในแกนไม่เพียงพอ บางทีฉันควรปรับปรุง MWE ตามนั้นเพื่อให้ชัดเจนขึ้น
mapf

คำตอบ:


0

คุณสามารถค้นหาศิลปินคนไหนที่อยู่ในพื้นที่ปัจจุบันของแกนหากคุณมุ่งเน้นไปที่ข้อมูลที่ศิลปินกำลังวางแผน

ตัวอย่างเช่นถ้าคุณใส่ข้อมูลคะแนนของคุณ ( aและbอาร์เรย์) ในอาร์เรย์แบบนี้:

self.points = np.random.randint(0, 100, (1000, 2))

คุณสามารถรับรายการคะแนนภายในขีด จำกัด x และ y ปัจจุบัน:

xmin, xmax = self.ax.get_xlim()
ymin, ymax = self.ax.get_ylim()

p = self.points

indices_of_visible_points = (np.argwhere((p[:, 0] > xmin) & (p[:, 0] < xmax) & (p[:, 1] > ymin) &  (p[:, 1] < ymax))).flatten()

คุณสามารถใช้indices_of_visible_pointsเพื่อจัดทำดัชนีself.artistsรายการที่เกี่ยวข้องของคุณ


ขอบคุณสำหรับคำตอบ! น่าเสียดายที่นี่ใช้งานได้เฉพาะในกรณีที่ศิลปินเป็นจุดเดียว มันไม่สามารถใช้งานได้อีกต่อไปหากศิลปินเป็นสาย เช่นรูปภาพเส้นที่กำหนดโดยจุดสองจุดเท่านั้นที่จุดนั้นอยู่นอกขีด จำกัด ของแกนอย่างไรก็ตามเส้นที่เชื่อมต่อจุดนั้นตัดกันกรอบแกน บางทีฉันควรแก้ไข MWE ตามนั้นเพื่อให้ชัดเจนยิ่งขึ้น
mapf

ให้ฉันวิธีการที่จะเหมือนกันมุ่งเน้นไปที่ข้อมูล หากศิลปินเป็นเส้นคุณสามารถตรวจสอบจุดตัดกันด้วยสี่เหลี่ยมมุมมองเพิ่มเติมได้ หากคุณกำลังพล็อตเส้นโค้งคุณอาจกำลังสุ่มพวกมันในช่วงเวลาคงที่เพื่อลดพวกมันลงในส่วนของเส้น โดยวิธีการที่คุณสามารถให้ตัวอย่างที่สมจริงมากขึ้นของสิ่งที่คุณกำลังวางแผน?
Guglie

ฉันอัปเดตเป็น MWE
mapf
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.