Skip to content

Commit

Permalink
Refactor handling of Plotly updates (#2251)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr authored Apr 26, 2021
1 parent 74088e7 commit 6a0c8c8
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 120 deletions.
178 changes: 96 additions & 82 deletions panel/models/plotly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,22 +117,29 @@ export class PlotlyPlotView extends PanelHTMLBoxView {
_reacting: boolean = false
_relayouting: boolean = false
_layout_wrapper: PlotlyHTMLElement
_gd: any

_watched_sources: string[]
_end_relayouting = debounce(() => {
this._relayouting = false
}, 2000, false)
}, 2000, false)

connect_signals(): void {
super.connect_signals();

this.connect(this.model.properties.viewport_update_policy.change,
this._updateSetViewportFunction);
this.connect(this.model.properties.viewport_update_throttle.change,
this._updateSetViewportFunction);

this.connect(this.model.properties._render_count.change, this.plot);
this.connect(this.model.properties.viewport.change, this._updateViewportFromProperty);
const {data, data_sources, layout} = this.model.properties
this.on_change([data, data_sources, layout], () => {
const render_count = this.model._render_count
setTimeout(() => {
if (this.model._render_count === render_count)
this.model._render_count += 1;
}, 250)
});
this.connect(this.model.properties.viewport_update_policy.change, () => {
this._updateSetViewportFunction()
});
this.connect(this.model.properties.viewport_update_throttle.change, () => {
this._updateSetViewportFunction()
});
this.connect(this.model.properties._render_count.change, () => this.plot());
this.connect(this.model.properties.viewport.change, () => this._updateViewportFromProperty());
}

render(): void {
Expand All @@ -143,15 +150,16 @@ export class PlotlyPlotView extends PanelHTMLBoxView {
this.plot()
}

plot(): void {
if (!(window as any).Plotly) { return }
_trace_data(): void {
const data = [];
for (let i = 0; i < this.model.data.length; i++) {
data.push(this._get_trace(i, false));
}
return data
}

let newLayout = deepCopy(this.model.layout);

_layout_data(): void {
const newLayout = deepCopy(this.model.layout);
if (this._relayouting) {
const {layout} = this._layout_wrapper;

Expand All @@ -163,78 +171,84 @@ export class PlotlyPlotView extends PanelHTMLBoxView {
}
}, {});
}
return newLayout
}

