The `layout.shapes` property does not update after drawing shapes with the mouse

I would like to be able to retrieve the size (height, width) of a rectangle (or other shapes) I have drawn onto a figure with the drawrect modebar tool.

My example is the following

import plotly.graph_objects as go
import numpy as np

N = 100
M = 50

xx = np.arange(float(N))
yy = np.arange(float(M))
x, y = np.meshgrid(xx, yy)
z = np.random.random([M, N])

h = go.Heatmap(x=xx, y=yy, z=z)
fig = go.FigureWidget(data=h)

fig.update_layout(
    autosize=False,
    width=700, height=500)

fig.update_layout(modebar_remove="editinchartstudio")
fig.update_layout(modebar_add=['drawline',
                                        'drawopenpath',
                                        'drawclosedpath',
                                        'drawcircle',
                                        'drawrect',
                                        'eraseshape'
                                       ])

fig

After that, I draw a rectangle and a circle with the tools to obtain
Screenshot at 2022-03-08 14-50-30

If I print the fig.layout, I can see in the string repr of the layout that it has two shapes, a rectangle and a circle

fig.layout

Layout({
    'autosize': False,
    'dragmode': 'drawcircle',
    'height': 500,
    'modebar': {'add': [drawline, drawopenpath, drawclosedpath, drawcircle,
                        drawrect, eraseshape],
                'remove': 'editinchartstudio'},
    'shapes': [{'editable': True,
                'fillcolor': 'rgba(0,0,0,0)',
                'fillrule': 'evenodd',
                'layer': 'above',
                'line': {'color': '#444', 'dash': 'solid', 'width': 4},
                'opacity': 1,
                'type': 'rect',
                'x0': 14.216981132075471,
                'x1': 45.160377358490564,
                'xref': 'x',
                'y0': 39.8125,
                'y1': 24.8125,
                'yref': 'y'},
               {'editable': True,
                'fillcolor': 'rgba(0,0,0,0)',
                'fillrule': 'evenodd',
                'layer': 'above',
                'line': {'color': '#444', 'dash': 'solid', 'width': 4},
                'opacity': 1,
                'type': 'circle',
                'x0': 83.81740584790728,
                'x1': 58.20146207662103,
                'xref': 'x',
                'y0': 16.609769580772138,
                'y1': 32.07773041922786,
                'yref': 'y'}],
    'template': '...',
    'width': 700
})

However, I would like to access their properties, but the tuple fig.layout.shapes is just empty.
Maybe there is something that doesn’t get synchronized between the python and the javascript?
Thanks for any help!

Did you find a way to deal with this? I’m running into the same problem

Hi @ianfox1992 , I spoke with one of the plotly developers at EuroScipy2022 this summer, and she told me that this was not currently possible in Python, only in Javascript can you get the properties of the shapes you draw. To me, it unfortunately makes them rather useless.

HI @nvaytet , @ianfox1992, this is indeed unfortunate.

If you are open to using Dash, you could extract the shape info. Maybe it’s a bit overkill for your application though :grimacing:

import dash
from dash import Input, Output, State, dcc, html
import plotly.graph_objects as go
import json


app = dash.Dash(__name__)
app.layout = html.Div([
    dcc.Graph(
        id='graph',
        figure=go.Figure(
            data=go.Scatter(x=[1, 2], y=[1, 2]),
            layout=dict(
                modebar_add=[
                    'drawline',
                    'drawopenpath',
                    'drawclosedpath',
                    'drawcircle',
                    'drawrect',
                    'eraseshape'
                ]
            )
        )
    ),
    html.Div(id='dump'),
    html.Button('Show shape info', id='btn')
])


@app.callback(
    Output('dump', 'children'),
    Input('btn', 'n_clicks'),
    State('graph', 'figure'),
    prevent_initial_call=True
)
def return_shape_info(_, figure):
    shapes = figure.get('layout', {}).get('shapes', {})
    return json.dumps(shapes, indent=2)


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

Yeah, in my case dash wasn’t an option (I was building something that had to integrate with a company-specific jupyter-like environment that wouldn’t support it). In general I think there’s something going wrong in the connection between python plotly and the javascript around shapes. I found that when manipulating a FigureWidget the shapes would often ‘lose state’. My solution was to just recreate shape functionality with traces, but the need to deal with resizing axes was a pain.

1 Like