Skip to content

Add shiny for python dashboard tips example #230

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/extensions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ jobs:
simple-mcp-server: extensions/simple-mcp-server/**
simple-shiny-chat-with-mcp: extensions/simple-shiny-chat-with-mcp/**
chat-with-content: extensions/chat-with-content/**
dashboard-tips: extensions/dashboard-tips/**

# Runs for each extension that has changed from `simple-extension-changes`
# Lints and packages in preparation for tests and and release.
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ required to group content in the Gallery.
{
...
"extension": {
"category": "extension,
"category": "extension",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch thank you!

...
}
}
Expand Down
1 change: 1 addition & 0 deletions extensions/dashboard-tips/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11
7 changes: 7 additions & 0 deletions extensions/dashboard-tips/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Dashboard tips app

<a href='https://connect.posit.cloud/publish?framework=shiny&sourceRepositoryURL=https%3A%2F%2Fgithub.com%2Fposit-dev%2Fpy-shiny-templates&sourceRef=main&sourceRefType=branch&primaryFile=dashboard-tips%2Fapp-express.py&pythonVersion=3.11'><img src='https://cdn.connect.posit.cloud/assets/deploy-to-connect-blue.svg' align="right" /></a>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels a bit strange to have a "Deploy to Connect Cloud" link here if someone is viewing the README from the Gallery. We also plan to expose these READMEs in the Connect UI for the Gallery which presents weird questions about showing this. Any thoughts on removing this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh interesting! Yes I agree this shouldnt be in the connect on-prem gallery as-is. Though, I really like that as a feature for one click installs!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could swap this out to a link to the posit-dev/py-shiny-templates repo?

That README has the link https://github.com/posit-dev/py-shiny-templates/tree/main/dashboard-tips and it makes more sense there.




This template gives you a more "complete" dashboard for exploring the tips dataset. For an overview of what's here, visit [this article](https://shiny.posit.co/py/docs/user-interfaces.html).
161 changes: 161 additions & 0 deletions extensions/dashboard-tips/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import faicons as fa
import plotly.express as px

# Load data and compute static values
from shared import app_dir, tips
from shiny import reactive, render
from shiny.express import input, ui
from shinywidgets import render_plotly

bill_rng = (min(tips.total_bill), max(tips.total_bill))

# Add page title and sidebar
ui.page_opts(title="Restaurant tipping", fillable=True)

with ui.sidebar(open="desktop"):
ui.input_slider(
"total_bill",
"Bill amount",
min=bill_rng[0],
max=bill_rng[1],
value=bill_rng,
pre="$",
)
ui.input_checkbox_group(
"time",
"Food service",
["Lunch", "Dinner"],
selected=["Lunch", "Dinner"],
inline=True,
)
ui.input_action_button("reset", "Reset filter")

# Add main content
ICONS = {
"user": fa.icon_svg("user", "regular"),
"wallet": fa.icon_svg("wallet"),
"currency-dollar": fa.icon_svg("dollar-sign"),
"ellipsis": fa.icon_svg("ellipsis"),
}

with ui.layout_columns(fill=False):
with ui.value_box(showcase=ICONS["user"]):
"Total tippers"

@render.express
def total_tippers():
tips_data().shape[0]

with ui.value_box(showcase=ICONS["wallet"]):
"Average tip"

@render.express
def average_tip():
d = tips_data()
if d.shape[0] > 0:
perc = d.tip / d.total_bill
f"{perc.mean():.1%}"

with ui.value_box(showcase=ICONS["currency-dollar"]):
"Average bill"

@render.express
def average_bill():
d = tips_data()
if d.shape[0] > 0:
bill = d.total_bill.mean()
f"${bill:.2f}"


with ui.layout_columns(col_widths=[6, 6, 12]):
with ui.card(full_screen=True):
ui.card_header("Tips data")

@render.data_frame
def table():
return render.DataGrid(tips_data())

with ui.card(full_screen=True):
with ui.card_header(class_="d-flex justify-content-between align-items-center"):
"Total bill vs tip"
with ui.popover(title="Add a color variable", placement="top"):
ICONS["ellipsis"]
ui.input_radio_buttons(
"scatter_color",
None,
["none", "sex", "smoker", "day", "time"],
inline=True,
)

@render_plotly
def scatterplot():
color = input.scatter_color()
return px.scatter(
tips_data(),
x="total_bill",
y="tip",
color=None if color == "none" else color,
trendline="lowess",
)

with ui.card(full_screen=True):
with ui.card_header(class_="d-flex justify-content-between align-items-center"):
"Tip percentages"
with ui.popover(title="Add a color variable"):
ICONS["ellipsis"]
ui.input_radio_buttons(
"tip_perc_y",
"Split by:",
["sex", "smoker", "day", "time"],
selected="day",
inline=True,
)

@render_plotly
def tip_perc():
from ridgeplot import ridgeplot

dat = tips_data()
dat["percent"] = dat.tip / dat.total_bill
yvar = input.tip_perc_y()
uvals = dat[yvar].unique()

samples = [[dat.percent[dat[yvar] == val]] for val in uvals]

plt = ridgeplot(
samples=samples,
labels=uvals,
bandwidth=0.01,
colorscale="viridis",
colormode="row-index",
)

plt.update_layout(
legend=dict(
orientation="h", yanchor="bottom", y=1.02, xanchor="center", x=0.5
)
)

return plt


ui.include_css(app_dir / "styles.css")

# --------------------------------------------------------
# Reactive calculations and effects
# --------------------------------------------------------


@reactive.calc
def tips_data():
bill = input.total_bill()
idx1 = tips.total_bill.between(bill[0], bill[1])
idx2 = tips.time.isin(input.time())
return tips[idx1 & idx2]


@reactive.effect
@reactive.event(input.reset)
def _():
ui.update_slider("total_bill", value=bill_rng)
ui.update_checkbox_group("time", selected=["Lunch", "Dinner"])
54 changes: 54 additions & 0 deletions extensions/dashboard-tips/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"version": 1,
"locale": "en_US.UTF-8",
"metadata": {
"appmode": "python-shiny",
"entrypoint": "shiny.express.app:app_2e_py"
},
"python": {
"version": "3.11.13",
"package_manager": {
"name": "pip",
"version": "25.1.1",
"package_file": "requirements.txt"
}
},
"environment": {
"python": {
"requires": "~=3.11.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a hard requirement or can this support back to 3.9?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a similar question to @mconflitti-pbc

}
},
"extension": {
"name": "dashboard-tips",
"title": "Restaurant tips dashboard",
"description": "An intermediate dashboard with input filters, value boxes, a plot, and table.",
"homepage": "https://github.com/posit-dev/connect-extensions/tree/main/extensions/dashboard-tips",
"category": "example",
"tags": ["python", "shiny"],
"minimumConnectVersion": "2025.04.0",
"version": "0.0.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can leave this 0.0.0 until merged and then the connect team can take this over to ensure it works as expected and run it through cicd

},
"files": {
"requirements.txt": {
"checksum": "78b8a4930d239b3bc4d5c0fedbc4c73d"
},
".python-version": {
"checksum": "c0479ff484dacd51dc5ba0461b16b9bf"
},
"README.md": {
"checksum": "60fdf69b53a2a06b6fc41ae84b76bdf9"
},
"app.py": {
"checksum": "25eb97a954aefab7b5d4fd888ec7d6fc"
},
"shared.py": {
"checksum": "2c9ddbcae51dc1faee86c4a6d1781b43"
},
"styles.css": {
"checksum": "da429f0b2b2ce1f9f3ca8ab942efb124"
},
"tips.csv": {
"checksum": "b8e189917e1e12e5e34247f90db7834f"
}
}
}
6 changes: 6 additions & 0 deletions extensions/dashboard-tips/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
faicons
shiny
shinywidgets
plotly
pandas
ridgeplot
6 changes: 6 additions & 0 deletions extensions/dashboard-tips/shared.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from pathlib import Path

import pandas as pd

app_dir = Path(__file__).parent
tips = pd.read_csv(app_dir / "tips.csv")
12 changes: 12 additions & 0 deletions extensions/dashboard-tips/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
:root {
--bslib-sidebar-main-bg: #f8f8f8;
}

.popover {
--bs-popover-header-bg: #222;
--bs-popover-header-color: #fff;
}

.popover .btn-close {
filter: var(--bs-btn-close-white-filter);
}
Loading
Loading