From a52e0c0f89c4403e33ba5325e81f76d9c289170f Mon Sep 17 00:00:00 2001
From: Adam Keester <72414466+akeeste@users.noreply.github.com>
Date: Thu, 8 Aug 2024 07:56:17 -0500
Subject: [PATCH] Speed up Wind Toolkit tests (#338)
This PR addresses #315 by:
- Changes the data called by the Wind Toolkit tests so that they run faster
- Updates the .csv files that the tests compare against
- Updates a few descriptions in the metocean example, fixes a sorting issue, reduces the data downloaded there
- Tests in hindcast match the notebooks and use the same cache.
---
.github/workflows/generate_notebook_matrix.py | 18 +-
.github/workflows/main.yml | 34 +
examples/WPTO_hindcast_example.ipynb | 1035 +-
examples/data/wave/hindcast/dir_spectra.nc | Bin 0 -> 42399 bytes
.../data/wave/hindcast/dir_spectra_meta.csv | 2 +
.../wave/hindcast/multi_year_dir_meta.csv | 3 -
.../wave/hindcast/multi_year_dir_spectra.nc | Bin 567207 -> 0 bytes
.../hindcast/multi_year_dir_spectra_meta.csv | 3 -
.../wave/hindcast/multi_year_hindcast.csv | 11698 +++---
.../hindcast/multi_year_hindcast_meta.csv | 2 +
.../data/wave/hindcast/multi_year_meta.csv | 2 -
examples/data/wave/hindcast/multiparm.csv | 8773 ----
.../wave/hindcast/single_year_hindcast.csv | 2921 ++
.../single_year_hindcast_multiloc.csv | 5842 +--
...=> single_year_hindcast_multiloc_meta.csv} | 0
.../single_year_hindcast_multiparam.csv | 8749 ++++
... single_year_hindcast_multiparam_meta.csv} | 0
.../data/wave/hindcast/single_year_meta.csv | 2 +
.../data/wave/wind_toolkit/wtk_multiloc.csv | 17520 ++++----
.../wave/wind_toolkit/wtk_multiloc_meta.csv | 6 +-
.../data/wave/wind_toolkit/wtk_multiparm.csv | 17544 ++++----
.../wave/wind_toolkit/wtk_multiparm_meta.csv | 4 +-
.../data/wave/wind_toolkit/wtk_multiyear.csv | 35042 ++++++++--------
.../wave/wind_toolkit/wtk_multiyear_meta.csv | 4 +-
examples/metocean_example.ipynb | 171 +-
mhkit/tests/wave/io/hindcast/test_hindcast.py | 205 +-
.../wave/io/hindcast/test_wind_toolkit.py | 88 +-
mhkit/wave/io/hindcast/wind_toolkit.py | 2 +-
28 files changed, 55866 insertions(+), 53804 deletions(-)
create mode 100644 examples/data/wave/hindcast/dir_spectra.nc
create mode 100644 examples/data/wave/hindcast/dir_spectra_meta.csv
delete mode 100644 examples/data/wave/hindcast/multi_year_dir_meta.csv
delete mode 100644 examples/data/wave/hindcast/multi_year_dir_spectra.nc
delete mode 100644 examples/data/wave/hindcast/multi_year_dir_spectra_meta.csv
create mode 100644 examples/data/wave/hindcast/multi_year_hindcast_meta.csv
delete mode 100644 examples/data/wave/hindcast/multi_year_meta.csv
delete mode 100644 examples/data/wave/hindcast/multiparm.csv
create mode 100644 examples/data/wave/hindcast/single_year_hindcast.csv
rename examples/data/wave/hindcast/{multiloc_meta.csv => single_year_hindcast_multiloc_meta.csv} (100%)
create mode 100644 examples/data/wave/hindcast/single_year_hindcast_multiparam.csv
rename examples/data/wave/hindcast/{multiparm_meta.csv => single_year_hindcast_multiparam_meta.csv} (100%)
create mode 100644 examples/data/wave/hindcast/single_year_meta.csv
diff --git a/.github/workflows/generate_notebook_matrix.py b/.github/workflows/generate_notebook_matrix.py
index 39899ff77..03a6b2ab4 100644
--- a/.github/workflows/generate_notebook_matrix.py
+++ b/.github/workflows/generate_notebook_matrix.py
@@ -14,26 +14,26 @@
"cdip_example.ipynb": 180,
"Delft3D_example.ipynb": 180,
"directional_waves.ipynb": 180,
- "environmental_contours_example.ipynb": 720,
+ "environmental_contours_example.ipynb": 360,
"extreme_response_contour_example.ipynb": 360,
- "extreme_response_full_sea_state_example.ipynb": 420,
- "extreme_response_MLER_example.ipynb": 650,
+ "extreme_response_full_sea_state_example.ipynb": 360,
+ "extreme_response_MLER_example.ipynb": 360,
"loads_example.ipynb": 180,
- "metocean_example.ipynb": 240,
- "mooring_example.ipynb": 300,
+ "metocean_example.ipynb": 180,
+ "mooring_example.ipynb": 240,
"PacWave_resource_characterization_example.ipynb": 780,
"power_example.ipynb": 180,
"qc_example.ipynb": 180,
- "river_example.ipynb": 240,
+ "river_example.ipynb": 180,
"short_term_extremes_example.ipynb": 180,
"SWAN_example.ipynb": 180,
- "tidal_example.ipynb": 240,
+ "tidal_example.ipynb": 180,
"tidal_performance_example.ipynb": 180,
"upcrossing_example.ipynb": 180,
"wave_example.ipynb": 180,
"wecsim_example.ipynb": 180,
- "WPTO_hindcast_example.ipynb": 1200,
- "default": 300, # Default timeout for other notebooks
+ "WPTO_hindcast_example.ipynb": 180,
+ "default": 60, # Default timeout for other notebooks
}
notebooks = []
for root, dirs, files in os.walk("examples"):
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index d6bd0ce7f..9513dd72e 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -9,6 +9,7 @@ on:
branches:
- master
- develop
+
jobs:
set-os:
runs-on: ubuntu-latest
@@ -429,6 +430,39 @@ jobs:
echo "conda activate TESTconda" >> ~/.bashrc
source ~/.bashrc
+ - name: Download non-hindcast data
+ uses: actions/download-artifact@v4
+ with:
+ name: data
+ path: ~/.cache/mhkit
+
+ - name: Download Wave Hindcast data (if available)
+ if: (needs.check-changes.outputs.should-run-hindcast == 'true')
+ uses: actions/download-artifact@v4
+ with:
+ name: wave-hindcast-data
+ path: ~/.cache/mhkit/wave-hindcast
+
+ - name: Download Wind Hindcast data (if available)
+ if: (needs.check-changes.outputs.should-run-hindcast == 'true')
+ uses: actions/download-artifact@v4
+ with:
+ name: wind-hindcast-data
+ path: ~/.cache/mhkit/wind-hindcast
+
+ - name: Consolidate hindcast data
+ if: (needs.check-changes.outputs.should-run-hindcast == 'true')
+ run: |
+ mkdir -p ~/.cache/mhkit/hindcast
+ mv ~/.cache/mhkit/wave-hindcast/hindcast/* ~/.cache/mhkit/hindcast/
+ mv ~/.cache/mhkit/wind-hindcast/hindcast/* ~/.cache/mhkit/hindcast/
+ shell: bash
+
+ - name: Copy .hscfg file to examples directory
+ shell: bash -l {0}
+ run: |
+ cp .hscfg examples/
+
- name: Run notebook
shell: bash -l {0}
run: |
diff --git a/examples/WPTO_hindcast_example.ipynb b/examples/WPTO_hindcast_example.ipynb
index 1b6565797..cae359565 100644
--- a/examples/WPTO_hindcast_example.ipynb
+++ b/examples/WPTO_hindcast_example.ipynb
@@ -119,7 +119,16 @@
"cell_type": "code",
"execution_count": 3,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "hash params: 3-hour_significant_wave_height_(44.624076, -124.280097)_[1995]_None_True_True_True_None_True\n",
+ "Cache filename: 8e07b75503ee52b12adb762f4fe2cb41.pkl\n"
+ ]
+ }
+ ],
"source": [
"data_type = \"3-hour\" # setting the data type to the 3-hour dataset\n",
"years = [1995]\n",
@@ -171,6 +180,7 @@
"
distance_to_shore | \n",
" timezone | \n",
" jurisdiction | \n",
+ " gid | \n",
" \n",
" \n",
" \n",
@@ -182,6 +192,7 @@
" 15622.175781 | \n",
" -8 | \n",
" Federal | \n",
+ " 413889 | \n",
" \n",
" \n",
"\n",
@@ -191,8 +202,8 @@
" water_depth latitude longitude distance_to_shore timezone \\\n",
"0 77.429497 44.624298 -124.278999 15622.175781 -8 \n",
"\n",
- " jurisdiction \n",
- "0 Federal "
+ " jurisdiction gid \n",
+ "0 Federal 413889 "
]
},
"execution_count": 4,
@@ -304,6 +315,14 @@
"execution_count": 6,
"metadata": {},
"outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "hash params: 3-hour_energy_period_((44.624076, -124.280097), (43.489171, -125.152137))_[1995]_None_True_True_True_None_True\n",
+ "Cache filename: 8c9ed0cc513f181b6f839f5397f065e2.pkl\n"
+ ]
+ },
{
"data": {
"text/html": [
@@ -423,6 +442,7 @@
" distance_to_shore | \n",
" timezone | \n",
" jurisdiction | \n",
+ " gid | \n",
" \n",
" \n",
" \n",
@@ -434,6 +454,7 @@
" 15622.175781 | \n",
" -8 | \n",
" Federal | \n",
+ " 413889 | \n",
" \n",
" \n",
" 1 | \n",
@@ -443,6 +464,7 @@
" 64236.390625 | \n",
" -8 | \n",
" Federal | \n",
+ " 296246 | \n",
"
\n",
" \n",
"\n",
@@ -453,9 +475,9 @@
"0 77.429497 44.624298 -124.278999 15622.175781 -8 \n",
"1 1337.407959 43.496300 -125.152000 64236.390625 -8 \n",
"\n",
- " jurisdiction \n",
- "0 Federal \n",
- "1 Federal "
+ " jurisdiction gid \n",
+ "0 Federal 413889 \n",
+ "1 Federal 296246 "
]
},
"execution_count": 7,
@@ -483,6 +505,14 @@
"scrolled": true
},
"outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "hash params: 3-hour_omni-directional_wave_power_(44.624076, -124.280097)_[1995, 1996]_None_True_True_True_None_True\n",
+ "Cache filename: 789df7c7aa7d1d46b909a027de537680.pkl\n"
+ ]
+ },
{
"data": {
"text/html": [
@@ -628,6 +658,7 @@
" distance_to_shore | \n",
" timezone | \n",
" jurisdiction | \n",
+ " gid | \n",
" \n",
" \n",
" \n",
@@ -639,6 +670,7 @@
" 15622.175781 | \n",
" -8 | \n",
" Federal | \n",
+ " 413889 | \n",
" \n",
" \n",
"\n",
@@ -648,8 +680,8 @@
" water_depth latitude longitude distance_to_shore timezone \\\n",
"0 77.429497 44.624298 -124.278999 15622.175781 -8 \n",
"\n",
- " jurisdiction \n",
- "0 Federal "
+ " jurisdiction gid \n",
+ "0 Federal 413889 "
]
},
"execution_count": 9,
@@ -673,7 +705,16 @@
"cell_type": "code",
"execution_count": 10,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "hash params: 1-hour_['significant_wave_height', 'peak_period', 'mean_wave_direction']_(44.624076, -124.280097)_[1995]_None_True_True_True_None_True\n",
+ "Cache filename: 8d8439bc10f2e7a023966833fe329c23.pkl\n"
+ ]
+ }
+ ],
"source": [
"data_type = \"1-hour\" # Setting the data_type to 1 hour data\n",
"years = [1995]\n",
@@ -854,962 +895,7 @@
"outputs": [
{
"data": {
- "application/javascript": [
- "/* Put everything inside the global mpl namespace */\n",
- "/* global mpl */\n",
- "window.mpl = {};\n",
- "\n",
- "mpl.get_websocket_type = function () {\n",
- " if (typeof WebSocket !== 'undefined') {\n",
- " return WebSocket;\n",
- " } else if (typeof MozWebSocket !== 'undefined') {\n",
- " return MozWebSocket;\n",
- " } else {\n",
- " alert(\n",
- " 'Your browser does not have WebSocket support. ' +\n",
- " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n",
- " 'Firefox 4 and 5 are also supported but you ' +\n",
- " 'have to enable WebSockets in about:config.'\n",
- " );\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n",
- " this.id = figure_id;\n",
- "\n",
- " this.ws = websocket;\n",
- "\n",
- " this.supports_binary = this.ws.binaryType !== undefined;\n",
- "\n",
- " if (!this.supports_binary) {\n",
- " var warnings = document.getElementById('mpl-warnings');\n",
- " if (warnings) {\n",
- " warnings.style.display = 'block';\n",
- " warnings.textContent =\n",
- " 'This browser does not support binary websocket messages. ' +\n",
- " 'Performance may be slow.';\n",
- " }\n",
- " }\n",
- "\n",
- " this.imageObj = new Image();\n",
- "\n",
- " this.context = undefined;\n",
- " this.message = undefined;\n",
- " this.canvas = undefined;\n",
- " this.rubberband_canvas = undefined;\n",
- " this.rubberband_context = undefined;\n",
- " this.format_dropdown = undefined;\n",
- "\n",
- " this.image_mode = 'full';\n",
- "\n",
- " this.root = document.createElement('div');\n",
- " this.root.setAttribute('style', 'display: inline-block');\n",
- " this._root_extra_style(this.root);\n",
- "\n",
- " parent_element.appendChild(this.root);\n",
- "\n",
- " this._init_header(this);\n",
- " this._init_canvas(this);\n",
- " this._init_toolbar(this);\n",
- "\n",
- " var fig = this;\n",
- "\n",
- " this.waiting = false;\n",
- "\n",
- " this.ws.onopen = function () {\n",
- " fig.send_message('supports_binary', { value: fig.supports_binary });\n",
- " fig.send_message('send_image_mode', {});\n",
- " if (fig.ratio !== 1) {\n",
- " fig.send_message('set_device_pixel_ratio', {\n",
- " device_pixel_ratio: fig.ratio,\n",
- " });\n",
- " }\n",
- " fig.send_message('refresh', {});\n",
- " };\n",
- "\n",
- " this.imageObj.onload = function () {\n",
- " if (fig.image_mode === 'full') {\n",
- " // Full images could contain transparency (where diff images\n",
- " // almost always do), so we need to clear the canvas so that\n",
- " // there is no ghosting.\n",
- " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
- " }\n",
- " fig.context.drawImage(fig.imageObj, 0, 0);\n",
- " };\n",
- "\n",
- " this.imageObj.onunload = function () {\n",
- " fig.ws.close();\n",
- " };\n",
- "\n",
- " this.ws.onmessage = this._make_on_message_function(this);\n",
- "\n",
- " this.ondownload = ondownload;\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._init_header = function () {\n",
- " var titlebar = document.createElement('div');\n",
- " titlebar.classList =\n",
- " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n",
- " var titletext = document.createElement('div');\n",
- " titletext.classList = 'ui-dialog-title';\n",
- " titletext.setAttribute(\n",
- " 'style',\n",
- " 'width: 100%; text-align: center; padding: 3px;'\n",
- " );\n",
- " titlebar.appendChild(titletext);\n",
- " this.root.appendChild(titlebar);\n",
- " this.header = titletext;\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n",
- "\n",
- "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n",
- "\n",
- "mpl.figure.prototype._init_canvas = function () {\n",
- " var fig = this;\n",
- "\n",
- " var canvas_div = (this.canvas_div = document.createElement('div'));\n",
- " canvas_div.setAttribute(\n",
- " 'style',\n",
- " 'border: 1px solid #ddd;' +\n",
- " 'box-sizing: content-box;' +\n",
- " 'clear: both;' +\n",
- " 'min-height: 1px;' +\n",
- " 'min-width: 1px;' +\n",
- " 'outline: 0;' +\n",
- " 'overflow: hidden;' +\n",
- " 'position: relative;' +\n",
- " 'resize: both;'\n",
- " );\n",
- "\n",
- " function on_keyboard_event_closure(name) {\n",
- " return function (event) {\n",
- " return fig.key_event(event, name);\n",
- " };\n",
- " }\n",
- "\n",
- " canvas_div.addEventListener(\n",
- " 'keydown',\n",
- " on_keyboard_event_closure('key_press')\n",
- " );\n",
- " canvas_div.addEventListener(\n",
- " 'keyup',\n",
- " on_keyboard_event_closure('key_release')\n",
- " );\n",
- "\n",
- " this._canvas_extra_style(canvas_div);\n",
- " this.root.appendChild(canvas_div);\n",
- "\n",
- " var canvas = (this.canvas = document.createElement('canvas'));\n",
- " canvas.classList.add('mpl-canvas');\n",
- " canvas.setAttribute('style', 'box-sizing: content-box;');\n",
- "\n",
- " this.context = canvas.getContext('2d');\n",
- "\n",
- " var backingStore =\n",
- " this.context.backingStorePixelRatio ||\n",
- " this.context.webkitBackingStorePixelRatio ||\n",
- " this.context.mozBackingStorePixelRatio ||\n",
- " this.context.msBackingStorePixelRatio ||\n",
- " this.context.oBackingStorePixelRatio ||\n",
- " this.context.backingStorePixelRatio ||\n",
- " 1;\n",
- "\n",
- " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
- "\n",
- " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n",
- " 'canvas'\n",
- " ));\n",
- " rubberband_canvas.setAttribute(\n",
- " 'style',\n",
- " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n",
- " );\n",
- "\n",
- " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n",
- " if (this.ResizeObserver === undefined) {\n",
- " if (window.ResizeObserver !== undefined) {\n",
- " this.ResizeObserver = window.ResizeObserver;\n",
- " } else {\n",
- " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n",
- " this.ResizeObserver = obs.ResizeObserver;\n",
- " }\n",
- " }\n",
- "\n",
- " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n",
- " var nentries = entries.length;\n",
- " for (var i = 0; i < nentries; i++) {\n",
- " var entry = entries[i];\n",
- " var width, height;\n",
- " if (entry.contentBoxSize) {\n",
- " if (entry.contentBoxSize instanceof Array) {\n",
- " // Chrome 84 implements new version of spec.\n",
- " width = entry.contentBoxSize[0].inlineSize;\n",
- " height = entry.contentBoxSize[0].blockSize;\n",
- " } else {\n",
- " // Firefox implements old version of spec.\n",
- " width = entry.contentBoxSize.inlineSize;\n",
- " height = entry.contentBoxSize.blockSize;\n",
- " }\n",
- " } else {\n",
- " // Chrome <84 implements even older version of spec.\n",
- " width = entry.contentRect.width;\n",
- " height = entry.contentRect.height;\n",
- " }\n",
- "\n",
- " // Keep the size of the canvas and rubber band canvas in sync with\n",
- " // the canvas container.\n",
- " if (entry.devicePixelContentBoxSize) {\n",
- " // Chrome 84 implements new version of spec.\n",
- " canvas.setAttribute(\n",
- " 'width',\n",
- " entry.devicePixelContentBoxSize[0].inlineSize\n",
- " );\n",
- " canvas.setAttribute(\n",
- " 'height',\n",
- " entry.devicePixelContentBoxSize[0].blockSize\n",
- " );\n",
- " } else {\n",
- " canvas.setAttribute('width', width * fig.ratio);\n",
- " canvas.setAttribute('height', height * fig.ratio);\n",
- " }\n",
- " canvas.setAttribute(\n",
- " 'style',\n",
- " 'width: ' + width + 'px; height: ' + height + 'px;'\n",
- " );\n",
- "\n",
- " rubberband_canvas.setAttribute('width', width);\n",
- " rubberband_canvas.setAttribute('height', height);\n",
- "\n",
- " // And update the size in Python. We ignore the initial 0/0 size\n",
- " // that occurs as the element is placed into the DOM, which should\n",
- " // otherwise not happen due to the minimum size styling.\n",
- " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n",
- " fig.request_resize(width, height);\n",
- " }\n",
- " }\n",
- " });\n",
- " this.resizeObserverInstance.observe(canvas_div);\n",
- "\n",
- " function on_mouse_event_closure(name) {\n",
- " return function (event) {\n",
- " return fig.mouse_event(event, name);\n",
- " };\n",
- " }\n",
- "\n",
- " rubberband_canvas.addEventListener(\n",
- " 'mousedown',\n",
- " on_mouse_event_closure('button_press')\n",
- " );\n",
- " rubberband_canvas.addEventListener(\n",
- " 'mouseup',\n",
- " on_mouse_event_closure('button_release')\n",
- " );\n",
- " rubberband_canvas.addEventListener(\n",
- " 'dblclick',\n",
- " on_mouse_event_closure('dblclick')\n",
- " );\n",
- " // Throttle sequential mouse events to 1 every 20ms.\n",
- " rubberband_canvas.addEventListener(\n",
- " 'mousemove',\n",
- " on_mouse_event_closure('motion_notify')\n",
- " );\n",
- "\n",
- " rubberband_canvas.addEventListener(\n",
- " 'mouseenter',\n",
- " on_mouse_event_closure('figure_enter')\n",
- " );\n",
- " rubberband_canvas.addEventListener(\n",
- " 'mouseleave',\n",
- " on_mouse_event_closure('figure_leave')\n",
- " );\n",
- "\n",
- " canvas_div.addEventListener('wheel', function (event) {\n",
- " if (event.deltaY < 0) {\n",
- " event.step = 1;\n",
- " } else {\n",
- " event.step = -1;\n",
- " }\n",
- " on_mouse_event_closure('scroll')(event);\n",
- " });\n",
- "\n",
- " canvas_div.appendChild(canvas);\n",
- " canvas_div.appendChild(rubberband_canvas);\n",
- "\n",
- " this.rubberband_context = rubberband_canvas.getContext('2d');\n",
- " this.rubberband_context.strokeStyle = '#000000';\n",
- "\n",
- " this._resize_canvas = function (width, height, forward) {\n",
- " if (forward) {\n",
- " canvas_div.style.width = width + 'px';\n",
- " canvas_div.style.height = height + 'px';\n",
- " }\n",
- " };\n",
- "\n",
- " // Disable right mouse context menu.\n",
- " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n",
- " event.preventDefault();\n",
- " return false;\n",
- " });\n",
- "\n",
- " function set_focus() {\n",
- " canvas.focus();\n",
- " canvas_div.focus();\n",
- " }\n",
- "\n",
- " window.setTimeout(set_focus, 100);\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._init_toolbar = function () {\n",
- " var fig = this;\n",
- "\n",
- " var toolbar = document.createElement('div');\n",
- " toolbar.classList = 'mpl-toolbar';\n",
- " this.root.appendChild(toolbar);\n",
- "\n",
- " function on_click_closure(name) {\n",
- " return function (_event) {\n",
- " return fig.toolbar_button_onclick(name);\n",
- " };\n",
- " }\n",
- "\n",
- " function on_mouseover_closure(tooltip) {\n",
- " return function (event) {\n",
- " if (!event.currentTarget.disabled) {\n",
- " return fig.toolbar_button_onmouseover(tooltip);\n",
- " }\n",
- " };\n",
- " }\n",
- "\n",
- " fig.buttons = {};\n",
- " var buttonGroup = document.createElement('div');\n",
- " buttonGroup.classList = 'mpl-button-group';\n",
- " for (var toolbar_ind in mpl.toolbar_items) {\n",
- " var name = mpl.toolbar_items[toolbar_ind][0];\n",
- " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
- " var image = mpl.toolbar_items[toolbar_ind][2];\n",
- " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
- "\n",
- " if (!name) {\n",
- " /* Instead of a spacer, we start a new button group. */\n",
- " if (buttonGroup.hasChildNodes()) {\n",
- " toolbar.appendChild(buttonGroup);\n",
- " }\n",
- " buttonGroup = document.createElement('div');\n",
- " buttonGroup.classList = 'mpl-button-group';\n",
- " continue;\n",
- " }\n",
- "\n",
- " var button = (fig.buttons[name] = document.createElement('button'));\n",
- " button.classList = 'mpl-widget';\n",
- " button.setAttribute('role', 'button');\n",
- " button.setAttribute('aria-disabled', 'false');\n",
- " button.addEventListener('click', on_click_closure(method_name));\n",
- " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
- "\n",
- " var icon_img = document.createElement('img');\n",
- " icon_img.src = '_images/' + image + '.png';\n",
- " icon_img.srcset = '_images/' + image + '_large.png 2x';\n",
- " icon_img.alt = tooltip;\n",
- " button.appendChild(icon_img);\n",
- "\n",
- " buttonGroup.appendChild(button);\n",
- " }\n",
- "\n",
- " if (buttonGroup.hasChildNodes()) {\n",
- " toolbar.appendChild(buttonGroup);\n",
- " }\n",
- "\n",
- " var fmt_picker = document.createElement('select');\n",
- " fmt_picker.classList = 'mpl-widget';\n",
- " toolbar.appendChild(fmt_picker);\n",
- " this.format_dropdown = fmt_picker;\n",
- "\n",
- " for (var ind in mpl.extensions) {\n",
- " var fmt = mpl.extensions[ind];\n",
- " var option = document.createElement('option');\n",
- " option.selected = fmt === mpl.default_extension;\n",
- " option.innerHTML = fmt;\n",
- " fmt_picker.appendChild(option);\n",
- " }\n",
- "\n",
- " var status_bar = document.createElement('span');\n",
- " status_bar.classList = 'mpl-message';\n",
- " toolbar.appendChild(status_bar);\n",
- " this.message = status_bar;\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n",
- " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n",
- " // which will in turn request a refresh of the image.\n",
- " this.send_message('resize', { width: x_pixels, height: y_pixels });\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.send_message = function (type, properties) {\n",
- " properties['type'] = type;\n",
- " properties['figure_id'] = this.id;\n",
- " this.ws.send(JSON.stringify(properties));\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.send_draw_message = function () {\n",
- " if (!this.waiting) {\n",
- " this.waiting = true;\n",
- " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
- " var format_dropdown = fig.format_dropdown;\n",
- " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n",
- " fig.ondownload(fig, format);\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_resize = function (fig, msg) {\n",
- " var size = msg['size'];\n",
- " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n",
- " fig._resize_canvas(size[0], size[1], msg['forward']);\n",
- " fig.send_message('refresh', {});\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n",
- " var x0 = msg['x0'] / fig.ratio;\n",
- " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n",
- " var x1 = msg['x1'] / fig.ratio;\n",
- " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n",
- " x0 = Math.floor(x0) + 0.5;\n",
- " y0 = Math.floor(y0) + 0.5;\n",
- " x1 = Math.floor(x1) + 0.5;\n",
- " y1 = Math.floor(y1) + 0.5;\n",
- " var min_x = Math.min(x0, x1);\n",
- " var min_y = Math.min(y0, y1);\n",
- " var width = Math.abs(x1 - x0);\n",
- " var height = Math.abs(y1 - y0);\n",
- "\n",
- " fig.rubberband_context.clearRect(\n",
- " 0,\n",
- " 0,\n",
- " fig.canvas.width / fig.ratio,\n",
- " fig.canvas.height / fig.ratio\n",
- " );\n",
- "\n",
- " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n",
- " // Updates the figure title.\n",
- " fig.header.textContent = msg['label'];\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n",
- " fig.rubberband_canvas.style.cursor = msg['cursor'];\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_message = function (fig, msg) {\n",
- " fig.message.textContent = msg['message'];\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n",
- " // Request the server to send over a new figure.\n",
- " fig.send_draw_message();\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n",
- " fig.image_mode = msg['mode'];\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n",
- " for (var key in msg) {\n",
- " if (!(key in fig.buttons)) {\n",
- " continue;\n",
- " }\n",
- " fig.buttons[key].disabled = !msg[key];\n",
- " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n",
- " if (msg['mode'] === 'PAN') {\n",
- " fig.buttons['Pan'].classList.add('active');\n",
- " fig.buttons['Zoom'].classList.remove('active');\n",
- " } else if (msg['mode'] === 'ZOOM') {\n",
- " fig.buttons['Pan'].classList.remove('active');\n",
- " fig.buttons['Zoom'].classList.add('active');\n",
- " } else {\n",
- " fig.buttons['Pan'].classList.remove('active');\n",
- " fig.buttons['Zoom'].classList.remove('active');\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.updated_canvas_event = function () {\n",
- " // Called whenever the canvas gets updated.\n",
- " this.send_message('ack', {});\n",
- "};\n",
- "\n",
- "// A function to construct a web socket function for onmessage handling.\n",
- "// Called in the figure constructor.\n",
- "mpl.figure.prototype._make_on_message_function = function (fig) {\n",
- " return function socket_on_message(evt) {\n",
- " if (evt.data instanceof Blob) {\n",
- " var img = evt.data;\n",
- " if (img.type !== 'image/png') {\n",
- " /* FIXME: We get \"Resource interpreted as Image but\n",
- " * transferred with MIME type text/plain:\" errors on\n",
- " * Chrome. But how to set the MIME type? It doesn't seem\n",
- " * to be part of the websocket stream */\n",
- " img.type = 'image/png';\n",
- " }\n",
- "\n",
- " /* Free the memory for the previous frames */\n",
- " if (fig.imageObj.src) {\n",
- " (window.URL || window.webkitURL).revokeObjectURL(\n",
- " fig.imageObj.src\n",
- " );\n",
- " }\n",
- "\n",
- " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
- " img\n",
- " );\n",
- " fig.updated_canvas_event();\n",
- " fig.waiting = false;\n",
- " return;\n",
- " } else if (\n",
- " typeof evt.data === 'string' &&\n",
- " evt.data.slice(0, 21) === 'data:image/png;base64'\n",
- " ) {\n",
- " fig.imageObj.src = evt.data;\n",
- " fig.updated_canvas_event();\n",
- " fig.waiting = false;\n",
- " return;\n",
- " }\n",
- "\n",
- " var msg = JSON.parse(evt.data);\n",
- " var msg_type = msg['type'];\n",
- "\n",
- " // Call the \"handle_{type}\" callback, which takes\n",
- " // the figure and JSON message as its only arguments.\n",
- " try {\n",
- " var callback = fig['handle_' + msg_type];\n",
- " } catch (e) {\n",
- " console.log(\n",
- " \"No handler for the '\" + msg_type + \"' message type: \",\n",
- " msg\n",
- " );\n",
- " return;\n",
- " }\n",
- "\n",
- " if (callback) {\n",
- " try {\n",
- " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n",
- " callback(fig, msg);\n",
- " } catch (e) {\n",
- " console.log(\n",
- " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n",
- " e,\n",
- " e.stack,\n",
- " msg\n",
- " );\n",
- " }\n",
- " }\n",
- " };\n",
- "};\n",
- "\n",
- "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
- "mpl.findpos = function (e) {\n",
- " //this section is from http://www.quirksmode.org/js/events_properties.html\n",
- " var targ;\n",
- " if (!e) {\n",
- " e = window.event;\n",
- " }\n",
- " if (e.target) {\n",
- " targ = e.target;\n",
- " } else if (e.srcElement) {\n",
- " targ = e.srcElement;\n",
- " }\n",
- " if (targ.nodeType === 3) {\n",
- " // defeat Safari bug\n",
- " targ = targ.parentNode;\n",
- " }\n",
- "\n",
- " // pageX,Y are the mouse positions relative to the document\n",
- " var boundingRect = targ.getBoundingClientRect();\n",
- " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n",
- " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n",
- "\n",
- " return { x: x, y: y };\n",
- "};\n",
- "\n",
- "/*\n",
- " * return a copy of an object with only non-object keys\n",
- " * we need this to avoid circular references\n",
- " * https://stackoverflow.com/a/24161582/3208463\n",
- " */\n",
- "function simpleKeys(original) {\n",
- " return Object.keys(original).reduce(function (obj, key) {\n",
- " if (typeof original[key] !== 'object') {\n",
- " obj[key] = original[key];\n",
- " }\n",
- " return obj;\n",
- " }, {});\n",
- "}\n",
- "\n",
- "mpl.figure.prototype.mouse_event = function (event, name) {\n",
- " var canvas_pos = mpl.findpos(event);\n",
- "\n",
- " if (name === 'button_press') {\n",
- " this.canvas.focus();\n",
- " this.canvas_div.focus();\n",
- " }\n",
- "\n",
- " var x = canvas_pos.x * this.ratio;\n",
- " var y = canvas_pos.y * this.ratio;\n",
- "\n",
- " this.send_message(name, {\n",
- " x: x,\n",
- " y: y,\n",
- " button: event.button,\n",
- " step: event.step,\n",
- " guiEvent: simpleKeys(event),\n",
- " });\n",
- "\n",
- " /* This prevents the web browser from automatically changing to\n",
- " * the text insertion cursor when the button is pressed. We want\n",
- " * to control all of the cursor setting manually through the\n",
- " * 'cursor' event from matplotlib */\n",
- " event.preventDefault();\n",
- " return false;\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n",
- " // Handle any extra behaviour associated with a key event\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.key_event = function (event, name) {\n",
- " // Prevent repeat events\n",
- " if (name === 'key_press') {\n",
- " if (event.key === this._key) {\n",
- " return;\n",
- " } else {\n",
- " this._key = event.key;\n",
- " }\n",
- " }\n",
- " if (name === 'key_release') {\n",
- " this._key = null;\n",
- " }\n",
- "\n",
- " var value = '';\n",
- " if (event.ctrlKey && event.key !== 'Control') {\n",
- " value += 'ctrl+';\n",
- " }\n",
- " else if (event.altKey && event.key !== 'Alt') {\n",
- " value += 'alt+';\n",
- " }\n",
- " else if (event.shiftKey && event.key !== 'Shift') {\n",
- " value += 'shift+';\n",
- " }\n",
- "\n",
- " value += 'k' + event.key;\n",
- "\n",
- " this._key_event_extra(event, name);\n",
- "\n",
- " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n",
- " return false;\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n",
- " if (name === 'download') {\n",
- " this.handle_save(this, null);\n",
- " } else {\n",
- " this.send_message('toolbar_button', { name: name });\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n",
- " this.message.textContent = tooltip;\n",
- "};\n",
- "\n",
- "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n",
- "// prettier-ignore\n",
- "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n",
- "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
- "\n",
- "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n",
- "\n",
- "mpl.default_extension = \"png\";/* global mpl */\n",
- "\n",
- "var comm_websocket_adapter = function (comm) {\n",
- " // Create a \"websocket\"-like object which calls the given IPython comm\n",
- " // object with the appropriate methods. Currently this is a non binary\n",
- " // socket, so there is still some room for performance tuning.\n",
- " var ws = {};\n",
- "\n",
- " ws.binaryType = comm.kernel.ws.binaryType;\n",
- " ws.readyState = comm.kernel.ws.readyState;\n",
- " function updateReadyState(_event) {\n",
- " if (comm.kernel.ws) {\n",
- " ws.readyState = comm.kernel.ws.readyState;\n",
- " } else {\n",
- " ws.readyState = 3; // Closed state.\n",
- " }\n",
- " }\n",
- " comm.kernel.ws.addEventListener('open', updateReadyState);\n",
- " comm.kernel.ws.addEventListener('close', updateReadyState);\n",
- " comm.kernel.ws.addEventListener('error', updateReadyState);\n",
- "\n",
- " ws.close = function () {\n",
- " comm.close();\n",
- " };\n",
- " ws.send = function (m) {\n",
- " //console.log('sending', m);\n",
- " comm.send(m);\n",
- " };\n",
- " // Register the callback with on_msg.\n",
- " comm.on_msg(function (msg) {\n",
- " //console.log('receiving', msg['content']['data'], msg);\n",
- " var data = msg['content']['data'];\n",
- " if (data['blob'] !== undefined) {\n",
- " data = {\n",
- " data: new Blob(msg['buffers'], { type: data['blob'] }),\n",
- " };\n",
- " }\n",
- " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
- " ws.onmessage(data);\n",
- " });\n",
- " return ws;\n",
- "};\n",
- "\n",
- "mpl.mpl_figure_comm = function (comm, msg) {\n",
- " // This is the function which gets called when the mpl process\n",
- " // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
- "\n",
- " var id = msg.content.data.id;\n",
- " // Get hold of the div created by the display call when the Comm\n",
- " // socket was opened in Python.\n",
- " var element = document.getElementById(id);\n",
- " var ws_proxy = comm_websocket_adapter(comm);\n",
- "\n",
- " function ondownload(figure, _format) {\n",
- " window.open(figure.canvas.toDataURL());\n",
- " }\n",
- "\n",
- " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n",
- "\n",
- " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n",
- " // web socket which is closed, not our websocket->open comm proxy.\n",
- " ws_proxy.onopen();\n",
- "\n",
- " fig.parent_element = element;\n",
- " fig.cell_info = mpl.find_output_cell(\"\");\n",
- " if (!fig.cell_info) {\n",
- " console.error('Failed to find cell for figure', id, fig);\n",
- " return;\n",
- " }\n",
- " fig.cell_info[0].output_area.element.on(\n",
- " 'cleared',\n",
- " { fig: fig },\n",
- " fig._remove_fig_handler\n",
- " );\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_close = function (fig, msg) {\n",
- " var width = fig.canvas.width / fig.ratio;\n",
- " fig.cell_info[0].output_area.element.off(\n",
- " 'cleared',\n",
- " fig._remove_fig_handler\n",
- " );\n",
- " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n",
- "\n",
- " // Update the output cell to use the data from the current canvas.\n",
- " fig.push_to_output();\n",
- " var dataURL = fig.canvas.toDataURL();\n",
- " // Re-enable the keyboard manager in IPython - without this line, in FF,\n",
- " // the notebook keyboard shortcuts fail.\n",
- " IPython.keyboard_manager.enable();\n",
- " fig.parent_element.innerHTML =\n",
- " '
';\n",
- " fig.close_ws(fig, msg);\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.close_ws = function (fig, msg) {\n",
- " fig.send_message('closing', msg);\n",
- " // fig.ws.close()\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n",
- " // Turn the data on the canvas into data in the output cell.\n",
- " var width = this.canvas.width / this.ratio;\n",
- " var dataURL = this.canvas.toDataURL();\n",
- " this.cell_info[1]['text/html'] =\n",
- " '
';\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.updated_canvas_event = function () {\n",
- " // Tell IPython that the notebook contents must change.\n",
- " IPython.notebook.set_dirty(true);\n",
- " this.send_message('ack', {});\n",
- " var fig = this;\n",
- " // Wait a second, then push the new image to the DOM so\n",
- " // that it is saved nicely (might be nice to debounce this).\n",
- " setTimeout(function () {\n",
- " fig.push_to_output();\n",
- " }, 1000);\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._init_toolbar = function () {\n",
- " var fig = this;\n",
- "\n",
- " var toolbar = document.createElement('div');\n",
- " toolbar.classList = 'btn-toolbar';\n",
- " this.root.appendChild(toolbar);\n",
- "\n",
- " function on_click_closure(name) {\n",
- " return function (_event) {\n",
- " return fig.toolbar_button_onclick(name);\n",
- " };\n",
- " }\n",
- "\n",
- " function on_mouseover_closure(tooltip) {\n",
- " return function (event) {\n",
- " if (!event.currentTarget.disabled) {\n",
- " return fig.toolbar_button_onmouseover(tooltip);\n",
- " }\n",
- " };\n",
- " }\n",
- "\n",
- " fig.buttons = {};\n",
- " var buttonGroup = document.createElement('div');\n",
- " buttonGroup.classList = 'btn-group';\n",
- " var button;\n",
- " for (var toolbar_ind in mpl.toolbar_items) {\n",
- " var name = mpl.toolbar_items[toolbar_ind][0];\n",
- " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n",
- " var image = mpl.toolbar_items[toolbar_ind][2];\n",
- " var method_name = mpl.toolbar_items[toolbar_ind][3];\n",
- "\n",
- " if (!name) {\n",
- " /* Instead of a spacer, we start a new button group. */\n",
- " if (buttonGroup.hasChildNodes()) {\n",
- " toolbar.appendChild(buttonGroup);\n",
- " }\n",
- " buttonGroup = document.createElement('div');\n",
- " buttonGroup.classList = 'btn-group';\n",
- " continue;\n",
- " }\n",
- "\n",
- " button = fig.buttons[name] = document.createElement('button');\n",
- " button.classList = 'btn btn-default';\n",
- " button.href = '#';\n",
- " button.title = name;\n",
- " button.innerHTML = '';\n",
- " button.addEventListener('click', on_click_closure(method_name));\n",
- " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n",
- " buttonGroup.appendChild(button);\n",
- " }\n",
- "\n",
- " if (buttonGroup.hasChildNodes()) {\n",
- " toolbar.appendChild(buttonGroup);\n",
- " }\n",
- "\n",
- " // Add the status bar.\n",
- " var status_bar = document.createElement('span');\n",
- " status_bar.classList = 'mpl-message pull-right';\n",
- " toolbar.appendChild(status_bar);\n",
- " this.message = status_bar;\n",
- "\n",
- " // Add the close button to the window.\n",
- " var buttongrp = document.createElement('div');\n",
- " buttongrp.classList = 'btn-group inline pull-right';\n",
- " button = document.createElement('button');\n",
- " button.classList = 'btn btn-mini btn-primary';\n",
- " button.href = '#';\n",
- " button.title = 'Stop Interaction';\n",
- " button.innerHTML = '';\n",
- " button.addEventListener('click', function (_evt) {\n",
- " fig.handle_close(fig, {});\n",
- " });\n",
- " button.addEventListener(\n",
- " 'mouseover',\n",
- " on_mouseover_closure('Stop Interaction')\n",
- " );\n",
- " buttongrp.appendChild(button);\n",
- " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n",
- " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._remove_fig_handler = function (event) {\n",
- " var fig = event.data.fig;\n",
- " if (event.target !== this) {\n",
- " // Ignore bubbled events from children.\n",
- " return;\n",
- " }\n",
- " fig.close_ws(fig, {});\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._root_extra_style = function (el) {\n",
- " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._canvas_extra_style = function (el) {\n",
- " // this is important to make the div 'focusable\n",
- " el.setAttribute('tabindex', 0);\n",
- " // reach out to IPython and tell the keyboard manager to turn it's self\n",
- " // off when our div gets focus\n",
- "\n",
- " // location in version 3\n",
- " if (IPython.notebook.keyboard_manager) {\n",
- " IPython.notebook.keyboard_manager.register_events(el);\n",
- " } else {\n",
- " // location in version 2\n",
- " IPython.keyboard_manager.register_events(el);\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype._key_event_extra = function (event, _name) {\n",
- " // Check for shift+enter\n",
- " if (event.shiftKey && event.which === 13) {\n",
- " this.canvas_div.blur();\n",
- " // select the cell after this one\n",
- " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n",
- " IPython.notebook.select(index + 1);\n",
- " }\n",
- "};\n",
- "\n",
- "mpl.figure.prototype.handle_save = function (fig, _msg) {\n",
- " fig.ondownload(fig, null);\n",
- "};\n",
- "\n",
- "mpl.find_output_cell = function (html_output) {\n",
- " // Return the cell and output element which can be found *uniquely* in the notebook.\n",
- " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n",
- " // IPython event is triggered only after the cells have been serialised, which for\n",
- " // our purposes (turning an active figure into a static one), is too late.\n",
- " var cells = IPython.notebook.get_cells();\n",
- " var ncells = cells.length;\n",
- " for (var i = 0; i < ncells; i++) {\n",
- " var cell = cells[i];\n",
- " if (cell.cell_type === 'code') {\n",
- " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n",
- " var data = cell.output_area.outputs[j];\n",
- " if (data.data) {\n",
- " // IPython >= 3 moved mimebundle to data attribute of output\n",
- " data = data.data;\n",
- " }\n",
- " if (data['text/html'] === html_output) {\n",
- " return [cell, data, j];\n",
- " }\n",
- " }\n",
- " }\n",
- " }\n",
- "};\n",
- "\n",
- "// Register the function which deals with the matplotlib target/channel.\n",
- "// The kernel may be null if the page has been refreshed.\n",
- "if (IPython.notebook.kernel !== null) {\n",
- " IPython.notebook.kernel.comm_manager.register_target(\n",
- " 'matplotlib',\n",
- " mpl.mpl_figure_comm\n",
- " );\n",
- "}\n"
- ],
+ "application/javascript": "/* Put everything inside the global mpl namespace */\n/* global mpl */\nwindow.mpl = {};\n\nmpl.get_websocket_type = function () {\n if (typeof WebSocket !== 'undefined') {\n return WebSocket;\n } else if (typeof MozWebSocket !== 'undefined') {\n return MozWebSocket;\n } else {\n alert(\n 'Your browser does not have WebSocket support. ' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.'\n );\n }\n};\n\nmpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = this.ws.binaryType !== undefined;\n\n if (!this.supports_binary) {\n var warnings = document.getElementById('mpl-warnings');\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent =\n 'This browser does not support binary websocket messages. ' +\n 'Performance may be slow.';\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = document.createElement('div');\n this.root.setAttribute('style', 'display: inline-block');\n this._root_extra_style(this.root);\n\n parent_element.appendChild(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message('supports_binary', { value: fig.supports_binary });\n fig.send_message('send_image_mode', {});\n if (fig.ratio !== 1) {\n fig.send_message('set_device_pixel_ratio', {\n device_pixel_ratio: fig.ratio,\n });\n }\n fig.send_message('refresh', {});\n };\n\n this.imageObj.onload = function () {\n if (fig.image_mode === 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function () {\n fig.ws.close();\n };\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n};\n\nmpl.figure.prototype._init_header = function () {\n var titlebar = document.createElement('div');\n titlebar.classList =\n 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n var titletext = document.createElement('div');\n titletext.classList = 'ui-dialog-title';\n titletext.setAttribute(\n 'style',\n 'width: 100%; text-align: center; padding: 3px;'\n );\n titlebar.appendChild(titletext);\n this.root.appendChild(titlebar);\n this.header = titletext;\n};\n\nmpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n\nmpl.figure.prototype._init_canvas = function () {\n var fig = this;\n\n var canvas_div = (this.canvas_div = document.createElement('div'));\n canvas_div.setAttribute('tabindex', '0');\n canvas_div.setAttribute(\n 'style',\n 'border: 1px solid #ddd;' +\n 'box-sizing: content-box;' +\n 'clear: both;' +\n 'min-height: 1px;' +\n 'min-width: 1px;' +\n 'outline: 0;' +\n 'overflow: hidden;' +\n 'position: relative;' +\n 'resize: both;' +\n 'z-index: 2;'\n );\n\n function on_keyboard_event_closure(name) {\n return function (event) {\n return fig.key_event(event, name);\n };\n }\n\n canvas_div.addEventListener(\n 'keydown',\n on_keyboard_event_closure('key_press')\n );\n canvas_div.addEventListener(\n 'keyup',\n on_keyboard_event_closure('key_release')\n );\n\n this._canvas_extra_style(canvas_div);\n this.root.appendChild(canvas_div);\n\n var canvas = (this.canvas = document.createElement('canvas'));\n canvas.classList.add('mpl-canvas');\n canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'pointer-events: none;' +\n 'position: relative;' +\n 'z-index: 0;'\n );\n\n this.context = canvas.getContext('2d');\n\n var backingStore =\n this.context.backingStorePixelRatio ||\n this.context.webkitBackingStorePixelRatio ||\n this.context.mozBackingStorePixelRatio ||\n this.context.msBackingStorePixelRatio ||\n this.context.oBackingStorePixelRatio ||\n this.context.backingStorePixelRatio ||\n 1;\n\n this.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n 'canvas'\n ));\n rubberband_canvas.setAttribute(\n 'style',\n 'box-sizing: content-box;' +\n 'left: 0;' +\n 'pointer-events: none;' +\n 'position: absolute;' +\n 'top: 0;' +\n 'z-index: 1;'\n );\n\n // Apply a ponyfill if ResizeObserver is not implemented by browser.\n if (this.ResizeObserver === undefined) {\n if (window.ResizeObserver !== undefined) {\n this.ResizeObserver = window.ResizeObserver;\n } else {\n var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n this.ResizeObserver = obs.ResizeObserver;\n }\n }\n\n this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n // There's no need to resize if the WebSocket is not connected:\n // - If it is still connecting, then we will get an initial resize from\n // Python once it connects.\n // - If it has disconnected, then resizing will clear the canvas and\n // never get anything back to refill it, so better to not resize and\n // keep something visible.\n if (fig.ws.readyState != 1) {\n return;\n }\n var nentries = entries.length;\n for (var i = 0; i < nentries; i++) {\n var entry = entries[i];\n var width, height;\n if (entry.contentBoxSize) {\n if (entry.contentBoxSize instanceof Array) {\n // Chrome 84 implements new version of spec.\n width = entry.contentBoxSize[0].inlineSize;\n height = entry.contentBoxSize[0].blockSize;\n } else {\n // Firefox implements old version of spec.\n width = entry.contentBoxSize.inlineSize;\n height = entry.contentBoxSize.blockSize;\n }\n } else {\n // Chrome <84 implements even older version of spec.\n width = entry.contentRect.width;\n height = entry.contentRect.height;\n }\n\n // Keep the size of the canvas and rubber band canvas in sync with\n // the canvas container.\n if (entry.devicePixelContentBoxSize) {\n // Chrome 84 implements new version of spec.\n canvas.setAttribute(\n 'width',\n entry.devicePixelContentBoxSize[0].inlineSize\n );\n canvas.setAttribute(\n 'height',\n entry.devicePixelContentBoxSize[0].blockSize\n );\n } else {\n canvas.setAttribute('width', width * fig.ratio);\n canvas.setAttribute('height', height * fig.ratio);\n }\n /* This rescales the canvas back to display pixels, so that it\n * appears correct on HiDPI screens. */\n canvas.style.width = width + 'px';\n canvas.style.height = height + 'px';\n\n rubberband_canvas.setAttribute('width', width);\n rubberband_canvas.setAttribute('height', height);\n\n // And update the size in Python. We ignore the initial 0/0 size\n // that occurs as the element is placed into the DOM, which should\n // otherwise not happen due to the minimum size styling.\n if (width != 0 && height != 0) {\n fig.request_resize(width, height);\n }\n }\n });\n this.resizeObserverInstance.observe(canvas_div);\n\n function on_mouse_event_closure(name) {\n /* User Agent sniffing is bad, but WebKit is busted:\n * https://bugs.webkit.org/show_bug.cgi?id=144526\n * https://bugs.webkit.org/show_bug.cgi?id=181818\n * The worst that happens here is that they get an extra browser\n * selection when dragging, if this check fails to catch them.\n */\n var UA = navigator.userAgent;\n var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n if(isWebKit) {\n return function (event) {\n /* This prevents the web browser from automatically changing to\n * the text insertion cursor when the button is pressed. We\n * want to control all of the cursor setting manually through\n * the 'cursor' event from matplotlib */\n event.preventDefault()\n return fig.mouse_event(event, name);\n };\n } else {\n return function (event) {\n return fig.mouse_event(event, name);\n };\n }\n }\n\n canvas_div.addEventListener(\n 'mousedown',\n on_mouse_event_closure('button_press')\n );\n canvas_div.addEventListener(\n 'mouseup',\n on_mouse_event_closure('button_release')\n );\n canvas_div.addEventListener(\n 'dblclick',\n on_mouse_event_closure('dblclick')\n );\n // Throttle sequential mouse events to 1 every 20ms.\n canvas_div.addEventListener(\n 'mousemove',\n on_mouse_event_closure('motion_notify')\n );\n\n canvas_div.addEventListener(\n 'mouseenter',\n on_mouse_event_closure('figure_enter')\n );\n canvas_div.addEventListener(\n 'mouseleave',\n on_mouse_event_closure('figure_leave')\n );\n\n canvas_div.addEventListener('wheel', function (event) {\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n on_mouse_event_closure('scroll')(event);\n });\n\n canvas_div.appendChild(canvas);\n canvas_div.appendChild(rubberband_canvas);\n\n this.rubberband_context = rubberband_canvas.getContext('2d');\n this.rubberband_context.strokeStyle = '#000000';\n\n this._resize_canvas = function (width, height, forward) {\n if (forward) {\n canvas_div.style.width = width + 'px';\n canvas_div.style.height = height + 'px';\n }\n };\n\n // Disable right mouse context menu.\n canvas_div.addEventListener('contextmenu', function (_e) {\n event.preventDefault();\n return false;\n });\n\n function set_focus() {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'mpl-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'mpl-button-group';\n continue;\n }\n\n var button = (fig.buttons[name] = document.createElement('button'));\n button.classList = 'mpl-widget';\n button.setAttribute('role', 'button');\n button.setAttribute('aria-disabled', 'false');\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n\n var icon_img = document.createElement('img');\n icon_img.src = '_images/' + image + '.png';\n icon_img.srcset = '_images/' + image + '_large.png 2x';\n icon_img.alt = tooltip;\n button.appendChild(icon_img);\n\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n var fmt_picker = document.createElement('select');\n fmt_picker.classList = 'mpl-widget';\n toolbar.appendChild(fmt_picker);\n this.format_dropdown = fmt_picker;\n\n for (var ind in mpl.extensions) {\n var fmt = mpl.extensions[ind];\n var option = document.createElement('option');\n option.selected = fmt === mpl.default_extension;\n option.innerHTML = fmt;\n fmt_picker.appendChild(option);\n }\n\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n};\n\nmpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n // which will in turn request a refresh of the image.\n this.send_message('resize', { width: x_pixels, height: y_pixels });\n};\n\nmpl.figure.prototype.send_message = function (type, properties) {\n properties['type'] = type;\n properties['figure_id'] = this.id;\n this.ws.send(JSON.stringify(properties));\n};\n\nmpl.figure.prototype.send_draw_message = function () {\n if (!this.waiting) {\n this.waiting = true;\n this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n var format_dropdown = fig.format_dropdown;\n var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n fig.ondownload(fig, format);\n};\n\nmpl.figure.prototype.handle_resize = function (fig, msg) {\n var size = msg['size'];\n if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n fig._resize_canvas(size[0], size[1], msg['forward']);\n fig.send_message('refresh', {});\n }\n};\n\nmpl.figure.prototype.handle_rubberband = function (fig, msg) {\n var x0 = msg['x0'] / fig.ratio;\n var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n var x1 = msg['x1'] / fig.ratio;\n var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n x0 = Math.floor(x0) + 0.5;\n y0 = Math.floor(y0) + 0.5;\n x1 = Math.floor(x1) + 0.5;\n y1 = Math.floor(y1) + 0.5;\n var min_x = Math.min(x0, x1);\n var min_y = Math.min(y0, y1);\n var width = Math.abs(x1 - x0);\n var height = Math.abs(y1 - y0);\n\n fig.rubberband_context.clearRect(\n 0,\n 0,\n fig.canvas.width / fig.ratio,\n fig.canvas.height / fig.ratio\n );\n\n fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n};\n\nmpl.figure.prototype.handle_figure_label = function (fig, msg) {\n // Updates the figure title.\n fig.header.textContent = msg['label'];\n};\n\nmpl.figure.prototype.handle_cursor = function (fig, msg) {\n fig.canvas_div.style.cursor = msg['cursor'];\n};\n\nmpl.figure.prototype.handle_message = function (fig, msg) {\n fig.message.textContent = msg['message'];\n};\n\nmpl.figure.prototype.handle_draw = function (fig, _msg) {\n // Request the server to send over a new figure.\n fig.send_draw_message();\n};\n\nmpl.figure.prototype.handle_image_mode = function (fig, msg) {\n fig.image_mode = msg['mode'];\n};\n\nmpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n for (var key in msg) {\n if (!(key in fig.buttons)) {\n continue;\n }\n fig.buttons[key].disabled = !msg[key];\n fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n }\n};\n\nmpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n if (msg['mode'] === 'PAN') {\n fig.buttons['Pan'].classList.add('active');\n fig.buttons['Zoom'].classList.remove('active');\n } else if (msg['mode'] === 'ZOOM') {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.add('active');\n } else {\n fig.buttons['Pan'].classList.remove('active');\n fig.buttons['Zoom'].classList.remove('active');\n }\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Called whenever the canvas gets updated.\n this.send_message('ack', {});\n};\n\n// A function to construct a web socket function for onmessage handling.\n// Called in the figure constructor.\nmpl.figure.prototype._make_on_message_function = function (fig) {\n return function socket_on_message(evt) {\n if (evt.data instanceof Blob) {\n var img = evt.data;\n if (img.type !== 'image/png') {\n /* FIXME: We get \"Resource interpreted as Image but\n * transferred with MIME type text/plain:\" errors on\n * Chrome. But how to set the MIME type? It doesn't seem\n * to be part of the websocket stream */\n img.type = 'image/png';\n }\n\n /* Free the memory for the previous frames */\n if (fig.imageObj.src) {\n (window.URL || window.webkitURL).revokeObjectURL(\n fig.imageObj.src\n );\n }\n\n fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n img\n );\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n } else if (\n typeof evt.data === 'string' &&\n evt.data.slice(0, 21) === 'data:image/png;base64'\n ) {\n fig.imageObj.src = evt.data;\n fig.updated_canvas_event();\n fig.waiting = false;\n return;\n }\n\n var msg = JSON.parse(evt.data);\n var msg_type = msg['type'];\n\n // Call the \"handle_{type}\" callback, which takes\n // the figure and JSON message as its only arguments.\n try {\n var callback = fig['handle_' + msg_type];\n } catch (e) {\n console.log(\n \"No handler for the '\" + msg_type + \"' message type: \",\n msg\n );\n return;\n }\n\n if (callback) {\n try {\n // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n callback(fig, msg);\n } catch (e) {\n console.log(\n \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n e,\n e.stack,\n msg\n );\n }\n }\n };\n};\n\nfunction getModifiers(event) {\n var mods = [];\n if (event.ctrlKey) {\n mods.push('ctrl');\n }\n if (event.altKey) {\n mods.push('alt');\n }\n if (event.shiftKey) {\n mods.push('shift');\n }\n if (event.metaKey) {\n mods.push('meta');\n }\n return mods;\n}\n\n/*\n * return a copy of an object with only non-object keys\n * we need this to avoid circular references\n * https://stackoverflow.com/a/24161582/3208463\n */\nfunction simpleKeys(original) {\n return Object.keys(original).reduce(function (obj, key) {\n if (typeof original[key] !== 'object') {\n obj[key] = original[key];\n }\n return obj;\n }, {});\n}\n\nmpl.figure.prototype.mouse_event = function (event, name) {\n if (name === 'button_press') {\n this.canvas.focus();\n this.canvas_div.focus();\n }\n\n // from https://stackoverflow.com/q/1114465\n var boundingRect = this.canvas.getBoundingClientRect();\n var x = (event.clientX - boundingRect.left) * this.ratio;\n var y = (event.clientY - boundingRect.top) * this.ratio;\n\n this.send_message(name, {\n x: x,\n y: y,\n button: event.button,\n step: event.step,\n modifiers: getModifiers(event),\n guiEvent: simpleKeys(event),\n });\n\n return false;\n};\n\nmpl.figure.prototype._key_event_extra = function (_event, _name) {\n // Handle any extra behaviour associated with a key event\n};\n\nmpl.figure.prototype.key_event = function (event, name) {\n // Prevent repeat events\n if (name === 'key_press') {\n if (event.key === this._key) {\n return;\n } else {\n this._key = event.key;\n }\n }\n if (name === 'key_release') {\n this._key = null;\n }\n\n var value = '';\n if (event.ctrlKey && event.key !== 'Control') {\n value += 'ctrl+';\n }\n else if (event.altKey && event.key !== 'Alt') {\n value += 'alt+';\n }\n else if (event.shiftKey && event.key !== 'Shift') {\n value += 'shift+';\n }\n\n value += 'k' + event.key;\n\n this._key_event_extra(event, name);\n\n this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n return false;\n};\n\nmpl.figure.prototype.toolbar_button_onclick = function (name) {\n if (name === 'download') {\n this.handle_save(this, null);\n } else {\n this.send_message('toolbar_button', { name: name });\n }\n};\n\nmpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n this.message.textContent = tooltip;\n};\n\n///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n// prettier-ignore\nvar _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\nmpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n\nmpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n\nmpl.default_extension = \"png\";/* global mpl */\n\nvar comm_websocket_adapter = function (comm) {\n // Create a \"websocket\"-like object which calls the given IPython comm\n // object with the appropriate methods. Currently this is a non binary\n // socket, so there is still some room for performance tuning.\n var ws = {};\n\n ws.binaryType = comm.kernel.ws.binaryType;\n ws.readyState = comm.kernel.ws.readyState;\n function updateReadyState(_event) {\n if (comm.kernel.ws) {\n ws.readyState = comm.kernel.ws.readyState;\n } else {\n ws.readyState = 3; // Closed state.\n }\n }\n comm.kernel.ws.addEventListener('open', updateReadyState);\n comm.kernel.ws.addEventListener('close', updateReadyState);\n comm.kernel.ws.addEventListener('error', updateReadyState);\n\n ws.close = function () {\n comm.close();\n };\n ws.send = function (m) {\n //console.log('sending', m);\n comm.send(m);\n };\n // Register the callback with on_msg.\n comm.on_msg(function (msg) {\n //console.log('receiving', msg['content']['data'], msg);\n var data = msg['content']['data'];\n if (data['blob'] !== undefined) {\n data = {\n data: new Blob(msg['buffers'], { type: data['blob'] }),\n };\n }\n // Pass the mpl event to the overridden (by mpl) onmessage function.\n ws.onmessage(data);\n });\n return ws;\n};\n\nmpl.mpl_figure_comm = function (comm, msg) {\n // This is the function which gets called when the mpl process\n // starts-up an IPython Comm through the \"matplotlib\" channel.\n\n var id = msg.content.data.id;\n // Get hold of the div created by the display call when the Comm\n // socket was opened in Python.\n var element = document.getElementById(id);\n var ws_proxy = comm_websocket_adapter(comm);\n\n function ondownload(figure, _format) {\n window.open(figure.canvas.toDataURL());\n }\n\n var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n\n // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n // web socket which is closed, not our websocket->open comm proxy.\n ws_proxy.onopen();\n\n fig.parent_element = element;\n fig.cell_info = mpl.find_output_cell(\"\");\n if (!fig.cell_info) {\n console.error('Failed to find cell for figure', id, fig);\n return;\n }\n fig.cell_info[0].output_area.element.on(\n 'cleared',\n { fig: fig },\n fig._remove_fig_handler\n );\n};\n\nmpl.figure.prototype.handle_close = function (fig, msg) {\n var width = fig.canvas.width / fig.ratio;\n fig.cell_info[0].output_area.element.off(\n 'cleared',\n fig._remove_fig_handler\n );\n fig.resizeObserverInstance.unobserve(fig.canvas_div);\n\n // Update the output cell to use the data from the current canvas.\n fig.push_to_output();\n var dataURL = fig.canvas.toDataURL();\n // Re-enable the keyboard manager in IPython - without this line, in FF,\n // the notebook keyboard shortcuts fail.\n IPython.keyboard_manager.enable();\n fig.parent_element.innerHTML =\n '
';\n fig.close_ws(fig, msg);\n};\n\nmpl.figure.prototype.close_ws = function (fig, msg) {\n fig.send_message('closing', msg);\n // fig.ws.close()\n};\n\nmpl.figure.prototype.push_to_output = function (_remove_interactive) {\n // Turn the data on the canvas into data in the output cell.\n var width = this.canvas.width / this.ratio;\n var dataURL = this.canvas.toDataURL();\n this.cell_info[1]['text/html'] =\n '
';\n};\n\nmpl.figure.prototype.updated_canvas_event = function () {\n // Tell IPython that the notebook contents must change.\n IPython.notebook.set_dirty(true);\n this.send_message('ack', {});\n var fig = this;\n // Wait a second, then push the new image to the DOM so\n // that it is saved nicely (might be nice to debounce this).\n setTimeout(function () {\n fig.push_to_output();\n }, 1000);\n};\n\nmpl.figure.prototype._init_toolbar = function () {\n var fig = this;\n\n var toolbar = document.createElement('div');\n toolbar.classList = 'btn-toolbar';\n this.root.appendChild(toolbar);\n\n function on_click_closure(name) {\n return function (_event) {\n return fig.toolbar_button_onclick(name);\n };\n }\n\n function on_mouseover_closure(tooltip) {\n return function (event) {\n if (!event.currentTarget.disabled) {\n return fig.toolbar_button_onmouseover(tooltip);\n }\n };\n }\n\n fig.buttons = {};\n var buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n var button;\n for (var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n /* Instead of a spacer, we start a new button group. */\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n buttonGroup = document.createElement('div');\n buttonGroup.classList = 'btn-group';\n continue;\n }\n\n button = fig.buttons[name] = document.createElement('button');\n button.classList = 'btn btn-default';\n button.href = '#';\n button.title = name;\n button.innerHTML = '';\n button.addEventListener('click', on_click_closure(method_name));\n button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n buttonGroup.appendChild(button);\n }\n\n if (buttonGroup.hasChildNodes()) {\n toolbar.appendChild(buttonGroup);\n }\n\n // Add the status bar.\n var status_bar = document.createElement('span');\n status_bar.classList = 'mpl-message pull-right';\n toolbar.appendChild(status_bar);\n this.message = status_bar;\n\n // Add the close button to the window.\n var buttongrp = document.createElement('div');\n buttongrp.classList = 'btn-group inline pull-right';\n button = document.createElement('button');\n button.classList = 'btn btn-mini btn-primary';\n button.href = '#';\n button.title = 'Stop Interaction';\n button.innerHTML = '';\n button.addEventListener('click', function (_evt) {\n fig.handle_close(fig, {});\n });\n button.addEventListener(\n 'mouseover',\n on_mouseover_closure('Stop Interaction')\n );\n buttongrp.appendChild(button);\n var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n titlebar.insertBefore(buttongrp, titlebar.firstChild);\n};\n\nmpl.figure.prototype._remove_fig_handler = function (event) {\n var fig = event.data.fig;\n if (event.target !== this) {\n // Ignore bubbled events from children.\n return;\n }\n fig.close_ws(fig, {});\n};\n\nmpl.figure.prototype._root_extra_style = function (el) {\n el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n};\n\nmpl.figure.prototype._canvas_extra_style = function (el) {\n // this is important to make the div 'focusable\n el.setAttribute('tabindex', 0);\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n } else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n};\n\nmpl.figure.prototype._key_event_extra = function (event, _name) {\n // Check for shift+enter\n if (event.shiftKey && event.which === 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n};\n\nmpl.figure.prototype.handle_save = function (fig, _msg) {\n fig.ondownload(fig, null);\n};\n\nmpl.find_output_cell = function (html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i = 0; i < ncells; i++) {\n var cell = cells[i];\n if (cell.cell_type === 'code') {\n for (var j = 0; j < cell.output_area.outputs.length; j++) {\n var data = cell.output_area.outputs[j];\n if (data.data) {\n // IPython >= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] === html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n};\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel !== null) {\n IPython.notebook.kernel.comm_manager.register_target(\n 'matplotlib',\n mpl.mpl_figure_comm\n );\n}\n",
"text/plain": [
""
]
@@ -1820,7 +906,7 @@
{
"data": {
"text/html": [
- ""
+ ""
],
"text/plain": [
""
@@ -1903,14 +989,17 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "\n",
- "Dimensions: (time_index: 8748, frequency: 29, direction: 24)\n",
+ "hash params: (43.489171, -125.152137)_1993_None_True_True_True_None\n",
+ "Cache filename: 4da1f094b0d510aa7891e26e42324a95.pkl\n",
+ " Size: 24MB\n",
+ "Dimensions: (time_index: 8748, frequency: 29, direction: 24, gid: 1)\n",
"Coordinates:\n",
- " * time_index (time_index) object 725850000000000000 ... 757378800000000000\n",
- " * frequency (frequency) float64 0.035 0.0385 0.0424 ... 0.4173 0.4591 0.505\n",
- " * direction (direction) float64 7.5 22.5 37.5 52.5 ... 322.5 337.5 352.5\n",
+ " * time_index (time_index) datetime64[ns] 70kB 1993-01-01T01:00:00 .....\n",
+ " * frequency (frequency) float32 116B 0.035 0.0385 ... 0.4591 0.505\n",
+ " * direction (direction) float32 96B 7.5 22.5 37.5 ... 337.5 352.5\n",
+ " * gid (gid) int32 4B 58\n",
"Data variables:\n",
- " 58 (time_index, frequency, direction) float32 0.0 0.0 ... 7.968e-05\n"
+ " spectral_density (time_index, frequency, direction, gid) float32 24MB 0....\n"
]
}
],
@@ -1944,7 +1033,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.9.12"
+ "version": "3.11.7"
}
},
"nbformat": 4,
diff --git a/examples/data/wave/hindcast/dir_spectra.nc b/examples/data/wave/hindcast/dir_spectra.nc
new file mode 100644
index 0000000000000000000000000000000000000000..a24d634e585eda1b276db560cedd2d8fe57b5d4e
GIT binary patch
literal 42399
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