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. ![]()
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)