Updating a graph with set_prop and Patch

Hi,

I am trying to update a Graph using Patch and set_prop. I can update a graph using

  • Patch and Output
  • Figure and Output
  • Figure and set_prop

but not using Patch and set_prop. Below is an example trying to extend an existing trace, but I am also interested in the general case (also updating layout, adding traces, …).

from dash import Dash, html, dcc, Input, Output, Patch, callback, set_props
import plotly.graph_objects as go

import datetime
import random

app = Dash()

figPatchOutput = go.Figure(data=go.Scatter(x=[], y=[]), layout={"title": "PatchOutput"})
figFigureOutput = go.Figure(
    data=go.Scatter(x=[], y=[]), layout={"title": "FigureOutput"}
)
figFigureSetProp = go.Figure(
    data=go.Scatter(x=[], y=[]), layout={"title": "FigureSetProp"}
)
figPatchSetProp = go.Figure(
    data=go.Scatter(x=[], y=[]), layout={"title": "PatchSetProp"}
)

app.layout = html.Div(
    [
        dcc.Graph(figure=figPatchOutput, id="append-patch-output"),
        dcc.Graph(figure=figFigureOutput, id="append-figure-output"),
        dcc.Graph(figure=figFigureSetProp, id="append-figure-setProp"),
        dcc.Graph(figure=figPatchSetProp, id="append-patch-setProp"),
        html.Button("Append", id="append-new-val"),
    ],
    style={
        "display": "grid",
        "gridTemplateColumns": "repeat(4, 1fr)",
        "height": "400px",
    },
)


@callback(
    Output("append-patch-output", "figure"),
    Output("append-figure-output", "figure"),
    Input("append-new-val", "n_clicks"),
)
def add_data_to_fig(n_clicks):
    currentTime = datetime.datetime.now()
    random_value = random.randrange(1, 30, 1)
    patchedOutput = Patch()
    patchedOutput["data"][0]["x"].append(currentTime)
    patchedOutput["data"][0]["y"].append(random_value)

    patchedSetProp = Patch()
    patchedSetProp["data"][0]["x"].append(currentTime)
    patchedSetProp["data"][0]["y"].append(random_value)

    figFigureOutput["data"][0]["x"] = [*figFigureOutput["data"][0]["x"], currentTime]
    figFigureOutput["data"][0]["y"] = [*figFigureOutput["data"][0]["y"], random_value]

    figFigureSetProp["data"][0]["x"] = [*figFigureSetProp["data"][0]["x"], currentTime]
    figFigureSetProp["data"][0]["y"] = [*figFigureSetProp["data"][0]["y"], random_value]

    set_props("append-figure-setProp", {"figure": figFigureSetProp})
    set_props("append-patch-setProp", {"figure": patchedSetProp})

    return patchedOutput, figFigureOutput


if __name__ == "__main__":
    app.run(debug=True)

The error I am getting in Plotly version 6.1.1, Dash version v3.1.0, Windows operating system is

Failed component prop type: Invalid component prop `figure` key `__dash_patch_update` supplied to Graph.
Bad object: {
  "__dash_patch_update": "__dash_patch_update",
  "operations": [
    {
      "operation": "Append",
      "location": [
        "data",
        0,
        "x"
      ],
      "params": {
        "value": "2025-08-06T07:17:26.594381"
      }
    },
    {
      "operation": "Append",
      "location": [
        "data",
        0,
        "y"
      ],
      "params": {
        "value": 6
      }
    }
  ]
}
Valid keys: [
  "data",
  "layout",
  "frames"
]

Why is figure not a valid property of a dcc.Graph?

Hey @Consto, welcome to the forums.

I think you are trying to mix two concepts which are not thought to use in conjunction.

I always understood the Patch() (which was released before set_prop) as an instruction you send to the frontend which alters properties directly in JS.

set_prop does pretty much the same, you are directly changing the props in the frontend.

I can’t think of a use case where you might use Patch and set_props together, maybe you can give me one?

