Please help me with my extendData callback

I have a graph that I update in 2 manners :

  • When I press on the save button : the whole content is updated, I send a request back to the server, then the server asks the backend for data crunching and analysis, send back dictionaries to the front server, updates the content and does some other bunch of things (updating inner custom objects). In this callback, I update the “figure”. My figure is in two parts : a heavy scatter in the background and 6 very light line traces on top. I call a first file that loads my Scatter, this file calls a second file that loads the lines. I separated the lines in a second file for the next purpose. THIS WORKS FINE.
    @app.callback(
        Output("P_T", "figure"),
        Output("heatmap", "figure"),
        Output("ts","figure"),
        Input('test-params', 'n_clicks'),
        Input('save-params', 'n_clicks'),
        [State(parameter["input"], "value") for parameter in app.structure["FLAT"].values() if parameter["type"] != "time"])
    def update_graph(click_test,click_save, *parameters):
        return [
            scatter_power_temp(app.data["df"], *parameters), # this is the dcc.Graph I'm worried about
            heatmap(app.data["df"].temperature.values,app.data["df"].unexplained.values),
            plot_ts_full(app.data["df"]),
            ]

Initialization goes fine, my scatter shows, the default lines show (0 all over as intended). The following is the end of scatter_power_temp

    fig = go.Figure(data=data,layout=layout)

    traces=params_to_traces(*parameters)[0]

    for i in range(6):
        x=traces["x"][i]
        y=traces["y"][i]
        model_i=go.Scatter(x=x,y=y)
        fig.add_trace(model_i)

    return fig

and now the return of params_to_traces :

    return dict(x=x,y=y),[1,2,3,4,5,6],7

Again, this works fine :
image

As you can see in the first callback, there are a bunch of parameters that will be converted to the 6 lines from params_to_traces. I want to make a clientside callback that instantly updates my 6 lines when someone changes one parameter, by using extendData. So I switch from State to Input on those parameters, and from “figure” to “extendData” and only call my params_to_traces. Also, I keep my first callback for general and obvious updating purposes of a Save button. But before I go on include a js file, I’m trying with the classic callback in python language.

    @app.callback(
        Output("P_T", "extendData"),
        [Input(parameter["input"], "value") for parameter in app.structure["FLAT"].values() if parameter["type"] != "time"])
    def extend_traces(*parameters):
        traces=params_to_traces(*parameters)
        print(traces)
        return traces

The traces don’t show, instead I have an empty dcc.Graph. The print statement gives :

