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
- Issue 2 Fix: Added
alignment=Qt.AlignTopwhen adding the splitter tomain_layout. This makes content stick to the window top, eliminating unwanted whitespace. - Issue 1 Fix:
- Modified
self.node_valuesto 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.
- Modified
内容的提问来源于stack exchange,提问作者Mustafa Şimşek
相关产品推荐
相关产品推荐

