Skip to content

Instantly share code, notes, and snippets.

@tanyaschlusser
Last active March 20, 2017 20:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tanyaschlusser/047148b1411ba4e05bb7 to your computer and use it in GitHub Desktop.
Save tanyaschlusser/047148b1411ba4e05bb7 to your computer and use it in GitHub Desktop.
Callbacks: Python + mpld3 + Jupyter 4
<head>
<title>Python Callback using mpld3</title>
<meta charset "UTF-8"/>
</head>
<body>
<h1>Python Callback using mpld3</h1>
<iframe
src="http://nbviewer.jupyter.org/gist/tanyaschlusser/047148b1411ba4e05bb7/Python%20Callback%20using%20mpld3.ipynb"
style="width:90vw;"
></iframe>
</body>
</html>
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Python callbacks from Jupyter (4) notebook \n",
"\n",
"The code here copies the [mpld3 example for dragged points plugin][drag_points], and adds a Python callback to the dragged circles. Five main things change: \n",
"\n",
"1. There's extra Javascript code to communicate with the IPython kernel.\n",
"2. We made Python code to update the points.\n",
"3. We add an extra unique class (`redrawable`) to the points we want to update after the drag.\n",
"4. We save the start position in dragstarted().\n",
"5. We call the extra javascript code in dragended();\n",
"\n",
"It's hacky and not perfect, but possibly the best we can do at the time. Code that is different from the original is bookended with a `// DIFFERENT` comment if multi-line, or else have `// DIFFERENT` inline.\n",
"\n",
"[drag_points]: http://mpld3.github.io/examples/drag_points.html\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib as mpl\n",
"\n",
"import mpld3"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# DIFFERENT\n",
"# This will be called from within the Javascript\n",
"def update_data(xprev, yprev, xnew, ynew):\n",
" \"\"\"Keep the overall mean the same.\"\"\"\n",
" print \"UPDATING...\"\n",
" global x, y\n",
" L = len(x)\n",
" if L > 1:\n",
" for i in range(L):\n",
" if xprev == x[i] and yprev == y[i]:\n",
" dx = (xprev - xnew) / (L - 1)\n",
" dy = (yprev - ynew) / (L - 1)\n",
" x = [xnew if j == 1 else x[j] + dx for j in range(L)]\n",
" y = [ynew if j == 1 else y[j] + dy for j in range(L)]\n",
" new_data = [list(xy) for xy in zip(x, y)]\n",
" print \"sending back new data:\", new_data\n",
" return new_data\n",
" #break\n",
" # Otherwise no changes\n",
" print \"sending back old data:\", [list(z) for z in zip(x,y)]\n",
" return [list(z) for z in zip(x,y)]\n",
"\n",
"# DIFFERENT"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Everything in this cell is new\n",
"javascript_response_handler = r\"\"\"\n",
"\n",
"// Handle Python output:\n",
"update_targets = function(out, ax){\n",
" console.log(\"UPDATE_TARGETS\");\n",
" var res = null;\n",
" var output = null;\n",
" if ((out.content.user_expressions === undefined) ||\n",
" (out.content.user_expressions.output === undefined)) {\n",
" return;\n",
" }\n",
" \n",
" output = out.content.user_expressions.output;\n",
" if ((output.status != \"ok\") || (output.data === undefined)) {\n",
" return;\n",
" }\n",
" \n",
" res = output.data[\"text/plain\"]; \n",
" console.log(res);\n",
" if (res == undefined)\n",
" return;\n",
" \n",
" new_positions = JSON.parse(res);\n",
" var redrawables = d3.selectAll(\"[name=redrawable]\");\n",
" console.log(\"redrawables: \" + redrawables);\n",
" redrawables.data(new_positions)\n",
" .attr(\"transform\", function(d) {\n",
" var x = ax.x(d[0]);\n",
" var y = ax.y(d[1]);\n",
" return \"translate(\" + [x, y] + \")\";\n",
" });\n",
"};\n",
"\"\"\""
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Everything in this cell is new\n",
"javascript_callback_invoker = r\"\"\"\n",
"\n",
"// Call the Python kernel from within the notebook:\n",
"request_update = function(x0, y0, x1, y1, ax){\n",
" console.log(\"REQUEST UPDATE\");\n",
" var kernel = IPython.notebook.kernel;\n",
" \n",
" custom_updater = function(python_response) {\n",
" update_targets(python_response, ax);\n",
" };\n",
" \n",
" var callbacks = {shell: {reply: custom_updater}};\n",
" if (!kernel) return;\n",
" args = x0 + \",\" + y0 + \",\" + x1 + \",\" + y1;\n",
" var msg_id = kernel.execute(\n",
" \"\", callbacks, {user_expressions:{output: \"update_data(\" + args + \")\"}});\n",
"};\n",
"\"\"\""
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"##\n",
"# This is the example from\n",
"# http://mpld3.github.io/examples/drag_points.html.\n",
"#\n",
"# Three things change: \n",
"# (1) There's extra Javascript code to communicate with the IPython kernel.\n",
"# (2) We add an extra unique class (`redrawable`) to the points we want\n",
"# to update after the drag.\n",
"# (3) We save the start position in dragstarted().\n",
"# (4) We call the extra javascript code in dragended();\n",
"#\n",
"class DragPlugin(mpld3.plugins.PluginBase):\n",
" JAVASCRIPT = r\"\"\"\n",
" mpld3.register_plugin(\"drag\", DragPlugin);\n",
" DragPlugin.prototype = Object.create(mpld3.Plugin.prototype);\n",
" DragPlugin.prototype.constructor = DragPlugin;\n",
" DragPlugin.prototype.requiredProps = [\"id\"];\n",
" DragPlugin.prototype.defaultProps = {};\n",
" function DragPlugin(fig, props){\n",
" mpld3.Plugin.call(this, fig, props);\n",
" mpld3.insert_css(\"#\" + fig.figid + \" path.dragging\",\n",
" {\"fill-opacity\": \"1.0 !important\",\n",
" \"stroke-opacity\": \"1.0 !important\"});\n",
" };\n",
"\n",
" DragPlugin.prototype.draw = function(){\n",
" var obj = mpld3.get_element(this.props.id);\n",
" \n",
" var drag = d3.behavior.drag()\n",
" .origin(function(d) { return {x:obj.ax.x(d[0]),\n",
" y:obj.ax.y(d[1])}; })\n",
" .on(\"dragstart\", dragstarted)\n",
" .on(\"drag\", dragged)\n",
" .on(\"dragend\", dragended);\n",
"\n",
" obj.elements()\n",
" .data(obj.offsets)\n",
" .style(\"cursor\", \"default\")\n",
" .attr(\"name\", \"redrawable\") // DIFFERENT\n",
" .call(drag);\n",
"\n",
" function dragstarted(d) {\n",
" d3.event.sourceEvent.stopPropagation();\n",
" d3.select(this).classed(\"dragging\", true);\n",
" // DIFFERENT. (This is crude, sorry.)\n",
" d[2] = d[0];\n",
" d[3] = d[1];\n",
" // DIFFERENT\n",
" }\n",
"\n",
" function dragged(d, i) {\n",
" d[0] = obj.ax.x.invert(d3.event.x);\n",
" d[1] = obj.ax.y.invert(d3.event.y);\n",
" d3.select(this)\n",
" .attr(\"transform\", \"translate(\" + [d3.event.x,d3.event.y] + \")\");\n",
" }\n",
"\n",
" function dragended(d) {\n",
" d3.select(this).classed(\"dragging\", false);\n",
" // DIFFERENT. (Below)\n",
" // Pass the axis object (obj.ax) too so we can transform\n",
" // from screen to data coordinates and back.\n",
" request_update(d[2], d[3], d[0], d[1], obj.ax);\n",
" }\n",
" };\n",
" \"\"\" + javascript_response_handler + javascript_callback_invoker\n",
"\n",
" def __init__(self, points):\n",
" if isinstance(points, mpl.lines.Line2D):\n",
" suffix = \"pts\"\n",
" else:\n",
" suffix = None\n",
"\n",
" self.dict_ = {\"type\": \"drag\",\n",
" \"id\": mpld3.utils.get_id(points, suffix)}"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"<style>\n",
"\n",
"</style>\n",
"\n",
"<div id=\"fig_el1684743692492964849718921\"></div>\n",
"<script>\n",
"function mpld3_load_lib(url, callback){\n",
" var s = document.createElement('script');\n",
" s.src = url;\n",
" s.async = true;\n",
" s.onreadystatechange = s.onload = callback;\n",
" s.onerror = function(){console.warn(\"failed to load library \" + url);};\n",
" document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
"}\n",
"\n",
"if(typeof(mpld3) !== \"undefined\" && mpld3._mpld3IsLoaded){\n",
" // already loaded: just create the figure\n",
" !function(mpld3){\n",
" \n",
" mpld3.register_plugin(\"drag\", DragPlugin);\n",
" DragPlugin.prototype = Object.create(mpld3.Plugin.prototype);\n",
" DragPlugin.prototype.constructor = DragPlugin;\n",
" DragPlugin.prototype.requiredProps = [\"id\"];\n",
" DragPlugin.prototype.defaultProps = {};\n",
" function DragPlugin(fig, props){\n",
" mpld3.Plugin.call(this, fig, props);\n",
" mpld3.insert_css(\"#\" + fig.figid + \" path.dragging\",\n",
" {\"fill-opacity\": \"1.0 !important\",\n",
" \"stroke-opacity\": \"1.0 !important\"});\n",
" };\n",
"\n",
" DragPlugin.prototype.draw = function(){\n",
" var obj = mpld3.get_element(this.props.id);\n",
" \n",
" var drag = d3.behavior.drag()\n",
" .origin(function(d) { return {x:obj.ax.x(d[0]),\n",
" y:obj.ax.y(d[1])}; })\n",
" .on(\"dragstart\", dragstarted)\n",
" .on(\"drag\", dragged)\n",
" .on(\"dragend\", dragended);\n",
"\n",
" obj.elements()\n",
" .data(obj.offsets)\n",
" .style(\"cursor\", \"default\")\n",
" .attr(\"name\", \"redrawable\") // DIFFERENT\n",
" .call(drag);\n",
"\n",
" function dragstarted(d) {\n",
" d3.event.sourceEvent.stopPropagation();\n",
" d3.select(this).classed(\"dragging\", true);\n",
" // DIFFERENT. (This is crude, sorry.)\n",
" d[2] = d[0];\n",
" d[3] = d[1];\n",
" // DIFFERENT\n",
" }\n",
"\n",
" function dragged(d, i) {\n",
" d[0] = obj.ax.x.invert(d3.event.x);\n",
" d[1] = obj.ax.y.invert(d3.event.y);\n",
" d3.select(this)\n",
" .attr(\"transform\", \"translate(\" + [d3.event.x,d3.event.y] + \")\");\n",
" }\n",
"\n",
" function dragended(d) {\n",
" d3.select(this).classed(\"dragging\", false);\n",
" // DIFFERENT. (Below)\n",
" // Pass the axis object (obj.ax) too so we can transform\n",
" // from screen to data coordinates and back.\n",
" request_update(d[2], d[3], d[0], d[1], obj.ax);\n",
" }\n",
" };\n",
" \n",
"\n",
"// Handle Python output:\n",
"update_targets = function(out, ax){\n",
" console.log(\"UPDATE_TARGETS\");\n",
" var res = null;\n",
" var output = null;\n",
" if ((out.content.user_expressions === undefined) ||\n",
" (out.content.user_expressions.output === undefined)) {\n",
" return;\n",
" }\n",
" \n",
" output = out.content.user_expressions.output;\n",
" if ((output.status != \"ok\") || (output.data === undefined)) {\n",
" return;\n",
" }\n",
" \n",
" res = output.data[\"text/plain\"]; \n",
" console.log(res);\n",
" if (res == undefined)\n",
" return;\n",
" \n",
" new_positions = JSON.parse(res);\n",
" var redrawables = d3.selectAll(\"[name=redrawable]\");\n",
" console.log(\"redrawables: \" + redrawables);\n",
" redrawables.data(new_positions)\n",
" .attr(\"transform\", function(d) {\n",
" var x = ax.x(d[0]);\n",
" var y = ax.y(d[1]);\n",
" return \"translate(\" + [x, y] + \")\";\n",
" });\n",
"};\n",
"\n",
"\n",
"// Call the Python kernel from within the notebook:\n",
"request_update = function(x0, y0, x1, y1, ax){\n",
" console.log(\"REQUEST UPDATE\");\n",
" var kernel = IPython.notebook.kernel;\n",
" \n",
" custom_updater = function(python_response) {\n",
" update_targets(python_response, ax);\n",
" };\n",
" \n",
" var callbacks = {shell: {reply: custom_updater}};\n",
" if (!kernel) return;\n",
" args = x0 + \",\" + y0 + \",\" + x1 + \",\" + y1;\n",
" var msg_id = kernel.execute(\n",
" \"\", callbacks, {user_expressions:{output: \"update_data(\" + args + \")\"}});\n",
"};\n",
"\n",
" mpld3.draw_figure(\"fig_el1684743692492964849718921\", {\"axes\": [{\"xlim\": [0.0, 2.5], \"yscale\": \"linear\", \"axesbg\": \"#FFFFFF\", \"texts\": [{\"v_baseline\": \"auto\", \"h_anchor\": \"middle\", \"color\": \"#000000\", \"text\": \"Click and Drag with Callback: Jupyter + IPython + mpld3\", \"coordinates\": \"axes\", \"zorder\": 3, \"alpha\": 1, \"fontsize\": 18.0, \"position\": [0.5, 1.0144675925925926], \"rotation\": -0.0, \"id\": \"el168474411332304\"}], \"zoomable\": true, \"images\": [], \"xdomain\": [0.0, 2.5], \"ylim\": [-1.0, 1.0], \"paths\": [], \"sharey\": [], \"sharex\": [], \"axesbgalpha\": null, \"axes\": [{\"scale\": \"linear\", \"tickformat\": null, \"grid\": {\"gridOn\": false}, \"fontsize\": 12.0, \"position\": \"bottom\", \"nticks\": 6, \"tickvalues\": null}, {\"scale\": \"linear\", \"tickformat\": null, \"grid\": {\"gridOn\": false}, \"fontsize\": 12.0, \"position\": \"left\", \"nticks\": 5, \"tickvalues\": null}], \"lines\": [], \"markers\": [{\"edgecolor\": \"#000000\", \"facecolor\": \"#FF0000\", \"edgewidth\": 1, \"yindex\": 1, \"coordinates\": \"data\", \"zorder\": 2, \"markerpath\": [[[0.0, 25.0], [6.6300775000000005, 25.0], [12.989496769621336, 22.36584228970604], [17.67766952966369, 17.67766952966369], [22.36584228970604, 12.989496769621336], [25.0, 6.6300775000000005], [25.0, 0.0], [25.0, -6.6300775000000005], [22.36584228970604, -12.989496769621336], [17.67766952966369, -17.67766952966369], [12.989496769621336, -22.36584228970604], [6.6300775000000005, -25.0], [0.0, -25.0], [-6.6300775000000005, -25.0], [-12.989496769621336, -22.36584228970604], [-17.67766952966369, -17.67766952966369], [-22.36584228970604, -12.989496769621336], [-25.0, -6.6300775000000005], [-25.0, 0.0], [-25.0, 6.6300775000000005], [-22.36584228970604, 12.989496769621336], [-17.67766952966369, 17.67766952966369], [-12.989496769621336, 22.36584228970604], [-6.6300775000000005, 25.0], [0.0, 25.0]], [\"M\", \"C\", \"C\", \"C\", \"C\", \"C\", \"C\", \"C\", \"C\", \"Z\"]], \"alpha\": 0.5, \"xindex\": 0, \"data\": \"data01\", \"id\": \"el168474369249104pts\"}], \"id\": \"el168474369251024\", \"ydomain\": [-1.0, 1.0], \"collections\": [], \"xscale\": \"linear\", \"bbox\": [0.125, 0.099999999999999978, 0.77500000000000002, 0.80000000000000004]}], \"height\": 480.0, \"width\": 640.0, \"plugins\": [{\"type\": \"reset\"}, {\"enabled\": false, \"button\": true, \"type\": \"zoom\"}, {\"enabled\": false, \"button\": true, \"type\": \"boxzoom\"}, {\"type\": \"drag\", \"id\": \"el168474369249104pts\"}], \"data\": {\"data01\": [[1.764052345967664, -0.977277879876411], [0.4001572083672233, 0.9500884175255894], [0.9787379841057392, -0.1513572082976979], [2.240893199201458, -0.10321885179355784], [1.8675579901499675, 0.41059850193837233]]}, \"id\": \"el168474369249296\"});\n",
" }(mpld3);\n",
"}else if(typeof define === \"function\" && define.amd){\n",
" // require.js is available: use it to load d3/mpld3\n",
" require.config({paths: {d3: \"https://mpld3.github.io/js/d3.v3.min\"}});\n",
" require([\"d3\"], function(d3){\n",
" window.d3 = d3;\n",
" mpld3_load_lib(\"https://mpld3.github.io/js/mpld3.v0.2.js\", function(){\n",
" \n",
" mpld3.register_plugin(\"drag\", DragPlugin);\n",
" DragPlugin.prototype = Object.create(mpld3.Plugin.prototype);\n",
" DragPlugin.prototype.constructor = DragPlugin;\n",
" DragPlugin.prototype.requiredProps = [\"id\"];\n",
" DragPlugin.prototype.defaultProps = {};\n",
" function DragPlugin(fig, props){\n",
" mpld3.Plugin.call(this, fig, props);\n",
" mpld3.insert_css(\"#\" + fig.figid + \" path.dragging\",\n",
" {\"fill-opacity\": \"1.0 !important\",\n",
" \"stroke-opacity\": \"1.0 !important\"});\n",
" };\n",
"\n",
" DragPlugin.prototype.draw = function(){\n",
" var obj = mpld3.get_element(this.props.id);\n",
" \n",
" var drag = d3.behavior.drag()\n",
" .origin(function(d) { return {x:obj.ax.x(d[0]),\n",
" y:obj.ax.y(d[1])}; })\n",
" .on(\"dragstart\", dragstarted)\n",
" .on(\"drag\", dragged)\n",
" .on(\"dragend\", dragended);\n",
"\n",
" obj.elements()\n",
" .data(obj.offsets)\n",
" .style(\"cursor\", \"default\")\n",
" .attr(\"name\", \"redrawable\") // DIFFERENT\n",
" .call(drag);\n",
"\n",
" function dragstarted(d) {\n",
" d3.event.sourceEvent.stopPropagation();\n",
" d3.select(this).classed(\"dragging\", true);\n",
" // DIFFERENT. (This is crude, sorry.)\n",
" d[2] = d[0];\n",
" d[3] = d[1];\n",
" // DIFFERENT\n",
" }\n",
"\n",
" function dragged(d, i) {\n",
" d[0] = obj.ax.x.invert(d3.event.x);\n",
" d[1] = obj.ax.y.invert(d3.event.y);\n",
" d3.select(this)\n",
" .attr(\"transform\", \"translate(\" + [d3.event.x,d3.event.y] + \")\");\n",
" }\n",
"\n",
" function dragended(d) {\n",
" d3.select(this).classed(\"dragging\", false);\n",
" // DIFFERENT. (Below)\n",
" // Pass the axis object (obj.ax) too so we can transform\n",
" // from screen to data coordinates and back.\n",
" request_update(d[2], d[3], d[0], d[1], obj.ax);\n",
" }\n",
" };\n",
" \n",
"\n",
"// Handle Python output:\n",
"update_targets = function(out, ax){\n",
" console.log(\"UPDATE_TARGETS\");\n",
" var res = null;\n",
" var output = null;\n",
" if ((out.content.user_expressions === undefined) ||\n",
" (out.content.user_expressions.output === undefined)) {\n",
" return;\n",
" }\n",
" \n",
" output = out.content.user_expressions.output;\n",
" if ((output.status != \"ok\") || (output.data === undefined)) {\n",
" return;\n",
" }\n",
" \n",
" res = output.data[\"text/plain\"]; \n",
" console.log(res);\n",
" if (res == undefined)\n",
" return;\n",
" \n",
" new_positions = JSON.parse(res);\n",
" var redrawables = d3.selectAll(\"[name=redrawable]\");\n",
" console.log(\"redrawables: \" + redrawables);\n",
" redrawables.data(new_positions)\n",
" .attr(\"transform\", function(d) {\n",
" var x = ax.x(d[0]);\n",
" var y = ax.y(d[1]);\n",
" return \"translate(\" + [x, y] + \")\";\n",
" });\n",
"};\n",
"\n",
"\n",
"// Call the Python kernel from within the notebook:\n",
"request_update = function(x0, y0, x1, y1, ax){\n",
" console.log(\"REQUEST UPDATE\");\n",
" var kernel = IPython.notebook.kernel;\n",
" \n",
" custom_updater = function(python_response) {\n",
" update_targets(python_response, ax);\n",
" };\n",
" \n",
" var callbacks = {shell: {reply: custom_updater}};\n",
" if (!kernel) return;\n",
" args = x0 + \",\" + y0 + \",\" + x1 + \",\" + y1;\n",
" var msg_id = kernel.execute(\n",
" \"\", callbacks, {user_expressions:{output: \"update_data(\" + args + \")\"}});\n",
"};\n",
"\n",
" mpld3.draw_figure(\"fig_el1684743692492964849718921\", {\"axes\": [{\"xlim\": [0.0, 2.5], \"yscale\": \"linear\", \"axesbg\": \"#FFFFFF\", \"texts\": [{\"v_baseline\": \"auto\", \"h_anchor\": \"middle\", \"color\": \"#000000\", \"text\": \"Click and Drag with Callback: Jupyter + IPython + mpld3\", \"coordinates\": \"axes\", \"zorder\": 3, \"alpha\": 1, \"fontsize\": 18.0, \"position\": [0.5, 1.0144675925925926], \"rotation\": -0.0, \"id\": \"el168474411332304\"}], \"zoomable\": true, \"images\": [], \"xdomain\": [0.0, 2.5], \"ylim\": [-1.0, 1.0], \"paths\": [], \"sharey\": [], \"sharex\": [], \"axesbgalpha\": null, \"axes\": [{\"scale\": \"linear\", \"tickformat\": null, \"grid\": {\"gridOn\": false}, \"fontsize\": 12.0, \"position\": \"bottom\", \"nticks\": 6, \"tickvalues\": null}, {\"scale\": \"linear\", \"tickformat\": null, \"grid\": {\"gridOn\": false}, \"fontsize\": 12.0, \"position\": \"left\", \"nticks\": 5, \"tickvalues\": null}], \"lines\": [], \"markers\": [{\"edgecolor\": \"#000000\", \"facecolor\": \"#FF0000\", \"edgewidth\": 1, \"yindex\": 1, \"coordinates\": \"data\", \"zorder\": 2, \"markerpath\": [[[0.0, 25.0], [6.6300775000000005, 25.0], [12.989496769621336, 22.36584228970604], [17.67766952966369, 17.67766952966369], [22.36584228970604, 12.989496769621336], [25.0, 6.6300775000000005], [25.0, 0.0], [25.0, -6.6300775000000005], [22.36584228970604, -12.989496769621336], [17.67766952966369, -17.67766952966369], [12.989496769621336, -22.36584228970604], [6.6300775000000005, -25.0], [0.0, -25.0], [-6.6300775000000005, -25.0], [-12.989496769621336, -22.36584228970604], [-17.67766952966369, -17.67766952966369], [-22.36584228970604, -12.989496769621336], [-25.0, -6.6300775000000005], [-25.0, 0.0], [-25.0, 6.6300775000000005], [-22.36584228970604, 12.989496769621336], [-17.67766952966369, 17.67766952966369], [-12.989496769621336, 22.36584228970604], [-6.6300775000000005, 25.0], [0.0, 25.0]], [\"M\", \"C\", \"C\", \"C\", \"C\", \"C\", \"C\", \"C\", \"C\", \"Z\"]], \"alpha\": 0.5, \"xindex\": 0, \"data\": \"data01\", \"id\": \"el168474369249104pts\"}], \"id\": \"el168474369251024\", \"ydomain\": [-1.0, 1.0], \"collections\": [], \"xscale\": \"linear\", \"bbox\": [0.125, 0.099999999999999978, 0.77500000000000002, 0.80000000000000004]}], \"height\": 480.0, \"width\": 640.0, \"plugins\": [{\"type\": \"reset\"}, {\"enabled\": false, \"button\": true, \"type\": \"zoom\"}, {\"enabled\": false, \"button\": true, \"type\": \"boxzoom\"}, {\"type\": \"drag\", \"id\": \"el168474369249104pts\"}], \"data\": {\"data01\": [[1.764052345967664, -0.977277879876411], [0.4001572083672233, 0.9500884175255894], [0.9787379841057392, -0.1513572082976979], [2.240893199201458, -0.10321885179355784], [1.8675579901499675, 0.41059850193837233]]}, \"id\": \"el168474369249296\"});\n",
" });\n",
" });\n",
"}else{\n",
" // require.js not available: dynamically load d3 & mpld3\n",
" mpld3_load_lib(\"https://mpld3.github.io/js/d3.v3.min.js\", function(){\n",
" mpld3_load_lib(\"https://mpld3.github.io/js/mpld3.v0.2.js\", function(){\n",
" \n",
" mpld3.register_plugin(\"drag\", DragPlugin);\n",
" DragPlugin.prototype = Object.create(mpld3.Plugin.prototype);\n",
" DragPlugin.prototype.constructor = DragPlugin;\n",
" DragPlugin.prototype.requiredProps = [\"id\"];\n",
" DragPlugin.prototype.defaultProps = {};\n",
" function DragPlugin(fig, props){\n",
" mpld3.Plugin.call(this, fig, props);\n",
" mpld3.insert_css(\"#\" + fig.figid + \" path.dragging\",\n",
" {\"fill-opacity\": \"1.0 !important\",\n",
" \"stroke-opacity\": \"1.0 !important\"});\n",
" };\n",
"\n",
" DragPlugin.prototype.draw = function(){\n",
" var obj = mpld3.get_element(this.props.id);\n",
" \n",
" var drag = d3.behavior.drag()\n",
" .origin(function(d) { return {x:obj.ax.x(d[0]),\n",
" y:obj.ax.y(d[1])}; })\n",
" .on(\"dragstart\", dragstarted)\n",
" .on(\"drag\", dragged)\n",
" .on(\"dragend\", dragended);\n",
"\n",
" obj.elements()\n",
" .data(obj.offsets)\n",
" .style(\"cursor\", \"default\")\n",
" .attr(\"name\", \"redrawable\") // DIFFERENT\n",
" .call(drag);\n",
"\n",
" function dragstarted(d) {\n",
" d3.event.sourceEvent.stopPropagation();\n",
" d3.select(this).classed(\"dragging\", true);\n",
" // DIFFERENT. (This is crude, sorry.)\n",
" d[2] = d[0];\n",
" d[3] = d[1];\n",
" // DIFFERENT\n",
" }\n",
"\n",
" function dragged(d, i) {\n",
" d[0] = obj.ax.x.invert(d3.event.x);\n",
" d[1] = obj.ax.y.invert(d3.event.y);\n",
" d3.select(this)\n",
" .attr(\"transform\", \"translate(\" + [d3.event.x,d3.event.y] + \")\");\n",
" }\n",
"\n",
" function dragended(d) {\n",
" d3.select(this).classed(\"dragging\", false);\n",
" // DIFFERENT. (Below)\n",
" // Pass the axis object (obj.ax) too so we can transform\n",
" // from screen to data coordinates and back.\n",
" request_update(d[2], d[3], d[0], d[1], obj.ax);\n",
" }\n",
" };\n",
" \n",
"\n",
"// Handle Python output:\n",
"update_targets = function(out, ax){\n",
" console.log(\"UPDATE_TARGETS\");\n",
" var res = null;\n",
" var output = null;\n",
" if ((out.content.user_expressions === undefined) ||\n",
" (out.content.user_expressions.output === undefined)) {\n",
" return;\n",
" }\n",
" \n",
" output = out.content.user_expressions.output;\n",
" if ((output.status != \"ok\") || (output.data === undefined)) {\n",
" return;\n",
" }\n",
" \n",
" res = output.data[\"text/plain\"]; \n",
" console.log(res);\n",
" if (res == undefined)\n",
" return;\n",
" \n",
" new_positions = JSON.parse(res);\n",
" var redrawables = d3.selectAll(\"[name=redrawable]\");\n",
" console.log(\"redrawables: \" + redrawables);\n",
" redrawables.data(new_positions)\n",
" .attr(\"transform\", function(d) {\n",
" var x = ax.x(d[0]);\n",
" var y = ax.y(d[1]);\n",
" return \"translate(\" + [x, y] + \")\";\n",
" });\n",
"};\n",
"\n",
"\n",
"// Call the Python kernel from within the notebook:\n",
"request_update = function(x0, y0, x1, y1, ax){\n",
" console.log(\"REQUEST UPDATE\");\n",
" var kernel = IPython.notebook.kernel;\n",
" \n",
" custom_updater = function(python_response) {\n",
" update_targets(python_response, ax);\n",
" };\n",
" \n",
" var callbacks = {shell: {reply: custom_updater}};\n",
" if (!kernel) return;\n",
" args = x0 + \",\" + y0 + \",\" + x1 + \",\" + y1;\n",
" var msg_id = kernel.execute(\n",
" \"\", callbacks, {user_expressions:{output: \"update_data(\" + args + \")\"}});\n",
"};\n",
"\n",
" mpld3.draw_figure(\"fig_el1684743692492964849718921\", {\"axes\": [{\"xlim\": [0.0, 2.5], \"yscale\": \"linear\", \"axesbg\": \"#FFFFFF\", \"texts\": [{\"v_baseline\": \"auto\", \"h_anchor\": \"middle\", \"color\": \"#000000\", \"text\": \"Click and Drag with Callback: Jupyter + IPython + mpld3\", \"coordinates\": \"axes\", \"zorder\": 3, \"alpha\": 1, \"fontsize\": 18.0, \"position\": [0.5, 1.0144675925925926], \"rotation\": -0.0, \"id\": \"el168474411332304\"}], \"zoomable\": true, \"images\": [], \"xdomain\": [0.0, 2.5], \"ylim\": [-1.0, 1.0], \"paths\": [], \"sharey\": [], \"sharex\": [], \"axesbgalpha\": null, \"axes\": [{\"scale\": \"linear\", \"tickformat\": null, \"grid\": {\"gridOn\": false}, \"fontsize\": 12.0, \"position\": \"bottom\", \"nticks\": 6, \"tickvalues\": null}, {\"scale\": \"linear\", \"tickformat\": null, \"grid\": {\"gridOn\": false}, \"fontsize\": 12.0, \"position\": \"left\", \"nticks\": 5, \"tickvalues\": null}], \"lines\": [], \"markers\": [{\"edgecolor\": \"#000000\", \"facecolor\": \"#FF0000\", \"edgewidth\": 1, \"yindex\": 1, \"coordinates\": \"data\", \"zorder\": 2, \"markerpath\": [[[0.0, 25.0], [6.6300775000000005, 25.0], [12.989496769621336, 22.36584228970604], [17.67766952966369, 17.67766952966369], [22.36584228970604, 12.989496769621336], [25.0, 6.6300775000000005], [25.0, 0.0], [25.0, -6.6300775000000005], [22.36584228970604, -12.989496769621336], [17.67766952966369, -17.67766952966369], [12.989496769621336, -22.36584228970604], [6.6300775000000005, -25.0], [0.0, -25.0], [-6.6300775000000005, -25.0], [-12.989496769621336, -22.36584228970604], [-17.67766952966369, -17.67766952966369], [-22.36584228970604, -12.989496769621336], [-25.0, -6.6300775000000005], [-25.0, 0.0], [-25.0, 6.6300775000000005], [-22.36584228970604, 12.989496769621336], [-17.67766952966369, 17.67766952966369], [-12.989496769621336, 22.36584228970604], [-6.6300775000000005, 25.0], [0.0, 25.0]], [\"M\", \"C\", \"C\", \"C\", \"C\", \"C\", \"C\", \"C\", \"C\", \"Z\"]], \"alpha\": 0.5, \"xindex\": 0, \"data\": \"data01\", \"id\": \"el168474369249104pts\"}], \"id\": \"el168474369251024\", \"ydomain\": [-1.0, 1.0], \"collections\": [], \"xscale\": \"linear\", \"bbox\": [0.125, 0.099999999999999978, 0.77500000000000002, 0.80000000000000004]}], \"height\": 480.0, \"width\": 640.0, \"plugins\": [{\"type\": \"reset\"}, {\"enabled\": false, \"button\": true, \"type\": \"zoom\"}, {\"enabled\": false, \"button\": true, \"type\": \"boxzoom\"}, {\"type\": \"drag\", \"id\": \"el168474369249104pts\"}], \"data\": {\"data01\": [[1.764052345967664, -0.977277879876411], [0.4001572083672233, 0.9500884175255894], [0.9787379841057392, -0.1513572082976979], [2.240893199201458, -0.10321885179355784], [1.8675579901499675, 0.41059850193837233]]}, \"id\": \"el168474369249296\"});\n",
" })\n",
" });\n",
"}\n",
"</script>"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"fig, ax = plt.subplots()\n",
"np.random.seed(0)\n",
"x = np.random.normal(size=5)\n",
"y = np.random.normal(size=5)\n",
"points = ax.plot(x, y, 'or', alpha=0.5,\n",
" markersize=50, markeredgewidth=1)\n",
"ax.set_title(\"Click and Drag with Callback: Jupyter + IPython + mpld3\", fontsize=18)\n",
"\n",
"mpld3.plugins.connect(fig, DragPlugin(points[0]))\n",
"\n",
"mpld3.display()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## An updated version of Jake Vanderplas's 2013 blog post\n",
"\n",
"[The code][jakevdp] works for versions below IPython 2 and below, then commenters' posts\n",
"work for [IPython 2 notebooks][pastebin] and [IPython 3 notebooks][gist]. This code works for Juptyer 4 notebooks.\n",
"\n",
"Everywhere where something is different from the original is bookended with a\n",
"`// DIFFERENT` comment if multi-line, or else have `// DIFFERENT` inline.\n",
"\n",
"[jakevdp]: https://jakevdp.github.io/blog/2013/06/01/ipython-notebook-javascript-python-communication/\n",
"[pastebin]: http://pastebin.com/bQy7en73\n",
"[gist]: https://gist.github.com/sanbor/e9c8b57c5759d1ff382a"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from math import pi, sin\n",
"from IPython.core.display import HTML"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/html": [
"\n",
"<div style=\"background-color:gainsboro; border:solid black; width:600px; padding:20px;\">\n",
"Code: <input type=\"text\" id=\"code_input\" size=\"50\" height=\"2\" value=\"sin(pi / 2)\"><br>\n",
"Result: <input type=\"text\" id=\"result_output\" size=\"50\" value=\"1.0\"><br>\n",
"<button onclick=\"exec_code()\">Execute</button>\n",
"</div>\n",
"\n",
"<script type=\"text/Javascript\">\n",
" function handle_output(out){ // DIFFERENT\n",
" var res = null;\n",
" // DIFFERENT\n",
" var output = null;\n",
" if ((out.content.user_expressions !== undefined) &&\n",
" (out.content.user_expressions.output !== undefined)) {\n",
" output = out.content.user_expressions.output;\n",
" }\n",
" if ((output.status == \"ok\") && (output.data !== undefined)) {\n",
" res = output.data[\"text/plain\"];\n",
" } \n",
" // DIFFERENT\n",
" console.log(res);\n",
" document.getElementById(\"result_output\").value = res;\n",
" };\n",
" \n",
" function exec_code(){\n",
" var code_input = document.getElementById('code_input').value;\n",
" var kernel = Jupyter.notebook.kernel; // DIFFERENT (IPython deprecated)\n",
" var callbacks = {shell : {reply : new_handle_output}}; // DIFFERENT\n",
" document.getElementById(\"result_output\").value = \"\"; // clear output box\n",
" // DIFFERENT\n",
" var msg_id = kernel.execute(\n",
" \"\", // DIFFERENT (For return value, put the function call in user_expressions.)\n",
" callbacks,\n",
" {user_expressions:{output: \"wrapper(\" + code_input + \")\"}});\n",
" // DIFFERENT\n",
" console.log(\"button pressed\");\n",
" }\n",
"</script>\n"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Add an input form similar to what we saw above\n",
"input_form = \"\"\"\n",
"<div style=\"background-color:gainsboro; border:solid black; width:600px; padding:20px;\">\n",
"Code: <input type=\"text\" id=\"code_input\" size=\"50\" height=\"2\" value=\"sin(pi / 2)\"><br>\n",
"Result: <input type=\"text\" id=\"result_output\" size=\"50\" value=\"1.0\"><br>\n",
"<button onclick=\"exec_code()\">Execute</button>\n",
"</div>\n",
"\"\"\"\n",
"\n",
"# here the javascript has a function to execute the code\n",
"# within the input box, and a callback to handle the output.\n",
"javascript = \"\"\"\n",
"<script type=\"text/Javascript\">\n",
" function handle_output(out){ // DIFFERENT\n",
" var res = null;\n",
" // DIFFERENT\n",
" var output = null;\n",
" if ((out.content.user_expressions !== undefined) &&\n",
" (out.content.user_expressions.output !== undefined)) {\n",
" output = out.content.user_expressions.output;\n",
" }\n",
" if ((output.status == \"ok\") && (output.data !== undefined)) {\n",
" res = output.data[\"text/plain\"];\n",
" } \n",
" // DIFFERENT\n",
" console.log(res);\n",
" document.getElementById(\"result_output\").value = res;\n",
" };\n",
" \n",
" function exec_code(){\n",
" var code_input = document.getElementById('code_input').value;\n",
" var kernel = Jupyter.notebook.kernel; // DIFFERENT (IPython deprecated)\n",
" var callbacks = {shell : {reply : new_handle_output}}; // DIFFERENT\n",
" document.getElementById(\"result_output\").value = \"\"; // clear output box\n",
" // DIFFERENT\n",
" var msg_id = kernel.execute(\n",
" \"\", // DIFFERENT (For return value, put the function call in user_expressions.)\n",
" callbacks,\n",
" {user_expressions:{output: \"wrapper(\" + code_input + \")\"}});\n",
" // DIFFERENT\n",
" console.log(\"button pressed\");\n",
" }\n",
"</script>\n",
"\"\"\"\n",
"\n",
"def wrapper(code):\n",
" print \"in the wrapper\"\n",
" result = code\n",
" return \"it's ...{}...\".format(result)\n",
"\n",
"HTML(input_form + javascript)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.10"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment