I have my backend in Python and I'm trying to create an interactive plot in the web browser, where the user can drag and drop elements across the plot. The plan is then to hook onto the 'drop' event with some JS that updates values in a form and submits the form, which would then cause the backend to update the plot.
So far I have been using mpld3, but I am not invested in it. I would be open to a solution using some other package like Plotly or Bokeh.
What I am looking for is quite similar to this mpld3 example, except that I want to allow the user to drag objects other than the points. Specifically, I want to create some vertical and horizontal lines and allow the user to drag these horizontally and vertically, as illustrated in this picture:
I have gotten so far as to adapt the mpld3 plugin to actually work with the latest version of d3.js, and I am able to drag the points (see gist). However, I am not able to adapt the plugin to make the vertical lines draggable like the points in the example.
My first idea was something like
lines = ax.vlines(xdata,min(ydata),ydata)
plugins.connect(fig, DragPlugin(lines))
vlines()
returns a matplotlib.collections.LineCollection
item, and I don't know how to access individual lines inside that. I tried simply modifying the type check inside the __init__
(if isinstance(object, mpl.lines.Line2D)
to be more permissive, but that didn't work.
My next idea was to just draw the lines using ax.plot()
:
lines = []
for i in range(len(xdata)):
from_xy = (xdata[i],min(ydata))
to_xy = (xdata[i],ydata[i])
xs = from_xy[0],to_xy[0]
ys = from_xy[1],to_xy[1]
lines.append(ax.plot(xs,ys, 'k-')[0])
In this case the list lines
contains matplotlib.lines.Line2D
objects, which I thought would be easier to manipulate individually in mpld3 than a LineCollection
. However, the Plugin seems to be written to deal with a the entire set of red points as a Line2D
object (like a polygonal chain with invisible edges, if you see what I mean). This is what I conclude from the fact that the string 'pts'
is used to find the mpld3 id of the points. Just passing a Line2D object to DragPlugin
, however, does not work:
# don't use the suffix:
def __init__(self, object):
self.dict_ = {"type": "drag","id": utils.get_id(object)}
# after defining `lines` as above:
plugins.connect(fig, DragPlugin(lines[0]))
I get the following in the browser console:
Uncaught TypeError: obj.elements(...).data(...).style is not a function
Which gets raised at the following part of the JS:
obj.elements()
.data(obj.offsets)
.style("cursor", "default")
.call(drag);
But I have no idea what that JS is doing and it's not documented in the plugin.
Another thing I will need is to restrict the vertical lines to be only draggable horizontally, and conversely for the horizontal lines. But first I would like to get dragging the lines to work at all.