Accessing Editable Annotations

[Disclaimer: I posted this question in Stack Overflow as well but figured I might get a better response on the forums]

I have a simple Dash application with a plot that updates when the ‘Update’ button is clicked. I’ve included an editable annotation on this graph. I want to be able to access whatever the user typed into the annotation so when the graph is updated, the annotation stays the same. I’m mostly wondering if there’s a way to access how they edited it.

I’ve been trying to save the current annotation into a storage component which is accessed when the graph is created. I tried making a ‘Save’ button that changes the storage data to the current annotation text My only guess is that when the annotation is edited, the new text isn’t stored in the same place that the default text was. That, or something is just going over my head, and I don’t realize it.

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, State, Output
import random

app = dash.Dash(__name__)
app.config['suppress_callback_exceptions'] = True

app.layout = html.Div(
    id = 'top_level',
    children=[
        html.Div(
            id='plot_div',
            children=[],
        ),

        html.Button(
            id = 'update',
            children="Update",
        ),

        dcc.Store(
            id='annotation_storage',
            data='Editable Annotation',
        )
    ]
)
@app.callback(
    Output('plot_div', 'children'),
    [Input('update', 'n_clicks')],
    [State('annotation_storage', 'data')]
)
def plot_update(clicked, annotation):
    if clicked:
        x = random.sample(range(1,101), 20)
        y = random.sample(range(1,101), 20)
        figure = {'data': [{'x': x, 'y': y, 
                            'type': 'scatter', 'mode': 'markers'}],
                  'layout': {
                      'xaxis': {
                          'fixedrange': True,
                          'zeroline': False,
                      },
                      'yaxis': {
                          'fixedrange': True,
                          'zeroline': False,
                      },
                      'annotations': [{
                            'x': -0.05,
                            'y': 0.5,
                            'showarrow':False,
                            'text':annotation,
                            'xref':'paper',
                            'yref':'paper',
                            }],
                        }
                  }
        return [dcc.Graph(
            id = 'plot_output',
            figure = figure,
            config = {
                'editable': True,
                'edits': {
                    'axisTitleText': False,
                    'titleText': False,
                },
                'displayModeBar': False,
            },
        ),
        html.Button(
            id = 'save_annotation',
            children='Save',
        ),
        ]

@app.callback(
    Output('annotation_storage', 'data'),
    [Input('save_annotation', 'n_clicks')],
    [State('plot_output', 'figure')]
)
def save_annotation(clicked, figure):
    if clicked:
        annotation = figure['layout']['annotations'][0]['text']
        return annotation
if __name__ == '__main__':
    app.run_server(debug=True, port=1000)

Currently the annotation is just reverting to the default text “Editable Annotation” when the graph is updated, even when the user edits it. I want the annotation to remain the same even when the graph is updated. This may not even be possible in Dash in its current state. Any insight would be greatly appreciated! Thanks!

I was wondering if you have found any solution to the same.

Yes someone at Stack Overflow (Shovalt) has found a solution. The ‘save_annotation’ method should look like the following.

@app.callback(
    Output('annotation_storage', 'data'),
    [Input('save_annotation', 'n_clicks')],
    [State('plot_output', 'relayoutData')]
)
def save_annotation(clicked, relayout_data):
    if clicked:
        annotation = relayout_data['annotations[0].text']
        return annotation

The figure I used before is itself not updated when the user interacts with the graph.