暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Neo4j+D3展现的应用实例

DataManager 2018-07-11
433

本文主要讲解图数据库在真实项目中的实践应用,取自于笔者参与的实际项目代码。

后端用的是图数据库neo4j来存节点和关系,前端用的是D3来画图。前后端交互是通过json数据来完成的,即neo4j查出的结果组装成json后,传递给D3来画图。

Neo4j

首先看一下pom文件相关代码,如下:


如图所示,笔者用的是neo4j-java-driver连接的neo4j,当然也可以选择其他方式。例如Spring-Data-Neo4j等连接方式(只不过笔者所在项目中的spring版本和Spring-Data-Neo4j版本冲突,所以不能使用)。这里需要注意的是在spring中已经有neo4j的相关支持了。本来程序一直连接不上,项目启动报错。后来发现是因为neo4j-harness包中的neo4j-server、junit,和spring中相关包有冲突。所以用exclusion去掉依赖即可。

然后是加载驱动的代码如下:

java连接neo4j的方式有三种:bolt、http和https,笔者选用的是bolt协议。同时为了方便,将neo4j的url、用户名和密码做成properties文件来读取。

最后就是具体的调用代码了。如下所示:

@Override

    public Map<Set<String>, Set<String>> searchAllPathsOfTwoNodes(String firstLabel, String firstName,

            String secondLabel, String secondName) {

        Map<Set<String>, Set<String>> searchResults = null;

        Driver driver = driverManager.getDriver();

        try (Session session = driver.session()) {

            searchResults = session.readTransaction(tx -> {

                return standardGraphDao.searchAllPathsOfTwoNodes(tx, firstLabel, firstName, secondLabel, secondName);

            });

        } catch (Exception e) {

            logger.info(e.getMessage());

        }

        return searchResults;

    }

   

用内部类和lambda表达式的方式调用dao层的代码。正如在之前文章中所提,应避免写出循环开闭事务的代码,应将循环放进dao层里。dao层相关代码如下:


/**

     * 

    * <p>Title: searchAllPathsOfTwoNodes </p>

    * <p>Description: 查找某两个节点是否有关系(全部路径) </p>

    * @param tx

    * @param firstLabel

    * @param firstName

    * @param secondLabel

    * @param secondName

    * @return    参数说明

    * @author houyishuang

    * @date 2018年1月21日

     */

    public Map<Set<String>, Set<String>> searchAllPathsOfTwoNodes(Transaction tx, String firstLabel, String firstName, String secondLabel, String secondName) {

        Map<Set<String>, Set<String>> returnMap = new HashMap<>(16);

        String cypher = "MATCH (a:" + firstLabel + "{name:$firstName}), (b:" + secondLabel + "{name:$secondName}), p = allShortestPaths((a)-[*]-(b)) RETURN p";

        List<Record> records = tx.run(cypher, parameters("firstName", firstName, "secondName", secondName)).list();

        if (!records.isEmpty()) {

            for (Record searchResult : records) {

                Map<Set<String>, Set<String>> map = StandardGraphUtils.getIdsFromPath(searchResult.values().get(0).toString());

                returnMap = StandardGraphUtils.pathMapMerge(returnMap, map);

            }

        }

        return returnMap;

    }


该方法实现的功能是查询两个节点之间的全部路径。可以看到,直接用传过来的参数拼装成cypher语句,到neo4j中查询出结果,然后组装成想要的格式返回即可。

D3

之所以决定要用D3而不是ECharts,主要是觉得D3的灵活性更大一些,可以做一些定制化的需求。而ECharts的功能都已经给你提供了,想要定制化比较困难一些。当然,D3相对于来说更难上手,所以这里先普及一些D3的基本概念。

D3是一个JavaScript的函数库,是用来做数据可视化的。D3的全称是Data-Driven Document,数据驱动的文档。D3的核心是数据和元素之间的绑定,这点需要读者自行进行理解。下面讲解一个核心概念:update、enter和exit

既然D3做的是数据和元素之间的绑定,那如果数组长度和元素数量不一致,就会带来三个选择集:update、enter和exit,如下图所示:

由图可知,没有被元素绑定的多余数据叫做enter;没有数据对应、多余的元素叫做exit;元素和数据一一对应的部分叫做update。

enter代表没有足够的元素,因此处理方法是添加元素;如果存在多余的元素,没有数据与之对应,那么就需要删除元素。所以可以看到,在D3中,数据是最为重要的。可以删元素,但是不能删除数据。

