Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions draftlogs/7873_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add `sort` option to Sankey links and nodes [[#7873](https://github.com/plotly/plotly.js/pull/7873)], with thanks to @adamreeve for the contribution!
18 changes: 18 additions & 0 deletions src/traces/sankey/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,15 @@ var attrs = (module.exports = overrideAll(
dflt: 'justify',
description: 'Sets the alignment method used to position the nodes along the horizontal axis.'
},
sort: {
valType: 'enumerated',
values: ['auto', 'input'],
dflt: 'auto',
description: [
'For `auto` (the default), the vertical order of nodes will be determined automatically by the layout.',
'For `input`, the vertical order of nodes is kept the same as the order in the input array.'
].join(' ')
},
description: 'The nodes of the Sankey plot.'
},

Expand Down Expand Up @@ -283,6 +292,15 @@ var attrs = (module.exports = overrideAll(
]
})
}),
sort: {
valType: 'enumerated',
values: ['auto', 'input'],
dflt: 'auto',
description: [
'For `auto` (the default), the order of links attached to each node is determined automatically by the layout.',
'For `input`, the order of links at each node is kept the same as the order in the input array.'
].join(' ')
},
description: 'The links of the Sankey plot.'
}
},
Expand Down
2 changes: 2 additions & 0 deletions src/traces/sankey/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
handleHoverLabelDefaults(nodeIn, nodeOut, coerceNode, hoverlabelDefault);
coerceNode('hovertemplate');
coerceNode('align');
coerceNode('sort');

var colors = layout.colorway;

Expand Down Expand Up @@ -62,6 +63,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
coerceLink('hoverinfo', traceIn.hoverinfo);
handleHoverLabelDefaults(linkIn, linkOut, coerceLink, hoverlabelDefault);
coerceLink('hovertemplate');
coerceLink('sort');

