Skip to content

Commit d1d852a

Browse files
committed
render circular links as closed paths
1 parent 9ca4d5d commit d1d852a

File tree

5 files changed

+137
-19
lines changed

5 files changed

+137
-19
lines changed

src/traces/sankey/attributes.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,7 @@ var attrs = module.exports = overrideAll({
177177
dflt: colorAttrs.defaultLine,
178178
arrayOk: true,
179179
description: [
180-
'Sets the color of the `line` around each `link`.',
181-
'Note that in the presence of circular links this attribute is ignored.'
180+
'Sets the color of the `line` around each `link`.'
182181
].join(' ')
183182
},
184183
width: {
@@ -188,8 +187,7 @@ var attrs = module.exports = overrideAll({
188187
dflt: 0,
189188
arrayOk: true,
190189
description: [
191-
'Sets the width (in px) of the `line` around each `link`.',
192-
'Note that in the presence of circular links this attribute is ignored.'
190+
'Sets the width (in px) of the `line` around each `link`.'
193191
].join(' ')
194192
}
195193
},

src/traces/sankey/plot.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,12 @@ function linkHoveredStyle(d, sankey, visitNodes, sankeyLink) {
7373
var label = sankeyLink.datum().link.label;
7474

7575
sankeyLink.style('fill-opacity', 0.4);
76-
sankeyLink.style('stroke-opacity', 0.4);
7776

7877
if(label) {
7978
ownTrace(sankey, d)
8079
.selectAll('.' + cn.sankeyLink)
8180
.filter(function(l) {return l.link.label === label;})
82-
.style('fill-opacity', 0.4)
83-
.style('stroke-opacity', 0.4);
81+
.style('fill-opacity', 0.4);
8482
}
8583

8684
if(visitNodes) {
@@ -96,13 +94,11 @@ function linkNonHoveredStyle(d, sankey, visitNodes, sankeyLink) {
9694
var label = sankeyLink.datum().link.label;
9795

9896
sankeyLink.style('fill-opacity', function(d) {return d.tinyColorAlpha;});
99-
sankeyLink.style('stroke-opacity', function(d) {return d.tinyColorAlpha;});
10097
if(label) {
10198
ownTrace(sankey, d)
10299
.selectAll('.' + cn.sankeyLink)
103100
.filter(function(l) {return l.link.label === label;})
104-
.style('fill-opacity', function(d) {return d.tinyColorAlpha;})
105-
.style('stroke-opacity', function(d) {return d.tinyColorAlpha;});
101+
.style('fill-opacity', function(d) {return d.tinyColorAlpha;});
106102
}
107103

108104
if(visitNodes) {

src/traces/sankey/render.js

Lines changed: 125 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,130 @@ function linkModel(d, l, i) {
124124
};
125125
}
126126

127+
function createCircularClosedPathString(link) {
128+
// Using coordinates computed by d3-sankey-circular
129+
var pathString = '';
130+
var offset = link.width / 2;
131+
var coords = link.circularPathData;
132+
if(link.circularLinkType === 'top') {
133+
// Top path
134+
pathString =
135+
// start at the left of the target node
136+
'M ' +
137+
coords.targetX + ' ' + (coords.targetY + offset) + ' ' +
138+
'L' +
139+
coords.rightInnerExtent + ' ' + (coords.targetY + offset) +
140+
'A' +
141+
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightSmallArcRadius + offset) + ' 0 0 1 ' +
142+
(coords.rightFullExtent - offset) + ' ' + (coords.targetY - coords.rightSmallArcRadius) +
143+
'L' +
144+
(coords.rightFullExtent - offset) + ' ' + coords.verticalRightInnerExtent +
145+
'A' +
146+
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 1 ' +
147+
coords.rightInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
148+
'L' +
149+
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
150+
'A' +
151+
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftLargeArcRadius + offset) + ' 0 0 1 ' +
152+
(coords.leftFullExtent + offset) + ' ' + coords.verticalLeftInnerExtent +
153+
'L' +
154+
(coords.leftFullExtent + offset) + ' ' + (coords.sourceY - coords.leftSmallArcRadius) +
155+
'A' +
156+
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftSmallArcRadius + offset) + ' 0 0 1 ' +
157+
coords.leftInnerExtent + ' ' + (coords.sourceY + offset) +
158+
'L' +
159+
coords.sourceX + ' ' + (coords.sourceY + offset) +
160+
161+
// Walking back
162+
'L' +
163+
coords.sourceX + ' ' + (coords.sourceY - offset) +
164+
'L' +
165+
coords.leftInnerExtent + ' ' + (coords.sourceY - offset) +
166+
'A' +
167+
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftSmallArcRadius - offset) + ' 0 0 0 ' +
168+
(coords.leftFullExtent - offset) + ' ' + (coords.sourceY - coords.leftSmallArcRadius) +
169+
'L' +
170+
(coords.leftFullExtent - offset) + ' ' + coords.verticalLeftInnerExtent +
171+
'A' +
172+
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftLargeArcRadius - offset) + ' 0 0 0 ' +
173+
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
174+
'L' +
175+
coords.rightInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
176+
'A' +
177+
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightLargeArcRadius - offset) + ' 0 0 0 ' +
178+
(coords.rightFullExtent + offset) + ' ' + coords.verticalRightInnerExtent +
179+
'L' +
180+
(coords.rightFullExtent + offset) + ' ' + (coords.targetY - coords.rightSmallArcRadius) +
181+
'A' +
182+
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 0 ' +
183+
coords.rightInnerExtent + ' ' + (coords.targetY - offset) +
184+
'L' +
185+
coords.targetX + ' ' + (coords.targetY - offset) +
186+
'Z';
187+
} else {
188+
// Bottom path
189+
pathString =
190+
// start at the left of the target node
191+
'M ' +
192+
coords.targetX + ' ' + (coords.targetY - offset) + ' ' +
193+
'L' +
194+
coords.rightInnerExtent + ' ' + (coords.targetY - offset) +
195+
'A' +
196+
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightSmallArcRadius + offset) + ' 0 0 0 ' +
197+
(coords.rightFullExtent - offset) + ' ' + (coords.targetY + coords.rightSmallArcRadius) +
198+
'L' +
199+
(coords.rightFullExtent - offset) + ' ' + coords.verticalRightInnerExtent +
200+
'A' +
201+
(coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 0 ' +
202+
coords.rightInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
203+
'L' +
204+
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
205+
'A' +
206+
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftLargeArcRadius + offset) + ' 0 0 0 ' +
207+
(coords.leftFullExtent + offset) + ' ' + coords.verticalLeftInnerExtent +
208+
'L' +
209+
(coords.leftFullExtent + offset) + ' ' + (coords.sourceY + coords.leftSmallArcRadius) +
210+
'A' +
211+
(coords.leftLargeArcRadius + offset) + ' ' + (coords.leftSmallArcRadius + offset) + ' 0 0 0 ' +
212+
coords.leftInnerExtent + ' ' + (coords.sourceY - offset) +
213+
'L' +
214+
coords.sourceX + ' ' + (coords.sourceY - offset) +
215+
216+
// Walking back
217+
'L' +
218+
coords.sourceX + ' ' + (coords.sourceY + offset) +
219+
'L' +
220+
coords.leftInnerExtent + ' ' + (coords.sourceY + offset) +
221+
'A' +
222+
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftSmallArcRadius - offset) + ' 0 0 1 ' +
223+
(coords.leftFullExtent - offset) + ' ' + (coords.sourceY + coords.leftSmallArcRadius) +
224+
'L' +
225+
(coords.leftFullExtent - offset) + ' ' + coords.verticalLeftInnerExtent +
226+
'A' +
227+
(coords.leftLargeArcRadius - offset) + ' ' + (coords.leftLargeArcRadius - offset) + ' 0 0 1 ' +
228+
coords.leftInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
229+
'L' +
230+
coords.rightInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
231+
'A' +
232+
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightLargeArcRadius - offset) + ' 0 0 1 ' +
233+
(coords.rightFullExtent + offset) + ' ' + coords.verticalRightInnerExtent +
234+
'L' +
235+
(coords.rightFullExtent + offset) + ' ' + (coords.targetY + coords.rightSmallArcRadius) +
236+
'A' +
237+
(coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 1 ' +
238+
coords.rightInnerExtent + ' ' + (coords.targetY + offset) +
239+
'L' +
240+
coords.targetX + ' ' + (coords.targetY + offset) +
241+
'Z';
242+
}
243+
return pathString;
244+
}
245+
127246
function linkPath() {
128247
var curvature = 0.5;
129248
function path(d) {
130-
if(d.circular) {
131-
return d.link.path; // TODO: turn this into a closed path to support link.line.(width|color)
249+
if(d.link.circular) {
250+
return createCircularClosedPathString(d.link);
132251
} else {
133252
var x0 = d.link.source.x1;
134253
var x1 = d.link.target.x0;
@@ -502,21 +621,18 @@ module.exports = function(svg, calcData, layout, callbacks) {
502621

503622
sankeyLink
504623
.style('stroke', function(d) {
505-
if(!d.circular) return salientEnough(d) ? Color.tinyRGB(tinycolor(d.linkLineColor)) : d.tinyColorHue;
506-
return d.tinyColorHue;
624+
return salientEnough(d) ? Color.tinyRGB(tinycolor(d.linkLineColor)) : d.tinyColorHue;
507625
})
508626
.style('stroke-opacity', function(d) {
509-
if(!d.circular) return salientEnough(d) ? Color.opacity(d.linkLineColor) : d.tinyColorAlpha;
510-
return d.tinyColorAlpha;
627+
return salientEnough(d) ? Color.opacity(d.linkLineColor) : d.tinyColorAlpha;
511628
})
512629
.style('fill', function(d) {
513-
if(!d.circular) return d.tinyColorHue;
630+
return d.tinyColorHue;
514631
})
515632
.style('fill-opacity', function(d) {
516-
if(!d.circular) return d.tinyColorAlpha;
633+
return d.tinyColorAlpha;
517634
})
518635
.style('stroke-width', function(d) {
519-
if(d.circular) return d.link.width;
520636
return salientEnough(d) ? d.linkLineWidth : 1;
521637
});
522638

Loading

test/image/mocks/sankey_circular_process.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,18 @@
22
"data": [{
33
"type": "sankey",
44
"node": {
5+
"line": {
6+
"width": 1,
7+
"color": "black"
8+
},
59
"pad": 5,
610
"label": ["startA", "startB", "process1", "process2", "process3", "process4", "process5", "process6", "process7", "process8", "process9", "process10", "process11", "process12", "process13", "process14", "process15", "process16", "finishA", "finishB"]
711
},
812
"link": {
13+
"line": {
14+
"width": 1,
15+
"color": "black"
16+
},
917
"source": [0, 0, 0, 1, 1, 2, 5, 3, 2, 6, 7, 5, 7, 5, 4, 4, 16, 14, 8, 9, 9, 17, 9, 12, 5, 13, 8, 16, 11, 11, 15, 10, 17, 10, 16, 16, 12],
1018
"target": [9, 6, 7, 2, 6, 5, 2, 8, 4, 2, 0, 3, 9, 1, 3, 1, 14, 10, 1, 2, 17, 10, 12, 11, 13, 12, 16, 15, 14, 17, 19, 18, 9, 19, 19, 18, 16],
1119
"value": [20, 20, 20, 15, 15, 30, 10, 35, 20, 20, 5, 5, 15, 5, 15, 5, 10, 10, 20, 10, 10, 10, 25, 20, 10, 10, 15, 10, 10, 10, 10, 10, 10, 10, 10, 10, 25]

0 commit comments

Comments
 (0)