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

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&amp;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&amp;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>

关键改动说明:

  1. CSS交互优化:

    • 给.link添加transition属性,让透明度变化更平滑自然
    • 给节点矩形添加cursor: pointer,明确提示用户该元素可交互
  2. 节点鼠标事件绑定:

    • 在节点矩形上绑定mouseover和mouseout事件:
      • mouseover:通过节点x坐标判断是左侧源节点还是右侧目标节点,筛选对应链接并高亮(透明度设为0.8),其余链接保持低透明度
      • mouseout:将所有链接的透明度恢复为初始的0.2
    • 用节点的node属性精准匹配链接的source.node或target.node,确保高亮逻辑准确
  3. 代码结构梳理:

    • 拆分了节点和链接的创建代码,确保link变量能被事件处理函数正常访问
    • 保留了你原本的配色、节点样式等核心设置,只新增交互逻辑

这样修改后,就能完美实现你想要的悬停高亮效果啦!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 16:12:29