05-Nebula Graph图数据可视化

分类:编程技术 时间:2024-02-20 17:46 浏览:0 评论:0
0

文章05-Nebula Graph 图数据 可视化,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

图数据库的可视化

Nebula本身自带的Studio

虽然很好用, 但是并不能直接嵌入到业务系统中, 也不能直接给客户用, 所以我找了好多也没有说直接能展示图关系的, 但是我看网上好多都说是基于D3.js就可以做, 但是我是一个后端呀, D3相对复杂, 但是需求刚在眼前还是要做的..

基于D3开发Nebula的关系可视化

前端

前端在网上找到了一个基于React+antd做的一个Demo, 为此我还特意去学习了React+Antd+D3

这个就可以用于做Nebula的可视化

于是我把这个代码从Git上拿了下来

看了一下, 发现大佬写的非常好

前端需要的数据结构

import React from 'react'import {Row, Col, Card} from 'antd'import D3SimpleForceChart from '../components/charts/D3SimpleForceChart'class SimpleForceChart extends React.Component {    render() {        const data = {            nodes:[                {                    "i": 0,                    "name": "test3",                    "description": "this is desc!",                    "id": "186415162885763072"                },                {                    "i": 1,                    "name": "test4",                    "description": "this is desc!",                    "id": "186415329756147712"                },                {                    "i": 2,                    "name": "test7",                    "description": "this is desc!",                    "id": "186420276928757760"                },                {                    "i": 3,                    "name": "test6",                    "description": "this is desc!",                    "id": "186417155309998080"                }            ],            edges:[                {                    "source": 0,                    "target": 1,                    "relation": "类-类",                    "id": "1",                    "value": 2                },                {                    "source": 1,                    "target": 2,                    "relation": "类-类",                    "id": "1",                    "value": 3                },                {                    "source": 1,                    "target": 3,                    "relation": "类-类",                    "id": "1",                    "value": 3                }            ]        }        return (            
) }}export default SimpleForceChart

D3渲染

