Convert Callback to Clientside Callback

I want to convert my dash app callbacks into client-side callbacks so that my app goes faster.

I have already read the Dash Clientside callback documentation.

My problem is that I have huge callbacks and I don’t have knowledge about JavaScript , so at the moment it’s so difficult for me to apply the conversion.

I was wondering if someone could convert one of my app callbacks and then I will try to convert the rest taking that one as reference:

@app.callback(
    dash.dependencies.Output('figures_container', 'children'),
    [dash.dependencies.Input('version1_dropdown', 'value'),
     dash.dependencies.Input('version2_dropdown', 'value'),
     dash.dependencies.Input('lang_dropdown', 'value')])
     def version_figures(value1, value2, lang_value):
        if(lang_value is None or value1 is None or value2 is None):
            return dash.no_update
        else:
            versions_stats = R.versions_stats
            if lang_value == 'Spanish':
                if value1 == 'Oct 1st 2016':
                    entities_version1 = versions_stats[0]
                    df1 = R.instance_types_es_2016_10_01
                    
                if value1 == 'Oct 1st 2020':
                    entities_version1 = versions_stats[18]
                    df1 = R.instance_types_es_2020_10_01
                    
                if value1 == 'May 1st 2021':
                    entities_version1 = versions_stats[36]
                    df1 = R.instance_types_es_2021_05_01
                    
                if value1 == 'June 1st 2021':
                    entities_version1 = versions_stats[54]
                    df1 = R.instance_types_es_2021_06_01
                    
                if value2 == 'Oct 1st 2016':
                    entities_version2 = versions_stats[0]
                    df2 = R.instance_types_es_2016_10_01
                    
                if value2 == 'Oct 1st 2020':
                    entities_version2 = versions_stats[18]
                    df2 = R.instance_types_es_2020_10_01
                    
                if value2 == 'May 1st 2021':
                    entities_version2 = versions_stats[36]
                    df2 = R.instance_types_es_2021_05_01
                    
                if value2 == 'June 1st 2021':
                    entities_version2 = versions_stats[54]
                    df2 = R.instance_types_es_2021_06_01
            
            if lang_value == 'English':
                if value1 == 'Oct 1st 2016':
                    entities_version1 = versions_stats[72]
                    df1 = R.instance_types_en_2016_10_01
                if value1 == 'Oct 1st 2020':
                    entities_version1 = versions_stats[90]
                    df1 = R.instance_types_en_2020_10_01
                    
                if value1 == 'May 1st 2021':
                    entities_version1 = versions_stats[108]
                    df1 = R.instance_types_en_2021_05_01
                    
                if value1 == 'June 1st 2021':
                    entities_version1 = versions_stats[126]
                    df1 = R.instance_types_en_2021_06_01
                    
                if value2 == 'Oct 1st 2016':
                    entities_version2 = versions_stats[72]
                    df2 = R.instance_types_en_2016_10_01
                    
                if value2 == 'Oct 1st 2020':
                    entities_version2 = versions_stats[90]
                    df2 = R.instance_types_en_2020_10_01
                    
                if value2 == 'May 1st 2021':
                    entities_version2 = versions_stats[108]
                    df2 = R.instance_types_en_2021_05_01
                    
                if value2 == 'June 1st 2021':
                    entities_version2 = versions_stats[126]
                    df2 = R.instance_types_en_2021_06_01
            
            bar_figure = F.get_version_bar_figure([value1, value2], [entities_version1, entities_version2])
            pie_figure = F.get_version_pie_figure([value1, value2], [entities_version1, entities_version2])
            bar_graph = dcc.Graph(id='versions_bar', figure=bar_figure, style={'height':'26.041666666666668vw', 'width':'45.572916666666664vw', 'display': 'inline-block'})
            pie_graph = dcc.Graph(id='versions_pie', figure=pie_figure, style={'height':'19.53125vw', 'width':'45.572916666666664vw', 'display': 'inline-block'})
            title = html.H3(value1 + " VS " + value2)
            type_title = html.H3("DBpedia types comparison")
            types_container = html.Div([
                dcc.Graph(id='ontology_version', figure=F.ontology_figure, style={'height':'26.041666666666668vw', 'width':'39.0625vw', 'display': 'inline-block'}),
                dcc.Graph(id='instance_types_version', figure=F.get_versions_instance_types_figure([value1, value2], df1, df2), 
                                     style={'height':'26.041666666666668vw', 'width':'45.572916666666664vw', 'display': 'inline-block'})
                ]
                )
            return title, html.Br(), bar_graph, pie_graph, html.Hr(), type_title, types_container

As you can see, this callback takes items ( dataframes , lists , figures …) from other local modules ( R , F …). I don’t know if it is possible to convert callbacks in this context.

Hope you can help me. Thanks in advance.

Hola @krakken

No estoy seguro, pero quizas lo que puede servirte es usar server side callback, que ejecuta el programa del lado del servidor mejorando el tiempo de respuesta ya que comparte la informacion sin enviar informacion desde o hacia el cliente y es facil de implementar porque solo hay que cambiar la forma en que es llamado el decorador de inputs y outputs, aca hay un link con interesante discusion sobre sus ventajas, espero te sirva:

1 Like

Hola @Eduardo

Muchas gracias por la respuesta.
Parece que han actualizado la sintaxis, lo he probado pero no me funciona:

No sé si serías capaz de mandarme un ejemplo comparativo de un callback “normal” vs un server-side callback.

Si, como no.

La version que yo uso es esta:
dash-extensions==0.0.44

tenes que importar la biblioteca que ademas del server side callback:

