python - matplotlib 中有没有办法检查当前显示的轴区域中的艺术家?
问题描述
我有一个带有交互式图形的程序,偶尔会画出许多艺术家。在此图中,您还可以使用鼠标进行缩放和平移。但是,缩放平移期间的性能不是很好,因为每个艺术家总是被重绘。有没有办法检查当前显示区域中有哪些艺术家并只重绘那些?(在下面的示例中,性能仍然比较好,但可以通过使用更多或更复杂的艺术家来任意变差)
我对这个方法有一个类似的性能问题hover
,每当它被调用时,它都会canvas.draw()
在最后运行。但是正如您所看到的,我通过使用缓存和恢复轴的背景(基于this)找到了一个巧妙的解决方法。这显着改善了性能,现在即使有许多艺术家,它也运行得非常流畅。也许有一种类似的方法可以做到这一点,但对于pan
andzoom
方法?
很抱歉,代码示例很长,其中大部分与问题没有直接关系,但对于突出问题的工作示例是必要的。
编辑
我将 MWE 更新为更能代表我的实际代码的内容。
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_())
解决方案
如果您专注于艺术家正在绘制的数据,您可以找到哪些艺术家在坐标轴的当前区域中。
例如,如果您将点数据(a
和b
数组)放在一个像这样的 numpy 数组中:
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
列表
推荐阅读
- java - 使用 findById 获取的实体的所有字段都设置为 NULL
- android - Mockito 为“verify()”抛出 InvalidUseOfMatchersException
- sql - Postgres | V9.4 | 从json中提取值
- android - 如果切换过滤器开关,如何取消选中微调器中的选中项目?
- python - 使用 Python 循环和维恩图来决定学习哪种语言
- angular - 如何以角度验证特定的ngModel
- snowflake-cloud-data-platform - Where are snowflake tables stored?
- python - 在python中的某个变量中从curl命令中获取数据
- concourse - 如何在 Concourse 任务运行配置中组合一个终端命令
- javascript - 如何安装 node_modules?