问题描述
我需要在FigureCanvas中有2个可拖动的点.但是我有一个补充约束:两个点必须用一条线链接.当然,当我拖动一个点时(否则这不会很有趣),必须动态绘制线条,并且仍将其链接到两个点.
I need to have 2 draggable points in a figureCanvas. But I have a supplementary constraint: the 2 points must be linked by a line.When I drag a point, of course (it wouldn't be funny otherwise), the line must be dynamically drawn, and still linked to the 2 points.
我设法创建了两个可拖动的点,并带有以下主题: Matplotlib以交互方式拖动重叠点
I managed to create the 2 draggable points, with this topic:Matplotlib drag overlapping points interactively
我修改了一些代码以通过FigureCanvas的子类使用它(以后将图形包含在PyQt应用程序中):
I modified a bit the code to use it trough a subclass of FigureCanvas (to later include the graph in a PyQt application):
import matplotlib.pyplot as plt
import matplotlib.patches as patches
class DraggablePoint:
# https://stackoverflow.com/questions/21654008/matplotlib-drag-overlapping-points-interactively
lock = None # only one can be animated at a time
def __init__(self, parent, x=0.1, y=0.1):
self.parent = parent
self.point = patches.Ellipse((x, y), 0.01, 0.03, fc='r', alpha=0.5)
self.x = x
self.y = y
parent.fig.axes[0].add_patch(self.point)
self.press = None
self.background = None
self.connect()
def connect(self):
'connect to all the events we need'
self.cidpress = self.point.figure.canvas.mpl_connect('button_press_event', self.on_press)
self.cidrelease = self.point.figure.canvas.mpl_connect('button_release_event', self.on_release)
self.cidmotion = self.point.figure.canvas.mpl_connect('motion_notify_event', self.on_motion)
def on_press(self, event):
if event.inaxes != self.point.axes: return
if DraggablePoint.lock is not None: return
contains, attrd = self.point.contains(event)
if not contains: return
self.press = (self.point.center), event.xdata, event.ydata
DraggablePoint.lock = self
# draw everything but the selected rectangle and store the pixel buffer
canvas = self.point.figure.canvas
axes = self.point.axes
self.point.set_animated(True)
canvas.draw()
self.background = canvas.copy_from_bbox(self.point.axes.bbox)
# now redraw just the rectangle
axes.draw_artist(self.point)
# and blit just the redrawn area
canvas.blit(axes.bbox)
def on_motion(self, event):
if DraggablePoint.lock is not self:
return
if event.inaxes != self.point.axes: return
self.point.center, xpress, ypress = self.press
dx = event.xdata - xpress
dy = event.ydata - ypress
self.point.center = (self.point.center[0]+dx, self.point.center[1]+dy)
canvas = self.point.figure.canvas
axes = self.point.axes
# restore the background region
canvas.restore_region(self.background)
# redraw just the current rectangle
axes.draw_artist(self.point)
# blit just the redrawn area
canvas.blit(axes.bbox)
def on_release(self, event):
'on release we reset the press data'
if DraggablePoint.lock is not self:
return
self.press = None
DraggablePoint.lock = None
# turn off the rect animation property and reset the background
self.point.set_animated(False)
self.background = None
# redraw the full figure
self.point.figure.canvas.draw()
self.x = self.point.center[0]
self.y = self.point.center[1]
def disconnect(self):
'disconnect all the stored connection ids'
self.point.figure.canvas.mpl_disconnect(self.cidpress)
self.point.figure.canvas.mpl_disconnect(self.cidrelease)
self.point.figure.canvas.mpl_disconnect(self.cidmotion)
将来的图上只有2个点,我可以从DraggablePoint类的self.parent中访问另一个点.
There will only be 2 points on the future graph, and I can access the other point from the class DraggablePoint trough self.parent.
我认为我需要在on_motion函数的2点之间画一条线.但是我尝试并没有发现任何东西.您对实现该目标有想法吗?
I think I need to draw a line between the 2 points, in the function on_motion. But I tried and found nothing.Do you have an idea about how to achieve that ?
推荐答案
好,我终于找到了解决方案.我在这里将其发布给可能需要它的人.此代码基本上允许通过一条线链接2个可拖动点.如果您移动这些点之一,则直线将跟随.在科学应用中建立基线非常有用.
Ok I finally found the solution. I post it here for those who might need it. This code basically allow to have 2 draggable points linked by a line. If you move one of the points, the line follows. Very useful to make a baseline in scientific applications.
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.lines import Line2D
class DraggablePoint:
# http://stackoverflow.com/questions/21654008/matplotlib-drag-overlapping-points-interactively
lock = None # only one can be animated at a time
def __init__(self, parent, x=0.1, y=0.1, size=0.1):
self.parent = parent
self.point = patches.Ellipse((x, y), size, size * 3, fc='r', alpha=0.5, edgecolor='r')
self.x = x
self.y = y
parent.fig.axes[0].add_patch(self.point)
self.press = None
self.background = None
self.connect()
if self.parent.list_points:
line_x = [self.parent.list_points[0].x, self.x]
line_y = [self.parent.list_points[0].y, self.y]
self.line = Line2D(line_x, line_y, color='r', alpha=0.5)
parent.fig.axes[0].add_line(self.line)
def connect(self):
'connect to all the events we need'
self.cidpress = self.point.figure.canvas.mpl_connect('button_press_event', self.on_press)
self.cidrelease = self.point.figure.canvas.mpl_connect('button_release_event', self.on_release)
self.cidmotion = self.point.figure.canvas.mpl_connect('motion_notify_event', self.on_motion)
def on_press(self, event):
if event.inaxes != self.point.axes: return
if DraggablePoint.lock is not None: return
contains, attrd = self.point.contains(event)
if not contains: return
self.press = (self.point.center), event.xdata, event.ydata
DraggablePoint.lock = self
# draw everything but the selected rectangle and store the pixel buffer
canvas = self.point.figure.canvas
axes = self.point.axes
self.point.set_animated(True)
if self == self.parent.list_points[1]:
self.line.set_animated(True)
else:
self.parent.list_points[1].line.set_animated(True)
canvas.draw()
self.background = canvas.copy_from_bbox(self.point.axes.bbox)
# now redraw just the rectangle
axes.draw_artist(self.point)
# and blit just the redrawn area
canvas.blit(axes.bbox)
def on_motion(self, event):
if DraggablePoint.lock is not self:
return
if event.inaxes != self.point.axes: return
self.point.center, xpress, ypress = self.press
dx = event.xdata - xpress
dy = event.ydata - ypress
self.point.center = (self.point.center[0]+dx, self.point.center[1]+dy)
canvas = self.point.figure.canvas
axes = self.point.axes
# restore the background region
canvas.restore_region(self.background)
# redraw just the current rectangle
axes.draw_artist(self.point)
if self == self.parent.list_points[1]:
axes.draw_artist(self.line)
else:
self.parent.list_points[1].line.set_animated(True)
axes.draw_artist(self.parent.list_points[1].line)
self.x = self.point.center[0]
self.y = self.point.center[1]
if self == self.parent.list_points[1]:
line_x = [self.parent.list_points[0].x, self.x]
line_y = [self.parent.list_points[0].y, self.y]
self.line.set_data(line_x, line_y)
else:
line_x = [self.x, self.parent.list_points[1].x]
line_y = [self.y, self.parent.list_points[1].y]
self.parent.list_points[1].line.set_data(line_x, line_y)
# blit just the redrawn area
canvas.blit(axes.bbox)
def on_release(self, event):
'on release we reset the press data'
if DraggablePoint.lock is not self:
return
self.press = None
DraggablePoint.lock = None
# turn off the rect animation property and reset the background
self.point.set_animated(False)
if self == self.parent.list_points[1]:
self.line.set_animated(False)
else:
self.parent.list_points[1].line.set_animated(False)
self.background = None
# redraw the full figure
self.point.figure.canvas.draw()
self.x = self.point.center[0]
self.y = self.point.center[1]
def disconnect(self):
'disconnect all the stored connection ids'
self.point.figure.canvas.mpl_disconnect(self.cidpress)
self.point.figure.canvas.mpl_disconnect(self.cidrelease)
self.point.figure.canvas.mpl_disconnect(self.cidmotion)
更新:
如何通过PyQt5使用DraggablePoint类:
UPDATE:
How to use the DraggablePoint class, with PyQt5:
#!/usr/bin/python
# -*-coding:Utf-8 -*
import sys
import matplotlib
matplotlib.use("Qt5Agg")
from PyQt5 import QtWidgets, QtGui
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
# Personnal modules
from drag import DraggablePoint
class MyGraph(FigureCanvas):
"""A canvas that updates itself every second with a new plot."""
def __init__(self, parent=None, width=5, height=4, dpi=100):
self.fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = self.fig.add_subplot(111)
self.axes.grid(True)
FigureCanvas.__init__(self, self.fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
# To store the 2 draggable points
self.list_points = []
self.show()
self.plotDraggablePoints([0.1, 0.1], [0.2, 0.2], [0.1, 0.1])
def plotDraggablePoints(self, xy1, xy2, size=None):
"""Plot and define the 2 draggable points of the baseline"""
# del(self.list_points[:])
self.list_points.append(DraggablePoint(self, xy1[0], xy1[1], size))
self.list_points.append(DraggablePoint(self, xy2[0], xy2[1], size))
self.updateFigure()
def clearFigure(self):
"""Clear the graph"""
self.axes.clear()
self.axes.grid(True)
del(self.list_points[:])
self.updateFigure()
def updateFigure(self):
"""Update the graph. Necessary, to call after each plot"""
self.draw()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = MyGraph()
sys.exit(app.exec_())
这篇关于带有可拖动点的可拖动线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!