Commit 04c3f76e authored by Pierre-Antoine Rouby's avatar Pierre-Antoine Rouby
Browse files

network: Add graphwidget view.

Showing with 343 additions and 36 deletions
+343 -36
...@@ -50,8 +50,12 @@ class Graph(object): ...@@ -50,8 +50,12 @@ class Graph(object):
def node(self, node_name:str): def node(self, node_name:str):
return list(filter(lambda n: n.name == node_name, self._nodes))[0] return list(filter(lambda n: n.name == node_name, self._nodes))[0]
def add_node(self): def add_node(self, x:float = 0.0, y:float = 0.0):
node = Node(self._nodes_ids, f"Node {self._nodes_ids}") node = Node(
self._nodes_ids,
f"Node {self._nodes_ids}",
x = x, y = y
)
self._nodes.append(node) self._nodes.append(node)
self._nodes_ids += 1 self._nodes_ids += 1
return node return node
......
...@@ -3,12 +3,13 @@ ...@@ -3,12 +3,13 @@
from model.network.Point import Point from model.network.Point import Point
class Node(object): class Node(object):
def __init__(self, id:str, name:str): def __init__(self, id:str, name:str,
x:float = 0.0, y:float = 0.0):
super(Node, self).__init__() super(Node, self).__init__()
self.id = id self.id = id
self.name = name self.name = name
self.pos = Point(0, 0) self.pos = Point(x, y)
def __repr__(self): def __repr__(self):
return f"Node {{id: {self.id}, name: {self.name}}}" return f"Node {{id: {self.id}, name: {self.name}}}"
...@@ -20,6 +21,8 @@ class Node(object): ...@@ -20,6 +21,8 @@ class Node(object):
ret = self.name ret = self.name
elif name == "id": elif name == "id":
ret = self.id ret = self.id
elif name == "pos":
ret = f"({self.pos.x},{self.pos.y})"
return ret return ret
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
def Point(object): class Point(object):
def __init__(self, x:float, y:float): def __init__(self, x:float, y:float):
super(Point, self).__init__() super(Point, self).__init__()
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from view.ASubWindow import ASubWindow
from model.network.Node import Node from model.network.Node import Node
from model.network.Edge import Edge from model.network.Edge import Edge
from model.network.Graph import Graph from model.network.Graph import Graph
from view.ASubWindow import ASubWindow
from view.network.GraphWidget import GraphWidget
from PyQt5.QtCore import ( from PyQt5.QtCore import (
Qt, QRect, QVariant, QAbstractTableModel, pyqtSlot, pyqtSignal, Qt, QRect, QVariant, QAbstractTableModel, pyqtSlot, pyqtSignal,
) )
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QTableView, QItemDelegate, QComboBox, QLineEdit, QTableView, QItemDelegate, QComboBox, QLineEdit, QHBoxLayout,
) )
class LineEditDelegate(QItemDelegate): class LineEditDelegate(QItemDelegate):
...@@ -121,11 +122,20 @@ class NetworkWindow(ASubWindow): ...@@ -121,11 +122,20 @@ class NetworkWindow(ASubWindow):
self.ui.setWindowTitle(title) self.ui.setWindowTitle(title)
self.graph = Graph() self.graph = Graph()
n1 = self.graph.add_node()
n2 = self.graph.add_node(50.0,50.0)
e1 = self.graph.add_edge(n1,n2)
# Graph Widget
self.graph_widget = GraphWidget(self.graph)
self.graph_layout = self.find(QHBoxLayout, "horizontalLayout_graph")
self.graph_layout.addWidget(self.graph_widget)
# Nodes table # Nodes table
self.nodes_model = TableModel( self.nodes_model = TableModel(
headers = ["name", "id"], headers = ["name", "id", "pos"],
rows = self.graph.nodes(), rows = self.graph.nodes(),
graph = self.graph, graph = self.graph,
) )
...@@ -140,11 +150,11 @@ class NetworkWindow(ASubWindow): ...@@ -140,11 +150,11 @@ class NetworkWindow(ASubWindow):
self.reachs_model = TableModel( self.reachs_model = TableModel(
headers = ["name", "node1", "node2"], headers = ["name", "node1", "node2"],
rows = self.graph.edges(), rows = self.graph.edges(),
graph=self.graph, graph = self.graph,
) )
self.delegate_combobox = ComboBoxDelegate( self.delegate_combobox = ComboBoxDelegate(
graph=self.graph, graph = self.graph,
parent=self, parent = self,
) )
table = self.find(QTableView, "tableView_reachs") table = self.find(QTableView, "tableView_reachs")
table.setModel(self.reachs_model) table.setModel(self.reachs_model)
...@@ -152,6 +162,7 @@ class NetworkWindow(ASubWindow): ...@@ -152,6 +162,7 @@ class NetworkWindow(ASubWindow):
table.setItemDelegateForColumn(2, self.delegate_combobox) table.setItemDelegateForColumn(2, self.delegate_combobox)
#table.resizeColumnsToContents() #table.resizeColumnsToContents()
# Connection the two table # Connection
self.nodes_model.dataChanged.connect(self.reachs_model.update) self.nodes_model.dataChanged.connect(self.reachs_model.update)
self.reachs_model.dataChanged.connect(self.nodes_model.update) self.reachs_model.dataChanged.connect(self.nodes_model.update)
# -*- 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)
...@@ -62,28 +62,7 @@ ...@@ -62,28 +62,7 @@
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_graph"/>
<item>
<widget class="QGraphicsView" name="graphicsView_3">
<property name="minimumSize">
<size>
<width>0</width>
<height>400</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="verticalSlider_2">
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</item> </item>
</layout> </layout>
</item> </item>
...@@ -140,7 +119,20 @@ ...@@ -140,7 +119,20 @@
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QTableView" name="tableView_reachs"/> <widget class="QTableView" name="tableView_reachs">
<property name="minimumSize">
<size>
<width>650</width>
<height>150</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>200</height>
</size>
</property>
</widget>
</item> </item>
</layout> </layout>
</item> </item>
...@@ -197,7 +189,20 @@ ...@@ -197,7 +189,20 @@
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QTableView" name="tableView_nodes"/> <widget class="QTableView" name="tableView_nodes">
<property name="minimumSize">
<size>
<width>0</width>
<height>150</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>200</height>
</size>
</property>
</widget>
</item> </item>
</layout> </layout>
</item> </item>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment