Skip to content

Instantly share code, notes, and snippets.

@gshotwell
Created March 28, 2023 13:04
Show Gist options
  • Save gshotwell/92fda56ba726670f30803f29ea71d431 to your computer and use it in GitHub Desktop.
Save gshotwell/92fda56ba726670f30803f29ea71d431 to your computer and use it in GitHub Desktop.
import datetime
from typing import Dict, List, Optional, Tuple
import astropy.units as u
import matplotlib.dates as mpldates
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pytz
import suntime
import timezonefinder
from astropy.coordinates import AltAz, EarthLocation, SkyCoord
from location import location_server, location_ui
from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui
app_ui = ui.page_fixed(
ui.tags.h3("Air mass calculator"),
ui.div(
ui.markdown(
"""This Shiny app uses [Astropy](https://www.astropy.org/) to calculate the
altitude (degrees above the horizon) and airmass (the amount of atmospheric
air along your line of sight to an object) of one or more astronomical
objects, over a given evening, at a given geographic location.
"""
),
class_="mb-5",
),
ui.row(
ui.column(
8,
ui.output_ui("timeinfo"),
ui.output_plot("plot", height="800px"),
# For debugging
# ui.output_table("table"),
class_="order-2 order-sm-1",
),
ui.column(
4,
ui.panel_well(
ui.input_date("date", "Date"),
class_="pb-1 mb-3",
),
ui.panel_well(
ui.input_text_area(
"objects", "Target object(s)", "M1, NGC35, PLX299", rows=3
),
class_="pb-1 mb-3",
),
ui.panel_well(
location_ui("location"),
class_="mb-3",
),
class_="order-1 order-sm-2",
),
),
)
def server(input: Inputs, output: Outputs, session: Session):
loc = location_server("location")
time_padding = datetime.timedelta(hours=1.5)
@reactive.Calc
def obj_names() -> List[str]:
"""Returns a split and *slightly* cleaned-up list of object names"""
req(input.objects())
return [x.strip() for x in input.objects().split(",") if x.strip() != ""]
@reactive.Calc
def obj_coords() -> List[SkyCoord]:
return [SkyCoord.from_name(name) for name in obj_names()]
@reactive.Calc
def times_utc() -> Tuple[datetime.datetime, datetime.datetime]:
req(input.date())
lat, long = loc()
sun = suntime.Sun(lat, long)
return (
sun.get_sunset_time(input.date()),
sun.get_sunrise_time(input.date() + datetime.timedelta(days=1)),
)
@reactive.Calc
def timezone() -> Optional[str]:
lat, long = loc()
return timezonefinder.TimezoneFinder().timezone_at(lat=lat, lng=long)
@reactive.Calc
def times_at_loc():
start, end = times_utc()
tz = pytz.timezone(timezone())
return (start.astimezone(tz), end.astimezone(tz))
@reactive.Calc
def df() -> Dict[str, pd.DataFrame]:
start, end = times_at_loc()
times = pd.date_range(
start - time_padding,
end + time_padding,
periods=100,
)
lat, long = loc()
eloc = EarthLocation(lat=lat * u.deg, lon=long * u.deg, height=0)
altaz_list = [
obj.transform_to(AltAz(obstime=times, location=eloc))
for obj in obj_coords()
]
return {
obj: pd.DataFrame(
{
"obj": obj,
"time": times,
"alt": altaz.alt,
# Filter out discontinuity
"secz": np.where(altaz.alt > 0, altaz.secz, np.nan),
}
)
for (altaz, obj) in zip(altaz_list, obj_names())
}
@output
@render.plot
def plot():
fig, [ax1, ax2] = plt.subplots(nrows=2)
sunset, sunrise = times_at_loc()
def add_boundary(ax, xval):
ax.axvline(x=xval, c="#888888", ls="dashed")
ax1.set_ylabel("Altitude (deg)")
ax1.set_xlabel("Time")
ax1.set_ylim(-10, 90)
ax1.set_xlim(sunset - time_padding, sunrise + time_padding)
ax1.grid()
add_boundary(ax1, sunset)
add_boundary(ax1, sunrise)
for obj_name, data in df().items():
ax1.plot(data["time"], data["alt"], label=obj_name)
ax1.xaxis.set_major_locator(mpldates.AutoDateLocator())
ax1.xaxis.set_major_formatter(
mpldates.DateFormatter("%H:%M", tz=pytz.timezone(timezone()))
)
ax1.legend(loc="upper right")
ax2.set_ylabel("Air mass")
ax2.set_xlabel("Time")
ax2.set_ylim(4, 1)
ax2.set_xlim(sunset - time_padding, sunrise + time_padding)
ax2.grid()
add_boundary(ax2, sunset)
add_boundary(ax2, sunrise)
for data in df().values():
ax2.plot(data["time"], data["secz"])
ax2.xaxis.set_major_locator(mpldates.AutoDateLocator())
ax2.xaxis.set_major_formatter(
mpldates.DateFormatter("%H:%M", tz=pytz.timezone(timezone()))
)
return fig
@output
@render.table
def table() -> pd.DataFrame:
return pd.concat(df())
@output
@render.ui
def timeinfo():
start_utc, end_utc = times_utc()
start_at_loc, end_at_loc = times_at_loc()
return ui.TagList(
f"Sunset: {start_utc.strftime('%H:%M')}, ",
f"Sunrise: {end_utc.strftime('%H:%M')} ",
"(UTC)",
ui.tags.br(),
f"Sunset: {start_at_loc.strftime('%H:%M')}, ",
f"Sunrise: {end_at_loc.strftime('%H:%M')} ",
f"({timezone()})",
)
# The debug=True causes it to print messages to the console.
app = App(app_ui, server, debug=False)
from typing import Optional
import ipyleaflet as L
from shinywidgets import output_widget, reactive_read, register_widget
from shiny import Inputs, Outputs, Session, module, reactive, req, ui
# ============================================================
# Module: location
# ============================================================
@module.ui
def location_ui(
label: str = "Location",
*,
lat: Optional[float] = None,
long: Optional[float] = None,
) -> ui.TagChildArg:
return ui.div(
ui.input_numeric("lat", "Latitude", value=lat),
ui.input_numeric("long", "Longitude", value=long),
ui.help_text("Click to select location"),
output_widget("map", height="200px"),
ui.tags.style(
"""
.jupyter-widgets.leaflet-widgets {
height: 100% !important;
}
"""
),
)
@module.server
def location_server(
input: Inputs, output: Outputs, session: Session, *, wrap_long: bool = True
):
map = L.Map(center=(0, 0), zoom=1, scoll_wheel_zoom=True)
with reactive.isolate():
marker = L.Marker(location=(input.lat() or 0, input.long() or 0))
with reactive.isolate(): # Use this to ensure we only execute one time
if input.lat() is None and input.long() is None:
ui.notification_show(
"Searching for location...", duration=99999, id="searching"
)
ui.insert_ui(
ui.tags.script(
"""
navigator.geolocation.getCurrentPosition(
({coords}) => {
const {latitude, longitude, altitude} = coords;
Shiny.setInputValue("#HERE#", {latitude, longitude});
},
(err) => {
Shiny.setInputValue("#HERE#", {latitude: 0, longitude: 0});
},
{maximumAge: Infinity, timeout: Infinity}
)
""".replace(
"#HERE#", module.resolve_id("here")
)
),
selector="body",
where="beforeEnd",
immediate=True,
)
@reactive.isolate()
def update_text_inputs(lat: Optional[float], long: Optional[float]) -> None:
req(lat is not None, long is not None)
lat = round(lat, 8)
long = round(long, 8)
if lat != input.lat():
input.lat.freeze()
ui.update_text("lat", value=lat)
if long != input.long():
input.long.freeze()
ui.update_text("long", value=long)
map.center = (lat, long)
@reactive.isolate()
def update_marker(lat: Optional[float], long: Optional[float]) -> None:
req(lat is not None, long is not None)
lat = round(lat, 8)
long = round(long, 8)
if marker.location != (lat, long):
marker.location = (lat, long)
if marker not in map.layers:
map.add_layer(marker)
map.center = marker.location
def on_map_interaction(**kwargs):
if kwargs.get("type") == "click":
lat, long = kwargs.get("coordinates")
update_text_inputs(lat, long)
map.on_interaction(on_map_interaction)
register_widget("map", map)
@reactive.Effect
def _():
coords = reactive_read(marker, "location")
if coords:
update_text_inputs(coords[0], coords[1])
@reactive.Effect
def sync_autolocate():
coords = input.here()
ui.notification_remove("searching")
if coords and not input.lat() and not input.long():
update_text_inputs(coords["latitude"], coords["longitude"])
@reactive.Effect
def sync_inputs_to_marker():
update_marker(input.lat(), input.long())
@reactive.Calc
def location():
"""Returns tuple of (lat,long) floats--or throws silent error if no lat/long is
selected"""
# Require lat/long to be populated before we can proceed
req(input.lat() is not None, input.long() is not None)
try:
long = input.long()
# Wrap longitudes so they're within [-180, 180]
if wrap_long:
long = (long + 180) % 360 - 180
return (input.lat(), long)
except ValueError:
raise ValueError("Invalid latitude/longitude specification")
return location
astropy
ipyleaflet
matplotlib
numpy
pandas
pytz
shiny
shinywidgets
suntime
timezonefinder
#
# This file is autogenerated by pip-compile with python 3.9
# To update, run:
#
# pip-compile
#
anyio==3.6.1
# via starlette
appdirs==1.4.4
# via shiny
appnope==0.1.3
# via
# ipykernel
# ipython
asgiref==3.5.2
# via shiny
astropy==5.1
# via -r requirements.in
asttokens==2.0.8
# via stack-data
backcall==0.2.0
# via ipython
branca==0.5.0
# via ipyleaflet
cffi==1.15.1
# via timezonefinder
click==8.1.3
# via
# shiny
# uvicorn
contextvars==2.4
# via shiny
contourpy==1.0.5
# via matplotlib
cycler==0.11.0
# via matplotlib
debugpy==1.6.3
# via ipykernel
decorator==5.1.1
# via ipython
entrypoints==0.4
# via jupyter-client
executing==1.1.0
# via stack-data
fonttools==4.37.3
# via matplotlib
h11==0.14.0
# via uvicorn
h3==3.7.4
# via timezonefinder
htmltools==0.1.2
# via shiny
idna==3.4
# via anyio
immutables==0.19
# via contextvars
ipykernel==6.16.0
# via ipywidgets
ipyleaflet==0.17.1
# via -r requirements.in
ipython==8.5.0
# via
# ipykernel
# ipywidgets
ipywidgets==8.0.2
# via
# ipyleaflet
# shinywidgets
jedi==0.18.1
# via ipython
jinja2==3.1.2
# via branca
jupyter-client==7.3.5
# via ipykernel
jupyter-core==4.11.2
# via
# jupyter-client
# shinywidgets
jupyterlab-widgets==3.0.3
# via ipywidgets
kiwisolver==1.4.4
# via matplotlib
linkify-it-py==2.0.0
# via shiny
markdown-it-py==2.1.0
# via
# mdit-py-plugins
# shiny
markupsafe==2.1.1
# via jinja2
matplotlib==3.6.0
# via -r requirements.in
matplotlib-inline==0.1.6
# via
# ipykernel
# ipython
mdit-py-plugins==0.3.1
# via shiny
mdurl==0.1.2
# via markdown-it-py
nest-asyncio==1.5.5
# via
# ipykernel
# jupyter-client
numpy==1.23.3
# via
# -r requirements.in
# astropy
# contourpy
# matplotlib
# pandas
# pyerfa
# timezonefinder
packaging==21.3
# via
# astropy
# htmltools
# ipykernel
# matplotlib
pandas==1.5.0
# via -r requirements.in
parso==0.8.3
# via jedi
pexpect==4.8.0
# via ipython
pickleshare==0.7.5
# via ipython
pillow==9.2.0
# via matplotlib
prompt-toolkit==3.0.31
# via ipython
psutil==5.9.2
# via ipykernel
ptyprocess==0.7.0
# via pexpect
pure-eval==0.2.2
# via stack-data
pycparser==2.21
# via cffi
pyerfa==2.0.0.1
# via astropy
pygments==2.13.0
# via ipython
pyparsing==3.0.9
# via
# matplotlib
# packaging
python-dateutil==2.8.2
# via
# jupyter-client
# matplotlib
# pandas
# shinywidgets
# suntime
python-multipart==0.0.5
# via shiny
pytz==2022.2.1
# via
# -r requirements.in
# pandas
pyyaml==6.0
# via astropy
pyzmq==24.0.1
# via
# ipykernel
# jupyter-client
shiny==0.2.6
# via
# -r requirements.in
# shinywidgets
shinywidgets==0.1.2
# via -r requirements.in
six==1.16.0
# via
# python-dateutil
# python-multipart
sniffio==1.3.0
# via anyio
stack-data==0.5.1
# via ipython
starlette==0.21.0
# via shiny
suntime==1.2.5
# via -r requirements.in
timezonefinder==6.1.3
# via -r requirements.in
tornado==6.2
# via
# ipykernel
# jupyter-client
traitlets==5.4.0
# via
# ipykernel
# ipython
# ipywidgets
# jupyter-client
# jupyter-core
# matplotlib-inline
# traittypes
traittypes==0.2.1
# via ipyleaflet
typing-extensions==4.3.0
# via
# htmltools
# shiny
# starlette
uc-micro-py==1.0.1
# via linkify-it-py
uvicorn==0.18.3
# via shiny
wcwidth==0.2.5
# via prompt-toolkit
websockets==10.3
# via shiny
widgetsnbextension==4.0.3
# via ipywidgets
xyzservices==2022.9.0
# via ipyleaflet
# The following packages are considered to be unsafe in a requirements file:
# setuptools
import math
from pathlib import Path
from brownian_motion import brownian_data, brownian_widget
from mediapipe import hand_to_camera_eye, xyz_mean
from shinymediapipe import input_hand
from shinywidgets import output_widget, register_widget
from shiny import App, reactive, render, req, ui
# Check that JS prerequisites are installed
if not (Path(__file__).parent / "shinymediapipe" / "node_modules").is_dir():
raise RuntimeError(
"Mediapipe dependencies are not installed. "
"Please run `npm install` in the 'shinymediapipe' subdirectory."
)
# Set to True to see underlying XYZ values and canvas
debug = True
app_ui = ui.page_fluid(
ui.input_action_button("data_btn", "New Data"),
output_widget("plot"),
input_hand("hand", debug=debug, throttle_delay_secs=0.05),
(
ui.panel_fixed(
ui.div(ui.tags.strong("x:"), ui.output_text("x_debug", inline=True)),
ui.div(ui.tags.strong("y:"), ui.output_text("y_debug", inline=True)),
ui.div(ui.tags.strong("z:"), ui.output_text("z_debug", inline=True)),
ui.div(ui.tags.strong("mag:"), ui.output_text("mag_debug", inline=True)),
left="12px",
bottom="12px",
width="200px",
height="auto",
class_="d-flex flex-column justify-content-end",
)
if debug
else None
),
class_="p-3",
)
def server(input, output, session):
# BROWNIAN MOTION ====
@reactive.Calc
def random_walk():
"""Generates brownian data whenever 'New Data' is clicked"""
input.data_btn()
return brownian_data(n=200)
# Create Plotly 3D widget and bind it to output_widget("plot")
widget = brownian_widget(600, 600)
register_widget("plot", widget)
@reactive.Effect
def update_plotly_data():
walk = random_walk()
layer = widget.data[0]
layer.x = walk["x"]
layer.y = walk["y"]
layer.z = walk["z"]
layer.marker.color = walk["z"]
# HAND TRACKING ====
@reactive.Calc
def camera_eye():
"""The eye position, as reflected by the hand input"""
hand_val = input.hand()
req(hand_val)
res = hand_to_camera_eye(hand_val, detect_ok=True)
req(res)
return res
# The raw data is a little jittery. Smooth it out by averaging a few samples
smooth_camera_eye = reactive_smooth(n_samples=5, smoother=xyz_mean)(camera_eye)
@reactive.Effect
def update_plotly_camera():
"""Update Plotly camera using the hand tracking"""
widget.layout.scene.camera.eye = smooth_camera_eye()
# DEBUGGING ====
@output
@render.text
def x_debug():
return camera_eye()["x"]
@output
@render.text
def y_debug():
return camera_eye()["y"]
@output
@render.text
def z_debug():
return camera_eye()["z"]
@output
@render.text
def mag_debug():
eye = camera_eye()
return f"{round(math.sqrt(eye['x']**2 + eye['y']**2 + eye['z']**2), 2)}"
app = App(app_ui, server)
def reactive_smooth(n_samples, smoother, *, filter_none=True):
"""Decorator for smoothing out reactive calculations over multiple samples"""
def wrapper(calc):
buffer = [] # Ring buffer of capacity `n_samples`
result = reactive.Value(None) # Holds the most recent smoothed result
@reactive.Effect
def _():
# Get latest value. Because this is happening in a reactive Effect, we'll
# automatically take a reactive dependency on whatever is happening in the
# calc().
new_value = calc()
buffer.append(new_value)
while len(buffer) > n_samples:
buffer.pop(0)
if not filter_none:
result.set(smoother(buffer))
else:
# The filter cannot handle None values; remove any in the buffer
filt_samples = [s for s in buffer if s is not None]
if len(filt_samples) == 0:
result.set(None)
else:
result.set(smoother(filt_samples))
# The return value for the wrapper
return result.get
return wrapper
import numpy as np
from plotly import graph_objects as go
# Code taken from https://plotly.com/python/3d-line-plots/
# * `brownian_motion()` is a function that generates a random walk
# * `brownian_widget()` uses restructured plotting code given in article
rs = np.random.RandomState()
# https://plotly.com/python/3d-line-plots/
def brownian_motion(T=1, N=100, mu=0.1, sigma=0.01, S0=20):
dt = float(T) / N
t = np.linspace(0, T, N)
W = rs.standard_normal(size=N)
W = np.cumsum(W) * np.sqrt(dt) # standard brownian motion
X = (mu - 0.5 * sigma**2) * t + sigma * W
S = S0 * np.exp(X) # geometric brownian motion
return S
def brownian_data(n=100, mu=(0.0, 0.00), sigma=(0.1, 0.1), S0=(1.0, 1.0)):
return {
"x": brownian_motion(T=1, N=n, mu=mu[0], sigma=sigma[0], S0=S0[0]),
"y": brownian_motion(T=1, N=n, mu=mu[1], sigma=sigma[1], S0=S0[1]),
# "y": [i for i in range(n)],
"z": [i for i in range(n)],
}
def brownian_widget(width=600, height=600):
widget = go.FigureWidget(
data=[
go.Scatter3d(
x=[],
y=[],
z=[],
marker=dict(
size=4,
color=[],
colorscale="Viridis",
),
line=dict(color="darkblue", width=2),
)
],
layout={"showlegend": False, "width": width, "height": height},
)
return widget
import math
from functools import reduce
from operator import add
from statistics import mean
import numpy as np
WRIST = 0
THUMB_CMC = 1
THUMB_MCP = 2
THUMB_IP = 3
THUMB_TIP = 4
INDEX_FINGER_MCP = 5
INDEX_FINGER_PIP = 6
INDEX_FINGER_DIP = 7
INDEX_FINGER_TIP = 8
MIDDLE_FINGER_MCP = 9
MIDDLE_FINGER_PIP = 10
MIDDLE_FINGER_DIP = 11
MIDDLE_FINGER_TIP = 12
RING_FINGER_MCP = 13
RING_FINGER_PIP = 14
RING_FINGER_DIP = 15
RING_FINGER_TIP = 16
PINKY_MCP = 17
PINKY_PIP = 18
PINKY_DIP = 19
PINKY_TIP = 20
def hand_to_camera_eye(hands, detect_ok=False):
# TODO: Pointing straight at the camera should not change (much) from the default
# position
# TODO: Use spherical coordinates instead of cartesian
left_hand = hands["multiHandedness"][0]["index"] == 0
hand = hands["multiHandLandmarks"][0]
def rel_hand(start_pos: int, end_pos: int):
return np.subtract(list(hand[start_pos].values()), list(hand[end_pos].values()))
if detect_ok:
# If the distance between the thumbtip and index finger tip are pretty close,
# ignore the hand. (Using "OK" sign to pause hand tracking)
ok_dist = np.linalg.norm(rel_hand(THUMB_TIP, INDEX_FINGER_TIP))
ref_dist = np.linalg.norm(rel_hand(INDEX_FINGER_TIP, INDEX_FINGER_DIP))
if ok_dist < ref_dist * 2:
return None
if not left_hand:
normal_vec = np.cross(
rel_hand(PINKY_MCP, WRIST),
rel_hand(INDEX_FINGER_MCP, WRIST),
)
else:
normal_vec = np.cross(
rel_hand(INDEX_FINGER_MCP, WRIST),
rel_hand(PINKY_MCP, WRIST),
)
normal_unit_vec = normal_vec / np.linalg.norm(normal_vec)
def list_to_xyz(x):
x = list(map(lambda y: round(y, 2), x))
return dict(x=x[0], y=x[1], z=x[2])
# Invert, for some reason
normal_unit_vec = normal_unit_vec * -1.0
normal_unit_vec[1] *= 2.0
# Stay a consistent distance from the origin
dist = 2
magnitude = math.sqrt(reduce(add, [a**2 for a in normal_unit_vec]))
normal_unit_vec = [a / magnitude * dist for a in normal_unit_vec]
new_eye = list_to_xyz(normal_unit_vec)
return {
# Adjust locations to match camera position
"x": new_eye["z"],
"y": new_eye["x"],
"z": new_eye["y"],
}
def xyz_mean(points):
return dict(
x=mean([p["x"] for p in points]),
y=mean([p["y"] for p in points]),
z=mean([p["z"] for p in points]),
)
numpy
shiny
plotly
shinywidgets
from ._hand import dependencies, input_hand, hand_options
__all__ = (
"dependencies",
"hand_options",
"input_hand",
)
import json
from typing import Any, Optional
from htmltools import HTMLDependency, Tag, tags
from shiny.module import resolve_id
HandOptions = dict[str, Any]
def dependencies() -> list[HTMLDependency]:
def subdep(name: str) -> HTMLDependency:
return HTMLDependency(
f"@mediapipe/{name}",
"1.0.0",
source={
"package": "shinymediapipe",
"subdir": f"node_modules/@mediapipe/{name}",
},
script=[{"src": f"{name}.js"}],
)
return [
subdep("camera_utils"),
subdep("control_utils"),
subdep("drawing_utils"),
subdep("hands"),
HTMLDependency(
"shinymediapipe-hands",
"1.0.0",
source={"package": "shinymediapipe", "subdir": ""},
script={"src": "index.js"},
),
]
def input_hand(
id: str,
options: Optional[HandOptions] = None,
*,
debug: bool = False,
throttle_delay_secs: float = 0.1,
precision: int = 3,
) -> Tag:
id = resolve_id(id)
if options is None:
options = hand_options()
return tags.template(
{
"id": id,
"class": "mediapipe-hand-input",
"data-throttle-delay": throttle_delay_secs * 1000,
"data-precision": precision,
},
{"class": "mediapipe-hand-input-debug"} if debug else None,
dependencies(),
tags.script(json.dumps(options), type="application/json"),
)
# https://google.github.io/mediapipe/solutions/hands.html#configuration-options
def hand_options(
*,
maxNumHands: int = 1,
modelComplexity: float = 1.0,
minDetectionConfidence: float = 0.9,
minTrackingConfidence: float = 0.9,
**kwargs,
) -> HandOptions:
maxNumHands = int(maxNumHands)
modelComplexity = float(modelComplexity)
minDetectionConfidence = float(minDetectionConfidence)
minTrackingConfidence = float(minTrackingConfidence)
assert 0 < maxNumHands
assert 0 <= modelComplexity <= 1
assert 0 <= minDetectionConfidence <= 1
assert 0 <= minTrackingConfidence <= 1
return {
"staticImageMode": False,
"maxNumHands": maxNumHands,
"modelComplexity": modelComplexity,
"minDetectionConfidence": minDetectionConfidence,
"minTrackingConfidence": minTrackingConfidence,
# Future model expansion
**kwargs,
}
function mediapipeHand({callback, videoElement, canvasElement, options = {}, debug = false} = {}) {
// Code taken from https://google.github.io/mediapipe/solutions/hands.html
// and modified to set the Shiny input value when a hand is detected.
const canvasCtx = canvasElement.getContext('2d');
function onResults(results) {
// Set Shiny input value
if (results.multiHandLandmarks.length > 0) {
callback(results);
} else {
callback(null);
}
// Do not draw anything unless debugging
if (!debug) return;
// Draw hand on canvas
canvasCtx.save();
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
canvasCtx.drawImage(
results.image, 0, 0, canvasElement.width, canvasElement.height);
if (results.multiHandLandmarks) {
for (const landmarks of results.multiHandLandmarks) {
drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS,
{color: '#00FF00', lineWidth: 2});
drawLandmarks(canvasCtx, landmarks, {color: '#FFFFFF', radius: 1});
}
}
canvasCtx.restore();
}
const hands = new Hands({locateFile: (file) => {
// TODO: This is totally cheating; this path is supposed to be an
// implementation detail
return `lib/@mediapipe/hands-1.0.0/${file}`;
}});
hands.setOptions(options);
hands.onResults(onResults);
const camera = new Camera(videoElement, {
onFrame: async () => {
await hands.send({image: videoElement});
},
width: 640,
height: 480
});
camera.start();
}
class HandInputBinding extends Shiny.InputBinding {
find(scope) {
return $(scope).find("template.mediapipe-hand-input");
}
initialize(el) {
}
getValue(el) {
return el.mediapipe_result || null;
}
setValue(el, value) {
console.warn("setValue called on HandInputBinding, this is not implemented");
}
getRatePolicy(el) {
return {
policy: "throttle",
delay: el.dataset.throttleDelay ?? 100
};
}
subscribe(el, callback) {
const id = this.getId(el);
const options = JSON.parse(el.content.querySelector("script").innerText);
const debug = el.matches(".mediapipe-hand-input-debug");
const precision = el.dataset.precision || 3;
const videoEl = document.createElement("video");
videoEl.style.display = "none";
const canvasEl = document.createElement("canvas");
canvasEl.style.display = debug ? "block" : "none";
canvasEl.style.position = "fixed";
canvasEl.style.right = "12px";
canvasEl.style.bottom = "12px";
canvasEl.style.width = "320px";
canvasEl.style.height = "240px";
canvasEl.style.opacity = 0.85;
document.body.appendChild(videoEl);
document.body.appendChild(canvasEl);
mediapipeHand({callback: (value) => {
// We should allow the options to indicate what values are desired by the server.
if (value) {
// Quick and dirty rounding to 4 decimals
value = JSON.parse(JSON.stringify(value, function(key, value) {
if (key === "image") {
return undefined;
}
if (typeof(value) === "number") {
return +value.toFixed(precision);
} else {
return value;
}
}));
}
el.mediapipe_result = value;
callback(true);
}, videoElement: videoEl, canvasElement: canvasEl, options, debug});
}
unsubscribe(el) {
// TODO: Destroy everything
}
}
// var handInputBinding = new Shiny.InputBinding();
// $.extend(handInputBinding, {
// find: function(scope) {
// return $(scope).find(".increment");
// },
// getValue: function(el) {
// return parseInt($(el).text());
// },
// setValue: function(el, value) {
// $(el).text(value);
// },
// subscribe: function(el, callback) {
// $(el).on("change.incrementBinding", function(e) {
// callback();
// });
// },
// unsubscribe: function(el) {
// $(el).off(".incrementBinding");
// }
// });
Shiny.inputBindings.register(new HandInputBinding());
{
"name": "shinymediapipe",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "shinymediapipe",
"version": "1.0.0",
"license": "Apache-2.0",
"dependencies": {
"@mediapipe/camera_utils": "^0.3.1640029074",
"@mediapipe/control_utils": "^0.6.1629159505",
"@mediapipe/drawing_utils": "^0.3.1620248257",
"@mediapipe/hands": "^0.4.1646424915"
}
},
"node_modules/@mediapipe/camera_utils": {
"version": "0.3.1640029074",
"resolved": "https://registry.npmjs.org/@mediapipe/camera_utils/-/camera_utils-0.3.1640029074.tgz",
"integrity": "sha512-jRV/Mp2lgqNYT68TeVRu/Xq+ptZO9F9vCjxzQsA3VM2r7oXg0TrnfO9De2KKOUTpt0p28dKHs625J8GdWjha8A=="
},
"node_modules/@mediapipe/control_utils": {
"version": "0.6.1629159505",
"resolved": "https://registry.npmjs.org/@mediapipe/control_utils/-/control_utils-0.6.1629159505.tgz",
"integrity": "sha512-n8ZfgwZlv3EDiF2bsz81+74qWqbu5JSukxRRVuJqkqxO1CsfQGFj8XI2Cxj0UlsnO1vzNelZiWV1tP3of47z7g=="
},
"node_modules/@mediapipe/drawing_utils": {
"version": "0.3.1620248257",
"resolved": "https://registry.npmjs.org/@mediapipe/drawing_utils/-/drawing_utils-0.3.1620248257.tgz",
"integrity": "sha512-s598oo1K6C62mX3rWXJ7n1RJFdXjyQn3f6CeI+lU6kD69MVyBcV3hdmO5LnEZlCw5NRDANtaM8WAOuqZHaldLg=="
},
"node_modules/@mediapipe/hands": {
"version": "0.4.1646424915",
"resolved": "https://registry.npmjs.org/@mediapipe/hands/-/hands-0.4.1646424915.tgz",
"integrity": "sha512-R1VM3DRCKTA49nVvkprInYUXx8cKisi86y6/9clvYA0vApmLqTjIHQFibJDHwSdy4Rykn2CjWywQAWw5+mGw8w=="
}
},
"dependencies": {
"@mediapipe/camera_utils": {
"version": "0.3.1640029074",
"resolved": "https://registry.npmjs.org/@mediapipe/camera_utils/-/camera_utils-0.3.1640029074.tgz",
"integrity": "sha512-jRV/Mp2lgqNYT68TeVRu/Xq+ptZO9F9vCjxzQsA3VM2r7oXg0TrnfO9De2KKOUTpt0p28dKHs625J8GdWjha8A=="
},
"@mediapipe/control_utils": {
"version": "0.6.1629159505",
"resolved": "https://registry.npmjs.org/@mediapipe/control_utils/-/control_utils-0.6.1629159505.tgz",
"integrity": "sha512-n8ZfgwZlv3EDiF2bsz81+74qWqbu5JSukxRRVuJqkqxO1CsfQGFj8XI2Cxj0UlsnO1vzNelZiWV1tP3of47z7g=="
},
"@mediapipe/drawing_utils": {
"version": "0.3.1620248257",
"resolved": "https://registry.npmjs.org/@mediapipe/drawing_utils/-/drawing_utils-0.3.1620248257.tgz",
"integrity": "sha512-s598oo1K6C62mX3rWXJ7n1RJFdXjyQn3f6CeI+lU6kD69MVyBcV3hdmO5LnEZlCw5NRDANtaM8WAOuqZHaldLg=="
},
"@mediapipe/hands": {
"version": "0.4.1646424915",
"resolved": "https://registry.npmjs.org/@mediapipe/hands/-/hands-0.4.1646424915.tgz",
"integrity": "sha512-R1VM3DRCKTA49nVvkprInYUXx8cKisi86y6/9clvYA0vApmLqTjIHQFibJDHwSdy4Rykn2CjWywQAWw5+mGw8w=="
}
}
}
{
"name": "shinymediapipe",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "Apache-2.0",
"dependencies": {
"@mediapipe/camera_utils": "^0.3.1640029074",
"@mediapipe/control_utils": "^0.6.1629159505",
"@mediapipe/drawing_utils": "^0.3.1620248257",
"@mediapipe/hands": "^0.4.1646424915"
}
}
ß
ß
†
†
†
¶
†
†
¶
†
†
†
†
†
†
†
annotation⁄id)⁄copyr
selected_datas
Nc
<listcomp>z+server.<locals>.ts_data.<locals>.<listcomp>0
⁄pd⁄ DataFrame⁄np⁄array⁄range⁄get⁄merge⁄fillna⁄print⁄type)⁄ dataframe⁄ annotatedr

