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

Graphing from dictionary of dataframes with multiple dropdowns

I have a dictionary, dict_main, of dataframes that I am trying to plot in Dash. I have two dropdowns that allow the user to select firstly the dataframe from within the dictionary, then secondly the columns within those dataframes. This works well and I now want to put the selected columns into a graph and I can’t seem to get this working. This is my first time using Dash, sorry if this is obvious. So far I have;

rpm = list(dict_main.keys())
channels = dict_main[rpm[0]]

app.layout = html.Div(
    [
        html.Div([
        dcc.Dropdown(
            id='rpm-dropdown',
            options=[{'label':speed, 'value':speed} for speed in rpm],
            value=list(dict_main.keys())[0],
            multi=True,
            searchable=False
            ),
            ],style={'width': '20%', 'display': 'inline-block'}),
        html.Div([
        dcc.Dropdown(
            id='channel-dropdown',
            multi=True
            ),
            ],style={'width': '20%', 'display': 'inline-block'}
        ),
    ]
)

@app.callback(
    dash.dependencies.Output('channel-dropdown', 'options'),
    [dash.dependencies.Input('rpm-dropdown', 'value')]
)
def update_date_dropdown(speed):
    return [{'label': i, 'value': i} for i in dict_main[speed]]


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

I’ve tried this;

html.Div([
    dcc.Graph(
        id='Main-Graph',
        figure=go.Figure(
            data=[
                go.Scatter(
                    x=rpm, y=channels
                )
            ]
        )
    ),
    ], style={'width': '98%', 'display': 'inline-block'}
    )

With a callback of;

@app.callback(
    dash.dependencies.Output('Main-Graph', 'figure'),
    [dash.dependencies.Input('channel-dropdown', 'value')])
def updateGraph(channels):
    return[{'label': i, 'value': i} for i in dict_main[channels]]

This returns a graph that will not update with a change in dropdown selection. It also appears with a table, which is something I didn’t expect. Any help to get this graph working would be really appreciatted. Thanks.

Hey I’m not an expert and not sure I entirely understand what you’re trying to build but there are a couple of things I see in your code -

  1. updateGraph needs to know the columns of which dataframe it needs to draw. Consider adding a dash.dependencies.State to this callback.
  2. The output of updateGraph is a figure and not a list. Also I think it’s better to define its initial view in the callback and not in the layout (all callbacks run once at startup).

I edited your code a little bit - not sure if this is what you meant but this one works…

import pandas as pd

import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
from dash.dependencies import Input, Output, State

df1 = pd.DataFrame({'col1': ['a', 'b', 'c'], 'col2': ['d', 'e', 'f']})
df2 = pd.DataFrame({'col1': ['g', 'h', 'i'], 'col2': ['j', 'k', 'l']})
dict_main = {'df1': df1, 'df2': df2}

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

rpm = list(dict_main.keys())
channels = dict_main[rpm[0]]

app.layout = html.Div(
    [
        html.Div([
        dcc.Dropdown(
            id='rpm-dropdown',
            options=[{'label':speed, 'value':speed} for speed in rpm],
            value=list(dict_main.keys())[0],
            # I removed the multi=True because it requires a distinction between the columns in the next dropdown...
            searchable=False
            ),
            ],style={'width': '20%', 'display': 'inline-block'}),
        html.Div([
        dcc.Dropdown(
            id='channel-dropdown',
            multi=True
            ),
            ],style={'width': '20%', 'display': 'inline-block'}
        ),
		html.Div([
			dcc.Graph(
				id='Main-Graph' # the initial graph is in the callback
			),
			], style={'width': '98%', 'display': 'inline-block'}
		)
    ]
)

@app.callback(
    Output('channel-dropdown', 'options'),
    [Input('rpm-dropdown', 'value')])
def update_date_dropdown(speed):
    return [{'label': i, 'value': i} for i in dict_main[speed]]

@app.callback(
    Output('Main-Graph', 'figure'),
    [Input('channel-dropdown', 'value')],
    [State('rpm-dropdown', 'value')]) # This is the way to inform the callback which dataframe is to be charted
def updateGraph(channels, speed):
	if channels:
        # return the entire figure with the different traces
		return go.Figure(data=[go.Scatter(x=dict_main[speed].index, y=dict_main[speed][i]) for i in channels])
	else: 
	    # at initialization the graph is returned empty
        return go.Figure(data=[])
	
if __name__ == '__main__':
	app.run_server(debug=True)

Thank you for the reply, its helped a lot! Do you know how to set a new y axis for each trace and also name the legend from the column headers? I’ve tried to expand the updateGraph callback to update the layout but can’t get it working.

Depends what exactly you want to do. I recommend to explore plotly’s reference (https://plot.ly/python/reference/). Dash uses plotly’s figure, so all the layout and data options are specified there.

I figured i should update this in the callback, I’ve managed to get the legend showing the column headers with name=i;

def updateGraph(channels, test):
    if channels:

        return go.Figure(data=[go.Scatter(x=dict_main[test].index, y=dict_main[test][i], name=i) for i in channels])
    else:
        return go.Figure(data=[])

All I want to do now is set an additional y axis for each column selected, this is because some column values are 0-1 while others are 100+.

Looking at the documentation, it seems that I need to specify y2, y3 y4 etc for each of the additional axis but I can’t see a way to do this without adding each trace seperately via add_trace.

Hi Iceberg_Slim,

I tried running the code from this thread and I’m having some issues with Host, and SystemExit: 1.

I am however not very computer litterate and I was wondering if you had any clue as to what the issue might be?

Best regards

SOLVED

Just add use_reloader = False to the final line so that it is:
app.run_server(debug=True, use_reloader = False)