Python+Neo4j+Django+Echarts知識圖譜可視化
;width:100%">highlight: a11y-dark theme: smartblue
0 版本
python == 3.8.0
Django == 4.1.2
py2neo == 2021.2.3
pyecharts == 1.9.1
1 目的及功能
目的: 由於neo4j自帶的可視化界面展示效果有限,重建可視化界面
功能: 實現與neo4j數據庫實時連接,可動態更新,動態顯示
2 Neo4j
2.1 Neo4安裝
在Neo4j官網下載社區版
下載對應版本壓縮包,解壓即可。我使用的是Ubuntu系統,解壓完成後進入bin文件夾下
在當前界面啟動終端,輸入啟動命令:
./neo4j console
可以看到啟動成功後可以在 localhost:7474
打開neo4j
默認用户名和密碼是:neo4j
登錄後會提示你修改,如果想更換用户名和密碼可以在system用户下操作。這裏不再贅述。
2.2 使用py2neo導入數據
本文使用json格式對數據存儲,參考[1][2]
導入到neo4j ``` python
!/usr/bin/env python3
coding: utf-8
File: MilitaryGraph.py
Author: zpeng
Date: 22-10-20
import os import json import codecs from py2neo import Graph,Node
class MilitaryGraph: def init(self): c_dir = '/'.join(os.path.abspath(file).split('/')[:-1]) self.datapath = os.path.join(c_dir,'military.json') self.g = Graph('http://localhost:7474/',auth=("neo4j","123456"))
def read_nodes(self):
# 節點類別
military_sys = [] #實體名稱
categorys = [] #類別
performances = [] #性能指標
locations = [] #地點
deploys = [] #部署方式
exps = [] #案例
militarysys_infos = [] #系統信息
# 實體關係
rels_per = [] #實體和性能的關係
rels_subcategory = [] #實體與小類之間的關係
rels_categorys = [] #大類與小類之間的關係
rels_rootcategory =[] #大類和根節點之間的關係
rels_loca = [] #實體與地點的關係
rels_deploy = [] #實體與部署的關係
rels_exp = [] #實體與案例的關係
cont = 0
for data in open(self.datapath):
militarysys_dict = {}
cont +=1
print(cont)
data_json = json.loads(data)
militarysys = data_json['name']
militarysys_dict['name'] = militarysys
military_sys.append(militarysys)
militarysys_dict['desc'] = ''
militarysys_dict['category'] = ''
militarysys_dict['performance'] = ''
militarysys_dict['location'] = ''
militarysys_dict['deploy'] = ''
militarysys_dict['exp'] = ''
if 'performance' in data_json:
performances += data_json['performance']
for performance in data_json['performance']:
rels_per.append([militarysys,performance])
if 'desc' in data_json:
militarysys_dict['desc'] = data_json['desc']
if 'location' in data_json:
locations += data_json['location']
for location in data_json['location']:
rels_loca.append([militarysys,location])
if 'deploy' in data_json:
deploys += data_json['deploy']
for deploy in data_json['deploy']:
rels_deploy.append([militarysys,deploy])
if 'exp' in data_json:
exps += data_json['exp']
for exp in data_json['exp']:
rels_exp.append([militarysys,exp])
if 'category' in data_json:
cure_categorys = data_json['category']
if len(cure_categorys) == 2:
rels_rootcategory.append([cure_categorys[1],cure_categorys[0]])
rels_subcategory.append([militarysys,cure_categorys[1]])
if len(cure_categorys) == 3:
root = cure_categorys[0]
big = cure_categorys[1]
small = cure_categorys[2]
rels_rootcategory.append([big,root])
rels_categorys.append([small,big])
rels_subcategory.append([militarysys,small])
militarysys_dict['category'] = cure_categorys
categorys += cure_categorys
militarysys_infos.append(militarysys_dict)
return militarysys_infos, set(military_sys),set(locations),set(performances),set(categorys),set(exps),set(deploys),\
rels_per,rels_loca,rels_deploy,rels_exp,rels_subcategory,rels_categorys,rels_rootcategory
'''創建實體節點'''
def create_node(self, label, nodes):
count = 0
for node_name in nodes:
node = Node(label, name=node_name)
self.g.create(node)
count += 1
print(count, len(nodes))
return
def create_militarysys_nodes(self, militarysys_infos):
count = 0
for militarysys_dict in militarysys_infos:
node = Node("MSYS", name=militarysys_dict['name'], desc=militarysys_dict['desc'],
performance = militarysys_dict['performance'],
category=militarysys_dict['category'] ,location=militarysys_dict['location'],
deploy=militarysys_dict['deploy'],exp=militarysys_dict['exp'])
self.g.create(node)
count += 1
print(count)
return
def create_graphnodes(self):
militarysys_infos, military_sys,locations,performances,categorys,exps,deploys,\
rels_per,rels_loca,rels_deploy,rels_exp,rels_subcategory,rels_categorys,rels_rootcategory = self.read_nodes()
self.create_militarysys_nodes(militarysys_infos)
self.create_node('LOCA',locations)
self.create_node('PERF',performances)
self.create_node('CATE',categorys)
print(len(categorys))
self.create_node('EXPS',exps)
self.create_node('DEPLOY',deploys)
return
'''創建實體關聯邊'''
def create_graphrels(self):
militarysys_infos, military_sys,locations,performances,categorys,exps,deploys,\
rels_per,rels_loca,rels_deploy,rels_exp,rels_subcategory,rels_categorys,rels_rootcategory = self.read_nodes()
self.create_relationship('MSYS','PERF',rels_per,'performances','性能指標')
self.create_relationship('MSYS','LOCA',rels_loca,'locations','位置')
self.create_relationship('MSYS','DEPLOY',rels_deploy,'deploys','部署')
self.create_relationship('MSYS','CATE',rels_subcategory,'belongsto','所屬類別')
self.create_relationship('CATE','CATE',rels_categorys,'belongsto','屬於')
self.create_relationship('CATE','CATE',rels_rootcategory,'belongsto','屬於')
self.create_relationship('MSYS','EXPS',rels_exp,'examples','案例')
def create_relationship(self, start_node, end_node, edges, rel_type, rel_name):
count = 0
# 去重處理
set_edges = []
for edge in edges:
set_edges.append('###'.join(edge))
all = len(set(set_edges))
for edge in set(set_edges):
edge = edge.split('###')
p = edge[0]
q = edge[1]
query = "match(p:%s),(q:%s) where p.name='%s'and q.name='%s' create (p)-[rel:%s{name:'%s'}]->(q)" % (
start_node, end_node, p, q, rel_type, rel_name)
try:
self.g.run(query)
count += 1
print(rel_type, count, all)
except Exception as e:
print(e)
return
'''導出數據txt格式'''
def export_txtdata(self):
militarysys_infos, military_sys,locations,performances,categorys,exps,deploys,\
rels_per,rels_loca,rels_deploy,rels_exp,rels_subcategory,rels_categorys,rels_rootcategory = self.read_nodes()
f_military_sys = open('militarysys.txt', 'w+')
f_locations = open('locations.txt', 'w+')
f_performances = open('performances.txt', 'w+')
f_categorys = open('categorys.txt', 'w+')
f_exps = open('exps.txt', 'w+')
f_deploys = open('deploys.txt', 'w+')
f_military_sys.write('\n'.join(list(military_sys)))
f_locations.write('\n'.join(list(locations)))
f_performances.write('\n'.join(list(performances)))
f_categorys.write('\n'.join(list(categorys)))
f_exps.write('\n'.join(list(exps)))
f_deploys.write('\n'.join(list(deploys)))
f_military_sys.close()
f_locations.close()
f_performances.close()
f_categorys.close()
f_exps.close()
f_deploys.close()
return
'''導出數據為json格式'''
def export_data(self,data,path):
if isinstance(data[0],str):
data = sorted([d.strip("...") for d in set(data)])
with codecs.open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
def export_entitys_relations(self):
militarysys_infos, military_sys,locations,performances,categorys,exps,deploys,\
rels_per,rels_loca,rels_deploy,rels_exp,rels_subcategory,rels_categorys,rels_rootcategory = self.read_nodes()
#導出實體,屬性
self.export_data(list(military_sys),'./graph_data/military_sys.json')
self.export_data(list(locations),'./graph_data/locations.json')
self.export_data(list(performances),'./graph_data/performances.json')
self.export_data(list(categorys),'./graph_data/categorys.json')
self.export_data(list(exps),'./graph_data/exps.json')
self.export_data(list(deploys),'./graph_data/deploys.json')
#導出關係
self.export_data(rels_per,'./graph_data/rels_per.json')
self.export_data(rels_loca,'./graph_data/rels_loca.json')
self.export_data(rels_deploy,'./graph_data/rels_deploy.json')
self.export_data(rels_exp,'./graph_data/rels_exp.json')
self.export_data(rels_subcategory,'./graph_data/rels_subcategory.json')
self.export_data(rels_categorys,'./graph_data/rels_categorys.json')
self.export_data(rels_rootcategory,'./graph_data/rels_rootcategory.json')
if name == 'main':
handler = MilitaryGraph()
print("step1:導入圖譜節點中")
handler.create_graphnodes()
print("step2:導入圖譜邊中")
handler.create_graphrels()
# print("step3:導出數據")
# handler.export_entitys_relations()
```
導入後在neo4j數據庫查看
包涵136個節點,150組關係。 輸入查詢語句 MATCH(n) RETURN n
輸出所有節點和關係
3 前後端控制
使用Django進行後端控制,Echarts進行前端顯示。參考[3]
項目結構
3.1 Django後端
後端使用Django控制,對view進行改寫,增加分類 view.py
文件內容
```
import json
from py2neo import *
from django.shortcuts import render
graph = Graph('http://localhost:7474/', auth=('zp', '123456')) # 連接數據庫
def search_all(): # 定義data數組,存放節點信息 data = [] # 定義關係數組,存放節點間的關係 links = [] # 查詢所有節點,並將節點信息取出存放在data數組中 for n in graph.nodes: # 將節點信息轉化為json格式,否則中文會不顯示 # print(n) nodesStr = json.dumps(graph.nodes[n], ensure_ascii=False) # 取出節點的name node_name = json.loads(nodesStr)['name']
# 構造字典,存儲單個節點信息
dict = {
# 'id':str(n), # 防止重複節點
'name': node_name,
'symbolSize': 50,
'category': '對象'
}
# 將單個節點信息存放在data數組中
data.append(dict)
# 查詢所有關係,並將所有的關係信息存放在links數組中
rps = graph.relationships
for r in rps:
# 取出開始節點的name
source = str(rps[r].start_node['name'])
# for i in data: #需要使用ID
# if source == i['name']:
# source = i['id']
# 取出結束節點的name
target = str(rps[r].end_node['name'])
# for i in data: #需要使用ID
# if target == i['name']:
# target = i['id']
# 取出開始節點的結束節點之間的關係
name = str(type(rps[r]).__name__)
# 構造字典存儲單個關係信息
dict = {
'source': source,
'target': target,
'name': name
}
# 將單個關係信息存放進links數組中
links.append(dict)
# 輸出所有節點信息
# for item in data:
# print(item)
# 輸出所有關係信息
# for item in links:
# print(item)
# 將所有的節點信息和關係信息存放在一個字典中
neo4j_data = {
'data': data,
'links': links
}
neo4j_data = json.dumps(neo4j_data)
return neo4j_data
def search_all_category(): data = []# 定義data數組,存放節點信息 links = []# 定義關係數組,存放節點間的關係 # 節點分類 node_DEPLOY = graph.run('MATCH (n:DEPLOY) RETURN n').data() node_CATE = graph.run('MATCH (n:CATE) RETURN n').data() node_EXPS = graph.run('MATCH (n:EXPS) RETURN n').data() node_LOCA = graph.run('MATCH (n:LOCA) RETURN n').data() node_MSYS = graph.run('MATCH (n:MSYS) RETURN n').data() node_PERF = graph.run('MATCH (n:PERF) RETURN n').data()
for n in node_DEPLOY:
nodesStr = json.dumps(n, ensure_ascii=False)# 將節點信息轉化為json格式,否則中文會不顯示
node_name = json.loads(nodesStr)
node_name = node_name['n']['name'] # 取出節點的name
# print(node_name)
dict = {
# 'id':str(n), # 防止重複節點
'name': node_name,
'symbolSize': 50,
'category': 'DEPLOY'
}
data.append(dict) # 將單個節點信息存放在data數組中
for n in node_CATE:
nodesStr = json.dumps(n, ensure_ascii=False)# 將節點信息轉化為json格式,否則中文會不顯示
node_name = json.loads(nodesStr)
node_name = node_name['n']['name'] # 取出節點的name
# print(node_name)
dict = {
# 'id':str(n), # 防止重複節點
'name': node_name,
'symbolSize': 50,
'category': 'CATE'
}
data.append(dict) # 將單個節點信息存放在data數組中
for n in node_EXPS:
nodesStr = json.dumps(n, ensure_ascii=False)# 將節點信息轉化為json格式,否則中文會不顯示
node_name = json.loads(nodesStr)
node_name = node_name['n']['name'] # 取出節點的name
# print(node_name)
dict = {
# 'id':str(n), # 防止重複節點
'name': node_name,
'symbolSize': 50,
'category': 'EXPS'
}
data.append(dict) # 將單個節點信息存放在data數組中
for n in node_LOCA:
nodesStr = json.dumps(n, ensure_ascii=False)# 將節點信息轉化為json格式,否則中文會不顯示
node_name = json.loads(nodesStr)
node_name = node_name['n']['name'] # 取出節點的name
# print(node_name)
dict = {
# 'id':str(n), # 防止重複節點
'name': node_name,
'symbolSize': 50,
'category': 'LOCA'
}
data.append(dict) # 將單個節點信息存放在data數組中
for n in node_MSYS:
nodesStr = json.dumps(n, ensure_ascii=False)# 將節點信息轉化為json格式,否則中文會不顯示
node_name = json.loads(nodesStr)
node_name = node_name['n']['name'] # 取出節點的name
# print(node_name)
dict = {
# 'id':str(n), # 防止重複節點
'name': node_name,
'symbolSize': 50,
'category': 'MSYS'
}
data.append(dict) # 將單個節點信息存放在data數組中
for n in node_PERF:
nodesStr = json.dumps(n, ensure_ascii=False)# 將節點信息轉化為json格式,否則中文會不顯示
node_name = json.loads(nodesStr)
node_name = node_name['n']['name'] # 取出節點的name
# print(node_name)
dict = {
# 'id':str(n), # 防止重複節點
'name': node_name,
'symbolSize': 50,
'category': 'PERF'
}
data.append(dict) # 將單個節點信息存放在data數組中
# 查詢所有關係,並將所有的關係信息存放在links數組中
rps = graph.relationships
for r in rps:
source = str(rps[r].start_node['name']) # 取出開始節點的name
target = str(rps[r].end_node['name'])
name = str(type(rps[r]).__name__)# 取出開始節點的結束節點之間的關係
# 構造字典存儲單個關係信息
dict = {
'source': source,
'target': target,
'name': name
}
links.append(dict)# 將單個關係信息存放進links數組中
neo4j_data = {
'data': data,
'links': links
}
neo4j_data = json.dumps(neo4j_data)
return neo4j_data
def search_one(value): # 定義data數組存儲節點信息 data = [] # 定義links數組存儲關係信息 links = [] # 查詢節點是否存在 node = graph.run('MATCH(n:person{name:"' + value + '"}) return n').data() # 如果節點存在len(node)的值為1不存在的話len(node)的值為0 if len(node): # 如果該節點存在將該節點存入data數組中 # 構造字典存放節點信息 dict = { 'name': value, 'symbolSize': 50, 'category': '對象' } data.append(dict) # 查詢與該節點有關的節點,無向,步長為1,並返回這些節點 nodes = graph.run('MATCH(n:person{name:"' + value + '"})<-->(m:person) return m').data() # 查詢該節點所涉及的所有relationship,無向,步長為1,並返回這些relationship reps = graph.run('MATCH(n:person{name:"' + value + '"})<-[rel]->(m:person) return rel').data() # 處理節點信息 for n in nodes: # 將節點信息的格式轉化為json node = json.dumps(n, ensure_ascii=False) node = json.loads(node) # 取出節點信息中person的name name = str(node['m']['name']) # 構造字典存放單個節點信息 dict = { 'name': name, 'symbolSize': 50, 'category': '對象' } # 將單個節點信息存儲進data數組中 data.append(dict) # 處理relationship for r in reps: source = str(r['rel'].start_node['name']) target = str(r['rel'].end_node['name']) name = str(type(r['rel']).name) dict = { 'source': source, 'target': target, 'name': name } links.append(dict) # 構造字典存儲data和links search_neo4j_data = { 'data': data, 'links': links } # 將dict轉化為json格式 search_neo4j_data = json.dumps(search_neo4j_data) return search_neo4j_data else: # print("查無此節點") return 0
def index(request): ctx = {} if request.method == 'POST': # 接收前端傳過來的查詢值 node_name = request.POST.get('node') # 查詢結果 search_neo4j_data = search_one(node_name) # 未查詢到該節點 if search_neo4j_data == 0: ctx = {'title': '數據庫中暫未添加該實體'} neo4j_data = search_all_category() return render(request, 'index.html', {'neo4j_data': neo4j_data, 'ctx': ctx}) # 查詢到了該節點 else: neo4j_data = search_all_category() return render(request, 'index.html', {'neo4j_data': neo4j_data, 'search_neo4j_data': search_neo4j_data, 'ctx': ctx})
neo4j_data = search_all_category()
return render(request, 'index.html', {'neo4j_data': neo4j_data, 'ctx': ctx})
if name == 'main':
neo4j_data = search_all_category()
print(neo4j_data)
```
3.2 Echarts前端
index.html
文件內容
```
{% load static %}
```
4 項目啟動
進入到主目錄文件夾下,運行manage.py
啟動項目,輸入命令行:
python manage.py runserver
manage.py
中的內容
```python
!/usr/bin/env python
"""Django's command-line utility for administrative tasks.""" import os import sys
def main(): """Run administrative tasks.""" os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'neo4jconnect_test.settings') try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc execute_from_command_line(sys.argv)
if name == 'main': main()
默認啟動地址和端口為:`http://127.0.0.1:8000/` 若希望指定ip地址和端口,可按格式:
python manage.py runserver --host 0.0.0.0 --port 9008
``
--host參數是ip地址,
--port`參數是端口號
啟動成功的命令輸出
瀏覽器中輸入地址和端口號,顯示效果
遇到問題
echarts問題
1.加載echarts.min.js
需要確定好路徑
2.初始化要定義好dom,即,div中的id
定義要和getElementById
方法中初始化名稱一致。否則會報錯誤 t is null
```
...
var dom = document.getElementById("chart-panel");
var myChart1 = echarts.init(dom);
```
3.導入data和link時,要確保數據中沒有重複字段,否則會造成節點和連線都不顯示
4.保證link中的target和source都是字符串類型
數據問題
1.導入neo4j中的數據,要避免重複字段。錯誤示例:實體名稱和類名稱一致
參考
[1] http://github.com/liuhuanyong/QASystemOnMedicalKG
[2] http://github.com/wangle1218/KBQA-for-Diagnosis
[3] http://github.com/Sjyzheishuai/Neo4j-visualization
[4] http://blog.csdn.net/Fimooo/article/details/103069928
[5] http://blog.csdn.net/weixin_44747173/article/details/124835406
[6] http://blog.csdn.net/zjw120/article/details/124194577
[7] http://github.com/zhangxiang0316/echartsDemo
[8] http://github.com/pyecharts/pyecharts-gallery
[9] http://github.com/ecomfe/awesome-echarts