d3.js多重力导向图多条关系线,mouseover只显示相关节点

直接上效果图

d3.js多重力导向图多条关系线,mouseover只显示相关节点

鼠标移至节点不相连节点变暗

d3.js多重力导向图多条关系线,mouseover只显示相关节点

代码如下:

<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<style>
.nodetext {
    font-size: 12px ;
    font-family: SimSun;
    fill:#000000;
}

.linetext {
    font-size: 12px ;
    font-family: SimSun;
    fill:#1f77b4;
    fill-opacity:0.0;
}

.circleImg {
  stroke: #ff7f0e;
  stroke-width: 1.5px;
}
.tooltip{  
    height: auto;  
    font-family: simsun;  
    font-size: 14px;  
    text-align: center;  
}
select#lineFontType.form-control , select#nodesFontType.form-control{
    width:184px;
}
.layout-split-east {
    border-left: 5px solid #E6EEF8;
}
</style>

</head>
<body>
<!-- 画图容器svg -->
<svg width="800" height="500"></svg>

<!-- 右侧属性栏 -->
<div class="panel layout-panel layout-panel-east layout-split-east" style="width: 300px; left: 847px; top: 0px;">
    <div data-options="region:'east',split:true" style="width: 300px;float: right;">
            <table class="table" border="0" width="100%">
                    <tr>
                        <td width="100px" align="right"><font size="2">背景颜色:</font></td>
                        <td width="*">
                            <div class="input-group">
                              <input onblur="showChart()" type="text" class="form-control" id="backgroundColor" value="#eee">
                            </div>
                        </td>
                    </tr>                
                    <tr>
                        <td width="100px" align="right"><font size="2">节点字体:</font></td>
                        <td width="*">
                            <div class="input-group">
                              <select onblur="showChart()" class="form-control" id="nodesFontType">
                                <option value="SimSun">宋体</option>
                                <option value="SimHei">黑体</option>
                                <option value="Microsoft Yahei">微软雅黑</option>
                                <option value="Microsoft JhengHei">微软正黑体</option>
                                <option value="KaiTi">楷体</option>
                                <option value="NSimSun">新宋体</option>
                                <option value="FangSong">仿宋</option>
                              </select>
                            </div>
                        </td>
                    </tr>
    
                    <tr>
                        <td width="100px" align="right"><font size="2">节点字号:</font></td>
                        <td width="*">
                            <div class="input-group">
                              <input onblur="showChart()" type="text" class="form-control" id="nodesFontSize" value="20">
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td width="100px" align="right"><font size="2">关系线字体:</font></td>
                        <td width="*">
                            <div class="input-group">
                              <select onblur="showChart()" class="form-control" id="lineFontType">
                                <option value="SimSun">宋体</option>
                                <option value="SimHei">黑体</option>
                                <option value="Microsoft Yahei">微软雅黑</option>
                                <option value="Microsoft JhengHei">微软正黑体</option>
                                <option value="KaiTi">楷体</option>
                                <option value="NSimSun">新宋体</option>
                                <option value="FangSong">仿宋</option>
                              </select>
                            </div>
                        </td>
                    </tr>
    
                    <tr>
                        <td width="100px" align="right"><font size="2">关系线字号:</font></td>
                        <td width="*">
                            <div class="input-group">
                              <input onblur="showChart()" type="text" class="form-control" id="lineFontSize" value="15">
                            </div>
                        </td>
                    </tr>
    
                    <tr>
                        <td width="100px" align="right"><font size="2">关系线颜色:</font></td>
                        <td width="*">
                            <div class="input-group">
                              <input onblur="showChart()" type="text" class="form-control" value="#1f77b4" id="lineColor">
                            </div>
                        </td>
                    </tr>
                    
                    <tr>
                        <td width="100px" align="right"><font size="2">显示数据:</font></td>
                        <td width="*">
                            <div class="input-group">
                              <textarea onblur="showChart()" id="chartSeries" class="form-control" rows="3">
                                {
    "links":[
    { "source": "陆洋","target":"建银国际产业基金管理有限公司","relation" : "副董事长" ,"sourceId":"","targetId":"","sourceImg":"http://mail.tom.com/error/i2.gif","targetImg":"","sourceColor":"#00FF00","targetColor":"","sourceRadius":"30","targetRadius":"45"},
    { "source": "汪红辉"  , "target" : "乾行文化投资公司"  , "relation" : "总经理" ,"sourceId":"","targetId":"","sourceImg":"","targetImg":"","sourceColor":"","targetColor":"#00FF00","sourceRadius":"30","targetRadius":"40"},
    { "source": "汪红辉"  , "target" : "天津裕丰股权投资管理有限公司" , "relation" : "董事" ,"sourceId":"","targetId":"","sourceImg":"","targetImg":"","sourceColor":"","targetColor":"","sourceRadius":"30","targetRadius":"42"},
    { "source": "汪红辉"  , "target" : "乾行文化投资公司"  , "relation" : "法人" ,"sourceId":"","targetId":"","sourceImg":"","targetImg":"","sourceColor":"","targetColor":"","sourceRadius":"30","targetRadius":"40"},
    { "source": "汪红辉" , "target" : "乾行文化投资公司" , "relation" : "董事长" ,"sourceId":"","targetId":"","sourceImg":"","targetImg":"","sourceColor":"","targetColor":"","sourceRadius":"30","targetRadius":"40"},
    { "source": "汪红辉" , "target" : "建银国际产业基金管理有限公司"  , "relation" : "董事" ,"sourceId":"","targetId":"","sourceImg":"","targetImg":"","sourceColor":"","targetColor":"","sourceRadius":"30","targetRadius":"45"},
    { "source": "胡章宏" , "target" : "建银国际产业基金管理有限公司"   , "relation" : "董事长" ,"sourceId":"","targetId":"","sourceImg":"","targetImg":"","sourceColor":"","targetColor":"","sourceRadius":"30","targetRadius":"45"},
    { "source": "胡章宏" , "target" : "建银国际产业基金管理有限公司" , "relation" : "法人" ,"sourceId":"","targetId":"","sourceImg":"","targetImg":"","sourceColor":"","targetColor":"","sourceRadius":"30","targetRadius":"45"}
    ]
    }
                              </textarea>
                            </div>
                        </td>
                    </tr>
              </table>
        </div>   
    </div>
