Bootstrap

relation-graph实现企业股权穿透图,关系图谱等(类似天眼查股权穿透图)

relation-graph实现企业股权穿透图,关系图谱等(类似天眼查股权穿透图)

一、业务需求

公司想要一个类似天眼查的股权穿透图,如这种:
在这里插入图片描述
初始化时候,如上图只展示上下两层,再深层的通过手动点击展开按钮查看。如果子集过多,只展示持股比例大于5%的,并且不超过5个。

二、实现步骤

1.查阅资料,确定使用插件

relation-graph参考文档如下:
http://relation-graph.com/#/docs/start
确定使用的demo如下:
http://relation-graph.com/#/demo/scene-org
在这里插入图片描述

2.前台实现(html、vue)

官网示例给的是vue3的,我们是用的vue

<!DOCTYPE html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=3.0,user-scalable=yes">
    <title>relation-graph 模版html</title>
     <script type="text/javascript" src="../../jr/common/jquery-1.11.1.min.js"></script>
	<script src="../../webpage/js/vue_graph.js"></script>
    <script src="../../webpage/js/relation-graph.min.js"></script>
</head>
<body>
    <div id="app">
        <div style="height:calc(100vh - 50px);">
            <Relation-Graph ref="seeksRelationGraph" :options="graphOptions" :on-node-click="onNodeClick" :on-line-click="onLineClick" :on-node-expand="onNodeExpand"/>
        </div>
    </div>
    <script>
 const RelationGraph = window['relation-graph']
 var tyxydm = “91370500164881385H”;//传过来一个参数
        var app = new Vue({
            el:'#app',
            components: { RelationGraph },
            data: {
				g_loading: true,
				demoname: '---',
                graphOptions: {
                	"allowShowMiniToolBar": false,//不显示工具栏
				'layouts': [
				  {
					'label': '中心',
					'layoutName': 'tree',//布局方式(tree树状布局/center中心布局/force自动布局)
					'layoutClassName': 'seeks-layout-center',
					'defaultJunctionPoint': 'border',//默认的连线与节点接触的方式
					'defaultNodeShape': 0,//默认的节点形状,0:圆形;1:矩形
					'defaultLineShape': 1,//默认的线条样式(1:直线/2:样式2/3:样式3/4:折线/5:样式5/6:样式6)
			         'allowShowMiniToolBar': false, // 是否显示工具栏
			         allowShowDownloadButton:false,
					 'centerOffset_x': 100,
					 'centerOffset_y': 150,
					// 通过这4个属性来调整 tree-层级距离&节点距离
					'max_per_width': '200',//节点距离限制:节点之间横向距离最大值
					'min_per_height': undefined,//节点距离限制:节点之间纵向距离最小值
					'max_per_height': '150',//节点距离限制:节点之间纵向距离最大值
					'min_per_width': undefined,//节点距离限制:节点之间横向距离最小值
					'levelDistance': '' // 如果此选项有值,则优先级高于上面那4个选项
				  }
				],
				    defaultNodeShape: 1,    //默认的节点形状,0:圆形;1:矩形
			        defaultExpandHolderPosition: "bottom",  //节点展开关闭的按钮位置
			        defaultLineShape: 4,   //默认的线条样式(1:直线/2:样式2/3:样式3/4:折线/5:样式5/6:样式6)
			        defaultJunctionPoint: "tb", //默认的连线与节点接触的方式(border:边缘/ltrb:上下左右/tb:上下/lr:左右)当布局为树状布局时应使用tb或者lr,这样才会好看
			        defaultNodeBorderWidth: 0.2, //节点边框粗细
			        defaultLineColor: "rgba(0, 186, 189, 1)",    //默认的线条颜色
			        defaultNodeColor: "rgba(0, 206, 209, 1)",    //默认的节点背景颜色
			        defaultNodeWidth: "100", //节点宽度
			        defaultNodeHeight: "50", //节点高度
			        defaultFocusRootNode: false,   //默认为根节点添加一个被选中的样式
			        moveToCenterWhenResize: false,   //当图谱的大小发生变化时,是否重新让图谱的内容看起来居中
			  }
            },
			mounted() {
            	this.getData();//获取企业关联关系
            },
			 methods: {
				 getData:function(){
					 $.ajax({
							url : $$ContextPath.webContext +"jr/common/ytjrSjzxGdxx/getData?tyxydm="+tyxydm,
							type : "post", 
							dataType : "json",
							success : function(result){
								var jsonData = result.parameters.list;//后台返回的数据
								app.setGraphData(jsonData);//给页面填充数据
							 }
						})
				 },
                setGraphData:function(jsonData) {
					var jsonList = jsonData;
						console.log("jsonListnodes===="+jsonList);
						jsonList.nodes.forEach(thisNode => {
						if (thisNode.id === tyxydm) {
							  thisNode.width = 200;
							  thisNode.height = 60;
							  thisNode.offset_x = -20; // 调整x偏移,让根节点看起来更居中
							 // thisNode.offset_y = 40; // 调整x偏移,让根节点看起来更居中
							}
						  });   
						  setTimeout(() => {
							this.g_loading = false;
							this.$refs.seeksRelationGraph.setJsonData(jsonList, (graphInstance) => {
							  // 这些写上当图谱初始化完成后需要执行的代码
							});
						  }, 1000);
				},
				 onNodeExpand(node, e) {//点击展开按钮触发
					  const _new_json_data = {
						nodes: [],//!!!!!,不这样写的话,子集会跑到0,0坐标上
						lines: []//!!!!!,不这样写的话,子集会跑到0,0坐标上
					  };
					  this.$refs.seeksRelationGraph.appendJsonData(_new_json_data, (graphInstance) => {
						// 拼接展开的子集
					  });
					return;
				},
				onNodeClick(nodeObject, $event) {
                    console.log('onNodeClick:', nodeObject)
                },
                onLineClick(lineObject, $event) {
                    console.log('onLineClick:', lineObject)
                },
            }
        })

