如何在 Plotly 中为 Sankey 图指定节点标签位置

Bry*_*ger 7 python plotly sankey-diagram

我想弄清楚是否有办法在 python 中为 Plotly Sankey 图指定节点标签的位置。在下面链接的图像中,我在每个节点的右侧自动放置了带有标签的图表,但是我想看看是否可以将它们完全放在节点的左侧。

我已经到处搜索文档,但节点标签似乎没有任何其他规范,但我可能遗漏了一些东西。理想情况下,如果可以将它们定位在节点的右侧或左侧,那将会很棒,但是将它们放在左侧是最初的目标。

情节桑基图示例

Eri*_*ult 3

截至 2023 年 3 月,Plotly 仍然没有提供指定 sankey 节点标签位置的方法,但是有一种方法可以使用一些 javascript 覆盖它们的坐标,我们可以将其嵌入到 python 中,因此下面的解决方案适用于 Plotly.js和 Plotly.py(现场演示)。

首先我们需要引入两个参数:

  • position - 'left' | 'center' | 'right'
  • forcePos - true | false

默认情况下,所有标签都位于各自节点的右侧,除了最后一层节点的标签位于左侧(我猜是为了防止文本溢出并最大化节点之间的间距),允许精确指定是否forcePos或无论节点层如何,都不强制放置标签。

以下函数根据这些参数重新排列所有节点标签:

const TEXTPAD = 3; // constant used in Plotly.js

function sankeyNodeLabelsAlign(position, forcePos) {
  const textAnchor = {left: 'end', right: 'start', center: 'middle'}[position];
  const nodes = gd.getElementsByClassName('sankey-node');

  for (const node of nodes) {
    const d = node.__data__;
    const label = node.getElementsByClassName('node-label').item(0);

    // Ensure to reset any previous modifications
    label.setAttribute('x', 0);

    if (!d.horizontal)
      continue;

    // This is how Plotly's default text positioning is computed (coordinates
    // are relative to that of the cooresponding node).
    const padX = d.nodeLineWidth / 2 + TEXTPAD;
    const posX = padX + d.visibleWidth;
    let x;

    switch (position) {
      case 'left':
        if (d.left || d.node.originalLayer === 0 && !forcePos)
          continue;
        x = -posX - padX;
        break;

      case 'right':
        if (!d.left || !forcePos)
          continue;
        x = posX + padX;
        break;

      case 'center':
        if (!forcePos && (d.left || d.node.originalLayer === 0))
          continue;
        x = (d.nodeLineWidth + d.visibleWidth)/2 + (d.left ? padX : -posX);
        break;
    }

    label.setAttribute('x', x);
    label.setAttribute('text-anchor', textAnchor);
  }
}
Run Code Online (Sandbox Code Playgroud)

这个想法是在每次(重新)绘制图表时触发该函数,为此,事件plotly_afterplot非常适合:

const gd = document.getElementById('graphDivId');
const position = 'left';
const forcePos = true;

gd.on('plotly_afterplot', sankeyNodeLabelsAlign.bind(gd, position, forcePos));
gd.emit('plotly_afterplot'); // manual trigger for initial rendering
Run Code Online (Sandbox Code Playgroud)

对于使用 Plotly.js 构建的桑基图表,就是这样。


对于使用 Plotly.py 构建的桑基图表,我们需要的只是将此 javascript 代码嵌入到一个字符串中,我们可以Figure.show()通过参数将其传递给plotly 的方法post_script。唯一的区别是为了识别在本例中生成 id 的图 div,因此我们使用占位符{plot_id}

fig = go.Figure(data, layout)

js = '''
const TEXTPAD = 3; // constant used by Plotly.js

function sankeyNodeLabelsAlign(position, forcePos) { ... }

const gd = document.getElementById('{plot_id}');
const position = 'left';
const forcePos = true;

gd.on('plotly_afterplot', sankeyNodeLabelsAlign.bind(gd, position, forcePos));
gd.emit('plotly_afterplot');
'''

fig.show(post_script=[js])
Run Code Online (Sandbox Code Playgroud)

注意。传递后脚本show()仅适用于输出 HTML 的渲染器(静态导出显然不会运行 javascript)。write_html()我们还可以在使用方法或时传递后脚本to_html()


position='left'带有和 的桑基图示例forcePos=true

截屏