Fire a js function, when a graph is loaded (or changed)

I’m trying to run a function that does a simple task (like truncating the legends titles after 5 letters) after the graph is loaded or changed. So far I managed to do with a clientside_callback this by waiting 500ms before applying the truncation. In my real application however is very hard to predict how long to wait, because it depends on the user input. Therefore it would be better to wait until the div graph is fully loaded.

I tried to

  • set up a eventListener
  • work with plotly_afterplot
  • with window.onload()
  • or with a MutationObserver

but all three did not work. They all could not “wait long enough” it seemed. Either way, the graph div was still null or div id="graph" class="dash-graph dash-graph--pending", meaning loaded asynchronously and still pending.

Is there a way to achieve this? It would be preferable to have it done without the need of importing the entire d3 or plotly library.

This is my only working, but not very elegant or robust solution with clientside_callback and fixed waiting time:

from dash import Dash, html, dcc, callback, clientside_callback, ClientsideFunction
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc
import plotly.express as px


df = px.data.iris()

app = Dash(__name__)

app.layout = html.Div([
    dbc.Button('change', id='btn-change'),
    dcc.Graph(id='graph',
              style={'width': '100vw', 'height': '80vh'},
              figure=px.scatter(df,
                                x='sepal_width', y='sepal_length', color='species')),
])

clientside_callback(
    ClientsideFunction(
        namespace='clientside',
        function_name='trunc_legend_titles'
    ),
    Output('graph', 'id'),
    Input('graph', 'figure'),
)


@callback(
    Output('graph', 'figure'),
    Input('btn-change', 'n_clicks'),
)
def change_graph(n_clicks):
    return px.scatter(df, x='petal_width', y='petal_length', color='species')


if __name__ == '__main__':
    app.run_server(debug=True)

window.dash_clientside = Object.assign({}, window.dash_clientside, {
    clientside: {
        trunc_legend_titles: function(figure) {
            setTimeout(function() {
                var legendItems = document.querySelectorAll('.legendtext');
                legendItems.forEach(function(item) {
                    var name = item.textContent;
                    var truncatedName = name.length > 5 ? name.substring(0, 5) + '...' : name;
                    item.textContent = truncatedName;
                });
            }, 500); // Wait for the plot to be fully rendered
            return window.dash_clientside.no_update
        }
    }
});

Have you tried wrapping it in a dcc.Loading to see when it’s ready?

I tried to wrap dcc.Loading around the graph div:

...
dcc.Loading(id='loading',
        dcc.Graph(...

and let the clientside_callback fire with loading_state:

clientside_callback(
    ClientsideFunction(
        namespace='clientside',
        function_name='trunc_legend_titles'
    ),
    ...
    Input('loading', 'loading_state'),
)

but the loading_state is undefined when loading the app. Later the js-function is not called.

        ...
        trunc_legend_titles: function(loading_state) {
            console.log('loading_state', loading_state);
       ...

I suppose, you has something else in mind?

don’t know if this belongs in py or js forum

(Resize <g class legend> after truncating legend text)