From aea1ea0f08b31a16c7b06f5fe1f194666d69dc2a Mon Sep 17 00:00:00 2001
From: Jon Mease <jon.mease@gmail.com>
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 <jon.mease@gmail.com>
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 <jon.mease@gmail.com>
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 <jon.mease@gmail.com>
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 <jon.mease@gmail.com>
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 <jon.mease@gmail.com>
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 <jon.mease@gmail.com>
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 <jon.mease@gmail.com>
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 <jon.mease@gmail.com>
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",