I'm trying to make individual markers along a Line2d object draggable. I've seen how this was done using bar() in Matplotlib's draggable rectangle example. The fundamental difference here is that ax.bar(...)
generates a set of Artists, while ax.plot(...)
returns a single Line2D object, and while I have discovered how to edit properties of the entire line, I don't know how to edit properties of a single marker element in that line, and I've scoured the docs and can't find anything. Do I need to do this by using Circle() objects as my "markers", and then redrawing the line continuously as an motion_notify_event? I can paste my code in here, but guaranteed it is fundamentally wrong (lol).

- 765
- 2
- 16
- 31
-
Check the following question: http://stackoverflow.com/questions/28001655/draggable-line-with-draggable-points – armatita May 29 '16 at 19:34
-
That question is very useful but it is also using an Ellipse() as the marker. Is there a way to do this with 'native' Line2D markers? – Null Salad May 29 '16 at 19:37
-
You can put the markers there but transparent (giving the illusion you are dragging the line vertices). I've put it into an answer. See if it fits you. – armatita May 30 '16 at 07:15
1 Answers
There are a few ways for you to do this. I doubt there is an easy way for you to drag the line itself. To drag the line markers (but without seeing any) just make sure they exist but are transparent. I'm going to use an example from matplotlib documentation: The Path Editor.
Here is the modified code:
import numpy as np
import matplotlib.path as mpath
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
Path = mpath.Path
fig, ax = plt.subplots()
pathdata = [
(Path.MOVETO, (1.58, -2.57)),
(Path.MOVETO, (0.35, -1.1)),
(Path.MOVETO, (-1.75, 2.0)),
(Path.MOVETO, (0.375, 2.0)),
(Path.MOVETO, (0.85, 1.15)),
(Path.MOVETO, (2.2, 3.2)),
(Path.MOVETO, (3, 0.05)),
(Path.MOVETO, (2.0, -0.5)),
#(Path.CLOSEPOLY, (1.58, -2.57)),
]
codes, verts = zip(*pathdata)
path = mpath.Path(verts, codes)
patch = mpatches.PathPatch(path, facecolor='green', edgecolor='yellow', alpha=0.5)
ax.add_patch(patch)
class PathInteractor(object):
"""
An path editor.
Key-bindings
't' toggle vertex markers on and off. When vertex markers are on,
you can move them, delete them
"""
showverts = True
epsilon = 5 # max pixel distance to count as a vertex hit
def __init__(self, pathpatch):
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.line.set_markerfacecolor((1, 1, 0, 0))
self._ind = None # the active vert
canvas.mpl_connect('draw_event', self.draw_callback)
canvas.mpl_connect('button_press_event', self.button_press_callback)
canvas.mpl_connect('key_press_event', self.key_press_callback)
canvas.mpl_connect('button_release_event', self.button_release_callback)
canvas.mpl_connect('motion_notify_event', self.motion_notify_callback)
self.canvas = canvas
def draw_callback(self, event):
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 pathpatch_changed(self, pathpatch):
'this method is called whenever the pathpatchgon object is called'
# only copy the artist props to the line (except visibility)
vis = self.line.get_visible()
plt.Artist.update_from(self.line, pathpatch)
self.line.set_visible(vis) # don't use the pathpatch visibility state
def get_ind_under_point(self, event):
'get the index of the vertex under point if within epsilon tolerance'
# display coords
xy = np.asarray(self.pathpatch.get_path().vertices)
xyt = self.pathpatch.get_transform().transform(xy)
xt, yt = xyt[:, 0], xyt[:, 1]
d = np.sqrt((xt - event.x)**2 + (yt - event.y)**2)
ind = d.argmin()
if d[ind] >= self.epsilon:
ind = None
return ind
def button_press_callback(self, event):
'whenever a mouse button is pressed'
if not self.showverts:
return
if event.inaxes is None:
return
if event.button != 1:
return
self._ind = self.get_ind_under_point(event)
def button_release_callback(self, event):
'whenever a mouse button is released'
if not self.showverts:
return
if event.button != 1:
return
self._ind = None
def key_press_callback(self, event):
'whenever a key is pressed'
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 motion_notify_callback(self, event):
'on mouse movement'
if not self.showverts:
return
if self._ind is None:
return
if event.inaxes is None:
return
if event.button != 1:
return
x, y = event.xdata, event.ydata
vertices = self.pathpatch.get_path().vertices
vertices[self._ind] = x, y
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)
interactor = PathInteractor(patch)
ax.set_title('drag vertices to update path')
ax.set_xlim(-3, 4)
ax.set_ylim(-3, 4)
plt.show()
The line you need to take specially into account is this:
self.line.set_markerfacecolor((1, 1, 0, 0))
It's in the __init__
function right after the definition of the plot
. The last number is the alpha channel and I made it zero. The plot itself looks like this:
An you can drag the line marker although you kind of need to guess where they are (but I'll assume you have a plane for this).
In any case if you choose to use this example make sure you transform your line data into a path.
EDIT: Let me just give you an example of how to do this path building (might as well make it a complete example):
import numpy as np
def buildpath(x,y):
path = []
for i in range(len(x)):
path.append((Path.MOVETO, (x[i], y[i])))
return path
x = np.linspace(-2,2,5)
y = x*1
pathdata = buildpath(x,y)
If you use this to build your pathdata
(the rest of the code remains the same) you should obtain a personalized path using numpy arrays (as you usually do with plot commands in matplotlib).

- 12,825
- 8
- 48
- 49