前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于图形项的弹性节点程序

基于图形项的弹性节点程序

作者头像
用户6021899
发布2019-08-14 17:34:31
1.2K0
发布2019-08-14 17:34:31
举报

本例原是PyQt4版本的官方demo 程序,现已改成了PyQt5版本。程序可响应用户的键盘操作,如方向键,“+”,“-”键和空格键,以及鼠标拖放和滚轮操作。其功能是模拟弹性节点网络----节点小球在收到外力时会移动,在外力撤除后会回弹。

代码如下:

代码语言:javascript
复制
import math
 
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)
from PyQt5.QtWidgets import (QApplication, QGraphicsItem, QGraphicsScene,
        QGraphicsView, QStyle)
 
 
class Edge(QGraphicsItem):#边线图形项
    Pi = math.pi
    TwoPi = 2.0 * Pi
 
    Type = QGraphicsItem.UserType + 2
 
    def __init__(self, sourceNode, destNode):
        super(Edge, self).__init__()
 
        self.arrowSize = 10.0
        self.sourcePoint = QPointF()
        self.destPoint = QPointF()
 
        self.setAcceptedMouseButtons(Qt.NoButton)
        self.source = sourceNode #起始节点
        self.dest = destNode #终节点
        self.source.addEdge(self)
        self.dest.addEdge(self)
        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 Node(QGraphicsItem):#节点小球图形项
    Type = QGraphicsItem.UserType + 1
 
    def __init__(self, graphWidget):
        super(Node, self).__init__()
 
        self.graph = graphWidget
        self.edgeList = []
        self.newPos = QPointF()
 
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges)
        self.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        self.setZValue(1)
 
    def type(self):
        return Node.Type
 
    def addEdge(self, edge):
        self.edgeList.append(edge)
        edge.adjust()
 
    def edges(self):
        return self.edgeList
 
    def calculateForces(self):
        if not self.scene() or self.scene().mouseGrabberItem() is self:
            self.newPos = self.pos()
            return
 
        # Sum up all forces pushing this item away.
        xvel = 0.0
        yvel = 0.0
        for item in self.scene().items():
            if not isinstance(item, Node):
                continue
 
            line = QLineF(self.mapFromItem(item, 0, 0), QPointF(0, 0))
            dx = line.dx()
            dy = line.dy()
            l = 2.0 * (dx * dx + dy * dy)
            if l > 0:
                xvel += (dx * 150.0) / l
                yvel += (dy * 150.0) / l
 
        # Now subtract all forces pulling items together.
        weight = (len(self.edgeList) + 1) * 10.0
        for edge in self.edgeList:
            if edge.sourceNode() is self:
                pos = self.mapFromItem(edge.destNode(), 0, 0)
            else:
                pos = self.mapFromItem(edge.sourceNode(), 0, 0)
            xvel += pos.x() / weight
            yvel += pos.y() / weight
 
        if qAbs(xvel) < 0.1 and qAbs(yvel) < 0.1:
            xvel = yvel = 0.0
 
        sceneRect = self.scene().sceneRect()
        self.newPos = self.pos() + QPointF(xvel, yvel)
        self.newPos.setX(min(max(self.newPos.x(), sceneRect.left() + 10), sceneRect.right() - 10))
        self.newPos.setY(min(max(self.newPos.y(), sceneRect.top() + 10), sceneRect.bottom() - 10))
 
    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)
 
    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemPositionHasChanged:
            for edge in self.edgeList:
                edge.adjust()
            self.graph.itemMoved()
 
        return super(Node, self).itemChange(change, value)
 
    def mousePressEvent(self, event):
        self.update()
        super(Node, self).mousePressEvent(event)
 
    def mouseReleaseEvent(self, event):
        self.update()
        super(Node, self).mouseReleaseEvent(event)
 
 
