From 0953cdac9108be2619219125e76aa81e4291d218 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Mon, 6 Jan 2025 16:25:56 -0600 Subject: [PATCH 1/6] Update traitlet values before initial render of widget --- packages/python/plotly/js/widget.ts | 64 ++++++++++----------- packages/python/plotly/plotly/basewidget.py | 16 +++++- 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/packages/python/plotly/js/widget.ts b/packages/python/plotly/js/widget.ts index dc4de6dcd6..ae5b0ffcc5 100644 --- a/packages/python/plotly/js/widget.ts +++ b/packages/python/plotly/js/widget.ts @@ -168,12 +168,12 @@ export class FigureModel { // Data and Layout // --------------- - // The _data and _layout properties are synchronized with the + // The _widget_data and _widget_layout properties are synchronized with the // Python side on initialization only. After initialization, these // properties are kept in sync through the use of the _py2js_* // messages - _data: [], - _layout: {}, + _widget_data: [], + _widget_layout: {}, _config: {}, // Python -> JS messages @@ -315,7 +315,7 @@ export class FigureModel { * @property {Array.>} remove_props * Array of property paths to remove. Each propery path is an * array of property names or array indexes that locate a property - * inside the _layout object + * inside the _widget_layout object */ _py2js_removeLayoutProps: null, @@ -326,7 +326,7 @@ export class FigureModel { * @property {Array.>} remove_props * Array of property paths to remove. Each propery path is an * array of property names or array indexes that locate a property - * inside the _data[remove_trace] object + * inside the _widget_data[remove_trace] object */ _py2js_removeTraceProps: null, @@ -389,7 +389,7 @@ export class FigureModel { * @property {Object} layout_delta * The layout delta object that contains all of the properties of * _fullLayout that are not identical to those in the - * FigureModel's _layout property + * FigureModel's _widget_layout property * @property {Number} layout_edit_id * Edit ID of message that triggered the creation of layout delta */ @@ -400,7 +400,7 @@ export class FigureModel { * @property {Array.} trace_deltas * Array of trace delta objects. Each trace delta contains the * trace's uid along with all of the properties of _fullData that - * are not identical to those in the FigureModel's _data property + * are not identical to those in the FigureModel's _widget_data property * @property {Number} trace_edit_id * Edit ID of message that triggered the creation of trace deltas */ @@ -528,8 +528,8 @@ export class FigureModel { * constructed */ initialize() { - this.model.on("change:_data", () => this.do_data()); - this.model.on("change:_layout", () => this.do_layout()); + this.model.on("change:_widget_data", () => this.do_data()); + this.model.on("change:_widget_layout", () => this.do_layout()); this.model.on("change:_py2js_addTraces", () => this.do_addTraces()); this.model.on("change:_py2js_deleteTraces", () => this.do_deleteTraces()); this.model.on("change:_py2js_moveTraces", () => this.do_moveTraces()); @@ -556,7 +556,7 @@ export class FigureModel { */ _normalize_trace_indexes(trace_indexes?: null | number | number[]): number[] { if (trace_indexes === null || trace_indexes === undefined) { - var numTraces = this.model.get("_data").length; + var numTraces = this.model.get("_widget_data").length; trace_indexes = _.range(numTraces); } if (!Array.isArray(trace_indexes)) { @@ -567,14 +567,14 @@ export class FigureModel { } /** - * Log changes to the _data trait + * Log changes to the _widget_data trait * * This should only happed on FigureModel initialization */ do_data() {} /** - * Log changes to the _layout trait + * Log changes to the _widget_layout trait * * This should only happed on FigureModel initialization */ @@ -589,7 +589,7 @@ export class FigureModel { var msgData: Py2JsAddTracesMsg = this.model.get("_py2js_addTraces"); if (msgData !== null) { - var currentTraces = this.model.get("_data"); + var currentTraces = this.model.get("_widget_data"); var newTraces = msgData.trace_data; _.forEach(newTraces, function (newTrace) { currentTraces.push(newTrace); @@ -608,7 +608,7 @@ export class FigureModel { if (msgData !== null) { var delete_inds = msgData.delete_inds; - var tracesData = this.model.get("_data"); + var tracesData = this.model.get("_widget_data"); // Remove del inds in reverse order so indexes remain valid // throughout loop @@ -629,7 +629,7 @@ export class FigureModel { var msgData: Py2JsMoveTracesMsg = this.model.get("_py2js_moveTraces"); if (msgData !== null) { - var tracesData = this.model.get("_data"); + var tracesData = this.model.get("_widget_data"); var currentInds = msgData.current_trace_inds; var newInds = msgData.new_trace_inds; @@ -646,7 +646,7 @@ export class FigureModel { if (msgData !== null) { var restyleData = msgData.restyle_data; var restyleTraces = this._normalize_trace_indexes(msgData.restyle_traces); - performRestyleLike(this.model.get("_data"), restyleData, restyleTraces); + performRestyleLike(this.model.get("_widget_data"), restyleData, restyleTraces); } } @@ -658,7 +658,7 @@ export class FigureModel { var msgData: Py2JsRelayoutMsg = this.model.get("_py2js_relayout"); if (msgData !== null) { - performRelayoutLike(this.model.get("_layout"), msgData.relayout_data); + performRelayoutLike(this.model.get("_widget_layout"), msgData.relayout_data); } } @@ -673,8 +673,8 @@ export class FigureModel { var style = msgData.style_data; var layout = msgData.layout_data; var styleTraces = this._normalize_trace_indexes(msgData.style_traces); - performRestyleLike(this.model.get("_data"), style, styleTraces); - performRelayoutLike(this.model.get("_layout"), layout); + performRestyleLike(this.model.get("_widget_data"), style, styleTraces); + performRelayoutLike(this.model.get("_widget_layout"), layout); } } @@ -692,11 +692,11 @@ export class FigureModel { for (var i = 0; i < styles.length; i++) { var style = styles[i]; var trace_index = trace_indexes[i]; - var trace = this.model.get("_data")[trace_index]; + var trace = this.model.get("_widget_data")[trace_index]; performRelayoutLike(trace, style); } - performRelayoutLike(this.model.get("_layout"), layout); + performRelayoutLike(this.model.get("_widget_layout"), layout); } } @@ -711,7 +711,7 @@ export class FigureModel { if (msgData !== null) { var keyPaths = msgData.remove_props; - var layout = this.model.get("_layout"); + var layout = this.model.get("_widget_layout"); performRemoveProps(layout, keyPaths); } } @@ -725,7 +725,7 @@ export class FigureModel { if (msgData !== null) { var keyPaths = msgData.remove_props; var traceIndex = msgData.remove_trace; - var trace = this.model.get("_data")[traceIndex]; + var trace = this.model.get("_widget_data")[traceIndex]; performRemoveProps(trace, keyPaths); } @@ -733,11 +733,11 @@ export class FigureModel { } const serializers: Record = { - _data: { + _widget_data: { deserialize: py2js_deserializer, serialize: js2py_serializer, }, - _layout: { + _widget_layout: { deserialize: py2js_deserializer, serialize: js2py_serializer, }, @@ -864,8 +864,8 @@ export class FigureView { // --------------------------- // We must clone the model's data and layout properties so that // the model is not directly mutated by the Plotly.js library. - var initialTraces = _.cloneDeep(this.model.get("_data")); - var initialLayout = _.cloneDeep(this.model.get("_layout")); + var initialTraces = _.cloneDeep(this.model.get("_widget_data")); + var initialLayout = _.cloneDeep(this.model.get("_widget_layout")); if (!initialLayout.height) { initialLayout.height = 360; } @@ -964,11 +964,11 @@ export class FigureView { autosizeFigure() { var that = this; - var layout = that.model.get("_layout"); + var layout = that.model.get("_widget_layout"); if (_.isNil(layout) || _.isNil(layout.width)) { // @ts-ignore Plotly.Plots.resize(that.el).then(function () { - var layout_edit_id = that.model.get("_last_layout_edit_id"); + var layout_edit_id = that.model.get("_last_widget_layout_edit_id"); that._sendLayoutDelta(layout_edit_id); }); } @@ -1530,7 +1530,7 @@ export class FigureView { // ### Handle layout delta ### var layout_delta = createDeltaObject( this.getFullLayout(), - this.model.get("_layout") + this.model.get("_widget_layout") ); /** @type{Js2PyLayoutDeltaMsg} */ @@ -1539,7 +1539,7 @@ export class FigureView { layout_edit_id: layout_edit_id, }; - this.model.set("_js2py_layoutDelta", layoutDeltaMsg); + this.model.set("_js2py_widget_layoutDelta", layoutDeltaMsg); this.touch(); } @@ -1552,7 +1552,7 @@ export class FigureView { * @private */ _sendTraceDeltas(trace_edit_id: any) { - var trace_data = this.model.get("_data"); + var trace_data = this.model.get("_widget_data"); var traceIndexes = _.range(trace_data.length); var trace_deltas = new Array(traceIndexes.length); diff --git a/packages/python/plotly/plotly/basewidget.py b/packages/python/plotly/plotly/basewidget.py index 42fa8cdb89..ce54637ec5 100644 --- a/packages/python/plotly/plotly/basewidget.py +++ b/packages/python/plotly/plotly/basewidget.py @@ -1,4 +1,7 @@ +from copy import deepcopy +import datetime import pathlib +import time from traitlets import List, Dict, observe, Integer from plotly.io._renderers import display_jupyter_version_warnings @@ -27,8 +30,8 @@ class BaseFigureWidget(BaseFigure, anywidget.AnyWidget): # them to the front-end on FigureWidget construction. All other updates # are made using mutation, and they are manually synced to the frontend # using the relayout/restyle/update/etc. messages. - _layout = Dict().tag(sync=True, **custom_serializers) - _data = List().tag(sync=True, **custom_serializers) + _widget_layout = Dict().tag(sync=True, **custom_serializers) + _widget_data = List().tag(sync=True, **custom_serializers) _config = Dict().tag(sync=True, **custom_serializers) # ### Python -> JS message properties ### @@ -104,6 +107,9 @@ def __init__( **kwargs, ) + self._widget_layout = deepcopy(self._layout_obj._props) + self._widget_data = deepcopy(self._widget_data) + # Validate Frames # --------------- # Frames are not supported by figure widget @@ -143,6 +149,8 @@ def __init__( self._view_count = 0 def show(self, *args, **kwargs): + self._widget_layout = deepcopy(self._layout_obj._props) + self._widget_data = deepcopy(self._widget_data) return self # Python -> JavaScript Messages @@ -510,7 +518,7 @@ def _handler_js2py_layoutDelta(self, change): # If a property is present in both _layout and _layout_defaults # then we remove the copy from _layout removed_props = self._remove_overlapping_props( - self._layout, self._layout_defaults + self._widget_layout, self._layout_defaults ) # ### Notify frontend model of property removal ### @@ -729,6 +737,8 @@ def _repr_mimebundle_(self, include=None, exclude=None, validate=True, **kwargs) Return mimebundle corresponding to default renderer. """ display_jupyter_version_warnings() + self._widget_layout = deepcopy(self._layout_obj._props) + self._widget_data = deepcopy(self._data) return { "application/vnd.jupyter.widget-view+json": { "version_major": 2, From 273bab69eff02d2fda2b661eac857c507927e148 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Tue, 7 Jan 2025 12:17:57 -0600 Subject: [PATCH 2/6] Remove extra imports and extra setting of traitlets --- packages/python/plotly/plotly/basewidget.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/python/plotly/plotly/basewidget.py b/packages/python/plotly/plotly/basewidget.py index ce54637ec5..4bb0fca905 100644 --- a/packages/python/plotly/plotly/basewidget.py +++ b/packages/python/plotly/plotly/basewidget.py @@ -1,7 +1,5 @@ from copy import deepcopy -import datetime import pathlib -import time from traitlets import List, Dict, observe, Integer from plotly.io._renderers import display_jupyter_version_warnings @@ -149,8 +147,6 @@ def __init__( self._view_count = 0 def show(self, *args, **kwargs): - self._widget_layout = deepcopy(self._layout_obj._props) - self._widget_data = deepcopy(self._widget_data) return self # Python -> JavaScript Messages From 0929e2dcba6e505444b38c8eb099a3acca78b6f5 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Tue, 7 Jan 2025 12:20:56 -0600 Subject: [PATCH 3/6] Use correct source data for data traitlet --- packages/python/plotly/plotly/basewidget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/basewidget.py b/packages/python/plotly/plotly/basewidget.py index 4bb0fca905..8381f8f43f 100644 --- a/packages/python/plotly/plotly/basewidget.py +++ b/packages/python/plotly/plotly/basewidget.py @@ -106,7 +106,7 @@ def __init__( ) self._widget_layout = deepcopy(self._layout_obj._props) - self._widget_data = deepcopy(self._widget_data) + self._widget_data = deepcopy(self._data) # Validate Frames # --------------- From 215017f8d919c981f227d37a21f6fb02140f84b7 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Tue, 7 Jan 2025 12:24:27 -0600 Subject: [PATCH 4/6] Remove unnecessary setting of traitlets --- packages/python/plotly/plotly/basewidget.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/python/plotly/plotly/basewidget.py b/packages/python/plotly/plotly/basewidget.py index 8381f8f43f..1361f92ea0 100644 --- a/packages/python/plotly/plotly/basewidget.py +++ b/packages/python/plotly/plotly/basewidget.py @@ -105,9 +105,6 @@ def __init__( **kwargs, ) - self._widget_layout = deepcopy(self._layout_obj._props) - self._widget_data = deepcopy(self._data) - # Validate Frames # --------------- # Frames are not supported by figure widget From 429eeb93219468ec4b2b7121d34489cf1ef2bdb3 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Thu, 9 Jan 2025 17:04:36 -0600 Subject: [PATCH 5/6] Add comment and revert changes to variable --- packages/python/plotly/js/widget.ts | 2 +- packages/python/plotly/plotly/basewidget.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/python/plotly/js/widget.ts b/packages/python/plotly/js/widget.ts index ae5b0ffcc5..284116648d 100644 --- a/packages/python/plotly/js/widget.ts +++ b/packages/python/plotly/js/widget.ts @@ -968,7 +968,7 @@ export class FigureView { if (_.isNil(layout) || _.isNil(layout.width)) { // @ts-ignore Plotly.Plots.resize(that.el).then(function () { - var layout_edit_id = that.model.get("_last_widget_layout_edit_id"); + var layout_edit_id = that.model.get("_last_layout_edit_id"); that._sendLayoutDelta(layout_edit_id); }); } diff --git a/packages/python/plotly/plotly/basewidget.py b/packages/python/plotly/plotly/basewidget.py index 1361f92ea0..cedcf6aac6 100644 --- a/packages/python/plotly/plotly/basewidget.py +++ b/packages/python/plotly/plotly/basewidget.py @@ -730,6 +730,10 @@ def _repr_mimebundle_(self, include=None, exclude=None, validate=True, **kwargs) Return mimebundle corresponding to default renderer. """ display_jupyter_version_warnings() + + # Widget layout and data need to be set here in case there are + # changes made to the figure after the widget is created but before + # the cell is run. self._widget_layout = deepcopy(self._layout_obj._props) self._widget_data = deepcopy(self._data) return { From 0089f32e7bce53c4ee94d190adde71720349a4d7 Mon Sep 17 00:00:00 2001 From: Martha Cryan Date: Thu, 9 Jan 2025 17:16:15 -0600 Subject: [PATCH 6/6] Lint --- packages/python/plotly/plotly/basewidget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/basewidget.py b/packages/python/plotly/plotly/basewidget.py index cedcf6aac6..23761b27fc 100644 --- a/packages/python/plotly/plotly/basewidget.py +++ b/packages/python/plotly/plotly/basewidget.py @@ -733,7 +733,7 @@ def _repr_mimebundle_(self, include=None, exclude=None, validate=True, **kwargs) # Widget layout and data need to be set here in case there are # changes made to the figure after the widget is created but before - # the cell is run. + # the cell is run. self._widget_layout = deepcopy(self._layout_obj._props) self._widget_data = deepcopy(self._data) return {