数据可视化 D3 力导向图鼠标右击菜单的制作及缩放(zoom)和拖拽(drag)的应用

先展示一波最终效果

最终效果

在这里插入图片描述

这段时间再学习D3,力导向图

力导向图(Force-Directed Graph),是绘图的一种算法。在二维或三维空间里配置节点,节点之间用线连接,称为连线。各连线的长度几乎相等,且尽可能不相交。节点和连线都被施加了力的作用,力是根据节点和连线的相对位置计算的。根据力的作用,来计算节点和连线的运动轨迹,并不断降低它们的能量,最终达到一种能量很低的安定状态。

力导向图能表示节点之间的多对多的关系。

定义一个力导向图的布局关键方法 force()
d3在v3和v4版本上对于 force() 这个方法变动很大

话不多说,代码如下:(注意:这次使用的D3.js版本是V3的)

页面代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>The END</title>
  <script src="http://d3js.org/d3.v3.min.js"></script>
  <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
  <script src="./js/smartMenu.js"></script>
  <script src="./fun.js"></script>
  <link rel="stylesheet" href="./css/smartMenu.css">
</head>
<body>
  <div id="tpContainer">

  </div>   
 
</body>
</html>

力导向图代码 fun.js

//获取数据
d3.json('./preson.json', function(error,data){
  if (error) {
      return console.log(error);
    }
  // console.log(data);
  drawToPofun(data.nodes,data.lines);
});

function drawToPofun(nodes,edges){
  
  //定义初始变量
  var width  = 1503;
  var height = 654;
  var img_w = 40;
  var img_h = 40;
  var text_dx = -20;
  var text_dy = 20;
  var i = 0;

  //初始化拖拽方法
  var drag = d3.behavior.drag()
    .origin(function(d) {
      return d;
    })
    .on("dragstart", dragstarted)
    .on("drag", dragged)
    .on("dragend", dragended);

  //初始化缩放方法
  var zoom=d3.behavior.zoom()
    .scaleExtent([-10,10]).on("zoom",zoomed);

  //定义画布
  var svg = d3.select("#tpContainer")
  .append("svg")
  .attr("width",width)
  .attr("height",height)
  .call(zoom)
  .on("dblclick.zoom", () => {});//禁止双击放大
  
  //拆分子节点
  var view = svg.append("g")
  .attr("class","graphCon");
  
  var node = view.selectAll("g.node").data(nodes,function (d) {
    return d.id || (d.id = ++i);
  });

  var nodeEnter = node
  .enter()
  .append("g")
  .attr("class","node")
  .attr("id",d=>d.id)//添加节点,以自定义图标代替
  var nodes_img = nodeEnter.append("image")
  .data(nodes)
  .attr("width",function(d){
      return img_w;
    })
  .attr("height",function(d){
      return img_h;
    })
  .attr("xlink:href",function(d){
    return d.image;
    })
  .call(drag)//开始拖拽方法
  
  //添加连线
  var edges_line = nodeEnter.append("line")
  .data(edges)
  .attr("class","line")
  .style("stroke","#ccc")
  .style("stroke-width",1);

  //添加节点说明(文本)
  var nodes_text = nodeEnter.append("text")
  .data(nodes)
  .attr("class","nodetext")
  .attr("dx",text_dx)
  .attr("dy",text_dy)
  .text(function(d){
    return d.name;
  });

  //定义力学图布局
   var force  = d3.layout.force().nodes(nodes)
    .links(edges)
    .size([width,height])
    .linkDistance(200)
    .charge(-800)
    .start()
    .on("tick", function(){
      
      //限制结点的边界
      nodes.forEach(function(d,i){
        d.x = d.x - img_w/2 < 0? img_w/2 : d.x ;
        d.x = d.x + img_w/2 > width ? width - img_w/2 : 
        d.x ; 
        d.y = d.y - img_h/2 < 0? img_h/2 : d.y ;
        d.y = d.y + img_h/2 + text_dy > height ? 
        height - img_h/2 - text_dy : d.y ;
      });

      //更新连接线的位置
      edges_line.attr("x1",function(d){ return d.source.x; });
      edges_line.attr("y1",function(d){ return d.source.y; });
      edges_line.attr("x2",function(d){ return d.target.x; });
      edges_line.attr("y2",function(d){ return d.target.y; });
      //更新结点图片和文字
      nodes_img.attr("x",function(d){
        return d.x - img_w/2; 
      });
      nodes_img.attr("y",function(d){ 
        return d.y - img_h/2; 
      });                  
      nodes_text.attr("x",function(d){ return d.x; });
      nodes_text.attr("y",function(d){ 
        return d.y + img_h/2; 
      });
    }); 

    // 定义菜单选项
    var userMenuData = [
      [{
          text: "菜单1",
          func: function () {
            var id = Number($(this).attr("id"))
            alert("菜单1"+",No."+ id)
          }
        },
        {
          text: "菜单2",
          func: function () {
            var id = Number($(this).attr("id"))
            alert("菜单2"+",No."+ id)
          }
        },
        {
          text: "菜单3",
          func: function () {
            var id = Number($(this).attr("id"))
            alert("菜单3"+",No."+ id)
          }
        } 
      ]
    ];
    // 事件监听方式添加事件绑定
    $("body").smartMenu(userMenuData, {
      name: "chatRightControl",
      container: "g.node"
    });
    
    function zoomed(){
      view.attr("transform",
      "translate("+d3.event.translate+")scale(" +
      d3.event.scale + ")");
    }
    function dragstarted(d) {
      d3.event.sourceEvent.stopPropagation();
      d3.select(this).classed("dragging", true);
      force.start();
    }
    function dragged(d) {
      d.x = d3.event.x;
      d.y = d3.event.y;
    }
    function dragended(d) {
      d3.select(this).classed("dragging", false);
    }
}

数据格式 preson.json

{
  "nodes": [
    {"name": "preson1", "image": "./person.png"}, 
    {"name": "preson2", "image": "./person.png"}, 
    {"name": "preson3", "image": "./person.png"}, 
    {"name": "preson4", "image": "./person.png"}, 
    {"name": "preson5", "image": "./person.png"}, 
    {"name": "preson6", "image": "./person.png"},
    {"name": "preson7", "image": "./person.png"},
    {"name": "preson8", "image": "./person.png"},
    {"name": "preson9", "image": "./person.png"},
    {"name": "preson10","image": "./person.png"}
],
"lines": [
  { "source": 0 , "target": 1 },
  { "source": 0 , "target": 2 },
  { "source": 0 , "target": 3 },
  { "source": 1 , "target": 4 },
  { "source": 1 , "target": 5 },
  { "source": 1 , "target": 6 },
  { "source": 0 , "target": 7 },
  { "source": 7 , "target": 8 },
  { "source": 7 , "target": 9 }
]
}

菜单我用的是jQuery插件,下载下来的,js/css代码就不展示了。

发布了79 篇原创文章 · 获赞 89 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_39141486/article/details/102940383