D3.js桑基图:如何高亮节点关联的链路集合?
实现D3.js Sankey图节点悬停高亮对应链接的交互
没问题!要实现你想要的悬停左侧节点高亮所有出发链接、悬停右侧节点高亮所有指向链接的交互效果,我们可以通过给节点绑定鼠标事件,结合节点位置判断来筛选并切换链接的透明度。下面是修改后的完整代码,我会在后面拆解关键改动:
<!DOCTYPE html> <html> <head> <script src="https://d3js.org/d3.v4.min.js"></script> <link href="https://fonts.googleapis.com/css2?family=Assistant:wght@700&display=swap" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/gh/holtzy/D3-graph-gallery@master/LIB/sankey.js"></script> <style> @import url('https://fonts.googleapis.com/css2?family=Assistant:wght@600&display=swap'); text { font-family: 'Assistant', sans-serif } text { font-size: 14px } .link { fill: none; stroke: #000; stroke-opacity: .2; transition: stroke-opacity 0.3s ease; } .node rect { cursor: pointer; } </style> </head> <body> <div id="chart"></div> <script> // set the dimensions and margins of the graph var margin = { top: 75, right: 10, bottom: 10, left: 10 }, width = 900 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; // append the svg object to the body of the page var svg = d3.select("#chart").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); //Color scale used var color = d3.scaleOrdinal().range(["#002060ff", "#164490ff", "#4d75bcff", "#98b3e6ff", "#d5e2feff", "#008cb0ff"]); // Set the sankey diagram properties var sankey = d3.sankey() .nodeWidth(175) .nodePadding(10) .size([width, height]); // load the data var data = { "years": [{ "year": 2019 }, { "year": 2020 } ], "nodes": [{ "node": 0, "id": "a", "name": "A" }, { "node": 1, "id": "b", "name": "B" }, { "node": 2, "id": "c", "name": "C" }, { "node": 3, "id": "d", "name": "D" }, { "node": 4, "id": "e", "name": "E" }, { "node": 5, "id": "f", "name": "F" }, { "node": 6, "id": "a", "name": "A" }, { "node": 7, "id": "b", "name": "B" }, { "node": 8, "id": "c", "name": "C" }, { "node": 9, "id": "d", "name": "D" }, { "node": 10, "id": "e", "name": "E" }, { "node": 11, "id": "f", "name": "F" } ], "links": [{ "source": 0, "target": 6, "value": 20 }, { "source": 0, "target": 7, "value": 10 }, { "source": 0, "target": 8, "value": 10 }, { "source": 0, "target": 9, "value": 10 }, { "source": 0, "target": 10, "value": 20 }, { "source": 0, "target": 11, "value": 10 }, { "source": 1, "target": 6, "value": 40 }, { "source": 1, "target": 7, "value": 10 }, { "source": 1, "target": 8, "value": 10 }, { "source": 1, "target": 9, "value": 10 }, { "source": 1, "target": 10, "value": 10 }, { "source": 1, "target": 11, "value": 10 }, { "source": 2, "target": 6, "value": 20 }, { "source": 2, "target": 7, "value": 10 }, { "source": 2, "target": 8, "value": 10 }, { "source": 2, "target": 9, "value": 10 }, { "source": 2, "target": 10, "value": 10 }, { "source": 2, "target": 11, "value": 10 }, { "source": 3, "target": 6, "value": 20 }, { "source": 3, "target": 7, "value": 10 }, { "source": 3, "target": 8, "value": 10 }, { "source": 3, "target": 9, "value": 10 }, { "source": 3, "target": 10, "value": 10 }, { "source": 3, "target": 11, "value": 10 }, { "source": 4, "target": 6, "value": 20 }, { "source": 4, "target": 7, "value": 10 }, { "source": 4, "target": 8, "value": 10 }, { "source": 4, "target": 9, "value": 10 }, { "source": 4, "target": 10, "value": 10 }, { "source": 4, "target": 11, "value": 10 }, { "source": 5, "target": 6, "value": 20 }, { "source": 5, "target": 7, "value": 10 }, { "source": 5, "target": 8, "value": 10 }, { "source": 5, "target": 9, "value": 10 }, { "source": 5, "target": 10, "value": 10 }, { "source": 5, "target": 11, "value": 10 } ] } // Constructs a new Sankey sankey .nodes(data.nodes) .links(data.links) .layout(0); // add in the links var link = svg.append("g") .selectAll(".link") .data(data.links) .enter() .append("path") .attr("class", "link") .attr("d", sankey.link()) .style("stroke-width", function(d) { return Math.max(1, d.dy); }) .sort(function(a, b) { return b.dy - a.dy; }); // add in the nodes var node = svg.append("g") .selectAll(".node") .data(data.nodes) .enter().append("g") .attr("class", "node") .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); // add the rectangles for the nodes node .append("rect") .attr("height", function(d) { return d.dy; }) .attr("width", sankey.nodeWidth()) .attr("rx", 3) .attr("ry", 3) .style("fill", function(d) { return d.color = color(d.id.replace(/ .*/, "")); }) // 添加鼠标交互事件 .on("mouseover", function(event, d) { // 通过x坐标区分左侧/右侧节点 const isLeftNode = d.x < width / 2; if (isLeftNode) { // 高亮所有从当前节点出发的链接 link.style("stroke-opacity", l => l.source.node === d.node ? 0.8 : 0.2); } else { // 高亮所有指向当前节点的链接 link.style("stroke-opacity", l => l.target.node === d.node ? 0.8 : 0.2); } }) .on("mouseout", function() { // 恢复所有链接的初始透明度 link.style("stroke-opacity", 0.2); }) // Add hover text .append("title") .text(function(d) { return d.name + "\n" + d.value }); // add in the title for the nodes node .append("text") .attr("x", 87) .attr("y", function(d) { return d.dy / 2; }) .attr("dy", ".35em") .attr("text-anchor", "middle") .text(function(d) { return d.name; }) .style("fill", "white"); </script> </body> </html>
关键改动说明:
CSS交互优化:
- 给
.link添加transition属性,让透明度变化更平滑自然 - 给节点矩形添加
cursor: pointer,明确提示用户该元素可交互
- 给
节点鼠标事件绑定:
- 在节点矩形上绑定
mouseover和mouseout事件:- mouseover:通过节点x坐标判断是左侧源节点还是右侧目标节点,筛选对应链接并高亮(透明度设为0.8),其余链接保持低透明度
- mouseout:将所有链接的透明度恢复为初始的0.2
- 用节点的
node属性精准匹配链接的source.node或target.node,确保高亮逻辑准确
- 在节点矩形上绑定
代码结构梳理:
- 拆分了节点和链接的创建代码,确保
link变量能被事件处理函数正常访问 - 保留了你原本的配色、节点样式等核心设置,只新增交互逻辑
- 拆分了节点和链接的创建代码,确保
这样修改后,就能完美实现你想要的悬停高亮效果啦!
内容的提问来源于stack exchange,提问作者Gangrel
相关产品推荐
相关产品推荐

