Dashboard with two different tabs

Hello,

First of all, Dash is amazing and it’s such a blast to work on it, great work!

I have massive problem with how my Dash app is unable to run.

What I try to achieve is a Dashboard for a Forecast. One Tab will be used to see specific forecasts for a given item and country. The second tab would be used for say - comparison purposes, compare items of a given country with items from a different country etc.

I get Dash to render the app just fine, the problem is with the callbacks to update the graph with two drop downs, I just don’t know where they should be put.

This is the AttributeError I receive:

AttributeError: 'Div' object has no attribute 'keys'

This is the code to my whole app:

import forecast

import dash
import dash_auth
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import dash_table as dt

import plotly as pt

import pandas as pd

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

forecast = forecast.run_forecast()

forecast_transposed = forecast.set_index('SALES GROUP').T

forecast_transposed['month'] = forecast_transposed.index

sales_grp = forecast['SALES GROUP'].unique()

cntr_grp = list(set([i.split('-')[0] for i in sales_grp]))
item_grp = list(set([i.split('-')[1] for i in sales_grp]))

valid_user_password_pairs = [

	['user', 'password']

]

app = dash.Dash(__name__, external_stylesheets = external_stylesheets)

auth = dash_auth.BasicAuth(
		app,
		valid_user_password_pairs
	)

app.layout = html.Div([
	dcc.Tabs(id='forecast_tabs', 
			value='tab-1', 
			children=[
				dcc.Tab(label='Tab One', value='tab-1'),
				dcc.Tab(label='Tab Two', value='tab-2'),
			]),
	html.Div(id='tabs-content')
])

@app.callback(
	Output(component_id='forecast_table', component_property='children'),
	[Input(component_id='cntr_dropdown', component_property='value'),
	 Input(component_id='item_dropdown', component_property='value')]
)

def update_table(cntr_name, item_name):

	cntr_detail = forecast[forecast['SALES GROUP'] == cntr_name + "-" + item_name]

	# return cntr_detail

	return dt.DataTable(
			id='cntr_table',
			columns=[{'name': i, 'id': i} for i in forecast.columns],
			data=cntr_detail.to_dict('rows'),
			# editable=True,
			# filtering=True,
			style_table={
				'maxHeight': '300',
				'overflowY': 'scroll',
				'whiteSpace': 'normal',
				'maxWidth': '300'
			},
			style_cell={'textAlign': 'center'},
			css=[{

				'selector': '.dash-spreadsheet-container .dash-spreadsheet-inner *, .dash-spreadsheet-container .dash-spreadsheet-inner *:after, .dash-spreadsheet-container .dash-spreadsheet-inner *:before',
				'rule': '    box-sizing: inherit; width: 100%;'

			}]
			# style_cell_conditional=[{
			# 	'if': {'row_index': 'odd'},
			# 	'backgroundColor': 'rgb(248, 248, 248)'
			# }]
		)

@app.callback(
	Output(component_id='forecast_graph', component_property='figure'),
	[Input(component_id='cntr_dropdown', component_property='value'),
	 Input(component_id='item_dropdown', component_property='value')]
	)

def update_graph(cntr_name, item_name):

	data_name = cntr_name + '-' + item_name

	mnths = forecast_transposed['month'].unique()

	figure = {

		'data': [

			{

				'y': forecast_transposed[data_name],
				'x': mnths,
				'type': 'line',
				'name': f'Line Graph for {data_name}'

			}
		]
	}

	return figure

@app.callback(
	Output(component_id='tabs-content', component_property='children'),
	[Input(component_id='forecast_tabs', component_property='value')]
)

def render_content(tab):

	if tab == 'tab-1':

		return html.Div([

				html.Div([
						html.Div(
							[html.H2('Forecast Dashboard',
							)],
							className='app-header-h1',
							style={
								   'flex': 5,
								   'font-size': '2.65em',
								   'margin': '7px',
								   'margin-top': '20px',
								   'margin-bottom': '0'
							},
						),
					],		
						className='app-header',
						style={
							'display': 'flex'
						}
				),
				dcc.Dropdown(
					id='cntr_dropdown',
					options=[{'label': i, 'value': i} for i in cntr_grp],
					value=cntr_grp[0]
				),

				dcc.Dropdown(
					id='item_dropdown',
					options=[{'label': i, 'value': i} for i in item_grp],
					value=item_grp[0],
					style={
						'margin-top': '20px'
					}
				),

				html.Div(
					id='forecast_table',
					style={
						'width': '100%',
						'margin-top': '20px',
						'table-layout': 'fixed'
					}
				),
				dcc.Graph(
					id='forecast_graph'
				),
		])

	elif tab == 'tab-2':

		return html.Div([
			html.H3('Tab Content 2')
		])

# @app.callback(
# 	Output('content', 'children'),
# 	events=[Event('refresh', 'interval')]
# )


app.scripts.config.serve_locally = True

if __name__ == '__main__':
	app.run_server(debug=False,
				   # dev_tools_props_check=False)

Any help would be greatly appreciated.

The issue I believe lies in the callbacks for the dropdowns, don’t know how to deal with those.

I tried a number of different methods, keeping the callbacks below the callback to render the tabs, above it, nothing worked.

Here is the traceback I receive:

Traceback (most recent call last):
  File "app.py", line 121, in <module>
    Input(component_id='item_dropdown', component_property='value')]
  File "C:\Users\AppData\Local\Continuum\anaconda3\lib\site-packages\dash\dash.py", line 1156, in callback
    self._validate_callback(output, inputs, state)
  File "C:\Users\AppData\Local\Continuum\anaconda3\lib\site-packages\dash\dash.py", line 865, in _validate_callback
    list(layout.keys()) + (
AttributeError: 'Div' object has no attribute 'keys'

it looks like there is a blank line between your callback and your function. could you try removing that?

Thank you for such a quick response!

Is that what you meant by ‘blank line’?

@app.callback(
	Output(component_id='forecast_graph', component_property='figure'),
	[Input(component_id='cntr_dropdown', component_property='value'),
	 Input(component_id='item_dropdown', component_property='value')]
	)

def update_graph(cntr_name, item_name):

	data_name = cntr_name + '-' + item_name

	mnths = forecast_transposed['month'].unique()

	figure = {

		'data': [

			{

				'y': forecast_transposed[data_name],
				'x': mnths,
				'type': 'line',
				'name': f'Line Graph for {data_name}'

			}
		]
	}

	return figure

Therefore it should look like this?

@app.callback(
	Output(component_id='forecast_graph', component_property='figure'),
	[Input(component_id='cntr_dropdown', component_property='value'),
	 Input(component_id='item_dropdown', component_property='value')]
	)
def update_graph(cntr_name, item_name):

	data_name = cntr_name + '-' + item_name

	mnths = forecast_transposed['month'].unique()

	figure = {

		'data': [

			{

				'y': forecast_transposed[data_name],
				'x': mnths,
				'type': 'line',
				'name': f'Line Graph for {data_name}'

			}
		]
	}

	return figure

And that is for all of the callbacks, right?

EDIT.

That did not do the trick I am afraid.

Should the callback be used with a state? The state would check which tab is currently open.