You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5桑基图生成器GUI布局问题:边距异常及标题对齐故障

Solution to Your PyQt5 Sankey Generator Issues

Fix for Issue 2: Top Whitespace and Misaligned Headers

The top whitespace occurs because the QSplitter is centered vertically in the main layout by default. To make content stick to the top, add alignment when adding the splitter to the main layout.

Fix for Issue 1: Right Frame Margin Expansion

The margin increase happens because empty row layouts are left in the right frame when nodes are removed. You need to track and remove these layouts along with their widgets when a node is no longer present.

Corrected Code

from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QWidget, QLabel, QLineEdit, QPushButton, QSplitter, QSizePolicy
from PyQt5.QtCore import Qt
import matplotlib.pyplot as plt
from matplotlib.sankey import Sankey

class SankeyWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("SankeyFlow Diagram Creator")
        self.setGeometry(100, 100, 1200, 800)

        main_layout = QVBoxLayout()

        splitter = QSplitter(Qt.Horizontal)

        #Left Frame: Node and Flow Inputs
        left_frame = QWidget()
        left_layout = QVBoxLayout()
        left_layout.setSpacing(2)  
        left_layout.setContentsMargins(0, 0, 0, 0)  

        header_layout = QHBoxLayout()
        header_layout.setSpacing(5)
        header_layout.addWidget(QLabel("Node 1"))
        header_layout.addWidget(QLabel("Node 2"))
        header_layout.addWidget(QLabel("Flow"))
        left_layout.addLayout(header_layout)

        self.node_inputs = []
        self.flow_inputs = []

        for i in range(2):  # Initial two rows for inputs
            row_layout = QHBoxLayout()
            row_layout.setSpacing(5)  

            node1_input = QLineEdit()
            node2_input = QLineEdit()
            flow_input = QLineEdit()

            row_layout.addWidget(node1_input)
            row_layout.addWidget(node2_input)
            row_layout.addWidget(flow_input)

            left_layout.addLayout(row_layout)
            self.node_inputs.append((node1_input, node2_input))
            self.flow_inputs.append(flow_input)

        generate_button = QPushButton("Generate Sankey Diagram")
        generate_button.clicked.connect(self.generate_sankey)
        left_layout.addWidget(generate_button)

        # Connect text change events for each node input
        for node1, node2 in self.node_inputs:
            node1.textChanged.connect(self.update_right_frame)
            node2.textChanged.connect(self.update_right_frame)

        left_frame.setLayout(left_layout)

        right_frame = QWidget()
        right_layout = QVBoxLayout()
        right_layout.setSpacing(5)  
        right_layout.setContentsMargins(0, 0, 0, 0)

        node_values_header = QHBoxLayout()
        node_values_header.setSpacing(5)
        node_values_header.addWidget(QLabel("Nodes"))
        node_values_header.addWidget(QLabel("Values"))
        right_layout.addLayout(node_values_header)

        self.node_value_inputs_layout = QVBoxLayout()
        self.node_value_inputs_layout.setSpacing(5)
        right_layout.addLayout(self.node_value_inputs_layout)

        right_frame.setLayout(right_layout)

        self.node_values = {}  # Now stores (label_widget, input_widget, row_layout)

        splitter.addWidget(left_frame)
        splitter.addWidget(right_frame)
        splitter.setSizes([600, 600])  
        splitter.setStretchFactor(0, 1)
        splitter.setStretchFactor(1, 1)

        # Fix Issue 2: Align splitter to top to eliminate top whitespace
        main_layout.addWidget(splitter, alignment=Qt.AlignTop)
        main_layout.setSpacing(0)  
        main_layout.setContentsMargins(0, 0, 0, 0) 
        container = QWidget()
        container.setLayout(main_layout)
        self.setCentralWidget(container)

    def update_right_frame(self):
        """Update the right frame with nodes as they are typed in the left frame."""
        current_nodes = set()
        for node1, node2 in self.node_inputs:
            node1_label = node1.text().strip()
            node2_label = node2.text().strip()

            if node1_label:
                current_nodes.add(node1_label)
            if node2_label:
                current_nodes.add(node2_label)

        # Sort nodes alphabetically
        sorted_nodes = sorted(current_nodes)

        for node_label in sorted_nodes:
            if node_label not in self.node_values:
                node_row_layout = QHBoxLayout()
                node_row_layout.setSpacing(5) 
                label_widget = QLabel(f"{node_label}:")
                input_widget = QLineEdit()

                input_widget.setFixedWidth(150) 
                input_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)

                node_row_layout.addWidget(label_widget)
                node_row_layout.addWidget(input_widget)
                self.node_value_inputs_layout.addLayout(node_row_layout)

                # Store the row layout along with widgets
                self.node_values[node_label] = (label_widget, input_widget, node_row_layout)

        # Remove nodes no longer present
        for node_label in list(self.node_values.keys()):
            if node_label not in current_nodes:
                label_widget, input_widget, node_row_layout = self.node_values.pop(node_label)
                # Delete widgets
                label_widget.deleteLater()  
                input_widget.deleteLater()
                # Remove and delete the empty row layout (Fix Issue 1)
                self.node_value_inputs_layout.removeItem(node_row_layout)
                node_row_layout.deleteLater()

        # Force layout recalculation
        self.node_value_inputs_layout.update()

    def generate_sankey(self):
        flows = []
        nodes = []

        for i, (node1, node2) in enumerate(self.node_inputs):
            node1_label = node1.text().strip()
            node2_label = node2.text().strip()
            flow_value = self.flow_inputs[i].text().strip()

            try:
                flow_value = float(flow_value) if flow_value else 0
            except ValueError:
                flow_value = 0

            # Create the nodes and flow entries
            if node1_label and node2_label:
                nodes.append([(f'{node1_label}:', abs(flow_value))])
                nodes.append([(f'{node2_label}:', abs(flow_value))])
                flows.append((f'{node1_label}:', f'{node2_label}:', flow_value))

        # Display the Sankey diagram
        plt.figure(figsize=(12, 6), dpi=144)
        s = Sankey(flows=flows, nodes=nodes, node_opts=dict(label_format='{label} {value:.2f}%'))
        s.draw()
        plt.show()

if __name__ == "__main__":
    app = QApplication([])
    window = SankeyWindow()
    window.show()
    app.exec_()

Key Changes Explained

  1. Issue 2 Fix: Added alignment=Qt.AlignTop when adding the splitter to main_layout. This makes content stick to the window top, eliminating unwanted whitespace.
  2. Issue 1 Fix:
    • Modified self.node_values to store the row layout alongside label and input widgets.
    • When removing a node, the row layout is now removed from the parent layout and deleted, preventing empty layouts from accumulating and expanding margins.

内容的提问来源于stack exchange,提问作者Mustafa Şimşek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 11:45:55