数据可视化 D3 力导向图右键菜单实现动态添加节点

前段时间将力导向图节点绑定上了菜单,实现了节点能够右击展开菜单功能这次更新实现动态更新节点数据。

本次使用的D3 版本是V4

merger:d3v4的函数更新神器

  由于在v4版本中nodes的x、y坐标和加速度vx、vy只在nodes中计算一次,所有在变成有节点或连线增加的时候,必须重新执行一次force.nodes(nodes)和force(‘link’, d3.forceLink(links)),初始化节点的数据结构。
  如果在v3版本中,只需在布局初始化时执行即可,在d3会在每次force.start()方法执行时重新初始化一次节点和连线的数据结构,这是一个特别需要注意的地方,另外在v4版本中start方法被遗弃,需使用restart方法。


好的话不多说,下面是完整代码

代码实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>The End</title>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.js"></script>
    <script src="./smartMenu.js"></script>
    <link rel="stylesheet" href="./smartMenu.css">
</head>
<body>

<div id="tpContainer">
</div>

</body>
<script>
	//json 是我的数据这些数据就不放上来了
  var json = {};
  var json2 = {};
  var json3 = {};
  
  var nodes = [];
  var lines = [];
  var result = {};
  var MacIdList = [];
  var CarIdList = [];
  var IMSIIdList = [];
  console.log(result);
  result.nodes = nodes;
  result.lines = lines;

  var getParam = "0A749C8A25EC"

  main();
  function main() {
    theTypeIs(getParam);
  }
  //判断方法,判断引入的数据是什么格式的
  function theTypeIs(getParam) {
    var result = false;
    if (getParam.length == 7){
      var express = /^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[DF])|([DF]([A-HJ-NP-Z0-9])[0-9]{4})))|([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$/;
      result = express.test(getParam);
      if(result == true){
        alert("Is car");
        initCAR(getParam)
      }else{
        alert("请核对您的输入内容");
      }
    }else if(getParam.length == 12){
      var express = /^[A-Fa-f0-9]{2}[A-Fa-f0-9]{2}[A-Fa-f0-9]{2}[A-Fa-f0-9]{2}[A-Fa-f0-9]{2}[A-Fa-f0-9]{2}$/;
      result = express.test(getParam);
      if(result == true){
        alert("Is MAC");
        initMAC(getParam)
      }else{
        alert("请核对您的输入内容");
      }
    }else if(getParam.length == 15 ){
      var express = /^4600[0,1,2,3,5,6,7]\d{10}$/;
      result = express.test(getParam);
      if(result == true){
        alert("Is IMSI");
        initIMSI(getParam)
      }else{
        alert("请核对您的输入内容");
      }
    }else{
      alert("请核对您的输入内容");
    }
    return result;
  }

  //判断根据不同的引入数据,定义初始节点的图片不同
  function initMAC(params) {
    nodes.push({"name":params,"image":"./person.png"})
    lines.push({"source":0,"target":0});
    drawToPo(nodes,lines)
  }
  function initCAR(params) {
    nodes.push({"name":params,"image":"./car.png"})
    lines.push({"source":0,"target":0});
    drawToPo(nodes,lines)
  }
  function initIMSI(params) {
    nodes.push({"name":params,"image":"./phone.png"})
    lines.push({"source":0,"target":0});
    drawToPo(nodes,lines)
  }

