Skip to content

Commit a8d2667

Browse files
authored
Support for embedding plotlywidget instances that use numpy arrays (#1117)
* Null out _js2py properties after touch() so they don't end up in the saved model * 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` * 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. * 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. * 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. * Emit ''plotlywidget-after-render' event when the widget has finished rendering. * Updated version to 0.2.2-rc.1 and published to npm * Convert dispatch 'plotlywidget-after-render' to CustomEvent dispatched on the document * Version 0.2.2-rc.2 published to npm.
1 parent 7767b44 commit a8d2667

File tree

3 files changed

+91
-15
lines changed

3 files changed

+91
-15
lines changed

Diff for: js/package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: js/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "plotlywidget",
3-
"version": "0.2.1",
3+
"version": "0.2.2-rc.2",
44
"description": "The plotly.py ipywidgets library",
55
"author": "The plotly.py team",
66
"license": "MIT",

Diff for: js/src/Figure.js

+89-13
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,6 @@ var FigureView = widgets.DOMWidgetView.extend({
743743

744744
Plotly.newPlot(that.el, initialTraces, initialLayout).then(
745745
function () {
746-
// Plotly.Plots.resize(that.el);
747746

748747
// ### Send trace deltas ###
749748
// We create an array of deltas corresponding to the new
@@ -786,6 +785,14 @@ var FigureView = widgets.DOMWidgetView.extend({
786785
function (update) {
787786
that.handle_plotly_doubleclick(update)
788787
});
788+
789+
// Emit event indicating that the widget has finished
790+
// rendering
791+
var event = new CustomEvent("plotlywidget-after-render",
792+
{ "detail": {"element": that.el, 'viewID': that.viewID}});
793+
794+
// Dispatch/Trigger/Fire the event
795+
document.dispatchEvent(event);
789796
});
790797
},
791798

@@ -796,23 +803,47 @@ var FigureView = widgets.DOMWidgetView.extend({
796803
FigureView.__super__.processPhosphorMessage.apply(this, arguments);
797804
var that = this;
798805
switch (msg.type) {
806+
case 'before-attach':
807+
// Render an initial empty figure. This establishes with
808+
// the page that the element will not be empty, avoiding
809+
// some occasions where the dynamic sizing behavior leads
810+
// to collapsed figure dimensions.
811+
var axisHidden = {
812+
showgrid: false, showline: false, tickvals: []};
813+
814+
Plotly.newPlot(that.el, [], {
815+
xaxis: axisHidden, yaxis: axisHidden
816+
});
817+
818+
window.addEventListener("resize", function(){
819+
that.autosizeFigure();
820+
});
821+
break;
799822
case 'after-attach':
823+
// Rendering actual figure in the after-attach event allows
824+
// Plotly.js to size the figure to fill the available element
800825
this.perform_render();
826+
console.log([that.el._fullLayout.height, that.el._fullLayout.width]);
801827
break;
802828
case 'resize':
803-
var layout = this.model.get('_layout');
804-
if (_.isNil(layout) ||
805-
(_.isNil(layout.width) && _.isNil(layout.height))) {
806-
Plotly.Plots.resize(this.el).then(function(){
807-
var layout_edit_id = that.model.get(
808-
"_last_layout_edit_id");
809-
that._sendLayoutDelta(layout_edit_id);
810-
});
811-
}
829+
this.autosizeFigure();
812830
break
813831
}
814832
},
815833

834+
autosizeFigure: function() {
835+
var that = this;
836+
var layout = that.model.get('_layout');
837+
if (_.isNil(layout) ||
838+
_.isNil(layout.width)) {
839+
Plotly.Plots.resize(that.el).then(function(){
840+
var layout_edit_id = that.model.get(
841+
"_last_layout_edit_id");
842+
that._sendLayoutDelta(layout_edit_id);
843+
});
844+
}
845+
},
846+
816847
/**
817848
* Purge Plotly.js data structures from the notebook output display
818849
* element when the view is destroyed
@@ -996,6 +1027,7 @@ var FigureView = widgets.DOMWidgetView.extend({
9961027

9971028
this.model.set("_js2py_restyle", restyleMsg);
9981029
this.touch();
1030+
this.model.set("_js2py_restyle", null);
9991031
},
10001032

10011033
/**
@@ -1026,6 +1058,7 @@ var FigureView = widgets.DOMWidgetView.extend({
10261058

10271059
this.model.set("_js2py_relayout", relayoutMsg);
10281060
this.touch();
1061+
this.model.set("_js2py_relayout", null);
10291062
},
10301063

10311064
/**
@@ -1059,6 +1092,7 @@ var FigureView = widgets.DOMWidgetView.extend({
10591092

10601093
this.model.set("_js2py_update", updateMsg);
10611094
this.touch();
1095+
this.model.set("_js2py_update", null);
10621096
},
10631097

10641098
/**
@@ -1123,6 +1157,7 @@ var FigureView = widgets.DOMWidgetView.extend({
11231157

11241158
this.model.set("_js2py_pointsCallback", pointsMsg);
11251159
this.touch();
1160+
this.model.set("_js2py_pointsCallback", null);
11261161
}
11271162
},
11281163

@@ -1355,6 +1390,7 @@ var FigureView = widgets.DOMWidgetView.extend({
13551390

13561391
this.model.set("_js2py_layoutDelta", layoutDeltaMsg);
13571392
this.touch();
1393+
this.model.set("_js2py_layoutDelta", null);
13581394
},
13591395

13601396
/**
@@ -1386,6 +1422,7 @@ var FigureView = widgets.DOMWidgetView.extend({
13861422
console.log(["traceDeltasMsg", traceDeltasMsg]);
13871423
this.model.set("_js2py_traceDeltas", traceDeltasMsg);
13881424
this.touch();
1425+
this.model.set("_js2py_traceDeltas", null);
13891426
}
13901427
});
13911428

@@ -1405,13 +1442,45 @@ var numpy_dtype_to_typedarray_type = {
14051442
float64: Float64Array
14061443
};
14071444

1445+
function serializeTypedArray(v) {
1446+
var numpyType;
1447+
if (v instanceof Int8Array) {
1448+
numpyType = 'int8';
1449+
} else if (v instanceof Int16Array) {
1450+
numpyType = 'int16';
1451+
} else if (v instanceof Int32Array) {
1452+
numpyType = 'int32';
1453+
} else if (v instanceof Uint8Array) {
1454+
numpyType = 'uint8';
1455+
} else if (v instanceof Uint16Array) {
1456+
numpyType = 'uint16';
1457+
} else if (v instanceof Uint32Array) {
1458+
numpyType = 'uint32';
1459+
} else if (v instanceof Float32Array) {
1460+
numpyType = 'float32';
1461+
} else if (v instanceof Float64Array) {
1462+
numpyType = 'float64';
1463+
} else {
1464+
// Don't understand it, return as is
1465+
return v;
1466+
}
1467+
var res = {
1468+
dtype: numpyType,
1469+
shape: [v.length],
1470+
value: v.buffer
1471+
};
1472+
return res
1473+
}
1474+
14081475
/**
14091476
* ipywidget JavaScript -> Python serializer
14101477
*/
14111478
function js2py_serializer(v, widgetManager) {
14121479
var res;
14131480

1414-
if (Array.isArray(v)) {
1481+
if (_.isTypedArray(v)) {
1482+
res = serializeTypedArray(v);
1483+
} else if (Array.isArray(v)) {
14151484
// Serialize array elements recursively
14161485
res = new Array(v.length);
14171486
for (var i = 0; i < v.length; i++) {
@@ -1450,11 +1519,18 @@ function py2js_deserializer(v, widgetManager) {
14501519
res[i] = py2js_deserializer(v[i]);
14511520
}
14521521
} else if (_.isPlainObject(v)) {
1453-
if (_.has(v, "buffer") && _.has(v, "dtype") && _.has(v, "shape")) {
1522+
if ((_.has(v, 'value') || _.has(v, 'buffer')) &&
1523+
_.has(v, 'dtype') &&
1524+
_.has(v, 'shape')) {
14541525
// Deserialize special buffer/dtype/shape objects into typed arrays
14551526
// These objects correspond to numpy arrays on the Python side
1527+
//
1528+
// Note plotly.py<=3.1.1 called the buffer object `buffer`
1529+
// This was renamed `value` in 3.2 to work around a naming conflict
1530+
// when saving widget state to a notebook.
14561531
var typedarray_type = numpy_dtype_to_typedarray_type[v.dtype];
1457-
res = new typedarray_type(v.buffer.buffer);
1532+
var buffer = _.has(v, 'value')? v.value.buffer: v.buffer.buffer;
1533+
res = new typedarray_type(buffer);
14581534
} else {
14591535
// Deserialize object properties recursively
14601536
res = {};

0 commit comments

Comments
 (0)