python networkx + pyqtgraph实现交互式网络图
实现网络图中的节点可以随意拖动。
解决方案
- networkx_viewer:暂停开发比较久
- networkx
- tk
- 基于pyqtgraph
# -*- coding: utf-8 -*-
import sys
import pyqtgraph as pg
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import numpy as np
import networkx as nx
import random
# Enable antialiasing for prettier plots
#pg.setConfigOptions(antialias=True)
class DrawGraph(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.setWindowTitle('NetViz')
self.mw = pg.PlotWidget()#background=QColor(255,255,255))
self.mw.setAspectLocked(lock=True, ratio=1)
self.setCentralWidget(self.mw)
self.showMaximized()
self.mw.getPlotItem().showAxis('bottom', show=False)
self.mw.getPlotItem().showAxis('left', show=False)
self.g = Graph()
self.G=None
self.mw.addItem(self.g)
self.create_menu()
def create_action( self, text, slot=None, shortcut=None,
icon=None, tip=None, checkable=False,
signal="triggered()"):
action = QAction(text, self)
if icon is not None:
action.setIcon(QIcon(":/%s.png" % icon))
if shortcut is not None:
action.setShortcut(shortcut)
if tip is not None:
action.setToolTip(tip)
action.setStatusTip(tip)
if slot is not None:
action.triggered.connect(slot)
#self.connect(action, SIGNAL(signal), slot)
if checkable:
action.setCheckable(True)
return action
def create_menu(self):
self.file_menu = self.menuBar().addMenu("&File")
load_file_action = self.create_action("&Open Network File",
shortcut="Ctrl+O", slot=self.open_network,tip="Open Network File")
load_model_action = self.create_action("&Karate_club",
slot=self.zk_network,tip="Using Karate club graph")
quit_action = self.create_action("&Quit", slot=self.close,
shortcut="Ctrl+Q", tip="Close the application")
self.add_actions(self.file_menu, (load_file_action, None,load_model_action, quit_action))
def add_actions(self, target, actions):
for action in actions:
if action is None:
target.addSeparator()
else:
target.addAction(action)
def open_network(self):
file_choices = "Network File (*.gml)"
path = QFileDialog.getOpenFileName(self, 'Open file', '', file_choices)
if path:
G=nx.read_gml(filename[0])
self.on_draw(G)
def zk_network(self):
G=nx.karate_club_graph()
self.on_draw(G)
########################################################
def on_draw(self,G):
self.G = G
self.G.pos=nx.layout.spring_layout(self.G)
poslist=[]
texts=[]
sizes=[]
for node in sorted(self.G.nodes()):
poslist.append(self.G.pos[node]) # node position
texts.append(str(node)) # node label
sizes.append(self.G.degree(node)*0.005) # node size
pos = np.array(poslist, dtype=float)
adjlist=[]
linelist=[]
for edge in self.G.edges():
adjlist.append(edge)
linelist.append((125,125,125,122,1))
adj = np.array(adjlist)
NodeColors=[QColor(random.randint(1,255), random.randint(1,255),random.randint(1,255)) for i in range(self.G.number_of_nodes())]
lines = np.array(linelist, dtype=[('red',np.ubyte),('green',np.ubyte),('blue',np.ubyte),('alpha',np.ubyte),('width',float)])
self.g.setData(pos=pos, adj=adj,pxMode=False,size=sizes,pen=lines,symbolPen=NodeColors,symbolBrush=NodeColors, text=texts)
class Graph(pg.GraphItem):
def __init__(self):
self.dragPoint = None
self.dragOffset = None
self.textItems = []
pg.GraphItem.__init__(self)
def setData(self, **kwds):
self.text = kwds.pop('text', [])
self.data = kwds
if 'pos' in self.data:
npts = self.data['pos'].shape[0]
self.data['data'] = np.empty(npts, dtype=[('index', int)])
self.data['data']['index'] = np.arange(npts)
self.setTexts(self.text)
self.updateGraph()
def setTexts(self, text):
for i in self.textItems:
i.scene().removeItem(i)
self.textItems = []
for t in text:
item = pg.TextItem(t)
self.textItems.append(item)
item.setParentItem(self)
def updateGraph(self):
pg.GraphItem.setData(self, **self.data)
for i,item in enumerate(self.textItems):
item.setPos(*self.data['pos'][i])
def mouseDragEvent(self, ev):
if ev.button() != Qt.LeftButton:
ev.ignore()
return
if ev.isStart():
pos = ev.buttonDownPos()
pts = self.scatter.pointsAt(pos)
if len(pts) == 0:
ev.ignore()
return
self.dragPoint = pts[0]
ind = pts[0].data()[0]
self.dragOffset = self.data['pos'][ind] - pos
elif ev.isFinish():
self.dragPoint = None
return
else:
if self.dragPoint is None:
ev.ignore()
return
ind = self.dragPoint.data()[0]
self.data['pos'][ind] = ev.pos() + self.dragOffset
self.updateGraph()
ev.accept()
def main():
app = QApplication(sys.argv)
form = DrawGraph()
form.show()
app.exec_()
if __name__ == "__main__":
main()