import pathlib
import sys
from typing import Optional, cast
import pandas as pd
import solara
import solara.express as solara_px # similar to plotly express, but comes with cross filters
import solara.lab
from solara.components.columns import Columns
from solara.components.file_drop import FileDrop
github_url = solara.util.github_url(__file__)
if sys.platform != "emscripten":
pycafe_url = solara.util.pycafe_url(path=pathlib.Path(__file__), requirements=["pandas", "plotly"])
else:
pycafe_url = None
df_sample = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv")
class State:
size_max = solara.reactive(40.0)
size = solara.reactive(cast(Optional[str], None))
color = solara.reactive(cast(Optional[str], None))
x = solara.reactive(cast(Optional[str], None))
y = solara.reactive(cast(Optional[str], None))
logx = solara.reactive(False)
logy = solara.reactive(False)
df = solara.reactive(cast(Optional[pd.DataFrame], None))
@staticmethod
def load_sample():
State.x.value = "gdpPercap"
State.y.value = "lifeExp"
State.size.value = "pop"
State.color.value = "continent"
State.logx.value = True
State.df.value = df_sample
@staticmethod
def load_from_file(file):
df = pd.read_csv(file["file_obj"])
State.x.value = str(df.columns[0])
State.y.value = str(df.columns[1])
State.size.value = str(df.columns[2])
State.color.value = str(df.columns[3])
State.df.value = df
@staticmethod
def reset():
State.df.value = None
@solara.component
def Page():
df = State.df.value
dark_effective = solara.lab.use_dark_effective()
# the .scatter will set this cross filter
filter, _set_filter = solara.use_cross_filter(id(df))
# only apply the filter if the filter or dataframe changes
def filter_df():
if filter is not None and df is not None:
return df.loc[filter]
dff = solara.use_memo(filter_df, dependencies=[df, filter])
with solara.AppBar():
solara.lab.ThemeToggle()
with solara.Sidebar():
with solara.Card("Controls", margin=0, elevation=0):
with solara.Column():
with solara.Row():
solara.Button("Sample dataset", color="primary", text=True, outlined=True, on_click=State.load_sample, disabled=df is not None)
solara.Button("Clear dataset", color="primary", text=True, outlined=True, on_click=State.reset)
if sys.platform != "emscripten":
FileDrop(on_file=State.load_from_file, on_total_progress=lambda *args: None, label="Drag file here")
else:
solara.Info("File upload not supported in this environment")
if df is not None:
solara.SliderFloat(label="Size", value=State.size_max, min=1, max=100)
solara.Checkbox(label="Log x", value=State.logx)
solara.Checkbox(label="Log y", value=State.logy)
columns = list(map(str, df.columns))
solara.Select("Column x", values=columns, value=State.x)
solara.Select("Column y", values=columns, value=State.y)
solara.Select("Size", values=columns, value=State.size)
solara.Select("Color", values=columns, value=State.color)
if filter is None:
solara.Info("If you select points in the scatter plot, you can download the points here.")
else:
def get_data():
return dff.to_csv(index=False)
solara.FileDownload(get_data, label=f"Download {len(dff):,} selected points", filename="selected.csv")
if df is not None:
with Columns(widths=[2, 4]):
if State.x.value and State.y.value:
solara_px.scatter(
df,
State.x.value,
State.y.value,
size=State.size.value,
color=State.color.value,
size_max=State.size_max.value,
log_x=State.logx.value,
log_y=State.logy.value,
template="plotly_dark" if dark_effective else "plotly_white",
)
else:
solara.Warning("Select x and y columns")
else:
solara.Info("No data loaded, click on the sample dataset button to load a sample dataset, or upload a file.")
with solara.Column(style={"max-width": "400px"}):
solara.Button(label="View source", icon_name="mdi-github-circle", attributes={"href": github_url, "target": "_blank"}, text=True, outlined=True)
if sys.platform != "emscripten":
solara.Button(
label="Edit this example live on py.cafe",
icon_name="mdi-coffee-to-go-outline",
attributes={"href": pycafe_url, "target": "_blank"},
text=True,
outlined=True,
)
@solara.component
def Layout(children):
route, routes = solara.use_route()
dark_effective = solara.lab.use_dark_effective()
return solara.AppLayout(children=children, toolbar_dark=dark_effective, color=None) # if dark_effective else "primary")