You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:如何在D3.js中通过复选框增删节点链接而非修改透明度

D3.js力导向图动态增删节点与链接实现方案

问题背景

现有基于D3.js v3的力导向图代码,当前通过复选框切换时仅调整节点和链接的透明度实现过滤。需求变更为:复选框切换时动态添加或移除对应节点及关联链接,而非仅修改视觉样式。


原代码及数据

原HTML/CSS/JS代码

<style>
.link {
  stroke: #ccc;
  stroke-width: 1.5px;
  fill:transparent;
}
</style>
<body>
      <div>
          <input type="checkbox" value="Application Server" id="applicationServer" name="check" checked>
          <label for="applicationServer">Application Server</label>
      </div>
      <div>
          <input type="checkbox" value="Access Switch" id="acessSwitch" name="check" checked>
          <label for="acessSwitch">Access Switch</label>
      </div>
      <div>
          <input type="checkbox" value="Distribution Switch" id="distSwitch" name="check" checked>
          <label for="distSwitch">Distribution Switch</label>
      </div>

<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var width = 1060,
    height = 500

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);
 
var color = d3.scale.category20();

var force = d3.layout.force()
    .gravity(0.05)
    .distance(60)
    .charge(-100)
    .size([width, height]);

d3.json("data2.json", function(error, json) {
  if (error) throw error;

  force
      .nodes(json.nodes)
      .links(json.links)
      .start();

  var link = svg.selectAll(".link")
      .data(json.links)
    .enter().append("line")
      .attr("class", "link");

  var node = svg.selectAll(".node")
      .data(json.nodes)
    .enter().append("g")
      .attr("class", "node")
      .call(force.drag);

  var circles = node.append("circle")
    .attr("r", 8)
    .attr("fill", function(d) { return color(d.group); });

  d3.selectAll("input[name=check]").on("change", function() {
    function getCheckedBoxes(chkboxName) {
            var checkboxes = document.getElementsByName(chkboxName);
            var checkboxesChecked = [];
            for (var i=0; i<checkboxes.length; i++) {
               if (checkboxes[i].checked) {
                  checkboxesChecked.push(checkboxes[i].defaultValue);
               }
            }
            return checkboxesChecked.length > 0 ? checkboxesChecked : " ";
          }

    var checkedBoxes = getCheckedBoxes("check");
    
    node.style("opacity", 1);
    link.style("opacity", 1);
    
    node.filter(function(d) {
        return checkedBoxes.indexOf(d.role) === -1;
      })
      .style("opacity", "0");

    link.filter(function(d) {
        return checkedBoxes.indexOf(d.source.type) === -1 && 
        checkedBoxes.indexOf(d.target.type) === -1;
      })
      .style("opacity", "0.2");

    link.filter(function(d) {
        return checkedBoxes.indexOf(d.source.type) > -1 && 
        checkedBoxes.indexOf(d.target.type) > -1;
      })
      .style("opacity", "0.2");
  
  });

  force.on("tick", function() {
    link.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; });

    node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
  });
});
</script>
</body>

原data2.json数据

{
  "nodes": [
        {"id": 1, "name": "Beric", "role": "Application Server", "group": 2}, 
        {"id": 2, "name": "dmi01-stamford-sw01", "role": "Access Switch", "group": 5}, 
        {"id": 4, "name": "ncsu118-distswitch1", "role": "Distribution Switch", "group": 6}, 
        {"id": 8, "name": "ncsu128-distswitch1", "role": "Distribution Switch", "group": 6}
    ],
  "links": [
        {"source": 1, "target": 2}, 
        {"source": 4, "target": 8}, 
        {"source": 4, "target": 2}
    ]
}

修改后的解决方案代码

核心思路是保留原始数据副本,每次复选框切换时重新筛选要显示的节点和链接,通过D3的enter()/exit()机制动态增删元素,并重启动力导向图。

<style>
.link {
  stroke: #ccc;
  stroke-width: 1.5px;
  fill: transparent;
}
</style>
<body>
      <div>
          <input type="checkbox" value="Application Server" id="applicationServer" name="check" checked>
          <label for="applicationServer">Application Server</label>
      </div>
      <div>
          <input type="checkbox" value="Access Switch" id="acessSwitch" name="check" checked>
          <label for="acessSwitch">Access Switch</label>
      </div>
      <div>
          <input type="checkbox" value="Distribution Switch" id="distSwitch" name="check" checked>
          <label for="distSwitch">Distribution Switch</label>
      </div>

