flowchart LR A[/input_origin/] --> P([my_table])
Learning goals
DataGrid and DataTable in Shinydata_view() and cell_selection() to drive reactive outputsCovered apps
app-01a-table-basic.py — basic DataGridapp-01b-table-basic.py — basic DataTableapp-02-table-from-input.py — dropdown -> update tableapp-03-table-selection.py — row selection -> chartapp-04-table-linked.py — row selection -> linked Altair chartShiny provides two built-in table renderers:
Both use the same ui.output_data_frame("my_table") placeholder.
Both support: sorting · filtering · row selection · editable cells
Docs: render.DataGrid · render.DataTable
pagination, filtering, sorting enabled
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [editor, viewer]
#| viewerHeight: 500
#| packages: [vega_datasets]
from shiny import App, ui, render
from vega_datasets import data as vega_data
import pandas as pd
cars = pd.DataFrame(vega_data.cars()).iloc[:, :5]
app_ui = ui.page_fluid(
ui.h4("Cars dataset"),
ui.layout_columns(
ui.input_switch("filters", "Show filters", True),
col_widths=[3],
),
ui.output_data_frame("grid"),
)
def server(input, output, session):
@render.data_frame
def grid():
return render.DataGrid(
cars,
filters=input.filters(),
height="400px",
width="100%",
)
app = App(app_ui, server)
Same features — different visual style (wider rows, HTML table look):
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [editor, viewer]
#| viewerHeight: 500
#| packages: [vega_datasets]
from shiny import App, ui, render
from vega_datasets import data as vega_data
import pandas as pd
cars = pd.DataFrame(vega_data.cars()).iloc[:, :5]
app_ui = ui.page_fluid(
ui.h4("Cars dataset — DataTable"),
ui.layout_columns(
ui.input_switch("filters", "Show filters", True),
col_widths=[3],
),
ui.output_data_frame("table"),
)
def server(input, output, session):
@render.data_frame
def table():
return render.DataTable(
cars,
filters=input.filters(),
height="400px",
width="100%",
)
app = App(app_ui, server)
DataGrid vs DataTableSame data, different presentation:
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 600
#| packages: [vega_datasets]
from shiny import App, ui, render
from vega_datasets import data as vega_data
import pandas as pd
cars = pd.DataFrame(vega_data.cars()).iloc[:, :5]
app_ui = ui.page_fluid(
ui.layout_columns(
ui.card(
ui.card_header(ui.code("render.DataGrid")),
ui.output_data_frame("grid"),
),
ui.card(
ui.card_header(ui.code("render.DataTable")),
ui.output_data_frame("table"),
),
),
)
def server(input, output, session):
@render.data_frame
def grid():
return render.DataGrid(cars, height="300px")
@render.data_frame
def table():
return render.DataTable(cars, height="300px")
app = App(app_ui, server)flowchart LR A[/input_origin/] --> P([my_table])
The table is just a reactive output
my_table is a @render.data_frame — it re-runs whenever any input.* it reads changesinput.origin() filters the dataframe in Python before returning it to render.DataGridinput -> output pattern as any other Shiny output#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 500
#| packages: [vega_datasets]
import pandas as pd
from vega_datasets import data as vega_data
from shiny import App, ui, render
cars = pd.DataFrame(vega_data.cars())
origins = sorted(cars["Origin"].unique())
cols = ["Name", "Miles_per_Gallon", "Cylinders", "Horsepower", "Weight_in_lbs"]
app_ui = ui.page_fluid(
ui.h4("Filter table with a dropdown"),
ui.layout_columns(
ui.input_select("origin", "Origin", choices=["All"] + origins),
col_widths=[3],
),
ui.output_data_frame("tbl"),
)
def server(input, output, session):
@render.data_frame
def tbl():
df = cars[cols]
if input.origin() != "All":
df = df[cars["Origin"] == input.origin()]
return render.DataGrid(df, width="100%", height="380px")
app = App(app_ui, server)
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [editor, viewer]
#| viewerHeight: 500
#| packages: [vega_datasets]
import pandas as pd
from vega_datasets import data as vega_data
from shiny import App, ui, render
cars = pd.DataFrame(vega_data.cars())
origins = sorted(cars["Origin"].unique())
cols = ["Name", "Miles_per_Gallon", "Cylinders", "Horsepower", "Weight_in_lbs"]
app_ui = ui.page_fluid(
ui.h4("Filter table with a dropdown"),
ui.layout_columns(
ui.input_select("origin", "Origin", choices=["All"] + origins),
col_widths=[3],
),
ui.output_data_frame("tbl"),
)
def server(input, output, session):
@render.data_frame
def tbl():
df = cars[cols]
if input.origin() != "All":
df = df[cars["Origin"] == input.origin()]
return render.DataGrid(df, width="100%", height="380px")
app = App(app_ui, server)
data_view()Once rendered, the decorator result exposes methods to read the table’s current state:
| Method | Returns |
|---|---|
tbl.data_view() |
All rows after sort + filter |
tbl.data_view(selected=True) |
Only selected rows |
tbl.data() |
Original underlying data |
tbl.cell_selection() |
Row indices of selection |
Docs: render.data_frame · cell_selection()
Set selection_mode="rows" to let users select rows.
Access selected rows with .data_view(selected=True):
data_view(selected=True) returns selected rows with any sorting/filtering the user has applied — it is the data as the user sees it.
flowchart LR A([my_table]) -- ".data_view<br>selected=True" --> P([scatter])
A rendered table can also be a reactive source
my_table is a @render.data_frame output — but it also exposes .data_view(selected=True).data_view(selected=True) is invalidated and re-runsscatter re-renders with the selected rows highlighted — no separate input.* needed#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 560
#| packages: [vega_datasets, altair, shinywidgets]
import pandas as pd
import altair as alt
from vega_datasets import data as vega_data
from shiny import App, ui, render, reactive, req
from shinywidgets import output_widget, render_altair
cars = pd.DataFrame(vega_data.cars()).iloc[:, :5]
app_ui = ui.page_fluid(
ui.h4("Select rows to highlight them in the chart"),
ui.layout_columns(
ui.card(ui.output_data_frame("tbl"), height="300px"),
ui.card(output_widget("scatter")),
col_widths=[5, 7],
),
)
def server(input, output, session):
@render.data_frame
def tbl():
return render.DataGrid(cars, selection_mode="rows", height="250px")
@render_altair
def scatter():
selected = tbl.data_view(selected=True)
base = alt.Chart(cars).mark_circle(color="#D1D5DB", size=60).encode(
x=alt.X("Miles_per_Gallon:Q"),
y=alt.Y("Horsepower:Q"),
tooltip=["Name:N", "Miles_per_Gallon:Q", "Horsepower:Q"],
)
if selected is None or selected.empty:
return base.properties(width="container", height=260)
highlight = alt.Chart(selected).mark_circle(color="#3B82F6", size=80).encode(
x="Miles_per_Gallon:Q",
y="Horsepower:Q",
)
return (base + highlight).properties(width="container", height=260)
app = App(app_ui, server)
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [editor, viewer]
#| viewerHeight: 560
#| packages: [vega_datasets, altair, shinywidgets]
import pandas as pd
import altair as alt
from vega_datasets import data as vega_data
from shiny import App, ui, render, reactive, req
from shinywidgets import output_widget, render_altair
cars = pd.DataFrame(vega_data.cars()).iloc[:, :5]
app_ui = ui.page_fluid(
ui.h4("Select rows to highlight them in the chart"),
ui.layout_columns(
ui.card(ui.output_data_frame("tbl"), height="300px"),
ui.card(output_widget("scatter")),
col_widths=[5, 7],
),
)
def server(input, output, session):
@render.data_frame
def tbl():
return render.DataGrid(cars, selection_mode="rows", height="250px")
@render_altair
def scatter():
selected = tbl.data_view(selected=True)
base = alt.Chart(cars).mark_circle(color="#D1D5DB", size=60).encode(
x=alt.X("Miles_per_Gallon:Q"),
y=alt.Y("Horsepower:Q"),
tooltip=["Name:N", "Miles_per_Gallon:Q", "Horsepower:Q"],
)
if selected is None or selected.empty:
return base.properties(width="container", height=260)
highlight = alt.Chart(selected).mark_circle(color="#3B82F6", size=80).encode(
x="Miles_per_Gallon:Q",
y="Horsepower:Q",
)
return (base + highlight).properties(width="container", height=260)
app = App(app_ui, server)
flowchart LR
A[/input_origin/] --> F{{filtered}}
F --> P1([my_table])
F --> P2([scatter])
P1 -- ".data_view<br>selected=True" --> P2
@reactive.calc shares the selected rows between table and chart — the same pattern as with maps.
filtered() runs once when input.origin() changes — both my_table and scatter share the cached resultscatter has two reactive dependencies: filtered() (the full filtered set, grey points) and my_table.data_view(selected=True) (highlighted points)filtered(), which invalidates both outputs at oncescatter — my_table itself does not re-render#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| viewerHeight: 620
#| packages: [vega_datasets, altair, shinywidgets]
import pandas as pd
import altair as alt
from vega_datasets import data as vega_data
from shiny import App, ui, render, reactive, req
from shinywidgets import output_widget, render_altair
cars = pd.DataFrame(vega_data.cars())
origins = sorted(cars["Origin"].unique())
display_cols = ["Name", "Miles_per_Gallon", "Cylinders", "Horsepower", "Origin"]
app_ui = ui.page_fluid(
ui.h4("Cars explorer"),
ui.input_select("origin", "Origin", choices=["All"] + origins),
ui.layout_columns(
ui.card(
ui.card_header("Data — select rows to highlight"),
ui.output_data_frame("tbl"),
),
ui.card(
ui.card_header("MPG vs Horsepower"),
output_widget("scatter"),
),
col_widths=[5, 7],
),
)
def server(input, output, session):
@reactive.calc
def filtered():
df = cars[display_cols]
if input.origin() != "All":
df = df[cars["Origin"] == input.origin()]
return df
@render.data_frame
def tbl():
return render.DataGrid(filtered(), selection_mode="rows", height="300px")
@render_altair
def scatter():
df = filtered()
selected = tbl.data_view(selected=True)
base = alt.Chart(df).mark_circle(color="#D1D5DB", size=60).encode(
x="Miles_per_Gallon:Q", y="Horsepower:Q",
tooltip=display_cols,
)
if selected is None or selected.empty:
return base.properties(width="container", height=320)
hi = alt.Chart(selected).mark_circle(color="#3B82F6", size=90).encode(
x="Miles_per_Gallon:Q", y="Horsepower:Q",
)
return (base + hi).properties(width="container", height=320)
app = App(app_ui, server)
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [editor, viewer]
#| viewerHeight: 620
#| packages: [vega_datasets, altair, shinywidgets]
import pandas as pd
import altair as alt
from vega_datasets import data as vega_data
from shiny import App, ui, render, reactive, req
from shinywidgets import output_widget, render_altair
cars = pd.DataFrame(vega_data.cars())
origins = sorted(cars["Origin"].unique())
display_cols = ["Name", "Miles_per_Gallon", "Cylinders", "Horsepower", "Origin"]
app_ui = ui.page_fluid(
ui.h4("Cars explorer"),
ui.input_select("origin", "Origin", choices=["All"] + origins),
ui.layout_columns(
ui.card(
ui.card_header("Data — select rows to highlight"),
ui.output_data_frame("tbl"),
),
ui.card(
ui.card_header("MPG vs Horsepower"),
output_widget("scatter"),
),
col_widths=[5, 7],
),
)
def server(input, output, session):
@reactive.calc
def filtered():
df = cars[display_cols]
if input.origin() != "All":
df = df[cars["Origin"] == input.origin()]
return df
@render.data_frame
def tbl():
return render.DataGrid(filtered(), selection_mode="rows", height="300px")
@render_altair
def scatter():
df = filtered()
selected = tbl.data_view(selected=True)
base = alt.Chart(df).mark_circle(color="#D1D5DB", size=60).encode(
x="Miles_per_Gallon:Q", y="Horsepower:Q",
tooltip=display_cols,
)
if selected is None or selected.empty:
return base.properties(width="container", height=320)
hi = alt.Chart(selected).mark_circle(color="#3B82F6", size=90).encode(
x="Miles_per_Gallon:Q", y="Horsepower:Q",
)
return (base + hi).properties(width="container", height=320)
app = App(app_ui, server)