Skip to content

Commit 542c55e

Browse files
author
wfr
committed
Introduce direction attribute instead of more parameters for orientation
1 parent 5ea67d9 commit 542c55e

6 files changed

Lines changed: 62 additions & 45 deletions

File tree

draftlogs/7870_add.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +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)]
1+
- 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)]

src/traces/sankey/attributes.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,24 @@ var attrs = (module.exports = overrideAll(
3131

3232
orientation: {
3333
valType: 'enumerated',
34-
values: ['v', 'h', 'left-right', 'right-left', 'top-down', 'bottom-up'],
34+
values: ['v', 'h'],
3535
dflt: 'h',
3636
description: [
3737
'Sets the orientation of the Sankey diagram.',
38-
'`left-right` (synonym of the legacy value `h`) places sources on the left',
39-
'with the flow running rightward; `right-left` places sources on the right',
40-
'with the flow running leftward; `top-down` (synonym of the legacy value `v`)',
41-
'places sources at the top with the flow running downward; `bottom-up` places',
42-
'sources at the bottom with the flow running upward.'
38+
'With `h` (the default), the flow runs horizontally.',
39+
'With `v`, the flow runs vertically.',
40+
'Use `direction` to control which side the sources are placed on.'
41+
].join(' ')
42+
},
43+
44+
direction: {
45+
valType: 'enumerated',
46+
values: ['forward', 'reverse'],
47+
dflt: 'forward',
48+
description: [
49+
'Sets the direction of the flow along the `orientation` axis.',
50+
'With `forward` (the default), sources are on the left (horizontal) or top (vertical).',
51+
'With `reverse`, sources are on the right (horizontal) or bottom (vertical).',
4352
].join(' ')
4453
},
4554

src/traces/sankey/defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
101101
handleDomainDefaults(traceOut, layout, coerce);
102102

103103
coerce('orientation');
104+
coerce('direction');
104105
coerce('valueformat');
105106
coerce('valuesuffix');
106107

src/traces/sankey/plot.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -193,15 +193,14 @@ module.exports = function plot(gd, calcData) {
193193
hoverCenterX = (link.source.x1 + link.target.x0) / 2;
194194
hoverCenterY = (link.y0 + link.y1) / 2;
195195
}
196-
var orientation = link.trace.orientation;
196+
var vertical = link.trace.orientation === 'v';
197+
var reverse = link.trace.direction === 'reverse';
197198
var center = [hoverCenterX, hoverCenterY];
198-
// Vertical orientations transpose x/y to match the group transform.
199-
if(orientation === 'v' || orientation === 'top-down' || orientation === 'bottom-up') {
200-
center.reverse();
201-
}
202-
// bottom-up / right-left additionally mirror the flow axis (matching the translate).
203-
if(orientation === 'bottom-up') center[1] = d.parent.height - center[1];
204-
if(orientation === 'right-left') center[0] = d.parent.width - center[0];
199+
// Vertical orientation transposes x/y to match the group transform.
200+
if(vertical) center.reverse();
201+
// reverse direction additionally mirrors the flow axis (matching the translate).
202+
if(vertical && reverse) center[1] = d.parent.height - center[1];
203+
if(!vertical && reverse) center[0] = d.parent.width - center[0];
205204
center[0] += d.parent.translateX;
206205
center[1] += d.parent.translateY;
207206
return center;

src/traces/sankey/render.js

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,10 @@ function sankeyModel(layout, d, traceIndex) {
3131
var calcData = unwrap(d);
3232
var trace = calcData.trace;
3333
var domain = trace.domain;
34-
var horizontal = trace.orientation === 'h' ||
35-
trace.orientation === 'left-right' ||
36-
trace.orientation === 'right-left';
37-
var rightLeft = trace.orientation === 'right-left';
38-
var bottomUp = trace.orientation === 'bottom-up';
34+
var horizontal = trace.orientation === 'h';
35+
// reverse flips the source side along the flow axis: horizontal -> sources on the
36+
// right (flow leftward), vertical -> sources at the bottom (flow upward).
37+
var reverse = trace.direction === 'reverse';
3938
var nodePad = trace.node.pad;
4039
var nodeThickness = trace.node.thickness;
4140
var nodeAlign = {
@@ -289,8 +288,7 @@ function sankeyModel(layout, d, traceIndex) {
289288
trace: trace,
290289
guid: Lib.randstr(),
291290
horizontal: horizontal,
292-
rightLeft: rightLeft,
293-
bottomUp: bottomUp,
291+
reverse: reverse,
294292
width: width,
295293
height: height,
296294
nodePad: trace.node.pad,
@@ -594,8 +592,7 @@ function nodeModel(d, n) {
594592
sizeAcross: d.width,
595593
forceLayouts: d.forceLayouts,
596594
horizontal: d.horizontal,
597-
rightLeft: d.rightLeft,
598-
bottomUp: d.bottomUp,
595+
reverse: d.reverse,
599596
darkBackground: Color.color(n.color).isDark(),
600597
rgb: Color.rgb(n.color),
601598
alpha: Color.color(n.color).alpha(),
@@ -638,19 +635,19 @@ function salientEnough(d) {return (d.link.width > 1 || d.linkLineWidth > 0);}
638635

639636
function sankeyTransform(d) {
640637
if(d.horizontal) {
641-
if(d.rightLeft) {
642-
// right-left: sources on the right, flow leftward; horizontal mirror of left-right.
638+
if(d.reverse) {
639+
// horizontal + reverse: sources on the right, flow leftward; a mirror of forward.
643640
return strTranslate(d.translateX + d.width, d.translateY) + 'matrix(-1 0 0 1 0 0)';
644641
}
645-
// h / left-right: sources on the left, flow rightward.
642+
// horizontal + forward: sources on the left, flow rightward.
646643
return strTranslate(d.translateX, d.translateY) + 'matrix(1 0 0 1 0 0)';
647644
}
648-
if(d.bottomUp) {
649-
// bottom-up: sources at the bottom, flow upward; a vertical mirror of top-down.
645+
if(d.reverse) {
646+
// vertical + reverse: sources at the bottom, flow upward; a mirror of forward.
650647
// Pure 90deg rotation (det +1) keeps the cross axis intact.
651648
return strTranslate(d.translateX, d.translateY + d.height) + 'matrix(0 -1 1 0 0 0)';
652649
}
653-
// top-down (also 'v'): reflection about y=x, sources at the top, flow downward.
650+
// vertical + forward: reflection about y=x, sources at the top, flow downward.
654651
return strTranslate(d.translateX, d.translateY) + 'matrix(0 1 1 0 0 0)';
655652
}
656653

@@ -1071,9 +1068,9 @@ module.exports = function(gd, svg, calcData, layout, callbacks) {
10711068
})
10721069
.attr('text-anchor', function(d) {
10731070
// vertical: labels are centered over the node. horizontal: aligned to the outer
1074-
// edge (right-left mirrors the layout, so the outer side and anchor flip).
1071+
// edge (reverse mirrors the layout, so the outer side and anchor flip).
10751072
if(!d.horizontal) return 'middle';
1076-
return (d.left !== d.rightLeft) ? 'end' : 'start';
1073+
return (d.left !== d.reverse) ? 'end' : 'start';
10771074
})
10781075
.attr('transform', function(d) {
10791076
var e = d3.select(this);
@@ -1088,9 +1085,9 @@ module.exports = function(gd, svg, calcData, layout, callbacks) {
10881085
if(!d.horizontal) {
10891086
var posY = d.visibleHeight / 2;
10901087
// last Column (originalLayer === 1): put label towards center.
1091-
var posX = d.bottomUp ?
1088+
var posX = d.reverse ?
10921089
(d.left ? -(pad + CAP_SHIFT * d.textFont.size) : (d.visibleWidth + pad)) : (d.left ? -pad : (d.visibleWidth + pad + CAP_SHIFT * d.textFont.size));
1093-
var flipV = d.bottomUp ? strRotate(90) : ('scale(-1,1)' + strRotate(90));
1090+
var flipV = d.reverse ? strRotate(90) : ('scale(-1,1)' + strRotate(90));
10941091
return strTranslate(posX, posY) + flipV;
10951092
}
10961093

@@ -1102,10 +1099,10 @@ module.exports = function(gd, svg, calcData, layout, callbacks) {
11021099
} else {
11031100
posX += d.visibleWidth;
11041101
}
1105-
return strTranslate(posX, posY) + (d.rightLeft ? 'scale(-1,1)' : '');
1102+
return strTranslate(posX, posY) + (d.reverse ? 'scale(-1,1)' : '');
11061103
});
11071104

11081105
nodeLabel
11091106
.transition()
11101107
.ease(c.ease).duration(c.duration);
1111-
};
1108+
};

test/jasmine/tests/sankey_test.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,24 @@ describe('sankey tests', function () {
138138
expect(fullTrace.domain.y).toEqual(attributes.domain.y.dflt, 'y domain by default');
139139
});
140140

141-
it('coerces the vertical orientation values', function() {
142-
['h', 'v', 'left-right', 'right-left', 'top-down', 'bottom-up'].forEach(function(o) {
141+
it('coerces the orientation values', function() {
142+
['h', 'v'].forEach(function(o) {
143143
expect(_supply({orientation: o}).orientation)
144144
.toBe(o, o + ' is a valid orientation');
145145
});
146146
expect(_supply({orientation: 'sideways'}).orientation)
147147
.toBe(attributes.orientation.dflt, 'invalid orientation falls back to default');
148148
});
149149

150+
it('coerces the direction values', function() {
151+
['forward', 'reverse'].forEach(function(dir) {
152+
expect(_supply({direction: dir}).direction)
153+
.toBe(dir, dir + ' is a valid direction');
154+
});
155+
expect(_supply({direction: 'backward'}).direction)
156+
.toBe(attributes.direction.dflt, 'invalid direction falls back to default');
157+
});
158+
150159
it("'Sankey' layout dependent specification should have proper types", function () {
151160
var fullTrace = _supplyWithLayout(
152161
{},
@@ -385,38 +394,40 @@ describe('sankey tests', function () {
385394
});
386395
afterEach(destroyGraphDiv);
387396

388-
it('applies the correct group transform per orientation', function(done) {
397+
it('applies the correct group transform per orientation and direction', function(done) {
389398
function groupTransform() {
390399
return d3Select('.sankey').attr('transform');
391400
}
392-
function plotWith(orientation) {
401+
function plotWith(orientation, direction) {
393402
var fig = Lib.extendDeep({}, mock);
394403
fig.data[0].orientation = orientation;
404+
if(direction !== undefined) fig.data[0].direction = direction;
395405
// newPlot re-enters the trace, so the transform is set synchronously
396406
// (no mid-transition interpolation to race against).
397407
return Plotly.newPlot(gd, fig);
398408
}
399409

400410
plotWith('h')
401411
.then(function() {
412+
// forward is the default direction
402413
expect(groupTransform()).toContain('matrix(1 0 0 1 0 0)');
403-
return plotWith('left-right'); // legacy synonym of h
414+
return plotWith('h', 'forward');
404415
})
405416
.then(function() {
406417
expect(groupTransform()).toContain('matrix(1 0 0 1 0 0)');
407-
return plotWith('right-left');
418+
return plotWith('h', 'reverse');
408419
})
409420
.then(function() {
410421
expect(groupTransform()).toContain('matrix(-1 0 0 1 0 0)');
411-
return plotWith('top-down');
422+
return plotWith('v', 'forward');
412423
})
413424
.then(function() {
414425
expect(groupTransform()).toContain('matrix(0 1 1 0 0 0)');
415-
return plotWith('v'); // legacy synonym of top-down
426+
return plotWith('v'); // forward is the default direction
416427
})
417428
.then(function() {
418429
expect(groupTransform()).toContain('matrix(0 1 1 0 0 0)');
419-
return plotWith('bottom-up');
430+
return plotWith('v', 'reverse');
420431
})
421432
.then(function() {
422433
expect(groupTransform()).toContain('matrix(0 -1 1 0 0 0)');

0 commit comments

Comments
 (0)