Reputation: 99
Toward learning how to create a new mpld3 plugin, I took an existing example, LinkedDataPlugin (http://mpld3.github.io/examples/heart_path.html), and modified it slightly by deleting references to lines object. That is, I created the following:
class DragPlugin(plugins.PluginBase):
JAVASCRIPT = r"""
mpld3.register_plugin("drag", DragPlugin);
DragPlugin.prototype = Object.create(mpld3.Plugin.prototype);
DragPlugin.prototype.constructor = DragPlugin;
DragPlugin.prototype.requiredProps = ["idpts", "idpatch"];
DragPlugin.prototype.defaultProps = {}
function DragPlugin(fig, props){
mpld3.Plugin.call(this, fig, props);
};
DragPlugin.prototype.draw = function(){
var patchobj = mpld3.get_element(this.props.idpatch, this.fig);
var ptsobj = mpld3.get_element(this.props.idpts, this.fig);
var drag = d3.behavior.drag()
.origin(function(d) { return {x:ptsobj.ax.x(d[0]),
y:ptsobj.ax.y(d[1])}; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
patchobj.path.attr("d", patchobj.datafunc(ptsobj.offsets,
patchobj.pathcodes));
patchobj.data = ptsobj.offsets;
ptsobj.elements()
.data(ptsobj.offsets)
.style("cursor", "default")
.call(drag);
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d, i) {
d[0] = ptsobj.ax.x.invert(d3.event.x);
d[1] = ptsobj.ax.y.invert(d3.event.y);
d3.select(this)
.attr("transform", "translate(" + [d3.event.x,d3.event.y] + ")");
patchobj.path.attr("d", patchobj.datafunc(ptsobj.offsets,
patchobj.pathcodes));
}
function dragended(d, i) {
d3.select(this).classed("dragging", false);
}
}
mpld3.register_plugin("drag", DragPlugin);
"""
def __init__(self, points, patch):
print "Points ID : ", utils.get_id(points)
self.dict_ = {"type": "drag",
"idpts": utils.get_id(points),
"idpatch": utils.get_id(patch)}
However, when I try to link the plugin to a figure, as in
plugins.connect(fig, DragPlugin(points[0], patch))
I get an error, 'module' is not callable, pointing to this line. What does this mean and why doesn't it work? Thanks.
I'm adding additional code to show that linking more than one Plugin might be problematic. But this may be entirely due to some silly mistake on my part, or there is a way around it. The following code based on LinkedViewPlugin generates three panels, in which the top and the bottom panel are supposed to be identical. Mouseover in the middle panel was expected to control the display in the top and bottom panels, but updates occur in the bottom panel only. It would be nice to be able to figure out how to reflect the changes in multiple panels. Thanks.
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import mpld3
from mpld3 import plugins, utils
class LinkedView(plugins.PluginBase):
"""A simple plugin showing how multiple axes can be linked"""
JAVASCRIPT = """
mpld3.register_plugin("linkedview", LinkedViewPlugin);
LinkedViewPlugin.prototype = Object.create(mpld3.Plugin.prototype);
LinkedViewPlugin.prototype.constructor = LinkedViewPlugin;
LinkedViewPlugin.prototype.requiredProps = ["idpts", "idline", "data"];
LinkedViewPlugin.prototype.defaultProps = {}
function LinkedViewPlugin(fig, props){
mpld3.Plugin.call(this, fig, props);
};
LinkedViewPlugin.prototype.draw = function(){
var pts = mpld3.get_element(this.props.idpts);
var line = mpld3.get_element(this.props.idline);
var data = this.props.data;
function mouseover(d, i){
line.data = data[i];
line.elements().transition()
.attr("d", line.datafunc(line.data))
.style("stroke", this.style.fill);
}
pts.elements().on("mouseover", mouseover);
};
"""
def __init__(self, points, line, linedata):
if isinstance(points, matplotlib.lines.Line2D):
suffix = "pts"
else:
suffix = None
self.dict_ = {"type": "linkedview",
"idpts": utils.get_id(points, suffix),
"idline": utils.get_id(line),
"data": linedata}
class LinkedView2(plugins.PluginBase):
"""A simple plugin showing how multiple axes can be linked"""
JAVASCRIPT = """
mpld3.register_plugin("linkedview", LinkedViewPlugin2);
LinkedViewPlugin2.prototype = Object.create(mpld3.Plugin.prototype);
LinkedViewPlugin2.prototype.constructor = LinkedViewPlugin2;
LinkedViewPlugin2.prototype.requiredProps = ["idpts", "idline", "data"];
LinkedViewPlugin2.prototype.defaultProps = {}
function LinkedViewPlugin2(fig, props){
mpld3.Plugin.call(this, fig, props);
};
LinkedViewPlugin2.prototype.draw = function(){
var pts = mpld3.get_element(this.props.idpts);
var line = mpld3.get_element(this.props.idline);
var data = this.props.data;
function mouseover(d, i){
line.data = data[i];
line.elements().transition()
.attr("d", line.datafunc(line.data))
.style("stroke", this.style.fill);
}
pts.elements().on("mouseover", mouseover);
};
"""
def __init__(self, points, line, linedata):
if isinstance(points, matplotlib.lines.Line2D):
suffix = "pts"
else:
suffix = None
self.dict_ = {"type": "linkedview",
"idpts": utils.get_id(points, suffix),
"idline": utils.get_id(line),
"data": linedata}
fig, ax = plt.subplots(3)
# scatter periods and amplitudes
np.random.seed(0)
P = 0.2 + np.random.random(size=20)
A = np.random.random(size=20)
x = np.linspace(0, 10, 100)
data = np.array([[x, Ai * np.sin(x / Pi)]
for (Ai, Pi) in zip(A, P)])
points = ax[1].scatter(P, A, c=P + A,
s=200, alpha=0.5)
ax[1].set_xlabel('Period')
ax[1].set_ylabel('Amplitude')
# create the line object
lines = ax[0].plot(x, 0 * x, '-w', lw=3, alpha=0.5)
ax[0].set_ylim(-1, 1)
ax[0].set_title("Hover over points to see lines")
linedata = data.transpose(0, 2, 1).tolist()
plugins.connect(fig, LinkedView(points, lines[0], linedata))
# second set of lines exactly the same but in a different panel
lines2 = ax[2].plot(x, 0 * x, '-w', lw=3, alpha=0.5)
ax[2].set_ylim(-1, 1)
ax[2].set_title("Hover over points to see lines #2")
plugins.connect(fig, LinkedView2(points, lines2[0], linedata))
mpld3.show()
I am editing this code further to work out problems with creating a plugin to control behavior in two axes. Here's the code:
class LinkedDragPlugin(plugins.PluginBase):
JAVASCRIPT = r"""
mpld3.register_plugin("drag", LinkedDragPlugin);
LinkedDragPlugin.prototype = Object.create(mpld3.Plugin.prototype);
LinkedDragPlugin.prototype.constructor = LinkedDragPlugin;
LinkedDragPlugin.prototype.requiredProps = ["idpts", "idline", "idpatch",
"idpts2", "idline2", "idpatch2"];
LinkedDragPlugin.prototype.defaultProps = {}
function LinkedDragPlugin(fig, props){
mpld3.Plugin.call(this, fig, props);
};
LinkedDragPlugin.prototype.draw = function(){
var ptsobj = mpld3.get_element(this.props.idpts, this.fig);
var ptsobj2 = mpld3.get_element(this.props.idpts2, this.fig);
console.log(ptsobj)
console.log(ptsobj2)
var lineobj = mpld3.get_element(this.props.idline, this.fig);
var lineobj2 = mpld3.get_element(this.props.idline2, this.fig);
console.log(lineobj)
console.log(lineobj2)
var patchobj = mpld3.get_element(this.props.idpatch, this.fig);
var patchobj2 = mpld3.get_element(this.props.idpatch2, this.fig);
console.log(patchobj)
console.log(patchobj2)
mpld3.register_plugin("drag", LinkedDragPlugin);
"""
def __init__(self, points, line, patch):
if isinstance(points[0], mpl.lines.Line2D):
suffix = "pts"
else:
suffix = None
self.dict_ = {"type": "drag",
"idpts": utils.get_id(points[0], suffix),
"idline": utils.get_id(line[0]),
"idpatch": utils.get_id(patch[0]),
"idpts2": utils.get_id(points[1], suffix),
"idline2": utils.get_id(line[1]),
"idpatch2": utils.get_id(patch[1])}
print "ids :", self.dict_
bmap=brewer2mpl.get_map('Greys','Sequential',5)
fig, ax = plt.subplots(1, 2)
fig.set_size_inches(6, 4)
w = 500
h = 300
pt1 = ax[0].plot([w*0.1, w*0.9], [h*0.1, h*0.9], 'bo', ms=10, alpha=0.3)
line1 = ax[0].plot([w*0.1, w*0.9], [h*0.1, h*0.9], 'k', ms=10, lw=2, alpha=0.0)
v = [(0, h*0.1), (w, h*0.1), (0, h*0.9), (w, h*0.9),
(w*0.1, 0), (w*0.1, h), (w*0.9, 0), (w*0.9, h)]
c = [1, 2, 1, 2, 1, 2, 1, 2]
p = path.Path(v, c)
patch = patches.PathPatch(p, fill=None, alpha=0.5)
patch1 = ax[0].add_patch(patch)
ax[0].set_xlim([0, 500])
ax[0].set_ylim([0, 300])
w = 400
h = 400
pt2 = ax[1].plot([w*0.1, w*0.9], [h*0.1, h*0.9], 'bo', ms=10, alpha=0.3)
#line2 = ax[1].plot([w*0.1, w*0.9], [h*0.1, h*0.9], 'bo', ms=10, alpha=0.3)
line2 = ax[1].plot([w*0.1, w*0.9], [h*0.1, h*0.9], 'k', ms=10, lw=2, alpha=0.0)
v = [(0, h*0.1), (w, h*0.1), (0, h*0.9), (w, h*0.9),
(w*0.1, 0), (w*0.1, h), (w*0.9, 0), (w*0.9, h)]
c = [1, 2, 1, 2, 1, 2, 1, 2]
p = path.Path(v, c)
patch = patches.PathPatch(p, fill=None, alpha=0.5)
patch2 = ax[1].add_patch(patch)
ax[1].set_xlim([0, 400])
ax[1].set_ylim([0, 400])
plugins.connect(fig, LinkedDragPlugin([pt1[0], pt2[0]], [line1[0], line2[0]], [patch1, patch2]))
mpld3.show()
When I examine the ptsobj, ptsobj2, lineobj, lineobj2, patchobj, and patchobj2, I see that
ptsobj = Marker
ptsobj2 = Marker
lineobj = Line2D
lineobj2 = null
patchobj = Patch
patchobj2 = Patch
So there are issues to be worked out...
Upvotes: 2
Views: 884
Reputation: 86353
My guess is that you named your file DragPlugin.py
and used import DragPlugin
at the top of your script. Try doing from DragPlugin import DragPlugin
instead
Upvotes: 1