From 5887787506f1c2c9f1c8f7fae75691fe31ce07be Mon Sep 17 00:00:00 2001 From: wfr Date: Fri, 26 Jun 2026 07:25:10 +0200 Subject: [PATCH 1/8] Add bottom-up and right-left orientations to Sankey Rebase to v4. --- src/traces/sankey/attributes.js | 11 +++++-- src/traces/sankey/plot.js | 9 +++++- src/traces/sankey/render.js | 37 +++++++++++++++++++----- test/jasmine/tests/sankey_test.js | 48 +++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 10 deletions(-) diff --git a/src/traces/sankey/attributes.js b/src/traces/sankey/attributes.js index 60b7cb3e419..abfbb0fbdb0 100644 --- a/src/traces/sankey/attributes.js +++ b/src/traces/sankey/attributes.js @@ -31,9 +31,16 @@ var attrs = (module.exports = overrideAll( orientation: { valType: 'enumerated', - values: ['v', 'h'], + values: ['v', 'h', 'left-right', 'right-left', 'top-down', 'bottom-up'], dflt: 'h', - description: 'Sets the orientation of the Sankey diagram.' + description: [ + 'Sets the orientation of the Sankey diagram.', + '`left-right` (synonym of the legacy value `h`) places sources on the left', + 'with the flow running rightward; `right-left` places sources on the right', + 'with the flow running leftward; `top-down` (synonym of the legacy value `v`)', + 'places sources at the top with the flow running downward; `bottom-up` places', + 'sources at the bottom with the flow running upward.' + ].join(' ') }, valueformat: { diff --git a/src/traces/sankey/plot.js b/src/traces/sankey/plot.js index 8b179c767cb..96e209d56be 100644 --- a/src/traces/sankey/plot.js +++ b/src/traces/sankey/plot.js @@ -193,8 +193,15 @@ module.exports = function plot(gd, calcData) { hoverCenterX = (link.source.x1 + link.target.x0) / 2; hoverCenterY = (link.y0 + link.y1) / 2; } + var orientation = link.trace.orientation; var center = [hoverCenterX, hoverCenterY]; - if(link.trace.orientation === 'v') center.reverse(); + // Vertical orientations transpose x/y to match the group transform. + if(orientation === 'v' || orientation === 'top-down' || orientation === 'bottom-up') { + center.reverse(); + } + // bottom-up / right-left additionally mirror the flow axis (matching the translate). + if(orientation === 'bottom-up') center[1] = d.parent.height - center[1]; + if(orientation === 'right-left') center[0] = d.parent.width - center[0]; center[0] += d.parent.translateX; center[1] += d.parent.translateY; return center; diff --git a/src/traces/sankey/render.js b/src/traces/sankey/render.js index 5f31b5d1d20..8513679972d 100644 --- a/src/traces/sankey/render.js +++ b/src/traces/sankey/render.js @@ -31,7 +31,11 @@ function sankeyModel(layout, d, traceIndex) { var calcData = unwrap(d); var trace = calcData.trace; var domain = trace.domain; - var horizontal = trace.orientation === 'h'; + var horizontal = trace.orientation === 'h' || + trace.orientation === 'left-right' || + trace.orientation === 'right-left'; + var rightLeft = trace.orientation === 'right-left'; + var bottomUp = trace.orientation === 'bottom-up'; var nodePad = trace.node.pad; var nodeThickness = trace.node.thickness; var nodeAlign = { @@ -285,6 +289,8 @@ function sankeyModel(layout, d, traceIndex) { trace: trace, guid: Lib.randstr(), horizontal: horizontal, + rightLeft: rightLeft, + bottomUp: bottomUp, width: width, height: height, nodePad: trace.node.pad, @@ -588,6 +594,8 @@ function nodeModel(d, n) { sizeAcross: d.width, forceLayouts: d.forceLayouts, horizontal: d.horizontal, + rightLeft: d.rightLeft, + bottomUp: d.bottomUp, darkBackground: Color.color(n.color).isDark(), rgb: Color.rgb(n.color), alpha: Color.color(n.color).alpha(), @@ -629,8 +637,21 @@ function sizeNode(rect) { function salientEnough(d) {return (d.link.width > 1 || d.linkLineWidth > 0);} function sankeyTransform(d) { - var offset = strTranslate(d.translateX, d.translateY); - return offset + (d.horizontal ? 'matrix(1 0 0 1 0 0)' : 'matrix(0 1 1 0 0 0)'); + if(d.horizontal) { + if(d.rightLeft) { + // right-left: sources on the right, flow leftward; horizontal mirror of left-right. + return strTranslate(d.translateX + d.width, d.translateY) + 'matrix(-1 0 0 1 0 0)'; + } + // h / left-right: sources on the left, flow rightward. + return strTranslate(d.translateX, d.translateY) + 'matrix(1 0 0 1 0 0)'; + } + if(d.bottomUp) { + // bottom-up: sources at the bottom, flow upward; a vertical mirror of top-down. + // Pure 90deg rotation (det +1) keeps the cross axis intact. + return strTranslate(d.translateX, d.translateY + d.height) + 'matrix(0 -1 1 0 0 0)'; + } + // top-down (also 'v'): reflection about y=x, sources at the top, flow downward. + return strTranslate(d.translateX, d.translateY) + 'matrix(0 1 1 0 0 0)'; } // event handling @@ -1049,7 +1070,8 @@ module.exports = function(gd, svg, calcData, layout, callbacks) { svgTextUtils.convertToTspans(e, gd); }) .attr('text-anchor', function(d) { - return (d.horizontal && d.left) ? 'end' : 'start'; + // right-left mirrors the layout horizontally, so the outer side (and anchor) flips. + return (d.horizontal && (d.left !== d.rightLeft)) ? 'end' : 'start'; }) .attr('transform', function(d) { var e = d3.select(this); @@ -1069,9 +1091,10 @@ module.exports = function(gd, svg, calcData, layout, callbacks) { } } - var flipText = d.horizontal ? '' : ( - 'scale(-1,1)' + strRotate(90) - ); + var flipText = d.horizontal ? + (d.rightLeft ? 'scale(-1,1)' : '') : ( + d.bottomUp ? strRotate(90) : ('scale(-1,1)' + strRotate(90)) + ); return strTranslate( d.horizontal ? posX : posY, diff --git a/test/jasmine/tests/sankey_test.js b/test/jasmine/tests/sankey_test.js index 1d326af1399..db7f98aa16d 100644 --- a/test/jasmine/tests/sankey_test.js +++ b/test/jasmine/tests/sankey_test.js @@ -138,6 +138,15 @@ describe('sankey tests', function () { expect(fullTrace.domain.y).toEqual(attributes.domain.y.dflt, 'y domain by default'); }); + it('coerces the vertical orientation values', function() { + ['h', 'v', 'left-right', 'right-left', 'top-down', 'bottom-up'].forEach(function(o) { + expect(_supply({orientation: o}).orientation) + .toBe(o, o + ' is a valid orientation'); + }); + expect(_supply({orientation: 'sideways'}).orientation) + .toBe(attributes.orientation.dflt, 'invalid orientation falls back to default'); + }); + it("'Sankey' layout dependent specification should have proper types", function () { var fullTrace = _supplyWithLayout( {}, @@ -376,6 +385,45 @@ describe('sankey tests', function () { }); afterEach(destroyGraphDiv); + it('applies the correct group transform per orientation', function(done) { + function groupTransform() { + return d3Select('.sankey').attr('transform'); + } + function plotWith(orientation) { + var fig = Lib.extendDeep({}, mock); + fig.data[0].orientation = orientation; + // newPlot re-enters the trace, so the transform is set synchronously + // (no mid-transition interpolation to race against). + return Plotly.newPlot(gd, fig); + } + + plotWith('h') + .then(function() { + expect(groupTransform()).toContain('matrix(1 0 0 1 0 0)'); + return plotWith('left-right'); // legacy synonym of h + }) + .then(function() { + expect(groupTransform()).toContain('matrix(1 0 0 1 0 0)'); + return plotWith('right-left'); + }) + .then(function() { + expect(groupTransform()).toContain('matrix(-1 0 0 1 0 0)'); + return plotWith('top-down'); + }) + .then(function() { + expect(groupTransform()).toContain('matrix(0 1 1 0 0 0)'); + return plotWith('v'); // legacy synonym of top-down + }) + .then(function() { + expect(groupTransform()).toContain('matrix(0 1 1 0 0 0)'); + return plotWith('bottom-up'); + }) + .then(function() { + expect(groupTransform()).toContain('matrix(0 -1 1 0 0 0)'); + }) + .then(done, done.fail); + }); + it('Plotly.deleteTraces with two traces removes the deleted plot', function (done) { var mockCopy = Lib.extendDeep({}, mock); var mockCopy2 = Lib.extendDeep({}, mockDark); From 0758cdb1e3742a5054775520cfe8ce449f899d93 Mon Sep 17 00:00:00 2001 From: wfr Date: Fri, 26 Jun 2026 09:15:36 +0200 Subject: [PATCH 2/8] Add bottom-up and right-left orientations to Sankey Adjust node label alignment for vertical case (labels below still need work) --- src/traces/sankey/render.js | 42 ++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/traces/sankey/render.js b/src/traces/sankey/render.js index 8513679972d..7ae183e5c8e 100644 --- a/src/traces/sankey/render.js +++ b/src/traces/sankey/render.js @@ -1070,8 +1070,10 @@ module.exports = function(gd, svg, calcData, layout, callbacks) { svgTextUtils.convertToTspans(e, gd); }) .attr('text-anchor', function(d) { - // right-left mirrors the layout horizontally, so the outer side (and anchor) flips. - return (d.horizontal && (d.left !== d.rightLeft)) ? 'end' : 'start'; + // vertical: labels are centered over the node. horizontal: aligned to the outer + // edge (right-left mirrors the layout, so the outer side and anchor flip). + if(!d.horizontal) return 'middle'; + return (d.left !== d.rightLeft) ? 'end' : 'start'; }) .attr('transform', function(d) { var e = d3.select(this); @@ -1081,25 +1083,27 @@ module.exports = function(gd, svg, calcData, layout, callbacks) { (nLines - 1) * LINE_SPACING - CAP_SHIFT ); - var posX = d.nodeLineWidth / 2 + TEXTPAD; - var posY = ((d.horizontal ? d.visibleHeight : d.visibleWidth) - blockHeight) / 2; - if(d.horizontal) { - if(d.left) { - posX = -posX; - } else { - posX += d.visibleWidth; - } - } + var pad = d.nodeLineWidth / 2 + TEXTPAD; - var flipText = d.horizontal ? - (d.rightLeft ? 'scale(-1,1)' : '') : ( - d.bottomUp ? strRotate(90) : ('scale(-1,1)' + strRotate(90)) - ); + if(!d.horizontal) { + var across = d.visibleHeight / 2; + var gap = pad + CAP_SHIFT * d.textFont.size; + // letzte Spalte (originalLayer === 1): Label nach innen, damit es nicht ueber den + // aeusseren Plot-Rand laeuft - analog zum horizontalen d.left-Fall. + var outside = d.left ? -gap : (d.visibleWidth + gap); + var flipV = d.bottomUp ? strRotate(90) : ('scale(-1,1)' + strRotate(90)); + return strTranslate(outside, across) + flipV; + } - return strTranslate( - d.horizontal ? posX : posY, - d.horizontal ? posY : posX - ) + flipText; + // horizontal: center along the node length, place just past the thickness edge. + var posX = pad; + var posY = (d.visibleHeight - blockHeight) / 2; + if(d.left) { + posX = -posX; + } else { + posX += d.visibleWidth; + } + return strTranslate(posX, posY) + (d.rightLeft ? 'scale(-1,1)' : ''); }); nodeLabel From bdadff886e6772fa9c19694a65e6e72d15154d3c Mon Sep 17 00:00:00 2001 From: wfr Date: Fri, 26 Jun 2026 09:38:55 +0200 Subject: [PATCH 3/8] Add bottom-up and right-left orientations to Sankey Finalize node label alignment for vertical cases --- src/traces/sankey/render.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/traces/sankey/render.js b/src/traces/sankey/render.js index 7ae183e5c8e..838ff6c7e03 100644 --- a/src/traces/sankey/render.js +++ b/src/traces/sankey/render.js @@ -1086,13 +1086,12 @@ module.exports = function(gd, svg, calcData, layout, callbacks) { var pad = d.nodeLineWidth / 2 + TEXTPAD; if(!d.horizontal) { - var across = d.visibleHeight / 2; - var gap = pad + CAP_SHIFT * d.textFont.size; - // letzte Spalte (originalLayer === 1): Label nach innen, damit es nicht ueber den - // aeusseren Plot-Rand laeuft - analog zum horizontalen d.left-Fall. - var outside = d.left ? -gap : (d.visibleWidth + gap); + var posY = d.visibleHeight / 2; + // last Column (originalLayer === 1): put label towards center. + var posX = d.bottomUp ? + (d.left ? -(pad + CAP_SHIFT * d.textFont.size) : (d.visibleWidth + pad)) : (d.left ? -pad : (d.visibleWidth + pad + CAP_SHIFT * d.textFont.size)); var flipV = d.bottomUp ? strRotate(90) : ('scale(-1,1)' + strRotate(90)); - return strTranslate(outside, across) + flipV; + return strTranslate(posX, posY) + flipV; } // horizontal: center along the node length, place just past the thickness edge. From 5ea67d977c79f2c199e0060a605b5961642fb705 Mon Sep 17 00:00:00 2001 From: wfr Date: Fri, 26 Jun 2026 10:01:41 +0200 Subject: [PATCH 4/8] Add draft log --- draftlogs/7870_add.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 draftlogs/7870_add.md diff --git a/draftlogs/7870_add.md b/draftlogs/7870_add.md new file mode 100644 index 00000000000..40753530003 --- /dev/null +++ b/draftlogs/7870_add.md @@ -0,0 +1 @@ +- Add `right-left` and `bottom-up` values to Sankey `orientation`, with `left-right` and `top-down` as aliases for `h` and `v` [[#7870](https://github.com/plotly/plotly.js/pull/7870)] \ No newline at end of file From 542c55e4e0809614a882319f6ed38fb3f2364c8e Mon Sep 17 00:00:00 2001 From: wfr Date: Wed, 22 Jul 2026 11:39:29 +0200 Subject: [PATCH 5/8] Introduce `direction` attribute instead of more parameters for `orientation` --- draftlogs/7870_add.md | 2 +- src/traces/sankey/attributes.js | 21 ++++++++++++----- src/traces/sankey/defaults.js | 1 + src/traces/sankey/plot.js | 15 ++++++------ src/traces/sankey/render.js | 39 ++++++++++++++----------------- test/jasmine/tests/sankey_test.js | 29 ++++++++++++++++------- 6 files changed, 62 insertions(+), 45 deletions(-) diff --git a/draftlogs/7870_add.md b/draftlogs/7870_add.md index 40753530003..49624d6af38 100644 --- a/draftlogs/7870_add.md +++ b/draftlogs/7870_add.md @@ -1 +1 @@ -- Add `right-left` and `bottom-up` values to Sankey `orientation`, with `left-right` and `top-down` as aliases for `h` and `v` [[#7870](https://github.com/plotly/plotly.js/pull/7870)] \ No newline at end of file +- Add `direction` attribute (`forward` / `reverse`) to the Sankey trace, controlling the flow direction along the `orientation` axis: `forward` keeps sources on the left (horizontal) or top (vertical), `reverse` moves them to the right or bottom [[#7870](https://github.com/plotly/plotly.js/pull/7870)] diff --git a/src/traces/sankey/attributes.js b/src/traces/sankey/attributes.js index abfbb0fbdb0..22edd7c4e5b 100644 --- a/src/traces/sankey/attributes.js +++ b/src/traces/sankey/attributes.js @@ -31,15 +31,24 @@ var attrs = (module.exports = overrideAll( orientation: { valType: 'enumerated', - values: ['v', 'h', 'left-right', 'right-left', 'top-down', 'bottom-up'], + values: ['v', 'h'], dflt: 'h', description: [ 'Sets the orientation of the Sankey diagram.', - '`left-right` (synonym of the legacy value `h`) places sources on the left', - 'with the flow running rightward; `right-left` places sources on the right', - 'with the flow running leftward; `top-down` (synonym of the legacy value `v`)', - 'places sources at the top with the flow running downward; `bottom-up` places', - 'sources at the bottom with the flow running upward.' + 'With `h` (the default), the flow runs horizontally.', + 'With `v`, the flow runs vertically.', + 'Use `direction` to control which side the sources are placed on.' + ].join(' ') + }, + + direction: { + valType: 'enumerated', + values: ['forward', 'reverse'], + dflt: 'forward', + description: [ + 'Sets the direction of the flow along the `orientation` axis.', + 'With `forward` (the default), sources are on the left (horizontal) or top (vertical).', + 'With `reverse`, sources are on the right (horizontal) or bottom (vertical).', ].join(' ') }, diff --git a/src/traces/sankey/defaults.js b/src/traces/sankey/defaults.js index 118b58db30f..98a8e1a018e 100644 --- a/src/traces/sankey/defaults.js +++ b/src/traces/sankey/defaults.js @@ -101,6 +101,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleDomainDefaults(traceOut, layout, coerce); coerce('orientation'); + coerce('direction'); coerce('valueformat'); coerce('valuesuffix'); diff --git a/src/traces/sankey/plot.js b/src/traces/sankey/plot.js index 96e209d56be..f8b2bdf4f17 100644 --- a/src/traces/sankey/plot.js +++ b/src/traces/sankey/plot.js @@ -193,15 +193,14 @@ module.exports = function plot(gd, calcData) { hoverCenterX = (link.source.x1 + link.target.x0) / 2; hoverCenterY = (link.y0 + link.y1) / 2; } - var orientation = link.trace.orientation; + var vertical = link.trace.orientation === 'v'; + var reverse = link.trace.direction === 'reverse'; var center = [hoverCenterX, hoverCenterY]; - // Vertical orientations transpose x/y to match the group transform. - if(orientation === 'v' || orientation === 'top-down' || orientation === 'bottom-up') { - center.reverse(); - } - // bottom-up / right-left additionally mirror the flow axis (matching the translate). - if(orientation === 'bottom-up') center[1] = d.parent.height - center[1]; - if(orientation === 'right-left') center[0] = d.parent.width - center[0]; + // Vertical orientation transposes x/y to match the group transform. + if(vertical) center.reverse(); + // reverse direction additionally mirrors the flow axis (matching the translate). + if(vertical && reverse) center[1] = d.parent.height - center[1]; + if(!vertical && reverse) center[0] = d.parent.width - center[0]; center[0] += d.parent.translateX; center[1] += d.parent.translateY; return center; diff --git a/src/traces/sankey/render.js b/src/traces/sankey/render.js index 838ff6c7e03..4c6a960414b 100644 --- a/src/traces/sankey/render.js +++ b/src/traces/sankey/render.js @@ -31,11 +31,10 @@ function sankeyModel(layout, d, traceIndex) { var calcData = unwrap(d); var trace = calcData.trace; var domain = trace.domain; - var horizontal = trace.orientation === 'h' || - trace.orientation === 'left-right' || - trace.orientation === 'right-left'; - var rightLeft = trace.orientation === 'right-left'; - var bottomUp = trace.orientation === 'bottom-up'; + var horizontal = trace.orientation === 'h'; + // reverse flips the source side along the flow axis: horizontal -> sources on the + // right (flow leftward), vertical -> sources at the bottom (flow upward). + var reverse = trace.direction === 'reverse'; var nodePad = trace.node.pad; var nodeThickness = trace.node.thickness; var nodeAlign = { @@ -289,8 +288,7 @@ function sankeyModel(layout, d, traceIndex) { trace: trace, guid: Lib.randstr(), horizontal: horizontal, - rightLeft: rightLeft, - bottomUp: bottomUp, + reverse: reverse, width: width, height: height, nodePad: trace.node.pad, @@ -594,8 +592,7 @@ function nodeModel(d, n) { sizeAcross: d.width, forceLayouts: d.forceLayouts, horizontal: d.horizontal, - rightLeft: d.rightLeft, - bottomUp: d.bottomUp, + reverse: d.reverse, darkBackground: Color.color(n.color).isDark(), rgb: Color.rgb(n.color), alpha: Color.color(n.color).alpha(), @@ -638,19 +635,19 @@ function salientEnough(d) {return (d.link.width > 1 || d.linkLineWidth > 0);} function sankeyTransform(d) { if(d.horizontal) { - if(d.rightLeft) { - // right-left: sources on the right, flow leftward; horizontal mirror of left-right. + if(d.reverse) { + // horizontal + reverse: sources on the right, flow leftward; a mirror of forward. return strTranslate(d.translateX + d.width, d.translateY) + 'matrix(-1 0 0 1 0 0)'; } - // h / left-right: sources on the left, flow rightward. + // horizontal + forward: sources on the left, flow rightward. return strTranslate(d.translateX, d.translateY) + 'matrix(1 0 0 1 0 0)'; } - if(d.bottomUp) { - // bottom-up: sources at the bottom, flow upward; a vertical mirror of top-down. + if(d.reverse) { + // vertical + reverse: sources at the bottom, flow upward; a mirror of forward. // Pure 90deg rotation (det +1) keeps the cross axis intact. return strTranslate(d.translateX, d.translateY + d.height) + 'matrix(0 -1 1 0 0 0)'; } - // top-down (also 'v'): reflection about y=x, sources at the top, flow downward. + // vertical + forward: reflection about y=x, sources at the top, flow downward. return strTranslate(d.translateX, d.translateY) + 'matrix(0 1 1 0 0 0)'; } @@ -1071,9 +1068,9 @@ module.exports = function(gd, svg, calcData, layout, callbacks) { }) .attr('text-anchor', function(d) { // vertical: labels are centered over the node. horizontal: aligned to the outer - // edge (right-left mirrors the layout, so the outer side and anchor flip). + // edge (reverse mirrors the layout, so the outer side and anchor flip). if(!d.horizontal) return 'middle'; - return (d.left !== d.rightLeft) ? 'end' : 'start'; + return (d.left !== d.reverse) ? 'end' : 'start'; }) .attr('transform', function(d) { var e = d3.select(this); @@ -1088,9 +1085,9 @@ module.exports = function(gd, svg, calcData, layout, callbacks) { if(!d.horizontal) { var posY = d.visibleHeight / 2; // last Column (originalLayer === 1): put label towards center. - var posX = d.bottomUp ? + var posX = d.reverse ? (d.left ? -(pad + CAP_SHIFT * d.textFont.size) : (d.visibleWidth + pad)) : (d.left ? -pad : (d.visibleWidth + pad + CAP_SHIFT * d.textFont.size)); - var flipV = d.bottomUp ? strRotate(90) : ('scale(-1,1)' + strRotate(90)); + var flipV = d.reverse ? strRotate(90) : ('scale(-1,1)' + strRotate(90)); return strTranslate(posX, posY) + flipV; } @@ -1102,10 +1099,10 @@ module.exports = function(gd, svg, calcData, layout, callbacks) { } else { posX += d.visibleWidth; } - return strTranslate(posX, posY) + (d.rightLeft ? 'scale(-1,1)' : ''); + return strTranslate(posX, posY) + (d.reverse ? 'scale(-1,1)' : ''); }); nodeLabel .transition() .ease(c.ease).duration(c.duration); -}; +}; \ No newline at end of file diff --git a/test/jasmine/tests/sankey_test.js b/test/jasmine/tests/sankey_test.js index db7f98aa16d..435e122127c 100644 --- a/test/jasmine/tests/sankey_test.js +++ b/test/jasmine/tests/sankey_test.js @@ -138,8 +138,8 @@ describe('sankey tests', function () { expect(fullTrace.domain.y).toEqual(attributes.domain.y.dflt, 'y domain by default'); }); - it('coerces the vertical orientation values', function() { - ['h', 'v', 'left-right', 'right-left', 'top-down', 'bottom-up'].forEach(function(o) { + it('coerces the orientation values', function() { + ['h', 'v'].forEach(function(o) { expect(_supply({orientation: o}).orientation) .toBe(o, o + ' is a valid orientation'); }); @@ -147,6 +147,15 @@ describe('sankey tests', function () { .toBe(attributes.orientation.dflt, 'invalid orientation falls back to default'); }); + it('coerces the direction values', function() { + ['forward', 'reverse'].forEach(function(dir) { + expect(_supply({direction: dir}).direction) + .toBe(dir, dir + ' is a valid direction'); + }); + expect(_supply({direction: 'backward'}).direction) + .toBe(attributes.direction.dflt, 'invalid direction falls back to default'); + }); + it("'Sankey' layout dependent specification should have proper types", function () { var fullTrace = _supplyWithLayout( {}, @@ -385,13 +394,14 @@ describe('sankey tests', function () { }); afterEach(destroyGraphDiv); - it('applies the correct group transform per orientation', function(done) { + it('applies the correct group transform per orientation and direction', function(done) { function groupTransform() { return d3Select('.sankey').attr('transform'); } - function plotWith(orientation) { + function plotWith(orientation, direction) { var fig = Lib.extendDeep({}, mock); fig.data[0].orientation = orientation; + if(direction !== undefined) fig.data[0].direction = direction; // newPlot re-enters the trace, so the transform is set synchronously // (no mid-transition interpolation to race against). return Plotly.newPlot(gd, fig); @@ -399,24 +409,25 @@ describe('sankey tests', function () { plotWith('h') .then(function() { + // forward is the default direction expect(groupTransform()).toContain('matrix(1 0 0 1 0 0)'); - return plotWith('left-right'); // legacy synonym of h + return plotWith('h', 'forward'); }) .then(function() { expect(groupTransform()).toContain('matrix(1 0 0 1 0 0)'); - return plotWith('right-left'); + return plotWith('h', 'reverse'); }) .then(function() { expect(groupTransform()).toContain('matrix(-1 0 0 1 0 0)'); - return plotWith('top-down'); + return plotWith('v', 'forward'); }) .then(function() { expect(groupTransform()).toContain('matrix(0 1 1 0 0 0)'); - return plotWith('v'); // legacy synonym of top-down + return plotWith('v'); // forward is the default direction }) .then(function() { expect(groupTransform()).toContain('matrix(0 1 1 0 0 0)'); - return plotWith('bottom-up'); + return plotWith('v', 'reverse'); }) .then(function() { expect(groupTransform()).toContain('matrix(0 -1 1 0 0 0)'); From 61a556d0cffd09e94e04e14ba709f81ea8bc4097 Mon Sep 17 00:00:00 2001 From: wfr Date: Wed, 22 Jul 2026 13:37:07 +0200 Subject: [PATCH 6/8] Add tests and mocks for direction argument. --- test/image/mocks/sankey_circular_reverse.json | 22 ++++ .../image/mocks/sankey_circular_vertical.json | 22 ++++ .../sankey_circular_vertical_reverse.json | 23 ++++ .../sankey_circular_with_arrows_reverse.json | 23 ++++ ...circular_with_arrows_vertical_reverse.json | 24 ++++ test/jasmine/tests/sankey_test.js | 116 ++++++++++++++++++ 6 files changed, 230 insertions(+) create mode 100644 test/image/mocks/sankey_circular_reverse.json create mode 100644 test/image/mocks/sankey_circular_vertical.json create mode 100644 test/image/mocks/sankey_circular_vertical_reverse.json create mode 100644 test/image/mocks/sankey_circular_with_arrows_reverse.json create mode 100644 test/image/mocks/sankey_circular_with_arrows_vertical_reverse.json diff --git a/test/image/mocks/sankey_circular_reverse.json b/test/image/mocks/sankey_circular_reverse.json new file mode 100644 index 00000000000..34028be7b75 --- /dev/null +++ b/test/image/mocks/sankey_circular_reverse.json @@ -0,0 +1,22 @@ +{ + "data": [ + { + "type": "sankey", + "direction": "reverse", + "node": { + "pad": 5, + "label": ["0", "1", "2", "3", "4", "5", "6"] + }, + "link": { + "source": [0, 0, 1, 2, 5, 4, 3], + "target": [5, 3, 4, 3, 0, 2, 2], + "value": [1, 2, 1, 1, 1, 1, 1] + } + } + ], + "layout": { + "title": { "text": "Sankey with circular data" }, + "width": 800, + "height": 800 + } +} diff --git a/test/image/mocks/sankey_circular_vertical.json b/test/image/mocks/sankey_circular_vertical.json new file mode 100644 index 00000000000..ad7992c4e3b --- /dev/null +++ b/test/image/mocks/sankey_circular_vertical.json @@ -0,0 +1,22 @@ +{ + "data": [ + { + "type": "sankey", + "orientation": "v", + "node": { + "pad": 5, + "label": ["0", "1", "2", "3", "4", "5", "6"] + }, + "link": { + "source": [0, 0, 1, 2, 5, 4, 3], + "target": [5, 3, 4, 3, 0, 2, 2], + "value": [1, 2, 1, 1, 1, 1, 1] + } + } + ], + "layout": { + "title": { "text": "Sankey with circular data" }, + "width": 800, + "height": 800 + } +} diff --git a/test/image/mocks/sankey_circular_vertical_reverse.json b/test/image/mocks/sankey_circular_vertical_reverse.json new file mode 100644 index 00000000000..4e2b6ccb535 --- /dev/null +++ b/test/image/mocks/sankey_circular_vertical_reverse.json @@ -0,0 +1,23 @@ +{ + "data": [ + { + "type": "sankey", + "orientation": "v", + "direction": "reverse", + "node": { + "pad": 5, + "label": ["0", "1", "2", "3", "4", "5", "6"] + }, + "link": { + "source": [0, 0, 1, 2, 5, 4, 3], + "target": [5, 3, 4, 3, 0, 2, 2], + "value": [1, 2, 1, 1, 1, 1, 1] + } + } + ], + "layout": { + "title": { "text": "Sankey with circular data" }, + "width": 800, + "height": 800 + } +} diff --git a/test/image/mocks/sankey_circular_with_arrows_reverse.json b/test/image/mocks/sankey_circular_with_arrows_reverse.json new file mode 100644 index 00000000000..82ac9ddd165 --- /dev/null +++ b/test/image/mocks/sankey_circular_with_arrows_reverse.json @@ -0,0 +1,23 @@ +{ + "data": [ + { + "type": "sankey", + "direction": "reverse", + "node": { + "pad": 5, + "label": ["0", "1", "2", "3", "4", "5", "6"] + }, + "link": { + "arrowlen": 20, + "source": [0, 0, 1, 2, 5, 4, 3], + "target": [5, 3, 4, 3, 0, 2, 2], + "value": [1, 2, 1, 1, 1, 1, 1] + } + } + ], + "layout": { + "title": { "text": "Sankey with circular data and arrows" }, + "width": 800, + "height": 800 + } +} diff --git a/test/image/mocks/sankey_circular_with_arrows_vertical_reverse.json b/test/image/mocks/sankey_circular_with_arrows_vertical_reverse.json new file mode 100644 index 00000000000..6bc8094af34 --- /dev/null +++ b/test/image/mocks/sankey_circular_with_arrows_vertical_reverse.json @@ -0,0 +1,24 @@ +{ + "data": [ + { + "type": "sankey", + "orientation": "v", + "direction": "reverse", + "node": { + "pad": 5, + "label": ["0", "1", "2", "3", "4", "5", "6"] + }, + "link": { + "arrowlen": 20, + "source": [0, 0, 1, 2, 5, 4, 3], + "target": [5, 3, 4, 3, 0, 2, 2], + "value": [1, 2, 1, 1, 1, 1, 1] + } + } + ], + "layout": { + "title": { "text": "Sankey with circular data and arrows" }, + "width": 800, + "height": 800 + } +} diff --git a/test/jasmine/tests/sankey_test.js b/test/jasmine/tests/sankey_test.js index 435e122127c..37fe193a96a 100644 --- a/test/jasmine/tests/sankey_test.js +++ b/test/jasmine/tests/sankey_test.js @@ -127,6 +127,8 @@ describe('sankey tests', function () { expect(fullTrace.orientation).toEqual(attributes.orientation.dflt, 'use orientation by default'); + expect(fullTrace.direction).toEqual(attributes.direction.dflt, 'use direction by default'); + expect(fullTrace.valueformat).toEqual(attributes.valueformat.dflt, 'valueformat by default'); expect(fullTrace.valuesuffix).toEqual(attributes.valuesuffix.dflt, 'valuesuffix by default'); @@ -978,6 +980,66 @@ describe('sankey tests', function () { .then(done, done.fail); }); + it('@noCI should position hover labels correctly - horizontal, reverse', function (done) { + var gd = createGraphDiv(); + var mockCopy = Lib.extendDeep({}, mock); + mockCopy.data[0].direction = 'reverse'; + + Plotly.newPlot(gd, mockCopy) + .then(function () { + hoverLink('Thermal generation', 'Losses'); + + assertLabel( + ['source: Thermal generation', 'target: Losses', '787TWh'], + ['rgb(0, 0, 96)', 'rgb(255, 255, 255)', 13, 'Arial', 'rgb(255, 255, 255)'] + ); + + // With direction:'reverse' the link is drawn mirrored on screen + // (see 'applies the correct group transform per orientation and + // direction'). The hover label must track that mirrored link, + // not the 'forward' position - which is exactly what a + // hoverCenterPosition unaware of `direction` would still produce. + var linkRect = rectForLink('Thermal generation', 'Losses'); + var g = d3Select('.hovertext'); + var pos = g.node().getBoundingClientRect(); + expect(pos.x).toBeCloseTo( + linkRect.left + linkRect.width / 2, + -1.5, + 'label tracks the mirrored link horizontally' + ); + expect(pos.x).not.toBeCloseTo(782, -1.5, 'label must not sit at the un-mirrored (forward) position'); + }) + .then(done, done.fail); + }); + + it('@noCI should position hover labels correctly - vertical, reverse', function (done) { + var gd = createGraphDiv(); + var mockCopy = Lib.extendDeep({}, mock); + mockCopy.data[0].orientation = 'v'; + mockCopy.data[0].direction = 'reverse'; + + Plotly.newPlot(gd, mockCopy) + .then(function () { + hoverLink('Thermal generation', 'Losses'); + + assertLabel( + ['source: Thermal generation', 'target: Losses', '787TWh'], + ['rgb(0, 0, 96)', 'rgb(255, 255, 255)', 13, 'Arial', 'rgb(255, 255, 255)'] + ); + + var linkRect = rectForLink('Thermal generation', 'Losses'); + var g = d3Select('.hovertext'); + var pos = g.node().getBoundingClientRect(); + expect(pos.y).toBeCloseTo( + linkRect.top + linkRect.height / 2, + -1.5, + 'label tracks the mirrored link vertically' + ); + expect(pos.y).not.toBeCloseTo(500, -1.5, 'label must not sit at the un-mirrored (forward) position'); + }) + .then(done, done.fail); + }); + it('should show the correct hover labels when hovertemplate is specified', function (done) { var gd = createGraphDiv(); var mockCopy = Lib.extendDeep({}, mock); @@ -1679,6 +1741,60 @@ describe('sankey tests', function () { }); }); + describe('for mirrored direction:', function () { + var gd; + var mockCopy; + var nodeId = 4; // Selecting node with label 'Solid' + + beforeEach(function () { + gd = createGraphDiv(); + mockCopy = Lib.extendDeep({}, mock); + mockCopy.data[0].arrangement = 'freeform'; + }); + + afterEach(function () { + Plotly.purge(gd); + destroyGraphDiv(); + }); + + // Same helper as 'for arrangement freeform:' above: getNodeCoords + // reads the node's actual on-screen position, so a correct drag + // must land at position + move regardless of any mirroring applied + // by the orientation/direction group transform. + const testDragNode = (move) => async () => { + let nodes = document.getElementsByClassName('sankey-node'); + const node = nodes.item(nodeId); + const position = getNodeCoords(node); + await drag({ node: node, dpos: move, nsteps: 10, timeDelay: 0 }); + + nodes = document.getElementsByClassName('sankey-node'); + const draggedNode = nodes.item(nodes.length - 1); + if (!draggedNode) return; + const newPosition = getNodeCoords(draggedNode); + expect(newPosition.x).toBeCloseTo(position.x + move[0], 0, 'final x position is off'); + expect(newPosition.y).toBeCloseTo(position.y + move[1], 0, 'final y position is off'); + }; + + [ + { orientation: 'h', direction: 'reverse' }, + { orientation: 'v', direction: 'reverse' } + ].forEach(function (combo) { + it( + 'should change the position of a node on drag - orientation ' + + combo.orientation + + ', direction ' + + combo.direction, + function (done) { + mockCopy.data[0].orientation = combo.orientation; + mockCopy.data[0].direction = combo.direction; + var move = [50, -50]; + + Plotly.newPlot(gd, mockCopy).then(testDragNode(move)).then(done, done.fail); + } + ); + }); + }); + describe('in relation to uirevision', function () { var gd; From 3b8c5864856679bf4981385de1d6f2561e4e1d0b Mon Sep 17 00:00:00 2001 From: wfr Date: Wed, 22 Jul 2026 13:52:55 +0200 Subject: [PATCH 7/8] Adjust test. --- test/jasmine/tests/sankey_test.js | 80 ++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/test/jasmine/tests/sankey_test.js b/test/jasmine/tests/sankey_test.js index 37fe193a96a..e09e4612ae9 100644 --- a/test/jasmine/tests/sankey_test.js +++ b/test/jasmine/tests/sankey_test.js @@ -982,10 +982,28 @@ describe('sankey tests', function () { it('@noCI should position hover labels correctly - horizontal, reverse', function (done) { var gd = createGraphDiv(); - var mockCopy = Lib.extendDeep({}, mock); - mockCopy.data[0].direction = 'reverse'; + var forwardOffsetX; - Plotly.newPlot(gd, mockCopy) + function plotWith(direction) { + var fig = Lib.extendDeep({}, mock); + if (direction !== undefined) fig.data[0].direction = direction; + return Plotly.newPlot(gd, fig); + } + + plotWith() + .then(function () { + // Baseline: the label's offset from its own link's center, + // under the default 'forward' direction (absolute pixel + // positions are font/browser sensitive - see the plain + // 'horizontal'/'vertical' tests above - so we anchor to the + // link itself rather than a hard-coded literal). + hoverLink('Thermal generation', 'Losses'); + var linkRect = rectForLink('Thermal generation', 'Losses'); + var pos = d3Select('.hovertext').node().getBoundingClientRect(); + forwardOffsetX = pos.x - (linkRect.left + linkRect.width / 2); + + return plotWith('reverse'); + }) .then(function () { hoverLink('Thermal generation', 'Losses'); @@ -994,31 +1012,47 @@ describe('sankey tests', function () { ['rgb(0, 0, 96)', 'rgb(255, 255, 255)', 13, 'Arial', 'rgb(255, 255, 255)'] ); - // With direction:'reverse' the link is drawn mirrored on screen + // The link itself is drawn mirrored under direction:'reverse' // (see 'applies the correct group transform per orientation and - // direction'). The hover label must track that mirrored link, - // not the 'forward' position - which is exactly what a - // hoverCenterPosition unaware of `direction` would still produce. + // direction' above). hoverCenterPosition places the label at a + // fixed offset from its anchor regardless of direction, so that + // offset - not the absolute position - is what should stay + // constant here. If hoverCenterPosition ignored `direction`, the + // link would still be mirrored but the label would stay glued to + // the old, un-mirrored anchor, breaking this invariant. var linkRect = rectForLink('Thermal generation', 'Losses'); - var g = d3Select('.hovertext'); - var pos = g.node().getBoundingClientRect(); - expect(pos.x).toBeCloseTo( - linkRect.left + linkRect.width / 2, + var pos = d3Select('.hovertext').node().getBoundingClientRect(); + var reverseOffsetX = pos.x - (linkRect.left + linkRect.width / 2); + + expect(reverseOffsetX).toBeCloseTo( + forwardOffsetX, -1.5, - 'label tracks the mirrored link horizontally' + 'label offset from its link is direction-independent' ); - expect(pos.x).not.toBeCloseTo(782, -1.5, 'label must not sit at the un-mirrored (forward) position'); }) .then(done, done.fail); }); it('@noCI should position hover labels correctly - vertical, reverse', function (done) { var gd = createGraphDiv(); - var mockCopy = Lib.extendDeep({}, mock); - mockCopy.data[0].orientation = 'v'; - mockCopy.data[0].direction = 'reverse'; + var forwardOffsetY; - Plotly.newPlot(gd, mockCopy) + function plotWith(direction) { + var fig = Lib.extendDeep({}, mock); + fig.data[0].orientation = 'v'; + if (direction !== undefined) fig.data[0].direction = direction; + return Plotly.newPlot(gd, fig); + } + + plotWith() + .then(function () { + hoverLink('Thermal generation', 'Losses'); + var linkRect = rectForLink('Thermal generation', 'Losses'); + var pos = d3Select('.hovertext').node().getBoundingClientRect(); + forwardOffsetY = pos.y - (linkRect.top + linkRect.height / 2); + + return plotWith('reverse'); + }) .then(function () { hoverLink('Thermal generation', 'Losses'); @@ -1028,14 +1062,14 @@ describe('sankey tests', function () { ); var linkRect = rectForLink('Thermal generation', 'Losses'); - var g = d3Select('.hovertext'); - var pos = g.node().getBoundingClientRect(); - expect(pos.y).toBeCloseTo( - linkRect.top + linkRect.height / 2, + var pos = d3Select('.hovertext').node().getBoundingClientRect(); + var reverseOffsetY = pos.y - (linkRect.top + linkRect.height / 2); + + expect(reverseOffsetY).toBeCloseTo( + forwardOffsetY, -1.5, - 'label tracks the mirrored link vertically' + 'label offset from its link is direction-independent' ); - expect(pos.y).not.toBeCloseTo(500, -1.5, 'label must not sit at the un-mirrored (forward) position'); }) .then(done, done.fail); }); From f89f62fa313d107ddbaee03a7e3f0c1d35cd924b Mon Sep 17 00:00:00 2001 From: wfr Date: Wed, 22 Jul 2026 18:33:59 +0200 Subject: [PATCH 8/8] Add updated plot-schema and horizontal baselines --- .../image/baselines/sankey_circular_reverse.png | Bin 0 -> 37673 bytes .../sankey_circular_with_arrows_reverse.png | Bin 0 -> 40321 bytes test/plot-schema.json | 12 +++++++++++- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 test/image/baselines/sankey_circular_reverse.png create mode 100644 test/image/baselines/sankey_circular_with_arrows_reverse.png diff --git a/test/image/baselines/sankey_circular_reverse.png b/test/image/baselines/sankey_circular_reverse.png new file mode 100644 index 0000000000000000000000000000000000000000..e931c50760b47ffb7a9cb7ba137429b3625b2238 GIT binary patch literal 37673 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Ð-`y`8&F@`Vm1Z9 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 KN{No+ss96ZsqIDp literal 0 HcmV?d00001 diff --git a/test/plot-schema.json b/test/plot-schema.json index e43f9d2f2d9..fb428984ad7 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -51264,6 +51264,16 @@ "editType": "calc", "valType": "data_array" }, + "direction": { + "description": "Sets the direction of the flow along the `orientation` axis. With `forward` (the default), sources are on the left (horizontal) or top (vertical). With `reverse`, sources are on the right (horizontal) or bottom (vertical).", + "dflt": "forward", + "editType": "calc", + "valType": "enumerated", + "values": [ + "forward", + "reverse" + ] + }, "domain": { "column": { "description": "If there is a layout grid, use the domain for this column in the grid for this sankey trace.", @@ -52187,7 +52197,7 @@ } }, "orientation": { - "description": "Sets the orientation of the Sankey diagram.", + "description": "Sets the orientation of the Sankey diagram. With `h` (the default), the flow runs horizontally. With `v`, the flow runs vertically. Use `direction` to control which side the sources are placed on.", "dflt": "h", "editType": "calc", "valType": "enumerated",