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

PerspectiveWidget HTML export support in Jupyter #2418

Merged
merged 1 commit into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"),
)
tomjakubowski marked this conversation as resolved.
Show resolved Hide resolved
}


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"
170 changes: 84 additions & 86 deletions python/perspective/requirements/requirements-310.txt
Original file line number Diff line number Diff line change
@@ -1,161 +1,159 @@
Babel==2.13.1
Faker==20.0.3
Jinja2==3.1.2
MarkupSafe==2.1.3
PyYAML==6.0.1
Pygments==2.16.1
Send2Trash==1.8.2
Sphinx==7.2.6
aiofiles==22.1.0
aiohttp==3.8.5
aiohttp==3.8.6
aiosignal==1.3.1
aiosqlite==0.19.0
alabaster==0.7.13
annotated-types==0.5.0
annotated-types==0.6.0
anyio==3.7.1
appnope==0.1.3
argon2-cffi==21.3.0
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
arrow==1.2.3
asttokens==2.2.1
async-timeout==4.0.2
arrow==1.3.0
asttokens==2.4.1
async-timeout==4.0.3
attrs==23.1.0
Babel==2.12.1
backcall==0.2.0
beautifulsoup4==4.12.2
black==23.1.0
bleach==6.0.0
bleach==6.1.0
certifi==2023.7.22
cffi==1.15.1
charset-normalizer==3.2.0
click==8.1.6
comm==0.1.4
coverage==7.2.7
debugpy==1.6.7
cffi==1.16.0
charset-normalizer==3.3.2
click==8.1.7
comm==0.2.0
coverage==7.3.2
debugpy==1.8.0
decorator==5.1.1
defusedxml==0.7.1
deprecation==2.1.0
docutils==0.20.1
entrypoints==0.4
exceptiongroup==1.1.2
executing==1.2.0
Faker==19.2.0
fastapi==0.100.1
fastjsonschema==2.18.0
executing==2.0.1
fastapi==0.104.1
fastjsonschema==2.19.0
flake8==6.1.0
flake8-black==0.3.6
fqdn==1.5.1
frozenlist==1.4.0
future==0.18.3
h11==0.14.0
html5lib==1.1
httpcore==0.17.3
httpx==0.24.1
httpcore==1.0.2
httpx==0.25.1
idna==3.4
imagesize==1.4.1
importlib-metadata==6.8.0
iniconfig==2.0.0
ipykernel==6.25.0
ipython==8.14.0
ipykernel==6.26.0
ipython==8.17.2
ipython-genutils==0.2.0
ipywidgets==8.1.0
ipywidgets==8.1.1
isoduration==20.11.0
jedi==0.19.0
Jinja2==3.1.2
jedi==0.19.1
json5==0.9.14
jsonpointer==2.4
jsonschema==4.18.6
jsonschema-specifications==2023.7.1
jupyter-events==0.7.0
jsonschema==4.19.2
jsonschema-specifications==2023.11.1
jupyter-events==0.9.0
jupyter-ydoc==0.2.5
jupyter_client==7.4.9
jupyter_core==5.3.1
jupyter_core==5.5.0
jupyter_packaging==0.12.3
jupyter_server==2.7.0
jupyter_server==2.10.1
jupyter_server_fileid==0.9.0
jupyter_server_terminals==0.4.4
jupyter_server_ydoc==0.8.0
jupyterlab==3.6.5
jupyterlab==3.6.6
jupyterlab-pygments==0.2.2
jupyterlab-widgets==3.0.8
jupyterlab_server==2.24.0
MarkupSafe==2.1.3
jupyterlab-widgets==3.0.9
jupyterlab_server==2.25.1
matplotlib-inline==0.1.6
mccabe==0.7.0
mistune==3.0.1
mistune==3.0.2
multidict==6.0.4
mypy-extensions==1.0.0
nbclassic==1.0.0
nbclient==0.8.0
nbconvert==7.7.3
nbclient==0.9.0
nbconvert==7.11.0
nbformat==5.9.2
nest-asyncio==1.5.7
notebook==6.5.5
nest-asyncio==1.5.8
notebook==6.5.6
notebook_shim==0.2.3
numpy==1.25.2
overrides==7.3.1
packaging==23.1
pandas==2.0.3
numpy==1.26.2
overrides==7.4.0
packaging==23.2
pandas==2.1.3
pandocfilters==1.5.0
parso==0.8.3
pathspec==0.11.2
perspective-python==2.6.1
pexpect==4.8.0
pickleshare==0.7.5
platformdirs==3.10.0
pluggy==1.2.0
prometheus-client==0.17.1
prompt-toolkit==3.0.39
psutil==5.9.5
pip==23.3.1
platformdirs==4.0.0
pluggy==1.3.0
prometheus-client==0.18.0
prompt-toolkit==3.0.41
psutil==5.9.6
ptyprocess==0.7.0
pure-eval==0.2.2
pyarrow==12.0.1
pyarrow==14.0.1
pybind11==2.11.1
pycodestyle==2.11.0
pycodestyle==2.11.1
pycparser==2.21
pydantic==2.1.1
pydantic_core==2.4.0
pydantic==2.5.1
pydantic_core==2.14.3
pyflakes==3.1.0
Pygments==2.15.1
pytest==7.4.0
pytest-aiohttp==1.0.4
pytest==7.4.3
pytest-aiohttp==1.0.5
pytest-asyncio==0.21.1
pytest-cov==4.1.0
pytest-tornado==0.8.1
pytest_check_links==0.9.0
python-dateutil==2.8.2
python-json-logger==2.0.7
pytz==2023.3
PyYAML==6.0.1
pytz==2023.3.post1
pyzmq==24.0.1
referencing==0.30.1
referencing==0.31.0
requests==2.31.0
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rpds-py==0.9.2
Send2Trash==1.8.2
rpds-py==0.12.0
setuptools==68.2.2
six==1.16.0
sniffio==1.3.0
snowballstemmer==2.2.0
soupsieve==2.4.1
Sphinx==7.1.2
sphinx-markdown-builder==0.6.4
sphinxcontrib-applehelp==1.0.4
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.1
soupsieve==2.5
sphinx-markdown-builder==0.6.5
sphinxcontrib-applehelp==1.0.7
sphinxcontrib-devhelp==1.0.5
sphinxcontrib-htmlhelp==2.0.4
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
stack-data==0.6.2
sphinxcontrib-qthelp==1.0.6
sphinxcontrib-serializinghtml==1.1.9
stack-data==0.6.3
starlette==0.27.0
tabulate==0.9.0
terminado==0.17.1
terminado==0.18.0
tinycss2==1.2.1
tomli==2.0.1
tomlkit==0.12.1
tornado==6.3.2
traitlets==5.9.0
typing_extensions==4.7.1
tomlkit==0.12.3
tornado==6.3.3
traitlets==5.13.0
types-python-dateutil==2.8.19.14
typing_extensions==4.8.0
tzdata==2023.3
uri-template==1.3.0
urllib3==2.0.4
wcwidth==0.2.6
urllib3==2.1.0
wcwidth==0.2.10
webcolors==1.13
webencodings==0.5.1
websocket-client==1.6.1
widgetsnbextension==4.0.8
y-py==0.6.0
websocket-client==1.6.4
wheel==0.41.3
widgetsnbextension==4.0.9
y-py==0.6.2
yarl==1.9.2
ypy-websocket==0.8.4
zipp==3.16.2
Loading
Loading