import React from 'react'import PropTypes from 'prop-types'import * as d3 from 'd3'class D3SimpleForceChart extends React.Component {  componentDidMount() {    // 容器宽度    const containerWidth = this.chartRef.parentElement.offsetWidth    // 数据    const data = this.props.data    // 外边距    const margin = { top: 60, right: 60, bottom: 60, left: 60 }    // 计算宽度    const width = containerWidth - margin.left - margin.right    // 固定高度    const height = 700 - margin.top - margin.bottom    // this.chartRef 是个啥 看着像SVG标签    console.log("this.chartRef",this.chartRef)    console.log("data",this.props.data)    let chart = d3      .select(this.chartRef)      .attr('width', width + margin.left + margin.right)      .attr('height', height + margin.top + margin.bottom)    let g = chart      .append('g')      .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') // 设最外包层在总图上的相对位置    let simulation = d3      .forceSimulation() // 构建力导向图      .force('link',          d3.forceLink()          .id((d,i) => i)          .distance(d => d.value * 50)      )      .force('charge', d3.forceManyBody())      .force('center', d3.forceCenter(width / 2, height / 2))    let z = d3.scaleOrdinal(d3.schemeCategory20) // 通用线条的颜色    let link = g      .append('g') // 画连接线      .attr('class', 'links')      .selectAll('line')      .data(data.edges)      .enter()      .append('line')        // .on('click',function (d,i) {        //   console.log("click",d,i)        //   // 连接线条点击事件    //           调用接口请求属性数据, 但是感觉, 线的话, 太细了, 不容易点击, 考虑点击标题, 或者悬浮到线上        // })        // .on('mouseover',function (d, i) {        //   console.log("mouseover",d,i)        //    // 线条悬浮事件        //   //  被文字遮盖了一部份, 还是考虑点击文字        // })    // 画连接连上面的关系文字    let linkText = g      .append('g')      .attr('class', 'link-text')      .selectAll('text')      .data(data.edges)      .enter()      .append('text')      .text(d => d.relation)      .on('click',function (d,i) {        // 线上标题文本的点击事件        // 可以在这里做请求接口然后 获取属性展示        // 取d.id即可          console.log("clicktitle",d,i)        })        .style("fill-opacity",1)    let node = g      .append('g') // 画圆圈和文字      .attr('class', 'nodes')      .selectAll('g')      .data(data.nodes)      .enter()      .append('g')      // 这个是悬浮节点展示线路的标签 感觉听炫酷的      // .on('mouseover', function(d, i) {      //   //显示连接线上的文字      //   linkText.style('fill-opacity', function(edge) {      //     if (edge.source === d || edge.target === d) {      //       return 1      //     }      //   })      //   //连接线加粗      //   link      //     .style('stroke-width', function(edge) {      //       if (edge.source === d || edge.target === d) {      //         return '2px'      //       }      //     })      //     .style('stroke', function(edge) {      //       if (edge.source === d || edge.target === d) {      //         return '#000'      //       }      //     })      // })      // .on('mouseout', function(d, i) {      //   //隐去连接线上的文字      //   linkText.style('fill-opacity', function(edge) {      //     if (edge.source === d || edge.target === d) {      //       return 0      //     }      //   })      //   //连接线减粗      //   link      //     .style('stroke-width', function(edge) {      //       if (edge.source === d || edge.target === d) {      //         return '1px'      //       }      //     })      //     .style('stroke', function(edge) {      //       if (edge.source === d || edge.target === d) {      //         return '#ddd'      //       }      //     })      // })        .on('click', function (d,i){          console.log(d,i)          // d是数据 i 是索引          // 在这里可以做点击事件, 请求后端接口 返回属性数据, 然后渲染        })      .call(        d3.drag()        .on('start', dragstarted)        .on('drag', dragged)        .on('end', dragended)      )    node.append('circle')        .attr('r', 5)        .attr('fill', (d,i) => z(i))    node.append('text')      .attr('fill', (d,i) => z(i))      .attr('y', -20)      .attr('dy', '.71em')      .text(d => d.name)    // 初始化力导向图    simulation.nodes(data.nodes)              .on('tick', ticked)    simulation.force('link')              .links(data.edges)    chart.append('g') // 输出标题      .attr('class', 'bar--title')      .append('text')      .attr('fill', '#000')      .attr('font-size', '16px')      .attr('font-weight', '700')      .attr('text-anchor', 'middle')      .attr('x', containerWidth / 2)      .attr('y', 20)      .text('人物关系图')    function ticked() {      // 力导向图变化函数,让力学图不断更新      link        .attr('x1', function(d) {          return d.source.x        })        .attr('y1', function(d) {          return d.source.y        })        .attr('x2', function(d) {          return d.target.x        })        .attr('y2', function(d) {          return d.target.y        })      linkText        .attr('x', function(d) {          return (d.source.x + d.target.x) / 2        })        .attr('y', function(d) {          return (d.source.y + d.target.y) / 2        })      node.attr('transform', function(d) {        return 'translate(' + d.x + ',' + d.y + ')'      })    }    function dragstarted(d) {      if (!d3.event.active) {        simulation.alphaTarget(0.3).restart()      }      d.fx = d.x      d.fy = d.y    }    function dragged(d) {      d.fx = d3.event.x      d.fy = d3.event.y    }    function dragended(d) {      if (!d3.event.active) {        simulation.alphaTarget(0)      }      d.fx = null      d.fy = null    }  }  render() {    return (      
(this.chartRef = r)} />
) }}D3SimpleForceChart.propTypes = { data: PropTypes.shape({ nodes: PropTypes.arrayOf( PropTypes.shape({ name: PropTypes.string.isRequired // href:PropTypes.string.isRequired, }).isRequired ).isRequired, edges: PropTypes.arrayOf( PropTypes.shape({ source: PropTypes.number.isRequired, target: PropTypes.number.isRequired, relation: PropTypes.string.isRequired }).isRequired ).isRequired }).isRequired}export default D3SimpleForceChart

虽然代码看不懂, 但是并不影响我完成功能, 我在样式上面对原有的做了一些改变

后端

做数据结构转化, 转为D3需要的数据结构

虽然我前端不咋地, 但是后端我行呀

MATCH p=(v:test3)-[*2]->() where id(v) == '186344099868655616' return [n in nodes(p) | properties(n)] as node,[x in relationships(p) | properties(x)] as rela

这个是查询test3 id=186344099868655616 近2跳的数据, 我在语法上做了一些处理

本来是直接返回路径变量p的, 但是居然直接报错了

Nebula自身提供的Jar包解析不了, 自己的返回结果, 当时差点绝望了, 还不底层的调用全部都封装了起来...

最重只能在语法上进行处理, 通过两个函数和管道符循环,来完成, 但是会吧节点和关系拆开, 拆成两个列.., 不过也算是能返回结果了

然后在程序里面处理, 转为D3需要的数据结构

导入需要的模型类

package com.jd.knowledgeextractionplatform.nebulagraph.model;import lombok.Data;import java.util.List;@Datapublic class PathPar {    private List node;    private List rela;}
package com.jd.knowledgeextractionplatform.nebulagraph.model;import lombok.Data;import lombok.EqualsAndHashCode;@Datapublic class Node {    private Integer i;    private String name;    private String description;    private String id;    public boolean equals(Node node) {        return this.id.equals(node.id);    }}
package com.jd.knowledgeextractionplatform.nebulagraph.d3model;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data@AllArgsConstructor@NoArgsConstructorpublic class Edges {    private Integer source;    private Integer target;    private String relation;    private String id;    private Integer value;}
package com.jd.knowledgeextractionplatform.nebulagraph.d3model;import com.jd.knowledgeextractionplatform.nebulagraph.model.Node;import lombok.Data;import java.util.List;import java.util.Set;@Datapublic class D3Model {    private List nodes;    private List edges;}
package com.jd.knowledgeextractionplatform.service.impl;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.jd.knowledgeextractionplatform.common.CommonResult;import com.jd.knowledgeextractionplatform.mapper.ClassAndAttrMapper;import com.jd.knowledgeextractionplatform.nebulagraph.d3model.D3Model;import com.jd.knowledgeextractionplatform.nebulagraph.d3model.Edges;import com.jd.knowledgeextractionplatform.nebulagraph.d3model.SE;import com.jd.knowledgeextractionplatform.nebulagraph.model.Node;import com.jd.knowledgeextractionplatform.nebulagraph.model.PathPar;import com.jd.knowledgeextractionplatform.nebulagraph.model.Rela;import com.jd.knowledgeextractionplatform.nebulagraph.template.NebulaTemplate;import com.jd.knowledgeextractionplatform.pojo.ClassAndAttr;import com.jd.knowledgeextractionplatform.service.SearchService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.List;@Service@Slf4jpublic class SearchServiceImpl implements SearchService {    @Autowired    private NebulaTemplate nebulaTemplate;    @Autowired    private ClassAndAttrMapper classAndAttrMapper;    @Override    public CommonResult search(Long projectId, String name, Integer skip) {        LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();        lambdaQueryWrapper.eq(ClassAndAttr::getProjectId, projectId);        lambdaQueryWrapper.eq(ClassAndAttr::getName, name);        lambdaQueryWrapper.eq(ClassAndAttr::getType, 1);        lambdaQueryWrapper.eq(ClassAndAttr::getDeleted, 0);        ClassAndAttr classAndAttrs = classAndAttrMapper.selectOne(lambdaQueryWrapper);        String match = "MATCH p=(v:%s)-[*%s]->() where id(v) == '%s' return [n in nodes(p) | properties(n)] as node,[x in relationships(p) | properties(x)] as rela";        String matchSql = String.format(match, classAndAttrs.getCode(), skip, classAndAttrs.getId());        log.info("search sql : {}", matchSql);        JSONObject resultSet = nebulaTemplate.executeJson(matchSql);        String datas = resultSet.getString("data");        List pathPars = JSONArray.parseArray(datas, PathPar.class);        D3Model d3Model = pathParsConvertToD3Model(pathPars);        return CommonResult.success("查询成功", d3Model);    }    private D3Model pathParsConvertToD3Model(List pathPars) {        D3Model d3Model = new D3Model();        d3Model.setNodes(new ArrayList<>());        d3Model.setEdges(new ArrayList<>());        int i = -1;        for (PathPar pathPar : pathPars) {            List nodes = pathPar.getNode();            List relas = pathPar.getRela();            int jul = 2;            for (int i1 = 0; i1 < nodes.size() - 1; i1++) {                Node node = nodes.get(i1);                Node node2 = nodes.get(i1 + 1);                Node fir = null;                Node sed = null;                for (Node d3ModelNode : d3Model.getNodes()) {                    boolean equals = d3ModelNode.getId().equals(node.getId());                    if (equals) {fir = d3ModelNode;                    }                    boolean equals2 = d3ModelNode.getId().equals(node2.getId());                    if (equals2) {sed = d3ModelNode;break;                    }                }                if (null == fir) {                    i = i + 1;                    fir = new Node();                    BeanUtils.copyProperties(node, fir);                    fir.setI(i);                    d3Model.getNodes().add(fir);                }                if (null == sed) {                    i = i + 1;                    sed = new Node();                    BeanUtils.copyProperties(node2, sed);                    sed.setI(i);                    d3Model.getNodes().add(sed);                }                Rela rela = relas.get(i1);                List edges1 = d3Model.getEdges();                Edges edges = new Edges(fir.getI(), sed.getI(), rela.getName(), rela.getId(), jul);                boolean flag = true;                for (Edges edges2 : edges1) {                    if (edges2.getSource().equals(edges.getSource()) && edges2.getTarget().equals(edges.getTarget())) {flag = false;break;                    }                }                if (flag) {                    d3Model.getEdges().add(edges);                }                jul++;            }        }//        List collect = d3Model.getNodes().stream().sorted((x, y) -> {//            if (x.getI() < y.getI()) {//                return 1;//            } else if (x.getI() > y.getI()) {//                return -1;//            }//            return 0;//        }).collect(Collectors.toList());//        d3Model.setNodes(collect);        // 获取到所有的自环边        List nodes = d3Model.getNodes();        List edges = d3Model.getEdges();        List indexs = new ArrayList<>();        for (int i1 = 0; i1 < nodes.size(); i1++) {            Node node = nodes.get(i1);            String id = node.getId();            for (int i2 = i1+1; i2 <= nodes.size() - 1; i2++) {                Node node2 = nodes.get(i2);                String id2 = node2.getId();                if (id.equals(id2)) {                    // 存在重复, 自环数据                    SE se = new SE();                    se.setS(node.getI());                    se.setE(node2.getI());                    indexs.add(se);                }            }        }        // 解决图数据库存在自环边的问题 必须倒序遍历, 不然会造成数据越界问题        for (int i1 = indexs.size()-1; i1 >= 0 ; i1--) {            SE index = indexs.get(i1);            Integer s = index.getS();            Integer e = index.getE();            // 删除重复的节点            nodes.remove(e.intValue());            for (Edges edge : edges) {                Integer source = edge.getSource();                Integer target = edge.getTarget();                if(source.equals(e)){                    // 将e 设置为 s                    edge.setSource(s);                }                if(target.equals(e)){                    // 将e 设置为 s                    edge.setTarget(s);                }            }        }        // 处理后面的数据全部前移        for (int i1 = 0; i1 < nodes.size(); i1++) {            Node node = nodes.get(i1);            if(!node.getI().equals(i1)){                // 如果不一样                Integer i2 = node.getI();                // 设置为当前的I                node.setI(i1);                // 循环遍历边                for (Edges edge : edges) {                    Integer source = edge.getSource();                    Integer target = edge.getTarget();                    if(source.equals(i2)){// 将e 设置为 sedge.setSource(i1);                    }                    if(target.equals(i2)){// 将e 设置为 sedge.setTarget(i1);                    }                }            }        }        // 获取到所有的重复点位        return d3Model;    }}

给大家看一个 我执行返回的结果

{    "code": 200,    "msg": "查询成功",    "data": {        "nodes": [            {                "i": 0,                "name": "test3",                "description": "this is desc!",                "id": "186415162885763072"            },            {                "i": 1,                "name": "test4",                "description": "this is desc!",                "id": "186415329756147712"            },            {                "i": 2,                "name": "test7",                "description": "this is desc!",                "id": "186420276928757760"            },            {                "i": 3,                "name": "test6",                "description": "this is desc!",                "id": "186417155309998080"            }        ],        "edges": [            {                "source": 0,                "target": 1,                "relation": "类-类",                "id": "1",                "value": 2            },            {                "source": 1,                "target": 2,                "relation": "类-类",                "id": "1",                "value": 3            },            {                "source": 1,                "target": 3,                "relation": "类-类",                "id": "1",                "value": 3            }        ]    }}

解决了自环和双向的问题

这就是上面前端需要的数据结构

把这个数据直接放入前端的静态数据里面就能展示了

到此, 基于D3的图可视化完成, 当然了, 样式不是很好看, 前端大佬自行美化吧~



1. 本站所有资源来源于用户上传或网络,仅作为参考研究使用,如有侵权请邮件联系站长!
2. 本站积分货币获取途径以及用途的解读,想在本站混的好,请务必认真阅读!
3. 本站强烈打击盗版/破解等有损他人权益和违法作为,请各位会员支持正版!
4. 编程技术 > 05-Nebula Graph图数据可视化

用户评论