javascript tutorial Parcelas interactivas en el cuaderno Jupyter(IPython) con puntos que se pueden arrastrar y que llaman al código Python cuando se arrastran




tablas en jupyter notebook (2)

Me gustaría hacer algunos gráficos interactivos en el cuaderno de Jupyter, en los que el usuario puede arrastrar ciertos puntos de la gráfica. Las ubicaciones de esos puntos se deben usar como entrada para una función de Python (en el cuaderno) que actualiza la trama.

Algo como esto se ha logrado aquí:

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

Pero las devoluciones de llamada son a funciones de Javascript. En algunos casos, el código que actualiza la trama debe ser extremadamente complejo y llevaría mucho tiempo reescribirlo en Javascript. Estoy dispuesto a designar los puntos arrastrables en Javascript si es necesario, pero ¿es posible volver a llamar a Python para actualizar la trama?

Me pregunto si las herramientas como Bokeh o Plotly podrían proporcionar esta funcionalidad.


¿Has probado bqplot ? El Scatter tiene un parámetro enable_move , que cuando se establece en True , permiten que se arrastren los puntos. Además, cuando arrastras puedes observar un cambio en el valor x o y de la Scatter o la Label y activar una función python a través de eso, lo que a su vez genera una nueva trama. Lo hacen en el cuaderno de Introduction .

Código de cuaderno 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: aquí hay un enlace a la esencia que muestra la actualización al arrastrar.

Para hacer esto necesitas saber:

  • Cómo interactuar con el kernel IPython desde la interfaz de Javascript de Jupyter. En este momento, es a través de Jupyter.Kernel.execute ( código fuente actual ).
  • Suficiente d3.js para estar cómodo. (Al igual que con la pantalla para trazar la conversión de coordenadas.)
  • La biblioteca d3-via-Python de su elección. mpld3 para este ejemplo.

mpld3 tiene su propio complemento para puntos arrastrables y capacidad para un complemento mpld3 personalizado . Pero en este momento no hay ninguna función para volver a dibujar el gráfico en la actualización de datos ; los mantenedores dicen que en este momento la mejor manera de hacerlo es eliminar y volver a dibujar la trama completa en la actualización, o bien sumergirse en el javascript.

Ipywidgets es, como dijiste (y por lo que puedo decir), una forma de vincular elementos de input HTML a diagramas de notebook Jupyter cuando usas el kernel IPython, y por lo tanto no es lo que quieres. Pero mil veces más fácil de lo que estoy proponiendo. Los enlaces README de ipywidgets github reposit para el cuaderno de IPython correcto para comenzar en su suite de ejemplo.

La mejor publicación del blog sobre la interacción directa de la notebook Jupyter con el kernel IPython es de Jake Vanderplas en 2013. Es para IPython <= 2.0 y comentaristas tan recientes como hace unos meses (agosto de 2015) que publicaron actualizaciones para IPython 2 e IPython 3 pero el código No funcionó con mi portátil Jupyter 4. El problema parece ser que la API de javascript para el kernel de Jupyter está en proceso de cambio.

Actualicé el ejemplo de arrastre de mpld3 y el ejemplo de Jake Vanderplas en un gist (el enlace se encuentra en la parte superior de esta respuesta) para dar un ejemplo lo más corto posible, ya que esto es muy largo, pero los fragmentos a continuación intentan comunicar la idea de manera más sucinta.

Pitón

La devolución de llamada de Python puede tener tantos argumentos como se desee, o incluso ser código en bruto. El kernel lo ejecutará a través de una sentencia eval y devolverá el último valor de retorno. La salida, sin importar de qué tipo sea, se pasará como una cadena ( text/plain ) a la devolución de llamada de javascript.

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

Javascript

La devolución de llamada de Javascript debe tomar un argumento, que es un Object Javascript que obedece a la estructura documentada aquí .

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.
  //
}

Llame al kernel IPython desde Jupyter usando la función Jupyter.notebook.kernel.execute . El contenido enviado al Kernel está documentado aquí .

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

Javscript dentro del plugin mpld3

Modifique el complemento de la biblioteca mpld3 para agregar una clase única a los elementos HTML que se actualizarán, para que podamos encontrarlos nuevamente en el futuro.

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






ipython-notebook