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
- A dropdown list that selects a group. Mine are cryptocurrencies, so let’s say BTC
- 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