├── .DS_Store
├── .ipynb_checkpoints
└── Data Science Meetup-checkpoint.ipynb
├── 18271-good-news-everyone-i-was-just-kidding-professor-farnsworth-wallpaper-1280x1280-1-750x410.jpg
├── Data Science Meetup.html
├── Data Science Meetup.ipynb
├── README
├── bokeh_example_1.py
├── bokeh_example_2.py
├── bokeh_example_3.py
├── bqplot_example_1.py
├── bqplot_example_2.py
├── bqplot_example_3.py
├── bqplot_example_4.py
├── c2fo_python.png
├── data_files
├── country_codes.csv
└── gdp_per_capita.csv
├── input_output_control_large.png
├── public
└── .DS_Store
├── pygal_example_1.py
├── pygal_example_2.py
├── pygal_example_3.py
├── resized_chemistry-cat.jpg
├── sample_script_10.py
├── spyre_script_1.py
├── spyre_script_10.py
├── spyre_script_2.py
├── spyre_script_3.py
├── spyre_script_4.py
├── spyre_script_5.py
├── spyre_script_6.py
├── spyre_script_7.py
├── spyre_script_8.py
├── spyre_script_9.py
└── thats-cool-i8t8sz.jpg
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pm8k/dataspyre_tutorial/8ae564967337be2f293cbf30f35cc8a5f9cfe852/.DS_Store
--------------------------------------------------------------------------------
/18271-good-news-everyone-i-was-just-kidding-professor-farnsworth-wallpaper-1280x1280-1-750x410.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pm8k/dataspyre_tutorial/8ae564967337be2f293cbf30f35cc8a5f9cfe852/18271-good-news-everyone-i-was-just-kidding-professor-farnsworth-wallpaper-1280x1280-1-750x410.jpg
--------------------------------------------------------------------------------
/Data Science Meetup.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | ""
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "# Leveraging Python for Data Visibility - Maximizing the view into our data by creating a lightweight stand alone webapp and integrating interactive plotting libraries"
15 | ]
16 | },
17 | {
18 | "cell_type": "markdown",
19 | "metadata": {},
20 | "source": [
21 | "In this talk we aim to help python users maximize their information gain with some easy to use libraries, specifically Spyre, Bokeh, and Pygal"
22 | ]
23 | },
24 | {
25 | "cell_type": "markdown",
26 | "metadata": {},
27 | "source": [
28 | "---"
29 | ]
30 | },
31 | {
32 | "cell_type": "markdown",
33 | "metadata": {},
34 | "source": [
35 | "## About me\n",
36 | "My name is Matthew Russell, and I'm one of the Data Scientists here at C2FO, the world's market for working capital. After earning my BS and MS in Physics, I left academia to find something new, and stumbled upon the world of data science. After using python in my graduate research, I naturally gravitated to the libraries that have been making significant headway for data science in the past few years, and I will be sharing and presenting several of these libraries to you today."
37 | ]
38 | },
39 | {
40 | "cell_type": "markdown",
41 | "metadata": {},
42 | "source": [
43 | "## Contact Info\n",
44 | " - Email - matthew.russell@c2fo.com\n",
45 | " - Github - pm8k"
46 | ]
47 | },
48 | {
49 | "cell_type": "markdown",
50 | "metadata": {},
51 | "source": [
52 | "--------\n"
53 | ]
54 | },
55 | {
56 | "cell_type": "markdown",
57 | "metadata": {},
58 | "source": [
59 | "# Topic Headers\n",
60 | " 1. How as Data Scientists do we access our Data?\n",
61 | " - Introduction to Data Spyre: What is it?\n",
62 | " - Alright, this looks Cool! How do I make my own Webapp?\n",
63 | " - How do I make my Cool webapp even Cooler?\n",
64 | " - Let's get Interactive with some plotting libraries!\n",
65 | " - The Grand Finale: Interactive plots in Data Spyre!\n",
66 | " - Questions?\n"
67 | ]
68 | },
69 | {
70 | "cell_type": "markdown",
71 | "metadata": {},
72 | "source": [
73 | "--------"
74 | ]
75 | },
76 | {
77 | "cell_type": "markdown",
78 | "metadata": {},
79 | "source": [
80 | "# 1. How as Data Scientists do we access our Data?\n",
81 | " - What are some of the common tools you work with visualize your data?\n",
82 | " - What problems challenge you with your workflow?"
83 | ]
84 | },
85 | {
86 | "cell_type": "markdown",
87 | "metadata": {},
88 | "source": [
89 | "## Our talk will help to address the following questions:\n",
90 | "- How can I provide the best view into my data? \n",
91 | "- How can I easily introduce aggregations and filtering into my workflow? \n",
92 | "- How do I share my data with decision makers?\n",
93 | "- How can I maximize my information within one visualization?"
94 | ]
95 | },
96 | {
97 | "cell_type": "markdown",
98 | "metadata": {},
99 | "source": [
100 | "---"
101 | ]
102 | },
103 | {
104 | "cell_type": "markdown",
105 | "metadata": {},
106 | "source": [
107 | ""
108 | ]
109 | },
110 | {
111 | "cell_type": "markdown",
112 | "metadata": {},
113 | "source": [
114 | "## Good News! Python has some new libraries to help us in our just cause in the pursuit of data!\n",
115 | "\n",
116 | "### In this talk I'm going to introduce Data Spyre, a lightweight interactive web application built on python. Inspired by R's Shiny, Data Spyre creates an easy to use framework to customize how you need to look at your data.\n",
117 | "\n",
118 | "### I will also be covering Pygal, Bokeh, and BQplot, a series of libraries to add some interactivity to your plotting toolkit."
119 | ]
120 | },
121 | {
122 | "cell_type": "markdown",
123 | "metadata": {},
124 | "source": [
125 | "## Here is the location of all the libraries we will be discussing\n",
126 | " - Data Spyre - https://github.com/adamhajari/spyre\n",
127 | " - Bokeh - http://bokeh.pydata.org/en/latest/\n",
128 | " - Pygal - http://www.pygal.org/en/latest/\n",
129 | " - BQPlot - https://github.com/bloomberg/bqplot"
130 | ]
131 | },
132 | {
133 | "cell_type": "markdown",
134 | "metadata": {},
135 | "source": [
136 | "-------"
137 | ]
138 | },
139 | {
140 | "cell_type": "markdown",
141 | "metadata": {},
142 | "source": [
143 | "# 2. Introduction to Data Spyre: What is it?"
144 | ]
145 | },
146 | {
147 | "cell_type": "markdown",
148 | "metadata": {},
149 | "source": [
150 | "### Data Spyre is a python implementation of a web facing application inspired by R's Shiny. It is structured to cover most of the front end and back end work to standing up a web application, only requiring you to define what to plot and what data to show. In order to accomplish this, data spyre leverages the power of 4 well established libraries:\n",
151 | "\n",
152 | " - Cherrypy - Python Web application library to handle front and back end (Webapp)\n",
153 | " - Jinja2 - Python Templating library based off of Django's templates (HTML and Javascript)\n",
154 | " - Pandas - Python library for data aggregation, filtering, and plotting (Data Manipulation)\n",
155 | " - Matplotlib - Python's plotting library (Plots)"
156 | ]
157 | },
158 | {
159 | "cell_type": "markdown",
160 | "metadata": {},
161 | "source": [
162 | "## Script 1"
163 | ]
164 | },
165 | {
166 | "cell_type": "code",
167 | "execution_count": null,
168 | "metadata": {
169 | "collapsed": true
170 | },
171 | "outputs": [],
172 | "source": [
173 | "from spyre import server\n",
174 | "\n",
175 | "class SimpleApp(server.App):\n",
176 | " title = \"Simple App\"\n",
177 | " inputs = [{ \"type\":\"text\",\n",
178 | " \"key\":\"words\",\n",
179 | " \"label\": \"write here\",\n",
180 | " \"value\":\"hello world\"}]\n",
181 | " outputs = [{\"type\":\"html\",\n",
182 | " \"id\":\"some_html\",\n",
183 | " \"control_id\":\"button1\"}]\n",
184 | " controls = [{\"type\":\"button\",\n",
185 | " \"label\":\"press to update\",\n",
186 | " \"id\":\"button1\"}]\n",
187 | " def getHTML(self, params):\n",
188 | " words = params['words']\n",
189 | " return \"Here are some words: %s\"%words\n",
190 | "\n",
191 | "app = SimpleApp()\n",
192 | "#app.launch()"
193 | ]
194 | },
195 | {
196 | "cell_type": "markdown",
197 | "metadata": {},
198 | "source": [
199 | "### With just a few lines of code, we have a webapp we can launch from our browser. While we aren't looking at any data in this example, we have all of the important elements for our web application:\n",
200 | " - An input to help control what our web app displays\n",
201 | " - A control mechanism that updates the text on the page\n",
202 | " - A function that creates the output onto the webapp"
203 | ]
204 | },
205 | {
206 | "cell_type": "markdown",
207 | "metadata": {},
208 | "source": [
209 | "### Let's look at something with some data... how about a simple sine wave?"
210 | ]
211 | },
212 | {
213 | "cell_type": "markdown",
214 | "metadata": {},
215 | "source": [
216 | "## Script 2"
217 | ]
218 | },
219 | {
220 | "cell_type": "code",
221 | "execution_count": null,
222 | "metadata": {
223 | "collapsed": true
224 | },
225 | "outputs": [],
226 | "source": [
227 | "from spyre import server\n",
228 | "\n",
229 | "import matplotlib.pyplot as plt\n",
230 | "import numpy as np\n",
231 | "\n",
232 | "class SimpleSineApp(server.App):\n",
233 | " title = \"Simple Sine App\"\n",
234 | " inputs = [{ \"type\":\"text\",\n",
235 | " \"key\":\"freq\",\n",
236 | " \"value\":5,\n",
237 | " \"label\":\"frequency\",\n",
238 | " \"action_id\":\"sine_wave_plot\"}]\n",
239 | " outputs = [{\"type\":\"plot\",\n",
240 | " \"id\":\"sine_wave_plot\"}]\n",
241 | " controls = [{\"type\":\"HIDDEN\",\n",
242 | " \"id\":\"sine_wave_plot\"}]\n",
243 | " def getPlot(self, params):\n",
244 | " f = float(params['freq'])\n",
245 | " x = np.arange(0,2*np.pi,np.pi/150)\n",
246 | " y = np.sin(f*x)\n",
247 | " fig = plt.figure()\n",
248 | " splt1 = fig.add_subplot(1,1,1)\n",
249 | " splt1.plot(x,y)\n",
250 | " return fig\n",
251 | "\n",
252 | "if __name__ == '__main__':\n",
253 | " app = SimpleSineApp()\n",
254 | " #app.launch()"
255 | ]
256 | },
257 | {
258 | "cell_type": "markdown",
259 | "metadata": {},
260 | "source": [
261 | "### Similar to the previous app, we have defined an input, a control, and a function to generate output onto our webapp, all in just a few lines of code! "
262 | ]
263 | },
264 | {
265 | "cell_type": "markdown",
266 | "metadata": {},
267 | "source": [
268 | "----------------------------"
269 | ]
270 | },
271 | {
272 | "cell_type": "markdown",
273 | "metadata": {},
274 | "source": [
275 | "# 3. Alright, this looks Cool! How do I make my own?"
276 | ]
277 | },
278 | {
279 | "cell_type": "markdown",
280 | "metadata": {},
281 | "source": [
282 | "### So far we've seen a couple of these webapps in action, so let's go down the rabbit hole a bit further and see what's actually going on under the hood of our Data Spyre class. Let's look again at the Sine App we just had up"
283 | ]
284 | },
285 | {
286 | "cell_type": "markdown",
287 | "metadata": {},
288 | "source": [
289 | "## Script 3"
290 | ]
291 | },
292 | {
293 | "cell_type": "code",
294 | "execution_count": null,
295 | "metadata": {
296 | "collapsed": true
297 | },
298 | "outputs": [],
299 | "source": [
300 | "from spyre import server\n",
301 | "\n",
302 | "import matplotlib.pyplot as plt\n",
303 | "import numpy as np\n",
304 | "\n",
305 | "class SimpleSineApp(server.App):\n",
306 | " title = \"Simple Sine App\"\n",
307 | " inputs = [{ \"type\":\"text\",\n",
308 | " \"key\":\"freq\",\n",
309 | " \"value\":5,\n",
310 | " \"label\":\"frequency\",\n",
311 | " \"action_id\":\"sine_wave_plot\"}]\n",
312 | " outputs = [{\"type\":\"plot\",\n",
313 | " \"id\":\"sine_wave_plot\"}]\n",
314 | " controls = [{\"type\":\"HIDDEN\",\n",
315 | " \"id\":\"sine_wave_plot\"}]\n",
316 | " def getPlot(self, params):\n",
317 | " f = float(params['freq'])\n",
318 | " x = np.arange(0,2*np.pi,np.pi/150)\n",
319 | " y = np.sin(f*x)\n",
320 | " fig = plt.figure()\n",
321 | " splt1 = fig.add_subplot(1,1,1)\n",
322 | " splt1.plot(x,y)\n",
323 | " return fig\n",
324 | "\n",
325 | "if __name__ == '__main__':\n",
326 | " app = SimpleSineApp()\n",
327 | " #app.launch()"
328 | ]
329 | },
330 | {
331 | "cell_type": "markdown",
332 | "metadata": {},
333 | "source": [
334 | "### The main wrapper for our web application is a python class, which inherits from dataspyre's server.App class. Don't worry too much of the details of the class inheritance, other than the structure of your class objects needs to be correctly defined for your webapp to run smoothly. So we start with naming our class, and pass in server.App as the inherited class. Now within the class we need to define a few parameters that is the key to any app:"
335 | ]
336 | },
337 | {
338 | "cell_type": "markdown",
339 | "metadata": {},
340 | "source": [
341 | "### title \n",
342 | "A string that represents the name of your webapp. In our case, it is \"Simple Sine App\". Only one title is assigned per webapp"
343 | ]
344 | },
345 | {
346 | "cell_type": "markdown",
347 | "metadata": {},
348 | "source": [
349 | "### inputs\n",
350 | "The inputs variable is a python list of dictionaries, with each dictionary representing another input variable. In our example, we have one input: frequency. Let's breakdown each of the elements quickly that make up an input:\n",
351 | " 1. label - The text name to be displayed for your key\n",
352 | " 2. type - The type of input to be created. In our webapp, we have a text box, which is created as type 'text'. We have also seen type 'slider' in our previous example.\n",
353 | " 3. value - The default value for the webapp upon initial load. When the value is changed by the user, the dictionary reflects this change\n",
354 | " 4. key - The key is used as the name reference in the data and plotting functions. They are stored in class dictionary called 'params' keyed by this key value. \n",
355 | " 5. Action Id - The Action Id is used to tie which control to use, which we will be going over next."
356 | ]
357 | },
358 | {
359 | "cell_type": "markdown",
360 | "metadata": {},
361 | "source": [
362 | "### controls\n",
363 | "The control variable is also a list of python dictionaries. Each dictionary contains one control to tie a set of inputs and outputs together. What exactly does the control do? The control updates your data based on any changes you make to the input. When there are multiple inputs and/or multiple outputs, multiple controls allow you to refresh specific points in your data. The control has three properties:\n",
364 | " 1. type - defines whether a control is 'hidden' or is a 'button'. Hidden allows the output to update upon change of input, while a button must be pressed to update your changes.\n",
365 | "\n",
366 | " 2. label - if the control type is a button, the label will be the text on the button.\n",
367 | "\n",
368 | " 3. id - the control id to identify which inputs and outputs are controlled by this id. Any inputs with the 'action_id' the same as this id trigger this control, and all outputs with an action_id with this id will be refreshed with new data."
369 | ]
370 | },
371 | {
372 | "cell_type": "markdown",
373 | "metadata": {},
374 | "source": [
375 | "### outputs\n",
376 | "And once again, our output variable is defined as a list of dictionaries, with each dicitonary tied to a single output. In our webapp, an output is a data object being returned to our webapp to be displayed. Lets dive back in to what makes up an output entry:\n",
377 | " 1. type - This tells the webapp which kind of data object is being returned as an output. The main three types of output are table (pandas dataframe), plot (matplotlib figure object), or html.\n",
378 | "\n",
379 | " 2. control_id - Once again, this id ties back to a control we've already defined. This output will update when the specified control is triggered by the output.\n",
380 | "\n",
381 | " 3. tab - The name of the tab to display your output. So far we've only demonstrated a single output webapp, but when you have multiple outputs, you can specify what location each output goes to. We'll dive a bit more into this later.\n",
382 | "\n",
383 | " 4. on page load - a boolean flag to tell the webapp whether to load the page upon connecting to the web server. Generally you will want to set this to true.\n",
384 | "\n",
385 | " 5. id - a seemingly deceptive trait. Each output requires an id name which will be parsed to act as an identifier for the output. However, the true functionality comes with function overloading. Again, let's get a simple app up and running and we can dive more into the customization we can introduce.\n",
386 | " "
387 | ]
388 | },
389 | {
390 | "cell_type": "markdown",
391 | "metadata": {},
392 | "source": [
393 | ""
394 | ]
395 | },
396 | {
397 | "cell_type": "markdown",
398 | "metadata": {},
399 | "source": [
400 | "### Sweet! All of our inputs and outputs are created, and we know how to control everything! Now all we need to do is define where we get our data from. Data Spyre has built in function types to handle our main 3 output types: getData, getPlot, getHTML. Each one has its own return type as previously specified. And here is where the magic happens: The user can define these functions to create the data representation that they want to return, return the appropriate object, and your webapp is ready to go! Lets look at the sine getPlot example:"
401 | ]
402 | },
403 | {
404 | "cell_type": "markdown",
405 | "metadata": {},
406 | "source": [
407 | "## Plot Function"
408 | ]
409 | },
410 | {
411 | "cell_type": "code",
412 | "execution_count": null,
413 | "metadata": {
414 | "collapsed": true
415 | },
416 | "outputs": [],
417 | "source": [
418 | "def getPlot(self, params):\n",
419 | " f = float(params['freq'])\n",
420 | " x = np.arange(0,2*np.pi,np.pi/150)\n",
421 | " y = np.sin(f*x)\n",
422 | " fig = plt.figure()\n",
423 | " splt1 = fig.add_subplot(1,1,1)\n",
424 | " splt1.plot(x,y)\n",
425 | " return fig"
426 | ]
427 | },
428 | {
429 | "cell_type": "markdown",
430 | "metadata": {},
431 | "source": [
432 | "### Our getPlot function takes 2 arguments: self, because we are working with a class object, as well as params, which is the current values of all objects in our input. Note that we don't actually call this function ourselves, Data Spyre does it for us, and it is expecting us to pass the params object, so even if your function doesn't require the params dictionary, it still needs to be passed in. The rest of the function is a simple matplotlib figure. As you can see, we get our frequency from the params dictionary by subsetting on the name 'freq' which we defined in the inputs. At the end we return a matplotlib figure, which Data Spyre takes and displays on the web app."
433 | ]
434 | },
435 | {
436 | "cell_type": "markdown",
437 | "metadata": {},
438 | "source": [
439 | "### Alright, cool, we've got ourselves a simple webapp with a graph. What if we wanted to display not just the plot, but the data behind it as well? That's easy! We just need to introduce tabs. Tabs is a list of strings that will be created in the webapp. Each tab should be tied to one unique output. As explained earlier, we can tie in the tab name as a parameter of the output. Let's look back to our sine example:"
440 | ]
441 | },
442 | {
443 | "cell_type": "markdown",
444 | "metadata": {},
445 | "source": [
446 | "## Script 4"
447 | ]
448 | },
449 | {
450 | "cell_type": "code",
451 | "execution_count": null,
452 | "metadata": {
453 | "collapsed": true
454 | },
455 | "outputs": [],
456 | "source": [
457 | "from spyre import server\n",
458 | "\n",
459 | "import matplotlib.pyplot as plt\n",
460 | "import numpy as np\n",
461 | "import pandas as pd\n",
462 | "\n",
463 | "class SimpleSineApp(server.App):\n",
464 | " title = \"Simple Sine App\"\n",
465 | " inputs = [{ \"type\":\"text\",\n",
466 | " \"key\":\"freq\",\n",
467 | " \"value\":5,\n",
468 | " \"label\":\"frequency\",\n",
469 | " \"action_id\":\"sine_wave_plot\"}]\n",
470 | " tabs=[\"Plot\",\"Data\"]\n",
471 | " outputs = [{\"type\":\"plot\",\n",
472 | " \"id\":\"wave_plot\",\n",
473 | " \"tab\":\"Plot\",\n",
474 | " \"control_id\":\"sine_wave_plot\"},\n",
475 | " {\"type\":\"table\",\n",
476 | " \"id\":\"wave_data\",\n",
477 | " \"tab\":\"Data\",\n",
478 | " \"control_id\":\"sine_wave_plot\"}]\n",
479 | " controls = [{\"type\":\"HIDDEN\",\n",
480 | " \"id\":\"sine_wave_plot\"}]\n",
481 | " def getPlot(self, params):\n",
482 | " f = float(params['freq'])\n",
483 | " df=self.getData(params)\n",
484 | " fig = plt.figure()\n",
485 | " splt1 = fig.add_subplot(1,1,1)\n",
486 | " splt1.plot(df.x,df.y)\n",
487 | " return fig\n",
488 | " def getData(self,params):\n",
489 | " f=float(params['freq'])\n",
490 | " x = pd.Series(np.arange(0,2*np.pi,np.pi/150))\n",
491 | " y = pd.Series(np.sin(f*x))\n",
492 | " df=pd.concat([x,y],axis=1)\n",
493 | " df.columns=['x','y']\n",
494 | " return df\n",
495 | "\n",
496 | "if __name__ == '__main__':\n",
497 | " app = SimpleSineApp()\n",
498 | " #app.launch()"
499 | ]
500 | },
501 | {
502 | "cell_type": "markdown",
503 | "metadata": {},
504 | "source": [
505 | "### Lets dive into what's new to our code:\n",
506 | "\n",
507 | " 1. We've added a tabs list to control how we want our outputs organized\n",
508 | " 2. Each ouput now has a \"tab\" parameter which we can link to our tabs list we created\n",
509 | " 3. We've added a getData function to our class. Note that it takes the same arguments as the getPlot function. DataSpyre knows to call the getPlot function for the plot tab and it knows to call the getData function for the table tab. \n",
510 | " 4. We've now introduced pandas into our workflow. As you can see, we create a pandas dataframe, and return it for the getData Function\n",
511 | " 5. In addition, we now utilize our getData function in our getPlot function, so we only have one function to generate the data used for the plot and table."
512 | ]
513 | },
514 | {
515 | "cell_type": "markdown",
516 | "metadata": {},
517 | "source": [
518 | "### We've now got a working webapp with a control as well as multiple tabs to show both plot and data! And the best part, is it took less than 40 lines of code, including import statements. This simple framework can get you running with your data quickly with little overhead. "
519 | ]
520 | },
521 | {
522 | "cell_type": "markdown",
523 | "metadata": {},
524 | "source": [
525 | ""
526 | ]
527 | },
528 | {
529 | "cell_type": "markdown",
530 | "metadata": {},
531 | "source": [
532 | "---"
533 | ]
534 | },
535 | {
536 | "cell_type": "markdown",
537 | "metadata": {},
538 | "source": [
539 | "# 4. How do I make my Cool WebApp even Cooler?"
540 | ]
541 | },
542 | {
543 | "cell_type": "markdown",
544 | "metadata": {},
545 | "source": [
546 | "### Alright, we've got a easy, lightweight to use app. However, as with any data project, our needs move outside the realm of simple and require us to dive in deeper. Let's go into how we can increase the functionality in our webapp."
547 | ]
548 | },
549 | {
550 | "cell_type": "markdown",
551 | "metadata": {},
552 | "source": [
553 | "## A. Maximize the most out of our getData Function\n",
554 | "We had a really simple getData Function before, where we input a frequency and generated a sine wave. Generally, we won't be generating data on the fly. We'll want to select a portion of our data that we already have. There are many ways to do this.\n",
555 | " - We can define a global dataFrame and subset that based on our input\n",
556 | " - We can read in data locally from the filesystem\n",
557 | " - We can create some dynamic sql and pull in data based on our input \n",
558 | " - We could even scrape the data from a website given some input\n",
559 | " \n",
560 | "Long story short, we have a number of ways to get the data we want into our webapp, the question is about which method best suits your needs."
561 | ]
562 | },
563 | {
564 | "cell_type": "markdown",
565 | "metadata": {},
566 | "source": [
567 | "## B. Function Overload!"
568 | ]
569 | },
570 | {
571 | "cell_type": "markdown",
572 | "metadata": {},
573 | "source": [
574 | "So far we've mentioned how we can define getData, getPlot, getHTML functions in order to tell Data Spyre what type of data to generate for the web application. However, this becomes a problem when we want to have multiple functions of the same datatypes. Data Spyre has a simple solution to this, function overloading! "
575 | ]
576 | },
577 | {
578 | "cell_type": "markdown",
579 | "metadata": {},
580 | "source": [
581 | "## Script 5"
582 | ]
583 | },
584 | {
585 | "cell_type": "code",
586 | "execution_count": null,
587 | "metadata": {
588 | "collapsed": true
589 | },
590 | "outputs": [],
591 | "source": [
592 | "from spyre import server\n",
593 | "\n",
594 | "import matplotlib.pyplot as plt\n",
595 | "import numpy as np\n",
596 | "import pandas as pd\n",
597 | "\n",
598 | "class SimpleSineApp(server.App):\n",
599 | " title = \"Simple Sine App\"\n",
600 | " inputs = [{ \"type\":\"text\",\n",
601 | " \"key\":\"freq\",\n",
602 | " \"value\":5,\n",
603 | " \"label\":\"frequency\",\n",
604 | " \"action_id\":\"sine_wave_plot\"}]\n",
605 | " tabs=[\"Plot1\",\"Plot2\",\"Data\"]\n",
606 | " outputs = [{\"type\":\"plot\",\n",
607 | " \"id\":\"wave_plot\",\n",
608 | " \"tab\":\"Plot1\",\n",
609 | " \"control_id\":\"sine_wave_plot\"},\n",
610 | " {\"type\":\"plot\",\n",
611 | " \"id\":\"wave_plot2\",\n",
612 | " \"tab\":\"Plot2\",\n",
613 | " \"control_id\":\"sine_wave_plot\"},\n",
614 | " {\"type\":\"table\",\n",
615 | " \"id\":\"wave_data\",\n",
616 | " \"tab\":\"Data\",\n",
617 | " \"control_id\":\"sine_wave_plot\"}]\n",
618 | " controls = [{\"type\":\"HIDDEN\",\n",
619 | " \"id\":\"sine_wave_plot\"}]\n",
620 | " def wave_plot(self, params):\n",
621 | " f = float(params['freq'])\n",
622 | " df=self.wave_data(params)\n",
623 | " fig = plt.figure()\n",
624 | " splt1 = fig.add_subplot(1,1,1)\n",
625 | " splt1.plot(df.x,df.y1)\n",
626 | " return fig\n",
627 | " def wave_plot2(self, params):\n",
628 | " f = float(params['freq'])\n",
629 | " df=self.wave_data(params)\n",
630 | " fig = plt.figure()\n",
631 | " splt1 = fig.add_subplot(1,1,1)\n",
632 | " splt1.plot(df.x,df.y2)\n",
633 | " return fig\n",
634 | " def wave_data(self,params):\n",
635 | " f=float(params['freq'])\n",
636 | " x = pd.Series(np.arange(0,2*np.pi,np.pi/150))\n",
637 | " y1 = pd.Series(np.sin(f*x))\n",
638 | " y2 = pd.Series(np.sin(2*f*x))\n",
639 | " df=pd.concat([x,y1,y2],axis=1)\n",
640 | " df.columns=['x','y1','y2']\n",
641 | " return df\n",
642 | "\n",
643 | "if __name__ == '__main__':\n",
644 | " app = SimpleSineApp()\n",
645 | " #app.launch()"
646 | ]
647 | },
648 | {
649 | "cell_type": "markdown",
650 | "metadata": {},
651 | "source": [
652 | "Let's look at our new class object. What exactly did we do here?\n",
653 | "- In this example we've introduced function overloading. We've renamed our getData and getPlot functions to represent the \"id\" of each output tied to the output. \n",
654 | "- Because of this, we're no longer tied to overriding Data Spyre's getData, getPlot and getHTML functions.\n",
655 | "- Why does this work? Data spyre will parse the class function names for the given output ids, and if it detects any matches, it will call the given function instead of the type default."
656 | ]
657 | },
658 | {
659 | "cell_type": "markdown",
660 | "metadata": {},
661 | "source": [
662 | "## C. One input is never enough!"
663 | ]
664 | },
665 | {
666 | "cell_type": "markdown",
667 | "metadata": {},
668 | "source": [
669 | "So far we've worked with a single text input. There are multiple other inputs we can use in addition or instead of a text box. Let's look at all of the options in another webapp:"
670 | ]
671 | },
672 | {
673 | "cell_type": "markdown",
674 | "metadata": {},
675 | "source": [
676 | "## Script 6"
677 | ]
678 | },
679 | {
680 | "cell_type": "code",
681 | "execution_count": null,
682 | "metadata": {
683 | "collapsed": true
684 | },
685 | "outputs": [],
686 | "source": [
687 | "from spyre import server\n",
688 | "\n",
689 | "import matplotlib.pyplot as plt\n",
690 | "import numpy as np\n",
691 | "import pandas as pd\n",
692 | "\n",
693 | "class SimpleSineApp(server.App):\n",
694 | " title = \"Simple Sine App\"\n",
695 | " inputs = [{ \"type\":\"text\",\n",
696 | " \"key\":\"freq\",\n",
697 | " \"value\":5,\n",
698 | " \"label\":\"frequency\",\n",
699 | " \"action_id\":\"sine_wave_plot\"},\n",
700 | " { \"type\":\"radiobuttons\",\n",
701 | " \"key\":\"func_type\",\n",
702 | " \"label\":\"func_type\",\n",
703 | " \"action_id\":\"sine_wave_plot\",\n",
704 | " \"options\":[\n",
705 | " {\"label\":\"Sine\",\"value\":\"sin\",\"checked\":True},\n",
706 | " {\"label\":\"Cosine\",\"value\":\"cos\"}]},\n",
707 | " { \"type\":\"dropdown\",\n",
708 | " \"key\":\"datatoplot\",\n",
709 | " \"options\":[\n",
710 | " {\"label\":\"x\",\"value\":\"y1\"},\n",
711 | " {\"label\":\"2x\",\"value\":\"y2\"}],\n",
712 | " \"label\":\"Data to Plot\",\n",
713 | " \"action_id\":\"sine_wave_plot\"},\n",
714 | " { \"type\":\"slider\",\n",
715 | " \"key\":\"x_slider\",\n",
716 | " \"value\":100,\n",
717 | " \"min\":1,\n",
718 | " \"max\":200,\n",
719 | " \"label\":\"x_axis\",\n",
720 | " \"action_id\":\"sine_wave_plot\"},\n",
721 | " { \"type\":\"checkboxgroup\",\n",
722 | " \"key\":\"checkboxes\",\n",
723 | " \"options\":[\n",
724 | " {\"label\":\"show x gridlines\",\"value\":\"showx\",\"checked\":True},\n",
725 | " {\"label\":\"show y gridlines\",\"value\":\"showy\"}\n",
726 | " ],\n",
727 | " \"label\":\"Plot Options\",\n",
728 | " \"action_id\":\"sine_wave_plot\"}]\n",
729 | " tabs=[\"Plot\",\"Data\"]\n",
730 | " outputs = [{\"type\":\"plot\",\n",
731 | " \"id\":\"wave_plot\",\n",
732 | " \"tab\":\"Plot\",\n",
733 | " \"control_id\":\"sine_wave_plot\"},\n",
734 | " {\"type\":\"table\",\n",
735 | " \"id\":\"wave_data\",\n",
736 | " \"tab\":\"Data\",\n",
737 | " \"control_id\":\"sine_wave_plot\"}]\n",
738 | " controls = [{\"type\":\"HIDDEN\",\n",
739 | " \"id\":\"sine_wave_plot\"}]\n",
740 | " def getPlot(self, params):\n",
741 | " f = float(params['freq'])\n",
742 | " data = params['datatoplot']\n",
743 | " checkboxlist=params['checkboxes']\n",
744 | " df=self.getData(params)\n",
745 | " fig = plt.figure()\n",
746 | " splt1 = fig.add_subplot(1,1,1)\n",
747 | " splt1.plot(df.x,df[data])\n",
748 | " if 'showx' in checkboxlist:\n",
749 | " splt1.xaxis.grid(True)\n",
750 | " if 'showy' in checkboxlist:\n",
751 | " splt1.yaxis.grid(True)\n",
752 | " return fig\n",
753 | " def getData(self,params):\n",
754 | " f=float(params['freq'])\n",
755 | " index=int(params['x_slider'])\n",
756 | " functouse=params['func_type']\n",
757 | " funcdict={\"sin\":np.sin,\"cos\":np.cos}\n",
758 | " func=funcdict[functouse]\n",
759 | " x = pd.Series(np.arange(0,2*np.pi,np.pi/150))\n",
760 | " y1 = pd.Series(func(f*x))\n",
761 | " y2 = pd.Series(func(2*f*x))\n",
762 | " df=pd.concat([x,y1,y2],axis=1)\n",
763 | " df.columns=['x','y1','y2']\n",
764 | " return df[:index]\n",
765 | "\n",
766 | "if __name__ == '__main__':\n",
767 | " app = SimpleSineApp()\n",
768 | " #app.launch()"
769 | ]
770 | },
771 | {
772 | "cell_type": "markdown",
773 | "metadata": {},
774 | "source": [
775 | "As you can see, we've got 5 types of inputs. Each has its own quirks in terms of how you structure the input, but follow the same basic structure as the text. Let's play around with a few...\n",
776 | "\n",
777 | "Alright! As we all know, various use cases require unique solutions, and this should cover most if not all."
778 | ]
779 | },
780 | {
781 | "cell_type": "markdown",
782 | "metadata": {},
783 | "source": [
784 | "## D. Multiple Controls to handle the inputs amongst your tabs"
785 | ]
786 | },
787 | {
788 | "cell_type": "markdown",
789 | "metadata": {},
790 | "source": [
791 | "So far we've created multiple inputs in order to update our webapp. However, we've limited it all to a single control. If any input was changed, all tabs will refresh. Sometimes this is unnecessary, and potentially unwanted. Let's look at an example where we have multiple controls:"
792 | ]
793 | },
794 | {
795 | "cell_type": "markdown",
796 | "metadata": {},
797 | "source": [
798 | "## Script 7"
799 | ]
800 | },
801 | {
802 | "cell_type": "code",
803 | "execution_count": null,
804 | "metadata": {
805 | "collapsed": true
806 | },
807 | "outputs": [],
808 | "source": [
809 | "from spyre import server\n",
810 | "\n",
811 | "import matplotlib.pyplot as plt\n",
812 | "import numpy as np\n",
813 | "import pandas as pd\n",
814 | "\n",
815 | "class SimpleSineApp(server.App):\n",
816 | " title = \"Simple Sine App\"\n",
817 | " inputs = [{ \"type\":\"slider\",\n",
818 | " \"key\":\"x_slider\",\n",
819 | " \"value\":200,\n",
820 | " \"min\":1,\n",
821 | " \"max\":200,\n",
822 | " \"label\":\"x_axis\",\n",
823 | " \"action_id\":\"sine_wave_plot\"},{ \"type\":\"text\",\n",
824 | " \"key\":\"freq\",\n",
825 | " \"value\":5,\n",
826 | " \"label\":\"frequency\",\n",
827 | " \"action_id\":\"sine_wave_data\"}]\n",
828 | " tabs=[\"Plot\",\"Data\"]\n",
829 | " outputs = [{\"type\":\"plot\",\n",
830 | " \"id\":\"wave_plot\",\n",
831 | " \"tab\":\"Plot\",\n",
832 | " \"control_id\":\"sine_wave_data\"},\n",
833 | " {\"type\":\"table\",\n",
834 | " \"id\":\"wave_data\",\n",
835 | " \"tab\":\"Data\",\n",
836 | " \"control_id\":\"sine_wave_plot\"}]\n",
837 | " controls = [{\"type\":\"HIDDEN\",\n",
838 | " \"id\":\"sine_wave_plot\"},\n",
839 | " {\"type\":\"HIDDEN\",\"id\":\"sine_wave_data\"}]\n",
840 | " def getPlot(self, params):\n",
841 | " f = float(params['freq'])\n",
842 | " index=int(params['x_slider'])\n",
843 | " df=self.getData(params)\n",
844 | " fig = plt.figure()\n",
845 | " splt1 = fig.add_subplot(1,1,1)\n",
846 | " splt1.plot(df.x,df.y)\n",
847 | " return fig\n",
848 | " def getData(self,params):\n",
849 | " f=float(params['freq'])\n",
850 | " index=int(params['x_slider'])\n",
851 | " x = pd.Series(np.arange(0,2*np.pi,np.pi/150))\n",
852 | " y = pd.Series(np.sin(f*x))\n",
853 | " df=pd.concat([x,y],axis=1)\n",
854 | " df.columns=['x','y']\n",
855 | " return df[:index]\n",
856 | "\n",
857 | "if __name__ == '__main__':\n",
858 | " app = SimpleSineApp()\n",
859 | " #app.launch()"
860 | ]
861 | },
862 | {
863 | "cell_type": "markdown",
864 | "metadata": {},
865 | "source": [
866 | "While this may not demonstrate the best use case for it, we can clearly see how multiple controls allow us to modify the data in some tabs within our webapp while leaving others untouched. This is all controlled through the list of controls above, and tying each input and output with the respective control id"
867 | ]
868 | },
869 | {
870 | "cell_type": "markdown",
871 | "metadata": {},
872 | "source": [
873 | "## E. Accessing Cherrypy root objects to store local files"
874 | ]
875 | },
876 | {
877 | "cell_type": "markdown",
878 | "metadata": {},
879 | "source": [
880 | "Alright, lets take a step back a minute and go back to our roots, literally. As I mentioned in the beginning of this talk, Data Spyre is built on top of Cherrypy for the backend web portion. However, as with any wrapper, you may lose functionality that the more granular class object has. In our example, we don't have access to all of the features of Cherrypy. While there are other features that may be lost, an important one that is useful is being able to access specified directories from the filesystem as part of the webapp. Let's go through the process, and we'll see a great example of how this is useful."
881 | ]
882 | },
883 | {
884 | "cell_type": "markdown",
885 | "metadata": {},
886 | "source": [
887 | "## Access Cherrypy Root"
888 | ]
889 | },
890 | {
891 | "cell_type": "code",
892 | "execution_count": null,
893 | "metadata": {
894 | "collapsed": true
895 | },
896 | "outputs": [],
897 | "source": [
898 | "def initiate_directs(self,params):\n",
899 | " root=self.getRoot()\n",
900 | " current_dir=os.path.dirname(os.path.abspath(\"__file__\"))\n",
901 | " config_public={\n",
902 | " '/':{\n",
903 | " 'tools.staticdir.root' : current_dir,\n",
904 | " },\n",
905 | " '/static': {\n",
906 | " 'tools.staticdir.on': True,\n",
907 | " 'tools.staticdir.dir': 'public',\n",
908 | " 'tools.staticdir.content_types':{'.min.js':\"text/javascript\",'svg':\"image/svg+xml\"}\n",
909 | "\n",
910 | " }\n",
911 | " }\n",
912 | " cherrypy.tree.mount(root, \"/\", config=config_public)\n"
913 | ]
914 | },
915 | {
916 | "cell_type": "markdown",
917 | "metadata": {},
918 | "source": [
919 | "In order to directly modify our cherrypy instance, we must get access to its root object. After experimenting around a bit, I got the following method to work. Keeping in mind that the Data Spyre application inherits the properties of the Cherrypy app class, we can directly access the root once the class has been initiated. I managed to do this by defining a function and calling it through the output directories in data spyre. It isn't linked to a tab, but the code still runs. I list it first in my list so that it will run first, guaranteeing i have access for all my output runs.\n",
920 | "\n",
921 | "Lets dive into what's going on here: The gist of the config_public dictionary links specific directories in my file system to my webapp. In this example, the directory I am in has a folder 'public', and it is accessable by adding '/static/' to your hyperlink within your webapp. Once we've established this linking, we mount it onto our cherrypy instance. And voila! We now have access to our filesystem through our cherrypy server.\n"
922 | ]
923 | },
924 | {
925 | "cell_type": "markdown",
926 | "metadata": {},
927 | "source": [
928 | "## F. Let's build an app with multiple elements on a single page!"
929 | ]
930 | },
931 | {
932 | "cell_type": "markdown",
933 | "metadata": {},
934 | "source": [
935 | "So far we've leveraged the ability to create multiple tabs, either with data or plots on each tab. However, we currently don't have the capability to introduce multiple elements onto a single page. We could concat multiple dataframes in order to show them all in one tab, and we could leverage matplotlibs subplots to get multiple plots in a single figure. However, we can't get plots and tables onto a single page. In order to do this, we can leverage the getHTML function (along with our cherrypy root) in order to get everything we want into a single page."
936 | ]
937 | },
938 | {
939 | "cell_type": "markdown",
940 | "metadata": {},
941 | "source": [
942 | "## Script 8"
943 | ]
944 | },
945 | {
946 | "cell_type": "code",
947 | "execution_count": null,
948 | "metadata": {
949 | "collapsed": true
950 | },
951 | "outputs": [],
952 | "source": [
953 | "from spyre import server\n",
954 | "\n",
955 | "import matplotlib.pyplot as plt\n",
956 | "import numpy as np\n",
957 | "import pandas as pd\n",
958 | "import os\n",
959 | "import cherrypy\n",
960 | "class SimpleSineApp(server.App):\n",
961 | " title = \"Simple Sine App\"\n",
962 | " inputs = [{ \"type\":\"text\",\n",
963 | " \"key\":\"freq\",\n",
964 | " \"value\":5,\n",
965 | " \"label\":\"frequency\",\n",
966 | " \"action_id\":\"sine_wave_plot\"}]\n",
967 | " outputs = [{\"type\" : \"html\",\n",
968 | " \"id\" : \"initiate_directs\",\n",
969 | " \"on_page_load\" : True },\n",
970 | " {\"type\":\"html\",\n",
971 | " \"id\":\"wave_plot_and_data\",\n",
972 | " \"control_id\":\"sine_wave_plot\"}]\n",
973 | " controls = [{\"type\":\"HIDDEN\",\n",
974 | " \"id\":\"sine_wave_plot\"}]\n",
975 | " def initiate_directs(self,params):\n",
976 | " root=self.getRoot()\n",
977 | " current_dir=os.path.dirname(os.path.abspath(\"__file__\"))\n",
978 | " config_public={\n",
979 | " '/':{\n",
980 | " 'tools.staticdir.root' : current_dir,\n",
981 | " },\n",
982 | " '/static': {\n",
983 | " 'tools.staticdir.on': True,\n",
984 | " 'tools.staticdir.dir': 'public',\n",
985 | " 'tools.staticdir.content_types':{'.min.js':\"text/javascript\",'svg':\"image/svg+xml\"}\n",
986 | " }\n",
987 | " }\n",
988 | " cherrypy.tree.mount(root, \"/\", config=config_public)\n",
989 | " def wave_plot_and_data(self, params):\n",
990 | " f = float(params['freq'])\n",
991 | " df=self.getData(params)\n",
992 | " fig = plt.figure()\n",
993 | " splt1 = fig.add_subplot(1,1,1)\n",
994 | " splt1.plot(df.x,df.y)\n",
995 | " fig.savefig('public/ourfig.png')\n",
996 | " dfhtml=df[:20].to_html(index=False)\n",
997 | " html=''\n",
998 | " finalhtml=html+\" \"+dfhtml\n",
999 | " return finalhtml\n",
1000 | " def getData(self,params):\n",
1001 | " f=float(params['freq'])\n",
1002 | " x = pd.Series(np.arange(0,2*np.pi,np.pi/150))\n",
1003 | " y = pd.Series(np.sin(f*x))\n",
1004 | " df=pd.concat([x,y],axis=1)\n",
1005 | " df.columns=['x','y']\n",
1006 | " return df\n",
1007 | "\n",
1008 | "if __name__ == '__main__':\n",
1009 | " app = SimpleSineApp()\n",
1010 | " #app.launch()"
1011 | ]
1012 | },
1013 | {
1014 | "cell_type": "markdown",
1015 | "metadata": {},
1016 | "source": [
1017 | "Now, lets go into what just happened here really quickly. \n",
1018 | " - We've created a function that returns some html to be loaded. \n",
1019 | " - To get our image and dataframe onto the same page, we had to mount a directory onto the cherrypy server. \n",
1020 | " - We added a simple image tag in our html, citing the source as our relative html path defined by our cherrypy server. \n",
1021 | " - We saved our file to the directory 'public', which maps to the '/static/' directory in our webapp, allowing us to access our image. \n",
1022 | " - Lastly, pandas dataframes have a handy to_html function which lets us create an html table from our dataframe!\n",
1023 | " \n",
1024 | "Now while I haven't done any formatting, some simple python string replacement functions can be used to modify the html we just created to customize any number of features of our html page, allowing you to present a customly designed front end to data spyre!"
1025 | ]
1026 | },
1027 | {
1028 | "cell_type": "markdown",
1029 | "metadata": {},
1030 | "source": [
1031 | "## G. Yeshup? No, Nohup!"
1032 | ]
1033 | },
1034 | {
1035 | "cell_type": "markdown",
1036 | "metadata": {},
1037 | "source": [
1038 | "From what we've seen in these examples, we have a python connection that stays open in the terminal while we have our webapp running. However, we can make this a background process with nohup! we simply need to call our script as:\n",
1039 | "\n",
1040 | " nohup python script.py &\n",
1041 | "\n",
1042 | "\n",
1043 | "This will run the webapp as a background process. Woo!\n"
1044 | ]
1045 | },
1046 | {
1047 | "cell_type": "markdown",
1048 | "metadata": {},
1049 | "source": [
1050 | "## H. Updating your webapp\n",
1051 | "\n",
1052 | "So far we've also run through several example of how we can update our webapp. However, you don't always want to restart your webapp, especially if you used nohup. An easy way to update your server is by simply updating the file that the server is referencing. If you make some changes and save it, or replace the file with a new file of the same name, your webapp will automatically update the chanages! Simple, but effective."
1053 | ]
1054 | },
1055 | {
1056 | "cell_type": "markdown",
1057 | "metadata": {},
1058 | "source": [
1059 | "---"
1060 | ]
1061 | },
1062 | {
1063 | "cell_type": "markdown",
1064 | "metadata": {},
1065 | "source": [
1066 | "# 5. Let's get Interactive with some plotting libraries!"
1067 | ]
1068 | },
1069 | {
1070 | "cell_type": "markdown",
1071 | "metadata": {},
1072 | "source": [
1073 | "### We've been getting in depth into all the inner workings of Data Spyre , so let's take a step back and focus on our visualizations. While matplotlib is very extensible for most purposes, there are some other great plotting libraries that bring your plotting visualizations to a whole new level, especially with interactivity. Let's dive into a few of them:"
1074 | ]
1075 | },
1076 | {
1077 | "cell_type": "markdown",
1078 | "metadata": {},
1079 | "source": [
1080 | "## A. Bokeh"
1081 | ]
1082 | },
1083 | {
1084 | "cell_type": "markdown",
1085 | "metadata": {},
1086 | "source": [
1087 | "Bokeh is a Python interactive visualization library that targets modern web browsers for presentation. Its goal is to provide elegant, concise construction of novel graphics in the style of D3.js, but also deliver this capability with high-performance interactivity over very large or streaming datasets.\n",
1088 | "\n",
1089 | "Bokeh is very easy to use, and offers a variety of tools to enhance your plot experience, such as hovering, zooming, and selection. It also has a variety of outputs, such as exporting your plot to its own html page, or you could embed it into another html page. It is also built for Jupyter notebook integration, as you can see below."
1090 | ]
1091 | },
1092 | {
1093 | "cell_type": "markdown",
1094 | "metadata": {},
1095 | "source": [
1096 | "## Bokeh Example 1 - Zooming, Panning, and Selection"
1097 | ]
1098 | },
1099 | {
1100 | "cell_type": "code",
1101 | "execution_count": null,
1102 | "metadata": {
1103 | "collapsed": false
1104 | },
1105 | "outputs": [],
1106 | "source": [
1107 | "import numpy as np\n",
1108 | "\n",
1109 | "from bokeh.plotting import figure, show, output_file, vplot\n",
1110 | "from bokeh.io import output_notebook\n",
1111 | "N = 100\n",
1112 | "\n",
1113 | "x = np.linspace(0, 4*np.pi, N)\n",
1114 | "y = np.sin(x)\n",
1115 | "\n",
1116 | "#output_file(\"legend.html\", title=\"legend.py example\")\n",
1117 | "\n",
1118 | "TOOLS = \"pan,wheel_zoom,box_zoom,reset,save,box_select\"\n",
1119 | "\n",
1120 | "p2 = figure(title=\"Another Legend Example\", tools=TOOLS)\n",
1121 | "\n",
1122 | "p2.circle(x, y, legend=\"sin(x)\")\n",
1123 | "p2.line(x, y, legend=\"sin(x)\")\n",
1124 | "\n",
1125 | "p2.line(x, 2*y, legend=\"2*sin(x)\",\n",
1126 | " line_dash=[4, 4], line_color=\"orange\", line_width=2)\n",
1127 | "\n",
1128 | "p2.square(x, 3*y, legend=\"3*sin(x)\", fill_color=None, line_color=\"green\")\n",
1129 | "p2.line(x, 3*y, legend=\"3*sin(x)\", line_color=\"green\")\n",
1130 | "output_notebook()\n",
1131 | "show(p2)# open a browser"
1132 | ]
1133 | },
1134 | {
1135 | "cell_type": "markdown",
1136 | "metadata": {},
1137 | "source": [
1138 | "## Bokeh Example 2 - Hovering"
1139 | ]
1140 | },
1141 | {
1142 | "cell_type": "code",
1143 | "execution_count": null,
1144 | "metadata": {
1145 | "collapsed": false
1146 | },
1147 | "outputs": [],
1148 | "source": [
1149 | "from bokeh.plotting import figure, output_file, show, ColumnDataSource\n",
1150 | "from bokeh.models import HoverTool\n",
1151 | "from bokeh.io import output_notebook\n",
1152 | "\n",
1153 | "source = ColumnDataSource(\n",
1154 | " data=dict(\n",
1155 | " x=[1, 2, 3, 4, 5],\n",
1156 | " y=[2, 5, 8, 2, 7],\n",
1157 | " desc=['A', 'b', 'C', 'd', 'E'],\n",
1158 | " imgs = [\n",
1159 | " 'http://bokeh.pydata.org/static/snake.jpg',\n",
1160 | " 'http://bokeh.pydata.org/static/snake2.png',\n",
1161 | " 'http://bokeh.pydata.org/static/snake3D.png',\n",
1162 | " 'http://bokeh.pydata.org/static/snake4_TheRevenge.png',\n",
1163 | " 'http://bokeh.pydata.org/static/snakebite.jpg'\n",
1164 | " ]\n",
1165 | " )\n",
1166 | " )\n",
1167 | "\n",
1168 | "hover = HoverTool(\n",
1169 | " tooltips=\"\"\"\n",
1170 | "