this._reacting = true;
(window as any).Plotly.react(this._layout_wrapper, data, newLayout, this.model.config).then(() => {
this._updateSetViewportFunction();
this._updateViewportProperty();

if (!this._plotInitialized) {
// Install callbacks
_install_callbacks(): void {
// - plotly_relayout
this._layout_wrapper.on('plotly_relayout', (eventData: any) => {
if (eventData['_update_from_property'] !== true) {
this.model.relayout_data = filterEventData(
this._layout_wrapper, eventData, 'relayout');

// - plotly_relayout
this._layout_wrapper.on('plotly_relayout', (eventData: any) => {
if (eventData['_update_from_property'] !== true) {
this.model.relayout_data = filterEventData(
this._layout_wrapper, eventData, 'relayout');
this._updateViewportProperty();

this._updateViewportProperty();
this._end_relayouting();
}
});

this._end_relayouting();
}
});
// - plotly_relayouting
this._layout_wrapper.on('plotly_relayouting', () => {
if (this.model.viewport_update_policy !== 'mouseup') {
this._relayouting = true;
this._updateViewportProperty();
}
});

// - plotly_restyle
this._layout_wrapper.on('plotly_restyle', (eventData: any) => {
this.model.restyle_data = filterEventData(
this._layout_wrapper, eventData, 'restyle');

this._updateViewportProperty();
});

// - plotly_click
this._layout_wrapper.on('plotly_click', (eventData: any) => {
this.model.click_data = filterEventData(
this._layout_wrapper, eventData, 'click');
});

// - plotly_hover
this._layout_wrapper.on('plotly_hover', (eventData: any) => {
this.model.hover_data = filterEventData(
this._layout_wrapper, eventData, 'hover');
});

// - plotly_selected
this._layout_wrapper.on('plotly_selected', (eventData: any) => {
this.model.selected_data = filterEventData(
this._layout_wrapper, eventData, 'selected');
});

// - plotly_clickannotation
this._layout_wrapper.on('plotly_clickannotation', (eventData: any) => {
delete eventData["event"];
delete eventData["fullAnnotation"];
this.model.clickannotation_data = eventData
});

// - plotly_deselect
this._layout_wrapper.on('plotly_deselect', () => {
this.model.selected_data = null;
});

// - plotly_unhover
this._layout_wrapper.on('plotly_unhover', () => {
this.model.hover_data = null;
});
}

// - plotly_relayouting
this._layout_wrapper.on('plotly_relayouting', () => {
if (this.model.viewport_update_policy !== 'mouseup') {
this._relayouting = true;
this._updateViewportProperty();
}
});

// - plotly_restyle
this._layout_wrapper.on('plotly_restyle', (eventData: any) => {
this.model.restyle_data = filterEventData(
this._layout_wrapper, eventData, 'restyle');

this._updateViewportProperty();
});

// - plotly_click
this._layout_wrapper.on('plotly_click', (eventData: any) => {
this.model.click_data = filterEventData(
this._layout_wrapper, eventData, 'click');
});

// - plotly_hover
this._layout_wrapper.on('plotly_hover', (eventData: any) => {
this.model.hover_data = filterEventData(
this._layout_wrapper, eventData, 'hover');
});

// - plotly_selected
this._layout_wrapper.on('plotly_selected', (eventData: any) => {
this.model.selected_data = filterEventData(
this._layout_wrapper, eventData, 'selected');
});

// - plotly_clickannotation
this._layout_wrapper.on('plotly_clickannotation', (eventData: any) => {
delete eventData["event"];
delete eventData["fullAnnotation"];
this.model.clickannotation_data = eventData
});

// - plotly_deselect
this._layout_wrapper.on('plotly_deselect', () => {
this.model.selected_data = null;
});

// - plotly_unhover
this._layout_wrapper.on('plotly_unhover', () => {
this.model.hover_data = null;
});
}
plot(): void {
if (!(window as any).Plotly) { return }
const data = this._trace_data()
const newLayout = this._layout_data()
this._reacting = true;
(window as any).Plotly.react(this._layout_wrapper, data, newLayout, this.model.config).then(() => {
this._updateSetViewportFunction();
this._updateViewportProperty();
if (!this._plotInitialized)
this._install_callbacks()
this._plotInitialized = true;
this._reacting = false;
if(!_isHidden(this._layout_wrapper))
Expand Down
65 changes: 27 additions & 38 deletions panel/pane/plotly.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,15 +177,11 @@ def _plotly_json_wrapper(fig):
data[idx][key] = arr
return json

def _get_model(self, doc, root=None, parent=None, comm=None):
"""
Should return the bokeh model to be rendered.
"""
PlotlyPlot = lazy_load('panel.models.plotly', 'PlotlyPlot', isinstance(comm, JupyterComm))
def _init_params(self):
viewport_params = [p for p in self.param if 'viewport' in p]
params = list(Layoutable.param)+viewport_params
properties = {p : getattr(self, p) for p in params
if getattr(self, p) is not None}
parameters = list(Layoutable.param)+viewport_params
params = {p: getattr(self, p) for p in parameters
if getattr(self, p) is not None}

if self.object is None:
json, sources = {}, []
Expand All @@ -194,33 +190,21 @@ def _get_model(self, doc, root=None, parent=None, comm=None):
json = self._plotly_json_wrapper(fig)
sources = Plotly._get_sources(json)

data = json.get('data', [])
layout = json.get('layout', {})
params['_render_count'] = self._render_count
params['config'] = self.config or {}
params['data'] = json.get('data', [])
params['data_sources'] = sources
params['layout'] = layout = json.get('layout', {})
if layout.get('autosize') and self.sizing_mode is self.param.sizing_mode.default:
properties['sizing_mode'] = 'stretch_both'

model = PlotlyPlot(
data=data, layout=layout, config=self.config or {},
data_sources=sources, _render_count=self._render_count,
**properties
)

if root is None:
root = model

self._link_props(
model, [
'config', 'relayout_data', 'restyle_data', 'click_data', 'hover_data',
'clickannotation_data', 'selected_data', 'viewport',
'viewport_update_policy', 'viewport_update_throttle', '_render_count'
],
doc,
root,
comm
)
params['sizing_mode'] = 'stretch_both'
return params

def _get_model(self, doc, root=None, parent=None, comm=None):
PlotlyPlot = lazy_load('panel.models.plotly', 'PlotlyPlot', isinstance(comm, JupyterComm))
model = PlotlyPlot(**self._init_params())
if root is None:
root = model
self._link_props(model, self._linkable_params, doc, root, comm)
self._models[root.ref['id']] = (model, parent)
return model

Expand Down Expand Up @@ -259,28 +243,33 @@ def _update(self, ref=None, model=None):
try:
update_data = (
{k: v for k, v in new.items() if k != 'uid'} !=
{k: v for k, v in old.items() if k != 'uid'})
{k: v for k, v in old.items() if k != 'uid'}
)
except Exception:
update_data = True
if update_data:
break

updates = {}
if self.sizing_mode is self.param.sizing_mode.default and 'autosize' in layout:
autosize = layout.get('autosize')
if autosize and model.sizing_mode != 'stretch_both':
model.sizing_mode = 'stretch_both'
updates['sizing_mode'] = 'stretch_both'
elif not autosize and model.sizing_mode != 'fixed':
model.sizing_mode = 'fixed'
updates['sizing_mode'] = 'fixed'

if new_sources:
model.data_sources += new_sources
updates['data_sources'] = model.data_sources + new_sources

if update_data:
model.data = json.get('data')
updates['data'] = json.get('data')

if update_layout:
model.layout = layout
updates['layout'] = layout

if updates:
model.update(**updates)

# Check if we should trigger rendering
if new_sources or update_sources or update_data or update_layout:
if updates or update_sources:
model._render_count += 1

0 comments on commit 6a0c8c8

Please sign in to comment.