Hi community,
Recently I came across the editable property available in the config options of the figure object or dcc.Graph.
If you are not aware config can be passed to the fig.show() and dcc.Graph function if using Dash. It allows us to control a bunch of cool chart configuration options.
Editable graphs in Plotly
editable(boolean; optional): We can edit titles, move annotations, etc - sets all pieces ofeditsunless a separateeditsconfig item overrides individual parts.
As mentioned in the docs, this flag if set to True allows you to edit titles (both chart and axes), annotations text, legend items text, and move legend, color bar, and annotations in the graph.
Below is the complete list of possible edits:
annotationPositionannotationTailannotationTextaxisTitleTextcolorbarPositioncolorbarTitleTextlegendPositionlegendTextshapePositiontitleText
We can choose to only allow selected of the above properties to be editable by passing edits in combination with the editable flag to the config dictionary.
Example
import plotly.express as px
df = px.data.tips()
fig = px.histogram(
df, x="sex", y="total_bill", color="smoker", barmode="group"
)
fig.add_annotation(
x="Female", y=1000, text="Text annotation", showarrow=True, arrowhead=1
)
fig.show(config=dict(editable=True))

The user edits does not persist, which means the graph will reset on refresh.
Persisting user edits with Dash
In Dash we can store the modified figure by using dcc.Store to store the figure by listening to the relayoutData property of the graph via a callback.
By setting the storage_type = "session" of dcc.Store we can persist the data on page refresh.
Code:
from dash import Dash, html, dcc, callback, Input, State, Output
import plotly.express as px
import plotly.graph_objects as go
# function to create graph
def generate_graph():
df = px.data.tips()
fig = px.histogram(df, x="sex", y="total_bill", color="smoker", barmode="group")
fig.add_annotation(
x="Female", y=1000, text="Text annotation", showarrow=True, arrowhead=1
)
return fig
app = Dash(__name__)
app.layout = html.Div(
children=[
html.Br(),
dcc.Graph(
id="example-graph",
figure=generate_graph(),
config=dict(editable=True),
),
dcc.Store(id="fig-store", storage_type="session"),
]
)
# This callback renders the chart
@callback(
Output("example-graph", "figure"),
Input("fig-store", "data"),
)
def update(fig_data):
if fig_data:
fig = go.Figure(fig_data)
else:
fig = generate_graph()
return fig
# This callback stores the current state of the figure after each edits in dcc store
@callback(
Output("fig-store", "data"),
Input("example-graph", "relayoutData"),
State("example-graph", "figure"),
prevent_initial_call=True,
)
def update(relayoutData, fig):
return fig
if __name__ == "__main__":
app.run_server(debug=True)
