GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Respect explicit node x/y positions in fixed-arrangement Sankey by kyo5uke · Pull Request #7826 · plotly/plotly.js

Conversation

@kyo5uke

@kyo5uke kyo5uke commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Overview

Fixes the bug reported in #7758: when a Sankey trace specifies explicit node.x / node.y
positions (typically with arrangement: 'fixed'), the coordinates were applied to the wrong
nodes whenever the trace contained an unlinked (isolated) node or grouped nodes. In the issue's
example, right_a — specified at (0.9, 0.05) (top-right) — rendered at the bottom-left.

Root cause

The "Force node position" loop in src/traces/sankey/render.js applied the explicit positions by
array position, assuming graph.nodes[i] corresponds to input node i:

for(i = 0; i < Math.min(trace.node.x.length, trace.node.y.length, graph.nodes.length); i++) {
    if(trace.node.x[i] && trace.node.y[i]) { /* ... graph.nodes[i] ... */ }
}

That assumption does not hold:

  • calc.js skips unlinked nodes when building the node list (if(!linkedNodes[i]) continue;), so
    isolated nodes are absent from graph.nodes.
  • render.js unshifts transient group-child nodes onto the front of graph.nodes.

Either case shifts every later node's array position away from its original input index. In the
issue's data, left_r (index 17) has no links, so it is dropped from graph.nodes; every node
after it is then off by one, and right_a (index 18) receives left_r's coordinate (0.1, 0.82)
— the bottom-left position shown in the report.

Each node already stores its original input index as pointNumber (set in calc.js), so indexing
the explicit positions by pointNumber instead of array position restores the correct mapping.

Change

src/traces/sankey/render.js — iterate over all graph.nodes and look up the explicit position by
the node's own pointNumber:

for(i = 0; i < graph.nodes.length; i++) {
    var forcedNode = graph.nodes[i];
    if(forcedNode.partOfGroup) continue;

    var p = forcedNode.pointNumber;
    if(trace.node.x[p] && trace.node.y[p]) { /* ... forcedNode ... */ }
}
  • Synthetic group nodes have pointNumber >= nodeCount, so trace.node.x[p] is undefined and
    they are skipped by the existing truthy guard.
  • Transient group-children (partOfGroup) are invisible (filtered out before drawing, opacity 0)
    and carry no links, so they are skipped explicitly.

Testing

  • npx @biomejs/biome lint src/traces/sankey/render.js passes.
  • I was not able to run the full Jasmine / image suite locally (heavy browser build), so I have
    not run the test suite. The change is a no-op for the existing sankey_x_y and
    sankey_x_y_with_arrows image mocks — all of their nodes are linked and there are no groups, so
    pointNumber === array index for every node and the assigned positions are identical. The other
    sankey mocks don't set both node.x and node.y, so they never enter this branch. I'd expect the
    image baselines to be unchanged, but I'm relying on CI to confirm.
  • Happy to add a regression test if you'd like — e.g. an arrangement: 'fixed' trace with an
    unlinked node placed before a linked one, asserting the linked node lands at its specified
    coordinate.

I'll add the draftlogs/<PR number>_fix.md changelog entry in a follow-up commit per the
contributing guide.

Note

There is a related path I deliberately left out of scope: persistFinalNodePositions (~L799) also
walks graph.nodes positionally to write node.x / node.y back after a drag, so the same
index/order mismatch could affect the saved positions when isolated or grouped nodes are present.
That only surfaces through drag interaction (not the static rendering reported here) and is a more
involved change, so I kept this PR to the rendering fix. Happy to follow up on it separately if
that's useful.

Closes #7758

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
kyo5uke added 2 commits June 6, 2026 05:54
@kyo5uke
Fix explicit node positions in fixed-arrangement Sankey
bf95567
@kyo5uke
Add draftlog for plotly#7826
86084f9
@camdecoster camdecoster self-assigned this Jul 2, 2026
@camdecoster

camdecoster commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Thanks for the PR! I'll take a look and follow up with you.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

camdecoster
camdecoster requested changes Jul 15, 2026
View reviewed changes

@camdecoster camdecoster left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Good bug hunting. I requested some changes, but I'd also like to see if you could address the similar issue (that you mentioned) in persistFinalNodePositions. It would also be good to have a regression test, even if it's just a mock.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/traces/sankey/render.js Outdated
if(forcedNode.partOfGroup) continue;

var p = forcedNode.pointNumber;
if(trace.node.x[p] && trace.node.y[p]) {

@camdecoster camdecoster Jul 15, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think 0 coords will get dropped with the existing check (preexisting bug). Let's update it. This should take care of null and undefined.

Suggested change
if(trace.node.x[p] && trace.node.y[p]) {
if(trace.node.x[p] != null && trace.node.y[p] != null) {

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/traces/sankey/render.js
Comment on lines +247 to +250
// graph.nodes omits unlinked nodes and may have transient group
// children unshifted to its front, so its order does not match
// trace.node.x/y. Index the explicit positions by each node's
// original pointNumber rather than by array position.

@camdecoster camdecoster Jul 15, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// graph.nodes omits unlinked nodes and may have transient group
// children unshifted to its front, so its order does not match
// trace.node.x/y. Index the explicit positions by each node's
// original pointNumber rather than by array position.
// Index the positions by `pointNumber` to avoid issues with
// array order diverging from node order

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@camdecoster

camdecoster commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@kyo5uke are you still interested in finishing this PR?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

camdecoster added 5 commits October 27, 2026 11:31
@camdecoster
Fix explicit Sankey node positions with unlinked or grouped nodes
c07bbf4
@camdecoster
Add tests
0c7b1ea
@camdecoster
Update draftlog
6dbfa2d
@camdecoster
Merge remote-tracking branch 'origin/main' into sankey-fixed-node-pos…
6ed737a
…itions

# Conflicts:
#	test/jasmine/tests/sankey_test.js
@camdecoster
Revert handling of zero coords
dc26aa9
@camdecoster camdecoster mentioned this pull request Oct 27, 2026
[BUG]: sankey: node.x / node.y of 0 is ignored #8150
Open
@camdecoster
Fix test
e6ea65d
camdecoster
camdecoster approved these changes Oct 27, 2026
View reviewed changes
@camdecoster
camdecoster merged commit f752b3e into plotly:main Oct 27, 2026
87 of 88 checks passed

Uh oh!

There was an error while loading. Please reload this page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@camdecoster camdecoster camdecoster approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@camdecoster camdecoster

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Explicit node coordinates (x/y positions) not fully respected in Sankey diagram

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@kyo5uke @camdecoster
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.