from pathlib import Path from typing import List import pandas as pd import seaborn as sns import shinyswatch from colors import bg_palette, palette import shiny.experimental as x 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 = x.ui.page_fillable( shinyswatch.theme.pulse(), x.ui.layout_sidebar( x.ui.sidebar( # Artwork by @allison_horst ui.tags.img( src="palmerpenguins.png", width="80%", class_="mt-0 mb-2 mx-auto" ), 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_text_verbatim("brush"), x.ui.output_plot("scatter", fill=True, brush=ui.brush_opts()), fill=True, fillable=True, ), ) 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""" # This calculation "req"uires that at least one species is selected req(len(input.species()) > 0) if False: if "scatter_brush" in input: info = input.scatter_brush() print(str(info)) else: print("No brush!") # Filter the rows so we only include the desired species return df[df["Species"].isin(input.species())] @output @render.text def brush(): return str(input.scatter_brush()) @output @render.plot def scatter(): """Generates a plot for Shiny to display to the user""" # The plotting function to use depends on whether margins are desired 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 x.ui.value_box( f"{title}", count, {"class_": "pt-1 pb-0"}, # ui.h1(HTML("$1 Billion Dollars")), # ui.span( # # bsicons::bs_icon("arrow-up"), # # "X", # " 30% VS PREVIOUS 30 DAYS", # ), # showcase = bsicons::bs_icon("piggy-bank"), # showcase=f"{name}!", # class_="bg-success", showcase=x.ui.bind_fill_role( ui.tags.img( {"style": "object-fit:contain;"}, src=showcase_img, ), item=True, ), theme_color=None, style=f"background-color: {bgcol};", height="90px", full_screen=True, ) if not input.by_species(): return penguin_value_box( "Penguins", len(df.index), bg_palette["default"], # Artwork by @allison_horst showcase_img="penguins.png", ) value_boxes = [ penguin_value_box( name, len(df[df["Species"] == name]), bg_palette[name], # Artwork by @allison_horst showcase_img=f"{name}.png", ) for name in species # Only include boxes for _selected_ species if name in input.species() ] return x.ui.layout_column_wrap(1 / len(value_boxes), *value_boxes) app = App( app_ui, server, static_assets=str(www_dir), )