From aea1ea0f08b31a16c7b06f5fe1f194666d69dc2a Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Wed, 15 Aug 2018 19:44:34 -0400 Subject: [PATCH 1/9] Null out _js2py properties after touch() so they don't end up in the saved model --- js/src/Figure.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/js/src/Figure.js b/js/src/Figure.js index f3906dfe3bb..d2e613b196a 100644 --- a/js/src/Figure.js +++ b/js/src/Figure.js @@ -996,6 +996,7 @@ var FigureView = widgets.DOMWidgetView.extend({ this.model.set("_js2py_restyle", restyleMsg); this.touch(); + this.model.set("_js2py_restyle", null); }, /** @@ -1026,6 +1027,7 @@ var FigureView = widgets.DOMWidgetView.extend({ this.model.set("_js2py_relayout", relayoutMsg); this.touch(); + this.model.set("_js2py_relayout", null); }, /** @@ -1059,6 +1061,7 @@ var FigureView = widgets.DOMWidgetView.extend({ this.model.set("_js2py_update", updateMsg); this.touch(); + this.model.set("_js2py_update", null); }, /** @@ -1123,6 +1126,7 @@ var FigureView = widgets.DOMWidgetView.extend({ this.model.set("_js2py_pointsCallback", pointsMsg); this.touch(); + this.model.set("_js2py_pointsCallback", null); } }, @@ -1355,6 +1359,7 @@ var FigureView = widgets.DOMWidgetView.extend({ this.model.set("_js2py_layoutDelta", layoutDeltaMsg); this.touch(); + this.model.set("_js2py_layoutDelta", null); }, /** @@ -1386,6 +1391,7 @@ var FigureView = widgets.DOMWidgetView.extend({ console.log(["traceDeltasMsg", traceDeltasMsg]); this.model.set("_js2py_traceDeltas", traceDeltasMsg); this.touch(); + this.model.set("_js2py_traceDeltas", null); } }); From bcf7ae5eccd9792fdf48af67b40708cac31a5931 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Thu, 16 Aug 2018 18:46:43 -0400 Subject: [PATCH 2/9] Added TypedArray js serialization to support saving embedded widget state. For some reason, this required renaming 'buffer' in the array representation object. Perhaps a conflict with naming conventions in ipywidgets. So the buffers are now stored in property named `value` --- js/src/Figure.js | 38 +++++++++++++++++++++++++++++++++++--- plotly/serializers.py | 2 +- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/js/src/Figure.js b/js/src/Figure.js index d2e613b196a..5e392485cf0 100644 --- a/js/src/Figure.js +++ b/js/src/Figure.js @@ -1411,13 +1411,45 @@ var numpy_dtype_to_typedarray_type = { float64: Float64Array }; +function serializeTypedArray(v) { + var numpyType; + if (v instanceof Int8Array) { + numpyType = 'int8'; + } else if (v instanceof Int16Array) { + numpyType = 'int16'; + } else if (v instanceof Int32Array) { + numpyType = 'int32'; + } else if (v instanceof Uint8Array) { + numpyType = 'uint8'; + } else if (v instanceof Uint16Array) { + numpyType = 'uint16'; + } else if (v instanceof Uint32Array) { + numpyType = 'uint32'; + } else if (v instanceof Float32Array) { + numpyType = 'float32'; + } else if (v instanceof Float64Array) { + numpyType = 'float64'; + } else { + // Don't understand it, return as is + return v; + } + var res = { + dtype: numpyType, + shape: [v.length], + value: v.buffer + }; + return res +} + /** * ipywidget JavaScript -> Python serializer */ function js2py_serializer(v, widgetManager) { var res; - if (Array.isArray(v)) { + if (_.isTypedArray(v)) { + res = serializeTypedArray(v); + } else if (Array.isArray(v)) { // Serialize array elements recursively res = new Array(v.length); for (var i = 0; i < v.length; i++) { @@ -1456,11 +1488,11 @@ function py2js_deserializer(v, widgetManager) { res[i] = py2js_deserializer(v[i]); } } else if (_.isPlainObject(v)) { - if (_.has(v, "buffer") && _.has(v, "dtype") && _.has(v, "shape")) { + if (_.has(v, "value") && _.has(v, "dtype") && _.has(v, "shape")) { // Deserialize special buffer/dtype/shape objects into typed arrays // These objects correspond to numpy arrays on the Python side var typedarray_type = numpy_dtype_to_typedarray_type[v.dtype]; - res = new typedarray_type(v.buffer.buffer); + res = new typedarray_type(v.value.buffer); } else { // Deserialize object properties recursively res = {}; diff --git a/plotly/serializers.py b/plotly/serializers.py index 460dad92fce..56317f1bdfc 100644 --- a/plotly/serializers.py +++ b/plotly/serializers.py @@ -39,7 +39,7 @@ def _py_to_js(v, widget_manager): # Convert 1D numpy arrays with numeric types to memoryviews with # datatype and shape metadata. if v.ndim == 1 and v.dtype.kind in ['u', 'i', 'f']: - return {'buffer': memoryview(v), + return {'value': memoryview(v), 'dtype': str(v.dtype), 'shape': v.shape} else: From 22c464bd8e9bd8e49108d474550c45a1869d4aae Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Thu, 16 Aug 2018 19:50:51 -0400 Subject: [PATCH 3/9] Several dynamic resizing improvements - Render initial empty figure on 'before-attach' event. This keeps the figure view from collapsing when opening a classic notebook that was saved with embedded widget state - Autoresize width even if height has been explicitly set - Autoresize in the classic notebook by responding to window resize events. --- js/src/Figure.js | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/js/src/Figure.js b/js/src/Figure.js index 5e392485cf0..2e14d761276 100644 --- a/js/src/Figure.js +++ b/js/src/Figure.js @@ -743,7 +743,6 @@ var FigureView = widgets.DOMWidgetView.extend({ Plotly.newPlot(that.el, initialTraces, initialLayout).then( function () { - // Plotly.Plots.resize(that.el); // ### Send trace deltas ### // We create an array of deltas corresponding to the new @@ -796,23 +795,47 @@ var FigureView = widgets.DOMWidgetView.extend({ FigureView.__super__.processPhosphorMessage.apply(this, arguments); var that = this; switch (msg.type) { + case 'before-attach': + // Render an initial empty figure. This establishes with + // the page that the element will not be empty, avoiding + // some occasions where the dynamic sizing behavior leads + // to collapsed figure dimensions. + var axisHidden = { + showgrid: false, showline: false, tickvals: []}; + + Plotly.newPlot(that.el, [], { + xaxis: axisHidden, yaxis: axisHidden + }); + + window.addEventListener("resize", function(){ + that.autosizeFigure(); + }); + break; case 'after-attach': + // Rendering actual figure in the after-attach event allows + // Plotly.js to size the figure to fill the available element this.perform_render(); + console.log([that.el._fullLayout.height, that.el._fullLayout.width]); break; case 'resize': - var layout = this.model.get('_layout'); - if (_.isNil(layout) || - (_.isNil(layout.width) && _.isNil(layout.height))) { - Plotly.Plots.resize(this.el).then(function(){ - var layout_edit_id = that.model.get( - "_last_layout_edit_id"); - that._sendLayoutDelta(layout_edit_id); - }); - } + this.autosizeFigure(); break } }, + autosizeFigure: function() { + var that = this; + var layout = that.model.get('_layout'); + if (_.isNil(layout) || + _.isNil(layout.width)) { + Plotly.Plots.resize(that.el).then(function(){ + var layout_edit_id = that.model.get( + "_last_layout_edit_id"); + that._sendLayoutDelta(layout_edit_id); + }); + } + }, + /** * Purge Plotly.js data structures from the notebook output display * element when the view is destroyed @@ -1491,6 +1514,7 @@ function py2js_deserializer(v, widgetManager) { if (_.has(v, "value") && _.has(v, "dtype") && _.has(v, "shape")) { // Deserialize special buffer/dtype/shape objects into typed arrays // These objects correspond to numpy arrays on the Python side + var typedarray_type = numpy_dtype_to_typedarray_type[v.dtype]; res = new typedarray_type(v.value.buffer); } else { From 921bb037cab2ac3e4494361b3d5097aa507b820f Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Fri, 17 Aug 2018 07:25:15 -0400 Subject: [PATCH 4/9] Revert change to Python serializer. If the new plotlywidget can work with current version of plotly.py then we can put out a patch release of the widget only. --- plotly/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotly/serializers.py b/plotly/serializers.py index 56317f1bdfc..460dad92fce 100644 --- a/plotly/serializers.py +++ b/plotly/serializers.py @@ -39,7 +39,7 @@ def _py_to_js(v, widget_manager): # Convert 1D numpy arrays with numeric types to memoryviews with # datatype and shape metadata. if v.ndim == 1 and v.dtype.kind in ['u', 'i', 'f']: - return {'value': memoryview(v), + return {'buffer': memoryview(v), 'dtype': str(v.dtype), 'shape': v.shape} else: From 5ad6c2d7a2cafeccdca4e7db95bb794cbe5961e8 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Fri, 17 Aug 2018 07:38:43 -0400 Subject: [PATCH 5/9] Have plotlywidget accept multiple typed array representations The buffer is named `buffer` when sent from plotly.py<=3.1.1, but it is named `value` when restoring from saved widget state and when receiving updates from plotly.py>=3.2. --- js/src/Figure.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/js/src/Figure.js b/js/src/Figure.js index 2e14d761276..f270e26bc2a 100644 --- a/js/src/Figure.js +++ b/js/src/Figure.js @@ -1511,12 +1511,18 @@ function py2js_deserializer(v, widgetManager) { res[i] = py2js_deserializer(v[i]); } } else if (_.isPlainObject(v)) { - if (_.has(v, "value") && _.has(v, "dtype") && _.has(v, "shape")) { + if ((_.has(v, 'value') || _.has(v, 'buffer')) && + _.has(v, 'dtype') && + _.has(v, 'shape')) { // Deserialize special buffer/dtype/shape objects into typed arrays // These objects correspond to numpy arrays on the Python side - + // + // Note plotly.py<=3.1.1 called the buffer object `buffer` + // This was renamed `value` in 3.2 to work around a naming conflict + // when saving widget state to a notebook. var typedarray_type = numpy_dtype_to_typedarray_type[v.dtype]; - res = new typedarray_type(v.value.buffer); + var buffer = _.has(v, 'value')? v.value.buffer: v.buffer.buffer; + res = new typedarray_type(buffer); } else { // Deserialize object properties recursively res = {}; From 6d58fa915cf8e908158d7da7c675c56f0c1c6b8b Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Fri, 17 Aug 2018 07:47:10 -0400 Subject: [PATCH 6/9] Emit ''plotlywidget-after-render' event when the widget has finished rendering. --- js/src/Figure.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/src/Figure.js b/js/src/Figure.js index f270e26bc2a..dbfa80c0291 100644 --- a/js/src/Figure.js +++ b/js/src/Figure.js @@ -785,6 +785,10 @@ var FigureView = widgets.DOMWidgetView.extend({ function (update) { that.handle_plotly_doubleclick(update) }); + + // Emit event indicating that the widget has finished + // rendering + that.el.emit('plotlywidget-after-render'); }); }, From 9447585391a0e036e8eccb99dc76f996261c8d7c Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Fri, 17 Aug 2018 08:18:58 -0400 Subject: [PATCH 7/9] Updated version to 0.2.2-rc.1 and published to npm --- js/package-lock.json | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/package-lock.json b/js/package-lock.json index a81b51bc17d..194a4473163 100644 --- a/js/package-lock.json +++ b/js/package-lock.json @@ -1,6 +1,6 @@ { "name": "plotlywidget", - "version": "0.2.1", + "version": "0.2.2-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/js/package.json b/js/package.json index b73a04f8798..8ba72f5ff9c 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "plotlywidget", - "version": "0.2.1", + "version": "0.2.2-rc.1", "description": "The plotly.py ipywidgets library", "author": "The plotly.py team", "license": "MIT", From 701cdddf7f5b20e1c5fb1f214ecae246f4b05cc1 Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Fri, 17 Aug 2018 09:37:16 -0400 Subject: [PATCH 8/9] Convert dispatch 'plotlywidget-after-render' to CustomEvent dispatched on the document --- js/src/Figure.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/js/src/Figure.js b/js/src/Figure.js index dbfa80c0291..eabd1a91c55 100644 --- a/js/src/Figure.js +++ b/js/src/Figure.js @@ -788,7 +788,11 @@ var FigureView = widgets.DOMWidgetView.extend({ // Emit event indicating that the widget has finished // rendering - that.el.emit('plotlywidget-after-render'); + var event = new CustomEvent("plotlywidget-after-render", + { "detail": {"element": that.el, 'viewID': that.viewID}}); + + // Dispatch/Trigger/Fire the event + document.dispatchEvent(event); }); }, From 03d4d8fec7ce533b8758baff3ec061df1a7bb5ee Mon Sep 17 00:00:00 2001 From: Jon Mease Date: Fri, 17 Aug 2018 10:07:20 -0400 Subject: [PATCH 9/9] Version 0.2.2-rc.2 published to npm. --- js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/package.json b/js/package.json index 8ba72f5ff9c..0e26a0a2093 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "plotlywidget", - "version": "0.2.2-rc.1", + "version": "0.2.2-rc.2", "description": "The plotly.py ipywidgets library", "author": "The plotly.py team", "license": "MIT",