diff --git a/.gitignore b/.gitignore index 0725ba19..65bbc5de 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,7 @@ docs/_build # pyCharm files .idea + +# test and doc build files +tests/_* +docs/_* diff --git a/docs/advanced_usage.ipynb b/docs/advanced_usage.ipynb index 2487fe3c..81edcf86 100644 --- a/docs/advanced_usage.ipynb +++ b/docs/advanced_usage.ipynb @@ -122,6 +122,37 @@ "plt.show()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create detailed absence/presence plots\n", + "\n", + "Rather than just showing if a certain vegetation type is present or not present, it is possible to show a more detailed analysis on the reason a vegetation type is present or not present. This follows the the steps of the niche analysis: ie if soil type is possible, mxw checks are done and finally other input grids are matched." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'full' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_112934/1467200067.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0max\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfull\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mplot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m7\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0max\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfull\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mplot_detail\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m7\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mNameError\u001b[0m: name 'full' is not defined" + ] + } + ], + "source": [ + "ax = full.plot(7)\n", + "ax = full.plot_detail(7)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -279,6 +310,11 @@ } ], "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, "language_info": { "codemirror_mode": { "name": "ipython", @@ -289,7 +325,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.9.2" } }, "nbformat": 4, diff --git a/docs/getting_started.ipynb b/docs/getting_started.ipynb index 818966d3..4795702a 100644 --- a/docs/getting_started.ipynb +++ b/docs/getting_started.ipynb @@ -330,2003 +330,81 @@ "outputs": [ { "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "/* global mpl */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function () {\n", - " if (typeof WebSocket !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof MozWebSocket !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert(\n", - " 'Your browser does not have WebSocket support. ' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.'\n", - " );\n", - " }\n", - "};\n", - "\n", - "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = this.ws.binaryType !== undefined;\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById('mpl-warnings');\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent =\n", - " 'This browser does not support binary websocket messages. ' +\n", - " 'Performance may be slow.';\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = document.createElement('div');\n", - " this.root.setAttribute('style', 'display: inline-block');\n", - " this._root_extra_style(this.root);\n", - "\n", - " parent_element.appendChild(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message('supports_binary', { value: fig.supports_binary });\n", - " fig.send_message('send_image_mode', {});\n", - " if (fig.ratio !== 1) {\n", - " fig.send_message('set_device_pixel_ratio', {\n", - " device_pixel_ratio: fig.ratio,\n", - " });\n", - " }\n", - " fig.send_message('refresh', {});\n", - " };\n", - "\n", - " this.imageObj.onload = function () {\n", - " if (fig.image_mode === 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function () {\n", - " fig.ws.close();\n", - " };\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "};\n", - "\n", - "mpl.figure.prototype._init_header = function () {\n", - " var titlebar = document.createElement('div');\n", - " titlebar.classList =\n", - " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", - " var titletext = document.createElement('div');\n", - " titletext.classList = 'ui-dialog-title';\n", - " titletext.setAttribute(\n", - " 'style',\n", - " 'width: 100%; text-align: center; padding: 3px;'\n", - " );\n", - " titlebar.appendChild(titletext);\n", - " this.root.appendChild(titlebar);\n", - " this.header = titletext;\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._init_canvas = function () {\n", - " var fig = this;\n", - "\n", - " var canvas_div = (this.canvas_div = document.createElement('div'));\n", - " canvas_div.setAttribute(\n", - " 'style',\n", - " 'border: 1px solid #ddd;' +\n", - " 'box-sizing: content-box;' +\n", - " 'clear: both;' +\n", - " 'min-height: 1px;' +\n", - " 'min-width: 1px;' +\n", - " 'outline: 0;' +\n", - " 'overflow: hidden;' +\n", - " 'position: relative;' +\n", - " 'resize: both;'\n", - " );\n", - "\n", - " function on_keyboard_event_closure(name) {\n", - " return function (event) {\n", - " return fig.key_event(event, name);\n", - " };\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'keydown',\n", - " on_keyboard_event_closure('key_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'keyup',\n", - " on_keyboard_event_closure('key_release')\n", - " );\n", - "\n", - " this._canvas_extra_style(canvas_div);\n", - " this.root.appendChild(canvas_div);\n", - "\n", - " var canvas = (this.canvas = document.createElement('canvas'));\n", - " canvas.classList.add('mpl-canvas');\n", - " canvas.setAttribute('style', 'box-sizing: content-box;');\n", - "\n", - " this.context = canvas.getContext('2d');\n", - "\n", - " var backingStore =\n", - " this.context.backingStorePixelRatio ||\n", - " this.context.webkitBackingStorePixelRatio ||\n", - " this.context.mozBackingStorePixelRatio ||\n", - " this.context.msBackingStorePixelRatio ||\n", - " this.context.oBackingStorePixelRatio ||\n", - " this.context.backingStorePixelRatio ||\n", - " 1;\n", - "\n", - " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", - " 'canvas'\n", - " ));\n", - " rubberband_canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", - " );\n", - "\n", - " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", - " if (this.ResizeObserver === undefined) {\n", - " if (window.ResizeObserver !== undefined) {\n", - " this.ResizeObserver = window.ResizeObserver;\n", - " } else {\n", - " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", - " this.ResizeObserver = obs.ResizeObserver;\n", - " }\n", - " }\n", - "\n", - " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", - " var nentries = entries.length;\n", - " for (var i = 0; i < nentries; i++) {\n", - " var entry = entries[i];\n", - " var width, height;\n", - " if (entry.contentBoxSize) {\n", - " if (entry.contentBoxSize instanceof Array) {\n", - " // Chrome 84 implements new version of spec.\n", - " width = entry.contentBoxSize[0].inlineSize;\n", - " height = entry.contentBoxSize[0].blockSize;\n", - " } else {\n", - " // Firefox implements old version of spec.\n", - " width = entry.contentBoxSize.inlineSize;\n", - " height = entry.contentBoxSize.blockSize;\n", - " }\n", - " } else {\n", - " // Chrome <84 implements even older version of spec.\n", - " width = entry.contentRect.width;\n", - " height = entry.contentRect.height;\n", - " }\n", - "\n", - " // Keep the size of the canvas and rubber band canvas in sync with\n", - " // the canvas container.\n", - " if (entry.devicePixelContentBoxSize) {\n", - " // Chrome 84 implements new version of spec.\n", - " canvas.setAttribute(\n", - " 'width',\n", - " entry.devicePixelContentBoxSize[0].inlineSize\n", - " );\n", - " canvas.setAttribute(\n", - " 'height',\n", - " entry.devicePixelContentBoxSize[0].blockSize\n", - " );\n", - " } else {\n", - " canvas.setAttribute('width', width * fig.ratio);\n", - " canvas.setAttribute('height', height * fig.ratio);\n", - " }\n", - " canvas.setAttribute(\n", - " 'style',\n", - " 'width: ' + width + 'px; height: ' + height + 'px;'\n", - " );\n", - "\n", - " rubberband_canvas.setAttribute('width', width);\n", - " rubberband_canvas.setAttribute('height', height);\n", - "\n", - " // And update the size in Python. We ignore the initial 0/0 size\n", - " // that occurs as the element is placed into the DOM, which should\n", - " // otherwise not happen due to the minimum size styling.\n", - " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", - " fig.request_resize(width, height);\n", - " }\n", - " }\n", - " });\n", - " this.resizeObserverInstance.observe(canvas_div);\n", - "\n", - " function on_mouse_event_closure(name) {\n", - " return function (event) {\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " }\n", - "\n", - " rubberband_canvas.addEventListener(\n", - " 'mousedown',\n", - " on_mouse_event_closure('button_press')\n", - " );\n", - " rubberband_canvas.addEventListener(\n", - " 'mouseup',\n", - " on_mouse_event_closure('button_release')\n", - " );\n", - " rubberband_canvas.addEventListener(\n", - " 'dblclick',\n", - " on_mouse_event_closure('dblclick')\n", - " );\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband_canvas.addEventListener(\n", - " 'mousemove',\n", - " on_mouse_event_closure('motion_notify')\n", - " );\n", - "\n", - " rubberband_canvas.addEventListener(\n", - " 'mouseenter',\n", - " on_mouse_event_closure('figure_enter')\n", - " );\n", - " rubberband_canvas.addEventListener(\n", - " 'mouseleave',\n", - " on_mouse_event_closure('figure_leave')\n", - " );\n", - "\n", - " canvas_div.addEventListener('wheel', function (event) {\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " on_mouse_event_closure('scroll')(event);\n", - " });\n", - "\n", - " canvas_div.appendChild(canvas);\n", - " canvas_div.appendChild(rubberband_canvas);\n", - "\n", - " this.rubberband_context = rubberband_canvas.getContext('2d');\n", - " this.rubberband_context.strokeStyle = '#000000';\n", - "\n", - " this._resize_canvas = function (width, height, forward) {\n", - " if (forward) {\n", - " canvas_div.style.width = width + 'px';\n", - " canvas_div.style.height = height + 'px';\n", - " }\n", - " };\n", - "\n", - " // Disable right mouse context menu.\n", - " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", - " event.preventDefault();\n", - " return false;\n", - " });\n", - "\n", - " function set_focus() {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'mpl-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " continue;\n", - " }\n", - "\n", - " var button = (fig.buttons[name] = document.createElement('button'));\n", - " button.classList = 'mpl-widget';\n", - " button.setAttribute('role', 'button');\n", - " button.setAttribute('aria-disabled', 'false');\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - "\n", - " var icon_img = document.createElement('img');\n", - " icon_img.src = '_images/' + image + '.png';\n", - " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", - " icon_img.alt = tooltip;\n", - " button.appendChild(icon_img);\n", - "\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " var fmt_picker = document.createElement('select');\n", - " fmt_picker.classList = 'mpl-widget';\n", - " toolbar.appendChild(fmt_picker);\n", - " this.format_dropdown = fmt_picker;\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = document.createElement('option');\n", - " option.selected = fmt === mpl.default_extension;\n", - " option.innerHTML = fmt;\n", - " fmt_picker.appendChild(option);\n", - " }\n", - "\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "};\n", - "\n", - "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", - "};\n", - "\n", - "mpl.figure.prototype.send_message = function (type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "};\n", - "\n", - "mpl.figure.prototype.send_draw_message = function () {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1], msg['forward']);\n", - " fig.send_message('refresh', {});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", - " var x0 = msg['x0'] / fig.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", - " var x1 = msg['x1'] / fig.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0,\n", - " 0,\n", - " fig.canvas.width / fig.ratio,\n", - " fig.canvas.height / fig.ratio\n", - " );\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", - " fig.rubberband_canvas.style.cursor = msg['cursor'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_message = function (fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", - " for (var key in msg) {\n", - " if (!(key in fig.buttons)) {\n", - " continue;\n", - " }\n", - " fig.buttons[key].disabled = !msg[key];\n", - " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", - " if (msg['mode'] === 'PAN') {\n", - " fig.buttons['Pan'].classList.add('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " } else if (msg['mode'] === 'ZOOM') {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.add('active');\n", - " } else {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message('ack', {});\n", - "};\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function (fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " var img = evt.data;\n", - " if (img.type !== 'image/png') {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " img.type = 'image/png';\n", - " }\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src\n", - " );\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " img\n", - " );\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " } else if (\n", - " typeof evt.data === 'string' &&\n", - " evt.data.slice(0, 21) === 'data:image/png;base64'\n", - " ) {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig['handle_' + msg_type];\n", - " } catch (e) {\n", - " console.log(\n", - " \"No handler for the '\" + msg_type + \"' message type: \",\n", - " msg\n", - " );\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\n", - " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", - " e,\n", - " e.stack,\n", - " msg\n", - " );\n", - " }\n", - " }\n", - " };\n", - "};\n", - "\n", - "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", - "mpl.findpos = function (e) {\n", - " //this section is from http://www.quirksmode.org/js/events_properties.html\n", - " var targ;\n", - " if (!e) {\n", - " e = window.event;\n", - " }\n", - " if (e.target) {\n", - " targ = e.target;\n", - " } else if (e.srcElement) {\n", - " targ = e.srcElement;\n", - " }\n", - " if (targ.nodeType === 3) {\n", - " // defeat Safari bug\n", - " targ = targ.parentNode;\n", - " }\n", - "\n", - " // pageX,Y are the mouse positions relative to the document\n", - " var boundingRect = targ.getBoundingClientRect();\n", - " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", - " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", - "\n", - " return { x: x, y: y };\n", - "};\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * https://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys(original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object') {\n", - " obj[key] = original[key];\n", - " }\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function (event, name) {\n", - " var canvas_pos = mpl.findpos(event);\n", - "\n", - " if (name === 'button_press') {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " var x = canvas_pos.x * this.ratio;\n", - " var y = canvas_pos.y * this.ratio;\n", - "\n", - " this.send_message(name, {\n", - " x: x,\n", - " y: y,\n", - " button: event.button,\n", - " step: event.step,\n", - " guiEvent: simpleKeys(event),\n", - " });\n", - "\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We want\n", - " * to control all of the cursor setting manually through the\n", - " * 'cursor' event from matplotlib */\n", - " event.preventDefault();\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "};\n", - "\n", - "mpl.figure.prototype.key_event = function (event, name) {\n", - " // Prevent repeat events\n", - " if (name === 'key_press') {\n", - " if (event.key === this._key) {\n", - " return;\n", - " } else {\n", - " this._key = event.key;\n", - " }\n", - " }\n", - " if (name === 'key_release') {\n", - " this._key = null;\n", - " }\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.key !== 'Control') {\n", - " value += 'ctrl+';\n", - " }\n", - " else if (event.altKey && event.key !== 'Alt') {\n", - " value += 'alt+';\n", - " }\n", - " else if (event.shiftKey && event.key !== 'Shift') {\n", - " value += 'shift+';\n", - " }\n", - "\n", - " value += 'k' + event.key;\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", - " if (name === 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message('toolbar_button', { name: name });\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\n", - "\n", - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", - "// prettier-ignore\n", - "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", - "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", - "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", - "\n", - "mpl.default_extension = \"png\";/* global mpl */\n", - "\n", - "var comm_websocket_adapter = function (comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.binaryType = comm.kernel.ws.binaryType;\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " function updateReadyState(_event) {\n", - " if (comm.kernel.ws) {\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " } else {\n", - " ws.readyState = 3; // Closed state.\n", - " }\n", - " }\n", - " comm.kernel.ws.addEventListener('open', updateReadyState);\n", - " comm.kernel.ws.addEventListener('close', updateReadyState);\n", - " comm.kernel.ws.addEventListener('error', updateReadyState);\n", - "\n", - " ws.close = function () {\n", - " comm.close();\n", - " };\n", - " ws.send = function (m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function (msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " var data = msg['content']['data'];\n", - " if (data['blob'] !== undefined) {\n", - " data = {\n", - " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", - " };\n", - " }\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(data);\n", - " });\n", - " return ws;\n", - "};\n", - "\n", - "mpl.mpl_figure_comm = function (comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = document.getElementById(id);\n", - " var ws_proxy = comm_websocket_adapter(comm);\n", - "\n", - " function ondownload(figure, _format) {\n", - " window.open(figure.canvas.toDataURL());\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element;\n", - " fig.cell_info = mpl.find_output_cell(\"
\");\n", - " if (!fig.cell_info) {\n", - " console.error('Failed to find cell for figure', id, fig);\n", - " return;\n", - " }\n", - " fig.cell_info[0].output_area.element.on(\n", - " 'cleared',\n", - " { fig: fig },\n", - " fig._remove_fig_handler\n", - " );\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function (fig, msg) {\n", - " var width = fig.canvas.width / fig.ratio;\n", - " fig.cell_info[0].output_area.element.off(\n", - " 'cleared',\n", - " fig._remove_fig_handler\n", - " );\n", - " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable();\n", - " fig.parent_element.innerHTML =\n", - " '';\n", - " fig.close_ws(fig, msg);\n", - "};\n", - "\n", - "mpl.figure.prototype.close_ws = function (fig, msg) {\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "};\n", - "\n", - "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width / this.ratio;\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] =\n", - " '';\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message('ack', {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () {\n", - " fig.push_to_output();\n", - " }, 1000);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'btn-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " var button;\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " continue;\n", - " }\n", - "\n", - " button = fig.buttons[name] = document.createElement('button');\n", - " button.classList = 'btn btn-default';\n", - " button.href = '#';\n", - " button.title = name;\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message pull-right';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = document.createElement('div');\n", - " buttongrp.classList = 'btn-group inline pull-right';\n", - " button = document.createElement('button');\n", - " button.classList = 'btn btn-mini btn-primary';\n", - " button.href = '#';\n", - " button.title = 'Stop Interaction';\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', function (_evt) {\n", - " fig.handle_close(fig, {});\n", - " });\n", - " button.addEventListener(\n", - " 'mouseover',\n", - " on_mouseover_closure('Stop Interaction')\n", - " );\n", - " buttongrp.appendChild(button);\n", - " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", - " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", - "};\n", - "\n", - "mpl.figure.prototype._remove_fig_handler = function (event) {\n", - " var fig = event.data.fig;\n", - " if (event.target !== this) {\n", - " // Ignore bubbled events from children.\n", - " return;\n", - " }\n", - " fig.close_ws(fig, {});\n", - "};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (el) {\n", - " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (el) {\n", - " // this is important to make the div 'focusable\n", - " el.setAttribute('tabindex', 0);\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " } else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which === 13) {\n", - " this.canvas_div.blur();\n", - " // select the cell after this one\n", - " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", - " IPython.notebook.select(index + 1);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " fig.ondownload(fig, null);\n", - "};\n", - "\n", - "mpl.find_output_cell = function (html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i = 0; i < ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code') {\n", - " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] === html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "};\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel !== null) {\n", - " IPython.notebook.kernel.comm_manager.register_target(\n", - " 'matplotlib',\n", - " mpl.mpl_figure_comm\n", - " );\n", - "}\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "\n", - "import matplotlib.pyplot as plt\n", - "%matplotlib notebook\n", - "\n", - "simple.plot(11)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It is possible to give your model a `name` - this will be shown when plotting and will be used when writing the files." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "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_device_pixel_ratio', {\n", - " device_pixel_ratio: fig.ratio,\n", - " });\n", - " }\n", - " fig.send_message('refresh', {});\n", - " };\n", - "\n", - " this.imageObj.onload = function () {\n", - " if (fig.image_mode === 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function () {\n", - " fig.ws.close();\n", - " };\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "};\n", - "\n", - "mpl.figure.prototype._init_header = function () {\n", - " var titlebar = document.createElement('div');\n", - " titlebar.classList =\n", - " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", - " var titletext = document.createElement('div');\n", - " titletext.classList = 'ui-dialog-title';\n", - " titletext.setAttribute(\n", - " 'style',\n", - " 'width: 100%; text-align: center; padding: 3px;'\n", - " );\n", - " titlebar.appendChild(titletext);\n", - " this.root.appendChild(titlebar);\n", - " this.header = titletext;\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", - "\n", - "mpl.figure.prototype._init_canvas = function () {\n", - " var fig = this;\n", - "\n", - " var canvas_div = (this.canvas_div = document.createElement('div'));\n", - " canvas_div.setAttribute(\n", - " 'style',\n", - " 'border: 1px solid #ddd;' +\n", - " 'box-sizing: content-box;' +\n", - " 'clear: both;' +\n", - " 'min-height: 1px;' +\n", - " 'min-width: 1px;' +\n", - " 'outline: 0;' +\n", - " 'overflow: hidden;' +\n", - " 'position: relative;' +\n", - " 'resize: both;'\n", - " );\n", - "\n", - " function on_keyboard_event_closure(name) {\n", - " return function (event) {\n", - " return fig.key_event(event, name);\n", - " };\n", - " }\n", - "\n", - " canvas_div.addEventListener(\n", - " 'keydown',\n", - " on_keyboard_event_closure('key_press')\n", - " );\n", - " canvas_div.addEventListener(\n", - " 'keyup',\n", - " on_keyboard_event_closure('key_release')\n", - " );\n", - "\n", - " this._canvas_extra_style(canvas_div);\n", - " this.root.appendChild(canvas_div);\n", - "\n", - " var canvas = (this.canvas = document.createElement('canvas'));\n", - " canvas.classList.add('mpl-canvas');\n", - " canvas.setAttribute('style', 'box-sizing: content-box;');\n", - "\n", - " this.context = canvas.getContext('2d');\n", - "\n", - " var backingStore =\n", - " this.context.backingStorePixelRatio ||\n", - " this.context.webkitBackingStorePixelRatio ||\n", - " this.context.mozBackingStorePixelRatio ||\n", - " this.context.msBackingStorePixelRatio ||\n", - " this.context.oBackingStorePixelRatio ||\n", - " this.context.backingStorePixelRatio ||\n", - " 1;\n", - "\n", - " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", - " 'canvas'\n", - " ));\n", - " rubberband_canvas.setAttribute(\n", - " 'style',\n", - " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", - " );\n", - "\n", - " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", - " if (this.ResizeObserver === undefined) {\n", - " if (window.ResizeObserver !== undefined) {\n", - " this.ResizeObserver = window.ResizeObserver;\n", - " } else {\n", - " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", - " this.ResizeObserver = obs.ResizeObserver;\n", - " }\n", - " }\n", - "\n", - " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", - " var nentries = entries.length;\n", - " for (var i = 0; i < nentries; i++) {\n", - " var entry = entries[i];\n", - " var width, height;\n", - " if (entry.contentBoxSize) {\n", - " if (entry.contentBoxSize instanceof Array) {\n", - " // Chrome 84 implements new version of spec.\n", - " width = entry.contentBoxSize[0].inlineSize;\n", - " height = entry.contentBoxSize[0].blockSize;\n", - " } else {\n", - " // Firefox implements old version of spec.\n", - " width = entry.contentBoxSize.inlineSize;\n", - " height = entry.contentBoxSize.blockSize;\n", - " }\n", - " } else {\n", - " // Chrome <84 implements even older version of spec.\n", - " width = entry.contentRect.width;\n", - " height = entry.contentRect.height;\n", - " }\n", - "\n", - " // Keep the size of the canvas and rubber band canvas in sync with\n", - " // the canvas container.\n", - " if (entry.devicePixelContentBoxSize) {\n", - " // Chrome 84 implements new version of spec.\n", - " canvas.setAttribute(\n", - " 'width',\n", - " entry.devicePixelContentBoxSize[0].inlineSize\n", - " );\n", - " canvas.setAttribute(\n", - " 'height',\n", - " entry.devicePixelContentBoxSize[0].blockSize\n", - " );\n", - " } else {\n", - " canvas.setAttribute('width', width * fig.ratio);\n", - " canvas.setAttribute('height', height * fig.ratio);\n", - " }\n", - " canvas.setAttribute(\n", - " 'style',\n", - " 'width: ' + width + 'px; height: ' + height + 'px;'\n", - " );\n", - "\n", - " rubberband_canvas.setAttribute('width', width);\n", - " rubberband_canvas.setAttribute('height', height);\n", - "\n", - " // And update the size in Python. We ignore the initial 0/0 size\n", - " // that occurs as the element is placed into the DOM, which should\n", - " // otherwise not happen due to the minimum size styling.\n", - " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", - " fig.request_resize(width, height);\n", - " }\n", - " }\n", - " });\n", - " this.resizeObserverInstance.observe(canvas_div);\n", - "\n", - " function on_mouse_event_closure(name) {\n", - " return function (event) {\n", - " return fig.mouse_event(event, name);\n", - " };\n", - " }\n", - "\n", - " rubberband_canvas.addEventListener(\n", - " 'mousedown',\n", - " on_mouse_event_closure('button_press')\n", - " );\n", - " rubberband_canvas.addEventListener(\n", - " 'mouseup',\n", - " on_mouse_event_closure('button_release')\n", - " );\n", - " rubberband_canvas.addEventListener(\n", - " 'dblclick',\n", - " on_mouse_event_closure('dblclick')\n", - " );\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband_canvas.addEventListener(\n", - " 'mousemove',\n", - " on_mouse_event_closure('motion_notify')\n", - " );\n", - "\n", - " rubberband_canvas.addEventListener(\n", - " 'mouseenter',\n", - " on_mouse_event_closure('figure_enter')\n", - " );\n", - " rubberband_canvas.addEventListener(\n", - " 'mouseleave',\n", - " on_mouse_event_closure('figure_leave')\n", - " );\n", - "\n", - " canvas_div.addEventListener('wheel', function (event) {\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " on_mouse_event_closure('scroll')(event);\n", - " });\n", - "\n", - " canvas_div.appendChild(canvas);\n", - " canvas_div.appendChild(rubberband_canvas);\n", - "\n", - " this.rubberband_context = rubberband_canvas.getContext('2d');\n", - " this.rubberband_context.strokeStyle = '#000000';\n", - "\n", - " this._resize_canvas = function (width, height, forward) {\n", - " if (forward) {\n", - " canvas_div.style.width = width + 'px';\n", - " canvas_div.style.height = height + 'px';\n", - " }\n", - " };\n", - "\n", - " // Disable right mouse context menu.\n", - " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", - " event.preventDefault();\n", - " return false;\n", - " });\n", - "\n", - " function set_focus() {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'mpl-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'mpl-button-group';\n", - " continue;\n", - " }\n", - "\n", - " var button = (fig.buttons[name] = document.createElement('button'));\n", - " button.classList = 'mpl-widget';\n", - " button.setAttribute('role', 'button');\n", - " button.setAttribute('aria-disabled', 'false');\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - "\n", - " var icon_img = document.createElement('img');\n", - " icon_img.src = '_images/' + image + '.png';\n", - " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", - " icon_img.alt = tooltip;\n", - " button.appendChild(icon_img);\n", - "\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " var fmt_picker = document.createElement('select');\n", - " fmt_picker.classList = 'mpl-widget';\n", - " toolbar.appendChild(fmt_picker);\n", - " this.format_dropdown = fmt_picker;\n", - "\n", - " for (var ind in mpl.extensions) {\n", - " var fmt = mpl.extensions[ind];\n", - " var option = document.createElement('option');\n", - " option.selected = fmt === mpl.default_extension;\n", - " option.innerHTML = fmt;\n", - " fmt_picker.appendChild(option);\n", - " }\n", - "\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "};\n", - "\n", - "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", - " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", - " // which will in turn request a refresh of the image.\n", - " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", - "};\n", - "\n", - "mpl.figure.prototype.send_message = function (type, properties) {\n", - " properties['type'] = type;\n", - " properties['figure_id'] = this.id;\n", - " this.ws.send(JSON.stringify(properties));\n", - "};\n", - "\n", - "mpl.figure.prototype.send_draw_message = function () {\n", - " if (!this.waiting) {\n", - " this.waiting = true;\n", - " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " var format_dropdown = fig.format_dropdown;\n", - " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", - " fig.ondownload(fig, format);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", - " var size = msg['size'];\n", - " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", - " fig._resize_canvas(size[0], size[1], msg['forward']);\n", - " fig.send_message('refresh', {});\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", - " var x0 = msg['x0'] / fig.ratio;\n", - " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", - " var x1 = msg['x1'] / fig.ratio;\n", - " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", - " x0 = Math.floor(x0) + 0.5;\n", - " y0 = Math.floor(y0) + 0.5;\n", - " x1 = Math.floor(x1) + 0.5;\n", - " y1 = Math.floor(y1) + 0.5;\n", - " var min_x = Math.min(x0, x1);\n", - " var min_y = Math.min(y0, y1);\n", - " var width = Math.abs(x1 - x0);\n", - " var height = Math.abs(y1 - y0);\n", - "\n", - " fig.rubberband_context.clearRect(\n", - " 0,\n", - " 0,\n", - " fig.canvas.width / fig.ratio,\n", - " fig.canvas.height / fig.ratio\n", - " );\n", - "\n", - " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", - " // Updates the figure title.\n", - " fig.header.textContent = msg['label'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", - " fig.rubberband_canvas.style.cursor = msg['cursor'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_message = function (fig, msg) {\n", - " fig.message.textContent = msg['message'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", - " // Request the server to send over a new figure.\n", - " fig.send_draw_message();\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", - " fig.image_mode = msg['mode'];\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", - " for (var key in msg) {\n", - " if (!(key in fig.buttons)) {\n", - " continue;\n", - " }\n", - " fig.buttons[key].disabled = !msg[key];\n", - " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", - " if (msg['mode'] === 'PAN') {\n", - " fig.buttons['Pan'].classList.add('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " } else if (msg['mode'] === 'ZOOM') {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.add('active');\n", - " } else {\n", - " fig.buttons['Pan'].classList.remove('active');\n", - " fig.buttons['Zoom'].classList.remove('active');\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Called whenever the canvas gets updated.\n", - " this.send_message('ack', {});\n", - "};\n", - "\n", - "// A function to construct a web socket function for onmessage handling.\n", - "// Called in the figure constructor.\n", - "mpl.figure.prototype._make_on_message_function = function (fig) {\n", - " return function socket_on_message(evt) {\n", - " if (evt.data instanceof Blob) {\n", - " var img = evt.data;\n", - " if (img.type !== 'image/png') {\n", - " /* FIXME: We get \"Resource interpreted as Image but\n", - " * transferred with MIME type text/plain:\" errors on\n", - " * Chrome. But how to set the MIME type? It doesn't seem\n", - " * to be part of the websocket stream */\n", - " img.type = 'image/png';\n", - " }\n", - "\n", - " /* Free the memory for the previous frames */\n", - " if (fig.imageObj.src) {\n", - " (window.URL || window.webkitURL).revokeObjectURL(\n", - " fig.imageObj.src\n", - " );\n", - " }\n", - "\n", - " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", - " img\n", - " );\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " } else if (\n", - " typeof evt.data === 'string' &&\n", - " evt.data.slice(0, 21) === 'data:image/png;base64'\n", - " ) {\n", - " fig.imageObj.src = evt.data;\n", - " fig.updated_canvas_event();\n", - " fig.waiting = false;\n", - " return;\n", - " }\n", - "\n", - " var msg = JSON.parse(evt.data);\n", - " var msg_type = msg['type'];\n", - "\n", - " // Call the \"handle_{type}\" callback, which takes\n", - " // the figure and JSON message as its only arguments.\n", - " try {\n", - " var callback = fig['handle_' + msg_type];\n", - " } catch (e) {\n", - " console.log(\n", - " \"No handler for the '\" + msg_type + \"' message type: \",\n", - " msg\n", - " );\n", - " return;\n", - " }\n", - "\n", - " if (callback) {\n", - " try {\n", - " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", - " callback(fig, msg);\n", - " } catch (e) {\n", - " console.log(\n", - " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", - " e,\n", - " e.stack,\n", - " msg\n", - " );\n", - " }\n", - " }\n", - " };\n", - "};\n", - "\n", - "// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", - "mpl.findpos = function (e) {\n", - " //this section is from http://www.quirksmode.org/js/events_properties.html\n", - " var targ;\n", - " if (!e) {\n", - " e = window.event;\n", - " }\n", - " if (e.target) {\n", - " targ = e.target;\n", - " } else if (e.srcElement) {\n", - " targ = e.srcElement;\n", - " }\n", - " if (targ.nodeType === 3) {\n", - " // defeat Safari bug\n", - " targ = targ.parentNode;\n", - " }\n", - "\n", - " // pageX,Y are the mouse positions relative to the document\n", - " var boundingRect = targ.getBoundingClientRect();\n", - " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", - " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", - "\n", - " return { x: x, y: y };\n", - "};\n", - "\n", - "/*\n", - " * return a copy of an object with only non-object keys\n", - " * we need this to avoid circular references\n", - " * https://stackoverflow.com/a/24161582/3208463\n", - " */\n", - "function simpleKeys(original) {\n", - " return Object.keys(original).reduce(function (obj, key) {\n", - " if (typeof original[key] !== 'object') {\n", - " obj[key] = original[key];\n", - " }\n", - " return obj;\n", - " }, {});\n", - "}\n", - "\n", - "mpl.figure.prototype.mouse_event = function (event, name) {\n", - " var canvas_pos = mpl.findpos(event);\n", - "\n", - " if (name === 'button_press') {\n", - " this.canvas.focus();\n", - " this.canvas_div.focus();\n", - " }\n", - "\n", - " var x = canvas_pos.x * this.ratio;\n", - " var y = canvas_pos.y * this.ratio;\n", - "\n", - " this.send_message(name, {\n", - " x: x,\n", - " y: y,\n", - " button: event.button,\n", - " step: event.step,\n", - " guiEvent: simpleKeys(event),\n", - " });\n", - "\n", - " /* This prevents the web browser from automatically changing to\n", - " * the text insertion cursor when the button is pressed. We want\n", - " * to control all of the cursor setting manually through the\n", - " * 'cursor' event from matplotlib */\n", - " event.preventDefault();\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", - " // Handle any extra behaviour associated with a key event\n", - "};\n", - "\n", - "mpl.figure.prototype.key_event = function (event, name) {\n", - " // Prevent repeat events\n", - " if (name === 'key_press') {\n", - " if (event.key === this._key) {\n", - " return;\n", - " } else {\n", - " this._key = event.key;\n", - " }\n", - " }\n", - " if (name === 'key_release') {\n", - " this._key = null;\n", - " }\n", - "\n", - " var value = '';\n", - " if (event.ctrlKey && event.key !== 'Control') {\n", - " value += 'ctrl+';\n", - " }\n", - " else if (event.altKey && event.key !== 'Alt') {\n", - " value += 'alt+';\n", - " }\n", - " else if (event.shiftKey && event.key !== 'Shift') {\n", - " value += 'shift+';\n", - " }\n", - "\n", - " value += 'k' + event.key;\n", - "\n", - " this._key_event_extra(event, name);\n", - "\n", - " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", - " return false;\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", - " if (name === 'download') {\n", - " this.handle_save(this, null);\n", - " } else {\n", - " this.send_message('toolbar_button', { name: name });\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", - " this.message.textContent = tooltip;\n", - "};\n", - "\n", - "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", - "// prettier-ignore\n", - "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", - "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", - "\n", - "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", - "\n", - "mpl.default_extension = \"png\";/* global mpl */\n", - "\n", - "var comm_websocket_adapter = function (comm) {\n", - " // Create a \"websocket\"-like object which calls the given IPython comm\n", - " // object with the appropriate methods. Currently this is a non binary\n", - " // socket, so there is still some room for performance tuning.\n", - " var ws = {};\n", - "\n", - " ws.binaryType = comm.kernel.ws.binaryType;\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " function updateReadyState(_event) {\n", - " if (comm.kernel.ws) {\n", - " ws.readyState = comm.kernel.ws.readyState;\n", - " } else {\n", - " ws.readyState = 3; // Closed state.\n", - " }\n", - " }\n", - " comm.kernel.ws.addEventListener('open', updateReadyState);\n", - " comm.kernel.ws.addEventListener('close', updateReadyState);\n", - " comm.kernel.ws.addEventListener('error', updateReadyState);\n", - "\n", - " ws.close = function () {\n", - " comm.close();\n", - " };\n", - " ws.send = function (m) {\n", - " //console.log('sending', m);\n", - " comm.send(m);\n", - " };\n", - " // Register the callback with on_msg.\n", - " comm.on_msg(function (msg) {\n", - " //console.log('receiving', msg['content']['data'], msg);\n", - " var data = msg['content']['data'];\n", - " if (data['blob'] !== undefined) {\n", - " data = {\n", - " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", - " };\n", - " }\n", - " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", - " ws.onmessage(data);\n", - " });\n", - " return ws;\n", - "};\n", - "\n", - "mpl.mpl_figure_comm = function (comm, msg) {\n", - " // This is the function which gets called when the mpl process\n", - " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", - "\n", - " var id = msg.content.data.id;\n", - " // Get hold of the div created by the display call when the Comm\n", - " // socket was opened in Python.\n", - " var element = document.getElementById(id);\n", - " var ws_proxy = comm_websocket_adapter(comm);\n", - "\n", - " function ondownload(figure, _format) {\n", - " window.open(figure.canvas.toDataURL());\n", - " }\n", - "\n", - " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", - "\n", - " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", - " // web socket which is closed, not our websocket->open comm proxy.\n", - " ws_proxy.onopen();\n", - "\n", - " fig.parent_element = element;\n", - " fig.cell_info = mpl.find_output_cell(\"
\");\n", - " if (!fig.cell_info) {\n", - " console.error('Failed to find cell for figure', id, fig);\n", - " return;\n", - " }\n", - " fig.cell_info[0].output_area.element.on(\n", - " 'cleared',\n", - " { fig: fig },\n", - " fig._remove_fig_handler\n", - " );\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_close = function (fig, msg) {\n", - " var width = fig.canvas.width / fig.ratio;\n", - " fig.cell_info[0].output_area.element.off(\n", - " 'cleared',\n", - " fig._remove_fig_handler\n", - " );\n", - " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", - "\n", - " // Update the output cell to use the data from the current canvas.\n", - " fig.push_to_output();\n", - " var dataURL = fig.canvas.toDataURL();\n", - " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", - " // the notebook keyboard shortcuts fail.\n", - " IPython.keyboard_manager.enable();\n", - " fig.parent_element.innerHTML =\n", - " '';\n", - " fig.close_ws(fig, msg);\n", - "};\n", - "\n", - "mpl.figure.prototype.close_ws = function (fig, msg) {\n", - " fig.send_message('closing', msg);\n", - " // fig.ws.close()\n", - "};\n", - "\n", - "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", - " // Turn the data on the canvas into data in the output cell.\n", - " var width = this.canvas.width / this.ratio;\n", - " var dataURL = this.canvas.toDataURL();\n", - " this.cell_info[1]['text/html'] =\n", - " '';\n", - "};\n", - "\n", - "mpl.figure.prototype.updated_canvas_event = function () {\n", - " // Tell IPython that the notebook contents must change.\n", - " IPython.notebook.set_dirty(true);\n", - " this.send_message('ack', {});\n", - " var fig = this;\n", - " // Wait a second, then push the new image to the DOM so\n", - " // that it is saved nicely (might be nice to debounce this).\n", - " setTimeout(function () {\n", - " fig.push_to_output();\n", - " }, 1000);\n", - "};\n", - "\n", - "mpl.figure.prototype._init_toolbar = function () {\n", - " var fig = this;\n", - "\n", - " var toolbar = document.createElement('div');\n", - " toolbar.classList = 'btn-toolbar';\n", - " this.root.appendChild(toolbar);\n", - "\n", - " function on_click_closure(name) {\n", - " return function (_event) {\n", - " return fig.toolbar_button_onclick(name);\n", - " };\n", - " }\n", - "\n", - " function on_mouseover_closure(tooltip) {\n", - " return function (event) {\n", - " if (!event.currentTarget.disabled) {\n", - " return fig.toolbar_button_onmouseover(tooltip);\n", - " }\n", - " };\n", - " }\n", - "\n", - " fig.buttons = {};\n", - " var buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " var button;\n", - " for (var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " /* Instead of a spacer, we start a new button group. */\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - " buttonGroup = document.createElement('div');\n", - " buttonGroup.classList = 'btn-group';\n", - " continue;\n", - " }\n", - "\n", - " button = fig.buttons[name] = document.createElement('button');\n", - " button.classList = 'btn btn-default';\n", - " button.href = '#';\n", - " button.title = name;\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', on_click_closure(method_name));\n", - " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", - " buttonGroup.appendChild(button);\n", - " }\n", - "\n", - " if (buttonGroup.hasChildNodes()) {\n", - " toolbar.appendChild(buttonGroup);\n", - " }\n", - "\n", - " // Add the status bar.\n", - " var status_bar = document.createElement('span');\n", - " status_bar.classList = 'mpl-message pull-right';\n", - " toolbar.appendChild(status_bar);\n", - " this.message = status_bar;\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = document.createElement('div');\n", - " buttongrp.classList = 'btn-group inline pull-right';\n", - " button = document.createElement('button');\n", - " button.classList = 'btn btn-mini btn-primary';\n", - " button.href = '#';\n", - " button.title = 'Stop Interaction';\n", - " button.innerHTML = '';\n", - " button.addEventListener('click', function (_evt) {\n", - " fig.handle_close(fig, {});\n", - " });\n", - " button.addEventListener(\n", - " 'mouseover',\n", - " on_mouseover_closure('Stop Interaction')\n", - " );\n", - " buttongrp.appendChild(button);\n", - " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", - " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", - "};\n", - "\n", - "mpl.figure.prototype._remove_fig_handler = function (event) {\n", - " var fig = event.data.fig;\n", - " if (event.target !== this) {\n", - " // Ignore bubbled events from children.\n", - " return;\n", - " }\n", - " fig.close_ws(fig, {});\n", - "};\n", - "\n", - "mpl.figure.prototype._root_extra_style = function (el) {\n", - " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", - "};\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function (el) {\n", - " // this is important to make the div 'focusable\n", - " el.setAttribute('tabindex', 0);\n", - " // reach out to IPython and tell the keyboard manager to turn it's self\n", - " // off when our div gets focus\n", - "\n", - " // location in version 3\n", - " if (IPython.notebook.keyboard_manager) {\n", - " IPython.notebook.keyboard_manager.register_events(el);\n", - " } else {\n", - " // location in version 2\n", - " IPython.keyboard_manager.register_events(el);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", - " // Check for shift+enter\n", - " if (event.shiftKey && event.which === 13) {\n", - " this.canvas_div.blur();\n", - " // select the cell after this one\n", - " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", - " IPython.notebook.select(index + 1);\n", - " }\n", - "};\n", - "\n", - "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", - " fig.ondownload(fig, null);\n", - "};\n", - "\n", - "mpl.find_output_cell = function (html_output) {\n", - " // Return the cell and output element which can be found *uniquely* in the notebook.\n", - " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", - " // IPython event is triggered only after the cells have been serialised, which for\n", - " // our purposes (turning an active figure into a static one), is too late.\n", - " var cells = IPython.notebook.get_cells();\n", - " var ncells = cells.length;\n", - " for (var i = 0; i < ncells; i++) {\n", - " var cell = cells[i];\n", - " if (cell.cell_type === 'code') {\n", - " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", - " var data = cell.output_area.outputs[j];\n", - " if (data.data) {\n", - " // IPython >= 3 moved mimebundle to data attribute of output\n", - " data = data.data;\n", - " }\n", - " if (data['text/html'] === html_output) {\n", - " return [cell, data, j];\n", - " }\n", - " }\n", - " }\n", - " }\n", - "};\n", - "\n", - "// Register the function which deals with the matplotlib target/channel.\n", - "// The kernel may be null if the page has been refreshed.\n", - "if (IPython.notebook.kernel !== null) {\n", - " IPython.notebook.kernel.comm_manager.register_target(\n", - " 'matplotlib',\n", - " mpl.mpl_figure_comm\n", - " );\n", - "}\n" - ], + "image/png": "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26RJkz755je/uWPcuHE9li9fnrVy5cqsnj177po4ceKqSy65pPeaNWtaANx9990rR4wYsX3atGk511133QEAZsbs2bPzOnToUHLrrbd2e/755zvu3r3bRo0alX/PPfesrajtzz77bPvK2lgZBXipc6l8mdTOM4cy9d4/0S6tZUM3pdG6vWvNyxzZ8TjaJr8p9SvNOLrvp9zedV65rJL1/ROvtvuS2rQq5a1cuTL78ccfXz5s2LBPTz311H5//etfO1x99dVbv/e97/W95557Vo4aNarw2muv7XHjjTf2eOihh1bdf//9Xf/whz+sOv7443fEq2/nzp1peXl5H7/yyis5V1xxRd8lS5YsAFiyZEn2u+++m5eTk+Onn35633Hjxm04+eSTC5csWdLi5JNP7r98+fIFd911V/cJEyZ8OmLEiO0FBQVprVq1KvnHP/7RdunSpdlz585d6O5861vfOuiVV17J6dev3+6K2l5VGyuiAC8iIimjZ8+eu4YNG7YT4Mgjj9yxYsWKrC1btqR//vnn6aNGjSoE+P73v79lzJgx1fq1oQsuuGArwCmnnFJYWFiYtnnz5nSAkSNH5pceSb/11lttlyxZsvdbfmFhYXpBQUHaMcccU3j99dfvf84552w9//zzPzvwwANLXn311bazZs1qO3DgwIEAO3bsSMvLy8vu16/f7nhtr01fKMCLiEjKaNGixd7h6/T0dN+5c2etrhYzs7jPW7duXVKa5u588MEHC1u1arXP0PlvfvOb9WeeeWbBiy++2O7rX//6gGnTpi1xd6699tp148eP3+fc1aJFi1oku+26TE5ERFJap06ditu2bVv86quv5gBMnjy507HHHlsIkJOTU1xQUJBeUdmnnnqqA8D06dNz2rRpU9ypU6fisuscd9xx237729/uPRk1e/bslgALFizIGjp06M5f//rX6w8//PDt8+fPzz7llFO2PfbYY50LCgrSAD755JPMNWvWVHqwXVUbK6IALyIidaJtp5yixlLfww8//MmNN97YKzc3d+DcuXNb3nHHHWsBxo4du/lHP/pR7wEDBgwsLCy0suWys7P9kEMOGXjNNdf0njhx4op4dU+aNGnVBx980Do3N3fggQceeOi9997bBeDOO+/s2r9//0Nzc3MHZmZm+tlnn13w7W9/e9uYMWO2Hn300QNyc3MHnnXWWQfm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\n", "text/plain": [ - "" + "
" ] }, - "metadata": {}, + "metadata": { + "needs_background": "light" + }, "output_type": "display_data" - }, + } + ], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "\n", + "ax = simple.plot(11)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is possible to give your model a `name` - this will be shown when plotting and will be used when writing the files." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ { "data": { - "text/html": [ - "" - ], + "image/png": "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nHPpLebBveGEvq40jnuAd84559JQ2p2Dd8451zR8r+sFg7esL0xZnMnrnFv8t7WTUnpjm3HjxnU+5ZRTtvTt27colfWmUrJt9ADvnKtXGR071rpMaeFWrGhnPbSmYcXatUOZLfdrNpXBvT7qA3j88ce7HHTQQduTDfBFRUVkZSW6i3bqJNvGlrvnOecaxCFvrKvV+qUm3rjzCPImv1tPLWoYymrFp/fvw+iv+VU9DWXevHmtRo4cOWDYsGGFM2bMyO3evfvOadOmLcjNzbW333679Y9+9KM+27dvj/Xp02fHk08+ueSFF17Imz17dpsxY8b0z8nJKZ0xY8bc3NzcXXMIhg0btu/++++/7Z133mlXUlKiCRMmLP72t7+9bezYsT0XLVqU/fnnn2f36tVrx/jx45decMEFfZYvX94K4O677/58+PDhW1988cXcq6++ei8ASbz99tsFHTt2LL3pppu6P/vss5127typE088cdM999yzorK2P/300x2qamNVPMC7epfOl0ltP20Yz9/7B9rHWjd2U5qs27rVvszBnY4kL/VNaVgxcWi/z7it26wKWaWrBiRfbY/5dWlV2vv8889zHn/88UVHHHHEZyeccEL/Rx99tOPll1++4Qc/+EG/e+655/MTTzyx8Morr+x53XXX9XzooYeW3n///d1+97vfLT3qqKO2Japv+/btsYKCgk9efvnl3EsuuaTf/Pnz5wDMnz8/57333ivIzc21k08+ud/YsWNXH3/88YXz589vdfzxxw9YtGjRnLvuuqvHuHHjPhs+fPjWzZs3x9q0aVP6zDPP5C1YsCBn5syZc82M73znO/u8/PLLuf37999ZWdura2NlPMA755xLG7169dpxxBFHbAc4+OCDty1ZsiR7/fr1GV988UXGiSeeWAjwwx/+cP2oUaNq9N+GzjnnnA0AI0eOLCwsLIytW7cuA2DEiBGbyo6k//3vf+fNnz9/16/8wsLCjM2bN8cOO+ywwmuuuWbPM888c8Po0aM37r333qWvvPJK3vTp0/MGDRo0CGDbtm2xgoKCnP79++9M1Pa69IUHeOecc2mjVatWu4avMzIybPv27XW6WkxSwudt27YtLUszMz788MO5bdq02W3o/Pbbb1912mmnbX7uuefaf/Ob3xz44osvzjczrrzyypXXXnvtbueu5s2b1yrVbffL5JxzzqW1zp07l+Tl5ZW88soruQATJ07sfPjhhxcC5ObmlmzevDmjsrKTJ0/uCDBt2rTcdu3alXTu3Lmk/DpHHnnkll//+te7Tka9/fbbrQHmzJmTPWzYsO2/+tWvVh144IFbZ8+enTNy5Mgtjz32WJfNmzfHABYvXpy1fPnyKg+2q2tjZTzAO+ecqxd5nXOLm0p9kyZNWnzdddf1zs/PHzRz5szWd9xxxwqAMWPGrPvJT37SZ+DAgYMKCwtVvlxOTo7tt99+g6644oo+48ePX5Ko7gkTJiz98MMP2+bn5w/ae++997/33nu7Atx5553dBgwYsH9+fv6grKwsO+OMMzZ/97vf3TJq1KgNhx566MD8/PxBp59++t6bNm2qMnhX18bKpN3tYocOHWr+v+iblrSeZHfqMKbcew8dYzkJ87NU6x/dDjjo15fT/b73Gu4FrRSS/S6MJX6PYznZtH81h6f6vV6HhiWot54m2Un6wMyG1qWOjz/+eMngwYNrd9lEEzZs2LB9k5nc1pA+/vjjLoMHD+6bKM/PwTtXB+1mLOPkn12DJfiOL+wlXrzkTvpl5TZ8w5q5Uy/+J9NP2afBXm/Jwu4MHDub0m21+x7P6NCegt/vQ59eFWNaZqyUa3v+DWiVolY6Vzse4J2rg+LlK+jw2IqEeZ0OPYDNF9fvP8BIV7d0nQNd5zTY613d6RDmZibxQyw7myuGvsHYTpXdEteDe3P2/vvvz2vsNtSFn4N3zjnn0pAHeOeccy4NeYB3zjnn0pAHeOeccy4N+SQ755xz9aJ09bDB2KbUxRl1KI51fz+lt4tNtXXr1mU8+OCDna6//vq1jd0WD/DO1UHsoEEU/LgtZJZWyMvtsJ3emcVAnf6dtGsAp3b8kL/fdwGlxbUb1MzILuH2ts/j73ElUhncU1RfcXExmfV4C9/169dnTJw4sZsHeOeaua19c/loZFV3k2vboO1xyTkqBxYeOynJ0g0b3Ku6E11Lv9PcvHnzWo0YMWLAAQccsG327Nlt8vPztz/99NNLBg4cuP8pp5yy4Z///GfelVdeuapLly4lv/zlL3vu3LlTffr02fHUU08tad++fenll1/ea9q0aR0yMjLs6KOP3jJhwoRlK1asyEx0K9ixY8f2XLp0aavPPvsse8WKFa0uu+yy1TfeeOOaq6++uvfSpUuzBw4cOOhb3/rWlvHjxy9rrP7wAO+ccy5tLFmyJGf8+PFLhg8fvnXUqFF9f/vb33YF6Ny5c/Enn3wyd+XKlZknn3zy3tOnT/80Ly+v9Oc//3mPW2+9tfs111yz5qWXXuq4aNGi2bFYjLK7xl166aV7JroVLMCCBQty3n777XmbNm3K2G+//b527bXXrr3rrruWnXTSSa0LCgo+acx+AA/wzjnn0kiPHj12Dh8+fCvAeeedt37cuHHdAMaMGbMR4M0332y7cOHCnGHDhg0EKCoq0pAhQwo7d+5ckp2dXXrWWWf1PemkkzadddZZm6HyW8ECDB8+fFPr1q2tdevWxZ06dSpatmxZk4qpTaoxzjnnXF1UdnvXdu3alUJ0a9cjjzxyy/PPP7+4fNmPPvpo7tSpU/OmTJnS8f777+/27rvvflrZrWABsrOz42/vSnFxcY1vBNMQqp1RIukhSWskzY5LGyzpHUmzJD0vKS+kZ0l6JKTPlXRDXJklIf0jSTPi0jtJelXS/PC3Y0iXpHGSFkiaKemQ1G66c865dLNy5cpWr732WluAJ554otMRRxxRGJ9/9NFHb50xY0bu7NmzswG2bNkSmzlzZvbmzZtjGzZsyDjrrLM2P/DAA0sLCgraQOW3gq1M+/btS7Zu3dokLkGvSSMeBkaUS3sQuN7MDgCeBa4N6aOA7JA+BLhUUt+4ct82s4PK3bHoeuAfZjYA+Ed4DjASGBAelwD313SjnHPONQHqkNLbxdakvr59+375xz/+sVv//v3337RpU+Y111yz22z2nj17Fo8fP37J2Wef3T8/P3/Q0KFDB86aNStn06ZNGSNGjBiQn58/6PDDD9/31ltvXQqV3wq2Mj169CgZMmRI4YABA/a/9NJLe9dtg+um2iF6M5teLkgD5APTw/KrwDTgJsCAtpIygdbATmBLNS9xKnB0WH4EeBO4LqQ/atH9bN+V1EHSHma2sro2O+eca3yNcc16ZmYmzz333G7D78uXL58V//yUU0754pRTTplbvuysWbMqpO2xxx7FL774YoW7Cd1999273WVq/vz5u+6OlGj4vzEkew5+DlEA/jvRUfueIX1KSF8JtAGuMrMNIc+A/5NkwHgzmxDSu8cF7VVA97DcC1ga95rLQlqFAC/pEqKjfPbaa68kN8nVRaJLd85efAybjtnaCK1xzjmX7HmCC4HLJX0AtCM6UgcYBpQAPYF+wNWS+oe8I83sEKKh9x9LOqp8peFovcJEhuqY2QQzG2pmQ7t2rXL0xDnnXJrad999d8YfSbd0SQV4Mysws+FmNgSYDCwMWecAr5hZkZmtAf4NDA1lloe/a4jO2w8LZVZL2gMg/F0T0pfz1cgAQO+Q5pxzrmkqLS0tbVIzydNZ6OuK/0YzSCrAS+oW/saAG4EHQtbnwDEhry1wGFAgqa2kdnHpw4GyWflTgfPD8vnAc3HpY8Js+sOAzX7+3TnnmrTZa9eube9Bvv6VlpZq7dq17fkqllZQ7Tl4SZOJJsF1kbQM+AWQK+nHYZVngLL/8XgfMEnSHEDAJDObGYbpnw3XI2YCT5rZK6HMHcBfJV0EfAacGdJfAk4AFgDbgAtqtNXOOecaRXFx8cWrVq16cNWqVV/D71Za30qB2cXFxRdXtkJNZtGPriTrDwnWLSSadFc+fREwuJL61wPHJkg34McVSzjnnGuKhgwZsgY4pbHb4SL+n+xcvRnS/jOeOn84sdReCdvg2n2+k6zXPkiYl7N6B8d9PIaczIob2aPtFv7c94UqbkTTdNT1JiVV3QDFOdc4PMC7enNtp4WM/cV9jd2MOhs64xy6vZY4T+98TMeTEp9u3Pz1A1g8OcZBfidR51wj8ADfwhwXq3AGpVqL7jyc+d9P7h8JZqj5n4aL/nVDFSxxvkpqfcWnc86lTPP/9nXOOedcBR7gnXPOuTTkAd4555xLQx7gnXPOuTTkk+ycq0bfDhvYNHxowrxWG3dgM2YnnGgX27aT25adSL+26yvk5Wbs4H86zaBjRpuUt7cydb0UzjnXvHiAd64aj/d/kQ0P7kyYd/niMyg6PpvSL7+skFfyyadsO7Edc2J5FQv26sFLz6zm3HYVg79zzqWCB3jnqtEm1oo2sVYJ8zpnb2VVZQXNKNmyJWFWZrt2FFlGahroXFDVPxzyEZyWx8/BO+ecc2nIA7xzzjmXhjzAO+ecc2nIA7xzzjmXhnySXQuTsV/t7/pVnFdSDy1JD+2ztrNqYD4ZO4oq5GlHEcVLlkKp959zruF5gG9h/uf5qbUuMyBrPZCb+sakgVu6v8V7Uz6mNMFg2JNrv87607tQvGp1I7TMOdfSeYBvYUa02ZFEKQ/ulWkfa83wNhWP3gGWtF/I8xmDG7hFzjkX8XPwzjnnXBryAO+cc86lIQ/wzjnnXBryAO+cc86lIZ9k51w9yVIJ1j6XjG0dK+SVts8lS375nHOu/niAd66enJq7kNVPtWdbacUb1bSJreCENkuBhrtdrHOuZfEAH+e42KjGbkKdZHTsyCFvrOO2brMauykO6JLRlp91mVfFGh7cXcOp6k5zLj35OXjnnHMuDXmAd84559JQtQFe0kOS1kiaHZc2WNI7kmZJel5SXkjPkvRISJ8r6YZydWVI+q+kF+LS+kl6T9ICSX+R1CqkZ4fnC0J+35RttXPOOZfmanIE/zAwolzag8D1ZnYA8CxwbUgfBWSH9CHApeUC80+BueXq+g1wj5ntA2wELgrpFwEbQ/o9YT3nnHPO1UC1Ad7MpgMbyiXnA9PD8qvA98pWB9pKygRaAzuBLQCSegMnEv04IKQJOAaYEpIeAU4Ly6eG54T8Y8P66SOWkdpHhp9xcc45F0l2Fv0cogD8d6Kj9j1D+pSQvpJoivBVZlb24+D3wP8C7eLq6QxsMrPi8HwZ0Css9wKWAphZsaTNYf115Rsj6RLgEoC99toryU1qWCVHH8LOGzaSEStNWZ3ZGcWM6fgu0DZldTrnnGuekg3wFwLjJN0ETCU6UgcYBpQAPYGOwFuSXgMGAWvM7ANJR9epxQmY2QRgAsDQoUMt1fXXh209WvHa/n+hTaziNdJ148HdJeaXSTnXsiQV4M2sABgOICmfaOgd4BzgFTMrAtZI+jcwFDgYOEXSCUAOkCfpceA8oIOkzHAU3xtYHupaTjQysCwM+bcH1ifTXuecc66lSeqkraRu4W8MuBF4IGR9TnROHUltgcOAAjO7wcx6m1lf4GzgdTP7vpkZ8AZwRih/PvBcWJ4anhPyXw/rO+ecc64aNblMbjLwDrCvpGWSLgJGS/oUKABWAJPC6vcBuZLmAP8BJpnZzGpe4jpgrKQFROfYJ4b0iUDnkD4WuL52m+acc861XNUO0ZvZ6Eqy/pBg3UKiSXdV1fcm8Gbc80VE5+7Lr/dldXU555xzLjG/rso555xLQ36zmTifThrSYK/Vt/dKslX77o/1mF+n1/WZ1M451zJ4gI+z+PiJ1a+UUj6A4pxzrn54hHHOOefSkAd455xzLg15gHfOOefSkAd455xzLg15gHfOOefSkM+id841uB1WxLc+Hs3qFR0S5l93xMtc1mF5wryq3LZuIBPfP7KOrdtddt4OXjvsfnpn5qa0XufqW4sL8H4duHON70srptWETuT//f2E+eOf+yaXHfpUret96MNvkH/xjLo2bzexAwey4Nk8emem7tbOzjUEH6J3zjnn0pAHeOeccy4NeYB3zjnn0pAHeOeccy4NeYB3zjnn0lCLm0Xf0lV1Nzq/wiCxV7Zlc/vCExLm7dthDX/qPZ0sZTRwqxL77Ya9eX75gQnzTu41k2s7Lax1nR/t2MFVC86kpDR1xwNfFmfSYfWOyvP/24mjck6vdb1tZ2cn1Z7MHt1Ze3x/LMHbuLOduGr2mbTLqby9icRk3DngaYZlZyXVpmRM/xL+34LTKDVVyNur3UYm9nmVbDVce1zj8gDvXDV+PvdUupy6EKziZVIFpw1j2x9fo71aN0LLKpr4zHD6/OLdxHm3DOfai++vdZ0T1n2L1t/bSMkXX9S1ebu0BjCrNH+vW95Jst4lSZXbNnhPptz6W3pltKmQ91RhV5448ShKFtau7ljr1tz96vE81e/1pNqUjNuXnEjOiSuw4qIKeSuOPYTVD73AXpke4FuK9AvwRbP9SNSllJmgtCRhnqoIUo3CVHngTHBUV+NqS0qqDMgp1wj9mgVkqOIoRQalqNRq3SYrSbzP1CczIStN2Fb5Zfwtjp+Dd84559KQB3jnnHMuDXmAd84559KQB3jnnHMuDaXfJDvnkvDm9hgvbh6cMG/Tkg50TfHrVXW5Yl18ehNw01VVrFFVXmIP9ABSN4G+CbsuYeo5PeCcBXBcbFTtqis13l/Yl2tbH1zrloxoP5NjW9d+kt7eeeuY+52Do0mB5awf1IocJT/R0jU/HuCdAy5440L2uyZx0N135yx8ArKrLSvayb4/msecrLxal51yx0UsPmVCrcvd0/Mt1k54LWFeK4luGW1rXadrvjzANzNVXQJYX0eFLUJxjJJNmxu7FS7NlG7dmlQ5FSV3pJ2tLHr7de4u8HPwzjnnXBryAO+cc86loWoDvKSHJK2RNDsubbCkdyTNkvS8pLyQniXpkZA+V9INIT1H0vuSPpY0R9ItcXX1k/SepAWS/iKpVUjPDs8XhPy+Kd9655xzLk3V5Aj+YWBEubQHgevN7ADgWeDakD4KyA7pQ4BLQ2DeARxjZoOBg4ARkg4LZX4D3GNm+wAbgYtC+kXAxpB+T1jPOeecczVQ7SQ7M5ue4Og5H5gell8FpgE3AQa0lZRJdD+JncAWMzOgMKyfFR4mScAxwDkh7xHgZuB+4NSwDDAFuFeSQl2uHjSHO82VWCnv7oD1JbkV8jJUyqHZ632msKsXr5Y+nVS5Wl9eV0+qn4Trl9Clm2Rn0c8hCsB/Jzpq3zOkTwnpK4E2wFVmtgFAUgbwAbAPcJ+ZvSepC7DJzIpD+WVAr7DcC1gKYGbFkjYDnYF15Rsj6RLgEoC9evmFAelsfel2xt58DZ3fXVMxUyJzQiFTB7zS8A1zzrkmJtloeCEwTtJNwFSiI3WAYUAJ0BPoCLwl6TUzW2RmJcBBkjoAz0r6GrCqTq0PzGwCMAFg6OAcP8JPYyVmtFldRMmnCe5rHsug8Mu9Gr5RzjnXBCU1i97MCsxsuJkNASYDZd+25wCvmFmRma0B/g0MLVd2E/AG0Xn99UCHMKQP0BtYHpaXE0YGQn77sL5zzjnnqpFUgJfULfyNATcCD4Ssz4nOqSOpLXAYUCCpazhyR1Jr4DigIJxPfwM4I5Q/H3guLE8Nzwn5r/v5d+ecc65manKZ3GTgHWBfScskXQSMlvQpUACsACaF1e8DciXNAf4DTDKzmcAewBuSZob0V83shVDmOmCspAVE59gnhvSJQOeQPha4vu6b65xzzrUMNZlFP7qSrD8kWLeQaNJd+fSZQMI7LpjZIqJz9+XTv0xUl3POOeeq51POXZNTYqWsLNmW8AYvK4pbEytO7kzNxpJtfGGJbxujHan/p45+bwCXrNhO8XlxYcK8dorRMaNNA7fINUce4F2TM2tnEZfcci3tPt9ZIU+lRvZHC6j9jTThkBevpP9fE5ccuHRNUnU6Vx/y71/NBS/+NGHeojMyWHxa7e8051oeD/BpJF3uNLfFsun84UZKZxYkzE82ELdenknm6++ntE7n6kPJgsVkLlicMK/N149o4Na45spvNuOcc86lIQ/wzjnnXBryAO+cc86lIQ/wzjnnXBrySXauRqqbpFfZBL8iq/30tSJrBbEYxDJqVU4ZGZSaKn/N+vg/iAY7rDTha2bXw8u55qequ9Alc6c5WeWfq+w9FtW6Ppe+PMC7evOb9QN4+vffIVbLGF/cGopv3kTntr1r/Zrr3+rBN566ImFenxkbEl5bXxftZizj5J9dgyX4LTLjoRS/mHPAXs+v5xurEu/jvs+5eB7gXb3575Y96fL4h9iOHbUql9mvD+ddOZ2z222sVbkiK+GIyVfQ8eF3EuanOrgDFC9fQYfHViTO9C9bVw9K5syj45xKMn2fc3H8HLxzzjmXhjzAO+ecc2nIA7xzzjmXhjzAO+ecc2nIA7xzzjmXhnwWvas3fz18PGxvuNfLBj54EHgwufLJXJPsXF1UdY28c3XlR/DOOedcGvIA75xzzqUhD/DOOedcGvIA75xzzqUhD/DOOedcGvIA75xzzqUhv0yukfx9ay5XvXV2re+AktW2iGlH3Ee/rNz6aViS+r10cYW0zy5shIbUgV+y5JxLJx7gG8nDK77BvpfOxIp21qpcxoD+fPxSD/plFdZTy5KTf/GMionNLMA751w68SF655xzLg15gHfOOefSkAd455xzLg1VG+AlPSRpjaTZcWmDJb0jaZak5yXlhfQsSY+E9LmSbgjpe0p6Q9InkuZI+mlcXZ0kvSppfvjbMaRL0jhJCyTNlHRI6jffOeecS081OYJ/GBhRLu1B4HozOwB4Frg2pI8CskP6EOBSSX2BYuBqMxsEHAb8WNKgUOZ64B9mNgD4R3gOMBIYEB6XAPfXeuucc865FqraWfRmNj0E6Xj5wPSw/CowDbgJMKCtpEygNbAT2GJmG4CVob4vJM0FegGfAKcCR4e6HgHeBK4L6Y+amQHvSuogaQ8zW1lVewu2d+CoWadXSM/OLOa+fZ4iP6ttdZtcKw9s6sWTS4fVutyKWd3Zu2RdrcupcBvX/OcM7u68uVbl/tWj1i/lnHOuGUv2Mrk5RAH470RH7XuG9CkhfSXQBrgqBPddwo+Fg4H3QlL3uKC9CugelnsBS+OKLgtpFQK8pEuIjvLJoQ2tRyyp0OCMbl2Z8uoh/KzLvJpvZQ38dtrJ7DP2vepXLGdvloBZrcsVr1zF3ueurnU5SmpfxDnnXPOVbIC/EBgn6SZgKtGROsAwolDSE+gIvCXpNTNbBCApF/gbcKWZbSlfqZmZpFpHPTObAEwAyFMnSxg4S0ooRbWtuqYNqJ96U/h6fq9z55xrWZIK8GZWAAwHkJQPnBiyzgFeMbMiYI2kfwNDgUWSsoiC+xNm9kxcdavLht4l7QGsCenL+WpkAKB3SHPOOedcNZK6TE5St/A3BtwIPBCyPgeOCXltiSbUFUgSMBGYa2Z3l6tuKnB+WD4feC4ufUyYTX8YsLm68+/OOeeci9TkMrnJwDvAvpKWSboIGC3pU6AAWAFMCqvfB+RKmgP8B5hkZjOBbwDnAcdI+ig8Tghl7gCOkzQf+E54DvASsAhYAPwZuLzum+ucc861DDWZRT+6kqw/JFi3kGjSXfn0f0HiE+Bmth44NkG6AT+urn3OOeecqyjtbjZjeW0oOnxohfRtuRn0bjUr5a+Xscc2ioZXfL3qtNrwJTZjdvUrOuecc0mQNfQM8Hp24IFZ9sJLXSqkZwBdMlqTpYyUvl5h6ZdsKi2udbnvF3yfnBNX1Ppucs2J337VueZD0gdmVvujFddkpd0RfCtl0Duz4e6VnhvLITeJqYodsrfzZeqb45xzzgF+sxnnnHMuLXmAd84559KQB3jnnHMuDXmAd84559KQB3jnnHMuDaXdZXKS1gKfVZLdBaj9PVpbBu+bqnn/VM77pnLNqW/6mFnXxm6ES520C/BVkTTDr/NMzPumat4/lfO+qZz3jWtMPkTvnHPOpSEP8M4551waamkBfkJjN6AJ876pmvdP5bxvKud94xpNizoH75xzzrUULe0I3jnnnGsRPMA755xzaahZBHhJe0p6Q9InkuZI+mlIHxWel0oaWq7MgZLeCfmzJOWE9FaSJkj6VFKBpO+F9GxJf5G0QNJ7kvrG1XVDSJ8n6fgG3PRqpbhvRofnMyW9IqlLSO8k6VVJ88PfjiFdksaFvpkp6ZCG3v6q1LZvJJ0r6aO4R6mkg0LekNA3C8I2K6Q3y76B1PWPpDaSXgyfpzmS7ogr0yI+V1XtO3HrTJU0O+55s913XDNhZk3+AewBHBKW2wGfAoOA/YB9gTeBoXHrZwIzgcHheWcgIyzfAtwWlmNAl7B8OfBAWD4b+EtYHgR8DGQD/YCFZXU1hUeq+iakr4nrjzuBm+OWrw/L1wO/CcsnAC8DAg4D3mvs/qhL35QrewCwMO75+2EbFbZ5ZHPum1T2D9AG+HZYbgW8Fdc/LeJzVdW+E9K+CzwJzI5La7b7jj+ax6NZHMGb2Uoz+zAsfwHMBXqZ2Vwzm5egyHBgppl9HMqsN7OSkHch8OuQXmpmZf9l6lTgkbA8BTg2HKWdCjxlZjvMbDGwABiW+q1MTgr7RuHRNmx3HrAilInvm0eA0+LSH7XIu0AHSXukfCOTlETfxBsNPAUQtinPzN41MwMeZfc+aHZ9A6nrHzPbZmZvhOWdwIdA77BeS/lcxdvVNwCScoGxwG3l1mu2+45rHppFgI8XhvgOBt6rYrV8wCRNk/ShpP8NZTuE/FtD+tOSuoe0XsBSADMrBjYTHd3uSg+WhbQmpy59Y2ZFwI+AWUSBfRAwMZTpbmYrw/IqoEKfBc29b+KdBUwOy72Itq1M/HY2+76BOvdPfD0dgJOBf4SklvK5ile+b24F7gK2lVsvLfYd13Q1qwAffgn/DbjSzLZUsWomcCRwbvh7uqRjQ3pv4G0zOwR4B/hd/ba6YdS1byRlEQX4g4GeRMP4N5QvHI5gm9W1lbXom7L1vw5sM7PZ1a0brzn2DaSufyRlEgW2cWa2qF4a28Dq2jfhPPzeZvZsVeWa677jmrZmE+BDAPob8ISZPVPN6suA6Wa2zsy2AS8BhwDriX5Fl5V/OqQDLAf2DK+VCbQP6+9KD3qHtCYjRX1zEICZLQxfNn8FjghlVpcNEYa/a0J6uvVNmbPZ/QhsOV8NOcPu29ls+wZS1j9lJgDzzez3cWkt5XNVpnzfHA4MlbQE+BeQL+nNkNes9x3X9DWLAB/O2U0E5prZ3TUoMg04IMzuzQS+BXwSAtfzwNFhvWOBT8LyVOD8sHwG8HpYfypwdpgN3A8YQDThqklIVd8QfYEMklR2N6njiM47wu59cz7wXFz6mDDr9zBgc9yQY6NLom+QFAPOJO4catimLZIOC3WOYfc+aHZ9A6nrn5B+G1HwvrJckZbyuaps37nfzHqaWV+iEbNPzezokN1s9x3XTNRlhl5DPYg+GEY0bPxReJwAnE50RLoDWA1MiyvzfWAOMBu4My69DzA91PUPYK+QnkN0RL+A6Iumf1yZnxPN8p1HmB3cVB4p7pvLiIL6TKIfQp1DeufQV/OB14BOIV3AfaFvZlHJrOJm1jdHA+8mqGto6K+FwL189V8gm2XfpLJ/iI4wLew7ZfVc3AI/Vwn3nbj8vuw+i77Z7jv+aB4P/1e1zjnnXBpqFkP0zjnnnKsdD/DOOedcGvIA75xzzqUhD/DOOedcGvIA75xzzqUhD/DOOedcGvIA75xzzqWh/w/Q/6mWHdw4XgAAAABJRU5ErkJggg==\n", "text/plain": [ - "" + "
" ] }, - "metadata": {}, + "metadata": { + "needs_background": "light" + }, "output_type": "display_data" - }, + } + ], + "source": [ + "simple.name=\"scen_1\"\n", + "ax = simple.plot(11)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Rather than showing just the presence, a detailed plot will also show why certain parts are not present" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ { "data": { + "image/png": "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r7/++ojPWUjGRx991HLBggW1b7311l5nnHHGQT/+8Y+33n///Xsff/zx22655ZaNjY2N7NixI6Ef7z/+8Y+33nPPPd1CL2tcddVV9bfddlsdwOmnn37AQw891OHss8/eBvDZZ5+V1NbWLn/uuefaXXjhhQesXr16WWh+F110Ue/x48d/NHz48J2rV69uNXz48D7vvvvusq9v2VvWUmGKXqE2Z9e/eblvfnVHq+NY5Yt1XAr1mJn0HHrooZ+98sor7X/yk5/0fP7559t17ty5afHixXv16tVr18CBA3cBnHvuuVteffXVci+2d+qpp37SokULBg8e/PmWLVtaAgwdOvTTmTNndhk/fvy+8+fPb9OpU6fmePlE89xzz5UPHDiwsm/fvtX//e9/y5cuXdomOO/ss8/+GOCUU07ZuXPnzpLNmze3CF33tddea/+zn/1sv8rKyurvfe97B+3cubPFtm3bMn7Ot6DCmAIRfoLuNmiS70++oeVL9HKHXwKlaCKVz+/HoVAMHDhw16JFi5YPGDDgs+uuu67nlVde2SPdPEXky+ngZZOgvfba68vbZ4PjaJ1yyik7586du7Jnz55fnHfeeQfceeednePlE0lDQ4NcccUV+z/++OPvrFq1avkPf/jDzZ9//vmX5+zQ/CL9r6osWrRoRW1t7fLa2trl9fX1Szp06JBygJMoCyqMKRD5fuJKtGXC7/vp9/IVsrVr17YsLy9vvuSSSz4eP378xrfeequspqbm8w0bNrRaunRpa4AZM2Z0/ta3vrUj0Tw7d+68e9GiRXs1NTXxxBNPdIq3/KpVq1r16tVr9xVXXLF5zJgxmxYtWlSWaD7t2rVr2rZtWwuAhoaGEoDu3bs3btu2reSpp57aY52ZM2d2Apg9e3a78vLyps6dOzeFzh82bNj2m266qVvw///+979tyALrU2GMMSYjSjp1avT6ltJY8xcuXNjmmmuu6VVSUkJpaanefffd75eVlenkyZPXjho16htNTU3U1NQ0XHnllZti5RNq4sSJG0477bSD9t5778aampqGTz/9NOaP8dmzZ5dPmjSpe2lpqZaVlTU98MAD7yWaz5gxYzb/9Kc/3f+qq65qXrBgwYpzzjlnU1VV1cFdu3ZtrKmp+TR02b322kurqqqqGxsb5d57730vPK977713/bhx4/br27dvdVNTkxx++OE7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\n", "text/plain": [ - "" + "
" ] }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" } ], "source": [ - "simple.name=\"scen_1\"\n", - "simple.plot(11)\n" + "ax = simple.plot_detail(7)" ] }, { @@ -2340,46 +418,9 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_summary.csv already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_log.txt already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V01.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V02.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V03.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V04.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V05.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V06.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V07.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V08.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V09.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V10.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V11.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V12.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V13.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V14.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V15.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V16.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V17.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V18.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V19.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V20.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V21.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V22.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V23.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V24.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V25.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V26.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V27.tif already exists\n", - "WARNING:niche_vlaanderen:Warning: file _output_scen1/scen_1_V28.tif already exists\n" - ] - } - ], + "outputs": [], "source": [ "simple.write(\"_output_scen1\", overwrite_files=True)" ] @@ -2393,7 +434,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -2431,7 +472,7 @@ " 'scen_1_V21.tif']" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -2451,7 +492,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -2459,7 +500,7 @@ "text/plain": [ "# Niche Vlaanderen version: 1.1\n", "# Using latest niche_vlaanderen 1.1\n", - "# Run at: 2022-01-10 10:05:33.767031\n", + "# Run at: 2022-01-17 13:29:44.502823\n", "\n", "package_versions:\n", " pandas: 1.3.4\n", @@ -2516,7 +557,7 @@ " " ] }, - "execution_count": 12, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -2540,28 +581,7 @@ "source": [ "## Running a full Niche model\n", "\n", - "A full Niche model requires more inputs that only mhw, mlw and soil_code. The full list can be found in the [documentation](cli.rst#full-model). It is also possible to look at the `minimal_input` set. When trying to run a model without sufficient inputs, a warning will be generated." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "ename": "AttributeError", - "evalue": "module 'niche_vlaanderen.niche' has no attribute 'minimal_input'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/tmp/ipykernel_25518/4009401367.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnv\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mniche\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mminimal_input\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;31mAttributeError\u001b[0m: module 'niche_vlaanderen.niche' has no attribute 'minimal_input'" - ] - } - ], - "source": [ - "nv.niche.minimal_input()" + "A full Niche model requires more inputs that only mhw, mlw and soil_code. The full list can be found in the [documentation](cli.rst#full-model). When trying to run a model without sufficient inputs, a warning will be generated." ] }, { @@ -2573,7 +593,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -2604,11 +624,86 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
vegetationpresencearea_ha
01not present15.44
11no data15.16
21present8.88
32not present21.52
42no data15.16
\n", + "
" + ], + "text/plain": [ + " vegetation presence area_ha\n", + "0 1 not present 15.44\n", + "1 1 no data 15.16\n", + "2 1 present 8.88\n", + "3 2 not present 21.52\n", + "4 2 no data 15.16" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "full.table.head()" ] @@ -2622,9 +717,84 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
vegetationpresencearea_ha
01no data15.16
11not present14.44
21present9.88
32no data15.16
42present12.80
\n", + "
" + ], + "text/plain": [ + " vegetation presence area_ha\n", + "0 1 no data 15.16\n", + "1 1 not present 14.44\n", + "2 1 present 9.88\n", + "3 2 no data 15.16\n", + "4 2 present 12.80" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "simple.table.head()" ] diff --git a/docs/vegetatie.rst b/docs/vegetatie.rst index 2323e338..c0ed3d19 100644 --- a/docs/vegetatie.rst +++ b/docs/vegetatie.rst @@ -98,6 +98,29 @@ Voorbeeld volledig model Indien ook nog inundatie wordt meegerekend, bvb regelmatig overstromen (1) valt een aantal mogelijke codes weg. Mogelijke vegetaties zijn dan 7, 12 en 16. +.. _detail: + +Uitgebreid model +---------------- + +Behalve aanwezig en afwezigheid van vegetatie is het ook mogelijk om na te gaan welke voorwaarden voldaan zijn om een bepaalde vegetatie toe te laten. +Dit gebeurt stapsgewijs: + +* eerst is er controle of de bodem geschikt is +* dan is er controle of de gxg geschikt zijn +* vervolgens wordt er nagegaan of de zuur en of nutrientvereisten voldaan zijn +* tot slot wordt ook gecontroleerd of aan de managements en overstromingsvereisten voldaan is. + +Voor vegetatiecode 6 (Betulo-Quercetum roboris) en 8 (Filipendulion) levert dit met bovenstaande invoergegevens: +vegetatiecode 6: +* bodem ongeschikt (geen verdere controle) + +vegetatiecode 8: +* bodem geschikt +* gxg geschikt +* nutrient niet geschikt +* zuurtegraad geschikt + .. _simple: Vereenvoudigd model diff --git a/niche_vlaanderen/niche.py b/niche_vlaanderen/niche.py index e8bd7afe..67219ee1 100644 --- a/niche_vlaanderen/niche.py +++ b/niche_vlaanderen/niche.py @@ -7,7 +7,7 @@ import numpy.ma as ma import pandas as pd -from .vegetation import Vegetation +from .vegetation import Vegetation, VegSuitable from .acidity import Acidity from .nutrient_level import NutrientLevel from .spatial_context import SpatialContext @@ -80,6 +80,7 @@ def __init__(self, ct_acidity=None, ct_soil_mlw_class=None, self._abiotic = dict() self._code_tables = dict() self._vegetation = dict() + self._vegetation_detail = dict() self._deviation = dict() self._options = dict() self._options["name"] = "" @@ -546,7 +547,7 @@ def run(self, full_model=True, deviation=False, strict_checks=True): veg_arguments['acidity'] = self._abiotic[ 'acidity'] - self._vegetation, self.occurrence = vegetation.calculate( + self._vegetation, self.occurrence, self._vegetation_detail = vegetation.calculate( full_model=full_model, **veg_arguments) if deviation: @@ -555,7 +556,7 @@ def run(self, full_model=True, deviation=False, strict_checks=True): self._inputarray["mlw"] ) - def write(self, folder, overwrite_files=False): + def write(self, folder, overwrite_files=False, detailed_files=False): """Saves the model results to a folder Saves the model results to a folder. Files will be written as geotiff. @@ -575,6 +576,9 @@ def write(self, folder, overwrite_files=False): Note writing will fail if any of the files to be written already exists. + detailed_files : bool + Save detailed information on factor affecting vegetation possibility + """ if not self.vegetation_calculated: @@ -617,10 +621,15 @@ def write(self, folder, overwrite_files=False): path = '{}/{}{}.tif'.format(folder, prefix, i) files[i] = path + if detailed_files: + for vi in self._vegetation_detail: + path = '{}/{}V{:02d}_detail.tif'.format(folder, prefix, vi) + files['%02d_detail'%vi] = path + for key in files: if os.path.exists(files[key]): if overwrite_files: - self._log.warning( + self._log.info( "Warning: file {} already exists".format(files[key])) else: raise NicheException( @@ -640,6 +649,12 @@ def write(self, folder, overwrite_files=False): dst.write(self._abiotic[vi], 1) self._files_written[vi] = os.path.normpath(files[vi]) + if detailed_files: + for vi in self._vegetation_detail: + with rasterio.open(files[vi], 'w', **params) as dst: + dst.write(self._vegetation_detail[vi], 1) + self._files_written['%02d_detail'%vi] = os.path.normpath(files['%02d_detail'%vi]) + # deviation params.update( dtype="float64", @@ -761,26 +776,105 @@ def plot(self, key, ax=None, fixed_scale=True): else: plt.colorbar() - return(ax) + return ax + + + def plot_detail(self, key, limit_legend=True, cmap="Set1"): + """Detailed plot for a vegetation type + key: veg_code (1..28) + key of the vegetation type that should be plotted + limit_legend: boolean + limits the legend to the types present in the figure + cmap: colormap + colormap to use for the maps. Note that 9 combinations of + presence/absence are possible, so keep this in mind if changing + from the default value. + """ + try: + import matplotlib.pyplot as plt + import matplotlib.patches as mpatches + from matplotlib import cm + + except (ImportError, RuntimeError): # pragma: no cover + msg = "Could not import matplotlib\n" + msg += "matplotlib required for plotting functions" + raise ImportError(msg) + + if key in self._vegetation.keys(): + v = self._vegetation[key] + v = ma.masked_equal(v, 255) + title = "{} ({})".format(self._vegcode2name(key), key) + + v = self._vegetation_detail[key] + v = ma.masked_equal(v, 255) + ((a, b), (c, d)) = self._context.extent + mpl_extent = (a, c, d, b) + + fig, ax = plt.subplots() + + legend = VegSuitable.legend() + legend_keys = np.array(list(legend.keys())) + + v_un = np.digitize(v, legend_keys, right=True) + + v_un = ma.masked_equal(v_un, len(legend)) + + plt.imshow(v_un, extent=mpl_extent, cmap=cmap, + interpolation=None, vmin=0, vmax=len(legend)) + + if limit_legend: + present = np.unique(v_un) + legend = {i: legend[k] for i, k in enumerate(legend) if + i in present} + + patches = [mpatches.Patch(color=cm.get_cmap(cmap)(i), + label=legend[j]) for i,j in enumerate(legend)] + + plt.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc=2, + borderaxespad=0.) + + if self.name != '': + title = self.name + " " + title + + ax.set_title(title) + + return ax + @property def table(self): """Dataframe containing the potential area (ha) per vegetation type """ + return self._table() + + def _table(self, detail=False): + """Dataframe containing the potential area (ha) per vegetation type + + detail: boolean + add info why vegetation is present, rather than just present/not present + """ if not self.vegetation_calculated: raise NicheException( "Error: You must run niche prior to requesting the " "result table") td = list() - - presence = dict({0: "not present", 1: "present", 255: "no data"}) - - for i in self._vegetation: - vi = pd.Series(self._vegetation[i].flatten()) - rec = vi.value_counts() * self._context.cell_area / 10000 - for a in rec.index: - td.append((i, presence[a], rec[a])) + if detail == False: + presence = dict({0: "not present", 1: "present", 255: "no data"}) + + for i in self._vegetation: + vi = pd.Series(self._vegetation[i].flatten()) + rec = vi.value_counts() * self._context.cell_area / 10000 + for a in rec.index: + td.append((i, presence[a], rec[a])) + else: + legend = VegSuitable.legend() + legend[255] = 'no data' + for i in self._vegetation_detail: + vi = pd.Series(self._vegetation_detail[i].flatten()) + rec = vi.value_counts() * self._context.cell_area / 10000 + for a in rec.index: + td.append((i, legend[a], rec[a])) df = pd.DataFrame(td, columns=['vegetation', 'presence', 'area_ha']) @@ -1078,6 +1172,8 @@ def plot(self, key, ax=None): return ax + + @property def table(self): """ Return a summarized dataframe for a NicheDelta object diff --git a/niche_vlaanderen/vegetation.py b/niche_vlaanderen/vegetation.py index e2391ea9..d4f11ccf 100644 --- a/niche_vlaanderen/vegetation.py +++ b/niche_vlaanderen/vegetation.py @@ -1,5 +1,6 @@ from __future__ import division from pkg_resources import resource_filename +from enum import IntEnum import numpy as np import pandas as pd @@ -11,6 +12,40 @@ from .exception import NicheException +class VegSuitable(IntEnum): + SOIL=1 + GXG=2 + NUTRIENT=4 + ACIDITY=8 + MANAGEMENT=16 + FLOODING=32 + + @staticmethod + def possible(): + """Possible legend items + + Due to the way niche works, only certain combinations are possible, + eg soil is checked first, so it is impossible to have suitable management + and unsuitable soil conditions. + """ + + return [0, 1, 3, 7, 11, 15, 31, 47, 63] + + @staticmethod + def legend(): + legend = {} + for i in range(64): + l = [] + for j in list(map(int, VegSuitable)): + if i & j == j: + l += [VegSuitable(i & j).name.lower()] + legend[i] = '+'.join(l) + " suitable" + legend[0] = "soil unsuitable" + + # only select possible combinations for legend + sel = VegSuitable.possible() + return {i: legend[i] for i in sel} + class Vegetation(object): """Helper class to calculate vegetation based on input arrays @@ -111,6 +146,8 @@ def calculate(self, soil_code, mhw, mlw, nutrient_level=None, acidity=None, A dictionary containing the different output arrays per veg_code value. -99 is used for nodata_veg values + expected: int + Expected code in veg arrays if all conditions are met veg_occurrence: dict A dictionary containing the percentage of the area where the vegetation can occur. @@ -146,39 +183,61 @@ def calculate(self, soil_code, mhw, mlw, nutrient_level=None, acidity=None, self._ct_management["code"]) veg_bands = dict() + veg_detail = dict() occurrence = dict() for veg_code, subtable in self._ct_vegetation.groupby(["veg_code"]): + subtable = subtable.reset_index() # vegi is the prediction for the current veg_code # it is a logical or of the result of every row: - # if a row is true for a pixel, that vegetation can occur - vegi = np.zeros(soil_code.shape, dtype=bool) + # if a row is 0 for a pixel, that vegetation can occur + + vegi = np.zeros(soil_code.shape, dtype='int64') + + # filter for GxG for row in subtable.itertuples(): warnings.simplefilter(action='ignore', category=RuntimeWarning) - current_row = ((row.soil_code == soil_code) + row_soil = (row.soil_code == soil_code) + vegi |= row_soil * VegSuitable.SOIL + current_row = ( row_soil & (row.mhw_min >= mhw) & (row.mhw_max <= mhw) & (row.mlw_min >= mlw) & (row.mlw_max <= mlw)) + + vegi |= current_row * VegSuitable.GXG warnings.simplefilter("default") if full_model: - current_row = (current_row - & (nutrient_level == row.nutrient_level) - & (row.acidity == acidity)) + vegi |= (current_row & (nutrient_level == row.nutrient_level))* VegSuitable.NUTRIENT + vegi |= (current_row & (acidity == row.acidity)) * VegSuitable.ACIDITY if inundation is not None: - current_row = current_row & (row.inundation == inundation) + vegi |= (current_row & (row.inundation == inundation)) * VegSuitable.FLOODING if management is not None: - current_row = current_row & (row.management == management) - vegi = vegi | current_row - vegi = vegi.astype("uint8") + vegi |= (current_row & (row.management == management)) * VegSuitable.MANAGEMENT + + # this should give same result as before + + expected = VegSuitable.SOIL + VegSuitable.GXG + if full_model: + expected += VegSuitable.NUTRIENT + VegSuitable.ACIDITY + \ + (inundation is not None) * VegSuitable.FLOODING + \ + (management is not None) * VegSuitable.MANAGEMENT + + vegi = vegi.astype('uint8') vegi[nodata] = self.nodata_veg + vegi_summary = vegi == expected + vegi_summary = vegi_summary.astype('uint8') + vegi_summary[nodata] = self.nodata_veg + if return_all or np.any(vegi): - veg_bands[veg_code] = vegi + veg_bands[veg_code] = vegi_summary + + veg_detail[veg_code] = vegi - occi = (np.sum(vegi == 1) / (vegi.size - np.sum(nodata))) + occi = (np.sum(vegi_summary == 1) / (vegi_summary.size - np.sum(nodata))) occurrence[veg_code] = occi.item() - return veg_bands, occurrence + return veg_bands, occurrence, veg_detail def calculate_deviation(self, soil_code, mhw, mlw): """ Calculates the deviation between the mhw/mlw and the reference diff --git a/tests/test_vegetation.py b/tests/test_vegetation.py index 078ebafc..b2e868e7 100644 --- a/tests/test_vegetation.py +++ b/tests/test_vegetation.py @@ -7,6 +7,7 @@ import niche_vlaanderen from niche_vlaanderen.exception import NicheException +from niche_vlaanderen.vegetation import VegSuitable def raster_to_numpy(filename): @@ -39,7 +40,7 @@ def test_one_value_doc(self): mhw = np.array([10]) soil_code = np.array([14]) v = niche_vlaanderen.Vegetation() - veg_predict, veg_occurrence = v.calculate(soil_code, mhw, mlw, + veg_predict, veg_occurrence, _ = v.calculate(soil_code, mhw, mlw, nutrient_level, acidity) correct = [7, 8, 12, 16] for vi in veg_predict: @@ -53,7 +54,7 @@ def test_one_value_simple(self): mhw = np.array([10]) soil_code = np.array([8]) v = niche_vlaanderen.Vegetation() - veg_predict, veg_occurrence = v.calculate(soil_code, + veg_predict, veg_occurrence, _ = v.calculate(soil_code, mhw, mlw, full_model=False) correct = [3, 8, 11, 18, 23, 27] for vi in veg_predict: @@ -67,7 +68,7 @@ def test_borders(self): mhw = np.array([21, 20, 10, 1, 0]) mlw = np.array([30, 30, 30, 30, 30]) v = niche_vlaanderen.Vegetation() - veg_predict, _ = v.calculate(soil_code, mhw, mlw, full_model=False) + veg_predict, _, _ = v.calculate(soil_code, mhw, mlw, full_model=False) expected = [0, 1, 1, 1, 0] np.testing.assert_equal(expected, veg_predict[1]) @@ -78,7 +79,7 @@ def test_one_value(self): mhw = np.array([10]) soil_code = np.array([140000]) v = niche_vlaanderen.Vegetation() - veg_predict, veg_occurrence = v.calculate(soil_code, mhw, mlw, + veg_predict, veg_occurrence, veg_detail = v.calculate(soil_code, mhw, mlw, nutrient_level, acidity) correct = [] # no types should match for vi in veg_predict: @@ -87,6 +88,9 @@ def test_one_value(self): else: np.testing.assert_equal(np.array([0]), veg_predict[vi]) + for vi in veg_detail: + np.testing.assert_equal(np.array([0]), veg_detail[vi]) + def test_simple_doc_inundation(self): nutrient_level = np.array([4]) acidity = np.array([3]) @@ -95,16 +99,25 @@ def test_simple_doc_inundation(self): soil_code = np.array([14]) inundation = np.array([1]) v = niche_vlaanderen.Vegetation() - veg_predict, veg_occurrence = \ + veg_predict, veg_occurrence, veg_detail = \ v.calculate(soil_code, mhw, mlw, nutrient_level, acidity, inundation=inundation) correct = [7, 12, 16] + veg_detail_exp = {1:0, 2:35, 3:39, 4:7, 5:3, 6:0, 7:47, 8:15, + 9:1, 10:0, 11:0, 12:47, 13:1, 14:0, 15:1, 16:47, 17:0, 18:11, 19:39, + 20:1, 21:11, 22:0, 23:0, 24:0, 25:0, 26:0, 27:0, 28:0} + + # note that in the docs 8 was suitable except for inundation: its value is indeed: + # 1+2+4+8= 15 so suitable soil, gxg, nutrient and acidity, but unsuitable inundation (32) + for vi in veg_predict: + np.testing.assert_equal(veg_detail_exp[vi], veg_detail[vi]) if vi in correct: - np.testing.assert_equal(np.array([1]), veg_predict[vi]) + np.testing.assert_array_less(np.array([0]), veg_detail[vi]) else: np.testing.assert_equal(np.array([0]), veg_predict[vi]) + def test_occurrence(self): nutrient_level = np.array([[4, 4], [4, 5]]) acidity = np.array([[3, 3], [3, 255]]) @@ -113,7 +126,7 @@ def test_occurrence(self): soil_code = np.array([[14, 14], [14, 14]]) inundation = np.array([[1, 1], [1, 1]]) v = niche_vlaanderen.Vegetation() - veg_predict, veg_occurrence = \ + veg_predict, veg_occurrence, _ = \ v.calculate(soil_code=soil_code, mhw=mhw, mlw=mlw, nutrient_level=nutrient_level, acidity=acidity, inundation=inundation) @@ -153,7 +166,7 @@ def test_testcase(self): conductivity, regenlens) v = niche_vlaanderen.Vegetation() - veg_predict, veg_occurrence = v.calculate(soil_code_r, mhw, mlw, + veg_predict, veg_occurrence, _ = v.calculate(soil_code_r, mhw, mlw, nutrient_level, acidity) for i in range(1, 28): @@ -200,3 +213,16 @@ def test_deviation_mlw(self): d = v.calculate_deviation(soil_code, mhw, mlw) expected = np.array([28, 12, 0, 0, -15, np.nan, np.nan]) np.testing.assert_equal(expected, d["mlw_01"]) + + def test_detailed_vegetation(self): + v = niche_vlaanderen.Vegetation() + soil_code = np.array([14]) + veg_bands, occurrence, veg_detail = v.calculate(soil_code, mhw=10, mlw=50, nutrient_level=5, acidity=3) + # cfr examples in vegetatie.rst + np.testing.assert_equal(11, veg_detail[8]) + np.testing.assert_equal(0, veg_detail[6]) + + def test_vegsuitable(self): + legend = VegSuitable.legend() + assert list(legend.keys()) == [0, 1, 3, 7, 11, 15, 31, 47, 63] + assert legend[63] == "soil+gxg+nutrient+acidity+management+flooding suitable"