【问题标题】:Interactive plots in Jupyter (IPython) notebook with draggable points that call Python code when draggedJupyter (IPython) 笔记本中的交互式绘图,带有可拖动的点,在拖动时调用 Python 代码
【发布时间】:2015-07-24 07:59:19
【问题描述】:

我想在 Jupyter 笔记本中制作一些交互式绘图,其中用户可以拖动绘图中的某些点。然后应将这些点的位置用作更新绘图的 Python 函数(在笔记本中)的输入。

这里已经完成了类似的事情:

http://nbviewer.ipython.org/github/maojrs/ipynotebooks/blob/master/interactive_test.ipynb

但回调是针对 Javascript 函数的。在某些情况下,更新绘图的代码需要非常复杂,并且需要很长时间才能用 Javascript 重写。如有必要,我愿意在 Javascript 中指定可拖动点,但是否可以回调 Python 以更新绘图?

我想知道 Bokeh 或 Plotly 等工具是否可以提供此功能。

【问题讨论】:

  • 你可能想使用散景而不是 matplotlib
  • @MaxNoe 如果您可以提供散景的工作示例,我将接受它作为答案。
  • 哇,谢谢,我从来没有听说过 mpld3。改变一切。这有帮助吗? github.com/ipython/ipython-in-depth/blob/…
  • @slushy 谢谢,但该示例只使用了常用的小部件。它不响应鼠标在绘图上的点击。

标签: javascript python matplotlib ipython-notebook


【解决方案1】:

你试过bqplot吗? Scatter 有一个enable_move 参数,当您设置为True 时,它们允许拖动点。此外,当您拖动时,您可以观察到ScatterLabelxy 值的变化,并通过它触发一个python 函数,进而生成一个新的绘图。他们在 Introduction 笔记本中执行此操作。

Jupyter 笔记本代码:

# Let's begin by importing some libraries we'll need
import numpy as np
from __future__ import print_function # So that this notebook becomes both Python 2 and Python 3 compatible

# And creating some random data
size = 10
np.random.seed(0)
x_data = np.arange(size)
y_data = np.cumsum(np.random.randn(size)  * 100.0)

from bqplot import pyplot as plt

# Creating a new Figure and setting it's title
plt.figure(title='My Second Chart')
# Let's assign the scatter plot to a variable
scatter_plot = plt.scatter(x_data, y_data)

# Let's show the plot
plt.show()

# then enable modification and attach a callback function:

def foo(change):
    print('This is a trait change. Foo was called by the fact that we moved the Scatter')
    print('In fact, the Scatter plot sent us all the new data: ')
    print('To access the data, try modifying the function and printing the data variable')
    global pdata 
    pdata = [scatter_plot.x,scatter_plot.y]

# First, we hook up our function `foo` to the colors attribute (or Trait) of the scatter plot
scatter_plot.observe(foo, ['y','x'])

scatter_plot.enable_move = True

【讨论】:

    【解决方案2】:

    tl;博士-Here's a link to the gist showing update-on-drag.


    要做到这一点,您需要知道:

    • 如何通过 Jupyter 的 Javascript 与 IPython 内核进行交互 前端。现在是通过Jupyter.Kernel.execute (current source code)。
    • d3.js 足够舒适。 (就像用屏幕来绘制坐标转换一样。)
    • 您选择的 d3-via-Python 库。 mpld3 这个例子。

    mpld3 有它的own plugin for draggable pointscustom mpld3 plugin 的能力。但是现在有no feature to redraw the plot on update of data;维护人员说,现在最好的方法是在更新时删除并重绘整个情节,或者真正深入 javascript。

    正如您所说(据我所知),Ipywidgets 是一种在使用 IPython 内核时将 HTML input 元素链接到 Jupyter 笔记本图的方法,因此不是您想要的。但比我建议的要容易一千倍。 ipywidgets github repo 的README 链接到他们示例套件中的correct IPython notebook to start with


    关于 Jupyter notebook 与 IPython 内核直接交互的最佳博文来自 2013 年的 Jake Vanderplas。它是针对 IPythonIPython 2 和IPython 3 但代码不适用于我的 Jupyter 4 笔记本。 问题似乎在于javascript API for the Jupyter kernel 不断变化。

    我更新了mpld3 dragging example 和 Jake Vanderplas 在要点中的示例(链接在此回复的顶部)以提供尽可能短的示例,因为这已经很长了,但是下面的 sn-ps 尝试进行交流这个想法更简洁。

    Python

    Python 回调可以有任意多的参数,甚至可以是原始代码。内核将通过eval 语句运行它并发回最后的返回值。输出,无论是什么类型,都将作为字符串 (text/plain) 传递给 javascript 回调。

    def python_callback(arg):
        """The entire expression is evaluated like eval(string)."""
        return arg + 42
    

    Javascript

    Javascript 回调应该有一个参数,它是一个 Javascript Object 遵循结构 documented here

    javascriptCallback = function(out) {
      // Error checking omitted for brevity.
      output = out.content.user_expressions.out1;
      res = output.data["text/plain"];
      newValue = JSON.parse(res);  // If necessary
      //
      // Use newValue to do something now.
      //
    }
    

    使用函数Jupyter.notebook.kernel.execute 从 Jupyter 调用 IPython 内核。发送到的内容 内核是documented here

    var kernel = Jupyter.notebook.kernel;
    var callbacks = {shell: {reply: javascriptCallback }};
    kernel.execute(
      "print('only the success/fail status of this code is reported')",
      callbacks,
      {user_expressions:
        {out1: "python_callback(" + 10 + ")"}  // function call as a string
      }
    );
    

    mpld3 插件中的 JavaScript

    修改 mpld3 库的插件,为 要更新的 HTML 元素,以便我们可以在 未来。

    import matplotlib as mpl
    import mpld3
    
    class DragPlugin(mpld3.plugins.PluginBase):
        JAVASCRIPT = r"""
        // Beginning content unchanged, and removed for brevity.
    
        DragPlugin.prototype.draw = function(){
            var obj = mpld3.get_element(this.props.id);
    
            var drag = d3.behavior.drag()
                .origin(function(d) { return {x:obj.ax.x(d[0]),
                                              y:obj.ax.y(d[1])}; })
                .on("dragstart", dragstarted)
                .on("drag", dragged)
                .on("dragend", dragended);
    
            // Additional content unchanged, and removed for brevity
    
            obj.elements()
               .data(obj.offsets)
               .style("cursor", "default")
               .attr("name", "redrawable")  // DIFFERENT
               .call(drag);
    
            // Also modify the 'dragstarted' function to store
            // the starting position, and the 'dragended' function
            // to initiate the exchange with the IPython kernel
            // that will update the plot.
        };
        """
    
        def __init__(self, points):
            if isinstance(points, mpl.lines.Line2D):
                suffix = "pts"
            else:
                suffix = None
    
        self.dict_ = {"type": "drag",
                      "id": mpld3.utils.get_id(points, suffix)}
    

    【讨论】:

      猜你喜欢
      • 2017-03-22
      • 2016-07-04
      • 2021-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-27
      • 2015-10-15
      • 1970-01-01
      相关资源
      最近更新 更多