Ò
Ù
à 
çdê9âoåo—‘–ÿ–r"
r"
Ò
Ù
à
r"
Nr1
rF
input_text⁄input_action_button)r
ö
†l∞B—7‘7ÒÙ
Ù
Ò Ù
⁄reactive⁄Value⁄Effect⁄eventrS
r
Ñ_›
á^Ç^êE‘)—*‘*(
brush_opts⁄ output_ui⁄ output_table⁄
page_fluid⁄ panel_title⁄h2⁄br⁄div⁄app_ui⁄Inputs⁄Outputs⁄Sessionrj
èäê}®BØM™M¿C®M—,H‘,Hà—I‘Iÿ
è ä ê[—!‘!ÿ
èäò
—&‘&Ò Ù
èäÿáNÇNê2ó5í5–<¿]ê5—S‘S—T‘TÿáEÇEêÄE—‘ÿáFÇFà3–4ÄF—5‘5Ò
ÄMê&
Äcà&ê&—‘ÄÄÄr"
a
j e
† e
j
dddç°e
†de
jded d
çd ç°d d
dçZe
†e
†e
jdddç°e
jddçe
jeddç°ZeeedúddÑZeeeÉZdS
date_rangeZcumsum⁄plot)⁄tsr
plotnine.datar
brush_opts⁄
page_fluid⁄ panel_title⁄h2⁄br⁄divZapp_ui⁄Inputs⁄Outputs⁄Sessionr%
 ˝
