# How to undo add\_selection() after custom selection

**URL:** <https://community.plotly.com/t/how-to-undo-add-selection-after-custom-selection/76316>\
**Category:** 📊 Plotly Python\
**Created:** [June 22, 2023, 9:49am UTC](https://community.plotly.com/t/how-to-undo-add-selection-after-custom-selection/76316 "2023-06-22T09:49:56Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![en84](https://avatars.discourse-cdn.com/v4/letter/e/5f9b8f/32.png) [@en84](https://community.plotly.com/u/en84)\
**Post date:** [June 22, 2023, 9:49am UTC](https://community.plotly.com/t/how-to-undo-add-selection-after-custom-selection/76316/1 "2023-06-22T09:49:56Z")

</div>

Hello!

Is there a `remove_selection()` function to clear a selection box I add with `Figure.add_selection()`?

Here is some context. In my app, I customize the behavior of box select, adjusting which points are selected by the user. In a selectedData callback for my graph, I set `trace.update(selectedpoints=adjusted_selection)`. This works but only for a second, as immediately another event occurs with empty selection that undoes what I just set. I can get around this issue by first setting `selectedpoints` as above, and then calling `figure.add_selection(...)` to match the points I selected. In this way, the second callback still sees the selection and keeps it there. The problem is that I can’t undo the selection I add with `add_selection`: it always stays there. I can add more with box select but not remove them.

What am I missing?

---

<div class="post-metadata">

**Author:** ![AIMPED](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/aimped/32/21758_2.png) [@AIMPED](https://community.plotly.com/u/AIMPED)\
**Post date:** [June 22, 2023, 9:59am UTC](https://community.plotly.com/t/how-to-undo-add-selection-after-custom-selection/76316/2 "2023-06-22T09:59:26Z")

</div>

Hey @en84 welcome to the forums!

> [@en84](#):
>
> In my app,

Are you using `dash`? Could you add some code because unfortunately I don’t fully understand what you are trying to do.

---

<div class="post-metadata">

**Author:** ![en84](https://avatars.discourse-cdn.com/v4/letter/e/5f9b8f/32.png) [@en84](https://community.plotly.com/u/en84)\
**Post date:** [June 22, 2023, 1:45pm UTC](https://community.plotly.com/t/how-to-undo-add-selection-after-custom-selection/76316/3 "2023-06-22T13:45:32Z")

</div>

Here is an abridged version of the code that reproduces the problem. You will need a TSV with a column “position” with x-values and “value” with y-values (from 0 to 100).

```auto
from typing import Any, List, Optional, Tuple
from attrs import define, field

import dash
import dash_bootstrap_components as dbc
import pandas as pd
import plotly.express as px
import plotly.graph_objs as go
from dash import dash_table, dcc, html
from dash.dependencies import Input, Output

@define
class MyData:
    df: pd.DataFrame = field()
    figure: go.Figure = field(init=False)

    def __attrs_post_init__ (self) -> None:
        self.figure = self.make_figure()

    def make_figure(self) -> None:
        self.df["position"] = range(self.df.shape[0])
        self.df["value"] = self.df["count"]

        figure = go.Figure()
        figure.add_trace(
            go.Scatter(
                x=self.df["position"],
                y=self.df["value"],
                mode="markers",
                name="my_series",
            )
        )
        figure.update_layout(yaxis_title="Value")
        return figure

my_data: Optional[MyData] = None

app = dash.Dash("my_app", prevent_initial_callbacks="initial_duplicate")

app.layout = html.Div(
    [
        dcc.Graph(id="my_plot", figure=px.scatter(None)),
        html.Div(dbc.Button("Load from file", id="load_btn")),
        html.Div(id="selection_div"),
    ]
)

def expand_selection(indices: List[int]) -> List[int]:
    if indices:
        return list(range(min(indices), max(indices) + 1))
    return []

@app.callback(
    Output("my_plot", "figure", allow_duplicate=True),
    Output("selection_div", "children"),
    Input("my_plot", "selectedData"),
)
def adjust_selection(sel_data: Any) -> Tuple[go.Figure, html.Div]:
    print("> adjust_selection()")
    global my_data

    if my_data is None:
        print(" - no data")
        return (go.Figure(), html.Div(html.P("Data not loaded")))

    figure: go.Figure = my_data.figure

    if sel_data is None or "range" not in sel_data:
        print(" - no selection")
        figure.for_each_trace(lambda trace: trace.update(selectedpoints=[]))
        return (figure, html.Div(html.P("No data selected")))
    
    print(" - selection")

    selection_start = sel_data["range"]["x"][0]
    selection_end = sel_data["range"]["x"][1]

    selected_points = [point["pointIndex"] for point in sel_data["points"]]
    selected_points = expand_selection(selected_points)

    figure.for_each_trace(lambda trace: trace.update(selectedpoints=selected_points))
    # figure.add_selection(x0=selection_start, y0=0, x1=selection_end, y1=100)

    selected_rows = (my_data.df["position"] >= selection_start) & (my_data.df["position"] <= selection_end)
    selected_df = my_data.df.loc[selected_rows]

    my_table = html.Div(
        [
            html.P("Selected Data"),
            dash_table.DataTable(
                selected_df.to_dict("records"),
                id="my_table",
                columns=[{"name": c, "id": c} for c in selected_df.columns]
            )
        ],
    )

    return (figure, my_table)

@app.callback(
    Output("my_plot", "figure"),
    Input("load_btn", "n_clicks"),
    prevent_initial_call=True,
)
def button_clicked(load_btn: Any) -> go.Figure:
    print("> button_clicked")

    return load_data_from_file()

def load_data_from_file() -> go.Figure:
    print("> load_data_from_file")
    global my_data

    df = pd.read_csv("my_data_table.tsv", sep="\t")
    print(f" df is {df.shape[0]}x{df.shape[1]}")

    my_data = MyData(df)

    return my_data.figure

# Run the app
if __name__ == " __main__":
    app.run_server(host="0.0.0.0", debug=True)

```

Using this code, when I load data and then select some points I see this output:

```auto
> adjust_selection()
  - no data
> button_clicked
> load_data_from_file
  df is 2000x10
> adjust_selection()
  - selection
> adjust_selection() <<< ???
  - no selection

```

As the first `adjust_selection()` is triggered, all works as expected. But immediately after, a second event is triggered (with no selection), and it clear the table and deselects all points in the graph. I do not understand where the second event comes from.

To go back to the original graph, I need to double click on the graph - twice - as the first time nothing happens.

To persist the selection, one can uncomment the `add_selection()` line. It gives me this output:

```auto
> adjust_selection()
  - no data
> button_clicked
> load_data_from_file
  df is 2000x10
> adjust_selection()
  - selection
> adjust_selection() <<< selection persists
  - selection

```

Now the table remains populated, and the graph shows the selected points, but also the selection box around them.

If I double-click on the graph, the table is cleared but the selection (and the box) remain in the graph. If I double-click a second time (and more), the original graph flashes and goes back to the selection.

---

<div class="post-metadata">

**Author:** ![en84](https://avatars.discourse-cdn.com/v4/letter/e/5f9b8f/32.png) [@en84](https://community.plotly.com/u/en84)\
**Post date:** [June 28, 2023, 11:30am UTC](https://community.plotly.com/t/how-to-undo-add-selection-after-custom-selection/76316/4 "2023-06-28T11:30:33Z")

</div>

I seem to understand this better now - please do correct me if I’m wrong!

Because `adjust_selection()` returns a Figure, which is going to overwrite the figure currently displayed, callbacks are triggered as when the page is loaded for the first time (?). This would explain the second call to that function with no selection. A new figure is displayed, which overwrites the previous one. The selection box around the points of interest was on the previous figure but not on the new one.

Nevertheless, it would be great to be able to remove a selection added with `add_selection()`.
