求助:如何在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>
关键修改说明
- 保存原始数据:将加载的JSON数据深拷贝到
originalData,确保每次过滤都基于原始数据,避免数据污染。 - 封装渲染函数:将力导向图的渲染逻辑抽离为
renderGraph函数,复用初始化和更新逻辑。 - 筛选逻辑:
- 节点:筛选出角色在选中复选框列表中的节点。
- 链接:仅保留两端节点都在显示列表中的链接,避免出现孤立的链接。
- D3数据绑定机制:使用
enter()添加新元素,exit().remove()移除不需要的元素,实现动态增删。 - 链接映射处理:由于原始数据中链接的
source和target是节点索引,需要转换为对应节点对象,符合D3力导向图的数据要求。
内容的提问来源于stack exchange,提问作者Rania Alaoui
相关产品推荐
相关产品推荐