<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var width = 1060,
    height = 500

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);
 
var color = d3.scale.category20();

var force = d3.layout.force()
    .gravity(0.05)
    .distance(60)
    .charge(-100)
    .size([width, height]);

// 保存原始数据副本,避免过滤时修改原数据
var originalData;

d3.json("data2.json", function(error, json) {
  if (error) throw error;
  originalData = JSON.parse(JSON.stringify(json));
  // 初始化渲染全部节点和链接
  renderGraph(originalData.nodes, originalData.links);

  d3.selectAll("input[name=check]").on("change", function() {
    function getCheckedBoxes(chkboxName) {
      var checkboxes = document.getElementsByName(chkboxName);
      var checkboxesChecked = [];
      for (var i=0; i<checkboxes.length; i++) {
         if (checkboxes[i].checked) {
            checkboxesChecked.push(checkboxes[i].defaultValue);
         }
      }
      return checkboxesChecked;
    }

    var checkedRoles = getCheckedBoxes("check");
    
    // 筛选符合条件的节点
    var filteredNodes = originalData.nodes.filter(function(d) {
      return checkedRoles.indexOf(d.role) !== -1;
    });

    // 创建节点ID映射,方便筛选链接
    var nodeIds = filteredNodes.map(function(d) { return d.id; });
    
    // 筛选两端节点都在显示列表中的链接
    var filteredLinks = originalData.links.filter(function(link) {
      // 原始数据中source/target是索引,先转换为节点对象的id
      var sourceId = originalData.nodes[link.source].id;
      var targetId = originalData.nodes[link.target].id;
      return nodeIds.indexOf(sourceId) !== -1 && nodeIds.indexOf(targetId) !== -1;
    });

    // 更新力导向图数据并重新渲染
    renderGraph(filteredNodes, filteredLinks);
  });
});

// 封装渲染/更新力导向图的函数
function renderGraph(nodes, links) {
  // 处理链接:将source/target从索引转换为节点对象(D3力导向图要求)
  var mappedLinks = links.map(function(link) {
    return {
      source: nodes.find(function(n) { return n.id === originalData.nodes[link.source].id; }),
      target: nodes.find(function(n) { return n.id === originalData.nodes[link.target].id; })
    };
  });

  // 更新力导向图数据
  force
    .nodes(nodes)
    .links(mappedLinks)
    .start();

  // 更新链接元素
  var link = svg.selectAll(".link")
    .data(mappedLinks);

  // 移除不需要的链接
  link.exit().remove();

  // 添加新的链接
  link.enter().append("line")
    .attr("class", "link");

  // 更新节点元素
  var node = svg.selectAll(".node")
    .data(nodes);

  // 移除不需要的节点
  node.exit().remove();

  // 添加新的节点组
  var newNodes = node.enter().append("g")
    .attr("class", "node")
    .call(force.drag);

  // 给新节点添加圆形
  newNodes.append("circle")
    .attr("r", 8)
    .attr("fill", function(d) { return color(d.group); });

  // 更新tick事件,确保元素位置正确
  force.on("tick", function() {
    link.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; });

    node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
  });
}
</script>
</body>

关键修改说明

  1. 保存原始数据:将加载的JSON数据深拷贝到originalData,确保每次过滤都基于原始数据,避免数据污染。
  2. 封装渲染函数:将力导向图的渲染逻辑抽离为renderGraph函数,复用初始化和更新逻辑。
  3. 筛选逻辑:
    • 节点:筛选出角色在选中复选框列表中的节点。
    • 链接:仅保留两端节点都在显示列表中的链接,避免出现孤立的链接。
  4. D3数据绑定机制:使用enter()添加新元素,exit().remove()移除不需要的元素,实现动态增删。
  5. 链接映射处理:由于原始数据中链接的source和target是节点索引,需要转换为对应节点对象,符合D3力导向图的数据要求。

内容的提问来源于stack exchange,提问作者Rania Alaoui

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 17:15:49