I wrote a script heavily inspired from the draggable rectangle exercise here which lets me draw polygons, currently rectangles and triangles, which can be dragged with mouse.
Here is the code :
import numpy as np
import matplotlib.pyplot as plt
from abc import ABCMeta, abstractmethod
import logging
#
# Create and set logger
#
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# create formatter
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
class Polygon:
__metaclass__ = ABCMeta
_lock = None # only one can be animated at a time
@abstractmethod
def compute_edges(self):
raise NotImplementedError("Implemented in child classes")
def __init__(self, center, height, width, orientation, id_, type_,
**kwargs):
self.center = center
self.height = height
self.width = width
if not "radian" in orientation:
orientation["radian"] = orientation["degree"] / 180. * np.pi
self.orientation = orientation
self.polygon = plt.Polygon(self.compute_edges(), **kwargs)
self.press = None
self.background = None
self.id_ = id_
self.type_ = type_
def connect(self):
'connect to all the events we need'
self.cidpress = self.polygon.figure.canvas.mpl_connect(
'button_press_event', self.on_press)
self.cidrelease = self.polygon.figure.canvas.mpl_connect(
'button_release_event', self.on_release)
self.cidmotion = self.polygon.figure.canvas.mpl_connect(
'motion_notify_event', self.on_motion)
self.cidscroll = self.polygon.figure.canvas.mpl_connect(
'scroll_event', self.on_scroll)
def on_press(self, event):
'on button press we will see if the mouse is over us and store some data'
if event.inaxes != self.polygon.axes:
return
if Polygon._lock is not None:
return
contains, attrd = self.polygon.contains(event)
if not contains:
return
self.press = np.copy(self.polygon.xy), event.xdata, event.ydata
Polygon._lock = self
logger.debug("Pressed polygon (%s, %d) at (%f, %f)", self.type_,
self.id_, event.xdata, event.ydata)
# draw everything but the selected polygon and store the pixel buffer
canvas = self.polygon.figure.canvas
axes = self.polygon.axes
self.polygon.set_animated(True)
canvas.draw()
self.background = canvas.copy_from_bbox(self.polygon.axes.bbox)
# now redraw just the polygon
axes.draw_artist(self.polygon)
# and blit just the redrawn area
canvas.blit(axes.bbox)
def on_scroll(self, event):
if event.inaxes != self.polygon.axes:
return
if Polygon._lock is not None:
return
contains, attrd = self.polygon.contains(event)
if not contains:
return
def on_motion(self, event):
'on motion we will move the rect if the mouse is over us'
if Polygon._lock is not self:
return
if event.inaxes != self.polygon.axes:
return
xy, xpress, ypress = self.press
dx = event.xdata - xpress
dy = event.ydata - ypress
self.polygon.xy[:, 0] = xy[:, 0] + dx
self.polygon.xy[:, 1] = xy[:, 1] + dy
self.center = self.polygon.xy.mean(axis=0)
logger.debug("Move polygon (%s, %d) to (%f, %f)", self.type_, self.id_,
self.center[0], self.center[1])
canvas = self.polygon.figure.canvas
axes = self.polygon.axes
# restore the background region
canvas.restore_region(self.background)
# redraw just the current polygon
axes.draw_artist(self.polygon)
# blit just the redrawn area
canvas.blit(axes.bbox)
#plt.gcf().savefig("test.png")
def on_release(self, event):
'on release we reset the press data'
if Polygon._lock is not self:
return
self.press = None
Polygon._lock = None
logger.debug("Release polygon (%s, %d) at (%f, %f)", self.type_,
self.id_, self.center[0], self.center[1])
# turn off the polygon animation property and reset the background
self.polygon.set_animated(False)
self.background = None
# redraw the full figure
self.polygon.figure.canvas.draw()
def disconnect(self):
'disconnect all the stored connection ids'
self.polygon.figure.canvas.mpl_disconnect(self.cidpress)
self.polygon.figure.canvas.mpl_disconnect(self.cidrelease)
self.polygon.figure.canvas.mpl_disconnect(self.cidmotion)
self.polygon.figure.canvas.mpl_disconnect(self.cidscroll)
class Triangle(Polygon):
_id = 0
def compute_edges(self):
self.orientation["cos"] = np.cos(self.orientation["radian"])
self.orientation["sin"] = np.sin(self.orientation["radian"])
return np.array(
[[
self.center[0] + 2 * self.height * self.orientation["cos"] / 3,
self.center[1] + 2 * self.height * self.orientation["sin"] / 3
],
[
self.center[0] - self.width * 0.5 * self.orientation["sin"] -
self.height / 3. * self.orientation["cos"],
self.center[1] + self.width * 0.5 * self.orientation["cos"] -
self.height / 3. * self.orientation["sin"]
],
[
self.center[0] + self.width * 0.5 * self.orientation["sin"] -
self.height / 3. * self.orientation["cos"],
self.center[1] - self.width * 0.5 * self.orientation["cos"] -
self.height / 3. * self.orientation["sin"]
]])
def __init__(self, center, height, width, orientation, **kwargs):
Polygon.__init__(self, center, height, width, orientation,
Triangle._id, "target", **kwargs)
Triangle._id += 1
class Rectangle(Polygon):
_id = 0
def compute_edges(self):
self.orientation["cos"] = np.cos(self.orientation["radian"])
self.orientation["sin"] = np.sin(self.orientation["radian"])
return np.array(
[[
self.center[0] + 0.5 * self.height * self.orientation["cos"] +
0.5 * self.width * self.orientation["sin"],
self.center[1] + 0.5 * self.height * self.orientation["sin"] -
0.5 * self.width * self.orientation["cos"]
],
[
self.center[0] + 0.5 * self.height * self.orientation["cos"] -
0.5 * self.width * self.orientation["sin"],
self.center[1] + 0.5 * self.height * self.orientation["sin"] +
0.5 * self.width * self.orientation["cos"]
],
[
self.center[0] - 0.5 * self.height * self.orientation["cos"] -
0.5 * self.width * self.orientation["sin"],
self.center[1] - 0.5 * self.height * self.orientation["sin"] +
0.5 * self.width * self.orientation["cos"]
],
[
self.center[0] - 0.5 * self.height * self.orientation["cos"] +
0.5 * self.width * self.orientation["sin"],
self.center[1] - 0.5 * self.height * self.orientation["sin"] -
0.5 * self.width * self.orientation["cos"]
]])
def __init__(self, center, height, width, orientation, **kwargs):
Polygon.__init__(self, center, height, width, orientation,
Rectangle._id, "launcher", **kwargs)
Rectangle._id += 1
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlim(xmax=20)
ax.set_ylim(ymax=20)
ax.set_aspect("equal")
polygons = [
Triangle([2, 6], 2, 1, {"degree": 30.}, color="red"),
Triangle([6, 3], 2, 1, {"degree": 200.}, color="red"),
Rectangle([2, 2], 2, 1, {"degree": 60.}, color="blue"),
Rectangle([10, 2], 2, 1, {"degree": 60.}, color="blue")
]
for pol in polygons:
ax.add_patch(pol.polygon)
pol.connect()
plt.show()
I would like to do the same but with images (png,jpg or svg), that is to say replace the polygons with an image from a file.
I could not find any examples on matplotlib or elsewhere close to this.
Do you think it is possible ? How could I do ?
I guess I would first have to load the image in an patch and then add it to the plot axes, but it is not clear then how I could drag this patch which may not have the same properties as polygons.
Regards