Skip to content

Commit 4f3503a

Browse files
committed
add support for sankey links with arrows
1 parent 28835f7 commit 4f3503a

File tree

5 files changed

+82
-32
lines changed

5 files changed

+82
-32
lines changed

src/traces/sankey/attributes.js

+8
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,14 @@ var attrs = module.exports = overrideAll({
168168
},
169169

170170
link: {
171+
arrowwidth: {
172+
valType: 'number',
173+
min: 0,
174+
dflt: 0,
175+
description: [
176+
'Sets the width (in px) of the links arrow, if 0 no arrow will be drawn.'
177+
].join(' ')
178+
},
171179
label: {
172180
valType: 'data_array',
173181
dflt: [],

src/traces/sankey/defaults.js

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
5252
return Lib.coerce(linkIn, linkOut, attributes.link, attr, dflt);
5353
}
5454
coerceLink('label');
55+
coerceLink('arrowwidth');
5556
coerceLink('source');
5657
coerceLink('target');
5758
coerceLink('value');

src/traces/sankey/render.js

+46-32
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ function sankeyModel(layout, d, traceIndex) {
271271
nodeLineWidth: trace.node.line.width,
272272
linkLineColor: trace.link.line.color,
273273
linkLineWidth: trace.link.line.width,
274+
linkArrowWidth: trace.link.arrowwidth,
274275
valueFormat: trace.valueformat,
275276
valueSuffix: trace.valuesuffix,
276277
textFont: trace.textfont,
@@ -309,6 +310,7 @@ function linkModel(d, l, i) {
309310
linkPath: linkPath,
310311
linkLineColor: d.linkLineColor,
311312
linkLineWidth: d.linkLineWidth,
313+
linkArrowWidth: d.linkArrowWidth,
312314
valueFormat: d.valueFormat,
313315
valueSuffix: d.valueSuffix,
314316
sankey: d.sankey,
@@ -318,7 +320,7 @@ function linkModel(d, l, i) {
318320
};
319321
}
320322

321-
function createCircularClosedPathString(link) {
323+
function createCircularClosedPathString(link, arrowWidth) {
322324
// Using coordinates computed by d3-sankey-circular
323325
var pathString = '';
324326
var offset = link.width / 2;
@@ -328,17 +330,17 @@ function createCircularClosedPathString(link) {
328330
pathString =
329331
// start at the left of the target node
330332
'M ' +
331-
coords.targetX + ' ' + (coords.targetY + offset) + ' ' +
333+
(coords.targetX - arrowWidth) + ' ' + (coords.targetY + offset) + ' ' +
332334
'L' +
333-
coords.rightInnerExtent + ' ' + (coords.targetY + offset) +
335+
(coords.rightInnerExtent - arrowWidth) + ' ' + (coords.targetY + offset) +
334336
'A' +
335337
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightSmallArcRadius + offset) + ' 0 0 1 ' +
336-
(coords.rightFullExtent - offset) + ' ' + (coords.targetY - coords.rightSmallArcRadius) +
338+
(coords.rightFullExtent - offset - arrowWidth) + ' ' + (coords.targetY - coords.rightSmallArcRadius) +
337339
'L' +
338-
(coords.rightFullExtent - offset) + ' ' + coords.verticalRightInnerExtent +
340+
(coords.rightFullExtent - offset - arrowWidth) + ' ' + coords.verticalRightInnerExtent +
339341
'A' +
340342
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 1 ' +
341-
coords.rightInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
343+
(coords.rightInnerExtent - arrowWidth) + ' ' + (coords.verticalFullExtent - offset) +
342344
'L' +
343345
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
344346
'A' +
@@ -366,34 +368,35 @@ function createCircularClosedPathString(link) {
366368
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftLargeArcRadius - offset) + ' 0 0 0 ' +
367369
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
368370
'L' +
369-
coords.rightInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
371+
(coords.rightInnerExtent - arrowWidth) + ' ' + (coords.verticalFullExtent + offset) +
370372
'A' +
371373
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightLargeArcRadius - offset) + ' 0 0 0 ' +
372-
(coords.rightFullExtent + offset) + ' ' + coords.verticalRightInnerExtent +
374+
(coords.rightFullExtent + offset - arrowWidth) + ' ' + coords.verticalRightInnerExtent +
373375
'L' +
374-
(coords.rightFullExtent + offset) + ' ' + (coords.targetY - coords.rightSmallArcRadius) +
376+
(coords.rightFullExtent + offset - arrowWidth) + ' ' + (coords.targetY - coords.rightSmallArcRadius) +
375377
'A' +
376378
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 0 ' +
377-
coords.rightInnerExtent + ' ' + (coords.targetY - offset) +
379+
(coords.rightInnerExtent - arrowWidth) + ' ' + (coords.targetY - offset) +
378380
'L' +
379-
coords.targetX + ' ' + (coords.targetY - offset) +
381+
(coords.targetX - arrowWidth) + ' ' + (coords.targetY - offset) +
382+
(arrowWidth > 0 ? 'L ' + coords.targetX + ' ' + (coords.targetY) : '') +
380383
'Z';
381384
} else {
382385
// Bottom path
383386
pathString =
384387
// start at the left of the target node
385388
'M ' +
386-
coords.targetX + ' ' + (coords.targetY - offset) + ' ' +
389+
(coords.targetX - arrowWidth) + ' ' + (coords.targetY - offset) + ' ' +
387390
'L' +
388-
coords.rightInnerExtent + ' ' + (coords.targetY - offset) +
391+
(coords.rightInnerExtent - arrowWidth) + ' ' + (coords.targetY - offset) +
389392
'A' +
390393
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightSmallArcRadius + offset) + ' 0 0 0 ' +
391-
(coords.rightFullExtent - offset) + ' ' + (coords.targetY + coords.rightSmallArcRadius) +
394+
(coords.rightFullExtent - offset - arrowWidth) + ' ' + (coords.targetY + coords.rightSmallArcRadius) +
392395
'L' +
393-
(coords.rightFullExtent - offset) + ' ' + coords.verticalRightInnerExtent +
396+
(coords.rightFullExtent - offset - arrowWidth) + ' ' + coords.verticalRightInnerExtent +
394397
'A' +
395398
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 0 ' +
396-
coords.rightInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
399+
(coords.rightInnerExtent - arrowWidth) + ' ' + (coords.verticalFullExtent + offset) +
397400
'L' +
398401
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
399402
'A' +
@@ -421,17 +424,18 @@ function createCircularClosedPathString(link) {
421424
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftLargeArcRadius - offset) + ' 0 0 1 ' +
422425
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
423426
'L' +
424-
coords.rightInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
427+
(coords.rightInnerExtent - arrowWidth) + ' ' + (coords.verticalFullExtent - offset) +
425428
'A' +
426429
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightLargeArcRadius - offset) + ' 0 0 1 ' +
427-
(coords.rightFullExtent + offset) + ' ' + coords.verticalRightInnerExtent +
430+
(coords.rightFullExtent + offset - arrowWidth) + ' ' + coords.verticalRightInnerExtent +
428431
'L' +
429-
(coords.rightFullExtent + offset) + ' ' + (coords.targetY + coords.rightSmallArcRadius) +
432+
(coords.rightFullExtent + offset - arrowWidth) + ' ' + (coords.targetY + coords.rightSmallArcRadius) +
430433
'A' +
431434
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 1 ' +
432-
coords.rightInnerExtent + ' ' + (coords.targetY + offset) +
435+
(coords.rightInnerExtent - arrowWidth) + ' ' + (coords.targetY + offset) +
433436
'L' +
434-
coords.targetX + ' ' + (coords.targetY + offset) +
437+
(coords.targetX - arrowWidth) + ' ' + (coords.targetY + offset) +
438+
(arrowWidth > 0 ? 'L ' + coords.targetX + ' ' + (coords.targetY) : '') +
435439
'Z';
436440
}
437441
return pathString;
@@ -441,26 +445,36 @@ function linkPath() {
441445
var curvature = 0.5;
442446
function path(d) {
443447
if(d.link.circular) {
444-
return createCircularClosedPathString(d.link);
448+
return createCircularClosedPathString(d.link, d.linkArrowWidth);
445449
} else {
450+
var arrowWidth = d.linkArrowWidth;
446451
var x0 = d.link.source.x1;
447-
var x1 = d.link.target.x0;
452+
var x1 = d.link.target.x0 - arrowWidth;
448453
var xi = interpolateNumber(x0, x1);
449454
var x2 = xi(curvature);
450455
var x3 = xi(1 - curvature);
451456
var y0a = d.link.y0 - d.link.width / 2;
452457
var y0b = d.link.y0 + d.link.width / 2;
453458
var y1a = d.link.y1 - d.link.width / 2;
454459
var y1b = d.link.y1 + d.link.width / 2;
455-
return 'M' + x0 + ',' + y0a +
456-
'C' + x2 + ',' + y0a +
457-
' ' + x3 + ',' + y1a +
458-
' ' + x1 + ',' + y1a +
459-
'L' + x1 + ',' + y1b +
460-
'C' + x3 + ',' + y1b +
461-
' ' + x2 + ',' + y0b +
462-
' ' + x0 + ',' + y0b +
463-
'Z';
460+
var start = 'M' + x0 + ',' + y0a;
461+
var upperCurve = 'C' + x2 + ',' + y0a +
462+
' ' + x3 + ',' + y1a +
463+
' ' + x1 + ',' + y1a;
464+
var lowerCurve = 'C' + x3 + ',' + y1b +
465+
' ' + x2 + ',' + y0b +
466+
' ' + x0 + ',' + y0b;
467+
var rightEnd;
468+
469+
if(arrowWidth > 0) {
470+
rightEnd =
471+
'L' + (x1 + arrowWidth) + ',' + (y1a + d.link.width / 2) +
472+
' L' + x1 + ',' + y1b
473+
;
474+
} else {
475+
rightEnd = 'L' + x1 + ',' + y1b;
476+
}
477+
return start + upperCurve + rightEnd + lowerCurve + 'Z';
464478
}
465479
}
466480
return path;
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"data": [
3+
{
4+
"type": "sankey",
5+
"node": {
6+
"pad": 5,
7+
"label": ["0", "1", "2", "3", "4", "5", "6"]
8+
},
9+
"link": {
10+
"arrowwidth": 20,
11+
"source": [
12+
0, 0, 1, 2, 5, 4, 3
13+
],
14+
"target": [
15+
5, 3, 4, 3, 0, 2, 2
16+
],
17+
"value": [
18+
1, 2, 1, 1, 1, 1, 1
19+
]
20+
}
21+
}],
22+
"layout": {
23+
"title": {"text": "Sankey with circular data and arrows"},
24+
"width": 800,
25+
"height": 800
26+
}
27+
}

0 commit comments

Comments
 (0)