diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 9ff407a..fef8eda 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,10 +5,13 @@
+
+
+
+
+
-
-
-
+
@@ -40,6 +43,7 @@
"keyToString": {
"Python.CompareViews.executor": "Run",
"Python.CompareWindow.executor": "Run",
+ "Python.DAGGraph.executor": "Run",
"Python.MergeView.executor": "Run",
"Python.MileStone.executor": "Run",
"Python.NovelManage.executor": "Debug",
@@ -53,7 +57,7 @@
"last_opened_file_path": "D:/Projects/Python/StoryCheckTools"
}
}]]>
-
+
@@ -76,6 +80,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -120,28 +146,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -165,19 +169,19 @@
+
-
+
-
diff --git a/frame/ContentView.py b/frame/ContentView.py
new file mode 100644
index 0000000..6322abd
--- /dev/null
+++ b/frame/ContentView.py
@@ -0,0 +1,4 @@
+from PyQt5.QtWidgets import QApplication, QWidget
+from networkx import DiGraph
+import networkx as nx
+
diff --git a/frame/MergeView.py b/frame/MergeView.py
index 0b0613f..4519e79 100644
--- a/frame/MergeView.py
+++ b/frame/MergeView.py
@@ -22,10 +22,11 @@ class LinesMergeView(QWidget, MemorySkin):
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
- splitter = QSplitter(Qt.Orientation.Vertical, self)
+ splitter = QSplitter(Qt.Orientation.Horizontal, self)
layout.addWidget(splitter)
self.tabs_con: QTabWidget = QTabWidget(self)
+ self.tabs_con.setTabPosition(QTabWidget.TabPosition.West)
splitter.addWidget(self.tabs_con)
self.define_prev: QTextEdit = QTextEdit(self)
diff --git a/frame/__pycache__/MergeView.cpython-312.pyc b/frame/__pycache__/MergeView.cpython-312.pyc
index fd48e7e..20e055e 100644
Binary files a/frame/__pycache__/MergeView.cpython-312.pyc and b/frame/__pycache__/MergeView.cpython-312.pyc differ
diff --git a/graph/DataType.py b/graph/DataType.py
new file mode 100644
index 0000000..5446d1d
--- /dev/null
+++ b/graph/DataType.py
@@ -0,0 +1,51 @@
+from typing import List
+
+
+class Pos:
+ def __init__(self, x: float = 0, y: float = 0):
+ self.x_pos = x
+ self.y_pos = y
+ pass
+
+ def make_copy(self) -> 'Pos':
+ return Pos(self.x_pos, self.y_pos)
+
+
+class Point:
+ def __init__(self, name:str, pos: Pos = Pos()):
+ self.point_name = name
+ self.pos = pos
+ pass
+
+ def name(self) -> str:
+ return self.point_name
+
+ def make_copy(self) -> 'Point':
+ return Point(self.point_name, self.pos.make_copy())
+
+
+class Line:
+ def __init__(self, p0: Point, p1: Point):
+ self.point_set = [p0, p1]
+ pass
+
+ def points(self) -> List[Point]:
+ return self.point_set
+
+ def make_copy(self) -> 'Line':
+ return Line(self.points()[0].make_copy(), self.points()[1].make_copy())
+
+
+class Arrow(Line):
+ def __init__(self, start: Point, end: Point):
+ Line.__init__(self, start, end)
+ pass
+
+ def start_point(self):
+ return self.point_set[0]
+
+ def end_point(self):
+ return self.point_set[1]
+
+ pass
+
diff --git a/graph/__init__.py b/graph/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/graph/__pycache__/DataType.cpython-312.pyc b/graph/__pycache__/DataType.cpython-312.pyc
new file mode 100644
index 0000000..4ec0844
Binary files /dev/null and b/graph/__pycache__/DataType.cpython-312.pyc differ
diff --git a/graph/__pycache__/__init__.cpython-312.pyc b/graph/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..b6096b1
Binary files /dev/null and b/graph/__pycache__/__init__.cpython-312.pyc differ
diff --git a/graph/dagpresent/DAGGraph.py b/graph/dagpresent/DAGGraph.py
new file mode 100644
index 0000000..268d403
--- /dev/null
+++ b/graph/dagpresent/DAGGraph.py
@@ -0,0 +1,315 @@
+from graph.DataType import Point, Arrow
+from typing import List, Dict, Tuple
+from enum import Enum
+
+
+class Direction(Enum):
+ RankLR = 0,
+ RankTB = 1,
+
+
+class DAGLayerHelper:
+ def __init__(self, bind: Point):
+ self.bind_node = bind
+
+ self.input_count: int = 0
+ self.next_points: List[DAGLayerHelper] = []
+ self.layer_v: int = 0
+ pass
+
+ def bind_point(self) -> Point:
+ return self.bind_node
+
+ def next_append(self, inst: 'DAGLayerHelper'):
+ self.next_points.append(inst)
+ inst.input_count += 1
+ pass
+
+ def next_nodes(self) -> List['DAGLayerHelper']:
+ return self.next_points
+
+ def make_copy(self) -> 'DAGLayerHelper':
+ temp_ps = []
+ for n in self.next_points:
+ temp_ps.append(n.make_copy())
+ pass
+
+ ins = DAGLayerHelper(self.bind_node.make_copy())
+ ins.input_count = self.input_count
+ ins.next_points = temp_ps
+ ins.layer_v = self.layer_v
+ ins.sort_v = self.sort_v
+ return ins
+
+
+class DAGOrderHelper:
+ def __init__(self, level:int = 0, relate:DAGLayerHelper|None = None, bind: DAGLayerHelper|None = None):
+ self.layer_bind = bind
+ self.relate_bind = relate
+ self.layer_number = level
+ self.sort_number:float = 0
+ self.__prev_layer_nodes: List['DAGOrderHelper'] = []
+
+ if bind is not None:
+ self.layer_number = bind.layer_v
+ pass
+ pass
+
+ def is_fake_node(self) -> bool:
+ return self.layer_bind is None
+
+ def get_previous_sibling_layer_nodes(self):
+ return self.__prev_layer_nodes
+
+ def append_previous_sibling_layer_node(self, node: 'DAGOrderHelper'):
+ self.__prev_layer_nodes.append(node)
+ pass
+
+
+class DAGGraph:
+ def __init__(self, orie: Direction):
+ self.orientation = orie
+ self.graph_inst: Dict[str, DAGLayerHelper] = {}
+ self.nodes_with_layout: List[DAGOrderHelper] = []
+ pass
+
+ def rebuild_from_edges(self, arrow_list: List[Arrow]) -> None:
+ """
+ 通过有序边构建有向图
+ :param arrow_list: 有向边集合
+ """
+ for arr in arrow_list:
+ start = arr.start_point()
+ start_helper = None
+ if start.point_name in self.graph_inst:
+ start_helper = self.graph_inst[start.point_name]
+ else:
+ start_helper = DAGLayerHelper(start)
+ self.graph_inst[start.point_name] = start_helper
+
+ end = arr.end_point()
+ end_helper = None
+ if end.point_name in self.graph_inst:
+ end_helper = self.graph_inst[end.point_name]
+ else:
+ end_helper = DAGLayerHelper(end)
+ self.graph_inst[end.point_name] = end_helper
+
+ start_helper.next_append(end_helper)
+ pass
+ pass
+
+ def __spawns_peak(self, refset: List[DAGLayerHelper]) -> Tuple[DAGLayerHelper, List[DAGLayerHelper]] | None:
+ """
+ 拓扑排序迭代处理
+ :param refset:
+ :return:
+ """
+ for inst in refset:
+ if inst.input_count == 0:
+ for it_nxt in inst.next_nodes():
+ refset.append(it_nxt)
+ it_nxt.input_count -= 1
+ pass
+
+ refset.remove(inst)
+ if inst.bind_point().point_name in self.graph_inst:
+ self.graph_inst.pop(inst.bind_point().point_name)
+
+ return inst, refset
+
+ for inst in self.graph_inst.values():
+ if inst.input_count == 0:
+ if inst in refset:
+ refset.remove(inst)
+
+ for it_nxt in inst.next_nodes():
+ refset.append(it_nxt)
+ it_nxt.input_count -= 1
+ pass
+
+ self.graph_inst.pop(inst.bind_point().point_name)
+ return inst, refset
+ pass
+
+ if len(self.graph_inst) > 0:
+ raise RuntimeError("有向无环图中发现环形结构!")
+
+ return None
+
+ def __graph_recovery(self, sort_seqs: List[DAGLayerHelper]) -> None:
+ """
+ 通过拓扑排序结果恢复数据图
+ :param sort_seqs: 有序序列
+ """
+
+ # 清空cache
+ for it in sort_seqs:
+ it.input_count = 0
+ pass
+
+ # 入度复原
+ for it in sort_seqs:
+ for nxt in it.next_nodes():
+ nxt.input_count += 1
+ pass
+ pass
+
+ # 数据图恢复
+ self.graph_inst.clear()
+ for it in sort_seqs:
+ self.graph_inst[it.bind_point().point_name] = it
+ pass
+
+ pass
+
+ def __node_layering(self, inst: DAGLayerHelper, layer_current: int = 0) -> int:
+ """
+ 节点分层处理,返回路径最大长度
+ :param inst: 当前节点
+ :param layer_current: 节点等级
+ :return: 最长路径长度
+ """
+ inst.layer_v = max(inst.layer_v, layer_current)
+
+ max_remains = inst.layer_v
+ values = inst.next_nodes()
+ for fork in values:
+ max_remains = max(self.__node_layering(fork, inst.layer_v + 1), max_remains)
+ pass
+
+ return max_remains + 1
+
+ def __tidy_graph_nodes(self) -> List[DAGOrderHelper]:
+ nodes_temp: Dict[str, DAGOrderHelper] = {}
+
+ # 注册所有数据图实节点
+ for node in self.graph_inst.values():
+ nodes_temp[node.bind_point().point_name] = DAGOrderHelper(bind=node, relate=node)
+ pass
+
+ temp_array: List[DAGOrderHelper] = []
+ temp_array.extend(nodes_temp.values())
+
+ # 生成链接fake-node节点,并执行链接
+ for node in self.graph_inst.values():
+ for next in node.next_nodes():
+ node_links = [nodes_temp[node.bind_point().point_name]]
+ for layer_index in range(node.layer_v + 1, next.layer_v):
+ node_links.append(DAGOrderHelper(layer_index, relate=node))
+ pass
+
+ node_links.append(nodes_temp[next.bind_point().point_name])
+
+ # 节点链接串已经构建完成,链接各层级节点
+ for idx in range(1, len(node_links)):
+ start_point = node_links[idx-1]
+ end_point = node_links[idx]
+ end_point.append_previous_sibling_layer_node(start_point)
+ pass
+
+ temp_array.extend(node_links[1:len(node_links)-1])
+ pass
+ pass
+
+ return temp_array
+
+ def __graph_layer_nodes_sort(self, layer_index:int, nodes: List[DAGOrderHelper]):
+ # 提取当前层次的节点
+ target_nodes_within_layer = []
+ for n in nodes:
+ if n.layer_number == layer_index:
+ target_nodes_within_layer.append(n)
+ pass
+ pass
+
+ # 当前层次没有节点,则不做处理
+ if len(target_nodes_within_layer) == 0:
+ return
+
+ if layer_index == 0:
+ for idx in range(0, len(target_nodes_within_layer)):
+ target_nodes_within_layer[idx].sort_number = idx
+ pass
+ pass
+
+ elif layer_index > 0:
+ # 计算排序系数
+ for target_node in target_nodes_within_layer:
+ prev_sorts = list(map(lambda n:n.sort_number, target_node.get_previous_sibling_layer_nodes()))
+ target_node.sort_number = sum(prev_sorts)/len(prev_sorts)
+ pass
+
+ def compare_item(a: DAGOrderHelper):
+ return a.sort_number
+
+ # 整理节点排序
+ target_nodes_within_layer.sort(key=compare_item)
+ for idx in range(0, len(target_nodes_within_layer)):
+ target_nodes_within_layer[idx].sort_number = idx
+ pass
+
+ pass
+
+ self.__graph_layer_nodes_sort(layer_index + 1, nodes)
+ pass
+
+
+ def graph_layout(self):
+ sort_seqs = []
+
+ # 拓扑排序
+ head = None
+ refs = []
+ while True:
+ peaks_result = self.__spawns_peak(refs)
+ if peaks_result is None:
+ break
+
+ head, refs = peaks_result
+ sort_seqs.append(head)
+ pass
+
+ # 数据图恢复
+ self.__graph_recovery(sort_seqs)
+
+ # 数据图节点分层
+ max_length = 0
+ for item in sort_seqs:
+ if item.input_count == 0:
+ max_length = max(max_length, self.__node_layering(item))
+ pass
+ pass
+
+ # 整理数据图节点
+ rich_nodes = self.__tidy_graph_nodes()
+ self.__graph_layer_nodes_sort(0, rich_nodes)
+ self.nodes_with_layout = rich_nodes
+
+ pass
+
+ def visible_nodes(self) -> List[DAGOrderHelper]:
+ retvs = []
+ for n in self.nodes_with_layout:
+ if not n.is_fake_node():
+ retvs.append(n)
+ pass
+ pass
+ return retvs
+
+
+if __name__ == "__main__":
+ graph = DAGGraph(Direction.RankLR)
+ arrows = [
+ Arrow(Point('a'), Point('b')),
+ Arrow(Point('a'), Point('c')),
+ Arrow(Point('c'), Point('d')),
+ Arrow(Point('a'), Point('d')),
+ ]
+ graph.rebuild_from_edges(arrows)
+
+ graph.graph_layout()
+
+ points = graph.visible_nodes()
+ for p in points:
+ print(f"{p.layer_bind.bind_point().point_name},level{p.layer_number},sort{p.sort_number}")
\ No newline at end of file
diff --git a/graph/dagpresent/__init__.py b/graph/dagpresent/__init__.py
new file mode 100644
index 0000000..e69de29