-
Notifications
You must be signed in to change notification settings - Fork 3
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
3.11 |
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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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! There was a problem hiding this comment. Choose a reason for hiding this commentThe 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). |
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"]) |
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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
faicons | ||
shiny | ||
shinywidgets | ||
plotly | ||
pandas | ||
ridgeplot |
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") |
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); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch thank you!