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 20f65d0..da53a9f 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/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 c960f85..5f20c03 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
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