CytoscapeJS实现ER图
Cytoscape.js是一个很好的实现关系图的js组件。最近用的比较多,这里记录下如何用CytoscapeJS实现类ER图。
这是官方文档: Cytoscape.js
这是GitHub地址:GitHub-Cytoscape.js
一开始用的是力导向图,后来发现这样很难表现出表内字段关系,于是就想着用来实现ER图。
下面是写的一个小Demo,数据是自己造的,写在了文件里,具体数据还需做写修改,这里只提供一个思路。
主要包含三个文件
index.html
此文件里给一个div作为Cytoscape的容器,并引入需要的文件
<!DOCTYPE html>
<html>
<head>
<link href="css/style.css" rel="stylesheet" />
<meta charset=utf-8 />
</head>
<body>
<div id="cy"></div>
<script src="js/cytoscape.min.js"></script>
<script src="js/code.js"></script>
</body>
</html>
style.css
此文件给作为Cytoscape容器的div定一些样式,该容器需要指定宽和高
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
}
code.js
此文件造了些数据,并对数据进行处理(计算位置),最终画入到cytoscape中,具体请到翻到博客底部下载源码
(function (tableNum,fRow,fCol) {
var _ = function(){
return {
getRandomNum : function(n,m){
return Math.floor(Math.random()*(m-n+1)+n);
},
createNodesData : function(tNum){
var model = {
tableNodes:[],
fieldNodes:{}
};
for(var i = 0; i < tNum; i++){
var node = { id:'t'+i, name:'表'+i };
var param = { data: node };
model.tableNodes.push(param);
}
for(var i = 0; i < tNum; i++){
model.fieldNodes['t'+i]=[];
for(var j = 0; j < _.getRandomNum(1,80); j++){
var node = { id:'t'+i+'-f'+j, name:'t'+i+'字段'+j };
var param = { data: node };
model.fieldNodes['t'+i].push(param);
}
}
return model;
},
createEdgesData : function(model){
var edgesER = [];
for(var i = 0; i < model.tableNodes.length; i++){
var edge = { source:model.fieldNodes[model.tableNodes[i].data.id][model.fieldNodes[model.tableNodes[i].data.id].length-1].data.id,
target:model.fieldNodes[model.tableNodes[0].data.id][0].data.id }
var param = { data:edge, classes:'fieldRelation' };
edgesER.push(param);
}
return edgesER;
},
figureNodesPosition : function(model,fRow,fCol){
var nodesER = [];
nodesER.push({data: { id: 'main', name: 'ER图' }});
var fieldXOffset = 0,fieldYOffset = 40,fieldRow = fRow,fieldCol = fCol;
var maxX = 0,maxY = 0,entityXOffset=200;
var outLoopNum = 0,cols=0;
for(var i = 0; i < model.tableNodes.length; i++){
cols += parseInt(model.fieldNodes[model.tableNodes[i].data.id].length/fieldRow)+1;
model.tableNodes[i].data.parent = 'main';
nodesER.push(model.tableNodes[i]);
var tempJ = 0 ;
var innerLoopNum = 0;
for(var j = 0; j<model.fieldNodes[model.tableNodes[i].data.id].length; j++){
if(parseInt((j/fieldRow)-innerLoopNum)==1){//字段切换到下一列
fieldXOffset = 180*(++innerLoopNum);
tempJ = 0;
}
model.fieldNodes[model.tableNodes[i].data.id][j].data.parent = model.tableNodes[i].data.id;
model.fieldNodes[model.tableNodes[i].data.id][j].classes = 'cyField';
model.fieldNodes[model.tableNodes[i].data.id][j].position = { x: maxX+fieldXOffset, y: maxY+fieldYOffset*tempJ };
tempJ++;
nodesER.push(model.fieldNodes[model.tableNodes[i].data.id][j]);
}
fieldXOffset=0;//每个实体字段渲染完成后fieldXOffset清零
if(innerLoopNum==0){//若只够画一列,仍需偏移
maxX=maxX+entityXOffset;
}else{//否则记录上一个实体最后一个字段的X
maxX = model.fieldNodes[model.tableNodes[i].data.id][model.fieldNodes[model.tableNodes[i].data.id].length-1].position.x+entityXOffset;//偏移量200
}
if(cols>fieldCol){//实体切换到下一行
fieldXOffset = 0;
maxX = 0;
maxY=fieldYOffset*fieldRow*(++outLoopNum)+120*outLoopNum;
cols = 0;
}
}
return { nodes: nodesER, edges: _.createEdgesData(model)};
},
paintCytoER : function(datas){
var cy = cytoscape({
container: document.getElementById('cy'),
minZoom : 0.5,
maxZoom : 1.5,
userZoomingEnabled: true,
userPanningEnabled:true,
wheelSensitivity : 0.1,
style: cytoscape.stylesheet()
.selector('node')
.css({
'shape': 'roundrectangle',
'content': 'data(name)',
'text-valign': 'center',
'color': 'white',
'text-outline-width': 3,
'text-outline-color': '#888',
'font-size' : '8px',
'width': 100,
'height': 30,
'background-color': '#93CDDD',
'text-outline-color': '#93CDDD',
})
.selector('.fieldRelation')//关系线
.css({
'content': 'data(name)',
'width': 1,
"color" : "#FFFF00",
'font-family': "Microsoft YaHei",
'font-size' : '10px',
'line-color': 'red',
'target-arrow-color': 'red',
'source-arrow-color': 'red',
'curve-style' : 'bezier',//路线
'line-style' : 'dashed',//线的样式
'target-arrow-shape': 'triangle-backcurve',
'target-arrow-fill' : 'filled',
'text-background-opacity' : 0,
})
.selector('$node > node')//小节点外面的框
.css({
'shape': 'roundrectangle',
'text-valign': 'top',
// 'height': 'auto',
// 'width': 'auto',
'background-color': '#ccc',
'background-opacity': 0.333,
'color': '#888',
'text-outline-width':0,
'font-size': 15
})
.selector(':selected')//选中
.css({
'background-color': '#00BFFF',
'line-color': '#00BFFF',
'target-arrow-color': '#00BFFF',
'source-arrow-color': '#00BFFF',
'text-outline-color': '#00BFFF'
})
.selector('#main')
.css({
'background-opacity': 0,
'border-width': 1,
'border-color': '#aaa',
'border-opacity': 0.5,
'font-size': 30,
'padding-top': 40,
'padding-left': 40,
'padding-bottom': 40,
'padding-right': 40,
}),
elements: {
nodes: datas.nodes,
edges: []
},
layout: {
name: 'preset'
}
}).off('click').on('click','.cyField',function(e){//左键展开
//关闭存在的关系 目前只有表0的字段0有关系
if(e.target.id()=='t0-f0'){
cy.remove('.fieldRelation');
cy.add(datas.edges);
}
}).off('cxttap').on('cxttap','node',function(e){//右键关闭
if(e.target.id()=='t0-f0'){
cy.remove('.fieldRelation');
}
});
return cy;
}
};
}();
_.paintCytoER(
_.figureNodesPosition(
_.createNodesData(tableNum),fRow,fCol
)
);
})(10,10,8);//表数,每个实体的字段行数,每行的字段列数
源码下载
提供两种压缩包下载:
tar.gz下载:cytoscapeER.tar.gz
zip下载:cytoscapeER.zip