var darkBG = Color.color(layout.paper_bgcolor).luminosity() < 0.333;
var defaultLinkColor = darkBG ? 'rgba(255, 255, 255, 0.6)' : 'rgba(0, 0, 0, 0.2)';
Expand Down
15 changes: 15 additions & 0 deletions src/traces/sankey/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,21 @@ function sankeyModel(layout, d, traceIndex) {
.nodes(nodes)
.links(links);

function applyInputSort(type) {
if (trace[type].sort === 'input') {
if (circular) {
Lib.warn(
`Circular Sankey diagrams do not support the "input" ${type}.sort mode; falling back to the default sort.`
);
} else {
// Passing null maintains the input order
sankey[`${type}Sort`](null);
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
}
if (inputNodeSort) {
if (circular) {
Lib.warn('Circular Sankey diagrams do not support the "input" node.sort mode; falling back to the default sort.');
} else {
// Passing null keeps nodes in their input order
sankey.nodeSort(null);
}
}

applyInputSort('link');
applyInputSort('node');

var graph = sankey();

if(sankey.nodePadding() < nodePad) {
Expand Down
10 changes: 10 additions & 0 deletions src/types/generated/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6775,6 +6775,11 @@ export interface SankeyData {
*/
label?: Datum[] | Datum[][] | TypedArray;
line?: _internal.Line;
/**
* For `auto` (the default), the order of links attached to each node is determined automatically by the layout. For `input`, the order of links at each node is kept the same as the order in the input array.
* @default 'auto'
*/
sort?: 'auto' | 'input';
/**
* An integer number `[0..nodes.length - 1]` that represents the source node.
* @default []
Expand Down Expand Up @@ -6837,6 +6842,11 @@ export interface SankeyData {
* Minimum: 0
*/
pad?: number;
/**
* For `auto` (the default), the vertical order of nodes will be determined automatically by the layout. For `input`, the vertical order of nodes is kept the same as the order in the input array.
* @default 'auto'
*/
sort?: 'auto' | 'input';
/**
* Sets the thickness (in px) of the `nodes`.
* @default 20
Expand Down
Binary file added test/image/baselines/sankey_input_link_sort.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/image/baselines/sankey_input_node_sort.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions test/image/mocks/sankey_input_link_sort.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"data": [
{
"type": "sankey",
"node": {
"label": ["0", "1", "2", "3", "4", "5"],
"align": "center",
"color": "gray"
},
"link": {
"source": [0, 0, 0, 0, 1, 4],
"target": [1, 3, 2, 4, 5, 5],
"value": [1, 1, 2, 1, 1, 1],
"color": [
"rgba(230, 25, 75, 0.6)",
"rgba(255, 165, 0, 0.6)",
"rgba(60, 180, 75, 0.6)",
"rgba(0, 130, 200, 0.6)",
"rgba(145, 30, 180, 0.6)",
"rgba(128, 128, 128, 0.6)"
],
"sort": "input"
}
}
],
"layout": {
"title": { "text": "Sankey with link ordering matching the input array" },
"width": 800,
"height": 800
}
}
31 changes: 31 additions & 0 deletions test/image/mocks/sankey_input_node_sort.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"data": [
{
"type": "sankey",
"node": {
"label": ["0", "1", "2", "3", "4", "5"],
"align": "center",
"color": "gray",
"sort": "input"
},
"link": {
"source": [0, 0, 0, 0, 1, 4],
"target": [1, 3, 2, 4, 5, 5],
"value": [1, 1, 2, 1, 1, 1],
"color": [
"rgba(230, 25, 75, 0.6)",
"rgba(255, 165, 0, 0.6)",
"rgba(60, 180, 75, 0.6)",
"rgba(0, 130, 200, 0.6)",
"rgba(145, 30, 180, 0.6)",
"rgba(128, 128, 128, 0.6)"
]
}
}
],
"layout": {
"title": { "text": "Sankey with vertical node ordering matching the input array" },
"width": 800,
"height": 800
}
}
42 changes: 42 additions & 0 deletions test/jasmine/tests/sankey_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,48 @@ describe('sankey tests', function () {
.then(done, done.fail);
});

it('falls back to the default sort for circular Sankey with node.sort set', (done) => {
const warnings = [];
spyOn(Lib, 'warn').and.callFake((msg) => {
warnings.push(msg);
});

const mockCircularCopy = Lib.extendDeep({}, mockCircular);
mockCircularCopy.data[0].node.sort = 'input';

Plotly.newPlot(gd, mockCircularCopy)
.then(() => {
// The plot renders successfully
expect(gd.calcdata[0][0].circular).toBe(true);
expect(d3SelectAll('.sankey .node-rect').size()).toBeGreaterThan(0);

// An warning is logged about the fallback
expect(warnings.length).toBe(1);
})
.then(done, done.fail);
});

it('falls back to the default sort for circular Sankey with link.sort set', (done) => {
const warnings = [];
spyOn(Lib, 'warn').and.callFake((msg) => {
warnings.push(msg);
});

const mockCircularCopy = Lib.extendDeep({}, mockCircular);
mockCircularCopy.data[0].link.sort = 'input';

Plotly.newPlot(gd, mockCircularCopy)
.then(() => {
// The plot renders successfully
expect(gd.calcdata[0][0].circular).toBe(true);
expect(d3SelectAll('.sankey .sankey-link').size()).toBeGreaterThan(0);

// A warning is logged about the fallback
expect(warnings.length).toBe(1);
})
.then(done, done.fail);
});

it('can create groups, restyle groups and properly update DOM', function (done) {
var mockCircularCopy = Lib.extendDeep({}, mockCircular);
var firstGroup = [
Expand Down
20 changes: 20 additions & 0 deletions test/plot-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -50476,6 +50476,16 @@
}
},
"role": "object",
"sort": {
"description": "For `auto` (the default), the order of links attached to each node is determined automatically by the layout. For `input`, the order of links at each node is kept the same as the order in the input array.",
"dflt": "auto",
"editType": "calc",
"valType": "enumerated",
"values": [
"auto",
"input"
]
},
"source": {
"description": "An integer number `[0..nodes.length - 1]` that represents the source node.",
"dflt": [],
Expand Down Expand Up @@ -50746,6 +50756,16 @@
"valType": "number"
},
"role": "object",
"sort": {
"description": "For `auto` (the default), the vertical order of nodes will be determined automatically by the layout. For `input`, the vertical order of nodes is kept the same as the order in the input array.",
"dflt": "auto",
"editType": "calc",
"valType": "enumerated",
"values": [
"auto",
"input"
]
},
"thickness": {
"arrayOk": false,
"description": "Sets the thickness (in px) of the `nodes`.",
Expand Down
Loading