Black Lives Matter. Please consider donating to Black Girls Code today.

Explain Live Data Updates after Change in Dropdown Selection

I can see that what I want to do is in the sample satellite dashboard. But it’s so complicated I can’t see through it to what I want. In my project I get the “Multi output … that was already assigned” error because an output can only have one input, I surmise.

So can someone explain the basics if one has

  1. A dropdown list that selects a group. Mine are cryptocurrencies, so let’s say BTC
  2. That kicks off an live updating table and/or graph etc.

So far I have

#
# Callback: TABLE POPULATE based on drop down selection
#
@app.callback([Output('table-container', 'data'), Output('table-container', 'columns')],
    [Input('group-dropdown', 'value')])
def gen_table(dropdown_value):
    return data_table_populate(dropdown_value)

In my template, I have

dcc.Interval(id='invterval-component',
        interval=10*1000, # 10 seconds
        n_intervals=0) # 60 seconds, set counter to 0

Which I believe gives me an input that will update every X period.

But how do I get that to call the same output that was generated with the dropdown. I’ll want to keep updating both a table and graph every X period with whatever was selected.

My guess is that my dropdown would call a function that listens to the timer callback and then updates the table. Sorry, I just can’t see how that exactly works in what I’ve researched. The tuts are for updating ony, not for working with a change in data query. Sorry I don’t get it! THANKS IN ADVANCE!

I’ll try to put what I learn here in a tutorial of my own for starters.

Here is my whole script at this point:

from pathlib import Path

import dash
import dash_html_components as html # handles html tag stuff
import dash_core_components as dcc # handles controls, graphs, etc
import dash_table as dash_table
from dash.dependencies import Input, Output # Callback Input, Output only!
import plotly.express as px

import sqlite3
from sqlalchemy import create_engine
import pandas as pd

# Where is our database, wherever we're running this?
# and let's abstract with SQLAlchemy
scriptPath = Path(__file__).parent
dbPath = scriptPath / 'db'
dbPathFile = str(dbPath / 'db_cryptocompare.sqlite')
db_uri = r'sqlite:///' + str(dbPathFile)

# If problem, try hard path
#conn = sqlite3.connect('/home/maxrottersman/python_chart_automation/db_cryptocompare.sqlite')
conn = sqlite3.connect(dbPathFile)

# We want latest 15 prices but sorted ascending
sql = 'Select [symbol],[time],[close] From ' \
    '(select * from Value_ByMinute_BTC ' \
     'ORDER BY [time] DESC limit 15) ORDER BY [symbol],[Time] ASC;'
df= pd.read_sql(sql,conn)
# From that, create unique Managers for Drop Down
groups = sorted(df['symbol'].unique())

conn.close()

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, 
    external_stylesheets=external_stylesheets,
    suppress_callback_exceptions=True) # , server=server call flask server
# THIS IS FOR GUNICORN (or WSGI production server) TO HOOK INTO! like unicorn webapp:app.server -b 0.0.0.0 #:8000
server = app.server

# Part 1: DATA FUNCTIONS
#  Groups of data for our dropdown, which drives table and chart
def data_group_options_build():
    OptionList = [{'label': group, 'value': group} for group in groups]
    return OptionList

#  Fill DATATABLE from Dropdown List
def data_table_populate(dropdown_value):
    is_group = df['symbol']==dropdown_value
    # Create dataframe of those rows only by passing in those booleans
    dff = df[is_group] # dff as in dataframe filtered
    # Data table won't work without column defs
    my_table_columns = [{"name": i, "id": i,} for i in (df.columns)]
    
    return dff.to_dict('records'), my_table_columns
    
# Draw GRAPH from data picked in manager dropdown list
def data_graph_draw(dropdown_value):
    is_group = df['symbol']==dropdown_value
    # Create dataframe of those rows only by passing in those booleans
    dff = df[is_group] # dff as in dataframe filtered
    # Using HELPER library plotly.express as px
    fig = px.scatter(dff, x=dff['time'], y=dff.close)
    return fig

