|
from pathlib import Path |
|
from typing import List, Dict, Tuple |
|
import matplotlib.colors as mpl_colors |
|
|
|
import pandas as pd |
|
import seaborn as sns |
|
import shinyswatch |
|
|
|
from shiny import App, Inputs, Outputs, Session, reactive, render, req, ui |
|
|
|
sns.set_theme() |
|
|
|
www_dir = Path(__file__).parent.resolve() / "www" |
|
|
|
df = pd.read_csv(Path(__file__).parent / "penguins.csv", na_values="NA") |
|
numeric_cols: List[str] = df.select_dtypes(include=["float64"]).columns.tolist() |
|
species: List[str] = df["Species"].unique().tolist() |
|
species.sort() |
|
|
|
app_ui = ui.page_fillable( |
|
shinyswatch.theme.minty(), |
|
ui.layout_sidebar( |
|
ui.sidebar( |
|
|
|
ui.input_selectize( |
|
"xvar", |
|
"X variable", |
|
numeric_cols, |
|
selected="Bill Length (mm)", |
|
), |
|
ui.input_selectize( |
|
"yvar", |
|
"Y variable", |
|
numeric_cols, |
|
selected="Bill Depth (mm)", |
|
), |
|
ui.input_checkbox_group( |
|
"species", "Filter by species", species, selected=species |
|
), |
|
ui.hr(), |
|
ui.input_switch("by_species", "Show species", value=True), |
|
ui.input_switch("show_margins", "Show marginal plots", value=True), |
|
), |
|
ui.output_ui("value_boxes"), |
|
ui.output_plot("scatter", fill=True), |
|
ui.help_text( |
|
"Artwork by ", |
|
ui.a("@allison_horst", href="https://twitter.com/allison_horst"), |
|
class_="text-end", |
|
), |
|
), |
|
) |
|
|
|
|
|
def server(input: Inputs, output: Outputs, session: Session): |
|
@reactive.Calc |
|
def filtered_df() -> pd.DataFrame: |
|
"""Returns a Pandas data frame that includes only the desired rows""" |
|
|
|
|
|
req(len(input.species()) > 0) |
|
|
|
|
|
return df[df["Species"].isin(input.species())] |
|
|
|
@output |
|
@render.plot |
|
def scatter(): |
|
"""Generates a plot for Shiny to display to the user""" |
|
|
|
|
|
plotfunc = sns.jointplot if input.show_margins() else sns.scatterplot |
|
|
|
plotfunc( |
|
data=filtered_df(), |
|
x=input.xvar(), |
|
y=input.yvar(), |
|
palette=palette, |
|
hue="Species" if input.by_species() else None, |
|
hue_order=species, |
|
legend=False, |
|
) |
|
|
|
@output |
|
@render.ui |
|
def value_boxes(): |
|
df = filtered_df() |
|
|
|
def penguin_value_box(title: str, count: int, bgcol: str, showcase_img: str): |
|
return ui.value_box( |
|
title, |
|
count, |
|
{"class_": "pt-1 pb-0"}, |
|
showcase=ui.fill.as_fill_item( |
|
ui.tags.img( |
|
{"style": "object-fit:contain;"}, |
|
src=showcase_img, |
|
) |
|
), |
|
theme_color=None, |
|
style=f"background-color: {bgcol};", |
|
) |
|
|
|
if not input.by_species(): |
|
return penguin_value_box( |
|
"Penguins", |
|
len(df.index), |
|
bg_palette["default"], |
|
|
|
showcase_img="penguins.png", |
|
) |
|
|
|
value_boxes = [ |
|
penguin_value_box( |
|
name, |
|
len(df[df["Species"] == name]), |
|
bg_palette[name], |
|
|
|
showcase_img=f"{name}.png", |
|
) |
|
for name in species |
|
|
|
if name in input.species() |
|
] |
|
|
|
return ui.layout_column_wrap(*value_boxes, width = 1 / len(value_boxes)) |
|
|
|
|
|
|
|
colors = [[255, 140, 0], [160, 32, 240], [0, 139, 139]] |
|
colors = [(r / 255.0, g / 255.0, b / 255.0) for r, g, b in colors] |
|
|
|
palette: Dict[str, Tuple[float, float, float]] = { |
|
"Adelie": colors[0], |
|
"Chinstrap": colors[1], |
|
"Gentoo": colors[2], |
|
"default": sns.color_palette()[0], |
|
} |
|
|
|
bg_palette = {} |
|
|
|
for name, col in palette.items(): |
|
|
|
bg_palette[name] = mpl_colors.to_hex(sns.light_palette(col, n_colors=7)[1]) |
|
|
|
|
|
app = App( |
|
app_ui, |
|
server, |
|
static_assets=str(www_dir), |
|
) |
|
|