</script>
</body>
</html>

2.后台实现(java)

数据格式:
const __graph_json_data = {
rootId: ‘a’,//中心结点Id
nodes: [
{ id: ‘a’, text: ‘A’, borderColor: ‘yellow’ },//注意id不可以重复
{ id: ‘b’, text: ‘B’, color: ‘#43a2f1’, fontColor: ‘yellow’ },
{ id: ‘c’, text: ‘C’, nodeShape: 1, width: 80, height: 60 },
{ id: ‘e’, text: ‘E’, nodeShape: 0, width: 150, height: 150 }
],
lines: [
{ from: ‘a’, to: ‘b’, text: ‘关系1’, color: ‘#43a2f1’ },//线条指向
{ from: ‘a’, to: ‘c’, text: ‘关系2’ },
{ from: ‘a’, to: ‘e’, text: ‘关系3’ },
{ from: ‘b’, to: ‘e’, color: ‘#67C23A’ }
]
}
思路:关系很清晰,确定一个中心区域结点的Id,nodes的集合(所有的父子结点),lines的集合(所有指向关系)。一次性获取所有的企业关联关系,未展开的也获取,用isShow隐藏。

实体类

import com.fasterxml.jackson.annotation.JsonProperty;
import com.haiyisoft.cloud.jpa.entity.EntityBean;
public class YtjrGdjgNodesEntity extends EntityBean{
	private String id ;
    private String text ;
    private String color;
    private String expandHolderPosition;
    private boolean expanded;
    private boolean isShow;
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getText() {
		return text;
	}
	public void setText(String text) {
		this.text = text;
	}
	public String getColor() {
		return color;
	}
	public void setColor(String color) {
		this.color = color;
	}
	public String getExpandHolderPosition() {
		return expandHolderPosition;
	}
	public void setExpandHolderPosition(String expandHolderPosition) {
		this.expandHolderPosition = expandHolderPosition;
	}
	public boolean isExpanded() {
		//expanded = true;
		return expanded;
	}
	public void setExpanded(boolean expanded) {
		this.expanded = expanded;
	}
	@JsonProperty(value = "isShow")
	public boolean getIsShow() {
		return isShow;
	}
	public void setIsShow(boolean isShow) {
		this.isShow = isShow;
	}
}

实体类

package com.haiyisoft.entity.zx.qy;
import com.haiyisoft.cloud.jpa.entity.EntityBean;
public class YtjrGdjgLinesEntity extends EntityBean{
	private String from ;
    private String to ;
    private String text;
	public String getFrom() {
		return from;
	}
	public void setFrom(String from) {
		this.from = from;
	}
	public String getTo() {
		return to;
	}
	public void setTo(String to) {
		this.to = to;
	}
	public String getText() {
		return text;
	}
	public void setText(String text) {
		this.text = text;
	}
}

Controller

package com.haiyisoft.jr.common.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.fastjson.JSON;
import com.haiyisoft.cloud.core.model.QueryParam;
import com.haiyisoft.cloud.core.model.QueryParamList;
import com.haiyisoft.cloud.jpa.util.JPAUtil;
import com.haiyisoft.cloud.web.ui.spring.model.DataCenter;
import com.haiyisoft.entity.zx.qy.YtjrGdjgLinesEntity;
import com.haiyisoft.entity.zx.qy.YtjrGdjgNodesEntity;
import com.haiyisoft.entity.zx.qy.YtjrGdmcxxResult;
import com.haiyisoft.entity.zx.qy.ZxQyjbxxb;
import com.haiyisoft.jt.util.JtBaseController;
import com.haiyisoft.zx.qy.service.ZxQyjbxxbService;

@Controller
@RequestMapping("/jr/common/ytjrSjzxGdxx")
public class YtjrSjzxGdxxController extends JtBaseController {

	private static final long serialVersionUID = 1L;
	@Autowired
	private ZxQyjbxxbService zxQyjbxxbService;
	Map<String, Object> map = new HashMap<String,Object>();
	List<YtjrGdjgNodesEntity> nodesList = new ArrayList<YtjrGdjgNodesEntity>();
	List<YtjrGdjgLinesEntity> linesList = new ArrayList<YtjrGdjgLinesEntity>();
	/**
	 *通过统一信用代码,查询结构关系,生成json
	 */
	@RequestMapping("/getData")
	@ResponseBody
	public DataCenter getData(String tyxydm) {
		DataCenter responseData = new DataCenter();
		QueryParamList param=new QueryParamList();
		nodesList.removeAll(nodesList);
		linesList.removeAll(linesList);
		//1.根据统一信用代码查出主体企业名称,这里用的是hnzx_sjzx_qyxxdjb,后续如有改变可修改
		ZxQyjbxxb jbxx = null;
		param.addParam("tyxydm", tyxydm);
		List<ZxQyjbxxb> zxQyjbxxbs = zxQyjbxxbService.retrieveNone(param, null, null, null, null);
		if(null!=zxQyjbxxbs&&zxQyjbxxbs.size()>0) {
			jbxx = zxQyjbxxbs.get(0);
		}else {
			return null;
		}
		//先把主体放到nodesList中,作为中心区域
		YtjrGdjgNodesEntity nodeEntity = new YtjrGdjgNodesEntity();
		nodeEntity.setId(tyxydm);
		nodeEntity.setText(jbxx.getQymc());
		nodeEntity.setColor("#2E4E8F");
		nodeEntity.setExpanded(true);
		nodeEntity.setIsShow(true);
		nodesList.add(nodeEntity);
		
		List<YtjrGdmcxxResult> treeNodes = new ArrayList<YtjrGdmcxxResult>();
		//2.查该企业的上级根节点
		List<YtjrGdmcxxResult> topRootNodes = getTopRootNodes(tyxydm);
		for (YtjrGdmcxxResult topRootNode : topRootNodes) {
            buildTopChildNodes(topRootNode);
            treeNodes.add(topRootNode);
        }
		
		//3.查该企业的下级根节点
		List<YtjrGdmcxxResult> bomRootNodes = getBomRootNodes(tyxydm);
		for (YtjrGdmcxxResult bomRootNode : bomRootNodes) {
            buildBomChildNodes(bomRootNode);
            treeNodes.add(bomRootNode);
        }
		//nodesMap.put("nodes", nodesList);
		//linesMap.put("lines", linesList);
		
		//System.out.println(JSON.toJSONString(treeNodes));
		//System.out.println(nodesList);
		 map.put("rootId", tyxydm);//主体(中心)
		 map.put("nodes", nodesList);
		 map.put("lines", linesList);
		 String datasjsonArray = JSON.toJSONString(map);
	     System.out.println(datasjsonArray);
	        
		responseData.setParameter("list", map);
		return responseData;
	}
	
	 /**
     * 获取上层集合中所有的根节点
     * @return
     */
	@RequestMapping("/getTopRootNodes")
	@ResponseBody
    public List<YtjrGdmcxxResult> getTopRootNodes(String tyxydm) {
        List<YtjrGdmcxxResult> rootNodes = new ArrayList<YtjrGdmcxxResult>();
        QueryParamList params = new QueryParamList();
        params.addParam("uniscid", tyxydm);
		List<YtjrGdmcxxResult> list = JPAUtil.load(YtjrGdmcxxResult.class, params,null, null, null, null);
		if(list!=null&&list.size()>5) {//如果查询结果多余5个,就过滤掉持股比例低于百分之5的
			params.addParam("conprop", "5", QueryParam.RELATION_GE);
			List<YtjrGdmcxxResult> listTemp = JPAUtil.load(YtjrGdmcxxResult.class, params,null, null, null, null);
			if(listTemp==null||listTemp.size()==0) {
				list = list.subList(0, 5);
			}else {
				if(listTemp!=null&&listTemp.size()>5) {
					list = listTemp.subList(0, 5);
				}else {
					list = listTemp;
				}
			}
		}
        for (YtjrGdmcxxResult n : list){
                rootNodes.add(n);//把所有的根节点放入rootNodes集合中
                YtjrGdjgNodesEntity nodeEntity = new YtjrGdjgNodesEntity();
                nodeEntity.setId(String.valueOf(n.getId()));
        		nodeEntity.setText(n.getInv());
        		nodeEntity.setColor("#4ea2f0");
        		nodeEntity.setIsShow(true);
        		nodesList.add(nodeEntity);
        		YtjrGdjgLinesEntity linesEntity = new YtjrGdjgLinesEntity();
        		linesEntity.setFrom(String.valueOf(n.getId()));
        		linesEntity.setTo(tyxydm);//指向中心区域的ID,我中心区域设置的ID是tyxydm
        		linesEntity.setText("");
        		linesList.add(linesEntity);
        }
        return rootNodes;
    }
	/**
     * 递归子节点
     * @param node
     */
	@RequestMapping("/buildTopChildNodes")
	@ResponseBody
    public void buildTopChildNodes(YtjrGdmcxxResult node) {
        List<YtjrGdmcxxResult> children = getChildTopNodes(node);
        if (!children.isEmpty()) {
        	//如有有子集,需要将父级设置成有按钮
        	List<YtjrGdjgNodesEntity> removeList = new ArrayList<YtjrGdjgNodesEntity>();
        	 for (YtjrGdjgNodesEntity result : nodesList){
                 if (result.getId().equals(node.getId().toString())){
                     removeList.add(result);
                 }
             }
        	nodesList.removeAll(removeList);
        	YtjrGdjgNodesEntity nodeEntity = new YtjrGdjgNodesEntity();
            nodeEntity.setId(String.valueOf(node.getId()));
     		nodeEntity.setText(node.getInv());
     		nodeEntity.setColor("#4ea2f0");
     		nodeEntity.setExpandHolderPosition("top");
     		nodeEntity.setExpanded(false);
     		nodeEntity.setIsShow(true);
     		nodesList.add(nodeEntity);
            for(YtjrGdmcxxResult child : children) {
            	getChildTopNodes(child);
            }
            node.setChildren(children);
        }
    }
	/**
     * 获取父节点下所有的子节点
     * @param pnode
     * @return
     */
	@RequestMapping("/getChildTopNodes")
	@ResponseBody
    public List<YtjrGdmcxxResult> getChildTopNodes(YtjrGdmcxxResult pnode) {//传入父节点对象,如果为该父节点的子节点,则放入子节点集合中
        List<YtjrGdmcxxResult> childNodes = new ArrayList<YtjrGdmcxxResult>();
        QueryParamList params = new QueryParamList();
		params.addParam("uniscid", pnode.getBlicno());//这里的参数不一样
		List<YtjrGdmcxxResult> list = JPAUtil.load(YtjrGdmcxxResult.class, params,null, null, null, null);
        for (YtjrGdmcxxResult n : list){
                childNodes.add(n);
                YtjrGdjgNodesEntity nodeEntity = new YtjrGdjgNodesEntity();
                nodeEntity.setId(String.valueOf(n.getId()));
        		nodeEntity.setText(n.getInv());
        		nodeEntity.setColor("#4ea2f0");
        		nodeEntity.setIsShow(false);
        		nodesList.add(nodeEntity);
        		//去掉多余的线
            	List<YtjrGdjgLinesEntity> removeList = new ArrayList<YtjrGdjgLinesEntity>();
            	 for (YtjrGdjgLinesEntity result : linesList){
                     if (result.getTo().equals(String.valueOf(n.getId()))&&result.getFrom().equals(String.valueOf(pnode.getId()))){
                         removeList.add(result);
                     }
                 }
            	linesList.removeAll(removeList);
        		YtjrGdjgLinesEntity linesEntity = new YtjrGdjgLinesEntity();
        		linesEntity.setFrom(String.valueOf(n.getId()));
        		linesEntity.setTo(String.valueOf(pnode.getId()));
        		linesEntity.setText("");
        		//linesEntity.setIsReverse(true);//顶部的子集用反箭头
        		linesList.add(linesEntity);
        }
        return childNodes;
    }
	
	/*********************************************************************************************************************************/
	
	/**
     * 获取下层集合中所有的根节点
     * @return
     */
	@RequestMapping("/getBomRootNodes")
	@ResponseBody
    public List<YtjrGdmcxxResult> getBomRootNodes(String tyxydm) {
        List<YtjrGdmcxxResult> rootNodes = new ArrayList<YtjrGdmcxxResult>();
        QueryParamList params = new QueryParamList();
        params.addParam("blicno", tyxydm);
		List<YtjrGdmcxxResult> list = JPAUtil.load(YtjrGdmcxxResult.class, params,null, null, null, null);
		if(list!=null&&list.size()>5) {//如果查询结果多余5个,就过滤掉持股比例低于百分之5的
			params.addParam("conprop", "5", QueryParam.RELATION_GE);
			List<YtjrGdmcxxResult> listTemp = JPAUtil.load(YtjrGdmcxxResult.class, params,null, null, null, null);
			if(listTemp==null||listTemp.size()==0) {
				list = list.subList(0, 5);
			}else {
				if(listTemp!=null&&listTemp.size()>5) {
					list = listTemp.subList(0, 5);
				}else {
					list = listTemp;
				}
			}
		}
        for (YtjrGdmcxxResult n : list){
                rootNodes.add(n);//把所有的根节点放入rootNodes集合中
                YtjrGdjgNodesEntity nodeEntity = new YtjrGdjgNodesEntity();
                nodeEntity.setId(String.valueOf(n.getId()));
        		nodeEntity.setText(n.getEntname());
        		nodeEntity.setColor("#4ea2f0");
        		nodeEntity.setIsShow(true);
        		nodesList.add(nodeEntity);
        		
        		YtjrGdjgLinesEntity linesEntity = new YtjrGdjgLinesEntity();
        		linesEntity.setFrom(tyxydm);
        		linesEntity.setTo(String.valueOf(n.getId()));
        		linesEntity.setText("");
        		linesList.add(linesEntity);
        }
        return rootNodes;
    }
	/**
     * 递归子节点
     * @param node
     */
	@RequestMapping("/buildBomChildNodes")
	@ResponseBody
    public void buildBomChildNodes(YtjrGdmcxxResult node) {
        List<YtjrGdmcxxResult> children = getChildBomNodes(node);
        if (!children.isEmpty()) {
        	//如有有子集,需要将父级设置成有按钮
        	List<YtjrGdjgNodesEntity> removeList = new ArrayList<YtjrGdjgNodesEntity>();
        	 for (YtjrGdjgNodesEntity result : nodesList){
                 if (result.getId().equals(node.getId().toString())){
                     removeList.add(result);
                 }
             }
        	nodesList.removeAll(removeList);
        	YtjrGdjgNodesEntity nodeEntity = new YtjrGdjgNodesEntity();
            nodeEntity.setId(String.valueOf(node.getId()));
     		nodeEntity.setText(node.getEntname());
     		nodeEntity.setColor("#4ea2f0");
     		nodeEntity.setExpandHolderPosition("bottom");
     		nodeEntity.setExpanded(false);
     		nodeEntity.setIsShow(true);
     		nodesList.add(nodeEntity);
            for(YtjrGdmcxxResult child : children) {
            	buildBomChildNodes(child);
            }
            node.setChildren(children);
        }
    }
	/**
     * 获取父节点下所有的子节点
     * @param pnode
     * @return
     */
	@RequestMapping("/getChildBomNodes")
	@ResponseBody
    public List<YtjrGdmcxxResult> getChildBomNodes(YtjrGdmcxxResult pnode) {//传入父节点对象,如果为该父节点的子节点,则放入子节点集合中
        List<YtjrGdmcxxResult> childNodes = new ArrayList<YtjrGdmcxxResult>();
        QueryParamList params = new QueryParamList();
		params.addParam("blicno", pnode.getUniscid());
		List<YtjrGdmcxxResult> list = JPAUtil.load(YtjrGdmcxxResult.class, params,null, null, null, null);
        for (YtjrGdmcxxResult n : list){
                childNodes.add(n);
                YtjrGdjgNodesEntity nodeEntity = new YtjrGdjgNodesEntity();
                nodeEntity.setId(String.valueOf(n.getId()));
        		nodeEntity.setText(n.getEntname());
        		nodeEntity.setColor("#4ea2f0");
        		nodeEntity.setIsShow(false);
        		nodesList.add(nodeEntity);
        		//去掉多余的线
            	List<YtjrGdjgLinesEntity> removeList = new ArrayList<YtjrGdjgLinesEntity>();
            	 for (YtjrGdjgLinesEntity result : linesList){
                     if (result.getTo().equals(String.valueOf(n.getId()))&&result.getFrom().equals(String.valueOf(pnode.getId()))){
                         removeList.add(result);
                     }
                 }
            	linesList.removeAll(removeList);
        		YtjrGdjgLinesEntity linesEntity = new YtjrGdjgLinesEntity();
        		linesEntity.setFrom(String.valueOf(pnode.getId()));
        		linesEntity.setTo(String.valueOf(n.getId()));
        		linesEntity.setText("");
        		linesList.add(linesEntity);
        }
        return childNodes;
    }
}

3.最终效果

在这里插入图片描述

;