-
Notifications
You must be signed in to change notification settings - Fork 11
/
draggable_plot.py
117 lines (96 loc) · 3.56 KB
/
draggable_plot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# -*- coding: utf-8 -*-
import math
import matplotlib.pyplot as plt
from matplotlib.backend_bases import MouseEvent
class DraggablePlotExample(object):
u""" An example of plot with draggable markers """
def __init__(self):
self._figure, self._axes, self._line = None, None, None
self._dragging_point = None
self._points = {}
self._init_plot()
def _init_plot(self):
self._figure = plt.figure("Example plot")
axes = plt.subplot(1, 1, 1)
axes.set_xlim(0, 100)
axes.set_ylim(0, 100)
axes.grid(which="both")
self._axes = axes
self._figure.canvas.mpl_connect('button_press_event', self._on_click)
self._figure.canvas.mpl_connect('button_release_event', self._on_release)
self._figure.canvas.mpl_connect('motion_notify_event', self._on_motion)
plt.show()
def _update_plot(self):
if not self._points:
self._line.set_data([], [])
else:
x, y = zip(*sorted(self._points.items()))
# Add new plot
if not self._line:
self._line, = self._axes.plot(x, y, "b", marker="o", markersize=10)
# Update current plot
else:
self._line.set_data(x, y)
self._figure.canvas.draw()
def _add_point(self, x, y=None):
if isinstance(x, MouseEvent):
x, y = int(x.xdata), int(x.ydata)
self._points[x] = y
return x, y
def _remove_point(self, x, _):
if x in self._points:
self._points.pop(x)
def _find_neighbor_point(self, event):
u""" Find point around mouse position
:rtype: ((int, int)|None)
:return: (x, y) if there are any point around mouse else None
"""
distance_threshold = 3.0
nearest_point = None
min_distance = math.sqrt(2 * (100 ** 2))
for x, y in self._points.items():
distance = math.hypot(event.xdata - x, event.ydata - y)
if distance < min_distance:
min_distance = distance
nearest_point = (x, y)
if min_distance < distance_threshold:
return nearest_point
return None
def _on_click(self, event):
u""" callback method for mouse click event
:type event: MouseEvent
"""
# left click
if event.button == 1 and event.inaxes in [self._axes]:
point = self._find_neighbor_point(event)
if point:
self._dragging_point = point
else:
self._add_point(event)
self._update_plot()
# right click
elif event.button == 3 and event.inaxes in [self._axes]:
point = self._find_neighbor_point(event)
if point:
self._remove_point(*point)
self._update_plot()
def _on_release(self, event):
u""" callback method for mouse release event
:type event: MouseEvent
"""
if event.button == 1 and event.inaxes in [self._axes] and self._dragging_point:
self._dragging_point = None
self._update_plot()
def _on_motion(self, event):
u""" callback method for mouse motion event
:type event: MouseEvent
"""
if not self._dragging_point:
return
if event.xdata is None or event.ydata is None:
return
self._remove_point(*self._dragging_point)
self._dragging_point = self._add_point(event)
self._update_plot()
if __name__ == "__main__":
plot = DraggablePlotExample()