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-Iv*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