import sys
if "pyodide" in sys.modules:
# psutil doesn't work on pyodide--use fake data instead
from fakepsutil import cpu_count, cpu_percent
else:
from psutil import cpu_count, cpu_percent
from math import ceil
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from shiny import App, Inputs, Outputs, Session, reactive, render, ui
# The agg matplotlib backend seems to be a little more efficient than the default when
# running on macOS, and also gives more consistent results across operating systems
matplotlib.use("agg")
# max number of samples to retain
MAX_SAMPLES = 1000
# secs between samples
SAMPLE_PERIOD = 1
ncpu = cpu_count(logical=True)
app_ui = ui.page_fluid(
ui.tags.style(
"""
/* Don't apply fade effect, it's constantly recalculating */
.recalculating {
opacity: 1;
}
tbody > tr:last-child {
/*border: 3px solid var(--bs-dark);*/
box-shadow:
0 0 2px 1px #fff, /* inner white */
0 0 4px 2px #0ff, /* middle cyan */
0 0 5px 3px #00f; /* outer blue */
}
#table table {
table-layout: fixed;
width: %s;
font-size: 0.8em;
}
th, td {
text-align: center;
}
"""
% f"{ncpu*4}em"
),
ui.h3("CPU Usage %", class_="mt-2"),
ui.layout_sidebar(
ui.panel_sidebar(
ui.input_select(
"cmap",
"Colormap",
{
"inferno": "inferno",
"viridis": "viridis",
"copper": "copper",
"prism": "prism (not recommended)",
},
),
ui.p(ui.input_action_button("reset", "Clear history", class_="btn-sm")),
ui.input_switch("hold", "Freeze output", value=False),
class_="mb-3",
),
ui.panel_main(
ui.div(
{"class": "card mb-3"},
ui.div(
{"class": "card-body"},
ui.h5({"class": "card-title mt-0"}, "Graphs"),
ui.output_plot("plot", height=f"{ncpu * 40}px"),
),
ui.div(
{"class": "card-footer"},
ui.input_numeric("sample_count", "Number of samples per graph", 50),
),
),
ui.div(
{"class": "card"},
ui.div(
{"class": "card-body"},
ui.h5({"class": "card-title m-0"}, "Heatmap"),
),
ui.div(
{"class": "card-body overflow-auto pt-0"},
ui.output_table("table"),
),
ui.div(
{"class": "card-footer"},
ui.input_numeric("table_rows", "Rows to display", 5),
),
),
),
),
)
@reactive.Calc
def cpu_current():
reactive.invalidate_later(SAMPLE_PERIOD)
return cpu_percent(percpu=True)
def server(input: Inputs, output: Outputs, session: Session):
cpu_history = reactive.Value(None)
@reactive.Calc
def cpu_history_with_hold():
# If "hold" is on, grab an isolated snapshot of cpu_history; if not, then do a
# regular read
if not input.hold():
return cpu_history()
else:
# Even if frozen, we still want to respond to input.reset()
input.reset()
with reactive.isolate():
return cpu_history()
@reactive.Effect
def collect_cpu_samples():
"""cpu_percent() reports just the current CPU usage sample; this Effect gathers
them up and stores them in the cpu_history reactive value, in a numpy 2D array
(rows are CPUs, columns are time)."""
new_data = np.vstack(cpu_current())
with reactive.isolate():
if cpu_history() is None:
cpu_history.set(new_data)
else:
combined_data = np.hstack([cpu_history(), new_data])
# Throw away extra data so we don't consume unbounded amounts of memory
if combined_data.shape[1] > MAX_SAMPLES:
combined_data = combined_data[:, -MAX_SAMPLES:]
cpu_history.set(combined_data)
@reactive.Effect(priority=100)
@reactive.event(input.reset)
def reset_history():
cpu_history.set(None)
@output
@render.plot
def plot():
history = cpu_history_with_hold()
if history is None:
history = np.array([])
history.shape = (ncpu, 0)
nsamples = input.sample_count()
# Throw away samples too old to fit on the plot
if history.shape[1] > nsamples:
history = history[:, -nsamples:]
ncols = 2
nrows = int(ceil(ncpu / ncols))
fig, axeses = plt.subplots(
nrows=nrows,
ncols=ncols,
squeeze=False,
)
for i in range(0, ncols * nrows):
row = i // ncols
col = i % ncols
axes = axeses[row, col]
if i >= len(history):
axes.set_visible(False)
continue
data = history[i]
axes.yaxis.set_label_position("right")
axes.yaxis.tick_right()
axes.set_xlim(-(nsamples - 1), 0)
axes.set_ylim(0, 100)
assert len(data) <= nsamples
# Set up an array of x-values that will right-align the data relative to the
# plotting area
x = np.arange(0, len(data))
x = np.flip(-x)
# Color bars by cmap
color = plt.get_cmap(input.cmap())(data / 100)
axes.bar(x, data, color=color, linewidth=0, width=1.0)
axes.set_yticks([25, 50, 75])
for ytl in axes.get_yticklabels():
if col == ncols - 1 or i == ncpu - 1 or True:
ytl.set_fontsize(7)
else:
ytl.set_visible(False)
hide_ticks(axes.yaxis)
for xtl in axes.get_xticklabels():
xtl.set_visible(False)
hide_ticks(axes.xaxis)
axes.grid(True, linewidth=0.25)
return fig
@output
@render.table
def table():
history = cpu_history_with_hold()
latest = pd.DataFrame(history).transpose().tail(input.table_rows())
if latest.shape[0] == 0:
return latest
return (
latest.style.format(precision=0)
.hide(axis="index")
.set_table_attributes(
'class="dataframe shiny-table table table-borderless font-monospace"'
)
.background_gradient(cmap=input.cmap(), vmin=0, vmax=100)
)
def hide_ticks(axis):
for ticks in [axis.get_major_ticks(), axis.get_minor_ticks()]:
for tick in ticks:
tick.tick1line.set_visible(False)
tick.tick2line.set_visible(False)
tick.label1.set_visible(False)
tick.label2.set_visible(False)
app = App(app_ui, server)
"""Generates synthetic data"""
import numpy as np
def cpu_count(logical: bool = True):
return 8 if logical else 4
last_sample = np.random.uniform(0, 100, size=cpu_count(True))
def cpu_percent(interval=None, percpu=False):
global last_sample
delta = np.random.normal(scale=10, size=len(last_sample))
last_sample = (last_sample + delta).clip(0, 100)
if percpu:
return last_sample.tolist()
else:
return last_sample.mean()
ß
d¶
e
defdÑZ
The first time you click the button, you should see a 1 appear below the button,
as well as 2 messages in the python console (all reporting 1 click). After
clicking once, clicking again should increment the number below the button and
print the number of clicks in the console twice.
⁄Sync⁄btnzClick me⁄ btn_value⁄Async⁄ btn_async⁄btn_async_value⁄input⁄output⁄sessionc
Nc
–!•3†s†s°u§u°:§:—.‘.–.–.–.r
–'≠®UØ_™_—->‘->—)?‘)?—@‘@–@–@–@r
–'≠®S©¨—2‘2–2–2–2r
àeïcò#ëhîh——‘–›ê3âxåxàr

Ñ_›
á^Ç^êEîI—‘5
á^Ç^êEîI—‘ï
á^Ç^êEîI—‘ 
á^Ç^êEîO—$‘$A
á^Ç^êEîO—$‘$!ùs
á^Ç^êK— ‘ 
page_fluid⁄p⁄navset_tab_card⁄nav⁄input_action_button⁄ output_ui⁄app_ui⁄Inputs⁄Outputs⁄Sessionr-
ÄDÑF
ÒÙ
èäÿ ÿ ◊ "“ "†5®*— 5‘ 5ÿ èLäLò— %‘ %Ò
Ù
— ;‘ ;ÿ èLäL–*— +‘ +Ò
Ù
Ò
Ù
Ä0/ê&
Äcà&ê&—‘ÄÄÄr
import asyncio
from shiny import *
from shiny.ui import tags
app_ui = ui.page_fluid(
tags.p(
"""
The first time you click the button, you should see a 1 appear below the button,
as well as 2 messages in the python console (all reporting 1 click). After
clicking once, clicking again should increment the number below the button and
print the number of clicks in the console twice.
"""
),
ui.navset_tab_card(
ui.nav(
"Sync",
ui.input_action_button("btn", "Click me"),
ui.output_ui("btn_value"),
),
ui.nav(
"Async",
ui.input_action_button("btn_async", "Click me"),
ui.output_ui("btn_async_value"),
),
),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
@reactive.event(input.btn)
def _():
print("@effect() event: ", str(input.btn()))
@reactive.Calc
@reactive.event(input.btn)
def btn() -> int:
return input.btn()
@reactive.Effect
def _():
print("@calc() event: ", str(btn()))
@output
@render.ui
@reactive.event(input.btn)
def btn_value():
return str(input.btn())
# -----------------------------------------------------------------------------
# Async
# -----------------------------------------------------------------------------
@reactive.Effect
@reactive.event(input.btn_async)
async def _():
await asyncio.sleep(0)
print("async @effect() event: ", str(input.btn_async()))
@reactive.Calc
@reactive.event(input.btn_async)
async def btn_async_r() -> int:
await asyncio.sleep(0)
return input.btn_async()
@reactive.Effect
async def _():
val = await btn_async_r()
print("async @calc() event: ", str(val))
@output
@render.ui
@reactive.event(btn_async_r)
async def btn_async_value():
val = await btn_async_r()
print("== " + str(val))
return str(val)
app = App(app_ui, server)
ß
¶
edefdÑZ
page_fluid⁄input_checkbox⁄panel_conditional⁄tags⁄h5⁄ output_plot⁄app_uir
åè
ä
–F—G‘GÒÙ
åè
ä
–P—Q‘QÒÙ
Ä &ê&
Äcà&ê&—‘ÄÄÄr
import matplotlib.pyplot as plt
from shiny import App, Inputs, Outputs, Session, render, ui
app_ui = ui.page_fluid(
ui.input_checkbox("render", "Render", value=True),
ui.panel_conditional(
"input.render",
ui.tags.h5("A plot should appear immediately below this text."),
),
ui.output_plot("mpl"),
ui.panel_conditional(
"input.render",
ui.tags.h5("An error message should appear immediately below this text."),
),
ui.output_plot("mpl_bad"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.plot
def mpl():
if input.render():
plt.hist([1, 1, 2, 3, 5])
@output
@render.plot
async def mpl_bad():
if input.render():
plt.hist([1, 1, 2, 3, 5])
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.h3("HTTP request headers"),
ui.output_text_verbatim("headers", placeholder=True),
ui.h3("User and groups"),
ui.output_text_verbatim("user_groups", placeholder=True),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.text
def headers():
s = ""
for key, value in session.http_conn.headers.items():
s += f"{key}: {value}\n"
return s
@output
@render.text
def user_groups():
return f"session.user: {session.user}\nsession.groups: {session.groups}"
app = App(app_ui, server)
from datetime import date
from shiny import *
from shiny.ui import h2, tags
app_ui = ui.page_fluid(
ui.panel_title("Changing the values of inputs from the server"),
ui.row(
ui.column(
4,
ui.panel_well(
tags.h4("These inputs control the other inputs on the page"),
ui.input_text(
"control_label", "This controls some of the labels:", "LABEL TEXT"
),
ui.input_slider(
"control_num", "This controls values:", min=1, max=20, value=15
),
),
),
ui.column(
4,
ui.panel_well(
tags.h4("These inputs are controlled by the other inputs"),
ui.input_text("inText", "Text input:", value="start text"),
ui.input_numeric(
"inNumber", "Number input:", min=1, max=20, value=5, step=0.5
),
ui.input_numeric(
"inNumber2", "Number input 2:", min=1, max=20, value=5, step=0.5
),
ui.input_slider("inSlider", "Slider input:", min=1, max=20, value=15),
ui.input_slider(
"inSlider2", "Slider input 2:", min=1, max=20, value=(5, 15)
),
ui.input_slider(
"inSlider3", "Slider input 3:", min=1, max=20, value=(5, 15)
),
ui.input_date("inDate", "Date input:"),
ui.input_date_range("inDateRange", "Date range input:"),
),
),
ui.column(
4,
ui.panel_well(
ui.input_checkbox("inCheckbox", "Checkbox input", value=False),
ui.input_checkbox_group(
"inCheckboxGroup",
"Checkbox group input:",
{
"option1": "label 1",
"option2": "label 2",
},
),
ui.input_radio_buttons(
"inRadio",
"Radio buttons:",
{
"option1": "label 1",
"option2": "label 2",
},
),
ui.input_select(
"inSelect",
"Select input:",
{
"option1": "label 1",
"option2": "label 2",
},
),
ui.input_select(
"inSelect2",
"Select input 2:",
{
"option1": "label 1",
"option2": "label 2",
},
multiple=True,
),
),
ui.navset_tab(
ui.nav("panel1", h2("This is the first panel.")),
ui.nav("panel2", h2("This is the second panel.")),
id="inTabset",
),
),
),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
# We'll use these multiple times, so use short var names for
# convenience.
c_label = input.control_label()
c_num = input.control_num()
# Text =====================================================
# Change both the label and the text
ui.update_text(
"inText",
label="New " + c_label,
value="New text " + str(c_num),
)
# Number ===================================================
# Change the value
ui.update_numeric("inNumber", value=c_num)
# Change the label, value, min, and max
ui.update_numeric(
"inNumber2",
label="Number " + c_label,
value=c_num,
min=c_num - 10,
max=c_num + 10,
step=5,
)
# Slider input =============================================
# Only label and value can be set for slider
ui.update_slider("inSlider", label="Slider " + c_label, value=c_num)
# Slider range input =======================================
# For sliders that pick out a range, pass in a vector of 2
# values.
ui.update_slider("inSlider2", value=(c_num - 1, c_num + 1))
# Only change the upper handle
ui.update_slider("inSlider3", value=(input.inSlider3()[0], c_num + 2))
# Date input ===============================================
# Only label and value can be set for date input
ui.update_date("inDate", label="Date " + c_label, value=date(2013, 4, c_num))
# Date range input =========================================
# Only label and value can be set for date range input
ui.update_date_range(
"inDateRange",
label="Date range " + c_label,
start=date(2013, 1, c_num),
end=date(2013, 12, c_num),
min=date(2001, 1, c_num),
max=date(2030, 1, c_num),
)
# # Checkbox ===============================================
ui.update_checkbox("inCheckbox", value=c_num % 2)
# Checkbox group ===========================================
# Create a list of new options, where the name of the items
# is something like 'option label x A', and the values are
# 'option-x-A'.
opt_labels = [f"option label {c_num} {type}" for type in ["A", "B"]]
opt_vals = [f"option-{c_num}-{type}" for type in ["A", "B"]]
opts_dict = dict(zip(opt_vals, opt_labels))
# Set the label, choices, and selected item
ui.update_checkbox_group(
"inCheckboxGroup",
label="Checkbox group " + c_label,
choices=opts_dict,
selected=f"option-{c_num}-A",
)
# Radio group ==============================================
ui.update_radio_buttons(
"inRadio",
label="Radio " + c_label,
choices=opts_dict,
selected=f"option-{c_num}-A",
)
# Select input =============================================
# Create a list of new options, where the name of the items
# is something like 'option label x A', and the values are
# 'option-x-A'.
ui.update_select(
"inSelect",
label="Select " + c_label,
choices=opts_dict,
selected=f"option-{c_num}-A",
)
# Can also set the label and select an item (or more than
# one if it's a multi-select)
ui.update_select(
"inSelect2",
label="Select label " + c_label,
choices=opts_dict,
selected=f"option-{c_num}-B",
)
# Tabset input =============================================
# Change the selected tab.
# The tabsetPanel must have been created with an 'id' argument
ui.update_navs("inTabset", selected="panel2" if c_num % 2 else "panel1")
app = App(app_ui, server, debug=True)
import starlette.responses
from shiny import *
app_ui = ui.page_fluid(
ui.markdown(
"""
## Sticky load balancing test
The purpose of this app is to determine if HTTP requests made by the client are
correctly routed back to the same Python process where the session resides. It
is only useful for testing deployments that load balance traffic across more
than one Python process.
If this test fails, it means that sticky load balancing is not working, and
certain Shiny functionality (like file upload/download or server-side selectize)
are likely to randomly fail.
"""
),
ui.tags.div(
{"class": "card"},
ui.tags.div(
{"class": "card-body font-monospace"},
ui.tags.div("Attempts: ", ui.tags.span("0", id="count")),
ui.tags.div("Status: ", ui.tags.span(id="status")),
ui.output_ui("out"),
),
),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.ui
def out():
# Register a dynamic route for the client to try to connect to.
# It does nothing, just the 200 status code is all that the client
# will care about.
url = session.dynamic_route(
"test",
lambda req: starlette.responses.PlainTextResponse(
"OK", headers={"Cache-Control": "no-cache"}
),
)
# Send JS code to the client to repeatedly hit the dynamic route.
# It will succeed if and only if we reach the correct Python
# process.
return ui.tags.script(
f"""
const url = "{url}";
const count_el = document.getElementById("count");
const status_el = document.getElementById("status");
let count = 0;
async function check_url() {{
count_el.innerHTML = ++count;
try {{
const resp = await fetch(url);
if (!resp.ok) {{
status_el.innerHTML = "Failure!";
return;
}} else {{
status_el.innerHTML = "In progress";
}}
}} catch(e) {{
status_el.innerHTML = "Failure!";
return;
}}
if (count === 100) {{
status_el.innerHTML = "Test complete";
return;
}}
setTimeout(check_url, 10);
}}
check_url();
"""
)
app = App(app_ui, server)
from shiny import *
# ============================================================
# Counter module
# ============================================================
@module.ui
def counter_ui(label: str = "Increment counter") -> ui.TagChildArg:
return ui.div(
{"style": "border: 1px solid #ccc; border-radius: 5px; margin: 5px 0;"},
ui.h2("This is " + label),
ui.input_action_button(id="button", label=label),
ui.output_text_verbatim(id="out"),
)
@module.server
def counter_server(
input: Inputs, output: Outputs, session: Session, starting_value: int = 0
):
count: reactive.Value[int] = reactive.Value(starting_value)
@reactive.Effect
@reactive.event(input.button)
def _():
count.set(count() + 1)
@output
@render.text
def out() -> str:
return f"Click count is {count()}"
# ============================================================
# Counter Wrapper module -- shows that counter still works
# the same way when wrapped in a dynamic UI
# ============================================================
@module.ui
def counter_wrapper_ui() -> ui.TagChildArg:
return ui.output_ui("dynamic_counter")
@module.server
def counter_wrapper_server(
input: Inputs, output: Outputs, session: Session, label: str = "Increment counter"
):
@output()
@render.ui()
def dynamic_counter():
return counter_ui("counter", label)
counter_server("counter")
# =============================================================================
# App that uses module
# =============================================================================
app_ui = ui.page_fluid(
counter_ui("counter1", "Counter 1"),
counter_wrapper_ui("counter2_wrapper"),
ui.output_ui("counter3_ui"),
)
def server(input: Inputs, output: Outputs, session: Session):
counter_server("counter1")
counter_wrapper_server("counter2_wrapper", "Counter 2")
@output()
@render.ui()
def counter3_ui():
counter_server("counter3")
return counter_ui("counter3", "Counter 3")
counter_server("counter")
app = App(app_ui, server)
from shiny import *
from shiny.types import SafeException
app_ui = ui.page_fluid(
ui.input_action_button("safe", "Throw a safe error"),
ui.output_ui("safe"),
ui.input_action_button("unsafe", "Throw an unsafe error"),
ui.output_ui("unsafe"),
ui.input_text(
"txt",
"Enter some text below, then remove it. Notice how the text is never fully removed.",
),
ui.output_ui("txt_out"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Calc
def safe_click():
req(input.safe())
return input.safe()
@output
@render.ui
def safe():
raise SafeException(f"You've clicked {str(safe_click())} times")
@output
@render.ui
def unsafe():
req(input.unsafe())
raise Exception(f"Super secret number of clicks: {str(input.unsafe())}")
@reactive.Effect
def _():
req(input.unsafe())
print("unsafe clicks:", input.unsafe())
# raise Exception("Observer exception: this should cause a crash")
@output
@render.ui
def txt_out():
req(input.txt(), cancel_output=True)
return input.txt()
app = App(app_ui, server)
app.sanitize_errors = True
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from shiny import App, reactive, render, server, ui
# Load the data
df = pd.read_csv(
"https://raw.githubusercontent.com/rstudio/shiny-examples/main/050-kmeans-example/faithful.csv"
)
# Define the UI
app_ui = ui.page(
ui.title_panel("K-Means Clustering"),
ui.sidebar_panel(
ui.select_input("features", "Select features:", choices=list(df.columns[1:3])),
ui.numeric_input("clusters", "Number of clusters:", value=2, min=1, max=9),
),
ui.main_panel(ui.plot_output("plot")),
)
# Define the server logic
def app_server(input, output, session):
# Define the reactive expression to generate the plot
@reactive
def plot_data():
# Subset the data based on the selected features
data = df.loc[:, [input.features, "waiting"]]
# Run K-Means clustering
kmeans = KMeans(n_clusters=input.clusters)
kmeans.fit(data)
# Add the cluster assignments to the data frame
data["cluster"] = kmeans.labels_
# Create the scatter plot
fig, ax = plt.subplots()
ax.scatter(
data[input.features], data["waiting"], c=data["cluster"], cmap="Set1"
)
ax.set_xlabel(input.features)
ax.set_ylabel("Waiting time")
ax.set_title("K-Means Clustering")
plt.close(fig)
return fig
# Render the plot
@output
@render.plot(width="100%", height="500px")
def plot():
return plot_data()
# Run the app
app = App(app_ui, app_server)
ß
Z

d úe †

j
j
j
X variable⁄wt)⁄choices⁄selected⁄yz
Y variable⁄mpg⁄colorzColor variable⁄cyl⁄Seaborn⁄seabornr
ó
|t
fdѶ
d ¨ ¶
N)È
linewidths)⁄plt⁄subplots⁄sns⁄ scatterplot⁄histplot⁄kdeplot)r
¨ ¶
¶
facet_wrap⁄
geom_point⁄ggplot⁄ stat_smooth⁄theme⁄theme_bw⁄int64zfactor(˙))r

‡àk†–&—&‘&Ò
'
"
%
+
r8
date_range⁄cumsum⁄plot)⁄tss
Ò
Ù
à
matplotlib)⁄backend)r!
¶
sort_indexri
èäò
®Dà—1‘1–1ÿ
◊“†–—&‘&–&ÿèzäzâ|å|–r8
collisionss
Ù
à

