Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] dcc.Graph inserts phantom rectangular shape on callback update seemingly randomly #2741

Closed
matt-sd-watson opened this issue Feb 1, 2024 · 5 comments
Labels
bug something broken sev-4 cosmetic

Comments

@matt-sd-watson
Copy link

matt-sd-watson commented Feb 1, 2024

Describe your context
Please provide us your environment, so we can easily reproduce the issue.

  • replace the result of pip list | grep dash below
dash                          2.10.2
dash-ag-grid                  2.3.0
dash-auth                     2.0.0
dash-bootstrap-components     1.4.1
dash-canvas                   0.1.0
dash-core-components          2.0.0
dash-daq                      0.5.0
dash-draggable                0.1.2
dash-extensions               1.0.1
dash-google-auth              0.1.2
dash-html-components          2.0.0
dash-mantine-components       0.12.1
dash-table                    5.0.0
dash-testing-stub             0.0.2
dash-uploader                 0.7.0a1
jupyter-dash                  0.4.2
  • if frontend related, tell us your Browser, Version and OS

    • OS: [e.g. iOS] Ubuntu 22.04
    • Browser [e.g. chrome, safari] Chrome
    • Version [e.g. 22] Version 121.0.6167.85 (Official Build) (64-bit)

Describe the bug

I have a dcc.Graph output component that renders the callback output from px.imshow. When I interact with the component, such as adding/remocing an existing shape in the plot through a callback, a random "phantom" rectangle may appear:

9fe92f65bb154c84f214f67b4eedaa523bbe0d8c

The pattern of its appearance is difficult to establish, as it is not always deterministic. Often, it appears after I add or remove a different shape for the graph.

Expected behavior

Here is how the plot is expected to appear:

a21cafe72042a839082c7f7b24c29e11c53d2a17

Screenshots

Here is an additional example. When I have the graph and have already drawn the white rectangle in the top right, removing a line shape on a different part of the graph in a callback produces this:

Screenshot from 2024-02-01 10-49-11

When I view the shapes in the layout of the graph at this moment through a callback, only my original white rectangular shape is contained:

