Creating shapes (annotations) with Patch() method, how to do if no shapes exist in initial fig?

Hello @AIMPED,

Check this out, with storing the last clickData you can determine whether or not it is the first shape added. This should keep the use of Patch() working well. :slight_smile:

import json
import random
import dash
from dash import html, dcc, Input, Output, Patch, State
import plotly.graph_objs as go
import numpy as np


def create_shape(x, y, size=2, color='rgba(39,43,48,255)'):
    """
    function creates a shape for a dcc.Graph object

    Parameters:
        x: x coordinate of center point for the shape
        y: y coordinate of center point for the shape
        size: size of annotation (diameter)
        color: (rgba / rgb / hex) string or any other color string recognized by plotly

    Returns:
        a list containing a dictionary, keys corresponding to dcc.Graph layout update
    """
    shape = [
        {
            'editable': True,
            'xref': 'x',
            'yref': 'y',
            'layer': 'above',
            'opacity': 1,
            'line': {
                'color': color,
                'width': 1,
                'dash': 'solid'
            },
            'fillcolor': color,
            'fillrule': 'evenodd',
            'type': 'circle',
            'x0': x - size / 2,
            'y0': y - size / 2,
            'x1': x + size / 2,
            'y1': y + size / 2
        }
    ]
    return shape


def extract_click_coordinates(click_data: dict) -> tuple[int, int]:
    """
    function extracts information from a dictionary

    Parameters:
        click_data: dictionary which is return by the plotly-dash click event on a dcc.Graph

    Returns:
        x, y: coordinates of the click event
    """
    points = click_data.get('points')[0]
    x = points.get('x')
    y = points.get('y')

    return x, y


app = dash.Dash(
    __name__,
)

fig = go.Figure(
    data=go.Heatmap(
        z=np.random.randint(0, 255, size=(30, 30))
    ),
    layout={
        'width': 900,
        'height': 900,
        # 'shapes': create_shape(10, 10, size=2, color='black'),
        # 'shapes': []
    }
)

app.layout = html.Div(
    [
        dcc.Graph(
            id='graph',
            figure=fig
        ),
        html.Pre(
            id='out',
            children=json.dumps(
                fig.to_dict().get('layout').get('shapes', 'empty'),
                indent=2
            )
        ),
        dcc.Store(id='oldClickData')
    ],
)


@app.callback(
    Output('graph', 'figure'),
    Output('out', 'children'),
    Output('oldClickData', 'data'),
    Input('graph', 'clickData'),
    State('oldClickData', 'data'),
    prevent_initial_call=True
)
def show_fig(clickData, oldClickData):
    x, y = extract_click_coordinates(clickData)

    patched = Patch()
    if oldClickData:
        patched['layout']['shapes'].extend(create_shape(x, y, size=2, color='white'))
    else:
        patched['layout']['shapes'] = create_shape(x, y, size=2, color='white')
    return patched, f'just checking... {random.random()}', clickData


if __name__ == '__main__':
    app.run(debug=True)
3 Likes