({'x': [[-10, 5, 10, 15, 20, 30, 40], [-10, 5, 10, 15, 20, 30, 40], [-10, 5, 10, 15, 20, 30, 40], [-10, 5, 10, 15, 20, 30, 40], [-10, 5, 10, 15, 20, 30, 40], [-10, 5, 10, 15, 20, 30, 40]], 'y': [[0.0, 0.0, 0.0, 0, 
0, 0, 0], [0.0, 0.0, 0.0, 0, 0, 0, 0], [0.0, 0.0, 0.0, 0, 0, 0, 0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]}, [1, 2, 3, 4, 5, 6], 7)

EDIT : I went on and wrote the javascript, the clientside_callback is the following :


    app.clientside_callback(
        """
        function params_to_traces( lo_slope, up_slope, T_pivot, P_pivot, partial_power, full_power, heating_power, sat_heat, set_heat, cooling_power, set_cool, sat_cool){

            const temp=[-10,40,T_pivot,sat_heat,set_heat,sat_cool,set_cool];
            temp.sort();
            const refrigeration_powers = new Array(temp.length);
            const partial_lighting = new Array(temp.length);
            const full_lighting = new Array(temp.length);
            const hvac_powers = new Array(temp.length);
            const x = new Array(6);
            const y = new Array(6);
            
            for (let i = 0; i < 6; i++) {
                x[i] = new Array(temp.length).fill(0);
                y[i] = new Array(temp.length).fill(0);
            }
            for (let j = 0; j < temp.length; j++) {
                refrigeration_powers[j] = P_pivot + (temp[j] - T_pivot) * lo_slope + Math.max(0.0, temp[j] - T_pivot) * up_slope;
                partial_lighting[j] = partial_power;
                full_lighting[j] = full_power;
                hvac_powers[j] = heating_power * Math.min(1.0, Math.max(0.0, (temp[j] - sat_heat) / (set_heat - sat_heat)))
                            + cooling_power * Math.min(1.0, Math.max(0.0, (temp[j] - sat_cool) / (set_cool - sat_cool)));

                for (let i = 0; i < 6; i++){
                    x[i][j] = temp[j];
                }
                y[0][j] = refrigeration_powers[j];
                y[1][j] = refrigeration_powers[j] + partial_lighting[j];
                y[2][j] = refrigeration_powers[j] + partial_lighting[j] + full_lighting[j];
                y[3][j] = refrigeration_powers[j] + hvac_powers[j];
                y[4][j] = refrigeration_powers[j] + partial_lighting[j] + hvac_powers[j];
                y[5][j] = refrigeration_powers[j] + partial_lighting[j] + full_lighting[j] + hvac_powers[j];
            }
            return [{x:x,y:y},[1,2,3,4,5,6],7]
        }

        """,
        Output(Power_vs_Temperature, "extendData"),
        [Input(parameter["input"], "value") for parameter in app.structure["FLAT"].values() if parameter["type"] != "time"],
    )

About the Input array, I put some of the dash components in a dict in the app, this way I can have access to them in any python file and that let’s me rearrange and split my code in files and folders in a manner similar to my dash app layout. I wish there was something like a callbacks.init_app(dash_app) possibility, without having to import third party libraries with 1 or 2 stars, but in the meantime I use this architecture, and it works.

Except for this extendData stuff.

Hi,

Your extendData looks as it should, but I am wondering if the callback is not getting triggered before the figure is generated by the first callback…
You can try to add a prevent_initial_update on the extend_traces callback to see if this fixes the problem (or perhaps check for an empty figure, by passing State("P_T", "figure").

Let me know if this helps! :slightly_smiling_face:

Hi,

Thanks for the reply.
Following your advice, I tried both prevent_initial_update and prevent_initial_call, but it still doesn’t work.
The two callbacks follow each other in the code :

the hide_client_side_ is just a trick to hide commented code on VSCode, it’s my js code. The dcc.Graph are declared above.

The thing is that I create the components and the callbacks inside a function that has app as a parameter. I create the components, I add the callbacks, then I load the components into the layout via the return. So technically, I create the callbacks before the components are loaded.

from dash import dcc,html

from .content_site import content_site
from .content_phase import content_phase
from .content_model import content_model

CONTENT_STYLE = {
    "margin-left": "18rem",
    "margin-right": "2rem",
    "padding": "2rem 1rem",
}

def content(app):
    app.content_site=content_site(app)
    app.content_phase=content_phase(app)
    app.content_model=content_model(app)
    return html.Div(
        id="content",
        children=[app.content_site,app.content_phase,app.content_model],
        className="col-9",
    )

But this works for the rest of the code. I believe that the components don’t even have to be loaded (for instance a heavy dcc.store, hidden in the server side), for the callbacks to trigger.

Are there good practices for declaring callbacks in a non linear file structure?

I got it to work.
Two things I learned doing this :

  • load the whole layout before adding callbacks, and I found this thread helpful.
  • for loops to add callbacks DO NOT WORK if the first attribute given to Input, Output or State is a component. You need the id string to be passed. Which is kind of easy, you just add .id to the component, but then I don’t know if you need to define the id when you instantiate the component, or if the automatic id generator of the constructor is enough. Even more so if you use clientside callbacks, make sure you enter the ID.