|
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Generating statistics: https://hoxhunt.com/\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"application/javascript": [
|
|
"/* Put everything inside the global mpl namespace */\n",
|
|
"window.mpl = {};\n",
|
|
"\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('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",
|
|
"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 = $('<div/>');\n",
|
|
" this._root_extra_style(this.root)\n",
|
|
" this.root.attr('style', 'display: inline-block');\n",
|
|
"\n",
|
|
" $(parent_element).append(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 (mpl.ratio != 1) {\n",
|
|
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
|
|
" }\n",
|
|
" fig.send_message(\"refresh\", {});\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.imageObj.onload = function() {\n",
|
|
" if (fig.image_mode == 'full') {\n",
|
|
" // Full images could contain transparency (where diff images\n",
|
|
" // almost always do), so we need to clear the canvas so that\n",
|
|
" // there is no ghosting.\n",
|
|
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
|
|
" }\n",
|
|
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
|
|
" };\n",
|
|
"\n",
|
|
" this.imageObj.onunload = function() {\n",
|
|
" fig.ws.close();\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.ws.onmessage = this._make_on_message_function(this);\n",
|
|
"\n",
|
|
" this.ondownload = ondownload;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_header = function() {\n",
|
|
" var titlebar = $(\n",
|
|
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
|
|
" 'ui-helper-clearfix\"/>');\n",
|
|
" var titletext = $(\n",
|
|
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
|
|
" 'text-align: center; padding: 3px;\"/>');\n",
|
|
" titlebar.append(titletext)\n",
|
|
" this.root.append(titlebar);\n",
|
|
" this.header = titletext[0];\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_canvas = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var canvas_div = $('<div/>');\n",
|
|
"\n",
|
|
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
|
|
"\n",
|
|
" function canvas_keyboard_event(event) {\n",
|
|
" return fig.key_event(event, event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
|
|
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
|
|
" this.canvas_div = canvas_div\n",
|
|
" this._canvas_extra_style(canvas_div)\n",
|
|
" this.root.append(canvas_div);\n",
|
|
"\n",
|
|
" var canvas = $('<canvas/>');\n",
|
|
" canvas.addClass('mpl-canvas');\n",
|
|
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
|
|
"\n",
|
|
" this.canvas = canvas[0];\n",
|
|
" this.context = canvas[0].getContext(\"2d\");\n",
|
|
"\n",
|
|
" var backingStore = this.context.backingStorePixelRatio ||\n",
|
|
"\tthis.context.webkitBackingStorePixelRatio ||\n",
|
|
"\tthis.context.mozBackingStorePixelRatio ||\n",
|
|
"\tthis.context.msBackingStorePixelRatio ||\n",
|
|
"\tthis.context.oBackingStorePixelRatio ||\n",
|
|
"\tthis.context.backingStorePixelRatio || 1;\n",
|
|
"\n",
|
|
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
|
|
"\n",
|
|
" var rubberband = $('<canvas/>');\n",
|
|
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
|
|
"\n",
|
|
" var pass_mouse_events = true;\n",
|
|
"\n",
|
|
" canvas_div.resizable({\n",
|
|
" start: function(event, ui) {\n",
|
|
" pass_mouse_events = false;\n",
|
|
" },\n",
|
|
" resize: function(event, ui) {\n",
|
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
|
" },\n",
|
|
" stop: function(event, ui) {\n",
|
|
" pass_mouse_events = true;\n",
|
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
|
" },\n",
|
|
" });\n",
|
|
"\n",
|
|
" function mouse_event_fn(event) {\n",
|
|
" if (pass_mouse_events)\n",
|
|
" return fig.mouse_event(event, event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" rubberband.mousedown('button_press', mouse_event_fn);\n",
|
|
" rubberband.mouseup('button_release', mouse_event_fn);\n",
|
|
" // Throttle sequential mouse events to 1 every 20ms.\n",
|
|
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
|
|
"\n",
|
|
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
|
|
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
|
|
"\n",
|
|
" canvas_div.on(\"wheel\", function (event) {\n",
|
|
" event = event.originalEvent;\n",
|
|
" event['data'] = 'scroll'\n",
|
|
" if (event.deltaY < 0) {\n",
|
|
" event.step = 1;\n",
|
|
" } else {\n",
|
|
" event.step = -1;\n",
|
|
" }\n",
|
|
" mouse_event_fn(event);\n",
|
|
" });\n",
|
|
"\n",
|
|
" canvas_div.append(canvas);\n",
|
|
" canvas_div.append(rubberband);\n",
|
|
"\n",
|
|
" this.rubberband = rubberband;\n",
|
|
" this.rubberband_canvas = rubberband[0];\n",
|
|
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
|
|
" this.rubberband_context.strokeStyle = \"#000000\";\n",
|
|
"\n",
|
|
" this._resize_canvas = function(width, height) {\n",
|
|
" // Keep the size of the canvas, canvas container, and rubber band\n",
|
|
" // canvas in synch.\n",
|
|
" canvas_div.css('width', width)\n",
|
|
" canvas_div.css('height', height)\n",
|
|
"\n",
|
|
" canvas.attr('width', width * mpl.ratio);\n",
|
|
" canvas.attr('height', height * mpl.ratio);\n",
|
|
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
|
|
"\n",
|
|
" rubberband.attr('width', width);\n",
|
|
" rubberband.attr('height', height);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
|
|
" // upon first draw.\n",
|
|
" this._resize_canvas(600, 600);\n",
|
|
"\n",
|
|
" // Disable right mouse context menu.\n",
|
|
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\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 nav_element = $('<div/>');\n",
|
|
" nav_element.attr('style', 'width: 100%');\n",
|
|
" this.root.append(nav_element);\n",
|
|
"\n",
|
|
" // Define a callback function for later on.\n",
|
|
" function toolbar_event(event) {\n",
|
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
|
" }\n",
|
|
" function toolbar_mouse_event(event) {\n",
|
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
|
" }\n",
|
|
"\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",
|
|
" // put a spacer in here.\n",
|
|
" continue;\n",
|
|
" }\n",
|
|
" var button = $('<button/>');\n",
|
|
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
|
|
" 'ui-button-icon-only');\n",
|
|
" button.attr('role', 'button');\n",
|
|
" button.attr('aria-disabled', 'false');\n",
|
|
" button.click(method_name, toolbar_event);\n",
|
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
|
"\n",
|
|
" var icon_img = $('<span/>');\n",
|
|
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
|
|
" icon_img.addClass(image);\n",
|
|
" icon_img.addClass('ui-corner-all');\n",
|
|
"\n",
|
|
" var tooltip_span = $('<span/>');\n",
|
|
" tooltip_span.addClass('ui-button-text');\n",
|
|
" tooltip_span.html(tooltip);\n",
|
|
"\n",
|
|
" button.append(icon_img);\n",
|
|
" button.append(tooltip_span);\n",
|
|
"\n",
|
|
" nav_element.append(button);\n",
|
|
" }\n",
|
|
"\n",
|
|
" var fmt_picker_span = $('<span/>');\n",
|
|
"\n",
|
|
" var fmt_picker = $('<select/>');\n",
|
|
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
|
|
" fmt_picker_span.append(fmt_picker);\n",
|
|
" nav_element.append(fmt_picker_span);\n",
|
|
" this.format_dropdown = fmt_picker[0];\n",
|
|
"\n",
|
|
" for (var ind in mpl.extensions) {\n",
|
|
" var fmt = mpl.extensions[ind];\n",
|
|
" var option = $(\n",
|
|
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
|
|
" fmt_picker.append(option);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Add hover states to the ui-buttons\n",
|
|
" $( \".ui-button\" ).hover(\n",
|
|
" function() { $(this).addClass(\"ui-state-hover\");},\n",
|
|
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
|
|
" );\n",
|
|
"\n",
|
|
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
|
|
" nav_element.append(status_bar);\n",
|
|
" this.message = status_bar[0];\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",
|
|
"\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",
|
|
"\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]);\n",
|
|
" fig.send_message(\"refresh\", {});\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
|
|
" var x0 = msg['x0'] / mpl.ratio;\n",
|
|
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
|
|
" var x1 = msg['x1'] / mpl.ratio;\n",
|
|
" var y1 = (fig.canvas.height - msg['y1']) / mpl.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, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
|
|
"\n",
|
|
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
|
|
" // Updates the figure title.\n",
|
|
" fig.header.textContent = msg['label'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
|
|
" var cursor = msg['cursor'];\n",
|
|
" switch(cursor)\n",
|
|
" {\n",
|
|
" case 0:\n",
|
|
" cursor = 'pointer';\n",
|
|
" break;\n",
|
|
" case 1:\n",
|
|
" cursor = 'default';\n",
|
|
" break;\n",
|
|
" case 2:\n",
|
|
" cursor = 'crosshair';\n",
|
|
" break;\n",
|
|
" case 3:\n",
|
|
" cursor = 'move';\n",
|
|
" break;\n",
|
|
" }\n",
|
|
" fig.rubberband_canvas.style.cursor = cursor;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
|
|
" fig.message.textContent = msg['message'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
|
|
" // Request the server to send over a new figure.\n",
|
|
" fig.send_draw_message();\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
|
|
" fig.image_mode = msg['mode'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.updated_canvas_event = function() {\n",
|
|
" // Called whenever the canvas gets updated.\n",
|
|
" this.send_message(\"ack\", {});\n",
|
|
"}\n",
|
|
"\n",
|
|
"// A function to construct a web socket function for onmessage handling.\n",
|
|
"// Called in the figure constructor.\n",
|
|
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
|
|
" return function socket_on_message(evt) {\n",
|
|
" if (evt.data instanceof Blob) {\n",
|
|
" /* FIXME: We get \"Resource interpreted as Image but\n",
|
|
" * transferred with MIME type text/plain:\" errors on\n",
|
|
" * Chrome. But how to set the MIME type? It doesn't seem\n",
|
|
" * to be part of the websocket stream */\n",
|
|
" evt.data.type = \"image/png\";\n",
|
|
"\n",
|
|
" /* Free the memory for the previous frames */\n",
|
|
" if (fig.imageObj.src) {\n",
|
|
" (window.URL || window.webkitURL).revokeObjectURL(\n",
|
|
" fig.imageObj.src);\n",
|
|
" }\n",
|
|
"\n",
|
|
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
|
|
" evt.data);\n",
|
|
" fig.updated_canvas_event();\n",
|
|
" fig.waiting = false;\n",
|
|
" return;\n",
|
|
" }\n",
|
|
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\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(\"No handler for the '\" + msg_type + \"' message type: \", msg);\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(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
|
|
" }\n",
|
|
" }\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
|
|
"mpl.findpos = function(e) {\n",
|
|
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
|
|
" var targ;\n",
|
|
" if (!e)\n",
|
|
" e = window.event;\n",
|
|
" if (e.target)\n",
|
|
" targ = e.target;\n",
|
|
" else if (e.srcElement)\n",
|
|
" targ = e.srcElement;\n",
|
|
" if (targ.nodeType == 3) // defeat Safari bug\n",
|
|
" targ = targ.parentNode;\n",
|
|
"\n",
|
|
" // jQuery normalizes the pageX and pageY\n",
|
|
" // pageX,Y are the mouse positions relative to the document\n",
|
|
" // offset() returns the position of the element relative to the document\n",
|
|
" var x = e.pageX - $(targ).offset().left;\n",
|
|
" var y = e.pageY - $(targ).offset().top;\n",
|
|
"\n",
|
|
" return {\"x\": x, \"y\": y};\n",
|
|
"};\n",
|
|
"\n",
|
|
"/*\n",
|
|
" * return a copy of an object with only non-object keys\n",
|
|
" * we need this to avoid circular references\n",
|
|
" * http://stackoverflow.com/a/24161582/3208463\n",
|
|
" */\n",
|
|
"function simpleKeys (original) {\n",
|
|
" return Object.keys(original).reduce(function (obj, key) {\n",
|
|
" if (typeof original[key] !== 'object')\n",
|
|
" obj[key] = original[key]\n",
|
|
" 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",
|
|
" {\n",
|
|
" this.canvas.focus();\n",
|
|
" this.canvas_div.focus();\n",
|
|
" }\n",
|
|
"\n",
|
|
" var x = canvas_pos.x * mpl.ratio;\n",
|
|
" var y = canvas_pos.y * mpl.ratio;\n",
|
|
"\n",
|
|
" this.send_message(name, {x: x, y: y, button: event.button,\n",
|
|
" step: event.step,\n",
|
|
" guiEvent: simpleKeys(event)});\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",
|
|
"\n",
|
|
" // Prevent repeat events\n",
|
|
" if (name == 'key_press')\n",
|
|
" {\n",
|
|
" if (event.which === this._key)\n",
|
|
" return;\n",
|
|
" else\n",
|
|
" this._key = event.which;\n",
|
|
" }\n",
|
|
" if (name == 'key_release')\n",
|
|
" this._key = null;\n",
|
|
"\n",
|
|
" var value = '';\n",
|
|
" if (event.ctrlKey && event.which != 17)\n",
|
|
" value += \"ctrl+\";\n",
|
|
" if (event.altKey && event.which != 18)\n",
|
|
" value += \"alt+\";\n",
|
|
" if (event.shiftKey && event.which != 16)\n",
|
|
" value += \"shift+\";\n",
|
|
"\n",
|
|
" value += 'k';\n",
|
|
" value += event.which.toString();\n",
|
|
"\n",
|
|
" this._key_event_extra(event, name);\n",
|
|
"\n",
|
|
" this.send_message(name, {key: value,\n",
|
|
" 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",
|
|
"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\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
|
|
"\n",
|
|
"mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n",
|
|
"\n",
|
|
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
|
|
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
|
|
" // object with the appropriate methods. Currently this is a non binary\n",
|
|
" // socket, so there is still some room for performance tuning.\n",
|
|
" var ws = {};\n",
|
|
"\n",
|
|
" ws.close = function() {\n",
|
|
" comm.close()\n",
|
|
" };\n",
|
|
" ws.send = function(m) {\n",
|
|
" //console.log('sending', m);\n",
|
|
" comm.send(m);\n",
|
|
" };\n",
|
|
" // Register the callback with on_msg.\n",
|
|
" comm.on_msg(function(msg) {\n",
|
|
" //console.log('receiving', msg['content']['data'], msg);\n",
|
|
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
|
|
" ws.onmessage(msg['content']['data'])\n",
|
|
" });\n",
|
|
" return ws;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.mpl_figure_comm = function(comm, msg) {\n",
|
|
" // This is the function which gets called when the mpl process\n",
|
|
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
|
|
"\n",
|
|
" var id = msg.content.data.id;\n",
|
|
" // Get hold of the div created by the display call when the Comm\n",
|
|
" // socket was opened in Python.\n",
|
|
" var element = $(\"#\" + id);\n",
|
|
" var ws_proxy = comm_websocket_adapter(comm)\n",
|
|
"\n",
|
|
" function ondownload(figure, format) {\n",
|
|
" window.open(figure.imageObj.src);\n",
|
|
" }\n",
|
|
"\n",
|
|
" var fig = new mpl.figure(id, ws_proxy,\n",
|
|
" ondownload,\n",
|
|
" element.get(0));\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.get(0);\n",
|
|
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
|
|
" if (!fig.cell_info) {\n",
|
|
" console.error(\"Failed to find cell for figure\", id, fig);\n",
|
|
" return;\n",
|
|
" }\n",
|
|
"\n",
|
|
" var output_index = fig.cell_info[2]\n",
|
|
" var cell = fig.cell_info[0];\n",
|
|
"\n",
|
|
"};\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
|
|
" var width = fig.canvas.width/mpl.ratio\n",
|
|
" fig.root.unbind('remove')\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).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
|
|
" fig.close_ws(fig, msg);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
|
|
" fig.send_message('closing', msg);\n",
|
|
" // fig.ws.close()\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
|
|
" // Turn the data on the canvas into data in the output cell.\n",
|
|
" var width = this.canvas.width/mpl.ratio\n",
|
|
" var dataURL = this.canvas.toDataURL();\n",
|
|
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.updated_canvas_event = function() {\n",
|
|
" // Tell IPython that the notebook contents must change.\n",
|
|
" IPython.notebook.set_dirty(true);\n",
|
|
" this.send_message(\"ack\", {});\n",
|
|
" var fig = this;\n",
|
|
" // Wait a second, then push the new image to the DOM so\n",
|
|
" // that it is saved nicely (might be nice to debounce this).\n",
|
|
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_toolbar = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var nav_element = $('<div/>');\n",
|
|
" nav_element.attr('style', 'width: 100%');\n",
|
|
" this.root.append(nav_element);\n",
|
|
"\n",
|
|
" // Define a callback function for later on.\n",
|
|
" function toolbar_event(event) {\n",
|
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
|
" }\n",
|
|
" function toolbar_mouse_event(event) {\n",
|
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
|
" }\n",
|
|
"\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) { continue; };\n",
|
|
"\n",
|
|
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
|
|
" button.click(method_name, toolbar_event);\n",
|
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
|
" nav_element.append(button);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Add the status bar.\n",
|
|
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
|
|
" nav_element.append(status_bar);\n",
|
|
" this.message = status_bar[0];\n",
|
|
"\n",
|
|
" // Add the close button to the window.\n",
|
|
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
|
|
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
|
|
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
|
|
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
|
|
" buttongrp.append(button);\n",
|
|
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
|
|
" titlebar.prepend(buttongrp);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._root_extra_style = function(el){\n",
|
|
" var fig = this\n",
|
|
" el.on(\"remove\", function(){\n",
|
|
"\tfig.close_ws(fig, {});\n",
|
|
" });\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
|
|
" // this is important to make the div 'focusable\n",
|
|
" el.attr('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",
|
|
" }\n",
|
|
" else {\n",
|
|
" // location in version 2\n",
|
|
" IPython.keyboard_manager.register_events(el);\n",
|
|
" }\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
|
|
" var manager = IPython.notebook.keyboard_manager;\n",
|
|
" if (!manager)\n",
|
|
" manager = IPython.keyboard_manager;\n",
|
|
"\n",
|
|
" // 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",
|
|
"\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('matplotlib', mpl.mpl_figure_comm);\n",
|
|
"}\n"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.Javascript object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA4QAAAMgCAYAAAB7w6zDAAAgAElEQVR4nOzdeXSU5d3G8SeBbCQRghJAokE5KFoUUFRUKAgibriVKtbygsqrglakVsprq8BBaKlbpa1YUcGtrixiQQoWwQ2VihYVEC0aU0Vxgwqy53r/0JkyTBLyBLjD/bu/n3N+5+hkMnnCM/dc95WZTCIBAAAAAIIU1fUBAAAAAADqBoUQAAAAAAJFIQQAAACAQFEIAQAAACBQFEIAAAAACBSFEAAAAAACRSEEAAAAgEBRCAEAAAAgUBRCAAAAAAgUhRAAAAAAAkUhBAAAAIBAUQgBAAAAIFAUQgAAAAAIFIUQAAAAAAJFIQQAAACAQFEIAQAAACBQFEIAAAAACBSFEAAAAAACRSEEAAAAgEBRCAF4oWfPnjriiCPUrl07de7cWW+88UZdHxIAALVCpmFvQiEE4IWvv/46+d/Tpk1Thw4d6vBoAACoPTINexMKIQDvTJ48WUcffXRdHwYAALuMTENdoxAC8Ea/fv1UUlKikpISvf3223V9OAAA1BqZhr0FhRCAdyZPnqzTTjutrg8DAIBdRqahrlEIAXgpNzdXX3zxRV0fBgAAu4xMQ12iEALY661du1Yff/xx8v+nTp2qFi1aqKKiog6PCgCA+Mg07G0ohAD2eh999JGOOeYYtW3bVkceeaR69OjBW3QDALxEpmFvQyEEAAAAgEBRCAEAAAAgUBRCAAAAAAgUhRAAAAAAAkUhBAAAAIBAUQgBAAAAIFAUQgAAAAAIFIUQAAAAAAJFIQQAAACAQFEIAQAAACBQFEIAAAAACBSFEAAAAAACRSEEAAAAgEBRCAEAAAAgUBRCAAAAAAgUhRAAAAAAAkUhBAAAAIBAUQgBAAAAIFAUQgAAAAAIFIUQAAAAAAJFIQQAAACAQFEIAQAAACBQFEIAAAAACBSFEAAAAAACRSEEAAAAgEBRCAEAAAAgUBRCAAAAAAgUhRAAAAAAAkUhBAAAAIBAUQgBAAAAIFAUQgAAAAAIFIUQAAAAAAJFIQQAAACAQFEIAQAAACBQFEIAAAAACBSFEAAAAAACRSEEAAAAgEBRCAEAAAAgUBRCAAAAAAgUhRAAAAAAAkUhBAAAAIBAUQgBAAAAIFAUQgAAAAAIFIUQAAAAAAJFIQQAAACAQFEIAQAAACBQFEIAAAAACBSFEAAAAAACRSEEvjdp0iRFUZScnJwcNW3aVN26ddPYsWP12Wef1fUh1khpaan69+9f14fhjf79+6u0tLRWn/vwww/r9ttv370H9L09eR4T9/UPPvggeVlV38sHH3ygKIp088031+prPffcc4qiSE888USlH7/yyisVRVHKce1sEudrxIgRiqJIGRkZ+te//pV22+vWrVNhYaGiKEr5t9yV76lr1676wQ9+UOnHPv/8c0VRpBEjRiQv2/F7qlevnpo1a6YLLrhAK1asiHX7tVHZua6pmTNnpnwvu9uoUaN02GGHadu2bZLinZfqjm3MmDGaNm3a7jzUWjn33HN11llnpVz25Zdfqn79+po6daokKYoiXXnllc6PLbF2Pv/8c+dfW9p7ztGu2P5cbt68WQcffPAeywNgT6MQAt9LbJwmTZqkhQsX6vnnn9eTTz6pa665Rg0bNlTjxo01d+7cuj7MnVq8eLHef//9uj4Mb7z//vtavHhxrT73jDPOqHWZ3Jk9WQhXr16thQsXauPGjcnLqvpeXBbCxHFtP1EUqU+fPimXJc5XYlNbWFioX//612m3PWnSJOXm5iorK6vOC2HiceW5557TTTfdpLy8PBUXF+urr76q8e3Xxq4Uwu3Pze728ccfKz8/P+V+Eee8VHds+fn5df5DsXXr1ikvL0/3339/yuX33XefGjRooG+//VZSuIVwbzhHu2rHczl58mQVFRXpiy++qOMjA+KjEALfS2ycFi1alPaxsrIyHXDAASosLNSnn35aB0eHvZGvhbAye0MhrEx1G+bEpnbgwIE64IADks80JXTu3FkXXnhh2uazLgrhjo8ro0aNUhRFuu+++2p8+7WxtxbCYcOGqUWLFinnzFIhfPzxx5WVlZVW+E8//XT16dMn+f8UQn/teC43bdqkxo0ba8yYMXV4VEDtUAiB71VXCKXvAj6KIo0aNSrl8qeeekqdOnVSXl6eCgoKdPLJJ+vll19OuU4ifP/5z3+qT58+2meffVRUVKShQ4dqy5YtWr58uXr16qWCggKVlpZq3LhxKZ+/YcMG/fznP1e7du2Sn9upUydNnz497Th3LBKJDflf/vIXXX/99WrevLkKCwvVo0cPLV++POVzFy9erDPOOENNmjRRdna2mjdvrtNPP13l5eXV/tvNmTNHZ511llq0aKGcnBy1atVKl112WdpmY/Xq1frf//1flZSUKDs7W/vtt59OOOGElGdea3IMGzZs0PDhw9WyZUtlZWVp//331+DBg/X111+nHdvDDz+sTp06KT8/X/n5+WrXrp3uueee5Mcre8noH//4R3Xp0kVNmjRRgwYN1LZtW40bN06bN29OXqdr166VvpwxYdOmTRo9erQOPfTQ5Pc6YMAArV69OuVrbd68Wdddd52aNm2qvLw8nXjiiXr11VdrVAg7duyo008/PeWytm3bKooivfbaa8nLpkyZoiiKtGTJEknpJaG672X7Tfqtt96qli1bKj8/X506ddLChQurPT7JTSF8+eWXFUWRZs+enfzYu+++qyiKNHfu3L2yEM6cOVNRFOk3v/lNjW9/ZxYuXKgTTjhBOTk5at68uYYPH6677747rRA++uij6tmzp5o1a6bc3Fy1adNGv/zlL7Vu3brkdfr371/pfSJxOzVZI1XZtGmT9t13X1133XUpl9f0vlbdsVV2edeuXSX991zMmTNHAwYMUFFRkRo0aKAzzzwz7SXHtX0sTLjgggvUq1evlMvWrl2r7OxsPfLII8nLEvfvBx54QG3atFFeXp6OPPJIPf3002m3+cILL6h79+4qKChQXl6ejj/+eP31r39NfryiokKnnXaaGjdurLKysuTl69ev1+GHH642bdokz3Fi7bz99tvq27ev9tlnHxUXF+viiy/WmjVr0s7JpEmT0o5nx/t6TW+zunNUlY0bN2rUqFFq06aNcnJy1LhxY3Xr1k0vvfRS8jo1zYXS0lKdccYZevrpp9W+ffvkGkj8m0+aNElt2rRRgwYNdMwxx1S6J6jsXErSoEGDVFpaqoqKimq/H2BvQyEEvrezQrhu3TrVq1dPPXr0SF728MMPK4oinXLKKZo+fboee+wxHX300crOztYLL7yQvF4iKA899FCNHj1ac+fO1bBhwxRFka666iq1adNG48eP19y5c3XxxRcriiJNmTIl+flr1qzRgAED9OCDD2revHmaPXu2fvGLXygzMzPtJUlVFcKWLVvqoosu0syZM/XII4/owAMPVOvWrbV169bk97fvvvuqY8eOevzxx7VgwQI99thjuuKKK7R06dJq/+0mTJig3/zmN5oxY4YWLFig+++/X+3atdOhhx6askHs1auXmjRporvvvlvz58/X9OnTdeONN+rRRx+t8TFUVFSoV69eql+/vm644QbNmTNHt9xyi/Lz89WhQ4eUl0HecMMNiqJI5513np544gnNmTNHt912m2644YbkdSorhEOHDtWECRM0e/ZszZs3T7fffrv2228/XXzxxcnrvPPOOzrxxBPVrFmzlJczStK2bdt06qmnKj8/X6NGjdLcuXN1zz33qEWLFjr88MOTLzFKfP2MjAxdd911yeNr0aKF9tlnn50WwuHDh6ugoCD5b/zpp58qiiLl5eWl/JR60KBBatq0afL/dyyE1X0viQ1hy5Ytdeqpp2r69OmaPn26jjjiCBUVFaVs9CqTuP899thj2rJlS9oMHjx4lwvh559/ri5duuj8889PfuyXv/ylWrZsqYqKir2yEP7xj39MW+c7u/3qvPPOO2rQoIEOP/xwPfLII3rqqafUq1cvHXjggWmFcPTo0br99ts1c+ZMzZ8/X3fddZcOOuggnXTSScnrvP/+++rTp4+iKEq5TyTWV03WSFWef/55RVGkWbNmpVxe0/tadce2cOFC5eXl6fTTT09e/s4770j677k44IADdMkll+iZZ57R3XffreLiYh1wwAHJ4lDTx8LE/e+5555L+T42bNiggoIC3X333SmXP/TQQ8rJydF//vOf5GWJ7/fYY4/V448/rlmzZqlbt26qX79+SkmdP3++srKydPTRR+uxxx7T9OnTdcoppygjIyP5+ClJX3zxhUpKSnTcccclHxf69++vvLy85A+Etj/2Qw89VDfeeKPmzp2r2267TTk5OSnnsDaFcGe3Wd05qsyWLVt00kknqX79+vrFL36hWbNmacaMGbr++uuThSxOLpSWlqqkpERt27bVI488olmzZum4445TVlaWbrzxRp144omaOnWqpk2bpkMOOURNmzZNecyu6lxK0mOPPZbywzfAFxRC4Hs7K4SS1LRpUx122GGSvtv077///jriiCNSXvb0zTffqLi4WCeccELyskRQ3nrrrSm31759e0VRlHyDAem78GvSpInOO++8Ko9j69at2rJliy699FJ16NAh5WNVFcIdn0lKPOOZ2Pj/4x//UBRFlT7rGEdFRYW2bNmisrIyRVGkp556KvmxgoICXXPNNVV+bk2OYfbs2YqiSL/73e9SLk8EcWITtnLlStWrV08XXXRRtce7szeV2bZtm7Zs2aIHHnhA9erVS3kJWFUvs3zkkUcq3ewvWrRIURTpzjvvlCQtW7ZMURRp6NChKddL/KBhZ4Xw2WefVRRFev755yV9t0kpLCzU4MGDUzb3rVu31k9+8pPk/1f2MsKdvWT0iCOOSP7wQJJee+01RVGU9hPyHSXufzubqtS0EE6aNEk5OTn68ssvtXXrVjVv3lwjR46UlP7ytLoohK+88oq2bNmib775RrNnz1azZs30wx/+UFu2bKnx7VfnggsuUF5eXspL2rdu3ao2bdpU+5LRxHpdsGBB8lUMCTV9yWh1a6Qy48aNUxRFaS+/j3Nfq81LRhPn4txzz025/KWXXlIURbrpppsk1fyxcNSoUapXr57mz5+fcvn06dNVr169tFcDnHPOOerdu3fKZVEUqWnTpinF4tNPP1VmZmbKs8edOnVScXGxvvnmm+RlW7duVdu2bVVSUpLyjNSLL76o+vXr65prrtF9992nKIpSXhUh/Xft7Pg4OnjwYOXm5iZvrzaFcGe3KcV7yegDDzygKIo0ceLEKq9T01yQvsvIvLw8/fvf/05e9uabbyqKIjVv3lzr169PXj59+nRFUaQZM2ak3G5l51KS3nvvPUVRpAkTJtToewP2FhRC4Hs1KYTFxcXJQrh06dJKA0j67hmZzMzMZLAkgvLdd99Nud6FF16ojIwMbdiwIeXy448/XkcffXTKZY8//rhOOOEE5efnp2ykc3NzU65XVSG86667Uq63fPlyRVGU/OnymjVrVFRUpEMPPVQTJkyo9ie2O/rss890+eWXq6SkRJmZmSnH99vf/jZ5ve7du6tRo0YaPXq0Fi5cmPbyspocQ+KZ1R03W4lngi644AJJ0p///GdFUZT28t0dVVYIFy9erN69e6tx48ZpxeWVV15JXq+qEnXRRRepUaNG2rx5c9ozYs2aNUs+k3XnnXcqiiL94x//SPn8LVu2qH79+jvdMG3YsEG5ubm68cYbk99L7969NWPGDOXk5Gj9+vX66KOPFEWpv6tWm0I4fPjwlMs3btyYdn4rk7j/jRs3TosWLUqb888/f7cUwsQ7io4fP14zZsxQRkaGPvzwQ0l7RyHccQ477LBKX+Jc20JYXFysM888M+3yxL/R9uf6X//6ly688EI1bdpUGRkZKce1/bNN1ZWumq6RygwZMkQZGRkppU+Kd1/blUL45JNPpn2stLQ0+eqPXXkslKR+/fqpW7duKZcl3mRm8uTJKZdHUaS+ffum3UazZs10xRVXJD83IyNDgwcPTrteolwvW7as0stzcnL005/+NO3zEveLHX9t4K677kop67UphDu7TSleIbzwwguVm5ub9jvC26tpLkjfnevjjz8+5XqbNm1SFEW68MILUy5PvPT8D3/4Q/Kyqs6l9N1LSaMoqvRNroC9GYUQ+F7cl4y+8MILiqJIDz74YNp1R48erSiKkj+BrOoX+Pv376/8/Py0z99xU5j4HbAf//jHmjZtmhYuXKhFixbpkksuSdsUVVUId/wdrsqCfsmSJbrgggtUVFSU/GnpjTfeWO3vBW3btk3t2rVTkyZNNH78eD333HN67bXX9Morr6RtGD7//HMNGTJEpaWliqJIBQUF6tevn1atWlXjY7j00ktVv379So+lVatWOvnkkyVJN910k6Io0kcffVTlsUvphbCsrEz5+fk66qij9OCDD+qFF17QokWL9Kc//UlRlPrysKpK1Mknn1zts2Hdu3eXlH4/2V7Tpk1rtGHq0aNH8tnokpISjR8/Xv/5z39Uv359zZ49W/fee2/av0NtCmFl5WnH81sZF79DmFhXAwcOVPv27XX22WerZ8+eyevtzkLYo0cPtWnTptKPrVq1KuWZJum//9YPPPCAFi1apHnz5unyyy9XFEU69dRT026jtoWwXr16GjhwYNrlEyZMSDnX33zzjfbff38dfPDBmjhxohYsWKBFixZp6tSpaY8HVZ2bOGukMpdddpmys7PTLo9zX9uVQvjiiy+mfey4445T+/btk/9fm8dC6bvfCW7UqFFKgZC+e6aqfv36ac+eVnX/3v5xvLy8XFEUafTo0WnXe/DBByv9nv79738rOzs77VnfhKoyacfHhtoUwp3dphSvEJ588sk6+OCDq71OTXNB+u/vEO6osnNR2X2yqnMp/bdYXnvttdUeL7C3oRAC39tZIUy89CQRyrV5hrC2hfDcc8/VQQcdlPaL6hdddNFuLYQJFRUVevPNN3XNNdcoitLf+GJ7//znPxVFUdpPSxMvnamqMJSVlekPf/iD8vPz0958obpj2NlPghM/ba/tM4R33HGHoihKPruUMHHixBoXwr59+2rfffet9BmxRYsWJX+CvqvPEErS2LFjVb9+fb366qspzxQcf/zxuvbaa3XhhRfqkEMOSfkcq4Uw8eYymZmZKS8v3J2F8Cc/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H320vv7667o+FKcIT7vhCQChIdPsZhp7XHcohHvI6tWrFUWRFixYUOV1EoVwVwoJi6V2br31Vh111FH66quv6vpQnCM87YYnAISGTLObaexx3aEQ7iHvvfeeoijSW2+9VeV1EoWwZcuWatasmbp376558+ZVe7sbN27U2rVrk1NeXs5iiSnxb3bwwQerXbt2ateunY499ti6PixnCE+74QkAoSHT7GYahdAdCuEeUFFRod69e6tz587VXm/58uW6++679frrr+vll1/WoEGDlJGRUe2ziiNGjKj09w5ZLKgpwtNueAJAaMg0u5lGIXSHQrgHDB48WKWlpSovL4/9uWeeeaZ69+5d5cd5hhC7ivC0G54AEBoyzW6mUQjdoRDuZldddZVKSkq0cuXKWn3+TTfdpDZt2tT4+iwWxEV42g1PAAgNmWY309jjukMh3E0qKip05ZVXav/999eKFStqfTs/+tGPdNJJJ9X4+iwWxEV42g1PAAgNmWY309jjukMh3E0GDRqkhg0bav78+Sl/QuLbb79NXmf48OHq169f8v9vv/12TZs2TStWrNDbb7+t4cOHK4oiTZkypcZfl8WCuAhPu+EJAKEh0+xmGntcdyiEu0llb/QSRZEmTZqUvE7//v3VtWvX5P+PGzdOrVq1Um5uroqKitS5c2fNnDkz1tdlsSAuwtNueAJAaMg0u5nGHtcdCqHnWCyIi/C0G54AEBoyzW6mscd1h0LoORYL4iI87YYnAISGTLObaexx3aEQeo7FgrgIT7vhCQChIdPsZhp7XHcohJ5jsSAuwtNueAJAaMg0u5nGHtcdCqHnWCyIi/C0G54AEBoyzW6mscd1h0LoORYL4iI87YYnAISGTLObaexx3aEQeo7FgrgIT7vhCQChIdPsZhp7XHcohJ5jsSAuwtNueAJAaMg0u5nGHtcdCqHnWCyIi/C0G54AEBoyzW6mscd1h0LoORYL4iI87YYnAISGTLObaexx3aEQeo7FgrgIT7vhCQChIdPsZhp7XHcohJ5jsSAuwtNueAJAaMg0u5nGHtcdCqHnWCyIi/C0G54AEBoyzW6mscd1h0LoORYL4iI87YYnAISGTLObaexx3aEQeo7FgrgIT7vhCQChIdPsZhp7XHcohJ5jsSAuwtNueAJAaMg0u5nGHtcdCqHnWCyIi/C0G54AEBoyzW6mscd1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hdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugOhdBzFELDQyH0byiEAOAUmWY30yiE7lAIPUchNDwUQv+GQggATpFpdjONQugO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\" width=\"900\">"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Generating statistics: https://hs.fi\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"application/javascript": [
|
|
"/* Put everything inside the global mpl namespace */\n",
|
|
"window.mpl = {};\n",
|
|
"\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('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",
|
|
"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 = $('<div/>');\n",
|
|
" this._root_extra_style(this.root)\n",
|
|
" this.root.attr('style', 'display: inline-block');\n",
|
|
"\n",
|
|
" $(parent_element).append(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 (mpl.ratio != 1) {\n",
|
|
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
|
|
" }\n",
|
|
" fig.send_message(\"refresh\", {});\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.imageObj.onload = function() {\n",
|
|
" if (fig.image_mode == 'full') {\n",
|
|
" // Full images could contain transparency (where diff images\n",
|
|
" // almost always do), so we need to clear the canvas so that\n",
|
|
" // there is no ghosting.\n",
|
|
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
|
|
" }\n",
|
|
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
|
|
" };\n",
|
|
"\n",
|
|
" this.imageObj.onunload = function() {\n",
|
|
" fig.ws.close();\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.ws.onmessage = this._make_on_message_function(this);\n",
|
|
"\n",
|
|
" this.ondownload = ondownload;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_header = function() {\n",
|
|
" var titlebar = $(\n",
|
|
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
|
|
" 'ui-helper-clearfix\"/>');\n",
|
|
" var titletext = $(\n",
|
|
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
|
|
" 'text-align: center; padding: 3px;\"/>');\n",
|
|
" titlebar.append(titletext)\n",
|
|
" this.root.append(titlebar);\n",
|
|
" this.header = titletext[0];\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_canvas = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var canvas_div = $('<div/>');\n",
|
|
"\n",
|
|
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
|
|
"\n",
|
|
" function canvas_keyboard_event(event) {\n",
|
|
" return fig.key_event(event, event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
|
|
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
|
|
" this.canvas_div = canvas_div\n",
|
|
" this._canvas_extra_style(canvas_div)\n",
|
|
" this.root.append(canvas_div);\n",
|
|
"\n",
|
|
" var canvas = $('<canvas/>');\n",
|
|
" canvas.addClass('mpl-canvas');\n",
|
|
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
|
|
"\n",
|
|
" this.canvas = canvas[0];\n",
|
|
" this.context = canvas[0].getContext(\"2d\");\n",
|
|
"\n",
|
|
" var backingStore = this.context.backingStorePixelRatio ||\n",
|
|
"\tthis.context.webkitBackingStorePixelRatio ||\n",
|
|
"\tthis.context.mozBackingStorePixelRatio ||\n",
|
|
"\tthis.context.msBackingStorePixelRatio ||\n",
|
|
"\tthis.context.oBackingStorePixelRatio ||\n",
|
|
"\tthis.context.backingStorePixelRatio || 1;\n",
|
|
"\n",
|
|
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
|
|
"\n",
|
|
" var rubberband = $('<canvas/>');\n",
|
|
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
|
|
"\n",
|
|
" var pass_mouse_events = true;\n",
|
|
"\n",
|
|
" canvas_div.resizable({\n",
|
|
" start: function(event, ui) {\n",
|
|
" pass_mouse_events = false;\n",
|
|
" },\n",
|
|
" resize: function(event, ui) {\n",
|
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
|
" },\n",
|
|
" stop: function(event, ui) {\n",
|
|
" pass_mouse_events = true;\n",
|
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
|
" },\n",
|
|
" });\n",
|
|
"\n",
|
|
" function mouse_event_fn(event) {\n",
|
|
" if (pass_mouse_events)\n",
|
|
" return fig.mouse_event(event, event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" rubberband.mousedown('button_press', mouse_event_fn);\n",
|
|
" rubberband.mouseup('button_release', mouse_event_fn);\n",
|
|
" // Throttle sequential mouse events to 1 every 20ms.\n",
|
|
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
|
|
"\n",
|
|
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
|
|
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
|
|
"\n",
|
|
" canvas_div.on(\"wheel\", function (event) {\n",
|
|
" event = event.originalEvent;\n",
|
|
" event['data'] = 'scroll'\n",
|
|
" if (event.deltaY < 0) {\n",
|
|
" event.step = 1;\n",
|
|
" } else {\n",
|
|
" event.step = -1;\n",
|
|
" }\n",
|
|
" mouse_event_fn(event);\n",
|
|
" });\n",
|
|
"\n",
|
|
" canvas_div.append(canvas);\n",
|
|
" canvas_div.append(rubberband);\n",
|
|
"\n",
|
|
" this.rubberband = rubberband;\n",
|
|
" this.rubberband_canvas = rubberband[0];\n",
|
|
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
|
|
" this.rubberband_context.strokeStyle = \"#000000\";\n",
|
|
"\n",
|
|
" this._resize_canvas = function(width, height) {\n",
|
|
" // Keep the size of the canvas, canvas container, and rubber band\n",
|
|
" // canvas in synch.\n",
|
|
" canvas_div.css('width', width)\n",
|
|
" canvas_div.css('height', height)\n",
|
|
"\n",
|
|
" canvas.attr('width', width * mpl.ratio);\n",
|
|
" canvas.attr('height', height * mpl.ratio);\n",
|
|
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
|
|
"\n",
|
|
" rubberband.attr('width', width);\n",
|
|
" rubberband.attr('height', height);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
|
|
" // upon first draw.\n",
|
|
" this._resize_canvas(600, 600);\n",
|
|
"\n",
|
|
" // Disable right mouse context menu.\n",
|
|
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\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 nav_element = $('<div/>');\n",
|
|
" nav_element.attr('style', 'width: 100%');\n",
|
|
" this.root.append(nav_element);\n",
|
|
"\n",
|
|
" // Define a callback function for later on.\n",
|
|
" function toolbar_event(event) {\n",
|
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
|
" }\n",
|
|
" function toolbar_mouse_event(event) {\n",
|
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
|
" }\n",
|
|
"\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",
|
|
" // put a spacer in here.\n",
|
|
" continue;\n",
|
|
" }\n",
|
|
" var button = $('<button/>');\n",
|
|
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
|
|
" 'ui-button-icon-only');\n",
|
|
" button.attr('role', 'button');\n",
|
|
" button.attr('aria-disabled', 'false');\n",
|
|
" button.click(method_name, toolbar_event);\n",
|
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
|
"\n",
|
|
" var icon_img = $('<span/>');\n",
|
|
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
|
|
" icon_img.addClass(image);\n",
|
|
" icon_img.addClass('ui-corner-all');\n",
|
|
"\n",
|
|
" var tooltip_span = $('<span/>');\n",
|
|
" tooltip_span.addClass('ui-button-text');\n",
|
|
" tooltip_span.html(tooltip);\n",
|
|
"\n",
|
|
" button.append(icon_img);\n",
|
|
" button.append(tooltip_span);\n",
|
|
"\n",
|
|
" nav_element.append(button);\n",
|
|
" }\n",
|
|
"\n",
|
|
" var fmt_picker_span = $('<span/>');\n",
|
|
"\n",
|
|
" var fmt_picker = $('<select/>');\n",
|
|
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
|
|
" fmt_picker_span.append(fmt_picker);\n",
|
|
" nav_element.append(fmt_picker_span);\n",
|
|
" this.format_dropdown = fmt_picker[0];\n",
|
|
"\n",
|
|
" for (var ind in mpl.extensions) {\n",
|
|
" var fmt = mpl.extensions[ind];\n",
|
|
" var option = $(\n",
|
|
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
|
|
" fmt_picker.append(option);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Add hover states to the ui-buttons\n",
|
|
" $( \".ui-button\" ).hover(\n",
|
|
" function() { $(this).addClass(\"ui-state-hover\");},\n",
|
|
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
|
|
" );\n",
|
|
"\n",
|
|
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
|
|
" nav_element.append(status_bar);\n",
|
|
" this.message = status_bar[0];\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",
|
|
"\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",
|
|
"\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]);\n",
|
|
" fig.send_message(\"refresh\", {});\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
|
|
" var x0 = msg['x0'] / mpl.ratio;\n",
|
|
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
|
|
" var x1 = msg['x1'] / mpl.ratio;\n",
|
|
" var y1 = (fig.canvas.height - msg['y1']) / mpl.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, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
|
|
"\n",
|
|
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
|
|
" // Updates the figure title.\n",
|
|
" fig.header.textContent = msg['label'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
|
|
" var cursor = msg['cursor'];\n",
|
|
" switch(cursor)\n",
|
|
" {\n",
|
|
" case 0:\n",
|
|
" cursor = 'pointer';\n",
|
|
" break;\n",
|
|
" case 1:\n",
|
|
" cursor = 'default';\n",
|
|
" break;\n",
|
|
" case 2:\n",
|
|
" cursor = 'crosshair';\n",
|
|
" break;\n",
|
|
" case 3:\n",
|
|
" cursor = 'move';\n",
|
|
" break;\n",
|
|
" }\n",
|
|
" fig.rubberband_canvas.style.cursor = cursor;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
|
|
" fig.message.textContent = msg['message'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
|
|
" // Request the server to send over a new figure.\n",
|
|
" fig.send_draw_message();\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
|
|
" fig.image_mode = msg['mode'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.updated_canvas_event = function() {\n",
|
|
" // Called whenever the canvas gets updated.\n",
|
|
" this.send_message(\"ack\", {});\n",
|
|
"}\n",
|
|
"\n",
|
|
"// A function to construct a web socket function for onmessage handling.\n",
|
|
"// Called in the figure constructor.\n",
|
|
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
|
|
" return function socket_on_message(evt) {\n",
|
|
" if (evt.data instanceof Blob) {\n",
|
|
" /* FIXME: We get \"Resource interpreted as Image but\n",
|
|
" * transferred with MIME type text/plain:\" errors on\n",
|
|
" * Chrome. But how to set the MIME type? It doesn't seem\n",
|
|
" * to be part of the websocket stream */\n",
|
|
" evt.data.type = \"image/png\";\n",
|
|
"\n",
|
|
" /* Free the memory for the previous frames */\n",
|
|
" if (fig.imageObj.src) {\n",
|
|
" (window.URL || window.webkitURL).revokeObjectURL(\n",
|
|
" fig.imageObj.src);\n",
|
|
" }\n",
|
|
"\n",
|
|
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
|
|
" evt.data);\n",
|
|
" fig.updated_canvas_event();\n",
|
|
" fig.waiting = false;\n",
|
|
" return;\n",
|
|
" }\n",
|
|
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\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(\"No handler for the '\" + msg_type + \"' message type: \", msg);\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(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
|
|
" }\n",
|
|
" }\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
|
|
"mpl.findpos = function(e) {\n",
|
|
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
|
|
" var targ;\n",
|
|
" if (!e)\n",
|
|
" e = window.event;\n",
|
|
" if (e.target)\n",
|
|
" targ = e.target;\n",
|
|
" else if (e.srcElement)\n",
|
|
" targ = e.srcElement;\n",
|
|
" if (targ.nodeType == 3) // defeat Safari bug\n",
|
|
" targ = targ.parentNode;\n",
|
|
"\n",
|
|
" // jQuery normalizes the pageX and pageY\n",
|
|
" // pageX,Y are the mouse positions relative to the document\n",
|
|
" // offset() returns the position of the element relative to the document\n",
|
|
" var x = e.pageX - $(targ).offset().left;\n",
|
|
" var y = e.pageY - $(targ).offset().top;\n",
|
|
"\n",
|
|
" return {\"x\": x, \"y\": y};\n",
|
|
"};\n",
|
|
"\n",
|
|
"/*\n",
|
|
" * return a copy of an object with only non-object keys\n",
|
|
" * we need this to avoid circular references\n",
|
|
" * http://stackoverflow.com/a/24161582/3208463\n",
|
|
" */\n",
|
|
"function simpleKeys (original) {\n",
|
|
" return Object.keys(original).reduce(function (obj, key) {\n",
|
|
" if (typeof original[key] !== 'object')\n",
|
|
" obj[key] = original[key]\n",
|
|
" 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",
|
|
" {\n",
|
|
" this.canvas.focus();\n",
|
|
" this.canvas_div.focus();\n",
|
|
" }\n",
|
|
"\n",
|
|
" var x = canvas_pos.x * mpl.ratio;\n",
|
|
" var y = canvas_pos.y * mpl.ratio;\n",
|
|
"\n",
|
|
" this.send_message(name, {x: x, y: y, button: event.button,\n",
|
|
" step: event.step,\n",
|
|
" guiEvent: simpleKeys(event)});\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",
|
|
"\n",
|
|
" // Prevent repeat events\n",
|
|
" if (name == 'key_press')\n",
|
|
" {\n",
|
|
" if (event.which === this._key)\n",
|
|
" return;\n",
|
|
" else\n",
|
|
" this._key = event.which;\n",
|
|
" }\n",
|
|
" if (name == 'key_release')\n",
|
|
" this._key = null;\n",
|
|
"\n",
|
|
" var value = '';\n",
|
|
" if (event.ctrlKey && event.which != 17)\n",
|
|
" value += \"ctrl+\";\n",
|
|
" if (event.altKey && event.which != 18)\n",
|
|
" value += \"alt+\";\n",
|
|
" if (event.shiftKey && event.which != 16)\n",
|
|
" value += \"shift+\";\n",
|
|
"\n",
|
|
" value += 'k';\n",
|
|
" value += event.which.toString();\n",
|
|
"\n",
|
|
" this._key_event_extra(event, name);\n",
|
|
"\n",
|
|
" this.send_message(name, {key: value,\n",
|
|
" 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",
|
|
"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\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
|
|
"\n",
|
|
"mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n",
|
|
"\n",
|
|
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
|
|
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
|
|
" // object with the appropriate methods. Currently this is a non binary\n",
|
|
" // socket, so there is still some room for performance tuning.\n",
|
|
" var ws = {};\n",
|
|
"\n",
|
|
" ws.close = function() {\n",
|
|
" comm.close()\n",
|
|
" };\n",
|
|
" ws.send = function(m) {\n",
|
|
" //console.log('sending', m);\n",
|
|
" comm.send(m);\n",
|
|
" };\n",
|
|
" // Register the callback with on_msg.\n",
|
|
" comm.on_msg(function(msg) {\n",
|
|
" //console.log('receiving', msg['content']['data'], msg);\n",
|
|
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
|
|
" ws.onmessage(msg['content']['data'])\n",
|
|
" });\n",
|
|
" return ws;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.mpl_figure_comm = function(comm, msg) {\n",
|
|
" // This is the function which gets called when the mpl process\n",
|
|
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
|
|
"\n",
|
|
" var id = msg.content.data.id;\n",
|
|
" // Get hold of the div created by the display call when the Comm\n",
|
|
" // socket was opened in Python.\n",
|
|
" var element = $(\"#\" + id);\n",
|
|
" var ws_proxy = comm_websocket_adapter(comm)\n",
|
|
"\n",
|
|
" function ondownload(figure, format) {\n",
|
|
" window.open(figure.imageObj.src);\n",
|
|
" }\n",
|
|
"\n",
|
|
" var fig = new mpl.figure(id, ws_proxy,\n",
|
|
" ondownload,\n",
|
|
" element.get(0));\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.get(0);\n",
|
|
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
|
|
" if (!fig.cell_info) {\n",
|
|
" console.error(\"Failed to find cell for figure\", id, fig);\n",
|
|
" return;\n",
|
|
" }\n",
|
|
"\n",
|
|
" var output_index = fig.cell_info[2]\n",
|
|
" var cell = fig.cell_info[0];\n",
|
|
"\n",
|
|
"};\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
|
|
" var width = fig.canvas.width/mpl.ratio\n",
|
|
" fig.root.unbind('remove')\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).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
|
|
" fig.close_ws(fig, msg);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
|
|
" fig.send_message('closing', msg);\n",
|
|
" // fig.ws.close()\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
|
|
" // Turn the data on the canvas into data in the output cell.\n",
|
|
" var width = this.canvas.width/mpl.ratio\n",
|
|
" var dataURL = this.canvas.toDataURL();\n",
|
|
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.updated_canvas_event = function() {\n",
|
|
" // Tell IPython that the notebook contents must change.\n",
|
|
" IPython.notebook.set_dirty(true);\n",
|
|
" this.send_message(\"ack\", {});\n",
|
|
" var fig = this;\n",
|
|
" // Wait a second, then push the new image to the DOM so\n",
|
|
" // that it is saved nicely (might be nice to debounce this).\n",
|
|
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_toolbar = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var nav_element = $('<div/>');\n",
|
|
" nav_element.attr('style', 'width: 100%');\n",
|
|
" this.root.append(nav_element);\n",
|
|
"\n",
|
|
" // Define a callback function for later on.\n",
|
|
" function toolbar_event(event) {\n",
|
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
|
" }\n",
|
|
" function toolbar_mouse_event(event) {\n",
|
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
|
" }\n",
|
|
"\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) { continue; };\n",
|
|
"\n",
|
|
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
|
|
" button.click(method_name, toolbar_event);\n",
|
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
|
" nav_element.append(button);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Add the status bar.\n",
|
|
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
|
|
" nav_element.append(status_bar);\n",
|
|
" this.message = status_bar[0];\n",
|
|
"\n",
|
|
" // Add the close button to the window.\n",
|
|
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
|
|
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
|
|
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
|
|
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
|
|
" buttongrp.append(button);\n",
|
|
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
|
|
" titlebar.prepend(buttongrp);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._root_extra_style = function(el){\n",
|
|
" var fig = this\n",
|
|
" el.on(\"remove\", function(){\n",
|
|
"\tfig.close_ws(fig, {});\n",
|
|
" });\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
|
|
" // this is important to make the div 'focusable\n",
|
|
" el.attr('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",
|
|
" }\n",
|
|
" else {\n",
|
|
" // location in version 2\n",
|
|
" IPython.keyboard_manager.register_events(el);\n",
|
|
" }\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
|
|
" var manager = IPython.notebook.keyboard_manager;\n",
|
|
" if (!manager)\n",
|
|
" manager = IPython.keyboard_manager;\n",
|
|
"\n",
|
|
" // 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",
|
|
"\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('matplotlib', mpl.mpl_figure_comm);\n",
|
|
"}\n"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.Javascript object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA4QAAAMgCAYAAAB7w6zDAAAgAElEQVR4nOzde5hVdb348TWDwMAMAhY3QbHIvIemJy9pWCZ4Nz3mpTKsSBM7qcej9XQq8PESdNG0c9KyxEseb6GIaSQdwfKSUWCSl6OmIamJaaAgIDKf3x/+ZjebmYEZhO+wvvN6Pc/3eWTtNWt/Z/Ze2/WevWbtIgAAAOiSis6eAAAAAJ1DEAIAAHRRghAAAKCLEoQAAABdlCAEAADoogQhAABAFyUIAQAAuihBCAAA0EUJQgAAgC5KEAIAAHRRghAAAKCLEoQAAABdlCAEAADoogQhAABAFyUIAQAAuihBCAAA0EUJQgAAgC5KEAIAAHRRghAAAKCLEoQAAABdlCAEAADoogQhAABAFyUIAQAAuihBCAAA0EUJQgAAgC5KEAIAAHRRghAAAKCLEoQAAABdlCAEAADoogQhAABAFyUIAQAAuihBCAAA0EUJQgAAgC5KEAIAAHRRghAAAKCLEoQAAABdlCAEAADoogQhAABAFyUIAQAAuihBCAAA0EUJQgAAgC5KEAIAAHRRghAAAKCLEoQAAABdlCAEAADoogQhAABAFyUIAQAAuihBCAAA0EUJQgAAgC5KEAIAAHRRghAAAKCLEoQAAABdlCAEAADoogQhAABAFyUIAQAAuihBCAAA0EUJQgAAgC5KEAIAAHRRghAAAKCLEoQAAABdlCAEAADoogQhAABAFyUIAQAAuihBCAAA0EUJQgAAgC5KEAIAAHRRghAAAKCLEoQAAABdlCAEAADoogQhAABAFyUIAQAAuihBCAAA0EUJQgAAgC5KEAIAAHRRghAAAKCLEoRQElOmTImiKCqjZ8+eMWjQoNh///3jwgsvjBdffLGzp9guw4cPj7Fjx3b2NEpj7NixMXz48PX62uuuuy4uvvjiDTuh/29jPo5Nz/Vnnnmmsqyt7+WZZ56Joiji29/+9nrd16xZs6Ioirj55ptbvf20006Loiiq5rWu0fR4TZgwIYqiiJqamvjzn//cYttLly6NPn36RFEUVT/Lt/M9jRo1KnbaaadWb3vppZeiKIqYMGFCZdma31O3bt1i8ODBcdxxx8UTTzzRoe2vj9Ye6/a64447qr6XDe3cc8+NHXbYIVavXh0RHXtc1ja3Cy64IG699dYNOdX1ctRRR8URRxxRtezll1+OzTbbLG655ZaIiCiKIk477bSNOo8bbrghdtxxx6irq4uiKGLevHmVfae5/fbbL04//fSNOhfoqgQhlETTgdOUKVPigQceiF//+tfxs5/9LM4444zo27dvbLHFFjFz5szOnuY6zZ07N5566qnOnkZpPPXUUzF37tz1+tpDDz10vWNyXTZmEC5atCgeeOCBWLFiRWVZW99LyiBsmlfzURRFHHPMMVXLmh6vpoPaPn36xNe+9rUW254yZUrU1dVF9+7dOz0Im15XZs2aFeeff3706tUrBg4cGK+88kq7t78+3k4QNn9sNrTnnnsu6uvrq54XHXlc1ja3+vr6Tv+l2NKlS6NXr15x9dVXVy2/8soro3fv3vH6669HxMYPwkWLFkX37t3j8MMPj9mzZ8cDDzwQy5Yti4ULF8YDDzxQte7s2bOje/fu8fjjj2+0+UBXJQihJJoOnObMmdPitgULFsRWW20Vffr0ib/97W+dMDs2RWUNwtZsCkHYmrUdMDcF4bhx42KrrbaqvNPUZN99940TTjihRSB0RhCu+bpy7rnnRlEUceWVV7Z7++tjUw3Cc845J4YOHVr1mOUUhDfddFN07969RfAfcsghccwxx1T+vbGD8N57742iKOLGG29s1/o777xzfP7zn99o84GuShBCSawtCCPe+h98URRx7rnnVi2/7bbbYq+99opevXpFQ0NDfPSjH43777+/ap2mA9c//vGPccwxx8Tmm28e/fv3jzPPPDNWrVoVjz/+eIwZMyYaGhpi+PDhMXny5KqvX758efz7v/97jBw5svK1e+21V0ybNq3FPNcMiaYD8v/5n/+Jr371qzFkyJDo06dPHHDAAS1+Ezx37tw49NBDY8CAAdGjR48YMmRIHHLIIbFw4cK1/uzuuuuuOOKII2Lo0KHRs2fPGDFiRJx88snx0ksvVa23aNGi+PznPx/Dhg2LHj16xDvf+c7YZ599qt55bc8cli9fHl/5yldim222ie7du8eWW24Z48ePj3/84x8t5nbdddfFXnvtFfX19VFfXx8jR46MH//4x5XbWztl9L/+679iv/32iwEDBkTv3r1j5513jsmTJ8cbb7xRWWfUqFGtns7YZOXKlXHeeefFdtttV/leTzrppFi0aFHVfb3xxhtx9tlnx6BBg6JXr17xwQ9+MB588MF2BeEee+wRhxxySNWynXfeOYqiiN/97neVZVOnTo2iKOLhhx+OiJaRsLbvpflB+ne/+93YZpttor6+Pvbaa68W7zC0JkUQ3n///VEURcyYMaNy2//93/9FURQxc+bMTTII77jjjiiKIr75zW+2e/vr8sADD8Q+++wTPXv2jCFDhsRXvvKV+NGPftQiCG+44YY48MADY/DgwVFXVxfbb799fPnLX46lS5dW1hk7dmyrz4mm7bRnH2nLypUr4x3veEecffbZVcvb+1xb29xaWz5q1KiI+Odjcdddd8VJJ50U/fv3j969e8dhhx3W4pTj9X0tbHLcccfFmDFjqpYtWbIkevToEddff31lWdPz+5prrontt98+evXqFe973/vi9ttvr/ra9rx2rqm1n1PTz6K1U0YjIiZPnhz19fXx6quvtuv7BNpHEEJJrCsIly5dGt26dYsDDjigsuy6666Loihi9OjRMW3atLjxxhtj9913jx49esRvfvObynpN//Pdbrvt4rzzzouZM2fGOeecE0VRxBe/+MXYfvvt49JLL42ZM2fGZz7zmSiKIqZOnVr5+sWLF8dJJ50U1157bdx9990xY8aM+I//+I+ora1tcUpSW0G4zTbbxCc/+cm444474vrrr4+tt946tt1223jzzTcr39873vGO2GOPPeKmm26Ke+65J2688cb4whe+EI8++uhaf3aXXXZZfPOb34zp06fHPffcE1dffXWMHDkytttuu6oDxDFjxsSAAQPiRz/6UcyePTumTZsW3/jGN+KGG25o9xwaGxtjzJgxsdlmm8XXv/71uOuuu+I73/lO1NfXx2677VZ1GuTXv/71KIoijj766Lj55pvjrrvuiosuuii+/vWvV9ZpLQjPPPPMuOyyy2LGjBlx9913x8UXXxzvfOc74zOf+UxlnUceeSQ++MEPxuDBg6tOZ4yIWL16dRx00EFRX18f5557bsycOTN+/OMfx9ChQ2PHHXesnC7WdP81NTVx9tlnV+Y3dOjQ2HzzzdcZhF/5yleioaGh8jP+29/+FkVRRK9eveKCCy6orHfqqafGoEGDKv9eMwjX9r00HWRvs802cdBBB8W0adNi2rRpscsuu0T//v1j8eLFa51j0/PvxhtvjFWrVrUY48ePf9tB+NJLL8V+++0Xxx57bOW2L3/5y7HNNttEY2PjJhmE//Vf/9ViP1/X9tfmkUceid69e8eOO+4Y119/fdx2220xZsyY2HrrrVsE4XnnnRcXX3xx3HHHHTF79uy4/PLL413veld8+MMfrqzz1FNPxTHHHBNFUVQ9J5r2r/bsI2359a9/HUVRxJ133lm1vL3PtbXN7YEHHohevXrFIYccUln+yCOPRMQ/H4utttoqPvvZz8YvfvGL+NGPfhQDBw6MrbbaqvILpfa+FjY9/2bNmlX1fSxfvjwaGhriRz/6UdXyn/70p9GzZ8+q2Gr6fj/wgQ/ETTfdFHfeeWfsv//+sdlmm1VF6rpeO1vz1FNPxX//939HURRx4YUXVv0s2grCBx98MIqiiOnTp7e5XaDjBCGUxLqCMCJi0KBBscMOO0TEWwf9W265Zeyyyy5Vpz299tprMXDgwNhnn30qy5r+5/vd7363anu77rprFEVRucBARMSqVatiwIABcfTRR7c5jzfffDNWrVoVn/vc52K33Xaruq2tIFzznaSmdzybDvx///vfR1EUrb7r2BGNjY2xatWqWLBgQRRFEbfddlvltoaGhjjjjDPa/Nr2zGHGjBlRFEV861vfqlp+4403RlEUlYOwp59+Orp16xaf/OQn1zrfdV1UZvXq1bFq1aq45pprolu3blWngLV1muX111/f6sH+nDlzoiiK+MEPfhAREY899lgURRFnnnlm1XpNv2hYVxD+6le/iqIo4te//nVEvHXA2adPnxg/fnzVwf22224bn/jEJyr/bu00wnWdMrrLLrtUfnkQEfG73/0uiqKoerejNU3Pv3WNtrQ3CKdMmRI9e/aMl19+Od58880YMmRITJw4MSJankLYGUH429/+NlatWhWvvfZazJgxIwYPHhwf+tCHYtWqVe3e/tocd9xx0atXr6pT2t98883Yfvvt13rKaNP+es8991TOYmjS3lNG17aPtGby5MlRFEWL0+878lxbn1NGmx6Lo446qmr5fffdF0VRxPnnnx8R7X8tPPfcc6Nbt24xe/bsquXTpk2Lbt26tTgb4GMf+1gcfvjhVcuKoohBgwZVReLf/va3qK2trXr3eF2vnW1p6x36toLwjTfeiJqamvjyl7/c4fsC2iYIoSTaE4QDBw6sBOGjjz7aaphEvPWOTG1tbSxbtiwi/vk/3//7v/+rWu+EE06ImpqaWL58edXyvffeO3bfffeqZTfddFPss88+UV9fX3UgXVdXV7VeW0F4+eWXV633+OOPR1EUld8wL168OPr37x/bbbddXHbZZZXfJLfHiy++GKecckoMGzYsamtrq+Y3adKkynof+chHol+/fnHeeefFAw880OL0svbMoemd1TUPtpreCTruuOMiIuKHP/xh5XTCtWktCOfOnRuHH354bLHFFi3C5be//W1lvbYi6pOf/GT069cv3njjjRbviA0ePLjyTtYPfvCDKIoifv/731d9/apVq2KzzTZbZxAuX7486urq4hvf+Eblezn88MNj+vTp0bNnz1i2bFk8++yzURTVf6u2PkH4la98pWr5ihUrWjy+rWl6/k2ePDnmzJnTYhx77LEbJAibrih66aWXxvTp06Ompib+8pe/RMSmEYRrjh122KHVU5zXNwgHDhwYhx12WIvlTT+j5o/1n//85zjhhBNi0KBBUVNTUzWv5u84rS262ruPtOb000+PmpqaquiL6Nhz7e0E4c9+9rMWtw0fPrxy9sfbeS2MiDjxxBNj//33r1rWdJGZq666qmp5URRx/PHHt9jG4MGD4wtf+ELl3+t67WxLR4MwIqJ///7xqU99ql3bB9pHEEJJdPSU0d/85jdRFEVce+21LdY977zzoiiK+Otf/xoR1QeuzY0dOzbq6+tbfP2aB4VNfwP28Y9/PG699dZ44IEHYs6cOfHZz362xf/U2wrCNQ8Img6+pkyZUln28MMPx3HHHRf9+/ePoihiyJAh8Y1vfGOtBx+rV6+OkSNHxoABA+LSSy+NWbNmxe9+97v47W9/2+Lg+KWXXorTTz89hg8fHkVRRENDQ5x44onxwgsvtHsOn/vc52KzzTZrdS4jRoyIj370oxERcf7550dRFPHss8+2OfeIlkG4YMGCqK+vj/e///1x7bXXxm9+85uYM2dO5dSr5qeHtRVRH/3oR9f6bthHPvKRiGj5PGlu0KBB7bowxgEHHFB5N3rYsGFx6aWXxquvvhqbbbZZzJgxI37yk5+0+DmsTxC2Fk9rPr6tSfE3hE371bhx42LXXXeNI488Mg488MDKehsyCA844IDYfvvtW73thRdeqHqnKeKfP+trrrkm5syZE3fffXeccsopURRFHHTQQS22sb5B2K1btxg3blyL5ZdddlnVY/3aa6/FlltuGe9+97vjiiuuiHvuuSfmzJkTt9xyS4vXg7Yem47sI605+eSTo0ePHi2Wd+S59naC8N57721x25577hm77rpr5d/r81oY8dY7bP369Yvvf//7VctvvPHG2GyzzVq8e9rW83vN1/H2vHa2Zn2CcMiQIfGv//qva90u0DGCEEpiXUHYdErieeedFxHr9w7h+gbhUUcdFe9617uisbGxar1PfvKTGzQImzQ2NsZDDz0UZ5xxRhRFywtfNPfHP/4xiqJo8ZvvJ598cq3BsGDBgvj+978f9fX1LS6+sLY5rOsdwqbftq/vO4SXXHJJFEVReXepyRVXXNHuIDz++OPjHe94R6vviM2ZM6dyMZ+3+w5hRMSFF14Ym222WeVvfx577LGIeOtd5rPOOitOOOGEeO9731v1NbkGYdPFZWpra6tOL9yQQfiJT3wiNt988xb7YsQ/TzW84oorKsvael0ZN25cqz+Xjf0O4W233RZFUbQ4zXHmzJntDsKO7COt+epXvxpFUVRdxCYiXRCu6x3C5jryWhgRceedd0ZNTU2LX/Ice+yxMXr06Fa/r/YEYXPreu1sbn2CsK6uLk455ZS1bhfoGEEIJdGej53o27dvJURWr14dQ4cOjV133bXq4HDp0qUxcODA+OAHP1hZ9naD8Oijj47tttuuap0XXnghGhoaNkoQNtevX7/4+Mc/3ubtDz/8cBRFy78l+4//+I92BcPHPvaxGDBgQLvn8Mtf/jKKooiLLrqoap2bb7656mD8mWeeiW7dusWJJ5641m2vGYSXXnppFEVR9Zv3xsbG+MAHPtDiYPfoo4+OgQMHttjmT3/603adOtf0S4X1/RvCiH/+fdXo0aNj2LBhleVf//rXY+TIkTFo0KAYP3581de0FoRtfS9lCsKIiM9+9rPxr//6r1UXF9qQQXjllVdGUbS8IErEW7+sqK2trboYSFuvK6+88kr079+/6oPZIzb+3xBOnz49iqJocYXYpou0NH89+Pd///coiqLqIkgRHdtHWnPNNddEUVT/vWJEx55rbc0tImKLLbaousBQk3X9DWHTL/vasq7Xwoi3zmDYe++9q5Y1XWTmhz/8YYv11ycIm7TntbOjQfjcc89FURRxySWXrHW7QMcIQiiJNT9A+je/+U1MnTq16oPp77777qqvaTpwP+SQQ+K2226Lm266Kf7lX/6lzauMrm8QNh2EnnrqqfG///u/cdVVV8WIESNi22233WBBePvtt8fBBx8cP/zhD2PmzJlx1113xRe+8IUoiqLF1fKae+ONN2LEiBExfPjw+J//+Z+YMWNGnHbaafHe97636iBu8eLFsdtuu8W3v/3tuP3222P27Nnx7W9/O+rq6ioXPGnPHJquMtq9e/eYOHFizJw5M7773e9GQ0NDm1cZPeaYY2Lq1Knxq1/9Ki699NLK39w1PQbNg/Cxxx6LHj16xP777x933nln3HLLLXHggQdWftbND3abHtcf/OAH8eCDD1YO+t988804+OCDY4sttohzzz03fvGLX8SvfvWruOqqq2Ls2LFVFxH61Kc+FTU1NXHOOedUrjK65ZZbtusqoxFv/WKi6bS25ld4bLpISFFUX7QoovUgbOt7KVsQtqatIPz0pz8dN998c4ux5jtfza1cuTL22GOPaGhoiPPPPz9++ctfxrRp0+Lkk0+Ompqa+NKXvlS1/tp+0fStb30riqL6tPNRo0bFVltt1eq81nxXr7n58+dHr169Yscdd4wbbrghpk+fHmPGjImtttqq6rH++9//Hv3794+RI0fGLbfcErfffnscf/zxled38yBsmvuECRPit7/9bcyZMydWrlzZoX2kNU1/17pmIHXkudbW3Jp+hgMHDozp06dXvSPf/Cqjn/vc52LGjBlxxRVXxMCBA2Po0KHx8ssvR0T7XwvXvKjMm2++Ge985zvjO9/5TtXcb7311qitrY0XX3yx1e9rXUHYntfO1uYT0fEgXPMjaoANQxBCSax58YcePXrEwIEDY9SoUXHhhRe2OEWxybRp02LPPfeMurq6qK+vjwMOOCDuu+++qnXebhBGREyaNCm22Wab6NmzZ+ywww5xxRVXtPo/9fUNwscffzxOOOGEGDFiRPTq1Sv69u0bH/jAB1qcCtqaRx99NA488MDo06dP9O/fPz7+8Y9XDvqaDuJWrFgRX/jCF+J973tfbL755tGrV6/YbrvtYsKECZVTa9s7h+XLl8eXv/zlGD58eHTv3j2GDBkSp556aqsX6bjmmmviX/7lX6Kurq4Sjc0Pelu7qMztt98eI0eOjLq6uhg6dGicffbZ8Ytf/KLFwe4rr7wSxxxzTPTr169ycY4mq1atiu985zuV7TQ0NMT2228fp5xySjz55JOV9VauXBlnnXVWDBw4MOrq6iqfudaRD6Y/6qijoiiKuO666yrL3njjjaivr4/a2toWP5fW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\" width=\"900\">"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Generating statistics: https://ts.fi\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"application/javascript": [
|
|
"/* Put everything inside the global mpl namespace */\n",
|
|
"window.mpl = {};\n",
|
|
"\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('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",
|
|
"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 = $('<div/>');\n",
|
|
" this._root_extra_style(this.root)\n",
|
|
" this.root.attr('style', 'display: inline-block');\n",
|
|
"\n",
|
|
" $(parent_element).append(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 (mpl.ratio != 1) {\n",
|
|
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
|
|
" }\n",
|
|
" fig.send_message(\"refresh\", {});\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.imageObj.onload = function() {\n",
|
|
" if (fig.image_mode == 'full') {\n",
|
|
" // Full images could contain transparency (where diff images\n",
|
|
" // almost always do), so we need to clear the canvas so that\n",
|
|
" // there is no ghosting.\n",
|
|
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
|
|
" }\n",
|
|
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
|
|
" };\n",
|
|
"\n",
|
|
" this.imageObj.onunload = function() {\n",
|
|
" fig.ws.close();\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.ws.onmessage = this._make_on_message_function(this);\n",
|
|
"\n",
|
|
" this.ondownload = ondownload;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_header = function() {\n",
|
|
" var titlebar = $(\n",
|
|
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
|
|
" 'ui-helper-clearfix\"/>');\n",
|
|
" var titletext = $(\n",
|
|
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
|
|
" 'text-align: center; padding: 3px;\"/>');\n",
|
|
" titlebar.append(titletext)\n",
|
|
" this.root.append(titlebar);\n",
|
|
" this.header = titletext[0];\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_canvas = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var canvas_div = $('<div/>');\n",
|
|
"\n",
|
|
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
|
|
"\n",
|
|
" function canvas_keyboard_event(event) {\n",
|
|
" return fig.key_event(event, event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
|
|
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
|
|
" this.canvas_div = canvas_div\n",
|
|
" this._canvas_extra_style(canvas_div)\n",
|
|
" this.root.append(canvas_div);\n",
|
|
"\n",
|
|
" var canvas = $('<canvas/>');\n",
|
|
" canvas.addClass('mpl-canvas');\n",
|
|
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
|
|
"\n",
|
|
" this.canvas = canvas[0];\n",
|
|
" this.context = canvas[0].getContext(\"2d\");\n",
|
|
"\n",
|
|
" var backingStore = this.context.backingStorePixelRatio ||\n",
|
|
"\tthis.context.webkitBackingStorePixelRatio ||\n",
|
|
"\tthis.context.mozBackingStorePixelRatio ||\n",
|
|
"\tthis.context.msBackingStorePixelRatio ||\n",
|
|
"\tthis.context.oBackingStorePixelRatio ||\n",
|
|
"\tthis.context.backingStorePixelRatio || 1;\n",
|
|
"\n",
|
|
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
|
|
"\n",
|
|
" var rubberband = $('<canvas/>');\n",
|
|
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
|
|
"\n",
|
|
" var pass_mouse_events = true;\n",
|
|
"\n",
|
|
" canvas_div.resizable({\n",
|
|
" start: function(event, ui) {\n",
|
|
" pass_mouse_events = false;\n",
|
|
" },\n",
|
|
" resize: function(event, ui) {\n",
|
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
|
" },\n",
|
|
" stop: function(event, ui) {\n",
|
|
" pass_mouse_events = true;\n",
|
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
|
" },\n",
|
|
" });\n",
|
|
"\n",
|
|
" function mouse_event_fn(event) {\n",
|
|
" if (pass_mouse_events)\n",
|
|
" return fig.mouse_event(event, event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" rubberband.mousedown('button_press', mouse_event_fn);\n",
|
|
" rubberband.mouseup('button_release', mouse_event_fn);\n",
|
|
" // Throttle sequential mouse events to 1 every 20ms.\n",
|
|
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
|
|
"\n",
|
|
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
|
|
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
|
|
"\n",
|
|
" canvas_div.on(\"wheel\", function (event) {\n",
|
|
" event = event.originalEvent;\n",
|
|
" event['data'] = 'scroll'\n",
|
|
" if (event.deltaY < 0) {\n",
|
|
" event.step = 1;\n",
|
|
" } else {\n",
|
|
" event.step = -1;\n",
|
|
" }\n",
|
|
" mouse_event_fn(event);\n",
|
|
" });\n",
|
|
"\n",
|
|
" canvas_div.append(canvas);\n",
|
|
" canvas_div.append(rubberband);\n",
|
|
"\n",
|
|
" this.rubberband = rubberband;\n",
|
|
" this.rubberband_canvas = rubberband[0];\n",
|
|
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
|
|
" this.rubberband_context.strokeStyle = \"#000000\";\n",
|
|
"\n",
|
|
" this._resize_canvas = function(width, height) {\n",
|
|
" // Keep the size of the canvas, canvas container, and rubber band\n",
|
|
" // canvas in synch.\n",
|
|
" canvas_div.css('width', width)\n",
|
|
" canvas_div.css('height', height)\n",
|
|
"\n",
|
|
" canvas.attr('width', width * mpl.ratio);\n",
|
|
" canvas.attr('height', height * mpl.ratio);\n",
|
|
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
|
|
"\n",
|
|
" rubberband.attr('width', width);\n",
|
|
" rubberband.attr('height', height);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
|
|
" // upon first draw.\n",
|
|
" this._resize_canvas(600, 600);\n",
|
|
"\n",
|
|
" // Disable right mouse context menu.\n",
|
|
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\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 nav_element = $('<div/>');\n",
|
|
" nav_element.attr('style', 'width: 100%');\n",
|
|
" this.root.append(nav_element);\n",
|
|
"\n",
|
|
" // Define a callback function for later on.\n",
|
|
" function toolbar_event(event) {\n",
|
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
|
" }\n",
|
|
" function toolbar_mouse_event(event) {\n",
|
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
|
" }\n",
|
|
"\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",
|
|
" // put a spacer in here.\n",
|
|
" continue;\n",
|
|
" }\n",
|
|
" var button = $('<button/>');\n",
|
|
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
|
|
" 'ui-button-icon-only');\n",
|
|
" button.attr('role', 'button');\n",
|
|
" button.attr('aria-disabled', 'false');\n",
|
|
" button.click(method_name, toolbar_event);\n",
|
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
|
"\n",
|
|
" var icon_img = $('<span/>');\n",
|
|
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
|
|
" icon_img.addClass(image);\n",
|
|
" icon_img.addClass('ui-corner-all');\n",
|
|
"\n",
|
|
" var tooltip_span = $('<span/>');\n",
|
|
" tooltip_span.addClass('ui-button-text');\n",
|
|
" tooltip_span.html(tooltip);\n",
|
|
"\n",
|
|
" button.append(icon_img);\n",
|
|
" button.append(tooltip_span);\n",
|
|
"\n",
|
|
" nav_element.append(button);\n",
|
|
" }\n",
|
|
"\n",
|
|
" var fmt_picker_span = $('<span/>');\n",
|
|
"\n",
|
|
" var fmt_picker = $('<select/>');\n",
|
|
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
|
|
" fmt_picker_span.append(fmt_picker);\n",
|
|
" nav_element.append(fmt_picker_span);\n",
|
|
" this.format_dropdown = fmt_picker[0];\n",
|
|
"\n",
|
|
" for (var ind in mpl.extensions) {\n",
|
|
" var fmt = mpl.extensions[ind];\n",
|
|
" var option = $(\n",
|
|
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
|
|
" fmt_picker.append(option);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Add hover states to the ui-buttons\n",
|
|
" $( \".ui-button\" ).hover(\n",
|
|
" function() { $(this).addClass(\"ui-state-hover\");},\n",
|
|
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
|
|
" );\n",
|
|
"\n",
|
|
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
|
|
" nav_element.append(status_bar);\n",
|
|
" this.message = status_bar[0];\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",
|
|
"\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",
|
|
"\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]);\n",
|
|
" fig.send_message(\"refresh\", {});\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
|
|
" var x0 = msg['x0'] / mpl.ratio;\n",
|
|
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
|
|
" var x1 = msg['x1'] / mpl.ratio;\n",
|
|
" var y1 = (fig.canvas.height - msg['y1']) / mpl.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, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
|
|
"\n",
|
|
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
|
|
" // Updates the figure title.\n",
|
|
" fig.header.textContent = msg['label'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
|
|
" var cursor = msg['cursor'];\n",
|
|
" switch(cursor)\n",
|
|
" {\n",
|
|
" case 0:\n",
|
|
" cursor = 'pointer';\n",
|
|
" break;\n",
|
|
" case 1:\n",
|
|
" cursor = 'default';\n",
|
|
" break;\n",
|
|
" case 2:\n",
|
|
" cursor = 'crosshair';\n",
|
|
" break;\n",
|
|
" case 3:\n",
|
|
" cursor = 'move';\n",
|
|
" break;\n",
|
|
" }\n",
|
|
" fig.rubberband_canvas.style.cursor = cursor;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
|
|
" fig.message.textContent = msg['message'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
|
|
" // Request the server to send over a new figure.\n",
|
|
" fig.send_draw_message();\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
|
|
" fig.image_mode = msg['mode'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.updated_canvas_event = function() {\n",
|
|
" // Called whenever the canvas gets updated.\n",
|
|
" this.send_message(\"ack\", {});\n",
|
|
"}\n",
|
|
"\n",
|
|
"// A function to construct a web socket function for onmessage handling.\n",
|
|
"// Called in the figure constructor.\n",
|
|
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
|
|
" return function socket_on_message(evt) {\n",
|
|
" if (evt.data instanceof Blob) {\n",
|
|
" /* FIXME: We get \"Resource interpreted as Image but\n",
|
|
" * transferred with MIME type text/plain:\" errors on\n",
|
|
" * Chrome. But how to set the MIME type? It doesn't seem\n",
|
|
" * to be part of the websocket stream */\n",
|
|
" evt.data.type = \"image/png\";\n",
|
|
"\n",
|
|
" /* Free the memory for the previous frames */\n",
|
|
" if (fig.imageObj.src) {\n",
|
|
" (window.URL || window.webkitURL).revokeObjectURL(\n",
|
|
" fig.imageObj.src);\n",
|
|
" }\n",
|
|
"\n",
|
|
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
|
|
" evt.data);\n",
|
|
" fig.updated_canvas_event();\n",
|
|
" fig.waiting = false;\n",
|
|
" return;\n",
|
|
" }\n",
|
|
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\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(\"No handler for the '\" + msg_type + \"' message type: \", msg);\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(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
|
|
" }\n",
|
|
" }\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
|
|
"mpl.findpos = function(e) {\n",
|
|
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
|
|
" var targ;\n",
|
|
" if (!e)\n",
|
|
" e = window.event;\n",
|
|
" if (e.target)\n",
|
|
" targ = e.target;\n",
|
|
" else if (e.srcElement)\n",
|
|
" targ = e.srcElement;\n",
|
|
" if (targ.nodeType == 3) // defeat Safari bug\n",
|
|
" targ = targ.parentNode;\n",
|
|
"\n",
|
|
" // jQuery normalizes the pageX and pageY\n",
|
|
" // pageX,Y are the mouse positions relative to the document\n",
|
|
" // offset() returns the position of the element relative to the document\n",
|
|
" var x = e.pageX - $(targ).offset().left;\n",
|
|
" var y = e.pageY - $(targ).offset().top;\n",
|
|
"\n",
|
|
" return {\"x\": x, \"y\": y};\n",
|
|
"};\n",
|
|
"\n",
|
|
"/*\n",
|
|
" * return a copy of an object with only non-object keys\n",
|
|
" * we need this to avoid circular references\n",
|
|
" * http://stackoverflow.com/a/24161582/3208463\n",
|
|
" */\n",
|
|
"function simpleKeys (original) {\n",
|
|
" return Object.keys(original).reduce(function (obj, key) {\n",
|
|
" if (typeof original[key] !== 'object')\n",
|
|
" obj[key] = original[key]\n",
|
|
" 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",
|
|
" {\n",
|
|
" this.canvas.focus();\n",
|
|
" this.canvas_div.focus();\n",
|
|
" }\n",
|
|
"\n",
|
|
" var x = canvas_pos.x * mpl.ratio;\n",
|
|
" var y = canvas_pos.y * mpl.ratio;\n",
|
|
"\n",
|
|
" this.send_message(name, {x: x, y: y, button: event.button,\n",
|
|
" step: event.step,\n",
|
|
" guiEvent: simpleKeys(event)});\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",
|
|
"\n",
|
|
" // Prevent repeat events\n",
|
|
" if (name == 'key_press')\n",
|
|
" {\n",
|
|
" if (event.which === this._key)\n",
|
|
" return;\n",
|
|
" else\n",
|
|
" this._key = event.which;\n",
|
|
" }\n",
|
|
" if (name == 'key_release')\n",
|
|
" this._key = null;\n",
|
|
"\n",
|
|
" var value = '';\n",
|
|
" if (event.ctrlKey && event.which != 17)\n",
|
|
" value += \"ctrl+\";\n",
|
|
" if (event.altKey && event.which != 18)\n",
|
|
" value += \"alt+\";\n",
|
|
" if (event.shiftKey && event.which != 16)\n",
|
|
" value += \"shift+\";\n",
|
|
"\n",
|
|
" value += 'k';\n",
|
|
" value += event.which.toString();\n",
|
|
"\n",
|
|
" this._key_event_extra(event, name);\n",
|
|
"\n",
|
|
" this.send_message(name, {key: value,\n",
|
|
" 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",
|
|
"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\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
|
|
"\n",
|
|
"mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n",
|
|
"\n",
|
|
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
|
|
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
|
|
" // object with the appropriate methods. Currently this is a non binary\n",
|
|
" // socket, so there is still some room for performance tuning.\n",
|
|
" var ws = {};\n",
|
|
"\n",
|
|
" ws.close = function() {\n",
|
|
" comm.close()\n",
|
|
" };\n",
|
|
" ws.send = function(m) {\n",
|
|
" //console.log('sending', m);\n",
|
|
" comm.send(m);\n",
|
|
" };\n",
|
|
" // Register the callback with on_msg.\n",
|
|
" comm.on_msg(function(msg) {\n",
|
|
" //console.log('receiving', msg['content']['data'], msg);\n",
|
|
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
|
|
" ws.onmessage(msg['content']['data'])\n",
|
|
" });\n",
|
|
" return ws;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.mpl_figure_comm = function(comm, msg) {\n",
|
|
" // This is the function which gets called when the mpl process\n",
|
|
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
|
|
"\n",
|
|
" var id = msg.content.data.id;\n",
|
|
" // Get hold of the div created by the display call when the Comm\n",
|
|
" // socket was opened in Python.\n",
|
|
" var element = $(\"#\" + id);\n",
|
|
" var ws_proxy = comm_websocket_adapter(comm)\n",
|
|
"\n",
|
|
" function ondownload(figure, format) {\n",
|
|
" window.open(figure.imageObj.src);\n",
|
|
" }\n",
|
|
"\n",
|
|
" var fig = new mpl.figure(id, ws_proxy,\n",
|
|
" ondownload,\n",
|
|
" element.get(0));\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.get(0);\n",
|
|
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
|
|
" if (!fig.cell_info) {\n",
|
|
" console.error(\"Failed to find cell for figure\", id, fig);\n",
|
|
" return;\n",
|
|
" }\n",
|
|
"\n",
|
|
" var output_index = fig.cell_info[2]\n",
|
|
" var cell = fig.cell_info[0];\n",
|
|
"\n",
|
|
"};\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
|
|
" var width = fig.canvas.width/mpl.ratio\n",
|
|
" fig.root.unbind('remove')\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).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
|
|
" fig.close_ws(fig, msg);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
|
|
" fig.send_message('closing', msg);\n",
|
|
" // fig.ws.close()\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
|
|
" // Turn the data on the canvas into data in the output cell.\n",
|
|
" var width = this.canvas.width/mpl.ratio\n",
|
|
" var dataURL = this.canvas.toDataURL();\n",
|
|
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.updated_canvas_event = function() {\n",
|
|
" // Tell IPython that the notebook contents must change.\n",
|
|
" IPython.notebook.set_dirty(true);\n",
|
|
" this.send_message(\"ack\", {});\n",
|
|
" var fig = this;\n",
|
|
" // Wait a second, then push the new image to the DOM so\n",
|
|
" // that it is saved nicely (might be nice to debounce this).\n",
|
|
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_toolbar = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var nav_element = $('<div/>');\n",
|
|
" nav_element.attr('style', 'width: 100%');\n",
|
|
" this.root.append(nav_element);\n",
|
|
"\n",
|
|
" // Define a callback function for later on.\n",
|
|
" function toolbar_event(event) {\n",
|
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
|
" }\n",
|
|
" function toolbar_mouse_event(event) {\n",
|
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
|
" }\n",
|
|
"\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) { continue; };\n",
|
|
"\n",
|
|
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
|
|
" button.click(method_name, toolbar_event);\n",
|
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
|
" nav_element.append(button);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Add the status bar.\n",
|
|
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
|
|
" nav_element.append(status_bar);\n",
|
|
" this.message = status_bar[0];\n",
|
|
"\n",
|
|
" // Add the close button to the window.\n",
|
|
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
|
|
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
|
|
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
|
|
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
|
|
" buttongrp.append(button);\n",
|
|
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
|
|
" titlebar.prepend(buttongrp);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._root_extra_style = function(el){\n",
|
|
" var fig = this\n",
|
|
" el.on(\"remove\", function(){\n",
|
|
"\tfig.close_ws(fig, {});\n",
|
|
" });\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
|
|
" // this is important to make the div 'focusable\n",
|
|
" el.attr('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",
|
|
" }\n",
|
|
" else {\n",
|
|
" // location in version 2\n",
|
|
" IPython.keyboard_manager.register_events(el);\n",
|
|
" }\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
|
|
" var manager = IPython.notebook.keyboard_manager;\n",
|
|
" if (!manager)\n",
|
|
" manager = IPython.keyboard_manager;\n",
|
|
"\n",
|
|
" // 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",
|
|
"\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('matplotlib', mpl.mpl_figure_comm);\n",
|
|
"}\n"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.Javascript object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA4QAAAMgCAYAAAB7w6zDAAAgAElEQVR4nOzde5Td873/8e9M5J6QIIkIkspSl9JQThvKiVZd6lKXoy5Vh5ZD0RbHcTlWNfyU1lG02oPSI65VVRUcGqJubd1SjnLcFZH2pyhN2mgSIe/fH36zmzEzyUxsn8nn83081nqt1e7MbN9kz853P2fv2akCAACAWqp6+wAAAADoHYIQAACgpgQhAABATQlCAACAmhKEAAAANSUIAQAAakoQAgAA1JQgBAAAqClBCAAAUFOCEAAAoKYEIQAAQE0JQgAAgJoShAAAADUlCAEAAGpKEAIAANSUIAQAAKgpQQgAAFBTghAAAKCmBCEAAEBNCUIAAICaEoQAAAA1JQgBAABqShACAADUlCAEAACoKUEIAABQU4IQAACgpgQhAABATQlCAACAmhKEAAAANSUIAQAAakoQAgAA1JQgBAAAqClBCAAAUFOCEAAAoKYEIQAAQE0JQgAAgJoShAAAADUlCAEAAGpKEAIAANSUIAQAAKgpQQgAAFBTghAAAKCmBCEAAEBNCUIAAICaEoQAAAA1JQgBAABqShACAADUlCAEAACoKUEIAABQU4IQAACgpgQhAABATQlCAACAmhKEAAAANSUIAQAAakoQAgAA1JQgBAAAqClBCAAAUFOCEAAAoKYEIQAAQE0JQgAAgJoShAAAADUlCAEAAGpKEAIAANSUIAQAAKgpQQgAAFBTghAAAKCmBCEAAEBNCUIAAICaEoQAAAA1JQgBAABqShACAADUlCAEAACoKUEIAABQU4IQAACgpgQhtfPnP/85JkyY0Ng666wTffr0iddee623Dw0AAJIShNTemWeeGTvvvHNvH0a3TZkyJaqqaqx///4xatSo2HrrreP000+Pl19+ubcPsVvGjh0bBxxwQG8fRjYOOOCAGDt27DJ97pVXXhnnnHNOcw/o/3s/b8e2r/Xnn3++cVlXv5fnn38+qqqKM888c5n+W3fccUdUVRXXXHNNp79+xBFHRFVV7Y5raWu7vSZPnhxVVUVLS0v87ne/63Ddc+fOjaFDh0ZVVe3+LN/L72nSpEnxoQ99qNNfe/XVV6Oqqpg8eXLjsnf/nvr06ROrrbZa7L333vH000/36PqXRWe3dXfddNNN7X4vzXbKKafE+uuvH2+//XZE9Ox2WdKxnXbaaXHdddc181CXye677x6f+cxn2l322muvxQorrBA/+9nPIuL9OdbbbrstNt100xg0aFBUVRXXXXddp18Hn//852PXXXdt6n8baE8QUnsbbLDBcnFS7q62E+aUKVPi3nvvjbvvvjt++tOfxlFHHRUrrbRSrLzyyjF9+vTePsyleuihh+LZZ5/t7cPIxrPPPhsPPfTQMn3uTjvttMwxuTTvZxC+8sorce+998b8+fMbl3X1e0kZhG3Htfiqqoo999yz3WVtt1dbEA4dOjS+9rWvdbjuKVOmxIABA6Jv3769HoRtf6/ccccd8Y1vfCMGDhwYI0eOjNdff73b178s3ksQLn7bNNsf/vCHGDx4cLuvi57cLks6tsGDB/f6N8Xmzp0bAwcOjEsvvbTd5RdffHEMGjQo/va3v0VE84910aJFsfLKK8fEiRPjtttui3vvvTdef/31Tu/zzz77bKywwgrxi1/8omn/faA9QUit3XPPPTFq1KhYuHBhbx9Kt7U9cJoxY0aHX5s5c2asueaaMXTo0PjjH//YC0fH8ijXIOzM8hCEnamqKo444ohOf60tCA8++OBYc801G880tdlyyy1j33337fCguzeC8N1/r5xyyilRVVVcfPHF3b7+ZbG8BuFxxx0XY8aMaXeblRSEP/nJT6Jv374dgn/HHXeMPffcs/H/m32sv//976OqqjjjjDO69fE777xzbLvttk377wPtCUJq7eCDD45jjz22tw+jR5YUhBHvnOCrqopTTjml3eXXX399TJw4MQYOHBhDhgyJT33qU3HPPfe0+5i2B66//e1vY88994wVV1wxhg8fHkcffXQsXLgwnnzyydh+++1jyJAhMXbs2A4n83nz5sW//uu/xoQJExqfO3HixJg6dWqH43x3SLQ9IP/Rj34UJ554YowePTqGDh0a22yzTTz55JPtPvehhx6KnXbaKUaMGBH9+vWL0aNHx4477hizZs1a4p/drbfeGp/5zGdizJgx0b9//xg/fnwccsgh8eqrr7b7uFdeeSX+5V/+JdZYY43o169frLrqqrHFFlu0e+a1O8cwb968OOGEE2LcuHHRt2/fWH311ePwww+PP//5zx2O7corr4yJEyfG4MGDY/DgwTFhwoT44Q9/2Pj1zl4y+v3vfz+22mqrGDFiRAwaNCg23HDDOOOMM+LNN99sfMykSZM6fTljmwULFsSpp54a6667buP3euCBB8Yrr7zS7r/15ptvxrHHHhujRo2KgQMHxsc//vG4//77uxWEm222Wey4447tLttwww2jqqp44IEHGpdde+21UVVVPPLIIxHRMRKW9HtZ/EH6WWedFePGjYvBgwfHxIkT4957713i8UWkCcJ77rknqqqKadOmNX7tqaeeiqqqYvr06ctlEN50001RVVV885vf7Pb1L829994bW2yxRfTv3z9Gjx4dJ5xwQlx44YUdgvDHP/5xbLvttrHaaqvFgAEDYr311ovjjz8+5s6d2/iYAw44oNOvibbr6c59pCsLFiyIVVZZpcM5ortfa0s6ts4unzRpUkT8/ba49dZb48ADD4zhw4fHoEGDYuedd+7wkuNl/buwzd577x3bb799u8vmzJkT/fr1i6uuuioiYonH+sYbb8QxxxwT48aNi/79+8fw4cNj0003jR/96Edd/jfb7g+dvby6q28MXH311dHS0uJVJfA+EYTUVtvP7TzxxBO9fSg9srQgnDt3bvTp0ye22WabxmVXXnllVFUV2223XUydOjWuvvrq2HTTTaNfv37xy1/+svFxbSfqddddN0499dSYPn16HHfccVFVVXz5y1+O9dZbL84999yYPn16fOELX4iqquLaa69tfP7s2bPjwAMPjMsvvzxuv/32mDZtWvzbv/1btLa2dnhJUldBOG7cuNhvv/3ipptuiquuuirWWmutWGeddeKtt95q/P5WWWWV2GyzzeInP/lJ3HXXXXH11VfHl770pXj88ceX+Gd3/vnnxze/+c244YYb4q677opLL700JkyYEOuuu267B4jbb799jBgxIi688MK48847Y+rUqfH1r389fvzjH3f7GBYtWhTbb799rLDCCnHSSSfFrbfeGt/+9rdj8ODBsckmm7R7SdRJJ50UVVXFHnvsEddcc03ceuutcfbZZ8dJJ53U+JjOgvDoo4+O888/P6ZNmxa33357nHPOObHqqqvGF77whcbHPPbYY/Hxj388VltttXYvZ4yIePvtt2OHHXaIwYMHxymnnBLTp0+PH/7whzFmzJjYYIMNGi8Xa/vvt7S0xLHHHts4vjFjxsSKK6641CA84YQTYsiQIY0/4z/+8Y9RVVUMHDgwTjvttMbHHXbYYTFq1KjG/3/3g8Ml/V7aHmSPGzcudthhh5g6dWpMnTo1Ntpooxg+fHjMnj17icfY9vV39dVXx8KFCzvs8MMPf89B+Oqrr8ZWW20Ve+21V+PXjj/++Bg3blwsWrRouQzC73//+x3u50u7/iV57LHHYtCgQbHBBhvEVVddFddff31sv/32sdZaa3UIgVNPPTXOOeecuOmmm+LOO++MCy64ID7wgQ/EJz7xicbHPPvss7HnnntGVVXtviba7l/duY905e67746qquLmm29ud3l3v9aWdGz33ntvDBw4MHbcccfG5Y899lhE/P22WHPNNeOLX/xi/PznP48LL7wwRo4cGWuuuWbjG0rd/buw7evvjjvuaPf7mDdvXgwZMiQuvPDCdpdfccUV0b9///jLX/4SEbHEYz300ENj0KBBcfbZZ8cdd9wR//3f/x3f+ta34nvf+16Xf66zZs2Kn/3sZ1FVVXzlK19p9/LqroLw5Zdfjqqq4txzz13q7Qb0nCCktqZMmRIf//jHe/swemxpQRgRMWrUqFh//fUj4p0H/auvvnpstNFG7V729Ne//jVGjhwZW2yxReOytgcOZ511Vrvr23jjjaOqqsYbDERELFy4MEaMGBF77LFHl8fx1ltvxcKFC+Oggw6KTTbZpN2vdRWE734mqe0Zz7YH/r/5zW+iqqpOn3XsiUWLFsXChQtj5syZUVVVXH/99Y1fGzJkSBx11FFdfm53jmHatGlRVVX8x3/8R7vLr7766qiqqvEg7Lnnnos+ffrEfvvtt8TjXdqbyrz99tuxcOHCuOyyy6JPnz7tXgLW1cssr7rqqk4f7M+YMSOqqorzzjsvIiKeeOKJqKoqjj766HYf1/aNhqUF4W233RZVVcXdd98dEe884Bw6dGgcfvjh7R7cr7POOvG5z32u8f87e3C4tJeMbrTRRo1vHkREPPDAA1FVVePZjq60ff0tbV3pbhBOmTIl+vfvH6+99lq89dZbMXr06Dj55JMjouPL8nojCO+7775YuHBh/PWvf41p06bFaqutFv/4j//Y4WX1yxqEe++9dwwcOLDdS9rfeuutWG+99Zb4ktG2++tdd93VeBVDm+6+ZHRJ95HOnHHGGVFVVYeX3/fka21ZXjLadlvsvvvu7S7/9a9/HVVVxTe+8Y2I6P7fhaecckr06dMn7rzzznaXT506Nfr06dPh1QC77bZb7LLLLt061g033DB22223Jf73O9PV1/aS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MOKdVzBsvvnm7S5re5OZH/zgB90+1s60RWnbNxo709MgXLhwYQwaNKjD30FAcwhCyMy7/wHpX/7yl3Httde2+4fpb7/99naf0/bAfccdd4zrr78+fvKTn8Q//MM/dPkuo8sahG0PQg877LD4xS9+EZdcckmMHz8+1llnnaYF4Y033hif/vSn4wc/+EFMnz49br311vjSl74UVVV1eLe8xb355psxfvz4GDt2bPzoRz+KadOmxRFHHBEf/OAH2z2Imz17dmyyySZx5plnxo033hh33nlnnHnmmTFgwIDGG5505xja3mW0b9++cfLJJ8f06dPjrLPOiiFDhnT5LqN77rlnXHvttXHbbbfFueee2/iZu7bbYPEgfOKJJ6Jfv36x9dZbx8033xw/+9nPYtttt238WS/+YLftdj3vvPPi/vvvbzzof+utt+LTn/50rLzyynHKKafEz3/+87jtttvikksuiQMOOKDdmwh9/vOfj5aWljjuuOMa7zK6+uqrd+tdRiPe+cZE28vaFn+Hx7Y3Camq9m9aFNH5g8Oufi+5BWFnugrCf/7nf45rrrmmw979zNfiFixYEJtttlkMGTIkvvGNb8Qtt9wSU6dOjUMOOSRaWlriq1/9aruPX9I3mv7jP/4jqqr9y84nTZoUa665ZqfH9e5n9Rb36KOPxsCBA2ODDTaIH//4x3HDDTfE9ttvH2uuuWa72/pPf/pTDB8+PCZMmBA/+9nP4sYbb4x99tmn8fW9eBC2HfvkyZPjvvvuixkzZsSCBQt6dB/pTNvPtb47kHrytdbVsbX9GY4cOTJuuOGGds/IL/4uowcddFBMmzYtLrroohg5cmSMGTMmXnvttYjo/t+F735TmbfeeitWXXXV+Pa3v93u2K+77rpobW2Nl19+ucPvq6tj/ehHPxr/5//8n5g6dWrcddddccEFF8Qqq6zSLjYvvfTS6NOnT7t3mu5pED744INRVVXccMMNHY4NeO8EIWTm3W/+0K9fvxg5cmRMmjQpTj/99A4vUWwzderU+NjHPhYDBgyIwYMHxzbbbBO//vWv233Mew3CiIhvfetbjX+Tav3114+LLrqocb2LW9YgfPLJJ2PfffeN8ePHx8CBA2OllVaKj370ox1eCtqZxx9/PLbddtsYOnRoDB8+PD772c82HvS1PYibP39+fOlLX4oPf/jDseKKK8bAgQNj3XXXjcmTJze+493dY5g3b14cf/zxMXbs2Ojbt2+MHj06DjvssE7fpOOyyy6Lf/iHf4gBAwY0onHxB72dvanMjTfeGBMmTIgBAwbEmDFj4thjj42f//znHR7svv7667HnnnvGsGHDGm/O0WbhwoXx7W9/u3E9Q4YMifXWWy8OPfTQeOaZZxoft2DBgjjmmGNi5MiRMWDAgMa/udaTf5h+9913j6qq4sorr2xc9uabb8bgwYOjtbW1w59LZw8Ou/q9lByEXW1pz5r/5S9/ieOOOy7WWWed6NevXwwaNCg222yzuOCCCzq8lHRJQThv3rwO//xLV/8mZFX9/d+o68qvf/3rmDhxYvTv3z9WW221OPbYYzv9dwjvueee2HzzzWPQoEExYsSIOPjgg+Ohhx7q8HtfsGBBHHzwwTFixIjG10Tb9XT3PtKVrbbaqsM7H/fka21Jx/bwww/Hxz/+8Rg0aFC7P7fF/x3C/fffP4YNG9b4Zx8Wv0929++hd/+zE23v+vvcc8+1+7jPf/7zXd52XR3rCSecEJtttlkMHz48+vfvH2uvvXYcffTR8ac//anxuYt/E3Npf4ZdBeFJJ50Uq666artvpAHNIwgBADrx05/+NPr06dPpGyq9X7rzTtLvxWGHHRYf+chH2l22YMGCWGmllZbLf+fvrbfeinHjxsWJJ57Y24cCxRKEAACdWLRoUUycOLHLZ3/fD+93EObmkksuiVVXXbXTV1YAzSEIAQC68Oijj8Zpp53W7k113k+CsL2LL744brnllt4+DCiaIAQAAKgpQQgAAFBTghAAAKCmBCEAAEBNCcLMvf322zFr1qyYPXt2zJkzx8zMzMws+82ePTtmzZqV7A2d6kwQZm7WrFlL/IeLzczMzMxy3axZs3r74XbxBGHmZs+e3biz9PZ3cszMzMzMmrG2Jz1mz57d2w+3iycIMzdnzpyoqirmzJnT24cCAABN4TFuOoIwc+4sAACUxmPcdARh5txZAAAojce46QjCzLmzAABQGo9x0xGEmXNnAQCgNB7jpiMIM+fOAgBAaTzGTUcQZs6dBQCA0niMm44gzJw7CwAApfEYNx1BmDl3FgAASuMxbjqCMHPuLAAAlMZj3HQEYebcWQAAKI3HuOkIwsy5swAAUBqPcdMRhJlzZwEAoDQe46YjCDPnzgIAQGk8xk1HEGbOnQUAgNJ4jJuOIMycOwsAAKXxGDcdQZg5dxYAAErjMW46gjBz7iwAAJTGY9x0BGHm3FkAACiNx7jpCMLMubMAAFAaj3HTEYSZc2cBAKA0HuOmIwgz584CAEBpPMZNRxBmzp0FAIDSeIybjiDsRWPHjo2qqjrs8MMP7/Z1uLMAAFAaj3HTEYS96JVXXomXXnqpsenTp0dVVXHHHXd0+zrcWQAAKI3HuOkIwuXIkUceGePHj49FixZ1+3PcWQAAKI3HuOkIwuXEggULYpVVVonTTjttiR83f/78mDNnTmOzZs3q1TvLzJkz48EHH7T3aTNnzuyV2xUAoDcJwnQE4XLi6quvjj59+sQf/vCHJX7c5MmTO/25w964s8ycOTMGDBzQ6fFYczZg4ABRCADUjiBMRxAuJ7bbbrvYeeedl/pxy9MzhA8++OA74bJHFdUh1vTt8U4UPvjgg8lvWwCA3iQI0xGEy4EXXnghWltbY+rUqT3+3N68szSC8JAqqpOt6TtEEAIA9SQI0xGEy4HJkyfHaqutFgsXLuzx5wrCgicIAYCaEoTpCMJe9vbbb8daa60Vxx9//DJ9viAseIIQAKgpQZiOIOxlt9xyS1RVFU899dQyfb4gLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQczHHsEAACAASURBVJiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4gzJwgLHiCEACoKUGYjiDMnCAseIIQAKgpQZiOIMycICx4ghAAqClBmI4g7GW///3vY7/99ouVV145Bg4cGBMmTIjf/OY33f58QVjwBCEAUFOCMB1B2Itef/31GDt2bBx44IFx//33x/PPPx+33XZbPPvss92+DkFY8AQhAFBTgjAdQdiLjj/++Nhyyy3f03UIwoInCAGAmhKE6QjCXrT++uvHUUcdFXvuuWeMGDEiNt5447jwwguX+Dnz58+POXPmNDZr1ixBWOoEIQBQU4IwHUHYi/r37x/9+/ePf//3f4+HHnooLrjgghgwYEBceumlXX7O5MmT34mwd00QFjhBCADUlCBMRxD2or59+8bmm2/e7rKvfOUrMXHixC4/xzOENZogBABqShCmIwh70VprrRUHHXRQu8vOO++8WH31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\" width=\"900\">"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Generating statistics: https://facebook.com\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"application/javascript": [
|
|
"/* Put everything inside the global mpl namespace */\n",
|
|
"window.mpl = {};\n",
|
|
"\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('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",
|
|
"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 = $('<div/>');\n",
|
|
" this._root_extra_style(this.root)\n",
|
|
" this.root.attr('style', 'display: inline-block');\n",
|
|
"\n",
|
|
" $(parent_element).append(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 (mpl.ratio != 1) {\n",
|
|
" fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n",
|
|
" }\n",
|
|
" fig.send_message(\"refresh\", {});\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.imageObj.onload = function() {\n",
|
|
" if (fig.image_mode == 'full') {\n",
|
|
" // Full images could contain transparency (where diff images\n",
|
|
" // almost always do), so we need to clear the canvas so that\n",
|
|
" // there is no ghosting.\n",
|
|
" fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n",
|
|
" }\n",
|
|
" fig.context.drawImage(fig.imageObj, 0, 0);\n",
|
|
" };\n",
|
|
"\n",
|
|
" this.imageObj.onunload = function() {\n",
|
|
" fig.ws.close();\n",
|
|
" }\n",
|
|
"\n",
|
|
" this.ws.onmessage = this._make_on_message_function(this);\n",
|
|
"\n",
|
|
" this.ondownload = ondownload;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_header = function() {\n",
|
|
" var titlebar = $(\n",
|
|
" '<div class=\"ui-dialog-titlebar ui-widget-header ui-corner-all ' +\n",
|
|
" 'ui-helper-clearfix\"/>');\n",
|
|
" var titletext = $(\n",
|
|
" '<div class=\"ui-dialog-title\" style=\"width: 100%; ' +\n",
|
|
" 'text-align: center; padding: 3px;\"/>');\n",
|
|
" titlebar.append(titletext)\n",
|
|
" this.root.append(titlebar);\n",
|
|
" this.header = titletext[0];\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"\n",
|
|
"mpl.figure.prototype._root_extra_style = function(canvas_div) {\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_canvas = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var canvas_div = $('<div/>');\n",
|
|
"\n",
|
|
" canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n",
|
|
"\n",
|
|
" function canvas_keyboard_event(event) {\n",
|
|
" return fig.key_event(event, event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" canvas_div.keydown('key_press', canvas_keyboard_event);\n",
|
|
" canvas_div.keyup('key_release', canvas_keyboard_event);\n",
|
|
" this.canvas_div = canvas_div\n",
|
|
" this._canvas_extra_style(canvas_div)\n",
|
|
" this.root.append(canvas_div);\n",
|
|
"\n",
|
|
" var canvas = $('<canvas/>');\n",
|
|
" canvas.addClass('mpl-canvas');\n",
|
|
" canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n",
|
|
"\n",
|
|
" this.canvas = canvas[0];\n",
|
|
" this.context = canvas[0].getContext(\"2d\");\n",
|
|
"\n",
|
|
" var backingStore = this.context.backingStorePixelRatio ||\n",
|
|
"\tthis.context.webkitBackingStorePixelRatio ||\n",
|
|
"\tthis.context.mozBackingStorePixelRatio ||\n",
|
|
"\tthis.context.msBackingStorePixelRatio ||\n",
|
|
"\tthis.context.oBackingStorePixelRatio ||\n",
|
|
"\tthis.context.backingStorePixelRatio || 1;\n",
|
|
"\n",
|
|
" mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n",
|
|
"\n",
|
|
" var rubberband = $('<canvas/>');\n",
|
|
" rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n",
|
|
"\n",
|
|
" var pass_mouse_events = true;\n",
|
|
"\n",
|
|
" canvas_div.resizable({\n",
|
|
" start: function(event, ui) {\n",
|
|
" pass_mouse_events = false;\n",
|
|
" },\n",
|
|
" resize: function(event, ui) {\n",
|
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
|
" },\n",
|
|
" stop: function(event, ui) {\n",
|
|
" pass_mouse_events = true;\n",
|
|
" fig.request_resize(ui.size.width, ui.size.height);\n",
|
|
" },\n",
|
|
" });\n",
|
|
"\n",
|
|
" function mouse_event_fn(event) {\n",
|
|
" if (pass_mouse_events)\n",
|
|
" return fig.mouse_event(event, event['data']);\n",
|
|
" }\n",
|
|
"\n",
|
|
" rubberband.mousedown('button_press', mouse_event_fn);\n",
|
|
" rubberband.mouseup('button_release', mouse_event_fn);\n",
|
|
" // Throttle sequential mouse events to 1 every 20ms.\n",
|
|
" rubberband.mousemove('motion_notify', mouse_event_fn);\n",
|
|
"\n",
|
|
" rubberband.mouseenter('figure_enter', mouse_event_fn);\n",
|
|
" rubberband.mouseleave('figure_leave', mouse_event_fn);\n",
|
|
"\n",
|
|
" canvas_div.on(\"wheel\", function (event) {\n",
|
|
" event = event.originalEvent;\n",
|
|
" event['data'] = 'scroll'\n",
|
|
" if (event.deltaY < 0) {\n",
|
|
" event.step = 1;\n",
|
|
" } else {\n",
|
|
" event.step = -1;\n",
|
|
" }\n",
|
|
" mouse_event_fn(event);\n",
|
|
" });\n",
|
|
"\n",
|
|
" canvas_div.append(canvas);\n",
|
|
" canvas_div.append(rubberband);\n",
|
|
"\n",
|
|
" this.rubberband = rubberband;\n",
|
|
" this.rubberband_canvas = rubberband[0];\n",
|
|
" this.rubberband_context = rubberband[0].getContext(\"2d\");\n",
|
|
" this.rubberband_context.strokeStyle = \"#000000\";\n",
|
|
"\n",
|
|
" this._resize_canvas = function(width, height) {\n",
|
|
" // Keep the size of the canvas, canvas container, and rubber band\n",
|
|
" // canvas in synch.\n",
|
|
" canvas_div.css('width', width)\n",
|
|
" canvas_div.css('height', height)\n",
|
|
"\n",
|
|
" canvas.attr('width', width * mpl.ratio);\n",
|
|
" canvas.attr('height', height * mpl.ratio);\n",
|
|
" canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n",
|
|
"\n",
|
|
" rubberband.attr('width', width);\n",
|
|
" rubberband.attr('height', height);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Set the figure to an initial 600x600px, this will subsequently be updated\n",
|
|
" // upon first draw.\n",
|
|
" this._resize_canvas(600, 600);\n",
|
|
"\n",
|
|
" // Disable right mouse context menu.\n",
|
|
" $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\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 nav_element = $('<div/>');\n",
|
|
" nav_element.attr('style', 'width: 100%');\n",
|
|
" this.root.append(nav_element);\n",
|
|
"\n",
|
|
" // Define a callback function for later on.\n",
|
|
" function toolbar_event(event) {\n",
|
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
|
" }\n",
|
|
" function toolbar_mouse_event(event) {\n",
|
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
|
" }\n",
|
|
"\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",
|
|
" // put a spacer in here.\n",
|
|
" continue;\n",
|
|
" }\n",
|
|
" var button = $('<button/>');\n",
|
|
" button.addClass('ui-button ui-widget ui-state-default ui-corner-all ' +\n",
|
|
" 'ui-button-icon-only');\n",
|
|
" button.attr('role', 'button');\n",
|
|
" button.attr('aria-disabled', 'false');\n",
|
|
" button.click(method_name, toolbar_event);\n",
|
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
|
"\n",
|
|
" var icon_img = $('<span/>');\n",
|
|
" icon_img.addClass('ui-button-icon-primary ui-icon');\n",
|
|
" icon_img.addClass(image);\n",
|
|
" icon_img.addClass('ui-corner-all');\n",
|
|
"\n",
|
|
" var tooltip_span = $('<span/>');\n",
|
|
" tooltip_span.addClass('ui-button-text');\n",
|
|
" tooltip_span.html(tooltip);\n",
|
|
"\n",
|
|
" button.append(icon_img);\n",
|
|
" button.append(tooltip_span);\n",
|
|
"\n",
|
|
" nav_element.append(button);\n",
|
|
" }\n",
|
|
"\n",
|
|
" var fmt_picker_span = $('<span/>');\n",
|
|
"\n",
|
|
" var fmt_picker = $('<select/>');\n",
|
|
" fmt_picker.addClass('mpl-toolbar-option ui-widget ui-widget-content');\n",
|
|
" fmt_picker_span.append(fmt_picker);\n",
|
|
" nav_element.append(fmt_picker_span);\n",
|
|
" this.format_dropdown = fmt_picker[0];\n",
|
|
"\n",
|
|
" for (var ind in mpl.extensions) {\n",
|
|
" var fmt = mpl.extensions[ind];\n",
|
|
" var option = $(\n",
|
|
" '<option/>', {selected: fmt === mpl.default_extension}).html(fmt);\n",
|
|
" fmt_picker.append(option);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Add hover states to the ui-buttons\n",
|
|
" $( \".ui-button\" ).hover(\n",
|
|
" function() { $(this).addClass(\"ui-state-hover\");},\n",
|
|
" function() { $(this).removeClass(\"ui-state-hover\");}\n",
|
|
" );\n",
|
|
"\n",
|
|
" var status_bar = $('<span class=\"mpl-message\"/>');\n",
|
|
" nav_element.append(status_bar);\n",
|
|
" this.message = status_bar[0];\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",
|
|
"\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",
|
|
"\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]);\n",
|
|
" fig.send_message(\"refresh\", {});\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_rubberband = function(fig, msg) {\n",
|
|
" var x0 = msg['x0'] / mpl.ratio;\n",
|
|
" var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n",
|
|
" var x1 = msg['x1'] / mpl.ratio;\n",
|
|
" var y1 = (fig.canvas.height - msg['y1']) / mpl.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, 0, fig.canvas.width / mpl.ratio, fig.canvas.height / mpl.ratio);\n",
|
|
"\n",
|
|
" fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_figure_label = function(fig, msg) {\n",
|
|
" // Updates the figure title.\n",
|
|
" fig.header.textContent = msg['label'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_cursor = function(fig, msg) {\n",
|
|
" var cursor = msg['cursor'];\n",
|
|
" switch(cursor)\n",
|
|
" {\n",
|
|
" case 0:\n",
|
|
" cursor = 'pointer';\n",
|
|
" break;\n",
|
|
" case 1:\n",
|
|
" cursor = 'default';\n",
|
|
" break;\n",
|
|
" case 2:\n",
|
|
" cursor = 'crosshair';\n",
|
|
" break;\n",
|
|
" case 3:\n",
|
|
" cursor = 'move';\n",
|
|
" break;\n",
|
|
" }\n",
|
|
" fig.rubberband_canvas.style.cursor = cursor;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_message = function(fig, msg) {\n",
|
|
" fig.message.textContent = msg['message'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_draw = function(fig, msg) {\n",
|
|
" // Request the server to send over a new figure.\n",
|
|
" fig.send_draw_message();\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_image_mode = function(fig, msg) {\n",
|
|
" fig.image_mode = msg['mode'];\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.updated_canvas_event = function() {\n",
|
|
" // Called whenever the canvas gets updated.\n",
|
|
" this.send_message(\"ack\", {});\n",
|
|
"}\n",
|
|
"\n",
|
|
"// A function to construct a web socket function for onmessage handling.\n",
|
|
"// Called in the figure constructor.\n",
|
|
"mpl.figure.prototype._make_on_message_function = function(fig) {\n",
|
|
" return function socket_on_message(evt) {\n",
|
|
" if (evt.data instanceof Blob) {\n",
|
|
" /* FIXME: We get \"Resource interpreted as Image but\n",
|
|
" * transferred with MIME type text/plain:\" errors on\n",
|
|
" * Chrome. But how to set the MIME type? It doesn't seem\n",
|
|
" * to be part of the websocket stream */\n",
|
|
" evt.data.type = \"image/png\";\n",
|
|
"\n",
|
|
" /* Free the memory for the previous frames */\n",
|
|
" if (fig.imageObj.src) {\n",
|
|
" (window.URL || window.webkitURL).revokeObjectURL(\n",
|
|
" fig.imageObj.src);\n",
|
|
" }\n",
|
|
"\n",
|
|
" fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n",
|
|
" evt.data);\n",
|
|
" fig.updated_canvas_event();\n",
|
|
" fig.waiting = false;\n",
|
|
" return;\n",
|
|
" }\n",
|
|
" else if (typeof evt.data === 'string' && evt.data.slice(0, 21) == \"data:image/png;base64\") {\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(\"No handler for the '\" + msg_type + \"' message type: \", msg);\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(\"Exception inside the 'handler_\" + msg_type + \"' callback:\", e, e.stack, msg);\n",
|
|
" }\n",
|
|
" }\n",
|
|
" };\n",
|
|
"}\n",
|
|
"\n",
|
|
"// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n",
|
|
"mpl.findpos = function(e) {\n",
|
|
" //this section is from http://www.quirksmode.org/js/events_properties.html\n",
|
|
" var targ;\n",
|
|
" if (!e)\n",
|
|
" e = window.event;\n",
|
|
" if (e.target)\n",
|
|
" targ = e.target;\n",
|
|
" else if (e.srcElement)\n",
|
|
" targ = e.srcElement;\n",
|
|
" if (targ.nodeType == 3) // defeat Safari bug\n",
|
|
" targ = targ.parentNode;\n",
|
|
"\n",
|
|
" // jQuery normalizes the pageX and pageY\n",
|
|
" // pageX,Y are the mouse positions relative to the document\n",
|
|
" // offset() returns the position of the element relative to the document\n",
|
|
" var x = e.pageX - $(targ).offset().left;\n",
|
|
" var y = e.pageY - $(targ).offset().top;\n",
|
|
"\n",
|
|
" return {\"x\": x, \"y\": y};\n",
|
|
"};\n",
|
|
"\n",
|
|
"/*\n",
|
|
" * return a copy of an object with only non-object keys\n",
|
|
" * we need this to avoid circular references\n",
|
|
" * http://stackoverflow.com/a/24161582/3208463\n",
|
|
" */\n",
|
|
"function simpleKeys (original) {\n",
|
|
" return Object.keys(original).reduce(function (obj, key) {\n",
|
|
" if (typeof original[key] !== 'object')\n",
|
|
" obj[key] = original[key]\n",
|
|
" 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",
|
|
" {\n",
|
|
" this.canvas.focus();\n",
|
|
" this.canvas_div.focus();\n",
|
|
" }\n",
|
|
"\n",
|
|
" var x = canvas_pos.x * mpl.ratio;\n",
|
|
" var y = canvas_pos.y * mpl.ratio;\n",
|
|
"\n",
|
|
" this.send_message(name, {x: x, y: y, button: event.button,\n",
|
|
" step: event.step,\n",
|
|
" guiEvent: simpleKeys(event)});\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",
|
|
"\n",
|
|
" // Prevent repeat events\n",
|
|
" if (name == 'key_press')\n",
|
|
" {\n",
|
|
" if (event.which === this._key)\n",
|
|
" return;\n",
|
|
" else\n",
|
|
" this._key = event.which;\n",
|
|
" }\n",
|
|
" if (name == 'key_release')\n",
|
|
" this._key = null;\n",
|
|
"\n",
|
|
" var value = '';\n",
|
|
" if (event.ctrlKey && event.which != 17)\n",
|
|
" value += \"ctrl+\";\n",
|
|
" if (event.altKey && event.which != 18)\n",
|
|
" value += \"alt+\";\n",
|
|
" if (event.shiftKey && event.which != 16)\n",
|
|
" value += \"shift+\";\n",
|
|
"\n",
|
|
" value += 'k';\n",
|
|
" value += event.which.toString();\n",
|
|
"\n",
|
|
" this._key_event_extra(event, name);\n",
|
|
"\n",
|
|
" this.send_message(name, {key: value,\n",
|
|
" 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",
|
|
"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\", \"Pan axes with left mouse, zoom with right\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n",
|
|
"\n",
|
|
"mpl.extensions = [\"eps\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\"];\n",
|
|
"\n",
|
|
"mpl.default_extension = \"png\";var comm_websocket_adapter = function(comm) {\n",
|
|
" // Create a \"websocket\"-like object which calls the given IPython comm\n",
|
|
" // object with the appropriate methods. Currently this is a non binary\n",
|
|
" // socket, so there is still some room for performance tuning.\n",
|
|
" var ws = {};\n",
|
|
"\n",
|
|
" ws.close = function() {\n",
|
|
" comm.close()\n",
|
|
" };\n",
|
|
" ws.send = function(m) {\n",
|
|
" //console.log('sending', m);\n",
|
|
" comm.send(m);\n",
|
|
" };\n",
|
|
" // Register the callback with on_msg.\n",
|
|
" comm.on_msg(function(msg) {\n",
|
|
" //console.log('receiving', msg['content']['data'], msg);\n",
|
|
" // Pass the mpl event to the overridden (by mpl) onmessage function.\n",
|
|
" ws.onmessage(msg['content']['data'])\n",
|
|
" });\n",
|
|
" return ws;\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.mpl_figure_comm = function(comm, msg) {\n",
|
|
" // This is the function which gets called when the mpl process\n",
|
|
" // starts-up an IPython Comm through the \"matplotlib\" channel.\n",
|
|
"\n",
|
|
" var id = msg.content.data.id;\n",
|
|
" // Get hold of the div created by the display call when the Comm\n",
|
|
" // socket was opened in Python.\n",
|
|
" var element = $(\"#\" + id);\n",
|
|
" var ws_proxy = comm_websocket_adapter(comm)\n",
|
|
"\n",
|
|
" function ondownload(figure, format) {\n",
|
|
" window.open(figure.imageObj.src);\n",
|
|
" }\n",
|
|
"\n",
|
|
" var fig = new mpl.figure(id, ws_proxy,\n",
|
|
" ondownload,\n",
|
|
" element.get(0));\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.get(0);\n",
|
|
" fig.cell_info = mpl.find_output_cell(\"<div id='\" + id + \"'></div>\");\n",
|
|
" if (!fig.cell_info) {\n",
|
|
" console.error(\"Failed to find cell for figure\", id, fig);\n",
|
|
" return;\n",
|
|
" }\n",
|
|
"\n",
|
|
" var output_index = fig.cell_info[2]\n",
|
|
" var cell = fig.cell_info[0];\n",
|
|
"\n",
|
|
"};\n",
|
|
"\n",
|
|
"mpl.figure.prototype.handle_close = function(fig, msg) {\n",
|
|
" var width = fig.canvas.width/mpl.ratio\n",
|
|
" fig.root.unbind('remove')\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).html('<img src=\"' + dataURL + '\" width=\"' + width + '\">');\n",
|
|
" fig.close_ws(fig, msg);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.close_ws = function(fig, msg){\n",
|
|
" fig.send_message('closing', msg);\n",
|
|
" // fig.ws.close()\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.push_to_output = function(remove_interactive) {\n",
|
|
" // Turn the data on the canvas into data in the output cell.\n",
|
|
" var width = this.canvas.width/mpl.ratio\n",
|
|
" var dataURL = this.canvas.toDataURL();\n",
|
|
" this.cell_info[1]['text/html'] = '<img src=\"' + dataURL + '\" width=\"' + width + '\">';\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype.updated_canvas_event = function() {\n",
|
|
" // Tell IPython that the notebook contents must change.\n",
|
|
" IPython.notebook.set_dirty(true);\n",
|
|
" this.send_message(\"ack\", {});\n",
|
|
" var fig = this;\n",
|
|
" // Wait a second, then push the new image to the DOM so\n",
|
|
" // that it is saved nicely (might be nice to debounce this).\n",
|
|
" setTimeout(function () { fig.push_to_output() }, 1000);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._init_toolbar = function() {\n",
|
|
" var fig = this;\n",
|
|
"\n",
|
|
" var nav_element = $('<div/>');\n",
|
|
" nav_element.attr('style', 'width: 100%');\n",
|
|
" this.root.append(nav_element);\n",
|
|
"\n",
|
|
" // Define a callback function for later on.\n",
|
|
" function toolbar_event(event) {\n",
|
|
" return fig.toolbar_button_onclick(event['data']);\n",
|
|
" }\n",
|
|
" function toolbar_mouse_event(event) {\n",
|
|
" return fig.toolbar_button_onmouseover(event['data']);\n",
|
|
" }\n",
|
|
"\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) { continue; };\n",
|
|
"\n",
|
|
" var button = $('<button class=\"btn btn-default\" href=\"#\" title=\"' + name + '\"><i class=\"fa ' + image + ' fa-lg\"></i></button>');\n",
|
|
" button.click(method_name, toolbar_event);\n",
|
|
" button.mouseover(tooltip, toolbar_mouse_event);\n",
|
|
" nav_element.append(button);\n",
|
|
" }\n",
|
|
"\n",
|
|
" // Add the status bar.\n",
|
|
" var status_bar = $('<span class=\"mpl-message\" style=\"text-align:right; float: right;\"/>');\n",
|
|
" nav_element.append(status_bar);\n",
|
|
" this.message = status_bar[0];\n",
|
|
"\n",
|
|
" // Add the close button to the window.\n",
|
|
" var buttongrp = $('<div class=\"btn-group inline pull-right\"></div>');\n",
|
|
" var button = $('<button class=\"btn btn-mini btn-primary\" href=\"#\" title=\"Stop Interaction\"><i class=\"fa fa-power-off icon-remove icon-large\"></i></button>');\n",
|
|
" button.click(function (evt) { fig.handle_close(fig, {}); } );\n",
|
|
" button.mouseover('Stop Interaction', toolbar_mouse_event);\n",
|
|
" buttongrp.append(button);\n",
|
|
" var titlebar = this.root.find($('.ui-dialog-titlebar'));\n",
|
|
" titlebar.prepend(buttongrp);\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._root_extra_style = function(el){\n",
|
|
" var fig = this\n",
|
|
" el.on(\"remove\", function(){\n",
|
|
"\tfig.close_ws(fig, {});\n",
|
|
" });\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._canvas_extra_style = function(el){\n",
|
|
" // this is important to make the div 'focusable\n",
|
|
" el.attr('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",
|
|
" }\n",
|
|
" else {\n",
|
|
" // location in version 2\n",
|
|
" IPython.keyboard_manager.register_events(el);\n",
|
|
" }\n",
|
|
"\n",
|
|
"}\n",
|
|
"\n",
|
|
"mpl.figure.prototype._key_event_extra = function(event, name) {\n",
|
|
" var manager = IPython.notebook.keyboard_manager;\n",
|
|
" if (!manager)\n",
|
|
" manager = IPython.keyboard_manager;\n",
|
|
"\n",
|
|
" // 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",
|
|
"\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('matplotlib', mpl.mpl_figure_comm);\n",
|
|
"}\n"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.Javascript object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
},
|
|
{
|
|
"data": {
|
|
"text/html": [
|
|
"<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA4QAAAMgCAYAAAB7w6zDAAAgAElEQVR4nOzdeXTV9Z3/8W/CEpZEFgVBUFBKxY4KqFW0MlipO661LrUMVv2paKtSR2RsLToqHaattnamtm64jnVHHRXBBRS3UtFaxa21RdpStSpUVJTl/fvDuVcuSSDBL8Qvn8fznMc5enOTfENyb+4r9+YmC0mSJElSkmUtfQCSJEmSpJbJIJQkSZKkRDMIJUmSJCnRDEJJkiRJSjSDUJIkSZISzSCUJEmSpEQzCCVJkiQp0QxCSZIkSUo0g1CSJEmSEs0glCRJkqREMwglSZIkKdEMQkmSJElKNINQkiRJkhLNIJQkSZKkRDMIJUmSJCnRDEJJkiRJSjSDUJIkSZISzSCUJEmSpEQzCCVJkiQp0QxCSZKa2R577BHbbLNNDBw4MHbdddd4+umnW/qQJElaowxCSZKa2TvvvFP+79tvvz0GDx7cgkcjSdKaZxBKkvQpuuqqq2L77bdv6cOQJGmNMgglSVqDRo4cGb17947evXvHc88919KHI0nSGmUQSpL0Kbrqqqtin332aenDkCRpjTIIJUn6lLVr1y7+/ve/t/RhSJLU7AxCSZKa0cKFC+Mvf/lL+f9vu+226NWrVyxfvrwFj0qSpDXLIJQkqRm99tpr8cUvfjG23nrr2HbbbWP48OH+7IQkqbAZhJIkSZKUaAahJEmSJCWaQShJkiRJiWYQSpIkSVKiGYSSJEmSlGgGoSRJkiQlmkEoSZIkSYlmEEqSJElSohmEkiRJkpRoBqEkSZIkJZpBKEmSJEmJZhBKkiRJUqIZhJIkSZKUaAahJEmSJCWaQShJkiRJiWYQSpIkSVKiGYSSJEmSlGgGoSRJkiQlmkEoSZIkSYlmEEqSJElSohmEkiRJkpRoBqEkSZIkJZpBKEmSJEmJZhBKkiRJUqIZhJIkSZKUaAahJEmSJCWaQShJkiRJiWYQSpIkSVKiGYSSJEmSlGgGoSRJkiQlmkEoSZIkSYlmEEqSJElSohmEkiRJkpRoBqEkSZIkJZpBKEmSJEmJZhBKkiRJUqIZhJIkSZKUaAahJEmSJCWaQShJkiRJiWYQSpIkSVKiGYSSJEmSlGgGoSRJkiQlmkEoSZIkSYlmEEqSJElSohmEkiRJkpRoBqEkSZIkJZpBKEmSJEmJZhBKkiRJUqIZhFJETJo0KbIsK6upqYmNN944dtttt5gwYUK8/vrrLX2ITapPnz4xatSolj6MwjRq1Kjo06fPGr3u9ddfHxdddFG+B/R/rc3PY+lr/Y9//GP5tMY+lj/+8Y+RZVn88Ic/XKP39dBDD0WWZXHzzTc3+PKTTz45siyrOK7VKX2+xo8fH1mWRVVVVfzhD3+o97YXLVoUdXV1kWVZxb/lp/mYhg0bFv/0T//U4MvefPPNyLIsxo8fXz5t5Y+pVatW0aNHjzj88MPj5ZdfbtbbX5Ma+lw3tbvvvrviY8m7c889N7baaqtYtmxZRDTv87KqY7vgggvi9ttvz/NQ16iDDz44DjjggIrT3nrrrWjdunXcdttt5f8//PDDo1u3bpFlWRx44IEtcagR8cnl6c0331zn73vUqFHRsWPHdf5+8+7tt9+OTp06fSa+/qTmZhBK8ckNp0mTJsXjjz8eDz/8cNxyyy1x2mmnRadOnaJr164xbdq0lj7M1TZ79uz4/e9/39KHUZh+//vfx+zZs9fodffbb781HpOra20OwjfeeCMef/zxWLx4cfm0xj6WdTkIS8e1oizL4tBDD604rfT5Kt2Arauri+9973v13vakSZOiXbt20aZNmxYfhKXrlYceeijOP//8aN++fXTv3j3efvvtJr/9NenTDMIVPzd595e//CU6duxY8XXRnM/Lqo6tY8eOLf5DsUWLFkX79u3j6quvrjj9yiuvjA4dOsT7778fERGnnXZatG3bNq677rp4/PHH46WXXmqJw40IgzCvzjnnnPjc5z4XH374YUsfitSsDEIpPrnhNGvWrHovmzt3bmy66aZRV1cXf/vb31rg6PRZrKiDsKE+C4OwobIsi5NPPrnBl5VuwB533HGx6aablu9pKrXrrrvGkUceWW8gtMQgXPl65dxzz40sy+LKK69s8ttfkz6rg3Ds2LHRq1evis/Z+jQIb7rppmjTpk29wb/vvvvGoYceWv7/r3zlK7HVVlut68NrMIMwn/72t79F69at4/rrr2/pQ5GalUEoxaoHYcTH3+CzLItzzz234vQ77rgjhgwZEu3bt4/a2tr4yle+Eo899ljFeUrfaH/729/GoYceGhtssEF06dIlxowZE0uWLIkXX3wx9tprr6itrY0+ffrExIkTK17/gw8+iO985zsxcODA8usOGTIkJk+eXO84Vx4SpRvk//M//xNnnXVW9OzZM+rq6mL48OHx4osvVrzu7NmzY7/99otu3bpF27Zto2fPnrHvvvvGvHnzVvlvN3Xq1DjggAOiV69eUVNTE/369Yvjjz++3g2LN954I/7f//t/0bt372jbtm1stNFGscsuu1Tc89qUY/jggw9i3Lhx0bdv32jTpk1ssskmcdJJJ8U777xT79iuv/76GDJkSHTs2DE6duwYAwcOjMsvv7z88oYeMvpf//VfMXTo0OjWrVt06NAhtt5665g4cWJ89NFH5fMMGzaswYczlvrwww/jvPPOiy233LL8sR599NHxxhtvVLyvjz76KM4444zYeOONo3379vGlL30pnnzyySYNwh122CH23XffitO23nrryLIsfv3rX5dPu/XWWyPLsnj22Wcjov5IWNXHsuKN9B//+MfRt2/f6NixYwwZMiQef/zxVR5fxLoZhI899lhkWRZTpkwpv+yll16KLMti2rRpn8lBePfdd0eWZfGDH/ygyW9/dT3++OOxyy67RE1NTfTs2TPGjRsXl156ab1B+Ktf/Sr22GOP6NGjR7Rr1y4GDBgQZ555ZixatKh8nlGjRjX4NVF6O025jDTWhx9+GBtuuGGcccYZFac39WttVcfW0OnDhg2LiE8+F1OnTo2jjz46unTpEh06dIgRI0bUe8jxml4Xljr88MNjr732qjht4cKF0bZt27jhhhsaPdaHHnooIj6+l2nHHXeMLl26RF1dXQwePDguv/zyWL58eb33tbrruIiIadOmxe677x51dXXRvn372GWXXeL++++vOE/p8jR79uw4+OCDo66uLjbYYIM46qij6l1vLVu2LCZOnFi+fuvWrVuMHDmywX+fK664IrbddtuoqamJLl26xEEHHRRz5sypOE9Dg3DmzJmx4YYbxn777VfxtdlQTzzxRIwYMSK6du0aNTU1scUWW8Spp55acZ5HHnkkdt9996itrY327dvHzjvvHP/7v/9bcZ7S18gDDzwQxx13XHTt2jXq6upi5MiRsWjRopg/f3587Wtfi06dOkWPHj3i9NNPb/Brfp999omhQ4eu8pilz1oGoRSrH4SLFi2KVq1axfDhw8unXX/99ZFlWey5554xefLkuPHGG2P77bePtm3bxiOPPFI+X+kb7ZZbbhnnnXdeTJs2LcaOHRtZlsW3vvWtGDBgQFx88cUxbdq0+OY3vxlZlsWtt95afv0FCxbE0UcfHddee208+OCDMWXKlPjXf/3XqK6urveQpMYGYd++feOoo46Ku+++O2644YbYbLPNon///rF06dLyx7fhhhvGDjvsEDfddFPMmDEjbrzxxjjxxBPrffNeuUsuuSR+8IMfxJ133hkzZsyIq6++OgYOHBhbbrllxTfLvfbaK7p16xaXXnppTJ8+PSZPnhzf//7341e/+lWTj2H58uWx1157RevWrePss8+OqVOnxo9+9KPo2LFjDB48uOJhkGeffXZkWRaHHHJI3HzzzTF16tS48MIL4+yzzy6fp6FBOGbMmLjkkktiypQp8eCDD8ZFF10UG220UXzzm98sn+f555+PL33pS9GjR4+KhzNGfHxjae+9946OHTvGueeeG9OmTYvLL788evXqFV/4whfKDxcrvf+qqqo444wzysfXq1ev2GCDDVY7CMeNGxe1tbXlf+O//e1vkWVZtG/fPi644ILy+UaPHh0bb7xx+f9XHoSr+lhKN1z79u0be++9d0yePDkmT54c22yzTXTp0iUWLFiwymMsff3deOONsWTJknpOOumkTz0I33zzzRg6dGgcdthh5ZedeeaZ0bdv31i+fPlnchD+13/9V73L+ere/qp6/vnno0OHDvGFL3whbrjhhrjjjjtir732is0226zeIDzvvPPioosuirvvvjumT58ev/jFL2LzzTePL3/5y+Xz/P73v49DDz00siyr+JooXb6achlprIcffjiyLIt77rmn4vSmfq2t6tgef/zxaN++fey7777l059//vmI+ORzsemmm8YxxxwT9957b1x66aXRvXv32HTTTcs/UGrqdWHp66804kp98MEHUVtbG5deemnF6dddd13U1NTEP/7xj/KxDh48OLbYYovysS5cuDAiIo4++ui44oorYtq0aTFt2rQ477zzon379vV+INmU67hrr702qqqq4qCDDorbbrst7rrrrhgxYkS0atWqYhSWPp4+ffrEGWecEffdd19ceOGF5evWFa/Ljz/++PL3rylTpsQvfvGL6NatW2y66aYVPwicMGFCZFkWRx55ZNx9991xzTXXxBZbbBGdOnWq+B3alQfhjTfeGDU1NTF69Ojy96jGmjJlSrRp0ya23XbbuOqqq+LBBx+MK6+8Mo444ojyeaZPnx5t2rSJ7bffPm688caYPHly7LnnnlFVVVX+/hPxydfI5ptvHqeffnpMnTo1Jk6cGK1atYojjzwytttuuzj//PNj2rRpceaZZ0aWZfHjH/+43jFNnDgxqqurG/whpfRZzSCUYvWDMCJi4403Lj+8Z9myZbHJJpvENttsU/Gwp3fffTe6d+8eu+yyS/m00jfalb9xDBo0KLIsKz/BQETEkiVLolu3bnHIIYc0ehxLly6NJUuWxLHHHhuDBw+ueFljg3Dle5JK93iWbvj/5je/iSzLGrzXsTktX748lixZEnPnzo0sy+KOO+4ov6y2tjZOO+20Rl+3KccwZcqUyLIs/vM//7Pi9BtvvDGyLCvfCHv11VejVatWcdRRR63yeFf3pDLLli2LJUuWxDXXXBOtWrWqeAhYYw+zvOGGGxq8sT9r1qzIsix+/vOfR0TECy+8EFmWxZgxYyrOV/pBw+oG4f333x9ZlsXDDz8cER/f4Kyrq4uTTjqp4sZ9//794+tf/3r5/xt6GOHqHjK6zTbbVNww+/Wvfx1ZlsUNN9ywymMsff2tTmM1dRBOmjQpampq4q233oqlS5dGz54945xzzomI+g8hbIlB+MQTT8SSJUvi3XffjSlTpkSPHj3in//5n2PJkiVNfvur6vDDD4/27dtXPKR96dKlMWDAgFU+ZLR0eZ0xY0b5UQylmvqQ0VVdRhpq4sSJkWVZvYffN+drbU0eMlr6XBx88MEVpz/66KORZVmcf/75EdH068Jzzz03WrVqFdOnT684ffLkydGqVat696oddNBBsf/++1ec1pTPd+nf99///d9jww03LN9L2JTruPfeey+6du1a7/0uW7YsBg4cGDvuuGP5tNLlqbHro+uuuy4iPrneOumkkyrO9+STT0aWZXHWWWdFRMQ777xTHucr9tprr0VNTU3FddKKg/A//uM/olWrVvUeKdNY/fr1i379+sUHH3zQ6HmGDBkS3bt3j3fffbd82tKlS2PrrbeO3r17l/9NS18j3/72tyte/6CDDoosy+LCCy+sOH3QoEGx3Xbb1Xt/06ZNiyzL4t57723SxyB9FjIIpWjaIOzevXt5EM6ZM6fBYRLx8T0y1dXV8d5770XEJ99oV37CgCOPPDKqqqrqfSPbeeedY/vtt6847aabbopddtklOnbsWHFDul27dhXna2wQ/uIXv6g434svvhhZlpV/OrpgwYLo0qVLbLnllnHJJZeUf6relF5//fU44YQTonfv3lFdXV1xfP/xH/9RPt/uu+8enTt3jvPOOy8ef/zxeg+1acoxlO5ZXfnGVumeoMMPPzwiIn75y1+WH064qhoahLNnz479998/unbtWm+4PPHEE+XzNTaijjrqqOjcuXN89NFH9e4R69GjR/merJ///OeRZVn85je/qXj9JUuWROvWrVc7CD/44INo165dfP/73y9/LPvvv3/ceeedUVNTE++991689tprkWWVv6u2JoNw3LhxFacvXry43ue3oUpffxMnToxZs2bVc9hhh+UyCEvPKHrxxRfHnXfeGVVVVfGnP/0pIj4bg3BlW221VYP3HqzpIOzevXuMGDGi3umlf6MVP9d/+MMf4sgjj4yNN944qqqqKo5rxXtLVjW6mnoZaahTTz01qqqq6t3z05yvtU8zCG+55ZZ6L+vTp0/50R+f5rowImLkyJGx2267VZxWepKZq666quL0xj7fDzzwQAwfPjw22GCDev++pSHdlOu40jC55ZZb6l0XnXnmmVFVVVV+OGbpa6Wx66Njjz02Ij653lrxYemlttpqq9hpp50iIuKee+6JLMvipptuqne+ffbZp+JRC6NGjYoOHTrE8ccfHzU1NXHjjTc2+jGtWOmh4RMmTGj0PIsWLYqqqqp6Azbikx9OvPDCCxHxydfIfffdV3G+f/u3f2v0e/iGG25Y7+3+9re/jSzL6j10V/osZxBK0fyHjD7yyCORZVlce+219c573nnnRZZl8ec//zkiGv9l/cZ+kX7lGwml3wH72te+Frfffns8/vjjMWvWrDjmmGPq3ShqbBCu/DtcpRtfkyZNKp/27LPPxuGHHx5dunSJLMuiZ8+e8f3vf3+VvxdU+klzt27d4uKLL46HHnoofv3rX8cTTzxR78bxm2++Gaeeemr06dMnsiyL2traGDlyZMyfP7/Jx3DsscdG69atGzyWfv36xVe+8pWIiDj//PMjy7J47bXXGj32iPqDcO7cudGxY8fYbrvt4tprr41HHnkkZs2aFf/93/8dWVb58LDGRtRXvvKVVd4btvvuu0dE/a+TFdt4442b9MQYw4cPL98b3bt377j44ovjH//4R7Ru3TqmTJkSV1xxRb1/hzUZhA2Np5U/vw21Ln6HsHS5Ou6442LQoEFx4IEHxh577FE+X56DcPjw4TFgwIAGXzZ//vyKe5oiPvm3vuaaa2LWrFnx4IMPxgknnBBZlsXee+9d722s6SBs1apVHHfccfVOv+SSSyo+1++++25ssskmscUWW8Rll10WM2bMiFmzZsVtt91W7/qgsc9Ncy4jDXX88cdH27Zt653enK+1TzMIZ86cWe9lO+20UwwaNKj8/2tyXRjx8e8Ed+7cOX72s59VnH7jjTdG69atm/Sssk8++WT5e82NN94Yjz76aMyaNSu++93vVnwum3Idd911163yumjF1y9dnhq7PjrooIMi4pPrrYZ+X3D48OHxuc99LiI+fqhqlmUVvz5RauXr8VGjRkXr1q2jU6dOsd1228U//vGPRj+mFZs5c2b58tVY8+bNiyzL4rzzzqv3stIxlr4mGrsd0Nzv4aWhuvLXgfRZziCUYvWDsPSQxNI3lTW5h3BNB+HBBx8cm2++eb0nFDjqqKNyHYSlli9fHs8880ycdtppkWX1n/hixUo/CV35J9+vvPLKKgfD3Llz42c/+1l07Nix3pMvrOoYVncPYen3Rtb0HsKf/vSnkWVZ+d6lUpdddlmTB+ERRxwRG264YYP3iM2aNav8ZD6f9h7CiI9/R6d169blh2uVftK98847x+mnnx5HHnlkfP7zn694nfV1EJaeXKa6urri4YV5DsKvf/3rscEGGzT45B6lhxpedtll5dMau1457rjjGvx3Wdv3EN5xxx2RZVm9hzmW7klqyiBszmWkoc4666zIsqzeE4Wsq0G4unsIV6w514URH98rVlVVVW9UHXbYYbHnnnvWO39Dn+8xY8ZEu3bt6j1yZOVB2JTruNJD7H/2s581en1U+vMIed1DOGTIkPK/xaruIezRo0f5/0vfC2fPnh3dunWLHXfccbUPPY5o+j2E1dXVq7yHsHSdnNcgLP3JnNU9pF76LGUQStG0PzvRqVOn8hBZtmxZ9OrVKwYNGlRx43DRokXRvXv3+NKXvlQ+7dMOwkMOOSS23HLLivPMnz8/amtr18ogXLHOnTvH1772tUZf/uyzzzb4je9f//VfmzQYDjrooOjWrVuTj+G+++6LLKv/uxw333xzxY3xP/7xj9GqVasYOXLkKt/2yoPw4osvjizLKu61XL58eey44471buwecsgh0b1793pvs/RT+dU9dK70Q4U1/R3CiE9+v2rPPfeM3r17l08/++yzY+DAgbHxxhvXuyHU0CBs7GMp0iCMiDjmmGPiq1/9asWTC+U5CK+88srIsvpPiBLx8Q8rqqurK56xsrHrlbfffju6dOlS8YfZI9b+7xDeeeedkWVZvWeILT1Jy4rXB9/5znciy7KKJ0GKaN5lpKGuueaayLLK31eMaN7XWmPHFhHRtWvXiicYKrW63yFs6B6kFVvddWHEx/d87bzzzhWnlZ5k5pe//GW98zf0+f7Od75T8WRRERHvv/9+vScIasp13LvvvhudO3eO0aNHr/K4I1b/O4SlR8OUft3glFNOqThf6brou9/9bkR88juEBxxwQMX55s2bFzU1NRW/+7ji98I5c+bEJptsEttuu228/vrrqz3ufv36xec+97mKy/zK7bzzztGjR4+Kr5dly5bFNtts0+DvEH7aQVj6N1v5a1z6LGcQSlH/D0g/8sgjceutt1b8YfoHH3yw4nVKV/r77rtv3HHHHXHTTTfFF7/4xUafZXRNB2HpRujo0aPjgQceiKuuuir69esX/fv3z20Q3nXXXbHPPvvEL3/5y5g2bVpMnTo1TjzxxMiyrN6z5a3YRx99FP369Ys+ffrE//zP/8SUKVPi5JNPjs9//vMVN+IWLFgQgwcPjh/+8Idx1113xfTp0+OHP/xhtGvXrvzkAk05htKzjLZp0ybOOeecmDZtWvz4xz+O2traRp9l9NBDD41bb7017r///rj44ovLv3NX+hysOAhfeOGFaNu2bey2225xzz33xG233RZ77LFH+d96xRu7pc/rz3/+83jyySfLNyKWLl0a++yzT3Tt2jXOPffcuPfee+P++++Pq666KkaNGlXxJELf+MY3oqqqKsaOHVt+hsBNNtmkSc8yGvHxjZrSw9pWfIbH0pOEZFnlkxZFNDwIG/tYijYIG6qxQfgv//IvcfPNN9ez8j1fK/bhhx/GDjvsELW1tXH++efHfffdF5MnT47jjz8+qqqq6t1IXtUPmv7zP/+z4oZ2xMeX/U033bTB41r5Xr0V+93vfhft27ePL3zhC/GrX/0q7rzzzthrr71i0003rfhc//3vf48uXbrEwIEDy884ecQRR5S/vlcchKVjHz9+fDzxxBPle5OacxlpqNLvta48kJrztdbYsZX+Dbt37x533nlnxT3yKz7L6LHHHhtTpkyJyy67LLp37x69evWKt956KyKafl248pPKLF26NDbaaKP40Y9+VHHst99+e1RXVzc4bhoahA888ED5emvq1Klxww03xPbbb1/+913xctuU67hrr702qqur4/DDD4+bb745ZsyYEbfcckucffbZceKJJ5bPt/KzjE6dOjUuuuiiqK2tjYEDB1b8ofXS1/tpp50W9913X/zyl78sP1vr3//+9/L5Ss8yOnLkyLjnnnvi2muvjc997nOrfZbRP/zhD7H55pvHlltuWfHQ1GOOOSZatWpVcRktPcvooEGD4uqrr46HHnoorr766oonrSk9y+hOO+0UN998c/lZeBt7ltFPOwi//e1vVzwBkFSEDEIp6j/5Q9u2baN79+4xbNiwmDBhQr2HKJaaPHly7LTTTtGuXbvo2LFjDB8+PB599NGK83zaQRjx8TOv9e3bN2pqamKrrbaKyy67rPx2V2xNB+GLL74YRx55ZPTr1y/at28fnTp1ih133LHeQ0Ebas6cObHHHntEXV1ddOnSJb72ta+Vb/SVbsQtXrw4TjzxxNh2221jgw02iPbt28eWW24Z48ePLz+0tqnH8MEHH8SZZ54Zffr0iTZt2kTPnj1j9OjRDT5JxzXXXBNf/OIXo127duXRuOKN3oaeVOauu+6KgQMHRrt27aJXr15xxhlnxL333lvvxu7bb78dhx56aHTu3Ln85ByllixZEj/60Y/Kb6e2tjYGDBgQJ5xwQrzyyivl83344Ydx+umnR/fu3aNdu3blv7nWnD9Mf/DBB0eWZRV/CPmjjz6Kjh07NvjU5w0NwsY+lvV5EDZmdfea/+Mf/4ixY8dG//79o23bttGhQ4fYYYcd4he/+EW9G4CrGoQffPBBvT//0tjfhMyyT/6eXmM9+uijMWTIkKipqYkePXrEGWec0eDfIXzsscdi5513jg4dOkS3bt3iuOOOi9mzZ9f72D/88MM47rjjolu3buWvidLbaeplpLGGDh1a79knm/O1tqpje+aZZ+JLX/pSdOjQoeLfbcW/Qzhy5Mjo3Llz+VkwV7xMNvV6aOU/O1F61t9XX3214nzf+MY3Gv3cNXaP8JVXXhlbbrll+W/q/eAHPyj/PvDKzxi7uuu4iI9/QLTffvtF165do02bNtGrV6/Yb7/9Ki6XpY/nqaeeiv333z9qa2ujrq4ujjzyyHpjtvR3CD//+c9HmzZtYqONNopvfOMbDf5e4eWXXx7bbrtttG3bNjp16hQHHnhgvSfqaeh74Z///OcYMGBA9O3bt3yv+6j/+xuUK/8bPP7447HPPvtEp06dyn8Ld+V7Okt/h7Bjx47Rvn37GDJkSNx1110V58ljEC5fvjz69OlT75lKpc96BqEkSVpn3XLLLdGqVasGn8BkbdWUZ5L+NEGDGQcAACAASURBVI0ePbrenyD48MMPo1OnTnHxxRevlfepz173339/VFdXl3+fWypKBqEkSVpnLV++PIYMGdLovb9ro7U9CKWIiN12263BZ/yVPusZhJIkaZ32u9/9Li644IKKJ9VZmxmEWtu9/fbbMX78+CY9GY70WcsglCRJkqREMwglSZIkKdEMQkmSJElKNINQkiRJkhLNICx4y5Yti3nz5sWCBQti4cKFAABQeAsWLIh58+atsyefSjmDsODNmzdvlX9kGQAAimrevHktfXN7vc8gLHgLFiwoX1ha+ic5AACQh9KdHgsWLGjpm9vrfQZhwVu4cGFkWRYLFy5s6UORJEmScslt3HWXQVjwXFgkSZK0vuU27rrLICx4LiySJEla33Ibd91lEBY8FxZJkiStb7mNu+4yCAueC4skSZLWt9zGXXcZhAXPhUWSJEnrW27jrrsMwoLnwiJJkqT1Lbdx110GYcFzYZEkSdL6ltu46y6DsOC5sEiSJGl9y23cdZdBWPBcWCRJkrS+5TbuussgLHguLJIkSVrfcht33WUQFjwXFkmSJK1vuY277jIIC54LiyRJkta33MZddxmEBc+FRZIkSetbbuOuuwzCgufCIkmSpPUtt3HXXQZhwXNhkSRJ0vqW27jrLoOw4LmwSJIkaX3Lbdx1l0FY8FxYJEmStL7lNu66yyAseC4skiRJWt9yG3fdZRAWPBcWSZIkrW+5jbvuMggLnguLJEmS1rfcxl13GYQFz4VFkiRJ61tu4667DMKC58IiSZKk9S23cdddBmFO/fznP49tttkm6urqoq6uLoYMGRL33HPPKl9n+vTpsd1220VNTU1svvnmcckllzT7/bqwSJIkaX3Lbdx1l0GYU3feeWfcfffd8dJLL8VLL70UZ511VrRp0yaee+65Bs//6quvRocOHeLUU0+NOXPmxGWXXRZt2rSJW265pVnv14VFkiRJ61tu4667DMK1WJcuXeLyyy9v8GVjx46NAQMGVJx2wgknxJAh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++67m/0xGIQAAOTOIEympAdh9+7dY9KkSS19GJ8qgxAAgNwZhMmU9CDs0aNHvPzyyy19GJ8qgxAAgNwZhMmU9CCcOHFinHrqqS19GJ8qgxAAgNwZhMmU9CBctmxZ7L333rHFFlvEiBEj4uCDD65QhAxCAAByZxAmU9KD8KSTToqamprYe++9Y9SoUXH00UdXKEIGIQAAuTMIkynpQVhbWxv/+7//29KH8akyCAEAyJ1BmExJD8LNNtssXnjhhZY+jE+VQQgAQO4MwmRKehBeeeWVcdhhh8V7773X0oeyxhmEAADkziBMpqQH4aBBg6Kuri5qa2tj6623jsGDB1coQgYhAAC5MwiTKelBeM4556xSETIIAQDInUGYTEkPwvUhgxAAgNwZhMlkEEbEb37zm7j22mvjuuuui9mzZ7f04TQrgxAAgNwZhMmU9CB8/fXX48tf/nJUVVVFly5donPnzlFVVRW77757vPHGGy19eE3KIAQAIHcGYTIlPQgPO+yw2H777WPOnDnl055//vnYYYcd4ogjjmjBI2t6BiEAALkzCJMp6UG4wQYbxK9//et6pz/55JPRqVOnFjii5mcQAgCQO4MwmZIehLW1tfH000/XO3327NlRV1fXAkfU/AxCAAByZxAmU9KD8IADDoh//ud/jr/85S/l0/785z/HsGHD4qCDDmrBI2t6BiEAALkzCJMp6UH42muvxeDBg6NNmzaxxRZbRL9+/aJNmzax3Xbbxbx581r68JqUQQgAQO4MwmRKehCWmjp1alx88cXx05/+NKZNm9bSh9OsDEIAAHJnECaTQVjwDEIAAHJnECZTsoNw2bJlccUVV8R+++0X//RP/xRbb7117L///nH11VfH8uXLW/rwmpxBCABA7gzCZEpyEC5fvjz222+/qKqqikGDBsURRxwRhx9+eGy77bZRVVUVBx54YEsfYpMzCAEAyJ1BmExJDsIrr7wy6urq4sEHH6z3sgceeCDq6uri6quvboEja34GIQAAuTMIkynJQbjHHnvED37wg0ZffsEFF8See+65Do9ozTMIAQDInUGYTEkOwo033rjBP0hfavbs2bHxxhuvwyNa8wxCAAByZxAmU5KDsE2bNvHXv/610Zf/5S9/ibZt267DI1rzDEIAAHJnECZTkoOwuro63njjjUZf/re//S2qq6vX4RGteQYhAAC5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\" width=\"900\">"
|
|
],
|
|
"text/plain": [
|
|
"<IPython.core.display.HTML object>"
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"output_type": "display_data"
|
|
}
|
|
],
|
|
"source": [
|
|
"#!/bin/env python\n",
|
|
"\n",
|
|
"\"\"\"\n",
|
|
"URL data extractor\n",
|
|
"\n",
|
|
"Pekka Helenius <pekka [dot] helenius [at] fjordtek [dot] com>\n",
|
|
"\n",
|
|
"Requirements:\n",
|
|
"\n",
|
|
"Python 3\n",
|
|
"Python 3 BeautifulSoup4 (python-beautifulsoup4)\n",
|
|
"Python 3 whois (python-whois; PyPI)\n",
|
|
"Python 3 JSON Schema (python-jsonschema)\n",
|
|
"Python 3 Numpy (python-numpy)\n",
|
|
"Python 3 matplotlib (python-matplotlib)\n",
|
|
"\n",
|
|
"TODO: URL domain part length comparison analysis\n",
|
|
"TODO: URL non-TLD part length comparison analysis\n",
|
|
" - in phishing webpages, URL tends to be much longer than legitimate webpages\n",
|
|
" however, domains themselves tend to be much shorter (without TLD)\n",
|
|
" - phishing URLs often contain more number of dots and subdomains than legitimate URLs\n",
|
|
" - legitimate: robots.txt redirects bots to a legitimate domain rather than to the original phishing domain\n",
|
|
"\n",
|
|
"TODO: Website visual similarity analysis\n",
|
|
"TODO: consistency of RDN usage in HTML data\n",
|
|
"\"\"\"\n",
|
|
"\n",
|
|
"######################################\n",
|
|
"\n",
|
|
"%matplotlib notebook\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"\n",
|
|
"from bs4 import BeautifulSoup as bs\n",
|
|
"from collections import Counter\n",
|
|
"from datetime import date, datetime\n",
|
|
"import json\n",
|
|
"import os\n",
|
|
"import re\n",
|
|
"import requests\n",
|
|
"from time import sleep\n",
|
|
"import urllib\n",
|
|
"from whois import whois\n",
|
|
"\n",
|
|
"# Target URLs\n",
|
|
"urls = [\n",
|
|
" \"https://hoxhunt.com/\",\n",
|
|
" \"https://hs.fi\",\n",
|
|
" \"https://ts.fi\",\n",
|
|
" \"https://facebook.com\"\n",
|
|
"]\n",
|
|
"\n",
|
|
"# Some web servers may block our request unless we set a widely used, well-known user agent string\n",
|
|
"request_headers = {\n",
|
|
" 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36'\n",
|
|
"}\n",
|
|
"\n",
|
|
"# Date format for domain timestamps\n",
|
|
"dateformat = \"%Y/%m/%d\"\n",
|
|
"\n",
|
|
"# All webpages may not like fetching data too fast\n",
|
|
"# Sleep time in seconds\n",
|
|
"sleep_interval_between_requests = 0.5\n",
|
|
"\n",
|
|
"# Write JSON results to a file?\n",
|
|
"use_file = True\n",
|
|
"# Full file path + name\n",
|
|
"filename = os.getcwd() + \"/\" + \"url_info.json\"\n",
|
|
"\n",
|
|
"# Generate plot from existing JSON data?\n",
|
|
"plot_only = False\n",
|
|
"\n",
|
|
"# Save generated plot images?\n",
|
|
"save_plot_images = True\n",
|
|
"\n",
|
|
"# DPI of plot images\n",
|
|
"plot_images_dpi = 150\n",
|
|
"\n",
|
|
"# Common link attribute references in various HTML elements\n",
|
|
"link_refs = {\n",
|
|
" 'a': 'href',\n",
|
|
" 'img': 'src',\n",
|
|
" 'script': 'src'\n",
|
|
"}\n",
|
|
"\n",
|
|
"############################################################################\n",
|
|
"############################################################################\n",
|
|
"\n",
|
|
"class json_url_data(object):\n",
|
|
"\n",
|
|
"# def __init__(self):\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Set a new HTTP session and get response.\n",
|
|
"\n",
|
|
" Returns a requests.models.Response object.\n",
|
|
" \"\"\"\n",
|
|
" def set_session(self, url, method='get', redirects=True):\n",
|
|
" \n",
|
|
" # HTTP response status codes 1XX, 2XX and 3XX are OK\n",
|
|
" # Treat other codes as errors\n",
|
|
" sc = re.compile(r\"^[123]{1}[0-9]{2}\")\n",
|
|
" \n",
|
|
" sleep(sleep_interval_between_requests)\n",
|
|
" \n",
|
|
" try:\n",
|
|
" session = requests.Session()\n",
|
|
" response = session.request(method, url, headers=request_headers, allow_redirects=redirects)\n",
|
|
" \n",
|
|
" if not sc.match(str(response.status_code)):\n",
|
|
" raise Exception(\"Error: got invalid response status from the web server\")\n",
|
|
" return response\n",
|
|
" \n",
|
|
" except:\n",
|
|
" raise Exception(\"Error: HTTP session could not be established. URL: '\" + url + \"' (method: \" + method + \")\") from None\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Fetch HTML data.\n",
|
|
"\n",
|
|
" Returns a bs4.BeautifulSoup object.\n",
|
|
" \"\"\"\n",
|
|
" def get_html_data(self, url):\n",
|
|
" \n",
|
|
" try:\n",
|
|
" data = bs(self.set_session(url).content, 'html.parser')\n",
|
|
" return data\n",
|
|
" except:\n",
|
|
" raise Exception(\"Error: HTML data could not be retrieved\")\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Get URL redirects and related HTTP status codes.\n",
|
|
"\n",
|
|
" Returns a list object.\n",
|
|
" \"\"\"\n",
|
|
" def get_url_redirects(self, url):\n",
|
|
" \n",
|
|
" response = self.set_session(url)\n",
|
|
" list_data = []\n",
|
|
" \n",
|
|
" if response.history:\n",
|
|
" \n",
|
|
" for r in response.history:\n",
|
|
" list_data.append({'redirect_url': r.url, 'status': r.status_code})\n",
|
|
" \n",
|
|
" return list_data\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Extract title HTML element contents from given HTML data.\n",
|
|
"\n",
|
|
" Returns a string object.\n",
|
|
" \"\"\"\n",
|
|
" def get_webpage_title(self, url):\n",
|
|
" \n",
|
|
" html_data = self.get_html_data(url)\n",
|
|
" \n",
|
|
" title = html_data.title.string\n",
|
|
" return title\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Get WHOIS domain data.\n",
|
|
"\n",
|
|
" Returns a dict object.\n",
|
|
" \"\"\"\n",
|
|
" def get_whois_data(self, url):\n",
|
|
" dict_data = whois(url)\n",
|
|
" return dict_data\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Get domain name based on WHOIS domain data.\n",
|
|
" \"\"\"\n",
|
|
" def get_domain_name(self, url):\n",
|
|
" domain_name = self.get_whois_data(url).domain_name\n",
|
|
" \n",
|
|
" if type(domain_name) is list:\n",
|
|
" return domain_name[0].lower()\n",
|
|
" else:\n",
|
|
" return domain_name.lower()\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Get initial and final URLs\n",
|
|
" \n",
|
|
" Compare whether the final (destination) URL\n",
|
|
" matches with the initial URL in a request.\n",
|
|
" \n",
|
|
" Returns a dict object.\n",
|
|
" \"\"\"\n",
|
|
" def get_startfinal_urls(self, url):\n",
|
|
" \n",
|
|
" response = self.set_session(url)\n",
|
|
" end_url = response.url\n",
|
|
" \n",
|
|
" start_match = False\n",
|
|
" final_match = False\n",
|
|
" \n",
|
|
" # dr = re.compile(r\"^([a-z]+://)?([^/]+)\")\n",
|
|
" # dr_group_lastindex = dr.match(url).lastindex\n",
|
|
" # domain_name = dr.match(url).group(dr_group_lastindex)\n",
|
|
" \n",
|
|
" domain_name = self.get_domain_name(url)\n",
|
|
" \n",
|
|
" if re.search(domain_name, end_url):\n",
|
|
" final_match = True\n",
|
|
" \n",
|
|
" dict_data = {\n",
|
|
" 'startfinal_urls': {\n",
|
|
" 'start_url': {\n",
|
|
" 'url': url\n",
|
|
" },\n",
|
|
" 'final_url': {\n",
|
|
" 'url': end_url, 'domain_match': final_match\n",
|
|
" }\n",
|
|
" }\n",
|
|
" }\n",
|
|
" \n",
|
|
" return dict_data\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Get domain registrar\n",
|
|
" \n",
|
|
" Returns a dict object.\n",
|
|
" \"\"\"\n",
|
|
" def get_domain_registrar(self, url):\n",
|
|
" dict_data = {'domain_registrar': self.get_whois_data(url).registrar }\n",
|
|
" return dict_data\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Do comparison between the domain name, extracted\n",
|
|
" from WHOIS domain data and contents of a title HTML\n",
|
|
" element, extracted from HTML data based on a given URL.\n",
|
|
" \n",
|
|
" Returns a dict object.\n",
|
|
" \"\"\"\n",
|
|
" def get_domain_title_match(self, url):\n",
|
|
" \n",
|
|
" domain_name = self.get_domain_name(url)\n",
|
|
" title = self.get_webpage_title(url)\n",
|
|
" \n",
|
|
" # If is string:\n",
|
|
" if type(domain_name) is str:\n",
|
|
" if re.search(domain_name, title, re.IGNORECASE):\n",
|
|
" match = True\n",
|
|
" else:\n",
|
|
" match = False\n",
|
|
" \n",
|
|
" # If is list:\n",
|
|
" elif type(domain_name) is list:\n",
|
|
" for d in domain_name:\n",
|
|
" if re.search(d, title, re.IGNORECASE):\n",
|
|
" match = True\n",
|
|
" break\n",
|
|
" else:\n",
|
|
" match = False\n",
|
|
" else:\n",
|
|
" match = False\n",
|
|
" \n",
|
|
" dict_data = {\n",
|
|
" 'webpage_title': title,\n",
|
|
" 'domain_in_webpage_title': match\n",
|
|
" }\n",
|
|
" \n",
|
|
" return dict_data\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Get a single timestamp from given data\n",
|
|
" \n",
|
|
" Two scenarios are considered: dates argument is either\n",
|
|
" a list or a string. If it is a list, then we need\n",
|
|
" to decide which date value to extract.\n",
|
|
" \n",
|
|
" Returns a date object.\n",
|
|
" \"\"\"\n",
|
|
" def get_single_date(self, dates, newest=False):\n",
|
|
" \n",
|
|
" dates_epoch = []\n",
|
|
" \n",
|
|
" if type(dates) is list:\n",
|
|
" for d in dates:\n",
|
|
" dates_epoch.append(d.timestamp())\n",
|
|
" else:\n",
|
|
" dates_epoch.append(dates.timestamp())\n",
|
|
" \n",
|
|
" return datetime.fromtimestamp(sorted(dates_epoch, reverse=newest)[0])\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Get domain time information based on WHOIS domain data.\n",
|
|
" \n",
|
|
" Returns a dict object.\n",
|
|
" \"\"\"\n",
|
|
" def get_domain_timeinfo(self, url):\n",
|
|
" \n",
|
|
" whois_data = self.get_whois_data(url)\n",
|
|
" domain_creation_date = self.get_single_date(whois_data.creation_date, newest = False)\n",
|
|
" domain_updated_date = self.get_single_date(whois_data.updated_date, newest = False)\n",
|
|
" domain_expiration_date = self.get_single_date(whois_data.expiration_date, newest = False)\n",
|
|
" \n",
|
|
" dict_data = {\n",
|
|
" 'domain_timestamps':\n",
|
|
" {\n",
|
|
" 'created': domain_creation_date.strftime(dateformat),\n",
|
|
" 'updated': domain_updated_date.strftime(dateformat),\n",
|
|
" 'expires': domain_expiration_date.strftime(dateformat)\n",
|
|
" }\n",
|
|
" }\n",
|
|
" \n",
|
|
" return dict_data\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Get domain time information based on WHOIS domain data,\n",
|
|
" relative to the current date (UTC time).\n",
|
|
" \n",
|
|
" Returns a dict object.\n",
|
|
" \"\"\"\n",
|
|
" def get_domain_timeinfo_relative(self, url):\n",
|
|
" \n",
|
|
" date_now = datetime.utcnow()\n",
|
|
" \n",
|
|
" whois_data = self.get_whois_data(url)\n",
|
|
" domain_creation_date = self.get_single_date(whois_data.creation_date, newest = False)\n",
|
|
" domain_updated_date = self.get_single_date(whois_data.updated_date, newest = False)\n",
|
|
" domain_expiration_date = self.get_single_date(whois_data.expiration_date, newest = False)\n",
|
|
" \n",
|
|
" dict_data = {\n",
|
|
" 'domain_timestamps_relative':\n",
|
|
" {\n",
|
|
" 'current_date': (date_now.strftime(dateformat)),\n",
|
|
" 'created_days_ago': (date_now - domain_creation_date).days,\n",
|
|
" 'updated_days_ago': (date_now - domain_updated_date).days,\n",
|
|
" 'expires_days_left': (domain_expiration_date - date_now).days\n",
|
|
" }\n",
|
|
" }\n",
|
|
" \n",
|
|
" return dict_data\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Determine whether URL matches syntaxes such as\n",
|
|
" '../foo/bar/'\n",
|
|
" '/foo/../../bar/,\n",
|
|
" 'https://foo.bar/foo/../'\n",
|
|
" \n",
|
|
" etc.\n",
|
|
" \n",
|
|
" Returns a boolean object.\n",
|
|
" \"\"\"\n",
|
|
" def is_multidot_url(self, url):\n",
|
|
" \n",
|
|
" multidot = re.compile(r\".*[.]{2}/.*\")\n",
|
|
" \n",
|
|
" if multidot.match(url):\n",
|
|
" return True\n",
|
|
" return False\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Get HTML element data from HTML data contents.\n",
|
|
" \n",
|
|
" Two fetching methods are supported:\n",
|
|
" - A) use only HTML element/tag name and extract raw contents of\n",
|
|
" these tags\n",
|
|
" - B) use both HTML element/tag name and more fine-grained\n",
|
|
" inner attribute name to determine which HTML elements are extracted\n",
|
|
" \n",
|
|
" Special case - URL link references:\n",
|
|
" - attributes 'href' or 'src' are considered as link referrals and \n",
|
|
" they are handled in a special way\n",
|
|
" - A) link referrals to directly to domain are placed in 'self_refs' list\n",
|
|
" (patterns: '/', '#', '../' and '/<anything>')\n",
|
|
" - B) link referrals to external domains are placed in 'ext_refs' list\n",
|
|
" (patterns such as 'https://foo.bar.dot/fancysite' etc.)\n",
|
|
" \n",
|
|
" - Both A) and B) link categories have 'normal' and 'multidot' subcategories\n",
|
|
" - normal links do not contain pattern '../'\n",
|
|
" - multidot links contain '../' pattern\n",
|
|
" \n",
|
|
" Returns a dict object.\n",
|
|
" \"\"\"\n",
|
|
" \n",
|
|
" def get_tag_data(self, url, tag, attribute=None):\n",
|
|
" \n",
|
|
" html_data = self.get_html_data(url)\n",
|
|
" domain_name = self.get_domain_name(url)\n",
|
|
" data = []\n",
|
|
" \n",
|
|
" if attribute != None:\n",
|
|
" \n",
|
|
" for d in html_data.find_all(tag):\n",
|
|
" \n",
|
|
" # Ignore the HTML tag if it does not contain our attribute\n",
|
|
" if d.get(attribute) != None:\n",
|
|
" data.append(d.get(attribute))\n",
|
|
" \n",
|
|
" if attribute == 'href' or attribute == 'src':\n",
|
|
" \n",
|
|
" self_refs = { 'normal': [], 'multidot': []}\n",
|
|
" ext_refs = { 'normal': [], 'multidot': []}\n",
|
|
" \n",
|
|
" # Syntax: '#<anything>', '/<anything>', '../<anything>'\n",
|
|
" rs = re.compile(r\"^[/#]|^[.]{2}/.*\")\n",
|
|
" \n",
|
|
" # Syntax: '<text>:<text>/'\n",
|
|
" rd = re.compile(r\"^[a-z]+:[a-z]+/\")\n",
|
|
" \n",
|
|
" # Syntax examples:\n",
|
|
" # 'http://foo.bar/', 'https://foo.bar/, 'foo.bar/', 'https://virus.foo.bar/'\n",
|
|
" rl = re.compile(r\"^([a-z]+://)?([^/]*\" + domain_name + \"/)\")\n",
|
|
" \n",
|
|
" for s in data:\n",
|
|
" \n",
|
|
" # Ignore mailto links\n",
|
|
" if re.match(\"^mailto:\", s): continue\n",
|
|
" \n",
|
|
" if rs.match(s) or rl.match(s) or rd.match(s):\n",
|
|
" if self.is_multidot_url(s):\n",
|
|
" self_refs['multidot'].append(s)\n",
|
|
" else:\n",
|
|
" self_refs['normal'].append(s)\n",
|
|
" else:\n",
|
|
" \n",
|
|
" if self.is_multidot_url(s):\n",
|
|
" try:\n",
|
|
" ext_refs['multidot'].append({'url': s, 'registrar': self.get_whois_data(s).registrar })\n",
|
|
" except:\n",
|
|
" # Fallback if WHOIS query fails\n",
|
|
" ext_refs['normal'].append({'url': s, 'registrar': None })\n",
|
|
" pass\n",
|
|
" else:\n",
|
|
" try:\n",
|
|
" ext_refs['normal'].append({'url': s, 'registrar': self.get_whois_data(s).registrar })\n",
|
|
" except:\n",
|
|
" ext_refs['normal'].append({'url': s, 'registrar': None })\n",
|
|
" pass\n",
|
|
" \n",
|
|
" data = None\n",
|
|
" \n",
|
|
" dict_data = {\n",
|
|
" tag: {\n",
|
|
" attribute + '_ext': (ext_refs),\n",
|
|
" attribute + '_self': (self_refs)\n",
|
|
" }\n",
|
|
" }\n",
|
|
" \n",
|
|
" else:\n",
|
|
" dict_data = {\n",
|
|
" tag: {\n",
|
|
" attribute: (data)\n",
|
|
" }\n",
|
|
" }\n",
|
|
" \n",
|
|
" else:\n",
|
|
" for d in html_data.find_all(tag):\n",
|
|
" data.append(d.prettify())\n",
|
|
" \n",
|
|
" dict_data = {\n",
|
|
" tag: (data)\n",
|
|
" }\n",
|
|
" \n",
|
|
" return dict_data\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" How many external URL links have same registrar than\n",
|
|
" the webpage itself?\n",
|
|
" \"\"\"\n",
|
|
" def get_registrar_count(self, registrar, urls):\n",
|
|
" \n",
|
|
" i = 0\n",
|
|
" \n",
|
|
" for u in urls:\n",
|
|
" for k,v in u.items():\n",
|
|
" if k == 'registrar' and v == registrar:\n",
|
|
" i += 1\n",
|
|
" \n",
|
|
" o = len(urls) - i\n",
|
|
" \n",
|
|
" dict_data = {\n",
|
|
" 'same_registrar_count': i,\n",
|
|
" 'other_registrar_count': o\n",
|
|
" }\n",
|
|
" \n",
|
|
" return dict_data\n",
|
|
"\n",
|
|
"######################################\n",
|
|
"\n",
|
|
" \"\"\"\n",
|
|
" Get values existing in a dict object,\n",
|
|
" based on a known key string.\n",
|
|
" \n",
|
|
" Returns a list object.\n",
|
|
" \n",
|
|
" TODO: Major re-work for the fetch function\n",
|
|
"\n",
|
|
" TODO: Support for more sophisticated JSON key string filtering\n",
|
|
" (possibility to use multiple keys for filtering)\n",
|
|
" \"\"\"\n",
|
|
" class json_fetcher(object):\n",
|
|
"\n",
|
|
" def __init__(self, dict_data, json_key):\n",
|
|
" self.json_dict = json.loads(json.dumps(dict_data))\n",
|
|
" self.json_key = json_key\n",
|
|
"\n",
|
|
" ##########\n",
|
|
" # Ref: https://www.codespeedy.com/how-to-loop-through-json-with-subkeys-in-python/\n",
|
|
" def fetch(self, jdata):\n",
|
|
"\n",
|
|
" if isinstance(jdata, dict):\n",
|
|
"\n",
|
|
" for k,v in jdata.items():\n",
|
|
" if k == self.json_key:\n",
|
|
" yield v\n",
|
|
" elif isinstance(v, dict):\n",
|
|
" for val in self.fetch(v):\n",
|
|
" yield val\n",
|
|
" elif isinstance(v, list):\n",
|
|
" for l in v:\n",
|
|
" if isinstance(l, dict):\n",
|
|
" for ka,va in l.items():\n",
|
|
" if ka == self.json_key:\n",
|
|
" yield va\n",
|
|
"\n",
|
|
" elif isinstance(jdata, list):\n",
|
|
" for l in jdata:\n",
|
|
" if isinstance(l, dict):\n",
|
|
" for k,v in l.items():\n",
|
|
" if k == self.json_key:\n",
|
|
" yield v\n",
|
|
" elif isinstance(l, list):\n",
|
|
" for lb in v:\n",
|
|
" for ka,va in lb.items():\n",
|
|
" if ka == self.json_key:\n",
|
|
" yield va\n",
|
|
"\n",
|
|
" ##########\n",
|
|
" def get_data(self, flatten=True):\n",
|
|
"\n",
|
|
" data_extract = []\n",
|
|
" flat_data = []\n",
|
|
"\n",
|
|
" for i in self.fetch(self.json_dict):\n",
|
|
" data_extract.append(i)\n",
|
|
"\n",
|
|
" # Flatten possible nested lists\n",
|
|
" # (i.e. JSON data contains multiple keys in\n",
|
|
" # different nested sections)\n",
|
|
" def get_data_extract(ld):\n",
|
|
" for l in ld:\n",
|
|
" if isinstance(l, list):\n",
|
|
" for la in get_data_extract(l):\n",
|
|
" yield la\n",
|
|
" else:\n",
|
|
" yield l\n",
|
|
"\n",
|
|
" if flatten == True:\n",
|
|
" for u in get_data_extract(data_extract):\n",
|
|
" flat_data.append(u)\n",
|
|
" \n",
|
|
" return flat_data\n",
|
|
" else:\n",
|
|
" return data_extract\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Compile URL related data.\n",
|
|
" \"\"\"\n",
|
|
" def get_url_data(self, url):\n",
|
|
" \n",
|
|
" # Dict object for simple, non-nested data\n",
|
|
" data_simple = {}\n",
|
|
"\n",
|
|
" # Pre-defined dict object for specific data sets\n",
|
|
" webpage_data = {}\n",
|
|
" \n",
|
|
" startfinal_url = self.get_startfinal_urls(url)\n",
|
|
" redirect_url = self.get_url_redirects(url)\n",
|
|
" domain_registrar = self.get_domain_registrar(url)\n",
|
|
" domaintitle_match = self.get_domain_title_match(url)\n",
|
|
" \n",
|
|
" domain_time_relative = self.get_domain_timeinfo_relative(url)\n",
|
|
" domain_time = self.get_domain_timeinfo(url)\n",
|
|
" \n",
|
|
" html_element_iframe = self.get_tag_data(url, 'iframe')\n",
|
|
" html_element_a_href = self.get_tag_data(url, 'a', link_refs['a'])\n",
|
|
" html_element_img_src = self.get_tag_data(url, 'img', link_refs['img'])\n",
|
|
" html_element_script_src = self.get_tag_data(url, 'script', link_refs['script'])\n",
|
|
"\n",
|
|
" iframes_count = {\n",
|
|
" 'iframes_count':\n",
|
|
" len(self.json_fetcher(html_element_iframe, 'iframe').get_data())\n",
|
|
" }\n",
|
|
" \n",
|
|
" multidot_urls_count = {\n",
|
|
" 'multidot_url_count':\n",
|
|
" len(self.json_fetcher(html_element_a_href, 'multidot').get_data()) + len(self.json_fetcher(html_element_img_src, 'multidot').get_data()) + len(self.json_fetcher(html_element_script_src, 'multidot').get_data())\n",
|
|
" }\n",
|
|
" \n",
|
|
" ###################\n",
|
|
" def get_total_registrars():\n",
|
|
"\n",
|
|
" same_registrar_counts = 0\n",
|
|
" other_registrar_counts = 0\n",
|
|
" for k,v in link_refs.items():\n",
|
|
" \n",
|
|
" html_element = self.get_tag_data(url, k, v)\n",
|
|
" \n",
|
|
" same_registrar_counts += self.get_registrar_count(\n",
|
|
" domain_registrar['domain_registrar'],\n",
|
|
" html_element[k][v + '_ext']['normal']\n",
|
|
" )['same_registrar_count']\n",
|
|
" \n",
|
|
" other_registrar_counts += self.get_registrar_count(\n",
|
|
" domain_registrar['domain_registrar'],\n",
|
|
" html_element[k][v + '_ext']['normal']\n",
|
|
" )['other_registrar_count']\n",
|
|
" \n",
|
|
" registrar_counts = {\n",
|
|
" 'same_registrar_count': same_registrar_counts,\n",
|
|
" 'other_registrar_count': other_registrar_counts\n",
|
|
" }\n",
|
|
" return registrar_counts\n",
|
|
" \n",
|
|
" # Avoid unnecessary nesting of the following data\n",
|
|
" data_simple.update(domain_registrar)\n",
|
|
" data_simple.update(domaintitle_match)\n",
|
|
" data_simple.update(iframes_count)\n",
|
|
" data_simple.update(multidot_urls_count)\n",
|
|
" data_simple.update(get_total_registrars())\n",
|
|
" \n",
|
|
" url_data = dict({\n",
|
|
" url: [\n",
|
|
" data_simple,\n",
|
|
" startfinal_url,\n",
|
|
" {'redirects': redirect_url},\n",
|
|
" \n",
|
|
" domain_time_relative,\n",
|
|
" domain_time,\n",
|
|
" \n",
|
|
" {'webpage_data': [\n",
|
|
" html_element_iframe,\n",
|
|
" html_element_a_href,\n",
|
|
" html_element_img_src,\n",
|
|
" html_element_script_src\n",
|
|
" ]\n",
|
|
" }\n",
|
|
" ]\n",
|
|
" })\n",
|
|
" \n",
|
|
" return url_data\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"class write_operations(object):\n",
|
|
"\n",
|
|
" def __init__(self):\n",
|
|
" self.filename = filename\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Set JSON file name, append number suffix\n",
|
|
" # if file exists already.\n",
|
|
" \n",
|
|
" Returns file name path.\n",
|
|
" \"\"\"\n",
|
|
" def set_filename(self):\n",
|
|
" \n",
|
|
" c = 0\n",
|
|
" while True:\n",
|
|
" if os.path.exists(self.filename):\n",
|
|
" if c == 0:\n",
|
|
" self.filename = self.filename + \".\" + str(c)\n",
|
|
" else:\n",
|
|
" self.filename = re.sub(\"[0-9]+$\", str(c), self.filename)\n",
|
|
" else:\n",
|
|
" break\n",
|
|
" c += 1\n",
|
|
" return self.filename\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Append to a JSON file.\n",
|
|
" \"\"\"\n",
|
|
" def write_to_file(self, data):\n",
|
|
" \n",
|
|
" try:\n",
|
|
" json_file = open(self.filename, \"a\")\n",
|
|
" json_file.write(data)\n",
|
|
" json_file.close()\n",
|
|
" return 0\n",
|
|
" except:\n",
|
|
" return 1\n",
|
|
"\n",
|
|
"######################################\n",
|
|
" \"\"\"\n",
|
|
" Fetch all pre-defined URLs.\n",
|
|
" \"\"\"\n",
|
|
" def fetch_and_store_url_data(self, urls, use_file):\n",
|
|
"\n",
|
|
" data_parts = {}\n",
|
|
" fetch_json_data = json_url_data()\n",
|
|
"\n",
|
|
" for u in urls:\n",
|
|
" print(\"Fetching URL data: %s\" % u)\n",
|
|
" try:\n",
|
|
" data_parts.update(fetch_json_data.get_url_data(u))\n",
|
|
" except:\n",
|
|
" print(\"Failed: %s\" % u)\n",
|
|
" pass\n",
|
|
"\n",
|
|
" json_data = json.dumps(data_parts)\n",
|
|
"\n",
|
|
" if use_file == True:\n",
|
|
" self.write_to_file(json_data)\n",
|
|
"\n",
|
|
" return json_data\n",
|
|
"\n",
|
|
"######################################\n",
|
|
"\"\"\"\n",
|
|
"Visualize & summarize data.\n",
|
|
"\"\"\"\n",
|
|
"\n",
|
|
"class data_visualization(object):\n",
|
|
"\n",
|
|
" def __init__(self, url, json_data):\n",
|
|
" self.url = url\n",
|
|
" self.json_data = json_data\n",
|
|
"\n",
|
|
" self.data = json.loads(json.dumps(self.json_data)).get(self.url)\n",
|
|
" self.json_url_obj = json_url_data()\n",
|
|
" self.domain_registrar = self.json_url_obj.get_domain_registrar(self.url)['domain_registrar']\n",
|
|
" self.webpage_data = self.json_url_obj.json_fetcher(self.data, 'webpage_data').get_data()\n",
|
|
"\n",
|
|
" def get_urls_count_summary(self):\n",
|
|
"\n",
|
|
" unique_refs = []\n",
|
|
"\n",
|
|
" for k,v in link_refs.items():\n",
|
|
" if v in unique_refs: continue\n",
|
|
" unique_refs.append(v)\n",
|
|
"\n",
|
|
" def link_count(refs, suffix):\n",
|
|
"\n",
|
|
" urls_cnt = 0\n",
|
|
"\n",
|
|
" for u in self.webpage_data:\n",
|
|
" for l in refs:\n",
|
|
" urls = self.json_url_obj.json_fetcher(u, l + suffix).get_data()\n",
|
|
" for n in urls:\n",
|
|
" urls_cnt += len(n['normal'])\n",
|
|
" urls_cnt += len(n['multidot'])\n",
|
|
" return urls_cnt\n",
|
|
"\n",
|
|
" data = {\n",
|
|
" 'local_urls': link_count(unique_refs, '_self'),\n",
|
|
" 'external_urls': link_count(unique_refs, '_ext')\n",
|
|
" }\n",
|
|
" \n",
|
|
" return data\n",
|
|
"\n",
|
|
" def get_registrars(self):\n",
|
|
"\n",
|
|
" registrars = []\n",
|
|
" #registrars.append(self.domain_registrar)\n",
|
|
"\n",
|
|
" for w in self.webpage_data:\n",
|
|
" webpage_registrars = self.json_url_obj.json_fetcher(w, 'registrar').get_data()\n",
|
|
" for wa in webpage_registrars:\n",
|
|
" if wa != None:\n",
|
|
" registrars.append(wa)\n",
|
|
" return registrars\n",
|
|
"\n",
|
|
" def get_registrar_count_summary(self):\n",
|
|
" \n",
|
|
" domain_counter = dict(Counter(self.get_registrars()))\n",
|
|
" data = {'fetched_domains': domain_counter, 'url_domain_registrar': self.domain_registrar }\n",
|
|
" return data\n",
|
|
"\n",
|
|
"######################################\n",
|
|
"\"\"\"\n",
|
|
"Execute the main program code.\n",
|
|
"\n",
|
|
"TODO: this code must figure out the correct JSON file\n",
|
|
"if multiple generated files are present.\n",
|
|
"\"\"\"\n",
|
|
"if __name__ == '__main__':\n",
|
|
"\n",
|
|
" if plot_only == False:\n",
|
|
" write_obj = write_operations()\n",
|
|
" write_obj.set_filename()\n",
|
|
" data = write_obj.fetch_and_store_url_data(urls, use_file)\n",
|
|
"\n",
|
|
" url_str_pattern = re.compile(r\"(^[a-z]+://)?([^/]*)\")\n",
|
|
"\n",
|
|
" if os.path.exists(filename):\n",
|
|
" with open(filename, \"r\") as json_file:\n",
|
|
" json_data = json.load(json_file)\n",
|
|
" else:\n",
|
|
" json_data = data\n",
|
|
"\n",
|
|
" # Get URLs from an available JSON data\n",
|
|
" for key_url in json_data.keys():\n",
|
|
" \n",
|
|
" print(\"Generating statistics: %s\" % key_url)\n",
|
|
"\n",
|
|
" fig = plt.figure()\n",
|
|
" fig_params = {\n",
|
|
" 'xtick.labelsize': 8,\n",
|
|
" 'figure.figsize': [9,8]\n",
|
|
" # 'figure.constrained_layout.use': True\n",
|
|
" }\n",
|
|
" plt.rcParams.update(fig_params)\n",
|
|
" \n",
|
|
" domain_string = url_str_pattern.split(key_url)[2].replace('.','')\n",
|
|
" summary = data_visualization(key_url, json_data)\n",
|
|
" \n",
|
|
" summary_registrars = summary.get_registrar_count_summary()['fetched_domains']\n",
|
|
"\n",
|
|
" x_r = list(summary_registrars.keys())\n",
|
|
" y_r = list(summary_registrars.values())\n",
|
|
" \n",
|
|
" # Show bar values\n",
|
|
" for index,data in enumerate(y_r):\n",
|
|
" plt.text(x=index, y=data+0.5, s=data, fontdict=dict(fontsize=8))\n",
|
|
" \n",
|
|
" title_r = \"Domains associated with HTML URL data (\" + key_url + \")\"\n",
|
|
" xlabel_r = \"Fetched domains\"\n",
|
|
" ylabel_r = \"Domain count\"\n",
|
|
"\n",
|
|
" plt.bar(x_r, y_r, color=\"green\", edgecolor=\"black\")\n",
|
|
" plt.title(title_r)\n",
|
|
" plt.xlabel(xlabel_r)\n",
|
|
" plt.ylabel(ylabel_r)\n",
|
|
" plt.xticks(rotation=45, horizontalalignment=\"right\")\n",
|
|
"\n",
|
|
" if save_plot_images == True:\n",
|
|
" plt.savefig(os.getcwd() + \"/\" + \"domain_figure_\" + domain_string + \".png\", dpi=plot_images_dpi)\n",
|
|
" plt.show()\n",
|
|
"\n",
|
|
" #fig_u = plt.figure()\n",
|
|
" \n",
|
|
" #summary_urls = summary.get_urls_count_summary()\n",
|
|
" \n",
|
|
" #x_u = list(summary_urls.keys())\n",
|
|
" #y_u = list(summary_urls.values())\n",
|
|
" #title_u = \"Local and external URL references (\" + key_url + \")\"\n",
|
|
" #xlabel_u = \"Fetched URLs\"\n",
|
|
" #ylabel_u = \"URL count\"\n",
|
|
" \n",
|
|
" #plt.bar(x_u, y_u, color=\"blue\", edgecolor='black')\n",
|
|
" #plt.title(title_u)\n",
|
|
" #plt.xlabel(xlabel_u)\n",
|
|
" #plt.ylabel(ylabel_u)\n",
|
|
" #plt.show()\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.8.5"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
}
|