Draggable annotations are very much possible with Plotly. But you'll have to unleash Dash, and put your plotly graph object in a dcc.Graph component with the following settings for config
:
config={
'editable': True,
'edits': {
'annotationPosition': True
}
By "dynamic"
, my understanding is that you'd like it to be possible to insert as many annotations as you want. This is pretty straight forward using a dcc.Input
component and a html.Button
in a callback. The following app is produced by the complete code snippet below. If this is something you can use, I'd be happy to look more into the user friendliness with regards to how annotations are cleared. Right now, if the input field for annotations is empty, an initial figure fig1
is returned to the dcc.Graph
component without any annotations.
Dash app:

Complete code
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objs as go
import dash_bootstrap_components as dbc
from dash import Dash, html, dcc, Input, Output, State, dash_table, ctx
# some random data
np.random.seed(42)
y = np.random.normal(0, 10, 50)
x = np.arange(0, 50)
app = Dash(external_stylesheets=[dbc.themes.SLATE])
# initial figure for the dcc.Graph component
fig1 = px.scatter(x=x, y=y)
# app layout
app.layout = dbc.Container([dbc.Row([dbc.Col([html.H1("Dynamic and draggable annotations",
style={"text-align": "center"}, ),
dbc.Label(
'Annotation input field : '),
dcc.Input(
id='input_annotations', type='text', style={'width': '60%'}),
html.Button(id='btn_submit', type='submit', children='Add annotation')])]),
dbc.Row([dbc.Col([dcc.Graph(
id='graph1',
figure=fig1,
config={
'editable': True,
'edits': {
'shapePosition': True,
'annotationPosition': True
}
}
)])])])
# app interactivity
@app.callback(
Output(component_id='graph1', component_property='figure'),
Input(component_id='graph1', component_property='figure'),
State(component_id='input_annotations', component_property='value'),
Input('btn_submit', 'n_clicks')
)
def update_output_div(figure, annotations, n_clicks):
# Get existing figure in dcc.Graph with id='graph1'
# This lets you grap an existing figure with existing annotations
# and add more annotations to it
fig = go.Figure(figure)
# if the input field is empty,
# an initial figure is returned to id='graph1'
try:
print('trey')
if len(annotations) != 0:
fig.add_annotation(x=25, y=0,
text=annotations,
showarrow=True,
arrowcolor="black",
arrowsize=3,
arrowwidth=1,
arrowhead=1)
else:
return fig1
except:
return fig1
return fig
if __name__ == '__main__':
app.run_server(debug=True, port='8091')