dash-api/src/app.py

1199 lines
38 KiB
Python

import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from dash import dash_table
from dash.dash_table.Format import Group
import plotly.graph_objs as go
import dash_daq as daq
from textwrap import dedent
import pandas as pd
app = dash.Dash(__name__)
server = app.server
app.config['suppress_callback_exceptions'] = True
df = pd.read_csv("data/spc_data.csv")
params = list(df)
max_length = len(df)
suffix_row = '_row'
suffix_button_id = '_button'
suffix_sparkline_graph = '_sparkline_graph'
suffix_count = '_count'
suffix_ooc_n = '_OOC_number'
suffix_ooc_g = '_OOC_graph'
suffix_indicator = '_indicator'
theme = {
'dark': True,
'detail': '#2d3038', # Background-card
'primary': '#007439', # Green
'secondary': '#FFD15F', # Accent
}
def build_banner():
return html.Div(
id='banner',
className="banner",
children=[
html.H5('Manufacturing SPC Dashboard - Process Control and Exception Reporting'),
html.Button(
id='learn-more-button',
children="LEARN MORE",
n_clicks=0,
),
html.Img(
src="https://s3-us-west-1.amazonaws.com/plotly-tutorials/logo/new-branding/dash-logo-by-plotly-stripe-inverted.png")
]
)
def build_tabs():
return html.Div(
id='tabs',
className='row container scalable',
children=[
dcc.Tabs(
id='app-tabs',
value='tab1',
className='custom-tabs',
children=[
dcc.Tab(
id='Specs-tab',
label='Specification Settings',
value='tab1',
className='custom-tab',
selected_className='custom-tab--selected',
disabled_style={
'backgroundColor': '#2d3038',
'color': '#95969A',
'borderColor': '#23262E',
'display': 'flex',
'flex-direction': 'column',
'alignItems': 'center',
'justifyContent': 'center'
},
disabled=False
),
dcc.Tab(
id='Control-chart-tab',
label='Control Charts Dashboard',
value='tab2',
className='custom-tab',
selected_className='custom-tab--selected',
disabled_style= {
'backgroundColor': '#2d3038',
'color': '#95969A',
'borderColor': '#23262E',
'display': 'flex',
'flex-direction': 'column',
'alignItems': 'center',
'justifyContent': 'center'
},
disabled=False)
]
)
]
)
def init_df():
ret = {}
for col in list(df[1:]):
data = df[col]
stats = data.describe()
std = stats['std'].tolist()
ucl = (stats['mean'] + 3 * stats['std']).tolist()
lcl = (stats['mean'] - 3 * stats['std']).tolist()
usl = (stats['mean'] + stats['std']).tolist()
lsl = (stats['mean'] - stats['std']).tolist()
ret.update({
col: {
'count': stats['count'].tolist(),
'data': data,
'mean': stats['mean'].tolist(),
'std': std,
'ucl': round(ucl, 3),
'lcl': round(lcl, 3),
'usl': round(usl, 3),
'lsl': round(lsl, 3),
'min': stats['min'].tolist(),
'max': stats['max'].tolist(),
'ooc': populate_ooc(data, ucl, lcl)
}
})
return ret
def populate_ooc(data, ucl, lcl):
ooc_count = 0
ret = []
for i in range(len(data)):
if data[i] >= ucl or data[i] <= lcl:
ooc_count += 1
ret.append(ooc_count / (i + 1))
else:
ret.append(ooc_count / (i + 1))
return ret
state_dict = init_df()
def init_value_setter_store():
# Initialize store data
state_dict = init_df()
return state_dict
def build_tab_1():
return [
# Manually select metrics
html.Div(
id='set-specs-intro-container',
className='twelve columns',
children=html.P("Use historical control limits to establish a benchmark, or set new values.")
),
html.Div(
className='five columns',
children=[
html.Label(id='metric-select-title', children='Select Metrics'),
html.Br(),
dcc.Dropdown(
id='metric-select-dropdown',
options=list({'label': param, 'value': param} for param in params[1:]),
value=params[1]
)]),
html.Div(
className='five columns',
children=[
html.Div(
id='value-setter-panel'),
html.Br(),
html.Button('Update', id='value-setter-set-btn'),
html.Button('View current setup', id='value-setter-view-btn', n_clicks=0),
html.Div(id='value-setter-view-output', className='output-datatable')
]
)
]
ud_usl_input = daq.NumericInput(id='ud_usl_input', size=200, max=9999999, style={'width': '100%', 'height': '100%'})
ud_lsl_input = daq.NumericInput(id='ud_lsl_input', size=200, max=9999999, style={'width': '100%', 'height': '100%'})
ud_ucl_input = daq.NumericInput(id='ud_ucl_input', size=200, max=9999999, style={'width': '100%', 'height': '100%'})
ud_lcl_input = daq.NumericInput(id='ud_lcl_input', size=200, max=9999999, style={'width': '100%', 'height': '100%'})
def build_value_setter_line(line_num, label, value, col3):
return html.Div(
id=line_num,
children=[
html.Label(label, className='four columns'),
html.Label(value, className='four columns'),
html.Div(col3, className='four columns')],
className='row'
)
def generate_modal():
return html.Div(
id='markdown',
className="modal",
style={'display': 'none'},
children=(
html.Div(
id="markdown-container",
className="markdown-container",
children=[
html.Div(
className='close-container',
children=html.Button(
"Close",
id="markdown_close",
n_clicks=0,
className="closeButton"
)
),
html.Div(
className='markdown-text',
children=dcc.Markdown(
children=dedent('''
**What is this mock app about?**
'dash-manufacture-spc-dashboard` is a dashboard for monitoring read-time process quality along manufacture production line.
**What does this app shows**
Click on buttons in `Parameter' column to visualize details of measurement trendlines on the bottom panel.
The Sparkline on top panel and Control chart on bottom panel show Shewhart process monitor using mock data.
The trend is updated every other second to simulate real-time measurements. Data falling outside of six-sigma control limit are signals indicating 'Out of Control(OOC)', and will
trigger alerts instantly for a detailed checkup.
'''))
)
]
)
)
)
app.layout = html.Div(
children=[
build_banner(),
build_tabs(),
# Main app
html.Div(
id='app-content',
className='container scalable'
),
html.Button('Proceed to Measurement', id='tab-trigger-btn', n_clicks=0,
style={'display': 'inline-block',
'float': 'right'}),
dcc.Store(
id='value-setter-store',
data=init_value_setter_store()
),
generate_modal(),
]
)
# ===== Callbacks to update values based on store data and dropdown selection =====
@app.callback(
output=[
Output('value-setter-panel', 'children'),
Output('ud_usl_input', 'value'),
Output('ud_lsl_input', 'value'),
Output('ud_ucl_input', 'value'),
Output('ud_lcl_input', 'value')],
inputs=[Input('metric-select-dropdown', 'value')],
state=[State('value-setter-store', 'data')]
)
def build_value_setter_panel(dd_select, state_value):
return [
build_value_setter_line('value-setter-panel-header', 'Specs', 'Historical Value', 'Set new value'),
build_value_setter_line('value-setter-panel-usl', 'Upper Specification limit',
state_dict[dd_select]['usl'], ud_usl_input),
build_value_setter_line('value-setter-panel-lsl', 'Lower Specification limit',
state_dict[dd_select]['lsl'], ud_lsl_input),
build_value_setter_line('value-setter-panel-ucl', 'Upper Control limit', state_dict[dd_select]['ucl'],
ud_ucl_input),
build_value_setter_line('value-setter-panel-lcl', 'Lower Control limit', state_dict[dd_select]['lcl'],
ud_lcl_input)
], state_value[dd_select]['usl'], state_value[dd_select]['lsl'], state_value[dd_select]['ucl'], \
state_value[dd_select]['lcl']
# ====== Callbacks to update stored data via click =====
@app.callback(
output=Output('value-setter-store', 'data'),
inputs=[
Input('value-setter-set-btn', 'n_clicks')
],
state=[
State('metric-select-dropdown', 'value'),
State('value-setter-store', 'data'),
State('ud_usl_input', 'value'),
State('ud_lsl_input', 'value'),
State('ud_ucl_input', 'value'),
State('ud_lcl_input', 'value'),
]
)
def set_value_setter_store(set_btn, param, data, usl, lsl, ucl, lcl):
if set_btn is None:
return data
else:
data[param]['usl'] = usl
data[param]['lsl'] = lsl
data[param]['ucl'] = ucl
data[param]['lcl'] = lcl
# Recalculate ooc in case of param updates
data[param]['ooc'] = populate_ooc(df[param], ucl, lcl)
return data
@app.callback(
output=Output('value-setter-view-output', 'children'),
inputs=[Input('value-setter-view-btn', 'n_clicks'), Input('metric-select-dropdown', 'value'),
Input('value-setter-store', 'data')]
)
def show_current_specs(n_clicks, dd_select, store_data):
if n_clicks > 0:
curr_col_data = store_data[dd_select]
new_df_dict = {
'Specs': ['Upper Specification Limit', 'Lower Specification Limit', 'Upper Control Limit',
'Lower Control Limit'],
'Current Setup': [curr_col_data['usl'], curr_col_data['lsl'], curr_col_data['ucl'], curr_col_data['lcl']]
}
new_df = pd.DataFrame.from_dict(new_df_dict)
return dash_table.DataTable(
style_header={
'backgroundColor': '#2d3038',
'fontWeight': 'bold'
},
style_as_list_view=True,
style_cell_conditional=[
{
'if': {'column_id': c},
'textAlign': 'left'
} for c in ['Specs']
],
style_cell={'backgroundColor': '#2d3038', 'color': '#95969A', 'border': '#53555B'},
data=new_df.to_dict('rows'),
columns=[{'id': c, 'name': c} for c in ['Specs', 'Current Setup']]
)
def build_quick_stats_panel():
return html.Div(
id="quick-stats",
className='row',
children=
[
html.Div(
id="card-1",
className='four columns',
children=[
html.H5("Operator ID"),
daq.LEDDisplay(
value='1704',
color=theme['secondary'],
size=50,
theme=theme
)
]
),
html.Div(
id='card-2',
className='four columns',
children=[
html.H5("Time to completion"),
daq.Gauge(
id='progress-gauge',
value=0,
size=150,
max=max_length * 2,
min=0,
color=theme['secondary']
)
]
),
html.Div(
id='utility-card',
className='four columns',
children=[
daq.StopButton(id='stop-button', size=160, buttonText='start')
]
)
]
)
def generate_section_banner(title):
return html.Div(
className="section-banner",
children=title,
)
def build_top_panel():
return html.Div(
id='top-section-container',
className='row',
style={
'height': '45vh'
},
children=[
# Metrics summary
html.Div(
id='metric-summary-session',
className='eight columns',
style={'height': '100%'},
children=[
generate_section_banner('Process Control Metrics Summary'),
generate_metric_list_header(),
html.Div(
# id='metric_div',
style={
'height': 'calc(100% - 90px)',
'width': '100%',
'overflow-x': 'hidden',
'overflow-y': 'scroll'
},
children=[
generate_metric_row_helper(1),
generate_metric_row_helper(2),
generate_metric_row_helper(3),
generate_metric_row_helper(4),
generate_metric_row_helper(5),
generate_metric_row_helper(6),
generate_metric_row_helper(7),
]
)
]
),
# Piechart
html.Div(
id='ooc-piechart-outer',
className='four columns',
children=[
generate_section_banner('% OOC per Parameter'),
generate_piechart()
]
)
]
)
def generate_piechart():
return dcc.Graph(
id='piechart',
figure={
'data': [
{
'labels': params[1:],
'values': [1, 1, 1, 1, 1, 1, 1],
'type': 'pie',
'marker': {'line': {'color': '#53555B', 'width': 2}},
'hoverinfo': 'label',
'textinfo': 'label'
}],
'layout': {
'showlegend': True,
'paper_bgcolor': 'rgb(45, 48, 56)',
'plot_bgcolor': 'rgb(45, 48, 56)'
}
}
)
# Build header
def generate_metric_list_header():
return generate_metric_row(
'metric_header',
{
'height': '30px',
'margin': '10px 0px',
'textAlign': 'center'
},
{
'id': "m_header_1",
'children': html.Div("Parameter")
},
{
'id': "m_header_2",
'children': html.Div("Count")
},
{
'id': "m_header_3",
'children': html.Div("Sparkline")
},
{
'id': "m_header_4",
'children': html.Div("OOC%")
},
{
'id': "m_header_5",
'children': html.Div("%OOC")
},
{
'id': "m_header_6",
'children': "Pass/Fail"
})
def generate_metric_row_helper(index):
item = params[index]
div_id = item + suffix_row
button_id = item + suffix_button_id
sparkline_graph_id = item + suffix_sparkline_graph
count_id = item + suffix_count
ooc_percentage_id = item + suffix_ooc_n
ooc_graph_id = item + suffix_ooc_g
indicator_id = item + suffix_indicator
return generate_metric_row(
div_id, None,
{
'id': item,
'children': html.Button(
id=button_id,
children=item,
title="Click to visualize live SPC chart",
n_clicks=0
)
},
{
'id': count_id,
'children': '0'
},
{
'id': item + '_sparkline',
'children': dcc.Graph(
id=sparkline_graph_id,
style={
'width': '100%',
'height': '95%',
},
config={
'staticPlot': False,
'editable': False,
'displayModeBar': False
},
figure=go.Figure({
'data': [{'x': [], 'y': [], 'mode': 'lines+markers', 'name': item,
'line': {'color': 'rgb(255,209,95)'}}],
'layout': {
'uirevision': True,
'margin': dict(
l=0, r=0, t=4, b=4, pad=0
),
'paper_bgcolor': 'rgb(45, 48, 56)',
'plot_bgcolor': 'rgb(45, 48, 56)'
}
}))
},
{
'id': ooc_percentage_id,
'children': '0.00%'
},
{
'id': ooc_graph_id + '_container',
'children':
daq.GraduatedBar(
id=ooc_graph_id,
color={"gradient": True, "ranges": {"green": [0, 3], "yellow": [3, 7], "red": [7, 15]}},
showCurrentValue=False,
max=15,
value=0
)
},
{
'id': item + '_pf',
'children': daq.Indicator(
id=indicator_id,
value=True,
color=theme['primary']
)
}
)
def generate_metric_row(id, style, col1, col2, col3, col4, col5, col6):
if style is None:
style = {
'height': '100px',
'width': '100%',
}
return html.Div(
id=id,
className='row metric-row',
style=style,
children=[
html.Div(
id=col1['id'],
style={},
className='one column',
children=col1['children']
),
html.Div(
id=col2['id'],
style={'textAlign': 'center'},
className='one column',
children=col2['children']
),
html.Div(
id=col3['id'],
style={
'height': '100%',
},
className='four columns',
children=col3['children']
),
html.Div(
id=col4['id'],
style={},
className='one column',
children=col4['children']
),
html.Div(
id=col5['id'],
style={
'height': '100%',
},
className='three columns',
children=col5['children']
),
html.Div(
id=col6['id'],
style={
'display': 'flex',
'justifyContent': 'center'
},
className='one column',
children=col6['children']
)
]
)
def build_chart_panel():
return html.Div(
id='control-chart-container',
className='twelve columns',
children=[
generate_section_banner('Live SPC Chart'),
dcc.Interval(
id='interval-component',
interval=2 * 1000, # in milliseconds
n_intervals=0,
disabled=True
),
dcc.Store(
id='control-chart-state'
),
dcc.Graph(
id="control-chart-live",
figure=go.Figure({
'data': [{'x': [], 'y': [], 'mode': 'lines+markers', 'name': params[1]}],
'layout': {
'paper_bgcolor': 'rgb(45, 48, 56)',
'plot_bgcolor': 'rgb(45, 48, 56)'
}
}
)
)
]
)
def generate_graph(interval, specs_dict, col):
stats = state_dict[col]
col_data = stats['data']
mean = stats['mean']
ucl = specs_dict[col]['ucl']
lcl = specs_dict[col]['lcl']
usl = specs_dict[col]['usl']
lsl = specs_dict[col]['lsl']
x_array = state_dict['Batch']['data'].tolist()
y_array = col_data.tolist()
total_count = 0
if interval > max_length:
total_count = max_length - 1
elif interval > 0:
total_count = interval
ooc_trace = {'x': [],
'y': [],
'name': 'Out of Control',
'mode': 'markers',
'marker': dict(color='rgba(210, 77, 87, 0.7)', symbol="square", size=11)
}
for index, data in enumerate(y_array[:total_count]):
if data >= ucl or data <= lcl:
ooc_trace['x'].append(index + 1)
ooc_trace['y'].append(data)
histo_trace = {
'x': x_array[:total_count],
'y': y_array[:total_count],
'type': 'histogram',
'orientation': 'h',
'name': 'Distribution',
'xaxis': 'x2',
'yaxis': 'y2',
'marker': {'color': 'rgb(255,209,95)'}
}
fig = {
'data': [
{
'x': x_array[:total_count],
'y': y_array[:total_count],
'mode': 'lines+markers',
'name': col,
'line': {'color': 'rgb(255,209,95)'}
},
ooc_trace,
histo_trace
]
}
len_figure = len(fig['data'][0]['x'])
fig['layout'] = dict(
hovermode='closest',
uirevision=col,
paper_bgcolor='rgb(45, 48, 56)',
plot_bgcolor='rgb(45, 48, 56)',
legend={'font': {'color': '#95969A'}},
font={'color': '#95969A'},
showlegend=True,
xaxis={
'zeroline': False,
'title': 'Batch_Num',
'showline': False,
'domain': [0, 0.8],
'titlefont': {'color': '#95969A'}
},
yaxis={
'title': col,
'autorange': True,
'titlefont': {'color': '#95969A'}
},
annotations=[
{'x': 0.75, 'y': lcl, 'xref': 'paper', 'yref': 'y',
'text': 'LCL:' + str(round(lcl, 3)),
'showarrow': False},
{'x': 0.75, 'y': ucl, 'xref': 'paper', 'yref': 'y',
'text': 'UCL: ' + str(round(ucl, 3)),
'showarrow': False},
{'x': 0.75, 'y': usl, 'xref': 'paper', 'yref': 'y',
'text': 'USL: ' + str(round(usl, 3)),
'showarrow': False},
{'x': 0.75, 'y': lsl, 'xref': 'paper', 'yref': 'y',
'text': 'LSL: ' + str(round(lsl, 3)),
'showarrow': False},
{'x': 0.75, 'y': mean, 'xref': 'paper', 'yref': 'y',
'text': 'Targeted mean: ' + str(round(mean, 3)),
'showarrow': False}
],
shapes=[
{
'type': 'line',
'xref': 'x',
'yref': 'y',
'x0': 1,
'y0': usl,
'x1': len_figure + 1,
'y1': usl,
'line': {
'color': 'rgb(50, 171, 96)',
'width': 1,
'dash': 'dashdot'
}
},
{
'type': 'line',
'xref': 'x',
'yref': 'y',
'x0': 1,
'y0': lsl,
'x1': len_figure + 1,
'y1': lsl,
'line': {
'color': 'rgb(50, 171, 96)',
'width': 1,
'dash': 'dashdot'
}
},
{
'type': 'line',
'xref': 'x',
'yref': 'y',
'x0': 1,
'y0': ucl,
'x1': len_figure + 1,
'y1': ucl,
'line': {
'color': 'rgb(255,127,80)',
'width': 1,
'dash': 'dashdot'
}
},
{
'type': 'line',
'xref': 'x',
'yref': 'y',
'x0': 1,
'y0': mean,
'x1': len_figure + 1,
'y1': mean,
'line': {
'color': 'rgb(255,127,80)',
'width': 2
}
},
{
'type': 'line',
'xref': 'x',
'yref': 'y',
'x0': 1,
'y0': lcl,
'x1': len_figure + 1,
'y1': lcl,
'line': {
'color': 'rgb(255,127,80)',
'width': 1,
'dash': 'dashdot'
}
}
],
xaxis2={
'title': 'count',
'domain': [0.8, 1], # 70 to 100 % of width
'titlefont': {'color': '#95969A'}
},
yaxis2={
'anchor': 'free',
'overlaying': 'y',
'side': 'right',
'showticklabels': False,
'titlefont': {'color': '#95969A'}
}
)
return fig
@app.callback(
output=[Output('app-tabs', 'value'),
Output('app-content', 'children'),
Output('Specs-tab', 'disabled'),
Output('Control-chart-tab', 'disabled'),
Output('tab-trigger-btn', 'style')],
inputs=[Input('tab-trigger-btn', 'n_clicks')]
)
def render_tab_content(tab_switch):
if tab_switch == 0:
return 'tab1', build_tab_1(), False, True, {'display': 'inline-block', 'float': 'right'}
if tab_switch:
return ['tab2', daq.DarkThemeProvider(theme=theme, children=[
html.Div(
id='status-container',
children=[
build_quick_stats_panel(),
build_top_panel(),
build_chart_panel(),
]
)
]), True, False, {'display': 'none'}]
# ======= Callbacks for modal popup =======
@app.callback(Output("markdown", "style"),
[Input("learn-more-button", "n_clicks"), Input("markdown_close", "n_clicks")])
def update_click_output(button_click, close_click):
ctx = dash.callback_context
if ctx.triggered:
prop_id = ctx.triggered[0]['prop_id'].split('.')[0]
if prop_id == "learn-more-button":
return {"display": "block"}
return {'display': 'none'}
# Callbacks for stopping interval update
@app.callback(
[Output('interval-component', 'disabled'),
Output('stop-button', 'buttonText')],
[Input('stop-button', 'n_clicks')],
state=[State('interval-component', 'disabled')]
)
def stop_production(_, current):
return not current, "stop" if current else "start"
# ======= update progress gauge =========
@app.callback(
output=Output('progress-gauge', 'value'),
inputs=[Input('interval-component', 'n_intervals')]
)
def update_gauge(interval):
if interval < max_length:
total_count = interval
else:
total_count = max_length
return int(total_count)
def update_sparkline(interval, param):
x_array = state_dict['Batch']['data'].tolist()
y_array = state_dict[param]['data'].tolist()
if interval == 0:
x_new = y_new = None
else:
if interval >= max_length:
total_count = max_length
else:
total_count = interval
x_new = x_array[:total_count][-1]
y_new = y_array[:total_count][-1]
return dict(x=[[x_new]], y=[[y_new]]), [0]
def update_count(interval, col, data):
if interval == 0:
return '0', '0.00%', 0.00001, theme['primary']
elif interval > 0:
if interval >= max_length:
total_count = max_length - 1
else:
total_count = interval - 1
ooc_percentage_f = data[col]['ooc'][total_count] * 100
ooc_percentage_str = "%.2f" % ooc_percentage_f + '%'
# Set maximum ooc to 15 for better grad bar display
if ooc_percentage_f > 15:
ooc_percentage_f = 15
if ooc_percentage_f == 0.0:
ooc_grad_val = 0.00001
else:
ooc_grad_val = float(ooc_percentage_f)
# Set indicator theme according to threshold 5%
if 0 <= ooc_grad_val <= 5:
color = theme['primary']
else:
color = '#FF0000'
return str(total_count + 1), ooc_percentage_str, ooc_grad_val, color
# ======= update each row at interval =========
@app.callback(
output=[
Output(params[1] + suffix_count, 'children'),
Output(params[1] + suffix_sparkline_graph, 'extendData'),
Output(params[1] + suffix_ooc_n, 'children'),
Output(params[1] + suffix_ooc_g, 'value'),
Output(params[1] + suffix_indicator, 'color')
],
inputs=[Input('interval-component', 'n_intervals')],
state=[State('value-setter-store', 'data')]
)
def update_param1_row(interval, stored_data):
count, ooc_n, ooc_g_value, indicator = update_count(interval, params[1], stored_data)
spark_line_data = update_sparkline(interval, params[1])
return count, spark_line_data, ooc_n, ooc_g_value, indicator
@app.callback(
output=[
Output(params[2] + suffix_count, 'children'),
Output(params[2] + suffix_sparkline_graph, 'extendData'),
Output(params[2] + suffix_ooc_n, 'children'),
Output(params[2] + suffix_ooc_g, 'value'),
Output(params[2] + suffix_indicator, 'color')
],
inputs=[Input('interval-component', 'n_intervals')],
state=[State('value-setter-store', 'data')]
)
def update_param2_row(interval, stored_data):
count, ooc_n, ooc_g_value, indicator = update_count(interval, params[2], stored_data)
spark_line_data = update_sparkline(interval, params[2])
return count, spark_line_data, ooc_n, ooc_g_value, indicator
@app.callback(
output=[
Output(params[3] + suffix_count, 'children'),
Output(params[3] + suffix_sparkline_graph, 'extendData'),
Output(params[3] + suffix_ooc_n, 'children'),
Output(params[3] + suffix_ooc_g, 'value'),
Output(params[3] + suffix_indicator, 'color')
],
inputs=[Input('interval-component', 'n_intervals')],
state=[State('value-setter-store', 'data')]
)
def update_param3_row(interval, stored_data):
count, ooc_n, ooc_g_value, indicator = update_count(interval, params[3], stored_data)
spark_line_data = update_sparkline(interval, params[3])
return count, spark_line_data, ooc_n, ooc_g_value, indicator
@app.callback(
output=[
Output(params[4] + suffix_count, 'children'),
Output(params[4] + suffix_sparkline_graph, 'extendData'),
Output(params[4] + suffix_ooc_n, 'children'),
Output(params[4] + suffix_ooc_g, 'value'),
Output(params[4] + suffix_indicator, 'color')
],
inputs=[Input('interval-component', 'n_intervals')],
state=[State('value-setter-store', 'data')]
)
def update_param4_row(interval, stored_data):
count, ooc_n, ooc_g_value, indicator = update_count(interval, params[4], stored_data)
spark_line_data = update_sparkline(interval, params[4])
return count, spark_line_data, ooc_n, ooc_g_value, indicator
@app.callback(
output=[
Output(params[5] + suffix_count, 'children'),
Output(params[5] + suffix_sparkline_graph, 'extendData'),
Output(params[5] + suffix_ooc_n, 'children'),
Output(params[5] + suffix_ooc_g, 'value'),
Output(params[5] + suffix_indicator, 'color')
],
inputs=[Input('interval-component', 'n_intervals')],
state=[State('value-setter-store', 'data')]
)
def update_param5_row(interval, stored_data):
count, ooc_n, ooc_g_value, indicator = update_count(interval, params[5], stored_data)
spark_line_data = update_sparkline(interval, params[5])
return count, spark_line_data, ooc_n, ooc_g_value, indicator
@app.callback(
output=[
Output(params[6] + suffix_count, 'children'),
Output(params[6] + suffix_sparkline_graph, 'extendData'),
Output(params[6] + suffix_ooc_n, 'children'),
Output(params[6] + suffix_ooc_g, 'value'),
Output(params[6] + suffix_indicator, 'color')
],
inputs=[Input('interval-component', 'n_intervals')],
state=[State('value-setter-store', 'data')]
)
def update_param6_row(interval, stored_data):
count, ooc_n, ooc_g_value, indicator = update_count(interval, params[6], stored_data)
spark_line_data = update_sparkline(interval, params[6])
return count, spark_line_data, ooc_n, ooc_g_value, indicator
@app.callback(
output=[
Output(params[7] + suffix_count, 'children'),
Output(params[7] + suffix_sparkline_graph, 'extendData'),
Output(params[7] + suffix_ooc_n, 'children'),
Output(params[7] + suffix_ooc_g, 'value'),
Output(params[7] + suffix_indicator, 'color')
],
inputs=[Input('interval-component', 'n_intervals')],
state=[State('value-setter-store', 'data')]
)
def update_param7_row(interval, stored_data):
count, ooc_n, ooc_g_value, indicator = update_count(interval, params[7], stored_data)
spark_line_data = update_sparkline(interval, params[7])
return count, spark_line_data, ooc_n, ooc_g_value, indicator
# ======= button to choose/update figure based on click ============
@app.callback(
output=Output('control-chart-live', 'figure'),
inputs=[
Input('interval-component', 'n_intervals'),
Input(params[1] + suffix_button_id, 'n_clicks'),
Input(params[2] + suffix_button_id, 'n_clicks'),
Input(params[3] + suffix_button_id, 'n_clicks'),
Input(params[4] + suffix_button_id, 'n_clicks'),
Input(params[5] + suffix_button_id, 'n_clicks'),
Input(params[6] + suffix_button_id, 'n_clicks'),
Input(params[7] + suffix_button_id, 'n_clicks'),
],
state=[State("value-setter-store", 'data'), State('control-chart-live', 'figure')]
)
def update_control_chart(interval, n1, n2, n3, n4, n5, n6, n7, data, cur_fig):
# Find which one has been triggered
ctx = dash.callback_context
if not ctx.triggered:
return generate_graph(interval, data, params[1])
if ctx.triggered:
# Get most recently triggered id and prop_type
splitted = ctx.triggered[0]['prop_id'].split('.')
prop_id = splitted[0]
prop_type = splitted[1]
if prop_type == 'n_clicks':
curr_id = cur_fig['data'][0]['name']
prop_id = prop_id[:-7]
if curr_id == prop_id:
return generate_graph(interval, data, curr_id)
else:
return generate_graph(interval, data, prop_id)
if prop_type == 'n_intervals' and cur_fig is not None:
curr_id = cur_fig['data'][0]['name']
return generate_graph(interval, data, curr_id)
# Update piechart
@app.callback(
output=Output('piechart', 'figure'),
inputs=[
Input('interval-component', 'n_intervals')],
state=[State("value-setter-store", 'data')]
)
def update_piechart(interval, stored_data):
if interval == 0:
return {'data': [], 'layout': {'font': {'color': '#95969A'},
'paper_bgcolor': 'rgb(45, 48, 56)',
'plot_bgcolor': 'rgb(45, 48, 56)'}}
if interval >= max_length:
total_count = max_length - 1
else:
total_count = interval - 1
values = []
colors = []
for param in params[1:]:
ooc_param = (stored_data[param]['ooc'][total_count] * 100) + 1
values.append(ooc_param)
if ooc_param > 6:
colors.append('rgb(206,0,5)')
else:
colors.append('rgb(0, 116, 57)')
new_figure = {
'data': [
{
'labels': params[1:],
'values': values,
'type': 'pie',
'marker': {'colors': colors, 'line': dict(color='#53555B', width=2)},
'hoverinfo': 'label',
'textinfo': 'label'
}],
'layout': {
'uirevision': True,
'font': {'color': '#95969A'},
'showlegend': True,
'legend': {'font': {'color': '#95969A'}},
'paper_bgcolor': 'rgb(45, 48, 56)',
'plot_bgcolor': 'rgb(45, 48, 56)'
}
}
return new_figure
# Running the server
if __name__ == '__main__':
app.run_server(debug=True, port=8050)