点击选中故事线特性开发完成

This commit is contained in:
codeboss 2024-08-01 00:07:08 +08:00
parent 30574c9e1f
commit 74d7f05dca
8 changed files with 262 additions and 45 deletions

View File

@ -7,6 +7,8 @@
<list default="true" id="f609c0f2-cd0d-4eea-87f1-8caf02d3f04f" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frame/ContentView.py" beforeDir="false" afterPath="$PROJECT_DIR$/frame/ContentView.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/graph/DataType.py" beforeDir="false" afterPath="$PROJECT_DIR$/graph/DataType.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/graph/directed_acyclic_graph/DAGLayout.py" beforeDir="false" afterPath="$PROJECT_DIR$/graph/directed_acyclic_graph/DAGLayout.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/graph/directed_acyclic_graph/DAGPresent.py" beforeDir="false" afterPath="$PROJECT_DIR$/graph/directed_acyclic_graph/DAGPresent.py" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
@ -190,7 +192,7 @@
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-python-sdk-50da183f06c8-d3b881c8e49f-com.jetbrains.pycharm.community.sharedIndexes.bundled-PC-233.13135.95" />
<option value="bundled-python-sdk-5b207ade9991-746f403e7f0c-com.jetbrains.pycharm.community.sharedIndexes.bundled-PC-241.17890.14" />
</set>
</attachedChunks>
</component>

View File

@ -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()

View File

@ -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:

View File

@ -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])

View File

@ -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 = [