Matplotlib 简明教程

Matplotlib - Path Editor

Path Editor 是一款应用程序,允许用户在图形化环境中交互式编辑和操作路径。在 Matplotlib 中,路径编辑器通常指图形化用户界面 (GUI) 应用程序,它简化了使用 Matplotlib 的路径类定义路径的编辑工作。

A Path Editor is an application that allows users to interactively edit and manipulate paths in a graphical environment. In the context of Matplotlib, a Path Editor typically refers to a graphical user interface (GUI) application that facilitates the editing of paths defined using Matplotlib’s Path class.

在深入探究路径编辑器之前,理解 Matplotlib 路径的基础知识至关重要。 Path 是 Matplotlib 中的基本对象,它包含 matplotlib.patches 模块中的各种元素,如线段、曲线和形状。路径提供了一种用综合的轮廓定义复杂轮廓的方法,方法是指定一系列命令,例如 moveto、lineto 和 curveto。

Before diving into the Path Editor, it’s essential to understand the basics of Matplotlib paths. A Path is a fundamental object in Matplotlib that contains various elements like line segments, curves, and shapes within the matplotlib.patches module. Paths provide a versatile way to define complex outlines by specifying a series of commands such as moveto, lineto, and curveto.

Matplotlib 提供一个功能强大的 Path class ,它是可视化中创建和操纵路径的基础。

Matplotlib offers a powerful Path class that serves as the foundation for creating and manipulating paths in visualizations.

Step by Step implementation

在本教程中,我们将探讨 Matplotlib 路径编辑器,这是一个 cross-GUI application ,它使用 Matplotlib 的事件处理功能以交互式方式编辑和修改画布中的路径。

In this tutorial, we’ll explore the Matplotlib Path Editor, a cross-GUI application that uses Matplotlib’s event handling capabilities to edit and modify paths on the canvas interactively.

Creating the PathInteractor class

创建一个路径编辑器 (PathInteractor) 类来处理与已定义路径的交互。这个类包括用于切换顶点标记(使用 “t” 键)、拖动顶点以及响应鼠标和键盘事件的方法。

Create a path editor (PathInteractor) class to handle the interaction with the defined path. This class includes methods to toggle vertex markers (using the 't' key), drag vertices, and respond to mouse and key events.

import matplotlib.pyplot as plt
import numpy as np

from matplotlib.backend_bases import MouseButton
from matplotlib.patches import PathPatch
from matplotlib.path import Path

class PathInteractor:

   showverts = True
   # max pixel distance to count as a vertex hit
   epsilon = 5

   def __init__(self, pathpatch):
      # Initialization and event connections
      self.ax = pathpatch.axes
      canvas = self.ax.figure.canvas
      self.pathpatch = pathpatch
      self.pathpatch.set_animated(True)

      x, y = zip(*self.pathpatch.get_path().vertices)

      self.line, = ax.plot(
         x, y, marker='o', markerfacecolor='r', animated=True)

      self._ind = None  # the active vertex

      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 get_ind_under_point(self, event):
      # Return the index of the point closest to the event position or *None*
      xy = self.pathpatch.get_path().vertices
      xyt = self.pathpatch.get_transform().transform(xy)  # to display coords
      xt, yt = xyt[:, 0], xyt[:, 1]
      d = np.sqrt((xt - event.x)**2 + (yt - event.y)**2)
      ind = d.argmin()
      return ind if d[ind] < self.epsilon else None

   def on_draw(self, event):
      # Callback for draws.
      self.background = self.canvas.copy_from_bbox(self.ax.bbox)
      self.ax.draw_artist(self.pathpatch)
      self.ax.draw_artist(self.line)
      self.canvas.blit(self.ax.bbox)

   def on_button_press(self, event):
      # Callback for mouse button presses
      if (event.inaxes is None
            or event.button != MouseButton.LEFT
            or not self.showverts):
         return
     self._ind = self.get_ind_under_point(event)

   def on_button_release(self, event):
      # Callback for mouse button releases
      if (event.button != MouseButton.LEFT
            or not self.showverts):
         return
      self._ind = None

   def on_key_press(self, event):
      # Callback for key presses
      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
      self.canvas.draw()

   def on_mouse_move(self, event):
      # Callback for mouse movements
      if (self._ind is None
         or event.inaxes is None
         or event.button != MouseButton.LEFT
         or not self.showverts):
      return

      vertices = self.pathpatch.get_path().vertices

      vertices[self._ind] = event.xdata, event.ydata
      self.line.set_data(zip(*vertices))

      self.canvas.restore_region(self.background)
      self.ax.draw_artist(self.pathpatch)
      self.ax.draw_artist(self.line)
      self.canvas.blit(self.ax.bbox)

Event Handling and Canvas Interaction

PathInteractor 类将各种回调连接到画布事件,使用户能够与已定义的路径交互。这些交互包括按下并释放鼠标按钮、拖动顶点以及按键盘键来切换顶点标记。

The PathInteractor class connects various callbacks to canvas events, enabling users to interact with the defined path. These interactions include pressing and releasing mouse buttons, dragging vertices, and toggling vertex markers with key presses.

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)

Defining and Visualizing a Path

首先定义一个预定义路径,该路径由各种路径代码和顶点组成,使用 Matplotlib 路径类创建。然后使用 PathPatch 实例在画布上将该路径可视化,添加交互式组件到绘图中。

Start by defining a predefined path, consisting of various path codes and vertices, which is created using the Matplotlib Path class. This path is then visualized on the canvas using a PathPatch instance, adding an interactive component to the plot.