但是如果不知道数组的长度,如何为update、enter、exit提供处理方案呢?其实,数组长度和元素数量的大小并不重要。在多数可视化中,无论哪一边大,

<1>update所代表的元素都该“更新”。

<2>enter所代表的元素都该“添加”。

<3>exit所代表的元素都该“删除”。

因此,这种数据绑定(Data-Join)允许开发者在不知道新数据长度的情况下更新图形。将这种类似的处理方案总结为一个模板,代码如下:


需要注意的是exit.remove方法。D3中将数据和元素进行绑定,默认采用的是从上到下的顺序。也就是说来一个数据,就和一个元素绑定。这在没有exit.remove出现的场景中是没有问题的。但当有删除元素的情况出现的话,默认的绑定规则可能会出错。比如说原来是a对1、b对2、c对3,我删除了数据2,理论上应该变成a对1、c对3、b没有数据对应,所以是exit。但是,如前所说,默认绑定规则是从上到下,所以实际上变成了a对1、b对3、c没有数据与之对应。结果c变成了exit,导致了错误。解决办法就是不采用默认的绑定规则,改用按照某种规律进行绑定,如下:

如上所示nodes是按照inst_cd进行绑定,这样再删除数据的时候,就不会出现删除元素错误的情况出现了。

明白了update、enter和exit这三个概念,再来理解D3就容易多了。本例用的是D3的力导向图(Force-Directed Graph)。力导向图是绘图的一种算法,在二维或三维空间里配置节点,节点之间用线连接,称为连线。各连线的长度几乎相等,且尽可能不相交。节点和连线都被施加了力的作用,力是根据节点和连线的相对位置计算的。根据力的作用,来计算节点和连线的运动轨迹,并不断降低它们能量,最终达到一种能量很低的安定状态。

  1. function showInfo(metaID,analyseType){

  2.     var height = 1500;

  3. var width = 1500;

  4. nodes_data =[];

  5. edges_data =[];

  6. edgeWidth = 2;

  7. r1 = 40;

  8. r2 = 20;

  9. color = d3.scale.category20();

  10. $.ajax({

  11. type:"post",

  12. url:__contextPath+"/standard/standardGraph/initGraph.d",

  13. async:true,

  14. cache:false,

  15. success:function(result){

  16. if(result==null || result==""){

  17. return "";

  18. }else{

  19. arr=eval('('+result+')');

  20. }

  21. nodes_data=arr.nodes;

  22. edges_data=arr.links;

  23. edges_data.forEach(function (link) {

  24. nodes_data.forEach(function(node){

  25. if(link.source==node.inst_cd){

  26. link.source=node;

  27. }

  28. if(link.target==node.inst_cd){

  29. link.target=node;

  30. }

  31. })

  32. });

  33. svg = d3.select("#standardgraph").append("svg")

  34. .attr("width", width)

  35. .attr("height", height);

  36. force = d3.layout.force()

  37. .nodes(nodes_data)

  38. .links(edges_data)

  39. .size([width, height])

  40. .linkDistance(function(d){

  41.             if(d.target.model =="CommonCodeRoot" || d.target.model =="InfoSubject"){

  42. return 300;

  43. }else{

  44. return 100;

  45. }

  46. })

  47. // .friction(0.8)

  48. .charge(-1000)

  49. .on("start",forceStart)

  50. .on("tick", tick)

  51. .start();

  52. // 箭头

  53. drawMarker();

  54. drawLinks();

  55. drawNodes();

  56. // 标签

  57. drawNodes_lables();

  58. drawLinks_text();

  59.     function zoomed(){

  60.     // svg.attr("transform","translate("+d3.event.translate+")scale("+d3.event.scale+")")

  61.     svg.attr("transform","translate("+d3.event.translate+")")

  62.     } // d3.event.translate 是平移的坐标值,d3.event.scale 是缩放的值

  63. var zoom = d3.behavior.zoom()

  64. .scaleExtent([-10,10])// 用于设置最小和最大的缩放比例

  65. .on("zoom",zoomed);

  66. // svg.call(zoom);

  67. }

  68. })

  69. }


上图所示是笔者在实际项目中参与完成的、用D3力导向图画出图形的部分代码。后台向前台传进json数据,前台拿到json数据进行处理画图(代码不做讲解,感兴趣的读者可以自行查看D3力导向图的相关知识)。

展示

      最后的成果如下:









文章转载自DataManager,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论