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

render_widget now attaches it's return value to the decorated function #119

Merged
merged 10 commits into from
Jan 16, 2024
40 changes: 22 additions & 18 deletions examples/ipyleaflet/app.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,50 @@
import ipyleaflet as L
from htmltools import css
from shiny import *
from shiny import App, reactive, render, req, ui

from shinywidgets import output_widget, reactive_read, register_widget
from shinywidgets import output_widget, reactive_read, render_widget

app_ui = ui.page_fillable(
ui.div(
app_ui = ui.page_sidebar(
ui.sidebar(
ui.input_slider("zoom", "Map zoom level", value=4, min=1, max=10),
),
ui.card(
ui.output_text("map_bounds"),
style=css(
display="flex", justify_content="center", align_items="center", gap="2rem"
),
fill=False
),
ui.card(
output_widget("lmap")
),
output_widget("map"),
title="ipyleaflet demo"
)


def server(input, output, session):

# Initialize and display when the session starts (1)
map = L.Map(center=(52, 360), zoom=4)
register_widget("map", map)
@output
@render_widget
def lmap():
return L.Map(center=(52, 360), zoom=4)

# When the slider changes, update the map's zoom attribute (2)
@reactive.Effect
def _():
map.zoom = input.zoom()
lmap.widget.zoom = input.zoom()

# When zooming directly on the map, update the slider's value (2 and 3)
@reactive.Effect
def _():
ui.update_slider("zoom", value=reactive_read(map, "zoom"))
zoom = reactive_read(lmap.widget, "zoom")
ui.update_slider("zoom", value=zoom)

# Everytime the map's bounds change, update the output message (3)
@output
@render.text
def map_bounds():
b = reactive_read(map, "bounds")
b = reactive_read(lmap.widget, "bounds")
req(b)
lat = [b[0][0], b[0][1]]
lon = [b[1][0], b[1][1]]
return f"The current latitude is {lat} and longitude is {lon}"
lat = [round(x) for x in [b[0][0], b[0][1]]]
lon = [round(x) for x in [b[1][0], b[1][1]]]
return f"The map bounds is currently {lat} / {lon}"


app = App(app_ui, server)
41 changes: 22 additions & 19 deletions examples/ipywidgets/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,32 @@

from shinywidgets import *

app_ui = ui.page_fluid(output_widget("slider"), ui.output_text("value"))
app_ui = ui.page_fluid(output_widget("slider", fillable=False, fill=False), ui.output_text("slider_val"))


def server(input: Inputs, output: Outputs, session: Session):
s: Widget = ipy.IntSlider(
value=7,
min=0,
max=10,
step=1,
description="Test:",
disabled=False,
continuous_update=False,
orientation="horizontal",
readout=True,
readout_format="d",
)

register_widget("slider", s)

@output(id="value")

@output
@render_widget
def slider():
return ipy.IntSlider(
value=7,
min=0,
max=10,
step=1,
description="Test:",
disabled=False,
continuous_update=False,
orientation="horizontal",
readout=True,
readout_format="d",
)

@output
@render.text
def _():
return f"The value of the slider is: {reactive_read(s, 'value')}"
def slider_val():
val = reactive_read(slider.widget, "value")
return f"The value of the slider is: {val}"


app = App(app_ui, server, debug=True)
46 changes: 24 additions & 22 deletions examples/plotly/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from shiny import *
from sklearn.linear_model import LinearRegression

from shinywidgets import output_widget, register_widget
from shinywidgets import output_widget, render_widget

# Generate some data and fit a linear regression
n = 10000
Expand All @@ -15,35 +15,37 @@

app_ui = ui.page_fillable(
ui.input_checkbox("show_fit", "Show fitted line", value=True),
output_widget("scatterplot"),
output_widget("scatterplot")
)


def server(input, output, session):

scatterplot = go.FigureWidget(
data=[
go.Scattergl(
x=x,
y=y,
mode="markers",
marker=dict(color="rgba(0, 0, 0, 0.05)", size=5),
),
go.Scattergl(
x=xgrid,
y=fit.intercept_ + fit.coef_[0] * xgrid,
mode="lines",
line=dict(color="red", width=2),
),
],
layout={"showlegend": False},
)

register_widget("scatterplot", scatterplot)
@output
@render_widget
def scatterplot():
return go.FigureWidget(
data=[
go.Scattergl(
x=x,
y=y,
mode="markers",
marker=dict(color="rgba(0, 0, 0, 0.05)", size=5),
),
go.Scattergl(
x=xgrid,
y=fit.intercept_ + fit.coef_[0] * xgrid,
mode="lines",
line=dict(color="red", width=2),
),
],
layout={"showlegend": False},
)

@reactive.Effect
def _():
scatterplot.data[1].visible = input.show_fit()
plt.data[1].visible = input.show_fit()



app = App(app_ui, server)
63 changes: 32 additions & 31 deletions examples/pydeck/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,45 @@

app_ui = ui.page_fillable(
ui.input_slider("zoom", "Zoom", 0, 20, 6, step=1),
output_widget("pydeck")
output_widget("deckmap")
)

def server(input: Inputs, output: Outputs, session: Session):

UK_ACCIDENTS_DATA = "https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv"

layer = pdk.Layer(
"HexagonLayer", # `type` positional argument is here
UK_ACCIDENTS_DATA,
get_position=["lng", "lat"],
auto_highlight=True,
elevation_scale=50,
pickable=True,
elevation_range=[0, 3000],
extruded=True,
coverage=1,
)

view_state = pdk.ViewState(
longitude=-1.415,
latitude=52.2323,
zoom=6,
min_zoom=5,
max_zoom=15,
pitch=40.5,
bearing=-27.36,
)

deck = pdk.Deck(layers=[layer], initial_view_state=view_state)

# Register either the deck (or deck_widget) instance
register_widget("pydeck", deck)
@output
@render_widget
def deckmap():

UK_ACCIDENTS_DATA = "https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv"

layer = pdk.Layer(
"HexagonLayer", # `type` positional argument is here
UK_ACCIDENTS_DATA,
get_position=["lng", "lat"],
auto_highlight=True,
elevation_scale=50,
pickable=True,
elevation_range=[0, 3000],
extruded=True,
coverage=1,
)

view_state = pdk.ViewState(
longitude=-1.415,
latitude=52.2323,
zoom=6,
min_zoom=5,
max_zoom=15,
pitch=40.5,
bearing=-27.36,
)

return pdk.Deck(layers=[layer], initial_view_state=view_state)

@reactive.Effect()
def _():
deck.initial_view_state.zoom = input.zoom()
deck.update()
deckmap.value.initial_view_state.zoom = input.zoom()
deckmap.value.update()


app = App(app_ui, server)
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ setup_requires =
install_requires =
ipywidgets>=7.6.5
jupyter_core
shiny>=0.5.1.9003
# shiny>=0.6.1.9003
shiny @ git+https://github.com/posit-dev/py-shiny.git
python-dateutil>=2.8.2
# Needed because of https://github.com/python/importlib_metadata/issues/411
importlib-metadata>=4.8.3,<5; python_version < "3.8"
Expand Down
33 changes: 27 additions & 6 deletions shinywidgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,34 @@
__email__ = "[email protected]"
__version__ = "0.2.4.9000"

from ._as_widget import as_widget
from ._dependencies import bokeh_dependency
from ._shinywidgets import (
as_widget,
output_widget,
reactive_read,
register_widget,
from ._output_widget import output_widget
from ._render_widget import (
render_altair,
render_bokeh,
render_leaflet,
render_plotly,
render_pydeck,
render_widget,
)
from ._shinywidgets import reactive_read, register_widget

__all__ = ("output_widget", "register_widget", "render_widget", "reactive_read", "bokeh_dependency", "as_widget")
__all__ = (
# Render methods first
"render_widget",
"render_altair",
"render_bokeh",
"render_leaflet",
"render_plotly",
"render_pydeck",
# Reactive read second
"reactive_read",
# UI methods third
"output_widget",
# Other methods last
"as_widget",
"bokeh_dependency",
# Soft deprecated
"register_widget",
)
9 changes: 5 additions & 4 deletions shinywidgets/_as_widget.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Optional

from ipywidgets.widgets.widget import Widget
from ipywidgets.widgets.widget import Widget # pyright: ignore[reportMissingTypeStubs]

from ._dependencies import widget_pkg

Expand Down Expand Up @@ -46,7 +46,7 @@ def as_widget_altair(x: object) -> Optional[Widget]:

def as_widget_bokeh(x: object) -> Optional[Widget]:
try:
from jupyter_bokeh import BokehModel
from jupyter_bokeh import BokehModel # pyright: ignore[reportMissingTypeStubs]
except ImportError:
raise ImportError(
"Install the jupyter_bokeh package to use bokeh with shinywidgets."
Expand All @@ -56,15 +56,16 @@ def as_widget_bokeh(x: object) -> Optional[Widget]:
# `BokehModel(x)._model.sizing_mode = "stretch_both"`
# there, but that doesn't seem to work??
from bokeh.plotting import figure

if isinstance(x, figure):
x.sizing_mode = "stretch_both"
x.sizing_mode = "stretch_both" # pyright: ignore[reportGeneralTypeIssues]

return BokehModel(x) # type: ignore


def as_widget_plotly(x: object) -> Optional[Widget]:
# Don't need a try import here since this won't be called unless x is a plotly object
import plotly.graph_objects as go
import plotly.graph_objects as go # pyright: ignore[reportMissingTypeStubs]

if not isinstance(x, go.Figure):
raise TypeError(
Expand Down
17 changes: 17 additions & 0 deletions shinywidgets/_cdn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import os

all = (
"SHINYWIDGETS_CDN",
"SHINYWIDGETS_CDN_ONLY",
"SHINYWIDGETS_EXTENSION_WARNING",
)

# Make it easier to customize the CDN fallback (and make it CDN-only)
# https://ipywidgets.readthedocs.io/en/7.6.3/embedding.html#python-interface
# https://github.com/jupyter-widgets/ipywidgets/blob/6f6156c7/packages/html-manager/src/libembed-amd.ts#L6-L14
SHINYWIDGETS_CDN = os.getenv("SHINYWIDGETS_CDN", "https://cdn.jsdelivr.net/npm/")
SHINYWIDGETS_CDN_ONLY = os.getenv("SHINYWIDGETS_CDN_ONLY", "false").lower() == "true"
# Should shinywidgets warn if unable to find a local path to a widget extension?
SHINYWIDGETS_EXTENSION_WARNING = (
os.getenv("SHINYWIDGETS_EXTENSION_WARNING", "false").lower() == "true"
)
Loading