# -*- 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, 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 LandMark(QGraphicsItem): def paint(self, painter, option, widget): painter.setPen(QPen(Qt.black, 0)) painter.drawLine(-500, -500, 500, 500) painter.drawLine(-500, 500, 500, -500) painter.drawLine(-500, -500, 500, -500) painter.drawLine(500, -500, 500, 500) painter.drawLine(500, 500, -500, 500) painter.drawLine(-500, 500, -500, -500) class NodeItem(QGraphicsItem): Type = QGraphicsItem.UserType + 1 def __init__(self, node, graphWidget): super(NodeItem, self).__init__() self.node = node self.setPos(QPointF(self.node.pos.x, self.node.pos.y)) self.graph = graphWidget 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(QRectF(-10, -10, 20, 20), Qt.AlignCenter, 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): super(GraphWidget, self).__init__() self.timerId = 0 scene = QGraphicsScene(self) scene.setItemIndexMethod(QGraphicsScene.NoIndex) scene.setSceneRect(-500, -500, 500, 500) 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(0.8, 0.8) 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 timerEvent(self, event): nodes = [item for item in self.scene().items() if isinstance(item, Node)] for node in nodes: node.calculateForces() itemsMoved = False for node in nodes: if node.advance(): itemsMoved = True if not itemsMoved: self.killTimer(self.timerId) self.timerId = 0 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.07 or factor > 100: return self.scale(scaleFactor, scaleFactor)