from dash import Dash, dcc, html, Input, Output
import dash_bootstrap_components as dbc
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import os
import dash_ag_grid as dag
# Color palette
COLORS = {
'primary': '#228B22',
'text': '#222831',
'background': '#FFFFFF',
'pie_colors': [
'#145A32', '#229954', '#27AE60', '#52BE80', '#7DCEA0',
'#00B894', '#00B8A9', '#74B9FF', '#A3CB38', '#DFF9FB',
]
}
# Load and filter data (only 2020 data)
DATA_PATH = 'EV_2020.csv'
if not os.path.exists(DATA_PATH):
raise FileNotFoundError(f"CSV file not found at {DATA_PATH}")
df = pd.read_csv(DATA_PATH)
makes = sorted(df['Make'].dropna().unique())
makes.insert(0, "All")
default_make = makes[0]
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.title = "EV Dashboard"
app.layout = dbc.Container([
dbc.Row([
dbc.Col(
html.H1("Washington State Electric Vehicle (Model year:2020)", className="my-4 text-left",
style={'color': COLORS['primary'], 'fontWeight': 'bold'}),
width=12
)
]),
dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardBody([
html.Label("Manufacturer:", style={'color': COLORS['text'], 'fontWeight': 'bold'}),
dcc.Dropdown(
id="make-dropdown",
options=[{"label": make, "value": make} for make in makes],
value=default_make,
clearable=False,
style={'backgroundColor': COLORS['background'], 'color': COLORS['text']}
),
])
], style={'backgroundColor': COLORS['background'], 'border': 'none'})
], width=3),
dbc.Col(width=9)
], className="mb-4"),
dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardHeader(html.H5("Top 10 Manufacturers", style={'color': COLORS['primary']})),
dbc.CardBody([dcc.Graph(id="pie_chart", config={'displayModeBar': False}, style={'height': '600px'})])
], style={'backgroundColor': COLORS['background'], 'border': 'none'})
], width=6),
dbc.Col([
dbc.Card([
dbc.CardHeader(html.H5("Top 10 Models by Range", style={'color': COLORS['primary']})),
dbc.CardBody([dcc.Graph(id="top_10_chart", config={'displayModeBar': False}, style={'height': '600px'})])
], style={'backgroundColor': COLORS['background'], 'border': 'none'})
], width=6),
], className="mb-4"),
dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardHeader(html.H5("All Data (including all columns)", style={'color': COLORS['primary']})),
dbc.CardBody([
dag.AgGrid(
id='table',
columnDefs=[{'headerName': col, 'field': col} for col in df.columns],
rowData=[],
defaultColDef={'sortable': True, 'filter': True},
style={'height': '500px'}
)
])
], style={'backgroundColor': COLORS['background'], 'border': 'none'})
], width=12)
], className="mb-4")
], fluid=True, style={
'backgroundColor': COLORS['background'],
'padding': '40px',
'margin': '40px',
'borderRadius': '15px',
'boxShadow': '0 4px 8px rgba(0,0,0,0.1)'
})
@app.callback(
Output("top_10_chart", "figure"),
Output("pie_chart", "figure"),
Output("table", "rowData"),
Input("make-dropdown", "value")
)
def update_dashboard(selected_make):
# Filter data based on selected make
if selected_make == "All":
dff = df[
(df['Electric Range'].notna()) &
(df['Electric Range'] > 0)
]
else:
dff = df[
(df['Make'] == selected_make) &
(df['Electric Range'].notna()) &
(df['Electric Range'] > 0)
]
# Top 10 models by max range
grouped_df = (
dff.groupby(['Make', 'Model'])['Electric Range']
.max()
.reset_index()
.sort_values('Electric Range', ascending=False)
.head(10)
.sort_values('Electric Range', ascending=True)
)
grouped_df['Make_Model'] = grouped_df['Make'] + ' ' + grouped_df['Model']
fig_top10 = px.bar(
grouped_df,
x='Electric Range',
y='Make_Model',
orientation='h',
color_discrete_sequence=[COLORS['primary']]
)
fig_top10.update_layout(
plot_bgcolor=COLORS['background'],
paper_bgcolor=COLORS['background'],
font_color=COLORS['text'],
margin=dict(l=20, r=20, t=20, b=20),
showlegend=False,
xaxis_title="Range (miles)",
yaxis_title="Model",
height=600
)
# Pie chart: top 10 manufacturers by count
pie_df = (
dff.groupby('Make')
.size()
.reset_index(name='Count')
.sort_values('Count', ascending=False)
.head(10)
)
fig_pie = go.Figure()
fig_pie.add_trace(go.Pie(
labels=pie_df['Make'],
values=pie_df['Count'],
hole=0.6,
marker=dict(colors=COLORS['pie_colors']),
textinfo='label+percent',
textposition='outside',
textfont=dict(color=COLORS['text']),
hoverinfo='label+value'
))
fig_pie.add_layout_image(dict(
source="https://cdn-icons-png.flaticon.com/512/8587/8587847.png",
xref="paper", yref="paper",
x=0.5, y=0.5,
sizex=0.3, sizey=0.3,
xanchor="center", yanchor="middle",
layer="above"
))
fig_pie.update_layout(
plot_bgcolor=COLORS['background'],
paper_bgcolor=COLORS['background'],
font_color=COLORS['text'],
margin=dict(l=20, r=20, t=20, b=20),
height=600,
showlegend=True
)
# Prepare data for grid (table)
table_data = dff # Keep all columns
return fig_top10, fig_pie, table_data.to_dict('records')
if __name__ == "__main__":
app.run(debug=True)