How to Specify Node Label Position for Sankey Diagram in Plotly
Asked Answered
M

1

17

I am trying to figure out if there is a way to specify the location of the node's labels for a Plotly Sankey diagram in python. In the image linked below I have the diagram with the labels automatically to the right of each node, however I am trying to see if I can have them completely on the left side of the node.

I have searched all over for documentation but there doesn't seem to be any additional specifications to the nodes labels, but I might be missing something. Ideally it would be great if it is possible to position them more than just right or left of node, however having them on the left side is the initial goal.

Plotly Sankey Diagram Example

enter image description here

Mesitylene answered 25/11, 2020 at 21:24 Comment(1)
Additional information. Here is the python code I have for the full diagram. I still am not sure how or if you are able to change the location of the node's labels.Mesitylene
I
4

As of march 2023, Plotly still does not provide a way to specify the positioning of sankey node labels, but there is a way to override their coordinates using some javascript, which we can embed into python so the solution below works for both Plotly.js and Plotly.py (live demo).

First we need to introduce two parameters :

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

By default, all labels are positioned to the right of their respective node, except for the last layer of nodes whose labels are positioned to the left (I guess to prevent text overflow and maximize the spacing between nodes), forcePos allows precisely to specify whether or not to force label placement regardless of the node layer.

The following function realigns all node labels according to these parameters :

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);
  }
}

The idea is to trigger that function each time the chart is (re)plotted, for this purpose the plotly_afterplot event fits perfectly :

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

For sankey charts built with Plotly.js, that's it.


For sankey charts built with Plotly.py, all we need is to embed this javascript code into a string which we can pass to plotly's Figure.show() method via the post_script parameter. The only difference is for identifying the graph div whose id is generated in this case, so we use the placeholder {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])

NB. Passing a post script to show() works only with renderers that output HTML (a static export is not going to run javascript obviously). We can also pass a post script when using the methods write_html() or to_html().


Sankey Diagram example with position='left' and forcePos=true :

screenshot

Integral answered 7/3, 2023 at 13:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.