def data_get_latest():
    conn = sqlite3.connect(dbPathFile)

    # We want latest 15 prices but sorted ascending
    sql = 'Select [symbol],[time],[close] From ' \
        '(select * from Value_ByMinute_BTC ' \
        'ORDER BY [time] DESC limit 15) ORDER BY [symbol],[Time] ASC;'
    df_latestdata= pd.read_sql(sql,conn)

    my_table_columns = [{"name": i, "id": i,} for i in (df_latestdata.columns)]
   
    conn.close()
    return df_latestdata #, my_table_columns

##### END DATA FUNCTIONS #####
#
# STYLES
#

# Give style same names as DASH uses, purpose here to keep app.layout ucluttered
#
style_cell_conditional=[
        {
            'if': {'column_id': c},
            'textAlign': 'left',
            'width': '200px'
        } for c in ['close','time']
        ] + [
        {
            'if': {'column_id': 'symbol'},
            'display': 'none'
        } 
        ] + [
        {
            'if': {'column_id': 'close'},
            'textAlign': 'right',
            'width': '200px'
        } 
        ] + [
        {
            'if': {'column_id': 'time'},
            'width': '200px'
        } 
        ]
style_data_conditional=[
        {
            'if': {'row_index': 'odd'},
            'backgroundColor': 'rgb(248, 248, 248)'
        }
    ]
style_header={
        'backgroundColor': 'rgb(230, 230, 230)',
        'fontWeight': 'bold'
    }

#
# PART 2, Create our clean DASH LAYOUT object
#
def generate_layout():
    return html.Div([
    html.H1(children='Python automation charts using cryptocurrencies'),
        # dumm y callback to get things initialized
        dcc.Input(id='initialize_app_components_with_dummy_callback', value='', type='text', style={'display':'none'}),
        # html.Div to keep Dropdown and 'Data Table' text on same line
        html.Div([dcc.Dropdown(id='group-dropdown', value='BTC', style={'width': '100px', 'verticalAlign':'middle'}),
        html.H3(children=' Table Data', style={'verticalAlign':'middle','display': 'inline-block'}),
        html.A('Refresh', href='/')
        ]),
        
    html.Br(),
    dash_table.DataTable(id='table-container',
        style_table = {'width':'500px'},
        style_cell_conditional = style_cell_conditional,
        style_data_conditional = style_data_conditional,
        style_header = style_header),
    html.H3(children='Cryptocurrenty Prices by Minute'),
    dcc.Graph(id='graph-container'),
    dcc.Interval(id='invterval-component',
        interval=10*1000, # 10 seconds
        n_intervals=0) # 60 seconds, set counter to 0
])

suppress_callback_exceptions=True
app.layout = generate_layout
#
# PART 2, Load Initial DROPDOWN LIST DATA in our DASH layout which
# will happen after the app is run.
#
# 1. ON APP START, ALL CALLBACKS CALLED USING CURRENT VALUE OF INPUT CONTROLS!!!
# 2. WHEN INPUT CHANGES, The FUNCTION underneath is called
@app.callback(
    Output(component_id='group-dropdown', component_property='options'),
    [Input(component_id='initialize_app_components_with_dummy_callback', component_property='value')]
)
def group_dropdown_BuildOptions(df_for_dropdown):  
    return data_group_options_build()
#
# PART 3, set up our callbacks to hands dropdown selections, etc
#

#
# Callback: TABLE POPULATE based on drop down selection
#
@app.callback([Output('table-container', 'data'), Output('table-container', 'columns')],
    [Input('group-dropdown', 'value')])
def gen_table(dropdown_value):
    return data_table_populate(dropdown_value)
    
#
# Callback: GRAPH DRAW based on drop down selection
#  
@app.callback(
    dash.dependencies.Output('graph-container', 'figure'),
    [dash.dependencies.Input('group-dropdown', 'value')])
def gen_graph(dropdown_value):
    return data_graph_draw(dropdown_value)

# Interval Callback, update every 60 seconds.
@app.callback([Output('table-container', 'data')],
              [Input('interval-component', 'n_intervals')])
def callback_data_get_latest():
    return data_get_latest()
    
###
### OKAY, LET 'ER RIP
###    
if __name__ == '__main__':
    # Can this ever work instead of dummy callback?
    #app.callback(Output('mgr-dropdown', 'options')) (mgr_options_build())
    app.run_server() #http://127.0.0.1:8050