Possible bug in Plotly (python) when dragging and dropping shapes in scatter plots

Hi there. I describe here a problem that I found not solution so far. Is this a bug?

Using dash 2.17.1, plotly 5.23.0, python 3.12.4, Windows 11.

Context is: A python app that uses dcc.Graph to create a chart with 4 lines. The x-axis contains datetime values and the y-axis contain floats, and the chart is updated in a dcc.Interval callback using Patch() objects.

The problem is:

  • I draw a shape (line, rect, freeform) on the chart
  • I select the shape
  • I start dragging the shape, move the shape around, and then drop the shape
  • The shape disappears from the chart. The javascript console presents 4 errors (reproduced at the end of this post)

It seems this bug occurs when the x-axis uses datetime. For example, when using int values, the bug does not happen. Here’s a sample program to reproduce the error.

from    datetime                import datetime, timedelta
from    dash                    import Dash, dcc, html
import  pandas                  as pd

import  plotly.express          as px

config = {
    'displayModeBar'          : True,
    'modeBarButtonsToAdd'     : ['drawline', 'eraseshape', 'drawrect',  "drawopenpath" ]
}

# ____________________________________
#
#   Sample dataframe with int index

def df_int ():
    index   = [x for x in range(10)]
    y       = [x for x in range(10)]

    return pd.DataFrame (y, index=index)

# ____________________________________
#
#   Sample dataframe with datetime index

def df_datetime():

    now     = datetime.now()
    index   = [now + timedelta(minutes=x) for x in range(10)]
    y       = [x for x in range(10)]

    return pd.DataFrame (y, index=index)

# ____________________________________

df   = df_datetime()
#df  = df_int() 	# uncomment to test with int index

fig = px.line (data_frame= df, x=df.index, y=df.columns[0])

fig.update_layout (uirevision=True)


app         = Dash (__name__)
app.layout  = html.Div ([

                dcc.Graph       (id= "test_chart", config= config, figure= fig),
                dcc.Interval    (id= "interval",   interval= 500)

            ])

# ____________________________________
#
@callback (
    Output ("test_chart",   "figure"),

    Input  ("interval",     "n_intervals"),
    State  ("test_chart",   "figure")

)
def update (n, fig):

    debug_shapes (fig)
    return create_patch()


# ____________________________________
#
def debug_shapes (fig):
    layout = fig['layout']

    if "shapes" not in layout:
        return

    shapes       = layout ['shapes']
    n_shapes     = len (shapes)
    range_shapes = range(n_shapes)

    print ("__________")
    print (f"num shapes: {n_shapes}")

    for i, x in zip (range_shapes, shapes):
        print (f"{i} => {x}")

    print ()

# ____________________________________
#
def create_patch ():
    patch = Patch ()

    # sample update to illustrate the case
    patch ["data"][0]['y'][-1] = 5

    # tried this below, the end result was the same
    #patch ["data"]["layout"]["uirevision"] = True	

    return patch

# ____________________________________
#
if __name__ == "__main__":
    app.run(debug=True)

Other things:

  • This problem also happens if there’s no updating to the figure (eg. no update loop for dcc.Interval). There’s another thing that happens in the chart, this one I don’t know if it’s also a bug, or an error in my code:
  1. select shape

  2. try dragging the shape. The shape returns to the original position (before the drag) and stays there, even if I continue to drag movement. When I release the mouse button (to drop the shape), the shape jumps from the original position to this position.

Tested this with int index on x-axis. With datetime x-axis, the shape disappears when releasing the mouse button

  • I wrote debug_shapes to check where the shape ended after it disappeared. The result is something like this (notice that x0 and y0 have None as value):

