Skip to content

Commit

Permalink
PerspectiveWidget HTML export support in Jupyter
Browse files Browse the repository at this point in the history
Add `text/html` output to PerspectiveWidget's `_repr_mimebundle_`
method, which is read by nbconvert when exporting a notebook to
HTML.
  • Loading branch information
tomjakubowski committed Nov 15, 2023
1 parent 4b1f190 commit 19411ae
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 2 deletions.
6 changes: 6 additions & 0 deletions docs/docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ To install the Jupyterlab/Jupyter Notebook plugins from your local working
directory, simply install `python/perspective` with `pip` as you might normally
do.

```bash
(cd packages/perspective-jupyterlab && yarn run build)
pip install -e python/perspective
jupyter labextension develop python/perspective
```

Afterwards, you should see it listed as a "local extension" when you run
`jupyter labextension list` and as a normal extension when you run
`jupyter nbextension list`.
Expand Down
4 changes: 4 additions & 0 deletions python/perspective/perspective/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@
from .handlers import *
from .nbextension import _jupyter_nbextension_paths
from .widget import *


def _jupyter_labextension_paths():
return [{"src": "labextension", "dest": "@finos/perspective-jupyterlab"}]
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<script type="module"
src="https://cdn.jsdelivr.net/npm/@finos/perspective@{{psp_version}}/dist/cdn/perspective.js"></script>
<script type="module"
src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer@{{psp_version}}/dist/cdn/perspective-viewer.js"></script>
<script type="module"
src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer-datagrid@{{psp_version}}/dist/cdn/perspective-viewer-datagrid.js"></script>
<script type="module"
src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer-d3fc@{{psp_version}}/dist/cdn/perspective-viewer-d3fc.js"></script>

<link rel="stylesheet" crossorigin="anonymous"
href="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer@{{psp_version}}/dist/css/pro.css" />

<div class="perspective-envelope" id="perspective-envelope-{{viewer_id}}">
<script type="application/vnd.apache.arrow.file">
{{ b64_data }}
</script>
<perspective-viewer style="height: 690px;"></perspective-viewer>
<script type="module">
// from MDN
function base64ToBytes(base64) {
const binString = atob(base64);
return Uint8Array.from(binString, (m) => m.codePointAt(0));
}
import * as perspective from "https://cdn.jsdelivr.net/npm/@finos/perspective@{{psp_version}}/dist/cdn/perspective.js";
const viewerId = {{ viewer_id | tojson }};
const currentScript = document.scripts[document.scripts.length - 1];
const envelope = document.getElementById(`perspective-envelope-${viewerId}`);
const dataScript = envelope.querySelector('script[type="application/vnd.apache.arrow.file"]');;
if (!dataScript)
throw new Error('data script missing for viewer', viewerId);
const data = base64ToBytes(dataScript.textContent);
const viewerAttrs = {{ viewer_attrs | tojson }};
// Create a new worker, then a new table promise on that worker.
const table = await perspective.shared_worker().table(data.buffer);
const viewer = envelope.querySelector('perspective-viewer');
viewer.load(table);
viewer.restore(viewerAttrs);
</script>
</div>
5 changes: 3 additions & 2 deletions python/perspective/perspective/widget/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

from .widget import PerspectiveWidget
from .widget import PerspectiveWidget, set_jupyter_html_export

__all__ = ["PerspectiveWidget"]

__all__ = ["PerspectiveWidget", "set_jupyter_html_export"]
39 changes: 39 additions & 0 deletions python/perspective/perspective/widget/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import base64
import jinja2
import json
import os
from datetime import date, datetime
from functools import partial

Expand Down Expand Up @@ -515,3 +518,39 @@ def _make_load_message(self):
return _PerspectiveWidgetMessage(-2, "table", msg_data)
else:
raise PerspectiveError("Widget does not have any data loaded - use the `load()` method to provide it with data.")

def _repr_mimebundle_(self, **kwargs):
super_bundle = super(DOMWidget, self)._repr_mimebundle_(**kwargs)
if not _jupyter_html_export_enabled():
return super_bundle
# Serialize viewer attrs + view data to be rendered in the template
viewer_attrs = self.save()
data = self.table.view().to_arrow()
b64_data = base64.encodebytes(data)

jinja_env = jinja2.Environment(
loader=jinja2.PackageLoader("perspective"),
autoescape=jinja2.select_autoescape(),
)
template = jinja_env.get_template("exported_widget.html.jinja")

return super(DOMWidget, self)._repr_mimebundle_(**kwargs) | {
"text/html": template.render(
psp_version=__version__,
viewer_id=self.model_id,
viewer_attrs=viewer_attrs,
b64_data=b64_data.decode("utf-8"),
)
}


def _jupyter_html_export_enabled():
return os.environ.get("PSP_JUPYTER_HTML_EXPORT", None) == "1"


def set_jupyter_html_export(val):
"""Enables HTML export for Jupyter widgets, when set to True.
HTML export can also be enabled by setting the environment variable
`PSP_JUPYTER_HTML_EXPORT` to the string `1`.
"""
os.environ["PSP_JUPYTER_HTML_EXPORT"] = "1" if val else "0"
2 changes: 2 additions & 0 deletions python/perspective/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@
########################
# Get requirement info #
requires = [
"Jinja2>=2.0,<4",
"ipywidgets>=7.5.1,<9",
"future>=0.16.0,<1",
"numpy>=1.21.6,<2",
"pandas>=0.22.0,<3",
"python-dateutil>=2.8.0,<3",
Expand Down

0 comments on commit 19411ae

Please sign in to comment.