fig, ax = plt.subplots()

pathdata = [
   (Path.MOVETO, (1.58, -2.57)),
   (Path.CURVE4, (0.35, -1.1)),
   (Path.CURVE4, (-1.75, 2.0)),
   (Path.CURVE4, (0.375, 2.0)),
   (Path.LINETO, (0.85, 1.15)),
   (Path.CURVE4, (2.2, 3.2)),
   (Path.CURVE4, (3, 0.05)),
   (Path.CURVE4, (2.0, -0.5)),
   (Path.CLOSEPOLY, (1.58, -2.57)),
]

codes, verts = zip(*pathdata)
path = Path(verts, codes)
patch = PathPatch(
   path, facecolor='green', edgecolor='yellow', alpha=0.5)
ax.add_patch(patch)

Running the Path Editor

实例化 PathInteractor 类、设置绘图属性并显示绘图。用户现在可以交互拖动顶点、使用键 "t" 切换顶点标记并且观察实时更新。

Instantiate the PathInteractor class, set plot properties, and display the plot. Users can now interactively drag vertices, toggle vertex markers using the key "t", and observe real-time updates.

interactor = PathInteractor(patch)
ax.set_title('drag vertices to update path')
ax.set_xlim(-3, 4)
ax.set_ylim(-3, 4)

plt.show()

我们来看 Matplotlib 路径编辑器的完整示例。

Let’s see the complete example of the Matplotlib Path Editor.

import matplotlib.pyplot as plt
import numpy as np

from matplotlib.backend_bases import MouseButton
from matplotlib.patches import PathPatch
from matplotlib.path import Path


class PathInteractor:

   showverts = True
   # max pixel distance to count as a vertex hit
   epsilon = 5

   def __init__(self, pathpatch):
      # Initialization and event connections
      self.ax = pathpatch.axes
      canvas = self.ax.figure.canvas
      self.pathpatch = pathpatch
      self.pathpatch.set_animated(True)

      x, y = zip(*self.pathpatch.get_path().vertices)

      self.line, = ax.plot(
         x, y, marker='o', markerfacecolor='r', animated=True)

      self._ind = None  # the active vertex

      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 get_ind_under_point(self, event):
      # Return the index of the point closest to the event position or *None*
      xy = self.pathpatch.get_path().vertices
      xyt = self.pathpatch.get_transform().transform(xy)  # to display coords
      xt, yt = xyt[:, 0], xyt[:, 1]
      d = np.sqrt((xt - event.x)**2 + (yt - event.y)**2)
      ind = d.argmin()
         return ind if d[ind] < self.epsilon else None

   def on_draw(self, event):
      # Callback for draws.
      self.background = self.canvas.copy_from_bbox(self.ax.bbox)
      self.ax.draw_artist(self.pathpatch)
      self.ax.draw_artist(self.line)
      self.canvas.blit(self.ax.bbox)

   def on_button_press(self, event):
      # Callback for mouse button presses
      if (event.inaxes is None
         or event.button != MouseButton.LEFT
         or not self.showverts):
         return
      self._ind = self.get_ind_under_point(event)

   def on_button_release(self, event):
      # Callback for mouse button releases
      if (event.button != MouseButton.LEFT
         or not self.showverts):
         return
      self._ind = None

   def on_key_press(self, event):
      # Callback for key presses
      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
      self.canvas.draw()

   def on_mouse_move(self, event):
      # Callback for mouse movements
      if (self._ind is None
         or event.inaxes is None
         or event.button != MouseButton.LEFT
         or not self.showverts):
      return

      vertices = self.pathpatch.get_path().vertices

      vertices[self._ind] = event.xdata, event.ydata
      self.line.set_data(zip(*vertices))

      self.canvas.restore_region(self.background)
      self.ax.draw_artist(self.pathpatch)
      self.ax.draw_artist(self.line)
      self.canvas.blit(self.ax.bbox)

fig, ax = plt.subplots()

pathdata = [
   (Path.MOVETO, (1.58, -2.57)),
   (Path.CURVE4, (0.35, -1.1)),
   (Path.CURVE4, (-1.75, 2.0)),
   (Path.CURVE4, (0.375, 2.0)),
   (Path.LINETO, (0.85, 1.15)),
   (Path.CURVE4, (2.2, 3.2)),
   (Path.CURVE4, (3, 0.05)),
   (Path.CURVE4, (2.0, -0.5)),
   (Path.CLOSEPOLY, (1.58, -2.57)),
]

codes, verts = zip(*pathdata)
path = Path(verts, codes)
patch = PathPatch(
   path, facecolor='green', edgecolor='yellow', alpha=0.5)
ax.add_patch(patch)

interactor = PathInteractor(patch)
ax.set_title('drag vertices to update path')
ax.set_xlim(-3, 4)
ax.set_ylim(-3, 4)

plt.show()

在执行以上程序后,您将得到以下图表。在键盘上按 “t” 键。此操作将打开或关闭顶点标记的可见性。当顶点标记可见(在按 “t” 后)时,您可以使用鼠标拖动这些标记。观察拖动顶点如何影响路径的形状。

On executing the above program you will get the following figure. Press the "t" key on your keyboard. This action toggles the visibility of vertex markers on and off. When the vertex markers are visible (after pressing "t"), you can drag these markers with your mouse. Observe how dragging the vertices affects the shape of the path.

path editor

观看下面的视频以观察路径编辑器如何在此处工作。

Watch the video below to observe how the path editor works here.

path editor