美文网首页数据可视化让前端飞Web前端之路
深入浅出d3.js数据可视化之道(7)

深入浅出d3.js数据可视化之道(7)

作者: 金字笙调 | 来源:发表于2017-11-21 22:22 被阅读119次

    今天给大家带来的是比较常见的力导图。力导图的应用场景很多,表示关系连接的很多都可以用力导图来实现,比如企业投资关系,汽车行程路线等等。

    为了尽可能模拟工作中使用力导图的场景,我们这里直接使用json 数据来完成今天力导图的制作。

    模拟数据 和 初始化画布

    const width = 500,
              height = 500;
    
    var svg  = d3.select('body')
                  .append('svg')
                  .attr('width', width)
                  .attr('height', height);
    
    var nodes = [ 
        { name: "XiaMen" },
        { name: "BeiJing"}, 
        { name: "XiAn" }, 
        { name: "HangZhou"},
        { name: "ShangHai"}, 
        { name: "QingDao"},
        { name: "NanJing"},
        { name: "QueShan"}
    
    ];
    
    var links = [  
            { source : 'BeiJing'  , target: "XiaMen" } , 
            { source : 'BeiJing'  , target: "XiAn" } , 
            { source : 'BeiJing'  , target: "XiaMen" } , 
            { source : 'BeiJing'  , target: "HangZhou" } , 
            { source : 'BeiJing'  , target: "ShangHai" } , 
            { source : 'BeiJing'  , target: "QingDao" } , 
            { source : 'BeiJing'  , target: "NanJing" }, 
            { source : 'QueShan'  , target: "XiaMen" } , 
            { source : 'QueShan'  , target: "XiAn" } , 
            { source : 'QueShan'  , target: "XiaMen" } , 
            { source : 'QueShan'  , target: "HangZhou" } , 
            { source : 'QueShan'  , target: "ShangHai" } , 
            { source : 'QueShan'  , target: "QingDao" } , 
            { source : 'QueShan'  , target: "NanJing" } 
    ];  
    

    通过布局将原始数据转化为我们便于绘制力导图的格式。

    通过内置的函数,我们将数据进行转化,d3 v3到v4,由之前的d3.layout.force()变成了现在的d3.forceSimulation().

    var simulation = d3.forceSimulation(nodes) // 根据指定的节点数组创建一个没有作用力的仿真
                    .force("link", d3.forceLink(links).distance(100).strength(1).id((d)=>d.name))  // 连线作用力
                    .force("charge",d3.forceManyBody())  // 节点间的作用力
                    .force("center",d3.forceCenter(width / 2, height / 2)); //重力,布局的参考位置,力导向图的中心点
    

    具体这几个api 可以查看这里,在这里着重提一个 id(d) => d.name,这里使用name属性字段来进行各个节点的连接,也可以把它设置成实际项目的中的字段。

    经过转换 nodes 值
    和初始值对比,转换过后多了5个属性。
    • index 节点的索引
    • x 节点当前x坐标
    • y 节点当前y坐标
    • vx 节点当前x速度
    • vy 节点当前y速度

    把节点和连接加入到dom中

    var color = d3.scaleOrdinal(d3.schemeCategory20);  
    
    / 绘制连线
    var svg_links = svg.selectAll("line")
                .data(links)
                .enter()
                .append("line")
                .style("stroke","#ccc")
                .style("stroke-width",1)
    
    // 绘制节点
     var svg_nodes = svg.selectAll("circle")
                .data(nodes)
                .enter()
                .append("circle")
                .attr("r",10)
                .style("fill",function(d,i){
                    return color(i);
                })    
                .attr("cx",function(d){return d.x;})
                .attr("cy",function(d){return d.y;})
    
    // 绘制文字
    var svg_text = svg.selectAll("text")
                .data(nodes)
                .enter()
                .append("text")
                .style("fill","#000")
                .attr('font-size', '12px')
                .attr("dx",0)
                .attr("dy",20)
                .attr('text-anchor', 'middle')
                .text(function(d){return d.name;});
    
    当前画布

    在进行到这一步时,当前画布上的所有节点和线和文字都堆在左上角,这是由于此时采用了初始化布局计算的位置,接下来,添加tick方法,将其计算并调整到合适的布局。

        function draw(){
                svg_nodes
                    .attr("cx",function(d){return d.x;})
                    .attr("cy",function(d){return d.y;});
    
                svg_text
                    .attr("x", function(d){ return d.x; })
                        .attr("y", function(d){ return d.y; });
    
                svg_links
                    .attr("x1",function(d){return d.source.x; })
                    .attr("y1",function(d){ return d.source.y; })
                    .attr("x2",function(d){ return d.target.x; })
                    .attr("y2",function(d){ return d.target.y; });
        }
    
          simulation.on("tick",draw); 
    

    力导向图布局的形成是一个异步的过程,而且需要一定时间。而tick会在节点增加减少,事件操作时都会触发,实时计算。

    此时的布局

    添加拖动

    接下来,给力导向图添加拖拽事件。

    var svg_nodes = svg.selectAll("circle")
                .data(nodes)
                .enter()
                .append("circle")
                .attr("r",10)
                .style("fill",function(d,i){
                    return color(i);
                })    
                .attr("cx",function(d){return d.x;})
                .attr("cy",function(d){return d.y;})
                .call(d3.drag().on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended));
    
           function dragstarted(d) {
              if (!d3.event.active) simulation.alphaTarget(0.8).restart(); // 在交互时重新启动仿真,比如拖拽了某个节点或使用simulation.stop暂停仿真之后进行重新调整布局
              d.fx = d.x;
              d.fy = d.y;
            };
    
            function dragged(d) {
                d.fx = d3.event.x; // 设置当前位置
                d.fy = d3.event.y;
            };
    
            function dragended(d) { 
                if (!d3.event.active) simulation.alphaTarget(0);  //  阻止仿真继续计算位置
                d.fx = null;
                d.fy = null;
            };
    

    d3.event.active代表的是除去当前事件,当前正在发生的拖动事件的个数。在dragStart的时候,如果没有其他的拖拽事件,那么d3.event.active的将会是 0,仿真模拟计算将会被启动,各个点的位置将依次被计算;同样的道理,如果在dragended的时候,d3.event.active的如果是 0,说明计算的是最后一个点,此时可以关闭仿真模拟,不再计算。
    事实上,如果不在每次拖拽过后手动关闭仿真模拟,那么计算将会一直持续下去,再也不能完成第二次拖拽。而在拖拽开始时不判断开启仿真模拟,那么你一次拖动也不能完成。


    最后的效果

    接下来,我们可以给他们添加一些箭头

       //添加defs标签  
        var defs = svg.append("defs");  
        //添加marker标签及其属性  
        var arrowMarker = defs.append("marker")  
            .attr("id","arrow")  
            .attr("markerUnits","strokeWidth")  
            .attr("markerWidth",12)  
            .attr("markerHeight",12)  
            .attr("viewBox","0 0 12 12")  
            .attr("refX",20)  
            .attr("refY",6)  
            .attr("orient","auto")
    
        //绘制直线箭头  
        var arrow_path = "M2,2 L10,6 L2,10 L6,6 L2,2";  
        arrowMarker.append("path")  
            .attr("d",arrow_path)  
            .attr("fill","red") 
    
    最后的效果
    源码地址在这里,目前正在学习webgl,希望有时间能够和大家分享一下。接下来将会继续更新d3以及svg相关的知识,同时在之前一段时间内掌握到的操纵svg的技巧也会一并穿插在文章中,敬请期待。

    相关文章

      网友评论

        本文标题:深入浅出d3.js数据可视化之道(7)

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