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 8, 2023
1 parent 785b1ee commit 4198f1f
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 1 deletion.
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@{{psp_version}}-datagrid/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.worker().table(data.buffer);
const viewer = envelope.querySelector('perspective-viewer');
viewer.load(table);
viewer.restore(viewerAttrs);
</script>
</div>
23 changes: 23 additions & 0 deletions python/perspective/perspective/widget/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import base64
import jinja2
import json
from datetime import date, datetime
from functools import partial
Expand Down Expand Up @@ -515,3 +517,24 @@ 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):
# 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"),
)
}
2 changes: 1 addition & 1 deletion python/perspective/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@

requires_aiohttp = ["aiohttp>=3,<4"]

requires_jupyter = ["jupyterlab>=3.2,<4"]
requires_jupyter = ["jupyterlab>=3.2,<4", "Jinja2>=2.0,<4"]

requires_starlette = ["fastapi>=0.70,<1", "starlette>=0.20,<1"]

Expand Down

0 comments on commit 4198f1f

Please sign in to comment.