Введение
Этот практикум представляет собой пошаговое руководство по созданию кросс-приложений с графическим интерфейсом, использующих обработку событий Matplotlib для взаимодействия с объектами на холсте. Мы создадим редактор многоугольников, который позволит вам перемещать, удалять и вставлять вершины.
Советы по работе с ВМ
После запуска ВМ перейдите в левый верхний угол и переключитесь на вкладку Notebook, чтобы приступить к практике с использованием Jupyter Notebook.
Иногда вам может потребоваться подождать несколько секунд, пока Jupyter Notebook полностью загрузится. Валидация операций не может быть автоматизирована из-за ограничений Jupyter Notebook.
Если вы столкнетесь с проблемами во время обучения, не стесняйтесь обращаться к Labby. Оставьте отзыв после занятия, и мы оперативно решим проблему для вас.
Импортируем необходимые библиотеки
Для редактора многоугольников нам нужно импортировать необходимые библиотеки.
import numpy as np
from matplotlib.artist import Artist
from matplotlib.lines import Line2D
Создаем функцию для вычисления расстояния
Нам нужно создать функцию, которая вычисляет расстояние между точкой и отрезком. Эта функция будет использоваться позже для определения, следует ли добавить новую вершину в многоугольник.
def dist_point_to_segment(p, s0, s1):
"""
Получить расстояние от точки *p* до отрезка (*s0*, *s1*), где
*p*, *s0*, *s1* - массивы ``[x, y]``.
"""
s01 = s1 - s0
s0p = p - s0
if (s01 == 0).all():
return np.hypot(*s0p)
## Проецируем на отрезок, не выходя за его концы.
p1 = s0 + np.clip((s0p @ s01) / (s01 @ s01), 0, 1) * s01
return np.hypot(*(p - p1))
Создаем класс PolygonInteractor
Нам нужно создать класс PolygonInteractor, который является основным классом для редактора многоугольников. Этот класс будет обрабатывать все взаимодействия с многоугольником, такие как перемещение, удаление и вставка вершин.
class PolygonInteractor:
"""
Редактор многоугольников.
Назначение клавиш
't' - переключать отображение маркеров вершин вкл/выкл. Когда маркеры вершин включены,
вы можете перемещать их, удалять
'd' - удалить вершину под точкой
'i' - вставить вершину в точке. Вы должны находиться в пределах эпсилон от
линии, соединяющей две существующие вершины
"""
showverts = True
epsilon = 5 ## максимальное расстояние в пикселях, при котором считается, что вершина была достигнута
def __init__(self, ax, poly):
if poly.figure is None:
raise RuntimeError('Вы должны сначала добавить многоугольник к фигуре '
'или холсту, прежде чем определить интерактора')
self.ax = ax
canvas = poly.figure.canvas
self.poly = poly
x, y = zip(*self.poly.xy)
self.line = Line2D(x, y,
marker='o', markerfacecolor='r',
animated=True)
self.ax.add_line(self.line)
self.cid = self.poly.add_callback(self.poly_changed)
self._ind = None ## активная вершина
canvas.mpl_connect('draw_event', self.on_draw)
canvas.mpl_connect('button_press_event', self.on_button_press)
canvas.mpl_connect('key_press_event', self.on_key_press)
canvas.mpl_connect('button_release_event', self.on_button_release)
canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
self.canvas = canvas
def on_draw(self, event):
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
self.ax.draw_artist(self.poly)
self.ax.draw_artist(self.line)
## здесь не нужно использовать blit, это будет срабатывать перед обновлением экрана
def poly_changed(self, poly):
"""Этот метод вызывается при каждом вызове объекта pathpatch."""
## копируем только свойства художника в линию (за исключением видимости)
vis = self.line.get_visible()
Artist.update_from(self.line, poly)
self.line.set_visible(vis) ## не используем состояние видимости многоугольника
def get_ind_under_point(self, event):
"""
Возвращает индекс точки, ближайшей к позиции события, или *None*,
если нет точки в пределах ``self.epsilon`` от позиции события.
"""
## координаты отображения
xy = np.asarray(self.poly.xy)
xyt = self.poly.get_transform().transform(xy)
xt, yt = xyt[:, 0], xyt[:, 1]
d = np.hypot(xt - event.x, yt - event.y)
indseq, = np.nonzero(d == d.min())
ind = indseq[0]
if d[ind] >= self.epsilon:
ind = None
return ind
def on_button_press(self, event):
"""Обработчик нажатия кнопки мыши."""
if not self.showverts:
return
if event.inaxes is None:
return
if event.button!= 1:
return
self._ind = self.get_ind_under_point(event)
def on_button_release(self, event):
"""Обработчик отпускания кнопки мыши."""
if not self.showverts:
return
if event.button!= 1:
return
self._ind = None
def on_key_press(self, event):
"""Обработчик нажатия клавиши."""
if not event.inaxes:
return
if event.key == 't':
self.showverts = not self.showverts
self.line.set_visible(self.showverts)
if not self.showverts:
self._ind = None
elif event.key == 'd':
ind = self.get_ind_under_point(event)
if ind is not None:
self.poly.xy = np.delete(self.poly.xy,
ind, axis=0)
self.line.set_data(zip(*self.poly.xy))
elif event.key == 'i':
xys = self.poly.get_transform().transform(self.poly.xy)
p = event.x, event.y ## координаты отображения
for i in range(len(xys) - 1):
s0 = xys[i]
s1 = xys[i + 1]
d = dist_point_to_segment(p, s0, s1)
if d <= self.epsilon:
self.poly.xy = np.insert(
self.poly.xy, i+1,
[event.xdata, event.ydata],
axis=0)
self.line.set_data(zip(*self.poly.xy))
break
if self.line.stale:
self.canvas.draw_idle()
def on_mouse_move(self, event):
"""Обработчик движения мыши."""
if not self.showverts:
return
if self._ind is None:
return
if event.inaxes is None:
return
if event.button!= 1:
return
x, y = event.xdata, event.ydata
self.poly.xy[self._ind] = x, y
if self._ind == 0:
self.poly.xy[-1] = x, y
elif self._ind == len(self.poly.xy) - 1:
self.poly.xy[0] = x, y
self.line.set_data(zip(*self.poly.xy))
self.canvas.restore_region(self.background)
self.ax.draw_artist(self.poly)
self.ax.draw_artist(self.line)
self.canvas.blit(self.ax.bbox)
Создаем многоугольник
Нам нужно создать многоугольник, который мы будем редактировать с использованием класса Polygon.
theta = np.arange(0, 2*np.pi, 0.1)
r = 1.5
xs = r * np.cos(theta)
ys = r * np.sin(theta)
poly = Polygon(np.column_stack([xs, ys]), animated=True)
Создаем график
Нам нужно создать график и добавить в него многоугольник.
fig, ax = plt.subplots()
ax.add_patch(poly)
p = PolygonInteractor(ax, poly)
ax.set_title('Click and drag a point to move it')
ax.set_xlim((-2, 2))
ax.set_ylim((-2, 2))
plt.show()
Резюме
В этом практическом занятии мы научились создавать редактор многоугольников с использованием обработки событий Matplotlib. Мы создали класс для обработки всех взаимодействий с многоугольником и использовали класс Polygon для создания самого многоугольника. Затем мы создали график и добавили в него многоугольник. С этими знаниями вы можете создавать собственные интерактивные приложения с использованием Matplotlib.