美文网首页知识图谱
d3.js - force力学图

d3.js - force力学图

作者: 谢大见 | 来源:发表于2018-03-15 10:49 被阅读0次

    现已升级为npm包,方便大家使用
    新文章传送门


    relation-chart

    效果预览链接

    可进行拖拽,缩放

    demo.jpg

    高亮选中的节点

    高亮选中

    以下是旧文章版本


    D3.js V4版本详细文档

    力模型:d3.forceSimulation([nodes])

    用指定的节点数组创建一个新的力模拟。
    默认为空数组。如果节点被指定时,将仿真的节点到指定对象数组,初始化它们的位置,
    每个节点必须是一个对象,由力模型分配以下:

    • index-节点的从零开始的索引节点
    • x- 节点的当前x位置
    • y- 节点的当前y位置
    • vx- 节点当前的x- velocity
    • vy- 节点当前的y速度

    力模型新加一种力

    simulation.force(name,[force])函数
    例如

    //链接力
    .force("link", d3.forceLink(config.links))
     // 万有引力
    .force("charge", d3.forceManyBody().strength(-300))
    // 用指定的x坐标和y坐标创建一个居中力。
    .force("center", d3.forceCenter(config.width / 2, config.height / 2))
     //碰撞作用力,为节点指定一个radius区域来防止节点重叠,设置碰撞力的强度,范围[0,1], 默认为0.7。设置迭代次数,默认为1,迭代次数越多最终的布局效果越好,但是计算复杂度更高
     .force("collide", d3.forceCollide(100).strength(0.2).iterations(5))
    

    tick计时器事件

    simulation力模型会在运行中不停的调用 tick 事件,tick事件 收到 alpha系数的影响来衰减或停止

    alpha系数

    概念可能会比较理解,在计时器的每一帧中,仿真的alpha系数会不断削减,当alpha到达一个系数时,仿真将会停止,也就是alpha的目标系数alphaTarget,该值区间为[0,1]. 默认为0,控制力学模拟 alphaDecay 衰减率,[0-1] ,设为0则不停止 , 默认0.0228,直到0.001

    • alpha系数
    • alphaTarget 目标系数
    • alphaDecay 衰减率

    力模型的停止与重启

    力模型收到 alpha 影响会逐渐减弱,最后停止,也可以手动控制力模型的停止与重启

    • simulation.stop()
    • simulation.restart()

    d3的拖拽事件 d3.drag()

    3个事件阶段

    • start
    • drag
    • end

    d3缩放平移事件 d3.zoom()

    d3.zoom()
      //比例尺
      .scaleExtent([0.5,1.5])
      //transform
      .on("zoom",function(){
               this.g.attr("transform", d3.event.transform);
       });
    

    将事件应用到 元素

    常用 d3的 call() 函数来实现

     d3.select("#svg").append("circle").call(this.dra);
    

    综合案例
    案例演示

    参考代码

    /**
     * Created by PhpStorm.
     * User: admin
     * Date: 2018/3/22
     * Time: 13:31
     */
    
    require('../css-module/relsMap-tooltip.less');
    const d3 = require('d3');
    const $ = require('jquery');
    
    /**
     * 关系力导向图 - d3.js制作   (pc,wap通用)
     * @param id    {string} 父容器id
     * @param configPar      {JSON对象}    配置
     */
    function MakeSvgPicClass(id, configPar) {
        this.config = {
            width: 375, // 总画布svg的宽
            height: 610,
            nodes: [],
            links: [],
            isHighLight: false ,    //是否启动 鼠标 hover 到节点上高亮与节点有关的节点,其他无关节点透明的功能
            isScale: true,      //是否启用缩放平移zoom功能
            scaleExtent: [0.5, 1.5],  //缩放的比例尺
            chargeStrength:-300,    //万有引力
            collide:60,        //碰撞力的大小 (节点之间的间距)
            nodeWidth: 120,     // 每个node节点所占的宽度,正方形
            margin: 20,     // node节点距离父亲div的margin
            alphaDecay:0.0228,  //控制力学模拟衰减率
            r: 30,      // 头像的半径 [30 - 45]
            relFontSize: 12 ,   //关系文字字体大小
            linkSrc: 30, // 划线时候的弧度
            linkColor:'#bad4ed',    //链接线默认的颜色
            strokeColor: '#7ecef4', // 头像外围包裹的颜色
            strokeWidth: 3, // 头像外围包裹的宽度
        };
    
        this.R = 30;  //画线时候的影响弧度
        $.extend(true, this.config, configPar);
        var _that = this;
        var timeout;//定时器
    
        
        // 1. 创建一个力学模拟器
        this.simulation = d3.forceSimulation(this.config.nodes)
        // simulation.force(name,[force])函数,添加某种力
            .force("link", d3.forceLink(this.config.links))
            // 万有引力
            .force("charge", d3.forceManyBody().strength(this.config.chargeStrength))
            // d3.forceCenter()用指定的x坐标和y坐标创建一个新的居中力。
            .force("center", d3.forceCenter(this.config.width / 2, this.config.height / 2))
            // 碰撞作用力,为节点指定一个radius区域来防止节点重叠,设置碰撞力的强度,范围[0,1], 默认为0.7。设置迭代次数,默认为1,迭代次数越多最终的布局效果越好,但是计算复杂度更高
            .force("collide", d3.forceCollide(this.config.collide).strength(0.2).iterations(5))
            // 在计时器的每一帧中,仿真的alpha系数会不断削减,当alpha到达一个系数时,仿真将会停止,也就是alpha的目标系数alphaTarget,该值区间为[0,1]. 默认为0,
            // 控制力学模拟衰减率,[0-1] ,设为0则不停止 , 默认0.0228,直到0.001
            .alphaDecay(this.config.alphaDecay)
            // 监听事件 ,tick|end ,例如监听 tick 滴答事件
            .on("tick", ()=>this.ticked());
    
    
        // 2.创建svg标签
        this.SVG = d3.select("#" + id).append("svg")
            .attr("class", "svgclass")
            .attr("width", this.config.width)
            .attr("height", this.config.height)
            // .transition().duration(750).call(d3.zoom().transform, d3.zoomIdentity);
            .call( d3.zoom().scaleExtent(this.config.scaleExtent).on("zoom", ()=> {
                if(this.config.isScale){
                    _that.relMap_g.attr("transform", d3.event.transform);
                }
            }))
            .on('click',()=>$('.tooltip').remove())
            .on("dblclick.zoom", null);
    
    
        // 3.defs  <defs>标签的内容不会显示,只有调用的时候才显示
        this.defs=this.SVG.append('defs');
         // 3.1 添加箭头
        this.marker=this.defs
            .append("marker")
            .attr('id', "marker")
            .attr("markerWidth", 10)    //marker视窗的宽
            .attr("markerHeight", 10)   //marker视窗的高
            .attr("refX", this.config.r + 3*this.config.strokeWidth)            //refX和refY,指的是图形元素和marker连接的位置坐标
            .attr("refY", 4)
            .attr("orient", "auto")     //orient="auto"设置箭头的方向为自动适应线条的方向
            .attr("markerUnits", "userSpaceOnUse")  //marker是否进行缩放 ,默认值是strokeWidth,会缩放
            .append("path")
            .attr("d", "M 0 0 8 4 0 8Z")    //箭头的路径 从 (0,0) 到 (8,4) 到(0,8)
            .attr("fill", "steelblue");
    
        // 3.2 添加多个头像图片的 <pattern>
        this.patterns = this.defs
            .selectAll("pattern.patternclass")
            .data(this.config.nodes)
            .enter()
            .append("pattern")
            .attr("class", "patternclass")
            .attr("id", function (d, index) {
                return 'avatar' + id + d.id;
            })
            // 两个取值userSpaceOnUse  objectBoundingBox
            .attr('patternUnits', 'objectBoundingBox')
            // <pattern>,x、y值的改变决定图案的位置,宽度、高度默认为pattern图案占填充图形的百分比。
            .attr("x", "0")
            .attr("y", "0")
            .attr("width", "1")
            .attr("height", "1");
    
        // 3.3 向<defs> - <pattern>添加 头像
        this.patterns.append("image")
            .attr("class", "circle")
            .attr("xlink:href", function (d) {
                return d.avatar || "https://static.linkeddb.com/m/images/none.jpg"; // 修改节点头像
            })
            .attr("src", function (d) {
                return d.avatar || "https://static.linkeddb.com/m/images/none.jpg"; // 修改节点头像
            })
            .attr("height", this.config.r*2)
            .attr("width", this.config.r*2)
            .attr("preserveAspectRatio", "xMidYMin slice");
    
        // 3.4 名字
        this.patterns.append("rect").attr("x", "0").attr("y", 4/3*this.config.r).attr("width", 2*this.config.r).attr("height", 2/3*this.config.r).attr("fill", "black").attr("opacity", "0.5");
        this.patterns.append("text").attr("class", "nodetext")
            .attr("x", this.config.r).attr("y", (5/3*this.config.r))
            .attr('text-anchor', 'middle')
            .attr("fill", "#fff")
            .style("font-size", this.config.r/3)
            .text(function (d) {
                return d.name;
            });
    
        
    
        // 4.放关系图的容器
        this.relMap_g = this.SVG.append("g")
            .attr("class", "relMap_g")
            .attr("width", this.config.width)
            .attr("height", this.config.height);
    
    
        // 5.关系图添加线
        // 5.1  每条线是个容器,有线 和一个装文字的容器
        this.edges = this.relMap_g
        .selectAll("g.edge")
        .data(this.config.links)
        .enter()
        .append("g")
        .attr("class","edge")
        .on('mouseover', function () {
            d3.select(this).selectAll('path.links').attr('stroke-width', 4);
        })
        .on('mouseout', function () {
            d3.select(this).selectAll('path.links').attr('stroke-width', 1);
        })
        .on('click',function (d) {
            // $('.no-more').removeClass('hide').addClass('prompt');
            // if(timeout){
            //     clearTimeout(timeout);
            // }
            // console.log(d);
            // $('.no-more').html(`<a class="external" data-no-cache="true" href="${d.source.url}">${d.source.name}</a>&nbsp;  ${d.type} &nbsp;<a class="external" data-no-cache="true" href="${d.target.url}">${d.target.name}</a> `);
            // timeout=setTimeout(()=>$('.no-more').addClass('hide'),3000);
            
            // 浮窗展示
            event = d3.event || window.event;
            var pageX = event.pageX ? event.pageX : (event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft));
            var pageY = event.pageY ? event.pageY : (event.clientY + (document.body.scrollTop || document.documentElement.scrollTop));
            // console.log('pagex',pageX);
            // console.log('pageY',pageY);
            //阻止事件冒泡  阻止事件默认行为
            event.stopPropagation ? (event.stopPropagation()) :  (event.cancelBubble = true);
            event.preventDefault ? (event.preventDefault()) : (event.returnValue = false);
    
            var _html=`<a class="external linkname" data-no-cache="true"  href="${d.source.url}" >${d.source.name} </a>`;
            
            _html+=` &gt; <a class="external linkname" data-no-cache="true"  href="${d.target.url}" >${d.target.name}</a> : ${d.type}`;
    
            d3.selectAll('.tooltip').remove();
            var _div=d3.select('#'+id).append('div')
                    .attr('class','tooltip')
                    .html(_html);
    
            var _width=parseInt(_div.style('width'));
            console.log('width',_width);
    
            //判断浮窗的左上角坐标 ,如果太靠右侧/底部 边缘 ,浮窗位置平移
            console.log('dw'+document.body.clientWidth);
            if(document.body.clientWidth - pageX < _width){
                pageX=document.body.clientWidth - _width -5;
            }
            if(document.body.clientHeight - pageY < 50){
                pageY=document.body.clientHeight-50;
            }
    
            _div.style('top',pageY+'px')
            .style('left',pageX+'px');
        })
        .attr('fill', function (d) {
            var str = '#bad4ed';
            if (d.color) {
                str = "#" + d.color;
            }
            return str;
        })
    
        // 5.2 添加线
        this.links=this.edges.append("path").attr("class","links")
        .attr("d", d=> {return "M" + this.R + "," + 0 + " L" + getDis(d.source, d.target) + ",0";})
        .style("marker-end", "url(#marker)")
        // .attr("refX",this.config.r)
        .attr('stroke', (d)=> {
            var str=d.color ? "#"+d.color : this.config.linkColor ;
            return str;
        });
    
        // 5.3 添加关系文字的容器
        this.rect_g=this.edges.append("g").attr("class", "rect_g");
    
       // 5.4 添加rect
        this.rects =this.rect_g.append("rect")
        .attr("x", 40)
        .attr("y", -10)
        .attr("width", 40)
        .attr("height", 20)
        .attr("fill", "white")
        .attr('stroke',  (d)=> {
            var str=d.color ? "#"+d.color : this.config.linkColor ;
            return str;
        })
    
        // 5.5 文本标签  坐标(x,y)代表 文本的左下角的点
        this.texts = this.rect_g.append("text")
        .attr("x", 40)
        .attr("y", 5)
        .attr("text-anchor", "middle")  // <text>文本中轴对齐方式居中  start | middle | end
        .style("font-size",12).text(d=>{return d.type});
    
    
        // 6.关系图添加用于显示头像的节点
            this.circles =  this.relMap_g.selectAll("circle.circleclass")
                .data(this.config.nodes)
                .enter()
                .append("circle")
                .attr("class","circleclass")
                .style("cursor", "pointer")
                // .attr("cx", function (d) {
                //     return d.x;
                // })
                // .attr("cy", function (d) {
                //     return d.y;
                // })
                .attr("fill", function (d) {
                    return ("url(#avatar" + id + d.id + ")");
                })
                .attr("stroke", "#ccf1fc")
                .attr("stroke-width", this.config.strokeWidth)
                .attr("r", this.config.r)
                .on('mouseover', function(d){
                    d3.select(this).attr('stroke-width', '8');
                    d3.select(this).attr('stroke', '#a3e5f9');
                    if(_that.config.isHighLight){
                        _that.highlightObject(d);
                    }
                })
                .on('mouseout', function(d){
                    d3.select(this).attr('stroke-width', _that.config.strokeWidth);
                    d3.select(this).attr('stroke', '#c5dbf0');
                    if(_that.config.isHighLight){
                        _that.highlightObject(null);
                    }
                })
                .on('click', function (d) {
    
                    // // 展示方式1 :屏幕下方bar
                    // $('.no-more').removeClass('hide');
                    // if(timeout){
                    //     clearTimeout(timeout);
                    // }
                    // if(d.exdata){
                    //     $('.no-more').html(`<a class="external" data-no-cache="true" href="/person/${d.exdata.oid}">${d.exdata.name}</a>&nbsp; 饰 &nbsp;<a class="external" data-no-cache="true" href="${d.url}">${d.name}</a> `);
                    // }else {
                    //     $('.no-more').html(`<a href="${d.url}">${d.name}</a>`);
                    // }
                    // timeout=setTimeout(()=>$('.no-more').addClass('hide'),3000);
    
                    // 展示方式2 :浮窗展示
                    event = d3.event || window.event;
                    var pageX = event.pageX ? event.pageX : (event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft));
                    var pageY = event.pageY ? event.pageY : (event.clientY + (document.body.scrollTop || document.documentElement.scrollTop));
                    // console.log('pagex',pageX);
                    // console.log('pageY',pageY);
                    //阻止事件冒泡  阻止事件默认行为
                    event.stopPropagation ? (event.stopPropagation()) :  (event.cancelBubble = true);
                    event.preventDefault ? (event.preventDefault()) : (event.returnValue = false);
    
                    var _html=`<a href="${d.url}" class="external linkname" data-no-cache="true">${d.name}</a>`;
                    if(d.exdata){
                        _html+=`&nbsp;&nbsp;(<a class="external linkname" data-no-cache="true" href="${d.exdata.url}" >${d.exdata.name}</a> 饰)`;
                    }
    
    
                    d3.selectAll('.tooltip').remove();
                    var _div=d3.select('#'+id).append('div')
                            .attr('class','tooltip')
                            .html(_html);
    
                    var _width=parseInt(_div.style('width'));
                    console.log('width',_width);
    
                    //判断浮窗的左上角坐标 ,如果太靠右侧/底部 边缘 ,浮窗位置平移
                    console.log('dw'+document.body.clientWidth);
                    if(document.body.clientWidth - pageX < _width){
                        pageX=document.body.clientWidth - _width -5;
                    }
                    if(document.body.clientHeight - pageY < 50){
                        pageY=document.body.clientHeight-50;
                    }
    
                    _div.style('top',pageY+'px')
                        .style('left',pageX+'px');
                    
                })
                .on('mousedown', function (d) {     //监听鼠标落下
                    // console.log(event);
                    // console.log(window.event);
                    // event = event || window.event;
                    // var btnNum = event.button;
                    // if (btnNum == 2) {
                    //     console.log("您点击了鼠标右键!");
                    // }
    
                    // if (window.Event)
                    //     document.captureEvents(Event.MOUSEUP);
                    // var canClick=false;
                    // function nocontextmenu()
                    // {
                    //     if(canClick)return;
                    //     event.cancelBubble = true
                    //     event.returnValue = false;
                    //     canClick=true;
                    //     return false;
                    // }
                    // function norightclick(e)
                    // {
                    //     if (window.Event)
                    //     {
                    //         if (e.which == 2 || e.which == 3)
                    //             return false;
                    //     }
                    //     else if (event.button == 2 || event.button == 3)
                    //     {
                    //         event.cancelBubble = true;
                    //         event.returnValue = false;
                    //         return false;
                    //     }
                    // }
                    // document.oncontextmenu = nocontextmenu;  // for IE5+
                    // document.onmousedown = norightclick;  // for all others
                })
                .on('contextmenu', function () {    //鼠标右键菜单
                    // event = event || window.event;
                    // event.cancelBubble = true;
                    // event.returnValue = false;
                    // var pageX = event.pageX ? event.pageX : (event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft));
                    // var pageY = event.pageY ? event.pageY : (event.clientY + (document.body.scrollTop || document.documentElement.scrollTop));
                    // $('#myContextMenu').css('left', pageX);
                    // $('#myContextMenu').css('top', pageY);
                    // $('#myContextMenu').css('visibility ', 'visible');
                    // $('#myContextMenu').show();
                    // return false;
                })
                // 应用 自定义的 拖拽事件
                .call(d3.drag()
                .on('start', function (d) {
                    d3.event.sourceEvent.stopPropagation();
                    // restart()方法重新启动模拟器的内部计时器并返回模拟器。
                    // 与simulation.alphaTarget或simulation.alpha一起使用时,此方法可用于在交互
                    // 过程中进行“重新加热”模拟,例如在拖动节点时,在simulation.stop暂停之后恢复模拟。
                    // 当前alpha值为0,需设置alphaTarget让节点动起来
                    if (!d3.event.active) _that.simulation.alphaTarget(0.3).restart();
                    d.fx = d.x;
                    d.fy = d.y;
                })
                .on('drag', function (d) {
        
                    // d.fx属性- 节点的固定x位置
                    // 在每次tick结束时,d.x被重置为d.fx ,并将节点 d.vx设置为零
                    // 要取消节点,请将节点 .fx和节点 .fy设置为空,或删除这些属性。
                    d.fx = d3.event.x;
                    d.fy = d3.event.y;
                })
                .on('end', function (d) {
        
                    // 让alpha目标值值恢复为默认值0,停止力模型
                    if (!d3.event.active) _that.simulation.alphaTarget(0);
                    d.fx = null;
                    d.fy = null;
                }));
    
    
    
    
        
    
        // 7.动态改变 线和节点的 位置
        // tick心跳函数
        this.ticked = ()=>{
    
            // 7.1 修改每条容器edge的位置
            this.edges.attr("transform", function (d) {
                return getTransform(d.source, d.target, getDis(d.source, d.target))
            });
    
            // 7.2 修改每条线link位置
            this.links.attr("d", d=> {return "M" + this.R + "," + 0 + " L" + getDis(d.source, d.target) + ",0";})
        
            
            // 7.3 修改线中关系文字text的位置 及 文字的反正
            this.texts
            .attr("x", function(d){
                // 7.3.1 根据字的长度来更新兄弟元素 rect 的宽度
                var bbox=d3.select(this).node().getBBox();
                var width = bbox.width;
                $(this).prev('rect').attr('width',width+10);   
                // 7.3.2 更新 text 的位置
                return getDis(d.source, d.target) / 2})
            .attr("transform", function (d) {
                // 7.3.3 更新文本反正
                if (d.target.x < d.source.x) {
                    var x = getDis(d.source, d.target) / 2;
                    return 'rotate(180 ' + x + ' ' + 0 + ')';
                } else {
                    return 'rotate(0)';
                }
            });
    
            // 7.4 修改线中装文本矩形rect的位置
            this.rects 
            .attr("x", function(d){return getDis(d.source, d.target) / 2  - $(this).attr('width')/2})    // x 坐标为两点中心距离减去自身长度一半
    
            // 5.修改节点的位置
            this.circles
                .attr("cx", function (d) {
                    return d.x;
                })
                .attr("cy", function (d) {
                    return d.y;
                })
    
    
    
        };
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
        /**
         *  关于缩放平移踩的大坑
         *  1. 改变需要缩放平移对象的transform来实现 ,本例子是 this.relMap_g
         *  2. 但是zoom 事件的监听应该放到整个的svg上 ,本例子是 this.SVG
         *  3. 这样 监听出来的 d3.event.transform 的 transform 数值才是正确的
         *  4. 直接把zoom 事件监听 this.relMap_g 得到的 transform 数值会爆炸大,移动非常不平顺,瞬移消失在视野
         *
         *  d3.event.transform{
         *      k:1    // 当前比例尺
         *      x:0    // x 方向移动的大小
         *      y:0    // y 方向移动的大小
         *  }
         * */
        this.zoom = d3.zoom()
            .scaleExtent(this.config.scaleExtent)
            .on("zoom", ()=> {
                if(this.config.isScale){
                    _that.relMap_g.attr("transform", d3.event.transform);
                }
            });
    
    
        /**
         * d3 拖拽事件drag event 参数
         *   target- 相关的拖动行为。
         *   type - 字符串“start”, “drag” or “end”;  请参阅drag .on。
         *   subject- 拖动主题,由drag .subject定义。
         *   x- 主题的新x坐标; 请参阅drag .container。
         *   y- 主题的新y坐标; 请参阅drag .container。
         *   dx- 自上次拖动事件以来x坐标的变化。
         *   dy- 自上次拖动事件以来y坐标的变化。
         *   identifier- 字符串“鼠标”或数字触摸标识符。
         *   active - 当前活动拖动手势的数量(在开始和结束时,不包括这一个)。
         *   sourceEvent - 基础输入事件,如mousemove或touchmove。
         * */
        // d3.drag() 创建一个新的拖动行为。返回的行为拖动既是对象又是函数,通常通过选择 .call应用于选定的元素。
        // start - 新指针变为活动状态后(在mousedown或touchstart上)
        // drag - 活动指针移动后(在mousemove或touchmove上)。
        // end - 活动指针变为非活动状态后(在mouseup,touchend或touchcancel上)。
        this.drag =()=>{ 
            return d3.drag()
            .on('start', function (d) {
                d3.event.sourceEvent.stopPropagation();
                // restart()方法重新启动模拟器的内部计时器并返回模拟器。
                // 与simulation.alphaTarget或simulation.alpha一起使用时,此方法可用于在交互
                // 过程中进行“重新加热”模拟,例如在拖动节点时,在simulation.stop暂停之后恢复模拟。
                // 当前alpha值为0,需设置alphaTarget让节点动起来
                if (!d3.event.active) _that.simulation.alphaTarget(0.3).restart();
                d.fx = d.x;
                d.fy = d.y;
            })
            .on('drag', function (d) {
    
                // d.fx属性- 节点的固定x位置
                // 在每次tick结束时,d.x被重置为d.fx ,并将节点 d.vx设置为零
                // 要取消节点,请将节点 .fx和节点 .fy设置为空,或删除这些属性。
                d.fx = d3.event.x;
                d.fy = d3.event.y;
            })
            .on('end', function (d) {
    
                // 让alpha目标值值恢复为默认值0,停止力模型
                if (!d3.event.active) _that.simulation.alphaTarget(0);
                d.fx = null;
                d.fy = null;
            });
        }
    
        /*
         * 高亮和取消高亮
         * */
        this.highlighted = null;
        this.dependsNode = [];
        this.dependsLinkAndText = [];
        this.highlightObject = function (obj) {
            if (obj) {
                var objIndex = obj.index;
                _that.dependsNode = _that.dependsNode.concat([objIndex]);
                _that.dependsLinkAndText = _that.dependsLinkAndText.concat([objIndex]);
                _that.config.links.forEach(function (lkItem) {
                    if (objIndex == lkItem['source']['index']) {
                        _that.dependsNode = _that.dependsNode.concat([lkItem.target.index]);
                    } else if (objIndex == lkItem['target']['index']) {
                        _that.dependsNode = _that.dependsNode.concat([lkItem.source.index]);
                    }
                });
    
                // 隐藏节点
                _that.SVG.selectAll('circle').filter(function (d) {
                    return (_that.dependsNode.indexOf(d.index) == -1);
                }).transition().style('opacity', 0.1);
                // 隐藏线
                _that.SVG.selectAll('.edge').filter(function (d) {
                    // return true;
                    return ((_that.dependsLinkAndText.indexOf(d.source.index) == -1) && (_that.dependsLinkAndText.indexOf(d.target.index) == -1))
                }).transition().style('opacity', 0.1);
    
            } else {
                // 取消高亮
                // 恢复隐藏的线
                _that.SVG.selectAll('circle').filter(function () {
                    return true;
                }).transition().style('opacity', 1);
                // 恢复隐藏的线
                _that.SVG.selectAll('.edge').filter(function (d) {
                    // return true;
                    return ((_that.dependsLinkAndText.indexOf(d.source.index) == -1) && (_that.dependsLinkAndText.indexOf(d.target.index) == -1))
                }).transition().style('opacity', 1);
                _that.highlighted = null,
                    _that.dependsNode = [],
                    _that.dependsLinkAndText = [];
            }
        };
    
    }
    
    
    // 求两点间的距离
    function getDis(s, t) {
        return Math.sqrt((s.x - t.x) * (s.x - t.x) + (s.y - t.y) * (s.y - t.y));
    }
    
    // 求元素移动到目标位置所需要的 transform 属性值
    function getTransform(source, target, _dis) {
        var r;
        if (target.x > source.x) {
            if (target.y > source.y) {
                r = Math.asin((target.y - source.y) / _dis)
            } else {
                r = Math.asin((source.y - target.y) / _dis);
                r = -r;
            }
        } else {
            if (target.y > source.y) {
                r = Math.asin((target.y - source.y) / _dis);
                r = Math.PI - r;
            } else {
                r = Math.asin((source.y - target.y) / _dis);
                r -= Math.PI;
            }
        }
        r = r * (180 / Math.PI);
        return "translate(" + source.x + "," + source.y + ")rotate(" + r + ")";
    }
    
    module.exports = {
        D3MakeSvgPicClass: MakeSvgPicClass
    };
    

    数据格式模板

    {"nodes": [{"id": 41, "oid": "5a56e22893cc2b42a6bf001f", "type": "role", "name": "\u5468\u83b9", "avatar": "https://i.linkeddb.com/upload/image/2017920/140646844806.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf001f/", "exdata": {"id": 67864, "oid": "59fa71e218521569b662b81e", "name": "\u5b59\u4fea", "avatar": "https://i.linkeddb.com/person2/070d/d9c3/0fe9aa40e9c3014d23fadcce.jpg"}}, {"id": 42, "oid": "5a56e22893cc2b42a6bf0020", "type": "role", "name": "\u6c88\u661f\u79fb", "avatar": "https://i.linkeddb.com/upload/image/2017920/140848800133.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf0020/", "exdata": {"id": 146808, "oid": "59fa798518521569b66a0707", "name": "\u9648\u6653", "avatar": "https://i.linkeddb.com/person2/5fe0/efe2/375e86834b0fd8e423ed46ad.jpg"}}, {"id": 44, "oid": "5a56e22893cc2b42a6bf0022", "type": "role", "name": "\u8d75\u767d\u77f3", "avatar": "https://i.linkeddb.com/upload/image/2017920/141152124754.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf0022/", "exdata": {"id": 77558, "oid": "59fa72aa18521569b6637db9", "name": "\u4efb\u91cd", "avatar": "https://i.linkeddb.com/person2/f033/143e/fd25ff01c512e568bedb139f.jpg"}}, {"id": 49, "oid": "5a56e22893cc2b42a6bf0027", "type": "role", "name": "\u5468\u8001\u56db", "avatar": "https://i.linkeddb.com/upload/image/2017920/143059527666.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf0027/", "exdata": {"id": 18420, "oid": "59fa6ee218521569b65fc164", "name": "\u5218\u4f69\u7426", "avatar": "https://i.linkeddb.com/person2/6dc0/474c/800b91826457c39b325db6d1.jpg"}}, {"id": 43, "oid": "5a56e22893cc2b42a6bf0021", "type": "role", "name": "\u5434\u8058", "avatar": "https://i.linkeddb.com/upload/image/2017920/141041905393.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf0021/", "exdata": {"id": 76294, "oid": "59fa729b18521569b6636ff3", "name": "\u4f55\u6da6\u4e1c", "avatar": "https://i.linkeddb.com/person2/b4ab/93bf/76a67ed28d5bcf5e6e08d49e.jpg"}}, {"id": 50, "oid": "5a56e22893cc2b42a6bf0028", "type": "role", "name": "\u6c88\u56db\u6d77", "avatar": "https://i.linkeddb.com/upload/image/2017920/143624653261.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf0028/", "exdata": {"id": 20839, "oid": "59fa6eff18521569b65fe02c", "name": "\u8c22\u541b\u8c6a", "avatar": "https://i.linkeddb.com/person2/ef23/5499/0faf910b5f1c74a5e5beedbf.jpg"}}, {"id": 47, "oid": "5a56e22893cc2b42a6bf0025", "type": "role", "name": "\u5434\u851a\u6587", "avatar": "https://i.linkeddb.com/upload/image/2017920/143212760960.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf0025/", "exdata": {"id": 80081, "oid": "59fa72dd18521569b663b17a", "name": "\u5f20\u6668\u5149", "avatar": "https://i.linkeddb.com/person2/d951/b1d3/01f0a1ccfa0c8d2aef80f543.jpg"}}, {"id": 51, "oid": "5a56e22893cc2b42a6bf0029", "type": "role", "name": "\u5343\u7ea2", "avatar": "https://i.linkeddb.com/upload/image/2017920/144328263786.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf0029/", "exdata": {"id": 15313, "oid": "59fa6ec018521569b65f9fd4", "name": "\u5468\u4e3d\u6dc7", "avatar": "https://i.linkeddb.com/person2/aa47/0381/6cd27a6f890af3440fc813cd.jpg"}}, {"id": 48, "oid": "5a56e22893cc2b42a6bf0026", "type": "role", "name": "\u5434\u6f2a", "avatar": "https://i.linkeddb.com/upload/image/2017920/142808729350.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf0026/", "exdata": {"id": 171638, "oid": "59fa7e4f18521569b66dbcd9", "name": "\u66fe\u6dc7", "avatar": "https://i.linkeddb.com/upload/image/2017920/142410770419.jpg"}}, {"id": 833, "oid": "5a56e24b93cc2b42a6bf0337", "type": "role", "name": "\u6c88\u8001\u592b\u4eba", "avatar": "https://i.linkeddb.com/upload/742a/72f2/f933743cc2563895cb28a4ba.png", "url": "/movie/role/5a56e24b93cc2b42a6bf0337/", "exdata": {"id": 74843, "oid": "59fa729018521569b66364a3", "name": "\u5510\u7fa4", "avatar": "https://i.linkeddb.com/person2/97c3/9c83/fee3e8039a4c976f192447bf.jpg"}}, {"id": 832, "oid": "5a56e24b93cc2b42a6bf0336", "type": "role", "name": "\u6c88\u592b\u4eba", "avatar": "https://i.linkeddb.com/upload/05db/a955/dda11486be4f1b94717ea8bb.png", "url": "/movie/role/5a56e24b93cc2b42a6bf0336/", "exdata": {"id": 76304, "oid": "59fa729b18521569b6636eb9", "name": "\u5218\u6d01", "avatar": "https://i.linkeddb.com/person2/e8ac/f7a2/cf19348ed16ab3700bee93b9.jpg"}}, {"id": 55, "oid": "5a56e22893cc2b42a6bf002d", "type": "role", "name": "\u6c88\u6708\u751f", "avatar": "https://i.linkeddb.com/upload/image/2017920/165524797798.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf002d/", "exdata": {"id": 81417, "oid": "59fa72f718521569b663cccc", "name": "\u674e\u89e3", "avatar": "https://i.linkeddb.com/person2/8edb/31e8/c09ff047f76721dc268011fb.jpg"}}, {"id": 59, "oid": "5a56e22993cc2b42a6bf0031", "type": "role", "name": "\u5434\u9047", "avatar": "https://i.linkeddb.com/upload/image/2017920/171456600879.jpg", "url": "/movie/role/5a56e22993cc2b42a6bf0031/", "exdata": {"id": 155065, "oid": "59fa7ab118521569b66b0ae1", "name": "\u8bb8\u8bfa", "avatar": "https://i.linkeddb.com/person2/1cd8/47b9/04dc74ae4dbdc51614dd5e0d.jpg"}}, {"id": 45, "oid": "5a56e22893cc2b42a6bf0023", "type": "role", "name": "\u80e1\u548f\u6885", "avatar": "https://i.linkeddb.com/upload/image/2017920/141341851866.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf0023/", "exdata": {"id": 25465, "oid": "59fa6f4c18521569b6602932", "name": "\u80e1\u674f\u513f", "avatar": "https://i.linkeddb.com/person2/dd65/89e5/6c3163f8fb99fbac5a2fde3a.jpg"}}, {"id": 46, "oid": "5a56e22893cc2b42a6bf0024", "type": "role", "name": "\u675c\u660e\u793c", "avatar": "https://i.linkeddb.com/upload/image/2017920/141611471224.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf0024/", "exdata": {"id": 105183, "oid": "59fa751618521569b665e8a2", "name": "\u4fde\u704f\u660e", "avatar": "https://i.linkeddb.com/upload/2c33/76de/923efcfad5f70b2755601530.png"}}, {"id": 56, "oid": "5a56e22893cc2b42a6bf002e", "type": "role", "name": "\u5434\u6cfd", "avatar": "https://i.linkeddb.com/upload/image/2017920/165926893902.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf002e/", "exdata": {"id": 156844, "oid": "59fa7af418521569b66b44e1", "name": "\u5f20\u5929\u9633", "avatar": "https://i.linkeddb.com/person2/75f6/f682/2dbdb23443d37793c5b44250.jpg"}}, {"id": 831, "oid": "5a56e24b93cc2b42a6bf0335", "type": "role", "name": "\u67e5\u5764", "avatar": "https://i.linkeddb.com/upload/48e2/810a/3675c7b3b157438ce5a42dbf.png", "url": "/movie/role/5a56e24b93cc2b42a6bf0335/", "exdata": {"id": 182836, "oid": "5a150881b0f85226cf8678e4", "name": "\u674e\u827a\u79d1", "avatar": "https://i.linkeddb.com/upload/4a66/509d/39a25ad4a12d1b6486a0ff91.jpg"}}, {"id": 54, "oid": "5a56e22893cc2b42a6bf002c", "type": "role", "name": "\u56fe\u5c14\u4e39", "avatar": "https://i.linkeddb.com/upload/image/2017920/165123228566.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf002c/", "exdata": {"id": 171640, "oid": "59fa7e4f18521569b66dbc29", "name": "\u9ad8\u5723\u8fdc", "avatar": "https://i.linkeddb.com/upload/image/2017920/164932735319.jpg"}}, {"id": 57, "oid": "5a56e22893cc2b42a6bf002f", "type": "role", "name": "\u738b\u4e16\u5747", "avatar": "https://i.linkeddb.com/upload/image/2017920/170109871241.jpg", "url": "/movie/role/5a56e22893cc2b42a6bf002f/", "exdata": {"id": 139118, "oid": "59fa789918521569b6692e47", "name": "\u674e\u6cfd\u950b", "avatar": "https://i.linkeddb.com/person2/2fc9/f05b/2cdce261a4389909b416e501.jpg"}}], "links": [{"source": 0, "target": 1, "type": "\u670b\u53cb", "color": "734646"}, {"source": 0, "target": 2, "type": "\u670b\u53cb", "color": "734646"}, {"source": 0, "target": 3, "type": "\u517b\u5973", "color": "f2826a"}, {"source": 0, "target": 4, "type": "\u4e3b\u4ec6", "color": ""}, {"source": 0, "target": 5, "type": "\u4ec7\u4eba", "color": ""}, {"source": 0, "target": 6, "type": "\u513f\u5ab3", "color": "e3dce3"}, {"source": 0, "target": 7, "type": "\u59d0\u59b9", "color": ""}, {"source": 0, "target": 8, "type": "\u5ac2\u5b50", "color": ""}, {"source": 1, "target": 9, "type": "\u5b59\u5b50", "color": "6681ae"}, {"source": 1, "target": 10, "type": "\u6bcd\u5b50", "color": ""}, {"source": 1, "target": 5, "type": "\u513f\u5b50", "color": "f2826a"}, {"source": 1, "target": 11, "type": "\u5144\u5f1f", "color": ""}, {"source": 1, "target": 2, "type": "\u5408\u4f5c", "color": ""}, {"source": 1, "target": 3, "type": "\u5e2e\u52a9", "color": ""}, {"source": 1, "target": 4, "type": "\u4ec7\u4eba", "color": ""}, {"source": 1, "target": 7, "type": "\u60c5\u4eba", "color": "fcb7e9"}, {"source": 4, "target": 11, "type": "\u7ade\u4e89", "color": ""}, {"source": 4, "target": 5, "type": "\u7ade\u4e89", "color": ""}, {"source": 4, "target": 6, "type": "\u513f\u5b50", "color": "f2826a"}, {"source": 4, "target": 8, "type": "\u5144\u59b9", "color": ""}, {"source": 4, "target": 12, "type": "\u5802\u5144\u5f1f", "color": ""}, {"source": 4, "target": 2, "type": "\u6c11\u4e0e\u5b98", "color": ""}, {"source": 4, "target": 13, "type": "\u9752\u6885\u7af9\u9a6c", "color": ""}, {"source": 4, "target": 14, "type": "\u88ab\u5e2e\u52a9", "color": ""}, {"source": 2, "target": 15, "type": "\u540c\u7a97", "color": ""}, {"source": 2, "target": 14, "type": "\u5408\u4f5c", "color": ""}, {"source": 13, "target": 0, "type": "\u4ec7\u4eba\u80e1\u548f\u6885\u6b7b\u4ea1", "color": ""}, {"source": 13, "target": 14, "type": "\u6c42\u52a9", "color": ""}, {"source": 13, "target": 6, "type": "\u6c42\u52a9", "color": ""}, {"source": 14, "target": 5, "type": "\u5229\u7528", "color": ""}, {"source": 14, "target": 16, "type": "\u4e3b\u4ec6", "color": ""}, {"source": 14, "target": 0, "type": "\u4ec7\u4eba", "color": ""}, {"source": 14, "target": 1, "type": "\u4ec7\u4eba", "color": ""}, {"source": 14, "target": 6, "type": "\u8ba4\u8bc6", "color": ""}, {"source": 14, "target": 11, "type": "\u5229\u7528", "color": ""}, {"source": 8, "target": 2, "type": "\u592b\u59bb", "color": "fcb7e9"}, {"source": 8, "target": 7, "type": "\u4e92\u770b\u4e0d\u60ef", "color": ""}, {"source": 8, "target": 15, "type": "\u5144\u59b9", "color": ""}, {"source": 17, "target": 0, "type": "\u5408\u4f5c", "color": ""}, {"source": 17, "target": 1, "type": "\u7edd\u4ea4", "color": ""}, {"source": 15, "target": 0, "type": "\u5ac2\u5b50", "color": ""}, {"source": 18, "target": 0, "type": "\u4ec6\u4eba", "color": ""}, {"source": 12, "target": 14, "type": "\u88ab\u5e2e\u52a9", "color": ""}, {"source": 10, "target": 9, "type": "\u5a46\u5ab3", "color": ""}]}';
    

    实例化案例核心代码

          let configs = {
            nodes: rolesData.nodes,
            links: rolesData.links,
            width: 375,
            height: 600,
            // isHighLight:false,
            // isScale:false
          };
          new D3MakeSvgPicClass('roleMapId',configs);
    

    相关文章

      网友评论

        本文标题:d3.js - force力学图

        本文链接:https://www.haomeiwen.com/subject/fetqqftx.html