Skip to content

Commit ba2a485

Browse files
authored
Merge pull request #3535 from plotly/pr-large-sankey-circular
handle large circular sankey diagram
2 parents 777dc97 + 4ed7634 commit ba2a485

File tree

10 files changed

+388
-77
lines changed

10 files changed

+388
-77
lines changed

package-lock.json

Lines changed: 13 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"country-regex": "^1.1.0",
6767
"d3": "^3.5.12",
6868
"@plotly/d3-sankey": "0.7.2",
69-
"d3-sankey-circular": "0.30.0",
69+
"d3-sankey-circular": "0.32.0",
7070
"d3-force": "^1.0.6",
7171
"d3-interpolate": "1",
7272
"delaunay-triangulate": "^1.1.6",

src/traces/sankey/render.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ function sankeyModel(layout, d, traceIndex) {
4545
if(circular) {
4646
sankey = d3SankeyCircular
4747
.sankeyCircular()
48-
.circularLinkGap(2)
48+
.circularLinkGap(0)
4949
.nodeId(function(d) {
5050
return d.pointNumber;
5151
});
147 KB
Loading
-1.03 KB
Loading
39.2 KB
Loading
Loading

test/image/mocks/sankey_circular_large.json

Lines changed: 230 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"data": [
3+
{
4+
"type": "sankey",
5+
"node": {
6+
"pad": 5,
7+
"label": ["0", "1", "2", "3", "sink", "source"]
8+
},
9+
"link": {
10+
"source": [
11+
0, 1, 2, 3,
12+
0, 1, 2, 3,
13+
5
14+
],
15+
"target": [
16+
1, 2, 3, 0,
17+
4, 4, 4, 4,
18+
0
19+
],
20+
"value": [
21+
1, 0.85, 0.7, 0.55,
22+
0, 0.15, 0.15, 0.15,
23+
0.45
24+
]
25+
}
26+
}],
27+
"layout": {
28+
"width": 800,
29+
"height": 800
30+
}
31+
}

test/jasmine/tests/sankey_test.js

Lines changed: 112 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var d3SankeyCircular = require('d3-sankey-circular');
77
var mock = require('@mocks/sankey_energy.json');
88
var mockDark = require('@mocks/sankey_energy_dark.json');
99
var mockCircular = require('@mocks/sankey_circular.json');
10+
var mockCircularLarge = require('@mocks/sankey_circular_large.json');
1011
var Sankey = require('@src/traces/sankey');
1112

1213
var createGraphDiv = require('../assets/create_graph_div');
@@ -1069,91 +1070,131 @@ describe('sankey layout generators', function() {
10691070
describe('d3-sankey-ciruclar', function() {
10701071
var data, sankey, graph;
10711072

1072-
function _calc(trace) {
1073-
var gd = { data: [trace] };
1074-
1075-
supplyAllDefaults(gd);
1076-
var fullTrace = gd._fullData[0];
1077-
return Sankey.calc(gd, fullTrace);
1078-
}
1073+
describe('implements d3-sankey compatible API', function() {
1074+
function _calc(trace) {
1075+
var gd = { data: [trace] };
1076+
1077+
supplyAllDefaults(gd);
1078+
var fullTrace = gd._fullData[0];
1079+
return Sankey.calc(gd, fullTrace);
1080+
}
1081+
1082+
beforeEach(function() {
1083+
data = _calc(mockCircular.data[0]);
1084+
data = {
1085+
nodes: data[0]._nodes,
1086+
links: data[0]._links
1087+
};
1088+
sankey = d3SankeyCircular
1089+
.sankeyCircular()
1090+
.iterations(32)
1091+
.circularLinkGap(2)
1092+
.nodePadding(10)
1093+
.size([500, 500])
1094+
.nodeId(function(d) {
1095+
return d.pointNumber;
1096+
})
1097+
.nodes(data.nodes)
1098+
.links(data.links);
1099+
1100+
graph = sankey();
1101+
});
10791102

1080-
beforeEach(function() {
1081-
data = _calc(mockCircular.data[0]);
1082-
data = {
1083-
nodes: data[0]._nodes,
1084-
links: data[0]._links
1085-
};
1086-
sankey = d3SankeyCircular
1087-
.sankeyCircular()
1088-
.iterations(32)
1089-
.circularLinkGap(2)
1090-
.nodePadding(10)
1091-
.size([500, 500])
1092-
.nodeId(function(d) {
1093-
return d.pointNumber;
1094-
})
1095-
.nodes(data.nodes)
1096-
.links(data.links);
1103+
it('creates a graph with circular links', function() {
1104+
expect(graph.nodes.length).toEqual(6, 'there are 6 nodes');
1105+
var circularLinks = graph.links.filter(function(link) {
1106+
return link.circular;
1107+
});
1108+
expect(circularLinks.length).toEqual(2, 'there are two circular links');
1109+
});
10971110

1098-
graph = sankey();
1099-
});
1111+
it('keep a list of nodes with positions in integer (depth, height)', function() {
1112+
checkArray(graph.nodes, 'depth', [0, 0, 2, 3, 1, 1]);
1113+
checkArray(graph.nodes, 'height', [1, 3, 1, 0, 2, 0]);
1114+
});
11001115

1101-
it('creates a graph with circular links', function() {
1102-
expect(graph.nodes.length).toEqual(6, 'there are 6 nodes');
1103-
var circularLinks = graph.links.filter(function(link) {
1104-
return link.circular;
1116+
it('keep a list of nodes with positions in x and y', function() {
1117+
checkRoundedArray(graph.nodes, 'x0', [72, 72, 267, 365, 169, 169]);
1118+
checkRoundedArray(graph.nodes, 'y0', [303, 86, 72, 109, 86, 359]);
11051119
});
1106-
expect(circularLinks.length).toEqual(2, 'there are two circular links');
1107-
});
11081120

1109-
it('keep a list of nodes with positions in integer (depth, height)', function() {
1110-
checkArray(graph.nodes, 'depth', [0, 0, 2, 3, 1, 1]);
1111-
checkArray(graph.nodes, 'height', [1, 3, 1, 0, 2, 0]);
1112-
});
1121+
it('supports column reordering', function() {
1122+
var reorder = [ 2, 2, 1, 1, 0, 0 ];
11131123

1114-
it('keep a list of nodes with positions in x and y', function() {
1115-
checkRoundedArray(graph.nodes, 'x0', [72, 72, 267, 365, 169, 169]);
1116-
checkRoundedArray(graph.nodes, 'y0', [303, 86, 72, 109, 86, 359]);
1117-
});
1124+
checkArray(graph.nodes, 'column', [0, 0, 2, 3, 1, 1]);
11181125

1119-
it('supports column reordering', function() {
1120-
var reorder = [ 2, 2, 1, 1, 0, 0 ];
1126+
var a = graph.nodes[0].x0;
1127+
sankey.nodeAlign(function(node) {
1128+
return reorder[node.pointNumber];
1129+
});
1130+
graph = sankey();
1131+
checkArray(graph.nodes, 'column', [2, 2, 1, 1, 0, 0]);
1132+
checkArray(graph.nodes, 'height', [1, 3, 1, 0, 2, 0]);
1133+
var b = graph.nodes[0].x0;
1134+
expect(a).not.toEqual(b);
1135+
});
11211136

1122-
checkArray(graph.nodes, 'column', [0, 0, 2, 3, 1, 1]);
1137+
it('updates links vertical position and circularLinkType upon moving nodes', function() {
1138+
var linkIndex = 6;
1139+
var nodeIndex = 2;
1140+
var delta = [0, 400];
11231141

1124-
var a = graph.nodes[0].x0;
1125-
sankey.nodeAlign(function(node) {
1126-
return reorder[node.pointNumber];
1127-
});
1128-
graph = sankey();
1129-
checkArray(graph.nodes, 'column', [2, 2, 1, 1, 0, 0]);
1130-
checkArray(graph.nodes, 'height', [1, 3, 1, 0, 2, 0]);
1131-
var b = graph.nodes[0].x0;
1132-
expect(a).not.toEqual(b);
1133-
});
1142+
var link = graph.links[linkIndex];
1143+
var linkY1 = link.y1;
1144+
var node = graph.nodes[nodeIndex];
1145+
var offsetTopToBottom = (node.y1 - node.y0) * link.value / node.value;
11341146

1135-
it('updates links vertical position and circularLinkType upon moving nodes', function() {
1136-
var linkIndex = 6;
1137-
var nodeIndex = 2;
1138-
var delta = [0, 400];
1147+
// Start with a circular link on top
1148+
expect(link.circular).toBeTruthy();
1149+
expect(link.circularLinkType).toEqual('top');
11391150

1140-
var link = graph.links[linkIndex];
1141-
var linkY1 = link.y1;
1142-
var node = graph.nodes[nodeIndex];
1143-
var offsetTopToBottom = (node.y1 - node.y0) * link.value / node.value;
1151+
// Update graph
1152+
var updatedGraph = moveNode(sankey, graph, nodeIndex, delta);
1153+
var updatedLink = updatedGraph.links[linkIndex];
11441154

1145-
// Start with a circular link on top
1146-
expect(link.circular).toBeTruthy();
1147-
expect(link.circularLinkType).toEqual('top');
1155+
// End up with a cirular link on bottom
1156+
expect(updatedLink.circular).toBeTruthy();
1157+
expect(updatedLink.circularLinkType).toEqual('bottom');
1158+
expect(updatedLink.y1).toBeCloseTo(linkY1 + delta[1] + offsetTopToBottom, 0);
1159+
});
1160+
});
11481161

1149-
// Update graph
1150-
var updatedGraph = moveNode(sankey, graph, nodeIndex, delta);
1151-
var updatedLink = updatedGraph.links[linkIndex];
1162+
describe('handles large number of links', function() {
1163+
function _calc(trace) {
1164+
var gd = { data: [trace] };
1165+
1166+
supplyAllDefaults(gd);
1167+
var fullTrace = gd._fullData[0];
1168+
return Sankey.calc(gd, fullTrace);
1169+
}
1170+
1171+
beforeEach(function() {
1172+
data = _calc(mockCircularLarge.data[0]);
1173+
data = {
1174+
nodes: data[0]._nodes,
1175+
links: data[0]._links
1176+
};
1177+
sankey = d3SankeyCircular
1178+
.sankeyCircular()
1179+
.iterations(32)
1180+
.nodePadding(10)
1181+
.size([500, 500])
1182+
.nodeId(function(d) {
1183+
return d.pointNumber;
1184+
})
1185+
.nodes(data.nodes)
1186+
.links(data.links);
1187+
1188+
graph = sankey();
1189+
});
11521190

1153-
// End up with a cirular link on bottom
1154-
expect(updatedLink.circular).toBeTruthy();
1155-
expect(updatedLink.circularLinkType).toEqual('bottom');
1156-
expect(updatedLink.y1).toBeCloseTo(linkY1 + delta[1] + offsetTopToBottom, 0);
1191+
it('creates a graph with circular links', function() {
1192+
expect(graph.nodes.length).toEqual(26, 'right number of nodes');
1193+
var circularLinks = graph.links.filter(function(link) {
1194+
return link.circular;
1195+
});
1196+
expect(circularLinks.length).toEqual(89, 'right number of circular links');
1197+
});
11571198
});
11581199
});
11591200
});

0 commit comments

Comments
 (0)