summaryrefslogtreecommitdiff
path: root/prototype
diff options
context:
space:
mode:
authorjaseg <git-bigdata-wsl-arch@jaseg.de>2021-03-10 22:36:58 +0100
committerjaseg <git-bigdata-wsl-arch@jaseg.de>2021-03-10 22:37:15 +0100
commitdf2d79189c3295f01dcb36d4e738fddccab8ece8 (patch)
treedb58b59211f5a3afba1d05d6ba8a81d342e4187c /prototype
parentca375060529f4d7223f0a9b154d5bf5df97a0315 (diff)
downloadihsm-df2d79189c3295f01dcb36d4e738fddccab8ece8.tar.gz
ihsm-df2d79189c3295f01dcb36d4e738fddccab8ece8.tar.bz2
ihsm-df2d79189c3295f01dcb36d4e738fddccab8ece8.zip
Add initial accelerometer data analysis
Diffstat (limited to 'prototype')
-rw-r--r--prototype/sensor-analysis/Accelerometer Data Analysis.ipynb3293
-rwxr-xr-xprototype/sensor-analysis/test_run.sqlite3bin0 -> 114688 bytes
2 files changed, 3293 insertions, 0 deletions
diff --git a/prototype/sensor-analysis/Accelerometer Data Analysis.ipynb b/prototype/sensor-analysis/Accelerometer Data Analysis.ipynb
new file mode 100644
index 0000000..f25f4cb
--- /dev/null
+++ b/prototype/sensor-analysis/Accelerometer Data Analysis.ipynb
@@ -0,0 +1,3293 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 182,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import sys\n",
+ "import sqlite3\n",
+ "import math\n",
+ "import warnings\n",
+ "import struct\n",
+ "import itertools\n",
+ "import numpy as np\n",
+ "import scipy.fftpack\n",
+ "import scipy.signal\n",
+ "from matplotlib import pyplot as plt\n",
+ "import scipy.optimize\n",
+ "%matplotlib notebook"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "db = sqlite3.connect('test_run.sqlite3')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Last run was ID #2 with 1759 packets total, 504 distinct over 409.4534661769867s\n"
+ ]
+ }
+ ],
+ "source": [
+ "last_run, = db.execute('SELECT MAX(run_id) FROM packets').fetchone()\n",
+ "num_packets, = db.execute('SELECT COUNT(*) FROM packets WHERE run_id=?', (last_run,)).fetchone()\n",
+ "num_packets_distinct, = db.execute('SELECT COUNT(*) FROM (SELECT DISTINCT data FROM packets WHERE run_id=?)', (last_run,)).fetchone()\n",
+ "timespan_start, timespan_end = db.execute('SELECT MIN(timestamp_us)/1e6, MAX(timestamp_us)/1e6 FROM packets WHERE run_id=?', (last_run,)).fetchone()\n",
+ "timespan = timespan_end - timespan_start\n",
+ "print(f'Last run was ID #{last_run} with {num_packets} packets total, {num_packets_distinct} distinct over {timespan}s')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "timestamps = db.execute('SELECT timestamp_us/1e6 FROM packets WHERE run_id=? ORDER BY timestamp_us', (last_run,)).fetchall()\n",
+ "timestamps = [ ts - timespan_start for ts, in timestamps ]\n",
+ "deltas = [ b-a for a, b in zip(timestamps[:-1], timestamps[1:]) ]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 201,
+ "metadata": {
+ "scrolled": false
+ },
+ "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_dpi_ratio', { dpi_ratio: fig.ratio });\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",
+ " // 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",
+ " var cursor = msg['cursor'];\n",
+ " switch (cursor) {\n",
+ " case 0:\n",
+ " cursor = 'pointer';\n",
+ " break;\n",
+ " case 1:\n",
+ " cursor = 'default';\n",
+ " break;\n",
+ " case 2:\n",
+ " cursor = 'crosshair';\n",
+ " break;\n",
+ " case 3:\n",
+ " cursor = 'move';\n",
+ " break;\n",
+ " }\n",
+ " fig.rubberband_canvas.style.cursor = 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",
+ " /* 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",
+ " evt.data.type = 'image/png';\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",
+ " evt.data\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 http://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",
+ " * http://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.which === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.which;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.which !== 17) {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " if (event.altKey && event.which !== 18) {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " if (event.shiftKey && event.which !== 16) {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k';\n",
+ " value += event.which.toString();\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, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"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.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",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(msg['content']['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(\"<div id='\" + id + \"'></div>\");\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",
+ " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\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",
+ " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\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 = '<i class=\"fa ' + image + ' fa-lg\"></i>';\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 = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\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",
+ " var manager = IPython.notebook.keyboard_manager;\n",
+ " if (!manager) {\n",
+ " manager = IPython.keyboard_manager;\n",
+ " }\n",
+ "\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"
+ ],
+ "text/plain": [
+ "<IPython.core.display.Javascript object>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "<img src=\"data:image/png;base64,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etp95ERE9iYAzUMA+oAANAf++ONvr7/EDJqK/+HjtcopPqjknBIlzi1Uv2nr9D9Jy3TXmKW6efCChvBzXE89J6+I2HkJQPMQgD4gAM2BP/742+svMYPG8N958LjeytupId5mPZy0XP81dFFY4J1rtX9mnv775Wz1f3+DSoPHI3YtBKB5COswiW0AACAASURBVEAfEIDmwB9//O31l5hBtP237K1SuwHzzhl5XUYu0Z/fWaUXPtysSVnblbq2XDnFB7V131FV154ycj0EoHkIQB8QgObAH3/87fWXmEG0/T/cUNEQfAmzN2nayjKtKDmkqpONM38C0DwEoA8IQHPgjz/+9vpLzCDa/mnrdstxPf329WVROd+FIADNQwD6gAA0B/7442+vv8QMou0/a20oAB9OWh6V810IAtA8BKAPCEBz4I8//vb6S8wg2v4z15TLcT31SI7cd/L6gQA0DwHoAwLQHPjjj7+9/hIziLb/jFW7Iv6jXPxAAJqHAPQBAWgO/PHH315/iRlE23/6ylAA/uHNlVE534UgAM1DAPqAADQH/vjjb6+/xAyi7Z+yokyO6+mPb62KyvkuBAFoHgLQBwSgOfDHH397/SVmEG3/KctL5bie/vQ2AWgLBKAPCEBz4I8//vb6S8wg2v7v5O+U43rq9e7qqJzvQhCA5iEAfUAAmgN//PG3119iBtH2fysvFICPT1kTlfNdCALQPASgDwhAc+CPP/72+kvMINr+k3NL5Lie/jKVALQFAtAHBKA58Mcff3v9JWYQbf+k7B1yXE993lsblfNdCALQPASgDwhAc+CPP/72+kvMINr+r2eFArDftHVROd+FIADNQwD6gAA0B/7442+vv8QMou0/cel2Oa6nv01fH5XzXQgC0DwEoA8IQHPgjz/+9vpLzCDa/hMyt8lxPf19BgFoCwSgDwhAc+CPP/72+kvMINr+4xYXy3E99X9/Q1TOdyEIQPMQgD4gAM2BP/742+svMYNo+7+yKBSA8TM3RuV8F4IANA8B6AMC0Bz444+/vf4SM4i2/0sZW+W4ngakEoC2QAD6gAA0B/7442+vv8QMou0/ekGRHNfTs2kFUTnfhSAAzUMA+oAANAf++ONvr7/EDKLtPyo9FIAJszdF5XwXggA0DwHoAwLQHPjjj7+9/hIziLb/8Plb5LienptDANoCAegDAtAc+OOP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GhS1a/9XSUhIaHirq23btnrooYca/qW7Y8cOBQIBrV27Nuw53bt31+9+9ztJse//cWpra/WlL31JiYmJDcea8/0/+y+/SN3v5ORkXXnllZ8435VXXqnJkydHWsMXFxMAGRkZuuSSS8J++0PXrl315S9/WV/60pd0/fXX66mnngp7lTRWZnC+APT7OR/L/h9n3759uuyyyzR16tSw483l/jdFCMAmREVFhQKBgPLy8sKOJyYmqn379o10VZGnvr5e999/v77//e+HHZ82bZo8z1NBQYHmzJmjm2++WTfccEPDT4KfOnWqWrVq9Yn97rrrLv35z3+OyrX7Yd68efrggw+0ceNGZWRkqGvXrvrqV7+qYDCovLw8BQKBT3z905/+9CfdfffdkmLf/+NMnz5dLVq0CPNtzvf/7L/8InW/ExMT1a5du088pl27dho6dGgkFXxzoQA4efKkbr31Vj388MNhx19//XVlZGSooKBA7733ntq2batu3bo1fDxWZnAu/0h8zsey/8cZMWKE/u3f/k0nT54MO95c7n9ThABsQnwUgPn5+WHHhwwZog4dOjTSVUWexx9/XI7jXPCL9/fs2aOWLVtq5syZks7/P8Nu3brpscceM3KtJjl+/Li++tWvavTo0Q1BsGfPnrDHPProo7rnnnskNS//u++++4Jfo9Oc7v/5AtDv/T7fPw6vu+46DRs2LJIKvvm0AKirq9MDDzygW2655YK/+3X16tUKBAJas2aNpNiZwcW8AvqvfM43F/8OHTroiSeeuOA+sXr/myIEYBPChreAn3jiCV199dUqKSm5qMdfd911DV8nFctvAZ6Pbt26qVevXla9BVxaWqpLL71UaWlpF3xsc7n/vAV8/gCoq6vTgw8+qJtuuknBYPCC+9TX14d9XVyszOBiAlD67J/zzcE/OztbgUBA69evv+A+sXr/myIEYBOjc+fO6t27d9ixTp06xfw3gdTX1+svf/mLvv71r6u4uPiinhMMBtW6dWu9/fbbkv75BdHTp09veMyePXti4psAzkVNTY2+8Y1vaPDgwQ3fFDBixIiGj9fW1p7zmwJi3T8hIUFf+9rXdOrUqU99XHO6/+f7JhC/9/ujL4BfsWJFw2OW//927tgltTCM47iLhsVBEIIosaElCAPbWhwaokEa2sLhzEFE4NDc1tQW4RD9B20NQaBTCkEnkIIgOuHeEIIREr87neBcSy73Cvecnu9ntJLe9z3Id/B9Wq1IfgH+qwAI4m9hYSF0I36YdrsdukAWlz34kwD8m2f+J6zfdd2B29/fiev5RxEBGDHBGJiTkxPd399rd3dXExMTen5+/t//2j/Z2tpSJpNRo9EIXefv9XqSpG63q2q1qqurK/m+r3q9ruXlZc3MzAyMxcjlcrq8vNTNzY1WVlYiOwbkd9VqVY1GQ09PT2q1WiqXy3Ic5/NsDw4OlMlkdHZ2pna7rc3NzS/HgsR1/ZL08fGhfD6vvb290Os/8fy73a48z5PneUokEjo8PJTneZ83XEd13mtra1pcXFSz2VSz2VShUIjMCIxhe9Dv97W+vq5cLqfb29vQ58L7+7sk6fHxUfv7+7q+vpbv+zo/P9f8/LyKxWIs9mDY+kf5zMdx/YHX11eNj4/r+Ph44O/jfv5RRwBG0NHRkWZnZ5VKpbS0tBQalRJX3w38PD09lST1ej2trq5qcnJSyWRS+Xxeruuq0+mE3uft7U3b29vKZrNKp9Mql8sDvxNVwZy3ZDKp6elpbWxs6O7u7vPnwWDgqakpjY2NqVQqhW5JS/FevyRdXFwokUjo4eEh9PpPPP96vf7lM++6rqTRnffLy4sqlYocx5HjOKpUKpEZgjtsD3zf//ZzIZgN2el0VCqVlM1mlUqlNDc3p52dnYFZeVHdg2HrH+UzH8f1B2q1mtLp9MBsPyn+5x91BCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxBCAAAIAxvwDHZMgi9rF7ggAAAABJRU5ErkJggg==\" width=\"640\">"
+ ],
+ "text/plain": [
+ "<IPython.core.display.HTML object>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[<matplotlib.lines.Line2D at 0x7f9ceadd2610>]"
+ ]
+ },
+ "execution_count": 201,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "fig, ax = plt.subplots()\n",
+ "ax.grid()\n",
+ "ax.plot(sorted(deltas)[:-2])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 72,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Average speed of rotation: 7.94 Hz / 476 rpm\n"
+ ]
+ }
+ ],
+ "source": [
+ "def fun(x, args):\n",
+ " deltas, = args # poor api\n",
+ " return np.sqrt(np.mean([ ((val + 0.5*x[0]) % x[0] - 0.5*x[0])**2 for val in deltas ]))\n",
+ "res = scipy.optimize.minimize(fun, 0.1, args=[sorted(deltas)[:-2]])\n",
+ "interval = np.abs(res.x[0])\n",
+ "print(f'Average speed of rotation: {1/interval:.2f} Hz / {60 / interval:.0f} rpm')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 78,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Packet length: 40\n"
+ ]
+ }
+ ],
+ "source": [
+ "packet_lengths = db.execute('SELECT LENGTH(data) FROM packets WHERE run_id=? GROUP BY LENGTH(data)', (last_run,)).fetchall()\n",
+ "assert len(packet_lengths) == 1\n",
+ "packet_len, = packet_lengths[0]\n",
+ "print('Packet length:', packet_len)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 97,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Very approximate lower bound on baudrate: 92476.55870407648 bd\n"
+ ]
+ }
+ ],
+ "source": [
+ "approx_baudrate = 1.0 / (np.mean([ x for x in deltas if x < interval*0.1 ]) / (packet_len*10))\n",
+ "print(f'Very approximate lower bound on baudrate: {approx_baudrate} bd')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 152,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def decode_packet(packet):\n",
+ " seq, *data, _crc = struct.unpack('<I16hI', packet)\n",
+ " return (seq, tuple(data))\n",
+ "\n",
+ "packets = sorted([ decode_packet(data) for data, in db.execute('SELECT data FROM packets WHERE run_id=?', (last_run,)) ])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 154,
+ "metadata": {
+ "scrolled": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "BUG: Duplicate sequence number\n",
+ "Sequence number: 241\n",
+ " (-53, -80, -12, 44, 11, -67, -61, 5, 47, -7, -74, -59, 15, 50, -18, -80)\n",
+ " (43, 52, -14, -63, -32, 45, 60, -8, -59, -25, 44, 67, -1, -61, -25, 36)\n",
+ "BUG: Duplicate sequence number\n",
+ "Sequence number: 242\n",
+ " (-59, -25, 44, 67, -1, -61, -25, 36, 72, -2, -54, 6, 96, 98, 26, -20)\n",
+ " (47, -7, -74, -59, 15, 50, -18, -80, -49, 28, 31, -38, -86, -41, 36, 27)\n",
+ "BUG: Duplicate sequence number\n",
+ "Sequence number: 243\n",
+ " (-49, 28, 31, -38, -86, -41, 36, 27, -50, -74, -18, 39, 26, -61, -74, -1)\n",
+ " (72, -2, -54, 6, 96, 98, 26, -20, 21, 88, 73, -14, -26, 50, 95, 54)\n",
+ "BUG: Duplicate sequence number\n",
+ "Sequence number: 244\n",
+ " (-50, -74, -18, 39, 26, -61, -74, -1, 53, 5, -67, -61, 8, 38, -13, -74)\n",
+ " (21, 88, 73, -14, -26, 50, 95, 54, -32, -24, 22, 92, 30, -29, -8, 62)\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "<ipython-input-154-25bc058f475a>:7: UserWarning: BUG: Duplicate sequence number 241 for 2 payloads!\n",
+ " warnings.warn(f'BUG: Duplicate sequence number {seq} for {len(set(le_packets))} payloads!')\n",
+ "<ipython-input-154-25bc058f475a>:7: UserWarning: BUG: Duplicate sequence number 242 for 2 payloads!\n",
+ " warnings.warn(f'BUG: Duplicate sequence number {seq} for {len(set(le_packets))} payloads!')\n",
+ "<ipython-input-154-25bc058f475a>:7: UserWarning: BUG: Duplicate sequence number 243 for 2 payloads!\n",
+ " warnings.warn(f'BUG: Duplicate sequence number {seq} for {len(set(le_packets))} payloads!')\n",
+ "<ipython-input-154-25bc058f475a>:7: UserWarning: BUG: Duplicate sequence number 244 for 2 payloads!\n",
+ " warnings.warn(f'BUG: Duplicate sequence number {seq} for {len(set(le_packets))} payloads!')\n"
+ ]
+ }
+ ],
+ "source": [
+ "# group packets by sequence number\n",
+ "by_seq = { k: list(g) for k, g in itertools.groupby(packets, key=lambda x: x[0]) }\n",
+ "for seq, le_packets in by_seq.items():\n",
+ " # make sure we only ever have one version of a packet with a particular sequence number (no CRC collisions)\n",
+ " if len(set(le_packets)) > 1:\n",
+ " # In test_run.sqlite3 run 2 this happens to coincide with the time I intentionally bumped the rotor... ?\n",
+ " warnings.warn(f'BUG: Duplicate sequence number {seq} for {len(set(le_packets))} payloads!')\n",
+ " print('BUG: Duplicate sequence number')\n",
+ " print('Sequence number:', seq)\n",
+ " for seq, data in set(le_packets):\n",
+ " print(' ', data)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 155,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Sequence number range: 1 ... 510\n"
+ ]
+ }
+ ],
+ "source": [
+ "seqs = list(by_seq)\n",
+ "print(f'Sequence number range: {min(seqs)} ... {max(seqs)}')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 166,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# FIXME this is only approximate, doesn't consider sequence numbers properly!!!\n",
+ "reassembled_values = np.array([ val for (_seq, values), *_rest in by_seq.values() for val in values[:8] ])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 206,
+ "metadata": {},
+ "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_dpi_ratio', { dpi_ratio: fig.ratio });\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",
+ " // 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",
+ " var cursor = msg['cursor'];\n",
+ " switch (cursor) {\n",
+ " case 0:\n",
+ " cursor = 'pointer';\n",
+ " break;\n",
+ " case 1:\n",
+ " cursor = 'default';\n",
+ " break;\n",
+ " case 2:\n",
+ " cursor = 'crosshair';\n",
+ " break;\n",
+ " case 3:\n",
+ " cursor = 'move';\n",
+ " break;\n",
+ " }\n",
+ " fig.rubberband_canvas.style.cursor = 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",
+ " /* 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",
+ " evt.data.type = 'image/png';\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",
+ " evt.data\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 http://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",
+ " * http://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.which === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.which;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.which !== 17) {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " if (event.altKey && event.which !== 18) {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " if (event.shiftKey && event.which !== 16) {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k';\n",
+ " value += event.which.toString();\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, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"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.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",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(msg['content']['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(\"<div id='\" + id + \"'></div>\");\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",
+ " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\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",
+ " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\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 = '<i class=\"fa ' + image + ' fa-lg\"></i>';\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 = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\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",
+ " var manager = IPython.notebook.keyboard_manager;\n",
+ " if (!manager) {\n",
+ " manager = IPython.keyboard_manager;\n",
+ " }\n",
+ "\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"
+ ],
+ "text/plain": [
+ "<IPython.core.display.Javascript object>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "<img src=\"data:image/png;base64,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7cI555wTcZmkBeDAgQPx1FNPtbt+37596NWrFwBg3bp16NevX6JXLWYYgMahn/pId7S7X0cB2ORmAGpId5ToV+9iABph/PjxuOyyyzB27FicfPLJ2LNnDwCguLgYp512WsRlkhaARUVF6NWrF4YNG4YdO3YAADweDx555BFkZGQAaI3BUzP3Y2UAACAASURBVE89NVmrGBUGoHHopz7SHe3u11EAHmMA6kh3lOhX5/IwAA0QCATwwgsvYODAgZg0aZJ+/QcffIBLL7004jJJ/RzAoqIiXHfddejWrRt69uyJk08+GT179sTMmTMBALNnz8a3v/3tZK5ipzAAjUM/9ZHuaHe/jgKwsTm29bW7XzyQ7ijRr7aJARhPXnrpJTz33HMRv2aLD4Lev38/5s2bh9zcXBw8eFC/ftWqVfj000+TuGadwwA0Dv3UR7qj3f20HeT3n17MAOwA6Y4S/Y4yABNGQgNw69at8Ptj+zNFQOsRQjt/WDQD0Dj0Ux/pjnb303aQA8ICsIEBqCPdUaJfzTE3A9AAdXV1mDRpEh5//HG8+eabyM/PR1NTU6fLJDQAu3fv3qWPdenVqxdKSkosXCNzMACNQz/1ke5od7/gALx1ytf65frjDEAN6Y4S/XYdamQAGmDw4ME466yz8Otf/xrf//730aNHD5x00kno378/7rrrrojLJDQAu3XrhgcffBCPPfZYTJOWlsYAFPSDHQz91Ee6o939ggMw+LPT6l0MQA3pjhL97vnPWgagAU477TRs2LBBv+x2u7Fp0ya89957GD16dMRlEhqAgwYNwnXXXdelqaqqSuQqdgkGoHHopz7SHe3upwegczFafH79cp3LE9PydveLB9IdJfoNemkZA9AA1157LTZt2tSlZWzxJhBVYQAah37qI93R7n4dBWBtEwNQQ7qjRL+hr67Un8t/nbGBARgjy5cvx29+8xs0NzfHvAwD0AQMQOPQT32kO9rdT9tJXuZcDJ8/oF8+ygDUke4o0e/G11bpz+VX83YxAGNk//79+MUvfoF+/frhiSeeQE5ODsrKyjpdhgFoAgagceinPtId7e4XHID+oACsOeaOaXm7+8UD6Y4S/RiAxrj66qvRr18//PnPf8b111+PM888E927d0efPn0wePDgiMswAE3AADQO/dRHuqPd/YIDMBBgAEZCuqNEPwagMU499VRs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\" width=\"640\">"
+ ],
+ "text/plain": [
+ "<IPython.core.display.HTML object>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Text(0, 0.5, '$a\\\\; [ms^{-1}]$')"
+ ]
+ },
+ "execution_count": 206,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "mems_lsb_per_g = 68 # LSBs per 1g for our accelerometer\n",
+ "\n",
+ "fig, ax = plt.subplots()\n",
+ "ax.plot(reassembled_values / mems_lsb_per_g )\n",
+ "ax.grid()\n",
+ "\n",
+ "g = 9.8066\n",
+ "g_to_ms = lambda x: x * g\n",
+ "ms_to_g = lambda x: x / g\n",
+ "\n",
+ "ax.set_ylabel(r'$a\\; [g]$')\n",
+ "secax_y = ax.secondary_yaxis(\n",
+ " 'right', functions=(g_to_ms, ms_to_g))\n",
+ "secax_y.set_ylabel(r'$a\\; [ms^{-1}]$')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 210,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Largest peak at 3.1162324649298596 Hz / 186.97394789579158 rpm\n"
+ ]
+ },
+ {
+ "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_dpi_ratio', { dpi_ratio: fig.ratio });\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",
+ " // 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",
+ " var cursor = msg['cursor'];\n",
+ " switch (cursor) {\n",
+ " case 0:\n",
+ " cursor = 'pointer';\n",
+ " break;\n",
+ " case 1:\n",
+ " cursor = 'default';\n",
+ " break;\n",
+ " case 2:\n",
+ " cursor = 'crosshair';\n",
+ " break;\n",
+ " case 3:\n",
+ " cursor = 'move';\n",
+ " break;\n",
+ " }\n",
+ " fig.rubberband_canvas.style.cursor = 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",
+ " /* 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",
+ " evt.data.type = 'image/png';\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",
+ " evt.data\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 http://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",
+ " * http://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.which === this._key) {\n",
+ " return;\n",
+ " } else {\n",
+ " this._key = event.which;\n",
+ " }\n",
+ " }\n",
+ " if (name === 'key_release') {\n",
+ " this._key = null;\n",
+ " }\n",
+ "\n",
+ " var value = '';\n",
+ " if (event.ctrlKey && event.which !== 17) {\n",
+ " value += 'ctrl+';\n",
+ " }\n",
+ " if (event.altKey && event.which !== 18) {\n",
+ " value += 'alt+';\n",
+ " }\n",
+ " if (event.shiftKey && event.which !== 16) {\n",
+ " value += 'shift+';\n",
+ " }\n",
+ "\n",
+ " value += 'k';\n",
+ " value += event.which.toString();\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, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
+ "\n",
+ "mpl.extensions = [\"eps\", \"jpeg\", \"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.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",
+ " // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
+ " ws.onmessage(msg['content']['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(\"<div id='\" + id + \"'></div>\");\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",
+ " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\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",
+ " '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\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 = '<i class=\"fa ' + image + ' fa-lg\"></i>';\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 = '<i class=\"fa fa-power-off icon-remove icon-large\"></i>';\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",
+ " var manager = IPython.notebook.keyboard_manager;\n",
+ " if (!manager) {\n",
+ " manager = IPython.keyboard_manager;\n",
+ " }\n",
+ "\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"
+ ],
+ "text/plain": [
+ "<IPython.core.display.Javascript object>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "<img src=\"data:image/png;base64,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\" width=\"640\">"
+ ],
+ "text/plain": [
+ "<IPython.core.display.HTML object>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Text(0.5, 0, 'Frequency [Hz]')"
+ ]
+ },
+ "execution_count": 210,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "sampling_rate = 10 # sps, set in firmware\n",
+ "\n",
+ "N = 1000\n",
+ "T = 1/sampling_rate\n",
+ "x = np.linspace(0.0, N*T, N)\n",
+ "y = reassembled_values[500:2000] / mems_lsb_per_g # cut out beginning and that time we tapped the thing\n",
+ "yf = scipy.fftpack.fft(y)\n",
+ "xf = np.linspace(0.0, 1/(2*T), N//2)\n",
+ "mag = 2/N * np.abs(yf[:N//2])\n",
+ "\n",
+ "peaks, _ = scipy.signal.find_peaks(mag, height=.1, distance=1/T)\n",
+ "assert peaks\n",
+ "\n",
+ "peak_data = sorted([ (-mag[idx], xf[idx]) for idx in peaks ])\n",
+ "largest_peak_f = peak_data[0][1]\n",
+ "print(f'Largest peak at {largest_peak_f:.2} Hz / {largest_peak_f * 60:.0} rpm')\n",
+ "\n",
+ "fig, ax = plt.subplots()\n",
+ "ax.grid()\n",
+ "ax.axvline(xf[peaks], color='orange')\n",
+ "ax.plot(xf, mag)\n",
+ "ax.set_ylabel('Magnitude [g]')\n",
+ "ax.set_xlabel('Frequency [Hz]')\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig, ax = plt.subplots()\n",
+ "ax.magnitude_spectrum(reassembled_values[500:2000]/mems_lsb_per_g)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 217,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Centrifugal acceleration at 3.12 Hz: 21.09 m/s^2 / 2.15 g\n"
+ ]
+ }
+ ],
+ "source": [
+ "r_mems = 55e-3 # radius of our sensor from the axis of rotation in m\n",
+ "f = largest_peak_f\n",
+ "omega = 2*np.pi*f # angular velocity\n",
+ "centrifugal_acceleration = omega**2 * r_mems # m/s^2\n",
+ "print(f'Centrifugal acceleration at {largest_peak_f:.2f} Hz: {centrifugal_acceleration:.2f} m/s^2 / {centrifugal_acceleration/g:.2f} g')"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "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.9.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/prototype/sensor-analysis/test_run.sqlite3 b/prototype/sensor-analysis/test_run.sqlite3
new file mode 100755
index 0000000..d177eab
--- /dev/null
+++ b/prototype/sensor-analysis/test_run.sqlite3
Binary files differ