【问题标题】:Creating a new plugin for mpld3为 mpld3 创建一个新插件
【发布时间】:2014-08-07 16:07:59
【问题描述】:

为了学习如何创建新的 mpld3 插件,我采用了一个现有示例 LinkedDataPlugin (http://mpld3.github.io/examples/heart_path.html),并通过删除对 lines 对象的引用对其进行了轻微修改。也就是说,我创建了以下内容:

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

但是,当我尝试将插件链接到图形时,如

plugins.connect(fig, DragPlugin(points[0], patch))

我收到一个错误,“模块”不可调用,指向这一行。这是什么意思,为什么它不起作用?谢谢。

我正在添加额外的代码来表明链接多个插件可能会出现问题。但这可能完全是由于我的一些愚蠢的错误,或者有办法解决它。以下基于 LinkedViewPlugin 的代码生成三个面板,其中顶部和底部面板应该是相同的。预计中间面板中的鼠标悬停将控制顶部和底部面板中的显示,但仅在底部面板中发生更新。如果能够弄清楚如何在多个面板中反映变化,那就太好了。谢谢。

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

于 2014 年 8 月 22 日编辑

我正在进一步编辑此代码,以解决创建插件以控制两个轴的行为的问题。代码如下:

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

当我检查 ptsobj、ptsobj2、lineobj、lineobj2、patchobj 和 patchobj2 时,我发现

ptsobj = Marker
ptsobj2 = Marker
lineobj = Line2D
lineobj2 = null
patchobj = Patch
patchobj2 = Patch

所以还有一些问题需要解决……

【问题讨论】:

    标签: python plugins matplotlib mpld3


    【解决方案1】:

    我的猜测是您将文件命名为 DragPlugin.py 并在脚本顶部使用了 import DragPlugin。尝试改用from DragPlugin import DragPlugin

    【讨论】:

    • 最终我让它工作了;我认为它需要重新启动内核(或者我忘记了)。但是,我后来注意到有关插件的其他内容。我使用 LinkedViewPlugin 的变体(LinkedView1 和 LinkedView2)来连接三个面板,以便面板 1 中的鼠标悬停更新其他两个面板中的行为。我注意到我可以链接 1 和 2 或 1 和 3 但不能同时链接两者。这是正确的吗?有办法解决吗?谢谢。
    • 您当然可以链接多个面板:链接视图插件引用您要更新的对象。您可以使用相同的策略来更新任意数量的对象,并对 Python 和 Javascript 进行适当的修改。
    • 我不确定我做错了什么,但这不是我观察到的。请查看我测试的代码(上面已编辑)。这基于 custom_plugin.py 中的 LinkedViewPlugin。我复制并修改了适当的类并按照我应该的方式链接它(我认为)。但是第二个 plugin.connect 覆盖了第一个,我看到更新只发生在一个面板中。我还尝试在中间面板中绘制圆圈两次,认为一组将控制上面板,另一组将控制下面板。它没有用。谢谢。
    • 啊 - 当您使用 javascript on("mouseover", ...) 时,它会覆盖这些元素之前的任何鼠标悬停行为。所以当你连接第二个插件时,它会覆盖第一个插件的行为。如果您希望单个鼠标悬停影响多个绘图,您可以编写一个新的(单个)插件来执行此操作。
    • 是的,这是有道理的。接下来我会尝试一下。谢谢指点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多