import numpy as np
import pandas as pd
import plotly.graph_objects as go
import solara
def find_row_ids(fig, select, n_sel_max=99999):
"""A very annoying function to get row IDs because Plotly is unhelpful"""
# goes from trace index and point index to row index in a dataframe
# requires passing df.index as custom_data
trace_index = select["points"]["trace_indexes"]
point_index = select["points"]["point_indexes"]
point_ids = []
for t, p in zip(trace_index, point_index, strict=True):
point_trace = fig.data[t]
if point_trace.customdata is not None:
point_ids.append(point_trace.customdata[0][p]) # For go.Scatter
return point_ids[:n_sel_max]
# Create sample data
np.random.seed(42)
n_points = 10000
df = pd.DataFrame({
'x': np.random.normal(0, 1, n_points),
'y': np.random.normal(0, 1, n_points),
'group': np.random.choice(['A', 'B', 'C'], n_points),
'cell_idx': np.arange(n_points)
})
selected_points = solara.reactive([])
@solara.component
def MinimalScatterExample():
# Create figure
fig = go.Figure()
select_point_ids = lambda data: selected_points.set(find_row_ids(fig, data))
# Add scatter traces for each group
for n, g in df.groupby(['group']):
fig.add_trace(
go.Scattergl(
x=g.x,
y=g.y,
mode='markers',
selected=go.scattergl.Selected(marker={"color":"red", "size":5}),
customdata=[df.cell_idx],
)
)
# Update layout
fig.update_layout(
height=600,
width=800,
dragmode='lasso', # Set default mode to lasso
selectdirection='any', # Allow selection in any direction
hovermode='closest'
)
def configure():
fig_widget = solara.get_widget(fig_element)
fig_widget._config = fig_widget._config | {
'modeBarButtonsToAdd': ['select2d', 'lasso2d'],
'modeBarButtonsToRemove': ['autoScale2d'],
}
# Create figure element
fig_element = solara.FigurePlotly(fig, on_selection=select_point_ids)
solara.use_effect(configure)
# Display number of selected points
solara.Info(f"Number of selected points: {len(selected_points.value)}")
@solara.component
def Page():
MinimalScatterExample()
# # Run the app
# if __name__ == "__main__":
# Page()