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)