# check out https://dash.plotly.com/ for documentation
# And check out https://py.cafe/maartenbreddels for more examples
import dash
from dash import dcc, html, Input, Output
import plotly.express as px
import pandas as pd
# Load the data
df = pd.read_csv('Building_Permits_Issued_Past_180_Days.csv')
# Initialize the Dash app
app = dash.Dash(__name__, suppress_callback_exceptions=True)
# Add global CSS styling
app.index_string = '''
<!DOCTYPE html>
<html>
<head>
{%metas%}
<title>{%title%}</title>
{%favicon%}
{%css%}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
* {
font-family: Arial, sans-serif !important;
}
body {
background-image: url('assets/construction_site.jpg');
background-size: cover;
background-position: center;
background-attachment: fixed;
background-repeat: no-repeat;
margin: 0;
padding: 0;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.5);
z-index: -1;
}
</style>
</head>
<body>
{%app_entry%}
<footer>
{%config%}
{%scripts%}
{%renderer%}
</footer>
</body>
</html>
'''
# Define the layout
app.layout = html.Div([
html.Div([
html.H1("Raleigh Building Permits Analysis",
style={'textAlign': 'center', 'marginBottom': 10, 'fontFamily': 'Arial'}),
dcc.Tabs(id="main-tabs", value='project-types-tab', children=[
dcc.Tab(
label='📋 Project Types',
value='project-types-tab',
style={'fontFamily': 'Arial'}
),
dcc.Tab(
label='⛑️ Top Contractors',
value='contractors-tab',
style={'fontFamily': 'Arial'}
),
], style={'fontFamily': 'Arial'}),
html.Div(id='tabs-content', style={'marginTop': 30})
], style={'width': '75%', 'margin': '0 auto', 'padding': '20px', 'backgroundColor': 'rgba(255, 255, 255, 0.95)', 'borderRadius': '15px', 'boxShadow': '0 4px 15px rgba(0,0,0,0.1)', 'marginTop': '20px', 'marginBottom': '20px'})
], style={'fontFamily': 'Arial'})
# Callback to render tab content
@app.callback(
Output('tabs-content', 'children'),
Input('main-tabs', 'value')
)
def render_content(tab):
if tab == 'project-types-tab':
return html.Div([
html.Div([
html.Label("Permit Class:", style={'fontWeight': 'bold', 'marginBottom': 10, 'fontFamily': 'Arial'}),
dcc.RadioItems(
id='permit-class-radio',
options=[
{'label': 'Residential', 'value': 'Residential'},
{'label': 'Non-Residential', 'value': 'Non-Residential'}
],
value='Residential',
style={'marginBottom': 30, 'fontFamily': 'Arial'}
)
], style={'textAlign': 'center'}),
dcc.Graph(id='project-type-chart')
])
elif tab == 'contractors-tab':
return html.Div([
html.Div([
html.Label("Permit Class:", style={'fontWeight': 'bold', 'marginBottom': 10, 'fontFamily': 'Arial'}),
dcc.RadioItems(
id='contractor-permit-class-radio',
options=[
{'label': 'Residential', 'value': 'Residential'},
{'label': 'Non-Residential', 'value': 'Non-Residential'}
],
value='Residential',
style={'marginBottom': 30, 'fontFamily': 'Arial'}
)
], style={'textAlign': 'center'}),
html.Div(id='outlier-text', style={'textAlign': 'center', 'marginBottom': 20, 'fontFamily': 'Arial', 'fontStyle': 'italic', 'color': '#666'}),
dcc.Graph(id='contractor-bubble-chart')
])
# Callback to update the chart based on radio button selection
@app.callback(
Output('project-type-chart', 'figure'),
Input('permit-class-radio', 'value')
)
def update_chart(selected_class):
# Filter data based on selected permit class
filtered_df = df[df['permit_class_mapped'] == selected_class]
# Count project types
project_counts = filtered_df['class_work'].value_counts().reset_index()
project_counts.columns = ['Project Type', 'Count']
# Create horizontal bar chart
fig = px.bar(
project_counts,
x='Count',
y='Project Type',
orientation='h',
title=f'Project Types - {selected_class} Permits',
labels={'Count': 'Number of Permits', 'Project Type': 'Project Type'}
)
# Update layout for better appearance
fig.update_layout(
height=600,
yaxis={'categoryorder': 'total ascending'},
margin=dict(l=150, r=50, t=80, b=50),
font=dict(family="Arial")
)
return fig
# Callback for contractor bubble chart
@app.callback(
[Output('contractor-bubble-chart', 'figure'),
Output('outlier-text', 'children')],
Input('contractor-permit-class-radio', 'value')
)
def update_contractor_chart(selected_class):
# Filter data based on selected permit class
filtered_df = df[df['permit_class_mapped'] == selected_class]
# Remove null contractor names and calculate contractor metrics
contractor_df = filtered_df[filtered_df['contractor_company_name'].notna()]
contractor_summary = contractor_df.groupby('contractor_company_name').agg({
'contractor_company_name': 'count', # Number of contracts
'estimated_project_cost': ['sum', 'mean'] # Total and average cost
}).reset_index()
# Flatten column names
contractor_summary.columns = ['contractor_company_name', 'number_of_contracts', 'total_estimated_cost', 'avg_contract_price']
# Filter out Evans General Contractors for Non-Residential
if selected_class == 'Non-Residential':
contractor_summary = contractor_summary[contractor_summary['contractor_company_name'] != 'Evans General Contractors']
# Sort by total estimated cost and take top 15
contractor_summary = contractor_summary.sort_values('total_estimated_cost', ascending=False).head(15)
# Create bubble chart
fig = px.scatter(
contractor_summary,
x='number_of_contracts',
y='avg_contract_price',
size='total_estimated_cost',
hover_name='contractor_company_name',
hover_data={
'number_of_contracts': True,
'avg_contract_price': ':,.2f',
'total_estimated_cost': ':,.0f'
},
title=f'Top Contractors - {selected_class} Permits',
labels={
'number_of_contracts': 'Number of Contracts',
'avg_contract_price': 'Average Contract Price ($)',
'total_estimated_cost': 'Total Contracts ($)'
}
)
# Update layout
fig.update_layout(
height=600,
margin=dict(l=80, r=50, t=80, b=50),
font=dict(family="Arial")
)
# Format y-axis as currency
fig.update_yaxes(tickformat='$,.0f')
# Handle outlier text for Non-Residential
outlier_text = ""
if selected_class == 'Non-Residential':
outlier_text = "Note: Evans General Contractors excluded from chart (1 contract, $80,544,979)"
return fig, outlier_text
# Run the app
if __name__ == '__main__':
app.run(debug=True)