How to achieve 2 way plotly selection?

In the tutorial there is parent-child example. but I want to achieve two way selection (assuming 2 plots plotting using different dimension of a single df). how would i achieve that?
I tried to ask different LLM without success yet.

Plotly — Panel v1.7.0

import pandas as pd
import plotly.express as px

import panel as pn

pn.extension("plotly")
df = (
    pd.read_csv("https://datasets.holoviz.org/penguins/v1/penguins.csv")
    .dropna()
    .reset_index(drop=True)
)
df["index"] = df.index # Used to filter rows for child view

color_map = {"Adelie": "blue", "Chinstrap": "red", "Gentoo": "green"}

fig_parent = px.scatter(
    df,
    x="flipper_length_mm",
    y="body_mass_g",
    color_discrete_map=color_map,
    custom_data="index",  # Used to filter rows for child view
    color="species",
    title="<b>Parent Plot</b>: Box or Lasso Select Points",
)


def fig_child(selectedData):
    if selectedData:
        indices = [point["customdata"][0] for point in selectedData["points"]]
        filtered_df = df.iloc[indices]
        title = f"<b>Child Plot</b>: Selected Points({len(indices)})"
    else:
        filtered_df = df
        title = f"<b>Child Plot</b>: All Points ({len(filtered_df)})"

    fig = px.scatter(
        filtered_df,
        x="bill_length_mm",
        y="bill_depth_mm",
        color_discrete_map=color_map,
        color="species",
        hover_data={"flipper_length_mm": True, "body_mass_g": True},
        title=title,
    )
    return fig


parent_config = {
    "modeBarButtonsToAdd": ["select2d", "lasso2d"],
    "modeBarButtonsToRemove": [
        "zoomIn2d",
        "zoomOut2d",
        "pan2d",
        "zoom2d",
        "autoScale2d",
    ],
    "displayModeBar": True,
    "displaylogo": False,
}
parent_pane = pn.pane.Plotly(fig_parent, config=parent_config)

ifig_child = pn.bind(fig_child, parent_pane.param.selected_data)
child_config = {
    "modeBarButtonsToRemove": [
        "select2d",
        "lasso2d",
        "zoomIn2d",
        "zoomOut2d",
        "pan2d",
        "zoom2d",
        "autoScale2d",
    ],
    "displayModeBar": True,
    "displaylogo": False,
}
child_pane = pn.pane.Plotly(ifig_child, config=child_config)

pn.FlexBox(parent_pane, child_pane)

I am not sure to understand exactly what you want to do. But if you want, the Parent-Child Views example with the ability to select data on the child view and retrieve them, you have to:

  • Do not remove the “select2d” and “lasso2d” buttons in the child config
  • Use the child_pane.param.selected_data to get your data
1 Like

Agree. It depends on exactly what you want to do.

Here is an example where the plots display the value hold by

displayed_data = pn.rx(DATA)

When you select in either of the plots the displayed_data.rx.value is updated and the plots update accordingly.

import pandas as pd
import plotly.express as px

import panel as pn
import panel_material_ui as pmui # See https://panel-material-ui.holoviz.org/

pn.extension("plotly")

@pn.cache
def get_data():
    df = (
        pd.read_csv("https://datasets.holoviz.org/penguins/v1/penguins.csv")
        .dropna()
        .reset_index(drop=True)
    )
    df["index"] = df.index # Used to filter rows for child view
    return df

DATA = get_data()
COLOR_MAP = {"Adelie": "blue", "Chinstrap": "red", "Gentoo": "green"}
PLOT_CONFIG = {
    "modeBarButtonsToAdd": ["select2d", "lasso2d"],
    "modeBarButtonsToRemove": [
        "zoomIn2d",
        "zoomOut2d",
        "pan2d",
        "zoom2d",
        "autoScale2d",
    ],
    "displayModeBar": True,
    "displaylogo": False,
    "dragmode": "select"  # Set box select as the default drag mode
}

displayed_data = pn.rx(DATA)

def reset_displayed_data(event):
    displayed_data.rx.value = pn.rx(DATA).rx.value = DATA

def filter_displayed_data(selected_data):
    if selected_data:
        indices = [point["customdata"][0] for point in selected_data["points"]]
        if indices:
            data = displayed_data.rx.value
            mask = data["index"].isin(indices)
            data = data.loc[mask]
            displayed_data.rx.value = data
                

def plot_1(data):
    return px.scatter(
        data,
        x="flipper_length_mm",
        y="body_mass_g",
        color_discrete_map=COLOR_MAP,
        color="species",
        custom_data="index",  # Used to filter rows
        hover_data={"bill_length_mm": True, "bill_depth_mm": True, "index": True},
        title="<b>Plot 1</b>",
    )

def plot_2(data):
    return px.scatter(
        data,
        x="bill_length_mm",
        y="bill_depth_mm",
        color_discrete_map=COLOR_MAP,
        color="species",
        custom_data="index",  # Used to filter rows
        hover_data={"flipper_length_mm": True, "body_mass_g": True, "index": True},
        title="<b>Plot 2</b>",
    )

reset_button = pmui.Button(
    label="Reset Selections",
    variant="contained",
    color="primary",
    icon="restart_alt",
    on_click=reset_displayed_data,
)

plot_1_pane = pn.pane.Plotly(pn.bind(plot_1, displayed_data), config=PLOT_CONFIG)
plot_2_pane = pn.pane.Plotly(pn.bind(plot_2, displayed_data), config=PLOT_CONFIG)

plot_1_pane.param.selected_data.rx.watch(filter_displayed_data)
plot_2_pane.param.selected_data.rx.watch(filter_displayed_data)

pn.Column(reset_button, pn.FlexBox(plot_1_pane, plot_2_pane)).servable()