diff --git a/_toc.yml b/_toc.yml
index e851e59..8ef1e0e 100644
--- a/_toc.yml
+++ b/_toc.yml
@@ -11,4 +11,5 @@ parts:
- caption: Workflows
chapters:
- file: notebooks/enso-globus
+ - file: notebooks/enso-globus-flow
- file: notebooks/yearly-average-selection-globus
diff --git a/notebooks/enso-globus-flow.ipynb b/notebooks/enso-globus-flow.ipynb
new file mode 100644
index 0000000..7866077
--- /dev/null
+++ b/notebooks/enso-globus-flow.ipynb
@@ -0,0 +1,3286 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "48c69fff-ab3b-49c8-b85b-95fef1250249",
+ "metadata": {},
+ "source": [
+ " \n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "483dcdb6-e125-4a52-a21f-55cfe1000dea",
+ "metadata": {},
+ "source": [
+ "# ENSO Calculations using Globus Flows"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4a415308-0e9a-470c-bb68-da75b349c006",
+ "metadata": {},
+ "source": [
+ "## Overview\n",
+ "\n",
+ "In this workflow, we combine topics covered in previous Pythia Foundations and CMIP6 Cookbook content to compute the [Niño 3.4 Index](https://climatedataguide.ucar.edu/climate-data/nino-sst-indices-nino-12-3-34-4-oni-and-tni) to multiple datasets, with the primary computations occuring on a remote machine. As a refresher of what the ENSO 3.4 index is, please see the following text, which is also included in the [ENSO Xarray](https://foundations.projectpythia.org/core/xarray/enso-xarray.html) content in the Pythia Foundations content.\n",
+ "\n",
+ "> Niño 3.4 (5N-5S, 170W-120W): The Niño 3.4 anomalies may be thought of as representing the average equatorial SSTs across the Pacific from about the dateline to the South American coast. The Niño 3.4 index typically uses a 5-month running mean, and El Niño or La Niña events are defined when the Niño 3.4 SSTs exceed +/- 0.4C for a period of six months or more.\n",
+ "\n",
+ "> Niño X Index computation: a) Compute area averaged total SST from Niño X region; b) Compute monthly climatology (e.g., 1950-1979) for area averaged total SST from Niño X region, and subtract climatology from area averaged total SST time series to obtain anomalies; c) Smooth the anomalies with a 5-month running mean; d) Normalize the smoothed values by its standard deviation over the climatological period.\n",
+ "\n",
+ "![](https://www.ncdc.noaa.gov/monitoring-content/teleconnections/nino-regions.gif)\n",
+ "\n",
+ "The previous cookbook, we ran this in a single notebook locally. In this example, we aim to execute the workflow on a remote machine, with only the visualizion of the dataset occuring locally.\n",
+ "\n",
+ "The overall goal of this tutorial is to introduce the idea of functions as a service with Globus, and how this can be used to calculate ENSO indices."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2d4c6aed-a9c5-4d29-bfa3-c8e8be230567",
+ "metadata": {},
+ "source": [
+ "## Prerequisites\n",
+ "\n",
+ "| Concepts | Importance | Notes |\n",
+ "| --- | --- | --- |\n",
+ "| [Intro to Xarray](https://foundations.projectpythia.org/core/xarray/xarray-intro.html) | Necessary | |\n",
+ "| [hvPlot Basics](https://hvplot.holoviz.org/getting_started/hvplot.html) | Necessary | Interactive Visualization with hvPlot |\n",
+ "| [Understanding of NetCDF](https://foundations.projectpythia.org/core/data-formats/netcdf-cf.html) | Helpful | Familiarity with metadata structure |\n",
+ "| [Calculating ENSO with Xarray](https://foundations.projectpythia.org/core/xarray/enso-xarray.html) | Neccessary | Understanding of Masking and Xarray Functions |\n",
+ "| Dask | Helpful | |\n",
+ "\n",
+ "- **Time to learn**: 30 minutes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7ff38f37-8f14-443f-b0c7-188baf75d1be",
+ "metadata": {},
+ "source": [
+ "## Imports"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "52bcfa1a-3907-446d-b384-29e97b5c8cb9",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/javascript": [
+ "(function(root) {\n",
+ " function now() {\n",
+ " return new Date();\n",
+ " }\n",
+ "\n",
+ " var force = true;\n",
+ " var py_version = '3.3.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n",
+ " var reloading = false;\n",
+ " var Bokeh = root.Bokeh;\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.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.3.2/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.3.2/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.3.2/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.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.3.1.min.js\", \"https://cdn.holoviz.org/panel/1.3.2/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",
+ "\ttry {\n",
+ " inline_js[i].call(root, root.Bokeh);\n",
+ "\t} catch(e) {\n",
+ "\t if (!reloading) {\n",
+ "\t throw e;\n",
+ "\t }\n",
+ "\t}\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",
+ " root._bokeh_is_initializing = true\n",
+ " root._bokeh_onload_callbacks = []\n",
+ " var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n",
+ " if (!reloading && !bokeh_loaded) {\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.3.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n var reloading = false;\n var Bokeh = root.Bokeh;\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.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.3.2/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.3.2/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.3.2/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.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.3.1.min.js\", \"https://cdn.holoviz.org/panel/1.3.2/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\ttry {\n inline_js[i].call(root, root.Bokeh);\n\t} catch(e) {\n\t if (!reloading) {\n\t throw e;\n\t }\n\t}\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 root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\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": {
+ "application/vnd.holoviews_exec.v0+json": "",
+ "text/html": [
+ "
\n",
+ ""
+ ]
+ },
+ "metadata": {
+ "application/vnd.holoviews_exec.v0+json": {
+ "id": "p2076"
+ }
+ },
+ "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 hvplot.xarray\n",
+ "import holoviews as hv\n",
+ "import numpy as np\n",
+ "import hvplot.xarray\n",
+ "import matplotlib.pyplot as plt\n",
+ "import cartopy.crs as ccrs\n",
+ "from intake_esgf import ESGFCatalog\n",
+ "import xarray as xr\n",
+ "import cf_xarray\n",
+ "import warnings\n",
+ "import json\n",
+ "import os\n",
+ "import time\n",
+ "import globus_sdk\n",
+ "from globus_compute_sdk import Client, Executor\n",
+ "from globus_automate_client import FlowsClient\n",
+ "\n",
+ "# Import Globus scopes\n",
+ "from globus_sdk.scopes import SearchScopes\n",
+ "from globus_sdk.scopes import TransferScopes\n",
+ "from globus_sdk.scopes import FlowsScopes\n",
+ "warnings.filterwarnings(\"ignore\")\n",
+ "\n",
+ "hv.extension(\"bokeh\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "252748e9-c3a4-4018-8b9b-c26c40465faf",
+ "metadata": {},
+ "source": [
+ "## Accessing our Data and Computing the ENSO 3.4 Index\n",
+ "As mentioned in the introduction, we are utilizing functions from the previous ENSO notebooks. In order to run these with Globus Compute, we need to comply with the following requirements\n",
+ "- All libraries/packages used in the function need to be installed on the globus compute endpoint\n",
+ "- All functions/libraries/packages need to be imported and defined within the function to execute\n",
+ "- The output from the function needs to serializable (ex. xarray.Dataset, numpy.array)\n",
+ "\n",
+ "Using these constraints, we setup the following function, with the key parameter being which modeling center (model) to compare. Two examples here include The National Center for Atmospheric Research (NCAR) and the Model for Interdisciplinary Research on Climate (MIROC)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "2b74d939-f87d-4a44-9e4a-6643b7d04fe7",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "def run_plot_enso(model, return_path=False):\n",
+ " import numpy as np\n",
+ " import matplotlib.pyplot as plt\n",
+ " from intake_esgf import ESGFCatalog\n",
+ " import xarray as xr\n",
+ " import cf_xarray\n",
+ " import warnings\n",
+ " warnings.filterwarnings(\"ignore\")\n",
+ "\n",
+ " def search_esgf(institution_id, grid='gn'):\n",
+ "\n",
+ " # Search and load the ocean surface temperature (tos)\n",
+ " cat = ESGFCatalog()\n",
+ " cat.search(\n",
+ " activity_id=\"CMIP\",\n",
+ " experiment_id=\"historical\",\n",
+ " institution_id=institution_id,\n",
+ " variable_id=[\"tos\"],\n",
+ " member_id='r11i1p1f1',\n",
+ " table_id=\"Omon\",\n",
+ " )\n",
+ " try:\n",
+ " tos_ds = cat.to_datatree()[grid].to_dataset()\n",
+ " except ValueError:\n",
+ " tos_ds = cat.to_dataset_dict()[\"\"]\n",
+ "\n",
+ " return tos_ds\n",
+ "\n",
+ " def calculate_enso(ds):\n",
+ "\n",
+ " # Subset the El Nino 3.4 index region\n",
+ " dso = ds.where(\n",
+ " (ds.cf[\"latitude\"] < 5) & (ds.cf[\"latitude\"] > -5) & (ds.cf[\"longitude\"] > 190) & (ds.cf[\"longitude\"] < 240), drop=True\n",
+ " )\n",
+ "\n",
+ " # Calculate the monthly means\n",
+ " gb = dso.tos.groupby('time.month')\n",
+ "\n",
+ " # Subtract the monthly averages, returning the anomalies\n",
+ " tos_nino34_anom = gb - gb.mean(dim='time')\n",
+ "\n",
+ " # Determine the non-time dimensions and average using these\n",
+ " non_time_dims = set(tos_nino34_anom.dims)\n",
+ " non_time_dims.remove(ds.tos.cf[\"T\"].name)\n",
+ " weighted_average = tos_nino34_anom.weighted(ds[\"areacello\"]).mean(dim=list(non_time_dims))\n",
+ "\n",
+ " # Calculate the rolling average\n",
+ " rolling_average = weighted_average.rolling(time=5, center=True).mean()\n",
+ " std_dev = weighted_average.std()\n",
+ " return rolling_average / std_dev\n",
+ "\n",
+ " def add_enso_thresholds(da, threshold=0.4):\n",
+ "\n",
+ " # Conver the xr.DataArray into an xr.Dataset\n",
+ " ds = da.to_dataset()\n",
+ "\n",
+ " # Cleanup the time and use the thresholds\n",
+ " try:\n",
+ " ds[\"time\"]= ds.indexes[\"time\"].to_datetimeindex()\n",
+ " except:\n",
+ " pass\n",
+ " ds[\"tos_gt_04\"] = (\"time\", ds.tos.where(ds.tos >= threshold, threshold).data)\n",
+ " ds[\"tos_lt_04\"] = (\"time\", ds.tos.where(ds.tos <= -threshold, -threshold).data)\n",
+ "\n",
+ " # Add fields for the thresholds\n",
+ " ds[\"el_nino_threshold\"] = (\"time\", np.zeros_like(ds.tos) + threshold)\n",
+ " ds[\"la_nina_threshold\"] = (\"time\", np.zeros_like(ds.tos) - threshold)\n",
+ "\n",
+ " return ds\n",
+ " \n",
+ " ds = search_esgf(\"NCAR\")\n",
+ " enso_index = add_enso_thresholds(calculate_enso(ds).compute())\n",
+ " enso_index.attrs = ds.attrs\n",
+ " enso_index.attrs[\"model\"] = model\n",
+ "\n",
+ " return enso_index"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e5ad93de-5473-4579-8ee4-cadd0fbb90b2",
+ "metadata": {},
+ "source": [
+ "## Configure Globus Compute\n",
+ "\n",
+ "Now that we have our functions, we can move toward using [Globus Flows](https://www.globus.org/globus-flows-service) and [Globus Compute](https://www.globus.org/compute).\n",
+ "\n",
+ "Globus Flows is a reliable and secure platform for orchestrating and performing research data management and analysis tasks. A flow is often needed to manage data coming from instruments, e.g., image files can be moved from local storage attached to a microscope to a high-performance storage system where they may be accessed by all members of the research project.\n",
+ "\n",
+ "More examples of creating and running flows can be found on our [demo instance](https://jupyter.demo.globus.org/hub/)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "663dfed0-e099-43db-98ad-9eb5021ac69e",
+ "metadata": {},
+ "source": [
+ "### Setup a Globus Compute Endpoint\n",
+ "Globus Compute (GC) is a service that allows **python functions** to be sent to remote points, executed, with the output from that function returned to the user. While there are a collection of endpoints already installed, we highlight in this section the steps required to configure for yourself. This idea is also known as \"serverless\" computing, where users do not need to think about the underlying infrastructure executing the code, but rather submit functions to be run and returned.\n",
+ "\n",
+ "To start a GC endpoint at your system you need to login, [configure a conda environment](https://foundations.projectpythia.org/foundations/how-to-run-python.html#installing-and-managing-python-with-conda), and `pip install globus-compute-endpoint`.\n",
+ "\n",
+ "You can then run:\n",
+ "\n",
+ "```globus-compute-endpoint configure esgf-test```\n",
+ "\n",
+ "```globus-compute-endpoint start esgf-test```\n",
+ "\n",
+ "Note that by default your endpoint will execute tasks on the login node (if you are using a High Performance Compute System). Additional configuration is needed for the endpoint to provision compute nodes. For example, here is the documentation on configuring globus compute endpoints on the Argonne Leadership Computing Facility's Polaris system\n",
+ "- https://globus-compute.readthedocs.io/en/latest/endpoints.html#polaris-alcf"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "fe8d9e8b-e38d-41a5-b5f6-df9916d69f83",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "endpoint_id = \"6836803d-9831-4dc5-b159-eb658250e4bc\"\n",
+ "personal_endpoint_id = \"92bb829c-9d88-11ed-b579-33287ee02ec7\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ef408588-1e81-4726-892b-a9b0ad2f38cc",
+ "metadata": {},
+ "source": [
+ "### Setup an Executor to Run our Functions\n",
+ "Once we have our compute endpoint ID, we need to pass this to our executor, which will be used to pass our functions from our local machine to the machine we would like to compute on."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "0aa43e9e-6840-4b46-9a0c-ceeef8ca7e1e",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Executor"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "gce = Executor(endpoint_id=endpoint_id)\n",
+ "gce"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4afe4cec-fca9-40ed-b20a-39061ad1d45a",
+ "metadata": {},
+ "source": [
+ "### Test our Functions\n",
+ "Now that we have our functions prepared, and an executor to run on, we can test them out using our endpoint!\n",
+ "\n",
+ "We pass in our function name, and the additional arguments for our functions. For example, let's look at comparing at the NCAR and MIROC modeling center's CMIP6 simulations."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "664c9fd2-8822-4e34-9c2b-8558c489e487",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "ncar_task = gce.submit(run_plot_enso, model='NCAR')\n",
+ "miroc_task = gce.submit(run_plot_enso, model='MIROC')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ccffe7fe-f11c-4b43-9b1a-b140eb1aa8a5",
+ "metadata": {},
+ "source": [
+ "The results are started as python objects, with the resultant datasets available using `.result()`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "6c2f0f35-9847-43bb-8e4c-b42ba5060233",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "
<xarray.Dataset>\n",
+ "Dimensions: (time: 1980)\n",
+ "Coordinates:\n",
+ " * time (time) datetime64[ns] 1850-01-15T13:00:00.000008 ... 2...\n",
+ " month (time) int64 1 2 3 4 5 6 7 8 9 ... 4 5 6 7 8 9 10 11 12\n",
+ "Data variables:\n",
+ " tos (time) float32 nan nan 0.06341 ... 0.7921 nan nan\n",
+ " tos_gt_04 (time) float32 0.4 0.4 0.4 0.4 ... 0.6829 0.7921 0.4 0.4\n",
+ " tos_lt_04 (time) float32 -0.4 -0.4 -0.4 -0.4 ... -0.4 -0.4 -0.4\n",
+ " el_nino_threshold (time) float32 0.4 0.4 0.4 0.4 0.4 ... 0.4 0.4 0.4 0.4\n",
+ " la_nina_threshold (time) float32 -0.4 -0.4 -0.4 -0.4 ... -0.4 -0.4 -0.4\n",
+ "Attributes: (12/46)\n",
+ " Conventions: CF-1.7 CMIP-6.2\n",
+ " activity_id: CMIP\n",
+ " branch_method: standard\n",
+ " branch_time_in_child: 674885.0\n",
+ " branch_time_in_parent: 219000.0\n",
+ " case_id: 972\n",
+ " ... ...\n",
+ " table_id: Omon\n",
+ " tracking_id: hdl:21.14100/b0ffb89d-095d-4533-a159-a2e1241ff138\n",
+ " variable_id: tos\n",
+ " variant_info: CMIP6 20th century experiments (1850-2014) with C...\n",
+ " variant_label: r11i1p1f1\n",
+ " model: NCAR Dimensions:
Coordinates: (2)
Data variables: (5)
tos
(time)
float32
nan nan 0.06341 ... 0.7921 nan nan
array([ nan, nan, 0.06341499, ..., 0.79205155, nan,\n",
+ " nan], dtype=float32) tos_gt_04
(time)
float32
0.4 0.4 0.4 0.4 ... 0.7921 0.4 0.4
array([0.4 , 0.4 , 0.4 , ..., 0.79205155, 0.4 ,\n",
+ " 0.4 ], dtype=float32) tos_lt_04
(time)
float32
-0.4 -0.4 -0.4 ... -0.4 -0.4 -0.4
array([-0.4, -0.4, -0.4, ..., -0.4, -0.4, -0.4], dtype=float32) el_nino_threshold
(time)
float32
0.4 0.4 0.4 0.4 ... 0.4 0.4 0.4 0.4
array([0.4, 0.4, 0.4, ..., 0.4, 0.4, 0.4], dtype=float32) la_nina_threshold
(time)
float32
-0.4 -0.4 -0.4 ... -0.4 -0.4 -0.4
array([-0.4, -0.4, -0.4, ..., -0.4, -0.4, -0.4], dtype=float32) Indexes: (1)
PandasIndex
PandasIndex(DatetimeIndex(['1850-01-15 13:00:00.000008', '1850-02-14 00:00:00',\n",
+ " '1850-03-15 12:00:00', '1850-04-15 00:00:00',\n",
+ " '1850-05-15 12:00:00', '1850-06-15 00:00:00',\n",
+ " '1850-07-15 12:00:00', '1850-08-15 12:00:00',\n",
+ " '1850-09-15 00:00:00', '1850-10-15 12:00:00',\n",
+ " ...\n",
+ " '2014-03-15 12:00:00', '2014-04-15 00:00:00',\n",
+ " '2014-05-15 12:00:00', '2014-06-15 00:00:00',\n",
+ " '2014-07-15 12:00:00', '2014-08-15 12:00:00',\n",
+ " '2014-09-15 00:00:00', '2014-10-15 12:00:00',\n",
+ " '2014-11-15 00:00:00', '2014-12-15 12:00:00'],\n",
+ " dtype='datetime64[ns]', name='time', length=1980, freq=None)) Attributes: (46)
Conventions : CF-1.7 CMIP-6.2 activity_id : CMIP branch_method : standard branch_time_in_child : 674885.0 branch_time_in_parent : 219000.0 case_id : 972 cesm_casename : b.e21.BHIST.f09_g17.CMIP6-historical.011 contact : cesm_cmip6@ucar.edu creation_date : 2019-04-02T03:29:09Z data_specs_version : 01.00.29 experiment : Simulation of recent past (1850 to 2014). Impose changing conditions (consistent with observations). Should be initialised from a point early enough in the pre-industrial control run to ensure that the end of all the perturbed runs branching from the end of this historical run end before the end of the control. Only one ensemble member is requested but modelling groups are strongly encouraged to submit at least three ensemble members of their CMIP historical simulation. experiment_id : historical external_variables : areacello forcing_index : 1 frequency : mon further_info_url : https://furtherinfo.es-doc.org/CMIP6.NCAR.CESM2.historical.none.r11i1p1f1 grid : native gx1v7 displaced pole grid (384x320 latxlon) grid_label : gn initialization_index : 1 institution : National Center for Atmospheric Research, Climate and Global Dynamics Laboratory, 1850 Table Mesa Drive, Boulder, CO 80305, USA institution_id : NCAR license : CMIP6 model data produced by <The National Center for Atmospheric Research> is licensed under a Creative Commons Attribution-[]ShareAlike 4.0 International License (https://creativecommons.org/licenses/). Consult https://pcmdi.llnl.gov/CMIP6/TermsOfUse for terms of use governing CMIP6 output, including citation requirements and proper acknowledgment. Further information about this data, including some limitations, can be found via the further_info_url (recorded as a global attribute in this file)[]. The data producers and data providers make no warranty, either express or implied, including, but not limited to, warranties of merchantability and fitness for a particular purpose. All liabilities arising from the supply of the information (including any liability arising in negligence) are excluded to the fullest extent permitted by law. mip_era : CMIP6 model_doi_url : https://doi.org/10.5065/D67H1H0V nominal_resolution : 100 km parent_activity_id : CMIP parent_experiment_id : piControl parent_mip_era : CMIP6 parent_source_id : CESM2 parent_time_units : days since 0001-01-01 00:00:00 parent_variant_label : r1i1p1f1 physics_index : 1 product : model-output realization_index : 11 realm : ocean source : CESM2 (2017): atmosphere: CAM6 (0.9x1.25 finite volume grid; 288 x 192 longitude/latitude; 32 levels; top level 2.25 mb); ocean: POP2 (320x384 longitude/latitude; 60 levels; top grid cell 0-10 m); sea_ice: CICE5.1 (same grid as ocean); land: CLM5 0.9x1.25 finite volume grid; 288 x 192 longitude/latitude; 32 levels; top level 2.25 mb); aerosol: MAM4 (0.9x1.25 finite volume grid; 288 x 192 longitude/latitude; 32 levels; top level 2.25 mb); atmoschem: MAM4 (0.9x1.25 finite volume grid; 288 x 192 longitude/latitude; 32 levels; top level 2.25 mb); landIce: CISM2.1; ocnBgchem: MARBL (320x384 longitude/latitude; 60 levels; top grid cell 0-10 m) source_id : CESM2 source_type : AOGCM BGC sub_experiment : none sub_experiment_id : none table_id : Omon tracking_id : hdl:21.14100/b0ffb89d-095d-4533-a159-a2e1241ff138 variable_id : tos variant_info : CMIP6 20th century experiments (1850-2014) with CAM6, interactive land (CLM5), coupled ocean (POP2) with biogeochemistry (MARBL), interactive sea ice (CICE5.1), and non-evolving land ice (CISM2.1) variant_label : r11i1p1f1 model : NCAR "
+ ],
+ "text/plain": [
+ "\n",
+ "Dimensions: (time: 1980)\n",
+ "Coordinates:\n",
+ " * time (time) datetime64[ns] 1850-01-15T13:00:00.000008 ... 2...\n",
+ " month (time) int64 1 2 3 4 5 6 7 8 9 ... 4 5 6 7 8 9 10 11 12\n",
+ "Data variables:\n",
+ " tos (time) float32 nan nan 0.06341 ... 0.7921 nan nan\n",
+ " tos_gt_04 (time) float32 0.4 0.4 0.4 0.4 ... 0.6829 0.7921 0.4 0.4\n",
+ " tos_lt_04 (time) float32 -0.4 -0.4 -0.4 -0.4 ... -0.4 -0.4 -0.4\n",
+ " el_nino_threshold (time) float32 0.4 0.4 0.4 0.4 0.4 ... 0.4 0.4 0.4 0.4\n",
+ " la_nina_threshold (time) float32 -0.4 -0.4 -0.4 -0.4 ... -0.4 -0.4 -0.4\n",
+ "Attributes: (12/46)\n",
+ " Conventions: CF-1.7 CMIP-6.2\n",
+ " activity_id: CMIP\n",
+ " branch_method: standard\n",
+ " branch_time_in_child: 674885.0\n",
+ " branch_time_in_parent: 219000.0\n",
+ " case_id: 972\n",
+ " ... ...\n",
+ " table_id: Omon\n",
+ " tracking_id: hdl:21.14100/b0ffb89d-095d-4533-a159-a2e1241ff138\n",
+ " variable_id: tos\n",
+ " variant_info: CMIP6 20th century experiments (1850-2014) with C...\n",
+ " variant_label: r11i1p1f1\n",
+ " model: NCAR"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "ncar_ds = ncar_task.result()\n",
+ "miroc_ds = miroc_task.result()\n",
+ "\n",
+ "ncar_ds"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "6a862fe8-aa31-48f8-874a-2134a3133b1c",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "ncar_ds = ncar_task.result()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f1257d1a-9712-427b-b9ce-4db644420839",
+ "metadata": {},
+ "source": [
+ "### Plot our Data\n",
+ "Now that we have pre-computed datasets, the last step is to visualize the output. In the other example, we stepped through how to utilize the `.hvplot` tool to create interactive displays of ENSO values. We will utilize that functionality here, wrapping into a function."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "cac34be7-4faa-417c-b607-d8ee094be3e5",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "def plot_enso(ds):\n",
+ " el_nino = ds.hvplot.area(x=\"time\", y2='tos_gt_04', y='el_nino_threshold', color='red', hover=False)\n",
+ " el_nino_label = hv.Text(ds.isel(time=40).time.values, 2, 'El Niño').opts(text_color='red',)\n",
+ "\n",
+ " # Create the La Niña area graphs\n",
+ " la_nina = ds.hvplot.area(x=\"time\", y2='tos_lt_04', y='la_nina_threshold', color='blue', hover=False)\n",
+ " la_nina_label = hv.Text(ds.isel(time=-40).time.values, -2, 'La Niña').opts(text_color='blue')\n",
+ "\n",
+ " # Plot a timeseries of the ENSO 3.4 index\n",
+ " enso = ds.tos.hvplot(x='time', line_width=0.5, color='k', xlabel='Year', ylabel='ENSO 3.4 Index')\n",
+ "\n",
+ " # Combine all the plots into a single plot\n",
+ " return (el_nino_label * la_nina_label * el_nino * la_nina * enso).opts(title=f'{ds.attrs[\"model\"]} {ds.attrs[\"source_id\"]} \\n Ensemble Member: {ds.attrs[\"variant_label\"]}')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "13492e2d-c32a-4bcc-b341-837d5ea91a1a",
+ "metadata": {},
+ "source": [
+ "Once we have the function, we apply to our two datasets and combine into a single column."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "b6332c80-0ee9-4a4f-a277-95efc2cc8252",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {},
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.holoviews_exec.v0+json": "",
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ":Layout\n",
+ " .Overlay.I :Overlay\n",
+ " .Text.I :Text [x,y]\n",
+ " .Text.II :Text [x,y]\n",
+ " .Area.I :Area [time] (el_nino_threshold,tos_gt_04)\n",
+ " .Area.II :Area [time] (la_nina_threshold,tos_lt_04)\n",
+ " .Curve.I :Curve [time] (tos)\n",
+ " .Overlay.II :Overlay\n",
+ " .Text.I :Text [x,y]\n",
+ " .Text.II :Text [x,y]\n",
+ " .Area.I :Area [time] (el_nino_threshold,tos_gt_04)\n",
+ " .Area.II :Area [time] (la_nina_threshold,tos_lt_04)\n",
+ " .Curve.I :Curve [time] (tos)"
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {
+ "application/vnd.holoviews_exec.v0+json": {
+ "id": "p2078"
+ }
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(plot_enso(ncar_ds) + plot_enso(miroc_ds)).cols(1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "332e67da-2268-47c1-8c16-27f54f4a27bc",
+ "metadata": {},
+ "source": [
+ "## Modify to Run in a Globus Flow\n",
+ "Next, let's modify our script to:\n",
+ "- Search and download ESGF data\n",
+ "- Calculate ENSO\n",
+ "- Visualize and save the output as an html file"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "aa5d4f65-a8ef-4ed0-bd6e-f49efb4f23ab",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "def run_plot_enso(institution_id, return_path=False):\n",
+ " import numpy as np\n",
+ " import matplotlib.pyplot as plt\n",
+ " from intake_esgf import ESGFCatalog\n",
+ " import xarray as xr\n",
+ " import cf_xarray\n",
+ " import warnings\n",
+ " import holoviews as hv\n",
+ " import os\n",
+ " import hvplot\n",
+ " import hvplot.xarray\n",
+ " hv.extension('bokeh') \n",
+ " warnings.filterwarnings(\"ignore\")\n",
+ "\n",
+ " def search_esgf(institution_id, grid='gn'):\n",
+ "\n",
+ " # Search and load the ocean surface temperature (tos)\n",
+ " cat = ESGFCatalog()\n",
+ " cat.search(\n",
+ " activity_id=\"CMIP\",\n",
+ " experiment_id=\"historical\",\n",
+ " institution_id=institution_id,\n",
+ " variable_id=[\"tos\"],\n",
+ " member_id='r11i1p1f1',\n",
+ " table_id=\"Omon\",\n",
+ " )\n",
+ " try:\n",
+ " tos_ds = cat.to_datatree()[grid].to_dataset()\n",
+ " except ValueError:\n",
+ " tos_ds = cat.to_dataset_dict()[\"\"]\n",
+ "\n",
+ " return tos_ds\n",
+ "\n",
+ " def calculate_enso(ds):\n",
+ "\n",
+ " # Subset the El Nino 3.4 index region\n",
+ " dso = ds.where(\n",
+ " (ds.cf[\"latitude\"] < 5) & (ds.cf[\"latitude\"] > -5) & (ds.cf[\"longitude\"] > 190) & (ds.cf[\"longitude\"] < 240), drop=True\n",
+ " )\n",
+ "\n",
+ " # Calculate the monthly means\n",
+ " gb = dso.tos.groupby('time.month')\n",
+ "\n",
+ " # Subtract the monthly averages, returning the anomalies\n",
+ " tos_nino34_anom = gb - gb.mean(dim='time')\n",
+ "\n",
+ " # Determine the non-time dimensions and average using these\n",
+ " non_time_dims = set(tos_nino34_anom.dims)\n",
+ " non_time_dims.remove(ds.tos.cf[\"T\"].name)\n",
+ " weighted_average = tos_nino34_anom.weighted(ds[\"areacello\"]).mean(dim=list(non_time_dims))\n",
+ "\n",
+ " # Calculate the rolling average\n",
+ " rolling_average = weighted_average.rolling(time=5, center=True).mean()\n",
+ " std_dev = weighted_average.std()\n",
+ " return rolling_average / std_dev\n",
+ "\n",
+ " def add_enso_thresholds(da, threshold=0.4):\n",
+ "\n",
+ " # Conver the xr.DataArray into an xr.Dataset\n",
+ " ds = da.to_dataset()\n",
+ "\n",
+ " # Cleanup the time and use the thresholds\n",
+ " try:\n",
+ " ds[\"time\"]= ds.indexes[\"time\"].to_datetimeindex()\n",
+ " except:\n",
+ " pass\n",
+ " ds[\"tos_gt_04\"] = (\"time\", ds.tos.where(ds.tos >= threshold, threshold).data)\n",
+ " ds[\"tos_lt_04\"] = (\"time\", ds.tos.where(ds.tos <= -threshold, -threshold).data)\n",
+ "\n",
+ " # Add fields for the thresholds\n",
+ " ds[\"el_nino_threshold\"] = (\"time\", np.zeros_like(ds.tos) + threshold)\n",
+ " ds[\"la_nina_threshold\"] = (\"time\", np.zeros_like(ds.tos) - threshold)\n",
+ "\n",
+ " return ds\n",
+ "\n",
+ " def plot_enso(ds):\n",
+ " el_nino = ds.hvplot.area(x=\"time\", y2='tos_gt_04', y='el_nino_threshold', color='red', hover=False)\n",
+ " el_nino_label = hv.Text(ds.isel(time=40).time.values, 2, 'El Niño').opts(text_color='red',)\n",
+ "\n",
+ " # Create the La Niña area graphs\n",
+ " la_nina = ds.hvplot.area(x=\"time\", y2='tos_lt_04', y='la_nina_threshold', color='blue', hover=False)\n",
+ " la_nina_label = hv.Text(ds.isel(time=-40).time.values, -2, 'La Niña').opts(text_color='blue')\n",
+ "\n",
+ " # Plot a timeseries of the ENSO 3.4 index\n",
+ " enso = ds.tos.hvplot(x='time', line_width=0.5, color='k', xlabel='Year', ylabel='ENSO 3.4 Index')\n",
+ "\n",
+ " # Combine all the plots into a single plot\n",
+ " return (el_nino_label * la_nina_label * el_nino * la_nina * enso).opts(title=f'{ds.attrs[\"model\"]} {ds.attrs[\"source_id\"]} \\n Ensemble Member: {ds.attrs[\"variant_label\"]}')\n",
+ " \n",
+ " ds = search_esgf(institution_id)\n",
+ " enso_index = add_enso_thresholds(calculate_enso(ds).compute())\n",
+ " enso_index.attrs = ds.attrs\n",
+ " enso_index.attrs[\"model\"] = institution_id\n",
+ " \n",
+ " plot = plot_enso(enso_index)\n",
+ " \n",
+ " if return_path:\n",
+ " path = f\"{os.getcwd()}/plot.html\"\n",
+ " hvplot.save(plot, path)\n",
+ " \n",
+ " else:\n",
+ " path = plot\n",
+ "\n",
+ " return path\n",
+ "\n",
+ "\n",
+ " \n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "6ef8039b-b894-42b3-af19-dff99a6cff11",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/javascript": [
+ "(function(root) {\n",
+ " function now() {\n",
+ " return new Date();\n",
+ " }\n",
+ "\n",
+ " var force = true;\n",
+ " var py_version = '3.3.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n",
+ " var reloading = false;\n",
+ " var Bokeh = root.Bokeh;\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.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.3.2/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.3.2/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.3.2/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.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.3.1.min.js\", \"https://cdn.holoviz.org/panel/1.3.2/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",
+ "\ttry {\n",
+ " inline_js[i].call(root, root.Bokeh);\n",
+ "\t} catch(e) {\n",
+ "\t if (!reloading) {\n",
+ "\t throw e;\n",
+ "\t }\n",
+ "\t}\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",
+ " root._bokeh_is_initializing = true\n",
+ " root._bokeh_onload_callbacks = []\n",
+ " var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n",
+ " if (!reloading && !bokeh_loaded) {\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.3.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n var reloading = false;\n var Bokeh = root.Bokeh;\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.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.3.2/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.3.2/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.3.2/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.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.3.1.min.js\", \"https://cdn.holoviz.org/panel/1.3.2/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\ttry {\n inline_js[i].call(root, root.Bokeh);\n\t} catch(e) {\n\t if (!reloading) {\n\t throw e;\n\t }\n\t}\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 root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\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": {
+ "application/vnd.holoviews_exec.v0+json": "",
+ "text/html": [
+ "\n",
+ ""
+ ]
+ },
+ "metadata": {
+ "application/vnd.holoviews_exec.v0+json": {
+ "id": "p2481"
+ }
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "
\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ " Searching indices: 100%|█|1/1 [ 1.16s\n",
+ " Obtaining file info: 100%|█|2/2 [ 1.44dat\n",
+ "Adding cell measures: 100%|█|2/2 [ 4.71s/d\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "'/Users/mgrover/git_repos/esgf-cookbook/notebooks/get-flow-working/plot.html'"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "sample = run_plot_enso(\"NCAR\", return_path=True)\n",
+ "sample"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a6927dea-c744-4249-afde-01cd58a3d496",
+ "metadata": {},
+ "source": [
+ "### Deploy the Function as a Flow\n",
+ "Now, let's deploy this function within a Globus-Flow!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "39e19f20-fcda-4779-8169-8a242cc41f99",
+ "metadata": {
+ "tags": []
+ },
+ "source": [
+ "#### Configure Authentication"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "a58e9f59-b6fe-460c-a40a-e9b9f400513d",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Login Here:\n",
+ "\n",
+ "https://auth.globus.org/v2/oauth2/authorize?client_id=16659080-53cd-45c7-8737-833d3e719f32&redirect_uri=https%3A%2F%2Fauth.globus.org%2Fv2%2Fweb%2Fauth-code&scope=https%3A%2F%2Fauth.globus.org%2Fscopes%2Feec9b274-0c81-4334-bdc2-54e90e689b9a%2Fmanage_flows+urn%3Aglobus%3Aauth%3Ascope%3Asearch.api.globus.org%3Aall+urn%3Aglobus%3Aauth%3Ascope%3Atransfer.api.globus.org%3Aall&state=_default&response_type=code&code_challenge=Rwl-9Z7b7whfTSNyvr6nFkqrTuU34wAI7pXH8Lk7or0&code_challenge_method=S256&access_type=online\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Set Native App client\n",
+ "CLIENT_ID = '16659080-53cd-45c7-8737-833d3e719f32' # From \n",
+ "native_auth_client = globus_sdk.NativeAppAuthClient(CLIENT_ID)\n",
+ "\n",
+ "# Initialize Globus Auth flow with relevant scopes\n",
+ "auth_kwargs = {\"requested_scopes\": [FlowsScopes.manage_flows, SearchScopes.all, TransferScopes.all]}\n",
+ "native_auth_client.oauth2_start_flow(**auth_kwargs)\n",
+ "\n",
+ "# Explicitly start the flow\n",
+ "print(f\"Login Here:\\n\\n{native_auth_client.oauth2_get_authorize_url()}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "90ffaed3-6f77-45bf-a351-e0775ac893d8",
+ "metadata": {},
+ "source": [
+ "Once we have the authentication code, insert it in the cell below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "id": "a04273cc-fc04-4c7d-9837-7835fc04af96",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# Add the authorization code that you got from Globus\n",
+ "auth_code = \"qG4wPHv84EPYOj56deWgquwSEfSUdf\"\n",
+ "\n",
+ "# Exchange code for access tokens\n",
+ "response_token = native_auth_client.oauth2_exchange_code_for_tokens(auth_code)\n",
+ "\n",
+ "# Split tokens based on their resource server\n",
+ "# This is the token that allows to create a flow client\n",
+ "# but is before the flow gets authorized, after which \n",
+ "# you get another (more powerful) access token code\n",
+ "tokens = response_token.by_resource_server\n",
+ "\n",
+ "# Create a variable for storing flow scope tokens. Each newly deployed flow scope needs to be authorized separately,\n",
+ "# and will have its own set of tokens. Save each of these tokens by scope.\n",
+ "# Whatever is in this dictionary has passed the deployment authorization,\n",
+ "# meaning you do not need to authorize over and over each time you want to run the flow.\n",
+ "saved_flow_scopes = {}\n",
+ "\n",
+ "# Add a callback to the flows client for fetching scopes. It will draw scopes from `saved_flow_scopes`\n",
+ "def get_flow_authorizer(flow_url, flow_scope, client_id):\n",
+ " return globus_sdk.AccessTokenAuthorizer(access_token=saved_flow_scopes[flow_scope]['access_token'])\n",
+ "\n",
+ "# Setup the Flow client, using Globus Auth tokens to access the Globus Flows service, and\n",
+ "# set the `get_flow_authorizer` callback for any new flows we authorize.\n",
+ "flows_authorizer = globus_sdk.AccessTokenAuthorizer(access_token=tokens['flows.globus.org']['access_token'])\n",
+ "flows_client = FlowsClient.new_client(CLIENT_ID, get_flow_authorizer, flows_authorizer)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5e503ba4-5139-405f-acf1-c4fc2acc5642",
+ "metadata": {},
+ "source": [
+ "#### Configure the Globus Compute Client and Register our Function\n",
+ "Now that we have a function to run our analysis, we need to register it!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "id": "71e4261d-bc78-4c9f-b4fb-407b2c09cd91",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Instantiating a Globus Compute client ...\n",
+ "Registering the Globus Compute function ...\n",
+ "Compute function ID: 587412e2-355d-48e2-9336-eb25b6132cb7\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(\"Instantiating a Globus Compute client ...\")\n",
+ "gcc = Client()\n",
+ "\n",
+ "# Register the function within the Globus ecosystem\n",
+ "print(\"Registering the Globus Compute function ...\")\n",
+ "compute_function_id = gcc.register_function(run_plot_enso)\n",
+ "\n",
+ "print(f\"Compute function ID: {compute_function_id}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ba9cfcd0-7b51-4aed-9480-052acf18da52",
+ "metadata": {},
+ "source": [
+ "#### Define our Flow\n",
+ "We need to define our flow - setting the expected parameters, and transferring the output html file at the end of the analysis. Notice we pass in the function ID we setup in the cell above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "id": "62c26bcf-8789-44c0-af86-db67a32ef724",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "flow_definition = {\n",
+ " \"Comment\": \"Compute ENSO Index of a Given CMIP6 Model\",\n",
+ " \"StartAt\": \"RunPlotENSO\",\n",
+ " \"States\": {\n",
+ " \"RunPlotENSO\": {\n",
+ " \"Comment\": \"ESGF Search and Plot\",\n",
+ " \"Type\": \"Action\",\n",
+ " \"ActionUrl\": \"https://compute.actions.globus.org/\",\n",
+ " \"Parameters\": {\n",
+ " \"endpoint.$\": \"$.input.compute.id\",\n",
+ " \"function\": compute_function_id,\n",
+ " \"kwargs\": {\"institution_id.$\":\"$.input.compute_input_data.institution_id\",\n",
+ " \"return_path\": True\n",
+ " }\n",
+ " },\n",
+ " \"ResultPath\": \"$.ESGF_output\",\n",
+ " \"WaitTime\": 600,\n",
+ " \"Next\": \"TransferResult\"\n",
+ " },\n",
+ " \"TransferResult\": {\n",
+ " \"Comment\": \"Transfer files\",\n",
+ " \"Type\": \"Action\",\n",
+ " \"ActionUrl\": \"https://actions.automate.globus.org/transfer/transfer\",\n",
+ " \"Parameters\": {\n",
+ " \"source_endpoint_id.$\": \"$.input.destination.id\",\n",
+ " \"destination_endpoint_id.$\": \"$.input.destination.id\",\n",
+ " \"transfer_items\": [\n",
+ " {\n",
+ " \"source_path.$\": \"$.ESGF_output.details.result[0]\",\n",
+ " \"destination_path.$\": \"$.input.destination.path\",\n",
+ " \"recursive\": False\n",
+ " }\n",
+ " ]\n",
+ " },\n",
+ " \"ResultPath\": \"$.TransferFiles\",\n",
+ " \"WaitTime\": 300,\n",
+ " \"End\": True\n",
+ " }\n",
+ " }\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5737e27a-363f-4297-a5cf-da79fef5fda0",
+ "metadata": {},
+ "source": [
+ "#### Configure the Schema\n",
+ "We also need to define the schema, or what is expected, for our flow."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "id": "294c2a65-a08e-467c-a97f-6a633b6ae20f",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "input_schema = {\n",
+ " \"required\": [\n",
+ " \"input\"\n",
+ " ],\n",
+ " \"properties\": {\n",
+ " \"input\": {\n",
+ " \"type\": \"object\",\n",
+ " \"required\": [\n",
+ " \"compute\",\n",
+ " \"destination\",\n",
+ " \"compute_input_data\"\n",
+ " ],\n",
+ " \"properties\": {\n",
+ " \"compute\": {\n",
+ " \"type\": \"object\",\n",
+ " \"title\": \"Select source collection and path\",\n",
+ " \"description\": \"The source collection and path (path MUST end with a slash)\",\n",
+ " \"required\": [\n",
+ " \"id\",\n",
+ " ],\n",
+ " \"properties\": {\n",
+ " \"id\": {\n",
+ " \"type\": \"string\",\n",
+ " \"format\": \"uuid\",\n",
+ " \"default\": endpoint_id\n",
+ " },\n",
+ " },\n",
+ " \"additionalProperties\": False\n",
+ " },\n",
+ " \"destination\": {\n",
+ " \"type\": \"object\",\n",
+ " \"title\": \"Select destination collection and path\",\n",
+ " \"description\": \"The destination collection and path (path MUST end with a slash); default collection is 'Globus Tutorials on ALCF Eagle'\",\n",
+ " \"required\": [\n",
+ " \"id\",\n",
+ " \"path\"\n",
+ " ],\n",
+ " \"properties\": {\n",
+ " \"id\": {\n",
+ " \"type\": \"string\",\n",
+ " \"format\": \"uuid\",\n",
+ " \"default\": personal_endpoint_id\n",
+ " },\n",
+ " \"path\": {\n",
+ " \"type\": \"string\",\n",
+ " \"default\": f\"/plot.html\"\n",
+ " }\n",
+ " },\n",
+ " \"additionalProperties\": False\n",
+ " },\n",
+ " # Compute function input data\n",
+ " \"compute_input_data\": {\n",
+ " \"type\": \"object\",\n",
+ " \"title\": \"Input data required by compute function.\",\n",
+ " \"description\": \"Compute function input data.\",\n",
+ " \"required\": [\n",
+ " \"institution_id\",\n",
+ " ],\n",
+ " \"properties\": {\n",
+ " \"institution_id\": {\n",
+ " \"type\": \"string\",\n",
+ " \"description\": \"Institution Identifier for the model of interest\",\n",
+ " \"default\": \"NCAR\"\n",
+ " },\n",
+ " },\n",
+ " \"additionalProperties\": False\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ " }\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0f0078bc-f9e1-4e36-b6dc-bd872930284a",
+ "metadata": {},
+ "source": [
+ "#### Deploy the flow\n",
+ "We can deploy the flow as a test, passing in the default settings and schema."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "id": "434ac563-07ab-460e-be85-da6fad2f8c31",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# Deploy the flow\n",
+ "flow = flows_client.deploy_flow(\n",
+ " flow_definition, \n",
+ " title = \"ESGF ENSO Test\",\n",
+ " input_schema=input_schema,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1ea8a065-6d67-4407-aa0e-865415d74e41",
+ "metadata": {},
+ "source": [
+ "We need a few parameters to actually **run** the deployed flow."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "id": "ccca9c78-43ea-4b74-9270-80747916c6e8",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "# Store flow information\n",
+ "flow_id = flow['id']\n",
+ "flow_scope = flow['globus_auth_scope']\n",
+ "flow_name = flow['title']"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "id": "bff5546e-c73f-465a-aed3-895d7092b334",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Login Here:\n",
+ "\n",
+ "https://auth.globus.org/v2/oauth2/authorize?client_id=16659080-53cd-45c7-8737-833d3e719f32&redirect_uri=https%3A%2F%2Fauth.globus.org%2Fv2%2Fweb%2Fauth-code&scope=https%3A%2F%2Fauth.globus.org%2Fscopes%2F985052fb-0a73-44e3-8e85-abef40e355b7%2Fflow_985052fb_0a73_44e3_8e85_abef40e355b7_user&state=_default&response_type=code&code_challenge=NsDOpSdt_ejFgQ_ZiT5W5sfr7HW5nOMkepTQ3gz9dqA&code_challenge_method=S256&access_type=online\n"
+ ]
+ },
+ {
+ "name": "stdin",
+ "output_type": "stream",
+ "text": [
+ "Authorization Code: naL5nZlR2LpkPstmCyu69O2Mc2h2fI\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "{\n",
+ " \"https://auth.globus.org/scopes/985052fb-0a73-44e3-8e85-abef40e355b7/flow_985052fb_0a73_44e3_8e85_abef40e355b7_user\": {\n",
+ " \"scope\": \"https://auth.globus.org/scopes/985052fb-0a73-44e3-8e85-abef40e355b7/flow_985052fb_0a73_44e3_8e85_abef40e355b7_user\",\n",
+ " \"access_token\": \"Agj6oQNVxPkmDWgn3EK13lPn5YWYlMy7DB7DB5QPGGvmlr2VxKS9CwDVwG5kkze9owlPNobXndPvG8hOyxQ1jCp19eD\",\n",
+ " \"refresh_token\": null,\n",
+ " \"token_type\": \"Bearer\",\n",
+ " \"expires_at_seconds\": 1712239856,\n",
+ " \"resource_server\": \"985052fb-0a73-44e3-8e85-abef40e355b7\"\n",
+ " }\n",
+ "}\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Once deployed, the flow needs to be authorized\n",
+ "# If the flow scope is already saved, we don't need a new one.\n",
+ "if flow_scope not in saved_flow_scopes:\n",
+ "#if True:\n",
+ " \n",
+ " # Do a native app authentication flow and get tokens that include the newly deployed flow scope\n",
+ " native_auth_client = globus_sdk.NativeAppAuthClient(CLIENT_ID)\n",
+ " native_auth_client.oauth2_start_flow(requested_scopes=flow_scope)\n",
+ " print(f\"Login Here:\\n\\n{native_auth_client.oauth2_get_authorize_url()}\")\n",
+ " \n",
+ " # Authenticate and come back with your authorization code; paste it into the prompt below.\n",
+ " auth_code = input('Authorization Code: ')\n",
+ " token_response = native_auth_client.oauth2_exchange_code_for_tokens(auth_code)\n",
+ " \n",
+ " # Save the new token in a place where the flows client can retrieve it.\n",
+ " saved_flow_scopes[flow_scope] = token_response.by_scopes[flow_scope]\n",
+ " \n",
+ " # These are the saved scopes for the flow\n",
+ " print(json.dumps(saved_flow_scopes, indent=2))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "42d480ae-8c29-491d-88c3-bec67599b349",
+ "metadata": {},
+ "source": [
+ "#### Run the Deployed Flow\n",
+ "Once we setup the authentication, we can pass in our parameters and run the flow!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "id": "ee245f82-e647-4c50-bec7-09dfd5e18486",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "flow_input = {\n",
+ " \"input\": {\n",
+ " \"compute\": {\n",
+ " \"id\": endpoint_id\n",
+ " },\n",
+ " \"destination\": {\n",
+ " \"id\": personal_endpoint_id,\n",
+ " \"path\": f\"/{os.getcwd()}/esgf_plot.html\"\n",
+ " },\n",
+ " \"compute_input_data\":{\n",
+ " \"institution_id\": \"NCAR\"\n",
+ " }\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "flow_action = flows_client.run_flow(\n",
+ " flow_id = flow_id,\n",
+ " flow_scope = flow_scope,\n",
+ " flow_input = flow_input,\n",
+ " label=\"Test local to local\",\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "id": "a763c530-db2f-4917-9493-70171c17ffa7",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Flow can be monitored in the webapp below: \n",
+ "https://app.globus.org/runs/ac6d9848-50b4-4e4d-a9cd-575195afc91a\n",
+ "Flow action started with ID: ac6d9848-50b4-4e4d-a9cd-575195afc91a - Status: ACTIVE\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Get flow execution parameters\n",
+ "flow_action_id = flow_action['action_id']\n",
+ "flow_status = flow_action['status']\n",
+ "print(f\"Flow can be monitored in the webapp below: \\nhttps://app.globus.org/runs/{flow_action_id}\")\n",
+ "print(f\"Flow action started with ID: {flow_action_id} - Status: {flow_status}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "eaab23ce-9293-4e5e-9e25-92fac43fb8f1",
+ "metadata": {},
+ "source": [
+ "Here, we setup a check to ensure the flow is still running, and visualize the response when it finishes!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "id": "27039bc1-53d2-4b7c-9f47-e87c9ed2e248",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Flow status: ACTIVE\n",
+ "Flow status: ACTIVE\n",
+ "Flow status: ACTIVE\n",
+ "Flow status: ACTIVE\n",
+ "Flow status: ACTIVE\n",
+ "Flow status: ACTIVE\n",
+ "Flow status: ACTIVE\n",
+ "Flow status: ACTIVE\n",
+ "Flow status: ACTIVE\n",
+ "Flow status: ACTIVE\n",
+ "Flow status: ACTIVE\n",
+ "Flow status: SUCCEEDED\n",
+ "{\n",
+ " \"run_id\": \"ac6d9848-50b4-4e4d-a9cd-575195afc91a\",\n",
+ " \"flow_id\": \"985052fb-0a73-44e3-8e85-abef40e355b7\",\n",
+ " \"flow_title\": \"ESGF ENSO Test\",\n",
+ " \"flow_last_updated\": \"2024-04-02T14:10:44.626766+00:00\",\n",
+ " \"start_time\": \"2024-04-02T14:11:00.218156+00:00\",\n",
+ " \"completion_time\": \"2024-04-02T14:12:04.842000+00:00\",\n",
+ " \"status\": \"SUCCEEDED\",\n",
+ " \"display_status\": \"SUCCEEDED\",\n",
+ " \"details\": {\n",
+ " \"code\": \"FlowSucceeded\",\n",
+ " \"output\": {\n",
+ " \"input\": {\n",
+ " \"compute\": {\n",
+ " \"id\": \"6836803d-9831-4dc5-b159-eb658250e4bc\"\n",
+ " },\n",
+ " \"destination\": {\n",
+ " \"id\": \"92bb829c-9d88-11ed-b579-33287ee02ec7\",\n",
+ " \"path\": \"//Users/mgrover/git_repos/esgf-cookbook/notebooks/get-flow-working/esgf_plot.html\"\n",
+ " },\n",
+ " \"compute_input_data\": {\n",
+ " \"institution_id\": \"NCAR\"\n",
+ " }\n",
+ " },\n",
+ " \"ESGF_output\": {\n",
+ " \"label\": null,\n",
+ " \"status\": \"SUCCEEDED\",\n",
+ " \"details\": {\n",
+ " \"result\": [\n",
+ " \"/Users/mgrover/plot.html\"\n",
+ " ],\n",
+ " \"results\": [\n",
+ " {\n",
+ " \"output\": \"/Users/mgrover/plot.html\",\n",
+ " \"task_id\": \"542e2bbd-deba-4fb0-92b6-92868c45b791\"\n",
+ " }\n",
+ " ]\n",
+ " },\n",
+ " \"action_id\": \"tg_0d183567-7313-4cb5-ad57-3470c9e0c6c5\",\n",
+ " \"manage_by\": [\n",
+ " \"urn:globus:auth:identity:97c1da09-1d6d-4189-a5be-6ae3f85ae21f\"\n",
+ " ],\n",
+ " \"creator_id\": \"urn:globus:auth:identity:97c1da09-1d6d-4189-a5be-6ae3f85ae21f\",\n",
+ " \"monitor_by\": [\n",
+ " \"urn:globus:auth:identity:97c1da09-1d6d-4189-a5be-6ae3f85ae21f\"\n",
+ " ],\n",
+ " \"start_time\": \"2024-04-02T14:11:03.123757+00:00\",\n",
+ " \"state_name\": \"RunPlotENSO\",\n",
+ " \"release_after\": null,\n",
+ " \"display_status\": \"All tasks completed\",\n",
+ " \"completion_time\": \"2024-04-02T14:11:35.692466+00:00\"\n",
+ " },\n",
+ " \"TransferFiles\": {\n",
+ " \"label\": null,\n",
+ " \"status\": \"SUCCEEDED\",\n",
+ " \"details\": {\n",
+ " \"type\": \"TRANSFER\",\n",
+ " \"files\": 1,\n",
+ " \"is_ok\": null,\n",
+ " \"label\": \"For Action id 9M4iaezjMBLU\",\n",
+ " \"faults\": 0,\n",
+ " \"status\": \"SUCCEEDED\",\n",
+ " \"command\": \"API 0.10\",\n",
+ " \"task_id\": \"ed40cb70-f0fa-11ee-b6cd-473d136f742f\",\n",
+ " \"deadline\": \"2024-04-03T14:11:41+00:00\",\n",
+ " \"owner_id\": \"97c1da09-1d6d-4189-a5be-6ae3f85ae21f\",\n",
+ " \"symlinks\": 0,\n",
+ " \"username\": \"u_s7a5uci5nvaytjn6nlr7qwxcd4\",\n",
+ " \"DATA_TYPE\": \"task\",\n",
+ " \"is_paused\": false,\n",
+ " \"event_list\": [\n",
+ " {\n",
+ " \"code\": \"SUCCEEDED\",\n",
+ " \"time\": \"2024-04-02T14:11:47+00:00\",\n",
+ " \"details\": {\n",
+ " \"files_succeeded\": 1\n",
+ " },\n",
+ " \"is_error\": false,\n",
+ " \"DATA_TYPE\": \"event\",\n",
+ " \"description\": \"succeeded\"\n",
+ " },\n",
+ " {\n",
+ " \"code\": \"PROGRESS\",\n",
+ " \"time\": \"2024-04-02T14:11:47+00:00\",\n",
+ " \"details\": {\n",
+ " \"mbps\": 0.36,\n",
+ " \"duration\": 4.14,\n",
+ " \"bytes_transferred\": 184604\n",
+ " },\n",
+ " \"is_error\": false,\n",
+ " \"DATA_TYPE\": \"event\",\n",
+ " \"description\": \"progress\"\n",
+ " },\n",
+ " {\n",
+ " \"code\": \"STARTED\",\n",
+ " \"time\": \"2024-04-02T14:11:43+00:00\",\n",
+ " \"details\": {\n",
+ " \"type\": \"GridFTP Transfer\",\n",
+ " \"protocol\": \"UDT\",\n",
+ " \"pipelining\": 20,\n",
+ " \"concurrency\": 2,\n",
+ " \"parallelism\": 2\n",
+ " },\n",
+ " \"is_error\": false,\n",
+ " \"DATA_TYPE\": \"event\",\n",
+ " \"description\": \"started\"\n",
+ " }\n",
+ " ],\n",
+ " \"sync_level\": null,\n",
+ " \"directories\": 0,\n",
+ " \"fatal_error\": null,\n",
+ " \"nice_status\": null,\n",
+ " \"encrypt_data\": false,\n",
+ " \"filter_rules\": null,\n",
+ " \"request_time\": \"2024-04-02T14:11:41+00:00\",\n",
+ " \"files_skipped\": 0,\n",
+ " \"subtasks_total\": 2,\n",
+ " \"completion_time\": \"2024-04-02T14:11:47+00:00\",\n",
+ " \"history_deleted\": false,\n",
+ " \"source_endpoint\": \"u_s7a5uci5nvaytjn6nlr7qwxcd4#92bb829c-9d88-11ed-b579-33287ee02ec7\",\n",
+ " \"subtasks_failed\": 0,\n",
+ " \"verify_checksum\": false,\n",
+ " \"source_base_path\": null,\n",
+ " \"subtasks_expired\": 0,\n",
+ " \"subtasks_pending\": 0,\n",
+ " \"bytes_checksummed\": 0,\n",
+ " \"bytes_transferred\": 184604,\n",
+ " \"canceled_by_admin\": null,\n",
+ " \"files_transferred\": 1,\n",
+ " \"source_local_user\": null,\n",
+ " \"subtasks_canceled\": 0,\n",
+ " \"subtasks_retrying\": 0,\n",
+ " \"preserve_timestamp\": false,\n",
+ " \"recursive_symlinks\": \"ignore\",\n",
+ " \"skip_source_errors\": false,\n",
+ " \"source_endpoint_id\": \"92bb829c-9d88-11ed-b579-33287ee02ec7\",\n",
+ " \"subtasks_succeeded\": 2,\n",
+ " \"nice_status_details\": null,\n",
+ " \"destination_endpoint\": \"u_s7a5uci5nvaytjn6nlr7qwxcd4#92bb829c-9d88-11ed-b579-33287ee02ec7\",\n",
+ " \"fail_on_quota_errors\": false,\n",
+ " \"destination_base_path\": null,\n",
+ " \"destination_local_user\": null,\n",
+ " \"nice_status_expires_in\": null,\n",
+ " \"destination_endpoint_id\": \"92bb829c-9d88-11ed-b579-33287ee02ec7\",\n",
+ " \"subtasks_skipped_errors\": 0,\n",
+ " \"delete_destination_extra\": false,\n",
+ " \"source_local_user_status\": null,\n",
+ " \"canceled_by_admin_message\": null,\n",
+ " \"effective_bytes_per_second\": 28624,\n",
+ " \"source_endpoint_display_name\": \"Work Laptop\",\n",
+ " \"destination_local_user_status\": null,\n",
+ " \"nice_status_short_description\": null,\n",
+ " \"destination_endpoint_display_name\": \"Work Laptop\"\n",
+ " },\n",
+ " \"action_id\": \"9M4iaezjMBLU\",\n",
+ " \"manage_by\": [],\n",
+ " \"creator_id\": \"urn:globus:auth:identity:97c1da09-1d6d-4189-a5be-6ae3f85ae21f\",\n",
+ " \"monitor_by\": [],\n",
+ " \"start_time\": \"2024-04-02T14:11:40.281648+00:00\",\n",
+ " \"state_name\": \"TransferResult\",\n",
+ " \"release_after\": \"P30D\",\n",
+ " \"display_status\": \"SUCCEEDED\",\n",
+ " \"completion_time\": \"2024-04-02T14:11:40.281673+00:00\"\n",
+ " }\n",
+ " },\n",
+ " \"description\": \"The Flow run reached a successful completion state\"\n",
+ " },\n",
+ " \"run_owner\": \"urn:globus:auth:identity:97c1da09-1d6d-4189-a5be-6ae3f85ae21f\",\n",
+ " \"run_managers\": [],\n",
+ " \"run_monitors\": [],\n",
+ " \"user_role\": \"run_owner\",\n",
+ " \"label\": \"Test local to local\",\n",
+ " \"tags\": [],\n",
+ " \"action_id\": \"ac6d9848-50b4-4e4d-a9cd-575195afc91a\",\n",
+ " \"manage_by\": [],\n",
+ " \"monitor_by\": [],\n",
+ " \"created_by\": \"urn:globus:auth:identity:97c1da09-1d6d-4189-a5be-6ae3f85ae21f\"\n",
+ "}\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Poll the Flow service to check on the status of the flow\n",
+ "while flow_status == 'ACTIVE':\n",
+ " time.sleep(5)\n",
+ " flow_action = flows_client.flow_action_status(flow_id, flow_scope, flow_action_id)\n",
+ " flow_status = flow_action['status']\n",
+ " print(f'Flow status: {flow_status}')\n",
+ " \n",
+ "# Flow completed (hopefully successfully!)\n",
+ "print(json.dumps(flow_action.data, indent=2))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b3bfceb9-124a-4d72-9d49-3ca255965e29",
+ "metadata": {},
+ "source": [
+ "## Summary\n",
+ "In this notebook, we applied the ENSO 3.4 index calculations to CMIP6 datasets remotely using Globus Compute and created interactive plots comparing where we see El Niño and La Niña.\n",
+ "\n",
+ "### What's next?\n",
+ "We will see some more advanced examples of using the CMIP6 and other data access methods as well as computations.\n",
+ "\n",
+ "## Resources and references\n",
+ "- [Intake-ESGF Documentation](https://github.com/nocollier/intake-esgf)\n",
+ "- [Globus Compute Documentation](https://www.globus.org/compute)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f5b2864f-6661-4aa4-8d65-8dc10c961b36",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "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.10.13"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/yearly-average-selection-globus.ipynb b/notebooks/yearly-average-selection-globus.ipynb
new file mode 100644
index 0000000..9eceab5
--- /dev/null
+++ b/notebooks/yearly-average-selection-globus.ipynb
@@ -0,0 +1,1725 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "dff85968-989f-4e70-baf3-515f9670dc88",
+ "metadata": {},
+ "source": [
+ " \n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f45b06fc-7432-4d39-8ccc-e7c39264406c",
+ "metadata": {},
+ "source": [
+ "# Basic Demonstration of Data Reduction Using Globus, Intake-ESGF, and Clisops\n",
+ "\n",
+ "## Overview\n",
+ "Within this notebook, we highlight how to use a collection of open-source tools in the Earth System Grid Federation user-computing community, to reduce and select datasets available through the federation of servers. Mainly, we will\n",
+ "- Select a given time frame\n",
+ "- Subset for a point\n",
+ "- Average into yearly frequency"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7f17259b-602f-4480-bc77-c8d03fa4806e",
+ "metadata": {},
+ "source": [
+ "## Prerequisites\n",
+ "\n",
+ "| Concepts | Importance | Notes |\n",
+ "| --- | --- | --- |\n",
+ "| [Intro to Xarray](https://foundations.projectpythia.org/core/xarray/xarray-intro.html) | Necessary | |\n",
+ "| [hvPlot Basics](https://hvplot.holoviz.org/getting_started/hvplot.html) | Necessary | Interactive Visualization with hvPlot |\n",
+ "- **Time to learn**: 30 minutes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0c1b5757-4bd8-4d52-a1b7-45bd538af891",
+ "metadata": {
+ "tags": []
+ },
+ "source": [
+ "## Imports"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "a7beeef7-b818-4c60-a990-9ee8e692a6d5",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/javascript": [
+ "(function(root) {\n",
+ " function now() {\n",
+ " return new Date();\n",
+ " }\n",
+ "\n",
+ " var force = true;\n",
+ " var py_version = '3.3.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n",
+ " var reloading = false;\n",
+ " var Bokeh = root.Bokeh;\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.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.3.2/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.3.2/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.3.2/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.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.3.1.min.js\", \"https://cdn.holoviz.org/panel/1.3.2/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",
+ "\ttry {\n",
+ " inline_js[i].call(root, root.Bokeh);\n",
+ "\t} catch(e) {\n",
+ "\t if (!reloading) {\n",
+ "\t throw e;\n",
+ "\t }\n",
+ "\t}\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",
+ " root._bokeh_is_initializing = true\n",
+ " root._bokeh_onload_callbacks = []\n",
+ " var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n",
+ " if (!reloading && !bokeh_loaded) {\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.3.1'.replace('rc', '-rc.').replace('.dev', '-dev.');\n var reloading = false;\n var Bokeh = root.Bokeh;\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.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.3.2/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.3.2/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.3.2/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.3.2/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.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.3.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.3.1.min.js\", \"https://cdn.holoviz.org/panel/1.3.2/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\ttry {\n inline_js[i].call(root, root.Bokeh);\n\t} catch(e) {\n\t if (!reloading) {\n\t throw e;\n\t }\n\t}\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 root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n if (!reloading && !bokeh_loaded) {\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": {
+ "application/vnd.holoviews_exec.v0+json": "",
+ "text/html": [
+ "\n",
+ ""
+ ]
+ },
+ "metadata": {
+ "application/vnd.holoviews_exec.v0+json": {
+ "id": "p1122"
+ }
+ },
+ "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 hvplot.xarray\n",
+ "import holoviews as hv\n",
+ "import numpy as np\n",
+ "import hvplot.xarray\n",
+ "import matplotlib.pyplot as plt\n",
+ "import cartopy.crs as ccrs\n",
+ "from intake_esgf import ESGFCatalog\n",
+ "import xarray as xr\n",
+ "import warnings\n",
+ "from clisops.ops.subset import subset, subset_bbox\n",
+ "from clisops.ops.average import average_over_dims, average_time\n",
+ "import os\n",
+ "from globus_compute_sdk import Executor, Client\n",
+ "warnings.filterwarnings(\"ignore\")\n",
+ "\n",
+ "hv.extension(\"matplotlib\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d9c38227-22b6-4ce3-978a-5661ad55d316",
+ "metadata": {},
+ "source": [
+ "## Search and Find Data Using Intake-ESGF\n",
+ "Let's start with a sample dataset - which we can search for using intake-esgf."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "e77d622b-ca17-403f-8be2-8d090f9d837b",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Perform a search() to populate the catalog."
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cat = ESGFCatalog()\n",
+ "cat"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "2e78e4d4-fa09-404c-a192-ae5b762e10e1",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ " Searching indices: 100%|███████████████████████████████|1/1 [ 4.22s/index]\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Summary information for 3 results:\n",
+ "mip_era [CMIP6]\n",
+ "activity_id [CMIP]\n",
+ "institution_id [CCCma]\n",
+ "source_id [CanESM5]\n",
+ "experiment_id [historical]\n",
+ "member_id [r1i1p1f1]\n",
+ "table_id [Amon, Lmon]\n",
+ "variable_id [tas, pr, gpp]\n",
+ "grid_label [gn]\n",
+ "dtype: object"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cat.search(\n",
+ " experiment_id=\"historical\",\n",
+ " source_id=\"CanESM5\",\n",
+ " frequency=\"mon\",\n",
+ " variable_id=[\"gpp\", \"tas\", \"pr\"],\n",
+ " variant_label=\"r1i1p1f1\", # addition from the last search\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "444549b5-fcf4-43d9-ba72-252ef5d6d407",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ " Obtaining file info: 100%|███████████████████████████████|3/3 [ 1.24dataset/s]\n",
+ "Adding cell measures: 100%|███████████████████████████████|3/3 [ 3.04s/dataset]\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "dict_keys(['Amon.tas', 'Lmon.gpp', 'Amon.pr'])"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "dsd = cat.to_dataset_dict()\n",
+ "dsd.keys()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "5fc96317-6d59-4332-b591-52040e187fe7",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "
<xarray.Dataset>\n",
+ "Dimensions: (time: 1980, bnds: 2, lat: 64, lon: 128)\n",
+ "Coordinates:\n",
+ " * time (time) object 1850-01-16 12:00:00 ... 2014-12-16 12:00:00\n",
+ " * lat (lat) float64 -87.86 -85.1 -82.31 -79.53 ... 82.31 85.1 87.86\n",
+ " * lon (lon) float64 0.0 2.812 5.625 8.438 ... 348.8 351.6 354.4 357.2\n",
+ " height float64 ...\n",
+ "Dimensions without coordinates: bnds\n",
+ "Data variables:\n",
+ " time_bnds (time, bnds) object ...\n",
+ " lat_bnds (lat, bnds) float64 ...\n",
+ " lon_bnds (lon, bnds) float64 ...\n",
+ " tas (time, lat, lon) float32 ...\n",
+ " areacella (lat, lon) float32 ...\n",
+ "Attributes: (12/53)\n",
+ " CCCma_model_hash: 3dedf95315d603326fde4f5340dc0519d80d10c0\n",
+ " CCCma_parent_runid: rc3-pictrl\n",
+ " CCCma_pycmor_hash: 33c30511acc319a98240633965a04ca99c26427e\n",
+ " CCCma_runid: rc3.1-his01\n",
+ " Conventions: CF-1.7 CMIP-6.2\n",
+ " YMDH_branch_time_in_child: 1850:01:01:00\n",
+ " ... ...\n",
+ " tracking_id: hdl:21.14100/872062df-acae-499b-aa0f-9eaca76...\n",
+ " variable_id: tas\n",
+ " variant_label: r1i1p1f1\n",
+ " version: v20190429\n",
+ " license: CMIP6 model data produced by The Government ...\n",
+ " cmor_version: 3.4.0 Dimensions: time : 1980bnds : 2lat : 64lon : 128
Coordinates: (4)
time
(time)
object
1850-01-16 12:00:00 ... 2014-12-...
bounds : time_bnds axis : T long_name : time standard_name : time array([cftime.DatetimeNoLeap(1850, 1, 16, 12, 0, 0, 0, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(1850, 2, 15, 0, 0, 0, 0, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(1850, 3, 16, 12, 0, 0, 0, has_year_zero=True),\n",
+ " ...,\n",
+ " cftime.DatetimeNoLeap(2014, 10, 16, 12, 0, 0, 0, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(2014, 11, 16, 0, 0, 0, 0, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(2014, 12, 16, 12, 0, 0, 0, has_year_zero=True)],\n",
+ " dtype=object) lat
(lat)
float64
-87.86 -85.1 -82.31 ... 85.1 87.86
bounds : lat_bnds units : degrees_north axis : Y long_name : Latitude standard_name : latitude array([-87.863799, -85.096527, -82.312913, -79.525607, -76.7369 , -73.947515,\n",
+ " -71.157752, -68.367756, -65.577607, -62.787352, -59.99702 , -57.206632,\n",
+ " -54.4162 , -51.625734, -48.835241, -46.044727, -43.254195, -40.463648,\n",
+ " -37.67309 , -34.882521, -32.091944, -29.30136 , -26.510769, -23.720174,\n",
+ " -20.929574, -18.138971, -15.348365, -12.557756, -9.767146, -6.976534,\n",
+ " -4.185921, -1.395307, 1.395307, 4.185921, 6.976534, 9.767146,\n",
+ " 12.557756, 15.348365, 18.138971, 20.929574, 23.720174, 26.510769,\n",
+ " 29.30136 , 32.091944, 34.882521, 37.67309 , 40.463648, 43.254195,\n",
+ " 46.044727, 48.835241, 51.625734, 54.4162 , 57.206632, 59.99702 ,\n",
+ " 62.787352, 65.577607, 68.367756, 71.157752, 73.947515, 76.7369 ,\n",
+ " 79.525607, 82.312913, 85.096527, 87.863799]) lon
(lon)
float64
0.0 2.812 5.625 ... 354.4 357.2
bounds : lon_bnds units : degrees_east axis : X long_name : Longitude standard_name : longitude array([ 0. , 2.8125, 5.625 , 8.4375, 11.25 , 14.0625, 16.875 ,\n",
+ " 19.6875, 22.5 , 25.3125, 28.125 , 30.9375, 33.75 , 36.5625,\n",
+ " 39.375 , 42.1875, 45. , 47.8125, 50.625 , 53.4375, 56.25 ,\n",
+ " 59.0625, 61.875 , 64.6875, 67.5 , 70.3125, 73.125 , 75.9375,\n",
+ " 78.75 , 81.5625, 84.375 , 87.1875, 90. , 92.8125, 95.625 ,\n",
+ " 98.4375, 101.25 , 104.0625, 106.875 , 109.6875, 112.5 , 115.3125,\n",
+ " 118.125 , 120.9375, 123.75 , 126.5625, 129.375 , 132.1875, 135. ,\n",
+ " 137.8125, 140.625 , 143.4375, 146.25 , 149.0625, 151.875 , 154.6875,\n",
+ " 157.5 , 160.3125, 163.125 , 165.9375, 168.75 , 171.5625, 174.375 ,\n",
+ " 177.1875, 180. , 182.8125, 185.625 , 188.4375, 191.25 , 194.0625,\n",
+ " 196.875 , 199.6875, 202.5 , 205.3125, 208.125 , 210.9375, 213.75 ,\n",
+ " 216.5625, 219.375 , 222.1875, 225. , 227.8125, 230.625 , 233.4375,\n",
+ " 236.25 , 239.0625, 241.875 , 244.6875, 247.5 , 250.3125, 253.125 ,\n",
+ " 255.9375, 258.75 , 261.5625, 264.375 , 267.1875, 270. , 272.8125,\n",
+ " 275.625 , 278.4375, 281.25 , 284.0625, 286.875 , 289.6875, 292.5 ,\n",
+ " 295.3125, 298.125 , 300.9375, 303.75 , 306.5625, 309.375 , 312.1875,\n",
+ " 315. , 317.8125, 320.625 , 323.4375, 326.25 , 329.0625, 331.875 ,\n",
+ " 334.6875, 337.5 , 340.3125, 343.125 , 345.9375, 348.75 , 351.5625,\n",
+ " 354.375 , 357.1875]) height
()
float64
...
units : m axis : Z positive : up long_name : height standard_name : height [1 values with dtype=float64] Data variables: (5)
time_bnds
(time, bnds)
object
...
[3960 values with dtype=object] lat_bnds
(lat, bnds)
float64
...
[128 values with dtype=float64] lon_bnds
(lon, bnds)
float64
...
[256 values with dtype=float64] tas
(time, lat, lon)
float32
...
standard_name : air_temperature long_name : Near-Surface Air Temperature comment : ST+273.16, CMIP_table_comment: near-surface (usually, 2 meter) air temperature units : K original_name : ST history : degctok 2019-04-30T17:32:17Z altered by CMOR: Treated scalar dimension: 'height'. 2019-04-30T17:32:17Z altered by CMOR: Reordered dimensions, original order: lat lon time. 2019-04-30T17:32:17Z altered by CMOR: replaced missing value flag (1e+38) with standard missing value (1e+20). cell_methods : area: time: mean cell_measures : area: areacella [16220160 values with dtype=float32] areacella
(lat, lon)
float32
...
standard_name : cell_area long_name : Grid-Cell Area for Atmospheric Grid Variables comment : Use =mkwght=; afrac*4*Pi*R^2, CMIP_table_comment: For atmospheres with more than 1 mesh (e.g., staggered grids), report areas that apply to surface vertical fluxes of energy. units : m2 original_name : WGHT history : areacella 2019-05-01T18:36:43Z altered by CMOR: replaced missing value flag (1e+38) with standard missing value (1e+20). cell_methods : area: sum [8192 values with dtype=float32] Indexes: (3)
PandasIndex
PandasIndex(CFTimeIndex([1850-01-16 12:00:00, 1850-02-15 00:00:00, 1850-03-16 12:00:00,\n",
+ " 1850-04-16 00:00:00, 1850-05-16 12:00:00, 1850-06-16 00:00:00,\n",
+ " 1850-07-16 12:00:00, 1850-08-16 12:00:00, 1850-09-16 00:00:00,\n",
+ " 1850-10-16 12:00:00,\n",
+ " ...\n",
+ " 2014-03-16 12:00:00, 2014-04-16 00:00:00, 2014-05-16 12:00:00,\n",
+ " 2014-06-16 00:00:00, 2014-07-16 12:00:00, 2014-08-16 12:00:00,\n",
+ " 2014-09-16 00:00:00, 2014-10-16 12:00:00, 2014-11-16 00:00:00,\n",
+ " 2014-12-16 12:00:00],\n",
+ " dtype='object', length=1980, calendar='noleap', freq='None')) PandasIndex
PandasIndex(Index([ -87.86379883923273, -85.09652698831745, -82.31291294788636,\n",
+ " -79.52560657265951, -76.7368996803684, -73.94751515398974,\n",
+ " -71.15775201158739, -68.36775610831324, -65.57760701082788,\n",
+ " -62.78735179896313, -59.997020108491355, -57.2066315276433,\n",
+ " -54.41619952608627, -51.625733674938296, -48.83524096625064,\n",
+ " -46.044726631101724, -43.25419466535099, -40.46364817811508,\n",
+ " -37.67308962904537, -34.8825209937735, -32.091943881744044,\n",
+ " -29.301359621762764, -26.510769325211022, -23.72017393353477,\n",
+ " -20.92957425448953, -18.138970990239372, -15.348364759491508,\n",
+ " -12.55775611523069, -9.767145559195578, -6.976533553948642,\n",
+ " -4.185920533189158, -1.3953069108194975, 1.3953069108194975,\n",
+ " 4.185920533189158, 6.976533553948642, 9.767145559195578,\n",
+ " 12.55775611523069, 15.348364759491508, 18.138970990239372,\n",
+ " 20.92957425448953, 23.72017393353477, 26.510769325211022,\n",
+ " 29.301359621762764, 32.091943881744044, 34.8825209937735,\n",
+ " 37.67308962904537, 40.46364817811508, 43.25419466535099,\n",
+ " 46.044726631101724, 48.83524096625064, 51.625733674938296,\n",
+ " 54.41619952608627, 57.2066315276433, 59.997020108491355,\n",
+ " 62.78735179896313, 65.57760701082788, 68.36775610831324,\n",
+ " 71.15775201158739, 73.94751515398974, 76.7368996803684,\n",
+ " 79.52560657265951, 82.31291294788636, 85.09652698831745,\n",
+ " 87.86379883923273],\n",
+ " dtype='float64', name='lat')) PandasIndex
PandasIndex(Index([ 0.0, 2.8125, 5.625, 8.4375, 11.25, 14.0625, 16.875,\n",
+ " 19.6875, 22.5, 25.3125,\n",
+ " ...\n",
+ " 331.875, 334.6875, 337.5, 340.3125, 343.125, 345.9375, 348.75,\n",
+ " 351.5625, 354.375, 357.1875],\n",
+ " dtype='float64', name='lon', length=128)) Attributes: (53)
CCCma_model_hash : 3dedf95315d603326fde4f5340dc0519d80d10c0 CCCma_parent_runid : rc3-pictrl CCCma_pycmor_hash : 33c30511acc319a98240633965a04ca99c26427e CCCma_runid : rc3.1-his01 Conventions : CF-1.7 CMIP-6.2 YMDH_branch_time_in_child : 1850:01:01:00 YMDH_branch_time_in_parent : 5201:01:01:00 activity_id : CMIP branch_method : Spin-up documentation branch_time_in_child : 0.0 branch_time_in_parent : 1223115.0 contact : ec.cccma.info-info.ccmac.ec@canada.ca creation_date : 2019-04-30T17:32:17Z data_specs_version : 01.00.29 experiment : all-forcing simulation of the recent past experiment_id : historical external_variables : areacella forcing_index : 1 frequency : mon further_info_url : https://furtherinfo.es-doc.org/CMIP6.CCCma.CanESM5.historical.none.r1i1p1f1 grid : T63L49 native atmosphere, T63 Linear Gaussian Grid; 128 x 64 longitude/latitude; 49 levels; top level 1 hPa grid_label : gn history : 2019-04-30T17:32:17Z ;rewrote data to be consistent with CMIP for variable tas found in table Amon.;\n",
+ "Output from $runid initialization_index : 1 institution : Canadian Centre for Climate Modelling and Analysis, Environment and Climate Change Canada, Victoria, BC V8P 5C2, Canada institution_id : CCCma mip_era : CMIP6 nominal_resolution : 500 km parent_activity_id : CMIP parent_experiment_id : piControl parent_mip_era : CMIP6 parent_source_id : CanESM5 parent_time_units : days since 1850-01-01 0:0:0.0 parent_variant_label : r1i1p1f1 physics_index : 1 product : model-output realization_index : 1 realm : atmos references : Geophysical Model Development Special issue on CanESM5 (https://www.geosci-model-dev.net/special_issues.html) source : CanESM5 (2019): \n",
+ "aerosol: interactive\n",
+ "atmos: CanAM5 (T63L49 native atmosphere, T63 Linear Gaussian Grid; 128 x 64 longitude/latitude; 49 levels; top level 1 hPa)\n",
+ "atmosChem: specified oxidants for aerosols\n",
+ "land: CLASS3.6/CTEM1.2\n",
+ "landIce: specified ice sheets\n",
+ "ocean: NEMO3.4.1 (ORCA1 tripolar grid, 1 deg with refinement to 1/3 deg within 20 degrees of the equator; 361 x 290 longitude/latitude; 45 vertical levels; top grid cell 0-6.19 m)\n",
+ "ocnBgchem: Canadian Model of Ocean Carbon (CMOC); NPZD ecosystem with OMIP prescribed carbonate chemistry\n",
+ "seaIce: LIM2 source_id : CanESM5 source_type : AOGCM sub_experiment : none sub_experiment_id : none table_id : Amon table_info : Creation Date:(20 February 2019) MD5:374fbe5a2bcca535c40f7f23da271e49 title : CanESM5 output prepared for CMIP6 tracking_id : hdl:21.14100/872062df-acae-499b-aa0f-9eaca7681abc variable_id : tas variant_label : r1i1p1f1 version : v20190429 license : CMIP6 model data produced by The Government of Canada (Canadian Centre for Climate Modelling and Analysis, Environment and Climate Change Canada) is licensed under a Creative Commons Attribution ShareAlike 4.0 International License (https://creativecommons.org/licenses). Consult https://pcmdi.llnl.gov/CMIP6/TermsOfUse for terms of use governing CMIP6 output, including citation requirements and proper acknowledgment. Further information about this data, including some limitations, can be found via the further_info_url (recorded as a global attribute in this file) and at https:///pcmdi.llnl.gov/. The data producers and data providers make no warranty, either express or implied, including, but not limited to, warranties of merchantability and fitness for a particular purpose. All liabilities arising from the supply of the information (including any liability arising in negligence) are excluded to the fullest extent permitted by law. cmor_version : 3.4.0 "
+ ],
+ "text/plain": [
+ "\n",
+ "Dimensions: (time: 1980, bnds: 2, lat: 64, lon: 128)\n",
+ "Coordinates:\n",
+ " * time (time) object 1850-01-16 12:00:00 ... 2014-12-16 12:00:00\n",
+ " * lat (lat) float64 -87.86 -85.1 -82.31 -79.53 ... 82.31 85.1 87.86\n",
+ " * lon (lon) float64 0.0 2.812 5.625 8.438 ... 348.8 351.6 354.4 357.2\n",
+ " height float64 ...\n",
+ "Dimensions without coordinates: bnds\n",
+ "Data variables:\n",
+ " time_bnds (time, bnds) object ...\n",
+ " lat_bnds (lat, bnds) float64 ...\n",
+ " lon_bnds (lon, bnds) float64 ...\n",
+ " tas (time, lat, lon) float32 ...\n",
+ " areacella (lat, lon) float32 ...\n",
+ "Attributes: (12/53)\n",
+ " CCCma_model_hash: 3dedf95315d603326fde4f5340dc0519d80d10c0\n",
+ " CCCma_parent_runid: rc3-pictrl\n",
+ " CCCma_pycmor_hash: 33c30511acc319a98240633965a04ca99c26427e\n",
+ " CCCma_runid: rc3.1-his01\n",
+ " Conventions: CF-1.7 CMIP-6.2\n",
+ " YMDH_branch_time_in_child: 1850:01:01:00\n",
+ " ... ...\n",
+ " tracking_id: hdl:21.14100/872062df-acae-499b-aa0f-9eaca76...\n",
+ " variable_id: tas\n",
+ " variant_label: r1i1p1f1\n",
+ " version: v20190429\n",
+ " license: CMIP6 model data produced by The Government ...\n",
+ " cmor_version: 3.4.0"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "ds = dsd[\"Amon.tas\"]\n",
+ "ds"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "aa3b35d2-cccb-47b8-9792-7a945d947aba",
+ "metadata": {},
+ "source": [
+ "## Use clisops to subset for time and location"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "id": "404534be-f6fa-48dc-b4ba-cdd8aad75669",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "def subset_time(ds, start_time=\"1850-01-01T12:00:00Z\", end_time=\"2014-12-30T12:00:00Z\"):\n",
+ " from clisops.ops.subset import subset\n",
+ " \n",
+ " return subset(ds, time=f\"{start_time}/{end_time}\", output=\"xarray\")\n",
+ "\n",
+ "def subset_location(ds, lat_bounds=[30, 50], lon_bounds=[-100, -80]):\n",
+ " from clisops.ops.subset import subset_bbox\n",
+ " \n",
+ " return subset_bbox(ds, lat_bnds=lat_bounds, lon_bnds=lon_bounds)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "id": "56ebc5e7-e300-4819-be8b-cef0fe6b3d09",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " "
+ ],
+ "text/plain": [
+ ":QuadMesh [lon,lat] (tas)"
+ ]
+ },
+ "execution_count": 26,
+ "metadata": {
+ "application/vnd.holoviews_exec.v0+json": {}
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "ds.tas.isel(time=0).hvplot.quadmesh(geo=True, cmap=\"Reds\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "id": "937299ba-5838-494d-a77f-06cb5b4bc666",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " "
+ ],
+ "text/plain": [
+ ":Overlay\n",
+ " .Land.I :Feature [Longitude,Latitude]\n",
+ " .Ocean.I :Feature [Longitude,Latitude]\n",
+ " .Image.I :Image [lon,lat] (tas)\n",
+ " .Borders.I :Feature [Longitude,Latitude]\n",
+ " .Lakes.I :Feature [Longitude,Latitude]"
+ ]
+ },
+ "execution_count": 27,
+ "metadata": {
+ "application/vnd.holoviews_exec.v0+json": {}
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "subset_location(ds).tas.isel(time=-1).hvplot(x='lon',\n",
+ " y='lat',\n",
+ " features=[\"land\", \"lakes\", \"ocean\", \"borders\"],\n",
+ " cmap='Reds',\n",
+ " geo=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "aaf3c9a5-29bb-4145-b10e-3742d9b46779",
+ "metadata": {},
+ "source": [
+ "### Calculate a yearly average"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "id": "99861710-a1b3-4b94-bfe5-39919917c419",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "def yearly_average(ds):\n",
+ " from clisops.ops.average import average_time\n",
+ " return average_time(ds, \"year\", output_type=\"xarray\")[0]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 40,
+ "id": "92c6bb5b-cd66-44ca-bfbc-bb25dec72eab",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " "
+ ],
+ "text/plain": [
+ ":Overlay\n",
+ " .Land.I :Feature [Longitude,Latitude]\n",
+ " .Ocean.I :Feature [Longitude,Latitude]\n",
+ " .Image.I :Image [lon,lat] (tas)\n",
+ " .Borders.I :Feature [Longitude,Latitude]\n",
+ " .Lakes.I :Feature [Longitude,Latitude]"
+ ]
+ },
+ "execution_count": 40,
+ "metadata": {
+ "application/vnd.holoviews_exec.v0+json": {}
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "yearly_average(subset_location(ds)).isel(time=0).tas.hvplot(x='lon',\n",
+ " y='lat',\n",
+ " features=[\"land\", \"lakes\", \"ocean\", \"borders\"],\n",
+ " cmap='Reds',\n",
+ " geo=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "id": "4103dcaa-9fe0-4129-b62f-64921dec0658",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " "
+ ],
+ "text/plain": [
+ ":Overlay\n",
+ " .Land.I :Feature [Longitude,Latitude]\n",
+ " .Ocean.I :Feature [Longitude,Latitude]\n",
+ " .Image.I :Image [lon,lat] (tas)\n",
+ " .Borders.I :Feature [Longitude,Latitude]\n",
+ " .Lakes.I :Feature [Longitude,Latitude]"
+ ]
+ },
+ "execution_count": 42,
+ "metadata": {
+ "application/vnd.holoviews_exec.v0+json": {}
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "yearly_average(subset_location(ds)).isel(time=-1).tas.hvplot(x='lon',\n",
+ " y='lat',\n",
+ " features=[\"land\", \"lakes\", \"ocean\", \"borders\"],\n",
+ " cmap='Reds',\n",
+ " geo=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2111d7bc-b451-4d92-9794-46ac087ca248",
+ "metadata": {},
+ "source": [
+ "## Summary\n",
+ "In this notebook, we applied data reduction functions from the ESGF stack to data accessed through intake-esgf.\n",
+ "\n",
+ "### What's next?\n",
+ "We will see some more advanced examples of using these functions, including full task orchestration using Globus-Flows.\n",
+ "\n",
+ "## Resources and references\n",
+ "- [Intake-ESGF Documentation](https://github.com/nocollier/intake-esgf)\n",
+ "- [Globus Compute Documentation](https://www.globus.org/compute)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b2166127-1d65-4757-8953-fff73e71384f",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "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.10.13"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}