diff --git a/README.md b/README.md
index 12a39f2..4ec51b5 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ With such a large amount of model output produced, moving the data around is ine
## Authors
-[Ryan Abernathey](https://github.com/rabernat), [Henri Drake](https://github.com/hdrake), [Robert Ford](https://github.com/r-ford)
+[Ryan Abernathey](https://github.com/rabernat), [Henri Drake](https://github.com/hdrake), [Robert Ford](https://github.com/r-ford), [Max Grover](https://github.com/mgrover1)
### Contributors
diff --git a/_toc.yml b/_toc.yml
index c4a8e03..01d799f 100644
--- a/_toc.yml
+++ b/_toc.yml
@@ -15,4 +15,5 @@ parts:
- file: notebooks/example-workflows/gmst
- file: notebooks/example-workflows/precip-freq
- file: notebooks/example-workflows/xesmf-ohu
+ - file: notebooks/example-workflows/enso-esgf
- file: notebooks/example-workflows/esgf2-arm-comparison
diff --git a/environment.yml b/environment.yml
index 0b3ae52..16daf7c 100644
--- a/environment.yml
+++ b/environment.yml
@@ -4,13 +4,13 @@ channels:
- pyviz
dependencies:
- python<3.12
- - numpy<1.24.0
+ - numpy
- matplotlib
- pandas
- xarray
- xesmf
- xhistogram
- - numba
+ - numba>=0.58.0
- cartopy
- dask
- dask-gateway
diff --git a/notebooks/example-workflows/enso-esgf.ipynb b/notebooks/example-workflows/enso-esgf.ipynb
new file mode 100644
index 0000000..95ca845
--- /dev/null
+++ b/notebooks/example-workflows/enso-esgf.ipynb
@@ -0,0 +1,5648 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "7d1ea66b-fa0c-4fa4-9b65-3d7b07716e62",
+ "metadata": {},
+ "source": [
+ "# Calculating ENSO Using Intake-ESGF"
+ ]
+ },
+ {
+ "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 apply the [Niño 3.4 Index](https://climatedataguide.ucar.edu/climate-data/nino-sst-indices-nino-12-3-34-4-oni-and-tni) to a broader range of datasets. 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 example in the Pythia Foundations content detailed a single simulation. In this example, we aim to apply this computation more generically across a variety of datasets.\n",
+ "\n",
+ "The overall goal of this tutorial is to produce a plot of ENSO data using Xarray and intake-ESGF. The plots will resemble the Oceanic Niño Index plot shown below.\n",
+ "\n",
+ "![ONI index plot from NCAR Climate Data Guide](https://climatedataguide.ucar.edu/sites/default/files/styles/extra_large/public/2022-03/indices_oni_2_2_lg.png)"
+ ]
+ },
+ {
+ "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": 1,
+ "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.1.1'.replace('rc', '-rc.');\n",
+ " var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n",
+ " var reloading = false;\n",
+ " var Bokeh = root.Bokeh;\n",
+ " var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n",
+ "\n",
+ " if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n",
+ " root._bokeh_timeout = Date.now() + 5000;\n",
+ " root._bokeh_failed_load = false;\n",
+ " }\n",
+ "\n",
+ " function run_callbacks() {\n",
+ " try {\n",
+ " root._bokeh_onload_callbacks.forEach(function(callback) {\n",
+ " if (callback != null)\n",
+ " callback();\n",
+ " });\n",
+ " } finally {\n",
+ " delete root._bokeh_onload_callbacks;\n",
+ " }\n",
+ " console.debug(\"Bokeh: all callbacks have finished\");\n",
+ " }\n",
+ "\n",
+ " function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n",
+ " if (css_urls == null) css_urls = [];\n",
+ " if (js_urls == null) js_urls = [];\n",
+ " if (js_modules == null) js_modules = [];\n",
+ " if (js_exports == null) js_exports = {};\n",
+ "\n",
+ " root._bokeh_onload_callbacks.push(callback);\n",
+ "\n",
+ " if (root._bokeh_is_loading > 0) {\n",
+ " console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n",
+ " return null;\n",
+ " }\n",
+ " if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n",
+ " run_callbacks();\n",
+ " return null;\n",
+ " }\n",
+ " if (!reloading) {\n",
+ " console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n",
+ " }\n",
+ "\n",
+ " function on_load() {\n",
+ " root._bokeh_is_loading--;\n",
+ " if (root._bokeh_is_loading === 0) {\n",
+ " console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n",
+ " run_callbacks()\n",
+ " }\n",
+ " }\n",
+ " window._bokeh_on_load = on_load\n",
+ "\n",
+ " function on_error() {\n",
+ " console.error(\"failed to load \" + url);\n",
+ " }\n",
+ "\n",
+ " var skip = [];\n",
+ " if (window.requirejs) {\n",
+ " window.requirejs.config({'packages': {}, 'paths': {'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n",
+ " require([\"jspanel\"], function(jsPanel) {\n",
+ "\twindow.jsPanel = jsPanel\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-modal\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-tooltip\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-hint\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-layout\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-contextmenu\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-dock\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"gridstack\"], function(GridStack) {\n",
+ "\twindow.GridStack = GridStack\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"notyf\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " root._bokeh_is_loading = css_urls.length + 9;\n",
+ " } else {\n",
+ " root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n",
+ " }\n",
+ "\n",
+ " var existing_stylesheets = []\n",
+ " var links = document.getElementsByTagName('link')\n",
+ " for (var i = 0; i < links.length; i++) {\n",
+ " var link = links[i]\n",
+ " if (link.href != null) {\n",
+ "\texisting_stylesheets.push(link.href)\n",
+ " }\n",
+ " }\n",
+ " for (var i = 0; i < css_urls.length; i++) {\n",
+ " var url = css_urls[i];\n",
+ " if (existing_stylesheets.indexOf(url) !== -1) {\n",
+ "\ton_load()\n",
+ "\tcontinue;\n",
+ " }\n",
+ " const element = document.createElement(\"link\");\n",
+ " element.onload = on_load;\n",
+ " element.onerror = on_error;\n",
+ " element.rel = \"stylesheet\";\n",
+ " element.type = \"text/css\";\n",
+ " element.href = url;\n",
+ " console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n",
+ " document.body.appendChild(element);\n",
+ " } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n",
+ " var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n",
+ " for (var i = 0; i < urls.length; i++) {\n",
+ " skip.push(urls[i])\n",
+ " }\n",
+ " } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n",
+ " var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n",
+ " for (var i = 0; i < urls.length; i++) {\n",
+ " skip.push(urls[i])\n",
+ " }\n",
+ " } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n",
+ " var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n",
+ " for (var i = 0; i < urls.length; i++) {\n",
+ " skip.push(urls[i])\n",
+ " }\n",
+ " } var existing_scripts = []\n",
+ " var scripts = document.getElementsByTagName('script')\n",
+ " for (var i = 0; i < scripts.length; i++) {\n",
+ " var script = scripts[i]\n",
+ " if (script.src != null) {\n",
+ "\texisting_scripts.push(script.src)\n",
+ " }\n",
+ " }\n",
+ " for (var i = 0; i < js_urls.length; i++) {\n",
+ " var url = js_urls[i];\n",
+ " if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n",
+ "\tif (!window.requirejs) {\n",
+ "\t on_load();\n",
+ "\t}\n",
+ "\tcontinue;\n",
+ " }\n",
+ " var element = document.createElement('script');\n",
+ " element.onload = on_load;\n",
+ " element.onerror = on_error;\n",
+ " element.async = false;\n",
+ " element.src = url;\n",
+ " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
+ " document.head.appendChild(element);\n",
+ " }\n",
+ " for (var i = 0; i < js_modules.length; i++) {\n",
+ " var url = js_modules[i];\n",
+ " if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n",
+ "\tif (!window.requirejs) {\n",
+ "\t on_load();\n",
+ "\t}\n",
+ "\tcontinue;\n",
+ " }\n",
+ " var element = document.createElement('script');\n",
+ " element.onload = on_load;\n",
+ " element.onerror = on_error;\n",
+ " element.async = false;\n",
+ " element.src = url;\n",
+ " element.type = \"module\";\n",
+ " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
+ " document.head.appendChild(element);\n",
+ " }\n",
+ " for (const name in js_exports) {\n",
+ " var url = js_exports[name];\n",
+ " if (skip.indexOf(url) >= 0 || root[name] != null) {\n",
+ "\tif (!window.requirejs) {\n",
+ "\t on_load();\n",
+ "\t}\n",
+ "\tcontinue;\n",
+ " }\n",
+ " var element = document.createElement('script');\n",
+ " element.onerror = on_error;\n",
+ " element.async = false;\n",
+ " element.type = \"module\";\n",
+ " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
+ " element.textContent = `\n",
+ " import ${name} from \"${url}\"\n",
+ " window.${name} = ${name}\n",
+ " window._bokeh_on_load()\n",
+ " `\n",
+ " document.head.appendChild(element);\n",
+ " }\n",
+ " if (!js_urls.length && !js_modules.length) {\n",
+ " on_load()\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " function inject_raw_css(css) {\n",
+ " const element = document.createElement(\"style\");\n",
+ " element.appendChild(document.createTextNode(css));\n",
+ " document.body.appendChild(element);\n",
+ " }\n",
+ "\n",
+ " var js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js\", \"https://cdn.holoviz.org/panel/1.1.0/dist/panel.min.js\"];\n",
+ " var js_modules = [];\n",
+ " var js_exports = {};\n",
+ " var css_urls = [];\n",
+ " var inline_js = [ function(Bokeh) {\n",
+ " Bokeh.set_log_level(\"info\");\n",
+ " },\n",
+ "function(Bokeh) {} // ensure no trailing comma for IE\n",
+ " ];\n",
+ "\n",
+ " function run_inline_js() {\n",
+ " if ((root.Bokeh !== undefined) || (force === true)) {\n",
+ " for (var i = 0; i < inline_js.length; i++) {\n",
+ " inline_js[i].call(root, root.Bokeh);\n",
+ " }\n",
+ " // Cache old bokeh versions\n",
+ " if (Bokeh != undefined && !reloading) {\n",
+ "\tvar NewBokeh = root.Bokeh;\n",
+ "\tif (Bokeh.versions === undefined) {\n",
+ "\t Bokeh.versions = new Map();\n",
+ "\t}\n",
+ "\tif (NewBokeh.version !== Bokeh.version) {\n",
+ "\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n",
+ "\t}\n",
+ "\troot.Bokeh = Bokeh;\n",
+ " }} else if (Date.now() < root._bokeh_timeout) {\n",
+ " setTimeout(run_inline_js, 100);\n",
+ " } else if (!root._bokeh_failed_load) {\n",
+ " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n",
+ " root._bokeh_failed_load = true;\n",
+ " }\n",
+ " root._bokeh_is_initializing = false\n",
+ " }\n",
+ "\n",
+ " function load_or_wait() {\n",
+ " // Implement a backoff loop that tries to ensure we do not load multiple\n",
+ " // versions of Bokeh and its dependencies at the same time.\n",
+ " // In recent versions we use the root._bokeh_is_initializing flag\n",
+ " // to determine whether there is an ongoing attempt to initialize\n",
+ " // bokeh, however for backward compatibility we also try to ensure\n",
+ " // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n",
+ " // before older versions are fully initialized.\n",
+ " if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n",
+ " root._bokeh_is_initializing = false;\n",
+ " root._bokeh_onload_callbacks = undefined;\n",
+ " console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n",
+ " load_or_wait();\n",
+ " } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n",
+ " setTimeout(load_or_wait, 100);\n",
+ " } else {\n",
+ " Bokeh = root.Bokeh;\n",
+ " bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n",
+ " root._bokeh_is_initializing = true\n",
+ " root._bokeh_onload_callbacks = []\n",
+ " if (!reloading && (!bokeh_loaded || is_dev)) {\n",
+ "\troot.Bokeh = undefined;\n",
+ " }\n",
+ " load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n",
+ "\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n",
+ "\trun_inline_js();\n",
+ " });\n",
+ " }\n",
+ " }\n",
+ " // Give older versions of the autoload script a head-start to ensure\n",
+ " // they initialize before we start loading newer version.\n",
+ " setTimeout(load_or_wait, 100)\n",
+ "}(window));"
+ ],
+ "application/vnd.holoviews_load.v0+json": "(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n var py_version = '3.1.1'.replace('rc', '-rc.');\n var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n var reloading = false;\n var Bokeh = root.Bokeh;\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n run_callbacks();\n return null;\n }\n if (!reloading) {\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n var skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n require([\"jspanel\"], function(jsPanel) {\n\twindow.jsPanel = jsPanel\n\ton_load()\n })\n require([\"jspanel-modal\"], function() {\n\ton_load()\n })\n require([\"jspanel-tooltip\"], function() {\n\ton_load()\n })\n require([\"jspanel-hint\"], function() {\n\ton_load()\n })\n require([\"jspanel-layout\"], function() {\n\ton_load()\n })\n require([\"jspanel-contextmenu\"], function() {\n\ton_load()\n })\n require([\"jspanel-dock\"], function() {\n\ton_load()\n })\n require([\"gridstack\"], function(GridStack) {\n\twindow.GridStack = GridStack\n\ton_load()\n })\n require([\"notyf\"], function() {\n\ton_load()\n })\n root._bokeh_is_loading = css_urls.length + 9;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n var existing_stylesheets = []\n var links = document.getElementsByTagName('link')\n for (var i = 0; i < links.length; i++) {\n var link = links[i]\n if (link.href != null) {\n\texisting_stylesheets.push(link.href)\n }\n }\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n if (existing_stylesheets.indexOf(url) !== -1) {\n\ton_load()\n\tcontinue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } var existing_scripts = []\n var scripts = document.getElementsByTagName('script')\n for (var i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n\texisting_scripts.push(script.src)\n }\n }\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (var i = 0; i < js_modules.length; i++) {\n var url = js_modules[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n var url = js_exports[name];\n if (skip.indexOf(url) >= 0 || root[name] != null) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n var js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js\", \"https://cdn.holoviz.org/panel/1.1.0/dist/panel.min.js\"];\n var js_modules = [];\n var js_exports = {};\n var css_urls = [];\n var inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n\tvar NewBokeh = root.Bokeh;\n\tif (Bokeh.versions === undefined) {\n\t Bokeh.versions = new Map();\n\t}\n\tif (NewBokeh.version !== Bokeh.version) {\n\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n\t}\n\troot.Bokeh = Bokeh;\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n Bokeh = root.Bokeh;\n bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n if (!reloading && (!bokeh_loaded || is_dev)) {\n\troot.Bokeh = undefined;\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n\trun_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));"
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "\n",
+ "if ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n",
+ " window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n",
+ "}\n",
+ "\n",
+ "\n",
+ " function JupyterCommManager() {\n",
+ " }\n",
+ "\n",
+ " JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n",
+ " if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n",
+ " var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n",
+ " comm_manager.register_target(comm_id, function(comm) {\n",
+ " comm.on_msg(msg_handler);\n",
+ " });\n",
+ " } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n",
+ " window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n",
+ " comm.onMsg = msg_handler;\n",
+ " });\n",
+ " } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n",
+ " google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n",
+ " var messages = comm.messages[Symbol.asyncIterator]();\n",
+ " function processIteratorResult(result) {\n",
+ " var message = result.value;\n",
+ " console.log(message)\n",
+ " var content = {data: message.data, comm_id};\n",
+ " var buffers = []\n",
+ " for (var buffer of message.buffers || []) {\n",
+ " buffers.push(new DataView(buffer))\n",
+ " }\n",
+ " var metadata = message.metadata || {};\n",
+ " var msg = {content, buffers, metadata}\n",
+ " msg_handler(msg);\n",
+ " return messages.next().then(processIteratorResult);\n",
+ " }\n",
+ " return messages.next().then(processIteratorResult);\n",
+ " })\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n",
+ " if (comm_id in window.PyViz.comms) {\n",
+ " return window.PyViz.comms[comm_id];\n",
+ " } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n",
+ " var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n",
+ " var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n",
+ " if (msg_handler) {\n",
+ " comm.on_msg(msg_handler);\n",
+ " }\n",
+ " } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n",
+ " var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n",
+ " comm.open();\n",
+ " if (msg_handler) {\n",
+ " comm.onMsg = msg_handler;\n",
+ " }\n",
+ " } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n",
+ " var comm_promise = google.colab.kernel.comms.open(comm_id)\n",
+ " comm_promise.then((comm) => {\n",
+ " window.PyViz.comms[comm_id] = comm;\n",
+ " if (msg_handler) {\n",
+ " var messages = comm.messages[Symbol.asyncIterator]();\n",
+ " function processIteratorResult(result) {\n",
+ " var message = result.value;\n",
+ " var content = {data: message.data};\n",
+ " var metadata = message.metadata || {comm_id};\n",
+ " var msg = {content, metadata}\n",
+ " msg_handler(msg);\n",
+ " return messages.next().then(processIteratorResult);\n",
+ " }\n",
+ " return messages.next().then(processIteratorResult);\n",
+ " }\n",
+ " }) \n",
+ " var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n",
+ " return comm_promise.then((comm) => {\n",
+ " comm.send(data, metadata, buffers, disposeOnDone);\n",
+ " });\n",
+ " };\n",
+ " var comm = {\n",
+ " send: sendClosure\n",
+ " };\n",
+ " }\n",
+ " window.PyViz.comms[comm_id] = comm;\n",
+ " return comm;\n",
+ " }\n",
+ " window.PyViz.comm_manager = new JupyterCommManager();\n",
+ " \n",
+ "\n",
+ "\n",
+ "var JS_MIME_TYPE = 'application/javascript';\n",
+ "var HTML_MIME_TYPE = 'text/html';\n",
+ "var EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\n",
+ "var CLASS_NAME = 'output';\n",
+ "\n",
+ "/**\n",
+ " * Render data to the DOM node\n",
+ " */\n",
+ "function render(props, node) {\n",
+ " var div = document.createElement(\"div\");\n",
+ " var script = document.createElement(\"script\");\n",
+ " node.appendChild(div);\n",
+ " node.appendChild(script);\n",
+ "}\n",
+ "\n",
+ "/**\n",
+ " * Handle when a new output is added\n",
+ " */\n",
+ "function handle_add_output(event, handle) {\n",
+ " var output_area = handle.output_area;\n",
+ " var output = handle.output;\n",
+ " if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n",
+ " return\n",
+ " }\n",
+ " var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n",
+ " var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n",
+ " if (id !== undefined) {\n",
+ " var nchildren = toinsert.length;\n",
+ " var html_node = toinsert[nchildren-1].children[0];\n",
+ " html_node.innerHTML = output.data[HTML_MIME_TYPE];\n",
+ " var scripts = [];\n",
+ " var nodelist = html_node.querySelectorAll(\"script\");\n",
+ " for (var i in nodelist) {\n",
+ " if (nodelist.hasOwnProperty(i)) {\n",
+ " scripts.push(nodelist[i])\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " scripts.forEach( function (oldScript) {\n",
+ " var newScript = document.createElement(\"script\");\n",
+ " var attrs = [];\n",
+ " var nodemap = oldScript.attributes;\n",
+ " for (var j in nodemap) {\n",
+ " if (nodemap.hasOwnProperty(j)) {\n",
+ " attrs.push(nodemap[j])\n",
+ " }\n",
+ " }\n",
+ " attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n",
+ " newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n",
+ " oldScript.parentNode.replaceChild(newScript, oldScript);\n",
+ " });\n",
+ " if (JS_MIME_TYPE in output.data) {\n",
+ " toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n",
+ " }\n",
+ " output_area._hv_plot_id = id;\n",
+ " if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n",
+ " window.PyViz.plot_index[id] = Bokeh.index[id];\n",
+ " } else {\n",
+ " window.PyViz.plot_index[id] = null;\n",
+ " }\n",
+ " } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n",
+ " var bk_div = document.createElement(\"div\");\n",
+ " bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n",
+ " var script_attrs = bk_div.children[0].attributes;\n",
+ " for (var i = 0; i < script_attrs.length; i++) {\n",
+ " toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n",
+ " }\n",
+ " // store reference to server id on output_area\n",
+ " output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "/**\n",
+ " * Handle when an output is cleared or removed\n",
+ " */\n",
+ "function handle_clear_output(event, handle) {\n",
+ " var id = handle.cell.output_area._hv_plot_id;\n",
+ " var server_id = handle.cell.output_area._bokeh_server_id;\n",
+ " if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n",
+ " var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n",
+ " if (server_id !== null) {\n",
+ " comm.send({event_type: 'server_delete', 'id': server_id});\n",
+ " return;\n",
+ " } else if (comm !== null) {\n",
+ " comm.send({event_type: 'delete', 'id': id});\n",
+ " }\n",
+ " delete PyViz.plot_index[id];\n",
+ " if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n",
+ " var doc = window.Bokeh.index[id].model.document\n",
+ " doc.clear();\n",
+ " const i = window.Bokeh.documents.indexOf(doc);\n",
+ " if (i > -1) {\n",
+ " window.Bokeh.documents.splice(i, 1);\n",
+ " }\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "/**\n",
+ " * Handle kernel restart event\n",
+ " */\n",
+ "function handle_kernel_cleanup(event, handle) {\n",
+ " delete PyViz.comms[\"hv-extension-comm\"];\n",
+ " window.PyViz.plot_index = {}\n",
+ "}\n",
+ "\n",
+ "/**\n",
+ " * Handle update_display_data messages\n",
+ " */\n",
+ "function handle_update_output(event, handle) {\n",
+ " handle_clear_output(event, {cell: {output_area: handle.output_area}})\n",
+ " handle_add_output(event, handle)\n",
+ "}\n",
+ "\n",
+ "function register_renderer(events, OutputArea) {\n",
+ " function append_mime(data, metadata, element) {\n",
+ " // create a DOM node to render to\n",
+ " var toinsert = this.create_output_subarea(\n",
+ " metadata,\n",
+ " CLASS_NAME,\n",
+ " EXEC_MIME_TYPE\n",
+ " );\n",
+ " this.keyboard_manager.register_events(toinsert);\n",
+ " // Render to node\n",
+ " var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n",
+ " render(props, toinsert[0]);\n",
+ " element.append(toinsert);\n",
+ " return toinsert\n",
+ " }\n",
+ "\n",
+ " events.on('output_added.OutputArea', handle_add_output);\n",
+ " events.on('output_updated.OutputArea', handle_update_output);\n",
+ " events.on('clear_output.CodeCell', handle_clear_output);\n",
+ " events.on('delete.Cell', handle_clear_output);\n",
+ " events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n",
+ "\n",
+ " OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n",
+ " safe: true,\n",
+ " index: 0\n",
+ " });\n",
+ "}\n",
+ "\n",
+ "if (window.Jupyter !== undefined) {\n",
+ " try {\n",
+ " var events = require('base/js/events');\n",
+ " var OutputArea = require('notebook/js/outputarea').OutputArea;\n",
+ " if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n",
+ " register_renderer(events, OutputArea);\n",
+ " }\n",
+ " } catch(err) {\n",
+ " }\n",
+ "}\n"
+ ],
+ "application/vnd.holoviews_load.v0+json": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n"
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "(function(root) {\n",
+ " function now() {\n",
+ " return new Date();\n",
+ " }\n",
+ "\n",
+ " var force = true;\n",
+ " var py_version = '3.1.1'.replace('rc', '-rc.');\n",
+ " var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n",
+ " var reloading = true;\n",
+ " var Bokeh = root.Bokeh;\n",
+ " var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n",
+ "\n",
+ " if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n",
+ " root._bokeh_timeout = Date.now() + 5000;\n",
+ " root._bokeh_failed_load = false;\n",
+ " }\n",
+ "\n",
+ " function run_callbacks() {\n",
+ " try {\n",
+ " root._bokeh_onload_callbacks.forEach(function(callback) {\n",
+ " if (callback != null)\n",
+ " callback();\n",
+ " });\n",
+ " } finally {\n",
+ " delete root._bokeh_onload_callbacks;\n",
+ " }\n",
+ " console.debug(\"Bokeh: all callbacks have finished\");\n",
+ " }\n",
+ "\n",
+ " function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n",
+ " if (css_urls == null) css_urls = [];\n",
+ " if (js_urls == null) js_urls = [];\n",
+ " if (js_modules == null) js_modules = [];\n",
+ " if (js_exports == null) js_exports = {};\n",
+ "\n",
+ " root._bokeh_onload_callbacks.push(callback);\n",
+ "\n",
+ " if (root._bokeh_is_loading > 0) {\n",
+ " console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n",
+ " return null;\n",
+ " }\n",
+ " if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n",
+ " run_callbacks();\n",
+ " return null;\n",
+ " }\n",
+ " if (!reloading) {\n",
+ " console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n",
+ " }\n",
+ "\n",
+ " function on_load() {\n",
+ " root._bokeh_is_loading--;\n",
+ " if (root._bokeh_is_loading === 0) {\n",
+ " console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n",
+ " run_callbacks()\n",
+ " }\n",
+ " }\n",
+ " window._bokeh_on_load = on_load\n",
+ "\n",
+ " function on_error() {\n",
+ " console.error(\"failed to load \" + url);\n",
+ " }\n",
+ "\n",
+ " var skip = [];\n",
+ " if (window.requirejs) {\n",
+ " window.requirejs.config({'packages': {}, 'paths': {'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n",
+ " require([\"jspanel\"], function(jsPanel) {\n",
+ "\twindow.jsPanel = jsPanel\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-modal\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-tooltip\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-hint\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-layout\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-contextmenu\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-dock\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"gridstack\"], function(GridStack) {\n",
+ "\twindow.GridStack = GridStack\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"notyf\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " root._bokeh_is_loading = css_urls.length + 9;\n",
+ " } else {\n",
+ " root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n",
+ " }\n",
+ "\n",
+ " var existing_stylesheets = []\n",
+ " var links = document.getElementsByTagName('link')\n",
+ " for (var i = 0; i < links.length; i++) {\n",
+ " var link = links[i]\n",
+ " if (link.href != null) {\n",
+ "\texisting_stylesheets.push(link.href)\n",
+ " }\n",
+ " }\n",
+ " for (var i = 0; i < css_urls.length; i++) {\n",
+ " var url = css_urls[i];\n",
+ " if (existing_stylesheets.indexOf(url) !== -1) {\n",
+ "\ton_load()\n",
+ "\tcontinue;\n",
+ " }\n",
+ " const element = document.createElement(\"link\");\n",
+ " element.onload = on_load;\n",
+ " element.onerror = on_error;\n",
+ " element.rel = \"stylesheet\";\n",
+ " element.type = \"text/css\";\n",
+ " element.href = url;\n",
+ " console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n",
+ " document.body.appendChild(element);\n",
+ " } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n",
+ " var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n",
+ " for (var i = 0; i < urls.length; i++) {\n",
+ " skip.push(urls[i])\n",
+ " }\n",
+ " } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n",
+ " var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n",
+ " for (var i = 0; i < urls.length; i++) {\n",
+ " skip.push(urls[i])\n",
+ " }\n",
+ " } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n",
+ " var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n",
+ " for (var i = 0; i < urls.length; i++) {\n",
+ " skip.push(urls[i])\n",
+ " }\n",
+ " } var existing_scripts = []\n",
+ " var scripts = document.getElementsByTagName('script')\n",
+ " for (var i = 0; i < scripts.length; i++) {\n",
+ " var script = scripts[i]\n",
+ " if (script.src != null) {\n",
+ "\texisting_scripts.push(script.src)\n",
+ " }\n",
+ " }\n",
+ " for (var i = 0; i < js_urls.length; i++) {\n",
+ " var url = js_urls[i];\n",
+ " if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n",
+ "\tif (!window.requirejs) {\n",
+ "\t on_load();\n",
+ "\t}\n",
+ "\tcontinue;\n",
+ " }\n",
+ " var element = document.createElement('script');\n",
+ " element.onload = on_load;\n",
+ " element.onerror = on_error;\n",
+ " element.async = false;\n",
+ " element.src = url;\n",
+ " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
+ " document.head.appendChild(element);\n",
+ " }\n",
+ " for (var i = 0; i < js_modules.length; i++) {\n",
+ " var url = js_modules[i];\n",
+ " if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n",
+ "\tif (!window.requirejs) {\n",
+ "\t on_load();\n",
+ "\t}\n",
+ "\tcontinue;\n",
+ " }\n",
+ " var element = document.createElement('script');\n",
+ " element.onload = on_load;\n",
+ " element.onerror = on_error;\n",
+ " element.async = false;\n",
+ " element.src = url;\n",
+ " element.type = \"module\";\n",
+ " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
+ " document.head.appendChild(element);\n",
+ " }\n",
+ " for (const name in js_exports) {\n",
+ " var url = js_exports[name];\n",
+ " if (skip.indexOf(url) >= 0 || root[name] != null) {\n",
+ "\tif (!window.requirejs) {\n",
+ "\t on_load();\n",
+ "\t}\n",
+ "\tcontinue;\n",
+ " }\n",
+ " var element = document.createElement('script');\n",
+ " element.onerror = on_error;\n",
+ " element.async = false;\n",
+ " element.type = \"module\";\n",
+ " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
+ " element.textContent = `\n",
+ " import ${name} from \"${url}\"\n",
+ " window.${name} = ${name}\n",
+ " window._bokeh_on_load()\n",
+ " `\n",
+ " document.head.appendChild(element);\n",
+ " }\n",
+ " if (!js_urls.length && !js_modules.length) {\n",
+ " on_load()\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " function inject_raw_css(css) {\n",
+ " const element = document.createElement(\"style\");\n",
+ " element.appendChild(document.createTextNode(css));\n",
+ " document.body.appendChild(element);\n",
+ " }\n",
+ "\n",
+ " var js_urls = [];\n",
+ " var js_modules = [];\n",
+ " var js_exports = {};\n",
+ " var css_urls = [];\n",
+ " var inline_js = [ function(Bokeh) {\n",
+ " Bokeh.set_log_level(\"info\");\n",
+ " },\n",
+ "function(Bokeh) {} // ensure no trailing comma for IE\n",
+ " ];\n",
+ "\n",
+ " function run_inline_js() {\n",
+ " if ((root.Bokeh !== undefined) || (force === true)) {\n",
+ " for (var i = 0; i < inline_js.length; i++) {\n",
+ " inline_js[i].call(root, root.Bokeh);\n",
+ " }\n",
+ " // Cache old bokeh versions\n",
+ " if (Bokeh != undefined && !reloading) {\n",
+ "\tvar NewBokeh = root.Bokeh;\n",
+ "\tif (Bokeh.versions === undefined) {\n",
+ "\t Bokeh.versions = new Map();\n",
+ "\t}\n",
+ "\tif (NewBokeh.version !== Bokeh.version) {\n",
+ "\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n",
+ "\t}\n",
+ "\troot.Bokeh = Bokeh;\n",
+ " }} else if (Date.now() < root._bokeh_timeout) {\n",
+ " setTimeout(run_inline_js, 100);\n",
+ " } else if (!root._bokeh_failed_load) {\n",
+ " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n",
+ " root._bokeh_failed_load = true;\n",
+ " }\n",
+ " root._bokeh_is_initializing = false\n",
+ " }\n",
+ "\n",
+ " function load_or_wait() {\n",
+ " // Implement a backoff loop that tries to ensure we do not load multiple\n",
+ " // versions of Bokeh and its dependencies at the same time.\n",
+ " // In recent versions we use the root._bokeh_is_initializing flag\n",
+ " // to determine whether there is an ongoing attempt to initialize\n",
+ " // bokeh, however for backward compatibility we also try to ensure\n",
+ " // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n",
+ " // before older versions are fully initialized.\n",
+ " if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n",
+ " root._bokeh_is_initializing = false;\n",
+ " root._bokeh_onload_callbacks = undefined;\n",
+ " console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n",
+ " load_or_wait();\n",
+ " } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n",
+ " setTimeout(load_or_wait, 100);\n",
+ " } else {\n",
+ " Bokeh = root.Bokeh;\n",
+ " bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n",
+ " root._bokeh_is_initializing = true\n",
+ " root._bokeh_onload_callbacks = []\n",
+ " if (!reloading && (!bokeh_loaded || is_dev)) {\n",
+ "\troot.Bokeh = undefined;\n",
+ " }\n",
+ " load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n",
+ "\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n",
+ "\trun_inline_js();\n",
+ " });\n",
+ " }\n",
+ " }\n",
+ " // Give older versions of the autoload script a head-start to ensure\n",
+ " // they initialize before we start loading newer version.\n",
+ " setTimeout(load_or_wait, 100)\n",
+ "}(window));"
+ ],
+ "application/vnd.holoviews_load.v0+json": "(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n var py_version = '3.1.1'.replace('rc', '-rc.');\n var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n var reloading = true;\n var Bokeh = root.Bokeh;\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n run_callbacks();\n return null;\n }\n if (!reloading) {\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n var skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n require([\"jspanel\"], function(jsPanel) {\n\twindow.jsPanel = jsPanel\n\ton_load()\n })\n require([\"jspanel-modal\"], function() {\n\ton_load()\n })\n require([\"jspanel-tooltip\"], function() {\n\ton_load()\n })\n require([\"jspanel-hint\"], function() {\n\ton_load()\n })\n require([\"jspanel-layout\"], function() {\n\ton_load()\n })\n require([\"jspanel-contextmenu\"], function() {\n\ton_load()\n })\n require([\"jspanel-dock\"], function() {\n\ton_load()\n })\n require([\"gridstack\"], function(GridStack) {\n\twindow.GridStack = GridStack\n\ton_load()\n })\n require([\"notyf\"], function() {\n\ton_load()\n })\n root._bokeh_is_loading = css_urls.length + 9;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n var existing_stylesheets = []\n var links = document.getElementsByTagName('link')\n for (var i = 0; i < links.length; i++) {\n var link = links[i]\n if (link.href != null) {\n\texisting_stylesheets.push(link.href)\n }\n }\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n if (existing_stylesheets.indexOf(url) !== -1) {\n\ton_load()\n\tcontinue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } var existing_scripts = []\n var scripts = document.getElementsByTagName('script')\n for (var i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n\texisting_scripts.push(script.src)\n }\n }\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (var i = 0; i < js_modules.length; i++) {\n var url = js_modules[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n var url = js_exports[name];\n if (skip.indexOf(url) >= 0 || root[name] != null) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n var js_urls = [];\n var js_modules = [];\n var js_exports = {};\n var css_urls = [];\n var inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n\tvar NewBokeh = root.Bokeh;\n\tif (Bokeh.versions === undefined) {\n\t Bokeh.versions = new Map();\n\t}\n\tif (NewBokeh.version !== Bokeh.version) {\n\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n\t}\n\troot.Bokeh = Bokeh;\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n Bokeh = root.Bokeh;\n bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n if (!reloading && (!bokeh_loaded || is_dev)) {\n\troot.Bokeh = undefined;\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n\trun_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));"
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "\n",
+ "if ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n",
+ " window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n",
+ "}\n",
+ "\n",
+ "\n",
+ " function JupyterCommManager() {\n",
+ " }\n",
+ "\n",
+ " JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n",
+ " if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n",
+ " var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n",
+ " comm_manager.register_target(comm_id, function(comm) {\n",
+ " comm.on_msg(msg_handler);\n",
+ " });\n",
+ " } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n",
+ " window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n",
+ " comm.onMsg = msg_handler;\n",
+ " });\n",
+ " } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n",
+ " google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n",
+ " var messages = comm.messages[Symbol.asyncIterator]();\n",
+ " function processIteratorResult(result) {\n",
+ " var message = result.value;\n",
+ " console.log(message)\n",
+ " var content = {data: message.data, comm_id};\n",
+ " var buffers = []\n",
+ " for (var buffer of message.buffers || []) {\n",
+ " buffers.push(new DataView(buffer))\n",
+ " }\n",
+ " var metadata = message.metadata || {};\n",
+ " var msg = {content, buffers, metadata}\n",
+ " msg_handler(msg);\n",
+ " return messages.next().then(processIteratorResult);\n",
+ " }\n",
+ " return messages.next().then(processIteratorResult);\n",
+ " })\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n",
+ " if (comm_id in window.PyViz.comms) {\n",
+ " return window.PyViz.comms[comm_id];\n",
+ " } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n",
+ " var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n",
+ " var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n",
+ " if (msg_handler) {\n",
+ " comm.on_msg(msg_handler);\n",
+ " }\n",
+ " } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n",
+ " var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n",
+ " comm.open();\n",
+ " if (msg_handler) {\n",
+ " comm.onMsg = msg_handler;\n",
+ " }\n",
+ " } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n",
+ " var comm_promise = google.colab.kernel.comms.open(comm_id)\n",
+ " comm_promise.then((comm) => {\n",
+ " window.PyViz.comms[comm_id] = comm;\n",
+ " if (msg_handler) {\n",
+ " var messages = comm.messages[Symbol.asyncIterator]();\n",
+ " function processIteratorResult(result) {\n",
+ " var message = result.value;\n",
+ " var content = {data: message.data};\n",
+ " var metadata = message.metadata || {comm_id};\n",
+ " var msg = {content, metadata}\n",
+ " msg_handler(msg);\n",
+ " return messages.next().then(processIteratorResult);\n",
+ " }\n",
+ " return messages.next().then(processIteratorResult);\n",
+ " }\n",
+ " }) \n",
+ " var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n",
+ " return comm_promise.then((comm) => {\n",
+ " comm.send(data, metadata, buffers, disposeOnDone);\n",
+ " });\n",
+ " };\n",
+ " var comm = {\n",
+ " send: sendClosure\n",
+ " };\n",
+ " }\n",
+ " window.PyViz.comms[comm_id] = comm;\n",
+ " return comm;\n",
+ " }\n",
+ " window.PyViz.comm_manager = new JupyterCommManager();\n",
+ " \n",
+ "\n",
+ "\n",
+ "var JS_MIME_TYPE = 'application/javascript';\n",
+ "var HTML_MIME_TYPE = 'text/html';\n",
+ "var EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\n",
+ "var CLASS_NAME = 'output';\n",
+ "\n",
+ "/**\n",
+ " * Render data to the DOM node\n",
+ " */\n",
+ "function render(props, node) {\n",
+ " var div = document.createElement(\"div\");\n",
+ " var script = document.createElement(\"script\");\n",
+ " node.appendChild(div);\n",
+ " node.appendChild(script);\n",
+ "}\n",
+ "\n",
+ "/**\n",
+ " * Handle when a new output is added\n",
+ " */\n",
+ "function handle_add_output(event, handle) {\n",
+ " var output_area = handle.output_area;\n",
+ " var output = handle.output;\n",
+ " if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n",
+ " return\n",
+ " }\n",
+ " var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n",
+ " var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n",
+ " if (id !== undefined) {\n",
+ " var nchildren = toinsert.length;\n",
+ " var html_node = toinsert[nchildren-1].children[0];\n",
+ " html_node.innerHTML = output.data[HTML_MIME_TYPE];\n",
+ " var scripts = [];\n",
+ " var nodelist = html_node.querySelectorAll(\"script\");\n",
+ " for (var i in nodelist) {\n",
+ " if (nodelist.hasOwnProperty(i)) {\n",
+ " scripts.push(nodelist[i])\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " scripts.forEach( function (oldScript) {\n",
+ " var newScript = document.createElement(\"script\");\n",
+ " var attrs = [];\n",
+ " var nodemap = oldScript.attributes;\n",
+ " for (var j in nodemap) {\n",
+ " if (nodemap.hasOwnProperty(j)) {\n",
+ " attrs.push(nodemap[j])\n",
+ " }\n",
+ " }\n",
+ " attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n",
+ " newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n",
+ " oldScript.parentNode.replaceChild(newScript, oldScript);\n",
+ " });\n",
+ " if (JS_MIME_TYPE in output.data) {\n",
+ " toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n",
+ " }\n",
+ " output_area._hv_plot_id = id;\n",
+ " if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n",
+ " window.PyViz.plot_index[id] = Bokeh.index[id];\n",
+ " } else {\n",
+ " window.PyViz.plot_index[id] = null;\n",
+ " }\n",
+ " } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n",
+ " var bk_div = document.createElement(\"div\");\n",
+ " bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n",
+ " var script_attrs = bk_div.children[0].attributes;\n",
+ " for (var i = 0; i < script_attrs.length; i++) {\n",
+ " toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n",
+ " }\n",
+ " // store reference to server id on output_area\n",
+ " output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "/**\n",
+ " * Handle when an output is cleared or removed\n",
+ " */\n",
+ "function handle_clear_output(event, handle) {\n",
+ " var id = handle.cell.output_area._hv_plot_id;\n",
+ " var server_id = handle.cell.output_area._bokeh_server_id;\n",
+ " if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n",
+ " var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n",
+ " if (server_id !== null) {\n",
+ " comm.send({event_type: 'server_delete', 'id': server_id});\n",
+ " return;\n",
+ " } else if (comm !== null) {\n",
+ " comm.send({event_type: 'delete', 'id': id});\n",
+ " }\n",
+ " delete PyViz.plot_index[id];\n",
+ " if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n",
+ " var doc = window.Bokeh.index[id].model.document\n",
+ " doc.clear();\n",
+ " const i = window.Bokeh.documents.indexOf(doc);\n",
+ " if (i > -1) {\n",
+ " window.Bokeh.documents.splice(i, 1);\n",
+ " }\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "/**\n",
+ " * Handle kernel restart event\n",
+ " */\n",
+ "function handle_kernel_cleanup(event, handle) {\n",
+ " delete PyViz.comms[\"hv-extension-comm\"];\n",
+ " window.PyViz.plot_index = {}\n",
+ "}\n",
+ "\n",
+ "/**\n",
+ " * Handle update_display_data messages\n",
+ " */\n",
+ "function handle_update_output(event, handle) {\n",
+ " handle_clear_output(event, {cell: {output_area: handle.output_area}})\n",
+ " handle_add_output(event, handle)\n",
+ "}\n",
+ "\n",
+ "function register_renderer(events, OutputArea) {\n",
+ " function append_mime(data, metadata, element) {\n",
+ " // create a DOM node to render to\n",
+ " var toinsert = this.create_output_subarea(\n",
+ " metadata,\n",
+ " CLASS_NAME,\n",
+ " EXEC_MIME_TYPE\n",
+ " );\n",
+ " this.keyboard_manager.register_events(toinsert);\n",
+ " // Render to node\n",
+ " var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n",
+ " render(props, toinsert[0]);\n",
+ " element.append(toinsert);\n",
+ " return toinsert\n",
+ " }\n",
+ "\n",
+ " events.on('output_added.OutputArea', handle_add_output);\n",
+ " events.on('output_updated.OutputArea', handle_update_output);\n",
+ " events.on('clear_output.CodeCell', handle_clear_output);\n",
+ " events.on('delete.Cell', handle_clear_output);\n",
+ " events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n",
+ "\n",
+ " OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n",
+ " safe: true,\n",
+ " index: 0\n",
+ " });\n",
+ "}\n",
+ "\n",
+ "if (window.Jupyter !== undefined) {\n",
+ " try {\n",
+ " var events = require('base/js/events');\n",
+ " var OutputArea = require('notebook/js/outputarea').OutputArea;\n",
+ " if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n",
+ " register_renderer(events, OutputArea);\n",
+ " }\n",
+ " } catch(err) {\n",
+ " }\n",
+ "}\n"
+ ],
+ "application/vnd.holoviews_load.v0+json": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n"
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "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",
+ "warnings.filterwarnings(\"ignore\")\n",
+ "\n",
+ "hv.extension(\"bokeh\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7aa56cff-6a80-47fc-b99e-fd9fa960032c",
+ "metadata": {},
+ "source": [
+ "## Access ESGF-hosted CMIP6 Data\n",
+ "We will use the Climate Model Intercomparison Project version 6 (CMIP6) dataset, which is available from the Earth System Grid Federation (ESGF) data servers.\n",
+ "\n",
+ "There is a toolkit, `intake-esgf`, we can use to interface with the data servers, making it easier to search for our datasets."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "b82ae2da-1928-4f6b-b17b-551b82465845",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "mip_era [CMIP6]\n",
+ "activity_id [CMIP]\n",
+ "institution_id [NCAR]\n",
+ "source_id [CESM2]\n",
+ "experiment_id [historical]\n",
+ "member_id [r11i1p1f1]\n",
+ "table_id [Omon]\n",
+ "variable_id [tos]\n",
+ "grid_label [gr, gn]\n",
+ "dtype: object"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cat = ESGFCatalog()\n",
+ "cat.search(\n",
+ " activity_id='CMIP',\n",
+ " experiment_id=[\"historical\",\"ssp585\"],\n",
+ " source_id=\"CESM2\",\n",
+ " variable_id=[\"tos\"],\n",
+ " member_id='r11i1p1f1',\n",
+ " table_id=\"Omon\",\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "29bc9830-67b0-40ed-aae3-ad5b759878d5",
+ "metadata": {},
+ "source": [
+ "### Load into a DataTree\n",
+ "Once we subset for our data, we can load the data into a datatree, which is a nested structure of `xarray.Dataset`s."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "2b8d3bc8-f568-44f7-bee2-833cf532d823",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Loading datasets: 100%|############################################################################################################| 2/2 [00:09<00:00, 4.74s/dataset]\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "
<xarray.DatasetView>\n",
+ "Dimensions: ()\n",
+ "Data variables:\n",
+ " *empty* Groups: (2)
\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "
<xarray.DatasetView>\n",
+ "Dimensions: (time: 1980, d2: 2, lat: 180, lon: 360)\n",
+ "Coordinates:\n",
+ " * time (time) object 1850-01-15 13:00:00.000008 ... 2014-12-15 12:00:00\n",
+ " * lat (lat) float64 -89.5 -88.5 -87.5 -86.5 ... 86.5 87.5 88.5 89.5\n",
+ " * lon (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 356.5 357.5 358.5 359.5\n",
+ "Dimensions without coordinates: d2\n",
+ "Data variables:\n",
+ " time_bnds (time, d2) object dask.array<chunksize=(1, 2), meta=np.ndarray>\n",
+ " lat_bnds (time, lat, d2) float64 dask.array<chunksize=(600, 180, 2), meta=np.ndarray>\n",
+ " lon_bnds (time, lon, d2) float64 dask.array<chunksize=(600, 360, 2), meta=np.ndarray>\n",
+ " tos (time, lat, lon) float32 dask.array<chunksize=(1, 180, 360), meta=np.ndarray>\n",
+ "Attributes: (12/45)\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",
+ " sub_experiment_id: none\n",
+ " table_id: Omon\n",
+ " tracking_id: hdl:21.14100/0b412958-1dae-4684-9a1e-0c2ab29c492e\n",
+ " variable_id: tos\n",
+ " variant_info: CMIP6 20th century experiments (1850-2014) with C...\n",
+ " variant_label: r11i1p1f1 Groups: (0)
Dimensions: time : 1980d2 : 2lat : 180lon : 360
Coordinates: (3)
time
(time)
object
1850-01-15 13:00:00.000008 ... 2...
axis : T bounds : time_bnds standard_name : time title : time type : double array([cftime.DatetimeNoLeap(1850, 1, 15, 13, 0, 0, 8, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(1850, 2, 14, 0, 0, 0, 0, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(1850, 3, 15, 12, 0, 0, 0, has_year_zero=True),\n",
+ " ...,\n",
+ " cftime.DatetimeNoLeap(2014, 10, 15, 12, 0, 0, 0, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(2014, 11, 15, 0, 0, 0, 0, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(2014, 12, 15, 12, 0, 0, 0, has_year_zero=True)],\n",
+ " dtype=object) lat
(lat)
float64
-89.5 -88.5 -87.5 ... 88.5 89.5
axis : Y bounds : lat_bnds long_name : latitude standard_name : latitude units : degrees_north array([-89.5, -88.5, -87.5, -86.5, -85.5, -84.5, -83.5, -82.5, -81.5, -80.5,\n",
+ " -79.5, -78.5, -77.5, -76.5, -75.5, -74.5, -73.5, -72.5, -71.5, -70.5,\n",
+ " -69.5, -68.5, -67.5, -66.5, -65.5, -64.5, -63.5, -62.5, -61.5, -60.5,\n",
+ " -59.5, -58.5, -57.5, -56.5, -55.5, -54.5, -53.5, -52.5, -51.5, -50.5,\n",
+ " -49.5, -48.5, -47.5, -46.5, -45.5, -44.5, -43.5, -42.5, -41.5, -40.5,\n",
+ " -39.5, -38.5, -37.5, -36.5, -35.5, -34.5, -33.5, -32.5, -31.5, -30.5,\n",
+ " -29.5, -28.5, -27.5, -26.5, -25.5, -24.5, -23.5, -22.5, -21.5, -20.5,\n",
+ " -19.5, -18.5, -17.5, -16.5, -15.5, -14.5, -13.5, -12.5, -11.5, -10.5,\n",
+ " -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5,\n",
+ " 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5,\n",
+ " 10.5, 11.5, 12.5, 13.5, 14.5, 15.5, 16.5, 17.5, 18.5, 19.5,\n",
+ " 20.5, 21.5, 22.5, 23.5, 24.5, 25.5, 26.5, 27.5, 28.5, 29.5,\n",
+ " 30.5, 31.5, 32.5, 33.5, 34.5, 35.5, 36.5, 37.5, 38.5, 39.5,\n",
+ " 40.5, 41.5, 42.5, 43.5, 44.5, 45.5, 46.5, 47.5, 48.5, 49.5,\n",
+ " 50.5, 51.5, 52.5, 53.5, 54.5, 55.5, 56.5, 57.5, 58.5, 59.5,\n",
+ " 60.5, 61.5, 62.5, 63.5, 64.5, 65.5, 66.5, 67.5, 68.5, 69.5,\n",
+ " 70.5, 71.5, 72.5, 73.5, 74.5, 75.5, 76.5, 77.5, 78.5, 79.5,\n",
+ " 80.5, 81.5, 82.5, 83.5, 84.5, 85.5, 86.5, 87.5, 88.5, 89.5]) lon
(lon)
float64
0.5 1.5 2.5 ... 357.5 358.5 359.5
axis : X bounds : lon_bnds long_name : longitude standard_name : longitude units : degrees_east array([ 0.5, 1.5, 2.5, ..., 357.5, 358.5, 359.5]) Data variables: (4)
time_bnds
(time, d2)
object
dask.array<chunksize=(1, 2), meta=np.ndarray>
\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Array \n",
+ " Chunk \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Bytes \n",
+ " 30.94 kiB \n",
+ " 16 B \n",
+ " \n",
+ " \n",
+ " \n",
+ " Shape \n",
+ " (1980, 2) \n",
+ " (1, 2) \n",
+ " \n",
+ " \n",
+ " Dask graph \n",
+ " 1980 chunks in 9 graph layers \n",
+ " \n",
+ " \n",
+ " Data type \n",
+ " object numpy.ndarray \n",
+ " \n",
+ " \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " 2 \n",
+ " 1980 \n",
+ " \n",
+ " \n",
+ " \n",
+ "
lat_bnds
(time, lat, d2)
float64
dask.array<chunksize=(600, 180, 2), meta=np.ndarray>
long_name : latitude bounds units : degrees_north \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Array \n",
+ " Chunk \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Bytes \n",
+ " 5.44 MiB \n",
+ " 1.65 MiB \n",
+ " \n",
+ " \n",
+ " \n",
+ " Shape \n",
+ " (1980, 180, 2) \n",
+ " (600, 180, 2) \n",
+ " \n",
+ " \n",
+ " Dask graph \n",
+ " 4 chunks in 13 graph layers \n",
+ " \n",
+ " \n",
+ " Data type \n",
+ " float64 numpy.ndarray \n",
+ " \n",
+ " \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " 2 \n",
+ " 180 \n",
+ " 1980 \n",
+ " \n",
+ " \n",
+ " \n",
+ "
lon_bnds
(time, lon, d2)
float64
dask.array<chunksize=(600, 360, 2), meta=np.ndarray>
long_name : longitude bounds units : degrees_east \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Array \n",
+ " Chunk \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Bytes \n",
+ " 10.88 MiB \n",
+ " 3.30 MiB \n",
+ " \n",
+ " \n",
+ " \n",
+ " Shape \n",
+ " (1980, 360, 2) \n",
+ " (600, 360, 2) \n",
+ " \n",
+ " \n",
+ " Dask graph \n",
+ " 4 chunks in 13 graph layers \n",
+ " \n",
+ " \n",
+ " Data type \n",
+ " float64 numpy.ndarray \n",
+ " \n",
+ " \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " 2 \n",
+ " 360 \n",
+ " 1980 \n",
+ " \n",
+ " \n",
+ " \n",
+ "
tos
(time, lat, lon)
float32
dask.array<chunksize=(1, 180, 360), meta=np.ndarray>
cell_measures : area: areacello cell_methods : area: mean where sea time: mean comment : Model data on the 1x1 grid includes values in all cells for which ocean cells on the native grid cover more than 52.5 percent of the 1x1 grid cell. This 52.5 percent cutoff was chosen to produce ocean surface area on the 1x1 grid as close as possible to ocean surface area on the native grid, while not introducing fractional cell coverage. description : This may differ from "surface temperature" in regions of sea ice or floating ice shelves. For models using conservative temperature as the prognostic field, they should report the top ocean layer as surface potential temperature, which is the same as surface in situ temperature. frequency : mon id : tos long_name : Sea Surface Temperature mipTable : Omon out_name : tos prov : Omon ((isd.003)) realm : ocean standard_name : sea_surface_temperature time : time time_label : time-mean time_title : Temporal mean title : Sea Surface Temperature type : real units : degC variable_id : tos \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Array \n",
+ " Chunk \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Bytes \n",
+ " 489.44 MiB \n",
+ " 253.12 kiB \n",
+ " \n",
+ " \n",
+ " \n",
+ " Shape \n",
+ " (1980, 180, 360) \n",
+ " (1, 180, 360) \n",
+ " \n",
+ " \n",
+ " Dask graph \n",
+ " 1980 chunks in 9 graph layers \n",
+ " \n",
+ " \n",
+ " Data type \n",
+ " float32 numpy.ndarray \n",
+ " \n",
+ " \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " 360 \n",
+ " 180 \n",
+ " 1980 \n",
+ " \n",
+ " \n",
+ " \n",
+ "
Attributes: (45)
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 : ocean data regridded from native gx1v7 displaced pole grid (384x320 latxlon) to 180x360 latxlon using conservative regridding grid_label : gr 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 : 1x1 degree 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/0b412958-1dae-4684-9a1e-0c2ab29c492e 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
\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "
<xarray.DatasetView>\n",
+ "Dimensions: (time: 1980, nlat: 384, nlon: 320, d2: 2, vertices: 4)\n",
+ "Coordinates:\n",
+ " lat (nlat, nlon) float64 dask.array<chunksize=(384, 320), meta=np.ndarray>\n",
+ " lon (nlat, nlon) float64 dask.array<chunksize=(384, 320), meta=np.ndarray>\n",
+ " * nlat (nlat) int32 1 2 3 4 5 6 7 8 ... 377 378 379 380 381 382 383 384\n",
+ " * nlon (nlon) int32 1 2 3 4 5 6 7 8 ... 313 314 315 316 317 318 319 320\n",
+ " * time (time) object 1850-01-15 13:00:00.000008 ... 2014-12-15 12:00:00\n",
+ "Dimensions without coordinates: d2, vertices\n",
+ "Data variables:\n",
+ " tos (time, nlat, nlon) float32 dask.array<chunksize=(1, 384, 320), meta=np.ndarray>\n",
+ " time_bnds (time, d2) object dask.array<chunksize=(1, 2), meta=np.ndarray>\n",
+ " lat_bnds (time, nlat, nlon, vertices) float32 dask.array<chunksize=(600, 384, 320, 4), meta=np.ndarray>\n",
+ " lon_bnds (time, nlat, nlon, vertices) float32 dask.array<chunksize=(600, 384, 320, 4), meta=np.ndarray>\n",
+ "Attributes: (12/45)\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",
+ " sub_experiment_id: none\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 Groups: (0)
Dimensions: time : 1980nlat : 384nlon : 320d2 : 2vertices : 4
Coordinates: (5)
lat
(nlat, nlon)
float64
dask.array<chunksize=(384, 320), meta=np.ndarray>
axis : Y bounds : lat_bnds standard_name : latitude title : Latitude type : double units : degrees_north valid_max : 90.0 valid_min : -90.0 \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Array \n",
+ " Chunk \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Bytes \n",
+ " 0.94 MiB \n",
+ " 0.94 MiB \n",
+ " \n",
+ " \n",
+ " \n",
+ " Shape \n",
+ " (384, 320) \n",
+ " (384, 320) \n",
+ " \n",
+ " \n",
+ " Dask graph \n",
+ " 1 chunks in 15 graph layers \n",
+ " \n",
+ " \n",
+ " Data type \n",
+ " float64 numpy.ndarray \n",
+ " \n",
+ " \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " 320 \n",
+ " 384 \n",
+ " \n",
+ " \n",
+ " \n",
+ "
lon
(nlat, nlon)
float64
dask.array<chunksize=(384, 320), meta=np.ndarray>
axis : X bounds : lon_bnds standard_name : longitude title : Longitude type : double units : degrees_east valid_max : 360.0 valid_min : 0.0 \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Array \n",
+ " Chunk \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Bytes \n",
+ " 0.94 MiB \n",
+ " 0.94 MiB \n",
+ " \n",
+ " \n",
+ " \n",
+ " Shape \n",
+ " (384, 320) \n",
+ " (384, 320) \n",
+ " \n",
+ " \n",
+ " Dask graph \n",
+ " 1 chunks in 15 graph layers \n",
+ " \n",
+ " \n",
+ " Data type \n",
+ " float64 numpy.ndarray \n",
+ " \n",
+ " \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " 320 \n",
+ " 384 \n",
+ " \n",
+ " \n",
+ " \n",
+ "
nlat
(nlat)
int32
1 2 3 4 5 6 ... 380 381 382 383 384
long_name : cell index along second dimension units : 1 array([ 1, 2, 3, ..., 382, 383, 384], dtype=int32) nlon
(nlon)
int32
1 2 3 4 5 6 ... 316 317 318 319 320
long_name : cell index along first dimension units : 1 array([ 1, 2, 3, ..., 318, 319, 320], dtype=int32) time
(time)
object
1850-01-15 13:00:00.000008 ... 2...
axis : T bounds : time_bnds standard_name : time title : time type : double array([cftime.DatetimeNoLeap(1850, 1, 15, 13, 0, 0, 8, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(1850, 2, 14, 0, 0, 0, 0, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(1850, 3, 15, 12, 0, 0, 0, has_year_zero=True),\n",
+ " ...,\n",
+ " cftime.DatetimeNoLeap(2014, 10, 15, 12, 0, 0, 0, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(2014, 11, 15, 0, 0, 0, 0, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(2014, 12, 15, 12, 0, 0, 0, has_year_zero=True)],\n",
+ " dtype=object) Data variables: (4)
tos
(time, nlat, nlon)
float32
dask.array<chunksize=(1, 384, 320), meta=np.ndarray>
cell_measures : area: areacello cell_methods : area: mean where sea time: mean comment : TEMP[:,0,:,:] description : This may differ from "surface temperature" in regions of sea ice or floating ice shelves. For models using conservative temperature as the prognostic field, they should report the top ocean layer as surface potential temperature, which is the same as surface in situ temperature. frequency : mon id : tos long_name : Sea Surface Temperature mipTable : Omon out_name : tos prov : Omon ((isd.003)) realm : ocean standard_name : sea_surface_temperature time : time time_label : time-mean time_title : Temporal mean title : Sea Surface Temperature type : real units : degC variable_id : tos \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Array \n",
+ " Chunk \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Bytes \n",
+ " 0.91 GiB \n",
+ " 480.00 kiB \n",
+ " \n",
+ " \n",
+ " \n",
+ " Shape \n",
+ " (1980, 384, 320) \n",
+ " (1, 384, 320) \n",
+ " \n",
+ " \n",
+ " Dask graph \n",
+ " 1980 chunks in 9 graph layers \n",
+ " \n",
+ " \n",
+ " Data type \n",
+ " float32 numpy.ndarray \n",
+ " \n",
+ " \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " 320 \n",
+ " 384 \n",
+ " 1980 \n",
+ " \n",
+ " \n",
+ " \n",
+ "
time_bnds
(time, d2)
object
dask.array<chunksize=(1, 2), meta=np.ndarray>
\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Array \n",
+ " Chunk \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Bytes \n",
+ " 30.94 kiB \n",
+ " 16 B \n",
+ " \n",
+ " \n",
+ " \n",
+ " Shape \n",
+ " (1980, 2) \n",
+ " (1, 2) \n",
+ " \n",
+ " \n",
+ " Dask graph \n",
+ " 1980 chunks in 9 graph layers \n",
+ " \n",
+ " \n",
+ " Data type \n",
+ " object numpy.ndarray \n",
+ " \n",
+ " \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " 2 \n",
+ " 1980 \n",
+ " \n",
+ " \n",
+ " \n",
+ "
lat_bnds
(time, nlat, nlon, vertices)
float32
dask.array<chunksize=(600, 384, 320, 4), meta=np.ndarray>
\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Array \n",
+ " Chunk \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Bytes \n",
+ " 3.63 GiB \n",
+ " 1.10 GiB \n",
+ " \n",
+ " \n",
+ " \n",
+ " Shape \n",
+ " (1980, 384, 320, 4) \n",
+ " (600, 384, 320, 4) \n",
+ " \n",
+ " \n",
+ " Dask graph \n",
+ " 4 chunks in 13 graph layers \n",
+ " \n",
+ " \n",
+ " Data type \n",
+ " float32 numpy.ndarray \n",
+ " \n",
+ " \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " 1980 \n",
+ " 1 \n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " 4 \n",
+ " 320 \n",
+ " 384 \n",
+ " \n",
+ " \n",
+ " \n",
+ "
lon_bnds
(time, nlat, nlon, vertices)
float32
dask.array<chunksize=(600, 384, 320, 4), meta=np.ndarray>
\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Array \n",
+ " Chunk \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Bytes \n",
+ " 3.63 GiB \n",
+ " 1.10 GiB \n",
+ " \n",
+ " \n",
+ " \n",
+ " Shape \n",
+ " (1980, 384, 320, 4) \n",
+ " (600, 384, 320, 4) \n",
+ " \n",
+ " \n",
+ " Dask graph \n",
+ " 4 chunks in 13 graph layers \n",
+ " \n",
+ " \n",
+ " Data type \n",
+ " float32 numpy.ndarray \n",
+ " \n",
+ " \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " 1980 \n",
+ " 1 \n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " 4 \n",
+ " 320 \n",
+ " 384 \n",
+ " \n",
+ " \n",
+ " \n",
+ "
Attributes: (45)
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 Dimensions:
Coordinates: (0)
Data variables: (0)
Attributes: (0)
"
+ ],
+ "text/plain": [
+ "DataTree('None', parent=None)\n",
+ "├── DataTree('gr')\n",
+ "│ Dimensions: (time: 1980, d2: 2, lat: 180, lon: 360)\n",
+ "│ Coordinates:\n",
+ "│ * time (time) object 1850-01-15 13:00:00.000008 ... 2014-12-15 12:00:00\n",
+ "│ * lat (lat) float64 -89.5 -88.5 -87.5 -86.5 ... 86.5 87.5 88.5 89.5\n",
+ "│ * lon (lon) float64 0.5 1.5 2.5 3.5 4.5 ... 356.5 357.5 358.5 359.5\n",
+ "│ Dimensions without coordinates: d2\n",
+ "│ Data variables:\n",
+ "│ time_bnds (time, d2) object dask.array\n",
+ "│ lat_bnds (time, lat, d2) float64 dask.array\n",
+ "│ lon_bnds (time, lon, d2) float64 dask.array\n",
+ "│ tos (time, lat, lon) float32 dask.array\n",
+ "│ Attributes: (12/45)\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",
+ "│ sub_experiment_id: none\n",
+ "│ table_id: Omon\n",
+ "│ tracking_id: hdl:21.14100/0b412958-1dae-4684-9a1e-0c2ab29c492e\n",
+ "│ variable_id: tos\n",
+ "│ variant_info: CMIP6 20th century experiments (1850-2014) with C...\n",
+ "│ variant_label: r11i1p1f1\n",
+ "└── DataTree('gn')\n",
+ " Dimensions: (time: 1980, nlat: 384, nlon: 320, d2: 2, vertices: 4)\n",
+ " Coordinates:\n",
+ " lat (nlat, nlon) float64 dask.array\n",
+ " lon (nlat, nlon) float64 dask.array\n",
+ " * nlat (nlat) int32 1 2 3 4 5 6 7 8 ... 377 378 379 380 381 382 383 384\n",
+ " * nlon (nlon) int32 1 2 3 4 5 6 7 8 ... 313 314 315 316 317 318 319 320\n",
+ " * time (time) object 1850-01-15 13:00:00.000008 ... 2014-12-15 12:00:00\n",
+ " Dimensions without coordinates: d2, vertices\n",
+ " Data variables:\n",
+ " tos (time, nlat, nlon) float32 dask.array\n",
+ " time_bnds (time, d2) object dask.array\n",
+ " lat_bnds (time, nlat, nlon, vertices) float32 dask.array\n",
+ " lon_bnds (time, nlat, nlon, vertices) float32 dask.array\n",
+ " Attributes: (12/45)\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",
+ " sub_experiment_id: none\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"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "tos_tree = cat.to_datatree()\n",
+ "tos_tree"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2cf8ff53-028b-4998-b6f2-467bfc1e6599",
+ "metadata": {},
+ "source": [
+ "### Follow the Same Process for our Cell Area Dataset\n",
+ "We follow the same process for our grid cell area dataset, using the following query."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "ad09b4da-0aac-4a0e-972f-3896fb3293b4",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Loading datasets: 100%|############################################################################################################| 2/2 [00:02<00:00, 1.06s/dataset]\n"
+ ]
+ }
+ ],
+ "source": [
+ "cat = ESGFCatalog()\n",
+ "cat.search(\n",
+ " activity_id='CMIP',\n",
+ " experiment_id=[\"historical\",\"ssp585\"],\n",
+ " source_id=\"CESM2\",\n",
+ " variable_id=[\"areacello\"],\n",
+ " member_id='r11i1p1f1',\n",
+ " )\n",
+ "area_tree = cat.to_datatree()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fcd5409e-9a4e-4065-b3a5-2e1ab25e3d5a",
+ "metadata": {},
+ "source": [
+ "### Merge the Datasets Together\n",
+ "We would like to merge the datasets together, which makes them easier to work with!\n",
+ "\n",
+ "We would like to stay on the native model grid, using the `gn` node of the datatree, which represents the model native grid data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "317a9198-19ba-4e73-b006-4a8ba61327a8",
+ "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, nlat: 384, nlon: 320, d2: 2, vertices: 4)\n",
+ "Coordinates:\n",
+ " lat (nlat, nlon) float64 -79.22 -79.22 -79.22 ... 72.2 72.19 72.19\n",
+ " lon (nlat, nlon) float64 320.6 321.7 322.8 ... 318.9 319.4 319.8\n",
+ " * nlat (nlat) int32 1 2 3 4 5 6 7 8 ... 377 378 379 380 381 382 383 384\n",
+ " * nlon (nlon) int32 1 2 3 4 5 6 7 8 ... 313 314 315 316 317 318 319 320\n",
+ " * time (time) object 1850-01-15 13:00:00.000008 ... 2014-12-15 12:00:00\n",
+ "Dimensions without coordinates: d2, vertices\n",
+ "Data variables:\n",
+ " tos (time, nlat, nlon) float32 dask.array<chunksize=(1, 384, 320), meta=np.ndarray>\n",
+ " time_bnds (time, d2) object dask.array<chunksize=(1, 2), meta=np.ndarray>\n",
+ " lat_bnds (time, nlat, nlon, vertices) float32 -79.49 -79.49 ... 72.41\n",
+ " lon_bnds (time, nlat, nlon, vertices) float32 320.0 321.1 ... 320.0 319.6\n",
+ " areacello (nlat, nlon) float32 ...\n",
+ "Attributes: (12/45)\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",
+ " sub_experiment_id: none\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 Dimensions: time : 1980nlat : 384nlon : 320d2 : 2vertices : 4
Coordinates: (5)
lat
(nlat, nlon)
float64
-79.22 -79.22 ... 72.19 72.19
axis : Y bounds : lat_bnds standard_name : latitude title : Latitude type : double units : degrees_north valid_max : 90.0 valid_min : -90.0 array([[-79.22052261, -79.22052261, -79.22052261, ..., -79.22052261,\n",
+ " -79.22052261, -79.22052261],\n",
+ " [-78.68630626, -78.68630626, -78.68630626, ..., -78.68630626,\n",
+ " -78.68630626, -78.68630626],\n",
+ " [-78.15208992, -78.15208992, -78.15208992, ..., -78.15208992,\n",
+ " -78.15208992, -78.15208992],\n",
+ " ...,\n",
+ " [ 71.29031715, 71.29408252, 71.30160692, ..., 71.30160692,\n",
+ " 71.29408252, 71.29031716],\n",
+ " [ 71.73524335, 71.73881845, 71.74596231, ..., 71.74596231,\n",
+ " 71.73881845, 71.73524335],\n",
+ " [ 72.18597561, 72.18933231, 72.19603941, ..., 72.19603941,\n",
+ " 72.18933231, 72.18597562]]) lon
(nlat, nlon)
float64
320.6 321.7 322.8 ... 319.4 319.8
axis : X bounds : lon_bnds standard_name : longitude title : Longitude type : double units : degrees_east valid_max : 360.0 valid_min : 0.0 array([[320.56250892, 321.68750895, 322.81250898, ..., 317.18750883,\n",
+ " 318.31250886, 319.43750889],\n",
+ " [320.56250892, 321.68750895, 322.81250898, ..., 317.18750883,\n",
+ " 318.31250886, 319.43750889],\n",
+ " [320.56250892, 321.68750895, 322.81250898, ..., 317.18750883,\n",
+ " 318.31250886, 319.43750889],\n",
+ " ...,\n",
+ " [320.25133086, 320.75380113, 321.25577325, ..., 318.74424456,\n",
+ " 319.24621668, 319.74869143],\n",
+ " [320.23459477, 320.70358949, 321.17207442, ..., 318.82794339,\n",
+ " 319.29642832, 319.76542721],\n",
+ " [320.21650899, 320.6493303 , 321.08163473, ..., 318.91838308,\n",
+ " 319.3506875 , 319.78351267]]) nlat
(nlat)
int32
1 2 3 4 5 6 ... 380 381 382 383 384
long_name : cell index along second dimension units : 1 array([ 1, 2, 3, ..., 382, 383, 384], dtype=int32) nlon
(nlon)
int32
1 2 3 4 5 6 ... 316 317 318 319 320
long_name : cell index along first dimension units : 1 array([ 1, 2, 3, ..., 318, 319, 320], dtype=int32) time
(time)
object
1850-01-15 13:00:00.000008 ... 2...
axis : T bounds : time_bnds standard_name : time title : time type : double array([cftime.DatetimeNoLeap(1850, 1, 15, 13, 0, 0, 8, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(1850, 2, 14, 0, 0, 0, 0, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(1850, 3, 15, 12, 0, 0, 0, has_year_zero=True),\n",
+ " ...,\n",
+ " cftime.DatetimeNoLeap(2014, 10, 15, 12, 0, 0, 0, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(2014, 11, 15, 0, 0, 0, 0, has_year_zero=True),\n",
+ " cftime.DatetimeNoLeap(2014, 12, 15, 12, 0, 0, 0, has_year_zero=True)],\n",
+ " dtype=object) Data variables: (5)
tos
(time, nlat, nlon)
float32
dask.array<chunksize=(1, 384, 320), meta=np.ndarray>
cell_measures : area: areacello cell_methods : area: mean where sea time: mean comment : TEMP[:,0,:,:] description : This may differ from "surface temperature" in regions of sea ice or floating ice shelves. For models using conservative temperature as the prognostic field, they should report the top ocean layer as surface potential temperature, which is the same as surface in situ temperature. frequency : mon id : tos long_name : Sea Surface Temperature mipTable : Omon out_name : tos prov : Omon ((isd.003)) realm : ocean standard_name : sea_surface_temperature time : time time_label : time-mean time_title : Temporal mean title : Sea Surface Temperature type : real units : degC variable_id : tos \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Array \n",
+ " Chunk \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Bytes \n",
+ " 0.91 GiB \n",
+ " 480.00 kiB \n",
+ " \n",
+ " \n",
+ " \n",
+ " Shape \n",
+ " (1980, 384, 320) \n",
+ " (1, 384, 320) \n",
+ " \n",
+ " \n",
+ " Dask graph \n",
+ " 1980 chunks in 9 graph layers \n",
+ " \n",
+ " \n",
+ " Data type \n",
+ " float32 numpy.ndarray \n",
+ " \n",
+ " \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " 320 \n",
+ " 384 \n",
+ " 1980 \n",
+ " \n",
+ " \n",
+ " \n",
+ "
time_bnds
(time, d2)
object
dask.array<chunksize=(1, 2), meta=np.ndarray>
\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Array \n",
+ " Chunk \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " Bytes \n",
+ " 30.94 kiB \n",
+ " 16 B \n",
+ " \n",
+ " \n",
+ " \n",
+ " Shape \n",
+ " (1980, 2) \n",
+ " (1, 2) \n",
+ " \n",
+ " \n",
+ " Dask graph \n",
+ " 1980 chunks in 9 graph layers \n",
+ " \n",
+ " \n",
+ " Data type \n",
+ " object numpy.ndarray \n",
+ " \n",
+ " \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " 2 \n",
+ " 1980 \n",
+ " \n",
+ " \n",
+ " \n",
+ "
lat_bnds
(time, nlat, nlon, vertices)
float32
-79.49 -79.49 ... 72.41 72.41
array([[[[-79.48714, -79.48714, -78.9529 , -78.9529 ],\n",
+ " [-79.48714, -79.48714, -78.9529 , -78.9529 ],\n",
+ " [-79.48714, -79.48714, -78.9529 , -78.9529 ],\n",
+ " ...,\n",
+ " [-79.48714, -79.48714, -78.9529 , -78.9529 ],\n",
+ " [-79.48714, -79.48714, -78.9529 , -78.9529 ],\n",
+ " [-79.48714, -79.48714, -78.9529 , -78.9529 ]],\n",
+ "\n",
+ " [[-78.9529 , -78.9529 , -78.41866, -78.41866],\n",
+ " [-78.9529 , -78.9529 , -78.41866, -78.41866],\n",
+ " [-78.9529 , -78.9529 , -78.41866, -78.41866],\n",
+ " ...,\n",
+ " [-78.9529 , -78.9529 , -78.41866, -78.41866],\n",
+ " [-78.9529 , -78.9529 , -78.41866, -78.41866],\n",
+ " [-78.9529 , -78.9529 , -78.41866, -78.41866]],\n",
+ "\n",
+ " [[-78.41866, -78.41866, -77.88441, -77.88441],\n",
+ " [-78.41866, -78.41866, -77.88441, -77.88441],\n",
+ " [-78.41866, -78.41866, -77.88441, -77.88441],\n",
+ " ...,\n",
+ "...\n",
+ " ...,\n",
+ " [ 71.08543, 71.07581, 71.51766, 71.52684],\n",
+ " [ 71.07581, 71.07003, 71.51215, 71.51766],\n",
+ " [ 71.07003, 71.0681 , 71.51031, 71.51215]],\n",
+ "\n",
+ " [[ 71.50664, 71.51215, 71.95984, 71.95463],\n",
+ " [ 71.51215, 71.51766, 71.96504, 71.95984],\n",
+ " [ 71.51766, 71.52684, 71.97371, 71.96504],\n",
+ " ...,\n",
+ " [ 71.52684, 71.51766, 71.96504, 71.97371],\n",
+ " [ 71.51766, 71.51215, 71.95984, 71.96504],\n",
+ " [ 71.51215, 71.51031, 71.9581 , 71.95984]],\n",
+ "\n",
+ " [[ 71.95463, 71.95984, 72.41355, 72.4087 ],\n",
+ " [ 71.95984, 71.96504, 72.41841, 72.41355],\n",
+ " [ 71.96504, 71.97371, 72.4265 , 72.41841],\n",
+ " ...,\n",
+ " [ 71.97371, 71.96504, 72.41841, 72.4265 ],\n",
+ " [ 71.96504, 71.95984, 72.41355, 72.41841],\n",
+ " [ 71.95984, 71.9581 , 72.41193, 72.41355]]]], dtype=float32) lon_bnds
(time, nlat, nlon, vertices)
float32
320.0 321.1 321.1 ... 320.0 319.6
array([[[[320. , 321.125 , 321.125 , 320. ],\n",
+ " [321.125 , 322.25 , 322.25 , 321.125 ],\n",
+ " [322.25 , 323.375 , 323.375 , 322.25 ],\n",
+ " ...,\n",
+ " [316.625 , 317.75 , 317.75 , 316.625 ],\n",
+ " [317.75 , 318.875 , 318.875 , 317.75 ],\n",
+ " [318.875 , 320. , 320. , 318.875 ]],\n",
+ "\n",
+ " [[320. , 321.125 , 321.125 , 320. ],\n",
+ " [321.125 , 322.25 , 322.25 , 321.125 ],\n",
+ " [322.25 , 323.375 , 323.375 , 322.25 ],\n",
+ " ...,\n",
+ " [316.625 , 317.75 , 317.75 , 316.625 ],\n",
+ " [317.75 , 318.875 , 318.875 , 317.75 ],\n",
+ " [318.875 , 320. , 320. , 318.875 ]],\n",
+ "\n",
+ " [[320. , 321.125 , 321.125 , 320. ],\n",
+ " [321.125 , 322.25 , 322.25 , 321.125 ],\n",
+ " [322.25 , 323.375 , 323.375 , 322.25 ],\n",
+ " ...,\n",
+ "...\n",
+ " ...,\n",
+ " [318.4456, 318.9632, 319.0276, 318.5423],\n",
+ " [318.9632, 319.4814, 319.5136, 319.0276],\n",
+ " [319.4814, 320. , 320. , 319.5136]],\n",
+ "\n",
+ " [[320.0003, 320.4864, 320.4516, 320.0004],\n",
+ " [320.4864, 320.9724, 320.9029, 320.4516],\n",
+ " [320.9724, 321.4578, 321.3534, 320.9029],\n",
+ " ...,\n",
+ " [318.5423, 319.0276, 319.0972, 318.6466],\n",
+ " [319.0276, 319.5136, 319.5484, 319.0972],\n",
+ " [319.5136, 320. , 320. , 319.5484]],\n",
+ "\n",
+ " [[320.0004, 320.4516, 320.414 , 320.0004],\n",
+ " [320.4516, 320.9029, 320.8276, 320.414 ],\n",
+ " [320.9029, 321.3534, 321.2405, 320.8276],\n",
+ " ...,\n",
+ " [318.6466, 319.0972, 319.1724, 318.7595],\n",
+ " [319.0972, 319.5484, 319.586 , 319.1724],\n",
+ " [319.5484, 320. , 320. , 319.586 ]]]], dtype=float32) areacello
(nlat, nlon)
float32
...
cell_methods : area: sum comment : TAREA description : Cell areas for any grid used to report ocean variables and variables which are requested as used on the model ocean grid (e.g. hfsso, which is a downward heat flux from the atmosphere interpolated onto the ocean grid). These cell areas should be defined to enable exact calculation of global integrals (e.g., of vertical fluxes of energy at the surface and top of the atmosphere). frequency : fx id : areacello long_name : Grid-Cell Area for Ocean Variables mipTable : Ofx out_name : areacello prov : Ofx ((isd.003)) realm : ocean standard_name : cell_area time_label : None time_title : No temporal dimensions ... fixed field title : Grid-Cell Area for Ocean Variables type : real units : m2 variable_id : areacello [122880 values with dtype=float32] Indexes: (3)
PandasIndex
PandasIndex(Index([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,\n",
+ " ...\n",
+ " 375, 376, 377, 378, 379, 380, 381, 382, 383, 384],\n",
+ " dtype='int32', name='nlat', length=384)) PandasIndex
PandasIndex(Index([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,\n",
+ " ...\n",
+ " 311, 312, 313, 314, 315, 316, 317, 318, 319, 320],\n",
+ " dtype='int32', name='nlon', length=320)) PandasIndex
PandasIndex(CFTimeIndex([1850-01-15 13:00:00.000008, 1850-02-14 00:00:00, 1850-03-15 12:00:00,\n",
+ " 1850-04-15 00:00:00, 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, 1850-09-15 00:00:00,\n",
+ " 1850-10-15 12:00:00,\n",
+ " ...\n",
+ " 2014-03-15 12:00:00, 2014-04-15 00:00:00, 2014-05-15 12:00:00,\n",
+ " 2014-06-15 00:00:00, 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, 2014-11-15 00:00:00,\n",
+ " 2014-12-15 12:00:00],\n",
+ " dtype='object', length=1980, calendar='noleap', freq='None')) Attributes: (45)
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 "
+ ],
+ "text/plain": [
+ "\n",
+ "Dimensions: (time: 1980, nlat: 384, nlon: 320, d2: 2, vertices: 4)\n",
+ "Coordinates:\n",
+ " lat (nlat, nlon) float64 -79.22 -79.22 -79.22 ... 72.2 72.19 72.19\n",
+ " lon (nlat, nlon) float64 320.6 321.7 322.8 ... 318.9 319.4 319.8\n",
+ " * nlat (nlat) int32 1 2 3 4 5 6 7 8 ... 377 378 379 380 381 382 383 384\n",
+ " * nlon (nlon) int32 1 2 3 4 5 6 7 8 ... 313 314 315 316 317 318 319 320\n",
+ " * time (time) object 1850-01-15 13:00:00.000008 ... 2014-12-15 12:00:00\n",
+ "Dimensions without coordinates: d2, vertices\n",
+ "Data variables:\n",
+ " tos (time, nlat, nlon) float32 dask.array\n",
+ " time_bnds (time, d2) object dask.array\n",
+ " lat_bnds (time, nlat, nlon, vertices) float32 -79.49 -79.49 ... 72.41\n",
+ " lon_bnds (time, nlat, nlon, vertices) float32 320.0 321.1 ... 320.0 319.6\n",
+ " areacello (nlat, nlon) float32 ...\n",
+ "Attributes: (12/45)\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",
+ " sub_experiment_id: none\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"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "tos_ds = tos_tree[\"gn\"].to_dataset()\n",
+ "area_ds = area_tree[\"gn\"].to_dataset()\n",
+ "\n",
+ "ds = xr.merge([tos_ds, area_ds])\n",
+ "ds"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f3ac774f-7a6f-425b-82c9-c5b3099eb203",
+ "metadata": {},
+ "source": [
+ "## Calculate ENSO\n",
+ "The calculation is covered in more detail in the Pythia Foundations book, here, we apply the calculation to our datasets!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "c01c56eb-71b9-4b4b-92c3-31ff3714b55b",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "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"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "193b0055-35a4-4f48-8d00-b24059f00160",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ "
<xarray.DataArray 'tos' (time: 1980)>\n",
+ "array([ nan, nan, 0.06341499, ..., 0.79205155, nan,\n",
+ " nan], dtype=float32)\n",
+ "Coordinates:\n",
+ " * time (time) object 1850-01-15 13:00:00.000008 ... 2014-12-15 12:00:00\n",
+ " month (time) int64 1 2 3 4 5 6 7 8 9 10 11 ... 2 3 4 5 6 7 8 9 10 11 12 Coordinates: (2)
Indexes: (1)
PandasIndex
PandasIndex(CFTimeIndex([1850-01-15 13:00:00.000008, 1850-02-14 00:00:00, 1850-03-15 12:00:00,\n",
+ " 1850-04-15 00:00:00, 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, 1850-09-15 00:00:00,\n",
+ " 1850-10-15 12:00:00,\n",
+ " ...\n",
+ " 2014-03-15 12:00:00, 2014-04-15 00:00:00, 2014-05-15 12:00:00,\n",
+ " 2014-06-15 00:00:00, 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, 2014-11-15 00:00:00,\n",
+ " 2014-12-15 12:00:00],\n",
+ " dtype='object', length=1980, calendar='noleap', freq='None')) Attributes: (0)
"
+ ],
+ "text/plain": [
+ "\n",
+ "array([ nan, nan, 0.06341499, ..., 0.79205155, nan,\n",
+ " nan], dtype=float32)\n",
+ "Coordinates:\n",
+ " * time (time) object 1850-01-15 13:00:00.000008 ... 2014-12-15 12:00:00\n",
+ " month (time) int64 1 2 3 4 5 6 7 8 9 10 11 ... 2 3 4 5 6 7 8 9 10 11 12"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "enso_index = calculate_enso(ds).compute()\n",
+ "enso_index"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0a6dba6d-f305-4909-bbde-63366c5cb2b5",
+ "metadata": {},
+ "source": [
+ "## Visualize ENSO\n",
+ "\n",
+ "### Basic Visualization\n",
+ "We can create a basic visualization of the dataset using hvplot!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "67cac155-bb58-4c80-a0e4-1c3fd8de8eb4",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "WARNING:param.CurvePlot00894: Converting cftime.datetime from a non-standard calendar (noleap) to a standard calendar for plotting. This may lead to subtle errors in formatting dates, for accurate tick formatting switch to the matplotlib backend.\n"
+ ]
+ },
+ {
+ "data": {},
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.holoviews_exec.v0+json": "",
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ":Curve [time] (tos)"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {
+ "application/vnd.holoviews_exec.v0+json": {
+ "id": "p1002"
+ }
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "enso_index.hvplot(x='time')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "629d1d24-accb-426d-858c-6422d568d97d",
+ "metadata": {},
+ "source": [
+ "### Identify El Niño and La Niña\n",
+ "Including the indices as we showed above is not always the most helpful. We need to add additional context to help the reader understand when we reach El Niño and La Niña, which are helpful thresholds for the wider community to use.\n",
+ "\n",
+ "A typical threshold to use is 0.4, which means El Niño occurs when the ENSO 3.4 index is equal to or greater than 0.4, and La Niña occurs when the ENSO 3.4 index is equal to or less than 0.4.\n",
+ "\n",
+ "We apply this using the following function."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "0bf6b460-f64a-47d2-b819-53abdc0b03f6",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "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"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "13680161-2f71-40f0-9c56-df8cac9621a5",
+ "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 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: (0)
"
+ ],
+ "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"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "enso_ds = add_enso_thresholds(enso_index)\n",
+ "enso_ds"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b502d1d8-e4d7-497d-84fa-c478ec7b4e91",
+ "metadata": {},
+ "source": [
+ "### Configure a Function to Plot the Data\n",
+ "We will use the `hvplot.area` functionality here, which enables us to shade the area between values. We use the newly added variables in our dataset to help here."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "d2e8e487-5158-4825-a7aa-9dfe582192c4",
+ "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)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "ed848ecc-1731-46a3-a898-a4cfcaa55d71",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {},
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.holoviews_exec.v0+json": "",
+ "text/html": [
+ "\n",
+ ""
+ ],
+ "text/plain": [
+ ":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": 12,
+ "metadata": {
+ "application/vnd.holoviews_exec.v0+json": {
+ "id": "p1318"
+ }
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "plot_enso(enso_ds)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c3bf5d22-57fa-4fad-b194-064a2aae342b",
+ "metadata": {},
+ "source": [
+ "## Apply to Multiple Datasets\n",
+ "Now that we have the workflow, let's apply this to multiple datasets. We focus here on two different instiutions:\n",
+ "- The National Center for Atmospheric Research (NCAR)\n",
+ "- Model for Interdisciplinary Research on Climate (MIROC)\n",
+ "\n",
+ "Both of these modeling centers produced output for CMIP6.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0624f670-f805-4813-8853-d7f94f2c2a86",
+ "metadata": {},
+ "source": [
+ "### Setup a Function for Searching and Combining Datasets\n",
+ "We can use the query mentioned previously to configure our search. Here, we parameterize based on the institution id (ex. NCAR, MIROC)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "256207a2-0275-4897-b380-334de13e9ce1",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "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",
+ " # Search and load the ocean grid cell area\n",
+ " cat = ESGFCatalog()\n",
+ " cat.search(\n",
+ " activity_id=\"CMIP\",\n",
+ " experiment_id=\"historical\",\n",
+ " institution_id=institution_id,\n",
+ " variable_id=[\"areacello\"],\n",
+ " member_id='r11i1p1f1',\n",
+ " )\n",
+ " try:\n",
+ " area_ds = cat.to_datatree()[grid].to_dataset()\n",
+ " except ValueError:\n",
+ " area_ds = cat.to_dataset_dict()[\"\"]\n",
+ " return xr.merge([tos_ds, area_ds])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "28fd9293-4b6e-4881-af47-8f95ece00c62",
+ "metadata": {},
+ "source": [
+ "### Apply the Search and Computations"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "3a173bf5-c152-4e63-a0c3-c1055ea1439b",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Loading datasets: 100%|############################################################################################################| 2/2 [00:07<00:00, 3.99s/dataset]\n",
+ "Loading datasets: 100%|############################################################################################################| 2/2 [00:02<00:00, 1.02s/dataset]\n"
+ ]
+ }
+ ],
+ "source": [
+ "ncar_ds = search_esgf(\"NCAR\")\n",
+ "enso_index_ncar = add_enso_thresholds(calculate_enso(ncar_ds).compute())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "64d12adf-1c0f-4f8d-a37e-6b45b20c55e8",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Loading datasets: 100%|############################################################################################################| 1/1 [00:01<00:00, 1.92s/dataset]\n",
+ "Loading datasets: 100%|############################################################################################################| 1/1 [00:02<00:00, 2.01s/dataset]\n",
+ "Loading datasets: 100%|############################################################################################################| 1/1 [00:00<00:00, 1.04dataset/s]\n",
+ "Loading datasets: 100%|############################################################################################################| 1/1 [00:01<00:00, 1.06s/dataset]\n"
+ ]
+ }
+ ],
+ "source": [
+ "miroc_ds = search_esgf(\"MIROC\")\n",
+ "enso_index_miroc = add_enso_thresholds(calculate_enso(miroc_ds).compute())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d2f0a6b0-07d7-4c95-9598-0d746cfcaf72",
+ "metadata": {},
+ "source": [
+ "### Visualize our ENSO Comparison\n",
+ "Now that we have our data, we can plot the comparison, stacking the two together using hvPlot."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "54594fe4-136b-4150-bb1f-25f70df82364",
+ "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": 16,
+ "metadata": {
+ "application/vnd.holoviews_exec.v0+json": {
+ "id": "p1794"
+ }
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "ncar_enso_plot = plot_enso(enso_index_ncar).opts(title=f'NCAR {ncar_ds.attrs[\"source_id\"]} \\n Ensemble Member: {ncar_ds.attrs[\"variant_label\"]}')\n",
+ "miroc_enso_plot = plot_enso(enso_index_miroc).opts(title=f'MIROC {miroc_ds.attrs[\"source_id\"]} \\n Ensemble Member: {miroc_ds.attrs[\"variant_label\"]}')\n",
+ "\n",
+ "(ncar_enso_plot + miroc_enso_plot).cols(1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b3bfceb9-124a-4d72-9d49-3ca255965e29",
+ "metadata": {},
+ "source": [
+ "## Summary\n",
+ "In this notebook, we searched for and accessed two different CMIP6 datasets hosted through ESGF, calculated the ENSO 3.4 indices for the datasets, 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)"
+ ]
+ },
+ {
+ "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.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/example-workflows/enso-globus.ipynb b/notebooks/example-workflows/enso-globus.ipynb
new file mode 100644
index 0000000..10ea8a2
--- /dev/null
+++ b/notebooks/example-workflows/enso-globus.ipynb
@@ -0,0 +1,1620 @@
+{
+ "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 Compute"
+ ]
+ },
+ {
+ "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": 131,
+ "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.1.1'.replace('rc', '-rc.');\n",
+ " var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n",
+ " var reloading = false;\n",
+ " var Bokeh = root.Bokeh;\n",
+ " var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n",
+ "\n",
+ " if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n",
+ " root._bokeh_timeout = Date.now() + 5000;\n",
+ " root._bokeh_failed_load = false;\n",
+ " }\n",
+ "\n",
+ " function run_callbacks() {\n",
+ " try {\n",
+ " root._bokeh_onload_callbacks.forEach(function(callback) {\n",
+ " if (callback != null)\n",
+ " callback();\n",
+ " });\n",
+ " } finally {\n",
+ " delete root._bokeh_onload_callbacks;\n",
+ " }\n",
+ " console.debug(\"Bokeh: all callbacks have finished\");\n",
+ " }\n",
+ "\n",
+ " function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n",
+ " if (css_urls == null) css_urls = [];\n",
+ " if (js_urls == null) js_urls = [];\n",
+ " if (js_modules == null) js_modules = [];\n",
+ " if (js_exports == null) js_exports = {};\n",
+ "\n",
+ " root._bokeh_onload_callbacks.push(callback);\n",
+ "\n",
+ " if (root._bokeh_is_loading > 0) {\n",
+ " console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n",
+ " return null;\n",
+ " }\n",
+ " if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n",
+ " run_callbacks();\n",
+ " return null;\n",
+ " }\n",
+ " if (!reloading) {\n",
+ " console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n",
+ " }\n",
+ "\n",
+ " function on_load() {\n",
+ " root._bokeh_is_loading--;\n",
+ " if (root._bokeh_is_loading === 0) {\n",
+ " console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n",
+ " run_callbacks()\n",
+ " }\n",
+ " }\n",
+ " window._bokeh_on_load = on_load\n",
+ "\n",
+ " function on_error() {\n",
+ " console.error(\"failed to load \" + url);\n",
+ " }\n",
+ "\n",
+ " var skip = [];\n",
+ " if (window.requirejs) {\n",
+ " window.requirejs.config({'packages': {}, 'paths': {'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n",
+ " require([\"jspanel\"], function(jsPanel) {\n",
+ "\twindow.jsPanel = jsPanel\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-modal\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-tooltip\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-hint\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-layout\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-contextmenu\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"jspanel-dock\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"gridstack\"], function(GridStack) {\n",
+ "\twindow.GridStack = GridStack\n",
+ "\ton_load()\n",
+ " })\n",
+ " require([\"notyf\"], function() {\n",
+ "\ton_load()\n",
+ " })\n",
+ " root._bokeh_is_loading = css_urls.length + 9;\n",
+ " } else {\n",
+ " root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n",
+ " }\n",
+ "\n",
+ " var existing_stylesheets = []\n",
+ " var links = document.getElementsByTagName('link')\n",
+ " for (var i = 0; i < links.length; i++) {\n",
+ " var link = links[i]\n",
+ " if (link.href != null) {\n",
+ "\texisting_stylesheets.push(link.href)\n",
+ " }\n",
+ " }\n",
+ " for (var i = 0; i < css_urls.length; i++) {\n",
+ " var url = css_urls[i];\n",
+ " if (existing_stylesheets.indexOf(url) !== -1) {\n",
+ "\ton_load()\n",
+ "\tcontinue;\n",
+ " }\n",
+ " const element = document.createElement(\"link\");\n",
+ " element.onload = on_load;\n",
+ " element.onerror = on_error;\n",
+ " element.rel = \"stylesheet\";\n",
+ " element.type = \"text/css\";\n",
+ " element.href = url;\n",
+ " console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n",
+ " document.body.appendChild(element);\n",
+ " } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n",
+ " var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n",
+ " for (var i = 0; i < urls.length; i++) {\n",
+ " skip.push(urls[i])\n",
+ " }\n",
+ " } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n",
+ " var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n",
+ " for (var i = 0; i < urls.length; i++) {\n",
+ " skip.push(urls[i])\n",
+ " }\n",
+ " } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n",
+ " var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n",
+ " for (var i = 0; i < urls.length; i++) {\n",
+ " skip.push(urls[i])\n",
+ " }\n",
+ " } var existing_scripts = []\n",
+ " var scripts = document.getElementsByTagName('script')\n",
+ " for (var i = 0; i < scripts.length; i++) {\n",
+ " var script = scripts[i]\n",
+ " if (script.src != null) {\n",
+ "\texisting_scripts.push(script.src)\n",
+ " }\n",
+ " }\n",
+ " for (var i = 0; i < js_urls.length; i++) {\n",
+ " var url = js_urls[i];\n",
+ " if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n",
+ "\tif (!window.requirejs) {\n",
+ "\t on_load();\n",
+ "\t}\n",
+ "\tcontinue;\n",
+ " }\n",
+ " var element = document.createElement('script');\n",
+ " element.onload = on_load;\n",
+ " element.onerror = on_error;\n",
+ " element.async = false;\n",
+ " element.src = url;\n",
+ " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
+ " document.head.appendChild(element);\n",
+ " }\n",
+ " for (var i = 0; i < js_modules.length; i++) {\n",
+ " var url = js_modules[i];\n",
+ " if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n",
+ "\tif (!window.requirejs) {\n",
+ "\t on_load();\n",
+ "\t}\n",
+ "\tcontinue;\n",
+ " }\n",
+ " var element = document.createElement('script');\n",
+ " element.onload = on_load;\n",
+ " element.onerror = on_error;\n",
+ " element.async = false;\n",
+ " element.src = url;\n",
+ " element.type = \"module\";\n",
+ " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
+ " document.head.appendChild(element);\n",
+ " }\n",
+ " for (const name in js_exports) {\n",
+ " var url = js_exports[name];\n",
+ " if (skip.indexOf(url) >= 0 || root[name] != null) {\n",
+ "\tif (!window.requirejs) {\n",
+ "\t on_load();\n",
+ "\t}\n",
+ "\tcontinue;\n",
+ " }\n",
+ " var element = document.createElement('script');\n",
+ " element.onerror = on_error;\n",
+ " element.async = false;\n",
+ " element.type = \"module\";\n",
+ " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n",
+ " element.textContent = `\n",
+ " import ${name} from \"${url}\"\n",
+ " window.${name} = ${name}\n",
+ " window._bokeh_on_load()\n",
+ " `\n",
+ " document.head.appendChild(element);\n",
+ " }\n",
+ " if (!js_urls.length && !js_modules.length) {\n",
+ " on_load()\n",
+ " }\n",
+ " };\n",
+ "\n",
+ " function inject_raw_css(css) {\n",
+ " const element = document.createElement(\"style\");\n",
+ " element.appendChild(document.createTextNode(css));\n",
+ " document.body.appendChild(element);\n",
+ " }\n",
+ "\n",
+ " var js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js\", \"https://cdn.holoviz.org/panel/1.1.0/dist/panel.min.js\"];\n",
+ " var js_modules = [];\n",
+ " var js_exports = {};\n",
+ " var css_urls = [];\n",
+ " var inline_js = [ function(Bokeh) {\n",
+ " Bokeh.set_log_level(\"info\");\n",
+ " },\n",
+ "function(Bokeh) {} // ensure no trailing comma for IE\n",
+ " ];\n",
+ "\n",
+ " function run_inline_js() {\n",
+ " if ((root.Bokeh !== undefined) || (force === true)) {\n",
+ " for (var i = 0; i < inline_js.length; i++) {\n",
+ " inline_js[i].call(root, root.Bokeh);\n",
+ " }\n",
+ " // Cache old bokeh versions\n",
+ " if (Bokeh != undefined && !reloading) {\n",
+ "\tvar NewBokeh = root.Bokeh;\n",
+ "\tif (Bokeh.versions === undefined) {\n",
+ "\t Bokeh.versions = new Map();\n",
+ "\t}\n",
+ "\tif (NewBokeh.version !== Bokeh.version) {\n",
+ "\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n",
+ "\t}\n",
+ "\troot.Bokeh = Bokeh;\n",
+ " }} else if (Date.now() < root._bokeh_timeout) {\n",
+ " setTimeout(run_inline_js, 100);\n",
+ " } else if (!root._bokeh_failed_load) {\n",
+ " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n",
+ " root._bokeh_failed_load = true;\n",
+ " }\n",
+ " root._bokeh_is_initializing = false\n",
+ " }\n",
+ "\n",
+ " function load_or_wait() {\n",
+ " // Implement a backoff loop that tries to ensure we do not load multiple\n",
+ " // versions of Bokeh and its dependencies at the same time.\n",
+ " // In recent versions we use the root._bokeh_is_initializing flag\n",
+ " // to determine whether there is an ongoing attempt to initialize\n",
+ " // bokeh, however for backward compatibility we also try to ensure\n",
+ " // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n",
+ " // before older versions are fully initialized.\n",
+ " if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n",
+ " root._bokeh_is_initializing = false;\n",
+ " root._bokeh_onload_callbacks = undefined;\n",
+ " console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n",
+ " load_or_wait();\n",
+ " } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n",
+ " setTimeout(load_or_wait, 100);\n",
+ " } else {\n",
+ " Bokeh = root.Bokeh;\n",
+ " bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n",
+ " root._bokeh_is_initializing = true\n",
+ " root._bokeh_onload_callbacks = []\n",
+ " if (!reloading && (!bokeh_loaded || is_dev)) {\n",
+ "\troot.Bokeh = undefined;\n",
+ " }\n",
+ " load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n",
+ "\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n",
+ "\trun_inline_js();\n",
+ " });\n",
+ " }\n",
+ " }\n",
+ " // Give older versions of the autoload script a head-start to ensure\n",
+ " // they initialize before we start loading newer version.\n",
+ " setTimeout(load_or_wait, 100)\n",
+ "}(window));"
+ ],
+ "application/vnd.holoviews_load.v0+json": "(function(root) {\n function now() {\n return new Date();\n }\n\n var force = true;\n var py_version = '3.1.1'.replace('rc', '-rc.');\n var is_dev = py_version.indexOf(\"+\") !== -1 || py_version.indexOf(\"-\") !== -1;\n var reloading = false;\n var Bokeh = root.Bokeh;\n var bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks;\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, js_modules, js_exports, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n if (js_modules == null) js_modules = [];\n if (js_exports == null) js_exports = {};\n\n root._bokeh_onload_callbacks.push(callback);\n\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls.length === 0 && js_modules.length === 0 && Object.keys(js_exports).length === 0) {\n run_callbacks();\n return null;\n }\n if (!reloading) {\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n }\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n window._bokeh_on_load = on_load\n\n function on_error() {\n console.error(\"failed to load \" + url);\n }\n\n var skip = [];\n if (window.requirejs) {\n window.requirejs.config({'packages': {}, 'paths': {'jspanel': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/jspanel', 'jspanel-modal': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal', 'jspanel-tooltip': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip', 'jspanel-hint': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint', 'jspanel-layout': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout', 'jspanel-contextmenu': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu', 'jspanel-dock': 'https://cdn.jsdelivr.net/npm/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock', 'gridstack': 'https://cdn.jsdelivr.net/npm/gridstack@7.2.3/dist/gridstack-all', 'notyf': 'https://cdn.jsdelivr.net/npm/notyf@3/notyf.min'}, 'shim': {'jspanel': {'exports': 'jsPanel'}, 'gridstack': {'exports': 'GridStack'}}});\n require([\"jspanel\"], function(jsPanel) {\n\twindow.jsPanel = jsPanel\n\ton_load()\n })\n require([\"jspanel-modal\"], function() {\n\ton_load()\n })\n require([\"jspanel-tooltip\"], function() {\n\ton_load()\n })\n require([\"jspanel-hint\"], function() {\n\ton_load()\n })\n require([\"jspanel-layout\"], function() {\n\ton_load()\n })\n require([\"jspanel-contextmenu\"], function() {\n\ton_load()\n })\n require([\"jspanel-dock\"], function() {\n\ton_load()\n })\n require([\"gridstack\"], function(GridStack) {\n\twindow.GridStack = GridStack\n\ton_load()\n })\n require([\"notyf\"], function() {\n\ton_load()\n })\n root._bokeh_is_loading = css_urls.length + 9;\n } else {\n root._bokeh_is_loading = css_urls.length + js_urls.length + js_modules.length + Object.keys(js_exports).length;\n }\n\n var existing_stylesheets = []\n var links = document.getElementsByTagName('link')\n for (var i = 0; i < links.length; i++) {\n var link = links[i]\n if (link.href != null) {\n\texisting_stylesheets.push(link.href)\n }\n }\n for (var i = 0; i < css_urls.length; i++) {\n var url = css_urls[i];\n if (existing_stylesheets.indexOf(url) !== -1) {\n\ton_load()\n\tcontinue;\n }\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error;\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n } if (((window['jsPanel'] !== undefined) && (!(window['jsPanel'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/jspanel.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/modal/jspanel.modal.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/tooltip/jspanel.tooltip.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/hint/jspanel.hint.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/layout/jspanel.layout.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/contextmenu/jspanel.contextmenu.js', 'https://cdn.holoviz.org/panel/1.1.0/dist/bundled/floatpanel/jspanel4@4.12.0/dist/extensions/dock/jspanel.dock.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['GridStack'] !== undefined) && (!(window['GridStack'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/gridstack/gridstack@7.2.3/dist/gridstack-all.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } if (((window['Notyf'] !== undefined) && (!(window['Notyf'] instanceof HTMLElement))) || window.requirejs) {\n var urls = ['https://cdn.holoviz.org/panel/1.1.0/dist/bundled/notificationarea/notyf@3/notyf.min.js'];\n for (var i = 0; i < urls.length; i++) {\n skip.push(urls[i])\n }\n } var existing_scripts = []\n var scripts = document.getElementsByTagName('script')\n for (var i = 0; i < scripts.length; i++) {\n var script = scripts[i]\n if (script.src != null) {\n\texisting_scripts.push(script.src)\n }\n }\n for (var i = 0; i < js_urls.length; i++) {\n var url = js_urls[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (var i = 0; i < js_modules.length; i++) {\n var url = js_modules[i];\n if (skip.indexOf(url) !== -1 || existing_scripts.indexOf(url) !== -1) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error;\n element.async = false;\n element.src = url;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n for (const name in js_exports) {\n var url = js_exports[name];\n if (skip.indexOf(url) >= 0 || root[name] != null) {\n\tif (!window.requirejs) {\n\t on_load();\n\t}\n\tcontinue;\n }\n var element = document.createElement('script');\n element.onerror = on_error;\n element.async = false;\n element.type = \"module\";\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n element.textContent = `\n import ${name} from \"${url}\"\n window.${name} = ${name}\n window._bokeh_on_load()\n `\n document.head.appendChild(element);\n }\n if (!js_urls.length && !js_modules.length) {\n on_load()\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n var js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.1.1.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.1.1.min.js\", \"https://cdn.holoviz.org/panel/1.1.0/dist/panel.min.js\"];\n var js_modules = [];\n var js_exports = {};\n var css_urls = [];\n var inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {} // ensure no trailing comma for IE\n ];\n\n function run_inline_js() {\n if ((root.Bokeh !== undefined) || (force === true)) {\n for (var i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\n // Cache old bokeh versions\n if (Bokeh != undefined && !reloading) {\n\tvar NewBokeh = root.Bokeh;\n\tif (Bokeh.versions === undefined) {\n\t Bokeh.versions = new Map();\n\t}\n\tif (NewBokeh.version !== Bokeh.version) {\n\t Bokeh.versions.set(NewBokeh.version, NewBokeh)\n\t}\n\troot.Bokeh = Bokeh;\n }} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n }\n root._bokeh_is_initializing = false\n }\n\n function load_or_wait() {\n // Implement a backoff loop that tries to ensure we do not load multiple\n // versions of Bokeh and its dependencies at the same time.\n // In recent versions we use the root._bokeh_is_initializing flag\n // to determine whether there is an ongoing attempt to initialize\n // bokeh, however for backward compatibility we also try to ensure\n // that we do not start loading a newer (Panel>=1.0 and Bokeh>3) version\n // before older versions are fully initialized.\n if (root._bokeh_is_initializing && Date.now() > root._bokeh_timeout) {\n root._bokeh_is_initializing = false;\n root._bokeh_onload_callbacks = undefined;\n console.log(\"Bokeh: BokehJS was loaded multiple times but one version failed to initialize.\");\n load_or_wait();\n } else if (root._bokeh_is_initializing || (typeof root._bokeh_is_initializing === \"undefined\" && root._bokeh_onload_callbacks !== undefined)) {\n setTimeout(load_or_wait, 100);\n } else {\n Bokeh = root.Bokeh;\n bokeh_loaded = Bokeh != null && (Bokeh.version === py_version || (Bokeh.versions !== undefined && Bokeh.versions.has(py_version)));\n root._bokeh_is_initializing = true\n root._bokeh_onload_callbacks = []\n if (!reloading && (!bokeh_loaded || is_dev)) {\n\troot.Bokeh = undefined;\n }\n load_libs(css_urls, js_urls, js_modules, js_exports, function() {\n\tconsole.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n\trun_inline_js();\n });\n }\n }\n // Give older versions of the autoload script a head-start to ensure\n // they initialize before we start loading newer version.\n setTimeout(load_or_wait, 100)\n}(window));"
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/javascript": [
+ "\n",
+ "if ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n",
+ " window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n",
+ "}\n",
+ "\n",
+ "\n",
+ " function JupyterCommManager() {\n",
+ " }\n",
+ "\n",
+ " JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n",
+ " if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n",
+ " var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n",
+ " comm_manager.register_target(comm_id, function(comm) {\n",
+ " comm.on_msg(msg_handler);\n",
+ " });\n",
+ " } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n",
+ " window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n",
+ " comm.onMsg = msg_handler;\n",
+ " });\n",
+ " } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n",
+ " google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n",
+ " var messages = comm.messages[Symbol.asyncIterator]();\n",
+ " function processIteratorResult(result) {\n",
+ " var message = result.value;\n",
+ " console.log(message)\n",
+ " var content = {data: message.data, comm_id};\n",
+ " var buffers = []\n",
+ " for (var buffer of message.buffers || []) {\n",
+ " buffers.push(new DataView(buffer))\n",
+ " }\n",
+ " var metadata = message.metadata || {};\n",
+ " var msg = {content, buffers, metadata}\n",
+ " msg_handler(msg);\n",
+ " return messages.next().then(processIteratorResult);\n",
+ " }\n",
+ " return messages.next().then(processIteratorResult);\n",
+ " })\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n",
+ " if (comm_id in window.PyViz.comms) {\n",
+ " return window.PyViz.comms[comm_id];\n",
+ " } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n",
+ " var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n",
+ " var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n",
+ " if (msg_handler) {\n",
+ " comm.on_msg(msg_handler);\n",
+ " }\n",
+ " } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n",
+ " var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n",
+ " comm.open();\n",
+ " if (msg_handler) {\n",
+ " comm.onMsg = msg_handler;\n",
+ " }\n",
+ " } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n",
+ " var comm_promise = google.colab.kernel.comms.open(comm_id)\n",
+ " comm_promise.then((comm) => {\n",
+ " window.PyViz.comms[comm_id] = comm;\n",
+ " if (msg_handler) {\n",
+ " var messages = comm.messages[Symbol.asyncIterator]();\n",
+ " function processIteratorResult(result) {\n",
+ " var message = result.value;\n",
+ " var content = {data: message.data};\n",
+ " var metadata = message.metadata || {comm_id};\n",
+ " var msg = {content, metadata}\n",
+ " msg_handler(msg);\n",
+ " return messages.next().then(processIteratorResult);\n",
+ " }\n",
+ " return messages.next().then(processIteratorResult);\n",
+ " }\n",
+ " }) \n",
+ " var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n",
+ " return comm_promise.then((comm) => {\n",
+ " comm.send(data, metadata, buffers, disposeOnDone);\n",
+ " });\n",
+ " };\n",
+ " var comm = {\n",
+ " send: sendClosure\n",
+ " };\n",
+ " }\n",
+ " window.PyViz.comms[comm_id] = comm;\n",
+ " return comm;\n",
+ " }\n",
+ " window.PyViz.comm_manager = new JupyterCommManager();\n",
+ " \n",
+ "\n",
+ "\n",
+ "var JS_MIME_TYPE = 'application/javascript';\n",
+ "var HTML_MIME_TYPE = 'text/html';\n",
+ "var EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\n",
+ "var CLASS_NAME = 'output';\n",
+ "\n",
+ "/**\n",
+ " * Render data to the DOM node\n",
+ " */\n",
+ "function render(props, node) {\n",
+ " var div = document.createElement(\"div\");\n",
+ " var script = document.createElement(\"script\");\n",
+ " node.appendChild(div);\n",
+ " node.appendChild(script);\n",
+ "}\n",
+ "\n",
+ "/**\n",
+ " * Handle when a new output is added\n",
+ " */\n",
+ "function handle_add_output(event, handle) {\n",
+ " var output_area = handle.output_area;\n",
+ " var output = handle.output;\n",
+ " if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n",
+ " return\n",
+ " }\n",
+ " var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n",
+ " var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n",
+ " if (id !== undefined) {\n",
+ " var nchildren = toinsert.length;\n",
+ " var html_node = toinsert[nchildren-1].children[0];\n",
+ " html_node.innerHTML = output.data[HTML_MIME_TYPE];\n",
+ " var scripts = [];\n",
+ " var nodelist = html_node.querySelectorAll(\"script\");\n",
+ " for (var i in nodelist) {\n",
+ " if (nodelist.hasOwnProperty(i)) {\n",
+ " scripts.push(nodelist[i])\n",
+ " }\n",
+ " }\n",
+ "\n",
+ " scripts.forEach( function (oldScript) {\n",
+ " var newScript = document.createElement(\"script\");\n",
+ " var attrs = [];\n",
+ " var nodemap = oldScript.attributes;\n",
+ " for (var j in nodemap) {\n",
+ " if (nodemap.hasOwnProperty(j)) {\n",
+ " attrs.push(nodemap[j])\n",
+ " }\n",
+ " }\n",
+ " attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n",
+ " newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n",
+ " oldScript.parentNode.replaceChild(newScript, oldScript);\n",
+ " });\n",
+ " if (JS_MIME_TYPE in output.data) {\n",
+ " toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n",
+ " }\n",
+ " output_area._hv_plot_id = id;\n",
+ " if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n",
+ " window.PyViz.plot_index[id] = Bokeh.index[id];\n",
+ " } else {\n",
+ " window.PyViz.plot_index[id] = null;\n",
+ " }\n",
+ " } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n",
+ " var bk_div = document.createElement(\"div\");\n",
+ " bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n",
+ " var script_attrs = bk_div.children[0].attributes;\n",
+ " for (var i = 0; i < script_attrs.length; i++) {\n",
+ " toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n",
+ " }\n",
+ " // store reference to server id on output_area\n",
+ " output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "/**\n",
+ " * Handle when an output is cleared or removed\n",
+ " */\n",
+ "function handle_clear_output(event, handle) {\n",
+ " var id = handle.cell.output_area._hv_plot_id;\n",
+ " var server_id = handle.cell.output_area._bokeh_server_id;\n",
+ " if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n",
+ " var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n",
+ " if (server_id !== null) {\n",
+ " comm.send({event_type: 'server_delete', 'id': server_id});\n",
+ " return;\n",
+ " } else if (comm !== null) {\n",
+ " comm.send({event_type: 'delete', 'id': id});\n",
+ " }\n",
+ " delete PyViz.plot_index[id];\n",
+ " if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n",
+ " var doc = window.Bokeh.index[id].model.document\n",
+ " doc.clear();\n",
+ " const i = window.Bokeh.documents.indexOf(doc);\n",
+ " if (i > -1) {\n",
+ " window.Bokeh.documents.splice(i, 1);\n",
+ " }\n",
+ " }\n",
+ "}\n",
+ "\n",
+ "/**\n",
+ " * Handle kernel restart event\n",
+ " */\n",
+ "function handle_kernel_cleanup(event, handle) {\n",
+ " delete PyViz.comms[\"hv-extension-comm\"];\n",
+ " window.PyViz.plot_index = {}\n",
+ "}\n",
+ "\n",
+ "/**\n",
+ " * Handle update_display_data messages\n",
+ " */\n",
+ "function handle_update_output(event, handle) {\n",
+ " handle_clear_output(event, {cell: {output_area: handle.output_area}})\n",
+ " handle_add_output(event, handle)\n",
+ "}\n",
+ "\n",
+ "function register_renderer(events, OutputArea) {\n",
+ " function append_mime(data, metadata, element) {\n",
+ " // create a DOM node to render to\n",
+ " var toinsert = this.create_output_subarea(\n",
+ " metadata,\n",
+ " CLASS_NAME,\n",
+ " EXEC_MIME_TYPE\n",
+ " );\n",
+ " this.keyboard_manager.register_events(toinsert);\n",
+ " // Render to node\n",
+ " var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n",
+ " render(props, toinsert[0]);\n",
+ " element.append(toinsert);\n",
+ " return toinsert\n",
+ " }\n",
+ "\n",
+ " events.on('output_added.OutputArea', handle_add_output);\n",
+ " events.on('output_updated.OutputArea', handle_update_output);\n",
+ " events.on('clear_output.CodeCell', handle_clear_output);\n",
+ " events.on('delete.Cell', handle_clear_output);\n",
+ " events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n",
+ "\n",
+ " OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n",
+ " safe: true,\n",
+ " index: 0\n",
+ " });\n",
+ "}\n",
+ "\n",
+ "if (window.Jupyter !== undefined) {\n",
+ " try {\n",
+ " var events = require('base/js/events');\n",
+ " var OutputArea = require('notebook/js/outputarea').OutputArea;\n",
+ " if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n",
+ " register_renderer(events, OutputArea);\n",
+ " }\n",
+ " } catch(err) {\n",
+ " }\n",
+ "}\n"
+ ],
+ "application/vnd.holoviews_load.v0+json": "\nif ((window.PyViz === undefined) || (window.PyViz instanceof HTMLElement)) {\n window.PyViz = {comms: {}, comm_status:{}, kernels:{}, receivers: {}, plot_index: []}\n}\n\n\n function JupyterCommManager() {\n }\n\n JupyterCommManager.prototype.register_target = function(plot_id, comm_id, msg_handler) {\n if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n comm_manager.register_target(comm_id, function(comm) {\n comm.on_msg(msg_handler);\n });\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n window.PyViz.kernels[plot_id].registerCommTarget(comm_id, function(comm) {\n comm.onMsg = msg_handler;\n });\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n google.colab.kernel.comms.registerTarget(comm_id, (comm) => {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n console.log(message)\n var content = {data: message.data, comm_id};\n var buffers = []\n for (var buffer of message.buffers || []) {\n buffers.push(new DataView(buffer))\n }\n var metadata = message.metadata || {};\n var msg = {content, buffers, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n })\n }\n }\n\n JupyterCommManager.prototype.get_client_comm = function(plot_id, comm_id, msg_handler) {\n if (comm_id in window.PyViz.comms) {\n return window.PyViz.comms[comm_id];\n } else if (window.comm_manager || ((window.Jupyter !== undefined) && (Jupyter.notebook.kernel != null))) {\n var comm_manager = window.comm_manager || Jupyter.notebook.kernel.comm_manager;\n var comm = comm_manager.new_comm(comm_id, {}, {}, {}, comm_id);\n if (msg_handler) {\n comm.on_msg(msg_handler);\n }\n } else if ((plot_id in window.PyViz.kernels) && (window.PyViz.kernels[plot_id])) {\n var comm = window.PyViz.kernels[plot_id].connectToComm(comm_id);\n comm.open();\n if (msg_handler) {\n comm.onMsg = msg_handler;\n }\n } else if (typeof google != 'undefined' && google.colab.kernel != null) {\n var comm_promise = google.colab.kernel.comms.open(comm_id)\n comm_promise.then((comm) => {\n window.PyViz.comms[comm_id] = comm;\n if (msg_handler) {\n var messages = comm.messages[Symbol.asyncIterator]();\n function processIteratorResult(result) {\n var message = result.value;\n var content = {data: message.data};\n var metadata = message.metadata || {comm_id};\n var msg = {content, metadata}\n msg_handler(msg);\n return messages.next().then(processIteratorResult);\n }\n return messages.next().then(processIteratorResult);\n }\n }) \n var sendClosure = (data, metadata, buffers, disposeOnDone) => {\n return comm_promise.then((comm) => {\n comm.send(data, metadata, buffers, disposeOnDone);\n });\n };\n var comm = {\n send: sendClosure\n };\n }\n window.PyViz.comms[comm_id] = comm;\n return comm;\n }\n window.PyViz.comm_manager = new JupyterCommManager();\n \n\n\nvar JS_MIME_TYPE = 'application/javascript';\nvar HTML_MIME_TYPE = 'text/html';\nvar EXEC_MIME_TYPE = 'application/vnd.holoviews_exec.v0+json';\nvar CLASS_NAME = 'output';\n\n/**\n * Render data to the DOM node\n */\nfunction render(props, node) {\n var div = document.createElement(\"div\");\n var script = document.createElement(\"script\");\n node.appendChild(div);\n node.appendChild(script);\n}\n\n/**\n * Handle when a new output is added\n */\nfunction handle_add_output(event, handle) {\n var output_area = handle.output_area;\n var output = handle.output;\n if ((output.data == undefined) || (!output.data.hasOwnProperty(EXEC_MIME_TYPE))) {\n return\n }\n var id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n var toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n if (id !== undefined) {\n var nchildren = toinsert.length;\n var html_node = toinsert[nchildren-1].children[0];\n html_node.innerHTML = output.data[HTML_MIME_TYPE];\n var scripts = [];\n var nodelist = html_node.querySelectorAll(\"script\");\n for (var i in nodelist) {\n if (nodelist.hasOwnProperty(i)) {\n scripts.push(nodelist[i])\n }\n }\n\n scripts.forEach( function (oldScript) {\n var newScript = document.createElement(\"script\");\n var attrs = [];\n var nodemap = oldScript.attributes;\n for (var j in nodemap) {\n if (nodemap.hasOwnProperty(j)) {\n attrs.push(nodemap[j])\n }\n }\n attrs.forEach(function(attr) { newScript.setAttribute(attr.name, attr.value) });\n newScript.appendChild(document.createTextNode(oldScript.innerHTML));\n oldScript.parentNode.replaceChild(newScript, oldScript);\n });\n if (JS_MIME_TYPE in output.data) {\n toinsert[nchildren-1].children[1].textContent = output.data[JS_MIME_TYPE];\n }\n output_area._hv_plot_id = id;\n if ((window.Bokeh !== undefined) && (id in Bokeh.index)) {\n window.PyViz.plot_index[id] = Bokeh.index[id];\n } else {\n window.PyViz.plot_index[id] = null;\n }\n } else if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n var bk_div = document.createElement(\"div\");\n bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n var script_attrs = bk_div.children[0].attributes;\n for (var i = 0; i < script_attrs.length; i++) {\n toinsert[toinsert.length - 1].childNodes[1].setAttribute(script_attrs[i].name, script_attrs[i].value);\n }\n // store reference to server id on output_area\n output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n }\n}\n\n/**\n * Handle when an output is cleared or removed\n */\nfunction handle_clear_output(event, handle) {\n var id = handle.cell.output_area._hv_plot_id;\n var server_id = handle.cell.output_area._bokeh_server_id;\n if (((id === undefined) || !(id in PyViz.plot_index)) && (server_id !== undefined)) { return; }\n var comm = window.PyViz.comm_manager.get_client_comm(\"hv-extension-comm\", \"hv-extension-comm\", function () {});\n if (server_id !== null) {\n comm.send({event_type: 'server_delete', 'id': server_id});\n return;\n } else if (comm !== null) {\n comm.send({event_type: 'delete', 'id': id});\n }\n delete PyViz.plot_index[id];\n if ((window.Bokeh !== undefined) & (id in window.Bokeh.index)) {\n var doc = window.Bokeh.index[id].model.document\n doc.clear();\n const i = window.Bokeh.documents.indexOf(doc);\n if (i > -1) {\n window.Bokeh.documents.splice(i, 1);\n }\n }\n}\n\n/**\n * Handle kernel restart event\n */\nfunction handle_kernel_cleanup(event, handle) {\n delete PyViz.comms[\"hv-extension-comm\"];\n window.PyViz.plot_index = {}\n}\n\n/**\n * Handle update_display_data messages\n */\nfunction handle_update_output(event, handle) {\n handle_clear_output(event, {cell: {output_area: handle.output_area}})\n handle_add_output(event, handle)\n}\n\nfunction register_renderer(events, OutputArea) {\n function append_mime(data, metadata, element) {\n // create a DOM node to render to\n var toinsert = this.create_output_subarea(\n metadata,\n CLASS_NAME,\n EXEC_MIME_TYPE\n );\n this.keyboard_manager.register_events(toinsert);\n // Render to node\n var props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n render(props, toinsert[0]);\n element.append(toinsert);\n return toinsert\n }\n\n events.on('output_added.OutputArea', handle_add_output);\n events.on('output_updated.OutputArea', handle_update_output);\n events.on('clear_output.CodeCell', handle_clear_output);\n events.on('delete.Cell', handle_clear_output);\n events.on('kernel_ready.Kernel', handle_kernel_cleanup);\n\n OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n safe: true,\n index: 0\n });\n}\n\nif (window.Jupyter !== undefined) {\n try {\n var events = require('base/js/events');\n var OutputArea = require('notebook/js/outputarea').OutputArea;\n if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n register_renderer(events, OutputArea);\n }\n } catch(err) {\n }\n}\n"
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ "\n",
+ "\n",
+ "\n",
+ "\n",
+ "
\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import 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 os\n",
+ "from globus_compute_sdk import Executor, Client\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": 121,
+ "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",
+ " # Search and load the ocean grid cell area\n",
+ " cat = ESGFCatalog()\n",
+ " cat.search(\n",
+ " activity_id=\"CMIP\",\n",
+ " experiment_id=\"historical\",\n",
+ " institution_id=institution_id,\n",
+ " variable_id=[\"areacello\"],\n",
+ " member_id='r11i1p1f1',\n",
+ " )\n",
+ " try:\n",
+ " area_ds = cat.to_datatree()[grid].to_dataset()\n",
+ " except ValueError:\n",
+ " area_ds = cat.to_dataset_dict()[\"\"]\n",
+ " return xr.merge([tos_ds, area_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": 133,
+ "id": "fe8d9e8b-e38d-41a5-b5f6-df9916d69f83",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "endpoint_id = \"b3d1d669-d49b-412e-af81-95f3368e525c\""
+ ]
+ },
+ {
+ "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": 135,
+ "id": "0aa43e9e-6840-4b46-9a0c-ceeef8ca7e1e",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Executor"
+ ]
+ },
+ "execution_count": 135,
+ "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": 136,
+ "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": 137,
+ "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": 137,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "ncar_ds = ncar_task.result()\n",
+ "miroc_ds = miroc_task.result()\n",
+ "\n",
+ "ncar_ds"
+ ]
+ },
+ {
+ "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": 138,
+ "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": 139,
+ "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": 139,
+ "metadata": {
+ "application/vnd.holoviews_exec.v0+json": {
+ "id": "59645fce-ca6a-4432-aa0e-77521837d618"
+ }
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(plot_enso(ncar_ds) + plot_enso(miroc_ds)).cols(1)"
+ ]
+ },
+ {
+ "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.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/images/globus-logo.png b/notebooks/images/globus-logo.png
new file mode 100644
index 0000000..2bdb508
Binary files /dev/null and b/notebooks/images/globus-logo.png differ