Pamhyr2/src/view/network/GraphWidget.py

276 lines
8.6 KiB
Python

# -*- coding: utf-8 -*-
import math
from model.network.Node import Node
from model.network.Edge import Edge
from model.network.Graph import Graph
from PyQt5.QtCore import (
qAbs, QLineF, QPointF, QPoint, qrand, QRectF, QSizeF, qsrand,
Qt, QTime
)
from PyQt5.QtGui import (
QBrush, QColor, QLinearGradient, QPainter,
QPainterPath, QPen, QPolygonF, QRadialGradient,
QFont,
)
from PyQt5.QtWidgets import (
QApplication, QGraphicsItem, QGraphicsScene,
QGraphicsView, QStyle
)
class NodeItem(QGraphicsItem):
Type = QGraphicsItem.UserType + 1
def __init__(self, node, graph_widget):
super(NodeItem, self).__init__()
self.node = node
self.setPos(QPointF(self.node.pos.x, self.node.pos.y))
self.graph = graph_widget
self.newPos = QPointF()
self.setFlag(QGraphicsItem.ItemIsMovable)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
self.setZValue(1)
def type(self):
return NodeItem.Type
def advance(self):
if self.newPos == self.pos():
return False
self.setPos(self.newPos)
return True
def boundingRect(self):
adjust = 2.0
return QRectF(-10 - adjust, -10 - adjust, 23 + adjust, 23 + adjust)
def shape(self):
path = QPainterPath()
path.addEllipse(-10, -10, 20, 20)
return path
def paint(self, painter, option, widget):
painter.setPen(Qt.NoPen)
# painter.setBrush(Qt.darkGray)
# painter.drawEllipse(-7, -7, 20, 20)
gradient = QRadialGradient(-3, -3, 10)
if option.state & QStyle.State_Sunken:
gradient.setCenter(3, 3)
gradient.setFocalPoint(3, 3)
gradient.setColorAt(1, QColor(Qt.yellow).lighter(120))
gradient.setColorAt(0, QColor(Qt.darkYellow).lighter(120))
else:
gradient.setColorAt(0, Qt.yellow)
gradient.setColorAt(1, Qt.darkYellow)
painter.setBrush(QBrush(gradient))
# painter.setPen(QPen(Qt.black, 0))
painter.drawEllipse(-10, -10, 20, 20)
painter.setFont(QFont("Arial", 20))
painter.drawText(QPoint(-10, -10), self.node.name)
def itemChange(self, change, value):
if change == QGraphicsItem.ItemPositionHasChanged:
self.graph.itemMoved()
return super(NodeItem, self).itemChange(change, value)
def mousePressEvent(self, event):
self.update()
super(NodeItem, self).mousePressEvent(event)
def mouseReleaseEvent(self, event):
self.update()
super(NodeItem, self).mouseReleaseEvent(event)
class EdgeItem(QGraphicsItem):
Type = QGraphicsItem.UserType + 2
def __init__(self, edge):
super(EdgeItem, self).__init__()
self.arrowSize = 10.0
self.sourcePoint = QPointF()
self.destPoint = QPointF()
self.setAcceptedMouseButtons(Qt.NoButton)
self.source = edge.node1
self.dest = edge.node2
self.edge = edge
self.adjust()
def type(self):
return Edge.Type
def sourceNode(self):
return self.source
def setSourceNode(self, node):
self.source = node
self.adjust()
def destNode(self):
return self.dest
def setDestNode(self, node):
self.dest = node
self.adjust()
def adjust(self):
if not self.source or not self.dest:
return
line = QLineF(
self.mapFromItem(self.source, 0, 0),
self.mapFromItem(self.dest, 0, 0)
)
length = line.length()
self.prepareGeometryChange()
if length > 20.0:
edgeOffset = QPointF((line.dx() * 10) / length,
(line.dy() * 10) / length)
self.sourcePoint = line.p1() + edgeOffset
self.destPoint = line.p2() - edgeOffset
else:
self.sourcePoint = line.p1()
self.destPoint = line.p1()
def boundingRect(self):
if not self.source or not self.dest:
return QRectF()
penWidth = 1.0
extra = (penWidth + self.arrowSize) / 2.0
return QRectF(
self.sourcePoint,
QSizeF(
self.destPoint.x() - self.sourcePoint.x(),
self.destPoint.y() - self.sourcePoint.y()
)
).normalized().adjusted(-extra, -extra, extra, extra)
def paint(self, painter, option, widget):
if not self.source or not self.dest:
return
# Draw the line itself.
line = QLineF(self.sourcePoint, self.destPoint)
if line.length() == 0.0:
return
painter.setPen(QPen(Qt.black, 1, Qt.SolidLine, Qt.RoundCap,
Qt.RoundJoin))
painter.drawLine(line)
# Draw the arrows if there's enough room.
angle = math.acos(line.dx() / line.length())
if line.dy() >= 0:
angle = Edge.TwoPi - angle
sourceArrowP1 = self.sourcePoint + QPointF(math.sin(angle + Edge.Pi / 3) * self.arrowSize,
math.cos(angle + Edge.Pi / 3) * self.arrowSize)
sourceArrowP2 = self.sourcePoint + QPointF(math.sin(angle + Edge.Pi - Edge.Pi / 3) * self.arrowSize,
math.cos(angle + Edge.Pi - Edge.Pi / 3) * self.arrowSize);
destArrowP1 = self.destPoint + QPointF(math.sin(angle - Edge.Pi / 3) * self.arrowSize,
math.cos(angle - Edge.Pi / 3) * self.arrowSize)
destArrowP2 = self.destPoint + QPointF(math.sin(angle - Edge.Pi + Edge.Pi / 3) * self.arrowSize,
math.cos(angle - Edge.Pi + Edge.Pi / 3) * self.arrowSize)
painter.setBrush(Qt.black)
painter.drawPolygon(QPolygonF([line.p1(), sourceArrowP1, sourceArrowP2]))
painter.drawPolygon(QPolygonF([line.p2(), destArrowP1, destArrowP2]))
class GraphWidget(QGraphicsView):
def __init__(self, graph, parent=None):
super(GraphWidget, self).__init__(parent=parent)
self.timerId = 0
self.parent = parent
scene = QGraphicsScene(self)
scene.setItemIndexMethod(QGraphicsScene.NoIndex)
scene.setSceneRect(0, 0, 10000, 10000)
self.setScene(scene)
self.setCacheMode(QGraphicsView.CacheBackground)
self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
self.setRenderHint(QPainter.Antialiasing)
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QGraphicsView.AnchorViewCenter)
# scene.addItem(LandMark())
self.graph = graph
for node in self.graph.nodes():
scene.addItem(NodeItem(node, self))
# for edge in self.graph.edges():
# scene.addItem(EdgeItem(edge))
self.scale(1, 1)
self.previousScale = 1
self.setMinimumSize(400, 400)
def itemMoved(self):
if not self.timerId:
self.timerId = self.startTimer(1000 / 25)
def keyPressEvent(self, event):
key = event.key()
if key == Qt.Key_Up:
self.centerNode.moveBy(0, -20)
elif key == Qt.Key_Down:
self.centerNode.moveBy(0, 20)
elif key == Qt.Key_Left:
self.centerNode.moveBy(-20, 0)
elif key == Qt.Key_Right:
self.centerNode.moveBy(20, 0)
elif key == Qt.Key_Plus:
self.scaleView(1.2)
elif key == Qt.Key_Minus:
self.scaleView(1 / 1.2)
elif key == Qt.Key_Space or key == Qt.Key_Enter:
for item in self.scene().items():
if isinstance(item, Node):
item.setPos(-150 + qrand() % 300, -150 + qrand() % 300)
else:
super(GraphWidget, self).keyPressEvent(event)
def drawBackground(self, painter, rect):
sceneRect = self.sceneRect()
gradient = QLinearGradient(sceneRect.topLeft(), sceneRect.bottomRight())
gradient.setColorAt(0, Qt.lightGray)
gradient.setColorAt(1, Qt.lightGray)
painter.fillRect(rect.intersected(sceneRect), QBrush(gradient))
painter.setBrush(Qt.NoBrush)
painter.drawRect(sceneRect)
def wheelEvent(self, event):
self.scaleView(math.pow(2.0, -event.angleDelta().y() / 240.0))
def scaleView(self, scaleFactor):
factor = self.transform().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width()
if factor < 0.05 or factor > 1.5:
return
# print(f"{scaleFactor} : {factor}")
self.scale(scaleFactor, scaleFactor)