Skip to content

[BUG]: deserialization of xrange of box select in figure with multiple subplots broken #5703

Description

@flpi-sun

Description

When you do a box (or lasso) select in a figure with multiple subplots (not the first subplot) the selection x/yrange is not a list/dict but a plain python object.

Image

The gif above was created with this example:

import plotly.graph_objects as go
import solara
from plotly.subplots import make_subplots

def build_figure() -> go.Figure:
	fig = make_subplots(rows=1, cols=2, subplot_titles=("Scatter", "Bar"))
	fig.add_trace(go.Scatter(x=[1, 2, 3, 4, 5, 6], y=[2, 1, 3, 5, 4, 6], name="Series A"), row=1, col=1)
	fig.add_trace(go.Bar(x=["A", "B", "C"], y=[3, 1, 2], name="Series B"), row=1, col=2)
	fig.update_layout(title="FigureWidget example with 2 subplots",)
	return fig

@solara.component
def Page():
	def on_selection(data):
		print("on_select data:", data, flush=True)
	solara.FigurePlotly(build_figure(), on_selection=on_selection)

Steps to reproduce

  1. Create a figure with more than one subplot.
  2. Do a box select in the second subplot.
  3. Observe the data send to on_select.

Activity

  1. flpi-sun commented on Aug 14, 2026

    @flpi-sun
    Author

    Let me know if this is actually a solara bug.

  2. camdecoster commented on Aug 14, 2026

    @camdecoster
    Contributor

    @flpi-sun would you be able to investigate if this a bug related to solara?

  3. flpi-sun commented on Aug 24, 2026

    @flpi-sun
    Author

    It seems to be related to solara, because I can't replicate the bug using dash.
    I couldn't replicate the bug using dash so I at first thought its a solara issue but its actually inside Plotly widgetbundle.js.

    Here's the dash example anyway (where the bug does not occur):

    import json
    from collections.abc import Mapping, Sequence
    
    import plotly.graph_objects as go
    from dash import Dash, Input, Output, dcc, html
    from plotly.subplots import make_subplots
    
    
    def build_figure() -> go.Figure:
        fig = make_subplots(rows=1, cols=2, subplot_titles=("Scatter", "Bar"))
        fig.add_trace(
            go.Scatter(
                x=[1, 2, 3, 4, 5, 6],
                y=[2, 1, 3, 5, 4, 6],
                mode="markers",
                name="Series A",
            ),
            row=1,
            col=1,
        )
        fig.add_trace(go.Bar(x=["A", "B", "C"], y=[3, 1, 2], name="Series B"), row=1, col=2)
        fig.update_layout(dragmode="select")
        return fig
    
    
    app = Dash(__name__)
    app.layout = html.Div(
        [
            dcc.Graph(id="selection-graph", figure=build_figure()),
            html.Pre(id="selection-debug", style={"whiteSpace": "pre-wrap"}),
        ]
    )
    
    
    @app.callback(Output("selection-debug", "children"), Input("selection-graph", "selectedData"))
    def on_selection(selected_data):
        return "selectedData:\n\n" + json.dumps(selected_data, indent=2)
    
    
    if __name__ == "__main__":
        app.run(debug=True)
    Image
  4. flpi-sun commented on Aug 24, 2026

    @flpi-sun
    Author

    Here's my proposed patch that I verified using the first example above.

    Index: js/src/widget.ts
    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    ===================================================================
    diff --git a/js/src/widget.ts b/js/src/widget.ts
    --- a/js/src/widget.ts	(revision a5974ced8c177bb23bd51d65a5461aa7562c9f82)
    +++ b/js/src/widget.ts	(revision 9721bf3db12464f43f14e633344e21b800d0bdd9)
    @@ -109,9 +109,7 @@
     
     type Selector = {
       type: "box" | "lasso";
    -  selector_state:
    -    | { xrange: number[]; yrange: number[] }
    -    | { xs: number[]; ys: number[] };
    +  selector_state: Record<string, any>;
     };
     
     // Model
    @@ -1149,22 +1147,30 @@
         var selectorObject: Selector;
     
         if (data.hasOwnProperty("range")) {
    -      // Box selection
    +      // Box selection - preserve all subplot-specific axis keys (x, x2, x3, ... and y, y2, y3, ...)
    +      var rangeData = data["range"];
    +      // Verify we have at least an x and y range (in any subplot)
    +      var hasXRange = Object.keys(rangeData || {}).some((key: string) => /^x\d*$/.test(key));
    +      var hasYRange = Object.keys(rangeData || {}).some((key: string) => /^y\d*$/.test(key));
    +      if (!hasXRange || !hasYRange) {
    +        return null;
    +      }
           selectorObject = {
             type: "box",
    -        selector_state: {
    -          xrange: data["range"]["x"],
    -          yrange: data["range"]["y"],
    -        },
    +        selector_state: rangeData,
           };
         } else if (data.hasOwnProperty("lassoPoints")) {
    -      // Lasso selection
    +      // Lasso selection - preserve all subplot-specific axis keys
    +      var lassoData = data["lassoPoints"];
    +      // Verify we have at least an x and y coordinate array (in any subplot)
    +      var hasXCoords = Object.keys(lassoData || {}).some((key: string) => /^x\d*$/.test(key));
    +      var hasYCoords = Object.keys(lassoData || {}).some((key: string) => /^y\d*$/.test(key));
    +      if (!hasXCoords || !hasYCoords) {
    +        return null;
    +      }
           selectorObject = {
             type: "lasso",
    -        selector_state: {
    -          xs: data["lassoPoints"]["x"],
    -          ys: data["lassoPoints"]["y"],
    -        },
    +        selector_state: lassoData,
           };
         } else {
           selectorObject = null;

    It passes all xNrange, yNrange attributes which solves the issue described above.
    @camdecoster Please let me know if it is worth pursuing this idea then I can create a fork and PR.
    @camdecoster heres the corresponding PR #5710

  5. added a commit that references this issue on Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2considered for next cyclebugsomething brokensize: 1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions