Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update traitlet values before initial render of widget #4956

Merged
merged 6 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 31 additions & 31 deletions packages/python/plotly/js/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -315,7 +315,7 @@ export class FigureModel {
* @property {Array.<Array.<String|Number>>} 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,

Expand All @@ -326,7 +326,7 @@ export class FigureModel {
* @property {Array.<Array.<String|Number>>} 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,

Expand Down Expand Up @@ -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
*/
Expand All @@ -400,7 +400,7 @@ export class FigureModel {
* @property {Array.<Object>} 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
*/
Expand Down Expand Up @@ -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());
Expand All @@ -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)) {
Expand All @@ -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
*/
Expand All @@ -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);
Expand All @@ -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
Expand All @@ -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;

Expand All @@ -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);
}
}

Expand All @@ -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);
}
}

Expand All @@ -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);
}
}

Expand All @@ -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);
}
}

Expand All @@ -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);
}
}
Expand All @@ -725,19 +725,19 @@ 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);
}
}
}

const serializers: Record<string, Serializer> = {
_data: {
_widget_data: {
deserialize: py2js_deserializer,
serialize: js2py_serializer,
},
_layout: {
_widget_layout: {
deserialize: py2js_deserializer,
serialize: js2py_serializer,
},
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -964,7 +964,7 @@ 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 () {
Expand Down Expand Up @@ -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} */
Expand All @@ -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();
}

Expand All @@ -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);

Expand Down
13 changes: 10 additions & 3 deletions packages/python/plotly/plotly/basewidget.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from copy import deepcopy
import pathlib
from traitlets import List, Dict, observe, Integer
from plotly.io._renderers import display_jupyter_version_warnings
Expand Down Expand Up @@ -27,8 +28,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 ###
Expand Down Expand Up @@ -510,7 +511,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 ###
Expand Down Expand Up @@ -729,6 +730,12 @@ 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)
marthacryan marked this conversation as resolved.
Show resolved Hide resolved
self._widget_data = deepcopy(self._data)
return {
"application/vnd.jupyter.widget-view+json": {
"version_major": 2,
Expand Down