</body>
<script>

$(document).ready(function(){
    showChart();
})

function showChart(){
    var backgroundColor = $("#backgroundColor").val();
    var nodesFontType = $("#nodesFontType").val();
    var nodesFontSize = $("#nodesFontSize").val();
    var lineFontType = $("#lineFontType").val();
    var lineFontSize = $("#lineFontSize").val();
    var lineColor = $("#lineColor").val();
    var data = $("#chartSeries").val();
    data = JSON.parse(data);
    drawChart(backgroundColor,nodesFontType,nodesFontSize,lineFontType,
            lineFontSize,lineColor,data);
}

function drawChart(backgroundColor,nodesFontType,nodesFontSize,lineFontType,
        lineFontSize,lineColor,data){
    var radius = 40;    //圆形半径
    var width = 800;
    var height = 500;
    var svgChart = d3.select("svg");
    svgChart.remove();
    
    var links = data.links;
    //关系分组  
    var linkGroup = {};  
    //对连接线进行统计和分组,不区分连接线的方向,只要属于同两个实体,即认为是同一组  
    var linkmap = {};  
    for(var i=0; i<links.length; i++){  
        var key = links[i].source<links[i].target?links[i].source+':'+links[i].target:links[i].target+':'+links[i].source;  
        if(!linkmap.hasOwnProperty(key)){  
            linkmap[key] = 0;  
        }  
        linkmap[key]+=1;  
        if(!linkGroup.hasOwnProperty(key)){  
            linkGroup[key]=[];  
        }  
        linkGroup[key].push(links[i]);  
    }  
    //为每一条连接线分配size属性,同时对每一组连接线进行编号  
    for(var i=0; i<links.length; i++){  
        var key = links[i].source<links[i].target?links[i].source+':'+links[i].target:links[i].target+':'+links[i].source;  
        links[i].size = linkmap[key];  
        //同一组的关系进行编号  
        var group = linkGroup[key];  
        var keyPair = key.split(':');  
        var type = 'noself';//标示该组关系是指向两个不同实体还是同一个实体  
        if(keyPair[0]==keyPair[1]){
            type = 'self';  
        }
        //给节点分配编号  
        setLinkNumber(group,type);  
    }  
 
    var nodes = {};  
 
    for(var i = 0; i < links.length; i++){
        links[i].source = nodes[links[i].source] || (nodes[links[i].source] = {name: links[i].source,color:links[i].sourceColor,image:links[i].sourceImg,radius:links[i].sourceRadius});  
        links[i].target = nodes[links[i].target] || (nodes[links[i].target] = {name: links[i].target,color:links[i].targetColor,image:links[i].targetImg,radius:links[i].targetRadius});
    }
    
    nodes = d3.values(nodes);
   
    //绑定相连节点
   for(var i = 0; i < nodes.length; i++){
        for(var j = 0; j < links.length; j++){
            if(nodes[i].name == links[j].source.name){
                nodes[i][links[j].target.name] = {name: links[j].target.name};
            }
            if(nodes[i].name == links[j].target.name){
                nodes[i][links[j].source.name] = {name: links[j].source.name};
            }
        }
    }
    
    var svg = d3.select("body").append("svg")
                               .attr("width",width)
                               .attr("height",height)
                               .attr("style","background-color:" + backgroundColor);
        //D3力导向布局
        var force = d3.layout.force()
                        .nodes(nodes)
                        .links(links)
                        .size([width,height])
                        .linkDistance(200)
                        .charge(-1500)
                        .start();
    
        //箭头
        /* var arrowMarker = svg.append("marker")
        .attr("id","arrow")
        .attr("markerUnits","strokeWidth")
        .attr("markerWidth","16")
        .attr("markerHeight","15")
        .attr("viewBox","0 0 12 12")
        .attr("refX","30")
        .attr("refY","6")
        .attr("orient","auto");
        .append("path")
        .attr("d","M2,2 L10,6 L2,10 L6,6 L2,2")
        .attr("fill",lineColor); */
        
        /* var arrowMarker = svg.append("marker")
        .attr("id","arrow")
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 55)
        .attr("refY", 0)
        .attr("markerWidth", 9)
        .attr("markerHeight", 16)
        .attr("markerUnits","userSpaceOnUse")
        .attr("orient", "auto")
        .append("svg:path")
        .attr("d", "M0,-5L10,0L0,5")
        .attr('fill',lineColor); */
        
        //边             
        var edges_path = svg.selectAll(".edgepath")
                            .data(links)
                            .enter()
                            .append("path")
                            .attr("marker-end",function(d,i){
                                var arrowMarker = svg.append("marker")
                                .attr("id","arrow"+i)
                                .attr("markerUnits","userSpaceOnUse")
                                .attr("markerWidth","16")
                                .attr("markerHeight","15")
                                .attr("viewBox","0 0 12 12")
                                .attr("refX",d.targetRadius)
                                .attr("refY","6")
                                .attr("orient","auto")
                                .append("svg:path")
                                .attr("d","M2,2 L10,6 L2,10 L6,6 L2,2")
                                .attr("fill",lineColor);
                                
                                return "url(#arrow" + i + ")";
                            })
                            .style("stroke",lineColor)
                            .style("stroke-width",1.5)
                            .style("fill","none");
                            
    
        //边上的文字(人物之间的关系)            
        var edges_text = svg.selectAll(".linetext")
                            .data(links)
                            .enter()
                            .append("text")
                            .attr("fill-opacity",1)
                            .style("font-size",(lineFontSize+"px"))
                            .style("font-family",lineFontType)
                            .style("fill",lineColor)
                            .text(function(d){
                                return d.relation;
                            });
    
    
        // 圆形图片节点(人物头像)
        var circle = svg.selectAll("circle")
                            .data(nodes)
                            .enter()
                            .append("circle")
                            .attr("class", "circleImg")
                            .attr("r", function(d){
                                return d.radius;
                            })
                            .attr("fill", function(d,i){
                                //节点图片不为空是添加背景色
                                if(d.image == ""){
                                    if(d.color == ""){
                                        return "#eee";
                                    }
                                    return d.color;
                                }else{
                                    //创建圆形图片
                                    var defs = svg.append("defs").attr("id", "imgdefs")
        
                                    var catpattern = defs.append("pattern")
                                                            .attr("id", "catpattern" + i)
                                                            .attr("height", 1)
                                                            .attr("width", 1)
        
                                    catpattern.append("image")
                                            /* .attr("x", - (img_w / 2 - radius))
                                            .attr("y", - (img_h / 2 - radius)) */
                                            .attr("width", radius*2)
                                            .attr("height", radius*2)
                                            .attr("xlink:href", d.image)
        
                                    return "url(#catpattern" + i + ")";
                                }
                                
                            })
                            .on("mouseover",function(d,i){
                                //影藏其它连线上文字
                                edges_text.style("fill-opacity",function(edge){
                                    if( edge.source === d || edge.target === d ){
                                        return 1;
                                    }
                                    if( edge.source !== d && edge.target !== d ){
                                        return 0;
                                    }
                                })
                                //其它节点亮度调低
                                circle.style("opacity",function(edge){
                                    var v = d.name;
                                    if( edge.name == v || (edge[v] != undefined &&  edge[v].name == v)){
                                        return 1;
                                    }else{
                                        return 0.2;
                                    }
                                })
                                //其他连先亮度调低
                                edges_path.style("opacity",function(edge){
                                    if( edge.source === d || edge.target === d ){
                                        return 1;
                                    }
                                    if( edge.source !== d && edge.target !== d ){
                                        return 0.2;
                                    }
                                })
                                //其他节点文字亮度调低
                                nodes_text.style("opacity",function(edge){
                                    var v = d.name;
                                    if( edge.name == v || (edge[v] != undefined &&  edge[v].name == v)){
                                        return 1;
                                    }else{
                                        return 0.2;
                                    }
                                })
                            
                            })
                            .on("mouseout",function(d,i){
                                //显示连线上的文字
                                edges_text.style("fill-opacity",1);
                                edges_path.style("opacity",1);
                                circle.style("opacity",1);
                                nodes_text.style("opacity",1);
                                tooltip.style("opacity",0.0);
                            
                            })
                            .call(force.drag);
    
        var tooltip = d3.select("body").append("div")
                         .attr("class","tooltip")
                         .attr("opacity",0.0);
        
        
        var nodes_text = svg.selectAll(".nodetext")
                            .data(nodes)
                            .enter()
                            .append("text")
                            .style("font-size",(nodesFontSize+"px"))
                            .style("font-family",nodesFontType)
                            .attr('x',function(d){
                                var name = d.name;
                                //如果小于四个字符,不换行
                                if(name.length < 4){
                                     d3.select(this).append('tspan')
                                       .attr("dx",-nodesFontSize*(name.length/2))
                                       .text(function(){return name;});
                                }else if(name.length >= 4 && name.length <= 6) {
                                    var top=d.name.substring(0,3);
                                    var bot=d.name.substring(3,name.length);
                        
                                    d3.select(this).append('tspan')
                                        .attr("dx",-nodesFontSize*1.5)
                                        .attr("dy",-nodesFontSize*0.5)
                                        .text(function(){return top;});
                        
                                    d3.select(this).append('tspan')
                                        .attr("dx",-(nodesFontSize*name.length/2))
                                        .attr("dy",nodesFontSize)
                                        .text(function(){return bot;});
                                }else if(name.length > 7){
                                    var top=d.name.substring(0,3);
                                    var mid=d.name.substring(3,6);
                                    var bot=d.name.substring(6,name.length);
                                    
                                    d3.select(this).append('tspan')
                                        .attr("dx",-nodesFontSize*1.5)
                                        .attr("dy",-nodesFontSize*0.5)
                                        .text(function(){return top;});
                                    
                        
                                    d3.select(this).append('tspan')
                                        .attr("dx",-nodesFontSize*3)
                                        .attr("dy",nodesFontSize)
                                        .text(function(){return mid;});
                                    
                                    d3.select(this).append('tspan')
                                        .attr("dx",-nodesFontSize*2)
                                        .attr("dy",nodesFontSize)
                                        .text(function(){return "...";});
                                }
                            })
                            .on("mouseover",function(d,i){
                                //影藏其它连线上文字
                                edges_text.style("fill-opacity",function(edge){
                                    if( edge.source === d || edge.target === d ){
                                        return 1;
                                    }
                                    if( edge.source !== d && edge.target !== d ){
                                        return 0;
                                    }
                                })
                                //其他节点亮度调低
                                circle.style("opacity",function(edge){
                                    var v = d.name;
                                    if( edge.name == v || (edge[v] != undefined &&  edge[v].name == v)){
                                        return 1;
                                    }else{
                                        return 0.2;
                                    }
                                })
                                //其他连线亮度调低
                                edges_path.style("opacity",function(edge){
                                    if( edge.source === d || edge.target === d ){
                                        return 1;
                                    }
                                    if( edge.source !== d && edge.target !== d ){
                                        return 0.2;
                                    }
                                })
                                //其他节点文字亮度调低
                                nodes_text.style("opacity",function(edge){
                                    var v = d.name;
                                    if( edge.name == v || (edge[v] != undefined &&  edge[v].name == v)){
                                        return 1;
                                    }else{
                                        return 0.2;
                                    }
                                })
                                tooltip.html("<span>"+d.name+"</span>")
                                       .style("left",(d3.event.pageX)+"px")
                                       .style("top",(d3.event.pageY+20)+"px")
                                       .style("display","block")
                                       .style("opacity",1.0);
                            })
                            .on("mouseout",function(d,i){
                                //显示连线上的文字
                                edges_text.style("fill-opacity",1);
                                edges_path.style("opacity",1);
                                circle.style("opacity",1);
                                nodes_text.style("opacity",1);
                                tooltip.style("opacity",0.0);
                            
                            })
                            .call(force.drag);
                           
        
    
        force.on("tick", function(){
            //限制结点的边界
            nodes.forEach(function(d,i){
                d.x = d.x - radius < 0 ? radius : d.x ;
                d.x = d.x + radius > width ? width - radius : d.x ;
                d.y = d.y - radius < 0 ? radius : d.y ;
                d.y = d.y + radius > height ? height - radius : d.y ;
            });
    
            edges_path.attr("d", function(d) {  
                //如果连接线连接的是同一个实体,则对path属性进行调整,绘制的圆弧属于长圆弧,同时对终点坐标进行微调,避免因坐标一致导致弧无法绘制  
                if(d.target==d.source){  
                    dr = 30/d.linknum;  
                    return"M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 1,1 " + d.target.x + "," + (d.target.y+1);  
                }else if(d.size%2!=0 && d.linknum==1){//如果两个节点之间的连接线数量为奇数条,则设置编号为1的连接线为直线,其他连接线会均分在两边  
                    return 'M'+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y;  
                }  
                //根据连接线编号值来动态确定该条椭圆弧线的长半轴和短半轴,当两者一致时绘制的是圆弧  
                //注意A属性后面的参数,前两个为长半轴和短半轴,第三个默认为0,第四个表示弧度大于180度则为1,小于则为0,这在绘制连接到相同节点的连接线时用到;第五个参数,0表示正角,1表示负角,即用来控制弧形凹凸的方向。本文正是结合编号的正负情况来控制该条连接线的凹凸方向,从而达到连接线对称的效果  
                var curve=1.5;  
                var homogeneous=1.2;  
                var dx = d.target.x - d.source.x,  
                    dy = d.target.y - d.source.y,  
                    dr = Math.sqrt(dx*dx+dy*dy)*(d.linknum+homogeneous)/(curve*homogeneous);  
                //当节点编号为负数时,对弧形进行反向凹凸,达到对称效果  
                if(d.linknum<0){  
                    dr = Math.sqrt(dx*dx+dy*dy)*(-1*d.linknum+homogeneous)/(curve*homogeneous);  
                    return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,0 " + d.target.x + "," + d.target.y;  
                }  
                return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;  
              });
    
             //更新连接线上文字的位置
             edges_text.attr("x",function(d){
                 return (d.source.x + d.target.x) / 2 + d.linknum*10; });
             edges_text.attr("y",function(d){
                 return (d.source.y + d.target.y) / 2 + d.linknum*15; });
    
    
             //更新结点图片和文字
             circle.attr("cx",function(d){ return d.x });
             circle.attr("cy",function(d){ return d.y });
    
             nodes_text.attr("x",function(d){ return d.x });
             nodes_text.attr("y",function(d){ return d.y });
        });
}


function setLinkNumber(group,type){   
    if(group.length==0) return;  
    //对该分组内的关系按照方向进行分类,此处根据连接的实体ASCII值大小分成两部分  
    var linksA = [], linksB = [];  
    for(var i = 0;i<group.length;i++){  
        var link = group[i];  
        if(link.source < link.target){  
            linksA.push(link);  
        }else{  
            linksB.push(link);  
        }  
    }  
    //确定关系最大编号。为了使得连接两个实体的关系曲线呈现对称,根据关系数量奇偶性进行平分。  
    //特殊情况:当关系都是连接到同一个实体时,不平分  
    var maxLinkNumber = 1;  
    if(type=='self'){  
        maxLinkNumber = group.length;  
    }else{  
        maxLinkNumber = group.length%2==0?group.length/2:(group.length+1)/2;  
    }  
    //如果两个方向的关系数量一样多,直接分别设置编号即可  
    if(linksA.length==linksB.length){  
        var startLinkNumber = 1;  
        for(var i=0;i<linksA.length;i++){  
            linksA[i].linknum = startLinkNumber++;  
        }  
        startLinkNumber = 1;  
        for(var i=0;i<linksB.length;i++){  
            linksB[i].linknum = startLinkNumber++;  
        }  
    }else{//当两个方向的关系数量不对等时,先对数量少的那组关系从最大编号值进行逆序编号,然后在对另一组数量多的关系从编号1一直编号到最大编号,再对剩余关系进行负编号  
        //如果抛开负号,可以发现,最终所有关系的编号序列一定是对称的(对称是为了保证后续绘图时曲线的弯曲程度也是对称的)  
        var biggerLinks,smallerLinks;  
        if(linksA.length>linksB.length){  
            biggerLinks = linksA;  
            smallerLinks = linksB;  
        }else{  
            biggerLinks = linksB;  
            smallerLinks = linksA;  
        }  

        var startLinkNumber = maxLinkNumber;  
        for(var i=0;i<smallerLinks.length;i++){  
            smallerLinks[i].linknum = startLinkNumber--;  
        }  
        var tmpNumber = startLinkNumber;  

        startLinkNumber = 1;  
        var p = 0;  
        while(startLinkNumber<=maxLinkNumber){  
            biggerLinks[p++].linknum = startLinkNumber++;  
        }  
        //开始负编号  
        startLinkNumber = 0-tmpNumber;  
        for(var i=p;i<biggerLinks.length;i++){  
            biggerLinks[i].linknum = startLinkNumber++;  
        }  
    }   
}

</script>


</html>