Skip to content

Commit

Permalink
Expressify, simplify, and improve UI for plot interaction examples
Browse files Browse the repository at this point in the history
  • Loading branch information
cpsievert committed Jan 29, 2024
1 parent 91172d6 commit 5d44902
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 245 deletions.
90 changes: 26 additions & 64 deletions examples/python/plot_interact_basic/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,90 +3,52 @@

import matplotlib.pyplot as plt
import pandas as pd
from shiny import App, render, ui
from shiny.express import input, output_args, render, ui

mtcars = pd.read_csv(Path(__file__).parent / "mtcars.csv")
mtcars.drop(["disp", "hp", "drat", "qsec", "vs", "gear", "carb"], axis=1, inplace=True)

with ui.sidebar():
ui.input_radio_buttons(
"plot_type", "Plot type", ["matplotlib", "plotnine"]
)

app_ui = ui.page_fluid(
ui.head_content(
ui.tags.style(
"""
/* Smaller font for preformatted text */
pre, table.table {
font-size: smaller;
}

pre, table.table {
font-size: smaller;
}
"""
)
),
ui.row(
ui.column(
4,
ui.panel_well(
ui.input_radio_buttons(
"plot_type", "Plot type", ["matplotlib", "plotnine"]
)
),
),
ui.column(
8,
ui.output_plot("plot1", click=True, dblclick=True, hover=True, brush=True),
),
),
ui.row(
ui.column(3, ui.output_text_verbatim("click_info")),
ui.column(3, ui.output_text_verbatim("dblclick_info")),
ui.column(3, ui.output_text_verbatim("hover_info")),
ui.column(3, ui.output_text_verbatim("brush_info")),
),
)
@output_args(click=True, dblclick=True, hover=True, brush=True)
@render.plot(alt="A scatterplot")
def plot1():
if input.plot_type() == "matplotlib":
fig, ax = plt.subplots()
plt.title("Good old mtcars")
ax.scatter(mtcars["wt"], mtcars["mpg"])
return fig

elif input.plot_type() == "plotnine":
from plotnine import aes, geom_point, ggplot, ggtitle

def server(input, output, session):
@output
@render.plot(alt="A scatterplot")
def plot1():
if input.plot_type() == "matplotlib":
fig, ax = plt.subplots()
plt.title("Good old mtcars")
ax.scatter(mtcars["wt"], mtcars["mpg"])
return fig
p = (
ggplot(mtcars, aes("wt", "mpg"))
+ geom_point()
+ ggtitle("Good old mtcars")
)

elif input.plot_type() == "plotnine":
from plotnine import aes, geom_point, ggplot, ggtitle
return p

p = (
ggplot(mtcars, aes("wt", "mpg"))
+ geom_point()
+ ggtitle("Good old mtcars")
)

return p
with ui.layout_column_wrap(heights_equal="row"):

@output
@render.text()
@render.code
def click_info():
return "click:\n" + json.dumps(input.plot1_click(), indent=2)

@output
@render.text()
@render.code
def dblclick_info():
return "dblclick:\n" + json.dumps(input.plot1_dblclick(), indent=2)

@output
@render.text()
@render.code
def hover_info():
return "hover:\n" + json.dumps(input.plot1_hover(), indent=2)

@output
@render.text()
@render.code
def brush_info():
return "brush:\n" + json.dumps(input.plot1_brush(), indent=2)


app = App(app_ui, server, debug=True)
132 changes: 55 additions & 77 deletions examples/python/plot_interact_exclude/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,89 +4,67 @@
import pandas as pd
import statsmodels.api as sm
from plotnine import aes, geom_point, geom_smooth, ggplot
from shiny import App, reactive, render, ui

from shiny import reactive
from shiny.express import input, output_args, render, ui
from shiny.plotutils import brushed_points, near_points

mtcars = pd.read_csv(Path(__file__).parent / "mtcars.csv")
mtcars.drop(["disp", "hp", "drat", "qsec", "vs", "gear", "carb"], axis=1, inplace=True)

@output_args(click=True, brush=True)
@render.plot
def plot1():
df = data_with_keep()
df_keep = df[df["keep"]]
df_exclude = df[~df["keep"]]

return (
ggplot(df_keep, aes("wt", "mpg"))
+ geom_point()
+ geom_point(data=df_exclude, color="#666", fill="white")
+ geom_smooth(method="lm", fullrange=True)
)


app_ui = ui.page_fluid(
ui.head_content(
ui.tags.style(
"""
pre, table.table {
font-size: smaller;
}
"""
)
),
ui.row(
ui.column(2),
ui.column(
8,
ui.output_plot("plot1", click=True, brush=True),
ui.div(
{"style": "text-align: center"},
ui.input_action_button("exclude_toggle", "Toggle brushed points"),
ui.input_action_button("exclude_reset", "Reset"),
),
),
),
ui.row(
ui.column(12, {"style": "margin-top: 15px;"}, ui.output_text_verbatim("model")),
),
ui.div(
ui.input_action_button("exclude_toggle", "Toggle brushed points"),
ui.input_action_button("exclude_reset", "Reset"),
class_="d-flex justify-content-center pb-3 gap-3",
)


def server(input, output, session):
keep_rows = reactive.Value([True] * len(mtcars))

@reactive.Calc
def data_with_keep():
df = mtcars.copy()
df["keep"] = keep_rows()
return df

@reactive.Effect
@reactive.event(input.plot1_click)
def _():
res = near_points(mtcars, input.plot1_click(), all_rows=True, max_points=1)
keep_rows.set(list(np.logical_xor(keep_rows(), res.selected_)))

@reactive.Effect
@reactive.event(input.exclude_toggle)
def _():
res = brushed_points(mtcars, input.plot1_brush(), all_rows=True)
keep_rows.set(list(np.logical_xor(keep_rows(), res.selected_)))

@reactive.Effect
@reactive.event(input.exclude_reset)
def _():
keep_rows.set([True] * len(mtcars))

@output
@render.plot()
def plot1():
df = data_with_keep()
df_keep = df[df["keep"]]
df_exclude = df[~df["keep"]]

return (
ggplot(df_keep, aes("wt", "mpg"))
+ geom_point()
+ geom_point(data=df_exclude, color="#666", fill="white")
+ geom_smooth(method="lm", fullrange=True)
)

@output
@render.text()
def model():
df = data_with_keep()
df_keep = df[df["keep"]]
mod = sm.OLS(df_keep["wt"], df_keep["mpg"])
res = mod.fit()
return res.summary()


app = App(app_ui, server)
@render.code
def model():
df = data_with_keep()
df_keep = df[df["keep"]]
mod = sm.OLS(df_keep["wt"], df_keep["mpg"])
res = mod.fit()
return res.summary()


@reactive.calc
def data_with_keep():
df = mtcars.copy()
df["keep"] = keep_rows()
return df


keep_rows = reactive.value([True] * len(mtcars))

@reactive.effect
@reactive.event(input.plot1_click)
def _():
res = near_points(mtcars, input.plot1_click(), all_rows=True, max_points=1)
keep_rows.set(list(np.logical_xor(keep_rows(), res.selected_)))

@reactive.effect
@reactive.event(input.exclude_toggle)
def _():
res = brushed_points(mtcars, input.plot1_brush(), all_rows=True)
keep_rows.set(list(np.logical_xor(keep_rows(), res.selected_)))

@reactive.effect
@reactive.event(input.exclude_reset)
def _():
keep_rows.set([True] * len(mtcars))
Loading

0 comments on commit 5d44902

Please sign in to comment.