Skip to content

Commit 6faeb7d

Browse files
authored
Merge pull request #7272 from giuseppe-straziota/dev-plotly-branch
Fix: Improvements to avoid distortions in sankey diagram links loops
2 parents f8d18de + 9898921 commit 6faeb7d

File tree

2 files changed

+158
-109
lines changed

2 files changed

+158
-109
lines changed

draftlogs/7272_fix.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Fix distortions in sankey diagram links loops [[#7272](https://github.com/plotly/plotly.js/pull/7272)],
2+
with thanks to @giuseppe-straziota for the contribution!

src/traces/sankey/render.js

+156-109
Original file line numberDiff line numberDiff line change
@@ -335,118 +335,165 @@ function createCircularClosedPathString(link, arrowLen) {
335335
var pathString = '';
336336
var offset = link.width / 2;
337337
var coords = link.circularPathData;
338-
if(link.circularLinkType === 'top') {
339-
// Top path
338+
var isSourceBeforeTarget = coords.sourceX + coords.verticalBuffer < coords.targetX;
339+
var isPathOverlapped = (coords.rightFullExtent - coords.rightLargeArcRadius - arrowLen) <= (coords.leftFullExtent - offset)
340+
var diff = Math.abs(coords.rightFullExtent- coords.leftFullExtent - offset) < offset ;
341+
if (link.circularLinkType === 'top') {
340342
pathString =
341-
// start at the left of the target node
342-
'M ' +
343-
(coords.targetX - arrowLen) + ' ' + (coords.targetY + offset) + ' ' +
344-
'L' +
345-
(coords.rightInnerExtent - arrowLen) + ' ' + (coords.targetY + offset) +
346-
'A' +
347-
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightSmallArcRadius + offset) + ' 0 0 1 ' +
348-
(coords.rightFullExtent - offset - arrowLen) + ' ' + (coords.targetY - coords.rightSmallArcRadius) +
349-
'L' +
350-
(coords.rightFullExtent - offset - arrowLen) + ' ' + coords.verticalRightInnerExtent +
351-
'A' +
352-
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 1 ' +
353-
(coords.rightInnerExtent - arrowLen) + ' ' + (coords.verticalFullExtent - offset) +
354-
'L' +
355-
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
356-
'A' +
357-
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftLargeArcRadius + offset) + ' 0 0 1 ' +
358-
(coords.leftFullExtent + offset) + ' ' + coords.verticalLeftInnerExtent +
359-
'L' +
360-
(coords.leftFullExtent + offset) + ' ' + (coords.sourceY - coords.leftSmallArcRadius) +
361-
'A' +
362-
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftSmallArcRadius + offset) + ' 0 0 1 ' +
363-
coords.leftInnerExtent + ' ' + (coords.sourceY + offset) +
364-
'L' +
365-
coords.sourceX + ' ' + (coords.sourceY + offset) +
366-
367-
// Walking back
368-
'L' +
369-
coords.sourceX + ' ' + (coords.sourceY - offset) +
370-
'L' +
371-
coords.leftInnerExtent + ' ' + (coords.sourceY - offset) +
372-
'A' +
373-
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftSmallArcRadius - offset) + ' 0 0 0 ' +
374-
(coords.leftFullExtent - offset) + ' ' + (coords.sourceY - coords.leftSmallArcRadius) +
375-
'L' +
376-
(coords.leftFullExtent - offset) + ' ' + coords.verticalLeftInnerExtent +
377-
'A' +
378-
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftLargeArcRadius - offset) + ' 0 0 0 ' +
379-
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
380-
'L' +
381-
(coords.rightInnerExtent - arrowLen) + ' ' + (coords.verticalFullExtent + offset) +
382-
'A' +
383-
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightLargeArcRadius - offset) + ' 0 0 0 ' +
384-
(coords.rightFullExtent + offset - arrowLen) + ' ' + coords.verticalRightInnerExtent +
385-
'L' +
386-
(coords.rightFullExtent + offset - arrowLen) + ' ' + (coords.targetY - coords.rightSmallArcRadius) +
387-
'A' +
388-
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 0 ' +
389-
(coords.rightInnerExtent - arrowLen) + ' ' + (coords.targetY - offset) +
390-
'L' +
391-
(coords.targetX - arrowLen) + ' ' + (coords.targetY - offset) +
392-
(arrowLen > 0 ? 'L' + coords.targetX + ' ' + (coords.targetY) : '') +
393-
'Z';
343+
// start at the left of the target node
344+
'M ' +
345+
(coords.targetX - arrowLen) + ' ' + (coords.targetY + offset) + ' ' +
346+
'L ' +
347+
(coords.rightInnerExtent - arrowLen) + ' ' + (coords.targetY + offset) +
348+
'A ' +
349+
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightSmallArcRadius + offset) + ' 0 0 1 ' +
350+
(coords.rightFullExtent - offset - arrowLen) + ' ' + (coords.targetY - coords.rightSmallArcRadius) +
351+
'L ' + (coords.rightFullExtent - offset - arrowLen) + ' ' + coords.verticalRightInnerExtent;
352+
353+
if (isSourceBeforeTarget && isPathOverlapped) {
354+
pathString += ' A ' +
355+
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 1 ' +
356+
(coords.rightFullExtent + offset - arrowLen - (coords.rightLargeArcRadius - offset)) + ' ' +
357+
(coords.verticalRightInnerExtent - (coords.rightLargeArcRadius + offset)) +
358+
' L ' +
359+
(coords.rightFullExtent + offset - (coords.rightLargeArcRadius - offset) - arrowLen) + ' ' +
360+
(coords.verticalRightInnerExtent - (coords.rightLargeArcRadius + offset)) +
361+
' A ' +
362+
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftLargeArcRadius + offset) + ' 0 0 1 ' +
363+
(coords.leftFullExtent + offset) + ' ' + coords.verticalRightInnerExtent;
364+
} else if (isSourceBeforeTarget) {
365+
pathString += ' A ' +
366+
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightLargeArcRadius - offset) + ' 0 0 0 ' +
367+
(coords.rightFullExtent - offset - arrowLen - (coords.rightLargeArcRadius - offset)) + ' ' +
368+
(coords.verticalRightInnerExtent - (coords.rightLargeArcRadius - offset)) +
369+
' L ' +
370+
(coords.leftFullExtent + offset + (coords.rightLargeArcRadius - offset)) + ' ' +
371+
(coords.verticalRightInnerExtent - (coords.rightLargeArcRadius - offset)) +
372+
' A ' +
373+
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftLargeArcRadius - offset) + ' 0 0 0 ' +
374+
(coords.leftFullExtent + offset) + ' ' + coords.verticalLeftInnerExtent;
375+
} else {
376+
pathString += ' A ' +
377+
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 1 ' +
378+
(coords.rightInnerExtent - arrowLen) + ' ' + (coords.verticalFullExtent - offset) +
379+
' L ' +
380+
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
381+
' A ' +
382+
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftLargeArcRadius + offset) + ' 0 0 1 ' +
383+
(coords.leftFullExtent + offset) + ' ' + coords.verticalLeftInnerExtent;
384+
}
385+
386+
pathString += ' L ' +
387+
(coords.leftFullExtent + offset) + ' ' + (coords.sourceY - coords.leftSmallArcRadius) +
388+
' A ' +
389+
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftSmallArcRadius + offset) + ' 0 0 1 ' +
390+
coords.leftInnerExtent + ' ' + (coords.sourceY + offset) +
391+
' L ' +
392+
coords.sourceX + ' ' + (coords.sourceY + offset) +
393+
394+
// Walking back
395+
' L ' +
396+
coords.sourceX + ' ' + (coords.sourceY - offset) +
397+
' L ' +
398+
coords.leftInnerExtent + ' ' + (coords.sourceY - offset) +
399+
' A ' +
400+
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftSmallArcRadius - offset) + ' 0 0 0 ' +
401+
(coords.leftFullExtent - offset) + ' ' + (coords.sourceY - coords.leftSmallArcRadius) +
402+
' L ' +
403+
(coords.leftFullExtent - offset) + ' ' + coords.verticalLeftInnerExtent;
404+
405+
if (isSourceBeforeTarget && isPathOverlapped) {
406+
pathString += ' A ' +
407+
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftSmallArcRadius + offset) + ' 0 0 0 ' +
408+
(coords.leftFullExtent - offset) + ' ' + (coords.verticalFullExtent + offset) +
409+
'L' + (coords.rightFullExtent + offset - arrowLen) + ' ' + (coords.verticalFullExtent + offset) +
410+
' A ' +
411+
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftSmallArcRadius + offset) + ' 0 0 0 ' +
412+
(coords.rightFullExtent + offset - arrowLen) + ' ' + coords.verticalRightInnerExtent;
413+
} else if (isSourceBeforeTarget) {
414+
pathString += ' A ' +
415+
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftSmallArcRadius + offset) + ' 0 0 1 ' +
416+
(coords.leftFullExtent + offset) + ' ' + (coords.verticalFullExtent - offset) +
417+
' L ' +
418+
(coords.rightFullExtent - offset - arrowLen) + ' ' + (coords.verticalFullExtent - offset) +
419+
' A ' +
420+
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftSmallArcRadius + offset) + ' 0 0 1 ' +
421+
(coords.rightFullExtent + offset - arrowLen) + ' ' + coords.verticalRightInnerExtent;
422+
} else {
423+
pathString += ' A ' +
424+
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftLargeArcRadius - offset) + ' 0 0 0 ' +
425+
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
426+
' L ' +
427+
(coords.rightInnerExtent - arrowLen) + ' ' + (coords.verticalFullExtent + offset) +
428+
' A ' +
429+
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightLargeArcRadius - offset) + ' 0 0 0 ' +
430+
(coords.rightFullExtent + offset - arrowLen) + ' ' + coords.verticalRightInnerExtent;
431+
}
432+
433+
pathString += ' L ' +
434+
(coords.rightFullExtent + offset - arrowLen) + ' ' + (coords.targetY - coords.rightSmallArcRadius) +
435+
' A ' +
436+
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 0 ' +
437+
(coords.rightInnerExtent - arrowLen) + ' ' + (coords.targetY - offset) +
438+
' L ' +
439+
(coords.targetX - arrowLen) + ' ' + (coords.targetY - offset) +
440+
(arrowLen > 0 ? ' L ' + coords.targetX + ' ' + coords.targetY : '') +
441+
'Z';
394442
} else {
395-
// Bottom path
396443
pathString =
397-
// start at the left of the target node
398-
'M ' +
399-
(coords.targetX - arrowLen) + ' ' + (coords.targetY - offset) + ' ' +
400-
'L' +
401-
(coords.rightInnerExtent - arrowLen) + ' ' + (coords.targetY - offset) +
402-
'A' +
403-
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightSmallArcRadius + offset) + ' 0 0 0 ' +
404-
(coords.rightFullExtent - offset - arrowLen) + ' ' + (coords.targetY + coords.rightSmallArcRadius) +
405-
'L' +
406-
(coords.rightFullExtent - offset - arrowLen) + ' ' + coords.verticalRightInnerExtent +
407-
'A' +
408-
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 0 ' +
409-
(coords.rightInnerExtent - arrowLen) + ' ' + (coords.verticalFullExtent + offset) +
410-
'L' +
411-
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
412-
'A' +
413-
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftLargeArcRadius + offset) + ' 0 0 0 ' +
414-
(coords.leftFullExtent + offset) + ' ' + coords.verticalLeftInnerExtent +
415-
'L' +
416-
(coords.leftFullExtent + offset) + ' ' + (coords.sourceY + coords.leftSmallArcRadius) +
417-
'A' +
418-
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftSmallArcRadius + offset) + ' 0 0 0 ' +
419-
coords.leftInnerExtent + ' ' + (coords.sourceY - offset) +
420-
'L' +
421-
coords.sourceX + ' ' + (coords.sourceY - offset) +
422-
423-
// Walking back
424-
'L' +
425-
coords.sourceX + ' ' + (coords.sourceY + offset) +
426-
'L' +
427-
coords.leftInnerExtent + ' ' + (coords.sourceY + offset) +
428-
'A' +
429-
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftSmallArcRadius - offset) + ' 0 0 1 ' +
430-
(coords.leftFullExtent - offset) + ' ' + (coords.sourceY + coords.leftSmallArcRadius) +
431-
'L' +
432-
(coords.leftFullExtent - offset) + ' ' + coords.verticalLeftInnerExtent +
433-
'A' +
434-
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftLargeArcRadius - offset) + ' 0 0 1 ' +
435-
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
436-
'L' +
437-
(coords.rightInnerExtent - arrowLen) + ' ' + (coords.verticalFullExtent - offset) +
438-
'A' +
439-
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightLargeArcRadius - offset) + ' 0 0 1 ' +
440-
(coords.rightFullExtent + offset - arrowLen) + ' ' + coords.verticalRightInnerExtent +
441-
'L' +
442-
(coords.rightFullExtent + offset - arrowLen) + ' ' + (coords.targetY + coords.rightSmallArcRadius) +
443-
'A' +
444-
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 1 ' +
445-
(coords.rightInnerExtent - arrowLen) + ' ' + (coords.targetY + offset) +
446-
'L' +
447-
(coords.targetX - arrowLen) + ' ' + (coords.targetY + offset) +
448-
(arrowLen > 0 ? 'L' + coords.targetX + ' ' + (coords.targetY) : '') +
449-
'Z';
444+
'M ' + (coords.targetX - arrowLen) + ' ' + (coords.targetY - offset) + ' ' +
445+
' L ' + (coords.rightInnerExtent - arrowLen) + ' ' + (coords.targetY - offset) +
446+
' A ' + (coords.rightLargeArcRadius + offset) + ' ' + (coords.rightSmallArcRadius + offset) + ' 0 0 0 ' + (coords.rightFullExtent - offset - arrowLen) + ' ' + (coords.targetY + coords.rightSmallArcRadius) +
447+
' L ' + (coords.rightFullExtent - offset - arrowLen) + ' ' + coords.verticalRightInnerExtent;
448+
449+
if (isSourceBeforeTarget && isPathOverlapped) {
450+
pathString += ' A ' + (coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 0 ' +
451+
(coords.rightInnerExtent - offset - arrowLen) + ' ' + (coords.verticalFullExtent + offset) +
452+
' L ' + (coords.rightFullExtent + offset - arrowLen - (coords.rightLargeArcRadius - offset)) + ' ' + (coords.verticalFullExtent + offset) +
453+
' A ' + (coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 0 ' +
454+
(coords.leftFullExtent + offset) + ' ' + coords.verticalLeftInnerExtent;
455+
} else if (isSourceBeforeTarget) {
456+
pathString += ' A ' + (coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 1 ' +
457+
(coords.rightFullExtent - arrowLen - offset - (coords.rightLargeArcRadius - offset)) + ' ' + (coords.verticalFullExtent - offset) +
458+
' L ' + (coords.leftFullExtent + offset + (coords.rightLargeArcRadius - offset)) + ' ' + (coords.verticalFullExtent - offset) +
459+
' A ' + (coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 1 ' +
460+
(coords.leftFullExtent + offset) + ' ' + coords.verticalLeftInnerExtent;
461+
} else {
462+
pathString += ' A ' + (coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 0 ' + (coords.rightInnerExtent - arrowLen) + ' ' + (coords.verticalFullExtent + offset) +
463+
' L ' + coords.leftInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
464+
' A ' + (coords.leftLargeArcRadius + offset) + ' ' + (coords.leftLargeArcRadius + offset) + ' 0 0 0 ' + (coords.leftFullExtent + offset) + ' ' + coords.verticalLeftInnerExtent;
465+
}
466+
467+
pathString += ' L ' + (coords.leftFullExtent + offset) + ' ' + (coords.sourceY + coords.leftSmallArcRadius) +
468+
' A ' + (coords.leftLargeArcRadius + offset) + ' ' + (coords.leftSmallArcRadius + offset) + ' 0 0 0 ' + coords.leftInnerExtent + ' ' + (coords.sourceY - offset) +
469+
' L ' + coords.sourceX + ' ' + (coords.sourceY - offset) +
470+
471+
// Walking back
472+
' L ' + coords.sourceX + ' ' + (coords.sourceY + offset) +
473+
' L ' + coords.leftInnerExtent + ' ' + (coords.sourceY + offset) +
474+
' A ' + (coords.leftLargeArcRadius - offset) + ' ' + (coords.leftSmallArcRadius - offset) + ' 0 0 1 ' + (coords.leftFullExtent - offset) + ' ' + (coords.sourceY + coords.leftSmallArcRadius) +
475+
' L ' + (coords.leftFullExtent - offset) + ' ' + coords.verticalLeftInnerExtent;
476+
477+
if (isSourceBeforeTarget && isPathOverlapped) {
478+
pathString +=
479+
' A ' + (coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 1 ' +
480+
(coords.leftFullExtent - offset - (coords.rightLargeArcRadius - offset)) + ' ' + (coords.verticalFullExtent - offset) +
481+
' L ' + (coords.rightFullExtent + offset - arrowLen + (coords.rightLargeArcRadius - offset)) + ' ' + (coords.verticalFullExtent - offset) +
482+
' A ' + (coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 1 ' +
483+
(coords.rightFullExtent + offset - arrowLen) + ' ' + coords.verticalRightInnerExtent;
484+
} else if (isSourceBeforeTarget) {
485+
pathString += ' A ' + (coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 0 ' + (coords.leftFullExtent + offset) + ' ' + (coords.verticalFullExtent + offset) +
486+
' L ' + (coords.rightFullExtent - arrowLen - offset) + ' ' + (coords.verticalFullExtent + offset) +
487+
' A ' + (coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 0 ' + (coords.rightFullExtent + offset - arrowLen) + ' ' + coords.verticalRightInnerExtent;
488+
} else {
489+
pathString += ' A ' + (coords.leftLargeArcRadius - offset) + ' ' + (coords.leftLargeArcRadius - offset) + ' 0 0 1 ' + coords.leftInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
490+
' L ' + (coords.rightInnerExtent - arrowLen) + ' ' + (coords.verticalFullExtent - offset) +
491+
' A ' + (coords.rightLargeArcRadius - offset) + ' ' + (coords.rightLargeArcRadius - offset) + ' 0 0 1 ' + (coords.rightFullExtent + offset - arrowLen) + ' ' + coords.verticalRightInnerExtent;
492+
}
493+
494+
pathString += ' L ' + (coords.rightFullExtent + offset - arrowLen) + ' ' + (coords.targetY + coords.rightSmallArcRadius) +
495+
' A ' + (coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 1 ' + (coords.rightInnerExtent - arrowLen) + ' ' + (coords.targetY + offset) +
496+
' L ' + (coords.targetX - arrowLen) + ' ' + (coords.targetY + offset) + (arrowLen > 0 ? ' L ' + coords.targetX + ' ' + coords.targetY : '') + 'Z';
450497
}
451498
return pathString;
452499
}

0 commit comments

Comments
 (0)