Python+Neo4j+Django+Echarts知識圖譜可視化

語言: CN / TW / HK
;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官網下載社區版

image.png 下載對應版本壓縮包,解壓即可。我使用的是Ubuntu系統,解壓完成後進入bin文件夾下 image.png 在當前界面啟動終端,輸入啟動命令: ./neo4j console 可以看到啟動成功後可以在 localhost:7474打開neo4j image.png

默認用户名和密碼是:neo4j 登錄後會提示你修改,如果想更換用户名和密碼可以在system用户下操作。這裏不再贅述。 image.png

2.2 使用py2neo導入數據

本文使用json格式對數據存儲,參考[1][2]

image.png

導入到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數據庫查看

image.png 包涵136個節點,150組關係。 輸入查詢語句 MATCH(n) RETURN n 輸出所有節點和關係

3 前後端控制

使用Django進行後端控制,Echarts進行前端顯示。參考[3]項目結構

image.png

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 %}

Title

```

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`參數是端口號

啟動成功的命令輸出

image.png

瀏覽器中輸入地址和端口號,顯示效果

image.png

遇到問題

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