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

Plotly.js桑基图链接添加数值标签的实现疑问

Plotly.js桑基图链接添加数值标签的实现疑问

你好呀!刚看了你的问题和代码,你尝试给桑基图的link对象加label属性来显示链接数值,但没生效——这其实是因为Plotly.js的Sankey组件目前并不支持直接通过link.label来渲染链接上的文本标签,这个属性和node.label的作用不一样,它不会被可视化出来,所以你加了也不会显示~

不过别担心,我们可以用Plotly的annotations(注释功能)来手动实现这个需求,通过计算每个链接的中心位置,把数值(或自定义文本)作为注释添加到对应位置。下面我基于你的代码修改了一个可运行的版本,你可以参考:

window.addEventListener('DOMContentLoaded', () => {
  const chartDiv = document.getElementById('chartDiv');
  const labels = ["Start", "Middle", "Begin", "End", "Final"];
  const labelIndex = new Map(labels.map((label, i) => [label, i]));
  const links = [
    { source: "Start", target: "Middle", value: 5, label: "Test" },
    { source: "Start", target: "Middle", value: 3, label: "Test2" },
    { source: "Middle", target: "Start", value: 1, label: "" },
    { source: "Start", target: "End", value: 2, label: "" },
    { source: "Begin", target: "Middle", value: 5, label: "Test" },
    { source: "Middle", target: "End", value: 3, label: "" },
    { source: "Final", target: "Final", value: 0.0001, label: "" }
  ];

  const sources = links.map(link => labelIndex.get(link.source));
  const targets = links.map(link => labelIndex.get(link.target));
  const values = links.map(link => link.value);
  const customData = links.map(link => [link.source, link.target, link.value]);

  const trace = {
    type: "sankey",
    orientation: "h",
    arrangement: "fixed",
    node: {
      label: labels,
      pad: 15,
      thickness: 20,
      line: { color: "black", width: 0.5 },
      hoverlabel: {
        bgcolor: "white",
        bordercolor: "darkgrey",
        font: { color: "black", family: "Open Sans, Arial", size: 14 }
      },
      hovertemplate: '%{label}<extra></extra>',
      color: ["#a6cee3", "#1f78b4", "#b2df8a", "#a9b1b9", "#a9b1b9"]
    },
    link: {
      source: sources,
      target: targets,
      value: values,
      arrowlen: 20,
      pad: 20,
      thickness: 20,
      line: { color: "black", width: 0.2 },
      color: sources.map(i => ["#a6cee3", "#1f78b4", "#b2df8a", "#a9b1b9", "#a9b1b9"][i]),
      customdata: customData,
      hoverlabel: {
        bgcolor: "white",
        bordercolor: "darkgrey",
        font: { color: "black", family: "Open Sans, Arial", size: 14 }
      },
      hovertemplate: '<b>%{customdata[0]}</b> → <b>%{customdata[1]}</b><br>' +
        'Flow Value: <b>%{customdata[2]}</b><extra></extra>'
    }
  };

  // 计算每个链接的注释内容与位置
  const annotations = [];
  // 固定布局下,节点x坐标均匀分布(0到1的相对位置)
  const nodeXPositions = labels.map((_, i) => i / (labels.length - 1));
  // 水平桑基图中,节点默认在y=0.5的水平线上
  const nodeYPositions = Array(labels.length).fill(0.5);

  links.forEach((link, idx) => {
    const sourceIdx = labelIndex.get(link.source);
    const targetIdx = labelIndex.get(link.target);

    // 跳过极小值的自循环链接,避免无意义标签
    if (link.value <= 0.0001 && sourceIdx === targetIdx) return;

    // 计算链接中点的相对坐标
    const midX = (nodeXPositions[sourceIdx] + nodeXPositions[targetIdx]) / 2;
    const midY = nodeYPositions[sourceIdx];

    annotations.push({
      x: midX,
      y: midY,
      text: link.value.toString(), // 可替换为link.label显示自定义文本
      showarrow: false,
      font: { size: 12, color: '#222' },
      xanchor: 'center',
      yanchor: 'middle'
    });
  });

  const layout = {
    font: { size: 14 },
    annotations: annotations // 将注释加入布局
  };

  Plotly.newPlot(chartDiv, [trace], layout, { responsive: true, displayModeBar: true })
    .then((plot) => {
      chartDiv.on('plotly_click', function(eventData) {
        console.log(eventData);
        if (!eventData || !eventData.points || !eventData.points.length) return;
        const point = eventData.points[0];
        if (typeof point.pointIndex === "number") {
          const nodeLabel = point.label;
          alert("Node clicked: " + nodeLabel + "\nNode index: " + point.pointIndex);
          console.log("Node clicked:", point);
        } else if (typeof point.pointNumber === "number") {
          const linkIdx = point.pointNumber;
          const linkData = customData[linkIdx];
          alert(
            "Link clicked: " + linkData[0] + " → " + linkData[1] + "\nValue: " + linkData[2] + "\nLink index: " + linkIdx
          );
          console.log("Link clicked:", point);
        } else {
          console.log("Clicked background", point);
        }
      });
    });
});

关键说明

  1. 为什么link.label不生效?
    Plotly的Sankey组件中,link对象的label属性并不是用于渲染可视化文本的,它和node.label的设计逻辑不同,官方文档里也没有把它作为可渲染的属性列出,所以直接设置不会有效果。

  2. 注释方案的核心逻辑
    因为你用了arrangement: "fixed"(固定节点布局),我们可以手动计算每个节点的相对坐标,再通过源节点和目标节点的坐标算出链接中点,把数值(或你定义的link.label)作为无箭头注释放在中点位置。

  3. 特殊情况处理
    代码里跳过了Final→Final这种极小值的自循环链接,你可以根据自己的需求调整过滤条件;如果是垂直方向的桑基图,只需要把x/y坐标的计算逻辑互换即可。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 03:10:25