diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index c563925..ee5e252 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,14 +5,10 @@
-
-
-
-
-
-
+
+
@@ -40,40 +36,40 @@
- {
+ "keyToString": {
+ "Python.CompareViews.executor": "Run",
+ "Python.CompareWindow.executor": "Run",
+ "Python.ContentView.executor": "Debug",
+ "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": "debugger.dataViews"
}
-}]]>
+}
-
+
@@ -193,8 +189,8 @@
-
+
@@ -222,4 +218,15 @@
+
+
+
+
+ file://$PROJECT_DIR$/graph/undirected_graph/UDGPresent.py
+ 239
+
+
+
+
+
\ No newline at end of file
diff --git a/frame/ContentView.py b/frame/ContentView.py
index 330e7dc..2811631 100644
--- a/frame/ContentView.py
+++ b/frame/ContentView.py
@@ -1,13 +1,16 @@
-import sys
+import sys, os
from typing import Dict, List
-from PyQt5.QtCore import QPoint
-from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout
-from PyQt5.QtWidgets import QMenu
+from PyQt5.QtCore import QPoint, Qt
+from PyQt5.QtGui import QTransform
+from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QSlider, QDoubleSpinBox
+from PyQt5.QtWidgets import QMenu, QHBoxLayout
-from graph.DataType import Arrow, Point
+sys.path.append(os.path.dirname(os.path.dirname(__file__)))
+from graph.DataType import Arrow, Point, Line
from graph.directed_acyclic_graph.DAGPresent import DAGActiveView
-from parse.StoryMap import StoryMap, XAST_ParseTool
+from graph.undirected_graph.UDGPresent import UDGPresent
+from parse.StoryMap import StoryMap, XAST_ParseTool, ArticleSlice
from parse.ast_load import global_ast_path
@@ -24,7 +27,7 @@ class StorylinesView(QWidget):
self.fragment_view = DAGActiveView(self)
layout.setContentsMargins(0,0,0,0)
layout.addWidget(self.fragment_view)
- self.fragment_view.nodes_clicked.connect(self.highlisth_node_path)
+ self.fragment_view.nodes_clicked.connect(self.highlight_node_path)
self.present_graph: Dict[str, StoryMap] = {}
pass
pass
@@ -58,7 +61,7 @@ class StorylinesView(QWidget):
self.fragment_view.update_with_edges(arrows)
pass
- def highlisth_node_path(self, xpos, ypos, list):
+ def highlight_node_path(self, xpos, ypos, list):
if len(list) == 0:
return
@@ -92,12 +95,12 @@ class StorylinesView(QWidget):
pass
if len(story_list) == 1:
- self.highlisth_node_path(xpos, ypos, [("node", story_list[0])])
+ self.highlight_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.highlisth_node_path(xpos, ypos, [("node", story_name)])
+ return lambda : self.highlight_node_path(xpos, ypos, [("node", story_name)])
menu.addAction(f"story/{story}", trigger(story))
pass
@@ -116,16 +119,70 @@ class StorylinesView(QWidget):
class ArticleRefsView(QWidget):
def __init__(self, parent):
QWidget.__init__(self, parent)
+ self.refer_view = UDGPresent(self)
+ layout = QGridLayout(self)
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.setSpacing(0)
+ layout.addWidget(self.refer_view)
+
+ self.ppu_slider = QSlider(Qt.Orientation.Horizontal, self)
+ self.scala_slider = QSlider(Qt.Orientation.Vertical, self)
+ self.scala_slider.setRange(0, 1000)
+ self.scala_slider.setValue(100)
+ layout.addWidget(self.scala_slider, 0, 1)
+
+ slide_panel = QWidget(self)
+ layout.addWidget(slide_panel, 1, 0, 1, 2)
+ bottom_layout = QHBoxLayout(slide_panel)
+ bottom_layout.setSpacing(0)
+ bottom_layout.setContentsMargins(0, 0, 0, 0)
+ bottom_layout.addWidget(self.ppu_slider)
+
+ self.ppu_max = QDoubleSpinBox(self)
+ self.ppu_max.setRange(0, 2**31)
+ self.ppu_max.setValue(10000)
+ self.ppu_slider.setRange(0, 10000)
+ self.ppu_slider.setValue(int(self.refer_view.pixel_per_unit))
+ bottom_layout.addWidget(self.ppu_max)
+
+ self.refer_view.node_clicked.connect(self.highlight_sibling_nodes)
+
+ def scala_view(times:float):
+ tm = QTransform()
+ tm.scale(times, times)
+ return tm
+
+ self.scala_slider.valueChanged.connect(lambda iproc: self.refer_view.setTransform(scala_view(iproc/100.0)))
+ self.ppu_slider.valueChanged.connect(lambda ppu:self.refer_view.refresh_with_ppu(ppu))
+ self.ppu_slider.sliderReleased.connect(lambda : self.refer_view.update_scene_rect())
+ pass
+
+ def present_volumes_graph(self, ref_graph: List[ArticleSlice]):
+ node_edges = []
+
+ for line in ref_graph:
+ for target in line.refer_target:
+ node_edges.append(Line(Point(line.article_fullname), Point(target)))
+ pass
+ pass
+
+ self.refer_view.rebuild_from_edges(node_edges)
+ pass
+
+ def highlight_sibling_nodes(self, nodename: str):
+ self.refer_view.highlight_sibling_nodes(nodename)
pass
if __name__ == "__main__":
app = QApplication(sys.argv)
- view = StorylinesView(None)
+ #view = StorylinesView(None)
+ view = ArticleRefsView(None)
view.show()
tool = XAST_ParseTool(global_ast_path)
- view.present_stories_graph(tool.get_story_graph())
+# view.present_stories_graph(tool.get_story_graph())
+ view.present_volumes_graph(tool.get_article_nodes())
# view.fragment_view.highlight_graph_link(["血脉的源头", "血脉的源头&待续"])
diff --git a/graph/undirected_graph/UDGPresent.py b/graph/undirected_graph/UDGPresent.py
index b1bcd1f..55658bf 100644
--- a/graph/undirected_graph/UDGPresent.py
+++ b/graph/undirected_graph/UDGPresent.py
@@ -1,4 +1,4 @@
-import sys
+import sys, os
from abc import abstractmethod
from enum import Enum
from typing import List, Dict, Tuple
@@ -7,7 +7,9 @@ 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 PyQt5.QtWidgets import QSplitter, QHBoxLayout
+sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
from graph.DataType import Point, Line
@@ -36,15 +38,20 @@ class GraphNode:
@abstractmethod
def pos(self) -> QPointF:
raise NotImplementedError("pos")
+
+ @abstractmethod
+ def relayout_exec(self, ppu: float = 1):
+ raise NotImplementedError("relayout_exec")
class PresentNode(QGraphicsItem, GraphNode):
- def __init__(self, name: str, font: QFont, parent):
+ def __init__(self, name: str, font: QFont, prim_x:float, prim_y:float, parent):
QGraphicsItem.__init__(self, parent)
self.node_name = name
self.__is_highlight_mark = False
self.__font_bind = font
self.__sibling_list = []
+ self.__prim_pos: Tuple[float, float] = (prim_x, prim_y)
self.setZValue(10)
pass
@@ -85,7 +92,7 @@ class PresentNode(QGraphicsItem, GraphNode):
painter.save()
painter.setRenderHint(QPainter.Antialiasing)
- painter.drawRect(outline)
+ #painter.drawRect(outline)
if self.__is_highlight_mark:
brush = Qt.red
@@ -102,6 +109,13 @@ class PresentNode(QGraphicsItem, GraphNode):
painter.restore()
pass
+ def relayout_exec(self, ppu: float = 1):
+ pos_x = ppu * self.__prim_pos[0]
+ pos_y = ppu * self.__prim_pos[1]
+ self.setPos(pos_x, pos_y)
+ self.update()
+ pass
+
class ConnectionNode(QGraphicsItem, GraphNode):
def __init__(self, p0: GraphNode, p1: GraphNode, parent):
@@ -124,7 +138,7 @@ class ConnectionNode(QGraphicsItem, GraphNode):
return self.__highlight_mark
pass
- def relayout_exec(self):
+ def relayout_exec(self, ppi: float = 1):
start_pos = self.__point0.pos()
end_pos = self.__point1.pos()
@@ -135,6 +149,7 @@ class ConnectionNode(QGraphicsItem, GraphNode):
self.setPos(QPointF(start_x, start_y))
self.__outline = QRectF(0, 0, end_x - start_x, end_y - start_y)
+ self.update()
pass
def boundingRect(self):
@@ -176,6 +191,8 @@ class UDGPresent(QGraphicsView):
def __init__(self, parent):
QGraphicsView.__init__(self, parent)
+ self.pixel_per_unit = 5000
+
self.__highlight_nodes: List[GraphNode] = []
self.node_set: Dict[str, GraphNode] = {}
self.__layout_graph = nx.Graph()
@@ -190,69 +207,38 @@ class UDGPresent(QGraphicsView):
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]
+ for node_name in pos_map:
+ node_prim_pos = pos_map[node_name]
+ targetx_node = PresentNode(node_name, self.font(), node_prim_pos[0], node_prim_pos[1], None)
+ self.node_set[node_name] = targetx_node
- 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)
+ self.scene().addItem(targetx_node)
+ targetx_node.relayout_exec(self.pixel_per_unit)
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]
+ node_one: PresentNode = self.node_set[edge_start]
+ node_two: PresentNode = self.node_set[edge_end]
+ node_one.sibling_append(node_two)
+ node_two.sibling_append(node_one)
+
connection = ConnectionNode(node_one, node_two, None)
self.scene().addItem(connection)
- connection.relayout_exec()
+ connection.relayout_exec(self.pixel_per_unit)
+ self.node_set[f"conn::{edge_start}&{edge_end}"] = connection
pass
pass
@@ -260,7 +246,7 @@ class UDGPresent(QGraphicsView):
QGraphicsView.mousePressEvent(self, event)
if event.button() == Qt.MouseButton.LeftButton:
item: GraphNode = self.itemAt(event.pos())
- if item.node_type() == PresentNodeType.PresentNode:
+ if item is not None and item.node_type() == PresentNodeType.PresentNode:
vnode: PresentNode = item
self.node_clicked.emit(vnode.node_name)
print(vnode.node_name)
@@ -268,6 +254,74 @@ class UDGPresent(QGraphicsView):
pass
pass
+ def refresh_with_ppu(self, ppu: float):
+ self.pixel_per_unit = ppu
+
+ for node in self.node_set.values():
+ if node.node_type() == PresentNodeType.PresentNode:
+ node.relayout_exec(ppu)
+ pass
+ pass
+
+ for node in self.node_set.values():
+ if node.node_type() == PresentNodeType.ConnectionNode:
+ node.relayout_exec(ppu)
+ pass
+ pass
+
+ self.scene().update()
+ self.update()
+ pass
+
+ def update_scene_rect(self):
+ minx = 2*64
+ miny = 2*64
+ maxx = -2**64
+ maxy = -2**64
+ for item in self.node_set.values():
+ minx = min(item.pos().x(), minx)
+ miny = min(item.pos().y(), miny)
+ maxx = max(item.pos().x() + item.boundingRect().width(), maxx)
+ maxy = max(item.pos().y() + item.boundingRect().height(), maxy)
+ pass
+
+ self.scene().setSceneRect(minx, miny, maxx - minx, maxy - miny)
+ pass
+
+ def highlight_sibling_nodes(self, center_node: str):
+ for node in self.__highlight_nodes:
+ node.highlight(False)
+ pass
+ self.__highlight_nodes.clear()
+
+ target_node: PresentNode = self.node_set[center_node]
+ if target_node is None:
+ return
+
+ target_node.highlight(True)
+ self.__highlight_nodes.append(target_node)
+ for sib_node in target_node.sibling_nodes():
+ sib_node.highlight(True)
+ self.__highlight_nodes.append(sib_node)
+
+ sib_name = sib_node.node_name
+ constr1 = f"conn::{center_node}&{sib_name}"
+ constr2 = f"conn::{sib_name}&{center_node}"
+ if constr1 in self.node_set:
+ self.node_set[constr1].highlight(True)
+ self.__highlight_nodes.append(self.node_set[constr1])
+ elif constr2 in self.node_set:
+ self.node_set[constr2].highlight(True)
+ self.__highlight_nodes.append(self.node_set[constr2])
+ pass
+ pass
+
+ self.scene().update()
+ self.update()
+ pass
+
+
+
if __name__ == "__main__":
app = QApplication(sys.argv)
diff --git a/graph/undirected_graph/__pycache__/UDGPresent.cpython-312.pyc b/graph/undirected_graph/__pycache__/UDGPresent.cpython-312.pyc
new file mode 100644
index 0000000..1feb635
Binary files /dev/null and b/graph/undirected_graph/__pycache__/UDGPresent.cpython-312.pyc differ
diff --git a/graph/undirected_graph/__pycache__/__init__.cpython-312.pyc b/graph/undirected_graph/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..df9b4c2
Binary files /dev/null and b/graph/undirected_graph/__pycache__/__init__.cpython-312.pyc differ
diff --git a/parse/StoryMap.py b/parse/StoryMap.py
index f90d359..15e2540 100644
--- a/parse/StoryMap.py
+++ b/parse/StoryMap.py
@@ -27,6 +27,24 @@ class FragmentSlice(EmptyNode):
pass
+class ArticleSlice(EmptyNode):
+ def __init__(self, a_name: str, v_name: str):
+ self.article_name = a_name
+ self.volume_blongs = v_name
+ self.article_fullname = f"{v_name}@{a_name}"
+
+ self.refer_target: List[str] = [] # 引用切面
+ self.text_sections: List[str] = [] # 文本段落
+ pass
+
+ def get_from_memory(self) -> str:
+ return "\n".join(self.text_sections)
+
+ def set_to_memory(self, content: str):
+ self.text_sections = content.split("\n")
+ pass
+
+
class StoryMap:
def __init__(self, name: str):
self.story_name = name
@@ -44,7 +62,7 @@ class StoryMap:
self.slice_list.append(node)
pass
- def get_fragment_defined(self, name: str) -> FragmentSlice:
+ def get_fragment_defined(self, name: str) -> FragmentSlice|None:
for fit in self.slice_list:
if fit.is_define_node[0]:
if fit.is_define_node[1] == name:
@@ -140,6 +158,58 @@ class XAST_ParseTool:
self.storylines_plait(story_dict)
return story_dict
+ def get_article_nodes(self) -> List[ArticleSlice]:
+ retvalues = []
+
+ hangout_nodes = ArticleSlice("悬空节点", "")
+ retvalues.append(hangout_nodes)
+
+ fragments = self.__get_all_fragment_names()
+ volumes = self.dom_tree.getElementsByTagName("volume")
+ for vnode in volumes:
+ child_articles = self.__volume_node_parse(vnode)
+ retvalues.extend(child_articles)
+ for child in child_articles:
+ for refn in child.refer_target:
+ if refn in fragments:
+ fragments.remove(refn)
+ pass
+ pass
+ pass
+ pass
+
+ hangout_nodes.refer_target.extend(fragments)
+ return retvalues
+
+ def __get_all_fragment_names(self) -> List[str]:
+ values = []
+
+ frags = self.dom_tree.getElementsByTagName("fragment")
+ for frag in frags:
+ story: mdom.Element = frag.parentNode
+ values.append(f"{story.getAttribute("name")}&{frag.getAttribute("name")}")
+ pass
+
+ return values
+
+ def __volume_node_parse(self, vnode: mdom.Element)-> List[ArticleSlice]:
+ retvalues = []
+
+ vname = vnode.getAttribute("name")
+ articles = vnode.getElementsByTagName("article")
+ for anode in articles:
+ aname = anode.getAttribute("name")
+ node_inst = ArticleSlice(aname, vname)
+ retvalues.append(node_inst)
+ refsnode = anode.getElementsByTagName("refer")
+ for refnode in refsnode:
+ ref_story = refnode.getAttribute("story")
+ ref_fragment = refnode.getAttribute("fragment")
+ node_inst.refer_target.append(f"{ref_story}&{ref_fragment}")
+ pass
+ pass
+
+ return retvalues
pass
diff --git a/parse/__pycache__/StoryMap.cpython-312.pyc b/parse/__pycache__/StoryMap.cpython-312.pyc
index 23db8e7..edc8c0b 100644
Binary files a/parse/__pycache__/StoryMap.cpython-312.pyc and b/parse/__pycache__/StoryMap.cpython-312.pyc differ