Skip to content

Commit f8bf7fe

Browse files
committed
fallback to d3-sankey if no circularity is present
1 parent 067869f commit f8bf7fe

File tree

5 files changed

+142
-118
lines changed

5 files changed

+142
-118
lines changed

src/traces/sankey/calc.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@ function circularityPresent(nodeList, sources, targets) {
3838
}
3939

4040
module.exports = function calc(gd, trace) {
41-
42-
if(!circularityPresent(trace.node.label, trace.link.source, trace.link.target)) {
43-
// TODO: swap to original sankey engine
41+
var circular = false;
42+
if(circularityPresent(trace.node.label, trace.link.source, trace.link.target)) {
43+
circular = true;
4444
}
4545

4646
var result = convertToD3Sankey(trace);
4747

4848
return wrap({
49+
circular: circular,
4950
_nodes: result.nodes,
5051
_links: result.links
5152
});

src/traces/sankey/convert-to-d3-sankey.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@ module.exports = function(trace) {
3535
target = +target;
3636
linkedNodes[source] = linkedNodes[target] = true;
3737

38+
var label = '';
39+
if(linkSpec.label && linkSpec.label[i]) label = linkSpec.label[i];
40+
3841
links.push({
3942
pointNumber: i,
40-
label: linkSpec.label[i],
43+
label: label,
4144
color: hasLinkColorArray ? linkSpec.color[i] : linkSpec.color,
4245
source: source,
4346
target: target,

src/traces/sankey/render.js

Lines changed: 61 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,15 @@ var d3 = require('d3');
1313
var tinycolor = require('tinycolor2');
1414
var Color = require('../../components/color');
1515
var Drawing = require('../../components/drawing');
16-
// TODO: allow swapping Sankey engine
17-
// var d3Sankey = require('d3-sankey');
16+
var d3Sankey = require('d3-sankey');
1817
var d3SankeyCircular = require('d3-sankey-circular');
1918
var d3Force = require('d3-force');
2019
var Lib = require('../../lib');
2120
var gup = require('../../lib/gup');
2221
var keyFun = gup.keyFun;
2322
var repeat = gup.repeat;
2423
var unwrap = gup.unwrap;
25-
// var interpolateNumber = require('d3-interpolate').interpolateNumber;
24+
var interpolateNumber = require('d3-interpolate').interpolateNumber;
2625

2726
// view models
2827

@@ -40,18 +39,26 @@ function sankeyModel(layout, d, traceIndex) {
4039
var nodes = calcData._nodes;
4140
var links = calcData._links;
4241

43-
var sankey = d3SankeyCircular
44-
.sankeyCircular()
45-
.iterations(c.sankeyIterations)
46-
.size(horizontal ? [width, height] : [height, width])
47-
.nodeWidth(nodeThickness)
48-
.nodePadding(nodePad)
49-
.circularLinkGap(2)
50-
.nodes(nodes)
51-
.links(links)
52-
.nodeId(function(d) {
53-
return d.pointNumber;
54-
});
42+
var circular = calcData.circular;
43+
var sankey;
44+
if(circular) {
45+
sankey = d3SankeyCircular
46+
.sankeyCircular()
47+
.circularLinkGap(2)
48+
.nodeId(function(d) {
49+
return d.pointNumber;
50+
});
51+
} else {
52+
sankey = d3Sankey.sankey();
53+
}
54+
55+
sankey
56+
.iterations(c.sankeyIterations)
57+
.size(horizontal ? [width, height] : [height, width])
58+
.nodeWidth(nodeThickness)
59+
.nodePadding(nodePad)
60+
.nodes(nodes)
61+
.links(links);
5562

5663
var graph = sankey();
5764

@@ -60,6 +67,7 @@ function sankeyModel(layout, d, traceIndex) {
6067
}
6168

6269
return {
70+
circular: circular,
6371
key: traceIndex,
6472
trace: trace,
6573
guid: Math.floor(1e12 * (1 + Math.random())),
@@ -99,6 +107,7 @@ function linkModel(d, l, i) {
99107
l.curveNumber = d.trace.index;
100108

101109
return {
110+
circular: d.circular,
102111
key: key,
103112
traceId: d.key,
104113
pointNumber: l.pointNumber,
@@ -115,34 +124,32 @@ function linkModel(d, l, i) {
115124
}
116125

117126
function linkPath() {
118-
function circular(d) {
119-
return d.link.path;
127+
var curvature = 0.5;
128+
function path(d) {
129+
if(d.circular) {
130+
return d.link.path;
131+
} else {
132+
var x0 = d.link.source.x1,
133+
x1 = d.link.target.x0,
134+
xi = interpolateNumber(x0, x1),
135+
x2 = xi(curvature),
136+
x3 = xi(1 - curvature),
137+
y0a = d.link.y0 - d.link.width / 2,
138+
y0b = d.link.y0 + d.link.width / 2,
139+
y1a = d.link.y1 - d.link.width / 2,
140+
y1b = d.link.y1 + d.link.width / 2;
141+
return 'M' + x0 + ',' + y0a +
142+
'C' + x2 + ',' + y0a +
143+
' ' + x3 + ',' + y1a +
144+
' ' + x1 + ',' + y1a +
145+
'L' + x1 + ',' + y1b +
146+
'C' + x3 + ',' + y1b +
147+
' ' + x2 + ',' + y0b +
148+
' ' + x0 + ',' + y0b +
149+
'Z';
150+
}
120151
}
121-
122-
return circular;
123-
// var curvature = 0.5;
124-
125-
// function shape(d) {
126-
// var x0 = d.link.source.x1,
127-
// x1 = d.link.target.x0,
128-
// xi = interpolateNumber(x0, x1),
129-
// x2 = xi(curvature),
130-
// x3 = xi(1 - curvature),
131-
// y0a = d.link.y0 - d.link.width / 2,
132-
// y0b = d.link.y0 + d.link.width / 2,
133-
// y1a = d.link.y1 - d.link.width / 2,
134-
// y1b = d.link.y1 + d.link.width / 2;
135-
// return 'M' + x0 + ',' + y0a +
136-
// 'C' + x2 + ',' + y0a +
137-
// ' ' + x3 + ',' + y1a +
138-
// ' ' + x1 + ',' + y1a +
139-
// 'L' + x1 + ',' + y1b +
140-
// 'C' + x3 + ',' + y1b +
141-
// ' ' + x2 + ',' + y0b +
142-
// ' ' + x0 + ',' + y0b +
143-
// 'Z';
144-
// }
145-
// return shape;
152+
return path;
146153
}
147154

148155
function nodeModel(d, n, i) {
@@ -493,17 +500,23 @@ module.exports = function(svg, calcData, layout, callbacks) {
493500

494501
sankeyLink
495502
.style('stroke', function(d) {
503+
if(!d.circular) return salientEnough(d) ? Color.tinyRGB(tinycolor(d.linkLineColor)) : d.tinyColorHue;
496504
return d.tinyColorHue;
497-
// return salientEnough(d) ? Color.tinyRGB(tinycolor(d.linkLineColor)) : d.tinyColorHue;
498505
})
499506
.style('stroke-opacity', function(d) {
507+
if(!d.circular) return salientEnough(d) ? Color.opacity(d.linkLineColor) : d.tinyColorAlpha;
500508
return d.tinyColorAlpha;
501-
// return salientEnough(d) ? Color.opacity(d.linkLineColor) : d.tinyColorAlpha;
502509
})
503-
// .style('stroke-width', function(d) {return salientEnough(d) ? d.linkLineWidth : 1;})
504-
//.style('fill', function(d) {return d.link.color;})
505-
//.style('fill-opacity', function(d) {return d.tinyColorAlpha;})
506-
.style('stroke-width', function(d) {return d.link.width;});
510+
.style('fill', function(d) {
511+
if(!d.circular) return d.tinyColorHue;
512+
})
513+
.style('fill-opacity', function(d) {
514+
if(!d.circular) return d.tinyColorAlpha;
515+
})
516+
.style('stroke-width', function(d) {
517+
if(d.circular) return d.link.width;
518+
return salientEnough(d) ? d.linkLineWidth : 1;
519+
});
507520

508521
sankeyLink.transition()
509522
.ease(c.ease).duration(c.duration)

test/jasmine/tests/sankey_d3_sankey_test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ describe('d3-sankey', function() {
9494
expect(node_names).toEqual(['node0', 'node1', 'node2', 'node3', 'node4']);
9595
});
9696

97+
it('keep a list of nodes with x values', function() {
98+
var node_names = s().nodes.map(function(obj) {
99+
return Math.floor(obj.x0);
100+
});
101+
expect(node_names).toEqual([0, 0, 381, 762, 1144]);
102+
});
103+
97104
it('keep a list of links', function() {
98105
var link_widths = s().links.map(function(obj) {
99106
return (obj.width);

test/jasmine/tests/sankey_test.js

Lines changed: 66 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -239,72 +239,72 @@ describe('sankey tests', function() {
239239
});
240240
});
241241

242-
describe('sankey calc', function() {
243-
244-
function _calc(trace) {
245-
var gd = { data: [trace] };
246-
247-
supplyAllDefaults(gd);
248-
var fullTrace = gd._fullData[0];
249-
Sankey.calc(gd, fullTrace);
250-
return fullTrace;
251-
}
252-
253-
var base = { type: 'sankey' };
254-
255-
describe('remove nodes if encountering circularity', function() {
256-
var errors;
257-
258-
beforeEach(function() {
259-
errors = [];
260-
spyOn(Lib, 'error').and.callFake(function(msg) {
261-
errors.push(msg);
262-
});
263-
});
264-
265-
it('removing a single self-pointing node', function() {
266-
expect(errors.length).toBe(0);
267-
268-
var fullTrace = _calc(Lib.extendDeep({}, base, {
269-
node: {
270-
label: ['a']
271-
},
272-
link: {
273-
value: [1],
274-
source: [0],
275-
target: [0]
276-
}
277-
}));
278-
279-
expect(fullTrace.node.label).toEqual([], 'node label(s) removed');
280-
expect(fullTrace.link.value).toEqual([], 'link value(s) removed');
281-
expect(fullTrace.link.source).toEqual([], 'link source(s) removed');
282-
expect(fullTrace.link.target).toEqual([], 'link target(s) removed');
283-
expect(errors.length).toBe(1);
284-
});
285-
286-
it('removing everything if detecting a circle', function() {
287-
expect(errors.length).toBe(0);
288-
289-
var fullTrace = _calc(Lib.extendDeep({}, base, {
290-
node: {
291-
label: ['a', 'b', 'c', 'd', 'e']
292-
},
293-
link: {
294-
value: [1, 1, 1, 1, 1, 1, 1, 1],
295-
source: [0, 1, 2, 3],
296-
target: [1, 2, 0, 4]
297-
}
298-
}));
299-
300-
expect(fullTrace.node.label).toEqual([], 'node label(s) removed');
301-
expect(fullTrace.link.value).toEqual([], 'link value(s) removed');
302-
expect(fullTrace.link.source).toEqual([], 'link source(s) removed');
303-
expect(fullTrace.link.target).toEqual([], 'link target(s) removed');
304-
expect(errors.length).toBe(1);
305-
});
306-
});
307-
});
242+
// describe('sankey calc', function() {
243+
//
244+
// function _calc(trace) {
245+
// var gd = { data: [trace] };
246+
//
247+
// supplyAllDefaults(gd);
248+
// var fullTrace = gd._fullData[0];
249+
// Sankey.calc(gd, fullTrace);
250+
// return fullTrace;
251+
// }
252+
//
253+
// var base = { type: 'sankey' };
254+
//
255+
// describe('remove nodes if encountering circularity', function() {
256+
// var errors;
257+
//
258+
// beforeEach(function() {
259+
// errors = [];
260+
// spyOn(Lib, 'error').and.callFake(function(msg) {
261+
// errors.push(msg);
262+
// });
263+
// });
264+
//
265+
// it('removing a single self-pointing node', function() {
266+
// expect(errors.length).toBe(0);
267+
//
268+
// var fullTrace = _calc(Lib.extendDeep({}, base, {
269+
// node: {
270+
// label: ['a']
271+
// },
272+
// link: {
273+
// value: [1],
274+
// source: [0],
275+
// target: [0]
276+
// }
277+
// }));
278+
//
279+
// expect(fullTrace.node.label).toEqual([], 'node label(s) removed');
280+
// expect(fullTrace.link.value).toEqual([], 'link value(s) removed');
281+
// expect(fullTrace.link.source).toEqual([], 'link source(s) removed');
282+
// expect(fullTrace.link.target).toEqual([], 'link target(s) removed');
283+
// expect(errors.length).toBe(1);
284+
// });
285+
//
286+
// it('removing everything if detecting a circle', function() {
287+
// expect(errors.length).toBe(0);
288+
//
289+
// var fullTrace = _calc(Lib.extendDeep({}, base, {
290+
// node: {
291+
// label: ['a', 'b', 'c', 'd', 'e']
292+
// },
293+
// link: {
294+
// value: [1, 1, 1, 1, 1, 1, 1, 1],
295+
// source: [0, 1, 2, 3],
296+
// target: [1, 2, 0, 4]
297+
// }
298+
// }));
299+
//
300+
// expect(fullTrace.node.label).toEqual([], 'node label(s) removed');
301+
// expect(fullTrace.link.value).toEqual([], 'link value(s) removed');
302+
// expect(fullTrace.link.source).toEqual([], 'link source(s) removed');
303+
// expect(fullTrace.link.target).toEqual([], 'link target(s) removed');
304+
// expect(errors.length).toBe(1);
305+
// });
306+
// });
307+
// });
308308

309309
describe('lifecycle methods', function() {
310310
afterEach(destroyGraphDiv);

0 commit comments

Comments
 (0)