"""
Sankey Dialog Module
====================
:author: Dipl.-Ing. (FH) Jonas Pfeiffer
Displaying Sankey diagram using Plotly to visualize energy flows in district heating systems.
"""
import sys
import plotly.graph_objects as go
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QApplication
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtCore import QUrl
import tempfile
import os
import numpy as np
[docs]
class SankeyDialog(QDialog):
"""
Dialog to display a Sankey diagram using Plotly in a QWebEngineView.
"""
[docs]
def __init__(self, results=None, parent=None):
super().__init__(parent)
self.results = results
self.initUI()
[docs]
def initUI(self):
"""
Initialize the Sankey dialog UI and display the Plotly diagram.
"""
self.setWindowTitle("Sankey-Diagramm - Energieflüsse im Quartier")
self.resize(800, 600)
# Set the layout
layout = QVBoxLayout(self)
# Create a QWebEngineView to display the Plotly HTML content
self.browser = QWebEngineView(self)
layout.addWidget(self.browser)
# Create the Plotly Sankey diagram using results
self.plotSankey()
self.setLayout(layout)
[docs]
def plotSankey(self):
"""
Generate and display a Sankey diagram using Plotly based on results data.
"""
if self.results is None:
return
# Extract relevant data from results
jahreswärmebedarf_gebäude = np.sum(self.results['waerme_ges_kW']) / 1000 # Total building heat demand (input)
jahreswärmebedarf_netz = self.results['Jahreswärmebedarf'] # Total network heat demand
waermemengen = self.results['Wärmemengen'] # Heat generated by each generator (outputs)
erzeuger_labels = self.results['techs'] # Labels for the heat generators
colors = self.results['colors'] # Colors for the links
# Calculate network losses and actual building heat demand
netzverluste = jahreswärmebedarf_netz - jahreswärmebedarf_gebäude
if netzverluste < 0:
netzverluste = 0 # Ensure no negative losses
print(f"{netzverluste}, {jahreswärmebedarf_gebäude}, {jahreswärmebedarf_netz}")
# Define source and target nodes
sources = list(range(len(erzeuger_labels))) # Sources: heat generators
targets = [len(erzeuger_labels)] * len(erzeuger_labels) # Targets: total network heat demand node
# Add additional sources and targets for network losses and building demand
sources += [len(erzeuger_labels)] * 2 # From "Jahreswärmeerzeugung"
targets += [len(erzeuger_labels) + 1, len(erzeuger_labels) + 2] # To "Gebäudewärmebedarf" and "Netzverluste"
# Values for the Sankey links
values = list(waermemengen) # Heat generated by each generator
values += [jahreswärmebedarf_gebäude, netzverluste] # Add building demand and losses
print(f"Erzeuger_labels: {erzeuger_labels}, waermemengen: {waermemengen}")
# Define labels for the nodes
node_labels = list(erzeuger_labels) + ["Jahreswärmeerzeugung", "Gebäudewärmebedarf", "Netzverluste"]
# Add colors for the new links
link_colors = list(colors) + ["blue", "red"] # Add colors for building demand and losses
# Create the Sankey diagram
fig = go.Figure(go.Sankey(
node=dict(
pad=15,
thickness=20,
line=dict(color="black", width=0.5),
label=node_labels,
color="blue" # Color of nodes
),
link=dict(
source=sources, # Links start from each generator or network node
target=targets, # Links point to network demand, building demand, and losses
value=values, # The values correspond to the heat amounts
color=link_colors # Apply the custom colors for each link
)))
fig.update_layout(title_text="Sankey Diagram - Energieverteilung im Wärmenetz", font_size=10)
# Save the figure as an HTML file in a temporary directory
with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as temp_file:
fig.write_html(temp_file.name)
self.html_file_path = temp_file.name
# Load the HTML file in the QWebEngineView
self.browser.setUrl(QUrl.fromLocalFile(self.html_file_path))
[docs]
def closeEvent(self, event):
"""
Clean up the temporary HTML file when the dialog is closed.
:param event: The close event
:type event: QCloseEvent
"""
if os.path.exists(self.html_file_path):
os.remove(self.html_file_path)
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
# Simulated 'results' data for testing
results = {
'Jahreswärmebedarf': 4392.997250030184,
'Wärmemengen': [200.34259576, 1505.44989018, 1313.92519592, 1373.27956817],
'techs': ['Solarthermie_1', 'BHKW_1', 'Biomassekessel_1', 'Gaskessel_1'],
'colors': ['red', 'yellow', 'green', 'saddlebrown']
}
dialog = SankeyDialog(results=results)
dialog.show()
sys.exit(app.exec())