Hi Dashers
I have an odd issue where I create callbacks dynamically in a loop, similar to the “Store Clicks Example” in the documentation. What I have is a bunch of graph container divs, which are populated from a callback. The final graph that is created is always duplicated to all the graph containers, instead of each container rendering its own graph. Its hard to explain so I have stripped down the code to a bare minimum and pasted it here. After clicking submit I expect to see three different graphs, one in each container, however all three containers contain the same graph.
Any help will be much appreciated.
# -*- coding: utf-8 -*-
from collections import OrderedDict
import json
import dash
import dash_table
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output, State, ALL
import plotly.graph_objects as go
app = dash.Dash(__name__)
server = app.server # For running under Gunicorn
MULO_GEOS = (
('WALMART', 'Walmart'),
('TARGET', 'Target'),
('KROGER', 'Kroger'),
)
submit_button = html.Button(
id='submit-button',
children='Submit',
n_clicks=0,
)
model_output = [
html.Div('Model Output Placeholder', id='model-output')
]
output_table = html.Table(
html.Tbody([
html.Tr([
html.Th('Total Universe'),
html.Th('Channels'),
html.Th('Units/Period'),
html.Th('Accounts'),
html.Th('Units/Period')
]),
html.Tr([
html.Td('0.00', id='total-universe'),
html.Td('TOTAL MULO'),
html.Td('0.00', id='total-mulo'),
html.Td('Walmart'),
html.Td('0.00', id='walmart-average')
]),
html.Tr([
html.Td(),
html.Td(),
html.Td(),
html.Td('Target'),
html.Td('0.00', id='target-average')
]),
html.Tr([
html.Td(),
html.Td(),
html.Td(),
html.Td('Kroger'),
html.Td('0.00', id='kroger-average')
]),
])
)
def make_graph_div(geo):
'''
Return a graph container div
for the given geo.
'''
g, name = geo
id_ = name.lower()
id_ = f'{id_}-graph-container'
div = html.Div(
id_,
id=id_
)
return div
def make_graph_divs(geos):
divs = []
for geo in geos:
div = make_graph_div(geo)
divs.append(div)
return divs
app.layout = html.Div(
[
submit_button,
html.Div(output_table), # Probably this is where we will put the output table
] + make_graph_divs(MULO_GEOS) + [
# Hidden div inside the app that stores the intermediate value
# See example 1 from https://dash.plotly.com/sharing-data-between-callbacks
# We will use this to store the product_dict which can then be used
# by all the graphs...
html.Div(
id='product-dict', style={'display': 'none'}
)
]
)
def build_graph(predictions, title):
fig = go.Figure()
x = list(range(len(predictions)))
fig.add_trace(go.Scatter(x=x, y=predictions))
fig.update_layout(
title=title,
xaxis_title='Month',
yaxis_title='Units per Four Week Period'
)
graph = dcc.Graph(
id='example-graph',
figure=fig
)
return graph
@app.callback(
Output('product-dict', 'children'),
Input('submit-button', 'n_clicks'),
State({'type': 'attribute', 'name': ALL}, 'id'),
State({'type': 'attribute', 'name': ALL}, 'value'),
State({'type': 'diet-suitability', 'name': ALL}, 'id'),
State({'type': 'diet-suitability', 'name': ALL}, 'value'),
State({'type': 'certification', 'name': ALL}, 'id'),
State({'type': 'certification', 'name': ALL}, 'value'),
prevent_initial_call=True)
def convert_inputs(n_clicks, attribute_ids, attribute_values,
diet_suitability_ids,
diet_suitability_values,
certification_ids,
certification_values):
print('Started Conversion.')
product_dict = dict()
print('Done Conversion.')
return json.dumps(product_dict)
for i, (geo, short_name) in enumerate(MULO_GEOS):
output_id = short_name.lower()
output_id = f'{output_id}-graph-container'
average_id = short_name.lower() + '-average'
print(f'Creating callback for {output_id}')
@app.callback(
Output(output_id, 'children'),
Output(average_id, 'children'),
Input('product-dict', 'children'),
prevent_initial_call=True)
def generate_graph(product_dict):
print(f'Started {short_name}.')
product_dict = json.loads(product_dict)
g = geo.lower()
#predictions = predict_series(product_dict, g)
predictions = [i] * 12
avg = sum(predictions) / 12
print(geo, avg)
graph = build_graph(predictions, geo)
print(f'Done {short_name}')
return graph, avg
if __name__ == '__main__':
print('Starting server...')
app.run_server(debug=True)