基础的图形绘制完成
This commit is contained in:
parent
f86b7bf460
commit
0538a059d9
|
@ -6,6 +6,7 @@
|
|||
<component name="ChangeListManager">
|
||||
<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$/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>
|
||||
|
@ -35,29 +36,29 @@
|
|||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"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"
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"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"
|
||||
}
|
||||
}</component>
|
||||
}]]></component>
|
||||
<component name="RunManager" selected="Python.DAGPresent">
|
||||
<configuration name="CompareViews" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="StoryTools" />
|
||||
|
|
|
@ -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:
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
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()
|
||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue