From ead63f4eda739da1fb94b00d180b5b36ef9412e8 Mon Sep 17 00:00:00 2001 From: taga0512 Date: Wed, 16 Jun 2021 11:18:08 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=E3=81=A8=E3=82=8A=E3=81=82=E3=81=88?= =?UTF-8?q?=E3=81=9A=E5=90=8D=E5=89=8D=E3=81=A0=E3=81=91=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E7=94=A8=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ukr/taga/ukr_taga.ipynb | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 ukr/taga/ukr_taga.ipynb diff --git a/ukr/taga/ukr_taga.ipynb b/ukr/taga/ukr_taga.ipynb new file mode 100644 index 0000000..363fcab --- /dev/null +++ b/ukr/taga/ukr_taga.ipynb @@ -0,0 +1,6 @@ +{ + "cells": [], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} From 4bd548a69ac5c1ab1811c3b01cca4c34f98a4fc4 Mon Sep 17 00:00:00 2001 From: taga0512 Date: Wed, 30 Jun 2021 11:28:29 +0900 Subject: [PATCH 2/6] =?UTF-8?q?UKR=E3=81=AE=E5=AE=9F=E8=A3=85=E3=81=AE?= =?UTF-8?q?=E4=BB=AE=E5=AE=8C=E4=BA=86(=E6=9C=AA=E3=82=AF=E3=83=AD?= =?UTF-8?q?=E3=82=B9=E3=83=86=E3=82=B9=E3=83=88)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ukr/taga/ukr_taga.ipynb | 5206 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 5204 insertions(+), 2 deletions(-) diff --git a/ukr/taga/ukr_taga.ipynb b/ukr/taga/ukr_taga.ipynb index 363fcab..810b409 100644 --- a/ukr/taga/ukr_taga.ipynb +++ b/ukr/taga/ukr_taga.ipynb @@ -1,6 +1,5208 @@ { - "cells": [], - "metadata": {}, + "cells": [ + { + "cell_type": "code", + "execution_count": 115, + "id": "ae629221", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.animation as animation\n", + "from mpl_toolkits.mplot3d import Axes3D\n", + "from sklearn.datasets import make_swiss_roll\n", + "#%matplotlib inline\n", + "%matplotlib nbagg\n", + "#%matplotlib notebook\n", + "#from sample.data import gen_kura_data\n", + "N = 400\n", + "D = 3\n", + "L = 2\n", + "T = 100\n", + "eta = 1\n", + "sigma = 0.1\n", + "resolution = 20\n", + "\n", + "seed = 1\n", + "np.random.seed(seed)" + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "id": "8b4e65fe", + "metadata": {}, + "outputs": [], + "source": [ + "def gen_saddle_shape(num_samples, random_seed=None, noise_scale=0.05):\n", + " np.random.seed(seed)\n", + " z1 = np.random.uniform(low=-1, high=+1, size=(num_samples,))\n", + " z2 = np.random.uniform(low=-1, high=+1, size=(num_samples,))\n", + "\n", + " X = np.empty((num_samples, 3))\n", + " X[:, 0] = z1\n", + " X[:, 1] = z2\n", + " X[:, 2] = 0.5 * (z1**2 - z2**2)\n", + " X += np.random.normal(loc=0, scale=noise_scale, size=X.shape)\n", + "\n", + " return X" + ] + }, + { + "cell_type": "code", + "execution_count": 117, + "id": "1faed031", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(400, 3)\n" + ] + } + ], + "source": [ + "X = gen_saddle_shape(N)\n", + "print(X.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 118, + "id": "92acc5ca", + "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(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " 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": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "colormap = X[:, 0]\n", + "plt.close()\n", + "fig = plt.figure()\n", + "ax = fig.add_subplot(111, projection='3d')\n", + "ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=colormap)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 119, + "id": "6065a0e1", + "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(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " 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": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#真の潜在変数\n", + "plt.close()\n", + "fig = plt.figure()\n", + "plt.scatter(X[:, 0], X[:, 1], c=colormap, marker='x', linewidth=2)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 121, + "id": "00aa3d2a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(400, 2)\n" + ] + } + ], + "source": [ + "np.random.seed(seed)\n", + "Z = 2*np.random.rand(N, L)-1\n", + "Z *= 0.001\n", + "print(Z.shape)\n", + "#print(Z)" + ] + }, + { + "cell_type": "code", + "execution_count": 122, + "id": "1e648b82", + "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(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " 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": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 122, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "plt.close()\n", + "plt.scatter(Z[:, 0], Z[:, 1], c=colormap, marker='x', linewidth=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 123, + "id": "120ced52", + "metadata": {}, + "outputs": [], + "source": [ + "#データの距離\n", + "def distance(A, B):\n", + " #Aはデータ(こっちで用意するやつ),Bは構造(ノード)(NxN)\n", + " Dist = np.sum((A[:, None, :]-B[None, :, :])**2, axis=2)\n", + " return Dist" + ] + }, + { + "cell_type": "code", + "execution_count": 124, + "id": "7cf8dca0", + "metadata": {}, + "outputs": [], + "source": [ + "#写像の推定\n", + "def estimate_y(Z1, Z2, X, sigma):\n", + " #ガウス関数による重み行列k(z_n,z_i)(NxN)\n", + " k = np.exp((-1/(2*(sigma**2)))*distance(Z1, Z2))\n", + " #kをiでsumした行列K(z_n)(Nx1)\n", + " K = np.sum(k, axis=1) \n", + " Y = (k@X)/K[:,None]\n", + " return Y" + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "id": "85b15392", + "metadata": {}, + "outputs": [], + "source": [ + "#潜在変数の推定\n", + "def estimate_z(Z1, Z2, X, Y, sigma, eta):\n", + " #ガウス関数による重み行列k(z_n,z_i)(NxN)\n", + " k = np.exp((-1/(2*(sigma**2)))*distance(Z1, Z2))\n", + " #kをiでsumした行列K(z_n)(Nx1)\n", + " K = np.sum(k, axis=1)\n", + " \n", + " r = k/K[:,None]\n", + " d = Y[:,None,:] - X[None,:,:]\n", + " delta = Z1[:,None,:] - Z2[None,:,:]\n", + " \n", + " B = Y - X\n", + " A = np.sum(B[:,None,:]*d,axis=2)\n", + " \n", + " C = r*A\n", + " CC = C+C.T\n", + " E_bibun = (2/(N*(sigma**2)))*np.sum(CC[:,:,None]*delta,axis=1)\n", + " \n", + " Z_new = Z1 - eta*E_bibun\n", + " \n", + " return Z_new " + ] + }, + { + "cell_type": "code", + "execution_count": 126, + "id": "40564f73", + "metadata": {}, + "outputs": [], + "source": [ + "y_hist=np.zeros((T,N,D))\n", + "z_hist=np.zeros((T,N,L))\n", + "\n", + "for t in range(T):\n", + " Y = estimate_y(Z, Z, X, sigma)\n", + " Z = estimate_z(Z, Z, X, Y, sigma, eta)\n", + " z_hist[t]=Z\n", + " \n", + " A = np.linspace(Z[:,0].min(),Z[:,0].max(),resolution)\n", + " B = np.linspace(Z[:,1].min(),Z[:,1].max(),resolution)\n", + " XX, YY = np.meshgrid(A,B)\n", + " M = np.concatenate([XX.reshape(-1)[:,None], YY.reshape(-1)[:,None]], axis=1)\n", + " \n", + " Y_view = estimate_y(M, Z, X, sigma)\n", + " y_hist[t]=Y_view" + ] + }, + { + "cell_type": "code", + "execution_count": 127, + "id": "24c2e52b", + "metadata": {}, + "outputs": [], + "source": [ + "#潜在変数描画奴\n", + "def update(i):\n", + " ax1.cla()\n", + " ax2.cla()\n", + " Z = z_hist[i]\n", + " Y = y_hist[i]\n", + " \n", + " plt.title(f\"学習回数{i+1}回目\", fontname=\"MS Gothic\")\n", + " ax1.scatter(X[:, 0], X[:, 1], X[:, 2], c=colormap)\n", + " ax1.plot_wireframe(Y[:, 0].reshape(resolution,resolution), Y[:, 1].reshape(resolution,resolution), Y[:, 2].reshape(resolution,resolution), color='k')\n", + " #ax2.scatter(M[:, 0], M[:, 1], alpha=0.4, marker='D')\n", + " ax2.set_xlim(z_hist[:,:,0].min()-0.1,z_hist[:,:,0].max()+0.1)\n", + " ax2.set_ylim(z_hist[:,:,1].min()-0.1,z_hist[:,:,1].max()+0.1)\n", + " ax2.scatter(Z[:, 0], Z[:, 1], c=colormap, marker='x', linewidth=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 128, + "id": "472d4def", + "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(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " 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": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#潜在変数アニメーション\n", + "plt.close()\n", + "fig = plt.figure(figsize=(10,5))\n", + "ax1 = fig.add_subplot(121, projection='3d')\n", + "ax2 = fig.add_subplot(122)\n", + "viewer = animation.FuncAnimation(fig=fig, func=update, frames=range(T), repeat=True)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18c615a5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "4c89e24f", + "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(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " 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": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "#テスト用セル\n", + "plt.close()\n", + "fig = plt.figure(figsize=(10,5))\n", + "#fig = plt.figure()\n", + "ax1 = fig.add_subplot(121, projection='3d')\n", + "ax2 = fig.add_subplot(122, projection='3d')\n", + "#ax2 = fig.add_subplot(122)\n", + "#viewer = animation.FuncAnimation(fig=fig, func=update, frames=range(T), repeat=True)\n", + "ax1.scatter(X[:, 0], X[:, 1], X[:, 2], c=colormap)\n", + "ax2.scatter(X[:, 0], X[:, 1], X[:, 2], c=colormap)\n", + "A = np.linspace(-1,1,resolution)\n", + "B = np.linspace(-1,1,resolution)\n", + "XX, YY = np.meshgrid(A,B)\n", + "M = np.concatenate([XX.reshape(-1)[:,None], YY.reshape(-1)[:,None]], axis=1)\n", + "Z_test=np.zeros((N,L))\n", + "np.random.seed(seed)\n", + "z1 = np.random.uniform(low=-1, high=+1, size=(N,))\n", + "z2 = np.random.uniform(low=-1, high=+1, size=(N,))\n", + "Z_test[:,0]=z1\n", + "Z_test[:,1]=z2\n", + "#Y = estimate_y(Z, Z, X, sigma)\n", + "#Z = estimate_z(Z, Z, X, Y, sigma, eta)\n", + "ZZ = estimate_y(M ,Z_test, X, sigma)\n", + "ax1.plot_wireframe(ZZ[:,0].reshape(resolution,resolution), ZZ[:,1].reshape(resolution,resolution), ZZ[:,2].reshape(resolution,resolution), color='k')\n", + "ax2.plot_wireframe(XX, YY, ZZ[:,2].reshape(resolution,resolution), color='k')\n", + "#ax2.scatter(Z[:, 0], Z[:, 1], c=colormap, marker='x', linewidth=2)\n", + "#ax1.scatter(ZZ[:, 0], ZZ[:, 1], ZZ[:, 2], c='k')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "6e523840", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(10, 10)\n" + ] + } + ], + "source": [ + "a=np.ones((10,10,3))\n", + "print(a[:,:,0].shape)" + ] + } + ], + "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.8.8" + } + }, "nbformat": 4, "nbformat_minor": 5 } From 3fe028e875e780b18bf92085ef3b2a3e0b40c6bf Mon Sep 17 00:00:00 2001 From: taga0512 Date: Fri, 2 Jul 2021 17:22:05 +0900 Subject: [PATCH 3/6] =?UTF-8?q?.py=E3=81=B8=E3=81=AE=E7=A7=BB=E6=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ukr/taga/ukr.py | 92 ++++++++++++++++++++++++++++++++++++----- ukr/taga/ukr_taga.ipynb | 45 ++++++++++++-------- ukr/taga/visualizer.py | 30 +++++++++++++- 3 files changed, 138 insertions(+), 29 deletions(-) diff --git a/ukr/taga/ukr.py b/ukr/taga/ukr.py index b52deea..3707fd6 100644 --- a/ukr/taga/ukr.py +++ b/ukr/taga/ukr.py @@ -3,10 +3,74 @@ class UKR(object): - def __init__(self, _): - NotImplemented + def __init__(self, N, D, L, T, eta, sigma, resolution, seed): + self.N=N + self.D=D + self.L=L + self.T=T + self.eta = eta + self.sigma = sigma + self.resolution=resolution + self.seed=seed - def fit(self, _): + #データの距離 + def distance(self, A, B): + #Aはデータ(こっちで用意するやつ),Bは構造(ノード)(NxN) + Dist = np.sum((A[:, None, :]-B[None, :, :])**2, axis=2) + return Dist + + #写像の推定 + def estimate_y(self, Z1, Z2, X): + #ガウス関数による重み行列k(z_n,z_i)(NxN) + k = np.exp((-1/(2*(self.sigma**2)))*self.distance(Z1, Z2)) + #kをiでsumした行列K(z_n)(Nx1) + K = np.sum(k, axis=1) + Y = (k@X)/K[:,None] + return Y + + #潜在変数の推定 + def estimate_z(self, Z1, Z2, X, Y): + #ガウス関数による重み行列k(z_n,z_i)(NxN) + k = np.exp((-1/(2*(self.sigma**2)))*self.distance(Z1, Z2)) + #kをiでsumした行列K(z_n)(Nx1) + K = np.sum(k, axis=1) + + r = k/K[:,None] + d = Y[:,None,:] - X[None,:,:] + delta = Z1[:,None,:] - Z2[None,:,:] + + B = Y - X + A = np.sum(B[:,None,:]*d,axis=2) + + C = r*A + CC = C+C.T + E_bibun = (2/(N*(sigma**2)))*np.sum(CC[:,:,None]*delta,axis=1) + + Z_new = Z1 - self.eta*E_bibun + + return Z_new + + def fit(self, X): + np.random.seed(self.seed) + Z = 2*np.random.rand(self.N, self.L)-1 + Z *= 0.001 + + y_hist=np.zeros((self.T, self.N, self.D)) + z_hist=np.zeros((self.T, self.N, self.L)) + + for t in tqdm(range(self.T)): + Y = self.estimate_y(Z, Z, X) + Z = self.estimate_z(Z, Z, X, Y) + z_hist[t]=Z + + A = np.linspace(Z[:,0].min(),Z[:,0].max(),self.resolution) + B = np.linspace(Z[:,1].min(),Z[:,1].max(),self.resolution) + XX, YY = np.meshgrid(A,B) + M = np.concatenate([XX.reshape(-1)[:,None], YY.reshape(-1)[:,None]], axis=1) + + Y_view = self.estimate_y(M, Z, X) + y_hist[t]=Y_view + """ history = dict( E=np.zeros(()), Y=np.zeros(()), @@ -14,7 +78,7 @@ def fit(self, _): Z=np.zeros(()) ) - for epoch in tqdm(range(0)): + for epoch in tqdm(range(self.T)): ... history['Y'][epoch] = Y @@ -22,15 +86,23 @@ def fit(self, _): history['Z'][epoch] = Z history['E'][epoch] = ... return history + """ + return y_hist,z_hist if __name__ == '__main__': import data from visualizer import visualize_history + N = 400 + D = 3 + L = 2 + T = 100 + eta = 1 + sigma = 0.1 + resolution = 20 + seed = 1 - # 動かなさそうで動いてしまうプログラム - # お好きに書き直してください - X = data.gen_saddle_shape(num_samples=200, random_seed=0, noise_scale=0.05) - ukr = UKR(...) - history = ukr.fit(...) - visualize_history(X, history, save_gif=False) + X = data.gen_saddle_shape(num_samples=400, random_seed=1, noise_scale=0.05) + ukr = UKR(N, D, L, T, eta, sigma, resolution, seed) + y_hist,z_hist = ukr.fit(X) + visualize_history(X, z_hist, y_hist, colormap=X[:,0], resolution=20, T=100, save_gif=False, filename="tmp") diff --git a/ukr/taga/ukr_taga.ipynb b/ukr/taga/ukr_taga.ipynb index 810b409..e399d97 100644 --- a/ukr/taga/ukr_taga.ipynb +++ b/ukr/taga/ukr_taga.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 115, + "execution_count": 129, "id": "ae629221", "metadata": {}, "outputs": [], @@ -30,7 +30,7 @@ }, { "cell_type": "code", - "execution_count": 116, + "execution_count": 130, "id": "8b4e65fe", "metadata": {}, "outputs": [], @@ -51,7 +51,7 @@ }, { "cell_type": "code", - "execution_count": 117, + "execution_count": 131, "id": "1faed031", "metadata": {}, "outputs": [ @@ -70,7 +70,7 @@ }, { "cell_type": "code", - "execution_count": 118, + "execution_count": 132, "id": "92acc5ca", "metadata": {}, "outputs": [ @@ -1056,7 +1056,7 @@ }, { "cell_type": "code", - "execution_count": 119, + "execution_count": 133, "id": "6065a0e1", "metadata": {}, "outputs": [ @@ -2041,7 +2041,7 @@ }, { "cell_type": "code", - "execution_count": 121, + "execution_count": 134, "id": "00aa3d2a", "metadata": {}, "outputs": [ @@ -2063,7 +2063,7 @@ }, { "cell_type": "code", - "execution_count": 122, + "execution_count": 135, "id": "1e648b82", "metadata": {}, "outputs": [ @@ -3040,10 +3040,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 122, + "execution_count": 135, "metadata": {}, "output_type": "execute_result" } @@ -3055,7 +3055,7 @@ }, { "cell_type": "code", - "execution_count": 123, + "execution_count": 136, "id": "120ced52", "metadata": {}, "outputs": [], @@ -3069,7 +3069,7 @@ }, { "cell_type": "code", - "execution_count": 124, + "execution_count": 137, "id": "7cf8dca0", "metadata": {}, "outputs": [], @@ -3086,7 +3086,7 @@ }, { "cell_type": "code", - "execution_count": 125, + "execution_count": 138, "id": "85b15392", "metadata": {}, "outputs": [], @@ -3116,7 +3116,7 @@ }, { "cell_type": "code", - "execution_count": 126, + "execution_count": 139, "id": "40564f73", "metadata": {}, "outputs": [], @@ -3140,7 +3140,7 @@ }, { "cell_type": "code", - "execution_count": 127, + "execution_count": 140, "id": "24c2e52b", "metadata": {}, "outputs": [], @@ -3163,8 +3163,8 @@ }, { "cell_type": "code", - "execution_count": 128, - "id": "472d4def", + "execution_count": 144, + "id": "f27e2299", "metadata": {}, "outputs": [ { @@ -4128,7 +4128,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4148,6 +4148,17 @@ "plt.show()" ] }, + { + "cell_type": "code", + "execution_count": 145, + "id": "cd5a0812", + "metadata": {}, + "outputs": [], + "source": [ + "ani = animation.FuncAnimation(fig, update)\n", + "ani.save(f\"learning_sigma{sigma}_N{N}_eta{eta}_resolution{resolution}_seed{seed}.gif\", writer=\"pillow\")" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/ukr/taga/visualizer.py b/ukr/taga/visualizer.py index c8a05a8..d087c7e 100644 --- a/ukr/taga/visualizer.py +++ b/ukr/taga/visualizer.py @@ -1,2 +1,28 @@ -def visualize_history(X, history, save_gif=False, filename="tmp"): - NotImplemented +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.animation as animation + +def visualize_history(X, z_hist, y_hist, colormap, resolution, T, save_gif=False, filename="tmp"): + fig = plt.figure(figsize=(10,5)) + ax1 = fig.add_subplot(121, projection='3d') + ax2 = fig.add_subplot(122) + ani = animation.FuncAnimation(fig=fig, func=update, frames=range(T), repeat=True, fargs=(ax1, ax2, z_hist, y_hist, X, colormap, resolution)) + plt.show() + + if save_gif: + #ani.save(f"learning_sigma{sigma}_N{N}_eta{eta}_resolution{resolution}_seed{seed}.gif", writer="pillow") + ani.save(f"{filename}.mp4", writer='ffmpeg') + +def update(i, ax1, ax2, z_hist, y_hist, X, colormap, resolution): + ax1.cla() + ax2.cla() + Z = z_hist[i] + Y = y_hist[i] + + plt.title(f"学習回数{i+1}回目", fontname="MS Gothic") + ax1.scatter(X[:, 0], X[:, 1], X[:, 2], c=colormap) + ax1.plot_wireframe(Y[:, 0].reshape(resolution,resolution), Y[:, 1].reshape(resolution,resolution), Y[:, 2].reshape(resolution,resolution), color='k') + #ax2.scatter(M[:, 0], M[:, 1], alpha=0.4, marker='D') + ax2.set_xlim(z_hist[:,:,0].min()-0.1,z_hist[:,:,0].max()+0.1) + ax2.set_ylim(z_hist[:,:,1].min()-0.1,z_hist[:,:,1].max()+0.1) + ax2.scatter(Z[:, 0], Z[:, 1], c=colormap, marker='x', linewidth=2) \ No newline at end of file From 047f1aa13f9ba1e05f15b85b92dd1caac8ca8671 Mon Sep 17 00:00:00 2001 From: taga0512 Date: Mon, 5 Jul 2021 01:57:32 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=E3=82=AF=E3=83=AD=E3=82=B9=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=81=AE=E5=AE=9F=E6=96=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ukr/sample/cross_test_taga.py | 41 ++++++++++++ ukr/sample/ukr_taga.py | 100 +++++++++++++++++++++++++++++ ukr/taga/cross_test_taga.py | 39 ++++++++++++ ukr/taga/ukr.py | 70 +++++++++------------ ukr/taga/ukr_gaussian.py | 114 ++++++++++++++++++++++++++++++++++ ukr/taga/utils.py | 21 +++++++ 6 files changed, 346 insertions(+), 39 deletions(-) create mode 100644 ukr/sample/cross_test_taga.py create mode 100644 ukr/sample/ukr_taga.py create mode 100644 ukr/taga/cross_test_taga.py create mode 100644 ukr/taga/ukr_gaussian.py create mode 100644 ukr/taga/utils.py diff --git a/ukr/sample/cross_test_taga.py b/ukr/sample/cross_test_taga.py new file mode 100644 index 0000000..cab96c1 --- /dev/null +++ b/ukr/sample/cross_test_taga.py @@ -0,0 +1,41 @@ +import numpy as np +import data +from somf import UnsupervisedKernelRegression as SomfUKR +from ukr_uniform import UKR as UniformUKR +from ukr_gaussian import UKR as GaussianUKR +from ukr_taga import UKR as tagaUKR +from matplotlib import pyplot as plt + +seed = 1 + +if __name__ == '__main__': + N = 400 + X = data.gen_saddle_shape(num_samples=N, random_seed=seed, noise_scale=0.05) + + params_for_gukr = dict( + latent_dim=2, + eta=1, + rambda=0, + sigma=0.1, + scale=1e-2 + ) + params_for_tagaukr = dict( + N=N, + D=3, + L=2, + eta=1, + sigma=0.1, + scale=1e-2 + ) + + ukr_1 = GaussianUKR(**params_for_gukr) + history_1 = ukr_1.fit(X, num_epoch=100, seed=seed, f_resolution=10) + ukr_2 = tagaUKR(**params_for_tagaukr) + history_2 = ukr_2.fit(X, num_epoch=100, seed=seed, resolution=10) + + is_Y_close = np.allclose(history_1['Y'], history_2['Y']) + is_f_close = np.allclose(history_1['f'], history_2['f']) + is_Z_close = np.allclose(history_1['Z'], history_2['Z']) + print("Y: ", is_Y_close) + print("f: ", is_f_close) + print("Z: ", is_Z_close) \ No newline at end of file diff --git a/ukr/sample/ukr_taga.py b/ukr/sample/ukr_taga.py new file mode 100644 index 0000000..1749248 --- /dev/null +++ b/ukr/sample/ukr_taga.py @@ -0,0 +1,100 @@ +import numpy as np +from tqdm import tqdm +from utils import make_grid + +seed = 1 + +class UKR(object): + def __init__(self, N, D, L, eta, sigma, scale): + self.N = N + self.D = D + self.L = L + self.eta = eta + self.sigma = sigma + self.scale = scale + + #データの距離 + def distance(self, A, B): + #Aはデータ(こっちで用意するやつ),Bは構造(ノード)(NxN) + Dist = np.sum((A[:, None, :]-B[None, :, :])**2, axis=2) + return Dist + + #写像の推定 + def estimate_y(self, Z1, Z2, X): + #ガウス関数による重み行列k(z_n,z_i)(NxN) + k = np.exp((-1/(2*(self.sigma**2)))*self.distance(Z1, Z2)) + #kをiでsumした行列K(z_n)(Nx1) + K = np.sum(k, axis=1) + Y = (k@X)/K[:,None] + return Y + + #潜在変数の推定 + def estimate_z(self, Z1, Z2, X, Y): + #ガウス関数による重み行列k(z_n,z_i)(NxN) + k = np.exp((-1/(2*(self.sigma**2)))*self.distance(Z1, Z2)) + #kをiでsumした行列K(z_n)(Nx1) + K = np.sum(k, axis=1) + + r = k/K[:,None] + d = Y[:,None,:] - X[None,:,:] + delta = Z1[:,None,:] - Z2[None,:,:] + + B = Y - X + A = np.sum(B[:,None,:]*d,axis=2) + + C = r*A + CC = C+C.T + E_bibun = (2/(self.N*(self.sigma**2)))*np.sum(CC[:,:,None]*delta,axis=1) + + Z_new = Z1 - self.eta*E_bibun + + return Z_new + + def fit(self, X, num_epoch, seed, resolution): + np.random.seed(seed) + Z = np.random.normal(scale=self.scale, size=(self.N, self.L)) + + history = dict( + Y=np.zeros((num_epoch, self.N, self.D)), + f=np.zeros((num_epoch, resolution**self.L, self.D)), + Z=np.zeros((num_epoch, self.N, self.L)) + ) + + for epoch in tqdm(range(num_epoch)): + Y = self.estimate_y(Z, Z, X) + Z = self.estimate_z(Z, Z, X, Y) + + """ + A = np.linspace(Z[:,0].min(), Z[:,0].max(), resolution) + B = np.linspace(Z[:,1].min(), Z[:,1].max(), resolution) + XX, YY = np.meshgrid(A,B) + M = np.concatenate([XX.reshape(-1)[:,None], YY.reshape(-1)[:,None]], axis=1) + """ + #sampleプログラムに合わせた初期値,make_gridの中身はよくわかってない + Z_new = make_grid(resolution, + bounds=(np.min(Z), np.max(Z)), + dim=self.L) + f = self.estimate_y(Z_new, Z, X) + + history['Y'][epoch] = Y + history['f'][epoch] = f + history['Z'][epoch] = Z + + return history + + +if __name__ == '__main__': + import data + from visualizer import visualize_history + N = 400 + D = 3 + L = 2 + T = 100 + eta = 1 + sigma = 0.1 + resolution = 20 + + X = data.gen_saddle_shape(num_samples=400, random_seed=seed, noise_scale=0.05) + ukr = UKR(N, D, L, eta, sigma, scale=1e-2) + history = ukr.fit(X, num_epoch=T, seed=seed, resolution=resolution) + visualize_history(X, history['Z'], history['f'], colormap=X[:,0], resolution=20, T=100, save_gif=False, filename="tmp") diff --git a/ukr/taga/cross_test_taga.py b/ukr/taga/cross_test_taga.py new file mode 100644 index 0000000..7bc3aca --- /dev/null +++ b/ukr/taga/cross_test_taga.py @@ -0,0 +1,39 @@ +import numpy as np +import data +from ukr_gaussian import UKR as GaussianUKR +from ukr import UKR as tagaUKR +from matplotlib import pyplot as plt + +seed = 1 + +if __name__ == '__main__': + N = 400 + X = data.gen_saddle_shape(num_samples=N, random_seed=seed, noise_scale=0.05) + + params_for_gukr = dict( + latent_dim=2, + eta=1, + rambda=0, + sigma=0.1, + scale=1e-2 + ) + params_for_tagaukr = dict( + N=N, + D=3, + L=2, + eta=1, + sigma=0.1, + scale=1e-2 + ) + + ukr_1 = GaussianUKR(**params_for_gukr) + history_1 = ukr_1.fit(X, num_epoch=100, seed=seed, f_resolution=10) + ukr_2 = tagaUKR(**params_for_tagaukr) + history_2 = ukr_2.fit(X, num_epoch=100, seed=seed, resolution=10) + + is_Y_close = np.allclose(history_1['Y'], history_2['Y']) + is_f_close = np.allclose(history_1['f'], history_2['f']) + is_Z_close = np.allclose(history_1['Z'], history_2['Z']) + print("Y: ", is_Y_close) + print("f: ", is_f_close) + print("Z: ", is_Z_close) \ No newline at end of file diff --git a/ukr/taga/ukr.py b/ukr/taga/ukr.py index 3707fd6..1749248 100644 --- a/ukr/taga/ukr.py +++ b/ukr/taga/ukr.py @@ -1,17 +1,17 @@ import numpy as np from tqdm import tqdm +from utils import make_grid +seed = 1 class UKR(object): - def __init__(self, N, D, L, T, eta, sigma, resolution, seed): - self.N=N - self.D=D - self.L=L - self.T=T + def __init__(self, N, D, L, eta, sigma, scale): + self.N = N + self.D = D + self.L = L self.eta = eta self.sigma = sigma - self.resolution=resolution - self.seed=seed + self.scale = scale #データの距離 def distance(self, A, B): @@ -44,50 +44,43 @@ def estimate_z(self, Z1, Z2, X, Y): C = r*A CC = C+C.T - E_bibun = (2/(N*(sigma**2)))*np.sum(CC[:,:,None]*delta,axis=1) + E_bibun = (2/(self.N*(self.sigma**2)))*np.sum(CC[:,:,None]*delta,axis=1) Z_new = Z1 - self.eta*E_bibun return Z_new - def fit(self, X): - np.random.seed(self.seed) - Z = 2*np.random.rand(self.N, self.L)-1 - Z *= 0.001 + def fit(self, X, num_epoch, seed, resolution): + np.random.seed(seed) + Z = np.random.normal(scale=self.scale, size=(self.N, self.L)) - y_hist=np.zeros((self.T, self.N, self.D)) - z_hist=np.zeros((self.T, self.N, self.L)) + history = dict( + Y=np.zeros((num_epoch, self.N, self.D)), + f=np.zeros((num_epoch, resolution**self.L, self.D)), + Z=np.zeros((num_epoch, self.N, self.L)) + ) - for t in tqdm(range(self.T)): + for epoch in tqdm(range(num_epoch)): Y = self.estimate_y(Z, Z, X) Z = self.estimate_z(Z, Z, X, Y) - z_hist[t]=Z - A = np.linspace(Z[:,0].min(),Z[:,0].max(),self.resolution) - B = np.linspace(Z[:,1].min(),Z[:,1].max(),self.resolution) + """ + A = np.linspace(Z[:,0].min(), Z[:,0].max(), resolution) + B = np.linspace(Z[:,1].min(), Z[:,1].max(), resolution) XX, YY = np.meshgrid(A,B) M = np.concatenate([XX.reshape(-1)[:,None], YY.reshape(-1)[:,None]], axis=1) - - Y_view = self.estimate_y(M, Z, X) - y_hist[t]=Y_view - """ - history = dict( - E=np.zeros(()), - Y=np.zeros(()), - f=np.zeros(()), - Z=np.zeros(()) - ) - - for epoch in tqdm(range(self.T)): - ... + """ + #sampleプログラムに合わせた初期値,make_gridの中身はよくわかってない + Z_new = make_grid(resolution, + bounds=(np.min(Z), np.max(Z)), + dim=self.L) + f = self.estimate_y(Z_new, Z, X) history['Y'][epoch] = Y history['f'][epoch] = f history['Z'][epoch] = Z - history['E'][epoch] = ... + return history - """ - return y_hist,z_hist if __name__ == '__main__': @@ -100,9 +93,8 @@ def fit(self, X): eta = 1 sigma = 0.1 resolution = 20 - seed = 1 - X = data.gen_saddle_shape(num_samples=400, random_seed=1, noise_scale=0.05) - ukr = UKR(N, D, L, T, eta, sigma, resolution, seed) - y_hist,z_hist = ukr.fit(X) - visualize_history(X, z_hist, y_hist, colormap=X[:,0], resolution=20, T=100, save_gif=False, filename="tmp") + X = data.gen_saddle_shape(num_samples=400, random_seed=seed, noise_scale=0.05) + ukr = UKR(N, D, L, eta, sigma, scale=1e-2) + history = ukr.fit(X, num_epoch=T, seed=seed, resolution=resolution) + visualize_history(X, history['Z'], history['f'], colormap=X[:,0], resolution=20, T=100, save_gif=False, filename="tmp") diff --git a/ukr/taga/ukr_gaussian.py b/ukr/taga/ukr_gaussian.py new file mode 100644 index 0000000..c7c5e52 --- /dev/null +++ b/ukr/taga/ukr_gaussian.py @@ -0,0 +1,114 @@ +# Copyright 2021 tanacchi +# +# Permission is hereby granted, free of charge, +# to any person obtaining a copy of this software +# and associated documentation files (the "Software"), +# to deal in the Software without restriction, +# including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +import numpy as np +from scipy.spatial import distance as dist +try: + from scipy.spatial import distance + cdist = distance.cdist +except ModuleNotFoundError: + print("scipy is not installed, so the custom cdist defined.") + cdist = lambda XA, XB: np.sum((XA[:, None] - XB[None, :])**2, axis=2) + +from tqdm import tqdm + +from utils import make_grid + + +class UKR(object): + def __init__(self, latent_dim, eta, rambda, sigma, scale): + self.L = latent_dim + self.η = eta + self.σ = sigma + self.λ = rambda + self.scale = scale + self.kernel = lambda Z1, Z2: np.exp(-cdist(Z1, Z2)**2 / (2 * self.σ**2)) + + def fit(self, X, num_epoch=50, seed=0, f_resolution=10): + N, D = X.shape + + np.random.seed(seed) + Z = np.random.normal(scale=self.scale, size=(N, self.L)) + history = dict( + Y=np.zeros((num_epoch, N, D)), + f=np.zeros((num_epoch, f_resolution**self.L, D)), + Z=np.zeros((num_epoch, N, self.L))) + + for epoch in tqdm(range(num_epoch)): + Y, R = self.estimate_f(X, Z) + Z = self.estimate_e(X, Y, Z, R) + + Z_new = make_grid(f_resolution, + bounds=(np.min(Z), np.max(Z)), + dim=self.L) + f, _ = self.estimate_f(X, Z_new, Z) + + history['Y'][epoch] = Y + history['f'][epoch] = f + history['Z'][epoch] = Z + + return history + + def estimate_f(self, X, Z1, Z2=None): + Z2 = np.copy(Z1) if Z2 is None else Z2 + kernels = self.kernel(Z1, Z2) + R = kernels / np.sum(kernels, axis=1, keepdims=True) + return R @ X, R + + def estimate_e(self, X, Y, Z, R): + d_ii = Y - X + d_in = Y[:, np.newaxis, :] - X[np.newaxis, :, :] + d_ni = - d_in + δ_in = Z[:, np.newaxis, :] - Z[np.newaxis, :, :] + δ_ni = - δ_in + + diff_left = np.einsum("ni,nd,nid,nil->nl", + R, + d_ii, + d_ni, + δ_ni, + optimize=True) + diff_right = np.einsum("in,id,ind,inl->nl", + R, + d_ii, + d_in, + δ_in, + optimize=True) + #正則化項のない式に変更 + #diff = 2 * (diff_left - diff_right) / X.shape[0] + diff = 2 * (diff_left - diff_right) / (X.shape[0] * self.σ**2) + #Z -= self.η * (diff + 2 * self.λ * Z / X.shape[0]) + Z -= self.η * diff + return Z + + +if __name__ == '__main__': + from data import gen_saddle_shape + from visualizer import visualize_history + + + X = gen_saddle_shape(num_samples=200, random_seed=1, noise_scale=0.001) + ukr = UKR(latent_dim=2, eta=10, rambda=1e-1, sigma=1, scale=1e-2) + history = ukr.fit(X, num_epoch=200) + visualize_history(X, history['f'], history['Z'], save_gif=False) diff --git a/ukr/taga/utils.py b/ukr/taga/utils.py new file mode 100644 index 0000000..24d52f5 --- /dev/null +++ b/ukr/taga/utils.py @@ -0,0 +1,21 @@ +import numpy as np + + +def make_grid(resolution, bounds=(-1, +1), dim=2): + mesh, step = np.linspace(bounds[0], + bounds[1], + resolution, + endpoint=False, + retstep=True) + mesh += step / 2.0 + grid = np.meshgrid(*[np.copy(mesh)]*dim) + return np.dstack(grid).reshape(-1, dim) + + +if __name__ == '__main__': + grid = make_grid(5, dim=3) + print(f"3D:\n{grid}\n") + grid = make_grid(5, dim=2) + print(f"2D:\n{grid}\n") + grid = make_grid(5, dim=1) + print(f"1D:\n{grid}\n") From b312501139bed0bc437198231ead412ca5d5603a Mon Sep 17 00:00:00 2001 From: taga0512 Date: Mon, 5 Jul 2021 01:59:55 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=E3=82=AF=E3=83=AD=E3=82=B9=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=81=AE=E5=AE=9F=E6=96=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ukr/sample/cross_test_taga.py | 41 -------------- ukr/sample/ukr_taga.py | 100 ---------------------------------- 2 files changed, 141 deletions(-) delete mode 100644 ukr/sample/cross_test_taga.py delete mode 100644 ukr/sample/ukr_taga.py diff --git a/ukr/sample/cross_test_taga.py b/ukr/sample/cross_test_taga.py deleted file mode 100644 index cab96c1..0000000 --- a/ukr/sample/cross_test_taga.py +++ /dev/null @@ -1,41 +0,0 @@ -import numpy as np -import data -from somf import UnsupervisedKernelRegression as SomfUKR -from ukr_uniform import UKR as UniformUKR -from ukr_gaussian import UKR as GaussianUKR -from ukr_taga import UKR as tagaUKR -from matplotlib import pyplot as plt - -seed = 1 - -if __name__ == '__main__': - N = 400 - X = data.gen_saddle_shape(num_samples=N, random_seed=seed, noise_scale=0.05) - - params_for_gukr = dict( - latent_dim=2, - eta=1, - rambda=0, - sigma=0.1, - scale=1e-2 - ) - params_for_tagaukr = dict( - N=N, - D=3, - L=2, - eta=1, - sigma=0.1, - scale=1e-2 - ) - - ukr_1 = GaussianUKR(**params_for_gukr) - history_1 = ukr_1.fit(X, num_epoch=100, seed=seed, f_resolution=10) - ukr_2 = tagaUKR(**params_for_tagaukr) - history_2 = ukr_2.fit(X, num_epoch=100, seed=seed, resolution=10) - - is_Y_close = np.allclose(history_1['Y'], history_2['Y']) - is_f_close = np.allclose(history_1['f'], history_2['f']) - is_Z_close = np.allclose(history_1['Z'], history_2['Z']) - print("Y: ", is_Y_close) - print("f: ", is_f_close) - print("Z: ", is_Z_close) \ No newline at end of file diff --git a/ukr/sample/ukr_taga.py b/ukr/sample/ukr_taga.py deleted file mode 100644 index 1749248..0000000 --- a/ukr/sample/ukr_taga.py +++ /dev/null @@ -1,100 +0,0 @@ -import numpy as np -from tqdm import tqdm -from utils import make_grid - -seed = 1 - -class UKR(object): - def __init__(self, N, D, L, eta, sigma, scale): - self.N = N - self.D = D - self.L = L - self.eta = eta - self.sigma = sigma - self.scale = scale - - #データの距離 - def distance(self, A, B): - #Aはデータ(こっちで用意するやつ),Bは構造(ノード)(NxN) - Dist = np.sum((A[:, None, :]-B[None, :, :])**2, axis=2) - return Dist - - #写像の推定 - def estimate_y(self, Z1, Z2, X): - #ガウス関数による重み行列k(z_n,z_i)(NxN) - k = np.exp((-1/(2*(self.sigma**2)))*self.distance(Z1, Z2)) - #kをiでsumした行列K(z_n)(Nx1) - K = np.sum(k, axis=1) - Y = (k@X)/K[:,None] - return Y - - #潜在変数の推定 - def estimate_z(self, Z1, Z2, X, Y): - #ガウス関数による重み行列k(z_n,z_i)(NxN) - k = np.exp((-1/(2*(self.sigma**2)))*self.distance(Z1, Z2)) - #kをiでsumした行列K(z_n)(Nx1) - K = np.sum(k, axis=1) - - r = k/K[:,None] - d = Y[:,None,:] - X[None,:,:] - delta = Z1[:,None,:] - Z2[None,:,:] - - B = Y - X - A = np.sum(B[:,None,:]*d,axis=2) - - C = r*A - CC = C+C.T - E_bibun = (2/(self.N*(self.sigma**2)))*np.sum(CC[:,:,None]*delta,axis=1) - - Z_new = Z1 - self.eta*E_bibun - - return Z_new - - def fit(self, X, num_epoch, seed, resolution): - np.random.seed(seed) - Z = np.random.normal(scale=self.scale, size=(self.N, self.L)) - - history = dict( - Y=np.zeros((num_epoch, self.N, self.D)), - f=np.zeros((num_epoch, resolution**self.L, self.D)), - Z=np.zeros((num_epoch, self.N, self.L)) - ) - - for epoch in tqdm(range(num_epoch)): - Y = self.estimate_y(Z, Z, X) - Z = self.estimate_z(Z, Z, X, Y) - - """ - A = np.linspace(Z[:,0].min(), Z[:,0].max(), resolution) - B = np.linspace(Z[:,1].min(), Z[:,1].max(), resolution) - XX, YY = np.meshgrid(A,B) - M = np.concatenate([XX.reshape(-1)[:,None], YY.reshape(-1)[:,None]], axis=1) - """ - #sampleプログラムに合わせた初期値,make_gridの中身はよくわかってない - Z_new = make_grid(resolution, - bounds=(np.min(Z), np.max(Z)), - dim=self.L) - f = self.estimate_y(Z_new, Z, X) - - history['Y'][epoch] = Y - history['f'][epoch] = f - history['Z'][epoch] = Z - - return history - - -if __name__ == '__main__': - import data - from visualizer import visualize_history - N = 400 - D = 3 - L = 2 - T = 100 - eta = 1 - sigma = 0.1 - resolution = 20 - - X = data.gen_saddle_shape(num_samples=400, random_seed=seed, noise_scale=0.05) - ukr = UKR(N, D, L, eta, sigma, scale=1e-2) - history = ukr.fit(X, num_epoch=T, seed=seed, resolution=resolution) - visualize_history(X, history['Z'], history['f'], colormap=X[:,0], resolution=20, T=100, save_gif=False, filename="tmp") From 4fa130422a4d5af23a03f14784d5207afd5ef555 Mon Sep 17 00:00:00 2001 From: taga0512 Date: Tue, 13 Jul 2021 14:39:23 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=E3=82=AC=E3=82=A6=E3=82=B9=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E3=81=AB=E3=82=88=E3=82=8B=E4=BA=8B=E5=89=8D=E5=88=86?= =?UTF-8?q?=E5=B8=83=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ukr/sample/ukr_gaussian.py | 8 +-- ukr/taga/data.py | 16 ++++++ ukr/taga/ukr_gauss.py | 114 +++++++++++++++++++++++++++++++++++++ ukr/taga/ukr_taga.ipynb | 87 ++++++++++++++++++++++++++++ ukr/taga/visualizer.py | 89 +++++++++++++++++++++++------ 5 files changed, 292 insertions(+), 22 deletions(-) create mode 100644 ukr/taga/ukr_gauss.py diff --git a/ukr/sample/ukr_gaussian.py b/ukr/sample/ukr_gaussian.py index 2d0da59..5535458 100644 --- a/ukr/sample/ukr_gaussian.py +++ b/ukr/sample/ukr_gaussian.py @@ -95,7 +95,7 @@ def estimate_e(self, X, Y, Z, R): d_in, δ_in, optimize=True) - diff = 2 * (diff_left - diff_right) / X.shape[0] + diff = 2 * (diff_left - diff_right) / (X.shape[0] * self.σ**2) Z -= self.η * (diff + 2 * self.λ * Z / X.shape[0]) return Z @@ -105,7 +105,7 @@ def estimate_e(self, X, Y, Z, R): from visualizer import visualize_history - X = gen_saddle_shape(num_samples=200, random_seed=1, noise_scale=0.001) - ukr = UKR(latent_dim=2, eta=10, rambda=1e-1, sigma=1, scale=1e-2) - history = ukr.fit(X, num_epoch=200) + X = gen_saddle_shape(num_samples=400, random_seed=1, noise_scale=0.05) + ukr = UKR(latent_dim=1, eta=5, rambda=0, sigma=0.1, scale=1e-2) + history = ukr.fit(X, num_epoch=100) visualize_history(X, history['f'], history['Z'], save_gif=False) diff --git a/ukr/taga/data.py b/ukr/taga/data.py index 18fc39e..b80539c 100644 --- a/ukr/taga/data.py +++ b/ukr/taga/data.py @@ -28,6 +28,22 @@ def gen_2d_sin_curve(num_samples, random_seed=None, noise_scale=0.01): return X +def gen_helix(num_samples, random_seed=None, noise_scale=0.05): + np.random.seed(random_seed) + z = np.random.uniform(low=-1, high=+1, size=(num_samples,)) + + pi = np.pi + t = z * pi * 3 + + X = np.empty((num_samples,3)) + X[:, 0] = np.cos(t) + X[:, 1] = np.sin(t) + X[:, 2] = t + X += np.random.normal(loc=0, scale=noise_scale, size=X.shape) + + return X + + if __name__ == '__main__': from matplotlib import pyplot as plt diff --git a/ukr/taga/ukr_gauss.py b/ukr/taga/ukr_gauss.py new file mode 100644 index 0000000..6ae1230 --- /dev/null +++ b/ukr/taga/ukr_gauss.py @@ -0,0 +1,114 @@ +from typing import OrderedDict +import numpy as np +from tqdm import tqdm +from utils import make_grid +from collections import OrderedDict + +seed = 1 + +class UKR(object): + def __init__(self, N, D, L, eta, sigma, rambda, scale): + self.N = N + self.D = D + self.L = L + self.eta = eta + self.sigma = sigma + self.rambda = rambda + self.scale = scale + + #データの距離 + def distance(self, A, B): + #Aはデータ(こっちで用意するやつ),Bは構造(ノード) + Dist = np.sum((A[:, None]-B[None, :])**2, axis=2) + return Dist + + #写像の推定 + def estimate_y(self, Z1, Z2, X): + #ガウス関数による重み行列k(z_n,z_i)(NxN) + k = np.exp((-1/(2*(self.sigma**2)))*self.distance(Z1, Z2)) + #kをiでsumした行列K(z_n)(Nx1) + K = np.sum(k, axis=1) + Y = (k@X)/K[:,None] + return Y + + #潜在変数の推定 + def estimate_z(self, Z1, Z2, X, Y): + #ガウス関数による重み行列k(z_n,z_i)(NxN) + k = np.exp((-1/(2*(self.sigma**2)))*self.distance(Z1, Z2)) + #kをiでsumした行列K(z_n)(Nx1) + K = np.sum(k, axis=1) + + r = k/K[:,None] + d = Y[:,None,:] - X[None,:,:] + delta = Z1[:,None,:] - Z2[None,:,:] + + B = Y - X + A = np.sum(B[:,None,:]*d,axis=2) + + C = r*A + CC = C+C.T + E_bibun = (2/(self.N*(self.sigma**2)))*np.sum(CC[:,:,None]*delta,axis=1) + + Z_new = Z1 - self.eta*(E_bibun + 2 * self.rambda * Z1 / self.N) + + return Z_new + + def fit(self, X, num_epoch, seed, resolution): + np.random.seed(seed) + Z = np.random.normal(scale=self.scale, size=(self.N, self.L)) + + history = dict( + E=np.zeros((num_epoch, )), + Y=np.zeros((num_epoch, self.N, self.D)), + f=np.zeros((num_epoch, resolution**self.L, self.D)), + Z=np.zeros((num_epoch, self.N, self.L)) + ) + + with tqdm(range(num_epoch)) as pbar: + for epoch,i in enumerate(pbar): + Y = self.estimate_y(Z, Z, X) + Z = self.estimate_z(Z, Z, X, Y) + + """ + A = np.linspace(Z[:,0].min(), Z[:,0].max(), resolution) + B = np.linspace(Z[:,1].min(), Z[:,1].max(), resolution) + XX, YY = np.meshgrid(A,B) + M = np.concatenate([XX.reshape(-1)[:,None], YY.reshape(-1)[:,None]], axis=1) + """ + #sampleプログラムに合わせた初期値,make_gridの中身はよくわかってない + Z_new = make_grid(resolution, + bounds=(np.min(Z), np.max(Z)), + dim=self.L) + f = self.estimate_y(Z_new, Z, X) + + #E = (1/self.N) * np.sum(np.sum((Y - X)**2, axis=1) + self.rambda * np.sum(Z**2, axis=1), axis=0) + E = (1/self.N) * (np.sum((Y - X)**2) + self.rambda * np.sum(Z**2)) + + pbar.set_postfix(OrderedDict(loss=f'{E:.3f}')) + + history['E'][epoch] = E + history['Y'][epoch] = Y + history['f'][epoch] = f + history['Z'][epoch] = Z + + return history + + +if __name__ == '__main__': + import data + from visualizer import visualize_history + N = 400 + D = 3 + L = 2 + T = 100 + eta = 10 + sigma = 0.1 + rambda = 0 + scale=1e-8 + resolution = 20 + + X = data.gen_saddle_shape(num_samples=N, random_seed=seed, noise_scale=0.05) + ukr = UKR(N, D, L, eta, sigma, rambda, scale) + history = ukr.fit(X, num_epoch=T, seed=seed, resolution=resolution) + visualize_history(X, history['Z'], history['f'], history['E'], color=X[:, 0], resolution=resolution, save_gif=True, + filename=f"saddle_N_{N}_D_{D}_L_{L}_T_{T}_eta_{eta}_sigma_{sigma}_scale_{scale}_resolution_{resolution}_seed_{seed}") diff --git a/ukr/taga/ukr_taga.ipynb b/ukr/taga/ukr_taga.ipynb index e399d97..0029f50 100644 --- a/ukr/taga/ukr_taga.ipynb +++ b/ukr/taga/ukr_taga.ipynb @@ -5193,6 +5193,93 @@ "a=np.ones((10,10,3))\n", "print(a[:,:,0].shape)" ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "db30d18e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from mpl_toolkits.mplot3d import Axes3D\n", + "%matplotlib inline\n", + "\n", + "# Figureを追加\n", + "fig = plt.figure(figsize = (8, 8))\n", + "\n", + "# 3DAxesを追加\n", + "ax = fig.add_subplot(111, projection='3d')\n", + "\n", + "# Axesのタイトルを設定\n", + "ax.set_title(\"Helix\", size = 20)\n", + "\n", + "# 軸ラベルを設定\n", + "ax.set_xlabel(\"x\", size = 14)\n", + "ax.set_ylabel(\"y\", size = 14)\n", + "ax.set_zlabel(\"z\", size = 14)\n", + "\n", + "# 軸目盛を設定\n", + "ax.set_xticks([-1.0, -0.5, 0.0, 0.5, 1.0])\n", + "ax.set_yticks([-1.0, -0.5, 0.0, 0.5, 1.0])\n", + "\n", + "random_seed=1\n", + "num_samples=1000\n", + "np.random.seed(random_seed)\n", + "z_1 = np.random.uniform(low=-1, high=+1, size=(num_samples,))\n", + "#z_1=np.random.rand(num_samples)\n", + "\n", + "# 円周率の定義\n", + "pi = np.pi\n", + "\n", + "# パラメータ分割数\n", + "n = 100\n", + "\n", + "# パラメータtを作成\n", + "#t = np.linspace(-6*pi, 6*pi, n)\n", + "t=z_1*3*pi\n", + "\n", + "# らせんの方程式\n", + "#x = np.cos(t)\n", + "#y = np.sin(t)\n", + "#z = t\n", + "\n", + "noise_scale=0.03\n", + "\n", + "X = np.empty((num_samples,3))\n", + "X[:, 0] = np.cos(t)\n", + "X[:, 1] = np.sin(t)\n", + "X[:, 2] = t\n", + "X += np.random.normal(loc=0, scale=noise_scale, size=X.shape)\n", + "\n", + "\n", + "# 曲線を描画\n", + "ax.scatter(X[:, 0], X[:, 1], X[:, 2], color = \"red\")\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e0d6e56", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/ukr/taga/visualizer.py b/ukr/taga/visualizer.py index d087c7e..041b3de 100644 --- a/ukr/taga/visualizer.py +++ b/ukr/taga/visualizer.py @@ -1,28 +1,81 @@ import numpy as np import matplotlib.pyplot as plt +from matplotlib.gridspec import GridSpec import matplotlib.animation as animation -def visualize_history(X, z_hist, y_hist, colormap, resolution, T, save_gif=False, filename="tmp"): - fig = plt.figure(figsize=(10,5)) - ax1 = fig.add_subplot(121, projection='3d') - ax2 = fig.add_subplot(122) - ani = animation.FuncAnimation(fig=fig, func=update, frames=range(T), repeat=True, fargs=(ax1, ax2, z_hist, y_hist, X, colormap, resolution)) +def visualize_history(X, z_hist, f_hist, e_hist, color, resolution, save_gif=False, filename="tmp"): + input_dim, latent_dim = X.shape[1], z_hist[0].shape[1] + num_epoch = len(z_hist) + projection = '3d' if input_dim > 2 else 'rectilinear' + + fig = plt.figure(figsize=(10,7)) + fig.text(0.05, 0.9, f"{filename}") + gs = GridSpec(nrows=2, ncols=2, height_ratios=[1,0.5]) + input_ax = fig.add_subplot(gs[0,0], projection=projection) + latent_ax = fig.add_subplot(gs[0,1]) + objective_ax = fig.add_subplot(gs[1,:]) + + ani = animation.FuncAnimation( + fig=fig, + func=update, + frames=num_epoch, + repeat=True, + interval=100, + fargs=(input_dim, latent_dim, input_ax, latent_ax, objective_ax, z_hist, f_hist, e_hist, + X, num_epoch, color, resolution)) plt.show() if save_gif: - #ani.save(f"learning_sigma{sigma}_N{N}_eta{eta}_resolution{resolution}_seed{seed}.gif", writer="pillow") ani.save(f"{filename}.mp4", writer='ffmpeg') -def update(i, ax1, ax2, z_hist, y_hist, X, colormap, resolution): - ax1.cla() - ax2.cla() - Z = z_hist[i] - Y = y_hist[i] +def update(epoch, input_dim, latent_dim, input_ax, latent_ax, objective_ax, z_hist, f_hist, e_hist, + X, num_epoch, color, resolution): + input_ax.cla() + latent_ax.cla() + objective_ax.cla() + Z = z_hist[epoch] + Y = f_hist[epoch] + E = e_hist + + if input_dim == 3: + draw_observation_3D(input_ax, X, Y, latent_dim, color, resolution) + else: + draw_observation_2D - plt.title(f"学習回数{i+1}回目", fontname="MS Gothic") - ax1.scatter(X[:, 0], X[:, 1], X[:, 2], c=colormap) - ax1.plot_wireframe(Y[:, 0].reshape(resolution,resolution), Y[:, 1].reshape(resolution,resolution), Y[:, 2].reshape(resolution,resolution), color='k') - #ax2.scatter(M[:, 0], M[:, 1], alpha=0.4, marker='D') - ax2.set_xlim(z_hist[:,:,0].min()-0.1,z_hist[:,:,0].max()+0.1) - ax2.set_ylim(z_hist[:,:,1].min()-0.1,z_hist[:,:,1].max()+0.1) - ax2.scatter(Z[:, 0], Z[:, 1], c=colormap, marker='x', linewidth=2) \ No newline at end of file + if latent_dim == 2: + draw_latent_2D(latent_ax, Z, z_hist, color) + else: + draw_latent_1D(latent_ax, Z, color) + + draw_objective(objective_ax, E, epoch, num_epoch) + +def draw_observation_3D(ax, X, Y, latent_dim, color, resolution): + ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=color) + if latent_dim == 2: + ax.plot_wireframe( + Y[:, 0].reshape(resolution,resolution), + Y[:, 1].reshape(resolution,resolution), + Y[:, 2].reshape(resolution,resolution), + color='k') + else: + ax.plot(Y[:, 0], Y[:, 1], Y[:, 2], color='k') + +def draw_observation_2D(ax, X, Y, color): + ax.scatter(X[:, 0], X[:, 1], c=color) + ax.plot(Y[:, 0], Y[:, 1], c='k') + +def draw_latent_2D(ax, Z, z_hist , color): + ax.set_xlim(z_hist[:,:,0].min()-0.1,z_hist[:,:,0].max()+0.1) + ax.set_ylim(z_hist[:,:,1].min()-0.1,z_hist[:,:,1].max()+0.1) + ax.scatter(Z[:, 0], Z[:, 1], c=color, marker='x', linewidth=2) + +def draw_latent_1D(ax, Z, color): + ax.scatter(Z, np.zeros(Z.shape), c=color) + ax.set_ylim(-1, 1) + +def draw_objective(ax, E, i, num_epoch): + x=np.linspace(1,num_epoch,num_epoch) + ax.set_title(f"{i+1}epoch") + ax.plot(x,E) + ax.scatter(x[i], E[i], marker='d', c='red') + ax.set_ylim(0, 0.5) \ No newline at end of file