StoryCheckTools/graph/undirected_graph/UDGPresent.py

338 lines
10 KiB
Python

import sys, os
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 PyQt5.QtWidgets import QSplitter, QHBoxLayout
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
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")
@abstractmethod
def relayout_exec(self, ppu: float = 1):
raise NotImplementedError("relayout_exec")
class PresentNode(QGraphicsItem, GraphNode):
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
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
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):
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, ppi: float = 1):
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)
self.update()
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.pixel_per_unit = 5000
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()
for line in line_set:
start_node = line.points()[0]
self.__layout_graph.add_node(start_node.point_name)
end_node = line.points()[1]
self.__layout_graph.add_node(end_node.point_name)
self.__layout_graph.add_edge(start_node.point_name, end_node.point_name)
pass
pos_map = nx.spring_layout(self.__layout_graph)
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
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: 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(self.pixel_per_unit)
self.node_set[f"conn::{edge_start}&{edge_end}"] = connection
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 is not None and item.node_type() == PresentNodeType.PresentNode:
vnode: PresentNode = item
self.node_clicked.emit(vnode.node_name)
pass
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)
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()