OK here we go, I had to do lot of cleaning
How does it look? :
from dash import html, Output, Input, State, dcc, Dash, no_update, Patch
import dash_mantine_components as dmc
import plotly.express as px
import pandas as pd
import sqlite3
app = Dash(name=__name__)
con = sqlite3.connect('factory_db.db')
# extract only the columns we use
df = pd.read_sql(
'sources', con=con,
columns=["client_name", "shift_day", "endpoint_name", "state_begin", "state_end", 'reason',
'duration_min', 'color', 'state', 'period_name', 'operator']
)
# modify reason 'The reason is not specified' to 'Not specified'
df['reason'].replace('The reason is not specified', 'Not specified', inplace=True)
CARD_STYLE = dict(withBorder=True, shadow="sm", radius="md", style={'height': '450px'})
REASON_COLORS = dict(zip(df['reason'].unique(), df['color'].unique()))
# create custom labels like 'client_name' to 'Client name'
LABELS = {label: label.replace('_', ' ').capitalize() for label in df.columns}
# Init figures
fig_pie = px.pie(df, values='duration_min', names='reason', color='reason',
color_discrete_map=REASON_COLORS, labels=LABELS)
fig_timeline = px.timeline(
df,
x_start='state_begin',
x_end='state_end',
y='endpoint_name',
color='reason',
color_discrete_map=REASON_COLORS,
labels=LABELS,
custom_data=['state', 'reason', 'duration_min', "shift_day", 'period_name', 'operator']
)
fig_timeline.update_traces(
hovertemplate=(
'<br>State: <b>%{customdata[0]}</b>'
'<br>Reason: <b>%{customdata[1]}</b>'
'<br>Beginning: <b>%{base|%H:%M:%S</b> (%d.%m)}'
'<br>Duration: <b>%{customdata[2]:.2f}</b> min.'
'<br>'
'<br>Shift day: <b>%{customdata[3]|%d.%m.%Y}</b>'
'<br>Shift: <b>%{customdata[4]}</b>'
'<br>Operator: <b>%{customdata[5]}</b><extra></extra>'
)
)
fig_timeline.update_xaxes(side='top', dtick=3.6e+6, tickangle=0, tickformat='%H\n%Y-%m-%d')
fig_timeline.update_yaxes(title_text=None)
fig_timeline.update_layout(showlegend=False)
app.layout = dmc.Paper([
dmc.Grid([
dmc.Col([
dmc.Card([
html.H2(f'Client: {df["client_name"][0]}'),
html.H3(f'Shift day: {df["shift_day"][0]}'),
html.H3(f'Endpoint name: {df["endpoint_name"][0]}'),
html.H3(f'State begin: {df["state_begin"].min()[:-4]}'),
html.H3(f'State end: {df["state_end"].max()[:-4]}'),
html.P("Select reasons to highlight:"),
dcc.Dropdown(
id='dropdown-input',
value=df['reason'].unique(), options=df['reason'].unique(),
multi=True, placeholder='Select reasons'
),
dmc.Button('Filter', id='dropdown-button'),
], **CARD_STYLE)
], span=6),
dmc.Col([
dmc.Card(
dcc.Graph(figure=fig_pie),
**CARD_STYLE)
], span=6),
dmc.Col([
dmc.Card(
dcc.Graph(id='timeline-output', figure=fig_timeline),
**CARD_STYLE)
], span=12),
], gutter="xl")
])
@app.callback(
Output('timeline-output', 'figure'),
Input('dropdown-button', 'n_clicks'),
State('dropdown-input', 'value'),
State('timeline-output', 'figure'),
prevent_initial_call=True,
)
def graph_filtering(_, reasons, fig):
if not reasons:
return no_update
# get the index list of the traces (reason groups) to highlight
filtered_traces = [i for i in range(len(fig['data'])) if fig['data'][i]['name'] in reasons]
# set the opacity to 1 for selected reasons and 0.2 for the others
# using the new feature Patch to update only the prop 'opacity' without updating the whole figure
patched_fig = Patch()
for i in range(len(fig['data'])):
patched_fig['data'][i]['opacity'] = 1 if i in filtered_traces else 0.2
return patched_fig
if __name__ == '__main__':
app.run_server(debug=True)