基础的无向图呈现
This commit is contained in:
parent
b7f71fb29f
commit
d4bf6b186c
|
@ -5,8 +5,14 @@
|
|||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="f609c0f2-cd0d-4eea-87f1-8caf02d3f04f" name="Changes" comment="">
|
||||
<change afterPath="$PROJECT_DIR$/graph/undirected_graph/UDGLayout.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/graph/undirected_graph/UDGPresent.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/graph/undirected_graph/__init__.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" 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$/entry.py" beforeDir="false" afterPath="$PROJECT_DIR$/entry.py" 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/DAGPresent.py" beforeDir="false" afterPath="$PROJECT_DIR$/graph/directed_acyclic_graph/DAGPresent.py" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
|
@ -34,53 +40,40 @@
|
|||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"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"
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"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.UDGLayout.executor": "Run",
|
||||
"Python.UDGPresent.executor": "Run",
|
||||
"Python.ast_load.executor": "Debug",
|
||||
"Python.entry.executor": "Run",
|
||||
"Python.test.executor": "Run",
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"git-widget-placeholder": "master",
|
||||
"last_opened_file_path": "D:/Projects/Python/StoryTools",
|
||||
"settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable"
|
||||
}
|
||||
}</component>
|
||||
<component name="RunManager" selected="Python.ContentView">
|
||||
<configuration name="CompareViews" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="StoryTools" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/frame" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/frame/CompareViews.py" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
}]]></component>
|
||||
<component name="PyDebuggerOptionsProvider">
|
||||
<option name="mySaveCallSignatures" value="true" />
|
||||
<option name="mySupportGeventDebugging" value="true" />
|
||||
<option name="myDropIntoDebuggerOnFailedTests" value="true" />
|
||||
<option name="myPyQtBackend" value="pyqt5" />
|
||||
</component>
|
||||
<component name="RunManager" selected="Python.UDGPresent">
|
||||
<configuration name="ContentView" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="StoryTools" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
|
@ -103,28 +96,6 @@
|
|||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="DAGLayout" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="StoryTools" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/graph/directed_acyclic_graph" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/graph/directed_acyclic_graph/DAGLayout.py" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="DAGPresent" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="StoryTools" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
|
@ -147,7 +118,7 @@
|
|||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="NovelManage" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<configuration name="UDGLayout" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="StoryTools" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
|
@ -156,12 +127,56 @@
|
|||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/manage" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/graph/undirected_graph" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/manage/NovelManage.py" />
|
||||
<option name="PARAMETERS" value="wnss -cmp" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/graph/undirected_graph/UDGLayout.py" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="UDGPresent" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="StoryTools" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/graph/undirected_graph" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/graph/undirected_graph/UDGPresent.py" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="test" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="StoryTools" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/graph/undirected_graph" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/graph/undirected_graph/test.py" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
|
@ -170,26 +185,26 @@
|
|||
<method v="2" />
|
||||
</configuration>
|
||||
<list>
|
||||
<item itemvalue="Python.UDGPresent" />
|
||||
<item itemvalue="Python.test" />
|
||||
<item itemvalue="Python.UDGLayout" />
|
||||
<item itemvalue="Python.ContentView" />
|
||||
<item itemvalue="Python.DAGLayout" />
|
||||
<item itemvalue="Python.DAGPresent" />
|
||||
<item itemvalue="Python.CompareViews" />
|
||||
<item itemvalue="Python.NovelManage" />
|
||||
</list>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Python.UDGPresent" />
|
||||
<item itemvalue="Python.ContentView" />
|
||||
<item itemvalue="Python.test" />
|
||||
<item itemvalue="Python.UDGLayout" />
|
||||
<item itemvalue="Python.DAGPresent" />
|
||||
<item itemvalue="Python.DAGLayout" />
|
||||
<item itemvalue="Python.CompareViews" />
|
||||
<item itemvalue="Python.NovelManage" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-python-sdk-5b207ade9991-746f403e7f0c-com.jetbrains.pycharm.community.sharedIndexes.bundled-PC-241.17890.14" />
|
||||
<option value="bundled-python-sdk-975db3bf15a3-31b6be0877a2-com.jetbrains.pycharm.community.sharedIndexes.bundled-PC-241.18034.82" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
|
|
10
entry.py
10
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)
|
||||
print(__file__)
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Binary file not shown.
|
@ -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)
|
||||
|
|
Binary file not shown.
|
@ -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
|
|
@ -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()
|
|
@ -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()
|
Loading…
Reference in New Issue