From d4bf6b186c7245f7a84c73130edc91c1f788a66e Mon Sep 17 00:00:00 2001
From: codeboss <2422523675@qq.com>
Date: Fri, 2 Aug 2024 12:02:24 +0800
Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E7=A1=80=E7=9A=84=E6=97=A0=E5=90=91?=
=?UTF-8?q?=E5=9B=BE=E5=91=88=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/workspace.xml | 175 ++++++-----
entry.py | 10 +-
frame/ContentView.py | 28 +-
graph/DataType.py | 51 +++-
graph/__pycache__/DataType.cpython-312.pyc | Bin 3033 -> 6208 bytes
graph/directed_acyclic_graph/DAGPresent.py | 17 +-
.../__pycache__/DAGPresent.cpython-312.pyc | Bin 23959 -> 24107 bytes
graph/undirected_graph/UDGLayout.py | 167 ++++++++++
graph/undirected_graph/UDGPresent.py | 285 ++++++++++++++++++
graph/undirected_graph/__init__.py | 0
graph/undirected_graph/test.py | 21 ++
11 files changed, 645 insertions(+), 109 deletions(-)
create mode 100644 graph/undirected_graph/UDGLayout.py
create mode 100644 graph/undirected_graph/UDGPresent.py
create mode 100644 graph/undirected_graph/__init__.py
create mode 100644 graph/undirected_graph/test.py
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 58cfe2b..c563925 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,8 +5,14 @@
+
+
+
-
+
+
+
+
@@ -34,53 +40,40 @@
- {
- "keyToString": {
- "Python.CompareViews.executor": "Run",
- "Python.CompareWindow.executor": "Run",
- "Python.ContentView.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"
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+}]]>
+
+
+
+
+
+
+
@@ -103,28 +96,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -147,7 +118,7 @@
-
+
@@ -156,12 +127,56 @@
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -170,26 +185,26 @@
+
+
+
-
-
-
+
+
+
-
-
-
-
+
diff --git a/entry.py b/entry.py
index e6055c3..9ca187c 100644
--- a/entry.py
+++ b/entry.py
@@ -1,9 +1 @@
-from parse.StoryMap import XAST_ParseTool, storyline_list2map
-from parse.ast_load import global_ast_path
-
-
-astx = XAST_ParseTool(global_ast_path)
-storys = astx.story_list
-storys_map = storyline_list2map(storys)
-astx.storylines_plait(storys_map)
-print(storys_map)
\ No newline at end of file
+print(__file__)
\ No newline at end of file
diff --git a/frame/ContentView.py b/frame/ContentView.py
index 8e92d2a..330e7dc 100644
--- a/frame/ContentView.py
+++ b/frame/ContentView.py
@@ -2,7 +2,7 @@ import sys
from typing import Dict, List
from PyQt5.QtCore import QPoint
-from PyQt5.QtWidgets import QApplication, QMainWindow
+from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout
from PyQt5.QtWidgets import QMenu
from graph.DataType import Arrow, Point
@@ -17,13 +17,15 @@ class FragmentPoint(Point):
pass
-class ContentWindow(QMainWindow):
+class StorylinesView(QWidget):
def __init__(self, parent):
- QMainWindow.__init__(self, parent)
+ QWidget.__init__(self, parent)
+ layout = QVBoxLayout(self)
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
+ layout.setContentsMargins(0,0,0,0)
+ layout.addWidget(self.fragment_view)
+ self.fragment_view.nodes_clicked.connect(self.highlisth_node_path)
+ self.present_graph: Dict[str, StoryMap] = {}
pass
pass
@@ -56,7 +58,7 @@ class ContentWindow(QMainWindow):
self.fragment_view.update_with_edges(arrows)
pass
- def print_node_list(self, xpos, ypos, list):
+ def highlisth_node_path(self, xpos, ypos, list):
if len(list) == 0:
return
@@ -90,12 +92,12 @@ class ContentWindow(QMainWindow):
pass
if len(story_list) == 1:
- self.print_node_list(xpos, ypos, [("node", story_list[0])])
+ self.highlisth_node_path(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)])
+ return lambda : self.highlisth_node_path(xpos, ypos, [("node", story_name)])
menu.addAction(f"story/{story}", trigger(story))
pass
@@ -111,9 +113,15 @@ class ContentWindow(QMainWindow):
pass
+class ArticleRefsView(QWidget):
+ def __init__(self, parent):
+ QWidget.__init__(self, parent)
+ pass
+
+
if __name__ == "__main__":
app = QApplication(sys.argv)
- view = ContentWindow(None)
+ view = StorylinesView(None)
view.show()
tool = XAST_ParseTool(global_ast_path)
diff --git a/graph/DataType.py b/graph/DataType.py
index 100a964..792bf0a 100644
--- a/graph/DataType.py
+++ b/graph/DataType.py
@@ -1,3 +1,4 @@
+import math
from typing import List
@@ -7,6 +8,44 @@ class Pos:
self.y_pos = y
pass
+ def __add__(self, other: 'Pos') -> 'Pos':
+ return Pos(self.x_pos + other.x_pos, self.y_pos + other.y_pos)
+
+ def __sub__(self, other: 'Pos') -> 'Pos':
+ return Pos(self.x_pos - other.x_pos, self.y_pos - other.y_pos)
+
+ def __iadd__(self, other: 'Pos') -> 'Pos':
+ self.x_pos += other.x_pos
+ self.y_pos += other.y_pos
+ return self
+
+ def __isub__(self, other: 'Pos') -> 'Pos':
+ self.x_pos -= other.x_pos
+ self.y_pos -= other.y_pos
+ return self
+
+ def __mul__(self, t: float) -> 'Pos':
+ return Pos(self.x_pos * t, self.y_pos * t)
+
+ def __imul__(self, t: float) -> 'Pos':
+ self.x_pos *= t
+ self.y_pos *= t
+ return self
+
+ def vec_length(self) -> float:
+ return math.sqrt(self.x_pos**2 + self.y_pos**2)
+
+ def normalized(self) -> 'Pos':
+ dist = self.vec_length()
+
+ if dist == 0:
+ return Pos(0, 0)
+
+ return self * (1/dist)
+
+ def to_text(self) -> str:
+ return f"pos<{self.__hash__()}>{{{self.x_pos}, {self.y_pos}}}"
+
def make_copy(self) -> 'Pos':
return Pos(self.x_pos, self.y_pos)
@@ -23,6 +62,16 @@ class Point:
return Point(self.point_name)
+class PositionalPoint(Point, Pos):
+ def __init__(self, name: str, x: float, y: float):
+ Point.__init__(self, name)
+ Pos.__init__(self, x, y)
+ pass
+
+ def make_copy(self) -> 'PositionalPoint':
+ return PositionalPoint(self.name(), self.x_pos, self.y_pos)
+
+
class Line:
def __init__(self, p0: Point, p1: Point):
self.point_set = [p0, p1]
@@ -46,5 +95,3 @@ class Arrow(Line):
def end_point(self):
return self.point_set[1]
- pass
-
diff --git a/graph/__pycache__/DataType.cpython-312.pyc b/graph/__pycache__/DataType.cpython-312.pyc
index 20f65d0867f2a5073277b56d3a8908ed51eded70..da53a9f799765a1149d1c783a2f864f89d108aeb 100644
GIT binary patch
literal 6208
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
diff --git a/graph/directed_acyclic_graph/DAGPresent.py b/graph/directed_acyclic_graph/DAGPresent.py
index aad6521..ba5bcc3 100644
--- a/graph/directed_acyclic_graph/DAGPresent.py
+++ b/graph/directed_acyclic_graph/DAGPresent.py
@@ -422,18 +422,19 @@ class DAGActiveView(QGraphicsView):
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)
+ if event.button() == Qt.MouseButton.LeftButton:
+ 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
- pass
-
- self.nodes_clicked.emit(event.pos().x(), event.pos().y(), noderef_names[0:1])
+ self.nodes_clicked.emit(event.pos().x(), event.pos().y(), noderef_names[0:1])
pass
+
if __name__ == "__main__":
app = QApplication(sys.argv)
view = DAGActiveView(None)
diff --git a/graph/directed_acyclic_graph/__pycache__/DAGPresent.cpython-312.pyc b/graph/directed_acyclic_graph/__pycache__/DAGPresent.cpython-312.pyc
index c960f8570e0c6176ac3aea41c65a7d8a438319d6..5f20c03c108be6c0e7e8d26478c47c0a053ba1f9 100644
GIT binary patch
delta 414
zcmbQfn{o9XM!wU$yj%=G;1ISZjmvH$-@!1(Uy~=A8!
zoOxFb!(;<}5tbUpEPk+@X}C0_z~sPi$;nA_!jo&m`58qgKMWLRX2}wmT*xmqd2cxP
zn>mQEG9qYguYu
z$t|{|(vp(=ydr*(EVnO6)(I?fi_0f9tpviJyf9)8x(-y4$hpBPwpMJKS{5SF+u
ztaedYZ9(BhVa@BpCKrWGHiTUfwz(mqcwI!}qKL){(d(Kv7d36Jh}hl`k+>ls@_`em
z^8*h9ui$kaxr;n<*Ln1nFY+iaa9!@X&~vf(a^Ho%i~To*Zjam;xjA}!?8ey5@dt7*
pgoIuQ3%}qNafK)H#^hI#8jLENg`?z|80{w;$1G&bntV0p5C91?ksSa4
delta 325
zcmV-L0lNOHyaAWH0S?Oz4GI7N007@!s%Ei0u@1pj0i=_WH%A0~0cr=6aaT0~2$O+Q
z69)kq0001L3zNZDA(JsL69jw&Y6z2Y4;lvn00{t$J_M7%4;PcbI}`$Z0h6yL9g|`n
z8v=X*ldmQnlj2tslYkKjlet(Y0SlAiSQnEXS(|^W0001h0Ne-_*9axi2qmCos72HW
zGT;#vml}~8m_?sSnMs>V(-A0$2G|%Hhz-~k8}a}I2@!}z*bfxf4s*bETx2?Pld@C*P64c7=7(FhvX
z2p=xd2p^yYrwO46qYI}Ep$(%Cuobr%u^F=)w;iz^vmd}M&`?p(Qd7`ERMZGn;FEq@
XC;=g}r&=EZ0X&n(T%iGIlWASTp+{`Z
diff --git a/graph/undirected_graph/UDGLayout.py b/graph/undirected_graph/UDGLayout.py
new file mode 100644
index 0000000..8221bd0
--- /dev/null
+++ b/graph/undirected_graph/UDGLayout.py
@@ -0,0 +1,167 @@
+import sys, os
+sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
+from graph.DataType import PositionalPoint, Line, Pos, Point
+from typing import List, Dict
+import random as rd
+
+
+class ForceCalcHelper:
+ """
+ 力导向计算辅助节点
+ """
+ def __init__(self, init_v: PositionalPoint):
+ self.bind_point = init_v
+ self.sibling_nodes: Dict[str, 'ForceCalcHelper'] = {}
+ pass
+
+ def bind_point_name(self) -> str:
+ return self.bind_point.point_name
+
+ def get_sibling_nodes(self) -> Dict[str, 'ForceCalcHelper']:
+ return self.sibling_nodes
+ pass
+
+ def sibling_append(self, node: 'ForceCalcHelper'):
+ self.sibling_nodes[node.bind_point_name()] = node
+ pass
+
+
+class UDGGraph:
+ force2accx_rates = 1
+
+ def __init__(self):
+ self.random_gen = rd.Random()
+ self.node_set: Dict[str, ForceCalcHelper] = {}
+ pass
+
+ def rebuild_from_edges(self, line_set: List[Line]):
+ self.node_set.clear()
+
+ for line in line_set:
+ start_node = line.points()[0]
+ if start_node.point_name not in self.node_set:
+ pos_node = PositionalPoint(start_node.point_name, 0, 0)
+ self.node_set[start_node.point_name] = ForceCalcHelper(pos_node)
+ pass
+
+ end_node = line.points()[1]
+ if start_node.point_name == end_node.point_name:
+ continue
+ if end_node.point_name not in self.node_set:
+ pos_node = PositionalPoint(end_node.point_name, 0, 0)
+ self.node_set[end_node.point_name] = ForceCalcHelper(pos_node)
+ pass
+
+ start_force_point = self.node_set[start_node.point_name]
+ other_force_point = self.node_set[end_node.point_name]
+
+ start_force_point.sibling_append(other_force_point)
+ other_force_point.sibling_append(start_force_point)
+ pass
+ pass
+
+ def __eject_with_item(self, curr: ForceCalcHelper, node_set:Dict[str, ForceCalcHelper]) -> Pos:
+ init_value = Pos(0, 0)
+
+ for node in node_set.values():
+ if curr.bind_point_name() == node.bind_point_name():
+ continue
+
+ coord_span = curr.current_pos() - node.current_pos()
+ distance = coord_span.vec_length()
+ force_scalar = ForceCalcHelper.eject_k / (distance**2)
+
+ force_vec = coord_span.normalized() * force_scalar
+ init_value += force_vec
+ pass
+
+ return init_value
+
+ def __attract_with_item_sibs(self, curr: ForceCalcHelper) -> Pos:
+ init_value = Pos(0, 0)
+
+ for node in curr.get_sibling_nodes().values():
+ coord_span = curr.current_pos() - node.current_pos()
+ distance = coord_span.vec_length()
+ force_scalar = distance * ForceCalcHelper.attract_k
+
+ force_vec = coord_span.normalized() * force_scalar
+ init_value -= force_vec
+ pass
+
+ return init_value
+
+ def __calculate_item_force(self, curr: ForceCalcHelper, node_set:Dict[str, ForceCalcHelper]) -> float:
+ """
+ 计算指定节点对整个数据图节点的合力
+ :param times: 次数,初始迭代有特殊处理
+ :param curr: 当前节点
+ :param node_set: 所有节点
+ :return: 合力标量
+ """
+ eject_vec2 = self.__eject_with_item(curr, node_set)
+ attract_vec2 = self.__attract_with_item_sibs(curr)
+ curr.force_with_direction = eject_vec2 + attract_vec2
+
+ # 阻尼计算 f=fxG(fxg=1,m=1)
+ fr = ForceCalcHelper.damping_k * 10
+ if curr.force_with_direction.vec_length() > fr:
+ curr.force_with_direction -= curr.force_with_direction.normalized() * fr
+ pass
+ else:
+ curr.force_with_direction = Pos()
+ pass
+
+ return curr.force_with_direction.vec_length()
+
+ def __item_position_adjust(self, curr: ForceCalcHelper) -> None:
+ if curr.force_with_direction.vec_length() == 0:
+ return
+
+ vec_speed = curr.force_with_direction.normalized()
+ curr.move_by(vec_speed)
+ pass
+
+ def graph_layout(self):
+ for curr in self.node_set.values():
+ random_pos = Pos(self.random_gen.random() * 100, self.random_gen.random() * 100)
+ curr.move_by(random_pos)
+ pass
+
+ for idx in range(0, 10):
+ for curr in self.node_set.values():
+ self.__calculate_item_force(curr, self.node_set)
+ pass
+
+ for curr in self.node_set.values():
+ self.__item_position_adjust(curr)
+ pass
+ pass
+ pass
+
+ def visible_positon_set(self) -> List[PositionalPoint]:
+ retvs = []
+ for node in self.node_set.values():
+ retvs.append(node.bind_point)
+ pass
+ return retvs
+
+
+if __name__ == "__main__":
+ list_in = [
+ Line(Point("a"), Point("b")),
+ Line(Point("a"), Point("c")),
+ Line(Point("a"), Point("d")),
+ Line(Point("a"), Point("e")),
+ Line(Point("d"), Point("e")),
+ Line(Point("f"), Point("c")),
+ ]
+
+ graph = UDGGraph()
+ graph.rebuild_from_edges(list_in)
+ graph.graph_layout()
+
+ for p in graph.visible_positon_set():
+ print(f"node:{p.name()}<{p.x_pos},{p.y_pos}>")
+ pass
+ pass
diff --git a/graph/undirected_graph/UDGPresent.py b/graph/undirected_graph/UDGPresent.py
new file mode 100644
index 0000000..b1bcd1f
--- /dev/null
+++ b/graph/undirected_graph/UDGPresent.py
@@ -0,0 +1,285 @@
+import sys
+from abc import abstractmethod
+from enum import Enum
+from typing import List, Dict, Tuple
+
+import networkx as nx
+from PyQt5.QtCore import QPointF, QRectF, Qt, pyqtSignal
+from PyQt5.QtGui import QFont, QPainterPath, QPen, QPainter, QMouseEvent
+from PyQt5.QtWidgets import QGraphicsItem, QGraphicsView, QApplication, QGraphicsScene
+
+from graph.DataType import Point, Line
+
+
+class PresentNodeType(Enum):
+ PresentNode = 0,
+ ConnectionNode = 1,
+
+
+class GraphNode:
+ @abstractmethod
+ def node_type(self) -> PresentNodeType:
+ 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")
+
+ @abstractmethod
+ def pos(self) -> QPointF:
+ raise NotImplementedError("pos")
+
+
+class PresentNode(QGraphicsItem, GraphNode):
+ def __init__(self, name: str, font: QFont, parent):
+ QGraphicsItem.__init__(self, parent)
+ self.node_name = name
+ self.__is_highlight_mark = False
+ self.__font_bind = font
+ self.__sibling_list = []
+ self.setZValue(10)
+ pass
+
+ def sibling_append(self, node: 'PresentNode'):
+ if node not in self.__sibling_list:
+ self.__sibling_list.append(node)
+ pass
+ pass
+
+ def sibling_nodes(self) -> List['PresentNode']:
+ return self.__sibling_list
+
+ def node_type(self) -> PresentNodeType:
+ return PresentNodeType.PresentNode
+
+ def highlight(self, flag: bool):
+ self.__is_highlight_mark = flag
+ pass
+
+ def is_highlighted(self) -> bool:
+ return self.__is_highlight_mark
+ pass
+
+ def boundingRect(self) -> QRectF:
+ width_x = self.__font_bind.pixelSize() * (len(self.node_name)+1)
+ height_x = self.__font_bind.pixelSize()
+ return QRectF(0, 0, width_x + 10, height_x + 10)
+ pass
+
+ def paint(self, painter, option, widget = ...):
+ outline = self.boundingRect()
+
+ path_icon = QPainterPath()
+ path_icon.lineTo(outline.height()/2 - 5, outline.height() -5)
+ path_icon.lineTo(outline.height()/2, outline.height()/2)
+ path_icon.lineTo(outline.height() - 5, outline.height()/2 - 5)
+ path_icon.lineTo(0, 0)
+
+ painter.save()
+ painter.setRenderHint(QPainter.Antialiasing)
+ painter.drawRect(outline)
+
+ if self.__is_highlight_mark:
+ brush = Qt.red
+ painter.setPen(Qt.red)
+ else:
+ brush = Qt.black
+ painter.setPen(Qt.black)
+ pass
+
+ painter.fillPath(path_icon, brush)
+ painter.translate(outline.height(), 5)
+ painter.drawText(outline, self.node_name)
+
+ painter.restore()
+ pass
+
+
+class ConnectionNode(QGraphicsItem, GraphNode):
+ def __init__(self, p0: GraphNode, p1: GraphNode, parent):
+ QGraphicsItem.__init__(self, parent)
+ self.__highlight_mark = False
+ self.__point0 = p0
+ self.__point1 = p1
+ self.__outline = QRectF()
+ self.setZValue(1)
+ pass
+
+ def node_type(self) -> PresentNodeType:
+ return PresentNodeType.ConnectionNode
+
+ def highlight(self, flag: bool):
+ self.__highlight_mark = flag
+ pass
+
+ def is_highlighted(self) -> bool:
+ return self.__highlight_mark
+ pass
+
+ def relayout_exec(self):
+ start_pos = self.__point0.pos()
+ end_pos = self.__point1.pos()
+
+ start_x = min(start_pos.x(), end_pos.x())
+ start_y = min(start_pos.y(), end_pos.y())
+ end_x = max(start_pos.x(), end_pos.x())
+ end_y = max(start_pos.y(), end_pos.y())
+
+ self.setPos(QPointF(start_x, start_y))
+ self.__outline = QRectF(0, 0, end_x - start_x, end_y - start_y)
+ pass
+
+ def boundingRect(self):
+ return self.__outline
+ pass
+
+ def paint(self, painter, option, widget = ...):
+ start_pos = self.__point0.pos()
+ end_pos = self.__point1.pos()
+ outline = self.boundingRect()
+
+ painter.save()
+ painter.setRenderHint(QPainter.Antialiasing)
+
+ if self.__highlight_mark:
+ pen = QPen(Qt.red)
+ else:
+ pen = QPen(Qt.lightGray)
+
+ pen.setWidthF(3)
+ painter.setPen(pen)
+ if start_pos.y() < end_pos.y():
+ if start_pos.x() < end_pos.x():
+ painter.drawLine(outline.topLeft(), outline.bottomRight())
+ else:
+ painter.drawLine(outline.topRight(), outline.bottomLeft())
+ else:
+ if start_pos.x() < end_pos.x():
+ painter.drawLine(outline.bottomLeft(), outline.topRight())
+ else:
+ painter.drawLine(outline.topLeft(), outline.bottomRight())
+
+ painter.restore()
+ pass
+
+
+class UDGPresent(QGraphicsView):
+ node_clicked = pyqtSignal(str)
+
+ def __init__(self, parent):
+ QGraphicsView.__init__(self, parent)
+ self.__highlight_nodes: List[GraphNode] = []
+ self.node_set: Dict[str, GraphNode] = {}
+ self.__layout_graph = nx.Graph()
+ self.__scene_bind = QGraphicsScene(self)
+ self.setScene(self.__scene_bind)
+
+ font = QFont()
+ font.setPixelSize(20)
+ self.setFont(font)
+ pass
+
+ def rebuild_from_edges(self, line_set: List[Line]):
+ self.node_set.clear()
+
+ edge_set: Dict[str, Tuple[GraphNode, GraphNode]] = {}
+
+ for line in line_set:
+ start_node = line.points()[0]
+ if start_node.point_name not in self.node_set:
+ self.node_set[start_node.point_name] = PresentNode(start_node.point_name, self.font(), None)
+ pass
+
+ self.__layout_graph.add_node(start_node.point_name)
+
+ end_node = line.points()[1]
+ if start_node.point_name == end_node.point_name:
+ continue
+ if end_node.point_name not in self.node_set:
+ self.node_set[end_node.point_name] = PresentNode(end_node.point_name, self.font(), None)
+ pass
+
+ self.__layout_graph.add_node(end_node.point_name)
+ self.__layout_graph.add_edge(start_node.point_name, end_node.point_name)
+
+ start_force_point: PresentNode = self.node_set[start_node.point_name]
+ other_force_point: PresentNode = self.node_set[end_node.point_name]
+
+ if other_force_point not in start_force_point.sibling_nodes():
+ start_force_point.sibling_append(other_force_point)
+ if start_force_point not in other_force_point.sibling_nodes():
+ other_force_point.sibling_append(start_force_point)
+
+ pass
+
+ pos_map = nx.spring_layout(self.__layout_graph)
+ scala_value:float = 0
+ for name in pos_map:
+ primitive_pos = pos_map[name]
+ target_gnode: PresentNode = self.node_set[name]
+
+ sibling_nodes = target_gnode.sibling_nodes()
+ for sib in sibling_nodes:
+ sib_primitive_pos = pos_map[sib.node_name]
+ prim_x_span = primitive_pos[0] - sib_primitive_pos[0]
+ prim_y_span = primitive_pos[1] - sib_primitive_pos[1]
+
+ target_rect = target_gnode.boundingRect()
+ scala_value = max(scala_value, target_rect.width()/prim_x_span)
+ scala_value = max(scala_value, target_rect.height()/prim_y_span)
+ pass
+ pass
+
+ for name in pos_map:
+ primitive_pos = pos_map[name]
+ target_gnode: PresentNode = self.node_set[name]
+ target_gnode.setPos(primitive_pos[0] * scala_value, primitive_pos[1] * scala_value)
+ self.__scene_bind.addItem(target_gnode)
+ pass
+
+ for edge in nx.edges(self.__layout_graph):
+ edge_start = edge[0]
+ edge_end = edge[1]
+ node_one = self.node_set[edge_start]
+ node_two = self.node_set[edge_end]
+ connection = ConnectionNode(node_one, node_two, None)
+ self.scene().addItem(connection)
+ connection.relayout_exec()
+ pass
+ pass
+
+ def mousePressEvent(self, event: QMouseEvent):
+ QGraphicsView.mousePressEvent(self, event)
+ if event.button() == Qt.MouseButton.LeftButton:
+ item: GraphNode = self.itemAt(event.pos())
+ if item.node_type() == PresentNodeType.PresentNode:
+ vnode: PresentNode = item
+ self.node_clicked.emit(vnode.node_name)
+ print(vnode.node_name)
+ pass
+ pass
+ pass
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ view = UDGPresent(None)
+ view.show()
+
+ list_in = [
+ Line(Point('a中古'), Point('b')),
+ Line(Point('a中古'), Point('c')),
+ Line(Point('a中古'), Point('d')),
+ ]
+
+ view.rebuild_from_edges(list_in)
+
+ app.exec()
\ No newline at end of file
diff --git a/graph/undirected_graph/__init__.py b/graph/undirected_graph/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/graph/undirected_graph/test.py b/graph/undirected_graph/test.py
new file mode 100644
index 0000000..836347e
--- /dev/null
+++ b/graph/undirected_graph/test.py
@@ -0,0 +1,21 @@
+import sys, os
+sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
+import networkx as nx
+import matplotlib.pyplot as plt
+
+g = nx.Graph()
+g.add_node('a')
+g.add_node('b')
+g.add_node('c')
+g.add_node('d')
+g.add_edge('a', 'b')
+g.add_edge('b','c')
+g.add_edge('b','d')
+posx = nx.spring_layout(g)
+
+print(posx)
+for n in posx:
+ print(n)
+
+nx.draw(g, posx)
+plt.show()
\ No newline at end of file