Actually the message is telling you the key “`__dash_patch_update” is not valid. IMHO

Thanks for the quick response. I wasn’t aware that Patch and set_prop are this similar under the hood. To my understanding, I always need to provide the full value (in this case the figure object) to `set_prop`. I cannot directly modify parts of the JS, i.e. the following does not work.

set_props(
        "append-patch-setProp",
        {
            "data": [
                {
                    "type": "scatter",
                    "x": [1, 2, 3],
                    "y": [3, 1, 6],
                }
            ],
        },
    )

Back to my use case. I want to sent a partial update to a specific figure the user has previously selected. I acquire the selected figure from a dcc.Store. Something along the lines of

@app.callback(
    Input("acceptBtn", "n_clicks"),
    State("userStore", "data"),
    prevent_initial_call=True,
)
def acceptSelection(n, store):
    figId = store["selectedID"]
    figPatch = Patch()
    set_props(
        {"type": "myGraphs", "index": figId},
        {"figure": figPatch},
    )

In short, Patch()allows me, to not sent all the data if I just want to change a color, and set_propsallows me to target the correct graph. How would you solve this use case?

That’s my personal guess only. :slight_smile:

In this case I would use pattern matching callbacks.

I do have pattern matching callbacks for cases where the button is matched to the graph. In this case writing a MATCH callback is straight forward. My issue arises from only having one accept button for multiple graphs. In this case, there is nothing to match to, so one needs to use ALL. One then needs to

  • create a list of empty patches
  • iterate callback_context.outputs_list to find the position of the output with the relevant index
  • modify the patch at the correct position
  • return the list of patches

This is much more complex than calling set_props for the correct index. Am I overlooking a more straight forward solution?

Not sure how exactly you are doing this, but somehow the information of which image has been selected has to be stored in the dcc.Store(). I would have one dcc.Store() for each possible figure selection and trigger the callback using MATCH

Input({"type": "store", "index": MATCH}, "data")

How do you detect which graph has been changed?

Not sure, but you may just return no_update for the indices where nothing needs to be changed.

EDIT: Why don’t you use the graphs as trigger directly with pattern matching callbacks. What is the button for? Is the user able to select several graphs and trigger the update with the button?

Here is a small example showcasing the functionality with colored rectangles (not graphs). I also exchanged the dcc.Store by a simple dropdown. The user is able to add an arbitrary number of rectangles. Then they can select a rectangle to edit using the dropdown and click Make green. In the full version there are of course more buttons than just make green and the selecting of the graph is more streamlined compared to the dropdown.

from dash import (
    ALL,
    Dash,
    State,
    callback_context,
    html,
    dcc,
    Input,
    Output,
    Patch,
    callback,
)
import plotly.graph_objects as go

from dash.exceptions import PreventUpdate

app = Dash()


app.layout = html.Div(
    [
        html.Button("Add rectangle", id="btnAdd"),
        dcc.Dropdown([], id="dropDown"),
        html.Button("Make green", id="btnGreen"),
        html.Div(id="content"),
    ],
)


@callback(
    Output("content", "children"),
    Output("dropDown", "options"),
    Input("btnAdd", "n_clicks"),
    prevent_initial_call=True,
)
def addRect(n):
    if n is None:
        raise PreventUpdate
    content = Patch()
    content.append(
        html.Div(
            id={"type": "rect", "index": n},
            style={"width": "50px", "height": "50px", "background-color": "grey"},
        )
    )
    dropDown = Patch()
    dropDown.append(n)
    return content, dropDown


@callback(
    Output({"type": "rect", "index": ALL}, "style"),
    Input("btnGreen", "n_clicks"),
    State("dropDown", "value"),
    prevent_initial_call=True,
)
def makeGreen(n_clicks, selectedID):
    if selectedID is None:
        raise PreventUpdate
    patches = [Patch() for _ in callback_context.outputs_list]
    patches[selectedID - 1]["background-color"] = "green"
    return patches


if __name__ == "__main__":
    app.run(debug=True)

Why don’t you use the graphs as trigger directly with pattern matching callbacks. What is the button for? Is the user able to select several graphs and trigger the update with the button?

I want to have the buttons in one central place and not a settings menu for every graph.

I tested sending no_update instead of empty Patch() and it does also work. The issue I have is mainly having to iterate the outputs_list to match id and index. In the provided example this is not necessary as index = id -1, because there is only adding and no removing of rectangles.

I somewhat understand that, but I would do it this way. Maybe other users here have a direct answer to your initial question. I think I can´t help you any further unfortunately.