基础的图形绘制完成

This commit is contained in:
codeboss 2024-07-31 10:12:34 +08:00
parent f86b7bf460
commit 0538a059d9
8 changed files with 154 additions and 78 deletions

View File

@ -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">{
&quot;keyToString&quot;: {
&quot;Python.CompareViews.executor&quot;: &quot;Run&quot;,
&quot;Python.CompareWindow.executor&quot;: &quot;Run&quot;,
&quot;Python.DAGGraph (1).executor&quot;: &quot;Run&quot;,
&quot;Python.DAGGraph.executor&quot;: &quot;Run&quot;,
&quot;Python.DAGLayout (1).executor&quot;: &quot;Run&quot;,
&quot;Python.DAGLayout.executor&quot;: &quot;Run&quot;,
&quot;Python.DAGPresent.executor&quot;: &quot;Run&quot;,
&quot;Python.MergeView.executor&quot;: &quot;Run&quot;,
&quot;Python.MileStone.executor&quot;: &quot;Run&quot;,
&quot;Python.NovelManage.executor&quot;: &quot;Debug&quot;,
&quot;Python.ReferView.executor&quot;: &quot;Run&quot;,
&quot;Python.StoryMap.executor&quot;: &quot;Run&quot;,
&quot;Python.ast_load.executor&quot;: &quot;Debug&quot;,
&quot;Python.entry.executor&quot;: &quot;Run&quot;,
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;last_opened_file_path&quot;: &quot;D:/Projects/Python/StoryCheckTools&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;reference.settings.ide.settings.new.ui&quot;
<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" />

View File

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

View File

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

View File

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