{'editable': True, 'visible': True, 'showlegend': False, 'legend': 'legend', 'legendgroup': '', 'legendgrouptitle': {'text': '', 'font': {'weight': 'normal', 'style': 'normal', 'variant': 'normal', 'lineposition': 'none', 'textcase': 'normal', 'shadow': 'none'}}, 'legendrank': 1000, 'label': {'text': '', 'texttemplate': ''}, 'xref': 'x', 'yref': 'y', 'layer': 'above', 'opacity': 1, 'line': {'color': '#444', 'width': 4, 'dash': 'solid'}, 'fillcolor': 'rgba(0, 0, 0, 0)', 'fillrule': 'evenodd', 'type': 'rect', 'x0': None, 'y0': 4.7168458781362, 'x1': None, 'y1': 1.017921146953405}

  • The disappearing shape bug does not happen with plotly 5.22.

However, I can’t use this version on my project because it contains another bug corrected in 5.23):

  • when I select the pan tool and drags the chart, the chart image gets blank (eg disappears) and appears again when I release the mouse (in line charts using webgl)
  • The javascript console log with the errors:
plotly.min.js:8 ERROR: unrecognized date NaN
a.error @ plotly.min.js:8
e.cleanDate @ plotly.min.js:8
indexOf.t.cleanPos @ plotly.min.js:8
W.coercePosition @ plotly.min.js:8
l @ plotly.min.js:8
t.exports @ plotly.min.js:8
t.exports @ plotly.min.js:8
e.applyContainerArrayChanges @ plotly.min.js:8
Z @ plotly.min.js:8
q @ plotly.min.js:8
(anonymous) @ plotly.min.js:8
e.call @ plotly.min.js:8
M @ plotly.min.js:8
H @ plotly.min.js:8
T @ plotly.min.js:8

plotly.min.js:8 ERROR: unrecognized date NaN
a.error @ plotly.min.js:8
e.cleanDate @ plotly.min.js:8
indexOf.t.cleanPos @ plotly.min.js:8
W.coercePosition @ plotly.min.js:8
l @ plotly.min.js:8
t.exports @ plotly.min.js:8
t.exports @ plotly.min.js:8
e.applyContainerArrayChanges @ plotly.min.js:8
Z @ plotly.min.js:8
q @ plotly.min.js:8
(anonymous) @ plotly.min.js:8
e.call @ plotly.min.js:8
M @ plotly.min.js:8
H @ plotly.min.js:8
T @ plotly.min.js:8

plotly.min.js:8 Error: <path> attribute d: Expected number, "MNaN,121HNaNV215H…".
(anonymous) @ plotly.min.js:8
(anonymous) @ plotly.min.js:8
vt @ plotly.min.js:8
J.each @ plotly.min.js:8
J.attr @ plotly.min.js:8
M @ plotly.min.js:8
w @ plotly.min.js:8
e.applyContainerArrayChanges @ plotly.min.js:8
Z @ plotly.min.js:8
q @ plotly.min.js:8
(anonymous) @ plotly.min.js:8
e.call @ plotly.min.js:8
M @ plotly.min.js:8
H @ plotly.min.js:8
T @ plotly.min.js:8

plotly.min.js:8 Uncaught Error: malformed path data
    at plotly.min.js:8:3295125
    at [Symbol.replace] (<anonymous>)
    at String.replace (<anonymous>)
    at t.exports (plotly.min.js:8:3294858)
    at e.readPaths (plotly.min.js:8:267384)
    at M (plotly.min.js:8:258195)
    at w (plotly.min.js:8:262981)
    at e.applyContainerArrayChanges (plotly.min.js:8:411588)
    at Z (plotly.min.js:8:426560)
    at q (plotly.min.js:8:421733)
(anonymous) @ plotly.min.js:8
t.exports @ plotly.min.js:8
e.readPaths @ plotly.min.js:8
M @ plotly.min.js:8
w @ plotly.min.js:8
e.applyContainerArrayChanges @ plotly.min.js:8
Z @ plotly.min.js:8
q @ plotly.min.js:8
(anonymous) @ plotly.min.js:8
e.call @ plotly.min.js:8
M @ plotly.min.js:8
H @ plotly.min.js:8
T @ plotly.min.js:8