holoviz / panel

Panel: The powerful data exploration & web app framework for Python
https://panel.holoviz.org
BSD 3-Clause "New" or "Revised" License
4.73k stars 516 forks source link

ENH: Support multiple references to the same widget within a layout #552

Open jonmmease opened 5 years ago

jonmmease commented 5 years ago

Splitting this out from https://github.com/pyviz/panel/issues/550 because I think I understand it well enough as a separate situation.


Here are the results of another experiment. This time I placed two references to the dragon pane in a Column layout.

import panel as pn
pn.extension('vtk')

dragon = pn.pane.VTK('https://raw.githubusercontent.com/Kitware/vtk-js/master/Data/StanfordDragon.vtkjs',
                     sizing_mode='stretch_width', height=400)
pn.layout.Column(dragon, dragon).servable()

In the notebook I get the same error as above (Cannot read property 'content' of null), but I don't see the runaway feedback behavior. After the error is raised the first time, the two views are no longer linked.

Here's what I see when I serve this notebook with panel serve:

panel_serve_dragons

When I drag the top view, everything is synchronized perfectly, with no runaway feedback state. But when I drag the bottom view, the top view does not respond.

I can reproduce a similar behavior in the notebook with sliders

import panel as pn
pn.extension()
slider = pn.widgets.IntSlider(start=0, end=10)
pn.layout.Column(slider, slider)

[first view]

slider

[second view]

sliders

jonmmease commented 5 years ago

After digging into the code, it looks like this is a design constraint not a bug.

When widget models are created in the _get_model method, they are stored in the widgets self._models dictionary where the dictionary key is the root model's id.

https://github.com/pyviz/panel/blob/7a9fb669cf7c430db4f163df971fadbf2c446e78/panel/widgets/base.py#L48-L57

In the examples above, the root model is the Column layout. So even though a separate model is created for each reference to the widget within the layout, they overwrite each other in ._models since they share the same root widget. Therefore we end up only storing the last model in the layout.

The reason that the views other than the last one can still be used to update the widget is because we to call _link_props on all of the models individually, event though they are not all stored.

I think we could support this by storing a list of models per root model in the self._models dictionary, though I don't have a sense yet of how disruptive that change would be to the codebase. Alternatively we could raise an exception when this happens.