Skip to content

Commit 30eaa3a

Browse files
authored
Merge pull request #3406 from plotly/sankey-circular
d3-sankey-circular
2 parents b03bb3b + 063784a commit 30eaa3a

17 files changed

+1181
-267
lines changed

package-lock.json

+36-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,17 @@
5858
},
5959
"dependencies": {
6060
"3d-view": "^2.0.0",
61-
"@plotly/d3-sankey": "^0.5.1",
6261
"alpha-shape": "^1.0.0",
6362
"array-range": "^1.0.1",
6463
"canvas-fit": "^1.5.0",
6564
"color-normalize": "^1.3.0",
6665
"convex-hull": "^1.0.3",
6766
"country-regex": "^1.1.0",
6867
"d3": "^3.5.12",
68+
"@plotly/d3-sankey": "0.7.2",
69+
"d3-sankey-circular": "0.32.0",
6970
"d3-force": "^1.0.6",
71+
"d3-interpolate": "1",
7072
"delaunay-triangulate": "^1.1.6",
7173
"es6-promise": "^3.0.2",
7274
"fast-isnumeric": "^1.1.2",

src/traces/sankey/calc.js

+78-11
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,77 @@ var tarjan = require('strongly-connected-components');
1212
var Lib = require('../../lib');
1313
var wrap = require('../../lib/gup').wrap;
1414

15+
var isArrayOrTypedArray = Lib.isArrayOrTypedArray;
16+
var isIndex = Lib.isIndex;
17+
18+
19+
function convertToD3Sankey(trace) {
20+
var nodeSpec = trace.node;
21+
var linkSpec = trace.link;
22+
23+
var links = [];
24+
var hasLinkColorArray = isArrayOrTypedArray(linkSpec.color);
25+
var linkedNodes = {};
26+
27+
var nodeCount = nodeSpec.label.length;
28+
var i;
29+
for(i = 0; i < linkSpec.value.length; i++) {
30+
var val = linkSpec.value[i];
31+
// remove negative values, but keep zeros with special treatment
32+
var source = linkSpec.source[i];
33+
var target = linkSpec.target[i];
34+
if(!(val > 0 && isIndex(source, nodeCount) && isIndex(target, nodeCount))) {
35+
continue;
36+
}
37+
38+
source = +source;
39+
target = +target;
40+
linkedNodes[source] = linkedNodes[target] = true;
41+
42+
var label = '';
43+
if(linkSpec.label && linkSpec.label[i]) label = linkSpec.label[i];
44+
45+
links.push({
46+
pointNumber: i,
47+
label: label,
48+
color: hasLinkColorArray ? linkSpec.color[i] : linkSpec.color,
49+
source: source,
50+
target: target,
51+
value: +val
52+
});
53+
}
54+
55+
var hasNodeColorArray = isArrayOrTypedArray(nodeSpec.color);
56+
var nodes = [];
57+
var removedNodes = false;
58+
var nodeIndices = {};
59+
60+
for(i = 0; i < nodeCount; i++) {
61+
if(linkedNodes[i]) {
62+
var l = nodeSpec.label[i];
63+
nodeIndices[i] = nodes.length;
64+
nodes.push({
65+
pointNumber: i,
66+
label: l,
67+
color: hasNodeColorArray ? nodeSpec.color[i] : nodeSpec.color
68+
});
69+
} else removedNodes = true;
70+
}
71+
72+
// need to re-index links now, since we didn't put all the nodes in
73+
if(removedNodes) {
74+
for(i = 0; i < links.length; i++) {
75+
links[i].source = nodeIndices[links[i].source];
76+
links[i].target = nodeIndices[links[i].target];
77+
}
78+
}
79+
80+
return {
81+
links: links,
82+
nodes: nodes
83+
};
84+
}
85+
1586
function circularityPresent(nodeList, sources, targets) {
1687

1788
var nodeLen = nodeList.length;
@@ -36,20 +107,16 @@ function circularityPresent(nodeList, sources, targets) {
36107
}
37108

38109
module.exports = function calc(gd, trace) {
39-
110+
var circular = false;
40111
if(circularityPresent(trace.node.label, trace.link.source, trace.link.target)) {
41-
Lib.error('Circularity is present in the Sankey data. Removing all nodes and links.');
42-
trace.link.label = [];
43-
trace.link.source = [];
44-
trace.link.target = [];
45-
trace.link.value = [];
46-
trace.link.color = [];
47-
trace.node.label = [];
48-
trace.node.color = [];
112+
circular = true;
49113
}
50114

115+
var result = convertToD3Sankey(trace);
116+
51117
return wrap({
52-
link: trace.link,
53-
node: trace.node
118+
circular: circular,
119+
_nodes: result.nodes,
120+
_links: result.links
54121
});
55122
};

src/traces/sankey/plot.js

+12-6
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ function linkNonHoveredStyle(d, sankey, visitNodes, sankeyLink) {
9494
var label = sankeyLink.datum().link.label;
9595

9696
sankeyLink.style('fill-opacity', function(d) {return d.tinyColorAlpha;});
97-
9897
if(label) {
9998
ownTrace(sankey, d)
10099
.selectAll('.' + cn.sankeyLink)
@@ -152,16 +151,23 @@ module.exports = function plot(gd, calcData) {
152151
var obj = d.link.trace.link;
153152
if(obj.hoverinfo === 'none' || obj.hoverinfo === 'skip') return;
154153
var rootBBox = gd._fullLayout._paperdiv.node().getBoundingClientRect();
155-
var boundingBox = element.getBoundingClientRect();
156-
var hoverCenterX = boundingBox.left + boundingBox.width / 2;
157-
var hoverCenterY = boundingBox.top + boundingBox.height / 2;
154+
var hoverCenterX;
155+
var hoverCenterY;
156+
if(d.link.circular) {
157+
hoverCenterX = (d.link.circularPathData.leftInnerExtent + d.link.circularPathData.rightInnerExtent) / 2 + d.parent.translateX;
158+
hoverCenterY = d.link.circularPathData.verticalFullExtent + d.parent.translateY;
159+
} else {
160+
var boundingBox = element.getBoundingClientRect();
161+
hoverCenterX = boundingBox.left + boundingBox.width / 2 - rootBBox.left;
162+
hoverCenterY = boundingBox.top + boundingBox.height / 2 - rootBBox.top;
163+
}
158164

159165
var hovertemplateLabels = {valueLabel: d3.format(d.valueFormat)(d.link.value) + d.valueSuffix};
160166
d.link.fullData = d.link.trace;
161167

162168
var tooltip = Fx.loneHover({
163-
x: hoverCenterX - rootBBox.left,
164-
y: hoverCenterY - rootBBox.top,
169+
x: hoverCenterX,
170+
y: hoverCenterY,
165171
name: hovertemplateLabels.valueLabel,
166172
text: [
167173
d.link.label || '',

0 commit comments

Comments
 (0)