✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
🐇 Announcing Dash VTK for 3d simulation graphics. Check out the March webinar.

Components triggered by table not updating

Hi,

I see some strange behavior with components in the front-end not updating even when the callback function is clearly called. The behavior seems to be similar to this older issue which was fixed a long time ago:

https://community.plotly.com/t/dcc-graph-component-not-updating-when-callback-with-inputs-and-state-is-fired/13379

I reduced the problem to a sample app:

import dash
import dash_table
import pandas as pd

import dash_html_components as html
import dash_core_components as dcc

from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
import plotly.graph_objs as go

app = dash.Dash(__name__)
server = app.server

app.layout = html.Div(children=[
	dash_table.DataTable(
	id='table-data',
	data=[{'x':'test', 'graph': 0}],
	columns=[
		{'id': 'x', 'name': 'x', 'editable':False},
		{'id': 'graph', 'name': 'graph', 'presentation': 'dropdown', 'editable':True}],
	dropdown={
		'graph': {
			'options': [
				{'label': 'Do not show', 'value': 0x0},
				{'label': 'Plot 1', 'value': 1},
				{'label': 'Plot 2', 'value': 2}],
			},
		},
	row_deletable=False,
	editable=True,
	),
	dcc.Store(id='g1buffer', storage_type='memory'),
	dcc.Store(id='g2buffer', storage_type='memory'),
	dcc.Graph(id='plot-graph1'),
	dcc.Graph(id='plot-graph2'),
])


@app.callback(
	Output('plot-graph1', 'figure'),
	[Input('g1buffer', 'data')],
)
def update_graph1(data):
	if data is None:
		raise PreventUpdate
	return data

@app.callback(
	Output('plot-graph2', 'figure'),
	[Input('g2buffer', 'data')],
)
def update_graph2(data):
	if data is None:
		raise PreventUpdate
	return data

@app.callback(
	[
		Output('g1buffer', 'data'),
		Output('g2buffer', 'data'),
	],
	[Input('table-data', 'data')],
)
def update_on_table(table_data):
	data = go.Scatter(
				x=[1,2,3,4],
				y=[2,5,1,3],
				)
	g1 = {}
	g2 = {}
	if table_data[0]['graph'] == 1:
		g1 = {'data': [data]}
	if table_data[0]['graph'] == 2:
		g2 = {'data': [data]}
	return g1, g2

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

To give a bit more context: I have a callback which generates figures for multiple graph components. However, when one graph is updated, I don’t want loading bars showing on all graphs, but only the one which is changing. The only way I figured out to make this work is having a store (or hidden div) for every graph so you can update all the stores but the ones not changing will just send a PreventUpdate.

In the example above, you should see data in the top graph if ‘Plot 1’ is selected in the table and so on. What I see is that this works fine for Plot 2 but not 1.
The strange thing is, this works if I run this example in uwsgi vs the integrated debug server. In my actual app, it is the other way round, it works in dev but not in the uwsgi deployment.

One thing which seems to make it work in any case is setting row_deletable to false. This will have an undesired impact on the app though, so I don’t want to go that way. Right now, my best work-around is to have a dummy buffer for the first output component of the update_on_table() function and use the second, third, etc output for actual graph components.

My skills in React and JS are pretty non-existent so I’m lost as soon as the problems leaves the Python domain. I’d appreciate any comment.

Hi @etrauschke welcome to the forum! This is a strange behaviour indeed. When I tried your app I observed that the problem does not appear consistently, that is, it happens when in a seemingly random fashion when re-launching the app. Did you also observe this or does it happen every time for you? For me using row_deletable=True did not make a difference though.

Hi @Emmanuelle

yes, it is not consistent but for me it seems to be consistent between deployment styles and random code changes. So If I don’t see it, restarting the app usually doesn’t make it appear.

And yes, after I wrote the post I noticed that the row_deletable=True does not work all the time. So I think this is something random. It almost reminds me of a memory bug but that can’t be it.

I modified your example using a single dcc.Store containing both dicts for the two graphs, and the problem disappears. There might be some weird interaction between the two dcc.Store

import dash
import dash_table
import pandas as pd

import dash_html_components as html
import dash_core_components as dcc

from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
import plotly.graph_objs as go

app = dash.Dash(__name__)
server = app.server



app.layout = html.Div(children=[
	dash_table.DataTable(
	id='table-data',
	data=[{'x':'test', 'graph': 0}],
	columns=[
		{'id': 'x', 'name': 'x', 'editable':False},
		{'id': 'graph', 'name': 'graph', 'presentation': 'dropdown', 'editable':True}],
	dropdown={
		'graph': {
			'options': [
				{'label': 'Do not show', 'value': 0x0},
				{'label': 'Plot 1', 'value': 1},
				{'label': 'Plot 2', 'value': 2}],
			},
		},
	editable=True,
	),
	dcc.Store(id='gbuffer'),
	dcc.Graph(id='plot-graph1'),
	dcc.Graph(id='plot-graph2'),
])


@app.callback(
	Output('plot-graph1', 'figure'),
	[Input('gbuffer', 'data')],
)
def update_graph1(data):
    print('update_graph1', data)
    if data is None:
        raise PreventUpdate
    return data[0]

@app.callback(
	Output('plot-graph2', 'figure'),
	[Input('gbuffer', 'data')],
)
def update_graph2(data):
    print('update_graph2', data)
    if data is None:
        raise PreventUpdate
    return data[1]

@app.callback(
	 Output('gbuffer', 'data'),
	[Input('table-data', 'data')],
)
def update_on_table(table_data):
    data = go.Scatter(
                            x=[1,2,3,4],
                            y=[2,5,1,3],
                            )
    g1 = {}
    g2 = {}
    if table_data[0]['graph'] == 1:
        g1 = {'data': [go.Scatter(x=[1, 2], y=[1, 2])]}
    if table_data[0]['graph'] == 2:
        g2 = {'data': [go.Scatter(x=[1, 3], y=[2, 3])]}
    return [g1, g2]


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

Thanks, this does fix it in all the variants I have tried so far.

The only drawback is that I had to use my own dash.no_update marker because you can’t pass that as part of a list. But that is a minor issue which is easily fixed.

For the future I have to see if I can make it with only one store for a few other things, though. But I actually don’t believe there is a problem with the store component itself, since it behaved the same when I used an invisible div to achieve this.
There must be something going on when you have two outputs from the same callback which then have to trigger different callbacks on their own.

Thanks again for the quick help!
Erik

thanks for investigating this further, and also for providing a standalone and minimal example, this really helped. I’ve opened an issue in https://github.com/plotly/dash-core-components/issues/778, you can subscribe to the issue to know if there are further developments.