diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 61f4fb4..38adec9 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -7,6 +7,8 @@
+
+
@@ -190,7 +192,7 @@
-
+
diff --git a/frame/ContentView.py b/frame/ContentView.py
index 6070e3f..ece389c 100644
--- a/frame/ContentView.py
+++ b/frame/ContentView.py
@@ -1,7 +1,9 @@
import sys
from typing import Dict, List
-from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
+from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QMainWindow
+from PyQt5.QtWidgets import QMenu,QAction
+from PyQt5.QtCore import QPoint
from graph.DataType import Arrow, Point
from graph.directed_acyclic_graph.DAGPresent import DAGActiveView, Direction
@@ -10,21 +12,24 @@ from parse.ast_load import global_ast_path
class FragmentPoint(Point):
- def __init__(self, name: str, start_mark: bool):
- Point.__init__(self, name, start_mark)
+ def __init__(self, name: str):
+ Point.__init__(self, name)
pass
-class ContentWindow(QWidget):
+class ContentWindow(QMainWindow):
def __init__(self, parent):
- QWidget.__init__(self, parent)
- layout = QVBoxLayout(self)
- self.fragment_view = DAGActiveView(Direction.RankLR, self)
- layout.addWidget(self.fragment_view)
+ QMainWindow.__init__(self, parent)
+ self.fragment_view = DAGActiveView(self)
+ self.setCentralWidget(self.fragment_view)
+ self.fragment_view.nodes_clicked.connect(self.print_node_list)
+ self.present_graph: Dict[str, StoryMap] = None
pass
pass
def present_stories_graph(self, graph: Dict[str, StoryMap]) -> None:
+ self.present_graph = graph
+
arrows: List[Arrow] = []
for story_name in graph:
fragments = graph[story_name].slice_list
@@ -42,8 +47,8 @@ class ContentWindow(QWidget):
end_node_label = f"{end_frag.story_refer}&{end_frag.fragm_refer}"
arrows.append(Arrow(
- FragmentPoint(start_node_label, fragi == 1),
- FragmentPoint(end_node_label, False))
+ FragmentPoint(start_node_label),
+ FragmentPoint(end_node_label))
)
pass
pass
@@ -51,6 +56,60 @@ class ContentWindow(QWidget):
self.fragment_view.update_with_edges(arrows)
pass
+ def print_node_list(self, xpos, ypos, list):
+ if len(list) == 0:
+ return
+
+ if len(list) > 1:
+ pass
+ else:
+ node_x = list[0]
+ if node_x[0].startswith("node"):
+ node_sections = [s for s in node_x[1].split("&") if s != ""]
+ if len(node_sections) == 1:
+ story = self.present_graph[node_sections[0]]
+ node_names = [node_sections[0]]
+ for slice in story.slice_list[1:]:
+ if slice.is_define_node[0]:
+ node_names.append(f"{slice.parent_story}&{slice.is_define_node[1]}")
+ else:
+ node_names.append(f"{slice.story_refer}&{slice.fragm_refer}")
+ pass
+ pass
+ self.fragment_view.highlight_graph_link(node_names)
+ pass
+ elif len(node_sections) > 1:
+ story_node = self.present_graph[node_sections[0]]
+ fragm_node = story_node.get_fragment_defined(node_sections[1])
+
+ story_list = [node_sections[0]]
+ for vsname in fragm_node.refers_slice:
+ if vsname not in story_list:
+ story_list.append(vsname)
+ pass
+ pass
+
+ if len(story_list) == 1:
+ self.print_node_list(xpos, ypos, [("node", story_list[0])])
+ elif len(story_list) > 1:
+ menu = QMenu()
+ for story in story_list:
+ def trigger(story_name:str):
+ return lambda : self.print_node_list(xpos, ypos, [("node", story_name)])
+
+ menu.addAction(f"story/{story}", trigger(story))
+ pass
+
+ menu.exec(self.fragment_view.mapToGlobal(QPoint(xpos, ypos)))
+ pass
+ else:
+ print(story_list)
+ pass
+ pass
+ pass
+ pass
+ pass
+
if __name__ == "__main__":
app = QApplication(sys.argv)
@@ -60,4 +119,6 @@ if __name__ == "__main__":
tool = XAST_ParseTool(global_ast_path)
view.present_stories_graph(tool.get_story_graph())
+# view.fragment_view.highlight_graph_link(["血脉的源头", "血脉的源头&待续"])
+
app.exec()
\ No newline at end of file
diff --git a/graph/DataType.py b/graph/DataType.py
index e98f09d..100a964 100644
--- a/graph/DataType.py
+++ b/graph/DataType.py
@@ -12,16 +12,15 @@ class Pos:
class Point:
- def __init__(self, name:str, start_mark: bool):
+ def __init__(self, name:str):
self.point_name = name
- self.is_start = start_mark
pass
def name(self) -> str:
return self.point_name
def make_copy(self) -> 'Point':
- return Point(self.point_name, self.is_start)
+ return Point(self.point_name)
class Line:
diff --git a/graph/__pycache__/DataType.cpython-312.pyc b/graph/__pycache__/DataType.cpython-312.pyc
index 43b744f..20f65d0 100644
Binary files a/graph/__pycache__/DataType.cpython-312.pyc and b/graph/__pycache__/DataType.cpython-312.pyc differ
diff --git a/graph/directed_acyclic_graph/DAGLayout.py b/graph/directed_acyclic_graph/DAGLayout.py
index cde4ffb..e480cbb 100644
--- a/graph/directed_acyclic_graph/DAGLayout.py
+++ b/graph/directed_acyclic_graph/DAGLayout.py
@@ -36,11 +36,12 @@ class DAGLayerHelper:
class DAGOrderHelper:
- def __init__(self, level:int = 0, relate:DAGLayerHelper|None = None, bind: DAGLayerHelper|None = None):
+ def __init__(self, relate:DAGLayerHelper|None, towards:DAGLayerHelper | None, bind:DAGLayerHelper|None):
self.layer_bind = bind
self.relate_bind = relate
+ self.towards_to = towards
- self.layer_number = level
+ self.layer_number = 0
self.sort_number:float = 0
self.__prev_layer_nodes: List['DAGOrderHelper'] = []
@@ -161,18 +162,19 @@ class DAGGraph:
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 = layer_current
+ if layer_current == 0 or inst.layer_v < layer_current:
+ 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
+ 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
@@ -181,7 +183,7 @@ class DAGGraph:
# 注册所有数据图实节点
for node in self.graph_inst.values():
- nodes_temp[node.bind_point().point_name] = DAGOrderHelper(bind=node, relate=node)
+ nodes_temp[node.bind_point().point_name] = DAGOrderHelper(bind=node, relate=None, towards=None)
pass
temp_array: List[DAGOrderHelper] = []
@@ -192,7 +194,8 @@ class DAGGraph:
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))
+ node_links.append(DAGOrderHelper(relate=node, towards=next, bind=None))
+ node_links[-1].layer_number = layer_index
pass
node_links.append(nodes_temp[next.bind_point().point_name])
diff --git a/graph/directed_acyclic_graph/DAGPresent.py b/graph/directed_acyclic_graph/DAGPresent.py
index f29df4d..aad6521 100644
--- a/graph/directed_acyclic_graph/DAGPresent.py
+++ b/graph/directed_acyclic_graph/DAGPresent.py
@@ -2,14 +2,32 @@ from graph.directed_acyclic_graph.DAGLayout import DAGGraph, DAGOrderHelper
from graph.DataType import Arrow, Point
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, QPainterPath
+from PyQt5.QtCore import QRectF, Qt, QPointF, pyqtSignal
+from PyQt5.QtGui import QFont, QBrush, QPen, QPainter, QPainterPath, QMouseEvent
import sys
from enum import Enum
from abc import abstractmethod
+class GraphNodeType(Enum):
+ ActivePresentNode = 0,
+ PenetrateNode = 1,
+ TransitionCurve = 2,
+
+
class GraphNode:
+ @abstractmethod
+ def node_type(self) -> GraphNodeType:
+ raise NotImplementedError("node_type")
+
+ @abstractmethod
+ def highlight(self, flag: bool):
+ raise NotImplementedError("highlight")
+
+ @abstractmethod
+ def is_highlighted(self) -> bool:
+ raise NotImplementedError("is_highlighted")
+
@abstractmethod
def boundingRect(self) -> QRectF:
raise NotImplementedError("boundingRect")
@@ -35,12 +53,23 @@ class ActivePresentNode(QGraphicsItem, GraphNode):
def __init__(self, name: str, type: StoryNodeType, font: QFont, parent):
QGraphicsItem.__init__(self, parent)
+ self.__highlight_mark = False
self.node_type_within_story = type
self.fragment_name = name
self.font_bind = font
self.data_bind: object = None
pass
+ def node_type(self) -> GraphNodeType:
+ return GraphNodeType.ActivePresentNode
+
+ def highlight(self, flag: bool):
+ self.__highlight_mark = flag
+ pass
+
+ def is_highlighted(self) -> bool:
+ return self.__highlight_mark
+
def boundingRect(self) -> QRectF:
size = self.font_bind.pixelSize()
if self.node_type_within_story == StoryNodeType.FragmentNode:
@@ -53,10 +82,16 @@ class ActivePresentNode(QGraphicsItem, GraphNode):
text_rect = QRectF(outline.x() + 5, outline.y() + 5, outline.width() - 10, outline.height() - 10)
painter.save()
+
if self.node_type_within_story == StoryNodeType.FragmentNode:
- painter.fillRect(outline, QBrush(Qt.gray))
+ painter.fillRect(outline, Qt.gray)
else:
painter.fillRect(outline, Qt.green)
+ pass
+
+ if self.__highlight_mark:
+ painter.setPen(Qt.red)
+
painter.drawRect(outline)
painter.setFont(self.font_bind)
painter.drawText(text_rect, self.fragment_name)
@@ -83,9 +118,20 @@ class PenetrateNode(QGraphicsItem, GraphNode):
def __init__(self, width: float, parent):
QGraphicsItem.__init__(self, parent)
self.width_store = width
+ self.__highlight_mark = False
self.data_bind: object = None
pass
+ def node_type(self) -> GraphNodeType:
+ return GraphNodeType.PenetrateNode
+
+ def highlight(self, flag: bool):
+ self.__highlight_mark = flag
+ pass
+
+ def is_highlighted(self) -> bool:
+ return self.__highlight_mark
+
def resize_width(self, width: float):
self.width_store = width
self.update()
@@ -98,7 +144,13 @@ class PenetrateNode(QGraphicsItem, GraphNode):
outline = self.boundingRect()
painter.save()
- painter.fillRect(QRectF(0, 2, outline.width(), 4), Qt.black)
+
+ if self.__highlight_mark:
+ painter.fillRect(QRectF(0, 2, outline.width(), 4), Qt.red)
+ else:
+ painter.fillRect(QRectF(0, 2, outline.width(), 4), Qt.black)
+ pass
+
painter.restore()
pass
@@ -111,9 +163,10 @@ class PenetrateNode(QGraphicsItem, GraphNode):
return self.data_bind
-class TransitionCurve(QGraphicsItem):
+class TransitionCurve(QGraphicsItem, GraphNode):
def __init__(self, start: GraphNode, end: GraphNode, prev_layrer_width:float, parent):
QGraphicsItem.__init__(self, parent)
+ self.__highlight_mark = False
self.start_node = start
self.end_node = end
self.prev_layer_w = prev_layrer_width
@@ -121,6 +174,16 @@ class TransitionCurve(QGraphicsItem):
self.data_bind: object = None
pass
+ def node_type(self) -> GraphNodeType:
+ return GraphNodeType.TransitionCurve
+
+ def highlight(self, flag: bool):
+ self.__highlight_mark = flag
+ pass
+
+ def is_highlighted(self) -> bool:
+ return self.__highlight_mark
+
def layout_refresh(self):
orect = self.start_node.boundingRect()
erect = self.end_node.boundingRect()
@@ -160,6 +223,10 @@ class TransitionCurve(QGraphicsItem):
control_pos1 = end_pos - QPointF((outline.width() - line_span)/3, 0)
npen = QPen(Qt.black)
+ if self.__highlight_mark:
+ npen = QPen(Qt.red)
+ pass
+
npen.setWidthF(4)
painter.setPen(npen)
@@ -188,21 +255,24 @@ class Direction(Enum):
class DAGActiveView(QGraphicsView):
- def __init__(self, orie: Direction, parent):
+ nodes_clicked = pyqtSignal(int,int,list)
+
+ def __init__(self, parent):
QGraphicsView.__init__(self, parent)
self.setViewportUpdateMode(QGraphicsView.ViewportUpdateMode.FullViewportUpdate)
- self.layer_span = 200
- self.node_span = 20
-
- self.orientation = orie
- self.scene_bind = QGraphicsScene(self)
- self.setScene(self.scene_bind)
-
font = QFont()
font.setPixelSize(20)
self.setFont(font)
+ self.layer_span = 200
+ self.node_span = 20
+
+ self.scene_bind = QGraphicsScene(self)
+ self.setScene(self.scene_bind)
+
+ self.__highlight_nodelist: List[GraphNode] = []
+ self.__total_graph_nodes: Dict[str, GraphNode] = {}
pass
def update_with_edges(self, arrows: List[Arrow]) -> None:
@@ -229,18 +299,30 @@ class DAGActiveView(QGraphicsView):
ypos_acc += curr_gnode.boundingRect().height()
current_graphics_nodes.append(curr_gnode)
self.scene_bind.addItem(curr_gnode)
+
+ fragm_start = node.relate_bind.bind_point().point_name
+ fragm_end = node.towards_to.bind_node.point_name
+ node_key = (f"plac${fragm_start}::{fragm_end}-{node.layer_number}")
+ curr_gnode.node_key_bind = "plac", fragm_start, fragm_end, node.layer_number
+ self.__total_graph_nodes[node_key] = curr_gnode
+ pass
else:
- if node.layer_bind.bind_point().is_start:
- curr_gnode = ActivePresentNode(node.layer_bind.bind_point().point_name, StoryNodeType.StoryStartNode,
- self.font(), None)
- else:
- curr_gnode = ActivePresentNode(node.layer_bind.bind_point().point_name, StoryNodeType.FragmentNode,
- self.font(), None)
+ node_type_vx = StoryNodeType.FragmentNode
+ if node.layer_bind.input_count == 0:
+ node_type_vx = StoryNodeType.StoryStartNode
+
+ curr_gnode = ActivePresentNode(node.layer_bind.bind_point().point_name,
+ node_type_vx, self.font(), None)
+
curr_gnode.attach_data(node)
curr_gnode.setPos(previous_node_end, ypos_acc)
ypos_acc += curr_gnode.boundingRect().height()
current_graphics_nodes.append(curr_gnode)
self.scene_bind.addItem(curr_gnode)
+
+ node_key = f"node@{node.layer_bind.bind_point().point_name}"
+ curr_gnode.node_key_bind = "node", node.layer_bind.bind_point().point_name
+ self.__total_graph_nodes[node_key] = curr_gnode
pass
ypos_acc += self.node_span
@@ -271,6 +353,24 @@ class DAGActiveView(QGraphicsView):
line_cmbn = TransitionCurve(prev_gnode, curr_gnode, prev_layer_width,None)
self.scene_bind.addItem(line_cmbn)
line_cmbn.layout_refresh()
+
+ relate_node_name = ""
+ if prev_gnode.node_type() == GraphNodeType.ActivePresentNode:
+ relate_node_name = prev_gnode.get_data().layer_bind.bind_point().point_name
+ elif prev_gnode.node_type() == GraphNodeType.PenetrateNode:
+ relate_node_name = prev_gnode.get_data().relate_bind.bind_point().point_name
+
+ towards_node_name = ""
+ if curr_gnode.node_type() == GraphNodeType.ActivePresentNode:
+ towards_node_name = sort_helper.layer_bind.bind_point().point_name
+ elif curr_gnode.node_type() == GraphNodeType.PenetrateNode:
+ towards_node_name = sort_helper.towards_to.bind_point().point_name
+
+ fragm_start = relate_node_name
+ fragm_end = towards_node_name
+ node_key = f"curv&{relate_node_name}::{towards_node_name}-{sort_helper.layer_number}"
+ line_cmbn.node_key_bind = "curv", fragm_start, fragm_end, sort_helper.layer_number
+ self.__total_graph_nodes[node_key] = line_cmbn
pass
pass
pass
@@ -281,10 +381,62 @@ class DAGActiveView(QGraphicsView):
pass
+ def highlight_graph_link(self, highlight_path: List[str]):
+ for n in self.__highlight_nodelist:
+ n.highlight(False)
+ pass
+ self.__highlight_nodelist.clear()
+
+ start_node = self.__total_graph_nodes[f"node@{highlight_path[0]}"]
+ start_node.highlight(True)
+ self.__highlight_nodelist.append(start_node)
+
+ for idx in range(1, len(highlight_path)):
+ start_name = highlight_path[idx-1]
+ end_name = highlight_path[idx]
+
+ end_node = self.__total_graph_nodes[f"node@{end_name}"]
+ end_node.highlight(True)
+ self.__highlight_nodelist.append(end_node)
+
+ plac_key = f"plac${start_name}::{end_name}"
+ curv_key = f"curv&{start_name}::{end_name}"
+ for key in self.__total_graph_nodes:
+ if key.startswith(plac_key):
+ placex = self.__total_graph_nodes[key]
+ placex.highlight(True)
+ self.__highlight_nodelist.append(placex)
+ pass
+ if key.startswith(curv_key):
+ curvx = self.__total_graph_nodes[key]
+ curvx.highlight(True)
+ self.__highlight_nodelist.append(curvx)
+ pass
+ pass
+ pass
+
+ self.scene_bind.update()
+ self.update()
+ pass
+
+ def mousePressEvent(self, event: QMouseEvent):
+ QGraphicsView.mousePressEvent(self, event)
+
+ gitems = self.items(event.pos())
+ noderef_names = []
+ for gnode in gitems:
+ if gnode.node_key_bind[0].startswith("node"):
+ noderef_names.append(gnode.node_key_bind)
+ pass
+ pass
+
+ self.nodes_clicked.emit(event.pos().x(), event.pos().y(), noderef_names[0:1])
+
+ pass
if __name__ == "__main__":
app = QApplication(sys.argv)
- view = DAGActiveView(Direction.RankLR, None)
+ view = DAGActiveView(None)
view.show()
arrows = [
diff --git a/graph/directed_acyclic_graph/__pycache__/DAGLayout.cpython-312.pyc b/graph/directed_acyclic_graph/__pycache__/DAGLayout.cpython-312.pyc
index a1055ec..5ea70b7 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__/DAGPresent.cpython-312.pyc b/graph/directed_acyclic_graph/__pycache__/DAGPresent.cpython-312.pyc
index a6faba8..c960f85 100644
Binary files a/graph/directed_acyclic_graph/__pycache__/DAGPresent.cpython-312.pyc and b/graph/directed_acyclic_graph/__pycache__/DAGPresent.cpython-312.pyc differ