Skip to content

Instantly share code, notes, and snippets.

@jakevdp
Last active February 11, 2024 00:16
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jakevdp/289be7d8c6e3a1248921 to your computer and use it in GitHub Desktop.
Save jakevdp/289be7d8c6e3a1248921 to your computer and use it in GitHub Desktop.
D3/IPython widget example

D3 IPython Widget Example

A simple example of two-way communication between IPython notebook and javascript, using a D3 widget.

View this notebook on nbviewer, but be aware that it won't work without being connected to an IPython kernel!

It's better to download the notebook itself and open it with IPython notebook.

Display the source blob
Display the rendered blob
Raw
{
"metadata": {
"name": "",
"signature": "sha256:78b35e783e1039e34c42201f3e39c38a4ab4aac5e0194fbf70732afb5529db32"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Simple IPython Widgets + D3 Example"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"*By Jake Vanderplas, May 2014*\n",
"\n",
"This is the result of an afternoon playing around with the IPython widgets framework, and learning how to do two-directional callbacks between the Python kernel and the Javascript frontend.\n",
"\n",
"Note that this requires a Python kernel to run; that is, if you're looking at this on nbviewer it won't work!\n",
"\n",
"Here's the summary:\n",
"\n",
"- We create a ``CircleView`` widget in Python, and a corresponding ``CircleView`` object in Javascript.\n",
"- When the mouse hovers over the circle, we fire off a series of events\n",
" - The javascript event sends a message to the Python kernel, saying a mouseover has happened.\n",
" - This triggers the generation of some random numbers in Python\n",
" - These numbers are then sent back to the JS frontend, updating the diagram\n",
" \n",
"Below is the code that makes it all happen."
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Python-side Widget Object"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from IPython.html import widgets\n",
"from IPython.utils.traitlets import Unicode\n",
"\n",
"\n",
"class CircleView(widgets.DOMWidget):\n",
" _view_name = Unicode('CircleView', sync=True)\n",
"\n",
" def __init__(self, *pargs, **kwargs):\n",
" widgets.DOMWidget.__init__(self, *pargs, **kwargs)\n",
" self._handlers = widgets.CallbackDispatcher()\n",
" self.on_msg(self._handle_my_msg)\n",
"\n",
" def _ipython_display_(self, *pargs, **kwargs):\n",
" widgets.DOMWidget._ipython_display_(self, *pargs, **kwargs)\n",
"\n",
" def _handle_my_msg(self, _, content):\n",
" \"\"\"handle a message from the frontent\"\"\"\n",
" if content.get('event', '') == 'mouseover':\n",
" self._handlers(self)\n",
"\n",
" def on_mouseover(self, callback):\n",
" \"\"\"Register a callback at mouseover\"\"\"\n",
" self._handlers.register_callback(callback)"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 1
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Javascript side of the widget object"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%%javascript\n",
"\n",
"require([\"//cdnjs.cloudflare.com/ajax/libs/d3/3.4.1/d3.min.js\",\n",
" \"widgets/js/widget\"], function(d3, WidgetManager){\n",
"\n",
" var CircleView = IPython.DOMWidgetView.extend({\n",
"\n",
" render: function(){\n",
" this.guid = 'circle' + IPython.utils.uuid();\n",
" this.setElement($('<div />', {id: this.guid}));\n",
" \n",
" this.model.on('msg:custom', this.on_msg, this);\n",
" this.has_drawn = false;\n",
"\n",
" // Wait for element to be added to the DOM\n",
" var that = this;\n",
" setTimeout(function() {\n",
" that.update();\n",
" }, 0);\n",
" },\n",
"\n",
" update: function(){\n",
" var that = this;\n",
"\n",
" if (!this.has_drawn) {\n",
" this.has_drawn = true;\n",
"\n",
" this.svg = d3.select(\"#\" + this.guid).append(\"svg\")\n",
" .attr(\"width\", 200)\n",
" .attr(\"height\", 200);\n",
"\n",
" this.circle = this.svg.append(\"circle\")\n",
" .attr(\"cx\", 100)\n",
" .attr(\"cy\", 100)\n",
" .attr(\"r\", 20)\n",
" .style(\"fill\", \"red\")\n",
" .style(\"fill-opacity\", 0.5)\n",
" .on(\"mouseenter\", function(){that.send({event:'mouseover'})});\n",
" }\n",
" return CircleView.__super__.update.apply(this);\n",
" },\n",
"\n",
" on_msg: function(attrs){\n",
" this.circle.transition().attr(attrs).style(attrs);\n",
" }\n",
" });\n",
" WidgetManager.register_widget_view('CircleView', CircleView);\n",
"})"
],
"language": "python",
"metadata": {},
"outputs": [
{
"javascript": [
"\n",
"require([\"//cdnjs.cloudflare.com/ajax/libs/d3/3.4.1/d3.min.js\",\n",
" \"widgets/js/widget\"], function(d3, WidgetManager){\n",
"\n",
" var CircleView = IPython.DOMWidgetView.extend({\n",
"\n",
" render: function(){\n",
" this.guid = 'circle' + IPython.utils.uuid();\n",
" this.setElement($('<div />', {id: this.guid}));\n",
" \n",
" this.model.on('msg:custom', this.on_msg, this);\n",
" this.has_drawn = false;\n",
"\n",
" // Wait for element to be added to the DOM\n",
" var that = this;\n",
" setTimeout(function() {\n",
" that.update();\n",
" }, 0);\n",
" },\n",
"\n",
" update: function(){\n",
" var that = this;\n",
"\n",
" if (!this.has_drawn) {\n",
" this.has_drawn = true;\n",
"\n",
" this.svg = d3.select(\"#\" + this.guid).append(\"svg\")\n",
" .attr(\"width\", 200)\n",
" .attr(\"height\", 200);\n",
"\n",
" this.circle = this.svg.append(\"circle\")\n",
" .attr(\"cx\", 100)\n",
" .attr(\"cy\", 100)\n",
" .attr(\"r\", 20)\n",
" .style(\"fill\", \"red\")\n",
" .style(\"fill-opacity\", 0.5)\n",
" .on(\"mouseenter\", function(){that.send({event:'mouseover'})});\n",
" }\n",
" return CircleView.__super__.update.apply(this);\n",
" },\n",
"\n",
" on_msg: function(attrs){\n",
" this.circle.transition().attr(attrs).style(attrs);\n",
" }\n",
" });\n",
" WidgetManager.register_widget_view('CircleView', CircleView);\n",
"})"
],
"metadata": {},
"output_type": "display_data",
"text": [
"<IPython.core.display.Javascript object>"
]
}
],
"prompt_number": 2
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Creating the object and adding a callback"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from IPython.display import display\n",
"from random import randint\n",
"\n",
"colors = ['blue', 'green', 'orange', 'black', 'magenta', 'red']\n",
"\n",
"def update_circle(view):\n",
" view.send({\"cx\": randint(30, 170),\n",
" \"cy\": randint(30, 170),\n",
" \"r\": randint(10, 30),\n",
" \"fill\": colors[randint(0, 5)]})\n",
"\n",
"circle = CircleView()\n",
"circle.on_mouseover(update_circle)\n",
"\n",
"print(\"Try to catch the circle!\")\n",
"display(circle)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Try to catch the circle!\n"
]
}
],
"prompt_number": 5
}
],
"metadata": {}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment