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

效果展示

ER图
ER图