Ñ]7
Ò
.
plotnine.datar
page_fluid⁄ panel_title⁄h2⁄br⁄app_ui⁄Inputs⁄Outputs⁄Sessionrõ
èäêz—"‘"ÿ
èäÿ5¿ – L– Lÿ èOäOÿê\®4®4∞ ∞¥ ±
¥
—+>‘+>»

èOäOÿê\®4®4∞ ∞¥ ±
¥
—+>‘+>»

èOäOÿÿ ÿòò[òVú[ô]ú]—+‘+ÿ
Ò
Ù
ÒÙ
èäêy—!‘!ÿ
èäÿ
–5– 6ÿ èOäOòE†:∞3∏B¿aàO— H‘ Hÿ èOäOòE†=∞a∏Q¿càO— J‘ JÒ
Ù
ÒÙ
èäÿáNÇNê2ó5í5–<¿]ê5—S‘S—T‘TÿáEÇEêÄE—‘ÿáFÇFà3–4ÄF—5‘5Ò
Ä\3ê&
Äcà&ê&—‘ÄÄÄr8
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from plotnine.data import mtcars
from shiny import *
nav = ui.navset_pill_list(
ui.nav_control(ui.p("Choose a package", class_="lead text-center")),
ui.nav(
"Plotnine",
ui.output_plot("plotnine"),
ui.div(
{"class": "d-flex justify-content-center", "style": "gap: 5rem"},
ui.input_select(
"x", "X variable", choices=list(mtcars.keys()), selected="wt"
),
ui.input_select(
"y", "Y variable", choices=list(mtcars.keys()), selected="mpg"
),
ui.input_select(
"color",
"Color variable",
choices=list(mtcars.keys()),
selected="cyl",
),
),
),
ui.nav(
"Seaborn",
ui.output_plot("seaborn"),
ui.div(
{"class": "d-flex justify-content-around"},
ui.input_slider("var", "Variance", min=0.1, max=10, value=2),
ui.input_slider("cov", "Co-variance", min=0, max=1, value=0.4),
),
),
ui.nav("Pandas", ui.output_plot("pandas")),
ui.nav("Holoviews", ui.output_plot("holoviews", height="600px")),
ui.nav("xarray", ui.output_plot("xarray")),
ui.nav("geopandas", ui.output_plot("geopandas")),
ui.nav("missingno", ui.output_plot("missingno")),
widths=(2, 10),
well=False,
)
app_ui = ui.page_fluid(
ui.panel_title(ui.h2("Py-Shiny static plotting examples", class_="text-center")),
ui.br(class_="py-3"),
ui.div(nav, style="max-width: 90%; margin: auto"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Calc
def fake_data():
n = 5000
mean = [0, 0]
rng = np.random.RandomState(0)
cov = [(input.var(), input.cov()), (input.cov(), 1 / input.var())]
return rng.multivariate_normal(mean, cov, n).T
@output
@render.plot
def seaborn():
x, y = fake_data()
f, ax = plt.subplots(figsize=(6, 6))
sns.scatterplot(x=x, y=y, s=5, color=".15")
sns.histplot(x=x, y=y, bins=50, pthresh=0.1, cmap="mako")
sns.kdeplot(x=x, y=y, levels=5, color="w", linewidths=1)
return f
@output
@render.plot
def plotnine():
from plotnine import (
aes,
facet_wrap,
geom_point,
ggplot,
stat_smooth,
theme,
theme_bw,
)
color_var = input.color()
if str(mtcars[color_var].dtype) == "int64":
color_var = f"factor({color_var})"
return (
ggplot(mtcars, aes(input.x(), input.y(), color=color_var))
+ geom_point()
+ stat_smooth(method="lm")
+ facet_wrap("~gear")
+ theme_bw(base_size=16)
+ theme(legend_position="top")
)
@output
@render.plot
def pandas():
ts = pd.Series(
np.random.randn(1000), index=pd.date_range("1/1/2000", periods=1000)
)
ts = ts.cumsum()
return ts.plot()
@output
@render.plot
def holoviews():
import holoviews as hv
from bokeh.sampledata.les_mis import data as les_mis
links = pd.DataFrame(les_mis["links"])
return hv.render(hv.Chord(links), backend="matplotlib")
@output
@render.plot
def xarray():
import xarray as xr
airtemps = xr.tutorial.open_dataset("air_temperature")
air = airtemps.air - 273.15
air.attrs = airtemps.air.attrs
air.attrs["units"] = "deg C"
return air.isel(lon=10, lat=[19, 21, 22]).plot.line(x="time")
@output
@render.plot
def geopandas():
import geopandas
nybb_path = geopandas.datasets.get_path("nybb")
boros = geopandas.read_file(nybb_path)
boros.set_index("BoroCode", inplace=True)
boros.sort_index(inplace=True)
return boros.plot()
@output
@render.plot
def missingno():
import missingno as msno
collisions = pd.read_csv(
"https://raw.githubusercontent.com/ResidentMario/missingno-data/master/nyc_collision_factors.csv"
)
return msno.matrix(collisions.sample(250))
app = App(app_ui, server)
# pyright: reportUnusedFunction=false
import typing
from shiny import *
app_ui = ui.page_fluid(
ui.input_numeric("n", "N", 20),
ui.input_numeric("n2", "N2", 50),
ui.input_checkbox("checkbox", "Checkbox", True),
ui.output_text_verbatim("txt", placeholder=True),
ui.output_text_verbatim("txt2", placeholder=True),
ui.output_text_verbatim("txt3", placeholder=True),
)
# By default the type of any input value, like input.n(), is Any, so no type checking
# will be used.
#
# But it is possible to specify the type of the input value, by creating a subclass of
# Inputs. We'll do that for input.n2() and input.checkbox():
class ShinyInputs(Inputs):
n2: reactive.Value[int]
check: reactive.Value[bool]
def server(input: Inputs, output: Outputs, session: Session):
# Cast `input` to our ShinyInputs class. This just tells the static type checker
# that we want it treated as a ShinyInputs object for type checking; it has no
# run-time effect.
input = typing.cast(ShinyInputs, input)
# The type checker knows that r() returns an int, which you can see if you hover
# over it.
@reactive.Calc
def r():
if input.n() is None:
return 0
return input.n() * 2
# Because we did NOT add a type for input.n (we did input.n2), the type checker
# thinks the return type of input.n() is Any, so we don't get type checking here.
# The function is returning the wrong value here: it returns an int instead of a
# string, but this error is not flagged.
@output
@render.text
async def txt():
return input.n() * 2
# In contrast, input.n2() is declared to return an int, so the type check does flag
# this error -- the `render.text()` is underlined in red.
@output
@render.text
async def txt2():
return input.n2() * 2
# This is a corrected version of the function above. It returns a string, and is not
# marked in red.
@output
@render.text
async def txt3():
return str(input.n2() * 2)
app = App(app_ui, server)
from datetime import datetime
from starlette.requests import Request
from shiny import App, reactive, render, ui
def app_ui(request: Request):
return ui.page_fluid(
ui.div("This page was rendered at ", datetime.now().isoformat()),
ui.output_text("now"),
)
def server(input, output, session):
@output
@render.text
def now():
reactive.invalidate_later(0.1)
return f"The current time is {datetime.now().isoformat()}"
app = App(app_ui, server)
import random
import time
from shiny import *
app_ui = ui.page_fluid(
ui.input_action_button("first", "Invalidate first (slow) computation"),
ui.input_action_button("second", "Invalidate second (fast) computation"),
ui.br(),
ui.output_ui("result"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Calc
def first():
input.first()
p = ui.Progress()
for i in range(30):
p.set(i / 30, message="Computing, please wait...")
time.sleep(0.1)
p.close()
return random.randint(1, 1000)
@reactive.Calc
def second():
input.second()
return random.randint(1, 1000)
@output
@render.ui
def result():
return first() + second()
app = App(app_ui, server)
from datetime import datetime
from shiny import *
app_ui = ui.page_fluid(
ui.input_action_button("close", "Close the session"),
ui.p(
"""If this example is running on the browser (i.e., via shinylive),
closing the session will log a message to the JavaScript console
(open the browser's developer tools to see it).
"""
),
)
def server(input: Inputs, output: Outputs, session: Session):
def log():
print("Session ended at: " + datetime.now().strftime("%H:%M:%S"))
session.on_ended(log)
@reactive.Effect
@reactive.event(input.close)
async def _():
await session.close()
app = App(app_ui, server)
import asyncio
from datetime import date
import numpy as np
from shiny import *
app_ui = ui.page_fluid(
ui.download_button("downloadData", "Download"),
)
# For more examples of different types of download handlers, see:
# https://github.com/rstudio/py-shiny/blob/68ffc27/examples/download/app.py#L90
def server(input: Inputs, output: Outputs, session: Session):
@session.download(
filename=lambda: f"新型-{date.today().isoformat()}-{np.random.randint(100,999)}.csv"
)
async def downloadData():
await asyncio.sleep(0.25)
yield "one,two,three\n"
yield "Êñ∞,1,2\n"
yield "Âûã,4,5\n"
app = App(app_ui, server)
mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
21,6,160,110,3.9,2.62,16.46,0,1,4,4
21,6,160,110,3.9,2.875,17.02,0,1,4,4
22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
18.7,8,360,175,3.15,3.44,17.02,0,0,3,2
18.1,6,225,105,2.76,3.46,20.22,1,0,3,1
14.3,8,360,245,3.21,3.57,15.84,0,0,3,4
24.4,4,146.7,62,3.69,3.19,20,1,0,4,2
22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
15.2,8,275.8,180,3.07,3.78,18,0,0,3,3
10.4,8,472,205,2.93,5.25,17.98,0,0,3,4
10.4,8,460,215,3,5.424,17.82,0,0,3,4
14.7,8,440,230,3.23,5.345,17.42,0,0,3,4
32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
15.5,8,318,150,2.76,3.52,16.87,0,0,3,2
15.2,8,304,150,3.15,3.435,17.3,0,0,3,2
13.3,8,350,245,3.73,3.84,15.41,0,0,3,4
19.2,8,400,175,3.08,3.845,17.05,0,0,3,2
27.3,4,79,66,4.08,1.935,18.9,1,1,4,1
26,4,120.3,91,4.43,2.14,16.7,0,1,5,2
30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
15.8,8,351,264,4.22,3.17,14.5,0,1,5,4
19.7,6,145,175,3.62,2.77,15.5,0,1,5,6
15,8,301,335,3.54,3.57,14.6,0,1,5,8
21.4,4,121,109,4.11,2.78,18.6,1,1,4,2
import asyncio
from datetime import date
import numpy as np
from shiny import *
app_ui = ui.page_fluid(
ui.download_link("downloadData", "Download"),
)
# For more examples of different types of download handlers, see:
# https://github.com/rstudio/py-shiny/blob/68ffc27/examples/download/app.py#L90
def server(input: Inputs, output: Outputs, session: Session):
@session.download(
filename=lambda: f"新型-{date.today().isoformat()}-{np.random.randint(100,999)}.csv"
)
async def downloadData():
await asyncio.sleep(0.25)
yield "one,two,three\n"
yield "Êñ∞,1,2\n"
yield "Âûã,4,5\n"
app = App(app_ui, server)
mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
21,6,160,110,3.9,2.62,16.46,0,1,4,4
21,6,160,110,3.9,2.875,17.02,0,1,4,4
22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
18.7,8,360,175,3.15,3.44,17.02,0,0,3,2
18.1,6,225,105,2.76,3.46,20.22,1,0,3,1
14.3,8,360,245,3.21,3.57,15.84,0,0,3,4
24.4,4,146.7,62,3.69,3.19,20,1,0,4,2
22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
15.2,8,275.8,180,3.07,3.78,18,0,0,3,3
10.4,8,472,205,2.93,5.25,17.98,0,0,3,4
10.4,8,460,215,3,5.424,17.82,0,0,3,4
14.7,8,440,230,3.23,5.345,17.42,0,0,3,4
32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
15.5,8,318,150,2.76,3.52,16.87,0,0,3,2
15.2,8,304,150,3.15,3.435,17.3,0,0,3,2
13.3,8,350,245,3.73,3.84,15.41,0,0,3,4
19.2,8,400,175,3.08,3.845,17.05,0,0,3,2
27.3,4,79,66,4.08,1.935,18.9,1,1,4,1
26,4,120.3,91,4.43,2.14,16.7,0,1,5,2
30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
15.8,8,351,264,4.22,3.17,14.5,0,1,5,4
19.7,6,145,175,3.62,2.77,15.5,0,1,5,6
15,8,301,335,3.54,3.57,14.6,0,1,5,8
21.4,4,121,109,4.11,2.78,18.6,1,1,4,2
import asyncio
import io
import os
from datetime import date
from typing import Any
import matplotlib.pyplot as plt
import numpy as np
from shiny import *
from shiny.ui import div, p
def make_example(id: str, label: str, title: str, desc: str, extra: Any = None):
return ui.column(
4,
div(
{"class": "card mb-4"},
div(title, class_="card-header"),
div(
{"class": "card-body"},
p(desc, class_="card-text text-muted"),
extra,
ui.download_button(id, label, class_="btn-primary"),
),
),
)
app_ui = ui.page_fluid(
ui.row(
make_example(
"download1",
label="Download CSV",
title="Simple case",
desc="Downloads a pre-existing file, using its existing name on disk.",
),
),
ui.row(
make_example(
"download2",
label="Download plot",
title="Dynamic data generation",
desc="Downloads a PNG that's generated on the fly.",
extra=[
ui.input_text("title", "Plot title", "Random scatter plot"),
ui.input_slider(
"num_points", "Number of data points", min=1, max=100, value=50
),
],
),
),
ui.row(
make_example(
"download3",
"Download",
"Dynamic filename",
"Demonstrates that filenames can be generated on the fly (and use Unicode characters!).",
),
),
ui.row(
make_example(
"download4",
"Download",
"Failed downloads",
"Throws an error in the download handler, download should not succeed.",
),
),
ui.row(
make_example(
"download5",
"Download",
"Undefined download",
"This button doesn't have corresponding server code registered to it, download should result in 404 error",
),
),
)
def server(input: Inputs, output: Outputs, session: Session):
@session.download()
def download1():
"""
This is the simplest case. The implementation simply returns the name of a file.
Note that the function name (`download1`) determines which download_button()
corresponds to this function.
"""
path = os.path.join(os.path.dirname(__file__), "mtcars.csv")
return path
@session.download(filename="image.png")
def download2():
"""
Another way to implement a file download is by yielding bytes; either all at
once, like in this case, or by yielding multiple times. When using this
approach, you should pass a filename argument to @session.download, which
determines what the browser will name the downloaded file.
"""
print(input.num_points())
x = np.random.uniform(size=input.num_points())
y = np.random.uniform(size=input.num_points())
plt.figure()
plt.scatter(x, y)
plt.title(input.title())
with io.BytesIO() as buf:
plt.savefig(buf, format="png")
yield buf.getvalue()
@session.download(
filename=lambda: f"新型-{date.today().isoformat()}-{np.random.randint(100,999)}.csv"
)
async def download3():
await asyncio.sleep(0.25)
yield "one,two,three\n"
yield "Êñ∞,1,2\n"
yield "Âûã,4,5\n"
@session.download(id="download4", filename="failuretest.txt")
async def _():
yield "hello"
raise Exception("This error was caused intentionally")
app = App(app_ui, server)
mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
21,6,160,110,3.9,2.62,16.46,0,1,4,4
21,6,160,110,3.9,2.875,17.02,0,1,4,4
22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
18.7,8,360,175,3.15,3.44,17.02,0,0,3,2
18.1,6,225,105,2.76,3.46,20.22,1,0,3,1
14.3,8,360,245,3.21,3.57,15.84,0,0,3,4
24.4,4,146.7,62,3.69,3.19,20,1,0,4,2
22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
15.2,8,275.8,180,3.07,3.78,18,0,0,3,3
10.4,8,472,205,2.93,5.25,17.98,0,0,3,4
10.4,8,460,215,3,5.424,17.82,0,0,3,4
14.7,8,440,230,3.23,5.345,17.42,0,0,3,4
32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
15.5,8,318,150,2.76,3.52,16.87,0,0,3,2
15.2,8,304,150,3.15,3.435,17.3,0,0,3,2
13.3,8,350,245,3.73,3.84,15.41,0,0,3,4
19.2,8,400,175,3.08,3.845,17.05,0,0,3,2
27.3,4,79,66,4.08,1.935,18.9,1,1,4,1
26,4,120.3,91,4.43,2.14,16.7,0,1,5,2
30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
15.8,8,351,264,4.22,3.17,14.5,0,1,5,4
19.7,6,145,175,3.62,2.77,15.5,0,1,5,6
15,8,301,335,3.54,3.57,14.6,0,1,5,8
21.4,4,121,109,4.11,2.78,18.6,1,1,4,2
from starlette.requests import Request
from starlette.responses import JSONResponse
from shiny import *
app_ui = ui.page_fluid(
ui.input_action_button("serve", "Click to serve"), ui.div(id="messages")
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
@reactive.event(input.serve)
def _():
async def my_handler(request: Request) -> JSONResponse:
return JSONResponse({"n_clicks": input.serve()}, status_code=200)
path = session.dynamic_route("my_handler", my_handler)
print("Serving at: ", path)
ui.insert_ui(
ui.tags.script(
f"""
fetch('{path}')
.then(r => r.json())
.then(x => {{ $('#messages').text(`Clicked ${{x.n_clicks}} times`); }});
"""
),
selector="body",
)
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(ui.input_action_button("btn", "Press me!"))
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
@reactive.event(input.btn)
def _():
ui.insert_ui(
ui.p("Number of clicks: ", input.btn()), selector="#btn", where="afterEnd"
)
app = App(app_ui, server)
import random
from shiny import *
app_ui = ui.page_fluid(
ui.markdown(
f"""
This example demonstrates how `@reactive.event()` can be used to restrict
execution of: (1) a `@render` function, (2) `@reactive.Calc`, or (3)
`@reactive.Effect`.
In all three cases, the output is dependent on a random value that gets updated
every 0.5 seconds (currently, it is {ui.output_ui("number", inline=True)}), but
the output is only updated when the button is clicked.
"""
),
ui.row(
ui.column(
3,
ui.input_action_button("btn_out", "(1) Update number"),
ui.output_text("out_out"),
),
ui.column(
3,
ui.input_action_button("btn_calc", "(2) Show 1 / number"),
ui.output_text("out_calc"),
),
ui.column(
3,
ui.input_action_button("btn_effect", "(3) Log number"),
ui.div(id="out_effect"),
),
),
)
def server(input: Inputs, output: Outputs, session: Session):
# Update a random number every second
val = reactive.Value(random.randint(0, 1000))
@reactive.Effect
def _():
reactive.invalidate_later(0.5)
val.set(random.randint(0, 1000))
# Always update this output when the number is updated
@output
@render.ui
def number():
return val.get()
# Since ignore_none=False, the function executes before clicking the button.
# (input.btn_out() is 0 on page load, but @@reactive.event() treats 0 as None for
# action buttons.)
@output
@render.text
@reactive.event(input.btn_out, ignore_none=False)
def out_out():
return str(val.get())
@reactive.Calc
@reactive.event(input.btn_calc)
def calc():
return 1 / val.get()
@output
@render.text
def out_calc():
return str(calc())
@reactive.Effect
@reactive.event(input.btn_effect)
def _():
ui.insert_ui(
ui.p("Random number!", val.get()),
selector="#out_effect",
where="afterEnd",
)
app = App(app_ui, server)
import pathlib
import pandas as pd
from shiny import *
dir = pathlib.Path(__file__).parent
app_ui = ui.page_fluid(ui.output_table("result"), class_="p-3")
@reactive.file_reader(dir / "mtcars.csv")
def read_file():
return pd.read_csv(dir / "mtcars.csv")
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.table
def result():
return read_file()
app = App(app_ui, server)
mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
21,6,160,110,3.9,2.62,16.46,0,1,4,4
21,6,160,110,3.9,2.875,17.02,0,1,4,4
22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
18.7,8,360,175,3.15,3.44,17.02,0,0,3,2
18.1,6,225,105,2.76,3.46,20.22,1,0,3,1
14.3,8,360,245,3.21,3.57,15.84,0,0,3,4
24.4,4,146.7,62,3.69,3.19,20,1,0,4,2
22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
15.2,8,275.8,180,3.07,3.78,18,0,0,3,3
10.4,8,472,205,2.93,5.25,17.98,0,0,3,4
10.4,8,460,215,3,5.424,17.82,0,0,3,4
14.7,8,440,230,3.23,5.345,17.42,0,0,3,4
32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
15.5,8,318,150,2.76,3.52,16.87,0,0,3,2
15.2,8,304,150,3.15,3.435,17.3,0,0,3,2
13.3,8,350,245,3.73,3.84,15.41,0,0,3,4
19.2,8,400,175,3.08,3.845,17.05,0,0,3,2
27.3,4,79,66,4.08,1.935,18.9,1,1,4,1
26,4,120.3,91,4.43,2.14,16.7,0,1,5,2
30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
15.8,8,351,264,4.22,3.17,14.5,0,1,5,4
19.7,6,145,175,3.62,2.77,15.5,0,1,5,6
15,8,301,335,3.54,3.57,14.6,0,1,5,8
21.4,4,121,109,4.11,2.78,18.6,1,1,4,2
# Pandas needs Jinja2 for table styling, but it doesn't (yet) load automatically
# in Pyodide, so we need to explicitly list it here.
Jinja2
import matplotlib.pyplot as plt
import numpy as np
from shiny import *
app_ui = ui.page_fluid(
ui.input_slider("n", "Number of observations", min=0, max=1000, value=500),
ui.input_action_button("go", "Go!", class_="btn-success"),
ui.output_plot("plot"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.plot(alt="A histogram")
# Use reactive.event() to invalidate the plot only when the button is pressed
# (not when the slider is changed)
@reactive.event(input.go, ignore_none=False)
def plot():
np.random.seed(19680801)
x = 100 + 15 * np.random.randn(input.n())
fig, ax = plt.subplots()
ax.hist(x, bins=30, density=True)
return fig
app = App(app_ui, server)
import matplotlib.pyplot as plt
import numpy as np
from shiny import *
app_ui = ui.page_fluid(
ui.input_slider("n", "Number of observations", min=0, max=1000, value=500),
ui.input_action_link("go", "Go!"),
ui.output_plot("plot"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.plot(alt="A histogram")
# reactive.event() to invalidate the plot when the button is pressed but not when
# the slider is changed
@reactive.event(input.go, ignore_none=False)
def plot():
np.random.seed(19680801)
x = 100 + 15 * np.random.randn(input.n())
fig, ax = plt.subplots()
ax.hist(x, bins=30, density=True)
return fig
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_checkbox_group(
"colors",
"Choose color(s):",
{
"red": ui.span("Red", style="color: #FF0000;"),
"green": ui.span("Green", style="color: #00AA00;"),
"blue": ui.span("Blue", style="color: #0000AA;"),
},
),
ui.output_ui("val"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.ui
def val():
req(input.colors())
return "You chose " + ", ".join(input.colors())
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_checkbox("somevalue", "Some value", False),
ui.output_ui("value"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.ui
def value():
return input.somevalue()
app = App(app_ui, server)
from datetime import date
from shiny import *
app_ui = ui.page_fluid(
ui.input_date_range(
"daterange1", "Date range:", start="2001-01-01", end="2010-12-31"
),
# Default start and end is the current date in the client's time zone
ui.input_date_range("daterange2", "Date range:"),
# start and end are always specified in yyyy-mm-dd, even if the display
# format is different
ui.input_date_range(
"daterange3",
"Date range:",
start="2001-01-01",
end="2010-12-31",
min="2001-01-01",
max="2012-12-21",
format="mm/dd/yy",
separator=" - ",
),
# Pass in Date objects
ui.input_date_range(
"daterange4", "Date range:", start=date(2001, 1, 1), end=date(2010, 12, 31)
),
# Use different language and different first day of week
ui.input_date_range("daterange5", "Date range:", language="de", weekstart=1),
# Start with decade view instead of default month view
ui.input_date_range("daterange6", "Date range:", startview="decade"),
)
def server(input: Inputs, output: Outputs, session: Session):
pass
app = App(app_ui, server)
from datetime import date
from shiny import *
app_ui = ui.page_fluid(
ui.input_date("date1", "Date:", value="2016-02-29"),
# Default value is the date in client's time zone
ui.input_date("date2", "Date:"),
# value is always yyyy-mm-dd, even if the display format is different
ui.input_date("date3", "Date:", value="2016-02-29", format="mm/dd/yy"),
# Pass in a Date object
ui.input_date("date4", "Date:", value=date(2016, 2, 29)),
# Use different language and different first day of week
ui.input_date("date5", "Date:", language="ru", weekstart=1),
# Start with decade view instead of default month view
ui.input_date("date6", "Date:", startview="decade"),
# Disable Mondays and Tuesdays.
ui.input_date("date7", "Date:", daysofweekdisabled=[1, 2]),
# Disable specific dates.
ui.input_date(
"date8",
"Date:",
value="2016-02-29",
datesdisabled=["2016-03-01", "2016-03-02"],
),
)
def server(input: Inputs, output: Outputs, session: Session):
pass
app = App(app_ui, server)
import pandas as pd
from shiny import *
from shiny.types import FileInfo
app_ui = ui.page_fluid(
ui.layout_sidebar(
ui.panel_sidebar(
ui.input_file("file1", "Choose CSV File", accept=[".csv"], multiple=False),
ui.input_checkbox("header", "Header", True),
),
ui.panel_main(ui.output_ui("contents")),
)
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.ui
def contents():
if input.file1() is None:
return "Please upload a csv file"
f: list[FileInfo] = input.file1()
df = pd.read_csv(f[0]["datapath"], header=0 if input.header() else None)
return ui.HTML(df.to_html(classes="table table-striped"))
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_numeric("obs", "Observations:", 10, min=1, max=100),
ui.output_text_verbatim("value"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.text
def value():
return input.obs()
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_password("password", "Password:"),
ui.input_action_button("go", "Go"),
ui.output_text_verbatim("value"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.text
@reactive.event(input.go)
def value():
return input.password()
app = App(app_ui, server)
from htmltools import HTML
from shiny import *
app_ui = ui.page_fluid(
ui.input_radio_buttons(
"rb",
"Choose one:",
{
"html": HTML("<span style='color:red;'>Red Text</span>"),
"text": "Normal text",
},
),
ui.output_ui("val"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.ui
def val():
return "You chose " + input.rb()
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_select(
"state",
"Choose a state:",
{
"East Coast": {"NY": "New York", "NJ": "New Jersey", "CT": "Connecticut"},
"West Coast": {"WA": "Washington", "OR": "Oregon", "CA": "California"},
"Midwest": {"MN": "Minnesota", "WI": "Wisconsin", "IA": "Iowa"},
},
),
ui.output_text("value"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.text
def value():
return "You choose: " + str(input.state())
app = App(app_ui, server)
ß
d d úd
dddúdúd¨¶
d d úd
dddúdúdd
de fdÑZ 
ee ¶
New Jersey⁄ Connecticut)⁄NY⁄NJ⁄CT⁄
Washington⁄Oregon⁄
California)⁄WA⁄OR⁄CA⁄ Minnesota⁄ Wisconsin⁄Iowa)⁄MN⁄WI⁄IA)z
East Coastz
West Coast⁄MidwestT)⁄multiple⁄valuezSelectize OptionszCustomize with JavaScriptzb{option: function(item, escape) {return "<div><strong>Select " + item.label + "</strong></div>";}})⁄ placeholder⁄render)r
page_fluid⁄input_selectize⁄ output_text⁄br⁄app_ui⁄Inputs⁄Outputs⁄Sessionr%


ÄD3ê&
Äcà&ê&—‘ÄÄÄr#
from htmltools import HTML
from shiny import *
app_ui = ui.page_fluid(
ui.input_selectize(
"state",
"Choose a state:",
{
"East Coast": {"NY": "New York", "NJ": "New Jersey", "CT": "Connecticut"},
"West Coast": {"WA": "Washington", "OR": "Oregon", "CA": "California"},
"Midwest": {"MN": "Minnesota", "WI": "Wisconsin", "IA": "Iowa"},
},
multiple=True,
),
ui.output_text("value"),
ui.br(),
ui.input_selectize(
"state",
"Selectize Options",
{
"East Coast": {"NY": "New York", "NJ": "New Jersey", "CT": "Connecticut"},
"West Coast": {"WA": "Washington", "OR": "Oregon", "CA": "California"},
"Midwest": {"MN": "Minnesota", "WI": "Wisconsin", "IA": "Iowa"},
},
multiple=True,
options=(
{
"placeholder": "Customize with JavaScript",
"render": HTML(
'{option: function(item, escape) {return "<div><strong>Select " + item.label + "</strong></div>";}}'
),
}
),
),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.text
def value():
return "You choose: " + str(input.state())
app = App(app_ui, server)
import matplotlib.pyplot as plt
import numpy as np
from shiny import *
app_ui = ui.page_fluid(
ui.input_slider("obs", "Number of bins:", min=10, max=100, value=30),
ui.output_plot("distPlot"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.plot
def distPlot():
np.random.seed(19680801)
x = 100 + 15 * np.random.randn(437)
fig, ax = plt.subplots()
ax.hist(x, input.obs(), density=True)
return fig
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_switch("somevalue", "Some value", False),
ui.output_ui("value"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.ui
def value():
return input.somevalue()
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_text_area("caption", "Caption:", "Data summary"),
ui.output_text_verbatim("value"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.text
def value():
return input.caption()
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_text("caption", "Caption:", "Data summary"),
ui.output_text_verbatim("value"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.text
def value():
return input.caption()
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_action_button("add", "Add UI"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
@reactive.event(input.add)
def _():
ui.insert_ui(
ui.input_text("txt" + str(input.add()), "Enter some text"),
selector="#add",
where="afterEnd",
)
app = App(app_ui, server)
import random
from shiny import *
app_ui = ui.page_fluid(ui.output_ui("value"))
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
reactive.invalidate_later(0.5)
print("Random int: ", random.randint(0, 10000))
@output
@render.ui
def value():
reactive.invalidate_later(0.5)
return "Random int: " + str(random.randint(0, 10000))
app = App(app_ui, server)
import matplotlib.pyplot as plt
import numpy as np
from shiny import *
app_ui = ui.page_fluid(
ui.input_slider("n", "Number of observations", min=0, max=1000, value=500),
ui.input_action_button("go", "Go!", class_="btn-success"),
ui.output_plot("plot"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.plot(alt="A histogram")
def plot():
# Take a reactive dependency on the action button...
input.go()
# ...but don't take a reactive dependency on the slider
with reactive.isolate():
np.random.seed(19680801)
x = 100 + 15 * np.random.randn(input.n())
fig, ax = plt.subplots()
ax.hist(x, bins=30, density=True)
return fig
app = App(app_ui, server)
import matplotlib.pyplot as plt
import numpy as np
from shiny import *
app_ui = ui.page_fluid(
ui.layout_sidebar(
ui.panel_sidebar(ui.input_slider("n", "N", min=0, max=100, value=20)),
ui.panel_main(ui.output_plot("plot")),
),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.plot(alt="A histogram")
def plot() -> object:
np.random.seed(19680801)
x = 100 + 15 * np.random.randn(437)
fig, ax = plt.subplots()
ax.hist(x, input.n(), density=True)
return fig
app = App(app_ui, server)
from shiny import *
ui_app = ui.page_fluid(
ui.markdown(
"""
# Hello World
This is **markdown** and here is some `code`:
```python
print('Hello world!')
```
"""
)
)
def server(input: Inputs, output: Outputs, session: Session):
pass
app = App(ui_app, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_action_button("show", "Show modal dialog"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
@reactive.event(input.show)
def _():
m = ui.modal(
"This is a somewhat important message.",
title="Somewhat important message",
easy_close=True,
footer=None,
)
ui.modal_show(m)
app = App(app_ui, server)
from shiny import *
# ============================================================
# Counter module
# ============================================================
@module.ui
def counter_ui(label: str = "Increment counter") -> ui.TagChild:
return ui.div(
{"style": "border: 1px solid #ccc; border-radius: 5px; margin: 5px 0;"},
ui.h2("This is " + label),
ui.input_action_button(id="button", label=label),
ui.output_text_verbatim(id="out"),
)
@module.server
def counter_server(
input: Inputs, output: Outputs, session: Session, starting_value: int = 0
):
count: reactive.Value[int] = reactive.Value(starting_value)
@reactive.Effect
@reactive.event(input.button)
def _():
count.set(count() + 1)
@output
@render.text
def out() -> str:
return f"Click count is {count()}"
# =============================================================================
# App that uses module
# =============================================================================
app_ui = ui.page_fluid(
counter_ui("counter1", "Counter 1"),
counter_ui("counter2", "Counter 2"),
)
def server(input: Inputs, output: Outputs, session: Session):
counter_server("counter1")
counter_server("counter2")
app = App(app_ui, server)
from typing import List
from shiny import *
from shiny.types import NavSetArg
from shiny.ui import h4
def nav_controls(prefix: str) -> List[NavSetArg]:
return [
ui.nav("a", prefix + ": tab a content"),
ui.nav("b", prefix + ": tab b content"),
ui.nav_control(
ui.a(
"Shiny",
href="https://github.com/rstudio/shiny",
target="_blank",
)
),
ui.nav_spacer(),
ui.nav_menu(
"Other links",
ui.nav("c", prefix + ": tab c content"),
"----",
"Plain text",
"----",
ui.nav_control(
ui.a(
"RStudio",
href="https://rstudio.com",
target="_blank",
)
),
align="right",
),
]
app_ui = ui.page_navbar(
*nav_controls("page_navbar"),
title="page_navbar()",
bg="#0062cc",
inverse=True,
id="navbar_id",
footer=ui.div(
{"style": "width:80%;margin: 0 auto"},
ui.tags.style(
"""
h4 {
margin-top: 3em;
}
"""
),
h4("navset_tab()"),
ui.navset_tab(*nav_controls("navset_tab()")),
h4("navset_pill()"),
ui.navset_pill(*nav_controls("navset_pill()")),
h4("navset_tab_card()"),
ui.navset_tab_card(*nav_controls("navset_tab_card()")),
h4("navset_pill_card()"),
ui.navset_pill_card(*nav_controls("navset_pill_card()")),
h4("navset_pill_list()"),
ui.navset_pill_list(*nav_controls("navset_pill_list()")),
)
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
print("Current navbar page: ", input.navbar_id())
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.layout_sidebar(
ui.panel_sidebar(
ui.input_radio_buttons(
"controller", "Controller", ["1", "2", "3"], selected="1"
)
),
ui.panel_main(
ui.navset_hidden(
ui.nav(None, "Panel 1 content", value="panel1"),
ui.nav(None, "Panel 2 content", value="panel2"),
ui.nav(None, "Panel 3 content", value="panel3"),
id="hidden_tabs",
)
),
)
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
@reactive.event(input.controller)
def _():
ui.update_navs("hidden_tabs", selected="panel" + str(input.controller()))
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_action_button("show", "Show"),
ui.input_action_button("remove", "Remove"),
)
def server(input: Inputs, output: Outputs, session: Session):
ids: list[str] = []
n: int = 0
@reactive.Effect
@reactive.event(input.show)
def _():
nonlocal ids
nonlocal n
# Save the ID for removal later
id = ui.notification_show("Message " + str(n), duration=None)
ids.append(id)
n += 1
@reactive.Effect
@reactive.event(input.remove)
def _():
nonlocal ids
if ids:
ui.notification_remove(ids.pop())
app = App(app_ui, server, debug=True)
from datetime import datetime
from shiny import *
app_ui = ui.page_fluid(
ui.input_action_button("close", "Close the session"),
)
def server(input: Inputs, output: Outputs, session: Session):
def log():
print("Session ended at: " + datetime.now().strftime("%H:%M:%S"))
session.on_ended(log)
@reactive.Effect
@reactive.event(input.close)
async def _():
await session.close()
app = App(app_ui, server)
from datetime import datetime
from shiny import *
app_ui = ui.page_fluid(
ui.input_action_button("flush", "Trigger flush"),
ui.output_ui("n_clicks"),
ui.div(id="flush_time"),
)
def server(input: Inputs, output: Outputs, session: Session):
def log():
msg = "A reactive flush occurred at " + datetime.now().strftime("%H:%M:%S:%f")
print(msg)
ui.insert_ui(
ui.p(msg),
selector="#flush_time",
)
session.on_flush(log, once=False)
@output
@render.ui
def n_clicks():
return "Number of clicks: " + str(input.flush())
app = App(app_ui, server)
from datetime import datetime
from shiny import *
app_ui = ui.page_fluid(
ui.input_action_button("flush", "Trigger flush"),
ui.output_ui("n_clicks"),
ui.div(id="flush_time"),
)
def server(input: Inputs, output: Outputs, session: Session):
def log():
msg = "A reactive flush occurred at " + datetime.now().strftime("%H:%M:%S:%f")
print(msg)
ui.insert_ui(
ui.p(msg),
selector="#flush_time",
)
session.on_flushed(log, once=False)
@output
@render.ui
def n_clicks():
return "Number of clicks: " + str(input.flush())
app = App(app_ui, server)
from shiny import *
from shiny.types import ImgData
app_ui = ui.page_fluid(ui.output_image("image"))
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.image
def image():
from pathlib import Path
dir = Path(__file__).resolve().parent
img: ImgData = {"src": str(dir / "rstudio-logo.png"), "width": "150px"}
return img
app = App(app_ui, server)
âPNG

IHDR
óY#€ª≥a¨k««Gm:1fìÒnbª#i"ê;’≈ûssãπõùœ≥SsYov!ü]Ϫc˝L˜{Ex à<-¬‡(–{5Å#Ø∆ÄÀÅ[Uπ›©^oD.µ´6ØÔ&ónÂÚÌc\ºeî-∫¨[ï2>ö–MÖƒ$‹Ö*‰NÈısã9”ß2éL-Ú‚·ˆúcÔÅ9]»NŒe”YÆ/à®πxxò•¡ëW Ï
· £∫ÚU°◊wLœˆy·–OÏô··g¶Ÿı‚,'Neã™∫fly
kRnπz
w‹∏ûÎ.[≈˙’+^¯™K…~˘7+`Dp™úúÕÿµoñØ<~úØ}„8é.ê;ù7"œ
¯a‡
™¬˙U)∑Ô\«€n›ƒUè3⁄58ı£öB Œ≈UÍ 8"–œîœÛ•GéÒ∑„¿‰™®/`>
<Ù_`‰e
¯p–=ü†»y÷äÌ¿è0Æ
õ◊u¯Ó7mÊŒ◊odÌDäS-qêÛÜ,„}cÑ˘≈úØ?}ÇO˛›KÏŸ?˜‡C¿«Ä√ÁK[‰<Åëw? |ê7~ıéq~Ëm€π˛äU#ïÅ~9$fl¢.“8$‚èΩpxû?˝ÎÉ|Ì…dπNB¯;‡7Å˚B‹sNAëÛ
õ÷uŸΩñìsyî5¿Ìå«
cü\ÚO»_¯ÿ˘§∆*‡flˇ6º–Ω[Ø]Àõo⁄įƒ_yÉÒû´¢™˛˝WÂÓRÓîUcfi,Ózq∂M€«Ä7Æ{®–îïÇíú!L
kRæ˝⁄µÅ^¶KÚcaQt•ÓÎÀÏÜâ¿ŒÀWÒÿÆûyq3xç´Ç\ÆÒ¬JOóúÅvå
¸ÁÇä∂¬ÒQÀ’è£@?sµËK_Ó4Û9à3µÊ
ól„ÇuL.sVøL ∏ÂÇí¨
Ωâ ÷uÿ∏∂C/sCSSz¶“y≈›ce¥kÿ±yîìãK}p´¿K¡%f9†$+ ˙~≠È⁄∂˘—[7åêZ©—’+PúÛÕà∞u„i"√h´ÿ.~__yj9†,◊Ül ¯ÎNÎ∂Ya˝Íî<˜fi”ˇèõà≤f<°€1Ù]‡Êv ‡ßÄcgLYëv§!7ıée%+åçX≤‹ªçCØV©å ô0“+aá4ÿëN*åt,ßÊÚÂ|Ì{ÅGÅˇ‰KiI≤ ™∫3$
ìÂ\¨5Ç5DˆCW&Âeņg@~“
˚Å*æÇô⁄eª4‰A‡fi≥1Í€Ò)ÙÕ+πÌ‹˘J‹ÈK√∫‰/ 0…I˚9§ÜA3¡^˘yzπ§<◊ï¢yQêÂS¿‰0-Iñ–É/.}«JŒÍ‘w}x@Ù4√^1∆ß(Ú‹ÖºS≈R∞ù4\œPõ¡X„ K&|(W/¨‹ÖÛãïøK@µ@≤ƒ ÈııtΩmª¯¡ü¥¯•4§(ª&+—é,Wfr˙y√Âm «UïõÆZ√ï;∆yÈËì'ô9’gæóìß¿X!µÜn«0>bôMX=û≤z<a’X¬X7°”1$∆ ¥ü)s S3}O-pË<ì”=zAc͇hEÄq_Oc‘Ász}∑‚T`†ˇøæyZ ä¥£KTv]…ñ;e˙T∆ñ
√(´¢)UÂÇufir”F\ËßZË;s˙ô√©˝ùD˧ñnjH¨êX)[Çñ⁄˙ô„‰\∆ãáÁxl˜ æ±wöß˙àDL!ëFÌE8⁄àpr.c±ÁŒ$ì|-£¡Û öZ2lÙ[ Vº9«fz,ˆF.3OER—ß∏ÖëÆe§ka"='Qö÷ØÓ∞~uák/]ÕfiÉ≥¸ıCáyl˜ zYé16
Ìx?Ê≥Ò
7ØÎ`ç ¶÷£>´\Wl„™´„Gfiù.4I.ik|dÑçk∫\{…j¨ÅΩN≤–œK
æ∏á~¶Ï90«âìŸô÷Z÷ßıygÑ€4‰˙Â∆̸
'Á2&OÙÿ∂q$÷¿⁄Ü cÚp|Ì©)ücÌ∏eÌD ∫U)k&R∂mgÀ˙±ö–ùÂ‘≥Û}NÕıô]»XËÂÙ˙9ôsFª ÷å∞}”mû`ÌD∑û˘Mx◊m€Å?øoΩL1∆g¬TqQA_N´f=ı°”Ãl∆±ÈfiŸ*ªÅ?$*ˇ∂bU]x6g reˇ‰ÎV•$÷ªKbq<$-
¸Åc Ïõ<Ö∫ !√‡∞∆q◊∑_ƒ?}Ák±Å˜˙ô„œ˛ÊYÓ¸
∫⁄q6⁄k…±Èì”=ÔÜïœT…T˝1Á]T1` Cœ¡©«ÏB^suT·‘\èìs=≤úLA≈†∆ǵàMõÄM»≈0≥êÛ‰Û«˘˝œìfl˘Ûo∞Ô»©⁄5év-w›∫çu´,Ω¨OñÁdπ#sæhñ∫∫^ˇ˜‰|Œ°„ã^€ÕY!bÇñlç10ç›\uˆ…»Ï?∫»\/Øn,ÄPÇчúúXúX0â≤1≠°ãäA≈¢∆¢&õÇÌ@vÎw±L⁄•Øñøjí?¯¸3Lù¨gg/›:¡ŒÀ◊“œ3˙yNÓ<ıeZÄ‚A»úí)Ùùrpjë˘û√OOåúMÁ:‡∂&J≈6‹å˙Yiá1ǵ¬Ã\∆KS=˙πV Ä√ôcJ@‘$^»&ıZ”D∆¢&≠Å!II∫H⁄ŧˆp,I˘á]S‹˜¯Kçºõ·uó≠≈%À32Á»’ë;–ßpÏdü…ôbcç•p¡œ îq‡Ì±ÃMDWó∑ûF ãÎGœëÈSß˙• ó(9‡0a‰'8„w;pì8É3l–é$-µ¢
§‚oO-ˇÙQfÊj=”\∏iå±Æ–œ32óG@T◊ÎÄì 9ß<◊¿à@9C/Ò∂êV°{«jF˝÷”’:N FPac´Á
ßzòƒ∞j,°∞ÈDaô7Ù¶Ã˛™ÒÌÁÉî%8SñELäö‰E3w¥$ƒ°‚cå˝«9rbÅ’cUú≥fº√ÿàÂt∆y
øß!Ω∞ÿsºt|ë≈Ãa≠‡ƒß»ä4ô T£.g•eáãCä~OÏeuÒm,ù≥√
∆‡5§
1Öä!ÉM}|oÛã9≥ãYF†∫Ö^ŒÙúc1Ûö°∆è|ì·;j¿8Æ⁄ø^(óó∆Ä\C[≥€ŸÄiGe[¸(õYpddLå&§©D¥ÂˇÁÇ6–
àB! ö‚
™‘\oÜÊr`√Í.¨≠;ëGgòûΰ9ä:eæü3◊ì`¿=öG◊ `"Rdã¶E(∫,õ≤∏x0 %⁄kVîfóXÿTFº
*ïñ‡øw„ekÿ∏™n&wúff>√ë–À`1wdŒk†
6√ÁfiVY˚ fÒÉ4@)™‘Ó¥ÂÕN¿¿$¡æl%AüèRÉMµæ∆PiF
∆ó!ÄQhV©R…‘EQı
”ú cë∞çYåJ√ÍÄH(å;⁄Sπ˙îÜ8áq„r?HBÅ+a45lúHπzÛ(oºb
7_<¡ö—∫yÏeé?˘ ^Óvõ&à âIo +ò]Ω5¢Ú®" ¥| ßïv N ”÷∂≠.HÇv§Àµπ≥F5§§)€L”Û≤Ë`Ë+Gi∞DõX·-◊maÎÜq0∂L∑kȶñ’# &R∂¨Óp·⁄.':…`>læóÛâØ>œ«æ≤óL!I0äî
’òÆ¥ïK;e)`¥™«kó mCÆΩ9¢ l(lH≤\™*FvSCƒT˘´V-J©% "π≈ƒfiyÛÖ|WAWZp (߀ˆOÕÒ'_ŸÀg⁄«lflëtR0)bRÔflàløT.nÂI5lFDG¶–í†)&\§3Çq¡ˆ¢AÍJĵ æq⁄û^;†4é◊Äi–T´a∑u◊π“Zk(‚éï4Pià5æ∫ÎÒ˚xzˇ4N!ÈtJ≠ Ä`ÉÌtínÉû‘0
^U•8p&D÷≤4UÑX“Rѯü- ùkhä @àë÷ÿƒ‡''j+_«öπ¶uPÜ#%≈`b√eáãÇOEy≠CQ÷ ÷ âıªµÿ4·‡LèO<Ù"3 Uz›àŒ.‚çWn¡$õ&~Ôt∞ùõ¶ÿ$¡$k
í¯‡0Œ\∑'M+˘ò»„,ãsRd$⁄î_=˝¶@fl‡Á¬π∂õØiG JÌD
mëJËq¨—§´8Á€ô¢⁄h¢˙F)`k∞â≈vRí4%Èv´}§√É˚¶˘ÎoÆE˘k∆:¸–móq—∆ ∞ìZˇ;â≈™ΩÈVi¢°fieÏËH€
ì!bƒ«âıv ∆vRH∑Cû$|~◊1v≠7«]πiǘϋÏù˝¢ Vdå Ôp01
2
Ω∏F*à JC#"≠†'√
)ìxkêH´=¡‘
ñ»¿H≥FRä^ñ ÉëÕ±â/µñ¿§fi-NªrõÖÁé±{j∂fiº~åÔªÓ∆PÕ´÷â†)&¶g3Ë‚W-§m1G€Änë€ ÎÏ…˙˘ú ÷”@&Qt>E”†´!'(ÁyHKp9,ÜiÌ∞.¿2+çø)]XvìJöêvS&üyvíìΩ:uΩÈ“ıº)PWQÙ.Ï Ü·n~”ëiqi+πP
Lóh≈_ä¢Btp$¥çí `µ™ˆç©Úa√lŧ-¡1%uy◊ÿ˙«éŒrfl «k‘5öZæÔ∫Õ\∂Æãs¡ûÖ€6äé5∑6Ñ“b/–§∫º
s~J@[áÜN}Àé‚p8flW∫≠ò$PX”Wá«éŒÒÂΑ’M flsÌfÆX?Bøfl'À˚‰yFûÁ8¿ë(^â˙bñhjM óFé∞x
s"|5Èÿ§*›ä†àÏmçµ8i”0≈#!:)C4¶Ú Ö´;ÏXõF@*™9óÆÌ÷hÀºf√NÑ¥€Ò˘¨4≈$ √¡E«ÒÃ!‚º«$÷w√´“œsÓy˛8ØY?Œï—¸≈ÌkFxÔ
[˘´]G…ãBÁ`◊‰GOfıÖæZYB#Ü–»Ê∆2âV®d˚<¶öçõ,fi˜v∆fi˙E}^ÑHL‹J]c§iZTëÿ†È•˘¬ˇ.„Ω;◊—\*ÃöjÄ‘fi}’VÚ⁄z6˛7sU>±Ô$ú»Bå¶8ÅT-÷)⁄I92ø»ÁvOÚœo‹Œ™NUz˝Ekπ~€Í⁄≠/féflº˜
4^íFı
•Zºëlré˚‘%B̸≈np8ó°.Ø‹„∏ÛXÍ≈<h D©/ËQ¥°Ü'E∏«¶¶Wü“VNáê/„üü—¸"uZî˙(ê˙ç—∞3⁄
åm^.bï∆sLjˆÑAõ;P“@¢∑ûëj`Óofl∆™ª˛ä<s˚≠µw7 ˆ0 √Äj”ÍÙŸLìUÑ /úX¨FEº§ì‘ócäÌT ;`.À*0 Ühâáµì$ÿ‘qdaÅøÿ5…{ÆÇé?ÂôzÌ¥ó)≥ =p5Q˜ï‘›V®ö3¥eêï}«ıʸ\E>˚∆ˇ¯ÊCˇK_‚ƒˇ}k}ûz÷áN◊:Öœà√
ó»2∆Ø6¿àÑ’∆—˙ïÚ∑¶y¯¿tàòsѨt;KÔ¶÷—Råvk¯<V¬ÇÒÀ∆îZ@1àU_ÚM\íÚËë9û=Ú<Ù˚‰˝å<À—‹Ø’´ÍıwzA1bVËjQÄ∆?bõÿ“Æ(ÏA∏˚kø|oç:K@í¥‡}ˇdôü*[—d ™rQmØ•6O ˇoÆØÃıÛ
MQ0òÄqæ—¿™‡[€ I◊í
”5ÉUœ0cpAS≤‹qb°G∂®‰=Gfiw∏<«Âäsæ Eƒ˙åÇ™–⁄ *Kw}å^fl˘iî]>”A@NfiÛèπ‡˚æD/s=îè
Ú›≈ÏPñXYTܸ{ÿÎ∂ ÆOÿ$Zü}-ÎT≠©µŸØ!wU§Jí±ñZ¯ØÒ–—≤¶‡ßO$òD±i° ëú<wHÊ|ß°zP|WJµzP’ø#úv-߈5 ˜ ÚßI*y?œ8ˆÈ∑–ÎÂàTıD>Ö_πz…≥Iù´ZáFØ¥·/RˆWIflM Üç2ºV0IàƒMQåıç
uÆW≠ÅUÀ©∆⁄)1±äI<=Y5 9d1Œ˜fl:Ô:;ï–¨ª!¬Æ∞ÍD¯∏S}Jå`M£µ2–âªøìÖk˙¿áÔqiq2
o‘k€îÀ,UOz´|oø$ᱩ?os∑)í¯>. ö'ÕÓtPÕµ&5˙ê èÉ|4ILéÉ#ü|Ûp@
ãÊ≈(≈.•FsX$Z<¿îTBBÒ∫ö¢Fi=᧴æ\?Oºö/Æ·I≈j>^kjEã“∂‘œflÏØGN ’˙Ä-µ2∫˜fi_üö=Í(zË„wúê“o6`≠9å_¡@|“Å’¥A£ jÄR“HÙï˙≠¶%\ûܸÂö∫°
<Ø.ƒp>>Ù+*@‰(⁄\XôÍ|q~$Zü±∫Ó˙}◊=m Xˇw/¬o+S$pcÌkã∂rÙ”ˇ®¥≈™r˛XΩÅïV£—Q£Ñ⁄⁄Ö1Xa˘ m±CZ.€ eÉrLCÂ_◊
?/˛ÊäÀΩßTÏû£—ÓjØ– ık´Ctü4Ï|·èl'q
Jæ
|–ôŒN≤ˇ#o[R‰f©7ˇŸaæ7òƒ|ˇ¨©®(“éøj]#ä◊ıu ãõ7fiπ±(5¢˘.˙ÎZ4√’©´∞+y˝˜5z¥ë´iOhµÃ_Ü∆‘)ö=
+ªE¿vOøX∆iüAuÍf’Œ 6ÕÌ5∆L#rªàåôf±_™˘~CªeX˚å‘k
çB›Àk⁄õ"3Æ%ΩU•&ÙÚu^7¯}fi)ıcy|^ˇ\aü‚sîÔ©RÂNˆÓN;^Ãfl˙–mg¿…'>ÃöfiᣆOäH_åºAD∫q˚ãËo¨A©◊I⁄ä=Q™=WTs"{®-VY *BcA÷
BnkSÇ]˛˚8 Ø®Ú—tƒ€çÁÔ∂eÖÒÀ~J€Ã„fÕçÔ√„Ä«ƒ◊,oëN•2–
‘÷…G§≈“±≠Z1ê◊‘v
qM˙k∆!±-©ÑZë∑
î„¡˘˘_∆HOvˇèï≠ö∏¢ÁŒ<Òá¨π˘}E´exëYπEѱ°≠˘«Z⁄} ñ ©≥î–û,ä
G›;rÆç∂Z"¯ÿ¶¥kãV∂§ ä?~ÂW‘ÈÔâHÅ›ˇ}ÂKXƯIü”è¸În˘Ò¢1CÂ!Å‹(¬⁄∂fi•ˆΩLö‘#juPjë>¥D«•^QlOåª÷å¥ãº™˙kNCe±=™˙ (%–‘≥ø}fÎâ⁄3˘“âáüµ∑¸DË¡≈°Ó 1ÊiÑ´Eÿ6T#h4”n7ZS¯⁄–é0%∂û’≠Ç…ÿ†∑iIƒ˘A3¿9WéxkE‰rR_E˘ô≈Eπ€ZúàÃ}˝YÙúÂv≈ø| ÃˇÄk≈»åëÔ5V∫’"∆O!hNÏl]ÖÍfl“ò"÷“∏<P¿ßX‹T#Î8˛q ÿé
òà∫Ú˙Î(æô◊\?°éªãßH?˝õ∑úe√∆9ÿ^ÛØæ¶
È©ˇ|ÀYÀÚú=ËÏ ü~êÖE«ÿàÒkXfid帨π”Ø-Ò*A√ó⁄†1-!^¢1w•—Ç4D—ˢÉÄ
™äõπâ[U©W1â≤œ⁄Úÿ ä»ΩÉTF]ù”Á‘Òqu˙µ∫õæ_w≈·Òfl∏ÒúÀÓº>õÒ∫üEã›D¨\/Ffi+¬ª≈»e∆ä1çilÕuS©*2Ï»@s^≥|LãqWWî¥ë∑*mIÆN˜®”O´Úß.◊ßÚ‚w˝7ú7ôù˜áeæÓ˝˚^(ÁäyÉâ1ÚZ1ÚÆ
ï≠Õ≠`X®Æµf»(k
Ôã™:L¢ÏSt ”™<ÉÚ-uz\åߧnjD‡¡_|›+&ìWêxªÂ◊ü
import matplotlib.pyplot as plt
import numpy as np
from shiny import *
app_ui = ui.page_fluid(
ui.input_slider(
"n", "input_slider()", min=10, max=100, value=50, step=5, animate=True
),
ui.output_plot("p"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.plot
def p():
np.random.seed(19680801)
x_rand = 100 + 15 * np.random.randn(437)
fig, ax = plt.subplots()
ax.hist(x_rand, int(input.n()), density=True)
return fig
app = App(app_ui, server)
import pathlib
import pandas as pd
from shiny import *
dir = pathlib.Path(__file__).parent
mtcars = pd.read_csv(dir / "mtcars.csv")
app_ui = ui.page_fluid(
ui.input_checkbox("highlight", "Highlight min/max values"),
ui.output_table("result"),
# Legend
ui.panel_conditional(
"input.highlight",
ui.panel_absolute(
"Yellow is maximum, grey is minimum",
bottom="6px",
right="6px",
class_="p-1 bg-light border",
),
),
class_="p-3",
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.table
def result():
if not input.highlight():
# If we're not highlighting values, we can simply
# return the pandas data frame as-is; @render.table
# will call .to_html() on it.
return mtcars
else:
# We need to use the pandas Styler API. The default
# formatting options for Styler are not the same as
# DataFrame.to_html(), so we set a few options to
# make them match.
return (
mtcars.style.set_table_attributes(
'class="dataframe shiny-table table w-auto"'
)
.hide(axis="index")
.format(
{
"mpg": "{0:0.1f}",
"disp": "{0:0.1f}",
"drat": "{0:0.2f}",
"wt": "{0:0.3f}",
"qsec": "{0:0.2f}",
}
)
.set_table_styles(
[dict(selector="th", props=[("text-align", "right")])]
)
.highlight_min(color="silver")
.highlight_max(color="yellow")
)
app = App(app_ui, server)
mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
21,6,160,110,3.9,2.62,16.46,0,1,4,4
21,6,160,110,3.9,2.875,17.02,0,1,4,4
22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
18.7,8,360,175,3.15,3.44,17.02,0,0,3,2
18.1,6,225,105,2.76,3.46,20.22,1,0,3,1
14.3,8,360,245,3.21,3.57,15.84,0,0,3,4
24.4,4,146.7,62,3.69,3.19,20,1,0,4,2
22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4
17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4
16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3
17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3
15.2,8,275.8,180,3.07,3.78,18,0,0,3,3
10.4,8,472,205,2.93,5.25,17.98,0,0,3,4
10.4,8,460,215,3,5.424,17.82,0,0,3,4
14.7,8,440,230,3.23,5.345,17.42,0,0,3,4
32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1
30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2
33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1
21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1
15.5,8,318,150,2.76,3.52,16.87,0,0,3,2
15.2,8,304,150,3.15,3.435,17.3,0,0,3,2
13.3,8,350,245,3.73,3.84,15.41,0,0,3,4
19.2,8,400,175,3.08,3.845,17.05,0,0,3,2
27.3,4,79,66,4.08,1.935,18.9,1,1,4,1
26,4,120.3,91,4.43,2.14,16.7,0,1,5,2
30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2
15.8,8,351,264,4.22,3.17,14.5,0,1,5,4
19.7,6,145,175,3.62,2.77,15.5,0,1,5,6
15,8,301,335,3.54,3.57,14.6,0,1,5,8
21.4,4,121,109,4.11,2.78,18.6,1,1,4,2
# Pandas needs Jinja2 for table styling, but it doesn't (yet) load automatically
# in Pyodide, so we need to explicitly list it here.
Jinja2
from shiny import *
app_ui = ui.page_fluid(
ui.input_text("txt", "Enter the text to display below:"),
ui.row(
ui.column(6, ui.output_text("text")),
ui.column(6, ui.output_text_verbatim("verb", placeholder=True)),
),
ui.row(
ui.column(6),
ui.column(6, ui.output_text_verbatim("verb_no_placeholder", placeholder=False)),
),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.text
def text():
return input.txt()
@output
@render.text
def verb():
return input.txt()
@output
@render.text
def verb_no_placeholder():
return input.txt()
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_action_button("add", "Add more controls"),
ui.output_ui("moreControls"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.ui
@reactive.event(input.add)
def moreControls():
return ui.TagList(
ui.input_slider("n", "N", min=1, max=1000, value=500),
ui.input_text("label", "Label"),
)
app = App(app_ui, server)
import matplotlib.pyplot as plt
import numpy as np
from shiny import *
app_ui = ui.page_fixed(
ui.layout_sidebar(
ui.panel_sidebar(ui.input_slider("n", "N", min=0, max=100, value=20)),
ui.panel_main(ui.output_plot("plot")),
),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.plot(alt="A histogram")
def plot() -> object:
np.random.seed(19680801)
x = 100 + 15 * np.random.randn(437)
fig, ax = plt.subplots()
ax.hist(x, input.n(), density=True)
return fig
app = App(app_ui, server)
import matplotlib.pyplot as plt
import numpy as np
from shiny import *
app_ui = ui.page_fluid(
ui.layout_sidebar(
ui.panel_sidebar(ui.input_slider("n", "N", min=0, max=100, value=20)),
ui.panel_main(ui.output_plot("plot")),
),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.plot(alt="A histogram")
def plot() -> object:
np.random.seed(19680801)
x = 100 + 15 * np.random.randn(437)
fig, ax = plt.subplots()
ax.hist(x, input.n(), density=True)
return fig
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.panel_title("A basic absolute panel example", "Demo"),
ui.panel_absolute(
ui.panel_well(
"Drag me around!", ui.input_slider("n", "N", min=0, max=100, value=20)
),
draggable=True,
width="300px",
right="50px",
top="50%",
),
)
def server(input: Inputs, output: Outputs, session: Session):
pass
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_checkbox("show", "Show radio buttons", False),
ui.panel_conditional(
"input.show", ui.input_radio_buttons("radio", "Choose ", ["slider", "select"])
),
ui.panel_conditional(
"input.show && input.radio === 'slider'",
ui.input_slider("slider", None, min=0, max=100, value=50),
),
ui.panel_conditional(
"input.show && input.radio === 'select'",
ui.input_select("slider", None, ["A", "B", "C"]),
),
)
def server(input: Inputs, output: Outputs, session: Session):
pass
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(ui.panel_title("Page title", "Window title"))
def server(input: Inputs, output: Outputs, session: Session):
pass
app = App(app_ui, server)
import asyncio
import random
import sqlite3
from datetime import datetime
from typing import Any, Awaitable
import pandas as pd
from shiny import *
SYMBOLS = ["AAA", "BBB", "CCC", "DDD", "EEE", "FFF"]
def timestamp() -> str:
return datetime.now().strftime("%x %X")
def rand_price() -> float:
return round(random.random() * 250, 2)
# === Initialize the database =========================================
def init_db(con: sqlite3.Connection) -> None:
cur = con.cursor()
try:
cur.executescript(
"""
CREATE TABLE stock_quotes (timestamp text, symbol text, price real);
CREATE INDEX idx_timestamp ON stock_quotes (timestamp);
"""
)
cur.executemany(
"INSERT INTO stock_quotes (timestamp, symbol, price) VALUES (?, ?, ?)",
[(timestamp(), symbol, rand_price()) for symbol in SYMBOLS],
)
con.commit()
finally:
cur.close()
conn = sqlite3.connect(":memory:")
init_db(conn)
# === Randomly update the database with an asyncio.task ==============
def update_db(con: sqlite3.Connection) -> None:
"""Update a single stock price entry at random"""
cur = con.cursor()
try:
sym = SYMBOLS[random.randint(0, len(SYMBOLS) - 1)]
print(f"Updating {sym}")
cur.execute(
"UPDATE stock_quotes SET timestamp = ?, price = ? WHERE symbol = ?",
(timestamp(), rand_price(), sym),
)
con.commit()
finally:
cur.close()
async def update_db_task(con: sqlite3.Connection) -> Awaitable[None]:
"""Task that alternates between sleeping and updating prices"""
while True:
await asyncio.sleep(random.random() * 1.5)
update_db(con)
asyncio.create_task(update_db_task(conn))
# === Create the reactive.poll object ===============================
def tbl_last_modified() -> Any:
df = pd.read_sql_query("SELECT MAX(timestamp) AS timestamp FROM stock_quotes", conn)
return df["timestamp"].to_list()
@reactive.poll(tbl_last_modified, 0.5)
def stock_quotes() -> pd.DataFrame:
return pd.read_sql_query("SELECT timestamp, symbol, price FROM stock_quotes", conn)
# === Define the Shiny UI and server ===============================
app_ui = ui.page_fluid(
ui.row(
ui.column(
8,
ui.markdown(
"""
# `shiny.reactive.poll` demo
This example app shows how to stream results from a database (in this
case, an in-memory sqlite3) with the help of `shiny.reactive.poll`.
"""
),
class_="mb-3",
),
),
ui.input_selectize("symbols", "Filter by symbol", [""] + SYMBOLS, multiple=True),
ui.output_ui("table"),
)
def server(input: Inputs, output: Outputs, session: Session) -> None:
def filtered_quotes():
df = stock_quotes()
if input.symbols():
df = df[df["symbol"].isin(input.symbols())]
return df
@output
@render.ui
def table():
return ui.HTML(
filtered_quotes().to_html(
index=False, classes="table font-monospace w-auto"
)
)
app = App(app_ui, server)
import asyncio
from shiny import *
app_ui = ui.page_fluid(
ui.input_action_button("button", "Compute"),
ui.output_text("compute"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.text
@reactive.event(input.button)
async def compute():
with ui.Progress(min=1, max=15) as p:
p.set(message="Calculation in progress", detail="This may take a while...")
for i in range(1, 15):
p.set(i, message="Computing")
await asyncio.sleep(0.1)
# Normally use time.sleep() instead, but it doesn't yet work in Pyodide.
# https://github.com/pyodide/pyodide/issues/2354
return "Done computing!"
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_action_button("rmv", "Remove UI"),
ui.input_text("txt", "Click button above to remove me"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
@reactive.event(input.rmv)
def _():
ui.remove_ui(selector="div:has(> #txt)")
app = App(app_ui, server)
from shiny import *
from shiny.types import ImgData
app_ui = ui.page_fluid(ui.output_image("image"))
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.image
def image():
from pathlib import Path
dir = Path(__file__).resolve().parent
img: ImgData = {"src": str(dir / "rstudio-logo.png"), "width": "150px"}
return img
app = App(app_ui, server)
from shiny import *
from shiny.types import SafeException
app_ui = ui.page_fluid(
ui.input_action_button("safe", "Throw a safe error"),
ui.output_ui("safe"),
ui.input_action_button("unsafe", "Throw an unsafe error"),
ui.output_ui("unsafe"),
ui.input_text(
"txt",
"Enter some text below, then remove it. Notice how the text is never fully removed.",
),
ui.output_ui("txt_out"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Calc
def safe_click():
req(input.safe())
return input.safe()
@output
@render.ui
def safe():
raise SafeException(f"You've clicked {str(safe_click())} times")
@output
@render.ui
def unsafe():
req(input.unsafe())
raise Exception(f"Super secret number of clicks: {str(input.unsafe())}")
@reactive.Effect
def _():
req(input.unsafe())
print("unsafe clicks:", input.unsafe())
# raise Exception("Observer exception: this should cause a crash")
@output
@render.ui
def txt_out():
req(input.txt(), cancel_output=True)
return input.txt()
app = App(app_ui, server)
app.sanitize_errors = True
import matplotlib.pyplot as plt
import numpy as np
from shiny import *
app_ui = ui.page_fluid(
ui.row(
ui.column(4, ui.input_slider("n", "N", min=0, max=100, value=20)),
ui.column(8, ui.output_plot("plot")),
)
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.plot(alt="A histogram")
def plot() -> object:
np.random.seed(19680801)
x = 100 + 15 * np.random.randn(437)
fig, ax = plt.subplots()
ax.hist(x, input.n(), density=True)
return fig
app = App(app_ui, server)
from shiny import *
from shiny.types import SafeException
app_ui = ui.page_fluid(ui.output_ui("safe"), ui.output_ui("unsafe"))
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.ui
def safe():
raise SafeException("This is a safe exception")
@output
@render.ui
def unsafe():
raise Exception("This is an unsafe exception")
app = App(app_ui, server)
app.sanitize_errors = True
from shiny import *
app_ui = ui.page_fluid(
ui.input_text("msg", "Enter a message"),
ui.input_action_button("submit", "Submit the message"),
# It'd be better to use ui.insert_ui() in order to implement this kind of
# functionality...this is just a basic demo of how custom message handling works.
ui.tags.div(id="messages"),
ui.tags.script(
"""
$(function() {
Shiny.addCustomMessageHandler("append_msg", function(message) {
$("<p>").text(message.msg).appendTo("#messages");
});
});
"""
),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
@reactive.event(input.submit)
async def _():
await session.send_custom_message("append_msg", {"msg": input.msg()})
app = App(app_ui, server, debug=True)
from shiny import *
from shiny.types import SilentCancelOutputException
app_ui = ui.page_fluid(
ui.input_text(
"txt",
"Delete the input text completely: it won't get removed below the input",
"Some text",
width="400px",
),
ui.output_ui("txt_out"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.ui
def txt_out():
if not input.txt():
raise SilentCancelOutputException()
return "Your input: " + input.txt()
app = App(app_ui, server)
from shiny import *
from shiny.types import SilentException
app_ui = ui.page_fluid(
ui.input_text(
"txt",
"Enter text to see it displayed below the input",
width="400px",
),
ui.output_ui("txt_out"),
)
def server(input: Inputs, output: Outputs, session: Session):
@output
@render.ui
def txt_out():
if not input.txt():
raise SilentException()
return "Your input: " + input.txt()
app = App(app_ui, server)
from shiny import App, render, ui
app_ui = ui.page_fluid(
ui.h2("Hello Shiny!"),
ui.input_slider("n", "N", 0, 100, 20),
ui.output_text_verbatim("txt"),
)
def server(input, output, session):
@output
@render.text
def txt():
return f"n*2 is {input.n() * 2}"
app = App(app_ui, server)
from htmltools import br
from shiny import *
app_ui = ui.page_fluid(
ui.input_action_button("update", "Update other buttons and link"),
br(),
ui.input_action_button("goButton", "Go"),
br(),
ui.input_action_button("goButton2", "Go 2", icon="🤩"),
br(),
ui.input_action_button("goButton3", "Go 3"),
br(),
ui.input_action_link("goLink", "Go Link"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
req(input.update())
# Updates goButton's label and icon
ui.update_action_button("goButton", label="New label", icon="üìÖ")
# Leaves goButton2's label unchanged and removes its icon
ui.update_action_button("goButton2", icon=[])
# Leaves goButton3's icon, if it exists, unchanged and changes its label
ui.update_action_button("goButton3", label="New label 3")
# Updates goLink's label and icon
ui.update_action_link("goLink", label="New link label", icon="üîó")
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.tags.p("The first checkbox group controls the second"),
ui.input_checkbox_group(
"inCheckboxGroup", "Input checkbox", ["Item A", "Item B", "Item C"]
),
ui.input_checkbox_group(
"inCheckboxGroup2", "Input checkbox 2", ["Item A", "Item B", "Item C"]
),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
x = input.inCheckboxGroup()
if x is None:
x = []
elif isinstance(x, str):
x = [x]
# Can also set the label and select items
ui.update_checkbox_group(
"inCheckboxGroup2",
label="Checkboxgroup label " + str(len(x)),
choices=x,
selected=x,
)
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_slider("controller", "Controller", min=0, max=1, value=0, step=1),
ui.input_checkbox("inCheckbox", "Input checkbox"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
# True if controller is odd, False if even.
x_even = input.controller() % 2 == 1
ui.update_checkbox("inCheckbox", value=x_even)
app = App(app_ui, server)
from datetime import date, timedelta
from shiny import *
app_ui = ui.page_fluid(
ui.input_slider("n", "Day of month", min=1, max=30, value=10),
ui.input_date_range("inDateRange", "Input date"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
d = date(2013, 4, input.n())
ui.update_date_range(
"inDateRange",
label="Date range label " + str(input.n()),
start=d - timedelta(days=1),
end=d + timedelta(days=1),
min=d - timedelta(days=5),
max=d + timedelta(days=5),
)
app = App(app_ui, server)
from datetime import date, timedelta
from shiny import *
app_ui = ui.page_fluid(
ui.input_slider("n", "Day of month", min=1, max=30, value=10),
ui.input_date("inDate", "Input date"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
d = date(2013, 4, input.n())
ui.update_date(
"inDate",
label="Date label " + str(input.n()),
value=d,
min=d - timedelta(days=3),
max=d + timedelta(days=3),
)
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.layout_sidebar(
ui.panel_sidebar(
ui.input_slider("controller", "Controller", min=1, max=3, value=1)
),
ui.panel_main(
ui.navset_tab_card(
ui.nav("Panel 1", "Panel 1 content", value="panel1"),
ui.nav("Panel 2", "Panel 2 content", value="panel2"),
ui.nav("Panel 3", "Panel 3 content", value="panel3"),
id="inTabset",
)
),
)
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
ui.update_navs("inTabset", selected="panel" + str(input.controller()))
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_slider("controller", "Controller", min=0, max=20, value=10),
ui.input_numeric("inNumber", "Input number", 0),
ui.input_numeric("inNumber2", "Input number 2", 0),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
x = input.controller()
ui.update_numeric("inNumber", value=x)
ui.update_numeric(
"inNumber2",
label="Number label " + str(x),
value=x,
min=x - 10,
max=x + 10,
step=5,
)
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.tags.p("The first radio button group controls the second"),
ui.input_radio_buttons(
"inRadioButtons", "Input radio buttons", ["Item A", "Item B", "Item C"]
),
ui.input_radio_buttons(
"inRadioButtons2", "Input radio buttons 2", ["Item A", "Item B", "Item C"]
),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
x = input.inRadioButtons()
# Can also set the label and select items
ui.update_radio_buttons(
"inRadioButtons2",
label="Radio buttons label " + x,
choices=[x],
selected=x,
)
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.tags.p("The checkbox group controls the select input"),
ui.input_checkbox_group(
"inCheckboxGroup", "Input checkbox", ["Item A", "Item B", "Item C"]
),
ui.input_select("inSelect", "Select input", ["Item A", "Item B", "Item C"]),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
x = input.inCheckboxGroup()
# Can use [] to remove all choices
if x is None:
x = []
elif isinstance(x, str):
x = [x]
ui.update_select(
"inSelect",
label="Select input label " + str(len(x)),
choices=x,
selected=x[len(x) - 1] if len(x) > 0 else None,
)
app = App(app_ui, server)
ß
ed e fd ÑZ

d¨
¶
<listcomp>z%server.<locals>._.<locals>.<listcomp>
◊“ÿ ÿ6–6≠®u©¨–6—6‘6ÿòw–'ÿ
Ù
r
Nr
maxOptions⁄render)r
◊“ÿ ÿ6–6≠®u©¨–6—6‘6ÿêYÿ
Ù
r
Ñ_
Ò
ı
Ò
r
⁄ htmltoolsr
page_fluid⁄input_selectize⁄app_ui⁄Inputs⁄Outputs⁄Sessionr
1∏2»
Ä
ê&
†'
∞G
8
Äcà&ê&†–%—%‘%ÄÄÄr
from htmltools import HTML
from shiny import *
app_ui = ui.page_fluid(
ui.input_selectize("x", "Server side selectize", choices=[], multiple=True),
ui.input_selectize(
"y", "Server side selectize with options", choices=[], multiple=True
),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
ui.update_selectize(
"x",
choices=[f"Foo {i}" for i in range(10000)],
selected=["Foo 0", "Foo 1"],
server=True,
)
@reactive.Effect
def _():
ui.update_selectize(
"y",
choices=[f"Foo {i}" for i in range(10000)],
selected=["Foo 1"],
server=True,
options=(
{
"maxOptions": 3,
"render": HTML(
'{option: function(item, escape) {return "<div><strong>Select " + item.label + "</strong></div>";}}'
),
}
),
)
app = App(app_ui, server, debug=True)
from shiny import *
app_ui = ui.page_fluid(
ui.layout_sidebar(
ui.panel_sidebar(
ui.tags.p("The first slider controls the second"),
ui.input_slider("control", "Controller:", min=0, max=20, value=10, step=1),
ui.input_slider("receive", "Receiver:", min=0, max=20, value=10, step=1),
),
ui.panel_main(),
)
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
val = input.control()
# Control the value, min, max, and step.
# Step size is 2 when input value is even; 1 when value is odd.
ui.update_slider(
"receive", value=val, min=int(val / 2), max=val + 4, step=(val + 1) % 2 + 1
)
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_slider("controller", "Controller", min=0, max=20, value=10),
ui.input_text("inText", "Input text"),
ui.input_text("inText2", "Input text 2"),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
def _():
x = str(input.controller())
# This will change the value of input$inText, based on x
ui.update_text("inText", value="New text " + x)
# Can also set the label, this time for input$inText2
ui.update_text("inText2", label="New label " + x, value="New text" + x)
app = App(app_ui, server)
from shiny import *
app_ui = ui.page_fluid(
ui.input_action_button("minus", "-1"),
ui.input_action_button("plus", "+1"),
ui.br(),
ui.output_text("value"),
)
def server(input: Inputs, output: Outputs, session: Session):
val = reactive.Value(0)
@reactive.Effect
@reactive.event(input.minus)
def _():
newVal = val.get() - 1
val.set(newVal)
@reactive.Effect
@reactive.event(input.plus)
def _():
newVal = val.get() + 1
val.set(newVal)
@output
@render.text
def value():
return str(val.get())
app = App(app_ui, server)
from pathlib import Path
from shiny import *
app_ui = ui.page_fluid(
ui.tags.link(href="css/styles.css", rel="stylesheet"),
ui.tags.div(
"If you see this text, it failed",
id="target",
style="background-color: red;",
),
ui.tags.script(src="js/changetext.js"),
ui.tags.div(
"This box should be green: ",
ui.tags.div(
id="box",
style="width: 100px; height:100px; border: 1px solid black;",
),
),
"There should be a slider below: ",
ui.input_slider("n", "N", min=1, max=100, value=50),
)
def server(input: Inputs, output: Outputs, session: Session):
pass
app_dir = Path(__file__).parent.resolve()
app = App(app_ui, server, static_assets=str(app_dir / "www"))
body {
background-color: limegreen;
}
@import url('more-styles.css');
body {
font-size: 2rem;
}
document.getElementById("target").innerText = "If you see this text, it worked!";
document.getElementById("target").style.backgroundColor = "limegreen";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment