import dash
from dash import dcc, html
from dash.dependencies import Input, Output
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# Sample Data
years = list(range(2025, 2044))
bau_emissions = [25000] * len(years)
option_2a_emissions = [25000 * (1 - 0.78) if year >= 2028 else 25000 for year in years]
option_2b_emissions = [25000 * (1 - 0.46) if year >= 2028 else 25000 for year in years]
option_3_emissions = [25000 * (1 - 0.56) if year >= 2031 else 25000 for year in years]
option_4_emissions = [25000 * (1 - 0.75) if year >= 2031 else 25000 for year in years]
bau_costs = [0] * len(years)
option_2a_costs = [8.2 if year >= 2028 else 0 for year in years]
option_2b_costs = [4.5 if year >= 2028 else 0 for year in years]
option_3_costs = [0] * 6 + [30 + i * 1 for i in range(13)] + [40] * (len(years) - 19)
option_4_costs = [0] * 6 + [35 + i * 1.2 for i in range(13)] + [42.4] * (len(years) - 19)
# Initialize the Dash app
app = dash.Dash(__name__)
# Common font style
font_style = {
'font-family': 'Arial, sans-serif',
'font-size': '14px',
'font-weight': 'normal',
'line-height': '1.2'
}
# Define selected and unselected button styles with improved aesthetics
button_base_style = {
'width': '180px',
'padding': '10px',
'margin-top': '10px',
'margin-bottom': '5px',
'border': 'none',
'border-radius': '5px',
'box-shadow': '0px 4px 6px rgba(0, 0, 0, 0.1)',
'text-align': 'center',
'cursor': 'pointer',
'transition': 'background-color 0.3s, transform 0.3s',
**font_style # Apply the common font style here
}
selected_style = {
**button_base_style,
'background': 'linear-gradient(to right, #4CAF50, #8BC34A)',
'color': 'white',
}
unselected_style = {
**button_base_style,
'background': 'linear-gradient(to right, #ffffff, #dddddd)',
'color': '#333',
}
# Create the initial figure with invisible traces
def create_initial_figure():
fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
subplot_titles=("Carbon Emissions (MTCO2e)", "Additional Annual Costs ($Millions)"),
vertical_spacing=0.1)
fig.add_trace(go.Scatter(x=years, y=bau_emissions, mode='lines', name='BAU Emissions',
line=dict(color='orange', dash='dash'), opacity=0.2), row=1, col=1)
fig.add_trace(go.Scatter(x=years, y=option_2a_emissions, mode='lines+markers', name='Option 2A Emissions',
line=dict(color='blue', dash='dash'), opacity=0.2), row=1, col=1)
fig.add_trace(go.Scatter(x=years, y=option_2b_emissions, mode='lines+markers', name='Option 2B Emissions',
line=dict(color='purple', dash='dash'), opacity=0.2), row=1, col=1)
fig.add_trace(go.Scatter(x=years, y=option_3_emissions, mode='lines+markers', name='Option 3 Emissions',
line=dict(color='red', dash='dash'), opacity=0.2), row=1, col=1)
fig.add_trace(go.Scatter(x=years, y=option_4_emissions, mode='lines+markers', name='Option 4 Emissions',
line=dict(color='green', dash='dash'), opacity=0.2), row=1, col=1)
fig.add_trace(go.Scatter(x=years, y=bau_costs, mode='lines', name='BAU Costs',
line=dict(color='orange', dash='dot'), opacity=0.2), row=2, col=1)
fig.add_trace(go.Scatter(x=years, y=option_2a_costs, mode='lines+markers', name='Option 2A Costs',
line=dict(color='blue', dash='dot'), opacity=0.2), row=2, col=1)
fig.add_trace(go.Scatter(x=years, y=option_2b_costs, mode='lines+markers', name='Option 2B Costs',
line=dict(color='purple', dash='dot'), opacity=0.2), row=2, col=1)
fig.add_trace(go.Scatter(x=years, y=option_3_costs, mode='lines+markers', name='Option 3 Costs',
line=dict(color='red', dash='dot'), opacity=0.2), row=2, col=1)
fig.add_trace(go.Scatter(x=years, y=option_4_costs, mode='lines+markers', name='Option 4 Costs',
line=dict(color='green', dash='dot'), opacity=0.2), row=2, col=1)
fig.update_layout(
title="Interactive Trade-Offs in Carbon Emissions and Costs for Heating System Options",
xaxis=dict(tickmode='linear', tick0=2025, dtick=5, title="Year"),
yaxis=dict(title="Carbon Emissions (MTCO2e)"),
xaxis2=dict(tickmode='linear', tick0=2025, dtick=5, title="Year"),
yaxis2=dict(title="Additional Annual Costs ($Millions)"),
legend_title="Heating Options",
hovermode="x unified"
)
return fig
# Layout of the app
app.layout = html.Div([
html.Div([
dcc.Graph(id='emissions-cost-graph', figure=create_initial_figure(), style={
'flex': '2',
'width': '800px',
'resize': 'both',
'overflow': 'auto',
'border': '1px solid #ddd',
'box-shadow': '0px 4px 6px rgba(0, 0, 0, 0.1)',
'border-radius': '5px',
}),
html.Div([
html.Div(id='option-description', style={
'height': '550px',
'width': '500px',
'overflow-y': 'hidden', # Prevent scrolling
'display': 'flex', # Flexbox to center content
'flex-direction': 'column',
'justify-content': 'center', # Center content vertically
'align-items': 'center', # Center content horizontally
'margin-left': '20px',
'padding': '20px',
'background-color': 'white',
'border': '1px solid #ddd',
'border-radius': '5px',
'box-shadow': '0px 0px 10px rgba(0, 0, 0, 0.1)',
'line-height': '1.4',
'max-width': '500px',
**font_style
}),
html.Div([
html.Button('View BAU', id='button-bau', n_clicks=0, style=unselected_style),
html.Button('View Option 2A', id='button-2a', n_clicks=0, style=unselected_style),
html.Button('View Option 2B', id='button-2b', n_clicks=0, style=unselected_style),
html.Button('View Option 3', id='button-3', n_clicks=0, style=unselected_style),
html.Button('View Option 4', id='button-4', n_clicks=0, style=unselected_style),
], style={'display': 'flex', 'flex-direction': 'column', 'align-items': 'center', 'margin-top': '20px'})
], style={'display': 'flex', 'flex-direction': 'column', 'align-items': 'center', 'flex': '1'})
], style={'display': 'flex', 'flex-direction': 'row', 'height': '95vh'}),
], style={'background-color': '#83E672', 'padding': '20px'})
# Callback to update the graph based on button clicks
@app.callback(
Output('emissions-cost-graph', 'figure'),
Output('button-bau', 'style'),
Output('button-2a', 'style'),
Output('button-2b', 'style'),
Output('button-3', 'style'),
Output('button-4', 'style'),
Output('option-description', 'children'),
[Input('button-bau', 'n_clicks'),
Input('button-2a', 'n_clicks'),
Input('button-2b', 'n_clicks'),
Input('button-3', 'n_clicks'),
Input('button-4', 'n_clicks')]
)
def update_graph(n_clicks_bau, n_clicks_2a, n_clicks_2b, n_clicks_3, n_clicks_4):
ctx = dash.callback_context
# Ensure description is always defined
description = "Select options to compare."
selected_options = []
styles = []
# Handle individual button toggling
for i, (clicks, style) in enumerate([
(n_clicks_bau, selected_style),
(n_clicks_2a, selected_style),
(n_clicks_2b, selected_style),
(n_clicks_3, selected_style),
(n_clicks_4, selected_style)
]):
if clicks % 2 == 1:
selected_options.append(['BAU', 'Option 2A', 'Option 2B', 'Option 3', 'Option 4'][i])
styles.append(selected_style)
else:
styles.append(unselected_style)
# Create the figure
fig = create_initial_figure()
# Set opacity for non-selected options to 0.2, and selected options to 1
fig.update_traces(opacity=0.2)
if 'BAU' in selected_options:
fig.update_traces(opacity=1, selector=dict(name='BAU Emissions'))
fig.update_traces(opacity=1, selector=dict(name='BAU Costs'))
if 'Option 2A' in selected_options:
fig.update_traces(opacity=1, selector=dict(name='Option 2A Emissions'))
fig.update_traces(opacity=1, selector=dict(name='Option 2A Costs'))
if 'Option 2B' in selected_options:
fig.update_traces(opacity=1, selector=dict(name='Option 2B Emissions'))
fig.update_traces(opacity=1, selector=dict(name='Option 2B Costs'))
if 'Option 3' in selected_options:
fig.update_traces(opacity=1, selector=dict(name='Option 3 Emissions'))
fig.update_traces(opacity=1, selector=dict(name='Option 3 Costs'))
if 'Option 4' in selected_options:
fig.update_traces(opacity=1, selector=dict(name='Option 4 Emissions'))
fig.update_traces(opacity=1, selector=dict(name='Option 4 Costs'))
# Create description for the selected option
if 'Option 2A' in selected_options:
description = html.Div([
html.H3("Overview:"),
html.P("Option 2A involves the installation of an 18MW electrode boiler. "
"This technology uses electricity to heat water to produce steam, replacing the need for natural gas."),
html.H4("Analogy:"),
html.P("Think of this as switching from a gas stove to an electric kettle. "
"Both heat water, but the kettle uses electricity, which is cleaner if the power source is renewable."),
html.H4("Trade-Offs:"),
html.Ul([
html.Li("Quick Emissions Reduction: Significant reduction in emissions starting from 2028."),
html.Li("High Upfront Costs: Requires a substantial initial investment in the boiler system."),
html.Li("Increased Electricity Consumption: This option increases electricity use, potentially raising operational costs depending on electricity prices."),
html.Li("Reliance on Electricity Supply: The effectiveness depends on the availability and stability of the electricity grid.")
]),
html.H4("To Learn More:"),
html.Ul([
html.Li(html.A("Read more about electrode boilers", href="https://example.com", target="_blank")),
html.Li(html.A("Sustainability benefits of electric heating", href="https://example.com", target="_blank"))
]),
html.H4("Submit your thoughts to the taskforce:"),
html.A("Email the taskforce", href="mailto:taskforce@example.com?subject=Feedback on Option 2A")
], style=font_style) # Apply the common font style here
elif 'Option 2B' in selected_options:
description = html.Div([
html.H3("Overview:"),
html.P("Option 2B uses an 8MW electrode boiler, which is a smaller version of the technology in Option 2A, providing a balance between cost and emissions reduction."),
html.H4("Analogy:"),
html.P("This is like using a smaller electric kettle for a single cup of tea instead of a large one for multiple cups. It's more economical but still serves the purpose."),
html.H4("Trade-Offs:"),
html.Ul([
html.Li("Moderate Emissions Reduction: Less reduction compared to Option 2A, but still significant."),
html.Li("Lower Upfront Costs: More affordable than Option 2A but still requires capital investment."),
html.Li("Moderate Electricity Consumption: Uses less electricity than Option 2A, making it more cost-effective."),
html.Li("Scalability: Easier to scale or adjust if energy demand changes.")
]),
html.H4("To Learn More:"),
html.Ul([
html.Li(html.A("Explore smaller electrode boilers", href="https://example.com", target="_blank")),
html.Li(html.A("Cost vs. efficiency in electric heating", href="https://example.com", target="_blank"))
]),
html.H4("Submit your thoughts to the taskforce:"),
html.A("Email the taskforce", href="mailto:taskforce@example.com?subject=Feedback on Option 2B")
], style=font_style) # Apply the common font style here
elif 'Option 3' in selected_options:
description = html.Div([
html.H3("Overview:"),
html.P("Option 3 converts the campus heating system from steam to hot water, using heat recovery chillers. "
"This system captures waste heat from other processes and repurposes it to heat water."),
html.H4("Analogy:"),
html.P("Imagine a hybrid car that reuses energy from braking to power the engine. "
"This system similarly recycles energy to reduce waste."),
html.H4("Trade-Offs:"),
html.Ul([
html.Li("Long-Term Emissions Reduction: Significant but gradual reduction in emissions, with benefits realized over a longer timeline."),
html.Li("Higher Operational Costs: Requires ongoing maintenance and monitoring, leading to increased operational expenses."),
html.Li("Infrastructure Overhaul: Requires major changes to the existing heating infrastructure, which can be disruptive."),
html.Li("Energy Efficiency: Increases overall energy efficiency by reusing waste heat, but depends on other processes generating sufficient waste heat.")
]),
html.H4("To Learn More:"),
html.Ul([
html.Li(html.A("How heat recovery chillers work", href="https://example.com", target="_blank")),
html.Li(html.A("Energy efficiency in campus infrastructure", href="https://example.com", target="_blank"))
]),
html.H4("Submit your thoughts to the taskforce:"),
html.A("Email the taskforce", href="mailto:taskforce@example.com?subject=Feedback on Option 3")
], style=font_style) # Apply the common font style here
elif 'Option 4' in selected_options:
description = html.Div([
html.H3("Overview:"),
html.P("Option 4 is the most advanced, combining hot water distribution with geoexchange thermal storage. "
"This system stores excess heat in the ground during warmer periods and retrieves it during colder times."),
html.H4("Analogy:"),
html.P("Think of this like a rechargeable battery that stores energy when it's abundant and releases it when needed. "
"The ground acts as the battery in this system."),
html.H4("Trade-Offs:"),
html.Ul([
html.Li("Highest Emissions Reduction: Offers the most significant reduction in emissions, starting from 2031."),
html.Li("Very High Upfront and Operational Costs: The most expensive option, requiring significant investment in technology and infrastructure."),
html.Li("Complex Implementation: Requires careful planning and execution due to the complexity of the system."),
html.Li("Long-Term Sustainability: Provides a sustainable heating solution that can adapt to future energy needs and climate conditions.")
]),
html.H4("To Learn More:"),
html.Ul([
html.Li(html.A("Learn more about geoexchange systems", href="https://example.com", target="_blank")),
html.Li(html.A("Long-term benefits of thermal storage", href="https://example.com", target="_blank"))
]),
html.H4("Submit your thoughts to the taskforce:"),
html.A("Email the taskforce", href="mailto:taskforce@example.com?subject=Feedback on Option 4")
], style=font_style) # Apply the common font style here
elif 'BAU' in selected_options:
description = html.Div([
html.H3("Overview:"),
html.P("The Business As Usual (BAU) scenario assumes no major changes to the current heating system. "
"Emissions remain constant, and costs are minimal, as no significant investments are made."),
html.H4("Analogy:"),
html.P("Think of this like driving an old car without any upgrades. It works as it always has, but it isn't "
"getting any more efficient, and it will continue to produce the same level of emissions."),
html.H4("Trade-Offs:"),
html.Ul([
html.Li("No Emissions Reduction: Emissions stay the same, contributing to ongoing environmental impact."),
html.Li("Low Costs: Minimal additional costs as no new investments are made."),
html.Li("Status Quo: No improvements in system efficiency or environmental impact.")
]),
html.H4("To Learn More:"),
html.Ul([
html.Li(html.A("Read more about current heating systems", href="https://example.com", target="_blank")),
html.Li(html.A("Environmental impacts of business-as-usual scenarios", href="https://example.com", target="_blank"))
]),
html.H4("Submit your thoughts to the taskforce:"),
html.A("Email the taskforce", href="mailto:taskforce@example.com?subject=Feedback on BAU Scenario")
], style=font_style) # Apply the common font style here
else:
description = "Select options to compare."
# Update layout to ensure both y-axes are correctly labeled
fig.update_layout(
title="Interactive Trade-Offs in Carbon Emissions and Costs for Heating System Options",
xaxis=dict(tickmode='linear', tick0=2025, dtick=5, title="Year"),
yaxis=dict(title="Carbon Emissions (MTCO2e)"),
xaxis2=dict(tickmode='linear', tick0=2025, dtick=5, title="Year"),
yaxis2=dict(title="Additional Annual Costs ($Millions)"),
legend_title="Heating Options",
hovermode="x unified"
)
# Return the figure and the description
return fig, styles[0], styles[1], styles[2], styles[3], styles[4], description
# Run the app
if __name__ == '__main__':
app.run_server(debug=False)