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)