print(graph['layout'['shapes']
[{'editable': True, 'fillcolor': 'rgba(0, 0, 0, 0)', 'fillrule': 'evenodd', 'label': {'text': '', 'texttemplate': ''}, 'layer': 'above', 'line': {'color': 'white', 'dash': 'solid', 'width': 4}, 'opacity': 1, 'type': 'rect', 'x0': 302.01024590163945, 'x1': 462.6659836065575, 'xref': 'x', 'y0': 85.29234972677595, 'y1': 177.09562841530052, 'yref': 'y'}]

So it appears that the component is randomly adding a rectangle that doesn't show up in the canvas layout, making it impossible for me to filter or diagnose how to remove it consistently. Sometimes the rectangle will disappear if I redraw a new shape or update the underlying image, but sometimes it won't.

Generally, it is very hard to make a MRE for this problem given how random and unpredictable it is.

@alexcjohnson
Copy link
Collaborator

Bizarre. The fact that this shape is covering the middle half of the plot in each direction implies that it has no x or y coordinates, so it gets 1/4->3/4 as defaults. But the blue color isn't a default unless it's in a template somewhere...

Perhaps the next step debugging is to open the JS console when you see this, and type:

gd = document.querySelector('.js-plotly-plot'); // assuming this is the only or at least first plot on the page
console.log(JSON.stringify(gd.layout.shapes, null, 2));
console.log(JSON.stringify(gd._fullLayout.shapes, null, 2));

@matt-sd-watson
Copy link
Author

Here is the output for this graph state:

Screenshot from 2024-02-02 13-20-18

Console output of console.log(JSON.stringify(gd.layout.shapes, null, 2));:

[
  {
    "editable": true,
    "fillcolor": "rgba(0, 0, 0, 0)",
    "fillrule": "evenodd",
    "label": {
      "text": "",
      "texttemplate": ""
    },
    "layer": "above",
    "line": {
      "color": "white",
      "dash": "solid",
      "width": 4
    },
    "opacity": 1,
    "path": "M304.5304878048781,172.82317073170734L263.3719512195122,179.22560975609758L235.01829268292684,189.28658536585368L216.72560975609758,205.75000000000003L205.75000000000003,231.359756097561L203.9207317073171,255.14024390243904L211.23780487804882,271.6036585365854L232.27439024390247,286.2378048780488L256.0548780487805,292.640243902439L291.7256097560976,290.8109756097561L320.9939024390244,282.5792682926829L331.0548780487805,274.3475609756098Z",
    "type": "path",
    "xref": "x",
    "yref": "y"
  },
  {
    "editable": true,
    "fillcolor": "rgba(0, 0, 0, 0)",
    "fillrule": "evenodd",
    "label": {
      "text": "",
      "texttemplate": ""
    },
    "layer": "above",
    "line": {
      "color": "white",
      "dash": "solid",
      "width": 4
    },
    "opacity": 1,
    "path": "M455.4451219512195,153.6158536585366L420.6890243902439,163.6768292682927L398.7378048780488,174.65243902439028L385.0182926829268,191.11585365853662L382.2743902439025,204.83536585365857L389.5914634146342,221.2987804878049L416.1158536585366,244.16463414634146L439.8963414634147,256.969512195122L467.3353658536586,259.7134146341463L489.28658536585374,254.22560975609755L499.34756097560984,248.7378048780488Z",
    "type": "path",
    "xref": "x",
    "yref": "y"
  },
  {
    "editable": true,
    "fillcolor": "rgba(0, 0, 0, 0)",
    "fillrule": "evenodd",
    "label": {
      "text": "",
      "texttemplate": ""
    },
    "layer": "above",
    "line": {
      "color": "white",
      "dash": "solid",
      "width": 4
    },
    "opacity": 1,
    "path": "M388.5785530821918,344.70547945205476L417.345676369863,340.5958904109589L455.35937499999994,344.70547945205476L468.71553938356163,352.92465753424653L477.9621147260274,371.417808219178L472.8251284246575,389.9109589041096L455.35937499999994,410.458904109589L426.59225171232873,422.7876712328767L406.0443065068493,425.8698630136986L379.33197773972597,424.8424657534246L376.24978595890406,422.7876712328767Z",
    "type": "path",
    "xref": "x",
    "yref": "y"
  },
  {
    "editable": true,
    "label": {
      "text": "",
      "texttemplate": ""
    },
    "layer": "above",
    "opacity": 1,
    "line": {
      "dash": "solid"
    },
    "fillcolor": "rgba(0,0,0,0)",
    "fillrule": "evenodd"
  }
]

Console output of console.log(JSON.stringify(gd._fullLayout.shapes, null, 2));

[
  {
    "_input": {
      "editable": true,
      "fillcolor": "rgba(0, 0, 0, 0)",
      "fillrule": "evenodd",
      "label": {
        "text": "",
        "texttemplate": ""
      },
      "layer": "above",
      "line": {
        "color": "white",
        "dash": "solid",
        "width": 4
      },
      "opacity": 1,
      "path": "M304.5304878048781,172.82317073170734L263.3719512195122,179.22560975609758L235.01829268292684,189.28658536585368L216.72560975609758,205.75000000000003L205.75000000000003,231.359756097561L203.9207317073171,255.14024390243904L211.23780487804882,271.6036585365854L232.27439024390247,286.2378048780488L256.0548780487805,292.640243902439L291.7256097560976,290.8109756097561L320.9939024390244,282.5792682926829L331.0548780487805,274.3475609756098Z",
      "type": "path",
      "xref": "x",
      "yref": "y"
    },
    "_template": {
      "line": {
        "color": "#2a3f5f"
      }
    },
    "_index": 0,
    "visible": true,
    "path": "M304.5304878048781,172.82317073170734L263.3719512195122,179.22560975609758L235.01829268292684,189.28658536585368L216.72560975609758,205.75000000000003L205.75000000000003,231.359756097561L203.9207317073171,255.14024390243904L211.23780487804882,271.6036585365854L232.27439024390247,286.2378048780488L256.0548780487805,292.640243902439L291.7256097560976,290.8109756097561L320.9939024390244,282.5792682926829L331.0548780487805,274.3475609756098Z",
    "type": "path",
    "editable": true,
    "layer": "above",
    "opacity": 1,
    "fillcolor": "rgba(0, 0, 0, 0)",
    "fillrule": "evenodd",
    "line": {
      "width": 4,
      "color": "white",
      "dash": "solid"
    },
    "xsizemode": "scaled",
    "ysizemode": "scaled",
    "xref": "x",
    "yref": "y",
    "label": {
      "text": ""
    },
    "_extremes": {
      "x": {
        "min": [
          {
            "val": 203.9207317073171,
            "pad": 2,
            "extrapad": false
          }
        ],
        "max": [
          {
            "val": 331.0548780487805,
            "pad": 2,
            "extrapad": false
          }
        ],
        "opts": {
          "ppad": 2
        }
      },
      "y": {
        "min": [
          {
            "val": 172.82317073170734,
            "pad": 2,
            "extrapad": false
          }
        ],
        "max": [
          {
            "val": 292.640243902439,
            "pad": 2,
            "extrapad": false
          }
        ],
        "opts": {
          "ppad": 2
        }
      }
    }
  },
  {
    "_input": {
      "editable": true,
      "fillcolor": "rgba(0, 0, 0, 0)",
      "fillrule": "evenodd",
      "label": {
        "text": "",
        "texttemplate": ""
      },
      "layer": "above",
      "line": {
        "color": "white",
        "dash": "solid",
        "width": 4
      },
      "opacity": 1,
      "path": "M455.4451219512195,153.6158536585366L420.6890243902439,163.6768292682927L398.7378048780488,174.65243902439028L385.0182926829268,191.11585365853662L382.2743902439025,204.83536585365857L389.5914634146342,221.2987804878049L416.1158536585366,244.16463414634146L439.8963414634147,256.969512195122L467.3353658536586,259.7134146341463L489.28658536585374,254.22560975609755L499.34756097560984,248.7378048780488Z",
      "type": "path",
      "xref": "x",
      "yref": "y"
    },
    "_template": {
      "line": {
        "color": "#2a3f5f"
      }
    },
    "_index": 1,
    "visible": true,
    "path": "M455.4451219512195,153.6158536585366L420.6890243902439,163.6768292682927L398.7378048780488,174.65243902439028L385.0182926829268,191.11585365853662L382.2743902439025,204.83536585365857L389.5914634146342,221.2987804878049L416.1158536585366,244.16463414634146L439.8963414634147,256.969512195122L467.3353658536586,259.7134146341463L489.28658536585374,254.22560975609755L499.34756097560984,248.7378048780488Z",
    "type": "path",
    "editable": true,
    "layer": "above",
    "opacity": 1,
    "fillcolor": "rgba(0, 0, 0, 0)",
    "fillrule": "evenodd",
    "line": {
      "width": 4,
      "color": "white",
      "dash": "solid"
    },
    "xsizemode": "scaled",
    "ysizemode": "scaled",
    "xref": "x",
    "yref": "y",
    "label": {
      "text": ""
    },
    "_extremes": {
      "x": {
        "min": [
          {
            "val": 382.2743902439025,
            "pad": 2,
            "extrapad": false
          }
        ],
        "max": [
          {
            "val": 499.34756097560984,
            "pad": 2,
            "extrapad": false
          }
        ],
        "opts": {
          "ppad": 2
        }
      },
      "y": {
        "min": [
          {
            "val": 153.6158536585366,
            "pad": 2,
            "extrapad": false
          }
        ],
        "max": [
          {
            "val": 259.7134146341463,
            "pad": 2,
            "extrapad": false
          }
        ],
        "opts": {
          "ppad": 2
        }
      }
    }
  },
  {
    "_input": {
      "editable": true,
      "fillcolor": "rgba(0, 0, 0, 0)",
      "fillrule": "evenodd",
      "label": {
        "text": "",
        "texttemplate": ""
      },
      "layer": "above",
      "line": {
        "color": "white",
        "dash": "solid",
        "width": 4
      },
      "opacity": 1,
      "path": "M388.5785530821918,344.70547945205476L417.345676369863,340.5958904109589L455.35937499999994,344.70547945205476L468.71553938356163,352.92465753424653L477.9621147260274,371.417808219178L472.8251284246575,389.9109589041096L455.35937499999994,410.458904109589L426.59225171232873,422.7876712328767L406.0443065068493,425.8698630136986L379.33197773972597,424.8424657534246L376.24978595890406,422.7876712328767Z",
      "type": "path",
      "xref": "x",
      "yref": "y"
    },
    "_template": {
      "line": {
        "color": "#2a3f5f"
      }
    },
    "_index": 2,
    "visible": true,
    "path": "M388.5785530821918,344.70547945205476L417.345676369863,340.5958904109589L455.35937499999994,344.70547945205476L468.71553938356163,352.92465753424653L477.9621147260274,371.417808219178L472.8251284246575,389.9109589041096L455.35937499999994,410.458904109589L426.59225171232873,422.7876712328767L406.0443065068493,425.8698630136986L379.33197773972597,424.8424657534246L376.24978595890406,422.7876712328767Z",
    "type": "path",
    "editable": true,
    "layer": "above",
    "opacity": 1,
    "fillcolor": "rgba(0, 0, 0, 0)",
    "fillrule": "evenodd",
    "line": {
      "width": 4,
      "color": "white",
      "dash": "solid"
    },
    "xsizemode": "scaled",
    "ysizemode": "scaled",
    "xref": "x",
    "yref": "y",
    "label": {
      "text": ""
    },
    "_extremes": {
      "x": {
        "min": [
          {
            "val": 376.24978595890406,
            "pad": 2,
            "extrapad": false
          }
        ],
        "max": [
          {
            "val": 477.9621147260274,
            "pad": 2,
            "extrapad": false
          }
        ],
        "opts": {
          "ppad": 2
        }
      },
      "y": {
        "min": [
          {
            "val": 340.5958904109589,
            "pad": 2,
            "extrapad": false
          }
        ],
        "max": [
          {
            "val": 425.8698630136986,
            "pad": 2,
            "extrapad": false
          }
        ],
        "opts": {
          "ppad": 2
        }
      }
    }
  },
  {
    "_input": {
      "editable": true,
      "label": {
        "text": "",
        "texttemplate": ""
      },
      "layer": "above",
      "opacity": 1,
      "line": {
        "dash": "solid"
      },
      "fillcolor": "rgba(0,0,0,0)",
      "fillrule": "evenodd"
    },
    "_template": {
      "line": {
        "color": "#2a3f5f"
      }
    },
    "_index": 3,
    "visible": true,
    "type": "rect",
    "editable": true,
    "layer": "above",
    "opacity": 1,
    "fillcolor": "rgba(0,0,0,0)",
    "fillrule": "evenodd",
    "line": {
      "width": 2,
      "color": "#2a3f5f",
      "dash": "solid"
    },
    "xsizemode": "scaled",
    "ysizemode": "scaled",
    "xref": "x",
    "x0": 149.5,
    "x1": 449.5,
    "yref": "y",
    "y0": 449.5,
    "y1": 149.5,
    "label": {
      "texttemplate": "",
      "text": ""
    },
    "_extremes": {
      "x": {
        "min": [
          {
            "val": 149.5,
            "pad": 1,
            "extrapad": false
          }
        ],
        "max": [
          {
            "val": 449.5,
            "pad": 1,
            "extrapad": false
          }
        ],
        "opts": {
          "ppad": 1
        }
      },
      "y": {
        "min": [
          {
            "val": 149.5,
            "pad": 1,
            "extrapad": false
          }
        ],
        "max": [
          {
            "val": 449.5,
            "pad": 1,
            "extrapad": false
          }
        ],
        "opts": {
          "ppad": 1
        }
      }
    }
  }
]

It looks like the phantom shape could be represented by:

{'editable': True, 'label': {'text': '', 'texttemplate': ''}, 'layer': 'above', 'opacity': 1, 'line': {'dash': 'solid'}, 'fillcolor': 'rgba(0,0,0,0)', 'fillrule': 'evenodd'}

However, in a callback, I cannot see this shape, and filtering for correct shapes (such as those with defined types) does not fix the issue:

# invoked in callback before re-rendering: does not solve problem
canvas['layout']['shapes'] = [shape for shape in canvas['layout']['shapes'] if 'type' in shape]

@matt-sd-watson
Copy link
Author

Pinging to see if there are any additional debugging tips for this. It continues to happen sporadically on shape updates to the canvas.

@matt-sd-watson
Copy link
Author

This is the change in elements in fig['layout']['shapes'] after clearing, and re-parsing when the phantom shape appears:

# after clearing, right before the graph gets passed back to the client and the shape appears
[]

After the phantom shape appears, re-parsing the dictionary above:

[{'label': {'text': '', 'texttemplate': ''}}]

@matt-sd-watson
Copy link
Author

I have found a possible solution to the problem, which is to modify the truthy value for uirevision in the layout if it has already been set:

cur_canvas['layout']['uirevision'] = True if cur_canvas['layout']['uirevision'] not in [True] else "clear_shapes"

In subsequent updates to the canvas, the uirevision parameter is tracked if it is truthy, or set to True if it doesn't exist (i.e. fresh canvas). Using this, the phantom shape no longer appears when shapes are deleted and the ui status such as zoom is maintained on canvas edits.

@Coding-with-Adam Coding-with-Adam added bug something broken sev-4 cosmetic labels Apr 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something broken sev-4 cosmetic
Projects
None yet
Development

No branches or pull requests

3 participants