from dash_extensions.enrich import DashProxy, Output, Input, Trigger,ServersideOutputTransform, ServersideOutput, State

trae una distinta forma de “app =” en lugar del tradicional:

app = dash.Dash(__name__)

debes usar:

app = DashProxy(__name__, transforms=[ServersideOutputTransform()])

Esto no altera en nada la ejecucion del codigo que estes usando.

Agregar un dcc.Store que guardara la informacion a intercambiar entre los callback del lado del servidor:

dcc.Store(id='last_ticker', storage_type='local')

El decorador del calback va a enviar la informacion al output que se escribe de esta manera:

@app.callback(ServersideOutput('last_ticker', 'data'),
             Input('input-1-submit', 'value')

Como podras ver, los inputs que alimenten al output van a ser normales.

Cuando utilizas la informacion guardada en el dcc.Store como input de otro callback lo haces en forma habitual:

      Input('last_ticker', 'data')

Adicionalmente no es necesario transformar la informacion a json.

Con esto deberia funcionar, de todas formas @ Emil estuvo haciendo modificaciones hace poco para dividir todos los componentes que tiene en el mismo paquete (enrichments), Dash - How to implement chunked loading of custom components, podes visitar su pagina para ver si existen otras modificaciones, pero en mi app esta funcionando con esta version.

Saludos,

Hola @Eduardo

Muchas gracias por la respuesta.

El dcc.Store se debe agregar en la parte del layout donde se quiere tener el output?

Por ejemplo, en el callback que puse en mi pregunta, lo que se devuelve se guarda en un contenedor (div).

Con esta forma que me dices, el dcc.Store sustituiría al div? O se debe agregar aparte?

Podrías escribirme la cabecera equivalente de ese callback con este método que me comentas?

Un saludo.

Si, el dcc.Store es un componente como cualquier otro, va dentro del layout en cualquier lado, yo generalmente lo pongo al principio, al igual que los dcc.Interval, ya que son componentes que nunca van a mostrar nada al usuario.
Te adjunto la documentación al respecto, el dcc.Store es el mismo componente que usa Dash:
https://dash.plotly.com/dash-core-components/store

@Eduardo I can see that I am tagged, but I am not sure why. I don’t speak spanish :wink:

Hey @Emil

Haha, Sorry.
I left a space between the @ and Emil because I didn’t want to tagged you, just indicating him that you did changes in the enrichments module recently.

I explained how I’m using the server side callback (and the version I use) but I don’t know if you did changes after that, then I recommended him to visit your web page.

He is trying to implement the server side callback but needs an example to understand how to implement, because the link I provided in the first post is not working. The code I copied is what I’m using in my app, thanks for asking.

Hola @Eduardo

Entiendo, así que el dcc.Store puedo agregarlo en cualquier parte del layout. Sin embargo debo mantener el contenedor (div) tal y como lo tengo ahora. Es así?

Otra cuestión: debo agregar un dcc.Store para cada callback o basta con tener 1 solo para todos los callbacks?

Un saludo.

dcc.Store es un componente que guarda información en memoria, en Dash esa información debe ser json, en este caso de server side no es necesario. Cada información que necesites pasar entre dos callbacks del lado del servidor sin pasar al lado del cliente debe almacenarse en un dcc.Store distinto, esa información almacenada puede usarse tantas veces como lo requieras. A su vez en la documentación podrás ver tres formas distintas de mantener la información en la memoria (local, sesion y otra que no recuerdo).
Si finalmente esa información tiene que ser mostrada en un hrml.Div o no depende de la lógica del programa, no lo puedo decir, pero para intercambiar datos entre callbacks no se necesita pasar por ningún Div.
Espero que se entienda, por lo menos se que no es un problema de idioma :joy:

Creo que ya lo entiendo.

Parece que debo usar un dcc.Store como output para guardar la información de cada callback y luego ese mismo dcc.Store usarlo como input para mostrar la información actualizada.

Es decir, con este método parece que primero se debe guardar la información de los Inputs en un Store y luego ya operar con la información del Store.

Osea que si ahora utilizo 8 callbacks, con este método serían 16 callbacks. Ando mal encaminado?

Si y No. :woozy_face:

Por eso en mi primer post comente que quizás el server side callback pueda servirte (o quizás no), depende de la lógica de tu programa.

La idea no es duplicar callbacks, la idea es reemplazar todos aquellos callbacks que no envían información al usuario (no todos los callbacks del programa)

Por ejemplo, un callback realiza una serie de tareas y como resultado envía una gran cantidad de información que será utilizada luego por otro callback, en ese caso el server side callback permite que la información se transmita mucho más rápido (si lees el link que envíe en el primer post ahí explican la lógica del ahorro de tiempo)

En mi caso por ejemplo estoy tomando la información histórica de la cotización de una empresa en los últimos diez años, que luego es utilizada para hacer distintos gráficos, la guardo en el store y luego leo directamente de el y finalmente muestro los gráficos.

Si el proceso sólo toma información para mostrar al cliente no es necesario cambiar a server side porque no generaría ningún ahorro de tiempo, solo los callbacks que envían información a otros callbacks deben ser cambiados (por lo tanto no habría duplicación de callbacks.

Reiteró, depende de la lógica de tu programa si esta herramienta puede ser una solución de ahorro de tiempo o no.
Saludos.

Entiendo.
Lo probaré a ver qué tal.
Gracias por la ayuda.

Saludos

1 Like

:1st_place_medal: for the first foreign language dash thread!

2 Likes

Hi @Eduardo, would you mind summarising your solution in English as well as I am currently facing the same issue? Thank you!