import dash
from dash import dcc, html, Input, Output
import dash_bootstrap_components as dbc
import plotly.graph_objects as go
import pandas as pd
import plotly.colors
# Load data
df = pd.read_csv("https://raw.githubusercontent.com/plotly/Figure-Friday/refs/heads/main/2025/week-19/TLC_New_Driver_Application.csv")
df['App Date'] = pd.to_datetime(df['App Date'], errors='coerce')
STATUSES = list(df['Status'].dropna().unique())
COLOR_LIST = plotly.colors.qualitative.D3
STATUS_COLOR_MAP = {
status: COLOR_LIST[i % len(COLOR_LIST)]
for i, status in enumerate(STATUSES)
}
PALETTE = list(STATUS_COLOR_MAP.values())
KPI_BG = ["whitesmoke"]
KPI_CARD_STYLE = {
"boxShadow": "0px 4px 10px lightgrey",
"marginBottom": "14px",
"border": "none",
"height": "202px"
}
KPI_BODY_STYLE = {
"display": "flex",
"flexDirection": "column",
"alignItems": "center",
"justifyContent": "center",
"height": "100%",
"padding": "18px 0"
}
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.layout = dbc.Container([
dbc.Row([
dbc.Col(
html.H1(
'TLC Driver Applications',
style={
"font-family": "Curier New, Courier, monospace",
"color": "black",
"fontSize": "2.6rem",
"fontWeight": "bold",
"marginBottom": "0",
"marginLeft": "10px",
"whiteSpace": "nowrap"
}
),
width="auto",
style={"display": "flex", "alignItems": "center"}
),
dbc.Col([
dbc.Row([
dbc.Col([
html.Label('Status:', style={"color": "black", "fontWeight": "bold", "font-size":"20px"}),
dcc.Dropdown(
id='status-filter',
options=[{'label': s, 'value': s} for s in STATUSES],
value=None,
placeholder='Select status',
multi=True,
style={"width": "80%"}
),
], width=6),
dbc.Col([
html.Label('Date:', style={"color": "black", "fontWeight": "bold"}),
html.Div(
dcc.DatePickerRange(
id='date-filter',
min_date_allowed=df['App Date'].min(),
max_date_allowed=df['App Date'].max(),
start_date=df['App Date'].min(),
end_date=df['App Date'].max(),
display_format='YYYY-MM-DD',
style={"width": "80%"}
),
style={
"backgroundColor": "white",
"borderRadius": "6px",
"padding": "6px"
}
),
], width=6),
], justify="end")
], width=8)
], className="mb-4", align="right"),
dbc.Row([
dbc.Col(
html.Div(id="kpi-cards", style={"height": "100%"}),
width=2
),
dbc.Col([
dbc.Card([
dbc.CardHeader(
"Applications by Date and Status (Stacked Bar Chart)",
style={"background": "#EEEEEE", "color": PALETTE[0]}
),
dbc.CardBody(dcc.Graph(id='grouped-bar-chart'))
], className="mb-4", style={"background": "#fff", "boxShadow": "0 4px 16px 0 rgba(19,59,92,0.10)"}),
dbc.Card([
dbc.CardHeader(
"Applications Over Time (Total, Line Chart)",
style={"background": "#EEEEEE", "color": PALETTE[0]}
),
dbc.CardBody(dcc.Graph(id='apps-line'))
], className="mb-4", style={"background": "#fff", "boxShadow": "0 4px 16px 0 rgba(19,59,92,0.10)"})
], width=10)
])
], fluid=True)
@app.callback(
Output('kpi-cards', 'children'),
Output('grouped-bar-chart', 'figure'),
Output('apps-line', 'figure'),
Input('status-filter', 'value'),
Input('date-filter', 'start_date'),
Input('date-filter', 'end_date')
)
def update_graphs(selected_status, start_date, end_date):
dff = df.copy()
if selected_status:
dff = dff[dff['Status'].isin(selected_status)]
if start_date and end_date:
dff = dff[(dff['App Date'] >= pd.to_datetime(start_date)) & (dff['App Date'] <= pd.to_datetime(end_date))]
# Status KPI-k
status_counts = dff['Status'].value_counts().reindex(STATUSES).fillna(0).astype(int)
kpi_cards = []
for i, (status, count) in enumerate(status_counts.items()):
kpi_cards.append(
dbc.Card([
dbc.CardBody([
html.H5(f"{status}", className="card-title text-center", style={
"color": "#111", "fontSize": "1.2rem"
}),
html.H1(f"{count}", className="card-text", style={
"color": "#111",
"textAlign": "center",
"fontWeight": "bold",
"fontSize": "3rem"
})
], style=KPI_BODY_STYLE)
], className="mb-4", style={**KPI_CARD_STYLE, "background": KPI_BG[i % len(KPI_BG)]})
)
# 1. Stacked bar chart
if not dff.empty and 'App Date' in dff.columns and 'Status' in dff.columns:
dff['Date'] = dff['App Date'].dt.date
grouped = dff.groupby(['Date', 'Status']).size().reset_index(name='Count')
pivot = grouped.pivot(index='Date', columns='Status', values='Count').fillna(0)
pivot = pivot.sort_index()
fig_bar = go.Figure()
for status in STATUSES:
if status in pivot.columns:
fig_bar.add_trace(go.Bar(
x=pivot.index,
y=pivot[status],
name=str(status),
marker_color=STATUS_COLOR_MAP.get(status, "#CCCCCC"),
opacity=0.85
))
fig_bar.update_layout(
barmode='stack',
margin=dict(l=20, r=20, t=40, b=20),
height=480,
plot_bgcolor="#fff",
paper_bgcolor="#fff",
font_color=PALETTE[0],
xaxis_title="",
yaxis_title="Applications",
showlegend=True,
legend=dict(
orientation="h",
yanchor="bottom",
y=1.08,
xanchor="center",
x=0.5,
font=dict(size=14)
)
)
else:
fig_bar = go.Figure()
fig_bar.add_annotation(text="No data", x=0.5, y=0.5, showarrow=False)
# 2. Applications over time
if not dff.empty and 'App Date' in dff.columns and 'App No' in dff.columns:
date_counts = dff.groupby(dff['App Date'].dt.date)['App No'].count().reset_index()
date_counts.columns = ['Date', 'Applications']
fig_line = go.Figure()
fig_line.add_trace(go.Scatter(
x=date_counts['Date'],
y=date_counts['Applications'],
mode='lines+markers',
line=dict(shape='spline', color='lightgrey', width=4),
marker=dict(size=8, color='lightblue', line=dict(width=1, color=PALETTE[0]))
))
fig_line.update_layout(
legend=dict(
orientation="h",
yanchor="bottom",
y=1.08,
xanchor="center",
x=0.5,
font=dict(size=14)
)
)
else:
fig_line = go.Figure()
fig_line.add_annotation(text="No data", x=0.5, y=0.5, showarrow=False)
fig_line.update_layout(
margin=dict(l=20, r=20, t=40, b=20),
height=450,
plot_bgcolor="#fff",
paper_bgcolor="#fff",
font_color=PALETTE[0]
)
return kpi_cards, fig_bar, fig_line
if __name__ == '__main__':
app.run(debug=True)