Hello there. I’m trying to create Multiapp, and idea is next:
Person opens page, on this page only datepicker and button “search”. Person choose start date,end date, then click “Search” and on page appears table with data,that was generated in back using chosen date range.
The problems I faced are:
- I don’t know how to track was button clicked or not. I tried n_clicks, but it returns numbers. I don’t want to save previous number and then compare with current. I tried to use n_clicks_timestamp, and compare it with current date and time -1 sec as is How to update component when button is clicked, but I always receive ‘list index out of range error’. All what I need is to generate table on click, and since generation of table for me is not a big deal,I would appreciate any samples of code with button click even callbacks
- I tried to separate the data for users, because at least 10 people will use my service, and every person should see their own data.
First, ofc, I looked inside manual there:
https://dash.plot.ly/sharing-data-between-callbacks
I tried to use variant 4. but since I generate table via html.Table, I made a list with Tr ,Td,Th objects, that could not be serialized. And because of this it is not working. Maybe you can give me advice with samples how to split work for users.
Because it’s multiapps I use next structure:
|index.py
|app.py
| apps
–|| app1.py
–||app2.py
There is the code for every file:
index.py:
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from app import app
from apps import app2, app1
app.layout = html.Div([
dcc.Location(id='url', refresh=False),
html.Div(id='page-content')
])
@app.callback(Output('page-content', 'children'),
[Input('url', 'pathname')])
def display_page(pathname):
if pathname == '/apps/app1':
return app1.layout
elif pathname == '/apps/app2':
return app2.layout
else:
return app1.layout
if __name__ == '__main__':
app.run_server(debug=True)
app.py:
import dash
import dash_bootstrap_components as dbc
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
server = app.server
app.config.suppress_callback_exceptions = True
app1.py:
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import data_processor
from app import app
import pandas as pd
import dash_bootstrap_components as dbc
from datetime import datetime as dt
from itertools import islice
import time
import uuid
from flask_caching import Cache
import json
import datetime
import dash
data = data_processor.get_data("overview_critical_calls.csv",
1) # There we read the data from first .csv,with Call statistics
data2 = data_processor.get_data('alfa_gewerke_counts.csv', 2) # there we read the data from second .csv, with Gewerke
columnnames = ['Customer', 'Calls', 'Hours'] # columns name for the dataframe for 1st file
moderdf = pd.DataFrame.from_records(data,
columns=columnnames) # creation of the dataframe with pandas for the global statistics
# There we describe cache congif
cache = Cache(app.server, config={
'CACHE_TYPE': 'redis',
# Note that filesystem cache doesn't work on systems with ephemeral
# filesystems like Heroku.
'CACHE_TYPE': 'filesystem',
'CACHE_DIR': 'cache-directory',
# should be equal to maximum number of users on the app at a single time
# higher numbers will store more data in the filesystem / redis cache
'CACHE_THRESHOLD': 200
})
"""
In function below we generate links for every customer.
As input we use data2 (prepared above) and customer name
"""
def prepare_link_data(dataframe, customer):
arglist = []
if customer in dataframe.keys():
for keys in dataframe[customer].keys():
tempquantity = dataframe[customer][keys]
arglist.append(html.A(keys + '(' + tempquantity + ')', target='_blank', href='http://google.com'))
arglist.append(" ")
return arglist
"""
Function below (prepare_table_data) prepare data for the Table we built on the page.
As input we use dataframe, that was prepared before.
"""
def prepare_table_data(dataframe, max_rows=26):
rows = []
columns = []
for col in dataframe.columns:
columns.append(html.Th(col))
rows.append(html.Tr(columns)) # There we prepare headers for the table
for i in range(min(len(dataframe), max_rows)):
columns = []
for col in dataframe.columns: # There we prepare rows
if col == "Customer": # We don't need to make dropdown elements for every cell, only for 1st column
columns.append( # Default color scheme is white background,font is black
html.Td(html.Details([html.Summary(dataframe.iloc[i][col], style={'backgroundColor': '#ffffff',
'color': 'black'}),
*prepare_link_data(data2, dataframe.iloc[i][col])],
style={'border': '1px solid black', 'border-collapse': 'collapse'})))
else: # This 'else' for all cells that are not included in 1st column
columns.append(
html.Td(dataframe.iloc[i][col], style={'border': '1px solid black', 'border-collapse': 'collapse'}))
rows.append(html.Tr(columns)) # There we append headers for the table and rows
for elements in islice(rows, 1, None): # in this cycle we change the style of every cell of needed
if float(elements.children[1].children) >= 11: # if "Calls" is >=11 - then red
elements.children[0].children.children[0].style['backgroundColor'] = '#ff0000'
elif 5 <= float(elements.children[1].children) < 11: # if 5< = "Calls"<11 - then orange
elements.children[0].children.children[0].style['backgroundColor'] = '#ff8000'
elif 3 <= float(elements.children[1].children) < 5: # if 3 < = "Calls" < 5 - then yellow
elements.children[0].children.children[0].style['backgroundColor'] = '#ffff00'
return rows
def get_dataframe(session_id):
@cache.memoize()
def query_and_serialize_data(session_id):
data = json.dumps(prepare_table_data(moderdf))
return data
return pd.read_json(query_and_serialize_data(session_id))
# blue navigation bar on top of the page ( we can add some link in it later)
navbar = dbc.NavbarSimple(
brand="Overview",
brand_href="",
color="primary",
dark=True,
)
# There we prepare body using bootstrap enveloper for dash.
body = dbc.Container(
[
dbc.Row(
[
dbc.Col(children=[html.H2('Raw calls statistic') ,html.Table(prepare_table_data(moderdf), style={'width': '600px'})], id='table_column'
),
dbc.Col(
[
html.H1("Pick date range", id='date_range_header'), # Date picker. Need to be replaced
dcc.DatePickerRange(
id='my-date-picker-range',
min_date_allowed=dt(1995, 8, 5),
max_date_allowed=dt(2030, 1, 1),
initial_visible_month=dt(dt.today().year, dt.today().month, dt.today().day),
end_date=dt(dt.today().year, dt.today().month, dt.today().day)
),
dbc.Button('Search', id='submit_button', outline=True, color='primary') # Button, not working
]
)
]
)
],
className="mt-4",
)
# There we prepare layout for user
def prepare_layout():
session_id = str(uuid.uuid4())
return html.Div([
navbar,
html.Div(session_id, id='session-id', style={'display': 'none'}),
html.Div(children=[
body
], id='div_for_table'),
dcc.Location(id='url', refresh=False),
html.Div(id='app2_content')
])
# Creation of the layout for app
# the sequence of the arguments is very important: if navbar will be second, then it will fall down as footer
layout = html.Div([
navbar,
body,
dcc.Location(id='url', refresh=False)
])
As you can see in this code there are some parts prepared for using cache and sessions, but since they didn’t work I didn’t include them in body. Note that I deleted callbacks in app1.py, because they didn’t work anyway.
I changed code in app1.py a little bit to show problem I faced with callback from button:
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import data_processor
from app import app
import pandas as pd
import dash_bootstrap_components as dbc
from datetime import datetime as dt
from itertools import islice
import time
import uuid
from flask_caching import Cache
import json
import datetime
import dash
data = data_processor.get_data("overview_critical_calls.csv",
1) # There we read the data from first .csv,with Call statistics
data2 = data_processor.get_data('alfa_gewerke_counts.csv', 2) # there we read the data from second .csv, with Gewerke
columnnames = ['Customer', 'Calls', 'Hours'] # columns name for the dataframe for 1st file
moderdf = pd.DataFrame.from_records(data,
columns=columnnames) # creation of the dataframe with pandas for the global statistics
# There we describe cache congif
cache = Cache(app.server, config={
'CACHE_TYPE': 'redis',
# Note that filesystem cache doesn't work on systems with ephemeral
# filesystems like Heroku.
'CACHE_TYPE': 'filesystem',
'CACHE_DIR': 'cache-directory',
# should be equal to maximum number of users on the app at a single time
# higher numbers will store more data in the filesystem / redis cache
'CACHE_THRESHOLD': 200
})
"""
In function below we generate links for every customer.
As input we use data2 (prepared above) and customer name
"""
def prepare_link_data(dataframe, customer):
arglist = []
if customer in dataframe.keys():
for keys in dataframe[customer].keys():
tempquantity = dataframe[customer][keys]
arglist.append(html.A(keys + '(' + tempquantity + ')', target='_blank', href='http://google.com'))
arglist.append(" ")
return arglist
"""
Function below (prepare_table_data) prepare data for the Table we built on the page.
As input we use dataframe, that was prepared before.
"""
def prepare_table_data(dataframe, max_rows=26):
rows = []
columns = []
for col in dataframe.columns:
columns.append(html.Th(col))
rows.append(html.Tr(columns)) # There we prepare headers for the table
for i in range(min(len(dataframe), max_rows)):
columns = []
for col in dataframe.columns: # There we prepare rows
if col == "Customer": # We don't need to make dropdown elements for every cell, only for 1st column
columns.append( # Default color scheme is white background,font is black
html.Td(html.Details([html.Summary(dataframe.iloc[i][col], style={'backgroundColor': '#ffffff',
'color': 'black'}),
*prepare_link_data(data2, dataframe.iloc[i][col])],
style={'border': '1px solid black', 'border-collapse': 'collapse'})))
else: # This 'else' for all cells that are not included in 1st column
columns.append(
html.Td(dataframe.iloc[i][col], style={'border': '1px solid black', 'border-collapse': 'collapse'}))
rows.append(html.Tr(columns)) # There we append headers for the table and rows
for elements in islice(rows, 1, None): # in this cycle we change the style of every cell of needed
if float(elements.children[1].children) >= 11: # if "Calls" is >=11 - then red
elements.children[0].children.children[0].style['backgroundColor'] = '#ff0000'
elif 5 <= float(elements.children[1].children) < 11: # if 5< = "Calls"<11 - then orange
elements.children[0].children.children[0].style['backgroundColor'] = '#ff8000'
elif 3 <= float(elements.children[1].children) < 5: # if 3 < = "Calls" < 5 - then yellow
elements.children[0].children.children[0].style['backgroundColor'] = '#ffff00'
return rows
def get_dataframe(session_id):
@cache.memoize()
def query_and_serialize_data(session_id):
data = json.dumps(prepare_table_data(moderdf))
return data
return pd.read_json(query_and_serialize_data(session_id))
# blue navigation bar on top of the page ( we can add some link in it later)
navbar = dbc.NavbarSimple(
brand="Overview",
brand_href="",
color="primary",
dark=True,
)
# There we prepare body using bootstrap enveloper for dash.
body = dbc.Container(
[
dbc.Row(
[
dbc.Col(children=[], id='experiment'
),
dbc.Col(
[
html.H1("Pick date range", id='date_range_header'), # Date picker. Need to be replaced
dcc.DatePickerRange(
id='my-date-picker-range',
min_date_allowed=dt(1995, 8, 5),
max_date_allowed=dt(2030, 1, 1),
initial_visible_month=dt(dt.today().year, dt.today().month, dt.today().day),
end_date=dt(dt.today().year, dt.today().month, dt.today().day)
),
dbc.Button('Search', id='submit_button', outline=True, color='primary') # Button, not working
]
)
]
)
],
className="mt-4",
)
# There we prepare layout for user
def prepare_layout():
session_id = str(uuid.uuid4())
return html.Div([
navbar,
html.Div(session_id, id='session-id', style={'display': 'none'}),
html.Div(children=[
body
], id='div_for_table'),
dcc.Location(id='url', refresh=False),
html.Div(id='app2_content')
])
# Creation of the layout for app
# the sequence of the arguments is very important: if navbar will be second, then it will fall down as footer
layout = html.Div([
navbar,
body,
dcc.Location(id='url', refresh=False)
])
@app.callback(
Output("experiment", "children"), [Input("submit_button", "n_clicks")]
)
def on_button_click(n):
if n is None:
return html.H1('Kookook!')
pass
else:
print('Kookoo!')
return html.H3('Pikaboo!')
The problem is, that ‘experiment’ element is always being rewritted by empty place, so when I click button text appears, but immediately disappears. I understood why - because I’m making multiapp. I made some tests in separate application, and in it everything works perfect with button. Anyone know how to make the same with multiapp?
Will appreciate any help.