class GraphWidget(QGraphicsView):#图形视图类
    def __init__(self):
        super(GraphWidget, self).__init__()
 
        self.timerId = 0
 
        scene = QGraphicsScene(self)#场景
        scene.setItemIndexMethod(QGraphicsScene.NoIndex)
        scene.setSceneRect(-200, -200, 400, 400)
        self.setScene(scene)
        self.setCacheMode(QGraphicsView.CacheBackground)
        self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
        self.setRenderHint(QPainter.Antialiasing)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorViewCenter)
       
        #创建个节点视图项
        node1 = Node(self)
        node2 = Node(self)
        node3 = Node(self)
        node4 = Node(self)
        self.centerNode = Node(self)
        node6 = Node(self)
        node7 = Node(self)
        node8 = Node(self)
        node9 = Node(self)
        
        #将各节点加入场景
        scene.addItem(node1)
        scene.addItem(node2)
        scene.addItem(node3)
        scene.addItem(node4)
        scene.addItem(self.centerNode)
        scene.addItem(node6)
        scene.addItem(node7)
        scene.addItem(node8)
        scene.addItem(node9)
        
        #生成各边线视图项,并将其加入场景
        scene.addItem(Edge(node1, node2))
        scene.addItem(Edge(node2, node3))
        scene.addItem(Edge(node2, self.centerNode))
        scene.addItem(Edge(node3, node6))
        scene.addItem(Edge(node4, node1))
        scene.addItem(Edge(node4, self.centerNode))
        scene.addItem(Edge(self.centerNode, node6))
        scene.addItem(Edge(self.centerNode, node8))
        scene.addItem(Edge(node6, node9))
        scene.addItem(Edge(node7, node4))
        scene.addItem(Edge(node8, node7))
        scene.addItem(Edge(node9, node8))
 
        node1.setPos(-50, -50)
        node2.setPos(0, -50)
        node3.setPos(50, -50)
        node4.setPos(-50, 0)
        self.centerNode.setPos(0, 0)
        node6.setPos(50, 0)
        node7.setPos(-50, 50)
        node8.setPos(0, 50)
        node9.setPos(50, 50)
 
        self.scale(0.8, 0.8)
        self.setMinimumSize(400, 400)
        self.setWindowTitle("Elastic Nodes")
 
    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 drawBackground(self, painter, rect):
        # Shadow.
        sceneRect = self.sceneRect()
        rightShadow = QRectF(sceneRect.right(), sceneRect.top() + 5, 5,
                sceneRect.height())
        bottomShadow = QRectF(sceneRect.left() + 5, sceneRect.bottom(),
                sceneRect.width(), 5)
        if rightShadow.intersects(rect) or rightShadow.contains(rect):
         painter.fillRect(rightShadow, Qt.darkGray)
        if bottomShadow.intersects(rect) or bottomShadow.contains(rect):
         painter.fillRect(bottomShadow, Qt.darkGray)
 
        # Fill.
        gradient = QLinearGradient(sceneRect.topLeft(), sceneRect.bottomRight())
        gradient.setColorAt(0, Qt.white)
        gradient.setColorAt(1, Qt.lightGray)
        painter.fillRect(rect.intersected(sceneRect), QBrush(gradient))
        painter.setBrush(Qt.NoBrush)
        painter.drawRect(sceneRect)
 
        # Text.
        textRect = QRectF(sceneRect.left() + 4, sceneRect.top() + 4,
                sceneRect.width() - 4, sceneRect.height() - 4)
        message = "Click and drag the nodes around, and zoom with the " \
                "mouse wheel or the '+' and '-' keys"
 
        font = painter.font()
        font.setBold(True)
        font.setPointSize(14)
        painter.setFont(font)
        painter.setPen(Qt.lightGray)
        painter.drawText(textRect.translated(2, 2), message)
        painter.setPen(Qt.black)
        painter.drawText(textRect, message)
 
    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)
 
 
if __name__ == '__main__':
 
    import sys
 
    app = QApplication(sys.argv)
    qsrand(QTime(0,0,0).secsTo(QTime.currentTime()))
 
    widget = GraphWidget()
    widget.show()
 
    sys.exit(app.exec_())

qAbs()返回一个数的绝对值。

qsrand用来设置一个种子,该种子为qrand生成随机数的起始值。比如说qsrand(10),设置10为种子,那么qrand生成的随机数就在[10,32767]之间。而如果在qrand()前没有调用过qsrand(),那么qrand()就会自动调用qsrand(1),即系统默认将1作为随机数的起始值。使用相同的种子生成的随机数一样。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python可视化编程机器学习OpenCV 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档