Skip to content

Commit 45ed636

Browse files
authored
Make it possible/easier to render bokeh plots (#32)
1 parent c300ad4 commit 45ed636

File tree

2 files changed

+44
-20
lines changed

2 files changed

+44
-20
lines changed
Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,20 @@
55
from ipyshiny import *
66
import numpy as np
77

8+
9+
# TODO: jupyter_bokeh assumes this additional JS has been loaded into the
10+
# (in a notebook, this comes in via bokeh.io.output_notebook()).
11+
# We could probably insert this on widget construction, just before the comm
12+
# gets initialized (it's not currently working due to run_coro_sync() not working
13+
# when session._send_message() wants to send a big payload).
14+
from htmltools import head_content, HTML
15+
from bokeh.resources import Resources
16+
17+
bokeh_js = HTML(Resources(mode="inline").render())
18+
19+
820
app_ui = ui.page_fluid(
21+
head_content(bokeh_js),
922
ui.layout_sidebar(
1023
ui.panel_sidebar(
1124
ui.input_radio_buttons(
@@ -17,14 +30,14 @@
1730
"pydeck",
1831
"altair",
1932
"plotly",
20-
# TODO: fix me
21-
# "bokeh",
33+
"bokeh",
2234
"bqplot",
2335
"ipychart",
2436
"ipywebrtc",
25-
"ipyvolume",
37+
# TODO: fix me
38+
# "ipyvolume",
2639
],
27-
selected="qgrid",
40+
selected="ipyleaflet",
2841
)
2942
),
3043
ui.panel_main(
@@ -255,18 +268,12 @@ def _():
255268
@render_widget()
256269
def _():
257270
from bokeh.plotting import figure
258-
from jupyter_bokeh import BokehModel
259-
260-
# TODO: figure out what this does and try to replicate it
261-
import bokeh.io
262-
263-
bokeh.io.output_notebook()
264271

265272
x = [1, 2, 3, 4, 5]
266273
y = [6, 7, 2, 4, 5]
267274
p = figure(title="Simple line example", x_axis_label="x", y_axis_label="y")
268275
p.line(x, y, legend_label="Temp.", line_width=2)
269-
return BokehModel(p)
276+
return p
270277

271278

272279
app = App(app_ui, server)

ipyshiny/_ipyshiny.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -185,28 +185,45 @@ def wrapper(fn: Union[IPyWidgetRenderFunc, IPyWidgetRenderFuncAsync]) -> IPyWidg
185185
return wrapper
186186

187187

188-
# altair objects aren't directly renderable as an ipywidget,
189-
# but we can still render them as an ipywidget via ipyvega
190-
# TODO: we should probably do this for bokeh, pydeck, and probably others as well
188+
# altair/pydeck/bokeh objects aren't directly renderable as an ipywidget,
189+
# but we can coerce them into one
191190
def _as_widget(x: object) -> Widget:
192-
if widget_pkg(x) == "altair":
191+
pkg = widget_pkg(x)
192+
if pkg == "altair" and not isinstance(x, Widget):
193193
try:
194194
import altair
195195
from vega.widget import VegaWidget
196196

197197
x = cast(altair.Chart, x)
198198
x = VegaWidget(x.to_dict()) # type: ignore
199199
except ImportError:
200-
raise ImportError("ipyvega is required to render altair charts")
201-
elif widget_pkg(x) == "pydeck":
202-
import pydeck
200+
raise ImportError(
201+
"To render altair charts, the ipyvega package must be installed."
202+
)
203+
except Exception as e:
204+
raise RuntimeError(f"Failed to coerce {x} into a VegaWidget: {e}")
203205

204-
if isinstance(x, pydeck.Deck):
206+
elif pkg == "pydeck" and not isinstance(x, Widget):
207+
try:
205208
from pydeck.widget import DeckGLWidget
206209

207-
x_ = x.to_json()
210+
x_ = x.to_json() # type: ignore
208211
x = DeckGLWidget()
209212
x.json_input = x_
213+
except Exception as e:
214+
raise RuntimeError(f"Failed to coerce {x} into a DeckGLWidget: {e}")
215+
216+
elif pkg == "bokeh" and not isinstance(x, Widget):
217+
try:
218+
from jupyter_bokeh import BokehModel
219+
220+
x = BokehModel(x) # type: ignore
221+
except ImportError:
222+
raise ImportError(
223+
"To render bokeh charts, the jupyter_bokeh package must be installed."
224+
)
225+
except Exception as e:
226+
raise RuntimeError(f"Failed to coerce {x} into a BokehModel: {e}")
210227

211228
if isinstance(x, Widget):
212229
return x

0 commit comments

Comments
 (0)