diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 63b31c0..2d48b85 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -6,6 +6,7 @@
+
@@ -35,29 +36,29 @@
- {
- "keyToString": {
- "Python.CompareViews.executor": "Run",
- "Python.CompareWindow.executor": "Run",
- "Python.DAGGraph (1).executor": "Run",
- "Python.DAGGraph.executor": "Run",
- "Python.DAGLayout (1).executor": "Run",
- "Python.DAGLayout.executor": "Run",
- "Python.DAGPresent.executor": "Run",
- "Python.MergeView.executor": "Run",
- "Python.MileStone.executor": "Run",
- "Python.NovelManage.executor": "Debug",
- "Python.ReferView.executor": "Run",
- "Python.StoryMap.executor": "Run",
- "Python.ast_load.executor": "Debug",
- "Python.entry.executor": "Run",
- "RunOnceActivity.OpenProjectViewOnStart": "true",
- "RunOnceActivity.ShowReadmeOnStart": "true",
- "git-widget-placeholder": "master",
- "last_opened_file_path": "D:/Projects/Python/StoryCheckTools",
- "settings.editor.selected.configurable": "reference.settings.ide.settings.new.ui"
+
+}]]>
diff --git a/graph/DataType.py b/graph/DataType.py
index 5446d1d..100a964 100644
--- a/graph/DataType.py
+++ b/graph/DataType.py
@@ -12,16 +12,15 @@ class Pos:
class Point:
- def __init__(self, name:str, pos: Pos = Pos()):
+ def __init__(self, name:str):
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())
+ return Point(self.point_name)
class Line:
diff --git a/graph/__pycache__/DataType.cpython-312.pyc b/graph/__pycache__/DataType.cpython-312.pyc
index 9c16c97..7644fdf 100644
Binary files a/graph/__pycache__/DataType.cpython-312.pyc 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
index 37ebf26..b6096b1 100644
Binary files a/graph/__pycache__/__init__.cpython-312.pyc and b/graph/__pycache__/__init__.cpython-312.pyc differ
diff --git a/graph/directed_acyclic_graph/DAGLayout.py b/graph/directed_acyclic_graph/DAGLayout.py
index 1afc0bf..cde4ffb 100644
--- a/graph/directed_acyclic_graph/DAGLayout.py
+++ b/graph/directed_acyclic_graph/DAGLayout.py
@@ -32,7 +32,6 @@ class DAGLayerHelper:
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
@@ -53,10 +52,10 @@ class DAGOrderHelper:
def is_fake_node(self) -> bool:
return self.layer_bind is None
- def get_previous_sibling_layer_nodes(self):
+ def get_upstream_nodes(self):
return self.__prev_layer_nodes
- def append_previous_sibling_layer_node(self, node: 'DAGOrderHelper'):
+ def append_upstream_node(self, node: 'DAGOrderHelper'):
self.__prev_layer_nodes.append(node)
pass
@@ -202,7 +201,7 @@ class DAGGraph:
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)
+ end_point.append_upstream_node(start_point)
pass
temp_array.extend(node_links[1:len(node_links)-1])
@@ -233,7 +232,7 @@ class DAGGraph:
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()))
+ prev_sorts = list(map(lambda n:n.sort_number, target_node.get_upstream_nodes()))
target_node.sort_number = sum(prev_sorts)/len(prev_sorts)
pass
diff --git a/graph/directed_acyclic_graph/DAGPresent.py b/graph/directed_acyclic_graph/DAGPresent.py
index 4e98714..955fdd4 100644
--- a/graph/directed_acyclic_graph/DAGPresent.py
+++ b/graph/directed_acyclic_graph/DAGPresent.py
@@ -1,9 +1,9 @@
from graph.directed_acyclic_graph.DAGLayout import DAGGraph, DAGOrderHelper
from graph.DataType import Arrow, Point
-from typing import List
+from typing import List, Dict
from PyQt5.QtWidgets import QWidget, QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem
from PyQt5.QtCore import QRectF, Qt, QPointF
-from PyQt5.QtGui import QFont, QBrush, QPen, QPainter
+from PyQt5.QtGui import QFont, QBrush, QPen, QPainter, QPainterPath
import sys
from enum import Enum
from abc import abstractmethod
@@ -15,17 +15,27 @@ class GraphNode:
raise NotImplementedError("boundingRect")
@abstractmethod
- def pos(self)->QPointF:
+ def pos(self) -> QPointF:
raise NotImplementedError("pos")
+class StoryNodeType(Enum):
+ """
+ 故事节点类型
+ """
+ StoryStartNode = 0,
+ FragmentNode = 1,
+
+
class StoryFragmentNode(QGraphicsItem, GraphNode):
"""
故事情节名称节点
"""
- def __init__(self, name:str, font: QFont, parent):
+
+ def __init__(self, name: str, type: StoryNodeType, font: QFont, parent):
QGraphicsItem.__init__(self, parent)
+ self.node_type_within_story = type
self.fragment_name = name
self.font_bind = font
self.data_bind: object = None
@@ -33,14 +43,20 @@ class StoryFragmentNode(QGraphicsItem, GraphNode):
def boundingRect(self) -> QRectF:
size = self.font_bind.pixelSize()
- return QRectF(0, 0, size * len(self.fragment_name) + 10, size + 10)
+ if self.node_type_within_story == StoryNodeType.FragmentNode:
+ return QRectF(0, 0, size * len(self.fragment_name) + 10, size + 10)
+ else:
+ return QRectF(0, 0, size * len(self.fragment_name) + 10, size + 10)
- def paint(self, painter, option, widget = ...):
+ def paint(self, painter, option, widget=...):
outline = self.boundingRect()
- text_rect = QRectF(outline.x()+5, outline.y()+5, outline.width()-10, outline.height() - 10)
+ text_rect = QRectF(outline.x() + 5, outline.y() + 5, outline.width() - 10, outline.height() - 10)
painter.save()
- painter.fillRect(outline, QBrush(Qt.gray))
+ if self.node_type_within_story == StoryNodeType.FragmentNode:
+ painter.fillRect(outline, QBrush(Qt.gray))
+ else:
+ painter.fillRect(outline, Qt.green)
painter.drawRect(outline)
painter.setFont(self.font_bind)
painter.drawText(text_rect, self.fragment_name)
@@ -48,7 +64,6 @@ class StoryFragmentNode(QGraphicsItem, GraphNode):
pass
-
# 添加自定义功能函数
def attach_data(self, data_inst: object):
self.data_bind = data_inst
@@ -64,9 +79,11 @@ class PenetrateNode(QGraphicsItem, GraphNode):
"""
贯穿连线节点
"""
- def __init__(self, width:float, parent):
+
+ def __init__(self, width: float, parent):
QGraphicsItem.__init__(self, parent)
self.width_store = width
+ self.data_bind: object = None
pass
def resize(self, width: float):
@@ -77,7 +94,7 @@ class PenetrateNode(QGraphicsItem, GraphNode):
def boundingRect(self):
return QRectF(0, 0, self.width_store, 8)
- def paint(self, painter, option, widget = ...):
+ def paint(self, painter, option, widget=...):
outline = self.boundingRect()
painter.save()
@@ -85,27 +102,40 @@ class PenetrateNode(QGraphicsItem, GraphNode):
painter.restore()
pass
+ # 添加自定义功能函数
+ def attach_data(self, data_inst: object):
+ self.data_bind = data_inst
+ pass
-class CurveTransition(QGraphicsItem):
+ def get_data(self) -> object:
+ return self.data_bind
+
+
+class TransitionCurve(QGraphicsItem):
def __init__(self, start: GraphNode, end: GraphNode, parent):
QGraphicsItem.__init__(self, parent)
self.start_node = start
self.end_node = end
self.outline = QRectF()
+ self.data_bind: object = None
pass
def layout_refresh(self):
- self.setPos(self.start_node.boundingRect().topRight())
+ gpos = self.start_node.pos() + self.start_node.boundingRect().topRight()
+ self.setPos(gpos)
+
+ orect = self.start_node.boundingRect()
+ erect = self.end_node.boundingRect()
self.outline = QRectF(0, 0,
- self.end_node.pos().x() - self.start_node.pos().x() - self.start_node.boundingRect().width(),
- self.end_node.pos().y() + self.end_node.boundingRect().height() - self.start_node.pos().y())
+ self.end_node.pos().x() - self.start_node.pos().x() - orect.width(),
+ self.end_node.pos().y() + erect.height() - self.start_node.pos().y())
pass
def boundingRect(self):
return self.outline
pass
- def paint(self, painter, option, widget = ...):
+ def paint(self, painter, option, widget=...):
outline = self.outline
painter.save()
@@ -113,16 +143,31 @@ class CurveTransition(QGraphicsItem):
start_rect = self.start_node.boundingRect()
end_rect = self.end_node.boundingRect()
- start_pos = QPointF(0, start_rect.height()/2)
- end_pos = QPointF(outline.width(), outline.height() - end_rect.height()/2)
+ start_pos = QPointF(0, start_rect.height() / 2)
+ control_pos0 = start_pos + QPointF(outline.width()/3, 0)
+ end_pos = QPointF(outline.width(), outline.height() - end_rect.height() / 2)
+ control_pos1 = end_pos + QPointF(-outline.width()/3, 0)
npen = QPen(Qt.black)
npen.setWidthF(4)
painter.setPen(npen)
- painter.drawLine(start_pos, end_pos)
+
+ path0 = QPainterPath()
+ path0.moveTo(start_pos)
+ path0.cubicTo(control_pos0, control_pos1, end_pos)
+ painter.drawPath(path0)
+
painter.restore()
pass
+ # 添加自定义功能函数
+ def attach_data(self, data_inst: object):
+ self.data_bind = data_inst
+ pass
+
+ def get_data(self) -> object:
+ return self.data_bind
+
class Direction(Enum):
RankLR = 0,
@@ -133,24 +178,16 @@ class DAGActiveView(QGraphicsView):
def __init__(self, orie: Direction, parent):
QGraphicsView.__init__(self, parent)
- self.transition_with = 200
+ self.layer_span = 200
+ self.node_span = 50
self.orientation = orie
self.scene_bind = QGraphicsScene(self)
self.setScene(self.scene_bind)
- font_global = QFont()
- font_global.setPixelSize(20)
-
- one = StoryFragmentNode("实例节点:示例情节", font_global, None)
- self.scene_bind.addItem(one)
- two = PenetrateNode(200, None)
- two.setPos(QPointF(500, 400))
- self.scene_bind.addItem(two)
-
- connectx = CurveTransition(one, two, None)
- self.scene_bind.addItem(connectx)
- connectx.layout_refresh()
+ font = QFont()
+ font.setPixelSize(20)
+ self.setFont(font)
pass
@@ -160,39 +197,68 @@ class DAGActiveView(QGraphicsView):
tools.graph_layout()
total_nodes = tools.nodes_with_layout
+ previous_node_end = 0
+ previous_graphics_nodes = []
+ # 迭代呈现层
for layer_idx in range(0, tools.max_layer_count):
- current_level_nodes: List[DAGOrderHelper] = list(filter(lambda n:n.layer_number == layer_idx, total_nodes))
- current_level_nodes.sort(key=lambda n:n.sort_number)
+ current_level_nodes: List[DAGOrderHelper] = list(filter(lambda n: n.layer_number == layer_idx, total_nodes))
+ current_level_nodes.sort(key=lambda n: n.sort_number)
# 构建当前层节点
ypos_acc = 0
- all_nodes = []
+ all_graphics_nodes = []
for node in current_level_nodes:
if node.is_fake_node():
- node = PenetrateNode(20, None)
- ypos_acc += node.boundingRect().height()
- all_nodes.append(node)
- self.scene_bind.addItem(node)
+ curr_gnode = PenetrateNode(20, None)
+ curr_gnode.setPos(previous_node_end, ypos_acc)
+ curr_gnode.attach_data(node)
+ ypos_acc += curr_gnode.boundingRect().height()
+ all_graphics_nodes.append(curr_gnode)
+ self.scene_bind.addItem(curr_gnode)
else:
- node = StoryFragmentNode(node.layer_bind.bind_point().point_name, self.font(), None)
- ypos_acc += node.boundingRect().height()
- all_nodes.append(node)
- self.scene_bind.addItem(node)
+ if layer_idx == 0:
+ curr_gnode = StoryFragmentNode(node.layer_bind.bind_point().point_name, StoryNodeType.StoryStartNode,
+ self.font(), None)
+ else:
+ curr_gnode = StoryFragmentNode(node.layer_bind.bind_point().point_name, StoryNodeType.FragmentNode,
+ self.font(), None)
+ curr_gnode.attach_data(node)
+ curr_gnode.setPos(previous_node_end, ypos_acc)
+ ypos_acc += curr_gnode.boundingRect().height()
+ all_graphics_nodes.append(curr_gnode)
+ self.scene_bind.addItem(curr_gnode)
pass
+
+ ypos_acc += self.node_span
pass
+ # 调整同层节点宽度
width_max = 0
- for n in all_nodes:
+ for n in all_graphics_nodes:
width_max = max(width_max, n.boundingRect().width())
pass
-
- for n in all_nodes:
+ for n in all_graphics_nodes:
if n is PenetrateNode:
n.resize(width_max)
pass
pass
- pass
+ previous_node_end += width_max + self.layer_span
+ if len(previous_graphics_nodes) > 0:
+ for curr_gnode in all_graphics_nodes:
+ sort_helper: DAGOrderHelper = curr_gnode.get_data()
+ for prev_gnode in previous_graphics_nodes:
+ if prev_gnode.get_data() in sort_helper.get_upstream_nodes():
+ line_cmbn = TransitionCurve(prev_gnode, curr_gnode, None)
+ self.scene_bind.addItem(line_cmbn)
+ line_cmbn.layout_refresh()
+ pass
+ pass
+ pass
+ pass
+
+ previous_graphics_nodes = all_graphics_nodes
+ pass
pass
@@ -201,4 +267,15 @@ if __name__ == "__main__":
app = QApplication(sys.argv)
view = DAGActiveView(Direction.RankLR, None)
view.show()
- app.exec()
\ No newline at end of file
+
+ arrows = [
+ Arrow(Point('a'), Point('b')),
+ Arrow(Point('a'), Point('c')),
+ Arrow(Point('c'), Point('d')),
+ Arrow(Point('a'), Point('d')),
+ Arrow(Point('c'), Point('e')),
+ Arrow(Point('c'), Point('f')),
+ ]
+ view.update_with_edges(arrows)
+
+ app.exec()
diff --git a/graph/directed_acyclic_graph/__pycache__/DAGLayout.cpython-312.pyc b/graph/directed_acyclic_graph/__pycache__/DAGLayout.cpython-312.pyc
index de3f886..a1055ec 100644
Binary files a/graph/directed_acyclic_graph/__pycache__/DAGLayout.cpython-312.pyc and b/graph/directed_acyclic_graph/__pycache__/DAGLayout.cpython-312.pyc differ
diff --git a/graph/directed_acyclic_graph/__pycache__/__init__.cpython-312.pyc b/graph/directed_acyclic_graph/__pycache__/__init__.cpython-312.pyc
index ad685ac..13683fd 100644
Binary files a/graph/directed_acyclic_graph/__pycache__/__init__.cpython-312.pyc and b/graph/directed_acyclic_graph/__pycache__/__init__.cpython-312.pyc differ