chartflow 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chartflow/__init__.py +28 -0
- chartflow/_verify_importexport.py +169 -0
- chartflow/edit/__init__.py +119 -0
- chartflow/edit/code_editor.py +1007 -0
- chartflow/edit/code_folder.py +523 -0
- chartflow/edit/completer.py +2652 -0
- chartflow/edit/context_analyzer.py +521 -0
- chartflow/edit/demo.py +469 -0
- chartflow/edit/line_number_area.py +548 -0
- chartflow/edit/minimap.py +581 -0
- chartflow/edit/python_editor.py +509 -0
- chartflow/edit/syntax_highlighter.py +291 -0
- chartflow/edit/test_editor.py +190 -0
- chartflow/flow/__init__.py +31 -0
- chartflow/flow/_demo.py +98 -0
- chartflow/flow/chart.py +1101 -0
- chartflow/flow/editor.py +375 -0
- chartflow/flow/flow.py +6 -0
- chartflow/flow/qchart.py +1250 -0
- chartflow/flow/test/__init__.py +0 -0
- chartflow/flow/test/test_chart.py +537 -0
- chartflow/flow/test_decorator_sync.py +102 -0
- chartflow/fsm/__init__.py +84 -0
- chartflow/fsm/decorators.py +395 -0
- chartflow/fsm/demo.py +381 -0
- chartflow/fsm/demo_pyqt6.py +881 -0
- chartflow/fsm/demo_tree.py +46 -0
- chartflow/fsm/logic.py +447 -0
- chartflow/fsm/meta.py +569 -0
- chartflow/fsm/scheduler.py +427 -0
- chartflow/fsm/stage.py +2079 -0
- chartflow/fsm/stdio.py +66 -0
- chartflow/fsm/test/__init__.py +7 -0
- chartflow/fsm/test/test_decorators.py +210 -0
- chartflow/fsm/test/test_events.py +202 -0
- chartflow/fsm/test/test_integration.py +596 -0
- chartflow/fsm/test/test_logic.py +371 -0
- chartflow/fsm/test/test_meta.py +192 -0
- chartflow/fsm/test/test_runtime_param.py +336 -0
- chartflow/fsm/test/test_scheduler.py +280 -0
- chartflow/fsm/test/test_stage.py +314 -0
- chartflow/fsm/test_behavior_events.py +263 -0
- chartflow/fsm/test_nested_parallel.py +80 -0
- chartflow/fsm/test_stage_spec.py +448 -0
- chartflow/fsm/utils.py +34 -0
- chartflow/node/__init__.py +66 -0
- chartflow/node/_node_base.py +727 -0
- chartflow/node/_node_interaction.py +488 -0
- chartflow/node/_node_interface.py +426 -0
- chartflow/node/_node_markers.py +648 -0
- chartflow/node/_node_ports.py +536 -0
- chartflow/node/_port_interface.py +111 -0
- chartflow/node/arrow_item.py +287 -0
- chartflow/node/color_utils.py +113 -0
- chartflow/node/connection_item.py +939 -0
- chartflow/node/demo.py +258 -0
- chartflow/node/demo_arrow_drag.py +46 -0
- chartflow/node/demo_decorator.py +63 -0
- chartflow/node/demo_ports.py +69 -0
- chartflow/node/enums.py +35 -0
- chartflow/node/example.py +84 -0
- chartflow/node/layout_utils.py +307 -0
- chartflow/node/main_window.py +196 -0
- chartflow/node/menu_bar.py +240 -0
- chartflow/node/node_canvas.py +1730 -0
- chartflow/node/node_item.py +754 -0
- chartflow/node/popmenu.py +472 -0
- chartflow/node/port_item.py +591 -0
- chartflow/node/qnode_editor.py +73 -0
- chartflow/node/selection_rect.py +53 -0
- chartflow/node/style_panel.py +615 -0
- chartflow/node/styles.py +63 -0
- chartflow/node/test_qnode.py +2336 -0
- chartflow/showcase.py +528 -0
- chartflow/theme.py +508 -0
- chartflow-0.1.dist-info/METADATA +23 -0
- chartflow-0.1.dist-info/RECORD +79 -0
- chartflow-0.1.dist-info/WHEEL +5 -0
- chartflow-0.1.dist-info/top_level.txt +1 -0
chartflow/node/demo.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""
|
|
2
|
+
qnode 演示示例 - 展示节点图编辑器的基本功能
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from PyQt6.QtCore import QTimer
|
|
6
|
+
import random
|
|
7
|
+
from PyQt6.QtWidgets import QFileDialog
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
sys.path.insert(0, 'F:\\Python\\Python314\\Lib\\site-packages')
|
|
12
|
+
|
|
13
|
+
from PyQt6.QtWidgets import QApplication, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, QLabel, QComboBox
|
|
14
|
+
from PyQt6.QtCore import QPointF, Qt
|
|
15
|
+
from chartflow.node import NodeCanvasWindow, QNodeCanvas, NodeShape, ConnectionType
|
|
16
|
+
from chartflow.node.layout_utils import DenseLayoutEngine, ViewportFitter
|
|
17
|
+
from chartflow.theme import applyTheme, PALETTE
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DemoWindow(QWidget):
|
|
21
|
+
"""演示窗口 - 包含控制面板和节点画布"""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.setWindowTitle("QNode 演示 - 节点图编辑器")
|
|
26
|
+
self.resize(1200, 800)
|
|
27
|
+
|
|
28
|
+
# 主布局
|
|
29
|
+
layout = QHBoxLayout(self)
|
|
30
|
+
|
|
31
|
+
# 左侧控制面板
|
|
32
|
+
controlPanel = self.CreateControlPanel()
|
|
33
|
+
layout.addWidget(controlPanel, 0)
|
|
34
|
+
|
|
35
|
+
# 右侧节点画布窗口
|
|
36
|
+
self.nodeWindow = NodeCanvasWindow()
|
|
37
|
+
self.canvas = self.nodeWindow.canvas
|
|
38
|
+
layout.addWidget(self.nodeWindow, 1)
|
|
39
|
+
|
|
40
|
+
# 初始化一些示例节点
|
|
41
|
+
self.InitDemoNodes()
|
|
42
|
+
|
|
43
|
+
def CreateControlPanel(self):
|
|
44
|
+
"""创建控制面板"""
|
|
45
|
+
panel = QWidget()
|
|
46
|
+
panel.setFixedWidth(250)
|
|
47
|
+
panel.setObjectName("controlPanel")
|
|
48
|
+
|
|
49
|
+
layout = QVBoxLayout(panel)
|
|
50
|
+
layout.setContentsMargins(12, 12, 12, 12)
|
|
51
|
+
layout.setSpacing(8)
|
|
52
|
+
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
|
53
|
+
|
|
54
|
+
# 标题
|
|
55
|
+
title = QLabel("QNode 演示控制面板")
|
|
56
|
+
title.setStyleSheet("font-size: 15px; font-weight: 600; padding-bottom: 6px;")
|
|
57
|
+
layout.addWidget(title)
|
|
58
|
+
|
|
59
|
+
# 添加节点区域
|
|
60
|
+
layout.addWidget(self._sectionLabel("添加节点"))
|
|
61
|
+
|
|
62
|
+
self.shapeCombo = QComboBox()
|
|
63
|
+
self.shapeCombo.addItems(["椭圆 (开始/结束)", "矩形 (处理)", "菱形 (决策)"])
|
|
64
|
+
layout.addWidget(self.shapeCombo)
|
|
65
|
+
|
|
66
|
+
addBtn = QPushButton("添加节点")
|
|
67
|
+
addBtn.clicked.connect(self.AddNode)
|
|
68
|
+
layout.addWidget(addBtn)
|
|
69
|
+
|
|
70
|
+
layout.addSpacing(16)
|
|
71
|
+
|
|
72
|
+
# 布局算法区域
|
|
73
|
+
layout.addWidget(self._sectionLabel("自动布局"))
|
|
74
|
+
|
|
75
|
+
self.layoutCombo = QComboBox()
|
|
76
|
+
self.layoutCombo.addItems(["紧凑", "环形", "网格", "层次", "力导向"])
|
|
77
|
+
layout.addWidget(self.layoutCombo)
|
|
78
|
+
|
|
79
|
+
layoutBtn = QPushButton("应用布局")
|
|
80
|
+
layoutBtn.clicked.connect(self.ApplyLayout)
|
|
81
|
+
layout.addWidget(layoutBtn)
|
|
82
|
+
|
|
83
|
+
layout.addSpacing(16)
|
|
84
|
+
|
|
85
|
+
# 视图控制区域
|
|
86
|
+
layout.addWidget(self._sectionLabel("视图控制"))
|
|
87
|
+
|
|
88
|
+
fitBtn = QPushButton("适配视口")
|
|
89
|
+
fitBtn.clicked.connect(self.FitViewport)
|
|
90
|
+
layout.addWidget(fitBtn)
|
|
91
|
+
|
|
92
|
+
centerBtn = QPushButton("居中节点")
|
|
93
|
+
centerBtn.clicked.connect(self.CenterNodes)
|
|
94
|
+
layout.addWidget(centerBtn)
|
|
95
|
+
|
|
96
|
+
layout.addSpacing(16)
|
|
97
|
+
|
|
98
|
+
# 导入/导出区域
|
|
99
|
+
layout.addWidget(self._sectionLabel("导入/导出"))
|
|
100
|
+
|
|
101
|
+
exportBtn = QPushButton("导出为 JSON")
|
|
102
|
+
exportBtn.clicked.connect(self.ExportJson)
|
|
103
|
+
layout.addWidget(exportBtn)
|
|
104
|
+
|
|
105
|
+
importBtn = QPushButton("从 JSON 导入")
|
|
106
|
+
importBtn.clicked.connect(self.ImportJson)
|
|
107
|
+
layout.addWidget(importBtn)
|
|
108
|
+
|
|
109
|
+
clearBtn = QPushButton("清空画布")
|
|
110
|
+
clearBtn.clicked.connect(self.ClearCanvas)
|
|
111
|
+
layout.addWidget(clearBtn)
|
|
112
|
+
|
|
113
|
+
layout.addSpacing(16)
|
|
114
|
+
|
|
115
|
+
# 帮助信息
|
|
116
|
+
helpLabel = QLabel(
|
|
117
|
+
"操作说明\n"
|
|
118
|
+
"• 左键拖拽: 框选\n"
|
|
119
|
+
"• Ctrl+点击: 多选\n"
|
|
120
|
+
"• 从节点拖拽: 创建连接\n"
|
|
121
|
+
"• 双击节点: 编辑\n"
|
|
122
|
+
"• 右键连接: 切换线条类型\n"
|
|
123
|
+
"• 中键拖拽: 平移\n"
|
|
124
|
+
"• 空格/Alt+左键: 平移\n"
|
|
125
|
+
"• Alt 悬停节点: 调整大小光标\n"
|
|
126
|
+
"• 滚轮: 缩放\n"
|
|
127
|
+
"• Delete: 删除"
|
|
128
|
+
)
|
|
129
|
+
helpLabel.setStyleSheet(f"color: {PALETTE.text_dim}; font-size: 11px;")
|
|
130
|
+
layout.addWidget(helpLabel)
|
|
131
|
+
|
|
132
|
+
layout.addStretch()
|
|
133
|
+
|
|
134
|
+
return panel
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def _sectionLabel(text: str) -> QLabel:
|
|
138
|
+
"""A small accent-colored section header used inside the control panel."""
|
|
139
|
+
label = QLabel(text)
|
|
140
|
+
label.setStyleSheet(
|
|
141
|
+
f"color: {PALETTE.accent_hover}; font-size: 11px; font-weight: 600;"
|
|
142
|
+
f" border-bottom: 1px solid {PALETTE.border}; padding-bottom: 3px;"
|
|
143
|
+
)
|
|
144
|
+
return label
|
|
145
|
+
|
|
146
|
+
def InitDemoNodes(self):
|
|
147
|
+
"""初始化演示节点"""
|
|
148
|
+
# 创建流程图示例
|
|
149
|
+
start = self.canvas.addNode("开始", QPointF(0, 0), NodeShape.ELLIPSE)
|
|
150
|
+
|
|
151
|
+
inputNode = self.canvas.addNode("输入数据", QPointF(200, -100), NodeShape.RECTANGLE)
|
|
152
|
+
process = self.canvas.addNode("处理数据", QPointF(400, 0), NodeShape.RECTANGLE)
|
|
153
|
+
decision = self.canvas.addNode("验证?", QPointF(600, 0), NodeShape.DIAMOND)
|
|
154
|
+
success = self.canvas.addNode("成功", QPointF(800, -100), NodeShape.RECTANGLE)
|
|
155
|
+
error = self.canvas.addNode("错误处理", QPointF(800, 100), NodeShape.RECTANGLE)
|
|
156
|
+
end = self.canvas.addNode("结束", QPointF(1000, 0), NodeShape.ELLIPSE)
|
|
157
|
+
|
|
158
|
+
# 创建连接
|
|
159
|
+
self.canvas.addConnection(start, inputNode)
|
|
160
|
+
self.canvas.addConnection(inputNode, process)
|
|
161
|
+
self.canvas.addConnection(process, decision)
|
|
162
|
+
self.canvas.addConnection(decision, success)
|
|
163
|
+
self.canvas.addConnection(decision, error)
|
|
164
|
+
self.canvas.addConnection(success, end)
|
|
165
|
+
self.canvas.addConnection(error, end)
|
|
166
|
+
|
|
167
|
+
# 设置起始节点
|
|
168
|
+
self.canvas.startNode = start
|
|
169
|
+
|
|
170
|
+
# 适配视口 - 使用较小的边距确保节点清晰可见
|
|
171
|
+
QTimer.singleShot(100, self._fitViewportOnStartup)
|
|
172
|
+
|
|
173
|
+
def _fitViewportOnStartup(self):
|
|
174
|
+
"""启动时适配视口"""
|
|
175
|
+
fitter = ViewportFitter(self.canvas)
|
|
176
|
+
# 使用较小的边距(20)确保图形清晰可见
|
|
177
|
+
fitter.fitViewport(margin=20.0)
|
|
178
|
+
# 限制最小缩放比例,避免图形过小
|
|
179
|
+
current_scale = self.canvas.transform().m11()
|
|
180
|
+
if current_scale < 0.8:
|
|
181
|
+
self.canvas.resetTransform()
|
|
182
|
+
self.canvas.scale(0.8, 0.8)
|
|
183
|
+
|
|
184
|
+
def AddNode(self):
|
|
185
|
+
"""添加新节点"""
|
|
186
|
+
shapeIdx = self.shapeCombo.currentIndex()
|
|
187
|
+
shape = [NodeShape.ELLIPSE, NodeShape.RECTANGLE, NodeShape.DIAMOND][shapeIdx]
|
|
188
|
+
name = ["开始/结束", "处理节点", "决策节点"][shapeIdx]
|
|
189
|
+
|
|
190
|
+
# 在随机位置添加节点
|
|
191
|
+
x = random.randint(-200, 200)
|
|
192
|
+
y = random.randint(-200, 200)
|
|
193
|
+
node = self.canvas.addNode(name, QPointF(x, y), shape)
|
|
194
|
+
|
|
195
|
+
def ApplyLayout(self):
|
|
196
|
+
"""应用布局算法"""
|
|
197
|
+
layoutNames = ["compact", "circular", "grid", "hierarchical", "force"]
|
|
198
|
+
layoutName = layoutNames[self.layoutCombo.currentIndex()]
|
|
199
|
+
|
|
200
|
+
engine = DenseLayoutEngine(self.canvas)
|
|
201
|
+
engine.autoLayout(layoutName)
|
|
202
|
+
|
|
203
|
+
def FitViewport(self):
|
|
204
|
+
"""适配视口"""
|
|
205
|
+
fitter = ViewportFitter(self.canvas)
|
|
206
|
+
fitter.fitViewport()
|
|
207
|
+
|
|
208
|
+
def CenterNodes(self):
|
|
209
|
+
"""居中节点"""
|
|
210
|
+
fitter = ViewportFitter(self.canvas)
|
|
211
|
+
fitter.centerOnNodes()
|
|
212
|
+
|
|
213
|
+
def ExportJson(self):
|
|
214
|
+
"""导出为 JSON"""
|
|
215
|
+
|
|
216
|
+
json_str = self.canvas.exportToJson()
|
|
217
|
+
filePath, _ = QFileDialog.getSaveFileName(
|
|
218
|
+
self, "导出图", "graph.json", "JSON 文件 (*.json)"
|
|
219
|
+
)
|
|
220
|
+
if filePath:
|
|
221
|
+
with open(filePath, 'w', encoding='utf-8') as f:
|
|
222
|
+
f.write(json_str)
|
|
223
|
+
print(f"已导出到: {filePath}")
|
|
224
|
+
|
|
225
|
+
def ImportJson(self):
|
|
226
|
+
"""从 JSON 导入"""
|
|
227
|
+
|
|
228
|
+
filePath, _ = QFileDialog.getOpenFileName(
|
|
229
|
+
self, "导入图", "", "JSON 文件 (*.json)"
|
|
230
|
+
)
|
|
231
|
+
if filePath:
|
|
232
|
+
with open(filePath, 'r', encoding='utf-8') as f:
|
|
233
|
+
data = json.load(f)
|
|
234
|
+
self.canvas.importData(data)
|
|
235
|
+
print(f"已从 {filePath} 导入")
|
|
236
|
+
|
|
237
|
+
def ClearCanvas(self):
|
|
238
|
+
"""清空画布"""
|
|
239
|
+
self.canvas.clear()
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def main():
|
|
243
|
+
"""主函数"""
|
|
244
|
+
app = QApplication(sys.argv)
|
|
245
|
+
|
|
246
|
+
# 设置应用样式与统一主题
|
|
247
|
+
app.setStyle('Fusion')
|
|
248
|
+
applyTheme(app)
|
|
249
|
+
|
|
250
|
+
# 创建并显示演示窗口
|
|
251
|
+
window = DemoWindow()
|
|
252
|
+
window.show()
|
|
253
|
+
|
|
254
|
+
sys.exit(app.exec())
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
if __name__ == "__main__":
|
|
258
|
+
main()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
qnode 箭头拖拽功能演示
|
|
3
|
+
|
|
4
|
+
演示如何通过拖拽连接线上的箭头来创建新连接。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from PyQt6.QtWidgets import QApplication
|
|
9
|
+
from PyQt6.QtCore import QPointF
|
|
10
|
+
|
|
11
|
+
from chartflow.node import QNodeCanvas, QNodeItem, NodeShape
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def DemoArrowDrag():
|
|
15
|
+
"""演示箭头拖拽创建新连接功能"""
|
|
16
|
+
app = QApplication(sys.argv)
|
|
17
|
+
|
|
18
|
+
# 创建画布
|
|
19
|
+
canvas = QNodeCanvas()
|
|
20
|
+
canvas.setWindowTitle("箭头拖拽演示 - 拖拽连接线上的箭头到另一个节点")
|
|
21
|
+
canvas.resize(800, 600)
|
|
22
|
+
|
|
23
|
+
# 创建三个节点
|
|
24
|
+
node_a = canvas.addNode("节点 A", QPointF(200, 300), NodeShape.ELLIPSE)
|
|
25
|
+
node_b = canvas.addNode("节点 B", QPointF(500, 200), NodeShape.RECTANGLE)
|
|
26
|
+
node_c = canvas.addNode("节点 C", QPointF(500, 400), NodeShape.RECTANGLE)
|
|
27
|
+
|
|
28
|
+
# 创建初始连接 A -> B
|
|
29
|
+
conn = canvas.addConnection(node_a, node_b)
|
|
30
|
+
print(f"创建连接: {node_a.name} -> {node_b.name}")
|
|
31
|
+
|
|
32
|
+
# 演示说明
|
|
33
|
+
print("\n=== 箭头拖拽功能说明 ===")
|
|
34
|
+
print("1. 将鼠标悬停在连接线末端的箭头上")
|
|
35
|
+
print("2. 按住鼠标左键拖拽箭头")
|
|
36
|
+
print("3. 拖拽到另一个节点上释放,会创建新连接")
|
|
37
|
+
print("4. 如果是单向连接,原连接会被移除,新连接会被创建")
|
|
38
|
+
print("5. 如果是双向连接,会保留未拖拽方向的连接")
|
|
39
|
+
print("\n提示:可以拖拽箭头到节点C来创建新连接!")
|
|
40
|
+
|
|
41
|
+
canvas.show()
|
|
42
|
+
sys.exit(app.exec())
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
DemoArrowDrag()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
qnode 装饰器节点边框颜色演示
|
|
3
|
+
|
|
4
|
+
演示如何为不同类型的装饰器节点设置不同的边框颜色:
|
|
5
|
+
- recursive 装饰的节点 → 紫罗兰色 (violet) 边框
|
|
6
|
+
- behavior/sequence/selector/parallel 装饰的节点 → 皇家蓝色 (royal blue) 边框
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from PyQt6.QtWidgets import QApplication
|
|
11
|
+
from PyQt6.QtCore import QPointF
|
|
12
|
+
|
|
13
|
+
from chartflow.node import QNodeCanvas, QNodeItem, NodeShape
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def DemoDecoratorColors():
|
|
17
|
+
"""演示装饰器节点边框颜色"""
|
|
18
|
+
app = QApplication(sys.argv)
|
|
19
|
+
|
|
20
|
+
# 创建画布
|
|
21
|
+
canvas = QNodeCanvas()
|
|
22
|
+
canvas.setWindowTitle("装饰器节点边框颜色演示")
|
|
23
|
+
canvas.resize(900, 600)
|
|
24
|
+
|
|
25
|
+
# 创建普通节点(无装饰器)
|
|
26
|
+
node_normal = canvas.addNode("普通节点", QPointF(150, 150), NodeShape.ELLIPSE)
|
|
27
|
+
|
|
28
|
+
# 创建 recursive 装饰的节点 → 紫罗兰色边框
|
|
29
|
+
node_recursive = canvas.addNode("Recursive", QPointF(450, 150), NodeShape.RECTANGLE)
|
|
30
|
+
node_recursive.decorator = "recursive"
|
|
31
|
+
|
|
32
|
+
# 创建 behavior 装饰的节点 → 皇家蓝色边框
|
|
33
|
+
node_behavior = canvas.addNode("Behavior", QPointF(150, 300), NodeShape.RECTANGLE)
|
|
34
|
+
node_behavior.decorator = "behavior"
|
|
35
|
+
|
|
36
|
+
# 创建 sequence 装饰的节点 → 皇家蓝色边框
|
|
37
|
+
node_sequence = canvas.addNode("Sequence", QPointF(450, 300), NodeShape.RECTANGLE)
|
|
38
|
+
node_sequence.decorator = "sequence"
|
|
39
|
+
|
|
40
|
+
# 创建 selector 装饰的节点 → 皇家蓝色边框
|
|
41
|
+
node_selector = canvas.addNode("Selector", QPointF(150, 450), NodeShape.RECTANGLE)
|
|
42
|
+
node_selector.decorator = "selector"
|
|
43
|
+
|
|
44
|
+
# 创建 parallel 装饰的节点 → 皇家蓝色边框
|
|
45
|
+
node_parallel = canvas.addNode("Parallel", QPointF(450, 450), NodeShape.RECTANGLE)
|
|
46
|
+
node_parallel.decorator = "parallel"
|
|
47
|
+
|
|
48
|
+
# 创建菱形节点带 recursive 装饰器
|
|
49
|
+
node_diamond = canvas.addNode("Recursive\nDiamond", QPointF(750, 300), NodeShape.DIAMOND)
|
|
50
|
+
node_diamond.decorator = "recursive"
|
|
51
|
+
|
|
52
|
+
print("=== 装饰器节点边框颜色演示 ===")
|
|
53
|
+
print("紫罗兰色 (Violet) 边框: recursive 装饰的节点")
|
|
54
|
+
print("皇家蓝色 (Royal Blue) 边框: behavior, sequence, selector, parallel 装饰的节点")
|
|
55
|
+
print("")
|
|
56
|
+
print("提示: 点击节点查看选中状态下的颜色效果")
|
|
57
|
+
|
|
58
|
+
canvas.show()
|
|
59
|
+
sys.exit(app.exec())
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
DemoDecoratorColors()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
端口(ports)演示 - 展示节点的输入/输出端口
|
|
3
|
+
|
|
4
|
+
运行:
|
|
5
|
+
python -m chartflow.node.demo_ports
|
|
6
|
+
或:
|
|
7
|
+
python F:/Python/Python312/Lib/site-packages/chartflow/node/demo_ports.py
|
|
8
|
+
|
|
9
|
+
交互:
|
|
10
|
+
- hover 节点超过 0.5s: 显示所有端口 label(外侧文本)
|
|
11
|
+
- hover 单个端口 dot: 该端口立即显示 label + DodgerBlue 描边
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from PyQt6.QtCore import QPointF
|
|
17
|
+
from PyQt6.QtWidgets import QApplication
|
|
18
|
+
|
|
19
|
+
from chartflow.node import NodeCanvasWindow, NodeShape
|
|
20
|
+
from chartflow.node.port_item import QNodePortSpec
|
|
21
|
+
from chartflow.theme import applyTheme
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def build_counter_node(canvas) -> None:
|
|
25
|
+
"""构造一个带完整端口的计数器节点(模仿 uipcweb 的 Counter16)"""
|
|
26
|
+
node = canvas.addNode("Counter16", QPointF(0, 0), NodeShape.RECTANGLE)
|
|
27
|
+
node.ports = {
|
|
28
|
+
"left": [
|
|
29
|
+
QNodePortSpec(name="clk", shape="triangle", size=10, hit="1"),
|
|
30
|
+
QNodePortSpec(name="rst", shape="triangle", size=10, hit="1"),
|
|
31
|
+
QNodePortSpec(name="cmp", shape="trapezoid", size=12, hit="16", label="cmp[15:0]"),
|
|
32
|
+
QNodePortSpec(name="en", shape="circle", size=10, hit="1"),
|
|
33
|
+
],
|
|
34
|
+
"right": [
|
|
35
|
+
QNodePortSpec(name="cnt", shape="trapezoid", size=12, hit="16", label="cnt[15:0]"),
|
|
36
|
+
QNodePortSpec(name="rco", shape="circle", size=10, hit="1"),
|
|
37
|
+
QNodePortSpec(name="flag", shape="square", size=10, hit="8"),
|
|
38
|
+
],
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def build_simple_node(canvas) -> None:
|
|
43
|
+
"""一个最小节点: 只有两个端口,label 未指定(默认等同 name)"""
|
|
44
|
+
node = canvas.addNode("Buf", QPointF(260, 0), NodeShape.RECTANGLE)
|
|
45
|
+
node.ports = {
|
|
46
|
+
"left": [QNodePortSpec(name="in", shape="triangle", size=9, hit="1")],
|
|
47
|
+
"right": [QNodePortSpec(name="out", shape="triangle", size=9, hit="1")],
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def main() -> None:
|
|
52
|
+
app = QApplication(sys.argv)
|
|
53
|
+
applyTheme(app)
|
|
54
|
+
|
|
55
|
+
win = NodeCanvasWindow()
|
|
56
|
+
canvas = win.canvas
|
|
57
|
+
|
|
58
|
+
build_counter_node(canvas)
|
|
59
|
+
build_simple_node(canvas)
|
|
60
|
+
|
|
61
|
+
win.resize(960, 600)
|
|
62
|
+
win.setWindowTitle("chartflow · ports demo")
|
|
63
|
+
win.show()
|
|
64
|
+
|
|
65
|
+
sys.exit(app.exec())
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
main()
|
chartflow/node/enums.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
枚举定义模块
|
|
3
|
+
|
|
4
|
+
遵循代码规范:
|
|
5
|
+
- enum类不能定义函数,数据和方法必须分离
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from enum import Enum, auto
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class NodeState(Enum):
|
|
12
|
+
"""节点状态枚举"""
|
|
13
|
+
IDLE = auto()
|
|
14
|
+
HOVER = auto()
|
|
15
|
+
SELECTED = auto()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NodeShape(Enum):
|
|
19
|
+
"""节点形状枚举"""
|
|
20
|
+
ELLIPSE = auto() # 椭圆形/圆形
|
|
21
|
+
RECTANGLE = auto() # 矩形
|
|
22
|
+
DIAMOND = auto() # 菱形
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ArrowState(Enum):
|
|
26
|
+
"""箭头状态枚举"""
|
|
27
|
+
IDLE = auto()
|
|
28
|
+
HOVER = auto()
|
|
29
|
+
SELECTED = auto()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ConnectionType(Enum):
|
|
33
|
+
"""连线类型枚举"""
|
|
34
|
+
BEZIER = auto() # 贝塞尔曲线
|
|
35
|
+
STRAIGHT = auto() # 直线
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
qnode库使用示例
|
|
3
|
+
|
|
4
|
+
展示如何使用qnode库创建节点图
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from PyQt6.QtWidgets import QVBoxLayout, QLabel, QTextEdit
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
from PyQt6.QtWidgets import QApplication
|
|
12
|
+
from PyQt6.QtCore import QPointF
|
|
13
|
+
|
|
14
|
+
from chartflow.node import (
|
|
15
|
+
NodeCanvasWindow, QNodeCanvas, QNodeItem,
|
|
16
|
+
NodeShape, ConnectionType, QNodeEditor
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MyNodeEditor(QNodeEditor):
|
|
21
|
+
"""自定义节点编辑器示例"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, node, parent=None):
|
|
24
|
+
super().__init__(node, parent)
|
|
25
|
+
self.resize(400, 300)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
layout = QVBoxLayout(self)
|
|
29
|
+
|
|
30
|
+
# 显示节点信息
|
|
31
|
+
info_label = QLabel(f"Editing node: {node.name}")
|
|
32
|
+
layout.addWidget(info_label)
|
|
33
|
+
|
|
34
|
+
# 添加文本编辑器
|
|
35
|
+
self.text_edit = QTextEdit()
|
|
36
|
+
self.text_edit.setPlainText(node.data.get("content", ""))
|
|
37
|
+
layout.addWidget(self.text_edit)
|
|
38
|
+
|
|
39
|
+
def save(self) -> bool:
|
|
40
|
+
"""保存编辑器内容到节点"""
|
|
41
|
+
self.node.data["content"] = self.text_edit.toPlainText()
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def main():
|
|
46
|
+
"""主函数"""
|
|
47
|
+
app = QApplication(sys.argv)
|
|
48
|
+
|
|
49
|
+
# 创建主窗口
|
|
50
|
+
window = NodeCanvasWindow()
|
|
51
|
+
canvas = window.canvas
|
|
52
|
+
|
|
53
|
+
# 添加一些节点
|
|
54
|
+
node1 = canvas.addNode("Start", QPointF(0, 0), NodeShape.ELLIPSE)
|
|
55
|
+
node2 = canvas.addNode("Process", QPointF(200, 100), NodeShape.RECTANGLE)
|
|
56
|
+
node3 = canvas.addNode("Decision", QPointF(400, 0), NodeShape.DIAMOND)
|
|
57
|
+
node4 = canvas.addNode("End", QPointF(600, 100), NodeShape.ELLIPSE)
|
|
58
|
+
|
|
59
|
+
# 设置节点编辑器
|
|
60
|
+
node2.editorClass = MyNodeEditor
|
|
61
|
+
|
|
62
|
+
# 添加一些数据
|
|
63
|
+
node2.data["description"] = "This is a process node"
|
|
64
|
+
node2.data["content"] = "Process logic here..."
|
|
65
|
+
|
|
66
|
+
# 创建连接
|
|
67
|
+
canvas.addConnection(node1, node2)
|
|
68
|
+
canvas.addConnection(node2, node3)
|
|
69
|
+
canvas.addConnection(node3, node4)
|
|
70
|
+
|
|
71
|
+
# 设置双向连接示例
|
|
72
|
+
canvas.addConnection(node2, node4, is_bidirectional=True)
|
|
73
|
+
|
|
74
|
+
# 设置起点节点
|
|
75
|
+
canvas.startNode = node1
|
|
76
|
+
|
|
77
|
+
# 显示窗口
|
|
78
|
+
window.show()
|
|
79
|
+
|
|
80
|
+
sys.exit(app.exec())
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
main()
|