1"""
2===========
3Path Editor
4===========
5
6Sharing events across GUIs.
7
8This example demonstrates a cross-GUI application using Matplotlib event
9handling to interact with and modify objects on the canvas.
10"""
11
12import numpy as np
13from matplotlib.backend_bases import MouseButton
14from matplotlib.path import Path
15from matplotlib.patches import PathPatch
16import matplotlib.pyplot as plt
17
18
19fig, ax = plt.subplots()
20
21pathdata = [
22    (Path.MOVETO, (1.58, -2.57)),
23    (Path.CURVE4, (0.35, -1.1)),
24    (Path.CURVE4, (-1.75, 2.0)),
25    (Path.CURVE4, (0.375, 2.0)),
26    (Path.LINETO, (0.85, 1.15)),
27    (Path.CURVE4, (2.2, 3.2)),
28    (Path.CURVE4, (3, 0.05)),
29    (Path.CURVE4, (2.0, -0.5)),
30    (Path.CLOSEPOLY, (1.58, -2.57)),
31]
32
33codes, verts = zip(*pathdata)
34path = Path(verts, codes)
35patch = PathPatch(
36    path, facecolor='green', edgecolor='yellow', alpha=0.5)
37ax.add_patch(patch)
38
39
40class PathInteractor:
41    """
42    An path editor.
43
44    Press 't' to toggle vertex markers on and off.  When vertex markers are on,
45    they can be dragged with the mouse.
46    """
47
48    showverts = True
49    epsilon = 5  # max pixel distance to count as a vertex hit
50
51    def __init__(self, pathpatch):
52
53        self.ax = pathpatch.axes
54        canvas = self.ax.figure.canvas
55        self.pathpatch = pathpatch
56        self.pathpatch.set_animated(True)
57
58        x, y = zip(*self.pathpatch.get_path().vertices)
59
60        self.line, = ax.plot(
61            x, y, marker='o', markerfacecolor='r', animated=True)
62
63        self._ind = None  # the active vertex
64
65        canvas.mpl_connect('draw_event', self.on_draw)
66        canvas.mpl_connect('button_press_event', self.on_button_press)
67        canvas.mpl_connect('key_press_event', self.on_key_press)
68        canvas.mpl_connect('button_release_event', self.on_button_release)
69        canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
70        self.canvas = canvas
71
72    def get_ind_under_point(self, event):
73        """
74        Return the index of the point closest to the event position or *None*
75        if no point is within ``self.epsilon`` to the event position.
76        """
77        # display coords
78        xy = np.asarray(self.pathpatch.get_path().vertices)
79        xyt = self.pathpatch.get_transform().transform(xy)
80        xt, yt = xyt[:, 0], xyt[:, 1]
81        d = np.sqrt((xt - event.x)**2 + (yt - event.y)**2)
82        ind = d.argmin()
83
84        if d[ind] >= self.epsilon:
85            ind = None
86
87        return ind
88
89    def on_draw(self, event):
90        """Callback for draws."""
91        self.background = self.canvas.copy_from_bbox(self.ax.bbox)
92        self.ax.draw_artist(self.pathpatch)
93        self.ax.draw_artist(self.line)
94        self.canvas.blit(self.ax.bbox)
95
96    def on_button_press(self, event):
97        """Callback for mouse button presses."""
98        if (event.inaxes is None
99                or event.button != MouseButton.LEFT
100                or not self.showverts):
101            return
102        self._ind = self.get_ind_under_point(event)
103
104    def on_button_release(self, event):
105        """Callback for mouse button releases."""
106        if (event.button != MouseButton.LEFT
107                or not self.showverts):
108            return
109        self._ind = None
110
111    def on_key_press(self, event):
112        """Callback for key presses."""
113        if not event.inaxes:
114            return
115        if event.key == 't':
116            self.showverts = not self.showverts
117            self.line.set_visible(self.showverts)
118            if not self.showverts:
119                self._ind = None
120        self.canvas.draw()
121
122    def on_mouse_move(self, event):
123        """Callback for mouse movements."""
124        if (self._ind is None
125                or event.inaxes is None
126                or event.button != MouseButton.LEFT
127                or not self.showverts):
128            return
129
130        vertices = self.pathpatch.get_path().vertices
131
132        vertices[self._ind] = event.xdata, event.ydata
133        self.line.set_data(zip(*vertices))
134
135        self.canvas.restore_region(self.background)
136        self.ax.draw_artist(self.pathpatch)
137        self.ax.draw_artist(self.line)
138        self.canvas.blit(self.ax.bbox)
139
140
141interactor = PathInteractor(patch)
142ax.set_title('drag vertices to update path')
143ax.set_xlim(-3, 4)
144ax.set_ylim(-3, 4)
145
146plt.show()
147