Created
January 22, 2015 22:30
-
-
Save chriddyp/9827cb5086cfab8da092 to your computer and use it in GitHub Desktop.
A Collection of Plotly and IPython Widgets
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": { | |
"name": "", | |
"signature": "sha256:42de61e3f07fa85a3084dbe5ea254cc1397ab9727effdcf09a475c6e4168b359" | |
}, | |
"nbformat": 3, | |
"nbformat_minor": 0, | |
"worksheets": [ | |
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# Contour Plot Explorer\n", | |
"#### Plotly and IPython Notebook Widgets" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"from IPython.display import Image\n", | |
"Image(url='http://i.imgur.com/2Wbbo6l.gif')" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"html": [ | |
"<img src=\"http://i.imgur.com/2Wbbo6l.gif\"/>" | |
], | |
"metadata": {}, | |
"output_type": "pyout", | |
"prompt_number": 9, | |
"text": [ | |
"<IPython.core.display.Image at 0x107f8fa90>" | |
] | |
} | |
], | |
"prompt_number": 9 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"import plotly.plotly as py\n", | |
"from plotly.graph_objs import *\n", | |
"import plotly.tools as tls\n", | |
"\n", | |
"import json\n", | |
"import numpy as np\n", | |
"import math\n", | |
"from IPython.display import display" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"stream": "stderr", | |
"text": [ | |
"/Users/chris/anaconda/lib/python2.7/site-packages/pandas/computation/expressions.py:21: UserWarning: The installed version of numexpr 2.0.1 is not supported in pandas and will be not be used\n", | |
"The minimum supported version is 2.1\n", | |
"\n", | |
" \"version is 2.1\\n\".format(ver=ver), UserWarning)\n" | |
] | |
} | |
], | |
"prompt_number": 2 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"from plotly.widgets import GraphWidget" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"javascript": [ | |
"window.genUID = function() {\n", | |
" return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n", | |
" var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);\n", | |
" return v.toString(16);\n", | |
" });\n", | |
"};\n", | |
"\n", | |
"require([\"widgets/js/widget\"], function(WidgetManager){\n", | |
"\n", | |
" var GraphView = IPython.DOMWidgetView.extend({\n", | |
" render: function(){\n", | |
"\n", | |
" console.log('render!');\n", | |
"\n", | |
" var that = this;\n", | |
"\n", | |
" var graphId = window.genUID();\n", | |
" var loadingId = 'loading-'+graphId;\n", | |
"\n", | |
"\n", | |
" var _graph_url = that.model.get('_graph_url');\n", | |
"\n", | |
" // variable plotlyDomain in the case of enterprise\n", | |
" var url_parts = _graph_url.split('/');\n", | |
" var plotlyDomain = url_parts[0] + '//' + url_parts[2];\n", | |
"\n", | |
" if(!('plotlyDomains' in window)){\n", | |
" window.plotlyDomains = {};\n", | |
" }\n", | |
" window.plotlyDomains[graphId] = plotlyDomain;\n", | |
"\n", | |
" // Place IFrame in output cell div `$el`\n", | |
" that.$el.css('width', '100%');\n", | |
" that.$graph = $(['<iframe id=\"'+graphId+'\"',\n", | |
" 'src=\"'+_graph_url+'.embed\"',\n", | |
" 'seamless',\n", | |
" 'style=\"border: none;\"',\n", | |
" 'width=\"100%\"',\n", | |
" 'height=\"600\">',\n", | |
" '</iframe>'].join(' '));\n", | |
" that.$graph.appendTo(that.$el);\n", | |
"\n", | |
" that.$loading = $('<div id=\"'+loadingId+'\">Initializing...</div>')\n", | |
" .appendTo(that.$el);\n", | |
"\n", | |
" // initialize communication with the iframe\n", | |
" if(!('pingers' in window)){\n", | |
" window.pingers = {};\n", | |
" }\n", | |
"\n", | |
" window.pingers[graphId] = setInterval(function() {\n", | |
" that.graphContentWindow = $('#'+graphId)[0].contentWindow;\n", | |
" console.log('posting ping: ', plotlyDomain);\n", | |
" that.graphContentWindow.postMessage({task: 'ping'}, plotlyDomain);\n", | |
" }, 200);\n", | |
"\n", | |
" // Assign a message listener to the 'message' events\n", | |
" // from iframe's postMessage protocol.\n", | |
" // Filter the messages by iframe src so that the right message\n", | |
" // gets passed to the right widget\n", | |
" if(!('messageListeners' in window)){\n", | |
" window.messageListeners = {};\n", | |
" }\n", | |
"\n", | |
" window.messageListeners[graphId] = function(e) {\n", | |
" console.log('message: ', e.data);\n", | |
" if(_graph_url.indexOf(e.origin)>-1) {\n", | |
" var frame = document.getElementById(graphId);\n", | |
"\n", | |
" if(frame === null){\n", | |
" // frame doesn't exist in the dom anymore, clean up it's old event listener\n", | |
" window.removeEventListener('message', window.messageListeners[graphId]);\n", | |
" clearInterval(window.pingers[graphId]);\n", | |
" } else if(frame.contentWindow === e.source) {\n", | |
" // TODO: Stop event propagation, so each frame doesn't listen and filter\n", | |
" var frameContentWindow = $('#'+graphId)[0].contentWindow;\n", | |
" var message = e.data;\n", | |
"\n", | |
" if('pong' in message && message.pong) {\n", | |
" $('#loading-'+graphId).hide();\n", | |
" clearInterval(window.pingers[graphId]);\n", | |
" that.send({event: 'pong', graphId: graphId});\n", | |
" } else if (message.type==='hover' ||\n", | |
" message.type==='zoom' ||\n", | |
" message.type==='click' ||\n", | |
" message.type==='unhover') {\n", | |
"\n", | |
" // click and hover events contain all of the data in the traces,\n", | |
" // which can be a very large object and may take a ton of time\n", | |
" // to pass to the python backend. Strip out the data, and require\n", | |
" // the user to call get_figure if they need trace information\n", | |
" if(message.type !== 'zoom') {\n", | |
" for(var i in message.points) {\n", | |
" delete message.points[i].data;\n", | |
" }\n", | |
" }\n", | |
" that.send({event: message.type, message: message, graphId: graphId});\n", | |
" }\n", | |
" }\n", | |
" }\n", | |
" };\n", | |
"\n", | |
" window.removeEventListener('message', window.messageListeners[graphId]);\n", | |
" window.addEventListener('message', window.messageListeners[graphId]);\n", | |
"\n", | |
" },\n", | |
"\n", | |
" update: function() {\n", | |
" // Listen for messages from the graph widget in python\n", | |
" var jmessage = this.model.get('_message');\n", | |
" var message = JSON.parse(jmessage);\n", | |
"\n", | |
" // check for duplicate messages\n", | |
" if(!('messageIds' in window)){\n", | |
" window.messageIds = {};\n", | |
" }\n", | |
"\n", | |
" if(!(message.uid in window.messageIds)){\n", | |
" // message hasn't been received yet, do stuff\n", | |
" window.messageIds[message.uid] = true;\n", | |
"\n", | |
" var plot = $('#'+message.graphId)[0].contentWindow;\n", | |
" plot.postMessage(message, window.plotlyDomains[message.graphId]);\n", | |
" }\n", | |
"\n", | |
" return GraphView.__super__.update.apply(this);\n", | |
" }\n", | |
" });\n", | |
"\n", | |
" // Register the GraphView with the widget manager.\n", | |
" WidgetManager.register_widget_view('GraphView', GraphView);\n", | |
"});\n", | |
"\n", | |
"//@ sourceURL=graphWidget.js\n" | |
], | |
"metadata": {}, | |
"output_type": "display_data", | |
"text": [ | |
"<IPython.core.display.Javascript at 0x107f7ee10>" | |
] | |
} | |
], | |
"prompt_number": 3 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"contour_plot = GraphWidget('https://plot.ly/~bronsolo/63')\n", | |
"line_plot = GraphWidget('https://plot.ly/~chris/2150')\n", | |
"\n", | |
"display(contour_plot)\n", | |
"display(line_plot)" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [], | |
"prompt_number": 4 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"contour_fig = py.get_figure('https://plot.ly/~bronsolo/63')" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [], | |
"prompt_number": 5 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"def march(x0, y0, x1, y1):\n", | |
" '''\n", | |
" Return the closest path of integer coordinates \n", | |
" between (x0, y0) and (x1, y1)\n", | |
" '''\n", | |
" if abs(x1-x0) > abs(y1-y0): \n", | |
" if x1>x0:\n", | |
" x = range(int(x0), int(x1)+1)\n", | |
" else:\n", | |
" x = range(int(x0), int(x1)+1, -1)\n", | |
" y = []\n", | |
" tanth = (y1-y0)/(x1-x0)\n", | |
" for xi in x:\n", | |
" y.append(round(y0+(xi-x0)*tanth))\n", | |
" else:\n", | |
" if y1>y0:\n", | |
" y = range(int(y0), int(y1)+1)\n", | |
" else:\n", | |
" y = range(int(y0), int(y1)+1, -1)\n", | |
" x = []\n", | |
" tanth = (x1-x0)/(y1-y0)\n", | |
" for yi in y:\n", | |
" x.append(round(x0+(yi-y0)*tanth))\n", | |
"\n", | |
" return (x, y)\n", | |
"\n", | |
"class Responder(object):\n", | |
" '''\n", | |
" Stateful object that stores and computes the \n", | |
" elevation and distance data of the \n", | |
" line plot. The 'click' method is executed\n", | |
" on `click` events of the contour map.\n", | |
" '''\n", | |
" def __init__(self, data):\n", | |
" self.clicks = 0\n", | |
" self.x = []\n", | |
" self.y = []\n", | |
" self.xp = []\n", | |
" self.yp = []\n", | |
" self.z = []\n", | |
" self.d = []\n", | |
" self.data = data\n", | |
"\n", | |
" def append_point(self, point):\n", | |
" xp = point['pointNumber'][1]\n", | |
" yp = point['pointNumber'][0]\n", | |
" self.xp.append(xp)\n", | |
" self.yp.append(yp)\n", | |
"\n", | |
" if 'x' in self.data[0]:\n", | |
" x = self.data[0]['x'][xp]\n", | |
" else:\n", | |
" x = xp\n", | |
" if 'y' in self.data[0]:\n", | |
" y = self.data[0]['y'][xp]\n", | |
" else:\n", | |
" y = yp\n", | |
"\n", | |
" self.x.append(x)\n", | |
" self.y.append(y)\n", | |
" self.z.append(point['z'])\n", | |
" \n", | |
" if len(self.x) == 1:\n", | |
" self.d.append(0)\n", | |
" else:\n", | |
" self.d.append(math.sqrt((y-self.y[-2])**2+(x-self.x[-2])**2))\n", | |
" \n", | |
" \n", | |
" def click(self, contour_widget, click_obj):\n", | |
" point = click_obj[0]\n", | |
" if self.clicks==0 or self.clicks > 5:\n", | |
" self.__init__(self.data)\n", | |
" self.append_point(point)\n", | |
" else:\n", | |
" \n", | |
" (xpath, ypath) = march(self.xp[-1], self.yp[-1],\n", | |
" point['pointNumber'][1], point['pointNumber'][0])\n", | |
" \n", | |
" d = []\n", | |
" z = []\n", | |
" for i in range(1, len(xpath)):\n", | |
" xpi = xpath[i]\n", | |
" ypi = ypath[i]\n", | |
"\n", | |
" if 'x' in self.data[0]:\n", | |
" xi = self.data[0]['x'][xpi]\n", | |
" else:\n", | |
" xi = xpi\n", | |
" if 'y' in self.data[0]:\n", | |
" yi = self.data[0]['y'][ypi]\n", | |
" else:\n", | |
" yi = ypi\n", | |
" \n", | |
" self.d.append(self.d[-1]+math.sqrt((yi-self.y[-1])**2+(xi-self.x[-1])**2))\n", | |
" self.z.append(self.data[0]['z'][int(ypi)][int(xpi)])\n", | |
" self.x.append(xi)\n", | |
" self.y.append(yi)\n", | |
" self.xp.append(xpi)\n", | |
" self.yp.append(ypi)\n", | |
" \n", | |
"\n", | |
" self.clicks+=1\n", | |
" \n", | |
" line_plot.restyle({'x': [self.d], 'y': [self.z]})\n", | |
"\n", | |
" \n", | |
"r = Responder(contour_fig['data'])" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [], | |
"prompt_number": 6 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"contour_plot.on_click(r.click)" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [], | |
"prompt_number": 7 | |
}, | |
{ | |
"cell_type": "code", | |
"collapsed": false, | |
"input": [ | |
"# CSS styling within IPython notebook - feel free to re-use\n", | |
"from IPython.core.display import HTML\n", | |
"import urllib2\n", | |
"\n", | |
"HTML(urllib2.urlopen('http://bit.ly/1Bf5Hft').read())" | |
], | |
"language": "python", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"html": [ | |
"<style>\n", | |
"\n", | |
"html {\n", | |
" font-size: 62.5% !important; }\n", | |
"body {\n", | |
" font-size: 1.5em !important; /* currently ems cause chrome bug misinterpreting rems on body element */\n", | |
" line-height: 1.6 !important;\n", | |
" font-weight: 400 !important;\n", | |
" font-family: \"Raleway\", \"HelveticaNeue\", \"Helvetica Neue\", Helvetica, Arial, sans-serif !important;\n", | |
" color: #222 !important; }\n", | |
"\n", | |
"div{ border-radius: 0px !important; }\n", | |
"div.CodeMirror-sizer{ background: rgb(244, 244, 248) !important; }\n", | |
"div.input_area{ background: rgb(244, 244, 248) !important; }\n", | |
"\n", | |
"div.out_prompt_overlay:hover{ background: rgb(244, 244, 248) !important; }\n", | |
"div.input_prompt:hover{ background: rgb(244, 244, 248) !important; }\n", | |
"\n", | |
"h1, h2, h3, h4, h5, h6 {\n", | |
" color: #333 !important;\n", | |
" margin-top: 0 !important;\n", | |
" margin-bottom: 2rem !important;\n", | |
" font-weight: 300 !important; }\n", | |
"h1 { font-size: 4.0rem !important; line-height: 1.2 !important; letter-spacing: -.1rem !important;}\n", | |
"h2 { font-size: 3.6rem !important; line-height: 1.25 !important; letter-spacing: -.1rem !important; }\n", | |
"h3 { font-size: 3.0rem !important; line-height: 1.3 !important; letter-spacing: -.1rem !important; }\n", | |
"h4 { font-size: 2.4rem !important; line-height: 1.35 !important; letter-spacing: -.08rem !important; }\n", | |
"h5 { font-size: 1.8rem !important; line-height: 1.5 !important; letter-spacing: -.05rem !important; }\n", | |
"h6 { font-size: 1.5rem !important; line-height: 1.6 !important; letter-spacing: 0 !important; }\n", | |
"\n", | |
"@media (min-width: 550px) {\n", | |
" h1 { font-size: 5.0rem !important; }\n", | |
" h2 { font-size: 4.2rem !important; }\n", | |
" h3 { font-size: 3.6rem !important; }\n", | |
" h4 { font-size: 3.0rem !important; }\n", | |
" h5 { font-size: 2.4rem !important; }\n", | |
" h6 { font-size: 1.5rem !important; }\n", | |
"}\n", | |
"\n", | |
"p {\n", | |
" margin-top: 0 !important; }\n", | |
" \n", | |
"a {\n", | |
" color: #1EAEDB !important; }\n", | |
"a:hover {\n", | |
" color: #0FA0CE !important; }\n", | |
" \n", | |
"code {\n", | |
" padding: .2rem .5rem !important;\n", | |
" margin: 0 .2rem !important;\n", | |
" font-size: 90% !important;\n", | |
" white-space: nowrap !important;\n", | |
" background: #F1F1F1 !important;\n", | |
" border: 1px solid #E1E1E1 !important;\n", | |
" border-radius: 4px !important; }\n", | |
"pre > code {\n", | |
" display: block !important;\n", | |
" padding: 1rem 1.5rem !important;\n", | |
" white-space: pre !important; }\n", | |
" \n", | |
"button{ border-radius: 0px !important; }\n", | |
".navbar-inner{ background-image: none !important; }\n", | |
"select, textarea{ border-radius: 0px !important; }\n", | |
"\n", | |
"</style>" | |
], | |
"metadata": {}, | |
"output_type": "pyout", | |
"prompt_number": 8, | |
"text": [ | |
"<IPython.core.display.HTML at 0x107f8ff10>" | |
] | |
} | |
], | |
"prompt_number": 8 | |
} | |
], | |
"metadata": {} | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment