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