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)