繁体   English   中英

Jupyter(IPython)笔记本中的交互式绘图,带有可拖动的点,在拖动时调用Python代码

[英]Interactive plots in Jupyter (IPython) notebook with draggable points that call Python code when dragged

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

这样的事情已在这里完成:

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

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

我想知道像Bokeh或Plotly这样的工具是否可以提供此功能。

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

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

tl; dr - 这是一个显示拖拽更新的要点的链接。


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

  • 如何从Jupyter的Javascript前端与IPython内核进行交互。 现在,这是通过Jupyter.Kernel.execute当前源代码 )。
  • 足够d3.js舒服。 (与用于绘制坐标转换的屏幕一样。)
  • 您选择的d3-via-Python库。 这个例子的mpld3

mpld3有自己的可拖动点插件自定义mpld3插件的功能 但是现在没有重新绘制数据更新图的功能 ; 维护者现在说,最好的方法是在更新时删除并重绘整个情节,或者真正深入了解javascript。

Ipywidgets就像你说的那样(据我所知),是一种在使用IPython内核时将HTML input元素链接到Jupyter笔记本图的方法,所以不是你想要的。 但比我提议的容易一千倍。 ipywidgets github repo的README链接到正确的IPython笔记本,从他们的示例套件开始。


关于直接Jupyter笔记本与IPython内核交互的最佳博客文章来自于2013年的Jake Vanderplas 。这是针对IPython <= 2.0和几个月前的评论者(2015年8月)发布了IPython 2IPython 3的更新但代码没用我的Jupyter 4笔记本电脑。 问题似乎是Jupyter内核javascript API不断变化。

我更新了mpld3拖动示例和Jake Vanderplas的示例(链接位于此回复的顶部)以尽可能简短的示例,因为这已经很长,但下面的片段尝试更简洁地传达这个想法。

蟒蛇

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

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

使用Javascript

Javascript回调应该采用一个参数,这是一个服从此处记录的结构的Javascript Object

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内核。 发送到内核的内容将在此处记录

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插件中的Javscript

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM