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