Overview
Welcome to the learn Shiny overview! Here we’ll introduce Shiny’s capabilities and link to articles where you can learn more. In the next article, we’ll cover more user interface (UI) components by building this dashboard:
Many examples on this site have a code editor for modifying the source code for a Shiny app (which runs entirely in the browser, thanks to shinylive). If you’d like to run any examples locally, first install and run Shiny locally, then copy/paste relevant code into the app.py
file (created by shiny create
).
Basics
Shiny apps typically start with input components to gather information from a user, which are then used to reactively render output components. Here’s a basic example that displays a slider’s value as formatted text.
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 150
from shiny.express import input, render, ui
ui.input_slider("val", "Slider label", min=0, max=100, value=50)
@render.text
def slider_val():
return f"Slider value: {input.val()}"
This example demonstrates the basic mechanics behind Shiny apps:
- Inputs are created via
ui.input_*()
functions.- The first argument is the input’s
id
, which is used to read the input’s value.
- The first argument is the input’s
- Outputs are created by decorating a function with
@render.*
.- Inside a
render
function,input
values can be read reactively. - When those
input
values change, Shiny knows how to minimally re-render output.
- Inside a
- This example happens to use
shiny.express
which, compared to core Shiny, reduces the amount of code required.
Components
Shiny includes many useful user interface (ui
) components for creating inputs, outputs, displaying messages, and more. For brevity sake, we’ll highlight just a few output and layout components here, but for a more comprehensive list, see the components gallery.
Outputs
Shiny makes it easy to create dynamic plots, tables, and other interactive widgets. All you need to do is apply a @render
decorator to a function that returns a suitable object. Shiny includes a wide variety of these decorators in its render
module, but Shiny extensions like shinywidgets
provide additional decorators for rendering other kinds of outputs, like Jupyter Widgets.
To include a plot in an application, apply @render.plot
to a function that creates a matplotlib visual. Note that packages like seaborn, plotnine, pandas, etc., are all compatible (as long as they create a matplotlib visual).
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 480
from shiny.express import input, render, ui
ui.input_selectize(
"var", "Select variable",
choices=["bill_length_mm", "body_mass_g"]
)
@render.plot
def hist():
from matplotlib import pyplot as plt
from palmerpenguins import load_penguins
df = load_penguins()
df[input.var()].hist(grid=False)
plt.xlabel(input.var())
plt.ylabel("count")
## file: requirements.txt
palmerpenguins
Apply @render.data_frame
to any code that returns a pandas or polars DataFrame for a basic table. For more sophisticated tables, you can use render.DataGrid
to add things like filters to your table.
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 480
from shiny.express import input, render, ui
ui.input_selectize(
"var", "Select variable",
choices=["bill_length_mm", "body_mass_g"]
)
@render.data_frame
def head():
from palmerpenguins import load_penguins
df = load_penguins()
return df[["species", input.var()]]
## file: requirements.txt
palmerpenguins
See the Jupyter Widgets article for more information on rendering Jupyter Widgets in Shiny.
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 480
from shiny.express import input, ui
from shinywidgets import render_altair
ui.input_selectize(
"var", "Select variable",
choices=["bill_length_mm", "body_mass_g"]
)
@render_altair
def hist():
import altair as alt
from palmerpenguins import load_penguins
df = load_penguins()
return (
alt.Chart(df)
.mark_bar()
.encode(x=alt.X(f"{input.var()}:Q", bin=True), y="count()")
)
## file: requirements.txt
altair
anywidget
palmerpenguins
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 480
from shiny.express import input, ui
from shinywidgets import render_bokeh
ui.input_selectize(
"var", "Select variable",
choices=["bill_length_mm", "body_mass_g"]
)
@render_bokeh
def hist():
from bokeh.plotting import figure
from palmerpenguins import load_penguins
p = figure(x_axis_label=input.var(), y_axis_label="count")
bins = load_penguins()[input.var()].value_counts().sort_index()
p.quad(
top=bins.values,
bottom=0,
left=bins.index - 0.5,
right=bins.index + 0.5,
)
return p
## file: requirements.txt
bokeh
jupyter_bokeh
xyzservices
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 480
from shiny.express import input, ui
from shinywidgets import render_plotly
ui.input_selectize(
"var", "Select variable",
choices=["bill_length_mm", "body_mass_g"]
)
@render_plotly
def hist():
import plotly.express as px
from palmerpenguins import load_penguins
df = load_penguins()
return px.histogram(df, x=input.var())
## file: requirements.txt
palmerpenguins
plotly
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 480
import pydeck as pdk
import shiny.express
from shinywidgets import render_pydeck
@render_pydeck
def map():
UK_ACCIDENTS_DATA = "https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv"
layer = pdk.Layer(
"HexagonLayer", # `type` positional argument is here
UK_ACCIDENTS_DATA,
get_position=["lng", "lat"],
auto_highlight=True,
elevation_scale=50,
pickable=True,
elevation_range=[0, 3000],
extruded=True,
coverage=1,
)
# Set the viewport location
view_state = pdk.ViewState(
longitude=-1.415,
latitude=52.2323,
zoom=6,
min_zoom=5,
max_zoom=15,
pitch=40.5,
bearing=-27.36,
)
# Combined all of it and render a viewport
return pdk.Deck(layers=[layer], initial_view_state=view_state)
## file: requirements.txt
pydeck==0.8.0
Many other awesome Python packages provide widgets that are compatible with Shiny. In general, you can render them by applying the @render_widget
decorator.
import shiny.express
from shinywidgets import render_widget
@render_widget
def widget():
# Widget code goes here
...
Layouts
Shiny provides a full suite of layout components which help with arranging multiple inputs and outputs in a variety of ways. As seen below, with shiny.express
, layout components (e.g., ui.sidebar()
) can be used as context managers to help with nesting and readability.
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 350
import plotly.express as px
from shiny.express import input, render, ui
from shinywidgets import render_widget
ui.page_opts(title="Sidebar layout", fillable=True)
with ui.sidebar():
ui.input_select("var", "Select variable", choices=["total_bill", "tip"])
@render_widget
def hist():
return px.histogram(px.data.tips(), input.var())
## file: requirements.txt
pandas
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 350
import plotly.express as px
from shiny.express import input, render, ui
from shinywidgets import render_widget
ui.page_opts(title="Multi-page example", fillable=True)
with ui.sidebar():
ui.input_select("var", "Select variable", choices=["total_bill", "tip"])
with ui.nav_panel("Plot"):
@render_widget
def hist():
return px.histogram(px.data.tips(), input.var())
with ui.nav_panel("Table"):
@render.data_frame
def table():
return px.data.tips()
## file: requirements.txt
pandas
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 350
import plotly.express as px
from shiny.express import input, render, ui
from shinywidgets import render_widget
ui.page_opts(title="Multi-tab example", fillable=True)
with ui.sidebar():
ui.input_select("var", "Select variable", choices=["total_bill", "tip"])
with ui.navset_card_underline(title="Penguins"):
with ui.nav_panel("Plot"):
@render_widget
def hist():
return px.histogram(px.data.tips(), input.var())
with ui.nav_panel("Table"):
@render.data_frame
def table():
return px.data.tips()
## file: requirements.txt
pandas
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 350
import plotly.express as px
from shiny.express import input, render, ui
from shinywidgets import render_widget
ui.page_opts(title="Multi-column example")
ui.input_select("var", "Select variable", choices=["total_bill", "tip"])
with ui.layout_columns(height="300px"):
@render_widget
def hist():
return px.histogram(px.data.tips(), input.var())
@render.data_frame
def table():
return px.data.tips()
## file: requirements.txt
pandas
Shiny also integrates well with Quarto, allowing you to leverage its web-based output formats (e.g., dashboards) in combination with Shiny outputs and reactivity.
Reactivity
Shiny uses something called transparent reactivity to automatically infer relationships between components, and minimally re-render as needed when dependencies change.1 As a result, apps naturally retain performance as they grow in size, without workarounds like caching or memoization. All Shiny apps are also built on the same small set of reactive foundations, each of which are simple and easy to learn, but can be combined in novel ways to create seriously sophisticated and performant apps.
To demonstrate how Shiny minimally re-renders, consider the following app which contains two different plots, each of which depends on a different input. When the first input changes, Shiny knows to only re-render the first plot, and vice versa.
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 325
import plotly.express as px
from shiny.express import input, render, ui
from shinywidgets import render_plotly
tips = px.data.tips()
with ui.layout_columns():
@render_plotly
def plot1():
p = px.histogram(tips, x=input.var1())
p.update_layout(height=200, xaxis_title=None)
return p
@render_plotly
def plot2():
p = px.histogram(tips, x=input.var2())
p.update_layout(height=200, xaxis_title=None)
return p
with ui.layout_columns():
ui.input_select("var1", None, choices=["total_bill", "tip"], width="100%")
ui.input_select("var2", None, choices=["tip", "total_bill"], width="100%")
## file: requirements.txt
palmerpenguins
plotly
pandas
Shiny also knows when outputs are visible or not, and so, will only call render
functions when needed. For example, in the app below, the table
function doesn’t get called until the “Table” page is selected.
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 325
import plotly.express as px
from shiny.express import input, render, ui
from shinywidgets import render_plotly
tips = px.data.tips()
with ui.sidebar():
ui.input_selectize("var", "Select variable", choices=["total_bill", "tip"])
ui.nav_spacer()
with ui.nav_panel("Plot"):
@render_plotly
def plot():
p = px.histogram(tips, x=input.var())
p.update_layout(height=225)
return p
with ui.nav_panel("Table"):
@render.data_frame
def table():
return tips[[input.var()]]
## file: requirements.txt
palmerpenguins
plotly
pandas
For a more in-depth look at reactivity, check out the reactivity article.
Starter templates
Once you’ve installed Shiny, the shiny create
CLI command provides access to a collection of useful starter templates. This command walks you through a series of prompts to help you get started quickly with a helpful example. One great option is the dashboard template, which can be created with:
shiny create --template dashboard
See the workflow section for more information developing Shiny apps locally. Also keep in mind you can develop apps in the browser using the playground.
Extensible foundation
Shiny is built on a foundation of web standards, allowing you to incrementally adopt custom HTML, CSS, and/or JavaScript as needed. In fact, Shiny UI components themselves are built on a Python representation of HTML/CSS/JavaScript, which you can see by printing them in a Python REPL:
>>> from shiny import ui
>>> ui.input_action_button("btn", "Button")
<button class="btn btn-default action-button" id="btn" type="button">Button</button>
And, since UI is HTML, you can gently introduce HTML/CSS/JavaScript as needed in your apps to customize without having to learn complicated build tooling and frameworks. However, if you’re versed in web programming, you can also use Shiny to create custom components that leverage your favorite JavaScript framework from Python.
Next steps
Next, we’ll learn more about Shiny components and layouts by making a dashboard.