{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Lesson 40: High level plotting with HoloViews\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "tags": [] }, "outputs": [ { "data": { "text/html": [ "\n", "
\n", " \n", " Loading BokehJS ...\n", "
\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "(function(root) {\n", " function now() {\n", " return new Date();\n", " }\n", "\n", " const force = true;\n", "\n", " if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n", " root._bokeh_onload_callbacks = [];\n", " root._bokeh_is_loading = undefined;\n", " }\n", "\n", "const JS_MIME_TYPE = 'application/javascript';\n", " const HTML_MIME_TYPE = 'text/html';\n", " const EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';\n", " const CLASS_NAME = 'output_bokeh rendered_html';\n", "\n", " /**\n", " * Render data to the DOM node\n", " */\n", " function render(props, node) {\n", " const script = document.createElement(\"script\");\n", " node.appendChild(script);\n", " }\n", "\n", " /**\n", " * Handle when an output is cleared or removed\n", " */\n", " function handleClearOutput(event, handle) {\n", " const cell = handle.cell;\n", "\n", " const id = cell.output_area._bokeh_element_id;\n", " const server_id = cell.output_area._bokeh_server_id;\n", " // Clean up Bokeh references\n", " if (id != null && id in Bokeh.index) {\n", " Bokeh.index[id].model.document.clear();\n", " delete Bokeh.index[id];\n", " }\n", "\n", " if (server_id !== undefined) {\n", " // Clean up Bokeh references\n", " const cmd_clean = \"from bokeh.io.state import curstate; print(curstate().uuid_to_server['\" + server_id + \"'].get_sessions()[0].document.roots[0]._id)\";\n", " cell.notebook.kernel.execute(cmd_clean, {\n", " iopub: {\n", " output: function(msg) {\n", " const id = msg.content.text.trim();\n", " if (id in Bokeh.index) {\n", " Bokeh.index[id].model.document.clear();\n", " delete Bokeh.index[id];\n", " }\n", " }\n", " }\n", " });\n", " // Destroy server and session\n", " const cmd_destroy = \"import bokeh.io.notebook as ion; ion.destroy_server('\" + server_id + \"')\";\n", " cell.notebook.kernel.execute(cmd_destroy);\n", " }\n", " }\n", "\n", " /**\n", " * Handle when a new output is added\n", " */\n", " function handleAddOutput(event, handle) {\n", " const output_area = handle.output_area;\n", " const output = handle.output;\n", "\n", " // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only\n", " if ((output.output_type != \"display_data\") || (!Object.prototype.hasOwnProperty.call(output.data, EXEC_MIME_TYPE))) {\n", " return\n", " }\n", "\n", " const toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n", "\n", " if (output.metadata[EXEC_MIME_TYPE][\"id\"] !== undefined) {\n", " toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];\n", " // store reference to embed id on output_area\n", " output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n", " }\n", " if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n", " const bk_div = document.createElement(\"div\");\n", " bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n", " const script_attrs = bk_div.children[0].attributes;\n", " for (let i = 0; i < script_attrs.length; i++) {\n", " toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);\n", " toinsert[toinsert.length - 1].firstChild.textContent = bk_div.children[0].textContent\n", " }\n", " // store reference to server id on output_area\n", " output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n", " }\n", " }\n", "\n", " function register_renderer(events, OutputArea) {\n", "\n", " function append_mime(data, metadata, element) {\n", " // create a DOM node to render to\n", " const toinsert = this.create_output_subarea(\n", " metadata,\n", " CLASS_NAME,\n", " EXEC_MIME_TYPE\n", " );\n", " this.keyboard_manager.register_events(toinsert);\n", " // Render to node\n", " const props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n", " render(props, toinsert[toinsert.length - 1]);\n", " element.append(toinsert);\n", " return toinsert\n", " }\n", "\n", " /* Handle when an output is cleared or removed */\n", " events.on('clear_output.CodeCell', handleClearOutput);\n", " events.on('delete.Cell', handleClearOutput);\n", "\n", " /* Handle when a new output is added */\n", " events.on('output_added.OutputArea', handleAddOutput);\n", "\n", " /**\n", " * Register the mime type and append_mime function with output_area\n", " */\n", " OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n", " /* Is output safe? */\n", " safe: true,\n", " /* Index of renderer in `output_area.display_order` */\n", " index: 0\n", " });\n", " }\n", "\n", " // register the mime type if in Jupyter Notebook environment and previously unregistered\n", " if (root.Jupyter !== undefined) {\n", " const events = require('base/js/events');\n", " const OutputArea = require('notebook/js/outputarea').OutputArea;\n", "\n", " if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n", " register_renderer(events, OutputArea);\n", " }\n", " }\n", " if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n", " root._bokeh_timeout = Date.now() + 5000;\n", " root._bokeh_failed_load = false;\n", " }\n", "\n", " const NB_LOAD_WARNING = {'data': {'text/html':\n", " \"
\\n\"+\n", " \"

\\n\"+\n", " \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n", " \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n", " \"

\\n\"+\n", " \"\\n\"+\n", " \"\\n\"+\n", " \"from bokeh.resources import INLINE\\n\"+\n", " \"output_notebook(resources=INLINE)\\n\"+\n", " \"\\n\"+\n", " \"
\"}};\n", "\n", " function display_loaded() {\n", " const el = document.getElementById(\"bd0bc201-0b24-4e4d-96a2-41d1ff73a212\");\n", " if (el != null) {\n", " el.textContent = \"BokehJS is loading...\";\n", " }\n", " if (root.Bokeh !== undefined) {\n", " if (el != null) {\n", " el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n", " }\n", " } else if (Date.now() < root._bokeh_timeout) {\n", " setTimeout(display_loaded, 100)\n", " }\n", " }\n", "\n", " function run_callbacks() {\n", " try {\n", " root._bokeh_onload_callbacks.forEach(function(callback) {\n", " if (callback != null)\n", " callback();\n", " });\n", " } finally {\n", " delete root._bokeh_onload_callbacks\n", " }\n", " console.debug(\"Bokeh: all callbacks have finished\");\n", " }\n", "\n", " function load_libs(css_urls, js_urls, callback) {\n", " if (css_urls == null) css_urls = [];\n", " if (js_urls == null) js_urls = [];\n", "\n", " root._bokeh_onload_callbacks.push(callback);\n", " if (root._bokeh_is_loading > 0) {\n", " console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n", " return null;\n", " }\n", " if (js_urls == null || js_urls.length === 0) {\n", " run_callbacks();\n", " return null;\n", " }\n", " console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n", " root._bokeh_is_loading = css_urls.length + js_urls.length;\n", "\n", " function on_load() {\n", " root._bokeh_is_loading--;\n", " if (root._bokeh_is_loading === 0) {\n", " console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n", " run_callbacks()\n", " }\n", " }\n", "\n", " function on_error(url) {\n", " console.error(\"failed to load \" + url);\n", " }\n", "\n", " for (let i = 0; i < css_urls.length; i++) {\n", " const url = css_urls[i];\n", " const element = document.createElement(\"link\");\n", " element.onload = on_load;\n", " element.onerror = on_error.bind(null, url);\n", " element.rel = \"stylesheet\";\n", " element.type = \"text/css\";\n", " element.href = url;\n", " console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n", " document.body.appendChild(element);\n", " }\n", "\n", " for (let i = 0; i < js_urls.length; i++) {\n", " const url = js_urls[i];\n", " const element = document.createElement('script');\n", " element.onload = on_load;\n", " element.onerror = on_error.bind(null, url);\n", " element.async = false;\n", " element.src = url;\n", " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " document.head.appendChild(element);\n", " }\n", " };\n", "\n", " function inject_raw_css(css) {\n", " const element = document.createElement(\"style\");\n", " element.appendChild(document.createTextNode(css));\n", " document.body.appendChild(element);\n", " }\n", "\n", " const js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-3.1.1.min.js\", \"https://unpkg.com/@holoviz/panel@1.1.0/dist/panel.min.js\"];\n", " const css_urls = [];\n", "\n", " const inline_js = [ function(Bokeh) {\n", " Bokeh.set_log_level(\"info\");\n", " },\n", "function(Bokeh) {\n", " }\n", " ];\n", "\n", " function run_inline_js() {\n", " if (root.Bokeh !== undefined || force === true) {\n", " for (let i = 0; i < inline_js.length; i++) {\n", " inline_js[i].call(root, root.Bokeh);\n", " }\n", "if (force === true) {\n", " display_loaded();\n", " }} else if (Date.now() < root._bokeh_timeout) {\n", " setTimeout(run_inline_js, 100);\n", " } else if (!root._bokeh_failed_load) {\n", " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n", " root._bokeh_failed_load = true;\n", " } else if (force !== true) {\n", " const cell = $(document.getElementById(\"bd0bc201-0b24-4e4d-96a2-41d1ff73a212\")).parents('.cell').data().cell;\n", " cell.output_area.append_execute_result(NB_LOAD_WARNING)\n", " }\n", " }\n", "\n", " if (root._bokeh_is_loading === 0) {\n", " console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n", " run_inline_js();\n", " } else {\n", " load_libs(css_urls, js_urls, function() {\n", " console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n", " run_inline_js();\n", " });\n", " }\n", "}(window));" ], "application/vnd.bokehjs_load.v0+json": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n\n if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n root._bokeh_onload_callbacks = [];\n root._bokeh_is_loading = undefined;\n }\n\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n const NB_LOAD_WARNING = {'data': {'text/html':\n \"
\\n\"+\n \"

\\n\"+\n \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n \"

\\n\"+\n \"\\n\"+\n \"\\n\"+\n \"from bokeh.resources import INLINE\\n\"+\n \"output_notebook(resources=INLINE)\\n\"+\n \"\\n\"+\n \"
\"}};\n\n function display_loaded() {\n const el = document.getElementById(\"bd0bc201-0b24-4e4d-96a2-41d1ff73a212\");\n if (el != null) {\n el.textContent = \"BokehJS is loading...\";\n }\n if (root.Bokeh !== undefined) {\n if (el != null) {\n el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(display_loaded, 100)\n }\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n\n root._bokeh_onload_callbacks.push(callback);\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls == null || js_urls.length === 0) {\n run_callbacks();\n return null;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n root._bokeh_is_loading = css_urls.length + js_urls.length;\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n\n function on_error(url) {\n console.error(\"failed to load \" + url);\n }\n\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error.bind(null, url);\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n }\n\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error.bind(null, url);\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-3.1.1.min.js\", \"https://unpkg.com/@holoviz/panel@1.1.0/dist/panel.min.js\"];\n const css_urls = [];\n\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {\n }\n ];\n\n function run_inline_js() {\n if (root.Bokeh !== undefined || force === true) {\n for (let i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\nif (force === true) {\n display_loaded();\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n } else if (force !== true) {\n const cell = $(document.getElementById(\"bd0bc201-0b24-4e4d-96a2-41d1ff73a212\")).parents('.cell').data().cell;\n cell.output_area.append_execute_result(NB_LOAD_WARNING)\n }\n }\n\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n run_inline_js();\n } else {\n load_libs(css_urls, js_urls, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n}(window));" }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "(function(root) {\n", " function now() {\n", " return new Date();\n", " }\n", "\n", " var force = true;\n", " var py_version = '3.1.1'.replace('rc', '-rc.');\n", " var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n", " var reloading = false;\n", " var Bokeh = root.Bokeh;\n", " var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n", "\n", " if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n", " root._bokeh_timeout = Date.now() + 5000;\n", " root._bokeh_failed_load = false;\n", " }\n", "\n", " function run_callbacks() {\n", " try {\n", " root._bokeh_onload_callbacks.forEach(function(callback) {\n", " if (callback != null)\n", " callback();\n", " });\n", " } finally {\n", " delete root._bokeh_onload_callbacks;\n", " }\n", " console.debug(\"Bokeh: all callbacks have finished\");\n", " }\n", "\n", " function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n", " if (css_urls == null) css_urls = [];\n", " if (js_urls == null) js_urls = [];\n", " if (js_modules == null) js_modules = [];\n", " if (js_exports == null) js_exports = {};\n", "\n", " root._bokeh_onload_callbacks.push(callback);\n", "\n", " if (root._bokeh_is_loading > 0) {\n", " console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n", " return null;\n", " }\n", " if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n", " run_callbacks();\n", " return null;\n", " }\n", " if (!reloading) {\n", " console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n", " }\n", "\n", " function on_load() {\n", " root._bokeh_is_loading--;\n", " if (root._bokeh_is_loading === 0) {\n", " console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n", " run_callbacks()\n", " }\n", " }\n", " window._bokeh_on_load = on_load\n", "\n", " function on_error() {\n", " console.error(\"failed to load \" + url);\n", " }\n", "\n", " var skip = [];\n", " if (window.requirejs) {\n", " window.requirejs.config({'packages': {}, 'paths': {'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n", " require([\"jspanel\"], function(jsPanel) {\n", "\twindow.jsPanel = jsPanel\n", "\ton_load()\n", " })\n", " require([\"jspanel-modal\"], function() {\n", "\ton_load()\n", " })\n", " require([\"jspanel-tooltip\"], function() {\n", "\ton_load()\n", " })\n", " require([\"jspanel-hint\"], function() {\n", "\ton_load()\n", " })\n", " require([\"jspanel-layout\"], function() {\n", "\ton_load()\n", " })\n", " require([\"jspanel-contextmenu\"], function() {\n", "\ton_load()\n", " })\n", " require([\"jspanel-dock\"], function() {\n", "\ton_load()\n", " })\n", " require([\"gridstack\"], function(GridStack) {\n", "\twindow.GridStack = GridStack\n", "\ton_load()\n", " })\n", " require([\"notyf\"], function() {\n", "\ton_load()\n", " })\n", " root._bokeh_is_loading = css_urls.length + 9;\n", " } else {\n", " root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n", " }\n", "\n", " var existing_stylesheets = []\n", " var links = document.getElementsByTagName('link')\n", " for (var i = 0; i < links.length; i++) {\n", " var link = links[i]\n", " if (link.href != null) {\n", "\texisting_stylesheets.push(link.href)\n", " }\n", " }\n", " for (var i = 0; i < css_urls.length; i++) {\n", " var url = css_urls[i];\n", " if (existing_stylesheets.indexOf(url) !== -1) {\n", "\ton_load()\n", "\tcontinue;\n", " }\n", " const element = document.createElement(\"link\");\n", " element.onload = on_load;\n", " element.onerror = on_error;\n", " element.rel = \"stylesheet\";\n", " element.type = \"text/css\";\n", " element.href = url;\n", " console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n", " document.body.appendChild(element);\n", " } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n", " var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n", " for (var i = 0; i < urls.length; i++) {\n", " skip.push(urls[i])\n", " }\n", " } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n", " var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n", " for (var i = 0; i < urls.length; i++) {\n", " skip.push(urls[i])\n", " }\n", " } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n", " var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n", " for (var i = 0; i < urls.length; i++) {\n", " skip.push(urls[i])\n", " }\n", " } var existing_scripts = []\n", " var scripts = document.getElementsByTagName('script')\n", " for (var i = 0; i < scripts.length; i++) {\n", " var script = scripts[i]\n", " if (script.src != null) {\n", "\texisting_scripts.push(script.src)\n", " }\n", " }\n", " for (var i = 0; i < js_urls.length; i++) {\n", " var url = js_urls[i];\n", " if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n", "\tif (!window.requirejs) {\n", "\t on_load();\n", "\t}\n", "\tcontinue;\n", " }\n", " var element = document.createElement('script');\n", " element.onload = on_load;\n", " element.onerror = on_error;\n", " element.async = false;\n", " element.src = url;\n", " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " document.head.appendChild(element);\n", " }\n", " for (var i = 0; i < js_modules.length; i++) {\n", " var url = js_modules[i];\n", " if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n", "\tif (!window.requirejs) {\n", "\t on_load();\n", "\t}\n", "\tcontinue;\n", " }\n", " var element = document.createElement('script');\n", " element.onload = on_load;\n", " element.onerror = on_error;\n", " element.async = false;\n", " element.src = url;\n", " element.type = \"module\";\n", " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " document.head.appendChild(element);\n", " }\n", " for (const name in js_exports) {\n", " var url = js_exports[name];\n", " if (skip.indexOf(url) >= 0 || root[name] != null) {\n", "\tif (!window.requirejs) {\n", "\t on_load();\n", "\t}\n", "\tcontinue;\n", " }\n", " var element = document.createElement('script');\n", " element.onerror = on_error;\n", " element.async = false;\n", " element.type = \"module\";\n", " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " element.textContent = `\n", " import ${name} from \"${url}\"\n", " window.${name} = ${name}\n", " window._bokeh_on_load()\n", " `\n", " document.head.appendChild(element);\n", " }\n", " if (!js_urls.length && !js_modules.length) {\n", " on_load()\n", " }\n", " };\n", "\n", " function inject_raw_css(css) {\n", " const element = document.createElement(\"style\");\n", " element.appendChild(document.createTextNode(css));\n", " document.body.appendChild(element);\n", " }\n", "\n", " var js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js\", \"https://cdn.holoviz.org/panel/1.1.0/dist/panel.min.js\"];\n", " var js_modules = [];\n", " var js_exports = {};\n", " var css_urls = [];\n", " var inline_js = [ function(Bokeh) {\n", " Bokeh.set_log_level(\"info\");\n", " },\n", "function(Bokeh) {} // ensure no trailing comma for IE\n", " ];\n", "\n", " function run_inline_js() {\n", " if ((root.Bokeh !== undefined) || (force === true)) {\n", " for (var i = 0; i < inline_js.length; i++) {\n", " inline_js[i].call(root, root.Bokeh);\n", " }\n", " // Cache old bokeh versions\n", " if (Bokeh != undefined && !reloading) {\n", "\tvar NewBokeh = root.Bokeh;\n", "\tif (Bokeh.versions === undefined) {\n", "\t Bokeh.versions = new Map();\n", "\t}\n", "\tif (NewBokeh.version !== Bokeh.version) {\n", "\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n", "\t}\n", "\troot.Bokeh = Bokeh;\n", " }} else if (Date.now() < root._bokeh_timeout) {\n", " setTimeout(run_inline_js, 100);\n", " } else if (!root._bokeh_failed_load) {\n", " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n", " root._bokeh_failed_load = true;\n", " }\n", " root._bokeh_is_initializing = false\n", " }\n", "\n", " function load_or_wait() {\n", " // Implement a backoff loop that tries to ensure we do not load multiple\n", " // versions of Bokeh and its dependencies at the same time.\n", " // In recent versions we use the root._bokeh_is_initializing flag\n", " // to determine whether there is an ongoing attempt to initialize\n", " // bokeh, however for backward compatibility we also try to ensure\n", " // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n", " // before older versions are fully initialized.\n", " if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n", " root._bokeh_is_initializing = false;\n", " root._bokeh_onload_callbacks = undefined;\n", " console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n", " load_or_wait();\n", " } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n", " setTimeout(load_or_wait, 100);\n", " } else {\n", " Bokeh = root.Bokeh;\n", " bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n", " root._bokeh_is_initializing = true\n", " root._bokeh_onload_callbacks = []\n", " if (!reloading && (!bokeh_loaded || is_dev)) {\n", "\troot.Bokeh = undefined;\n", " }\n", " load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n", "\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n", "\trun_inline_js();\n", " });\n", " }\n", " }\n", " // Give older versions of the autoload script a head-start to ensure\n", " // they initialize before we start loading newer version.\n", " setTimeout(load_or_wait, 100)\n", "}(window));" ], "application/vnd.holoviews_load.v0+json": "(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n var py_version = '3.1.1'.replace('rc', '-rc.');\n var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n var reloading = false;\n var Bokeh = root.Bokeh;\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n run_callbacks();\n return null;\n }\n if (!reloading) {\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n var skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n require([\"jspanel\"], function(jsPanel) {\n\twindow.jsPanel = jsPanel\n\ton_load()\n })\n require([\"jspanel-modal\"], function() {\n\ton_load()\n })\n require([\"jspanel-tooltip\"], function() {\n\ton_load()\n })\n require([\"jspanel-hint\"], function() {\n\ton_load()\n })\n require([\"jspanel-layout\"], function() {\n\ton_load()\n })\n require([\"jspanel-contextmenu\"], function() {\n\ton_load()\n })\n require([\"jspanel-dock\"], function() {\n\ton_load()\n })\n require([\"gridstack\"], function(GridStack) {\n\twindow.GridStack = GridStack\n\ton_load()\n })\n require([\"notyf\"], function() {\n\ton_load()\n })\n root._bokeh_is_loading = css_urls.length + 9;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n var existing_stylesheets = []\n var links = document.getElementsByTagName('link')\n for (var i = 0; i < links.length; i++) {\n var link = links[i]\n if (link.href != null) {\n\texisting_stylesheets.push(link.href)\n }\n }\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n if (existing_stylesheets.indexOf(url) !== -1) {\n\ton_load()\n\tcontinue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } var existing_scripts = []\n var scripts = document.getElementsByTagName('script')\n for (var i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n\texisting_scripts.push(script.src)\n }\n }\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (var i = 0; i < js_modules.length; i++) {\n var url = js_modules[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n var url = js_exports[name];\n if (skip.indexOf(url) >= 0 || root[name] != null) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n var js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js\", \"https://cdn.holoviz.org/panel/1.1.0/dist/panel.min.js\"];\n var js_modules = [];\n var js_exports = {};\n var css_urls = [];\n var inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n\tvar NewBokeh = root.Bokeh;\n\tif (Bokeh.versions === undefined) {\n\t Bokeh.versions = new Map();\n\t}\n\tif (NewBokeh.version !== Bokeh.version) {\n\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n\t}\n\troot.Bokeh = Bokeh;\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n Bokeh = root.Bokeh;\n bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n if (!reloading && (!bokeh_loaded || is_dev)) {\n\troot.Bokeh = undefined;\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n\trun_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));" }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "\n", "if ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n", " window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n", "}\n", "\n", "\n", " function JupyterCommManager() {\n", " }\n", "\n", " JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n", " if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n", " var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n", " comm_manager.register_target(comm_id, function(comm) {\n", " comm.on_msg(msg_handler);\n", " });\n", " } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n", " window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n", " comm.onMsg = msg_handler;\n", " });\n", " } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n", " google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n", " var messages = comm.messages[Symbol.asyncIterator]();\n", " function processIteratorResult(result) {\n", " var message = result.value;\n", " console.log(message)\n", " var content = {data: message.data, comm_id};\n", " var buffers = []\n", " for (var buffer of message.buffers || []) {\n", " buffers.push(new DataView(buffer))\n", " }\n", " var metadata = message.metadata || {};\n", " var msg = {content, buffers, metadata}\n", " msg_handler(msg);\n", " return messages.next().then(processIteratorResult);\n", " }\n", " return messages.next().then(processIteratorResult);\n", " })\n", " }\n", " }\n", "\n", " JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n", " if (comm_id in window.PyViz.comms) {\n", " return window.PyViz.comms[comm_id];\n", " } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n", " var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n", " var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n", " if (msg_handler) {\n", " comm.on_msg(msg_handler);\n", " }\n", " } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n", " var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n", " comm.open();\n", " if (msg_handler) {\n", " comm.onMsg = msg_handler;\n", " }\n", " } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n", " var comm_promise = google.colab.kernel.comms.open(comm_id)\n", " comm_promise.then((comm) => {\n", " window.PyViz.comms[comm_id] = comm;\n", " if (msg_handler) {\n", " var messages = comm.messages[Symbol.asyncIterator]();\n", " function processIteratorResult(result) {\n", " var message = result.value;\n", " var content = {data: message.data};\n", " var metadata = message.metadata || {comm_id};\n", " var msg = {content, metadata}\n", " msg_handler(msg);\n", " return messages.next().then(processIteratorResult);\n", " }\n", " return messages.next().then(processIteratorResult);\n", " }\n", " }) \n", " var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n", " return comm_promise.then((comm) => {\n", " comm.send(data, metadata, buffers, disposeOnDone);\n", " });\n", " };\n", " var comm = {\n", " send: sendClosure\n", " };\n", " }\n", " window.PyViz.comms[comm_id] = comm;\n", " return comm;\n", " }\n", " window.PyViz.comm_manager = new JupyterCommManager();\n", " \n", "\n", "\n", "var JS_MIME_TYPE = 'application/javascript';\n", "var HTML_MIME_TYPE = 'text/html';\n", "var EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\n", "var CLASS_NAME = 'output';\n", "\n", "/**\n", " * Render data to the DOM node\n", " */\n", "function render(props, node) {\n", " var div = document.createElement(\"div\");\n", " var script = document.createElement(\"script\");\n", " node.appendChild(div);\n", " node.appendChild(script);\n", "}\n", "\n", "/**\n", " * Handle when a new output is added\n", " */\n", "function handle_add_output(event, handle) {\n", " var output_area = handle.output_area;\n", " var output = handle.output;\n", " if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n", " return\n", " }\n", " var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n", " var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n", " if (id !== undefined) {\n", " var nchildren = toinsert.length;\n", " var html_node = toinsert[nchildren-1].children[0];\n", " html_node.innerHTML = output.data[HTML_MIME_TYPE];\n", " var scripts = [];\n", " var nodelist = html_node.querySelectorAll(\"script\");\n", " for (var i in nodelist) {\n", " if (nodelist.hasOwnProperty(i)) {\n", " scripts.push(nodelist[i])\n", " }\n", " }\n", "\n", " scripts.forEach( function (oldScript) {\n", " var newScript = document.createElement(\"script\");\n", " var attrs = [];\n", " var nodemap = oldScript.attributes;\n", " for (var j in nodemap) {\n", " if (nodemap.hasOwnProperty(j)) {\n", " attrs.push(nodemap[j])\n", " }\n", " }\n", " attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n", " newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n", " oldScript.parentNode.replaceChild(newScript, oldScript);\n", " });\n", " if (JS_MIME_TYPE in output.data) {\n", " toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n", " }\n", " output_area._hv_plot_id = id;\n", " if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n", " window.PyViz.plot_index[id] = Bokeh.index[id];\n", " } else {\n", " window.PyViz.plot_index[id] = null;\n", " }\n", " } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n", " var bk_div = document.createElement(\"div\");\n", " bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n", " var script_attrs = bk_div.children[0].attributes;\n", " for (var i = 0; i < script_attrs.length; i++) {\n", " toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n", " }\n", " // store reference to server id on output_area\n", " output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n", " }\n", "}\n", "\n", "/**\n", " * Handle when an output is cleared or removed\n", " */\n", "function handle_clear_output(event, handle) {\n", " var id = handle.cell.output_area._hv_plot_id;\n", " var server_id = handle.cell.output_area._bokeh_server_id;\n", " if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n", " var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n", " if (server_id !== null) {\n", " comm.send({event_type: 'server_delete', 'id': server_id});\n", " return;\n", " } else if (comm !== null) {\n", " comm.send({event_type: 'delete', 'id': id});\n", " }\n", " delete PyViz.plot_index[id];\n", " if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n", " var doc = window.Bokeh.index[id].model.document\n", " doc.clear();\n", " const i = window.Bokeh.documents.indexOf(doc);\n", " if (i > -1) {\n", " window.Bokeh.documents.splice(i, 1);\n", " }\n", " }\n", "}\n", "\n", "/**\n", " * Handle kernel restart event\n", " */\n", "function handle_kernel_cleanup(event, handle) {\n", " delete PyViz.comms[\"hv-extension-comm\"];\n", " window.PyViz.plot_index = {}\n", "}\n", "\n", "/**\n", " * Handle update_display_data messages\n", " */\n", "function handle_update_output(event, handle) {\n", " handle_clear_output(event, {cell: {output_area: handle.output_area}})\n", " handle_add_output(event, handle)\n", "}\n", "\n", "function register_renderer(events, OutputArea) {\n", " function append_mime(data, metadata, element) {\n", " // create a DOM node to render to\n", " var toinsert = this.create_output_subarea(\n", " metadata,\n", " CLASS_NAME,\n", " EXEC_MIME_TYPE\n", " );\n", " this.keyboard_manager.register_events(toinsert);\n", " // Render to node\n", " var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n", " render(props, toinsert[0]);\n", " element.append(toinsert);\n", " return toinsert\n", " }\n", "\n", " events.on('output_added.OutputArea', handle_add_output);\n", " events.on('output_updated.OutputArea', handle_update_output);\n", " events.on('clear_output.CodeCell', handle_clear_output);\n", " events.on('delete.Cell', handle_clear_output);\n", " events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n", "\n", " OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n", " safe: true,\n", " index: 0\n", " });\n", "}\n", "\n", "if (window.Jupyter !== undefined) {\n", " try {\n", " var events = require('base/js/events');\n", " var OutputArea = require('notebook/js/outputarea').OutputArea;\n", " if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n", " register_renderer(events, OutputArea);\n", " }\n", " } catch(err) {\n", " }\n", "}\n" ], "application/vnd.holoviews_load.v0+json": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n" }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "\n", "
\n", "\n", "\n", "\n", " \n", " \n", "\n", "\n", "\n", "\n", "
\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import numpy as np\n", "import scipy.special\n", "import pandas as pd\n", "\n", "import bootcamp_utils.hv_defaults\n", "\n", "import bokeh.io\n", "\n", "import holoviews as hv\n", "\n", "bokeh.io.output_notebook()\n", "hv.extension('bokeh')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "*Note that if you are viewing this notebook as the static HTML rendering, you may need to refresh your browser for the plots below to show up.*\n", "\n", "## Introduction to HoloViews\n", "\n", "[HoloViews](http://holoviews.org/) is a high-level plotting library that is part of the [HoloViz ecosystem](http://holoviz.org/). It allows specification of plots, and is agnostic about what is used to render them. We will use Bokeh as our renderer.\n", "\n", "To set this up, we import HoloViews (as `hv`) and then set the Holoviews extension to be Bokeh using `hv.extension('bokeh')` at the top of the notebook." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Main ideas behind HoloViews\n", "\n", "Imagine you have a tidy data set. It is already logically organized; each row is an observation and each column a variable. Let us think for a moment conceptually (that is, not in terms of steps of coding) about how we might make a scatter plot from a tidy data frame. We need to (obviously) first decide that we want to make a scatter plot, i.e., we specify what kind of graphic **element** we want to convert our data set into. Then, we need to **annotate** the columns of the data frame. That is, we need to annotate which column will determine the x-coordinate of the glyphs in the scatter plot and which will determine the y-coordinate of the glyphs. After we have made these decisions, that is, what kind of graphic element we want to produce and what columns give the x-coordinates and what gives the y-coordinates, the fundamental plot is complete. Everything else is visual styling.\n", "\n", "The philosophy of HoloViews, right on the front of [the webpage](http://holoviews.org/index.html), is \"Stop plotting your data—annotate your data and let it visualize itself.\" With HoloViews, you add minimal annotations to your (tidy; must be tidy!) data to enable visualization. You can then later stylize the visualization, but the annotation is sufficient to describe the plot. Specifically, the annotations you need are:\n", "\n", "1. What kind of plotting element are you making (e.g., scatter, box-and-whisker, heat map, etc.).\n", "2. What columns specify the dimensions of the data, needed to set up axes.\n", "\n", "Once you make those annotations, HoloViews can take care of the rendering, using either Matplotlib, Bokeh, or Plotly. The main idea is that HoloViews objects are conceptual, agnostic to the particulars of rendering. You can stylize the rending if you like, but the fundamentals of the plotting object are already set by the annotation." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Importing HoloViews and choosing a renderer\n", "\n", "HoloViews is imported as `hv`, which we have done in the cell at the top of this notebook. Because HoloViews is agnostic to the ultimate renderer, we need to specify an **extension**, which we did above by executing `hv.extension('bokeh')`. Our plots will now be rendered using Bokeh." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## An example: A scatter plot of finch beak lengths and depths\n", "\n", "As an example of use of HoloViews, we will again visit the Grant and Grant finch beak data. We will load it in and take a look." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
bandbeak depth (mm)beak length (mm)speciesyear
0201238.059.25fortis1973
12012610.4511.35fortis1973
2201289.5510.15fortis1973
3201298.759.95fortis1973
42013310.1511.55fortis1973
\n", "
" ], "text/plain": [ " band beak depth (mm) beak length (mm) species year\n", "0 20123 8.05 9.25 fortis 1973\n", "1 20126 10.45 11.35 fortis 1973\n", "2 20128 9.55 10.15 fortis 1973\n", "3 20129 8.75 9.95 fortis 1973\n", "4 20133 10.15 11.55 fortis 1973" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = pd.read_csv('data/grant_complete.csv')\n", "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will now make a plot and explain how the syntax relates to the ideas behind annotating data sets. We will make a simple scatter plot of the beak length vs. beak depth for all birds measured in 2012." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ], "text/plain": [ ":Points [beak length (mm),beak depth (mm)] (species)" ] }, "execution_count": 3, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "p1002" } }, "output_type": "execute_result" } ], "source": [ "df_2012 = df.loc[df['year']==2012, :].copy()\n", "\n", "hv.Points(\n", " data=df_2012,\n", " kdims=['beak length (mm)', 'beak depth (mm)'],\n", " vdims=['species'],\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Specification of the element type\n", "\n", "We used `hv.Points` to invoke an **element of visualization**. An element is just a way of converting the tabular nature of the data to a graphical representation, in this case a scatter plot of points. That is, we want to make a plot where each glyph lies in a two-dimensional plot and the values of both the x- and y-axes are independent. (This is contrasted with `hv.Scatter` in which the x-coordinate is the independent variable and the y-coordinate is dependent on x; `hv.Points` is more appropriate here.)\n", "\n", "The available element types may be found in the [HoloViews reference gallery](http://holoviews.org/reference/index.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Specification of dimensions\n", "\n", "There are two types of dimensions, **key dimensions** and **value dimensions**, specified with the `kdims` and `vdims` arguments, respectively. You can think of key and value dimensions like keys and values of a dictionary (where you can have multidimensional keys). Key dimensions are indexing dimensions, which say where on the graphic the data in a row will reside. The value dimensions give information about each data point. In the simple plot above, the key dimensions are the the beak length and beak depth. Those columns determined where the glyphs were placed.\n", "\n", "We additionally had a value dimension, specified by `vdims`, which has additional information associated with each data point. This information was not used in the above plot, but we will put it to use momentarily." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Stylizing plots\n", "\n", "After a plotting Element is specified, we can stylize it using the `hv.opts` functionality. To investigate what styling options are available for each kind of plotting element, you can enter, for example\n", "\n", "```python\n", "hv.help(hv.Points)\n", "```\n", "\n", "and you will get detailed information on what options are available for stylizing `hv.Points` elements. Let's try a different styling for the above plot using `.opts()`." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ], "text/plain": [ ":Points [beak length (mm),beak depth (mm)] (species)" ] }, "execution_count": 4, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "p1148" } }, "output_type": "execute_result" } ], "source": [ "hv.Points(\n", " data=df_2012,\n", " kdims=['beak length (mm)', 'beak depth (mm)'],\n", " vdims=['species'],\n", ").opts(\n", " alpha=0.7,\n", " color='#1f77b3',\n", " frame_height=200,\n", " frame_width=200,\n", " show_grid=True,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I find the HoloViews defaults not very pleasing.\n", "\n", "If you agree and want to define defaults for an entire document, you may do so using `hv.opts.defaults()`. I have made some defaults that I find more pleasing that are available in the `bootcamp_utils.hv_defaults.set_defaults()` function. Let's set those defaults (which will be active for the rest of the notebook), and see how our plot looks.\n", "\n", "**Warning:** Setting the defaults in this way may affect some styling in more complex plots in unexpected ways. If you want more fine-grained control of each plot, I would recommend not setting the defaults and rather using `.opts()` for each plot." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ], "text/plain": [ ":Points [beak length (mm),beak depth (mm)] (species)" ] }, "execution_count": 5, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "p1296" } }, "output_type": "execute_result" } ], "source": [ "bootcamp_utils.hv_defaults.set_defaults()\n", "\n", "hv.Points(\n", " data=df_2012,\n", " kdims=['beak length (mm)', 'beak depth (mm)'],\n", " vdims=['species'],\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Grouping by value dimensions\n", "\n", "Recall that we have an unused value dimension in the element we created. We would naturally like to separate out the glyphs by species. To do this, we can do a `groupby` operation on the Element. That's right, we can do groupby operations on graphical elements!" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ], "text/plain": [ ":HoloMap [species]\n", " :Points [beak length (mm),beak depth (mm)] (species)" ] }, "execution_count": 6, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "p1444" } }, "output_type": "execute_result" } ], "source": [ "hv.Points(\n", " data=df_2012,\n", " kdims=['beak length (mm)', 'beak depth (mm)'],\n", " vdims=['species'],\n", ").groupby(\n", " 'species'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now have a pull down menu to the right of the plot where we can select the species we want and the glyphs on the plot will adjust accordingly. By default, after applying the groupby operation, HoloViews gives us a **HoloMap object**. The values in the column we used to group by are now selectable through a graphical interface (a pull-down menu).\n", "\n", "We may instead with to group by species and lay the plots out next to each other, creating a **layout**. We can use the `layout()` method do to this." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ], "text/plain": [ ":NdLayout [species]\n", " :Points [beak length (mm),beak depth (mm)] (species)" ] }, "execution_count": 7, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "p1667" } }, "output_type": "execute_result" } ], "source": [ "hv.Points(\n", " data=df_2012,\n", " kdims=['beak length (mm)', 'beak depth (mm)'],\n", " vdims=['species'],\n", ").groupby(\n", " 'species'\n", ").opts(\n", " frame_height=225,\n", " frame_width=225,\n", ").layout(\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we may wish to **overlay** the plots for each species that we split by species." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ], "text/plain": [ ":NdOverlay [species]\n", " :Points [beak length (mm),beak depth (mm)] (species)" ] }, "execution_count": 8, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "p2029" } }, "output_type": "execute_result" } ], "source": [ "hv.Points(\n", " data=df_2012,\n", " kdims=['beak length (mm)', 'beak depth (mm)'],\n", " vdims=['species'],\n", ").groupby(\n", " 'species'\n", ").opts(\n", " frame_height=225,\n", " frame_width=225,\n", ").overlay(\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "HoloViews was kind enough to automatically provide us with a (clickable) legend!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Further stylizing\n", "\n", "We can use `.opts()` to add tooltips where we can hover and get additional information from the vdims." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ], "text/plain": [ ":NdOverlay [species]\n", " :Points [beak length (mm),beak depth (mm)] (species)" ] }, "execution_count": 9, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "p2221" } }, "output_type": "execute_result" } ], "source": [ "hv.Points(\n", " data=df_2012,\n", " kdims=['beak length (mm)', 'beak depth (mm)'],\n", " vdims=['species'],\n", ").groupby(\n", " 'species'\n", ").opts(\n", " tools=['hover']\n", ").overlay(\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As a final example of constructing this plot, let's consider the entire data set and allow the year to be selected via a HoloMap, but color by species for each year." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ], "text/plain": [ ":HoloMap [year]\n", " :NdOverlay [species]\n", " :Points [beak length (mm),beak depth (mm)] (species,year)" ] }, "execution_count": 10, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "p2420" } }, "output_type": "execute_result" } ], "source": [ "hv.Points(\n", " data=df,\n", " kdims=['beak length (mm)', 'beak depth (mm)'],\n", " vdims=['species', 'year'],\n", ").groupby(\n", " ['species', 'year'],\n", ").opts(\n", " tools=['hover'],\n", ").overlay(\n", " 'species',\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Saving a HoloViews plot\n", "\n", "You can save a HoloViews plot (or layout, HoloMap, etc.) using the `hv.save()` function. The `hv.save()` function is smart; it will determine how you want to save your HoloViews object based on the suffix of the output file name.\n", "\n", "As an example, we can make the above plot again, but this time not displaying in the notebook, but rather storing it as a variable." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "finch_plot = hv.Points(\n", " data=df,\n", " kdims=['beak length (mm)', 'beak depth (mm)'],\n", " vdims=['species', 'year'],\n", ").groupby(\n", " ['species', 'year'],\n", ").opts(\n", " tools=['hover'],\n", ").overlay(\n", " 'species',\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we can use `hv.save()` to write this plot to disk as an HTML file." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " \r" ] } ], "source": [ "hv.save(finch_plot, 'finch_plot.html')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When we open `finch_plot.html`, the result is a fully interactive plot, self-contained in the HTML file. This file can be emailed to a colleague or even embedded in a publication.\n", "\n", "When we discuss [dashboarding](l26_dashboards.ipynb), we will see that we can make even more elaborate, informative interactive plots, though exporting these to standalone HTML is more challenging." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Extracting the Bokeh plotting object\n", "\n", "After making and displaying a HoloViews plot, we might want to get the Bokeh figure. We can extract that using `hv.render()`." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "(function(root) {\n", " function embed_document(root) {\n", " const docs_json = {\"b4625f9a-dcf1-43e4-a762-1f2a27df2f52\":{\"version\":\"3.1.1\",\"title\":\"Bokeh Application\",\"defs\":[{\"type\":\"model\",\"name\":\"ReactiveHTML1\"},{\"type\":\"model\",\"name\":\"FlexBox1\",\"properties\":[{\"name\":\"align_content\",\"kind\":\"Any\",\"default\":\"flex-start\"},{\"name\":\"align_items\",\"kind\":\"Any\",\"default\":\"flex-start\"},{\"name\":\"flex_direction\",\"kind\":\"Any\",\"default\":\"row\"},{\"name\":\"flex_wrap\",\"kind\":\"Any\",\"default\":\"wrap\"},{\"name\":\"justify_content\",\"kind\":\"Any\",\"default\":\"flex-start\"}]},{\"type\":\"model\",\"name\":\"FloatPanel1\",\"properties\":[{\"name\":\"config\",\"kind\":\"Any\",\"default\":{\"type\":\"map\"}},{\"name\":\"contained\",\"kind\":\"Any\",\"default\":true},{\"name\":\"position\",\"kind\":\"Any\",\"default\":\"right-top\"},{\"name\":\"offsetx\",\"kind\":\"Any\",\"default\":null},{\"name\":\"offsety\",\"kind\":\"Any\",\"default\":null},{\"name\":\"theme\",\"kind\":\"Any\",\"default\":\"primary\"},{\"name\":\"status\",\"kind\":\"Any\",\"default\":\"normalized\"}]},{\"type\":\"model\",\"name\":\"GridStack1\",\"properties\":[{\"name\":\"mode\",\"kind\":\"Any\",\"default\":\"warn\"},{\"name\":\"ncols\",\"kind\":\"Any\",\"default\":null},{\"name\":\"nrows\",\"kind\":\"Any\",\"default\":null},{\"name\":\"allow_resize\",\"kind\":\"Any\",\"default\":true},{\"name\":\"allow_drag\",\"kind\":\"Any\",\"default\":true},{\"name\":\"state\",\"kind\":\"Any\",\"default\":[]}]},{\"type\":\"model\",\"name\":\"drag1\",\"properties\":[{\"name\":\"slider_width\",\"kind\":\"Any\",\"default\":5},{\"name\":\"slider_color\",\"kind\":\"Any\",\"default\":\"black\"},{\"name\":\"value\",\"kind\":\"Any\",\"default\":50}]},{\"type\":\"model\",\"name\":\"click1\",\"properties\":[{\"name\":\"terminal_output\",\"kind\":\"Any\",\"default\":\"\"},{\"name\":\"debug_name\",\"kind\":\"Any\",\"default\":\"\"},{\"name\":\"clears\",\"kind\":\"Any\",\"default\":0}]},{\"type\":\"model\",\"name\":\"FastWrapper1\",\"properties\":[{\"name\":\"object\",\"kind\":\"Any\",\"default\":null},{\"name\":\"style\",\"kind\":\"Any\",\"default\":null}]},{\"type\":\"model\",\"name\":\"NotificationAreaBase1\",\"properties\":[{\"name\":\"position\",\"kind\":\"Any\",\"default\":\"bottom-right\"},{\"name\":\"_clear\",\"kind\":\"Any\",\"default\":0}]},{\"type\":\"model\",\"name\":\"NotificationArea1\",\"properties\":[{\"name\":\"notifications\",\"kind\":\"Any\",\"default\":[]},{\"name\":\"position\",\"kind\":\"Any\",\"default\":\"bottom-right\"},{\"name\":\"_clear\",\"kind\":\"Any\",\"default\":0},{\"name\":\"types\",\"kind\":\"Any\",\"default\":[{\"type\":\"map\",\"entries\":[[\"type\",\"warning\"],[\"background\",\"#ffc107\"],[\"icon\",{\"type\":\"map\",\"entries\":[[\"className\",\"fas fa-exclamation-triangle\"],[\"tagName\",\"i\"],[\"color\",\"white\"]]}]]},{\"type\":\"map\",\"entries\":[[\"type\",\"info\"],[\"background\",\"#007bff\"],[\"icon\",{\"type\":\"map\",\"entries\":[[\"className\",\"fas fa-info-circle\"],[\"tagName\",\"i\"],[\"color\",\"white\"]]}]]}]}]},{\"type\":\"model\",\"name\":\"Notification\",\"properties\":[{\"name\":\"background\",\"kind\":\"Any\",\"default\":null},{\"name\":\"duration\",\"kind\":\"Any\",\"default\":3000},{\"name\":\"icon\",\"kind\":\"Any\",\"default\":null},{\"name\":\"message\",\"kind\":\"Any\",\"default\":\"\"},{\"name\":\"notification_type\",\"kind\":\"Any\",\"default\":null},{\"name\":\"_destroyed\",\"kind\":\"Any\",\"default\":false}]},{\"type\":\"model\",\"name\":\"TemplateActions1\",\"properties\":[{\"name\":\"open_modal\",\"kind\":\"Any\",\"default\":0},{\"name\":\"close_modal\",\"kind\":\"Any\",\"default\":0}]},{\"type\":\"model\",\"name\":\"BootstrapTemplateActions1\",\"properties\":[{\"name\":\"open_modal\",\"kind\":\"Any\",\"default\":0},{\"name\":\"close_modal\",\"kind\":\"Any\",\"default\":0}]},{\"type\":\"model\",\"name\":\"MaterialTemplateActions1\",\"properties\":[{\"name\":\"open_modal\",\"kind\":\"Any\",\"default\":0},{\"name\":\"close_modal\",\"kind\":\"Any\",\"default\":0}]}],\"roots\":[{\"type\":\"object\",\"name\":\"Figure\",\"id\":\"p3621\",\"attributes\":{\"width\":450,\"height\":350,\"sizing_mode\":\"fixed\",\"align\":\"start\",\"x_range\":{\"type\":\"object\",\"name\":\"Range1d\",\"id\":\"p3617\",\"attributes\":{\"tags\":[[[\"beak length (mm)\",\"beak length (mm)\",null]]],\"start\":8.41611111111111,\"end\":16.28388888888889,\"reset_start\":8.41611111111111,\"reset_end\":16.28388888888889}},\"y_range\":{\"type\":\"object\",\"name\":\"Range1d\",\"id\":\"p3618\",\"attributes\":{\"tags\":[[[\"beak depth (mm)\",\"beak depth (mm)\",null]]],\"start\":6.61,\"end\":12.989999999999998,\"reset_start\":6.61,\"reset_end\":12.989999999999998}},\"x_scale\":{\"type\":\"object\",\"name\":\"LinearScale\",\"id\":\"p3633\"},\"y_scale\":{\"type\":\"object\",\"name\":\"LinearScale\",\"id\":\"p3635\"},\"title\":{\"type\":\"object\",\"name\":\"Title\",\"id\":\"p3624\",\"attributes\":{\"text\":\"year: 1973\"}},\"renderers\":[{\"type\":\"object\",\"name\":\"GlyphRenderer\",\"id\":\"p3683\",\"attributes\":{\"data_source\":{\"type\":\"object\",\"name\":\"ColumnDataSource\",\"id\":\"p3673\",\"attributes\":{\"selected\":{\"type\":\"object\",\"name\":\"Selection\",\"id\":\"p3674\",\"attributes\":{\"indices\":[],\"line_indices\":[]}},\"selection_policy\":{\"type\":\"object\",\"name\":\"UnionRenderers\",\"id\":\"p3675\"},\"data\":{\"type\":\"map\",\"entries\":[[\"beak length (mm)\",{\"type\":\"ndarray\",\"array\":{\"type\":\"bytes\",\"data\":\"AAAAAAAAJEAAAAAAAAApQJqZmZmZmSJAmpmZmZmZJEAAAAAAAAAmQDMzMzMzMyRAMzMzMzMzI0DNzMzMzMwlQJqZmZmZmSRAmpmZmZmZI0AzMzMzMzMkQM3MzMzMzCRAMzMzMzMzI0AzMzMzMzMlQDMzMzMzMyVAzczMzMzMJ0CamZmZmZkmQJqZmZmZmSZAZmZmZmZmI0BmZmZmZmYjQDMzMzMzMyRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJ0AAAAAAAAAjQGZmZmZmZiZAZmZmZmZmJUAAAAAAAAAkQJqZmZmZmSNAzczMzMzMJUBmZmZmZmYiQGZmZmZmZiRAmpmZmZmZJkCamZmZmZkkQGZmZmZmZiVAAAAAAAAAJEBmZmZmZmYjQM3MzMzMzCNAZmZmZmZmJUAAAAAAAAAmQGZmZmZmZiNAAAAAAAAAJUBmZmZmZmYnQJqZmZmZmSVAMzMzMzMzIkDNzMzMzMwlQGZmZmZmZihAzczMzMzMJUBmZmZmZmYlQM3MzMzMzCRAZmZmZmZmI0CamZmZmZkkQJqZmZmZmSNAMzMzMzMzJUAAAAAAAAAlQAAAAAAAAClAZmZmZmZmIkAzMzMzMzMkQDMzMzMzMyVAAAAAAAAAJ0CamZmZmZklQAAAAAAAACVAMzMzMzMzJEBmZmZmZmYlQDMzMzMzMyNAZmZmZmZmJUAzMzMzMzMkQAAAAAAAACRAAAAAAAAAJEDNzMzMzMwkQM3MzMzMzCRAZmZmZmZmJ0AzMzMzMzMlQAAAAAAAACdAZmZmZmZmJUDNzMzMzMwkQDMzMzMzMyRAmpmZmZmZJUAAAAAAAAAnQDMzMzMzMyZAzczMzMzMI0AAAAAAAAAlQGZmZmZmZihAzczMzMzMJUDNzMzMzMwpQGZmZmZmZiNAZmZmZmZmI0AAAAAAAAAkQDMzMzMzMyRAZmZmZmZmJEAzMzMzMzMjQM3MzMzMzCNAMzMzMzMzJUCamZmZmZkjQDMzMzMzMyZAAAAAAAAAJEDNzMzMzMwlQAAAAAAAACRAMzMzMzMzJUBmZmZmZmYlQM3MzMzMzCJAMzMzMzMzJECamZmZmZknQGZmZmZmZihAzczMzMzMKUAzMzMzMzMkQDMzMzMzMyRAAAAAAAAAIkBmZmZmZmYnQM3MzMzMzCVAzczMzMzMJEBmZmZmZmYpQAAAAAAAACVAMzMzMzMzI0AzMzMzMzMlQM3MzMzMzCRAAAAAAAAAJUAzMzMzMzMkQDMzMzMzMyVAMzMzMzMzJUA=\"},\"shape\":[121],\"dtype\":\"float64\",\"order\":\"little\"}],[\"beak depth (mm)\",{\"type\":\"ndarray\",\"array\":{\"type\":\"bytes\",\"data\":\"AAAAAAAAIUDNzMzMzMwhQAAAAAAAAB5AMzMzMzMzI0BmZmZmZmYiQGZmZmZmZiBAMzMzMzMzH0AzMzMzMzMhQM3MzMzMzCBAzczMzMzMHkAAAAAAAAAgQGZmZmZmZiFAMzMzMzMzIECamZmZmZkhQM3MzMzMzCJAAAAAAAAAJEAzMzMzMzMjQDMzMzMzMyNAMzMzMzMzIEAAAAAAAAAeQM3MzMzMzCBAmpmZmZmZH0CamZmZmZkgQM3MzMzMzCFAMzMzMzMzIkDNzMzMzMweQJqZmZmZmSBAMzMzMzMzIUDNzMzMzMwgQM3MzMzMzB5AMzMzMzMzIkDNzMzMzMweQAAAAAAAACJAZmZmZmZmJEAzMzMzMzMgQDMzMzMzMyFAzczMzMzMIEAzMzMzMzMgQAAAAAAAACBAmpmZmZmZIECamZmZmZkkQAAAAAAAACBAmpmZmZmZIUAAAAAAAAAiQJqZmZmZmSJAZmZmZmZmHkBmZmZmZmYgQAAAAAAAACRAZmZmZmZmIEBmZmZmZmYgQJqZmZmZmSBAMzMzMzMzH0AAAAAAAAAgQM3MzMzMzCBAmpmZmZmZIkDNzMzMzMwhQGZmZmZmZiNAZmZmZmZmHkAAAAAAAAAiQDMzMzMzMyFAzczMzMzMIUAzMzMzMzMhQJqZmZmZmSFAAAAAAAAAIUBmZmZmZmYiQAAAAAAAACBAzczMzMzMIkDNzMzMzMweQM3MzMzMzCBAAAAAAAAAIUAAAAAAAAAiQAAAAAAAACJAmpmZmZmZIkBmZmZmZmYhQDMzMzMzMyBAZmZmZmZmIUCamZmZmZkfQM3MzMzMzB5AzczMzMzMIEAAAAAAAIAiQDMzMzMzMyBAmpmZmZmZIEAAAAAAAAAhQM3MzMzMzCNAzczMzMzMIEDNzMzMzMwjQM3MzMzMzBxAZmZmZmZmIEBmZmZmZmYgQJqZmZmZmSBAzczMzMzMIEAAAAAAAAAeQGZmZmZmZiBAMzMzMzMzIUBmZmZmZmYgQJqZmZmZmSFAAAAAAAAAIUAzMzMzMzMgQJqZmZmZmSBAAAAAAAAAIUAzMzMzMzMgQDMzMzMzMx1AAAAAAAAAIEBmZmZmZmYkQDMzMzMzMyZAzczMzMzMI0BmZmZmZmYhQJqZmZmZmSBAMzMzMzMzH0DNzMzMzMwjQJqZmZmZmSRAzczMzMzMIEBmZmZmZmYhQJqZmZmZmSNAZmZmZmZmIUAAAAAAAAAiQDMzMzMzMx9AAAAAAAAAIUBmZmZmZmYgQGZmZmZmZiJAAAAAAAAAIkA=\"},\"shape\":[121],\"dtype\":\"float64\",\"order\":\"little\"}],[\"beak_length_left_parenthesis_mm_right_parenthesis\",{\"type\":\"ndarray\",\"array\":{\"type\":\"bytes\",\"data\":\"AAAAAAAAJEAAAAAAAAApQJqZmZmZmSJAmpmZmZmZJEAAAAAAAAAmQDMzMzMzMyRAMzMzMzMzI0DNzMzMzMwlQJqZmZmZmSRAmpmZmZmZI0AzMzMzMzMkQM3MzMzMzCRAMzMzMzMzI0AzMzMzMzMlQDMzMzMzMyVAzczMzMzMJ0CamZmZmZkmQJqZmZmZmSZAZmZmZmZmI0BmZmZmZmYjQDMzMzMzMyRAAAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJ0AAAAAAAAAjQGZmZmZmZiZAZmZmZmZmJUAAAAAAAAAkQJqZmZmZmSNAzczMzMzMJUBmZmZmZmYiQGZmZmZmZiRAmpmZmZmZJkCamZmZmZkkQGZmZmZmZiVAAAAAAAAAJEBmZmZmZmYjQM3MzMzMzCNAZmZmZmZmJUAAAAAAAAAmQGZmZmZmZiNAAAAAAAAAJUBmZmZmZmYnQJqZmZmZmSVAMzMzMzMzIkDNzMzMzMwlQGZmZmZmZihAzczMzMzMJUBmZmZmZmYlQM3MzMzMzCRAZmZmZmZmI0CamZmZmZkkQJqZmZmZmSNAMzMzMzMzJUAAAAAAAAAlQAAAAAAAAClAZmZmZmZmIkAzMzMzMzMkQDMzMzMzMyVAAAAAAAAAJ0CamZmZmZklQAAAAAAAACVAMzMzMzMzJEBmZmZmZmYlQDMzMzMzMyNAZmZmZmZmJUAzMzMzMzMkQAAAAAAAACRAAAAAAAAAJEDNzMzMzMwkQM3MzMzMzCRAZmZmZmZmJ0AzMzMzMzMlQAAAAAAAACdAZmZmZmZmJUDNzMzMzMwkQDMzMzMzMyRAmpmZmZmZJUAAAAAAAAAnQDMzMzMzMyZAzczMzMzMI0AAAAAAAAAlQGZmZmZmZihAzczMzMzMJUDNzMzMzMwpQGZmZmZmZiNAZmZmZmZmI0AAAAAAAAAkQDMzMzMzMyRAZmZmZmZmJEAzMzMzMzMjQM3MzMzMzCNAMzMzMzMzJUCamZmZmZkjQDMzMzMzMyZAAAAAAAAAJEDNzMzMzMwlQAAAAAAAACRAMzMzMzMzJUBmZmZmZmYlQM3MzMzMzCJAMzMzMzMzJECamZmZmZknQGZmZmZmZihAzczMzMzMKUAzMzMzMzMkQDMzMzMzMyRAAAAAAAAAIkBmZmZmZmYnQM3MzMzMzCVAzczMzMzMJEBmZmZmZmYpQAAAAAAAACVAMzMzMzMzI0AzMzMzMzMlQM3MzMzMzCRAAAAAAAAAJUAzMzMzMzMkQDMzMzMzMyVAMzMzMzMzJUA=\"},\"shape\":[121],\"dtype\":\"float64\",\"order\":\"little\"}],[\"beak_depth_left_parenthesis_mm_right_parenthesis\",{\"type\":\"ndarray\",\"array\":{\"type\":\"bytes\",\"data\":\"AAAAAAAAIUDNzMzMzMwhQAAAAAAAAB5AMzMzMzMzI0BmZmZmZmYiQGZmZmZmZiBAMzMzMzMzH0AzMzMzMzMhQM3MzMzMzCBAzczMzMzMHkAAAAAAAAAgQGZmZmZmZiFAMzMzMzMzIECamZmZmZkhQM3MzMzMzCJAAAAAAAAAJEAzMzMzMzMjQDMzMzMzMyNAMzMzMzMzIEAAAAAAAAAeQM3MzMzMzCBAmpmZmZmZH0CamZmZmZkgQM3MzMzMzCFAMzMzMzMzIkDNzMzMzMweQJqZmZmZmSBAMzMzMzMzIUDNzMzMzMwgQM3MzMzMzB5AMzMzMzMzIkDNzMzMzMweQAAAAAAAACJAZmZmZmZmJEAzMzMzMzMgQDMzMzMzMyFAzczMzMzMIEAzMzMzMzMgQAAAAAAAACBAmpmZmZmZIECamZmZmZkkQAAAAAAAACBAmpmZmZmZIUAAAAAAAAAiQJqZmZmZmSJAZmZmZmZmHkBmZmZmZmYgQAAAAAAAACRAZmZmZmZmIEBmZmZmZmYgQJqZmZmZmSBAMzMzMzMzH0AAAAAAAAAgQM3MzMzMzCBAmpmZmZmZIkDNzMzMzMwhQGZmZmZmZiNAZmZmZmZmHkAAAAAAAAAiQDMzMzMzMyFAzczMzMzMIUAzMzMzMzMhQJqZmZmZmSFAAAAAAAAAIUBmZmZmZmYiQAAAAAAAACBAzczMzMzMIkDNzMzMzMweQM3MzMzMzCBAAAAAAAAAIUAAAAAAAAAiQAAAAAAAACJAmpmZmZmZIkBmZmZmZmYhQDMzMzMzMyBAZmZmZmZmIUCamZmZmZkfQM3MzMzMzB5AzczMzMzMIEAAAAAAAIAiQDMzMzMzMyBAmpmZmZmZIEAAAAAAAAAhQM3MzMzMzCNAzczMzMzMIEDNzMzMzMwjQM3MzMzMzBxAZmZmZmZmIEBmZmZmZmYgQJqZmZmZmSBAzczMzMzMIEAAAAAAAAAeQGZmZmZmZiBAMzMzMzMzIUBmZmZmZmYgQJqZmZmZmSFAAAAAAAAAIUAzMzMzMzMgQJqZmZmZmSBAAAAAAAAAIUAzMzMzMzMgQDMzMzMzMx1AAAAAAAAAIEBmZmZmZmYkQDMzMzMzMyZAzczMzMzMI0BmZmZmZmYhQJqZmZmZmSBAMzMzMzMzH0DNzMzMzMwjQJqZmZmZmSRAzczMzMzMIEBmZmZmZmYhQJqZmZmZmSNAZmZmZmZmIUAAAAAAAAAiQDMzMzMzMx9AAAAAAAAAIUBmZmZmZmYgQGZmZmZmZiJAAAAAAAAAIkA=\"},\"shape\":[121],\"dtype\":\"float64\",\"order\":\"little\"}],[\"species\",[\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\",\"fortis\"]],[\"year\",{\"type\":\"ndarray\",\"array\":{\"type\":\"bytes\",\"data\":\"3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAAA==\"},\"shape\":[121],\"dtype\":\"int32\",\"order\":\"little\"}]]}}},\"view\":{\"type\":\"object\",\"name\":\"CDSView\",\"id\":\"p3684\",\"attributes\":{\"filter\":{\"type\":\"object\",\"name\":\"AllIndices\",\"id\":\"p3685\"}}},\"glyph\":{\"type\":\"object\",\"name\":\"Scatter\",\"id\":\"p3680\",\"attributes\":{\"tags\":[\"apply_ranges\"],\"x\":{\"type\":\"field\",\"field\":\"beak length (mm)\"},\"y\":{\"type\":\"field\",\"field\":\"beak depth (mm)\"},\"line_color\":{\"type\":\"value\",\"value\":\"#1f77b3\"},\"line_alpha\":{\"type\":\"value\",\"value\":0.75},\"fill_color\":{\"type\":\"value\",\"value\":\"#1f77b3\"},\"fill_alpha\":{\"type\":\"value\",\"value\":0.75},\"hatch_color\":{\"type\":\"value\",\"value\":\"#1f77b3\"},\"hatch_alpha\":{\"type\":\"value\",\"value\":0.75}}},\"selection_glyph\":{\"type\":\"object\",\"name\":\"Scatter\",\"id\":\"p3686\",\"attributes\":{\"tags\":[\"apply_ranges\"],\"x\":{\"type\":\"field\",\"field\":\"beak length (mm)\"},\"y\":{\"type\":\"field\",\"field\":\"beak depth (mm)\"},\"size\":{\"type\":\"value\",\"value\":4},\"angle\":{\"type\":\"value\",\"value\":0.0},\"line_color\":{\"type\":\"value\",\"value\":\"#1f77b3\"},\"line_alpha\":{\"type\":\"value\",\"value\":0.75},\"line_width\":{\"type\":\"value\",\"value\":1},\"line_join\":{\"type\":\"value\",\"value\":\"bevel\"},\"line_cap\":{\"type\":\"value\",\"value\":\"butt\"},\"line_dash\":{\"type\":\"value\",\"value\":[]},\"line_dash_offset\":{\"type\":\"value\",\"value\":0},\"fill_color\":{\"type\":\"value\",\"value\":\"#1f77b3\"},\"fill_alpha\":{\"type\":\"value\",\"value\":0.75},\"hatch_color\":{\"type\":\"value\",\"value\":\"#1f77b3\"},\"hatch_alpha\":{\"type\":\"value\",\"value\":0.75},\"hatch_scale\":{\"type\":\"value\",\"value\":12.0},\"hatch_pattern\":{\"type\":\"value\",\"value\":null},\"hatch_weight\":{\"type\":\"value\",\"value\":1.0},\"marker\":{\"type\":\"value\",\"value\":\"circle\"}}},\"nonselection_glyph\":{\"type\":\"object\",\"name\":\"Scatter\",\"id\":\"p3681\",\"attributes\":{\"tags\":[\"apply_ranges\"],\"x\":{\"type\":\"field\",\"field\":\"beak length (mm)\"},\"y\":{\"type\":\"field\",\"field\":\"beak depth (mm)\"},\"line_color\":{\"type\":\"value\",\"value\":\"#1f77b3\"},\"line_alpha\":{\"type\":\"value\",\"value\":0.75},\"fill_color\":{\"type\":\"value\",\"value\":\"#1f77b3\"},\"fill_alpha\":{\"type\":\"value\",\"value\":0.75},\"hatch_color\":{\"type\":\"value\",\"value\":\"#1f77b3\"},\"hatch_alpha\":{\"type\":\"value\",\"value\":0.1}}},\"muted_glyph\":{\"type\":\"object\",\"name\":\"Scatter\",\"id\":\"p3682\",\"attributes\":{\"tags\":[\"apply_ranges\"],\"x\":{\"type\":\"field\",\"field\":\"beak length (mm)\"},\"y\":{\"type\":\"field\",\"field\":\"beak depth (mm)\"},\"line_color\":{\"type\":\"value\",\"value\":\"#1f77b3\"},\"line_alpha\":{\"type\":\"value\",\"value\":0.2},\"fill_color\":{\"type\":\"value\",\"value\":\"#1f77b3\"},\"fill_alpha\":{\"type\":\"value\",\"value\":0.2},\"hatch_color\":{\"type\":\"value\",\"value\":\"#1f77b3\"},\"hatch_alpha\":{\"type\":\"value\",\"value\":0.2}}}}},{\"type\":\"object\",\"name\":\"GlyphRenderer\",\"id\":\"p3697\",\"attributes\":{\"data_source\":{\"type\":\"object\",\"name\":\"ColumnDataSource\",\"id\":\"p3687\",\"attributes\":{\"selected\":{\"type\":\"object\",\"name\":\"Selection\",\"id\":\"p3688\",\"attributes\":{\"indices\":[],\"line_indices\":[]}},\"selection_policy\":{\"type\":\"object\",\"name\":\"UnionRenderers\",\"id\":\"p3689\"},\"data\":{\"type\":\"map\",\"entries\":[[\"beak length (mm)\",{\"type\":\"ndarray\",\"array\":{\"type\":\"bytes\",\"data\":\"mpmZmZmZLEBmZmZmZmYrQJqZmZmZmStAAAAAAAAAKEAAAAAAAAAqQAAAAAAAACpAMzMzMzMzK0CamZmZmZkpQDMzMzMzMytAZmZmZmbmKUAzMzMzMzMqQM3MzMzMzCpAzczMzMzMK0CamZmZmZkoQAAAAAAAACxAAAAAAAAAKUCamZmZmZkoQM3MzMzMzCtAMzMzMzMzKkAAAAAAAAApQM3MzMzMzCtAZmZmZmZmK0AAAAAAAAAoQM3MzMzMzCxAAAAAAAAAK0CamZmZmZkrQAAAAAAAACpAzczMzMzMLUAAAAAAAAApQJqZmZmZmShAmpmZmZmZKUDNzMzMzMwqQJqZmZmZmStAAAAAAAAAK0AAAAAAAAArQM3MzMzMzCpAmpmZmZmZKEAzMzMzM7MsQGZmZmZmZipAmpmZmZmZK0AzMzMzMzMtQJqZmZmZmSxAmpmZmZmZK0AzMzMzMzMrQM3MzMzMzClAAAAAAAAAKkAAAAAAAAArQGZmZmZmZipAZmZmZmZmK0AzMzMzMzMqQGZmZmZmZipAMzMzMzMzKUAAAAAAAAAqQM3MzMzMzCtAZmZmZmZmKkAAAAAAAAAuQD0K16NwvSpAzczMzMzMJkCamZmZmZkrQAAAAAAAACpAAAAAAAAAKkAzMzMzMzMqQJqZmZmZmSlAmpmZmZmZKkAAAAAAAAArQM3MzMzMzChAMzMzMzMzKkAAAAAAAAAsQAAAAAAAACtAmpmZmZmZJ0BmZmZmZmYrQGZmZmZmZipAZmZmZmZmKEAAAAAAAAAqQDMzMzMzMypAZmZmZmZmLUBmZmZmZmYrQAAAAAAAACtAmpmZmZmZKkAzMzMzMzMsQAAAAAAAAClAZmZmZmZmK0AzMzMzMzMtQDMzMzMzMyxAzczMzMzMKUDNzMzMzMwrQM3MzMzMzCpAAAAAAAAAKkBmZmZmZmYpQDMzMzMzMyhAAAAAAAAALEDNzMzMzMwtQM3MzMzMzCtAzczMzMzMKUAzMzMzMzMtQAAAAAAAACxAAAAAAAAAKkBmZmZmZmYpQAAAAAAAACxAMzMzMzMzLEAzMzMzMzMsQAAAAAAAACpAAAAAAAAAK0DNzMzMzMwqQM3MzMzMzCtAMzMzMzMzKkDNzMzMzMwpQAAAAAAAACxAAAAAAAAALEAzMzMzMzMsQGZmZmZmZi1AzczMzMzMKkCamZmZmZkrQM3MzMzMzCpAmpmZmZmZK0DNzMzMzMwoQDMzMzMzMyxAzczMzMzMKUDNzMzMzMwrQJqZmZmZmSxAZmZmZmZmKkBmZmZmZmYsQAAAAAAAACpAMzMzMzMzLUAzMzMzMzMqQGZmZmZmZi5A\"},\"shape\":[126],\"dtype\":\"float64\",\"order\":\"little\"}],[\"beak depth (mm)\",{\"type\":\"ndarray\",\"array\":{\"type\":\"bytes\",\"data\":\"zczMzMzMIkAAAAAAAAAjQAAAAAAAACZAZmZmZmZmIUDNzMzMzMwgQDMzMzMzMyJAZmZmZmZmIUBmZmZmZmYkQDMzMzMzMyNAMzMzMzOzIUCamZmZmZkhQAAAAAAAACNAZmZmZmZmIkAAAAAAAAAiQJqZmZmZmSNAmpmZmZmZIkAAAAAAAAAiQGZmZmZmZiRAzczMzMzMHkAAAAAAAAAiQAAAAAAAACNAzczMzMzMIkAAAAAAAAAgQM3MzMzMzCFAzczMzMzMIkAAAAAAAAAjQAAAAAAAACBAAAAAAAAAJEBmZmZmZuYhQGZmZmZmZiBAmpmZmZmZIUBmZmZmZmYiQM3MzMzMzCJAAAAAAAAAI0AzMzMzMzMgQAAAAAAAACNAzczMzMzMIECamZmZmZkiQJqZmZmZmSJAMzMzMzMzI0BmZmZmZmYiQAAAAAAAACRAzczMzMzMIUAAAAAAAAAlQM3MzMzMzCFAMzMzMzMzIUCamZmZmZkhQM3MzMzMTCJAAAAAAAAAI0AzMzMzMzMiQGZmZmZmZiRAzczMzMzMIEAAAAAAAAAkQGZmZmZmZiRAmpmZmZmZIkCamZmZmZklQJqZmZmZmSBAMzMzMzMzH0CamZmZmZkjQJqZmZmZmR9AzczMzMzMIUDNzMzMzMweQM3MzMzMzCFAzczMzMzMIkDNzMzMzMwiQAAAAAAAACFAAAAAAAAAIUAzMzMzMzMjQGZmZmZmZiRAmpmZmZmZIUAAAAAAAAAjQJqZmZmZmSJAAAAAAAAAIkBmZmZmZmYiQGZmZmZmZiFAAAAAAAAAIkAzMzMzMzMiQGZmZmZmZiFAzczMzMzMIkCamZmZmZkjQDMzMzMzMyFAMzMzMzMzJUAAAAAAAAAiQAAAAAAAACNAMzMzMzMzIECamZmZmZkiQDMzMzMzMyNAAAAAAAAAIUBmZmZmZmYgQAAAAAAAACBAAAAAAAAAI0BmZmZmZmYjQM3MzMzMzCNAMzMzMzMzIkAAAAAAAAAjQJqZmZmZmSNAzczMzMzMIECamZmZmZkgQDMzMzMzMyNAzczMzMzMIkAAAAAAAAAkQM3MzMzMzCFAMzMzMzMzIkCamZmZmZkjQJqZmZmZmSJAzczMzMzMI0DNzMzMzMwhQAAAAAAAACFAMzMzMzMzJUCamZmZmZkiQM3MzMzMzCFAzczMzMzMIUBmZmZmZmYjQJqZmZmZmSNAAAAAAAAAJUDNzMzMzMwgQAAAAAAAACRAAAAAAAAAIkBmZmZmZmYhQJqZmZmZmSFAzczMzMzMIECamZmZmZkiQJqZmZmZmSNAzczMzMzMIUCamZmZmZkjQDMzMzMzMyJA\"},\"shape\":[126],\"dtype\":\"float64\",\"order\":\"little\"}],[\"beak_length_left_parenthesis_mm_right_parenthesis\",{\"type\":\"ndarray\",\"array\":{\"type\":\"bytes\",\"data\":\"mpmZmZmZLEBmZmZmZmYrQJqZmZmZmStAAAAAAAAAKEAAAAAAAAAqQAAAAAAAACpAMzMzMzMzK0CamZmZmZkpQDMzMzMzMytAZmZmZmbmKUAzMzMzMzMqQM3MzMzMzCpAzczMzMzMK0CamZmZmZkoQAAAAAAAACxAAAAAAAAAKUCamZmZmZkoQM3MzMzMzCtAMzMzMzMzKkAAAAAAAAApQM3MzMzMzCtAZmZmZmZmK0AAAAAAAAAoQM3MzMzMzCxAAAAAAAAAK0CamZmZmZkrQAAAAAAAACpAzczMzMzMLUAAAAAAAAApQJqZmZmZmShAmpmZmZmZKUDNzMzMzMwqQJqZmZmZmStAAAAAAAAAK0AAAAAAAAArQM3MzMzMzCpAmpmZmZmZKEAzMzMzM7MsQGZmZmZmZipAmpmZmZmZK0AzMzMzMzMtQJqZmZmZmSxAmpmZmZmZK0AzMzMzMzMrQM3MzMzMzClAAAAAAAAAKkAAAAAAAAArQGZmZmZmZipAZmZmZmZmK0AzMzMzMzMqQGZmZmZmZipAMzMzMzMzKUAAAAAAAAAqQM3MzMzMzCtAZmZmZmZmKkAAAAAAAAAuQD0K16NwvSpAzczMzMzMJkCamZmZmZkrQAAAAAAAACpAAAAAAAAAKkAzMzMzMzMqQJqZmZmZmSlAmpmZmZmZKkAAAAAAAAArQM3MzMzMzChAMzMzMzMzKkAAAAAAAAAsQAAAAAAAACtAmpmZmZmZJ0BmZmZmZmYrQGZmZmZmZipAZmZmZmZmKEAAAAAAAAAqQDMzMzMzMypAZmZmZmZmLUBmZmZmZmYrQAAAAAAAACtAmpmZmZmZKkAzMzMzMzMsQAAAAAAAAClAZmZmZmZmK0AzMzMzMzMtQDMzMzMzMyxAzczMzMzMKUDNzMzMzMwrQM3MzMzMzCpAAAAAAAAAKkBmZmZmZmYpQDMzMzMzMyhAAAAAAAAALEDNzMzMzMwtQM3MzMzMzCtAzczMzMzMKUAzMzMzMzMtQAAAAAAAACxAAAAAAAAAKkBmZmZmZmYpQAAAAAAAACxAMzMzMzMzLEAzMzMzMzMsQAAAAAAAACpAAAAAAAAAK0DNzMzMzMwqQM3MzMzMzCtAMzMzMzMzKkDNzMzMzMwpQAAAAAAAACxAAAAAAAAALEAzMzMzMzMsQGZmZmZmZi1AzczMzMzMKkCamZmZmZkrQM3MzMzMzCpAmpmZmZmZK0DNzMzMzMwoQDMzMzMzMyxAzczMzMzMKUDNzMzMzMwrQJqZmZmZmSxAZmZmZmZmKkBmZmZmZmYsQAAAAAAAACpAMzMzMzMzLUAzMzMzMzMqQGZmZmZmZi5A\"},\"shape\":[126],\"dtype\":\"float64\",\"order\":\"little\"}],[\"beak_depth_left_parenthesis_mm_right_parenthesis\",{\"type\":\"ndarray\",\"array\":{\"type\":\"bytes\",\"data\":\"zczMzMzMIkAAAAAAAAAjQAAAAAAAACZAZmZmZmZmIUDNzMzMzMwgQDMzMzMzMyJAZmZmZmZmIUBmZmZmZmYkQDMzMzMzMyNAMzMzMzOzIUCamZmZmZkhQAAAAAAAACNAZmZmZmZmIkAAAAAAAAAiQJqZmZmZmSNAmpmZmZmZIkAAAAAAAAAiQGZmZmZmZiRAzczMzMzMHkAAAAAAAAAiQAAAAAAAACNAzczMzMzMIkAAAAAAAAAgQM3MzMzMzCFAzczMzMzMIkAAAAAAAAAjQAAAAAAAACBAAAAAAAAAJEBmZmZmZuYhQGZmZmZmZiBAmpmZmZmZIUBmZmZmZmYiQM3MzMzMzCJAAAAAAAAAI0AzMzMzMzMgQAAAAAAAACNAzczMzMzMIECamZmZmZkiQJqZmZmZmSJAMzMzMzMzI0BmZmZmZmYiQAAAAAAAACRAzczMzMzMIUAAAAAAAAAlQM3MzMzMzCFAMzMzMzMzIUCamZmZmZkhQM3MzMzMTCJAAAAAAAAAI0AzMzMzMzMiQGZmZmZmZiRAzczMzMzMIEAAAAAAAAAkQGZmZmZmZiRAmpmZmZmZIkCamZmZmZklQJqZmZmZmSBAMzMzMzMzH0CamZmZmZkjQJqZmZmZmR9AzczMzMzMIUDNzMzMzMweQM3MzMzMzCFAzczMzMzMIkDNzMzMzMwiQAAAAAAAACFAAAAAAAAAIUAzMzMzMzMjQGZmZmZmZiRAmpmZmZmZIUAAAAAAAAAjQJqZmZmZmSJAAAAAAAAAIkBmZmZmZmYiQGZmZmZmZiFAAAAAAAAAIkAzMzMzMzMiQGZmZmZmZiFAzczMzMzMIkCamZmZmZkjQDMzMzMzMyFAMzMzMzMzJUAAAAAAAAAiQAAAAAAAACNAMzMzMzMzIECamZmZmZkiQDMzMzMzMyNAAAAAAAAAIUBmZmZmZmYgQAAAAAAAACBAAAAAAAAAI0BmZmZmZmYjQM3MzMzMzCNAMzMzMzMzIkAAAAAAAAAjQJqZmZmZmSNAzczMzMzMIECamZmZmZkgQDMzMzMzMyNAzczMzMzMIkAAAAAAAAAkQM3MzMzMzCFAMzMzMzMzIkCamZmZmZkjQJqZmZmZmSJAzczMzMzMI0DNzMzMzMwhQAAAAAAAACFAMzMzMzMzJUCamZmZmZkiQM3MzMzMzCFAzczMzMzMIUBmZmZmZmYjQJqZmZmZmSNAAAAAAAAAJUDNzMzMzMwgQAAAAAAAACRAAAAAAAAAIkBmZmZmZmYhQJqZmZmZmSFAzczMzMzMIECamZmZmZkiQJqZmZmZmSNAzczMzMzMIUCamZmZmZkjQDMzMzMzMyJA\"},\"shape\":[126],\"dtype\":\"float64\",\"order\":\"little\"}],[\"species\",[\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\",\"scandens\"]],[\"year\",{\"type\":\"ndarray\",\"array\":{\"type\":\"bytes\",\"data\":\"3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA3AcAANwHAADcBwAA\"},\"shape\":[126],\"dtype\":\"int32\",\"order\":\"little\"}]]}}},\"view\":{\"type\":\"object\",\"name\":\"CDSView\",\"id\":\"p3698\",\"attributes\":{\"filter\":{\"type\":\"object\",\"name\":\"AllIndices\",\"id\":\"p3699\"}}},\"glyph\":{\"type\":\"object\",\"name\":\"Scatter\",\"id\":\"p3694\",\"attributes\":{\"tags\":[\"apply_ranges\"],\"x\":{\"type\":\"field\",\"field\":\"beak length (mm)\"},\"y\":{\"type\":\"field\",\"field\":\"beak depth (mm)\"},\"line_color\":{\"type\":\"value\",\"value\":\"#ff7e0e\"},\"line_alpha\":{\"type\":\"value\",\"value\":0.75},\"fill_color\":{\"type\":\"value\",\"value\":\"#ff7e0e\"},\"fill_alpha\":{\"type\":\"value\",\"value\":0.75},\"hatch_color\":{\"type\":\"value\",\"value\":\"#ff7e0e\"},\"hatch_alpha\":{\"type\":\"value\",\"value\":0.75}}},\"selection_glyph\":{\"type\":\"object\",\"name\":\"Scatter\",\"id\":\"p3700\",\"attributes\":{\"tags\":[\"apply_ranges\"],\"x\":{\"type\":\"field\",\"field\":\"beak length (mm)\"},\"y\":{\"type\":\"field\",\"field\":\"beak depth (mm)\"},\"size\":{\"type\":\"value\",\"value\":4},\"angle\":{\"type\":\"value\",\"value\":0.0},\"line_color\":{\"type\":\"value\",\"value\":\"#ff7e0e\"},\"line_alpha\":{\"type\":\"value\",\"value\":0.75},\"line_width\":{\"type\":\"value\",\"value\":1},\"line_join\":{\"type\":\"value\",\"value\":\"bevel\"},\"line_cap\":{\"type\":\"value\",\"value\":\"butt\"},\"line_dash\":{\"type\":\"value\",\"value\":[]},\"line_dash_offset\":{\"type\":\"value\",\"value\":0},\"fill_color\":{\"type\":\"value\",\"value\":\"#ff7e0e\"},\"fill_alpha\":{\"type\":\"value\",\"value\":0.75},\"hatch_color\":{\"type\":\"value\",\"value\":\"#ff7e0e\"},\"hatch_alpha\":{\"type\":\"value\",\"value\":0.75},\"hatch_scale\":{\"type\":\"value\",\"value\":12.0},\"hatch_pattern\":{\"type\":\"value\",\"value\":null},\"hatch_weight\":{\"type\":\"value\",\"value\":1.0},\"marker\":{\"type\":\"value\",\"value\":\"circle\"}}},\"nonselection_glyph\":{\"type\":\"object\",\"name\":\"Scatter\",\"id\":\"p3695\",\"attributes\":{\"tags\":[\"apply_ranges\"],\"x\":{\"type\":\"field\",\"field\":\"beak length (mm)\"},\"y\":{\"type\":\"field\",\"field\":\"beak depth (mm)\"},\"line_color\":{\"type\":\"value\",\"value\":\"#ff7e0e\"},\"line_alpha\":{\"type\":\"value\",\"value\":0.75},\"fill_color\":{\"type\":\"value\",\"value\":\"#ff7e0e\"},\"fill_alpha\":{\"type\":\"value\",\"value\":0.75},\"hatch_color\":{\"type\":\"value\",\"value\":\"#ff7e0e\"},\"hatch_alpha\":{\"type\":\"value\",\"value\":0.1}}},\"muted_glyph\":{\"type\":\"object\",\"name\":\"Scatter\",\"id\":\"p3696\",\"attributes\":{\"tags\":[\"apply_ranges\"],\"x\":{\"type\":\"field\",\"field\":\"beak length (mm)\"},\"y\":{\"type\":\"field\",\"field\":\"beak depth (mm)\"},\"line_color\":{\"type\":\"value\",\"value\":\"#ff7e0e\"},\"line_alpha\":{\"type\":\"value\",\"value\":0.2},\"fill_color\":{\"type\":\"value\",\"value\":\"#ff7e0e\"},\"fill_alpha\":{\"type\":\"value\",\"value\":0.2},\"hatch_color\":{\"type\":\"value\",\"value\":\"#ff7e0e\"},\"hatch_alpha\":{\"type\":\"value\",\"value\":0.2}}}}}],\"toolbar\":{\"type\":\"object\",\"name\":\"Toolbar\",\"id\":\"p3626\",\"attributes\":{\"tools\":[{\"type\":\"object\",\"name\":\"HoverTool\",\"id\":\"p3619\",\"attributes\":{\"tags\":[\"hv_created\"],\"renderers\":[{\"id\":\"p3683\"},{\"id\":\"p3697\"}],\"tooltips\":[[\"species\",\"@{species}\"],[\"beak length (mm)\",\"@{beak_length_left_parenthesis_mm_right_parenthesis}\"],[\"beak depth (mm)\",\"@{beak_depth_left_parenthesis_mm_right_parenthesis}\"],[\"year\",\"@{year}\"]]}},{\"type\":\"object\",\"name\":\"SaveTool\",\"id\":\"p3651\"},{\"type\":\"object\",\"name\":\"PanTool\",\"id\":\"p3652\"},{\"type\":\"object\",\"name\":\"WheelZoomTool\",\"id\":\"p3653\"},{\"type\":\"object\",\"name\":\"BoxZoomTool\",\"id\":\"p3654\",\"attributes\":{\"overlay\":{\"type\":\"object\",\"name\":\"BoxAnnotation\",\"id\":\"p3655\",\"attributes\":{\"syncable\":false,\"level\":\"overlay\",\"visible\":false,\"left_units\":\"canvas\",\"right_units\":\"canvas\",\"bottom_units\":\"canvas\",\"top_units\":\"canvas\",\"line_color\":\"black\",\"line_alpha\":1.0,\"line_width\":2,\"line_dash\":[4,4],\"fill_color\":\"lightgrey\",\"fill_alpha\":0.5}}}},{\"type\":\"object\",\"name\":\"ResetTool\",\"id\":\"p3656\"}],\"active_drag\":{\"id\":\"p3652\"},\"active_scroll\":{\"id\":\"p3653\"}}},\"toolbar_location\":\"above\",\"left\":[{\"type\":\"object\",\"name\":\"LinearAxis\",\"id\":\"p3644\",\"attributes\":{\"ticker\":{\"type\":\"object\",\"name\":\"BasicTicker\",\"id\":\"p3645\",\"attributes\":{\"mantissas\":[1,2,5]}},\"formatter\":{\"type\":\"object\",\"name\":\"BasicTickFormatter\",\"id\":\"p3646\"},\"axis_label\":\"beak depth (mm)\",\"major_label_policy\":{\"type\":\"object\",\"name\":\"AllLabels\",\"id\":\"p3647\"}}}],\"below\":[{\"type\":\"object\",\"name\":\"LinearAxis\",\"id\":\"p3637\",\"attributes\":{\"ticker\":{\"type\":\"object\",\"name\":\"BasicTicker\",\"id\":\"p3638\",\"attributes\":{\"mantissas\":[1,2,5]}},\"formatter\":{\"type\":\"object\",\"name\":\"BasicTickFormatter\",\"id\":\"p3639\"},\"axis_label\":\"beak length (mm)\",\"major_label_policy\":{\"type\":\"object\",\"name\":\"AllLabels\",\"id\":\"p3640\"}}}],\"center\":[{\"type\":\"object\",\"name\":\"Grid\",\"id\":\"p3643\",\"attributes\":{\"axis\":{\"id\":\"p3637\"},\"ticker\":{\"id\":\"p3638\"}}},{\"type\":\"object\",\"name\":\"Grid\",\"id\":\"p3650\",\"attributes\":{\"dimension\":1,\"axis\":{\"id\":\"p3644\"},\"ticker\":{\"id\":\"p3645\"}}}],\"min_border_top\":10,\"min_border_bottom\":10,\"min_border_left\":10,\"min_border_right\":10,\"output_backend\":\"webgl\"}}],\"callbacks\":{\"type\":\"map\"}}};\n", " const render_items = [{\"docid\":\"b4625f9a-dcf1-43e4-a762-1f2a27df2f52\",\"roots\":{\"p3621\":\"be2de77a-5f24-4285-ad47-0848ea88a9cb\"},\"root_ids\":[\"p3621\"]}];\n", " root.Bokeh.embed.embed_items_notebook(docs_json, render_items);\n", " }\n", " if (root.Bokeh !== undefined) {\n", " embed_document(root);\n", " } else {\n", " let attempts = 0;\n", " const timer = setInterval(function(root) {\n", " if (root.Bokeh !== undefined) {\n", " clearInterval(timer);\n", " embed_document(root);\n", " } else {\n", " attempts++;\n", " if (attempts > 100) {\n", " clearInterval(timer);\n", " console.log(\"Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing\");\n", " }\n", " }\n", " }, 10, root)\n", " }\n", "})(window);" ], "application/vnd.bokehjs_exec.v0+json": "" }, "metadata": { "application/vnd.bokehjs_exec.v0+json": { "id": "p3621" } }, "output_type": "display_data" } ], "source": [ "hv_fig = hv.Points(\n", " data=df,\n", " kdims=['beak length (mm)', 'beak depth (mm)'],\n", " vdims=['species', 'year'],\n", ").groupby(\n", " ['species', 'year'],\n", ").opts(\n", " tools=['hover'],\n", " show_legend=False,\n", ").overlay(\n", " 'species',\n", ")\n", "\n", "# Take out the Bokeh object\n", "p = hv.render(hv_fig)\n", "\n", "# Display using Bokeh\n", "bokeh.io.show(p)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that we got the plot for 1973, which was the first year offered by the interactive HoloMap. If we wanted another year, we would have to make a plot specifically for that year." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Other kinds of plots\n", "\n", "We have seen the basics of how HoloViews works for a scatter plot specified by `hv.Points`. We now show some other kinds of plots we have encountered until now." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Smooth function\n", "\n", "HoloViews can plot a smooth function using the `hv.Curve`. For a Curve, there is one key dimension, which is the independent variable, and one value dimension, which is the dependent variable. This is to be contrasted with `hv.Path`, which has two key dimensions, meaning that neither of the variables is strictly dependent on the other.\n", "\n", "Here is a HoloViews plot of the x-section of the Airy disk. We can either provide a data frame with columns, or we can provide a 2-tuple of NumPy arrays that serve as the dependent and independent variable, respectively." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ], "text/plain": [ ":Curve [x] (normalized intensity)" ] }, "execution_count": 14, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "p3835" } }, "output_type": "execute_result" } ], "source": [ "# The x-values we want\n", "x = np.linspace(-15, 15, 400)\n", "\n", "# The normalized intensity\n", "norm_I = 4 * (scipy.special.j1(x) / x)**2\n", "\n", "hv.Curve(\n", " data=(x, norm_I),\n", " kdims='x',\n", " vdims='normalized intensity'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Box plot\n", "\n", "Box plots are made using `hv.BoxWhisker` elements. If multiple key dimensions are specified, nested categorical axes are automatically set up." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "BokehUserWarning: ColumnDataSource's columns must be of the same length. Current lengths: ('beak depth (mm)', 32), ('beak_depth_left_parenthesis_mm_right_parenthesis', 0), ('index', 32)\n", "BokehUserWarning: ColumnDataSource's columns must be of the same length. Current lengths: ('beak depth (mm)', 32), ('beak_depth_left_parenthesis_mm_right_parenthesis', 0), ('index', 32)\n" ] }, { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ], "text/plain": [ ":BoxWhisker [species,year] (beak depth (mm))" ] }, "execution_count": 15, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "p3996" } }, "output_type": "execute_result" } ], "source": [ "hv.BoxWhisker(\n", " data=df,\n", " kdims=['species', 'year'],\n", " vdims=['beak depth (mm)'],\n", ").opts(\n", " box_color='species',\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Strip plots\n", "\n", "We use `hv.Scatter()` to generate strip plots. When we specify the `jitter` kwargs, we specify the width of the jitter.\n", "\n", "Note that nested categorical axes are currently (as of June 10, 2021) only supported for box, violin, and bar plots, as [per the docs](http://holoviews.org/user_guide/Customizing_Plots.html#Categorical-axes), but will eventually be supported for many more plot types, including `Scatter`, which are used to generate strip plots." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ], "text/plain": [ ":NdLayout [species]\n", " :Scatter [year_str] (beak depth (mm),species)" ] }, "execution_count": 16, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "p4424" } }, "output_type": "execute_result" } ], "source": [ "# Make the year column a string to can use as categorical\n", "df['year_str'] = df['year'].astype(str)\n", "\n", "hv.Scatter(\n", " data=df,\n", " kdims=[('year_str', 'year')],\n", " vdims=['beak depth (mm)', 'species'],\n", ").groupby(\n", " 'species'\n", ").opts(\n", " color='species',\n", " jitter=0.4,\n", " show_legend=False,\n", " width=400,\n", " height=250,\n", ").layout(\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Histograms\n", "\n", "When making a histogram, the values of the bin edges and counts must be computed beforehand using `np.histogram()`." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "edges, counts = np.histogram(df_2012['beak depth (mm)'], bins=int(np.sqrt(len(df_2012))))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We then can pass the bin edges and counts into `hv.Histogram()`." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ], "text/plain": [ ":Histogram [beak depth (mm)] (Frequency)" ] }, "execution_count": 18, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "p4815" } }, "output_type": "execute_result" } ], "source": [ "hv.Histogram(\n", " data=(edges, counts),\n", " kdims='beak depth (mm)'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ECDFs\n", "\n", "HoloViews does not have native support for ECDFs ([my fault](https://github.com/holoviz/holoviews/issues/3821); I'm the one who is supposed to add this), but we can create ECDFs in a data frame and use `hv.Scatter` to make a plot of an ECDF." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "def ecdf_transform(data):\n", " return data.rank(method=\"first\") / len(data)\n", "\n", "df_2012[\"beak depth ECDF\"] = df_2012.groupby(\"species\")[\n", " \"beak depth (mm)\"\n", "].transform(ecdf_transform).values" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After supplying the y-values for the ECDF, we plot with `hv.Scatter`." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": {}, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.holoviews_exec.v0+json": "", "text/html": [ "
\n", "
\n", "
\n", "" ], "text/plain": [ ":NdOverlay [species]\n", " :Scatter [beak depth (mm)] (beak depth ECDF,species)" ] }, "execution_count": 20, "metadata": { "application/vnd.holoviews_exec.v0+json": { "id": "p4977" } }, "output_type": "execute_result" } ], "source": [ "hv.Scatter(\n", " data=df_2012,\n", " kdims='beak depth (mm)',\n", " vdims=[('beak depth ECDF', 'ECDF'), 'species'],\n", ").groupby(\n", " 'species'\n", ").overlay(\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Conclusions\n", "\n", "HoloViews is one of many high-level plotting libraries in Python. Others include [Vega-Altair](https://altair-viz.github.io), [Seaborn](https://seaborn.pydata.org), [Plotly](https://plotly.com/python/), and [plotnine](https://plotnine.readthedocs.io/). There is a pretty complete list available from [PyViz](https://pyviz.org/tools.html). HoloViews is my personal favorite, though, because of easy rendering with Bokeh and clear logic connecting annotated data sets to graphics.\n", "\n", "We have only begun to scratch the surface of what HoloViews can do. You can explore [HoloViews's extensive documentation](http://holoviews.org/index.html) to check out more of its capabilities. \n", "\n", "In the next few lessons, we will explore dealing with overplotting and dashboarding, two powerful plotting techniques you may not have thought about that can be transformative for your research." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Computing environment" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "tags": [ "hide-input" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Python implementation: CPython\n", "Python version : 3.11.3\n", "IPython version : 8.12.0\n", "\n", "numpy : 1.24.3\n", "scipy : 1.10.1\n", "pandas : 1.5.3\n", "bootcamp_utils: 0.0.7\n", "bokeh : 3.1.1\n", "holoviews : 1.16.2\n", "jupyterlab : 3.6.3\n", "\n" ] } ], "source": [ "%load_ext watermark\n", "%watermark -v -p numpy,scipy,pandas,bootcamp_utils,bokeh,holoviews,jupyterlab" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.3" } }, "nbformat": 4, "nbformat_minor": 4 }