plotly/plotly.js · warning

Node <nodeIndex> is already part of a group.

Error message

Node <nodeIndex> is already part of a group.

What it means

Plotly emits this warning during sankey preprocessing in convertToD3Sankey (src/traces/sankey/calc.js:50) when the trace's `arrangement.snap`/grouping attribute (`node.groups`) lists the same node index in more than one group. Only the first group containing the node is honored; later duplicates are ignored, so the grouping silently differs from what was specified.

Source

Thrown at src/traces/sankey/calc.js:50

    for(i = 0; i < linkSpec.value.length; i++) {
        if(linkSpec.source[i] > maxNodeId) maxNodeId = linkSpec.source[i];
        if(linkSpec.target[i] > maxNodeId) maxNodeId = linkSpec.target[i];
    }
    var nodeCount = maxNodeId + 1;
    trace.node._count = nodeCount;

    // Group nodes
    var j;
    var groups = trace.node.groups;
    var groupLookup = {};
    for(i = 0; i < groups.length; i++) {
        var group = groups[i];
        // Build a lookup table to quickly find in which group a node is
        for(j = 0; j < group.length; j++) {
            var nodeIndex = group[j];
            var groupIndex = nodeCount + i;
            if(groupLookup.hasOwnProperty(nodeIndex)) {
                Lib.warn('Node ' + nodeIndex + ' is already part of a group.');
            } else {
                groupLookup[nodeIndex] = groupIndex;
            }
        }
    }

    // Process links
    var groupedLinks = {
        source: [],
        target: []
    };
    for(i = 0; i < linkSpec.value.length; i++) {
        var val = linkSpec.value[i];
        // remove negative values, but keep zeros with special treatment
        var source = linkSpec.source[i];
        var target = linkSpec.target[i];
        if(!(val > 0 && isIndex(source, nodeCount) && isIndex(target, nodeCount))) {
            continue;

View on GitHub (pinned to 1d090e0b5f)

Solutions

  1. Remove the duplicate node index from all but one entry of node.groups so each node belongs to at most one group.
  2. Deduplicate programmatically before assigning: keep the first group that contains a node and drop it from the rest.
  3. If the overlap is intentional grouping semantics, restructure the links/nodes so one group suffices.

Example fix

// before
node: { groups: [[0, 1, 2], [2, 3, 4]] } // node 2 duplicated
// after
node: { groups: [[0, 1, 2], [3, 4]] }
Defensive patterns

Strategy: validation

Validate before calling

const seen = new Set();
for (const g of nodeGroups) {
  for (const n of g) {
    if (seen.has(n)) throw new Error('Node ' + n + ' appears in multiple groups');
    seen.add(n);
  }
}

Type guard

function groupsAreDisjoint(groups) {
  const seen = new Set();
  return groups.every(g => g.every(n => !seen.has(n) && seen.add(n)));
}

Prevention

When it happens

Trigger: Setting layout/trace sankey `node.groups` (array of arrays of node indices) where the same node index appears in two or more sub-arrays, e.g. groups: [[0,1],[1,2]].

Common situations: Hand-built group arrays assembled programmatically or by merging user selections where overlap was not deduplicated; sankey configs generated by tools that assume groups are disjoint.

Related errors


AI-assisted analysis of plotly/plotly.js@1d090e0b5f (2026-09-02). Data as JSON: /api/errors/4603382c22226ea7. Report an issue: GitHub.