2

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).

Null Salad
  • 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 Answers1

1

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:

Dragging line using transparent markers in matplotlib

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).

armatita
  • 12,825
  • 8
  • 48
  • 49