Last active
August 29, 2015 14:05
-
-
Save bollwyvl/8001ed62c6868301c26e to your computer and use it in GitHub Desktop.
Pretty linked widgets in the IPython notebook
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"metadata": { | |
"kernelspec": { | |
"codemirror_mode": { | |
"name": "python", | |
"version": 2 | |
}, | |
"display_name": "Python 2", | |
"language": "python", | |
"name": "python2" | |
}, | |
"name": "", | |
"signature": "sha256:64fe3fece86ae29eb3889ec515c58a654ba19e4418a3c7dc6acf33b61fcf0754" | |
}, | |
"nbformat": 3, | |
"nbformat_minor": 0, | |
"worksheets": [ | |
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# Wire Chase\n", | |
"A [fairly decent][mcnulty] way to bind together some IPython widgets for quick prototyping. Made of evil python `__magic__`.\n", | |
"\n", | |
"<a class=\"btn btn-primary\" href=\"#Example\">Go to example</a>\n", | |
"\n", | |
"> Bubbles: What the hell is that? \n", | |
"> McNulty: Baltimore knot. \n", | |
"> Bubbles: Baltimore knot? What the hell is a Baltimore knot?\n", | |
"> McNulty: I dunno, but it's never the same thing twice.\n", | |
"> _\u2013 The Wire: Undertow_\n", | |
"\n", | |
"[mcnulty]: https://31.media.tumblr.com/0e3bb59ff2e32ca57501384e9560876e/tumblr_inline_n0607sPY4K1sohsil.gif" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"from IPython.html import widgets\n", | |
"from IPython.utils import traitlets\n", | |
"from IPython.display import display" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [], | |
"prompt_number": 67 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"class Chase(object):\n", | |
" \"\"\"\n", | |
" An organizer for Wires.\n", | |
" \"\"\"\n", | |
" def __init__(self, **kwargs):\n", | |
" self.__dict__[\"_widgets\"] = dict([\n", | |
" (name, Wire(widget))\n", | |
" for name, widget in kwargs.items()\n", | |
" ])\n", | |
" \n", | |
" def __call__(self, wired, **kwargs):\n", | |
" \"\"\"\n", | |
" Decorator-style.\n", | |
" \"\"\"\n", | |
" def wrapper(func):\n", | |
" wire, trait = wired\n", | |
" setattr(wire, trait, (kwargs, func))\n", | |
" return wrapper\n", | |
" \n", | |
" def __dir__(self):\n", | |
" return self._widgets.keys()\n", | |
"\n", | |
" def __getattr__(self, name):\n", | |
" return self._widgets[name]\n", | |
"\n", | |
" def __setattr__(self, name, widget):\n", | |
" self._widgets[name] = Wire(widget)" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [], | |
"prompt_number": 57 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"class Wire(object):\n", | |
" \"\"\"\n", | |
" A wrapper for a widget that lets us talk about its traits easily.\n", | |
" \"\"\"\n", | |
" def __init__(self, widget):\n", | |
" self.__dict__[\"_widget\"] = widget\n", | |
" \n", | |
" def __dir__(self):\n", | |
" return self._widget.trait_names()\n", | |
" \n", | |
" def __getattr__(self, name):\n", | |
" return (self, name)\n", | |
" \n", | |
" def __setattr__(self, name, value):\n", | |
" if hasattr(value[-1], '__call__'):\n", | |
" fn = value[-1]\n", | |
" sources = value[:-1]\n", | |
" \n", | |
" if len(sources) == 1 and isinstance(sources[0], dict):\n", | |
" sources = sources[0]\n", | |
" def _change(changed, old, new):\n", | |
" args = dict([(arg, getattr(t[0]._widget, t[1]))\n", | |
" for arg, t in sources.items()])\n", | |
" setattr(self._widget, name, fn(**args))\n", | |
"\n", | |
" [t[0]._widget.on_trait_change(_change, t[1])\n", | |
" for arg, t in sources.items()]\n", | |
" else:\n", | |
" def _change(name, old, new):\n", | |
" args = [getattr(w._widget, t) for w, t in sources]\n", | |
" setattr(self._widget, name, fn(*args))\n", | |
"\n", | |
" [w._widget.on_trait_change(_change, t) for w, t in sources]\n", | |
" else:\n", | |
" w, t = value\n", | |
" w = w._widget\n", | |
" traitlets.link((self._widget, name), (w, t))" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [], | |
"prompt_number": 58 | |
}, | |
{ | |
"cell_type": "heading", | |
"level": 1, | |
"metadata": {}, | |
"source": [ | |
"Example" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"First, we set up some widgets." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"texts = dict([\n", | |
" (i, widgets.TextWidget(description=i, value=i))\n", | |
" for i in \"abcdef\"\n", | |
"])\n", | |
"wire = Chase(**texts)\n", | |
"widgets.ContainerWidget(children=sorted(texts.values()))" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [], | |
"prompt_number": 59 | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Link with assignment" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Setting one trait pointer to another will make one value equal another, bidirectionally." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"wire.a.value = wire.b.value" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [], | |
"prompt_number": 60 | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Now try changing the `a` and `b` boxes." | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## A lambda approaches!\n", | |
"If one trait is derived from another trait, give the trait that changes and a transformer. They will be linked, but only forwards." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"def but_reversed(b):\n", | |
" return b[::-1]\n", | |
"\n", | |
"wire.c.value = wire.b.value, but_reversed" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [], | |
"prompt_number": 61 | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"This is just a tuple, and any number of traits can be watched." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"def joined_and_reversed(a, b):\n", | |
" return a + b[::-1]\n", | |
"\n", | |
"wire.d.value = wire.a.value, wire.b.value, joined_and_reversed" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [], | |
"prompt_number": 62 | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Since this happens kinda often, you can also use a decorator form:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"@wire(wire.e.value, a=wire.a.value)\n", | |
"def thrice(a):\n", | |
" return a * 3" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [], | |
"prompt_number": 63 | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Secretly, this is using a dictionary instead of a tuple in assignment, or:" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"def joined_differently(a, c):\n", | |
" return c + a\n", | |
"wire.f.value = dict(a=wire.a.value, c=wire.c.value), joined_differently" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [], | |
"prompt_number": 64 | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Autocompleter\n", | |
"We proxy through what's connected, so you can autocomplete to see what widgets are wired." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"dir(wire)" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"metadata": {}, | |
"output_type": "pyout", | |
"prompt_number": 65, | |
"text": [ | |
"['a', 'b', 'c', 'd', 'e', 'f']" | |
] | |
} | |
], | |
"prompt_number": 65 | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Also the traits on a particular widget." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"dir(wire.a)" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"metadata": {}, | |
"output_type": "pyout", | |
"prompt_number": 66, | |
"text": [ | |
"['_css',\n", | |
" '_display_callbacks',\n", | |
" '_model_name',\n", | |
" '_msg_callbacks',\n", | |
" '_property_lock',\n", | |
" '_send_state_lock',\n", | |
" '_states_to_send',\n", | |
" '_view_name',\n", | |
" 'comm',\n", | |
" 'config',\n", | |
" 'description',\n", | |
" 'disabled',\n", | |
" 'keys',\n", | |
" 'log',\n", | |
" 'msg_throttle',\n", | |
" 'parent',\n", | |
" 'placeholder',\n", | |
" 'value',\n", | |
" 'visible']" | |
] | |
} | |
], | |
"prompt_number": 66 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [], | |
"prompt_number": 66 | |
} | |
], | |
"metadata": {} | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment