Markers disappear when moved by callback

Hello, everyone. Thanks for the great tool you’ve all created!

I’m building a rough schedule calendar based on a dcc.Graph. Calendar items are represented by shapes, rectangles in this case. Each shape has a corresponding marker, that will eventually be in “text” mode and will display a description of the calendar item. However, when I drag or resize a shape, its marker disappears.

Is this a bug or am doing something wrong?

Thanks in advance.

My imports:

import plotly.graph_objects as go

#import dash
import jupyter_dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Output, Input

import numpy as np

import json

Create figure

fig = go.Figure()

xrange = [-0.5, 7.5]
yrange = [6,18]

xwidth = abs(xrange[0] - xrange[1])
yheight = abs(yrange[0] - yrange[1])

# Set axes properties
fig.update_xaxes(range=[xrange[0], xrange[1]], showgrid=False)
fig.update_yaxes(range=[yrange[1], yrange[0]])

fig.update_layout(
    #autosize=False,
    showlegend = False
)

# Add shapes
fig.add_shape(
        go.layout.Shape(
            name = "blueShape",
            type="rect",
            xref = "x",
            yref = "y",
            x0=0,
            y0=7,
            x1=1,
            y1=8 - 0.1,            
            line=dict(
                width=0,
            ),
            fillcolor="skyBlue",
            opacity = 0.5,
            layer = "below"
        ))

fig.add_shape(
        # filled Rectangle
        go.layout.Shape(
            name = "pinkShape",
            type="rect",
            x0=0,
            y0=8,
            x1=1,
            y1=9.5 - 0.1,
            line=dict(
                width=0,
            ),
            fillcolor="Pink",
            opacity = 0.5,
            layer = "below"
        ))

fig.add_shape(
        # filled Rectangle
        go.layout.Shape(
            name = "greenShape",
            type="rect",
            x0=4,
            y0=15,
            x1=5,
            y1=15.5 - 0.1,
            line=dict(
                width=0,
            ),
            fillcolor="green",
            opacity = 0.5,
            layer = "below"
        ))

fig.add_shape(
        # filled Rectangle
        go.layout.Shape(
            name = "yellowShape",
            type="rect",
            x0=2,
            y0=13,
            x1=3,
            y1=14 - 0.1,
            line=dict(
                width=0,
            ),
            fillcolor="yellow",
            opacity = 0.5,
            layer = "below"
        ))

for shape in fig.layout.shapes:
    
    fig.add_trace(
        go.Scatter(
            x = [float(shape["x0"] + shape["x1"]) / 2],
            y = [float(shape["y0"] + shape["y1"]) / 2],
            visible = True,
            text = shape["name"],
            mode = "markers"))

fig.update_shapes(dict(xref='x', yref='y'))
#fig.show(config={'editable':True})

Layout and app

#app = dash.Dash()
app = jupyter_dash.JupyterDash()

app.layout = html.Div([
    
    dcc.Graph(id = 'chart',
              figure = fig,
              #config = {'editable': True, "edits":{"annotationPosition": False}}),
              config = {'editable': True}),
    html.Pre(id = "feedback"),
    html.Div(id = "store",
            #style={'display': 'none'}
            )
])

Callbacks

#snap the shapes to appropriate position
#output to "store" div
    #!! there is a dcc.Store object
@app.callback(Output("store", "children"),
              [Input("chart", "relayoutData"),
              Input("chart", "figure")])
def figToStore(feedback_data, fig):      
    
    if feedback_data != {"autosize": True}:
        
        shape = list(feedback_data.keys())[0][7]

        new_x0 = int(float(feedback_data["shapes[" + shape + "].x0"]) + 0.5)
        new_x1 = int(float(feedback_data["shapes[" + shape + "].x1"]) + 0.5)
        new_y0 = round(float(feedback_data["shapes[" + shape + "].y0"]) * 4) / 4
        new_y1 = round(float(feedback_data["shapes[" + shape + "].y1"] + 0.1) * 4) / 4 - 0.1# -0.1 to help differintiate between a block below

        if new_x0 == new_x1:
            new_x1 = new_x0 + 1

        if new_y0 == new_y1:
            new_y1 = new_y0 + 0.24

        fig["layout"]["shapes"][int(shape)]["x0"] = new_x0
        fig["layout"]["shapes"][int(shape)]["x1"] = new_x1
        fig["layout"]["shapes"][int(shape)]["y0"] = new_y0
        fig["layout"]["shapes"][int(shape)]["y1"] = new_y1
        
        #snap shape's marker<<<---------------------------------------------------<<<
        fig["data"][int(shape)]["x"] = (new_x0 + new_x1) / 2
        fig["data"][int(shape)]["y"] = (new_y0 + new_y1) / 2
        fig["data"][int(shape)]["visible"] = "true"

    return json.dumps(fig)

#placeholder, Input must not equal Output
@app.callback(Output("chart", "figure"),
             [Input("store", "children")])
def figToChart(fig):
    return json.loads(fig)

@app.callback(Output("feedback", "children"),
             [Input("chart", "relayoutData")])
def updatePre(feedback_data):
    return json.dumps(feedback_data)

Run server

if __name__ == '__main__':
    app.run_server()