glue-jupyter
glue-jupyter copied to clipboard
feat: include state to reproduce the visual state
cc @eteq
For any notebook, we'd like to be able to reproduce what was done by including the state of the glue app in the notebook. To get to this, a human-readable text with all parameters that are changed from default would do.
However, this means we have to render a custom text when:
- The kernel is disconnected
- The widget state is not in the notebook (is this required?)
- The widget libraries do not work/or render
1. The kernel is disconnected
The PR as it is now, would allow for the following:
After we render this:
and then kill the kernel, it would show:
Which can include a custom msg that can 'implement' the reproducibility text.
If the widget state is saved in the notebook, this would also render on page refresh, or when someone else would open the notebook on his/her computer.
2. (?) The widget state is not in the notebook
If the widget state is not included in the notebook, classical notebook will always render like:
Which is due to https://github.com/jupyter-widgets/ipywidgets/blob/90c1c14211a6be685c6077646f40973f96faced1/widgetsnbextension/src/extension.js#L149
The same in JupyterLab:
Which is due to: https://github.com/jupyter-widgets/ipywidgets/blob/90c1c14211a6be685c6077646f40973f96faced1/packages/jupyterlab-manager/src/renderer.ts#L56
Both is theory could default to do something else (render the HTML instead, like in item 3), but this might be a difficult change (does this break things?), or a difficult to configure setting (in notebook metadata?). Technically not difficult to change though.
3. The widget libraries do not work/or render
When a notebook is opened, but rendering fails due to js libraries not being present, or in the case of GitHub because of security concerns, Jupyter will fall back to rendering lower priority mine types, such as HTML. This means we need to get the mime bundle in the output of the notebook up to date with the state of the glue application/viewer.
One way to do this:
# create widget
import glue_jupyter as gj
points = gj.example_data_xyz(loc=60, scale=30, N=10*1000)
app = gj.jglue(points=points)
s = app.scatter2d(x='x', y='y', show=False)
# manually construct the mimebundle, like in https://github.com/jupyter-widgets/ipywidgets/blob/90c1c14211a6be685c6077646f40973f96faced1/ipywidgets/widgets/widget.py#L659
self = s.layout
plaintext = repr(self)
if len(plaintext) > 110:
plaintext = plaintext[:110] + '…'
data = {
'text/plain': plaintext,
}
data['application/vnd.jupyter.widget-view+json'] = {
'version_major': 2,
'version_minor': 0,
'model_id': self._model_id
}
# display app with our own mime bundle, and give it a display id so we can update it
handle = display(data, raw=True, display_id=1)
......
# any time the app changes state (or after collapsing many state changes, i.e. debouncing),
# we can update the mime bundle
# construct custom msg
html_info = '''
<h1>Oops</h1>
If you see this, your Python kernel is not alive anymore, to reproduce the...
'''
# and update the mime bundle
handle.update({'application/vnd.jupyter.widget-view+json': data['application/vnd.jupyter.widget-view+json'],
'text/html': html_info
}, raw=True)
This last step will however redraw the whole application/widget/viewer, and does not look good or lead to a good user experience.
We could possibly see if we can optimize this rendering (e.g. detect if we can skip a redraw), or find other ways of updating the mime bundle.
I hope this gives a bit of an overview of what is possible and what requires changes to the ecosystem.
Codecov Report
Merging #207 (1e5c60b) into master (0a7aee3) will decrease coverage by
0.27%
. The diff coverage is0.00%
.
@@ Coverage Diff @@
## master #207 +/- ##
==========================================
- Coverage 90.50% 90.23% -0.28%
==========================================
Files 83 84 +1
Lines 3960 3972 +12
==========================================
Hits 3584 3584
- Misses 376 388 +12
Impacted Files | Coverage Δ | |
---|---|---|
glue_jupyter/widgets/disconnect.py | 0.00% <0.00%> (ø) |
Continue to review full report at Codecov.
Legend - Click here to learn more
Δ = absolute <relative> (impact)
,ø = not affected
,? = missing data
Powered by Codecov. Last update 0a7aee3...1e5c60b. Read the comment docs.
Thanks @maartenbreddels ! This is a great overview.
I think, unfortunately, the use cases I'm thinking of are more like 2/3, although I have a 4 and a 5 in concept below that riffs off of 1...
But first some clarifications:
- On 1, the idea would be to make
Disconnect
essentially be a superclass for everything in eitherjdaviz
orglue-jupyter
, right? And then to customize the disconnect text, one just updatesbroken
as a trait, is that right? If so, I like that in concept the best except see my 4 to work around the downside... - Re: 3, is there any way to trigger that fall-back to lower MIME types if the libraries are not rendering correctly or the widget state is missing? Or is there no way to get to the lower MIME types (case 3) from case 2? If not, then I think my 5 is called for.
Now for my alternatives to consider:
- Exactly as 1, but with the widget state automatically getting saved when the notebook is saved. I don't know if this is possible in lab or notebook, nor how if we can do it in a fined-grained way for just the glue-jupyter widgets, but it would seem to let us use 1 (which is the best UX) without also requiring the user to manually save widget state (which I think they will basically never think to do).
- More radical (and maybe not lab-compatible, but I'm not sure): have an extension be installed with
glue-jupyter
that uses the file save hooks. I think this still requires at least some part of 3, so still might not solve the constant redraw, but might at least help with making things more consistent?
Some initial feedback: There was an ipywidget meeting yesterday, and there is interest in having ways to control the mimebundle, or at least have fallback methods. One use case is the ipympl widget, that wants to embed the static png image, which would have similar requirements.
Another idea would be to change the default of storing widgets in the notebook. If by default we store widgets that are output in the notebook, and all of its children, it should work in 99% of the cases, if not 100%, and not store widgets that are created, but not visualized.