A demo of Panel and Perspective (on server+client).
It’s just a quick hacking together of perspective tornado example and Panel.
Could potentially allow panel apps to efficiently stream lots of data to lots of users without too much of some types of overhead (e.g. server cpu, app coding difficulty level).
Note: I had never used python 3.9, panel 1.3.1, or perspective 1.9.4 (or tornado 6.3, or bokeh 3…) until making this post (I am stuck using many years older versions of everything). So I’m sure some stuff is wrong (not just the missing perspective theme/style) because the world seems to have changed a lot since I last looked
################################################################################
### some app (a data source, a "per-user" part, and a "server part")
# data source
import asyncio
import random
from datetime import datetime, timezone
async def send_data(f):
while True:
await asyncio.sleep(0.1)
f({"x": [datetime.now(tz=timezone.utc)], "y": [random.random()]})
# panel app
def widgets():
import panel as pn
return pn.Row(
ExamplePerspectiveWidget(width=400, height=500),
ExamplePerspectiveWidget(width=400, height=500),
)
# "server side" of this app (e.g. startup script)
def server_table():
from perspective import Table
from datetime import datetime, timezone
# https://perspective.finos.org/docs/python/#table
return ("testing123", Table({"x": datetime, "y": float}))
################################################################################
# demo of panel and perspective "server side" integration
import panel.io.server
from perspective import PerspectiveManager
def some_perspective_panel_server_integration(app, perspective_tables):
server = panel.io.server.get_server(app, start=False, admin=True, port=9876)
# https://perspective.finos.org/docs/python/#perspectivemanager
perspective_manager = PerspectiveManager(lock=True)
# https://perspective.finos.org/docs/python/#async-mode
perspective_manager.set_loop_callback(server.io_loop.add_callback)
# https://perspective.finos.org/docs/python/#perspectivetornadohandler
from perspective import PerspectiveTornadoHandler
server._tornado.add_handlers(".*", [
("/perspective_websocket",PerspectiveTornadoHandler,{"manager": perspective_manager, "check_origin": True})])
for name,table in perspective_tables:
# https://perspective.finos.org/docs/python/#hosting-table-and-view-instances
perspective_manager.host_table(name, table)
return server
###########################################################################
# sketch of a widget
from panel import config
from panel.reactive import ReactiveHTML
PSP_VERSION = "1.9.4"
class ExamplePerspectiveWidget(ReactiveHTML):
# copy paste hack of https://github.com/holoviz/panel/blob/main/panel/models/perspective.py
__javascript__ = [
f"{config.npm_cdn}/@finos/perspective@{PSP_VERSION}/dist/umd/perspective.js",
f"{config.npm_cdn}/@finos/perspective-viewer@{PSP_VERSION}/dist/umd/perspective-viewer.js",
f"{config.npm_cdn}/@finos/perspective-viewer-datagrid@{PSP_VERSION}/dist/umd/perspective-viewer-datagrid.js",
f"{config.npm_cdn}/@finos/perspective-viewer-d3fc@{PSP_VERSION}/dist/umd/perspective-viewer-d3fc.js",
]
__js_skip__ = {"perspective": __javascript__}
__js_require__ = {
"paths": {
"perspective": f"{config.npm_cdn}/@finos/perspective@{PSP_VERSION}/dist/umd/perspective",
"perspective-viewer": f"{config.npm_cdn}/@finos/perspective-viewer@{PSP_VERSION}/dist/umd/perspective-viewer",
"perspective-viewer-datagrid": f"{config.npm_cdn}/@finos/perspective-viewer-datagrid@{PSP_VERSION}/dist/umd/perspective-viewer-datagrid",
"perspective-viewer-d3fc": f"{config.npm_cdn}/@finos/perspective-viewer-d3fc@{PSP_VERSION}/dist/umd/perspective-viewer-d3fc",
},
"exports": {
"perspective": "perspective",
"perspective-viewer": "PerspectiveViewer",
"perspective-viewer-datagrid": "PerspectiveViewerDatagrid",
"perspective-viewer-d3fc": "PerspectiveViewerD3fc",
},
}
__css__ = [
f"{config.npm_cdn}/@finos/perspective-viewer@{PSP_VERSION}/dist/css/themes.css"
]
_template = """
<perspective-viewer
id="viewer"
plugin="datagrid"
style="height:100%; width:100%;"
editable
> </perspective-viewer>"""
_scripts = {
"after_layout": """
const websocket = perspective.websocket(`ws://${window.location['host']}/perspective_websocket`);
const table = websocket.open_table("testing123");
viewer.load(table);
viewer.restore({plugin: "Datagrid", columns: ["x","y"], sort: [["x","desc"]]});
viewer.toggleConfig();
"""
}
if __name__ == '__main__':
name, table = server_table()
server = some_perspective_panel_server_integration(widgets, [(name,table)])
# data sending
server.io_loop.add_callback(lambda: send_data(table.update))
server.start()
try:
server.io_loop.start()
except RuntimeError:
pass
To run this, I set up a fresh environment on linux python 3.9.18 via pip install "panel==1.3.1" "perspective-python==1.9.4"
.
For the record, pip freeze shows:
asttokens==2.4.1
bleach==6.1.0
bokeh==3.3.1
certifi==2023.11.17
charset-normalizer==3.3.2
comm==0.2.0
contourpy==1.2.0
decorator==5.1.1
exceptiongroup==1.1.3
executing==2.0.1
future==0.18.3
idna==3.4
importlib-metadata==6.8.0
ipython==8.17.2
ipywidgets==8.1.1
jedi==0.19.1
Jinja2==3.1.2
jupyterlab-widgets==3.0.9
linkify-it-py==2.0.2
Markdown==3.5.1
markdown-it-py==3.0.0
MarkupSafe==2.1.3
matplotlib-inline==0.1.6
mdit-py-plugins==0.4.0
mdurl==0.1.2
numpy==1.26.2
packaging==23.2
pandas==1.5.3
panel==1.3.1
param==2.0.1
parso==0.8.3
perspective-python==1.9.4
pexpect==4.8.0
Pillow==10.1.0
prompt-toolkit==3.0.41
ptyprocess==0.7.0
pure-eval==0.2.2
Pygments==2.17.0
python-dateutil==2.8.2
pytz==2023.3.post1
pyviz_comms==3.0.0
PyYAML==6.0.1
requests==2.31.0
six==1.16.0
stack-data==0.6.3
tornado==6.3.3
tqdm==4.66.1
traitlets==5.13.0
typing_extensions==4.8.0
uc-micro-py==1.0.2
urllib3==2.1.0
wcwidth==0.2.10
webencodings==0.5.1
widgetsnbextension==4.0.9
xyzservices==2023.10.1
zipp==3.17.0