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

Adding bokeh view information to ipywidget model #58

Open
Tracked by #91
govinda18 opened this issue Jul 19, 2022 · 3 comments
Open
Tracked by #91

Adding bokeh view information to ipywidget model #58

govinda18 opened this issue Jul 19, 2022 · 3 comments

Comments

@govinda18
Copy link
Contributor

Given that bokeh (2.x) CSS layout engine needs a layout invalidation using view.invalidate_layout, it might be the case that an ipywidget needs to call the invalidate_layout method.

For example, if the view creation uses react rendering (react-widget-cookiecutter), it might be the case that the rendering inside the div is done after the component is mounted causing bokeh to mess up the layout.

It will be helpful to attach the bokeh view to the DOMWidgetModel so that a custom widget can use the bokeh view methods if need be when used in a bokeh environment.

@peytondmurray
Copy link
Contributor

peytondmurray commented Sep 23, 2022

To add some context, this issue affects custom widgets which don't render fully until after being mounted. For example, an ipywidgets.widget.Widget with a View like this

export class ExampleView extends DOMWidgetView {
  render() {
    this.el.classList.add('custom-widget');

    setTimeout(() => {
        this.el.innerHTML = '<div style="width: 500px; height: 500px; background-color: red"></div>'
    }, 5000)
    return;
  }
}

will render a big red square in a Jupyter notebook, but will not show anything in an app served by bokeh or panel, which depends on ipywidgets_bokeh. Here's a minimal example of such an app that makes use of the snippet above as part of a custom ipywidgets.widget.Widget:

import panel as pn

from ipywidgets_bokeh import IPyWidget
from myproject import ExampleWidget

pn.extension('ipywidgets')


def app():
    return pn.Column(
        IPyWidget(widget=ExampleWidget())
    )


pn.serve({'app': app}, port=8890)

I spent some time making the ExampleWidget available in a repository for reference. Sadly you can't actually use it for testing this exact scenario because ipywidgets_bokeh searches unpkg.com for the javascript part of the custom-ipywidget-example, which of course it can't find because I haven't uploaded it as a package. Anyway, I thought it might be good to document this here publicly for those interested in the details.

@govinda18
Copy link
Contributor Author

@peytondmurray As we discussed, here are the steps to reproduce the issue:

  1. Create a venv and install panel, ipywidgets-bokeh in editable mode in it.
  2. Clone https://github.com/govinda18/ipywidget-bokeh-bug-example and install the package in editable mode.
  3. Try out the notebook https://github.com/govinda18/ipywidget-bokeh-bug-example/blob/master/Untitled.ipynb.

So far you should be able to reproduce it in a notebook. To reproduce it with ipywidget-bokeh, here is what I did:
4. Wen to js folder of the custom ipywidget this one and started a simple server.
image
5. Now replace this line with http://0.0.0.0:8000/dist/.

And this should help you reproduce the issue.

image

@peytondmurray
Copy link
Contributor

peytondmurray commented Oct 14, 2022

Hey @govinda18, I've been testing out strategies to handle this issue. In particular I've been trying to listen for ipywidgets events which I can use to trigger layout invalidation on the bokeh side.

To do this, in the WidgetManager I modified render to first create a view and then register a callback to be triggered when the widget is displayed. After that, I display the view as usual. Then in the widget on the bokeh side, I pass in a callback which invalidates the layout (and writes to console to let me know it did this successfully).

For reasons that might be clearer to someone who knows a lot more about ipywidgets internals, this callback seems to fire immediately when the widget is loaded, rather than when the widget actually renders. In short, this approach still doesn't work.


On a related note, I also went back to my custom-ipywidget-example and noticed that the return value of the ipywidget render() function can be either a value or a promise which resolves to a value. Assuming that ipywidgets would only trigger the displayed callback once this promise resolves, I tried making render() return a Promise that resolves after a timeout but again, the displayed callback is triggered before this promise resolves. Someone more familiar with ipywidgets might be able to explain this, but I can't really say why this is.

Edit

Okay, this approach actually seems like it works, but for the life of me I cannot get firefox to clear its cache and use the new js bundle from my most recent custom-ipywidget-example. For today I'm going to pause this, and I'll get this together into a PR later on. With chromium I was able to clear the cache, use the updated js bundle from custom-ipywidgets-example, and it just worked.

So in summary:

  1. I'll create a PR to ipywidgets_bokeh to trigger layout invalidation whenever the displayed event is triggered (i.e. whenever the ipywidget render() resolves)
  2. On the ipywidgets side of things, any widget that has a delayed render (e.g. the component is mounted first) needs to return a Promise that resolves once rendering is complete. For an example of what that looks like, check this example.. This is probably what such widgets should be doing already anyway.

@govinda18 Does this seem like an acceptable solution to you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants