Allowing users to edit graph properties without code changes

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 of edits unless a separate edits config 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:

  • annotationPosition
  • annotationTail
  • annotationText
  • axisTitleText
  • colorbarPosition
  • colorbarTitleText
  • legendPosition
  • legendText
  • shapePosition
  • titleText

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))

ezgif.com-gif-maker

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)

dash-app

6 Likes

HI @atharvakatre, thanks for the tip and sample code! This might be helpful in the future

3 Likes

Very useful! Thank you for sharing!

2 Likes