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

Add support for rendering in pyodide/pyscript #5338

Merged
merged 2 commits into from
Jun 26, 2022
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
5 changes: 4 additions & 1 deletion holoviews/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import os, io
import io, os, sys

import numpy as np # noqa (API import)
import param
Expand Down Expand Up @@ -44,6 +44,9 @@ class notebook_extension(param.ParameterizedFunction):
def __call__(self, *args, **opts): # noqa (dummy signature)
raise Exception("IPython notebook not available: use hv.extension instead.")

if '_pyodide' in sys.modules:
from .pyodide import pyodide_extension as extension # noqa (API import)

# A single holoviews.rc file may be executed if found.
for rcfile in [os.environ.get("HOLOVIEWSRC", ''),
os.path.abspath(os.path.join(os.path.split(__file__)[0],
Expand Down
92 changes: 92 additions & 0 deletions holoviews/pyodide.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import asyncio
import sys

from js import document

from bokeh.embed.elements import script_for_render_items
from bokeh.embed.util import standalone_docs_json_and_render_items
from bokeh.embed.wrappers import wrap_in_script_tag
from panel.io.pyodide import _link_docs
from panel.pane import panel as as_panel

from ..core.dimension import LabelledData
from ..core.options import Store
from ..util import extension as _extension


#-----------------------------------------------------------------------------
# Private API
#-----------------------------------------------------------------------------

async def _link(ref, doc):
from js import Bokeh
rendered = Bokeh.index.object_keys()
if ref not in rendered:
await asyncio.sleep(0.1)
await _link(ref, doc)
return
views = Bokeh.index.object_values()
view = views[rendered.indexOf(ref)]
_link_docs(doc, view.model.document)

def render_html(obj):
if hasattr(sys.stdout, '_out'):
out = sys.stdout._out # type: ignore
else:
raise ValueError("Could not determine target node to write to.")
doc = Document()
as_panel(obj).server_doc(doc, location=False)
docs_json, [render_item,] = standalone_docs_json_and_render_items(
doc.roots, suppress_callback_warning=True
)
for root in doc.roots:
render_item.roots._roots[root] = target
document.getElementById(target).classList.add('bk-root')
script = script_for_render_items(docs_json, [render_item])
asyncio.create_task(_link(doc.roots[0].ref['id'], doc))
return {'text/html': wrap_in_script_tag(script)}, {}

def render_image(element, fmt):
"""
Used to render elements to an image format (svg or png) if requested
in the display formats.
"""
if fmt not in Store.display_formats:b
return None

backend = Store.current_backend
if type(element) not in Store.registry[backend]:
return None
renderer = Store.renderers[backend]
plot = renderer.get_plot(element)

# Current renderer does not support the image format
if fmt not in renderer.param.objects('existing')['fig'].objects:
return None

data, info = renderer(plot, fmt=fmt)
return {info['mime_type']: data}, {}

def render_png(obj):
return render_image(element, 'png')

def render_svg(obj):
return render_image(element, 'svg')


#-----------------------------------------------------------------------------
# Public API
#-----------------------------------------------------------------------------

class pyodide_extension(_extension):

_loaded = False

def __call__(self, *args, **params):
super().__call__(*args, **params)
if not self._loaded:
Store.output_settings.initialize(list(Store.renderers.keys()))
Store.set_display_hook('html+js', LabelledData, render_html)
Store.set_display_hook('png', LabelledData, render_png)
Store.set_display_hook('svg', LabelledData, render_svg)
pyodide_extension._loaded = True
2 changes: 2 additions & 0 deletions holoviews/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,8 @@ class extension(_pyviz_extension):
# Hooks run when a backend is loaded
_backend_hooks = defaultdict(list)

_loaded = False

def __call__(self, *args, **params):
# Get requested backends
config = params.pop('config', {})
Expand Down