Matplotlib'de eksenlerin şu anda görüntülenen alanında hangi sanatçıların bulunduğunu kontrol etmenin bir yolu var mı?


9

Çok sayıda sanatçının çizildiği interaktif bir figüre sahip bir programım var. Bu şekilde, fareyi kullanarak da yakınlaştırabilir ve kaydırabilirsiniz. Ancak, kaydırma sırasındaki performans çok iyi değildir çünkü her sanatçı her zaman yeniden çizilir. Şu anda görüntülenen alanda hangi sanatçıların bulunduğunu kontrol etmenin ve sadece bunları yeniden çizmenin bir yolu var mı? (Aşağıdaki örnekte perfomace hala nispeten iyidir, ancak daha fazla veya daha fazla karmaşık sanatçı kullanılarak keyfi olarak daha kötü hale getirilebilir)

Ne hoverzaman çağrılırsa canvas.draw()sonunda koştu yöntemi ile benzer bir performans sorunu vardı . Gördüğünüz gibi Ama önbelleğe yararlanarak ve (dayalı eksen arka planını geri yükleyerek bunun için düzgün bir geçici çözüm bulundu bu ). Bu, performansı önemli ölçüde geliştirdi ve şimdi birçok sanatçıda bile çok düzgün çalışıyor. Belki de bunu yapmanın benzer bir yolu var ama panve zoomyöntemi için?

Uzun kod örneği için özür dileriz, çoğu soru ile doğrudan ilgili değil, ancak çalışan bir örneğin sorunu vurgulaması için gerekli.

DÜZENLE

MWE'yi gerçek kodumu daha iyi temsil eden bir şeye güncelledim.

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

Sorunu anlamıyorum. Eksenlerin dışındaki sanatçılar zaten çizilmediğinden, hiçbir şeyi yavaşlatmazlar.
ImportanceOfBeingErnest

Yani sanatçılardan hangilerinin görülebildiğini kontrol eden bir rutin olduğunu söylüyorsunuz, böylece sadece görünür olanlar gerçekte çiziliyor mu? Belki de bu rutin hesapsal olarak çok pahalı olan şeydir? Aşağıdakileri denerseniz performansta kolayca bir fark görebilirsiniz, örneğin: yukarıdaki 1000 sanatçım WME ile tek bir sanatçıyı yakınlaştırın ve yatay kaydırın. Önemli bir gecikme göreceksiniz. Şimdi aynısını yapın ama sadece 1 (hatta 100) sanatçıyı planlayın ve neredeyse hiç gecikme olmadığını göreceksiniz.
mapf

Soru şu ki, daha verimli bir rutin yazabiliyor musunuz? Basit bir durumda, belki. Böylece hangi sanatçıların görünüm sınırları dahilinde olduğunu kontrol edebilir ve diğer tüm görünmezleri ayarlayabilirsiniz. Eğer kontrol noktaların merkez koordinatlarını karşılaştırırsa, bu daha hızlıdır. Ama bu sadece merkezi dışarıdaysa, ancak yarısından biraz daha azı görünümün içinde kalırsa noktayı kaybedersiniz. Bununla birlikte, asıl sorun eksenlerde 1000 sanatçının olması. Bunun yerine, plottüm noktaları içeren tek bir tek kullandıysanız sorun oluşmaz.
ImportanceOfBeingErnest

Evet kesinlikle doğru. Sadece benim öncülüm yanlıştı. Kötü performansın sebebinin, tüm sanatçıların görülüp görülmeyeceklerinden bağımsız olarak çizilmeleri olduğunu düşündüm. Bu yüzden sadece görülecek sanatçıları çeken akıllı bir rutinin performansı artıracağını düşündüm, ancak görünüşe göre böyle bir rutin zaten var, bu yüzden burada yapılabilecek çok şey yok. En azından genel bir vaka için daha verimli bir rutin yazamayacağımdan eminim.
mapf

Ancak benim durumumda, aslında çizgi toplama (artı arka planda bir görüntü) ile uğraşıyorum ve daha önce de söylediğin gibi, MWE'mde olduğu gibi noktalar olsa bile, sadece koordinatların eksenlerin içinde olup olmadığını kontrol etmek yeterli değil. Belki daha açık hale getirmek için MWE'yi buna göre güncellemeliyim.
mapf

Yanıtlar:


0

Sanatçıların çizdiği verilere odaklanırsanız, eksenlerin mevcut alanında hangi sanatçıların bulunduğunu bulabilirsiniz.

Örneğin, puan verilerinizi ( ave bdizileri) aşağıdaki gibi bir numpy dizisine koyarsanız :

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

geçerli x ve y sınırları içindeki noktaların listesini alabilirsiniz:

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_pointsilgili self.artistslistenizi dizine eklemek için kullanabilirsiniz


Cevabınız için teşekkür ederim! Ne yazık ki, bu sadece sanatçıların tek puan olması durumunda işe yarar. Sanatçıların çizgi olması artık işe yaramıyor. Örneğin, noktaların eksen sınırlarının dışında kaldığı sadece iki nokta ile tanımlanan bir çizgi, ancak noktaları birleştiren çizgi eksen çerçevesini kesiştirir. Belki de MWE'yi buna göre düzenlemeliyim, bu yüzden daha açık.
mapf

Bana göre yaklaşım aynı, verilere odaklanın . Sanatçılar çizgi ise, ek olarak görünüm dikdörtgeni ile kesişimi kontrol edebilirsiniz. Eğrileri çiziyorsanız, muhtemelen bunları sabit aralıklarla örneklersiniz ve bunları çizgi segmentlerine indirirsiniz. Bu arada, çizdiğiniz şeyden daha gerçekçi bir örnek verebilir misiniz?
Guglie

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.