Skip to content

Commit 426b50d

Browse files
committed
flipping attrib representation to columnar
1 parent 1fb7cbd commit 426b50d

File tree

6 files changed

+940
-403
lines changed

6 files changed

+940
-403
lines changed

src/traces/sankey/attributes.js

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -108,43 +108,40 @@ module.exports = {
108108

109109
textfont: fontAttrs,
110110

111-
nodes: {
112-
_isLinkedToArray: 'node',
111+
node: {
113112
label: {
114-
valType: 'string',
113+
valType: 'data_array',
115114
dflt: '',
116115
role: 'info',
117116
description: 'The shown name of the node.'
118117
},
119-
visible: shapeAttrs.visible,
120118
color: extendFlat({}, shapeAttrs.fillcolor, {
119+
arrayOk: true,
121120
dflt: 'rgb(0,255,0,0.5)'
122121
}),
123122
description: 'The nodes of the Sankey plot.'
124123
},
125124

126-
links: {
127-
_isLinkedToArray: 'link',
125+
link: {
128126
label: {
129-
valType: 'string',
127+
valType: 'data_array',
130128
dflt: '',
131129
role: 'info',
132130
description: 'The shown name of the link.'
133131
},
134-
visible: shapeAttrs.visible,
135-
color: extendFlat({}, shapeAttrs.fillcolor, {dflt: 'rgba(0,0,0,0.2)'}),
132+
color: extendFlat({}, shapeAttrs.fillcolor, {arrayOk: true, dflt: 'rgba(0,0,0,0.2)'}),
136133
source: {
137-
valType: 'number',
134+
valType: 'data_array',
138135
role: 'info',
139136
description: 'An integer number `[0..nodes.length - 1]` that represents the source node.'
140137
},
141138
target: {
142-
valType: 'number',
139+
valType: 'data_array',
143140
role: 'info',
144141
description: 'An integer number `[0..nodes.length - 1]` that represents the target node.'
145142
},
146143
value: {
147-
valType: 'number',
144+
valType: 'data_array',
148145
dflt: 1,
149146
role: 'info',
150147
description: 'A numeric value representing the flow volume value.'

src/traces/sankey/defaults.js

Lines changed: 38 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -14,111 +14,34 @@ var colors = require('../../components/color/attributes').defaults;
1414
var Color = require('../../components/color');
1515
var tinycolor = require('tinycolor2');
1616

17-
function linksDefaults(traceIn, traceOut, layout) {
18-
var linksIn = traceIn.links || [],
19-
linksOut = traceOut.links = [];
20-
21-
var linkIn, linkOut, i;
22-
23-
function coerce(attr, dflt) {
24-
return Lib.coerce(linkIn, linkOut, attributes.links, attr, dflt);
25-
}
26-
27-
for(i = 0; i < linksIn.length; i++) {
28-
linkIn = linksIn[i];
29-
linkOut = {};
30-
31-
if(!Lib.isPlainObject(linkIn)) {
32-
continue;
33-
}
34-
35-
var visible = coerce('visible');
36-
37-
if(visible) {
38-
coerce('label');
39-
coerce('value');
40-
coerce('source');
41-
coerce('target');
42-
if(tinycolor(layout.paper_bgcolor).getLuminance() < 0.333) {
43-
coerce('color', 'rgba(255, 255, 255, 0.6)');
44-
} else {
45-
coerce('color', 'rgba(0, 0, 0, 0.2)');
46-
}
47-
}
48-
49-
linkOut._index = i;
50-
linksOut.push(linkOut);
51-
}
17+
function circularityPresent() {
18+
return false;
5219
}
5320

54-
function nodesDefaults(traceIn, traceOut) {
55-
var nodesIn = traceIn.nodes || [],
56-
nodesOut = traceOut.nodes = [];
57-
58-
var nodeIn, nodeOut, i, j, link, foundUse, visible,
59-
usedNodeCount = 0,
60-
indexMap = [];
61-
62-
var defaultPalette = function(i) {return colors[i % colors.length];};
21+
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
6322

6423
function coerce(attr, dflt) {
65-
return Lib.coerce(nodeIn, nodeOut, attributes.nodes, attr, dflt);
24+
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
6625
}
6726

68-
for(i = 0; i < nodesIn.length; i++) {
69-
nodeIn = nodesIn[i];
70-
71-
foundUse = false;
72-
for(j = 0; j < traceOut.links.length && !foundUse; j++) {
73-
link = traceOut.links[j];
74-
foundUse = link.source === i || link.target === i;
75-
}
76-
77-
indexMap.push(foundUse ? usedNodeCount : null);
78-
79-
if(!foundUse) {
80-
continue;
81-
}
27+
coerce('node.label');
8228

83-
if(!Lib.isPlainObject(nodeIn)) {
84-
continue;
85-
}
29+
var defaultNodePalette = function(i) {return colors[i % colors.length];};
8630

87-
nodeOut = {};
31+
coerce('node.color', traceIn.node.label.map(function(d, i) {
32+
return Color.addOpacity(defaultNodePalette(i), 0.8);
33+
}));
8834

89-
visible = coerce('visible');
35+
coerce('link.label');
36+
coerce('link.source');
37+
coerce('link.target');
38+
coerce('link.value');
9039

91-
if(visible) {
92-
coerce('label');
93-
if(nodeIn.color) {
94-
coerce('color');
95-
} else {
96-
coerce('color', Color.addOpacity(defaultPalette(i), 0.8));
97-
}
98-
}
99-
100-
nodeOut._index = usedNodeCount;
101-
nodesOut.push(nodeOut);
102-
usedNodeCount++;
103-
}
104-
105-
// since nodes were removed, update indices to nodes in links to reflect new reality
106-
for(j = 0; j < traceOut.links.length; j++) {
107-
link = traceOut.links[j];
108-
link.source = indexMap[link.source];
109-
link.target = indexMap[link.target];
110-
}
111-
112-
return nodesOut;
113-
}
114-
115-
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
116-
function coerce(attr, dflt) {
117-
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
118-
}
119-
120-
linksDefaults(traceIn, traceOut, layout);
121-
nodesDefaults(traceIn, traceOut);
40+
coerce('link.color', traceIn.link.value.map(function() {
41+
return tinycolor(layout.paper_bgcolor).getLuminance() < 0.333 ?
42+
'rgba(255, 255, 255, 0.6)' :
43+
'rgba(0, 0, 0, 0.2)';
44+
}));
12245

12346
coerce('hoverinfo', layout._dataLength === 1 ? 'label+text+value+percent' : undefined);
12447

@@ -131,7 +54,24 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
13154
coerce('valuesuffix');
13255
coerce('arrangement');
13356

134-
// Prefer Sankey-specific font spec e.g. with smaller default size
135-
var sankeyFontSpec = Lib.coerceFont(coerce, 'textfont');
136-
Lib.coerceFont(coerce, 'textfont', Lib.extendFlat({}, layout.font, sankeyFontSpec));
57+
Lib.coerceFont(coerce, 'textfont', Lib.extendFlat({}, layout.font));
58+
59+
var missing = function(n, i) {
60+
return traceIn.link.source.indexOf(i) === -1 &&
61+
traceIn.link.target.indexOf(i) === -1;
62+
};
63+
if(traceIn.node.label.some(missing)) {
64+
Lib.log('Some of the nodes are neither sources nor targets, please remove them.');
65+
}
66+
67+
if(circularityPresent(traceIn.link.source, traceIn.link.target)) {
68+
Lib.log('Circularity is present in the Sankey data. Removing all nodes and links.');
69+
traceOut.link.label = [];
70+
traceOut.link.source = [];
71+
traceOut.link.target = [];
72+
traceOut.link.value = [];
73+
traceOut.link.color = [];
74+
traceOut.node.label = [];
75+
traceOut.node.color = [];
76+
}
13777
};

src/traces/sankey/plot.js

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -171,23 +171,11 @@ module.exports = function plot(gd, calcData) {
171171
Fx.click(gd, { target: true });
172172
};
173173

174-
var repositioned = function(d, nodes) {
175-
var n, nOrig;
176-
var names = gd.data[0].nodes.map(function(nn) {return nn.label;});
177-
for(var i = 0; i < nodes.length; i++) {
178-
n = nodes[i];
179-
nOrig = gd.data[d.traceId].nodes[names.indexOf(n.label)];
180-
nOrig.parallel = (n.x - n.dx / 2) / n.width;
181-
nOrig.perpendicular = (n.y - n.dy / 2) / n.height;
182-
}
183-
};
184-
185174
var nodeHover = function(element, d, sankey) {
186175
d3.select(element).call(nodeHoveredStyle, d, sankey);
187176
Fx.hover(gd, d.node, 'sankey');
188177
};
189178

190-
191179
var nodeHoverFollow = function(element, d) {
192180

193181
var nodeRect = d3.select(element).select('.nodeRect');
@@ -252,8 +240,7 @@ module.exports = function plot(gd, calcData) {
252240
hover: nodeHover,
253241
follow: nodeHoverFollow,
254242
unhover: nodeUnhover,
255-
select: nodeSelect,
256-
repositioned: repositioned
243+
select: nodeSelect
257244
}
258245
}
259246
);

src/traces/sankey/render.js

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ var Color = require('../../components/color');
1515
var Drawing = require('../../components/drawing');
1616
var d3sankey = require('@monfera/d3-sankey').sankey;
1717
var d3Force = require('d3-force');
18+
var Lib = require('../../lib');
1819

1920
// basic data utilities
2021

2122
function keyFun(d) {return d.key;}
2223
function repeat(d) {return [d];} // d3 data binding convention
2324
function unwrap(d) {return d[0];} // plotly data structure convention
24-
// function visible(nodeOrLink) {return !('visible' in nodeOrLink) || nodeOrLink.visible;}
2525

2626
function persistOriginalPlace(nodes) {
2727
var i, distinctLayerPositions = [];
@@ -68,8 +68,8 @@ function sankeyModel(layout, d, i) {
6868

6969
var trace = unwrap(d).trace,
7070
domain = trace.domain,
71-
nodes = trace.nodes,
72-
links = trace.links,
71+
nodeSpec = trace.node,
72+
linkSpec = trace.link,
7373
arrangement = trace.arrangement,
7474
horizontal = trace.orientation === 'h',
7575
nodePad = trace.nodepad,
@@ -81,6 +81,23 @@ function sankeyModel(layout, d, i) {
8181
var width = layout.width * (domain.x[1] - domain.x[0]),
8282
height = layout.height * (domain.y[1] - domain.y[0]);
8383

84+
var nodes = nodeSpec.label.map(function(l, i) {
85+
return {
86+
label: l,
87+
color: Lib.isArray(nodeSpec.color) ? nodeSpec.color[i] : nodeSpec.color
88+
};
89+
});
90+
91+
var links = linkSpec.value.map(function(d, i) {
92+
return {
93+
label: linkSpec.label[i],
94+
color: Lib.isArray(linkSpec.color) ? linkSpec.color[i] : linkSpec.color,
95+
source: linkSpec.source[i],
96+
target: linkSpec.target[i],
97+
value: d
98+
};
99+
});
100+
84101
var sankey = d3sankey()
85102
.size(horizontal ? [width, height] : [height, width])
86103
.nodeWidth(nodeThickness)
@@ -266,8 +283,6 @@ function attachPointerEvents(selection, sankey, eventSet) {
266283

267284
function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
268285

269-
var repositionedCallback = callbacks.nodeEvents.repositioned;
270-
271286
var dragBehavior = d3.behavior.drag()
272287

273288
.origin(function(d) {return d.horizontal ? d.node : {x: d.node.y, y: d.node.x};})
@@ -286,7 +301,7 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
286301
if(d.forceLayouts[forceKey]) {
287302
d.forceLayouts[forceKey].alpha(1);
288303
} else { // make a forceLayout iff needed
289-
attachForce(sankeyNode, forceKey, repositionedCallback, d);
304+
attachForce(sankeyNode, forceKey, d);
290305
}
291306
startForce(sankeyNode, sankeyLink, d, forceKey);
292307
}
@@ -314,9 +329,6 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
314329
})
315330

316331
.on('dragend', function(d) {
317-
if(d.arrangement !== 'snap') {
318-
repositionedCallback(d, [d.node]);
319-
}
320332
d.interactionState.dragInProgress = false;
321333
});
322334

@@ -325,15 +337,15 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
325337
.call(dragBehavior);
326338
}
327339

328-
function attachForce(sankeyNode, forceKey, repositionedCallback, d) {
340+
function attachForce(sankeyNode, forceKey, d) {
329341
var nodes = d.sankey.nodes().filter(function(n) {return n.originalX === d.node.originalX;});
330342
d.forceLayouts[forceKey] = d3Force.forceSimulation(nodes)
331343
.alphaDecay(0)
332344
.force('collide', d3Force.forceCollide()
333345
.radius(function(n) {return n.dy / 2 + d.nodePad / 2;})
334346
.strength(1)
335347
.iterations(c.forceIterations))
336-
.force('constrain', snappingForce(sankeyNode, forceKey, nodes, repositionedCallback, d))
348+
.force('constrain', snappingForce(sankeyNode, forceKey, nodes, d))
337349
.stop();
338350
}
339351

@@ -350,7 +362,7 @@ function startForce(sankeyNode, sankeyLink, d, forceKey) {
350362
});
351363
}
352364

353-
function snappingForce(sankeyNode, forceKey, nodes, repositionedCallback, d) {
365+
function snappingForce(sankeyNode, forceKey, nodes, d) {
354366
return function _snappingForce() {
355367
var maxVelocity = 0;
356368
for(var i = 0; i < nodes.length; i++) {
@@ -368,7 +380,6 @@ function snappingForce(sankeyNode, forceKey, nodes, repositionedCallback, d) {
368380
d.forceLayouts[forceKey].alpha(0);
369381
window.setTimeout(function() {
370382
sankeyNode.call(crispLinesOnEnd);
371-
repositionedCallback(d, nodes);
372383
}, 30); // geome on move, crisp when static
373384
}
374385
};
@@ -416,7 +427,7 @@ module.exports = function(svg, styledData, layout, callbacks) {
416427
.data(function(d) {
417428
var uniqueKeys = {};
418429
return d.sankey.links()
419-
.filter(function(l) {return l.visible && l.value;})
430+
.filter(function(l) {return l.value;})
420431
.map(linkModel.bind(null, uniqueKeys, d));
421432
}, keyFun);
422433

@@ -464,7 +475,7 @@ module.exports = function(svg, styledData, layout, callbacks) {
464475
var uniqueKeys = {};
465476
persistOriginalPlace(nodes);
466477
return nodes
467-
.filter(function(n) {return n.visible && n.value;})
478+
.filter(function(n) {return n.value;})
468479
.map(nodeModel.bind(null, uniqueKeys, d));
469480
}, keyFun);
470481

0 commit comments

Comments
 (0)