From 5f29f28ded99426c8d304b9d6702e30fc8316d28 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 13 Jan 2016 10:32:59 +0100 Subject: [PATCH 01/24] back-reference from DataArrays to DataSets --- qcodes/data/data_array.py | 16 ++++++++++++++++ qcodes/data/data_set.py | 11 +++++++++++ qcodes/data/format.py | 4 ++-- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/qcodes/data/data_array.py b/qcodes/data/data_array.py index a32d66c05cd..f14e415935c 100644 --- a/qcodes/data/data_array.py +++ b/qcodes/data/data_array.py @@ -37,6 +37,10 @@ def __init__(self, parameter=None, name=None, label=None, array_id=None, self.size = size self._preset = False + # store a reference up to the containing DataSet + # this also lets us make sure a DataArray is only in one DataSet + self._data_set = None + self.data = None if preset_data is not None: self.init_data(preset_data) @@ -47,6 +51,18 @@ def __init__(self, parameter=None, name=None, label=None, array_id=None, self.last_saved_index = None self.modified_range = None + @property + def data_set(self): + return self._data_set + + @data_set.setter + def data_set(self, new_data_set): + if (self._data_set is not None and + new_data_set is not None and + self._data_set != new_data_set): + raise RuntimeError('A DataArray can only be part of one DataSet') + self._data_set = new_data_set + def nest(self, size, action_index=None, set_array=None): ''' nest this array inside a new outer loop diff --git a/qcodes/data/data_set.py b/qcodes/data/data_set.py index 221f91282ea..1bd39f014ad 100644 --- a/qcodes/data/data_set.py +++ b/qcodes/data/data_set.py @@ -208,12 +208,23 @@ def sync(self): def add_array(self, data_array): ''' add one DataArray to this DataSet + + note: DO NOT just set data_set.arrays[id] = data_array + because this will not check for overriding, nor set the + reference back to this DataSet. It would also allow you to + load the array in with different id than it holds itself. + ''' + # TODO: mask self.arrays so you *can't* set it directly + if data_array.array_id in self.arrays: raise ValueError('array_id {} already exists in this ' 'DataSet'.format(data_array.array_id)) self.arrays[data_array.array_id] = data_array + # back-reference to the DataSet + data_array.data_set = self + def _clean_array_ids(self, arrays): ''' replace action_indices tuple with compact string array_ids diff --git a/qcodes/data/format.py b/qcodes/data/format.py index 0a6f78718a6..5de18936625 100644 --- a/qcodes/data/format.py +++ b/qcodes/data/format.py @@ -283,7 +283,7 @@ def read_one_file(self, data_set, f, ids_read): set_array = DataArray(label=labels[i], array_id=array_id, set_arrays=set_arrays, size=set_size) set_array.init_data() - arrays[array_id] = set_array + data_set.add_array(set_array) set_arrays = set_arrays + (set_array, ) ids_read.add(array_id) @@ -300,7 +300,7 @@ def read_one_file(self, data_set, f, ids_read): data_array = DataArray(label=labels[i], array_id=array_id, set_arrays=set_arrays, size=size) data_array.init_data() - arrays[array_id] = data_array + data_set.add_array(data_array) data_arrays.append(data_array) ids_read.add(array_id) From 95875afc88f74687d88f99b2df07892366df61d5 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 13 Jan 2016 10:33:57 +0100 Subject: [PATCH 02/24] live plotting --- Qcodes example.ipynb | 6552 ++++++++++++++++++++++++++++++++-- qcodes/__init__.py | 8 + qcodes/data/data_set.py | 11 +- qcodes/plots.py | 124 + qcodes/widgets/__init__.py | 0 qcodes/widgets/widgets.js | 54 + qcodes/widgets/widgets.py | 50 + test_multi_d/chan1_chan0.dat | 2 +- test_multi_d/chan1_chan2.dat | 2 +- 9 files changed, 6546 insertions(+), 257 deletions(-) create mode 100644 qcodes/plots.py create mode 100644 qcodes/widgets/__init__.py create mode 100644 qcodes/widgets/widgets.js create mode 100644 qcodes/widgets/widgets.py diff --git a/Qcodes example.ipynb b/Qcodes example.ipynb index 6b3e96fefba..6bbc5e43e25 100644 --- a/Qcodes example.ipynb +++ b/Qcodes example.ipynb @@ -6,13 +6,80 @@ "metadata": { "collapsed": false }, - "outputs": [], + "outputs": [ + { + "data": { + "application/javascript": [ + "/*\n", + " * Qcodes Jupyter/IPython widgets\n", + " */\n", + "require([\n", + " 'nbextensions/widgets/widgets/js/widget',\n", + " 'nbextensions/widgets/widgets/js/manager'\n", + "], function (widget, manager) {\n", + "\n", + " var UpdateView = widget.DOMWidgetView.extend({\n", + " render: function() {\n", + " window.MYWIDGET = this;\n", + " this._interval = 0;\n", + " this.update();\n", + " },\n", + " update: function() {\n", + " var me = this;\n", + "\n", + " me.display(me.model.get('_message'));\n", + " me.setInterval();\n", + " },\n", + " display: function(message) {\n", + " /*\n", + " * display method: override this for custom display logic\n", + " */\n", + " this.el.innerHTML = message;\n", + " },\n", + " remove: function() {\n", + " this.setInterval(0);\n", + " },\n", + " setInterval: function(newInterval) {\n", + " var me = this;\n", + " if(newInterval===undefined) newInterval = me.model.get('interval');\n", + " if(newInterval===me._interval) return;\n", + "\n", + " me._interval = newInterval;\n", + "\n", + " if(me._updater) clearInterval(me._updater);\n", + "\n", + " if(me._interval) {\n", + " me._updater = setInterval(function() {\n", + " me.send({myupdate: true});\n", + " }, me._interval * 1000);\n", + " }\n", + " }\n", + " });\n", + " manager.WidgetManager.register_widget_view('UpdateView', UpdateView);\n", + "\n", + " var HiddenUpdateView = UpdateView.extend({\n", + " display: function(message) {\n", + " this.$el.parents('.widget-area').hide().css('background-color', '#fff');\n", + " }\n", + " });\n", + " manager.WidgetManager.register_widget_view('HiddenUpdateView', HiddenUpdateView);\n", + "});\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "%matplotlib inline\n", + "%matplotlib nbagg\n", "import matplotlib.pyplot as plt\n", + "plt.ioff()\n", "import time\n", - "from IPython import display\n", - "import numpy.ma as ma\n", + "# from IPython import display\n", + "# import numpy.ma as ma\n", "\n", "# load the qcodes path, until we have this installed as a package\n", "import sys\n", @@ -21,9 +88,9 @@ "if qcpath not in sys.path:\n", " sys.path.append(qcpath)\n", "\n", - "import qcodes as q\n", + "import qcodes as qc\n", "\n", - "q.set_mp_method('spawn') # force Windows behavior on mac" + "qc.set_mp_method('spawn') # force Windows behavior on mac" ] }, { @@ -50,7 +117,7 @@ "from toymodel import AModel, MockGates, MockSource, MockMeter, AverageGetter, AverageAndRaw\n", "\n", "# no extra processes running yet\n", - "q.active_children()" + "qc.active_children()" ] }, { @@ -78,7 +145,7 @@ "source = MockSource('source', model)\n", "meter = MockMeter('meter', model)\n", "\n", - "station = q.Station(gates, source, meter)\n", + "station = qc.Station(gates, source, meter)\n", "\n", "# could measure any number of things by adding arguments to this\n", "# function call, but here we're just measuring one, the meter amplitude\n", @@ -94,7 +161,7 @@ "\n", "# check that a DataServer is running (not yet implemented, but when we have\n", "# a monitor, defining a station will start the DataServer to run the monitor)\n", - "q.active_children()" + "qc.active_children()" ] }, { @@ -134,7 +201,7 @@ "DataSet: DataMode.PULL_FROM_SERVER, location='testsweep'\n", " chan0: chan0\n", " amplitude: amplitude\n", - "started at 2016-01-08 15:48:47\n" + "started at 2016-01-13 10:24:31\n" ] } ], @@ -143,7 +210,7 @@ "# the sweep values are defined by slicing the parameter object\n", "# but more complicated sweeps (eg nonlinear, or adaptive) can\n", "# easily be used instead\n", - "data = q.Loop(c0[-20:20:0.1], 0.03).run(location='testsweep')" + "data = qc.Loop(c0[-20:20:0.1], 0.03).run(location='testsweep')" ] }, { @@ -156,7 +223,7 @@ { "data": { "text/plain": [ - "[, ]" + "[, ]" ] }, "execution_count": 6, @@ -166,7 +233,7 @@ ], "source": [ "# now there should be two extra processes running, DataServer and a sweep\n", - "q.active_children()" + "qc.active_children()" ] }, { @@ -193,11 +260,11 @@ " 0.016, 0.017, 0.018, 0.019, 0.02 , 0.022, 0.023, 0.025,\n", " 0.027, 0.029, 0.031, 0.034, 0.037, 0.04 , 0.044, 0.048,\n", " 0.053, 0.058, 0.064, 0.071, 0.077, 0.085, 0.092, 0.099,\n", - " 0.106, 0.111, 0.115, 0.117, 0.117, 0.117, 0.115, 0.111,\n", - " 0.106, 0.099, 0.092, 0.085, 0.077, 0.071, 0.064, 0.058,\n", - " 0.053, 0.048, 0.044, 0.04 , 0.037, 0.034, 0.031, 0.029,\n", - " 0.027, 0.025, 0.023, 0.022, 0.02 , 0.019, 0.018, 0.017,\n", - " 0.016, 0.015, 0.014, 0.013, nan, nan, nan, nan,\n", + " 0.106, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", @@ -242,11 +309,11 @@ " -13.7, -13.6, -13.5, -13.4, -13.3, -13.2, -13.1, -13. , -12.9,\n", " -12.8, -12.7, -12.6, -12.5, -12.4, -12.3, -12.2, -12.1, -12. ,\n", " -11.9, -11.8, -11.7, -11.6, -11.5, -11.4, -11.3, -11.2, -11.1,\n", - " -11. , -10.9, -10.8, -10.7, -10.6, -10.5, -10.4, -10.3, -10.2,\n", - " -10.1, -10. , -9.9, -9.8, -9.7, -9.6, -9.5, -9.4, -9.3,\n", - " -9.2, -9.1, -9. , -8.9, -8.8, -8.7, -8.6, -8.5, -8.4,\n", - " -8.3, -8.2, -8.1, -8. , -7.9, -7.8, -7.7, -7.6, -7.5,\n", - " -7.4, -7.3, -7.2, -7.1, -7. , -6.9, -6.8, nan, nan,\n", + " -11. , -10.9, -10.8, -10.7, -10.6, -10.5, -10.4, -10.3, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", @@ -285,7 +352,7 @@ } ], "source": [ - "# bring the data into the main process and display it as numbers\n", + "# manually bring the data into the main process and display it as numbers\n", "data.sync()\n", "data.arrays" ] @@ -300,9 +367,775 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAswAAAE4CAYAAABPOqWIAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xl4XVXVP/DvStvQeZ7TeYACHRikzBBQoMArRUUoAoqi\n9FUQfP29gIpIQUScQJFXGUWBSoUyKyIyRMpc6Exb6EDTNGmTziNp02T//lj3ktv05o7nnL3POd/P\n8+SxubnD4rruvuvsUYwxICIiIiKi9EpsB0BERERE5DIWzEREREREGbBgJiIiIiLKgAUzEREREVEG\nLJiJiIiIiDJgwUxERERElEFOBbOITBKRpSLykYhcl+bvB4nImyJSLyLfT7l9kIi8IiIfiMhCEbnK\ny+CJiIiIiPwm2fZhFpESAB8B+CyAGgCzAUwxxixNuU9vAEMBnAtgszHm9sTt/QH0N8bME5HOAN4H\nMDn1sURERERELsulh3kigGXGmEpjTAOAGQAmp97BGLPBGPM+gL0tbl9njJmX+PcOAEsAlHkSORER\nERFRAHIpmMsAVKX8vgYFFL0iMgzAYQDeyfexRERERES2BLLoLzEdYyaAqxM9zUREREREodA2h/tU\nAxiS8vugxG05EZG20GL5YWPMMxnul3kyNRERERGRB4wxks/9c+lhng1glIgMFZFSAFMAPJvh/i0D\n+BOAxcaY32V7IWMMfzz4ufHGG63HEKUfvp98P1394XvJ99PlH76ffC9d/SlE1h5mY0yjiFwJ4EVo\ngf2AMWaJiEzVP5t7RaQfgPcAdAHQJCJXAzgEwAQAFwFYKCJzARgAPzLGvFBQtEREREREActlSgYS\nBe5BLW67J+XftQAGp3noGwDaFBMgEREREZFNPOkvgsrLy22HECl8P73F99M7fC+9xffTW3w/vcP3\n0r6sB5cERUSMK7EQERERUTSJCIwPi/4C8/Of246ASFVXA+ecA5x+uv584QvA+vW2oyJSN9zQnJun\nnw7MnGk7IiL15pv75uZllwFNTbajIgIaGoDJk4H6+sIe71QPc9euBh9/DPTsaTsairubbwaWLAG+\n/nX9/f/+DygvB/7nf6yGRYTqamDcOODRRwERYNUq4I47gMWL9Xcim845BzjsMOCEE/T3730PuOce\n4MQT7cZF9I9/ALfeCrzxRmE9zE4VzF/+ssHnPgdcfrntaCjOjAEOOgh45BFg4kS97eWXgWuvBd5/\n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\nAAAAAElFTkSuQmCC\n", + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('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 = $('
');\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", + " 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", + " this.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", + " '
');\n", + " var titletext = $(\n", + " '
');\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 = $('
');\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 = $('');\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 rubberband = $('');\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);\n", + " canvas.attr('height', height);\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 = $('
')\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 = $('');\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 = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var 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", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\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= 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": [ - "" + "" ] }, "metadata": {}, "output_type": "display_data" - } - ], - "source": [ - "fig = plt.figure(figsize=(12, 5))\n", - "ax = fig.add_subplot(1,1,1)\n", - "\n", - "# next up: create an active plot that will update itself automatically\n", - "# (via javascript, so the main python stays accessible) as long as the sweep is running\n", - "# as well as provide some nicer styling and labeling defaults\n", - "while True:\n", - " data2.sync()\n", - " ax.clear()\n", - " ax.pcolormesh(ma.masked_invalid(data2.chan0),\n", - " ma.masked_invalid(data2.chan1),\n", - " ma.masked_invalid(data2.amplitude_0),\n", - " cmap=plt.cm.hot)\n", - " display.display(fig)\n", - " display.clear_output(wait=True)\n", - " if not data2.is_live_mode:\n", - " break\n", - " time.sleep(1)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "collapsed": false - }, - "outputs": [ + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('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 = $('
');\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", + " 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", + " this.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", + " '
');\n", + " var titletext = $(\n", + " '
');\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 = $('
');\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 = $('');\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 rubberband = $('');\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);\n", + " canvas.attr('height', height);\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 = $('
')\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 = $('');\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 = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var 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", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\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= 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": [ - "" + "" ] }, "metadata": {}, "output_type": "display_data" - } - ], - "source": [ - "data3.sync()\n", - "plt.figure(figsize=(12, 5))\n", - "# pcolormesh needs NaN masked out or it barfs\n", - "plt.pcolormesh(ma.masked_invalid(data3.chan0),\n", - " ma.masked_invalid(data3.chan1),\n", - " ma.masked_invalid(data3.amplitude_3_0),\n", - " cmap=plt.cm.hot)\n", - "plt.colorbar()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": { - "collapsed": false - }, - "outputs": [ + }, { "data": { - "image/png": "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\nAAAAAElFTkSuQmCC\n", + "text/html": [ + "" + ], "text/plain": [ - "" + "" ] }, "metadata": {}, "output_type": "display_data" - } - ], - "source": [ - "data3.sync()\n", - "plt.figure(figsize=(12, 5))\n", - "# pcolormesh needs NaN masked out or it barfs\n", - "plt.pcolormesh(ma.masked_invalid(data3.chan2),\n", - " ma.masked_invalid(data3.chan1),\n", - " ma.masked_invalid(data3.amplitude_5_0),\n", - " cmap=plt.cm.hot)\n", - "plt.colorbar()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "collapsed": false, - "scrolled": false - }, - "outputs": [ + }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAsYAAAE4CAYAAABYGDVBAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xm4jVX7B/DvMmaIUA4llCkVSkUKHYTKLJk1K81vk3rr\n7aV+3kbNKhUNZhmOOQ6OU4ZMmSPzEErGzJxh/f647Ygz7L3P3vt+hu/nulw5zrb3V/s8z3Pv9ax1\nL2OtBRERERGR3+XSDkBERERE5AQsjImIiIiIwMKYiIiIiAgAC2MiIiIiIgAsjImIiIiIALAwJiIi\nIiICEERhbIwZaIzZZYxZkcn3nzPGLDXGLDHGrDTGpBpjLoh8VCIiIiKi6DHZ9TE2xtQFcBjAIGtt\n9Wwe2xzAv6y1t0YuIhERERFR9GU7YmytnQNgf5DP1wnA8BwlIiIiIiJSELE5xsaYAgBuAzAmUs9J\nRERERBQrkVx81wLAHGvtgQg+JxERERFRTOSJ4HN1RDbTKIwxWU9oJiIiIiKKEGutCeXxwRbG5tSv\njL9pTFEAtwDokt0TZbfYj5yrd+/e6N27t3YMCgPfO3fj++dufP/ci++duxkTUk0MIIjC2BgzDEA8\ngBLGmG0AegHIB8Baa7849bDWAKZZa4+FnICIiIiIyAGyLYyttZ2DeMy3AL6NSCIiIiIiIgXc+Y6C\nFh8frx2BwsT3zt34/rkb3z/34nvnP9lu8BHRFzPGco4xEREREUWbMSbkxXccMSYiIiIiAgtjIiIi\nIiIALIyJiIiIiACwMCYiIiIiAsDCmIiIiIgIAAtjIiIiIiIALIyJiIiIiACwMCYiIiIiAsDCmIiI\niIgIAAtjIiIiIiIALIyJiIiIiACwMCYiIiIiAsDCmIiIiIgIAAtjIiIiIiIALIyJiIiIiACwMCYi\nIiIiAsDCmIiIiIgIAAtjIiIiIiIALIyJiIiIiACwMCYiIiIiAsDCmIiIiIgIAAtjIiIiIiIALIyJ\niIiIiACwMCYiIiIiAsDCmMjzdu8GunQBrNVOQuQv1sqxt3u3dhIiClbMC+OjR2P9ikT+lpAADBsG\nLFumnYTIX5YulWNv3DjtJEQUrJgXxomJsX5FIn8bOxaoXFn+S0Sxw2OPyH1iXhgnJMT6FYn866+/\ngHnzgI8/5rFHFGsJCUC/fsDcuXIsEpHzxbwwnjQJSEmJ9asS+dPkycAttwC33grs2wesW6ediMgf\n1q4F9u8HGjUC6tcHpkzRTkREwYh5YVyhAvDjj7F+VSJ/GjsWaNMGyJULaNWKo8ZEsZKQALRuLcde\nmzacTkHkFjEvjNu25QmCKBaOHQOmTwdatpSv27ZlYUwUKwkJcswBcgwmJsoxSUTOFvPCuE0bWaGb\nnh7rVybyl+nTgZo1gQsvlK/j42UqxY4dqrGIPG/7dmDDBpnGBAAXXQRcey0wY4ZuLiLKXswL4ypV\ngKJFgUWLYv3KRP4SmEYRkDcv0KwZW0cRRdu4cXKs5c17+s84nYLIHVQ2+OB0CqLoSk2Vha6tW//z\nzzmdgij6zpxGEdC6NTBxohybRORcKoVxmzZy4uBOXETR8eOPwGWXAWXL/vPPmzaVuzX79unkIvK6\nvXuBxYuBJk3++eflygHlywOzZ6vEIqIgqRTGNWsCJ04Aq1drvDqR9509jSKgYEGgYUMZuSKiyJs4\nUVq0FSx47vc4nYLI+bItjI0xA40xu4wxK7J4TLwxZqkxZpUxZlb2z8kTBFG0pKfLHMeMCmOA0ymI\noimjaRQBgcXnvFtK5FzBjBh/DaBpZt80xhQF8AmA5tbaqwHcFcwLB6ZTEFFkLV4MnH8+ULVqxt9v\n3hxISgKOHIltLiKvO3wYmDVLFt5lpGpVoFAhOUaJyJmyLYyttXMA7M/iIZ0BjLHW7jj1+D3BvHDd\nutLSZsuWYB5NRMHKbBpFQLFiQO3awNSpsctE5AdTpwI33ijHWEZ4t5TI+SIxx7gygOLGmFnGmEXG\nmG7B/KXcuaXpOUeNiSLHWjmmsiqMAd6xIYoGHntE7pcnQs9RE0BDAIUA/GSM+clauyGjB/fu3fvv\n31eoEI+EhHg8/XQEUhAR1qwBjh4Frr8+68e1bg385z/AyZNAvnyxyUbkZSdPAt9/D/Ttm/Xjrr9e\npjGtWZP5dCciCk9ycjKSk5Nz9BzGBrEKwBhTDsBEa231DL73AoDzrLWvnvp6AIDvrbVjMnisPfP1\njh8HSpUC1q4F4uJy8K8gIgDA//4H7NoFfPRR9o+tUwd49dVz20oRUeimTZPjad687B/7xBNA6dLA\nSy9FPxeRnxljYK01ofydYKdSmFO/MjIeQF1jTG5jTEEAtQGsCeZJzzsPuO02YMKEIFMQUZaym198\nJs51JIocHntE3pDtiLExZhiAeAAlAOwC0AtAPgDWWvvFqcc8B+A+AGkAvrTWfpzJc9mzX2/kSODb\nb4EpU3L2DyHyu61bgeuuA/74A8gTxCSp9euB+vWBHTuAXCodzYm8IS0NuOQSYM4coGLF7B+fmip3\nS5csOXcTHiKKnHBGjIOaShEpGRXGBw8CZcoAv/0GFC0asyhEnvPhh8Dy5cBXXwX/d6pVAz7/HLjp\npujlIvK6uXOBRx4BVmTa7f9c990HXHst8OST0ctF5HfRnEoRNUWKAPXqccSYKKdCuZUbwFu6RDnH\nY4/IO9QLY4A7cRHl1O7dwLJlQOPGof29wLHHnbiIwhNokZjZbneZadwYWLpUjl0icg5HFMYtWwKJ\nidKlgohCN2EC0LSpLGgNRY0asoX0ypXRyUXkdYHpE9XP6dmUtQIFpCPMxImRz0RE4XNEYXzRRXKB\nnjFDOwmRO4VzKxfgTlxEORU49kxIsxgFjz0i53FEYQzIbSieIIhCd/AgMHs2cMcd4f19TmUiCl84\n0ygCmjUDfvwROHQospmIKHyOKYxbt5ZbSqmp2kmI3OX774G6dcPv6lKnjmwKsnFjZHMRed2GDcCf\nf8oxFI6iRYGbb5ZjmIicwTGFcbly0s9xzhztJETuEu40ioDcuWWeP0eNiUKTkAC0apWzPuCcTkHk\nLI4pjAFOpyAK1fHjshVty5Y5ex5OpyAKXU6mUQS0agVMnQqcOBGZTESUM44qjNu0AcaNY+soomDN\nnCmbdMTF5ex5GjYEVq+WXfOIKHu//w78+ivQoEHOnicuDrj6ajmWiUifowrjqlWlhc3PP2snIXKH\nSIxYAUC+fMDttwPjx+f8uYj8YPx4OWby5cv5c/GODZFzOKowNobTKYiClZoqF+eczC8+E489ouCN\nHRuZD6WAHMPjxwNpaZF5PiIKn6MKY0BOEPzkTJS9uXOBMmWA8uUj83y33Qb89BNw4EBkno/Iq/bv\nB+bPl2MmEi67DLj4YjmmiUiX4wrj66+Xno5r1mgnIXK2SE2jCChcGIiPByZPjtxzEnnR5Mkyt7hQ\nocg9J6dTEDmD4wrjXLk4akyUHWvlGInUNIoAto4iyl5OWyRmJHDscfE5kS7HFcYAC2Oi7CxZIot+\nrroqss/booVszX7sWGSfl8grjh6VDhItWkT2ea++GsibF1i6NLLPS0ShcWRhXL8+sHkzsG2bdhIi\nZwpMozAmss974YXAddcBiYmRfV4ir0hMlCl/JUpE9nkDi885KESky5GFcZ48QPPm0tOYiM4VjVu5\nAZxOQZQ5HntE3ubIwhjgJ2eizKxdK50jatWKzvO3bg1MmgSkpETn+YncKiVFFt61bh2d569dWzpe\nrFsXnecnouw5tjBu3FjmUe7Zo52EyFkSEuTCnCtKR++llwIVKgA//hid5ydyqx9+ACpWlDaJ0ZAr\nl2wRzUEhIj2OLYwLFJDieOJE7SREzhLJjQUyw1u6ROeK5jSKAG60Q6TL2Bj2hjHG2FBeb9gwYPhw\nFsdEAdu3AzVqAH/8ISvYo2XtWqBhQ+C336I3Mk3kJunpMlKcnAxUrhy910lJAeLigJUrgUsuid7r\nEPmBMQbW2pCWqTv6ktesmdy6OnRIOwmRM4wbJ8dFNItiAKhSBShaFFi0KLqvQ+QWCxcCxYpFtygG\n5Nhu1oyLz4m0OLowLloUuOkmYOpU7SREzhCLaRQBnE5BdFosplEEcDoFkR5HF8YATxBEAXv3Aj//\nDDRpEpvXC3SG4U5c5HeBnSZj9aG0aVO5W7N3b2xej4hOc3xh3KqVjBifOKGdhEjXxIlAo0ZAwYKx\neb2aNeW4W706Nq9H5FS//CJzf6+9NjavV7CgHOuTJsXm9YjoNMcXxnFxsu1tUpJ2EiJdsRyxAmQn\nrtat2TqKKNAiMdI7TWaFvfyJdDi+MAY4nYLo8GFg1ixZlBNLPPaIYju3P6B5cxkQOnIktq9L5Heu\nKIzbtAEmTADS0rSTEOmYOhW48UZZFR9LdetKi7gtW2L7ukROsXkzsGMHcPPNsX3dYsVkJzwuPieK\nLVcUxpddBpQuDcybp52ESEesp1EE5M4NtGjB1lHkX+PGAS1byrEQa5xOQRR7riiMAd7SJf86eRKY\nMkUWomrgsUd+pjGNIqBVK2DyZDkHEFFsuKYwbtOGraPIn5KSgKpV5a6JhkaNgBUrgD//1Hl9Ii27\ndskOdI0a6bz+xRcDV1wh6wuIKDZcUxhffTWQJw+wbJl2EqLYSkiI3cYCGTnvPOmrOmGCXgYiDRMm\nALfdBuTPr5chMChERLHhmsLYGO7ERf6TliZzHDULY4DHHvlTLHe7y0ybNnIO4OJzothwTWEMcCEC\n+c9PP0kv74oVdXPccQcwZw5w8KBuDqJY+esvYO5c+dnXVKkScNFFwPz5ujmI/MJVhXHt2sC+fcD6\n9dpJiGJDexpFQJEiQL16sgiQyA+mTAHq1wfOP187CadTEMWSqwrjXLlklS5PEOQH1uquiD8bp1OQ\nnzhhGkVAoDMMF58TRZ+rCmOAraPIP5Yvl7n11atrJxEtWwKJicDx49pJiKLr2DH5WW/ZUjuJqFFD\niuIVK7STEHlftoWxMWagMWaXMSbDQ9IYc4sx5oAxZsmpX/+JfMzT4uOBdetkJyIiLwtMozBGO4ko\nWVIu0DNmaCchiq4ZM4Brr5W5vU4QWHzOu6VE0RfMiPHXAJpm85gfrbU1T/3qE4FcmcqbF2jWDBg/\nPpqvQqRPa7e7rPDiTH7glLn9Z+Lic6LYyLYwttbOAbA/m4fFdEyL0ynI6zZskA016tTRTvJPbdpI\nb9fUVO0kRNGRmio/404rjOvUAf74A9i4UTsJkbdFao5xHWPMMmPMZGPMlRF6zkw1bQosWiQdKoi8\nKCFBFprmctgqgHLlgLJlpXUbkRfNng2ULy8/506SOzcXnxPFQiQuuz8DKGutvQZAPwDjIvCcWSpY\nEGjYEJg0KdqvRKTDibdyAzidgryMxx6RvxkbRP8XY0w5ABOttdmujzfGbAZwnbX2nPFcY4zt1avX\n31/Hx8cjPj4+pMABgwcDY8bIjkBEXrJzJ3DVVcCuXUC+fNppzrV6tWyTu3WrcxYGEkWCtTJSnJgI\nVK2qneZcJ04ApUrJMVi6tHYaIudJTk5GcnLy31+/+uqrsNaGdKUKtjAuDymMq2XwvThr7a5Tv68F\n4DtrbflMnscG83rB2L9fbnft3AkUKhSRpyRyhM8+k6kKQ4dqJ8mYtcAVV0i+66/XTkMUOYsWAd26\nAb/+qp0kc507y8YjPXpoJyFyPmNMyIVxMO3ahgGYB6CyMWabMeY+Y8zDxpiHTj2knTFmlTFmKYAP\nAHQIOXkYihUDatUCpk2LxasRxY6Tb+UCbB1F3uX0Yw/gsUcUbUGNGEfsxSI4YgzIyNrcucCQIRF7\nSiJV+/fLAredO4HChbXTZG7hQuCee4A1a7STEEXOFVfINL0bbtBOkrnDh4GLL5apTMWKaachcrao\njBg7WatWsp/9yZPaSYgiY9IkoEEDZxfFgEyhOHTI2beciUKxZg1w5IjzpwcVLiwbXU2erJ2EyJtc\nXRhffDFQpQpwxjxrIldzw61cQNrItW7NW7rkHQkJ8jPthgWlnE5BFD2uLowBbvZB3nH0qGxF26KF\ndpLg8NgjLxk71nk7TWamRQtg+nQ5ZxBRZLm+MG7TRraHTkvTTkKUM9OmydzGEiW0kwSnfn1g82Zg\n2zbtJEQ5s22bzNmtV087SXAuvFCmfHDxObldWhrw9NPOmhLr+sK4YkXp58jpFOR2w4YB7dtrpwhe\nnjzywXTECO0kRDkzfLj8LOfJo50keO3bS24iN5s1S3abdFLPftcXxgBw993AoEHaKYjCt3+/bCrg\npsIYOH3sxbC5DVFEWSs/w3ffrZ0kNO3by4jxgQPaSYjCN2iQ9A53Ek8Uxp06ARMmyIpiIjcaNQpo\n0sR97ZduvlnmOS5bpp2EKDxLlwLHjsnPspsULw40biznDiI3OnxYardOnbST/JMnCuO4OOCmm7hK\nl9zLiZ+ag5ErF9C1K+/YkHsFjj03dKM4W7duPPbIvRISZF5/yZLaSf7J1Rt8nGnkSGDgQLkdTeQm\nGzcCdeoA27c7a55VsNavB+rWBXbscNccTaKUFKBMGdkoqmJF7TShO3kSuOQSYMEC4PLLtdMQhaZx\nY6B79+hOIfTdBh9natkSWLxYLs5EbjJkCNCxozuLYgCoVAmoUIEfSsl9EhOlIHZjUQzIOaNjR+7+\nSu6zYwfw88/ObE/qmcK4QAHpQTlsmHYSouAFFv64cRrFmXhLl9zIS8ceF8CSmwwdCtx5p9RuTuOZ\nwhjgCnlyn3nzZNTH6dvQZqdDB+D774G//tJOQhScAweAqVPd1wnmbDfcIFOYfvpJOwlRcJzeCcZT\nhXHdurLKcfly7SREwRk8WE4Oblz4c6bixYFbbwVGj9ZOQhSc0aNljmPx4tpJcsYYOYcMHqydhCg4\ny5ZJNyOndoLxVGHMFfLkJsePS6ulLl20k0QGp1OQm3hhGkVAly7Ad98BJ05oJyHK3qBBUqvlcmgF\n6tBY4evWTXYDSk3VTkKUtcmTgRo1gLJltZNExh13AKtXA1u2aCchytrmzcCaNcDtt2sniYxy5YDq\n1eWcQuRkqalSozn5Q6nnCuPKleUkMX26dhKirHlpxAqQudLt23OFPDnfkCEyL96tnWAywjs25AaJ\nidJasFIl7SSZ81xhDHCLaHK+3buB5GRZleslXABLTuf0hT/hatcOmDUL2LNHOwlR5twwIOTJwjiw\nQv7gQe0kRBkbORJo3hwoUkQ7SWTVqiWLgRYs0E5ClLH584HcuaWbg5cUKQI0aybnFiIn+usv6QTT\noYN2kqx5sjAuUQJo0IAr5Mm53PCpORzGyL+LK+TJqQYPdu8W0NnhdApystGjgYYNnd8JxpOFMcDp\nFORcv/4K/PabtDfzoq5dZdSKK+TJaU6ckO4NXbtqJ4mOxo2BbduAtWu1kxCdyy1TmDxbGN9xB7Bq\nFbB1q3YSon8aPFjaK+XJo50kOsqXB66+GpgyRTsJ0T9NngxUqyYLtL0oTx6gc2fesSHn2bJFuhbd\ncYd2kux5tjDOn58r5Ml50tPlZ9KL0yjOxOkU5ESBaRReFjj20tO1kxCdNmSI1GRu6ATj2cIYOL0b\nEFfIk1P8+CNwwQXSv9jL2rUDZs4E9u7VTkIk9u6Vrg3t2mknia4aNYCiRYHZs7WTEAlrT+/y6gae\nLoxr15ZPzYsWaSchEm6ZY5VTRYvKLbPvvtNOQiRGjpSfSa91gjlbYItorrEhp1i4UP5bq5ZujmB5\nujAOrJDnCYKc4OhRICFB5gD6AY89chKvdoLJSOfOwNixcs4h0hY49tzSCcbThTFweoX8yZPaScjv\nxo+XuxilS2sniY0mTWTr3XXrtJOQ361dKwuxGzfWThIbF18so3MTJmgnIb87edJ9nWA8XxhfdhlQ\ntaps+EGkyU8jVoCskO/UiQtgSd+QIfKz6NVOMBnhHRtygilTgKuukm5FbmFsDFemGWNsLF8vYMAA\nKYzHjIn5SxMBAH7/XT6g7dgBFCqknSZ2li4F2rYFNm4Ecnn+Yzg5UXo6cPnlwLhxwDXXaKeJnSNH\ngEsukb7ppUpppyG/attWdmR84AGd1zfGwFob0iQOX1yq7rpLVsjv26edhPxq+HCgTRt/FcWAFCKF\nCwNz5mgnIb+aPVsW3Hm9E8zZChUCWreWcw+Rhn37gKQk93WC8UVhXLQo0LQpV8iTHr9NowjgFtGk\nzctbQGeH0ylI08iRwG23SQ3mJr4ojAG2ryE9K1bIJ+f4eO0kOrp0kWlMx45pJyG/OXZMujN06aKd\nREd8PLBnD7BypXYS8iO3tif1TWHcpInMc9ywQTsJ+c3gwbIi169zbC+5BLj+eq6Qp9gbPx644Qbp\n0uBHuXPLuYd3bCjW1q+XrkRNmmgnCZ1vLtV588qqZJ4gKJbS0oChQ/05jeJMnE5BGvywBXR2unWT\nc1BamnYS8pPBg93bCcY3hTHALaIp9mbOlBHTqlW1k+hq00YW4O3apZ2E/OKPP4C5c+Vnz8+uvFJ6\npyclaSchv0hPd9cW0GfzVWF87bVAwYJysiSKBbfOsYq0woWBVq24Qp5iZ/hw6crgt04wGeEaG4ql\nuXPlnO/W9oi+Koy5RTTF0qFDwKRJQMeO2kmcgdMpKJY4jeK0jh2BiROBw4e1k5AfuG0L6LP5qjAG\nTq+QP35cOwl53dixQP36wEUXaSdxhgYNZCrFL79oJyGvW7UK2L3bv51gzlayJFCvnpyTiKLp2DGp\nsdzcCcZ3hXGZMjKlYuJE7STkdX7tXZyZ3LnlZMlRY4q2wYPlZy13bu0kzsG7pRQLEydKF6JLLtFO\nEr5sC2NjzEBjzC5jzIpsHneDMSbFGNM2cvGig/OtKNp++022Q27RQjuJs3TrBgwZwhXyFD1pafIz\nxg+l/9SiBbBkCbB9u3YS8jIvDAgFM2L8NYCmWT3AGJMLwJsApkUiVLS1bSvbhP75p3YS8qqhQ2Ur\n8vPO007iLFdfDcTFAbNmaSchr0pKki4MV12lncRZChSQrXmHDtVOQl7155/e6ASTbWFsrZ0DYH82\nD3sCwGgArig1CxeWT88jRmgnIS+y1hufmqOFi/AomrjoLnOB6RRsWUrRMHy41FaFC2snyZkczzE2\nxlwMoLW19jMArlmDGOhpTBRpS5bI4s6bb9ZO4kydOsmOZFwhT5F2+LDssNipk3YSZ7r5ZlkctXSp\ndhLyIjf3Lj5TJPYk+QDAC2d8nWVx3Lt3779/Hx8fj3ilZcMNGwI7dwKrV0sDdKJICfQudmurmmiL\niwPq1gUSEjiyR5E1dqx0XyhZUjuJM+XKdXrUuGZN7TTkJb/8IpvqNGigmyM5ORnJyck5eg5jg7in\nYowpB2CitbZ6Bt/bFPgtgAsBHAHwkLV2QgaPtcG8Xqz07Cmrlt94QzsJeUVKiqzGnTcPqFhRO41z\njRwJDBwIJCZqJyEvadwY6N4daN9eO4lzrV8vH0y3bwfy5tVOQ17x4osyReett7ST/JMxBtbakIap\ngp1KYZDJSLC19vJTvy6DzDN+NKOi2InuvltWL6enaychr5g2DahUiUVxdlq2BBYvBnbs0E5CXrF9\nO/Dzz+wEk51KlYAKFfihlCInLU0WdXrlDmAw7dqGAZgHoLIxZpsx5j5jzMPGmIcyeLhzhoODcPXV\nsvlCDkfdif7GLaCDU6AAcOedXCFPkTN0qHRdKFBAO4nzsWUpRVJyskxfuvpq7SSREdRUioi9mMOm\nUgDA++8Dy5cD33yjnYTc7sABoFw5YMsWoFgx7TTO9+OPwKOPAitXcj425Yy1clHu31/mGFPW9u0D\nLrsM2LoVuOAC7TTkdvfcIxun/etf2knOFc2pFJ4VWCF/5Ih2EnK7UaNkjiOL4uDUrSvH3bJl2knI\n7ZYulW4L7AQTnOLFgVtvBUaP1k5CbnfkiPc6wfi+MC5VCrjpJmDcOO0k5HacRhGaM1fIE+VEoG94\nLt9f0YLH6RQUCQkJ8oE0Lk47SeT4fioFIBt9fPMNMHWqdhJyq02bgNq1ZTFZvnzaadxj/Xq59b19\nO5AnEs0jyXdSUoAyZYA5c2RhGQXn5EnpoLNwoUyrIApH06bA/fcDHTpoJ8kYp1KEqVUrOTns3Kmd\nhNxqyBCgY0cWxaGqVEkuylwhT+FKTJQuCyyKQ5MvnxQzQ4ZoJyG32rEDWLRIugx5CQtjyCrmNm2A\nYcO0k5AbcQvonOF0CsoJHnvh4xbRlBPDhgFt23qvEwwL41O4RTSFa/58mQZwww3aSdypQwfg+++B\nv/7STkJuc+CATIFz6m1cp6tVS+ZlL1ignYTcJjAg5MV1NSyMT6lXTy7My5drJyG34RbQOVOiBNCo\nEVfIU+hGj5buCsWLaydxJ2O4CI/Cs3w5cPiwdBfyGhbGp+TKBXTtyhMEhebECeC774AuXbSTuFu3\nbrxjQ6EbPJjTKHKqSxc5h504oZ2E3GTQIKmZvNgJxoP/pPB16yZzZlJTtZOQW0yeDFSvLht7UPju\nuANYtUo2RyEKxubNwOrV8rND4StfXjZHmTJFOwm5RWoqMHy4dz+UsjA+Q5UqUuDMmKGdhNzCq3Os\nYi1/fq6Qp9AMGSI/M+wEk3OcTkGhmD5dPlBVrqydJDpYGJ+FK+QpWHv2ALNmAXfeqZ3EGwLTKbhC\nnrJjLadRRFK7dkBSErB3r3YScgOvd4JhYXyWDh3kltLBg9pJyOlGjgSaNQOKFNFO4g21a0vBs3Ch\ndhJyugULZOFYrVraSbyhSBGZkjJypHYScrqDB6WLkJc7wbAwPsuFFwINGgBjxmgnIafjNIrI4gp5\nChY7wUQejz0KxujRQMOG0k3Iq1gYZ4Ar5Ck7a9cC27ZJqyiKnK5dZYX8yZPaScip2AkmOho3lsWv\n69ZpJyEn88MUJhbGGWjWDFixAti6VTsJOdXgwUDnzrKxB0VO+fLAlVdyhTxlbsoU6aJQvrx2Em/J\nk0fOaRwUosxs3QqsXOn9TjAsjDOQPz9w113A0KHaSciJ0tP98alZCxfAUla8vvBHU+BuaXq6dhJy\noiFDgPbtpUbyMhbGmQhsEc0V8nS22bOBokWBGjW0k3jTXXcBM2cC+/ZpJyGn2btXuie0a6edxJuu\nuQY4/3yHLTgGAAAgAElEQVRgzhztJOQ0gU4wflhXw8I4EzfeKE2sFy3STkJO8+23XPgTTUWLArff\nDowYoZ2EnGbECLmNW7SodhJvCiyA/fZb7STkNAsXyp2E2rW1k0SfsTEcEjXG2Fi+Xk717QssXcop\nFXTarl3AFVcAv/4KxMVpp/GuWbOARx6Rnc28uOUohS49HahaFejfXzoHUXQEznFr1wIlS2qnIafo\n3BmoWRN47jntJKExxsBaG9IwFi85WXjoIWDaNG5TS6f16wd07MiiONri4+WW7sSJ2knIKSZMkJHi\n+HjtJN4WFyc9avv1005CTrF5M5CYKDWRH3DEOBsvvggcOwZ8+KF2EtJ2+DBw2WXATz8BFStqp/G+\nUaOADz4A5s7VTkLarAVuugl49lnOL46F9evl//fmzUDhwtppSNuTTwKFCgFvvKGdJHThjBizMM7G\nzp3SGmj9em83tKbsffihLEoZNUo7iT+kpQGVK0sXgptv1k5DmubMAe69V27v586tncYf2rUD6teX\nooj8a88eOQ//8gtQurR2mtBxKkUUXHwx0LYt8Mkn2klIU0oK8N57QM+e2kn8I3dumc/21lvaSUjb\nW2/JzwKL4th5/nng3Xfl3Ef+9cknwJ13urMoDhdHjIPw66/ALbfIbaWCBbXTkIYhQ4CBA2VRGMXO\nsWMyfSUpSTb+IP/55RegUSM5/xYooJ3GX+Ljge7ducugXx09KhvpzJ4NVKminSY8HDGOkiuukPlW\n33yjnYQ0WAu8/TbwwgvaSfynQAHg8celQwz5U9++wBNPsCjW8MILcu5z4XgWRcDXXwN167q3KA4X\nR4yD9NNPQNeuMseN2wD7y9SpMoVi+XL2Ltawb58sdly5ErjkEu00FEvbtwPVqwMbNgDFi2un8R9r\n5f9/375A06baaSiWUlNlbvGwYbKvg1txxDiK6tSR+cZjxmgnoVh76y0pjFkU6yheXDYdYGcY//nw\nQ+Cee1gUazFGzn2c5+8/o0cDZcq4uygOF0eMQzBxItC7N7B4MYskv1i4ULYo3rAByJtXO41/bd0q\nzeU3bgQuuEA7DcXCgQNAhQqyyVLZstpp/CslRd6HMWOAG27QTkOxYC1w3XXAa68BzZtrp8kZjhhH\nWbNmwPHjshCI/OGdd4BnnmFRrK1cOdkK+PPPtZNQrPTvL+dcFsW68uaVc+A772gnoViZORM4eVLO\nuX7EEeMQffMNMHy47IhH3rZhg0yhYZN7Z1ixArjtNnk/8ufXTkPRdPw4cPnlcp6tVk07DXFzI39p\n0kQ6kdxzj3aSnOOIcQx07iztg5Yu1U5C0da3L9CjB4tip6heHahRQ1rnkbcNGQJccw2LYqcoXBh4\n+GHpa0zetmQJsGYN0KmTdhI9HDEOQ9++8sMzbJh2EoqWXbukTd/atUDJktppKGDWLOCRR4DVq4Fc\n/FjvSenpQNWqMm0mPl47DQUEzom//grExWmnoWjp1Am4/nrZft0LOGIcIw89BCQmyi1d8qaPPwY6\ndmRR7DTx8cD55wMTJmgnoWgZPx4oWlQ2VSLniIsDOnQA+vXTTkLRsnkzMH26bOriZxwxDtO//w0c\nOQJ89JF2Eoo0zqdzttGj5ZbuvHnsDuM11sq8/uefl21oyVm47sLbnnhCBh5ef107SeRwxDiGnnxS\n5sHt2aOdhCJtwACgQQMWxU7Vpg2wezcwd652Eoq0OXOAvXuB1q21k1BGKlaUuzYDB2onoUjbswcY\nOlRqG79jYRym0qVlROOTT7STUCSlpADvvScjVuRMuXMDzz0nW9WSt7z9try3uXNrJ6HM9Owp58iU\nFO0kFEn9+gHt2gGlSmkn0cepFDmwdi1Qrx6wZQtQsKB2GoqEwYNlf3j2qna2Y8dkuktSEnDlldpp\nKBJ++QVo1Ehu0xcooJ2GstKgAfDAA0DXrtpJKBKOHJHz6ezZQJUq2mkiKypTKYwxA40xu4wxKzL5\nfktjzHJjzFJjzGJjTMNQArhZlSrAzTdLIUXuZ62MWPXsqZ2EslOgAPD449x0wEveeUfmOLIodr6e\nPeVc6aFxLl/7+mugbl3vFcXhynbE2BhTF8BhAIOstdUz+H5Ba+3RU7+vBiDBWpvh7EyvjRgDwPz5\n0tt43TogTx7tNJQT338PvPgisGwZF3W5wb59MudxxQqgTBntNJQT27dLn+qNG4FixbTTUHaslZ7i\nb78tm+6Qe6WmApUqASNGALVra6eJvKiMGFtr5wDYn8X3j57xZWEAvlqOduONclEePVo7CeXUW2/J\nSAiLYncoXlx2ZvrwQ+0klFMffADcey+LYrcwRs6Vb72lnYRyatQo2Xbdi0VxuIKaY2yMKQdgYkYj\nxqe+3xrAGwBKAWhqrV2YyeM8N2IMAJMmAf/9L/Dzzyyq3GrBAqB9e2lHlDevdhoK1rZtwLXXykjj\nBRdop6FwHDgg2z8vWyYXaHKHlBSgQgUZFKpVSzsNhcNaoGZNoE8foFkz7TTRodauzVo7zlpbFUAL\nAIMj8ZxucscdwIkTwMyZ2kkoXO+8AzzzDItitylbVo6/zz/XTkLh6t8faN6cRbHb5M0r50zO83ev\nGTPkA87tt2sncZaIjBif9diNAGpZa/dm8D3bq1evv7+Oj49HvEf2/Pz2W+kBmJionYRCtX49cNNN\n0l2kUCHtNBSqlSuBpk2BTZuA887TTkOhOH5cVsMnJgLVqmmnoVBxMyR3a9wY6NYNuPtu7SSRk5yc\njOTk5L+/fvXVV0MeMQ62MC4PKYzPOXUZYypYazee+n1NAKOstRUyeR5PTqUAgJMn5bbShAlya5fc\no0cP2fr5tde0k1C47rgDaNsWePBB7SQUii+/BMaNAyZP1k5C4XrlFdkc4rPPtJNQKJYsAVq1kmlo\n+fJpp4mecKZSBNOVYhiAeAAlAOwC0AtAPgDWWvuFMaYngLsBnARwBMDT1trFmTyXZwtjQLapXbwY\nGD5cOwkF648/gKpVpSd1yZLaaShcycnAww8Da9YAubhtkSukpUkP6i++AG65RTsNhevPP6XN16+/\nAnFx2mkoWB07ytzwZ57RThJdUSmMI8nrhfHBg7KIZNEiub1Ezvfyy8D+/cCnn2onoZywVlZVv/QS\ntxN2i4QE4M03peUlFy272yOPACVKyCIucr5Nm6Qo3rwZOP987TTRxcLYAf79b5l39fHH2kkoO4cO\nyQeYBQtkGgy52+jRctdm3jwWWk5nLVCnjmy9fued2mkopzZskPdz82agcGHtNJSdxx8HihQBXn9d\nO0n0qXWloNOeekoW4e3xVTdndxowQLagZVHsDW3ayHE3Z452EsrO7NmyQQtH972hYkXZJnrAAO0k\nlJ3du4Fhw4Ann9RO4lwsjCOsVCmgXTugXz/tJJSVkyeB996TESvyhty5geeek924yNneflveq9y5\ntZNQpDz/vJxTU1K0k1BW+vUD7rpLahXKGKdSRMHatUC9enJbie2/nGnQIGmxx97T3nLsmEyPmTkT\nuOoq7TSUkVWrpE3U5s1sr+c1DRsC990nLcDIeY4ckfPjnDlA5craaWKDUykcokoVoG5d4OuvtZNQ\nRqyVEauePbWTUKQVKAA88QTQt692EspM377yHrEo9p6ePeXc6oPxL1f66isZtPNLURwujhhHyYIF\n0g5l/XogTx7tNHSmKVOke8HSpVyk5UX798u88RUrgDJltNPQmbZvB2rUkMVaxYppp6FIsxa45hrp\nNsLd1JwlNVXmgo8cKR18/IIjxg5Su7ZscTp6tHYSOltgtJhFsTcVKwbcey/wwQfaSehs778v7w2L\nYm8y5vSoMTnLqFFA+fL+KorDxRHjKJo8GfjPf2SHGRZhzrBgAdChg4zk582rnYaiZds2GbnatAm4\n4ALtNAScHslfvhy49FLtNBQtKSkyMjlqlPTKJX3Wyo68r78uu4T6CUeMHeb22+UkMWOGdhIKePtt\n2emHRbG3lS0LNG8O9O+vnYQC+vcHWrRgUex1efPKOZajxs4xfbrsNMnpLcHhiHGUffstMGSI/GCS\nrnXrgJtvBrZsYbcQP1i5EmjaVEaNudBL1/Hjshp++nTg6qu101C0HT4s7/e8eUClStpp6NZbgbvv\nll9+wxFjB+rUSfaQX7JEOwm9+y7w6KMsiv2iWjW5fTh4sHYSGjQIuO46FsV+UbiwbBP97rvaSejn\nn2VQqFMn7STuwRHjGHjvPWDhQmDECO0k/vXHH0DVqnKCuOgi7TQUKz/8ADz0ELB6NTeT0JKWJsfe\ngAFA/fraaShW/vxTWpeuWcPNJDR16ADceCPw9NPaSXRwxNihuneXecabNmkn8a+PPgI6d2ZR7Df1\n68viuwkTtJP41/jxQPHi0j+V/KNkSRml/Phj7ST+tXGjbHb04IPaSdyFI8Yx8tJLwMGD3Cpaw6FD\nMt9twQJZFU/+MmYM8M47wE8/sTtMrFkro1UvvAC0baudhmJtwwZ5/zdvBs4/XzuN/zz2mAwM/O9/\n2kn0cMTYwZ58Ehg2DNi9WzuJ/3z5JdCoEYtiv2rdGti3T7ZBpdiaPVvatLVqpZ2ENFSsKNtEDxig\nncR/du8Ghg+X2oNCwxHjGHr4YaB0aaB3b+0k/nHypBTE48bJ4h/ypy++kOkUkyZpJ/GXZs3kg0n3\n7tpJSMvixXK3YONGtsmMpV69gF272LIynBFjFsYxtG4dULeu3FZiZ4TY+PZb6UrAXtL+xnZhsbdy\nJdCkiZzv2C7P3xo1Au65x5/twjQcOSLnu7lz2S6PUykcrnJlWYDy1VfaSfwhPV3mlvbsqZ2EtJ13\nHvDEE0DfvtpJ/KNvX7mNy6KYAttE+3hcLKYGDpSFx34visPFEeMYC2xJvGEDkCePdhpv45bcdKb9\n+2XO4/LlQJky2mm87bffZEvuDRuAYsW005A2P29JHGspKVIQf/cdt+QGOGLsCrVrA+XLyz7yFF1v\nvy0jFSyKCZAC7d57gQ8+0E7ifR98ANx3H4tiEsacHjWm6Bo1SqZRsCgOH0eMFUyfDvToAaxYwbnG\n0TJ+PPD887KxA0fmKWD7dhnJnD9fRo8p8tavB+rUAZYt48g8nZaaKhu9vPsu0LKldhpvOnIEqF4d\n+Pxz2QaauPjOVbp0kd2AuGVm5B04AFx1lbTHu+UW7TTkNO+9Jx0qkpKAXLxnFlHp6UCDBtKJwq87\nbVHmkpOBrl2BVaukvy5F1jPPyI6DQ4ZoJ3EOFsYusmcPUK2atBGrXVs7jbc8+KC0BfrsM+0k5ERp\nacDNN8u0ih49tNN4y2efAYMGSc9obsFNGenRQ47BL7/UTuIt8+cDbdpIN5gLL9RO4xwsjF1m5Ejg\ntddkcVj+/NppvGHGDOD++2VEokgR7TTkVL/8AsTHy7F36aXaabxh2zbpFf7DD8CVV2qnIac6eFBa\nJn79tbRxo5w7cUIWN/buDbRvr53GWbj4zmXat5fVo37erjGSDh+WjQQ+/5xFMWXtqquklViPHmwh\nFQnWyv/Lp55iUUxZK1JENp3o3l3mxFLO9ekDVKkC3HWXdhJv4Iixsp07ZTHQ9OlAjRraadztqaek\nJdegQdpJyA1SUoDrr5dFml27aqdxt8GDZb3EokXc3YyC060bUKIEu8Tk1PLlQOPG8t/SpbXTOA+n\nUrjUV18Bn34qc4TYQSE8c+fKp+WVK+VkSxSMn3+WvqorVgBxcdpp3GnXLlkJ//33QM2a2mnILfbu\nlSkVY8YAN92kncadUlNljdLjj0t7RDoXp1K41H33AcWLs0NFuI4fBx54APjoIxbFFJrrrpPj74kn\ntJO41+OPy7x+FsUUihIl5Jz9wANyDqfQ9e0rC+3uvVc7ibdwxNghtmwBbrhBVnNXqaKdxl1eeglY\nu1ZGHohCdeyYTGd6801Z1U3BGztWjr9ly7j1M4XOWuDOO6W/MdfahGbtWqBuXZm+VL68dhrn4lQK\nl/v4Y9nG8Ycf2F81WEuWALfdJrfCS5XSTkNuNWeObNW+ahV3awvWvn1yK3zUKGl/RxSO33+X9TXT\npklnBcpeejpQvz7QsaPcsaHMcSqFyz32mHyC/vRT7STukJIit+HeeYdFMeVM3bpA27bSIJ+C88wz\nQLt2LIopZ0qXlq2i779fzumUvU8+kW22H31UO4k3ccTYYXh7JHj/+5+M9E2ZIicJopw4fFg23enf\nH2jaVDuNs02dCjzyiCx2LVxYOw25nbXA7bfLKOhLL2mncTZOuwwNp1J4xJtvyna106ax4MvM6tVy\nEl2yBChbVjsNeUViIvDQQ1LwnX++dhpnOnRIplAMGCBtoogiYetWWQw7e7bMOaZzWQs0aQLceivw\nwgvaadyBhbFHsAVL1tLSZFS9WzfeSqLIu/9+oGBBoF8/7STO9Nhj0kVg4EDtJOQ1n3wCDB0qxTG3\nFD8XW7uGjoWxh7Bpd+Y++EBWwycnc5EiRd7+/TIiOmIEUK+edhpn+fFHoFMn2VL7ggu005DXpKcD\nt9wic9efeko7jbNwM7DwsDD2mP/8Ry5AY8dySkXApk1ArVrAvHlA5craacirEhLkVuXy5UCBAtpp\nnOHYMbkgv/020Lq1dhryqrVrZUHnokXAZZdpp3EGa6WVZLVqwP/9n3Yad2FXCo955RU5SYwerZ3E\nGayV+Z89e7Iopuhq00ZGZ3r31k7iHL16STstFsUUTVWqyDbtDz0k53ySlojr18tgGUUfR4wd7qef\npI3UqlXc1W3AAOkYwPlVFAuBrY4nTwauv147ja5Fi4DmzWVRYsmS2mnI61JTgRtvlM4nDzygnUbX\nnj0yUpyQIP9PKDRRmUphjBkIoDmAXdba6hl8vzOAwPrIQwAesdauzOS5WBiH4emn5eAYPFg7iZ4d\nO2QEb+ZMKVaIYmHIEJk6sHgxkC+fdhodJ0/KB4MXXgC6dNFOQ36xfLl0X1i+HLj4Yu00erp2lQ+j\n772nncSdojWV4msAWXX13ASgvrW2BoA+AL4MJQBlr08fmVM7ZYp2Eh3WysjBo4+yKKbY6tIFuPRS\naaHoV2+8IS0RO3fWTkJ+UqOGnPcfecS/UyomT5a7xn36aCfxl6CmUhhjygGYmNGI8VmPuwDASmvt\npZl8nyPGYUpKAu69V6ZUFCminSa2hg+XzTyWLPHvqB3p+e03oGZNYNYs6VbhJ6tWAQ0aAEuXAmXK\naKchvzlxQo69V16R7Y/95K+/5HwzaJAcgxSeqHWlCKEwfg5AZWvtQ5l8n4VxDjz0kLQn699fO0ns\n7N4t86smTJBuFEQaPv9ceojOm+ef/qqpqcBNNwEPPijnHiINCxYArVrJ/PaLLtJOEzsPPyz//fxz\n3Rxup1oYG2MaAOgHoK61dn8mj7G9evX6++v4+HjEx8eHktfXAp8gBw8G/PK/rVMn4JJLgL59tZOQ\nn6WnA40ayQK0Z5/VThMbffvK9K2ZM9kuknQ9+yzw++/AsGHaSWJj1izgnnvkw0DRotpp3CU5ORnJ\nycl/f/3qq6/qFMbGmOoAxgC4zVq7MYvn4YhxDk2aBPzrX8CKFbI7l5dNmCAnxOXLvf9vJefbuFF2\npJw/H6hYUTtNdK1fD9SpI6N1FSpopyG/O3pU1pe8/z7QooV2mug6ckT+rR99BDRrpp3G/aLZx9ic\n+pXRi5aFFMXdsiqKKTKaN5eL8yuvaCeJrgMHZLHdgAEsiskZKlQAXnpJphakp2uniZ70dPk3vvwy\ni2JyhoIF5VrwyCNybfCyV16RD6UsivUE065tGIB4ACUA7ALQC0A+ANZa+4Ux5ksAbQFshRTPKdba\nDGeDcsQ4MgJ9DceNkyLZix58EMibF/jsM+0kRKelpcmuXPfeC/TooZ0mOj77TBb8zJnjn/nU5A49\nesgx+KVHe1/Nny+bC61cCVx4oXYab+CW0D4yciTw2mvSqSF/fu00kTVjBnD//f7swEHO98svMsd/\nyRJp5eYl27YB110H/PADcOWV2mmI/ungQVln8/XXMuffS06ckJ0le/cG2rfXTuMd3BLaR9q3BypV\nkjZmXnL4MNC9u6zEZVFMTnTVVcCTT8rolZc+51sr/6annmJRTM5UpIh0ZereXebiekmfPrId9l13\naSchjhi72M6dshvc9OnSDN0LnnoK2L9fbuUSOdXJk8ANNwDPPy87U3nB4MHSiWLxYpnGRORU3boB\nJUoAH3ygnSQyli8HGjeW/5YurZ3GWziVwocGDpQ5gfPnA3nyaKfJmXnzgDvvlCkUJUpopyHK2uLF\nskBmxQogLk47Tc7s2iUr4adMkakURE4WWGczdqwsVHOz1FRZK/TYYzKFkCKLUyl86P77gWLF3L+P\n+vHjwAMPSIsaFsXkBtdfL4vwnnhCO0nOPf44cN99LIrJHS68EPjwQ7lmnDihnSZn3n1Xrnn33aed\nhAI4YuwBmzfLbd1584DKlbXThOfll4E1a4AxY7iZALnHsWMyjemtt2Q1uRuNHQv8+9/AsmVAgQLa\naYiCYy3Qtq3M+e/TRztNeNauBerWBRYtAsqX107jTZxK4WMffQSMGiWryXO57D7A0qVA06acX0Xu\nNHs20LGjTAEqVkw7TWj27ZNV/t99JxdoIjf5/Xf5YJqYKOtt3CQ9HbjlFllI74W7Tk7FqRQ+9vjj\ncqC5re9vSopMB3n7bRbF5E716slo8TPPaCcJ3TPPyLx+FsXkRqVLy92a+++Xa4mbfPqpjHo/9ph2\nEjobR4w95Ndf5SI9dqz81+nS02V3u61bZdEPp1CQWx06JCNXL74IPPSQdprgfP65FBUrVgCFC2un\nIQqPtcAddwDlykmx6YY7pj/+KB9I58yRFm0UPRwx9rkrrgCGD5d5V7NmaafJWno68PDDcvt55EgW\nxeRu558vt3P79JGLs9N98gnw+uuSmUUxuZkxcg1ZuVL6cDt9u/akJCmKR4xgUexUHDH2oORkaRI+\nfDhw663aac6VliZbPm/aBEyezAszecemTUDDhjJF4ckntdNk7MMPpf9rUhJw2WXaaYgi49AhaZ9Y\nsaJsGe3E7cynTwc6d5b1QPHx2mn8gYvv6G+zZ8un0sGDZWGbU6SmSluanTuBCROAQoW0ExFF1tat\nUhw/+ijw7LPaaf6pb19Zh5CUJLeeibzkyBGgRQugTBnZNtpJxfHUqcDdd8tUR87pjx0WxvQP8+YB\nrVvLCaJZM+00UhR36wbs3QuMGwcULKidiCg6fvtNiuMHHpB5x07wxhvAV1/JNKsyZbTTEEXH0aNy\n3StRQgaGnLDx1aRJskBw/Hj3b0jiNiyM6RwLF8on6C++AFq10suRkgJ06iQnrbFjgfPO08tCFAs7\ndgCNGgFdugCvvKKb5bXXgGHDZKT44ot1sxBF2/Hj0immcGH5udfc4nzcOFlPM3EiUKuWXg6/4uI7\nOketWtLx4eGHZfMMDSdPSq/GkyeBhAQWxeQPl1wi8/2HDwd69ZLV87FmLfDf/8ripORkFsXkD+ed\nJwXp8eNAhw5y7dEwerQsCPz+exbFbsLC2Aeuu07mNz3+uFwgY+nECZnrbIycJPLnj+3rE2kqVUoK\n0rFjZXfHWBbH1gIvvSQFwqxZkoXIL/Lnl8Ega4F27WK/dfTIkbJxx7RpQM2asX1tyhkWxj5xzTXS\nmunpp4GhQ2PzmseOyVyvAgXkJJEvX2xel8hJSpaUwnTKFKBnz9gUx9YCzz8vH4iTkiQDkd/kyye7\nOubPL1Mrjh+PzesOGSLX2sRE6W9O7sLC2EeqVQNmzJCL87ffRve1jh4FWraULXK153gRabvwQilQ\nk5LkghnN4thaeY3kZGDmTHltIr/Km1emMxUtKteko0ej+3rffAO88IJca6tVi+5rUXSwMPaZK6+U\ni+XLLwMDB0bnNY4cAZo3l+06nbIqmEhb8eJysZw3T26xRmMjgvR0mTL100/yWsWLR/41iNwmTx65\nFsXFybXpyJHovM6AAbLQNilJrrXkTiyMfeiKK+TW7quvAv37R/a5Dx0Cbr9deqQ6rY8kkbZixaTJ\n/88/A488EtniOD1dFvosXSq3cC+4IHLPTeR2efLIaG65crKF9KFDkX3+zz6T7i9JSdzRzu1YGPtU\npUpSHL/5JtCvX2Se8+BB4LbbpPAeOJBFMVFGihaVBTm//AJ07y47QeZUYDfJNWvkuYsWzflzEnlN\n7txybapcWa5VBw9G5nk//hh46y2ZvlSpUmSek/SwMPaxChXkQH7vPeD993P2XAcOAI0by0KD/v2B\nXPzJIspUkSKyMG7jRtkJMifFcVqaPMfmzdIW6vzzI5eTyGty5QI+/1yuVU2ayLUrJwLXz+Rk4PLL\nIxKRlLF88bny5eWA/uQT4O23w3uOffuAW28FbrxRnodFMVH2ChcGJk+WjUDuvlt2hgxVYDfJnTvl\nuQoXjnxOIq/JlUuuVbVqyYDO/v3hPc9bbwGffgr88INcS8kbWMIQypaVA3vAAOB//wvt7+7dK7t7\nxccDH3wg/YqJKDiFCsl2sXv2yA55KSnB/92UFKBzZ/lgOnEit1gnCoUxwIcfAvXryzVs797Q/n6f\nPrLF+g8/AJdeGp2MpIOFMQGQXbp++EH6L776anDtpHbvBho0kLla77zDopgoHAUKAOPHA4cPAx07\nBrdL18mTsqPX0aOygUeBAtHPSeQ1xgB9+8qUioYN5ZqWHWuB3r2lDWlyslw7yVtYGNPfSpeWA33U\nKGk5k1VxvGuXFMWtWwOvv86imCgnzjtPdsdLTQXuuivrXbpOnJCdvNLTZWcvbrFOFD5jgDfekB7H\nDRrItS0z1gL/+Y8cd7NmyTWTvIeFMf1DXJwc8BMnAi++mHFx/PvvMnWifXtpT8OimCjn8ueXD6V5\n8gBt22a8S9fx4/K9vHlP7+hFRDljDPB//yfXtPh4ucadzVrZuGPSJGnJFhcX85gUIyyM6RwXXSQH\n/vTpwLPP/rM43r4duOUWWSz03//qZSTyonz5gBEjZO5xq1ayrXrAsWPyZ4ULy2O4xTpRZP33v7KY\nNSplv5sAAAWwSURBVD5eFsUGWAs884xsmpOUJNdI8i5jo7k36dkvZoyN5etRzuzfL3Ov6tSRRQq/\n/SbzsHr0AJ57TjsdkXelpgL33CO3dSdMkAtzy5ZAqVKynTt3kySKnnfekZZuSUmysO7JJ4EFC6RH\neLFi2ukoFMYYWGtDuq/NwpiydOCALK6rUgWYPVtOEP/6l3YqIu9LSwPuvx/YulW+Ll+eG+cQxcr7\n78vGHfXqAevWSd9xbpzjPiyMKSoOHpTV8s2bA48+qp2GyD/S0uTDKCAXafYIJ4qdTz+V/uDDh8um\nPOQ+LIyJiIiIiBBeYczxByIiIiIisDAmIiIiIgLAwpiIiIiICAALYyIiIiIiACyMiYiIiIgAsDAm\nIiIiIgIQRGFsjBlojNlljFmRyferGGPmGWOOG2OeiXxEIiIiIqLoC2bE+GsATbP4/l4ATwB4JyKJ\nyLGSk5O1I1CY+N65G98/d+P751587/wn28LYWjsHwP4svr/HWvszgNRIBiPn4QnCvfjeuRvfP3fj\n++defO/8h3OMiYiIiIjAwpiIiIiICABgrLXZP8iYcgAmWmurZ/GYXgAOWWvfy+Ix2b8YEREREVEE\nWGtNKI/PE+TjzKlfwTwuU6GGIyIiIiKKlWxHjI0xwwDEAygBYBeAXgDyAbDW2i+MMXEAFgM4H0A6\ngMMArrTWHo5ibiIiIiKiiApqKgURERERkdfFZPGdMaadMWaVMSbNGFPzjD8vZ4w5aoxZcurXp7HI\nQ8HL7L079b1/G2PWG2PWGGOaaGWk4Bhjehljtp9xvN2mnYmyZoy5zRjzqzFmnTHmBe08FBpjzBZj\nzHJjzFJjzELtPJS1jDY0M8YUM8YkGmPWGmOmGWOKamakzGXy/oV83YtVV4qVANoA+CGD722w1tY8\n9evRGOWh4GX43hljqgJoD6AqgNsBfGqM4Rxy53vvjONtqnYYypwxJheAfpANlq4C0MkYc4VuKgpR\nOoB4a+211tpa2mEoWxltaPYigBnW2ioAkgD8O+apKFiZbUgX0nUvJoWxtXattXY9Ml6cx2LKwbJ4\n71oBGGGtTbXWbgGwHgBP/M7H4809agFYb63daq1NATACctyRexiwLaprZLKhWSsA3576/bcAWsc0\nFAUtiw3pQrruOeGALX9qeHuWMaaudhgK2iUAfjvj6x2n/oyc7XFjzDJjzADeEnS8s4+x7eAx5jYW\nwHRjzCJjTHftMBSWktbaXQBgrf0DQEnlPBS6kK57wbZry5YxZjqAuDP/CHJSeNlaOzGTv7YTQFlr\n7f5T81fHGWPY0SLGwnzvyIGyei8BfArgNWutNcb0AfAegAdin5LIN2621v5ujLkIUiCvOTWqRe7F\njgXuEvJ1L2KFsbW2cRh/JwWnhr2ttUuMMRsBVAawJFK5KHvhvHeQEeJLz/i6zKk/I0UhvJdfAuCH\nHmfbAaDsGV/zGHMZa+3vp/672xiTAJkew8LYXXYZY+KstbuMMaUA/KkdiIJnrd19xpdBXfc0plL8\nPdfDGHPhqQUmMMZcDqAigE0KmSg4Z87TmQCgozEmnzHmMsh7x1XXDnbqpB7QFsAqrSwUlEUAKp7q\n3pMPQEfIcUcuYIwpaIwpfOr3hQA0AY85Nzh7Q7MJAO499ft7AIyPdSAKyT/ev3CuexEbMc6KMaY1\ngI8BXAhgkjFmmbX2dgD1AbxmjDkJWb37sLX2QCwyUXAye++stauNMd8BWA0gBcCjlk2xne5tY8w1\nkGNtC4CHdeNQVqy1acaYxwEkQgYxBlpr1yjHouDFAUgwxljItXaotTZRORNl4cwNzYwx2yAbmr0J\nYJQx5n4AWyHdmMiBMnn/GoR63eMGH0REREREcEZXCiIiIiIidSyMiYiIiIjAwpiIiIiICAALYyIi\nIiIiACyMiYiIiIgAsDAmIiIiIgLAwpiIiIiICAALYyIiIiIiAMD/A6qc5MTWBlVSAAAAAElFTkSu\nQmCC\n", + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('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 = $('
');\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", + " 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", + " this.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", + " '
');\n", + " var titletext = $(\n", + " '
');\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 = $('
');\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 = $('');\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 rubberband = $('');\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);\n", + " canvas.attr('height', height);\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 = $('
')\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 = $('');\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 = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var 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", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\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= 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": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" ] }, "metadata": {}, @@ -560,16 +5084,27 @@ } ], "source": [ - "data3.sync()\n", - "fig = plt.figure(figsize=(12, 5))\n", - "ax = fig.add_subplot(1,1,1)\n", - "ax.plot(data3.chan1, data3.avg_amplitude)\n", - "plt.show()" + "data3 = qc.Loop(c1[-15:15:1], 0.1).each(\n", + " qc.Task(c0.set, -10),\n", + " qc.Task(c2.set, 0),\n", + " # a 1D measurement\n", + " meter.amplitude,\n", + " # a 2D sweep, .each is actually unnecessary bcs this is the default measurement\n", + " qc.Loop(c0[-15:15:1], 0.001).each(meter.amplitude),\n", + " qc.Task(c0.set, -10),\n", + " # a 2D sweep with the same outer but different inner loop\n", + " qc.Loop(c2[-10:10:0.2], 0.001),\n", + " AverageGetter(meter.amplitude, c2[-10:10:0.2], 0.001)\n", + ").run(location='test_multi_d')\n", + "\n", + "plot3 = qc.Plot(x=data3.chan0, y=data3.chan1, z=data3.amplitude_3_0, cmap=plt.cm.hot)\n", + "plot3b = qc.Plot(x=data3.chan2, y=data3.chan1, z=data3.amplitude_5_0, cmap=plt.cm.hot)\n", + "plot3c = qc.Plot(x=data3.chan1, y=data3.avg_amplitude)" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 11, "metadata": { "collapsed": false, "scrolled": false @@ -579,63 +5114,1561 @@ "name": "stdout", "output_type": "stream", "text": [ - "DataSet: DataMode.PUSH_TO_SERVER, location='test_complex_param'\n", + "DataSet: DataMode.PULL_FROM_SERVER, location='test_complex_param'\n", + " chan2: chan2\n", " avg_amplitude: avg_amplitude\n", - " amplitude: amplitude\n", " chan1: chan1\n", - " chan2: chan2\n", - "started at 2016-01-08 15:50:32\n" + " amplitude: amplitude\n", + "started at 2016-01-13 10:26:07\n" ] - } - ], - "source": [ - "# An example of a parameter that returns several values of different dimension\n", - "# This produces the last two arrays from data3, but only takes the data once.\n", - "data4 = q.Loop(c1[-15:15:1], 0.1).each(\n", - " AverageAndRaw(meter.amplitude, c2[-10:10:0.2], 0.001)\n", - ").run(location='test_complex_param', background=False)" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "collapsed": false - }, - "outputs": [ + }, { "data": { - "image/png": "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\nAAAAAElFTkSuQmCC\n", + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('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 = $('
');\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", + " 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", + " this.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", + " '
');\n", + " var titletext = $(\n", + " '
');\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 = $('
');\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 = $('');\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 rubberband = $('');\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);\n", + " canvas.attr('height', height);\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 = $('
')\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 = $('');\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 = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var 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", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\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= 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": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, { "data": { - "image/png": "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\nQmCC\n", + "text/html": [ + "" + ], "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -643,11 +6676,24 @@ } ], "source": [ - "fig = plt.figure(figsize=(12, 5))\n", - "ax = fig.add_subplot(1,1,1)\n", - "ax.plot(data4.chan1, data4.avg_amplitude)\n", - "plt.show()" + "# An example of a parameter that returns several values of different dimension\n", + "# This produces the last two arrays from data3, but only takes the data once.\n", + "data4 = qc.Loop(c1[-15:15:1], 0.1).each(\n", + " AverageAndRaw(meter.amplitude, c2[-10:10:0.2], 0.001)\n", + ").run(location='test_complex_param')\n", + "\n", + "plot4 = qc.Plot(x=data4.chan2, y=data4.chan1, z=data4.amplitude, cmap=plt.cm.hot)\n", + "plot4b = qc.Plot(x=data4.chan1, y=data4.avg_amplitude)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/qcodes/__init__.py b/qcodes/__init__.py index f688e463633..ee124cdafcf 100644 --- a/qcodes/__init__.py +++ b/qcodes/__init__.py @@ -4,6 +4,14 @@ from qcodes.station import Station from qcodes.loops import get_bg, halt_bg, Loop, Task, Wait +# hack for code that should only be imported into the main (notebook) thread +# see: http://stackoverflow.com/questions/15411967 +# in particular, importing matplotlib in the side processes takes a long +# time and spins up other processes in order to try and get a front end +import sys as _sys +if 'ipy' in repr(_sys.stdout): + from qcodes.plots import Plot + from qcodes.data.manager import get_data_manager from qcodes.data.data_set import DataMode, DataSet from qcodes.data.data_array import DataArray diff --git a/qcodes/data/data_set.py b/qcodes/data/data_set.py index 1bd39f014ad..99b689f9c94 100644 --- a/qcodes/data/data_set.py +++ b/qcodes/data/data_set.py @@ -168,6 +168,8 @@ def sync(self): ''' synchronize this data set with a possibly newer version either in storage or on the DataServer, depending on its mode + + returns: boolean, is this DataSet live on the server ''' # TODO: sync implies bidirectional... and it could be! # we should keep track of last sync timestamp and last modification @@ -179,7 +181,10 @@ def sync(self): # LOCAL DataSet - just read it in # TODO: compare timestamps to know if we need to read? self.read() - return + return False + # TODO - for remote live plotting, maybe set some timestamp + # threshold and call it static after it's been dormant a long time? + # I'm thinking like a minute, or ten? Maybe it's configurable? with self.data_manager.query_lock: if self.is_on_server: @@ -198,12 +203,14 @@ def sync(self): # but the DataSet is still on the server, # so we got the data, and don't need to read. self.mode = DataMode.LOCAL - return + return False + return True else: # this DataSet *thought* it was on the server, but it wasn't, # so we haven't synced yet and need to read from storage self.mode = DataMode.LOCAL self.read() + return False def add_array(self, data_array): ''' diff --git a/qcodes/plots.py b/qcodes/plots.py new file mode 100644 index 00000000000..e7806edfd8e --- /dev/null +++ b/qcodes/plots.py @@ -0,0 +1,124 @@ +''' +Live plotting in Jupyter notebooks +using the nbagg backend + +Ironically, this also works best with interactive mode OFF +(or maybe doesn't care?) +''' +import matplotlib.pyplot as plt +from numpy.ma import masked_invalid as masked +from IPython.display import display +from collections import Mapping +import warnings + +from qcodes.widgets.widgets import HiddenUpdateWidget + + +class Plot(object): + ''' + Plot x/y lines or x/y/z heatmap data + + figsize: (width, height) tuple to pass to plt.figure + default (12, 5) + interval: period in seconds between update checks + + subplots: either a sequence (args) or mapping (kwargs) to pass to + plt.subplots. default is a single simple subplot (1, 1) + you can use this to pass kwargs to the plt.figure constructor + + kwargs: passed along to self.add() to add the first data trace + ''' + def __init__(self, figsize=(12, 5), interval=1, subplots=(1, 1), + **kwargs): + if isinstance(subplots, Mapping): + self.fig, self.subplots = plt.subplots(**subplots) + else: + self.fig, self.subplots = plt.subplots(*subplots) + if not hasattr(self.subplots, '__len__'): + self.subplots = (self.subplots,) + + self.traces = [] + self.data_updaters = set() + + if kwargs: + self.add(**kwargs) + + self.update_widget = HiddenUpdateWidget(self.update, interval) + display(self.update_widget) + + def add(self, updater=None, **kwargs): + ''' + adds one trace to this Plot. + + updater: a callable (with no args) that updates the data in this trace + if omitted, we will look for DataSets referenced in this data, and + call their sync methods. + + kwargs: with the following exceptions (mostly the data!), these are + passed directly to the matplotlib plotting routine. + + `subplot`: the 1-based axes number to append to (default 1) + + if kwargs include `z`, we will draw a heatmap (ax.pcolormesh): + `x`, `y`, and `z` are passed as positional args to pcolormesh + + without `z` we draw a scatter/lines plot (ax.plot): + `x`, `y`, and `fmt` (if present) are passed as positional args + ''' + # TODO some way to specify overlaid axes? + + # TODO if we don't specify x (and/or y) take them from + # y (or z) setpoints + self.traces.append(kwargs) + + if updater is not None: + self.data_updaters.add(updater) + else: + for part in ('x', 'y', 'z'): + data_array = kwargs.get(part, '') + if hasattr(data_array, 'data_set'): + self.data_updaters.add(data_array.data_set.sync) + + self.draw() + + def update(self): + any_updates = False + for updater in self.data_updaters: + updates = updater() + if updates is not False: + any_updates = True + + self.draw() + + # once all updaters report they're finished (by returning exactly + # FALSE) we stop updating the plot. + if any_updates is False and hasattr(self, 'update_widget'): + self.update_widget.halt() + + def draw(self): + for ax in self.subplots: + ax.clear() + + for trace in self.traces: + if 'z' in trace: + self._draw_pcolormesh(**trace) + else: + self._draw_plot(**trace) + + self.fig.show() + + def _draw_plot(self, y, x=None, fmt=None, subplot=1, **kwargs): + ax = self.subplots[subplot - 1] + args = [arg for arg in [x, y, fmt] if arg is not None] + ax.plot(*args, **kwargs) + + def _draw_pcolormesh(self, z, x=None, y=None, subplot=1, **kwargs): + ax = self.subplots[subplot - 1] + + with warnings.catch_warnings(): + # we get a warning about masking NaNs right at the beginning here. + # doesn't seem to be an issue so I'll ignore it. + warnings.simplefilter('ignore', UserWarning) + args = [masked(arg) for arg in [x, y, z] if arg is not None] + + ax.pcolormesh(*args, **kwargs) diff --git a/qcodes/widgets/__init__.py b/qcodes/widgets/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/qcodes/widgets/widgets.js b/qcodes/widgets/widgets.js new file mode 100644 index 00000000000..50bd88dc1ed --- /dev/null +++ b/qcodes/widgets/widgets.js @@ -0,0 +1,54 @@ +/* + * Qcodes Jupyter/IPython widgets + */ +require([ + 'nbextensions/widgets/widgets/js/widget', + 'nbextensions/widgets/widgets/js/manager' +], function (widget, manager) { + + var UpdateView = widget.DOMWidgetView.extend({ + render: function() { + window.MYWIDGET = this; + this._interval = 0; + this.update(); + }, + update: function() { + var me = this; + + me.display(me.model.get('_message')); + me.setInterval(); + }, + display: function(message) { + /* + * display method: override this for custom display logic + */ + this.el.innerHTML = message; + }, + remove: function() { + this.setInterval(0); + }, + setInterval: function(newInterval) { + var me = this; + if(newInterval===undefined) newInterval = me.model.get('interval'); + if(newInterval===me._interval) return; + + me._interval = newInterval; + + if(me._updater) clearInterval(me._updater); + + if(me._interval) { + me._updater = setInterval(function() { + me.send({myupdate: true}); + }, me._interval * 1000); + } + } + }); + manager.WidgetManager.register_widget_view('UpdateView', UpdateView); + + var HiddenUpdateView = UpdateView.extend({ + display: function(message) { + this.$el.parents('.widget-area').hide().css('background-color', '#fff'); + } + }); + manager.WidgetManager.register_widget_view('HiddenUpdateView', HiddenUpdateView); +}); diff --git a/qcodes/widgets/widgets.py b/qcodes/widgets/widgets.py new file mode 100644 index 00000000000..524cc8608b2 --- /dev/null +++ b/qcodes/widgets/widgets.py @@ -0,0 +1,50 @@ +import os + +from ipywidgets import widgets +from traitlets import Unicode, Float +from IPython.display import Javascript, display + +# load and display the javascript from this directory +# some people use pkg_resources.resource_string for this, but that seems to +# require an absolute path within the package, this way gives a relative path +with open(os.path.join(os.path.split(__file__)[0], 'widgets.js')) as jsfile: + display(Javascript(jsfile.read())) + + +class UpdateWidget(widgets.DOMWidget): + ''' + Execute a callable periodically, and display its return in the output area + + fn - the callable (with no parameters) to execute + interval - the period, in seconds + can be changed later by setting the interval attribute + interval=0 or the halt() method disables updates. + ''' + _view_name = Unicode('UpdateView', sync=True) # see widgets.js + _message = Unicode(sync=True) + interval = Float(sync=True) + + def __init__(self, fn, interval, **kwargs): + super().__init__(**kwargs) + + self._fn = fn + self.interval = interval + + self.on_msg(self._handle_msg) + + self._handle_msg({'init': True}) + + def _handle_msg(self, message=None): + self._message = str(self._fn()) + + def halt(self): + self.interval = 0 + + +class HiddenUpdateWidget(UpdateWidget): + ''' + A variant on UpdateWidget that hides its section of the output area + Just lets the front end periodically execute code + that takes care of its own display. + ''' + _view_name = Unicode('HiddenUpdateView', sync=True) # see widgets.js diff --git a/test_multi_d/chan1_chan0.dat b/test_multi_d/chan1_chan0.dat index f66102672ce..dc0912eab98 100644 --- a/test_multi_d/chan1_chan0.dat +++ b/test_multi_d/chan1_chan0.dat @@ -478,7 +478,7 @@ 0 -6 0.009 0 -5 0.006 0 -4 0.009 -0 -3 0.027 +0 -3 0.014 0 -2 0.027 0 -1 0.064 0 0 0.117 diff --git a/test_multi_d/chan1_chan2.dat b/test_multi_d/chan1_chan2.dat index 23fc578160a..ccdf54e3864 100644 --- a/test_multi_d/chan1_chan2.dat +++ b/test_multi_d/chan1_chan2.dat @@ -2461,7 +2461,7 @@ 9 -3.4 0.531 9 -3.2 0.498 9 -3 0.468 -9 -2.8 0.442 +9 -2.8 0.419 9 -2.6 0.419 9 -2.4 0.399 9 -2.2 0.382 From b6ad830fa923715b1c442c9a254b9718db3dda86 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 13 Jan 2016 12:14:47 +0100 Subject: [PATCH 03/24] infer setpoint data and dimensionality in Plot calls --- Qcodes example.ipynb | 77 ++++++++++++++++-------------------- qcodes/plots.py | 51 ++++++++++++++++++++---- test_multi_d/chan1_chan2.dat | 2 +- 3 files changed, 78 insertions(+), 52 deletions(-) diff --git a/Qcodes example.ipynb b/Qcodes example.ipynb index 6bbc5e43e25..2c318b47439 100644 --- a/Qcodes example.ipynb +++ b/Qcodes example.ipynb @@ -199,9 +199,9 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='testsweep'\n", - " chan0: chan0\n", " amplitude: amplitude\n", - "started at 2016-01-13 10:24:31\n" + " chan0: chan0\n", + "started at 2016-01-13 12:10:18\n" ] } ], @@ -254,13 +254,13 @@ " 0.02 , 0.019, 0.018, 0.017, 0.016, 0.015, 0.014, 0.013,\n", " 0.013, 0.012, 0.011, 0.011, 0.01 , 0.01 , 0.01 , 0.009,\n", " 0.009, 0.008, 0.008, 0.008, 0.007, 0.007, 0.007, 0.007,\n", - " 0.006, 0.006, 0.006, 0.006, 0.006, 0.007, 0.007, 0.007,\n", - " 0.007, 0.008, 0.008, 0.008, 0.009, 0.009, 0.01 , 0.01 ,\n", - " 0.01 , 0.011, 0.011, 0.012, 0.013, 0.013, 0.014, 0.015,\n", - " 0.016, 0.017, 0.018, 0.019, 0.02 , 0.022, 0.023, 0.025,\n", - " 0.027, 0.029, 0.031, 0.034, 0.037, 0.04 , 0.044, 0.048,\n", - " 0.053, 0.058, 0.064, 0.071, 0.077, 0.085, 0.092, 0.099,\n", - " 0.106, nan, nan, nan, nan, nan, nan, nan,\n", + " 0.006, 0.006, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", @@ -304,12 +304,12 @@ " -18.2, -18.1, -18. , -17.9, -17.8, -17.7, -17.6, -17.5, -17.4,\n", " -17.3, -17.2, -17.1, -17. , -16.9, -16.8, -16.7, -16.6, -16.5,\n", " -16.4, -16.3, -16.2, -16.1, -16. , -15.9, -15.8, -15.7, -15.6,\n", - " -15.5, -15.4, -15.3, -15.2, -15.1, -15. , -14.9, -14.8, -14.7,\n", - " -14.6, -14.5, -14.4, -14.3, -14.2, -14.1, -14. , -13.9, -13.8,\n", - " -13.7, -13.6, -13.5, -13.4, -13.3, -13.2, -13.1, -13. , -12.9,\n", - " -12.8, -12.7, -12.6, -12.5, -12.4, -12.3, -12.2, -12.1, -12. ,\n", - " -11.9, -11.8, -11.7, -11.6, -11.5, -11.4, -11.3, -11.2, -11.1,\n", - " -11. , -10.9, -10.8, -10.7, -10.6, -10.5, -10.4, -10.3, nan,\n", + " -15.5, -15.4, -15.3, -15.2, -15.1, -15. , nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", @@ -1144,7 +1144,7 @@ ], "source": [ "# live-updating plot, that syncs the data and stops updating when it's finished\n", - "plot = qc.Plot(x=data.chan0, y=data.amplitude)" + "plot = qc.Plot(data.amplitude)" ] }, { @@ -1159,11 +1159,11 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='test2d'\n", - " amplitude_0: amplitude\n", - " chan0: chan0\n", " chan1: chan1\n", " amplitude_3: amplitude\n", - "started at 2016-01-13 10:25:04\n" + " amplitude_0: amplitude\n", + " chan0: chan0\n", + "started at 2016-01-13 12:10:53\n" ] }, { @@ -2728,8 +2728,8 @@ " qc.Task(c2.set, 0)\n", " ).run(location='test2d')\n", "\n", - "plot2 = qc.Plot(x=data2.chan0, y=data2.chan1, z=data2.amplitude_0, cmap=plt.cm.hot)\n", - "plot2b = qc.Plot(x=data2.chan0, y=data2.chan1, z=data2.amplitude_3, cmap=plt.cm.hot)" + "plot2 = qc.Plot(data2.amplitude_0, cmap=plt.cm.hot)\n", + "plot2b = qc.Plot(data2.amplitude_3, cmap=plt.cm.hot)" ] }, { @@ -2744,14 +2744,14 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='test_multi_d'\n", - " amplitude_5_0: amplitude\n", " amplitude_2: amplitude\n", - " chan1: chan1\n", - " chan0: chan0\n", " chan2: chan2\n", + " chan0: chan0\n", + " chan1: chan1\n", " avg_amplitude: avg_amplitude\n", + " amplitude_5_0: amplitude\n", " amplitude_3_0: amplitude\n", - "started at 2016-01-13 10:25:37\n" + "started at 2016-01-13 12:11:47\n" ] }, { @@ -4297,7 +4297,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -5097,9 +5097,9 @@ " AverageGetter(meter.amplitude, c2[-10:10:0.2], 0.001)\n", ").run(location='test_multi_d')\n", "\n", - "plot3 = qc.Plot(x=data3.chan0, y=data3.chan1, z=data3.amplitude_3_0, cmap=plt.cm.hot)\n", - "plot3b = qc.Plot(x=data3.chan2, y=data3.chan1, z=data3.amplitude_5_0, cmap=plt.cm.hot)\n", - "plot3c = qc.Plot(x=data3.chan1, y=data3.avg_amplitude)" + "plot3 = qc.Plot(data3.amplitude_3_0, cmap=plt.cm.hot)\n", + "plot3b = qc.Plot(data3.amplitude_5_0, cmap=plt.cm.hot)\n", + "plot3c = qc.Plot(data3.avg_amplitude)" ] }, { @@ -5115,11 +5115,11 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='test_complex_param'\n", - " chan2: chan2\n", - " avg_amplitude: avg_amplitude\n", " chan1: chan1\n", " amplitude: amplitude\n", - "started at 2016-01-13 10:26:07\n" + " chan2: chan2\n", + " avg_amplitude: avg_amplitude\n", + "started at 2016-01-13 12:12:28\n" ] }, { @@ -6682,18 +6682,9 @@ " AverageAndRaw(meter.amplitude, c2[-10:10:0.2], 0.001)\n", ").run(location='test_complex_param')\n", "\n", - "plot4 = qc.Plot(x=data4.chan2, y=data4.chan1, z=data4.amplitude, cmap=plt.cm.hot)\n", - "plot4b = qc.Plot(x=data4.chan1, y=data4.avg_amplitude)" + "plot4 = qc.Plot(data4.amplitude, cmap=plt.cm.hot)\n", + "plot4b = qc.Plot(data4.avg_amplitude)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/qcodes/plots.py b/qcodes/plots.py index e7806edfd8e..4e63fef0390 100644 --- a/qcodes/plots.py +++ b/qcodes/plots.py @@ -16,7 +16,10 @@ class Plot(object): ''' - Plot x/y lines or x/y/z heatmap data + Plot x/y lines or x/y/z heatmap data. The first trace may be included + in the constructor, other traces can be added with Plot.add() + + args: shortcut to provide the x/y/z data. See Plot.add figsize: (width, height) tuple to pass to plt.figure default (12, 5) @@ -26,10 +29,11 @@ class Plot(object): plt.subplots. default is a single simple subplot (1, 1) you can use this to pass kwargs to the plt.figure constructor - kwargs: passed along to self.add() to add the first data trace + kwargs: passed along to Plot.add() to add the first data trace ''' - def __init__(self, figsize=(12, 5), interval=1, subplots=(1, 1), + def __init__(self, *args, figsize=(12, 5), interval=1, subplots=(1, 1), **kwargs): + if isinstance(subplots, Mapping): self.fig, self.subplots = plt.subplots(**subplots) else: @@ -40,16 +44,22 @@ def __init__(self, figsize=(12, 5), interval=1, subplots=(1, 1), self.traces = [] self.data_updaters = set() - if kwargs: - self.add(**kwargs) + if args or kwargs: + self.add(*args, **kwargs) self.update_widget = HiddenUpdateWidget(self.update, interval) display(self.update_widget) - def add(self, updater=None, **kwargs): + def add(self, *args, updater=None, **kwargs): ''' adds one trace to this Plot. + args: a way to provide x/y/z data without keywords + The last one is the dependent data, and we look at its + dimensionality to determine if it's `y` or `z`. + If it's `y`, it may optionally be preceded by `x`. + If it's `z`, it may optionally be preceded by `x` and `y`. + updater: a callable (with no args) that updates the data in this trace if omitted, we will look for DataSets referenced in this data, and call their sync methods. @@ -67,8 +77,14 @@ def add(self, updater=None, **kwargs): ''' # TODO some way to specify overlaid axes? - # TODO if we don't specify x (and/or y) take them from - # y (or z) setpoints + if args: + if hasattr(args[-1][0], '__len__'): + # 2D (or higher... but ignore this for now) + self._args_to_kwargs(args, kwargs, 'xyz', 'pcolormesh') + else: + # 1D + self._args_to_kwargs(args, kwargs, 'xy', 'plot') + self.traces.append(kwargs) if updater is not None: @@ -81,6 +97,18 @@ def add(self, updater=None, **kwargs): self.draw() + def _args_to_kwargs(self, args, kwargs, axletters, plot_func_name): + if len(args) not in (1, len(axletters)): + raise ValueError('{} needs either 1 or {} unnamed args'.format( + plot_func_name, len(axletters))) + + axletters = axletters[-len(args):] + + for arg, axletter in zip(args, axletters): + if axletter in kwargs: + raise ValueError(axletter + ' data provided twice') + kwargs[axletter] = arg + def update(self): any_updates = False for updater in self.data_updaters: @@ -109,11 +137,18 @@ def draw(self): def _draw_plot(self, y, x=None, fmt=None, subplot=1, **kwargs): ax = self.subplots[subplot - 1] + if x is None and hasattr(y, 'set_arrays'): + x = y.set_arrays[-1] args = [arg for arg in [x, y, fmt] if arg is not None] ax.plot(*args, **kwargs) def _draw_pcolormesh(self, z, x=None, y=None, subplot=1, **kwargs): ax = self.subplots[subplot - 1] + set_arrays = getattr(z, 'set_arrays', None) + if x is None and set_arrays: + x = set_arrays[-1] + if y is None and set_arrays: + y = set_arrays[-2] with warnings.catch_warnings(): # we get a warning about masking NaNs right at the beginning here. diff --git a/test_multi_d/chan1_chan2.dat b/test_multi_d/chan1_chan2.dat index ccdf54e3864..23fc578160a 100644 --- a/test_multi_d/chan1_chan2.dat +++ b/test_multi_d/chan1_chan2.dat @@ -2461,7 +2461,7 @@ 9 -3.4 0.531 9 -3.2 0.498 9 -3 0.468 -9 -2.8 0.419 +9 -2.8 0.442 9 -2.6 0.419 9 -2.4 0.399 9 -2.2 0.382 From d033e68e4f96c90673ede35e39eba174988c7d5c Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 13 Jan 2016 12:21:35 +0100 Subject: [PATCH 04/24] ion/ioff doesn't seem to matter --- Qcodes example.ipynb | 49 +++++++++++++++++++++----------------------- qcodes/plots.py | 3 --- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/Qcodes example.ipynb b/Qcodes example.ipynb index 2c318b47439..1261d41f5f6 100644 --- a/Qcodes example.ipynb +++ b/Qcodes example.ipynb @@ -76,10 +76,7 @@ "source": [ "%matplotlib nbagg\n", "import matplotlib.pyplot as plt\n", - "plt.ioff()\n", "import time\n", - "# from IPython import display\n", - "# import numpy.ma as ma\n", "\n", "# load the qcodes path, until we have this installed as a package\n", "import sys\n", @@ -199,9 +196,9 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='testsweep'\n", - " amplitude: amplitude\n", " chan0: chan0\n", - "started at 2016-01-13 12:10:18\n" + " amplitude: amplitude\n", + "started at 2016-01-13 12:18:04\n" ] } ], @@ -254,11 +251,11 @@ " 0.02 , 0.019, 0.018, 0.017, 0.016, 0.015, 0.014, 0.013,\n", " 0.013, 0.012, 0.011, 0.011, 0.01 , 0.01 , 0.01 , 0.009,\n", " 0.009, 0.008, 0.008, 0.008, 0.007, 0.007, 0.007, 0.007,\n", - " 0.006, 0.006, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " 0.006, 0.006, 0.006, 0.006, 0.006, 0.007, 0.007, 0.007,\n", + " 0.007, 0.008, 0.008, 0.008, 0.009, 0.009, 0.01 , 0.01 ,\n", + " 0.01 , 0.011, 0.011, 0.012, 0.013, 0.013, 0.014, 0.015,\n", + " 0.016, 0.017, 0.018, 0.019, 0.02 , 0.022, 0.023, 0.025,\n", + " 0.027, 0.029, 0.031, 0.034, 0.037, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", @@ -304,11 +301,11 @@ " -18.2, -18.1, -18. , -17.9, -17.8, -17.7, -17.6, -17.5, -17.4,\n", " -17.3, -17.2, -17.1, -17. , -16.9, -16.8, -16.7, -16.6, -16.5,\n", " -16.4, -16.3, -16.2, -16.1, -16. , -15.9, -15.8, -15.7, -15.6,\n", - " -15.5, -15.4, -15.3, -15.2, -15.1, -15. , nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " -15.5, -15.4, -15.3, -15.2, -15.1, -15. , -14.9, -14.8, -14.7,\n", + " -14.6, -14.5, -14.4, -14.3, -14.2, -14.1, -14. , -13.9, -13.8,\n", + " -13.7, -13.6, -13.5, -13.4, -13.3, -13.2, -13.1, -13. , -12.9,\n", + " -12.8, -12.7, -12.6, -12.5, -12.4, -12.3, -12.2, -12.1, -12. ,\n", + " -11.9, -11.8, -11.7, -11.6, -11.5, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", @@ -1159,11 +1156,11 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='test2d'\n", - " chan1: chan1\n", - " amplitude_3: amplitude\n", " amplitude_0: amplitude\n", " chan0: chan0\n", - "started at 2016-01-13 12:10:53\n" + " chan1: chan1\n", + " amplitude_3: amplitude\n", + "started at 2016-01-13 12:18:27\n" ] }, { @@ -2744,14 +2741,14 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='test_multi_d'\n", - " amplitude_2: amplitude\n", " chan2: chan2\n", - " chan0: chan0\n", - " chan1: chan1\n", - " avg_amplitude: avg_amplitude\n", + " amplitude_2: amplitude\n", " amplitude_5_0: amplitude\n", + " chan1: chan1\n", + " chan0: chan0\n", " amplitude_3_0: amplitude\n", - "started at 2016-01-13 12:11:47\n" + " avg_amplitude: avg_amplitude\n", + "started at 2016-01-13 12:18:59\n" ] }, { @@ -5115,11 +5112,11 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='test_complex_param'\n", - " chan1: chan1\n", - " amplitude: amplitude\n", " chan2: chan2\n", + " amplitude: amplitude\n", + " chan1: chan1\n", " avg_amplitude: avg_amplitude\n", - "started at 2016-01-13 12:12:28\n" + "started at 2016-01-13 12:19:36\n" ] }, { diff --git a/qcodes/plots.py b/qcodes/plots.py index 4e63fef0390..2137c221e20 100644 --- a/qcodes/plots.py +++ b/qcodes/plots.py @@ -1,9 +1,6 @@ ''' Live plotting in Jupyter notebooks using the nbagg backend - -Ironically, this also works best with interactive mode OFF -(or maybe doesn't care?) ''' import matplotlib.pyplot as plt from numpy.ma import masked_invalid as masked From 69714cac102f641ca818da73c0b7dd4cd5692f04 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 14 Jan 2016 12:37:08 +0100 Subject: [PATCH 05/24] update instead of redraw plot --- Qcodes example.ipynb | 86 +++++++++++++------------- qcodes/plots.py | 123 ++++++++++++++++++++++++++++---------- qcodes/widgets/widgets.js | 2 +- test_multi_d/chan1.dat | 64 ++++++++++---------- 4 files changed, 167 insertions(+), 108 deletions(-) diff --git a/Qcodes example.ipynb b/Qcodes example.ipynb index 1261d41f5f6..10804d49965 100644 --- a/Qcodes example.ipynb +++ b/Qcodes example.ipynb @@ -59,7 +59,7 @@ "\n", " var HiddenUpdateView = UpdateView.extend({\n", " display: function(message) {\n", - " this.$el.parents('.widget-area').hide().css('background-color', '#fff');\n", + " this.$el.hide();\n", " }\n", " });\n", " manager.WidgetManager.register_widget_view('HiddenUpdateView', HiddenUpdateView);\n", @@ -77,6 +77,7 @@ "%matplotlib nbagg\n", "import matplotlib.pyplot as plt\n", "import time\n", + "import numpy as np\n", "\n", "# load the qcodes path, until we have this installed as a package\n", "import sys\n", @@ -198,7 +199,7 @@ "DataSet: DataMode.PULL_FROM_SERVER, location='testsweep'\n", " chan0: chan0\n", " amplitude: amplitude\n", - "started at 2016-01-13 12:18:04\n" + "started at 2016-01-14 12:03:37\n" ] } ], @@ -220,7 +221,7 @@ { "data": { "text/plain": [ - "[, ]" + "[, ]" ] }, "execution_count": 6, @@ -255,20 +256,20 @@ " 0.007, 0.008, 0.008, 0.008, 0.009, 0.009, 0.01 , 0.01 ,\n", " 0.01 , 0.011, 0.011, 0.012, 0.013, 0.013, 0.014, 0.015,\n", " 0.016, 0.017, 0.018, 0.019, 0.02 , 0.022, 0.023, 0.025,\n", - " 0.027, 0.029, 0.031, 0.034, 0.037, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " 0.027, 0.029, 0.031, 0.034, 0.037, 0.04 , 0.044, 0.048,\n", + " 0.053, 0.058, 0.064, 0.071, 0.077, 0.085, 0.092, 0.099,\n", + " 0.106, 0.111, 0.115, 0.117, 0.117, 0.117, 0.115, 0.111,\n", + " 0.106, 0.099, 0.092, 0.085, 0.077, 0.071, 0.064, 0.058,\n", + " 0.053, 0.048, 0.044, 0.04 , 0.037, 0.034, 0.031, 0.029,\n", + " 0.027, 0.025, 0.023, 0.022, 0.02 , 0.019, 0.018, 0.017,\n", + " 0.016, 0.015, 0.014, 0.013, 0.013, 0.012, 0.011, 0.011,\n", + " 0.01 , 0.01 , 0.01 , 0.009, 0.009, 0.008, 0.008, 0.008,\n", + " 0.007, 0.007, 0.007, 0.007, 0.006, 0.006, 0.006, 0.006,\n", + " 0.006, 0.007, 0.007, 0.007, 0.007, 0.008, 0.008, 0.008,\n", + " 0.009, 0.009, 0.01 , 0.01 , 0.01 , 0.011, 0.011, 0.012,\n", + " 0.013, 0.013, 0.014, 0.015, 0.016, 0.017, 0.018, 0.019,\n", + " 0.02 , 0.022, 0.023, 0.025, 0.027, 0.029, 0.031, 0.034,\n", + " 0.037, 0.04 , 0.044, 0.048, 0.053, 0.058, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", @@ -305,19 +306,19 @@ " -14.6, -14.5, -14.4, -14.3, -14.2, -14.1, -14. , -13.9, -13.8,\n", " -13.7, -13.6, -13.5, -13.4, -13.3, -13.2, -13.1, -13. , -12.9,\n", " -12.8, -12.7, -12.6, -12.5, -12.4, -12.3, -12.2, -12.1, -12. ,\n", - " -11.9, -11.8, -11.7, -11.6, -11.5, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", - " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " -11.9, -11.8, -11.7, -11.6, -11.5, -11.4, -11.3, -11.2, -11.1,\n", + " -11. , -10.9, -10.8, -10.7, -10.6, -10.5, -10.4, -10.3, -10.2,\n", + " -10.1, -10. , -9.9, -9.8, -9.7, -9.6, -9.5, -9.4, -9.3,\n", + " -9.2, -9.1, -9. , -8.9, -8.8, -8.7, -8.6, -8.5, -8.4,\n", + " -8.3, -8.2, -8.1, -8. , -7.9, -7.8, -7.7, -7.6, -7.5,\n", + " -7.4, -7.3, -7.2, -7.1, -7. , -6.9, -6.8, -6.7, -6.6,\n", + " -6.5, -6.4, -6.3, -6.2, -6.1, -6. , -5.9, -5.8, -5.7,\n", + " -5.6, -5.5, -5.4, -5.3, -5.2, -5.1, -5. , -4.9, -4.8,\n", + " -4.7, -4.6, -4.5, -4.4, -4.3, -4.2, -4.1, -4. , -3.9,\n", + " -3.8, -3.7, -3.6, -3.5, -3.4, -3.3, -3.2, -3.1, -3. ,\n", + " -2.9, -2.8, -2.7, -2.6, -2.5, -2.4, -2.3, -2.2, -2.1,\n", + " -2. , -1.9, -1.8, -1.7, -1.6, -1.5, -1.4, -1.3, -1.2,\n", + " -1.1, -1. , nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", @@ -1148,7 +1149,8 @@ "cell_type": "code", "execution_count": 9, "metadata": { - "collapsed": false + "collapsed": false, + "scrolled": false }, "outputs": [ { @@ -1156,11 +1158,11 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='test2d'\n", - " amplitude_0: amplitude\n", " chan0: chan0\n", - " chan1: chan1\n", " amplitude_3: amplitude\n", - "started at 2016-01-13 12:18:27\n" + " chan1: chan1\n", + " amplitude_0: amplitude\n", + "started at 2016-01-14 12:06:58\n" ] }, { @@ -2741,14 +2743,14 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='test_multi_d'\n", + " chan0: chan0\n", " chan2: chan2\n", - " amplitude_2: amplitude\n", " amplitude_5_0: amplitude\n", - " chan1: chan1\n", - " chan0: chan0\n", - " amplitude_3_0: amplitude\n", " avg_amplitude: avg_amplitude\n", - "started at 2016-01-13 12:18:59\n" + " amplitude_3_0: amplitude\n", + " amplitude_2: amplitude\n", + " chan1: chan1\n", + "started at 2016-01-14 12:34:28\n" ] }, { @@ -5112,11 +5114,11 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='test_complex_param'\n", - " chan2: chan2\n", + " avg_amplitude: avg_amplitude\n", " amplitude: amplitude\n", + " chan2: chan2\n", " chan1: chan1\n", - " avg_amplitude: avg_amplitude\n", - "started at 2016-01-13 12:19:36\n" + "started at 2016-01-14 12:34:52\n" ] }, { diff --git a/qcodes/plots.py b/qcodes/plots.py index 2137c221e20..5734973fe92 100644 --- a/qcodes/plots.py +++ b/qcodes/plots.py @@ -3,10 +3,11 @@ using the nbagg backend ''' import matplotlib.pyplot as plt -from numpy.ma import masked_invalid as masked +from matplotlib.transforms import Bbox +import numpy as np +from numpy.ma import masked_invalid, getmask from IPython.display import display from collections import Mapping -import warnings from qcodes.widgets.widgets import HiddenUpdateWidget @@ -82,7 +83,12 @@ def add(self, *args, updater=None, **kwargs): # 1D self._args_to_kwargs(args, kwargs, 'xy', 'plot') - self.traces.append(kwargs) + self._find_data_in_set_arrays(kwargs) + + self.traces.append({ + 'config': kwargs, + 'plot_object': self._draw_trace(kwargs) + }) if updater is not None: self.data_updaters.add(updater) @@ -92,19 +98,32 @@ def add(self, *args, updater=None, **kwargs): if hasattr(data_array, 'data_set'): self.data_updaters.add(data_array.data_set.sync) - self.draw() - def _args_to_kwargs(self, args, kwargs, axletters, plot_func_name): if len(args) not in (1, len(axletters)): raise ValueError('{} needs either 1 or {} unnamed args'.format( plot_func_name, len(axletters))) - axletters = axletters[-len(args):] - - for arg, axletter in zip(args, axletters): - if axletter in kwargs: - raise ValueError(axletter + ' data provided twice') - kwargs[axletter] = arg + arg_axletters = axletters[-len(args):] + + for arg, arg_axletters in zip(args, arg_axletters): + if arg_axletters in kwargs: + raise ValueError(arg_axletters + ' data provided twice') + kwargs[arg_axletters] = arg + + def _find_data_in_set_arrays(self, kwargs): + axletters = 'xyz' if 'z' in kwargs else 'xy' + main_data = kwargs[axletters[-1]] + if hasattr(main_data, 'set_arrays'): + num_axes = len(axletters) - 1 + # things will probably fail if we try to plot arrays of the + # wrong dimension... but we'll give it a shot anyway. + set_arrays = main_data.set_arrays[-num_axes:] + # for 2D: y is outer loop, which is earlier in set_arrays, + # and x is the inner loop... is this the right convention? + set_axletters = reversed(axletters[:-1]) + for axletter, set_array in zip(set_axletters, set_arrays): + if axletter not in kwargs: + kwargs[axletter] = set_array def update(self): any_updates = False @@ -113,10 +132,49 @@ def update(self): if updates is not False: any_updates = True - self.draw() + # matplotlib doesn't know how to autoscale to a pcolormesh after the + # first draw (relim ignores it...) so we have to do this ourselves + bboxes = dict(zip(self.subplots, [[] for p in self.subplots])) + + for trace in self.traces: + config = trace['config'] + plot_object = trace['plot_object'] + if 'z' in config: + # pcolormesh doesn't seem to allow editing x and y data, only z + # so instead, we'll remove and re-add the data. + if plot_object: + plot_object.remove() + plot_object = self._draw_pcolormesh(**config) + trace['plot_object'] = plot_object + + if plot_object: + bboxes[plot_object.axes].append( + plot_object.get_datalim(plot_object.axes.transData)) + else: + for axletter in 'xy': + setter = 'set_' + axletter + 'data' + if axletter in config: + getattr(plot_object, setter)(config[axletter]) + + for ax in self.subplots: + if ax.get_autoscale_on(): + ax.relim() + if bboxes[ax]: + bbox = Bbox.union(bboxes[ax]) + if np.all(np.isfinite(ax.dataLim)): + # should take care of the case of lines + heatmaps + # where there's already a finite dataLim from relim + ax.dataLim.set(Bbox.union(ax.dataLim, bbox)) + else: + # when there's only a heatmap, relim gives inf bounds + # so just completely overwrite it + ax.dataLim = bbox + ax.autoscale() + + self.fig.canvas.draw() # once all updaters report they're finished (by returning exactly - # FALSE) we stop updating the plot. + # False) we stop updating the plot. if any_updates is False and hasattr(self, 'update_widget'): self.update_widget.halt() @@ -125,32 +183,31 @@ def draw(self): ax.clear() for trace in self.traces: - if 'z' in trace: - self._draw_pcolormesh(**trace) - else: - self._draw_plot(**trace) + self._draw_trace(trace['config']) self.fig.show() + def _draw_trace(self, config): + if 'z' in config: + return self._draw_pcolormesh(**config) + else: + return self._draw_plot(**config) + def _draw_plot(self, y, x=None, fmt=None, subplot=1, **kwargs): ax = self.subplots[subplot - 1] - if x is None and hasattr(y, 'set_arrays'): - x = y.set_arrays[-1] args = [arg for arg in [x, y, fmt] if arg is not None] - ax.plot(*args, **kwargs) + return ax.plot(*args, **kwargs)[0] def _draw_pcolormesh(self, z, x=None, y=None, subplot=1, **kwargs): ax = self.subplots[subplot - 1] - set_arrays = getattr(z, 'set_arrays', None) - if x is None and set_arrays: - x = set_arrays[-1] - if y is None and set_arrays: - y = set_arrays[-2] - - with warnings.catch_warnings(): - # we get a warning about masking NaNs right at the beginning here. - # doesn't seem to be an issue so I'll ignore it. - warnings.simplefilter('ignore', UserWarning) - args = [masked(arg) for arg in [x, y, z] if arg is not None] - - ax.pcolormesh(*args, **kwargs) + + args = [masked_invalid(arg) for arg in [x, y, z] + if arg is not None] + + for arg in args: + if np.all(getmask(arg)): + # if any entire array is masked, don't draw at all + # there's nothing to draw, and anyway it throws a warning + return False + + return ax.pcolormesh(*args, **kwargs) diff --git a/qcodes/widgets/widgets.js b/qcodes/widgets/widgets.js index 50bd88dc1ed..5d8f6bcd86d 100644 --- a/qcodes/widgets/widgets.js +++ b/qcodes/widgets/widgets.js @@ -47,7 +47,7 @@ require([ var HiddenUpdateView = UpdateView.extend({ display: function(message) { - this.$el.parents('.widget-area').hide().css('background-color', '#fff'); + this.$el.hide(); } }); manager.WidgetManager.register_widget_view('HiddenUpdateView', HiddenUpdateView); diff --git a/test_multi_d/chan1.dat b/test_multi_d/chan1.dat index db26202bc98..70acf2e9915 100644 --- a/test_multi_d/chan1.dat +++ b/test_multi_d/chan1.dat @@ -1,33 +1,33 @@ -# chan1 amplitude_2 avg_amplitude -# "chan1" "amplitude" "Average: amplitude" +# chan1 avg_amplitude amplitude_2 +# "chan1" "Average: amplitude" "amplitude" # 30 --15 0.006 1.63366 --14 0.009 1.48254 --13 0.014 1.3513 --12 0.027 1.24736 --11 0.064 1.17375 --10 0.117 1.12666 --9 0.064 1.17375 --8 0.027 1.24736 --7 0.014 1.3513 --6 0.009 1.48254 --5 0.006 1.63366 --4 0.009 1.48254 --3 0.014 1.3513 --2 0.027 1.24736 --1 0.064 1.17375 -0 0.117 1.12666 -1 0.064 1.17375 -2 0.027 1.24736 -3 0.014 1.3513 -4 0.009 1.48254 -5 0.006 1.63366 -6 0.009 1.48254 -7 0.014 1.3513 -8 0.027 1.24736 -9 0.064 1.17375 -10 0.117 1.12666 -11 0.064 1.17375 -12 0.027 1.24736 -13 0.014 1.3513 -14 0.009 1.48254 +-15 1.63366 0.006 +-14 1.48254 0.009 +-13 1.3513 0.014 +-12 1.24736 0.027 +-11 1.17375 0.064 +-10 1.12666 0.117 +-9 1.17375 0.064 +-8 1.24736 0.027 +-7 1.3513 0.014 +-6 1.48254 0.009 +-5 1.63366 0.006 +-4 1.48254 0.009 +-3 1.3513 0.014 +-2 1.24736 0.027 +-1 1.17375 0.064 +0 1.12666 0.117 +1 1.17375 0.064 +2 1.24736 0.027 +3 1.3513 0.014 +4 1.48254 0.009 +5 1.63366 0.006 +6 1.48254 0.009 +7 1.3513 0.014 +8 1.24736 0.027 +9 1.17375 0.064 +10 1.12666 0.117 +11 1.17375 0.064 +12 1.24736 0.027 +13 1.3513 0.014 +14 1.48254 0.009 From 2d242d23e9841ee32df8125dc2b1997228609d6a Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 14 Jan 2016 13:59:40 +0100 Subject: [PATCH 06/24] Plot.draw() is no longer used --- qcodes/plots.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/qcodes/plots.py b/qcodes/plots.py index 5734973fe92..5de877562df 100644 --- a/qcodes/plots.py +++ b/qcodes/plots.py @@ -178,15 +178,6 @@ def update(self): if any_updates is False and hasattr(self, 'update_widget'): self.update_widget.halt() - def draw(self): - for ax in self.subplots: - ax.clear() - - for trace in self.traces: - self._draw_trace(trace['config']) - - self.fig.show() - def _draw_trace(self, config): if 'z' in config: return self._draw_pcolormesh(**config) From e5328d2d9322e66a20dc0f5a8d3bd3d03957917a Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 14 Jan 2016 14:12:18 +0100 Subject: [PATCH 07/24] plot halt shortcut --- qcodes/plots.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qcodes/plots.py b/qcodes/plots.py index 5de877562df..9064da97d38 100644 --- a/qcodes/plots.py +++ b/qcodes/plots.py @@ -176,7 +176,10 @@ def update(self): # once all updaters report they're finished (by returning exactly # False) we stop updating the plot. if any_updates is False and hasattr(self, 'update_widget'): - self.update_widget.halt() + self.halt() + + def halt(self): + self.update_widget.halt() def _draw_trace(self, config): if 'z' in config: From edb72d0538af4c8baabbcca4db494a7d2dc86e3d Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 14 Jan 2016 14:15:04 +0100 Subject: [PATCH 08/24] plot docstrings --- qcodes/plots.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/qcodes/plots.py b/qcodes/plots.py index 9064da97d38..1c361ebeba9 100644 --- a/qcodes/plots.py +++ b/qcodes/plots.py @@ -126,6 +126,11 @@ def _find_data_in_set_arrays(self, kwargs): kwargs[axletter] = set_array def update(self): + ''' + update the data in this plot, using the updaters given with + Plot.add() or in the included DataSets, then include this in + the plot + ''' any_updates = False for updater in self.data_updaters: updates = updater() @@ -179,6 +184,9 @@ def update(self): self.halt() def halt(self): + ''' + stop automatic updates to this plot, by canceling its update widget + ''' self.update_widget.halt() def _draw_trace(self, config): From 3408bc73c3aba5378412736f6be447400bed5289 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Thu, 14 Jan 2016 15:08:24 +0100 Subject: [PATCH 09/24] colorbar --- Qcodes example.ipynb | 106 +++++++++++++++++++++---------------------- qcodes/plots.py | 14 +++++- 2 files changed, 66 insertions(+), 54 deletions(-) diff --git a/Qcodes example.ipynb b/Qcodes example.ipynb index 10804d49965..c521cae0302 100644 --- a/Qcodes example.ipynb +++ b/Qcodes example.ipynb @@ -199,7 +199,7 @@ "DataSet: DataMode.PULL_FROM_SERVER, location='testsweep'\n", " chan0: chan0\n", " amplitude: amplitude\n", - "started at 2016-01-14 12:03:37\n" + "started at 2016-01-14 15:03:41\n" ] } ], @@ -251,25 +251,25 @@ " 0.037, 0.034, 0.031, 0.029, 0.027, 0.025, 0.023, 0.022,\n", " 0.02 , 0.019, 0.018, 0.017, 0.016, 0.015, 0.014, 0.013,\n", " 0.013, 0.012, 0.011, 0.011, 0.01 , 0.01 , 0.01 , 0.009,\n", - " 0.009, 0.008, 0.008, 0.008, 0.007, 0.007, 0.007, 0.007,\n", - " 0.006, 0.006, 0.006, 0.006, 0.006, 0.007, 0.007, 0.007,\n", - " 0.007, 0.008, 0.008, 0.008, 0.009, 0.009, 0.01 , 0.01 ,\n", - " 0.01 , 0.011, 0.011, 0.012, 0.013, 0.013, 0.014, 0.015,\n", - " 0.016, 0.017, 0.018, 0.019, 0.02 , 0.022, 0.023, 0.025,\n", - " 0.027, 0.029, 0.031, 0.034, 0.037, 0.04 , 0.044, 0.048,\n", - " 0.053, 0.058, 0.064, 0.071, 0.077, 0.085, 0.092, 0.099,\n", - " 0.106, 0.111, 0.115, 0.117, 0.117, 0.117, 0.115, 0.111,\n", - " 0.106, 0.099, 0.092, 0.085, 0.077, 0.071, 0.064, 0.058,\n", - " 0.053, 0.048, 0.044, 0.04 , 0.037, 0.034, 0.031, 0.029,\n", - " 0.027, 0.025, 0.023, 0.022, 0.02 , 0.019, 0.018, 0.017,\n", - " 0.016, 0.015, 0.014, 0.013, 0.013, 0.012, 0.011, 0.011,\n", - " 0.01 , 0.01 , 0.01 , 0.009, 0.009, 0.008, 0.008, 0.008,\n", - " 0.007, 0.007, 0.007, 0.007, 0.006, 0.006, 0.006, 0.006,\n", - " 0.006, 0.007, 0.007, 0.007, 0.007, 0.008, 0.008, 0.008,\n", - " 0.009, 0.009, 0.01 , 0.01 , 0.01 , 0.011, 0.011, 0.012,\n", - " 0.013, 0.013, 0.014, 0.015, 0.016, 0.017, 0.018, 0.019,\n", - " 0.02 , 0.022, 0.023, 0.025, 0.027, 0.029, 0.031, 0.034,\n", - " 0.037, 0.04 , 0.044, 0.048, 0.053, 0.058, nan, nan,\n", + " 0.009, 0.008, 0.008, 0.008, 0.007, 0.007, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", @@ -302,23 +302,23 @@ " -18.2, -18.1, -18. , -17.9, -17.8, -17.7, -17.6, -17.5, -17.4,\n", " -17.3, -17.2, -17.1, -17. , -16.9, -16.8, -16.7, -16.6, -16.5,\n", " -16.4, -16.3, -16.2, -16.1, -16. , -15.9, -15.8, -15.7, -15.6,\n", - " -15.5, -15.4, -15.3, -15.2, -15.1, -15. , -14.9, -14.8, -14.7,\n", - " -14.6, -14.5, -14.4, -14.3, -14.2, -14.1, -14. , -13.9, -13.8,\n", - " -13.7, -13.6, -13.5, -13.4, -13.3, -13.2, -13.1, -13. , -12.9,\n", - " -12.8, -12.7, -12.6, -12.5, -12.4, -12.3, -12.2, -12.1, -12. ,\n", - " -11.9, -11.8, -11.7, -11.6, -11.5, -11.4, -11.3, -11.2, -11.1,\n", - " -11. , -10.9, -10.8, -10.7, -10.6, -10.5, -10.4, -10.3, -10.2,\n", - " -10.1, -10. , -9.9, -9.8, -9.7, -9.6, -9.5, -9.4, -9.3,\n", - " -9.2, -9.1, -9. , -8.9, -8.8, -8.7, -8.6, -8.5, -8.4,\n", - " -8.3, -8.2, -8.1, -8. , -7.9, -7.8, -7.7, -7.6, -7.5,\n", - " -7.4, -7.3, -7.2, -7.1, -7. , -6.9, -6.8, -6.7, -6.6,\n", - " -6.5, -6.4, -6.3, -6.2, -6.1, -6. , -5.9, -5.8, -5.7,\n", - " -5.6, -5.5, -5.4, -5.3, -5.2, -5.1, -5. , -4.9, -4.8,\n", - " -4.7, -4.6, -4.5, -4.4, -4.3, -4.2, -4.1, -4. , -3.9,\n", - " -3.8, -3.7, -3.6, -3.5, -3.4, -3.3, -3.2, -3.1, -3. ,\n", - " -2.9, -2.8, -2.7, -2.6, -2.5, -2.4, -2.3, -2.2, -2.1,\n", - " -2. , -1.9, -1.8, -1.7, -1.6, -1.5, -1.4, -1.3, -1.2,\n", - " -1.1, -1. , nan, nan, nan, nan, nan, nan, nan,\n", + " -15.5, -15.4, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", @@ -1158,11 +1158,11 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='test2d'\n", + " amplitude_0: amplitude\n", " chan0: chan0\n", - " amplitude_3: amplitude\n", " chan1: chan1\n", - " amplitude_0: amplitude\n", - "started at 2016-01-14 12:06:58\n" + " amplitude_3: amplitude\n", + "started at 2016-01-14 15:04:16\n" ] }, { @@ -1932,7 +1932,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2708,7 +2708,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2744,13 +2744,13 @@ "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='test_multi_d'\n", " chan0: chan0\n", - " chan2: chan2\n", + " chan1: chan1\n", + " amplitude_2: amplitude\n", + " amplitude_3_0: amplitude\n", " amplitude_5_0: amplitude\n", " avg_amplitude: avg_amplitude\n", - " amplitude_3_0: amplitude\n", - " amplitude_2: amplitude\n", - " chan1: chan1\n", - "started at 2016-01-14 12:34:28\n" + " chan2: chan2\n", + "started at 2016-01-14 15:04:49\n" ] }, { @@ -3520,7 +3520,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4296,7 +4296,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -5114,11 +5114,11 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='test_complex_param'\n", - " avg_amplitude: avg_amplitude\n", " amplitude: amplitude\n", - " chan2: chan2\n", " chan1: chan1\n", - "started at 2016-01-14 12:34:52\n" + " chan2: chan2\n", + " avg_amplitude: avg_amplitude\n", + "started at 2016-01-14 15:05:31\n" ] }, { @@ -5888,7 +5888,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" diff --git a/qcodes/plots.py b/qcodes/plots.py index 1c361ebeba9..6a981a77bc8 100644 --- a/qcodes/plots.py +++ b/qcodes/plots.py @@ -212,4 +212,16 @@ def _draw_pcolormesh(self, z, x=None, y=None, subplot=1, **kwargs): # there's nothing to draw, and anyway it throws a warning return False - return ax.pcolormesh(*args, **kwargs) + pc = ax.pcolormesh(*args, **kwargs) + + if getattr(ax, 'qcodes_colorbar', None): + # update_normal doesn't seem to work... + ax.qcodes_colorbar.update_bruteforce(pc) + else: + # TODO: what if there are several colormeshes on this subplot, + # do they get the same colorscale? + # We should make sure they do, and have it include + # the full range of both. + ax.qcodes_colorbar = self.fig.colorbar(pc, ax=ax) + + return pc From a22dfcabfd85154a7a9ec3de4c4f6c8a73c2a2cd Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Fri, 15 Jan 2016 11:51:21 +0100 Subject: [PATCH 10/24] automatic labels on plots --- qcodes/plots.py | 72 +++++++++++++++++++++++++++++++++++++++---------- toymodel.py | 3 +++ 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/qcodes/plots.py b/qcodes/plots.py index 6a981a77bc8..f42882d9132 100644 --- a/qcodes/plots.py +++ b/qcodes/plots.py @@ -42,6 +42,8 @@ def __init__(self, *args, figsize=(12, 5), interval=1, subplots=(1, 1), self.traces = [] self.data_updaters = set() + self.title = self.fig.suptitle('') + if args or kwargs: self.add(*args, **kwargs) @@ -85,19 +87,60 @@ def add(self, *args, updater=None, **kwargs): self._find_data_in_set_arrays(kwargs) + ax = self._get_axes(kwargs) + if 'z' in kwargs: + plot_object = self._draw_pcolormesh(ax, **kwargs) + else: + plot_object = self._draw_plot(ax, **kwargs) + + self._update_labels(ax, kwargs) + prev_default_title = self._get_default_title() + self.traces.append({ 'config': kwargs, - 'plot_object': self._draw_trace(kwargs) + 'plot_object': plot_object }) + if prev_default_title == self.title.get_text(): + # in case the user has updated title, don't change it anymore + self.title.set_text(self._get_default_title()) + if updater is not None: self.data_updaters.add(updater) else: - for part in ('x', 'y', 'z'): + for part in 'xyz': data_array = kwargs.get(part, '') if hasattr(data_array, 'data_set'): self.data_updaters.add(data_array.data_set.sync) + def _get_axes(self, config): + return self.subplots[config.get('subplot', 1) - 1] + + def _get_default_title(self): + title_parts = [] + for trace in self.traces: + config = trace['config'] + for part in 'xyz': + data_array = config.get(part, '') + if hasattr(data_array, 'data_set'): + location = data_array.data_set.location + if location not in title_parts: + title_parts.append(location) + return ', '.join(title_parts) + + def set_title(self, title): + self.title.set_text(title) + + def _update_labels(self, ax, config): + if 'x' in config and not ax.get_xlabel(): + ax.set_xlabel(self._get_label(config['x'])) + if 'y' in config and not ax.get_ylabel(): + ax.set_ylabel(self._get_label(config['y'])) + + def _get_label(self, data_array): + return (getattr(data_array, 'label', '') or + getattr(data_array, 'name', '')) + def _args_to_kwargs(self, args, kwargs, axletters, plot_func_name): if len(args) not in (1, len(axletters)): raise ValueError('{} needs either 1 or {} unnamed args'.format( @@ -149,7 +192,9 @@ def update(self): # so instead, we'll remove and re-add the data. if plot_object: plot_object.remove() - plot_object = self._draw_pcolormesh(**config) + + ax = self._get_axes(config) + plot_object = self._draw_pcolormesh(ax, **config) trace['plot_object'] = plot_object if plot_object: @@ -189,20 +234,12 @@ def halt(self): ''' self.update_widget.halt() - def _draw_trace(self, config): - if 'z' in config: - return self._draw_pcolormesh(**config) - else: - return self._draw_plot(**config) - - def _draw_plot(self, y, x=None, fmt=None, subplot=1, **kwargs): - ax = self.subplots[subplot - 1] + def _draw_plot(self, ax, y, x=None, fmt=None, subplot=1, **kwargs): + # subplot=1 is just there to strip this out of kwargs args = [arg for arg in [x, y, fmt] if arg is not None] return ax.plot(*args, **kwargs)[0] - def _draw_pcolormesh(self, z, x=None, y=None, subplot=1, **kwargs): - ax = self.subplots[subplot - 1] - + def _draw_pcolormesh(self, ax, z, x=None, y=None, subplot=1, **kwargs): args = [masked_invalid(arg) for arg in [x, y, z] if arg is not None] @@ -224,4 +261,11 @@ def _draw_pcolormesh(self, z, x=None, y=None, subplot=1, **kwargs): # the full range of both. ax.qcodes_colorbar = self.fig.colorbar(pc, ax=ax) + # ideally this should have been in _update_labels, but + # the colorbar doesn't necessarily exist there. + # I guess we could create the colorbar no matter what, + # and just give it a dummy mappable to start, so we could + # put this where it belongs. + ax.qcodes_colorbar.set_label(self._get_label(z)) + return pc diff --git a/toymodel.py b/toymodel.py index ebc386dc45f..883e0117cfc 100644 --- a/toymodel.py +++ b/toymodel.py @@ -75,6 +75,7 @@ def __init__(self, name, model): for i in range(3): cmdbase = 'c{}'.format(i) self.add_parameter('chan{}'.format(i), + label='Gate Channel {} (mV)'.format(i), get_cmd=cmdbase + '?', set_cmd=cmdbase + ' {:.4f}', parse_function=float, @@ -89,6 +90,7 @@ def __init__(self, name, model): # this parameter uses built-in sweeping to change slowly self.add_parameter('amplitude', + label='Source Amplitude (\u03bcV)', get_cmd='ampl?', set_cmd='ampl {:.4f}', parse_function=float, @@ -102,6 +104,7 @@ def __init__(self, name, model): super().__init__(name, model=model) self.add_parameter('amplitude', + label='Current (nA)', get_cmd='ampl?', parse_function=float) From 57cd01a355fba8a01345b88cb04aea0980575acf Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Fri, 15 Jan 2016 12:03:10 +0100 Subject: [PATCH 11/24] wire up figsize correctly --- Qcodes example.ipynb | 2539 ++-------------------------- qcodes/plots.py | 6 +- test2d.dat | 2 +- test_complex_param/chan1.dat | 2 +- test_complex_param/chan1_chan2.dat | 2 +- test_multi_d/chan1.dat | 64 +- test_multi_d/chan1_chan0.dat | 2 +- test_multi_d/chan1_chan2.dat | 2 +- testsweep.dat | 2 +- 9 files changed, 152 insertions(+), 2469 deletions(-) diff --git a/Qcodes example.ipynb b/Qcodes example.ipynb index c521cae0302..003043a0367 100644 --- a/Qcodes example.ipynb +++ b/Qcodes example.ipynb @@ -197,9 +197,9 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='testsweep'\n", - " chan0: chan0\n", " amplitude: amplitude\n", - "started at 2016-01-14 15:03:41\n" + " chan0: chan0\n", + "started at 2016-01-15 12:00:48\n" ] } ], @@ -221,7 +221,7 @@ { "data": { "text/plain": [ - "[, ]" + "[, ]" ] }, "execution_count": 6, @@ -246,12 +246,12 @@ "data": { "text/plain": [ "{'amplitude': DataArray[400]: amplitude\n", - " array([ 0.117, 0.117, 0.115, 0.111, 0.106, 0.099, 0.092, 0.085,\n", - " 0.077, 0.071, 0.064, 0.058, 0.053, 0.048, 0.044, 0.04 ,\n", - " 0.037, 0.034, 0.031, 0.029, 0.027, 0.025, 0.023, 0.022,\n", - " 0.02 , 0.019, 0.018, 0.017, 0.016, 0.015, 0.014, 0.013,\n", - " 0.013, 0.012, 0.011, 0.011, 0.01 , 0.01 , 0.01 , 0.009,\n", - " 0.009, 0.008, 0.008, 0.008, 0.007, 0.007, nan, nan,\n", + " array([ 0.117, 0.117, 0.115, 0.111, 0.106, 0.099, 0.092, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan,\n", @@ -297,12 +297,12 @@ " nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan]),\n", " 'chan0': DataArray[400]: chan0\n", - " array([-20. , -19.9, -19.8, -19.7, -19.6, -19.5, -19.4, -19.3, -19.2,\n", - " -19.1, -19. , -18.9, -18.8, -18.7, -18.6, -18.5, -18.4, -18.3,\n", - " -18.2, -18.1, -18. , -17.9, -17.8, -17.7, -17.6, -17.5, -17.4,\n", - " -17.3, -17.2, -17.1, -17. , -16.9, -16.8, -16.7, -16.6, -16.5,\n", - " -16.4, -16.3, -16.2, -16.1, -16. , -15.9, -15.8, -15.7, -15.6,\n", - " -15.5, -15.4, nan, nan, nan, nan, nan, nan, nan,\n", + " array([-20. , -19.9, -19.8, -19.7, -19.6, -19.5, -19.4, -19.3, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", + " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", " nan, nan, nan, nan, nan, nan, nan, nan, nan,\n", @@ -1130,7 +1130,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1158,11 +1158,11 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='test2d'\n", - " amplitude_0: amplitude\n", " chan0: chan0\n", " chan1: chan1\n", " amplitude_3: amplitude\n", - "started at 2016-01-14 15:04:16\n" + " amplitude_0: amplitude\n", + "started at 2016-01-15 12:01:05\n" ] }, { @@ -1932,7 +1932,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1940,6 +1940,43 @@ }, "metadata": {}, "output_type": "display_data" + } + ], + "source": [ + "data2 = qc.Loop(c1[-15:15:1], 0.1).loop(c0[-15:12:.5], 0.01).each(\n", + " meter.amplitude, # first measurement, at c2=0 -> amplitude_0 bcs it's action 0\n", + " qc.Task(c2.set, 1), # action 1 -> c2.set(1)\n", + " qc.Wait(0.001),\n", + " meter.amplitude, # second measurement, at c2=1 -> amplitude_4 bcs it's action 4\n", + " qc.Task(c2.set, 0)\n", + " ).run(location='test2d')\n", + "\n", + "# use the subplot and add features of qc.Plot\n", + "plot2 = qc.Plot(data2.amplitude_0, cmap=plt.cm.hot, figsize=(12, 4.5), subplots=(1, 2))\n", + "plot2.add(data2.amplitude_3, cmap=plt.cm.hot, subplot=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataSet: DataMode.PULL_FROM_SERVER, location='test_multi_d'\n", + " amplitude_5_0: amplitude\n", + " amplitude_3_0: amplitude\n", + " chan1: chan1\n", + " chan2: chan2\n", + " amplitude_2: amplitude\n", + " chan0: chan0\n", + " avg_amplitude: avg_amplitude\n", + "started at 2016-01-15 12:01:37\n" + ] }, { "data": { @@ -2708,7 +2745,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2716,42 +2753,6 @@ }, "metadata": {}, "output_type": "display_data" - } - ], - "source": [ - "data2 = qc.Loop(c1[-15:15:1], 0.1).loop(c0[-15:12:.5], 0.01).each(\n", - " meter.amplitude, # first measurement, at c2=0 -> amplitude_0 bcs it's action 0\n", - " qc.Task(c2.set, 1), # action 1 -> c2.set(1)\n", - " qc.Wait(0.001),\n", - " meter.amplitude, # second measurement, at c2=1 -> amplitude_4 bcs it's action 4\n", - " qc.Task(c2.set, 0)\n", - " ).run(location='test2d')\n", - "\n", - "plot2 = qc.Plot(data2.amplitude_0, cmap=plt.cm.hot)\n", - "plot2b = qc.Plot(data2.amplitude_3, cmap=plt.cm.hot)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "collapsed": false - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DataSet: DataMode.PULL_FROM_SERVER, location='test_multi_d'\n", - " chan0: chan0\n", - " chan1: chan1\n", - " amplitude_2: amplitude\n", - " amplitude_3_0: amplitude\n", - " amplitude_5_0: amplitude\n", - " avg_amplitude: avg_amplitude\n", - " chan2: chan2\n", - "started at 2016-01-14 15:04:49\n" - ] }, { "data": { @@ -3520,7 +3521,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -3528,6 +3529,47 @@ }, "metadata": {}, "output_type": "display_data" + } + ], + "source": [ + "data3 = qc.Loop(c1[-15:15:1], 0.1).each(\n", + " qc.Task(c0.set, -10),\n", + " qc.Task(c2.set, 0),\n", + " # a 1D measurement\n", + " meter.amplitude,\n", + " # a 2D sweep, .each is actually unnecessary bcs this is the default measurement\n", + " qc.Loop(c0[-15:15:1], 0.001).each(meter.amplitude),\n", + " qc.Task(c0.set, -10),\n", + " # a 2D sweep with the same outer but different inner loop\n", + " qc.Loop(c2[-10:10:0.2], 0.001),\n", + " AverageGetter(meter.amplitude, c2[-10:10:0.2], 0.001)\n", + ").run(location='test_multi_d')\n", + "\n", + "# several plots updating simultaneously\n", + "plot3 = qc.Plot(data3.amplitude_3_0, cmap=plt.cm.hot)\n", + "plot3b = qc.Plot(data3.amplitude_5_0, cmap=plt.cm.hot, figsize=(12, 4.5), subplots=(1,2))\n", + "plot3b.add(data3.avg_amplitude, subplot=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false, + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DataSet: DataMode.PULL_FROM_SERVER, location='test_complex_param'\n", + " amplitude: amplitude\n", + " chan1: chan1\n", + " avg_amplitude: avg_amplitude\n", + " chan2: chan2\n", + "started at 2016-01-15 12:02:19\n" + ] }, { "data": { @@ -4296,2375 +4338,7 @@ { "data": { "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('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 = $('
');\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", - " 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", - " this.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", - " '
');\n", - " var titletext = $(\n", - " '
');\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 = $('
');\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 = $('');\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 rubberband = $('');\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);\n", - " canvas.attr('height', height);\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 = $('
')\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 = $('');\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 = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var 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", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\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= 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": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('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 = $('
');\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", - " 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", - " this.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", - " '
');\n", - " var titletext = $(\n", - " '
');\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 = $('
');\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 = $('');\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 rubberband = $('');\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);\n", - " canvas.attr('height', height);\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 = $('
')\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 = $('');\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 = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var 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", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\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= 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": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# live-updating plot, that syncs the data and stops updating when it's finished\n", - "plot = qc.MatPlot(data.amplitude)" + "# plot = qc.MatPlot(data.amplitude)\n", + "plotQ = qc.QtPlot(data.amplitude)" ] }, { @@ -1158,2377 +382,53 @@ "output_type": "stream", "text": [ "DataSet: DataMode.PULL_FROM_SERVER, location='test2d'\n", - " chan1: chan1\n", " amplitude_3: amplitude\n", " chan0: chan0\n", + " chan1: chan1\n", " amplitude_0: amplitude\n", - "started at 2016-01-18 12:01:15\n" + "started at 2016-01-20 01:47:52\n" ] - }, + } + ], + "source": [ + "data2 = qc.Loop(c1[-15:15:1], 0.1).loop(c0[-15:12:.5], 0.01).each(\n", + " meter.amplitude, # first measurement, at c2=0 -> amplitude_0 bcs it's action 0\n", + " qc.Task(c2.set, 1), # action 1 -> c2.set(1)\n", + " qc.Wait(0.001),\n", + " meter.amplitude, # second measurement, at c2=1 -> amplitude_4 bcs it's action 4\n", + " qc.Task(c2.set, 0)\n", + " ).run(location='test2d')\n", + "\n", + "# use the subplot and add features of qc.MatPlot\n", + "# plot2 = qc.MatPlot(data2.amplitude_0, cmap=plt.cm.hot, figsize=(12, 4.5), subplots=(1, 2))\n", + "# plot2.add(data2.amplitude_3, cmap=plt.cm.hot, subplot=2)\n", + "\n", + "# the equivalent in QtPlot\n", + "plot2Q = qc.QtPlot(data2.amplitude_0, figsize=(1200, 500))\n", + "plot2Q.add(data2.amplitude_3, subplot=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('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 = $('
');\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", - " 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", - " this.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", - " '
');\n", - " var titletext = $(\n", - " '
');\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 = $('
');\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 = $('');\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 rubberband = $('');\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);\n", - " canvas.attr('height', height);\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 = $('
')\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 = $('');\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 = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var 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", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\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= 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": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('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 = $('
');\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", - " 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", - " this.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", - " '
');\n", - " var titletext = $(\n", - " '
');\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 = $('
');\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 = $('');\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 rubberband = $('');\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);\n", - " canvas.attr('height', height);\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 = $('
')\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 = $('');\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 = $('');\n", - " nav_element.append(status_bar);\n", - " this.message = status_bar[0];\n", - "\n", - " // Add the close button to the window.\n", - " var buttongrp = $('
');\n", - " var 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", - " event.shiftKey = false;\n", - " // Send a \"J\" for go to next cell\n", - " event.which = 74;\n", - " event.keyCode = 74;\n", - " manager.command_mode();\n", - " manager.handle_keydown(event);\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= 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": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" } ], "source": [ @@ -4355,8 +482,10 @@ " AverageAndRaw(meter.amplitude, c2[-10:10:0.2], 0.001)\n", ").run(location='test_complex_param')\n", "\n", - "plot4 = qc.MatPlot(data4.amplitude, cmap=plt.cm.hot, subplots=(1,2), figsize=(12, 4.5))\n", - "plot4.add(data4.avg_amplitude, subplot=2)" + "# plot4 = qc.MatPlot(data4.amplitude, cmap=plt.cm.hot, subplots=(1,2), figsize=(12, 4.5))\n", + "# plot4.add(data4.avg_amplitude, subplot=2)\n", + "plot4Q = qc.QtPlot(data4.amplitude, figsize=(1200, 500))\n", + "plot4Q.add(data4.avg_amplitude, subplot=2)" ] } ], diff --git a/qcodes/__init__.py b/qcodes/__init__.py index ef705e37b8d..87a6d419ec5 100644 --- a/qcodes/__init__.py +++ b/qcodes/__init__.py @@ -11,6 +11,7 @@ import sys as _sys if 'ipy' in repr(_sys.stdout): from qcodes.plots.matplotlib import MatPlot + from qcodes.plots.pyqtgraph import QtPlot from qcodes.data.manager import get_data_manager from qcodes.data.data_set import DataMode, DataSet diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py new file mode 100644 index 00000000000..93d73fb5ea8 --- /dev/null +++ b/qcodes/plots/pyqtgraph.py @@ -0,0 +1,340 @@ +''' +Live plotting using pyqtgraph +''' +import numpy as np +import pyqtgraph as pg +import pyqtgraph.multiprocess as pgmp +import warnings +from collections import namedtuple + +from .base import BasePlot +from .colors import color_cycle, colorscales + + +TransformState = namedtuple('TransformState', 'translate scale revisit') + + +class QtPlot(BasePlot): + ''' + Plot x/y lines or x/y/z heatmap data. The first trace may be included + in the constructor, other traces can be added with QtPlot.add() + + args: shortcut to provide the x/y/z data. See MatPlot.add + + figsize: (width, height) tuple in pixels to pass to GraphicsWindow + default (1000, 600) + interval: period in seconds between update checks + default 0.25 + theme: tuple of (foreground_color, background_color), where each is + a valid Qt color. default (dark gray, white), opposite the pyqtgraph + default of (white, black) + + kwargs: passed along to QtPlot.add() to add the first data trace + ''' + proc = None + rpg = None + + def __init__(self, *args, figsize=(1000, 600), interval=0.25, + theme=((60, 60, 60), 'w'), **kwargs): + super().__init__(interval) + + if not self.__class__.proc: + self._init_qt() + + self.theme = theme + + self.win = self.rpg.GraphicsWindow() + self.win.setBackground(theme[1]) + self.win.resize(*figsize) + self.subplots = [self.add_subplot()] + + if args or kwargs: + self.add(*args, **kwargs) + + def _init_qt(self): + # starting the process for the pyqtgraph plotting + # You do not want a new process to be created every time you start a + # run, so this only starts once and stores the process in the class + pg.mkQApp() + self.__class__.proc = pgmp.QtProcess() # pyqtgraph multiprocessing + self.__class__.rpg = self.proc._import('pyqtgraph') + + def add_subplot(self): + subplot_object = self.win.addPlot() + + for side in ('left', 'bottom'): + ax = subplot_object.getAxis(side) + ax.setGrid(0.2) + ax.setPen(self.theme[0]) + ax._qcodes_label = '' + + return subplot_object + + def add_to_plot(self, subplot=1, **kwargs): + if subplot > len(self.subplots): + for i in range(subplot - len(self.subplots)): + self.subplots.append(self.add_subplot()) + subplot_object = self.subplots[subplot - 1] + + if 'z' in kwargs: + plot_object = self._draw_image(subplot_object, **kwargs) + else: + plot_object = self._draw_plot(subplot_object, **kwargs) + + self._update_labels(subplot_object, kwargs) + prev_default_title = self.get_default_title() + + self.traces.append({ + 'config': kwargs, + 'plot_object': plot_object + }) + + if prev_default_title == self.win.windowTitle(): + self.win.setWindowTitle(self.get_default_title()) + + def _draw_plot(self, subplot_object, y, x=None, color=None, width=None, + antialias=None, **kwargs): + if 'pen' not in kwargs: + if color is None: + cycle = color_cycle + color = cycle[len(self.traces) % len(cycle)] + if width is None: + width = 2 + + kwargs['pen'] = self.rpg.mkPen(color, width=width) + + if antialias is None: + # looks a lot better antialiased, but slows down with many points + # TODO: dynamically update this based on total # of points + antialias = (len(y) < 1000) + + return subplot_object.plot(*self._line_data(x, y), antialias=antialias, + **kwargs) + + def _line_data(self, x, y): + return [self._clean_array(arg) for arg in [x, y] if arg is not None] + + def _draw_image(self, subplot_object, z, x=None, y=None, cmap='Hot', + **kwargs): + img = self.rpg.ImageItem() + subplot_object.addItem(img) + + hist = self.rpg.HistogramLUTItem() + hist.setImageItem(img) + # TODO - ensure this goes next to the correct subplot? + self.win.addItem(hist) + + plot_object = { + 'image': img, + 'hist': hist, + 'histlevels': hist.getLevels(), + 'cmap': cmap, + 'scales': { + 'x': TransformState(0, 1, True), + 'y': TransformState(0, 1, True) + } + } + + self._update_image(plot_object, {'x': x, 'y': y, 'z': z}) + + return plot_object + + def _update_image(self, plot_object, config): + z = config['z'] + img = plot_object['image'] + hist = plot_object['hist'] + scales = plot_object['scales'] + + # make sure z is a *new* numpy float array (pyqtgraph barfs on ints), + # and replace nan with minimum val bcs I can't figure out how to make + # pyqtgraph handle nans - though the source does hint at a way: + # http://www.pyqtgraph.org/documentation/_modules/pyqtgraph/widgets/ColorMapWidget.html + # see class RangeColorMapItem + z = np.asfarray(z).T + with warnings.catch_warnings(): + warnings.simplefilter('error') + try: + z_range = (np.nanmin(z), np.nanmax(z)) + except: + # we get a warning here when z is entirely NaN + # nothing to plot, so give up. + return + z[np.where(np.isnan(z))] = z_range[0] + + self._update_cmap(plot_object) + + hist_range = hist.getLevels() + if hist_range == plot_object['histlevels']: + plot_object['histlevels'] = z_range + hist.setLevels(*z_range) + hist_range = z_range + + img.setImage(self._clean_array(z), levels=hist_range) + + scales_changed = False + for axletter, axscale in scales.items(): + if axscale.revisit: + axdata = config.get(axletter, None) + newscale = self._get_transform(axdata) + if (newscale.translate != axscale.translate or + newscale.scale != axscale.scale): + scales_changed = True + scales[axletter] = newscale + + if scales_changed: + img.resetTransform() + img.translate(scales['x'].translate, scales['y'].translate) + img.scale(scales['x'].scale, scales['y'].scale) + + def _update_cmap(self, plot_object): + gradient = plot_object['hist'].gradient + gradient.setColorMap(self._cmap(plot_object['cmap'])) + + def set_cmap(self, cmap, traces=None): + if isinstance(traces, int): + traces = (traces,) + elif traces is None: + traces = range(len(self.traces)) + + for i in traces: + plot_object = self.traces[i]['plot_object'] + if not isinstance(plot_object, dict) or 'hist' not in plot_object: + continue + + plot_object['cmap'] = cmap + self._update_cmap(plot_object) + + def _get_transform(self, array): + ''' + pyqtgraph seems to only support uniform pixels in image plots. + + for a given setpoint array, extract the linear transform it implies + if the setpoint data is *not* linear (or close to it), or if it's not + uniform in any nested dimensions, issue a warning and return the + default transform of 0, 1 + + returns namedtuple TransformState(translate, scale, revisit) + + in pyqtgraph: + translate means how many pixels to shift the image, away + from the bottom or left edge being at zero on the axis + scale means the data delta + + revisit is True if we just don't have enough info to scale yet, + but we might later. + ''' + + if array is None: + return TransformState(0, 1, True) + + # do we have enough confidence in the setpoint data we've seen + # so far that we don't have to repeat this as more data comes in? + revisit = False + + # somewhat arbitrary - if the first 20% of the data or at least + # 10 rows is uniform, assume it's uniform thereafter + MINROWS = 10 + MINFRAC = 0.2 + + # maximum setpoint deviation from linear to accept is 10% of a pixel + MAXPX = 0.1 + + if hasattr(array[0], '__len__'): + # 2D array: check that all (non-empty) elements are congruent + inner_len = max(len(row) for row in array) + collapsed = np.array([np.nan] * inner_len) + rows_before_trusted = max(MINROWS, len(array) * MINFRAC) + for i, row in enumerate(array): + for j, val in enumerate(row): + if np.isnan(val): + if i < rows_before_trusted: + revisit = True + continue + if np.isnan(collapsed[j]): + collapsed[j] = val + elif val != collapsed[j]: + warnings.warn( + 'nonuniform nested setpoint array passed to ' + 'pyqtgraph. ignoring, using default scaling.') + return TransformState(0, 1, False) + else: + collapsed = array + + if np.isnan(collapsed).any(): + revisit = True + + indices_setpoints = list(zip(*((i, s) for i, s in enumerate(collapsed) + if not np.isnan(s)))) + if not indices_setpoints: + return TransformState(0, 1, revisit) + + indices, setpoints = indices_setpoints + npts = len(indices) + if npts == 1: + indices = indices + (indices[0] + 1,) + setpoints = setpoints + (setpoints[0] + 1,) + + i0 = indices[0] + s0 = setpoints[0] + total_di = indices[-1] - i0 + total_ds = setpoints[-1] - s0 + + if total_ds == 0: + warnings.warn('zero setpoint range passed to pyqtgraph. ' + 'ignoring, using default scaling.') + return TransformState(0, 1, False) + + for i, s in zip(indices[1:-1], setpoints[1:-1]): + icalc = i0 + (s - s0) * total_di / total_ds + if np.abs(i - icalc) > MAXPX: + warnings.warn('nonlinear setpoint array passed to pyqtgraph. ' + 'ignoring, using default scaling.') + return TransformState(0, 1, False) + + scale = total_ds / total_di + # extra 0.5 translation to get the first setpoint at the center of + # the first pixel + translate = s0 - (i0 + 0.5) * scale + + return TransformState(translate, scale, revisit) + + def _update_labels(self, subplot_object, config): + for axletter, side in (('x', 'bottom'), ('y', 'left')): + ax = subplot_object.getAxis(side) + if axletter in config and not ax._qcodes_label: + # pyqtgraph doesn't seem able to get labels, only set + # so we'll store it in the axis object and hope the user + # doesn't set it separately before adding all traces + label = self.get_label(config[axletter]) + if label: + ax._qcodes_label = label + ax.setLabel(label) + + def update_plot(self): + for trace in self.traces: + config = trace['config'] + plot_object = trace['plot_object'] + if 'z' in config: + self._update_image(plot_object, config) + else: + plot_object.setData(*self._line_data(config['x'], config['y'])) + + def _clean_array(self, array): + ''' + we can't send a DataArray to remote pyqtgraph for some reason, + so send the plain numpy array + ''' + if hasattr(array, 'data') and isinstance(array.data, np.ndarray): + return array.data + return array + + def _cmap(self, scale): + if isinstance(scale, str): + if scale in colorscales: + values, colors = zip(*colorscales[scale]) + else: + raise ValueError(scale + ' not found in colorscales') + elif len(scale) == 2: + values, colors = scale + + return self.rpg.ColorMap(values, colors) From 58ca8ad9c720385d82a41fb4b2d0e2f117d43646 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 20 Jan 2016 02:45:41 +0100 Subject: [PATCH 23/24] comment update --- qcodes/plots/matplotlib.py | 2 +- qcodes/plots/pyqtgraph.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qcodes/plots/matplotlib.py b/qcodes/plots/matplotlib.py index f3d66a511d1..e159c618d33 100644 --- a/qcodes/plots/matplotlib.py +++ b/qcodes/plots/matplotlib.py @@ -16,7 +16,7 @@ class MatPlot(BasePlot): Plot x/y lines or x/y/z heatmap data. The first trace may be included in the constructor, other traces can be added with MatPlot.add() - args: shortcut to provide the x/y/z data. See MatPlot.add + args: shortcut to provide the x/y/z data. See BasePlot.add figsize: (width, height) tuple in inches to pass to plt.figure default (8, 5) diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index 93d73fb5ea8..76f5d64ea87 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -19,7 +19,7 @@ class QtPlot(BasePlot): Plot x/y lines or x/y/z heatmap data. The first trace may be included in the constructor, other traces can be added with QtPlot.add() - args: shortcut to provide the x/y/z data. See MatPlot.add + args: shortcut to provide the x/y/z data. See BasePlot.add figsize: (width, height) tuple in pixels to pass to GraphicsWindow default (1000, 600) From 8d303dde6904e54dde2529ac2be6badc2af83ed1 Mon Sep 17 00:00:00 2001 From: alexcjohnson Date: Wed, 20 Jan 2016 10:05:06 +0100 Subject: [PATCH 24/24] pyqtgraph label colorscale, and tweak styling --- qcodes/plots/pyqtgraph.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qcodes/plots/pyqtgraph.py b/qcodes/plots/pyqtgraph.py index 76f5d64ea87..abfe14aaf7e 100644 --- a/qcodes/plots/pyqtgraph.py +++ b/qcodes/plots/pyqtgraph.py @@ -64,7 +64,6 @@ def add_subplot(self): for side in ('left', 'bottom'): ax = subplot_object.getAxis(side) - ax.setGrid(0.2) ax.setPen(self.theme[0]) ax._qcodes_label = '' @@ -121,6 +120,8 @@ def _draw_image(self, subplot_object, z, x=None, y=None, cmap='Hot', hist = self.rpg.HistogramLUTItem() hist.setImageItem(img) + hist.axis.setPen(self.theme[0]) + hist.axis.setLabel(self.get_label(z)) # TODO - ensure this goes next to the correct subplot? self.win.addItem(hist)