function drawToPo(nodes,lines) {
  //定义初始坐标
  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;
  
  //定义svg画布
  var svg = d3.select("#tpContainer")
      .append("svg")
      .attr("id","svg")
      .attr("width", width)
      .attr("height", height);

  //拆分子节点
  var view = svg.append("g")
    .attr("class", "graphCon");
  
  //开始力向布局
  var force = d3.forceSimulation(nodes)
    .force("charge", d3.forceManyBody().strength(-300))
    .force("link", d3.forceLink(lines).distance(150))
    .force("center",d3.forceCenter(width/2, height/2));
  
  //初始化缩放方法
  var zoom = d3.zoom()
  .scaleExtent([0.1, 10]).on("zoom", zoomed);
  
  //画布开始缩放
  svg.call(zoom)
  .on("dblclick.zoom", () => {}); //禁止双击放大
 
  //初始化拖拽方法
  var drag = d3.drag()
  .on("start", dragStarted)
  .on("drag", dragging)
  .on("end", dragEnded);

  //为class为node的group元素添加id
  var node = view.selectAll("g.node").data(nodes, function (d) {
    return d.id || (d.id = ++i);
  });

  //添加group元素并设置class为node
  var nodeEnter = node
      .enter()
      .append("g")
      .attr("class", "node")
      .attr("id", d => d.id)
      .attr("name", d => d.name);

  //设置连线
  var edges_line = nodeEnter.append("line")
  .data(lines)
  .attr("class", "line")
  .style("stroke", "#ccc")
  .style("stroke-width", 1);
  
  //添加加节点并以图标代替
  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 nodes_text = nodeEnter.append("text")
  .data(nodes)
  .attr("class", "nodetext")
  .attr("dx", text_dx)
  .attr("dy", text_dy)
  .text(function (d) {return d.name;});

  //力向布局监听:更新节点连线文字说明坐标
  force.on("tick", function() {
    
    nodes_img
      .attr("x", function(d) {
        return d.x - img_w / 2;
      }).attr("y", function(d) {
        return d.y - img_h / 2;
      });
      
    nodes_text.attr("x", function (d) {
        return d.x;
      }).attr("y", function (d) {
        return d.y + img_h / 2;
      });

    edges_line
    .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;
    });

  });
  // 定义菜单选项
  var userMenuData = [
    [{
        text: "MAC伴随",
        func: function () {
          var id = Number($(this).attr("id"));
          var isID = $.inArray(id,MacIdList);
          console.log(MacIdList);
          if (isID == -1) {
            MacIdList.push(id);
            insertMAC(id);
          }else{
            alert("该节点已经Mac了");
          }
        }
      },
      {
        text: "车辆伴随",
        func: function () {
          var id = Number($(this).attr("id"));
          var isID = $.inArray(id,CarIdList);
          console.log(CarIdList);
          if (isID == -1) {
            CarIdList.push(id);
            insertCAR(id);
          }else{
            alert("该节点已经Mac了");
          }
        }
      },
      {
        text: "IMSI伴随",
        func: function () {
          var id = Number($(this).attr("id"));
          var isID = $.inArray(id,IMSIIdList);
          console.log(IMSIIdList);
          if (isID == -1) {
            IMSIIdList.push(id);
            insertIMSI(id);
          }else{
            alert("该节点已经Mac了");
          }
        }
      }
    ]
  ];
  // 事件监听方式添加事件绑定
  $("body").smartMenu(userMenuData, {
    name: "chatRightControl",
    container: "g.node"
  });

    //插入mac数据
    function insertMAC(id,name) {
    var root = json.macStatistics;
    for(var i = 0;i<root.length;i++){
    nodes.push({"name":root[i].key,"image":"./person.png"})
    };
    for(var i = 0;i<root.length;i++){
    lines.push({"source": id-1,"target": lines.length});
    }
    console.log(result);
    update(result.nodes,result.lines);
    }

    //插入车牌数据
    function insertCAR(id,name) {
    var root = json2.macStatistics;
    for(var i = 0;i<root.length;i++){
    nodes.push({"name":root[i].key,"image":"./car.png"})
    };
    for(var i = 0;i<root.length;i++){
    lines.push({"source": id-1,"target": lines.length});
    }
    console.log(result);
    update(result.nodes,result.lines);
    }

    //插入IMSI数据
    function insertIMSI(id,name) {
    var root = json3.macStatistics;
    for(var i = 0;i<root.length;i++){
    nodes.push({"name":root[i].key,"image":"./phone.png"})
    };
    for(var i = 0;i<root.length;i++){
    lines.push({"source": id-1,"target": lines.length});
    }
    console.log(result);
    update(result.nodes,result.lines);
    }

    //实现动态更新数据
    function update(nodes,lines) {
    console.log(result);
    d3.select("#graphCon").selectAll("*");
    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)
    .attr("name", d => d.name);
    // 添加mouseover事件

    edges_line = nodeEnter.append("line")
      .data(lines)
      .attr("class", "line")
      .style("stroke", "#ccc")
      .style("stroke-width", 1)
      .merge(edges_line);

    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;})
      .merge(nodes_img)
      .call(drag);
    
    nodes_text = nodeEnter.append("text")
        .data(nodes)
        .attr("class", "nodetext")
        .attr("dx", text_dx)
        .attr("dy", text_dy)
        .text(function (d) {return d.name;})
        .merge(nodes_text);
    
    //在动态添加节点后,必须重启力布局
    force.alphaDecay(0.001);
    force.nodes(nodes);
    force.force("link", d3.forceLink(lines).distance(150));
    force.restart();
  }
  function dragStarted(d) {
    if (!d3.event.active) force.alpha(0.5).restart();
    d.fx = d.x;
    d.fy = d.y;
  }
  function dragging(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }
  function dragEnded(d) {
    if (!d3.event.active) force.alpha(0.5).restart();
    d.fx = null;
    d.fy = null;
  }
  function zoomed() {
    var transform = d3.event.transform;
    view.attr('transform',transform); 
  }
}
  window.oncontextmenu = function(event){
    event.preventDefault();
  }
</script>
 
</html>

菜单的js和css样式我用的是jquery插件,有需要的同学请私信我

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

猜你喜欢

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