import dash
from dash import dcc, html, Input, Output
import dash_bootstrap_components as dbc
import plotly.express as px
import pandas as pd
# --- Data Processing ---
df_nation = pd.read_csv("nation.csv")
nation_column_mapping = {
'Nation': 'Nation',
'Year': 'Year',
'Total CO2 emissions from fossil-fuels and cement production (thousand metric tons of C)': 'Total_Emissions',
'Emissions from solid fuel consumption': 'Solid_Fuel_Emissions',
'Emissions from liquid fuel consumption': 'Liquid_Fuel_Emissions',
'Emissions from gas fuel consumption': 'Gas_Fuel_Emissions',
'Emissions from cement production': 'Cement_Production_Emissions',
'Emissions from gas flaring': 'Gas_Flaring_Emissions',
'Per capita CO2 emissions (metric tons of carbon)': 'Per_Capita_Emissions',
'Emissions from bunker fuels (not included in the totals)': 'Bunker_Fuels'
}
df_nation = df_nation.rename(columns=nation_column_mapping)
emission_source_columns = [
'Solid_Fuel_Emissions', 'Liquid_Fuel_Emissions', 'Gas_Fuel_Emissions',
'Cement_Production_Emissions', 'Gas_Flaring_Emissions'
]
emission_source_labels = {
'Solid_Fuel_Emissions': 'Solid Fuel',
'Liquid_Fuel_Emissions': 'Liquid Fuel',
'Gas_Fuel_Emissions': 'Gas Fuel',
'Cement_Production_Emissions': 'Cement Production',
'Gas_Flaring_Emissions': 'Gas Flaring'
}
year_min = int(df_nation['Year'].min())
year_max = int(df_nation['Year'].max())
default_range = [max(year_min, year_max-20), year_max]
emission_source_options = [{'label': 'All', 'value': 'All'}] + [
{'label': emission_source_labels.get(col, col.replace('_', ' ')), 'value': col}
for col in emission_source_columns
]
CONTENT_STYLE = {
"marginLeft": "0px",
"marginRight": "0px",
"padding": "1.2rem 1rem",
"background": "white",
"minHeight": "100vh",
"fontSize": "1.2rem"
}
CARD_STYLE = {
"backgroundColor": "white",
"borderRadius": "1rem",
"boxShadow": "0 4px 16px rgba(0,0,0,0.10)",
"border": "10px solid white"
}
CARDHEADER_STYLE = {
"backgroundColor": "white",
"fontWeight": "bold",
"fontSize": "1.3rem",
"borderBottom": "none"
}
PIE_COLOR_MAP = {
"Solid Fuel": "#c7522a",
"Liquid Fuel": "#53534f",
"Gas Fuel": "#9a9995",
"Cement Production": "#74a892",
"Gas Flaring": "#008585"
}
# Only first and last year marks, black color
marks = {
str(year_min): {'label': str(year_min), 'style': {'color': 'black'}},
str(year_max): {'label': str(year_max), 'style': {'color': 'black'}}
}
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.LUX])
app.title = "CO2 Emissions Dashboard"
app.layout = html.Div([
html.H2(
"CO₂ Emissions globally and nation-wise",
className="text-primary text-left mb-4",
style={"fontSize": "2.5rem"}
),
dbc.Row([
dbc.Col([
dbc.Label("Select Emission Source:", html_for="emission-source-dropdown", className="text-dark", style={"fontSize": "1.2rem"}),
dbc.Select(
id='emission-source-dropdown',
options=emission_source_options,
value='All',
className="mb-2",
style={"fontSize": "1.1rem", "height": "3.2rem"}
),
], md=4),
dbc.Col([
dbc.Label("Select Year Range:", html_for="year-range-slider", style={"fontSize": "1.2rem"}),
dcc.RangeSlider(
id='year-range-slider',
min=1900,
max=year_max,
value=default_range,
marks=marks,
step=1,
updatemode='drag',
tooltip={"placement": "bottom", "always_visible": True},
className="mb-2"
),
], md=5, style={"paddingTop": "1.7rem"}),
dbc.Col([
dbc.Label("Emission Type:", html_for="emission-type-radio", className="text-dark", style={"fontSize": "1.2rem"}),
dbc.RadioItems(
id='emission-type-radio',
options=[
{'label': 'Total', 'value': 'total'},
{'label': 'Per Capita', 'value': 'per_capita'}
],
value='total',
inline=True,
className="mb-2"
),
], md=3, style={"paddingTop": "1.7rem"}),
], className="mb-4"),
dcc.Tabs(
id="main-tabs",
value="tab-1",
children=[
dcc.Tab(label="Trends & Source Share", value="tab-1", children=[
dbc.Row([
dbc.Col(
dbc.Card([
dbc.CardHeader("Emissions Over Time (World)", style=CARDHEADER_STYLE),
dbc.CardBody(
dcc.Graph(id='line-chart', config={'displayModeBar': False}, style={"height": "50vh"}),
className="p-2"
)
], style={**CARD_STYLE, "height": "60vh"}, className="h-100"),
md=8,
className="mb-4"
),
dbc.Col(
dbc.Card([
dbc.CardHeader("Source Share ", style=CARDHEADER_STYLE),
dbc.CardBody(
dcc.Graph(id='pie-chart', config={'displayModeBar': False}, style={"height": "50vh"}),
className="p-2"
)
], style={**CARD_STYLE, "height": "60vh"}, className="h-100"),
md=4,
className="mb-4"
),
]),
]),
dcc.Tab(label="Map & Top 10", value="tab-2", children=[
dbc.Row([
dbc.Col(
dbc.Card([
dbc.CardHeader("Emissions Map", style=CARDHEADER_STYLE),
dbc.CardBody(
dcc.Graph(id='map-chart', config={'displayModeBar': False}, style={"height": "50vh"}),
className="p-2"
)
], style={**CARD_STYLE, "height": "60vh"}, className="h-100"),
md=8,
className="mb-4"
),
dbc.Col(
dbc.Card([
dbc.CardHeader("Top 10 Countries", style=CARDHEADER_STYLE),
dbc.CardBody(
dcc.Graph(id='bar-chart', config={'displayModeBar': False}, style={"height": "50vh"}),
className="p-2"
)
], style={**CARD_STYLE, "height": "60vh"}, className="h-100"),
md=4,
className="mb-4"
),
]),
]),
dcc.Tab(label="Country Comparison", value="tab-3", children=[
dbc.Row([
dbc.Col([
dbc.Label("Select countries:", html_for="country-multiselect", style={"fontSize": "1.2rem"}),
dcc.Dropdown(
id="country-multiselect",
options=[
{"label": c, "value": c}
for c in sorted(df_nation["Nation"].unique())
],
value=["Hungary"],
multi=True,
style={"fontSize": "1.1rem"}
),
], md=6),
], className="mb-4"),
dbc.Row([
dbc.Col(
dbc.Card([
dbc.CardHeader("Emissions of selected countries", style=CARDHEADER_STYLE),
dbc.CardBody(
dcc.Graph(id="country-multiline-chart", config={'displayModeBar': False}, style={"height": "60vh"}),
className="p-2"
)
], style=CARD_STYLE),
md=12
)
])
])
],
style={"marginBottom": "2rem"}
),
dbc.Row(
dbc.Col(
html.P(
"Data Source: CDIAC/Appalachian State University, Boden, T.A., G. Marland, and R.J. Andres. 2017.",
className="text-center text-muted small",
style={"fontSize": "1rem", "marginTop": "80px"}
),
width=12
)
)
], style=CONTENT_STYLE, id="content")
@app.callback(
[
Output('line-chart', 'figure'),
Output('pie-chart', 'figure'),
Output('bar-chart', 'figure'),
Output('map-chart', 'figure')
],
[
Input('year-range-slider', 'value'),
Input('emission-source-dropdown', 'value'),
Input('emission-type-radio', 'value')
]
)
def update_charts(year_range, selected_source, emission_type):
start_year, end_year = year_range
if selected_source == 'All':
sources = emission_source_columns
else:
sources = [selected_source] if selected_source in emission_source_columns else []
if not sources:
empty_fig = px.line(title="No data selected")
return empty_fig, empty_fig, empty_fig, empty_fig
# --- Line chart: World, for the selected time range ---
world_df = df_nation[(df_nation['Year'] >= start_year) & (df_nation['Year'] <= end_year)].copy()
if emission_type == 'total':
world_grouped = world_df.groupby('Year')[sources].sum().reset_index()
world_melted = world_grouped.melt(id_vars=['Year'], value_vars=sources, var_name='Source', value_name='Emissions')
y_label = "Emissions (ktC)"
else:
world_grouped = world_df.groupby('Year').agg({
**{col: 'sum' for col in sources},
'Per_Capita_Emissions': 'mean'
}).reset_index()
for col in sources:
world_grouped[col] = world_grouped[col] / (world_grouped['Per_Capita_Emissions'] if (world_grouped['Per_Capita_Emissions'] > 0).all() else 1)
world_melted = world_grouped.melt(id_vars=['Year'], value_vars=sources, var_name='Source', value_name='Emissions')
y_label = "Emissions per capita (tC)"
world_melted['Source'] = world_melted['Source'].map(emission_source_labels).fillna(world_melted['Source'])
line_fig = px.area(
world_melted,
x="Year",
y="Emissions",
color="Source",
labels={
"Emissions": y_label,
"Year": "",
"Source": "Emission Source"
},
color_discrete_map=PIE_COLOR_MAP,
title=None
)
for i, trace in enumerate(line_fig.data):
line_fig.data[i].line.width = 7
line_fig.update_layout(
plot_bgcolor="white",
paper_bgcolor="white",
title_x=0.5,
margin=dict(t=40, b=30, l=30, r=30),
legend=dict(
orientation="v",
yanchor="bottom",
y=1.02,
xanchor="right",
x=1
),
legend_title_text=""
)
# --- Pie chart: Source share, for the last year in the selected range ---
pie_df = df_nation[df_nation['Year'] == end_year]
if emission_type == 'total':
pie_data = pie_df[sources].sum().reset_index()
pie_data.columns = ['Source', 'Emissions']
pie_unit = "ktC"
else:
pie_data = []
for col in sources:
if col in pie_df.columns and 'Per_Capita_Emissions' in pie_df.columns:
pop = pie_df[col] / (pie_df['Per_Capita_Emissions'].replace(0, 1))
per_capita = pie_df[col] / pop.replace(0, 1)
avg_per_capita = per_capita.mean()
pie_data.append({'Source': emission_source_labels.get(col, col), 'Emissions': avg_per_capita})
pie_data = pd.DataFrame(pie_data)
pie_unit = "tC/capita"
pie_data['Source'] = pie_data['Source'].map(emission_source_labels).fillna(pie_data['Source'])
total_emissions = pie_data['Emissions'].sum()
total_text = f"<b>{total_emissions:,.2f}</b><br><b>{pie_unit}</b><br><span style='font-size:14px'>Total Emissions</span>"
pie_fig = px.pie(
pie_data,
names='Source',
values='Emissions',
color='Source',
color_discrete_map=PIE_COLOR_MAP,
title=None,
hole=0.7
)
pie_fig.add_annotation(
text=total_text,
x=0.5, y=0.5, font=dict(size=20, color="black"),
showarrow=False
)
pie_fig.update_layout(
plot_bgcolor="white",
paper_bgcolor="white",
title_x=0.5,
margin=dict(t=40, b=30, l=30, r=30),
showlegend=False,
font=dict(size=16)
)
# --- Bar chart: Top 10 countries, for the last year in the selected range ---
bar_df = df_nation[df_nation['Year'] == end_year].copy()
if emission_type == 'total':
if selected_source == 'All':
bar_df['Total_Emissions'] = bar_df[emission_source_columns].sum(axis=1)
else:
bar_df['Total_Emissions'] = bar_df[selected_source] if selected_source in bar_df.columns else 0
bar_y = "Total_Emissions"
bar_label = "Emissions (ktC)"
else:
bar_df['Total_Emissions'] = bar_df['Per_Capita_Emissions']
bar_y = "Total_Emissions"
bar_label = "Emissions per capita (tC)"
bar_df = bar_df.groupby('Nation', as_index=False)[bar_y].sum()
bar_df = bar_df.sort_values(bar_y, ascending=False).head(10)
bar_fig = px.bar(
bar_df,
x=bar_y,
y='Nation',
orientation='h',
color=bar_y,
color_continuous_scale=px.colors.sequential.Oranges,
labels={
bar_y: bar_label,
"Nation": ""
},
title=None
)
bar_fig.update_layout(
plot_bgcolor="white",
paper_bgcolor="white",
margin=dict(t=40, b=30, l=30, r=30),
yaxis=dict(autorange="reversed"),
coloraxis_showscale=False
)
# --- Choropleth map: for the last year in the selected range ---
map_df = df_nation[df_nation['Year'] == end_year].copy()
if emission_type == 'total':
if selected_source == 'All':
map_df['Total_Emissions'] = map_df[emission_source_columns].sum(axis=1)
else:
map_df['Total_Emissions'] = map_df[selected_source] if selected_source in map_df.columns else 0
map_col = "Total_Emissions"
map_label = "Total Emissions (ktC)"
else:
map_df['Total_Emissions'] = map_df['Per_Capita_Emissions']
map_col = "Total_Emissions"
map_label = "Emissions per capita (tC)"
map_fig = px.choropleth(
map_df,
locations="Nation",
locationmode="country names",
color=map_col,
color_continuous_scale=px.colors.sequential.Oranges,
labels={map_col: map_label},
title=None
)
map_fig.update_traces(
marker_line_width=0.5,
marker_line_color="black"
)
map_fig.update_layout(
geo=dict(showframe=False, showcoastlines=True, projection_type='natural earth'),
margin=dict(t=40, b=30, l=30, r=30),
plot_bgcolor="white",
paper_bgcolor="white",
title_x=0.5
)
return line_fig, pie_fig, bar_fig, map_fig
@app.callback(
Output("country-multiline-chart", "figure"),
[
Input("country-multiselect", "value"),
Input("year-range-slider", "value"),
Input("emission-source-dropdown", "value"),
Input("emission-type-radio", "value")
]
)
def update_country_multiline(selected_countries, year_range, selected_source, emission_type):
if not selected_countries:
return px.line(title="No country selected")
start_year, end_year = year_range
df = df_nation[
(df_nation["Nation"].isin(selected_countries)) &
(df_nation["Year"] >= start_year) &
(df_nation["Year"] <= end_year)
].copy()
if selected_source == "All":
sources = emission_source_columns
else:
sources = [selected_source] if selected_source in emission_source_columns else []
if not sources:
return px.line(title="No data")
if emission_type == "total":
df["Emissions"] = df[sources].sum(axis=1)
y_label = "Emissions (ktC)"
else:
df["Emissions"] = df["Per_Capita_Emissions"]
y_label = "Emissions per capita (tC)"
fig = px.area(
df,
x="Year",
y="Emissions",
color="Nation",
labels={"Emissions": y_label, "Year": "", "Nation": "Country"},
title=None
)
fig.update_traces(line=dict(width=6))
fig.update_layout(
plot_bgcolor="white",
paper_bgcolor="white",
margin=dict(t=40, b=30, l=30, r=30),
legend_title_text=""
)
return fig
if __name__ == '__main__':
app.run(debug=True)