From 0538a059d9ac485fd8c46b9849aa818442c44fc1 Mon Sep 17 00:00:00 2001 From: codeboss <2422523675@qq.com> Date: Wed, 31 Jul 2024 10:12:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E7=A1=80=E7=9A=84=E5=9B=BE=E5=BD=A2?= =?UTF-8?q?=E7=BB=98=E5=88=B6=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/workspace.xml | 45 ++--- graph/DataType.py | 5 +- graph/__pycache__/DataType.cpython-312.pyc | Bin 3182 -> 3033 bytes graph/__pycache__/__init__.cpython-312.pyc | Bin 144 -> 144 bytes graph/directed_acyclic_graph/DAGLayout.py | 9 +- graph/directed_acyclic_graph/DAGPresent.py | 173 +++++++++++++----- .../__pycache__/DAGLayout.cpython-312.pyc | Bin 13321 -> 13274 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 167 -> 167 bytes 8 files changed, 154 insertions(+), 78 deletions(-) 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 9c16c97bbdae5238e04487896adcba4c18a9c084..7644fdf907547892bc1ce203f5f22e4fdff426e6 100644 GIT binary patch delta 438 zcmX9&O-lk%6n*#2htB9Q_|Y_JDSc>2(yomyi-L#{YURelv1pOS=tgj1U?9{&dxf=Z zDXzl)MH?Z4l4#pb42`zkHywEAoO`+FzB@4AC$(oyQwd`Kx_vnHsQHC0%1gv3iPR!_ zk^zfks0Df9kXp1RW<*PHyog@1T$Y3?m*s@JFQre93#GnTI5^4YD60y=l;7D>feOj; ztq#h>R?xJeub%Nhmd_`s?Zu`YMk?|n*q1P?@m7_*g(7wFN@vsJ2DN9{gBoCE(FXx` zDaKU+NBA2hSr1}n2hbyS|3AUCA*}3(liHMwl!0Z@hg2mWda$wA$~LnvdaUP;**bQ2 zFcIJh9!t3FAomlT4AzlW=A&q9WJsx`-K~uX%Fqo`w2>PTl(7s_oayo>gxNs8fM~bm z@&e4UbN3B|x`wg^(k!Ks??8$Q*Qy~6LkbX@C(!gyR#-<09`uYz+ HanAb#s83%= delta 459 zcmXAjPe@cz6vpp)_ulvC>SLmroR}F$so~W_V^)GR5<%2TM5IN|!YRCOu%J<{ZfZ5b z1TI`S9K+h#rUF;t-d!sn+NXqJLmB6oqLcyxf$++p<+}=f4|kI zzlYa(yFYmLfEMd4VO7wq>NqUno7O{B2MvkPPQvR#^OmeiEfP_8v}}p0t9|Vfy*FMQ zbmipy%f?byG+w+;x_n`NahBtAl}8EvkqIa(cDic8dTQ2U3Uy~?|cS#q2j#qu;rD~SI30O+0deu z0&aUn?1*aIoMv$T55?e?tMM z-S;rK^~dc6xQ)l&8sxX$`6Yn6_$BxR!}w6Op^U%OINZU3o)1dF*cl_a%;9!V{vQc{ BZ^!@u diff --git a/graph/__pycache__/__init__.cpython-312.pyc b/graph/__pycache__/__init__.cpython-312.pyc index 37ebf2608daae5111dcdc1178b132595550dfabd..b6096b16fdb5dfe97ba61e099854fc0893b6f3ee 100644 GIT binary patch delta 18 YcmbQhIDwJtG%qg~0}wp%oXFJ%04JdYY5)KL delta 18 YcmbQhIDwJtG%qg~0}wp 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 de3f8869931a1c6a37f20475b4c7a0a535bee4ca..a1055ec1aa3c3c1f1e78b82aca196abe7e443260 100644 GIT binary patch delta 1172 zcmZvbU1%It6vyw~kJ*pzWOlQgrm^X6o84wJ+3aUh*EF)(ZG%dsN-YtMF`KnJO&yGRXU!56(}VnwSL=6BDz z=gj}!d*;mLQ^taLR}|fZk00M#KUw%htU0JfZ#pd0Z9Ps{FAvBL_{b^4_s%1<27|6D zeCWDO-TJJ1g$g4)&f@U%jxidAum@-oj)?)d>lvnF@SA6bj-sFKUQimvMOAAsrBp4} z>SaYczi+DW^z*E&uxFHts<6a=Q?hVVT;d9;u7Qa_NmZ3v`QIYv!;fhEU(!KX@5;P1 zjt7n5ZQN`S?I>c1!?2#NHJA%sKf1Qp>h%gcfPNNn5FsN1h;9T5nHYpo=~)_si_#x7 z0T;ddddsv)iWVL>$2r10+>ouX?hSbjsakrySSg)TSn))Cu?Amy<$*kpYyBLT$mW6U zvTa4$%#JVHHk`rD&|aMTb+_*w3(f0S{1bLQeZA|tl}}6kq9YjR@3Gw+GG%DX9+t5C1es5p zWF==KJV^rI?#F}+0ylk->IsCK!i~R*PDzt57yp)g_zB!_EPj|Cg4K9TC~!gE)}P1s z!&bc8=HM>45|7hwAfG5d(G%PvHkWUCYE$xeq)cC|_QjcY;gxoDcGb4wjX)>yRS>1Oy9etgTek~uXG-I&#v*VhnWcN+_vG}Be{>kY(Z#0uh(F3jN?Vv;}F9gerjmc?cl?|Yz~y4D}$R-QWSJ?}Tx zo@<3he5pKZ<5h)x4fz zR~h~|NzCd)JcTej23v%s5HeIUyP_X(lk!K?IPY62rT0km9*ML_R{$B{kgG2g_ H%pCm_1^Oww delta 1291 zcmai!U2NM_6vyw4W5;omrfV9~ZEX{^NfWR6XxeUN?dY0OZPTD2F%TaEvTX5^lzizr zE3~+P0EvJ)U^)*&h=&SIsEDU2PiPVwXySnhiIAaH7E*aff-xcO1qtqPhJ^OOj^y7x z_v4&nAOCGL|8Bv3-R)KgxBcYvGi#UJ6$cG94Tp`w?>?X35Hb$jL^Firg!#Fn%FvKi z*_8Aut7n$sl6;J=z+ZA%@$I~D;{1t*u&zMC`4#PIMwE3b#d!=%z?WT9Gz!0U0Ud`o zU2}9VeC>Mf^(ox!=g=ufko9t!!Ms+_)R?whte({~dht}LSjlHf*%ggtD%G5(uO=7L zM;>Q6jUClWHH{_i7w{lf=;nBXYj;f54 zVPE%bP492mZ^%QqkmK2Lu$b77)tQ-Y&VBW8TzV=VIKAbA_mWo zr06WXKjNn|&Cf=zQRjTCZ~p4;8-0sSJ$%I`Pj({1&6#MFCMPgx0C5~~27xC`d#R|` zO`)8vS$!}C{cM?MKd(MZ!_8mSAayUb=9b!XrS@>SJy?PMu}|q?xIA{QH%He*gD#L4 zxbD^@Lt2s`>+<^OOT6c7ppLD!%n%I%SAssc-0SpwVe2feiB7Vfw8IB6Kl~DNiVlM; zc`!ai4adKSC^7hYM!uVf@W#5huR0+de;CT~h;#_2GJG5lk0|BrE18m3$=3^}y;#@E zroxJ+3mqNnm?H>3#rM(gz?H~NKHzUCTYZDKiQT!Qv0zCx(DDQ}Qa^e^KMw{sc5Oa+ zGoEh6(^o_7_>uPD(GL~aOng6t1v{$2W~u_)!3R@hT76k(VSwZ`9fIr0r@H>5J{TL1 zEgrzYlL(Y2dkgU@;yuJU#69Xeh--*>e(B$EyiIP~1krs;2)!9)w(K`LtEY%7Qmzig}i;6-+71SoLL{E%LQaaeO9Is#W<1qMNj4J)SAn z7=AG0cxor&Fv5}%>V~Bdd*Gpo{>UfXMe*zSYq=#*O1DX5n}k{ delta 19 ZcmZ3^xSWyuG%qg~0}wp