文章目录
- 1.现成的工具
- 2.Neo4j JavaScript Driver
- 3.neovis
- 4.neo4jd3
- 4.1neo4jd3和neovis对比
- 4.2获取neo4jd3
- 4.3neo4jd3的数据结构
- 4.4Spring data neo
- 4.4.1 定义返回数据格式
- 4.4.1.1NeoResults
- 4.4.1.2GraphVO
- 4.4.1.3NodeVO
- 4.4.1.4ShipVO
- 4.4.2 SDN查询解析
- 4.4.2.1 Repo查询语句
- 4.4.2.2 解析Repo查询
- 4.4.2.3返回解析结果
- 4.4.3前端处理渲染
- 4.5实现效果
本文最终技术架构:neo4jd3 + Spring boot + Spring Data Neo + neo4j
当我们刚开是接触图数据库的时候,我们进行各种关系查询,最终会得到一个拓扑图。和我们以前使用的数据库不一样的是,我们的数据库查询出来是一系列的表。
事实上,我们的图数据返回的的数据是类似于下面这样的格式的,然后通过前端(Neo4j Browser )来帮我们将返回的数据绘制成网络拓扑图。在我们之前的文章中介绍的Spring Data Neo中,返回的也都是java对象的数据
接下来本文就是介绍使用一些前端技术来帮我们将图数据库的数据返回给前端进行旋绕
1.现成的工具
比如Neo4j Browser 、Neo4j Bloom、这些官方提供的工具,免费或者有商业版权,这些工具特点都是人家已经开发好的工具,你安装上使用就行了。
例如Neo4j Browser,这些工具就好比,我们Navicat 、Sql Log、PL SQL这些客户端连接工具连接关系型数据库(mysql、oracle、post gre)等。本文就不在详细介绍。
2.Neo4j JavaScript Driver
Neo4j JavaScript Driver 是一个用于在 JavaScript 应用程序中与 Neo4j 图数据库进行通信的官方驱动程序。它提供了与 Neo4j 服务器进行连接、执行 Cypher 查询和处理查询结果等功能。我们可以在Jquery、React、Angular、Vue等前端框架中使用该驱动。
驱动安装
npm install neo4j-driver
代码示例
const neo4j = require('neo4j-driver')
const driver = neo4j.driver(uri, neo4j.auth.basic(user, password))
const session = driver.session()
const personName = 'Alice'
try {
const result = await session.run(
'CREATE (a:Person {name: $name}) RETURN a',
{ name: personName }
)
const singleRecord = result.records[0]
const node = singleRecord.get(0)
console.log(node.properties.name)
} finally {
await session.close()
}
// on application exit:
await driver.close()
官方地址:neo4j-javascript
适用于:前端直接和Neo4J直接连接
3.neovis
Neovis.js 是一个纯 JavaScript 库,使用 JavaScript 语言编写和开发开源框架。它可以在浏览器环境中直接使用,也可以与其他 JavaScript 框架和库集成,如 React、Angular 或 Vue.js
- Neovis.js 使用 Neo4j JavaScript Driver 与 Neo4j 图数据库进行通信。
- Neovis.js 在 Vis.js 的基础上构建了对 Neo4j 数据库的特定集成和功能
github地址:github-neovis.js
下面这是一个官方的示例:
Neovis.js可以通过npm安装
npm install --save neovis.js
Neovis.js可以从Neo4jLabs CDN获得
<script src="https://unpkg.com/neovis.js@2.0.2"></script>
<script src="https://unpkg.com/neovis.js@2.0.2/dist/neovis-without-dependencies.js"></script>
代码示例
需要在代码中定义每个节点,边,例如下乳,查询用户和角色
<script type="text/javascript">
let neoViz;
function draw() {
const config = {
containerId: "viz",
neo4j: {
serverUrl: "bolt://localhost:7687",
//neo4j的用户名和密码
serverUser: "neo4j",
serverPassword: "neo4j",
},
labels: {
//节点的标签1(节点类型:用户)
User: {
//在User类型的节点上,使用userName作为节点的显示
label: "userName"
},
//节点的标签2(节点类型:角色)
Role: {
//在Role类型的节点上,使用roleName作为节点的显示
label: "roleName",
}
//节点的标签3.......
},
relationships: {
//关系1(边)
PLAY_THE_ROLE: {
value: "name"
}
},
//Cypher语句
initialCypher: "MATCH (n)-[r:PLAY_THE_ROLE]->(m) RETURN *"
};
neoViz = new NeoVis.default(config);
neoViz.render();
}
</script>
4.neo4jd3
neo4jd3使用D3.js实现Neo4j图形可视化。
github地址:githug-neo4jd3,表现效果如下:
4.1neo4jd3和neovis对比
neo4jd3和neovis是两个完全不同的组件,使用方式也不一样。
-
在底层依赖上:
neovis.js 是基于 Vis.js、neo4j JavaScript Driver 构建的,而 neo4jd3.js 基于 D3.js。 -
在功能上:
neovis.js 能够直接和neo4j 数据库相连,将数据库查询结果直接进行渲染,而neo4jd3则不和数据库相连,而是通过数据进行渲染。所以对于neo4jd3来说,只要能提供数据,就能渲染,因此我们可以使用任何技术为neo4jd3来进行获取数据,最后将数据给neo4jd3。 -
在渲染上:
neovis.js侧重于将数据库的查询语句发送给数据,然后渲染数据库返回的结果值,因此在渲染效果上存在很多的不友好一面。而neo4jd3并不关心查询语句如何编写,数据库如何查询,只对最后的数据进行渲染,因此在渲染效果上就体现的非常友好
以下是对同一个数据的查询结果进行的渲染对比,左图是neovis,右图neo4jd3,单从拓扑图上来说,左边的效果就很差
4.2获取neo4jd3
从仓库中下载代码,在dist目录下,有css和js
git clone https://github.com/eisman/neo4jd3.git
4.3neo4jd3的数据结构
我们先看官网给的两组Json,也就是需要我们的数据组织者按照如下格式进行数据格式组织
返回节点和关系的json
{
"nodes": [
{
"id": "1",
"labels": ["User"],
"properties": {
"userId": "eisman"
}
},
{
"id": "8",
"labels": ["Project"],
"properties": {
"name": "neo4jd3",
"title": "neo4jd3.js",
"description": "Neo4j graph visualization using D3.js.",
"url": "https://eisman.github.io/neo4jd3"
}
}
],
"relationships": [
{
"id": "7",
"type": "DEVELOPES",
"startNode": "1",
"endNode": "8",
"properties": {
"from": 1470002400000
},
"source": "1",
"target": "8",
"linknum": 1
}
]
}
返回绘制图的Json
{
"results": [
{
"columns": ["user", "entity"],
"data": [
{
"graph": {
"nodes": [
{
"id": "1",
"labels": ["User"],
"properties": {
"userId": "eisman"
}
},
{
"id": "8",
"labels": ["Project"],
"properties": {
"name": "neo4jd3",
"title": "neo4jd3.js",
"description": "Neo4j graph visualization using D3.js.",
"url": "https://eisman.github.io/neo4jd3"
}
}
],
"relationships": [
{
"id": "7",
"type": "DEVELOPES",
"startNode": "1",
"endNode": "8",
"properties": {
"from": 1470002400000
}
}
]
}
}
]
}
],
"errors": []
}
4.4Spring data neo
我们现在已经知道了neo4jd3绘制图的Json格式了,现在就需要我们后台查询数据,然后返回
4.4.1 定义返回数据格式
我们当然也能通过数据格式发现,嵌套有点深,这里推荐按照这个格式来,因为不这样的话,你就得要求修改前端组建的源代码了。下面这个是前端渲染数据的一部分代码,如果后端返回的数据不按照这个格式来的话,前端这里就需要你修改代码了。
这里我避免创建很多单一属性的类,因此采用了内部类的方式,这里你不一定才用内部类,只要能返回和上面的Json格式就行
4.4.1.1NeoResults
@lombok.Data
public class NeoResults {
private List<Data> results = new ArrayList<>();
public NeoResults() {
super();
results.add(new NeoResults.Data());
}
@lombok.Data
public class Data{
private List<Graph> data = new ArrayList<>();
public Data() {
super();
data.add(new Data.Graph());
}
@lombok.Data
public class Graph{
private GraphVO graph = new GraphVO();
}
}
public void setNodes(List<NodeVO> nodes) {
results.get(0).getData().get(0).getGraph().setNodes(nodes);
}
public void setRelationships(List<ShipVO> relationships) {
results.get(0).getData().get(0).getGraph().setRelationships(relationships);
}
}
4.4.1.2GraphVO
@Data
public class GraphVO {
private List<NodeVO> nodes = new ArrayList<>();
private List<ShipVO> relationships = new ArrayList<>();
}
4.4.1.3NodeVO
@Data
@AllArgsConstructor
@NoArgsConstructor
public class NodeVO{
private Long id;
private List<String> labels;
private Map<String, Object> properties;
}
4.4.1.4ShipVO
@Data
public class ShipVO {
private Long id;
private String type;
private Long startNode;
private Long endNode;
private Map<String, Object> properties;
}
4.4.2 SDN查询解析
4.4.2.1 Repo查询语句
public interface D3jsRepo extends Neo4jRepository<Object, Long> {
/**
* @description:查询路径:根据roadName查询Road标签查询路径
* @author:hutao
* @mail:hutao1@epri.sgcc.com.cn
* @date:2023年8月21日 下午2:11:09
*/
@Query("MATCH (n:Road{name:$roadName}) MATCH path=(n)-[*]->(n1) RETURN path")
List<Map<String, InternalPath.SelfContainedSegment[]>> findPathsByRoadName(@Param("roadName") String roadName);
/**
* @description:查询路径:根据标签label和某个属性字段查询路径,性能比较慢,谨慎使用
* @author:hutao
* @mail:hutao1@epri.sgcc.com.cn
* @date:2023年8月21日 下午3:22:02
*/
@Query("MATCH (n) WHERE $label IN labels(n) AND n[$property] = $value MATCH path=(n)-[*]->(n1) RETURN path")
List<Map<String, InternalPath.SelfContainedSegment[]>> findByLabelAndProperty(@Param("label") String label, @Param("property") String property, @Param("value") String value);
/**
* @description:查询路径:根据标主键ID查询路径
* @author:hutao
* @mail:hutao1@epri.sgcc.com.cn
* @date:2023年8月21日 下午3:42:52
*/
@Query("MATCH (n) WHERE id(n) = $id MATCH path=(n)-[*]->(n1) RETURN path")
List<Map<String, InternalPath.SelfContainedSegment[]>> findPathById(@Param("id") Long id);
}
4.4.2.2 解析Repo查询
@Service
public class D3jsServiceImpl implements D3jsService{
@Autowired
private D3jsRepo d3jsRepo;
/**
* @description:通过节点ID找路径,以该节点为起点
* @author:hutao
* @mail:hutao1@epri.sgcc.com.cn
* @date:2023年8月22日 上午11:17:13
*/
@Override
public NeoResults findPathsById(Long id) {
NeoResults neoResult = new NeoResults();
List<NodeVO> nodes = new ArrayList<>();
List<ShipVO> relationships = new ArrayList<>();
List<Map<String, InternalPath.SelfContainedSegment[]>> paths = d3jsRepo.findPathById(id);
for (Map<String, InternalPath.SelfContainedSegment[]> path : paths) {
SelfContainedSegment[] segments = path.get("path");
for (SelfContainedSegment segment : segments) {
addNode(nodes, segment.start());
addNode(nodes, segment.end());
addShip(relationships, segment.relationship());
}
}
neoResult.setNodes(nodes);
neoResult.setRelationships(relationships);
return neoResult;
}
/**
* @description:添加关系
* @author:hutao
* @mail:hutao1@epri.sgcc.com.cn
* @date:2023年8月16日 下午1:23:54
*/
private void addShip(List<ShipVO> relationships, Relationship shipTemp) {
ShipVO shipVO =new ShipVO();
shipVO.setId(shipTemp.id());
shipVO.setStartNode(shipTemp.startNodeId());
shipVO.setEndNode(shipTemp.endNodeId());
shipVO.setType(shipTemp.type());
shipVO.setProperties(shipTemp.asMap());
relationships.add(shipVO);
}
/**
* @description:添加节点
* @author:hutao
* @mail:hutao1@epri.sgcc.com.cn
* @date:2023年8月16日 下午2:27:37
*/
private void addNode(List<NodeVO> nodes, Node nodeTemp) {
NodeVO noveVO = new NodeVO();
List<String> labels = new ArrayList<>();
nodeTemp.labels().forEach(labels::add);
noveVO.setId(nodeTemp.id());
noveVO.setLabels(labels);
noveVO.setProperties(nodeTemp.asMap());
nodes.add(noveVO);
}
}
4.4.2.3返回解析结果
@GetMapping("/node/info/path/{id}")
@ApiOperationSupport(order = 3)
@ApiOperation(value = "3获取指定节点为起点的路径")
public NeoResults queryNodeTopo(@PathVariable Long id) {
NeoResults findPaths = d3jsService.findPathsById(id);
return findPaths;
}
4.4.3前端处理渲染
<link rel="stylesheet" href="/plugin/neod3/css/neo4jd3.min.css">
<script src="/plugin/neod3/js/d3.min.js"></script>
<script src="/plugin/neod3/js/neo4jd3.js"></script>
光路起点<select id = "selectRoad" class="selectpicker" onchange = "changeRoad()" data-live-search="true" data-style="btn-info" title="请选择起点光路" ></select>
<div id="neo4jd3"></div>
/**
* @description:选择光路触发加载光路的路径
* @author:hutao
* @mail:hutao1@epri.sgcc.com.cn
* @date:2023年8月17日 下午2:10:18
*/
function changeRoad(){
let select = $('#selectRoad').val();
let url = '/node/info/path/'+select;
let resultData = httpRequestForJson(url,"","GET");
loadNeod3Topo(resultData);
}
/**
* @description:初始化节点拓扑矢量图
* @author:hutao
* @mail:hutao1@epri.sgcc.com.cn
* @date:2023年8月17日 下午2:18:48
*/
var neo4jd3
function loadNeod3Topo(resultData){
neo4jd3 = new Neo4jd3('#neo4jd3', {
//showLabel源代码中不存在,是我自己添加的,实现效果为:节点是否显示节点标签
showLabel: true,
minCollision: 100,
//neo4jDataUrl: '/aaa/bbbb',
neo4jData: resultData,
nodeRadius: 25,
onNodeDoubleClick: function(node) {
console.log('double click on node: ' + JSON.stringify(node));
},
onRelationshipDoubleClick: function(relationship) {
console.log('double click on relationship: ' + JSON.stringify(relationship));
},
//自动缩放
zoomFit: true,
});
}