Skip to content

Commit 132c8ea

Browse files
authored
Merge pull request #1591 from plotly/sankey-squashed
[WIP] squashed Sankey feature branch
2 parents 4e2a866 + 7427284 commit 132c8ea

17 files changed

+2698
-1
lines changed

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Plotly.register([
2121
require('./pie'),
2222
require('./contour'),
2323
require('./scatterternary'),
24+
require('./sankey'),
2425

2526
require('./scatter3d'),
2627
require('./surface'),

lib/sankey.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
module.exports = require('../src/traces/sankey');

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,13 @@
5454
},
5555
"dependencies": {
5656
"3d-view": "^2.0.0",
57+
"@plotly/d3-sankey": "^0.5.0",
5758
"alpha-shape": "^1.0.0",
5859
"color-rgba": "^1.0.4",
5960
"convex-hull": "^1.0.3",
6061
"country-regex": "^1.1.0",
6162
"d3": "^3.5.12",
63+
"d3-force": "^1.0.6",
6264
"delaunay-triangulate": "^1.1.6",
6365
"es6-promise": "^3.0.2",
6466
"fast-isnumeric": "^1.1.1",
@@ -92,6 +94,7 @@
9294
"right-now": "^1.0.0",
9395
"robust-orientation": "^1.1.3",
9496
"sane-topojson": "^2.0.0",
97+
"strongly-connected-components": "^1.0.1",
9598
"superscript-text": "^1.0.0",
9699
"tinycolor2": "^1.3.0",
97100
"topojson-client": "^2.1.0",

src/plots/cartesian/graph_interact.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ fx.hover = function(gd, evt, subplot) {
301301
// The actual implementation is here:
302302

303303
function hover(gd, evt, subplot) {
304-
if(subplot === 'pie') {
304+
if(['pie', 'sankey'].indexOf(subplot) !== -1) {
305305
gd.emit('plotly_hover', {
306306
event: evt.originalEvent,
307307
points: [evt]

src/traces/sankey/attributes.js

+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var shapeAttrs = require('../../components/shapes/attributes');
12+
var fontAttrs = require('../../plots/font_attributes');
13+
var plotAttrs = require('../../plots/attributes');
14+
var colorAttrs = require('../../components/color/attributes');
15+
16+
var extendFlat = require('../../lib/extend').extendFlat;
17+
18+
module.exports = {
19+
hoverinfo: extendFlat({}, plotAttrs.hoverinfo, {
20+
flags: ['label', 'text', 'value', 'percent', 'name']
21+
}),
22+
domain: {
23+
x: {
24+
valType: 'info_array',
25+
role: 'info',
26+
items: [
27+
{valType: 'number', min: 0, max: 1},
28+
{valType: 'number', min: 0, max: 1}
29+
],
30+
dflt: [0, 1],
31+
description: [
32+
'Sets the horizontal domain of this `sankey` trace',
33+
'(in plot fraction).'
34+
].join(' ')
35+
},
36+
y: {
37+
valType: 'info_array',
38+
role: 'info',
39+
items: [
40+
{valType: 'number', min: 0, max: 1},
41+
{valType: 'number', min: 0, max: 1}
42+
],
43+
dflt: [0, 1],
44+
description: [
45+
'Sets the vertical domain of this `sankey` trace',
46+
'(in plot fraction).'
47+
].join(' ')
48+
}
49+
},
50+
51+
orientation: {
52+
valType: 'enumerated',
53+
values: ['v', 'h'],
54+
dflt: 'h',
55+
role: 'style',
56+
description: 'Sets the orientation of the Sankey diagram.'
57+
},
58+
59+
valueformat: {
60+
valType: 'string',
61+
dflt: '.3s',
62+
role: 'style',
63+
description: [
64+
'Sets the value formatting rule using d3 formatting mini-language',
65+
'which is similar to those of Python. See',
66+
'https://github.com/d3/d3-format/blob/master/README.md#locale_format'
67+
].join(' ')
68+
},
69+
70+
valuesuffix: {
71+
valType: 'string',
72+
dflt: '',
73+
role: 'style',
74+
description: [
75+
'Adds a unit to follow the value in the hover tooltip. Add a space if a separation',
76+
'is necessary from the value.'
77+
].join(' ')
78+
},
79+
80+
arrangement: {
81+
valType: 'enumerated',
82+
values: ['snap', 'perpendicular', 'freeform', 'fixed'],
83+
dflt: 'snap',
84+
role: 'style',
85+
description: [
86+
'If value is `snap` (the default), the node arrangement is assisted by automatic snapping of elements to',
87+
'preserve space between nodes specified via `nodepad`.',
88+
'If value is `perpendicular`, the nodes can only move along a line perpendicular to the flow.',
89+
'If value is `freeform`, the nodes can freely move on the plane.',
90+
'If value is `fixed`, the nodes are stationary.'
91+
].join(' ')
92+
},
93+
94+
textfont: fontAttrs,
95+
96+
node: {
97+
label: {
98+
valType: 'data_array',
99+
dflt: [],
100+
role: 'info',
101+
description: 'The shown name of the node.'
102+
},
103+
color: extendFlat({}, shapeAttrs.fillcolor, {
104+
arrayOk: true,
105+
description: [
106+
'Sets the `node` color. It can be a single value, or an array for specifying color for each `node`.',
107+
'If `node.color` is omitted, then the default `Plotly` color palette will be cycled through',
108+
'to have a variety of colors. These defaults are not fully opaque, to allow some visibility of',
109+
'what is beneath the node.'
110+
].join(' ')
111+
}),
112+
line: {
113+
color: {
114+
valType: 'color',
115+
role: 'style',
116+
dflt: colorAttrs.defaultLine,
117+
arrayOk: true,
118+
description: [
119+
'Sets the color of the `line` around each `node`.'
120+
].join(' ')
121+
},
122+
width: {
123+
valType: 'number',
124+
role: 'style',
125+
min: 0,
126+
dflt: 0.5,
127+
arrayOk: true,
128+
description: [
129+
'Sets the width (in px) of the `line` around each `node`.'
130+
].join(' ')
131+
}
132+
},
133+
pad: {
134+
valType: 'number',
135+
arrayOk: false,
136+
min: 0,
137+
dflt: 20,
138+
role: 'style',
139+
description: 'Sets the padding (in px) between the `nodes`.'
140+
},
141+
thickness: {
142+
valType: 'number',
143+
arrayOk: false,
144+
min: 1,
145+
dflt: 20,
146+
role: 'style',
147+
description: 'Sets the thickness (in px) of the `nodes`.'
148+
},
149+
description: 'The nodes of the Sankey plot.'
150+
},
151+
152+
link: {
153+
label: {
154+
valType: 'data_array',
155+
dflt: [],
156+
role: 'info',
157+
description: 'The shown name of the link.'
158+
},
159+
color: extendFlat({}, shapeAttrs.fillcolor, {
160+
arrayOk: true,
161+
description: [
162+
'Sets the `link` color. It can be a single value, or an array for specifying color for each `link`.',
163+
'If `link.color` is omitted, then by default, a translucent grey link will be used.'
164+
].join(' ')
165+
}),
166+
line: {
167+
color: {
168+
valType: 'color',
169+
role: 'style',
170+
dflt: colorAttrs.defaultLine,
171+
arrayOk: true,
172+
description: [
173+
'Sets the color of the `line` around each `link`.'
174+
].join(' ')
175+
},
176+
width: {
177+
valType: 'number',
178+
role: 'style',
179+
min: 0,
180+
dflt: 0,
181+
arrayOk: true,
182+
description: [
183+
'Sets the width (in px) of the `line` around each `link`.'
184+
].join(' ')
185+
}
186+
},
187+
source: {
188+
valType: 'data_array',
189+
role: 'info',
190+
dflt: [],
191+
description: 'An integer number `[0..nodes.length - 1]` that represents the source node.'
192+
},
193+
target: {
194+
valType: 'data_array',
195+
role: 'info',
196+
dflt: [],
197+
description: 'An integer number `[0..nodes.length - 1]` that represents the target node.'
198+
},
199+
value: {
200+
valType: 'data_array',
201+
dflt: [],
202+
role: 'info',
203+
description: 'A numeric value representing the flow volume value.'
204+
},
205+
description: 'The links of the Sankey plot.'
206+
}
207+
};

src/traces/sankey/base_plot.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var Plots = require('../../plots/plots');
12+
var plot = require('./plot');
13+
14+
exports.name = 'sankey';
15+
16+
exports.attr = 'type';
17+
18+
exports.plot = function(gd) {
19+
var calcData = Plots.getSubplotCalcData(gd.calcdata, 'sankey', 'sankey');
20+
if(calcData.length) plot(gd, calcData);
21+
};
22+
23+
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
24+
var hadPlot = (oldFullLayout._has && oldFullLayout._has('sankey'));
25+
var hasPlot = (newFullLayout._has && newFullLayout._has('sankey'));
26+
27+
if(hadPlot && !hasPlot) {
28+
oldFullLayout._paperdiv.selectAll('.sankey').remove();
29+
}
30+
};

src/traces/sankey/calc.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var tarjan = require('strongly-connected-components');
12+
var Lib = require('../../lib');
13+
14+
function circularityPresent(nodeList, sources, targets) {
15+
16+
var nodes = nodeList.map(function() {return [];});
17+
18+
for(var i = 0; i < Math.min(sources.length, targets.length); i++) {
19+
if(sources[i] === targets[i]) {
20+
return true; // self-link which is also a scc of one
21+
}
22+
nodes[sources[i]].push(targets[i]);
23+
}
24+
25+
var scc = tarjan(nodes);
26+
27+
// Tarján's strongly connected components algorithm coded by Mikola Lysenko
28+
// returns at least one non-singular component if there's circularity in the graph
29+
return scc.components.some(function(c) {
30+
return c.length > 1;
31+
});
32+
}
33+
34+
module.exports = function calc(gd, trace) {
35+
36+
if(circularityPresent(trace.node.label, trace.link.source, trace.link.target)) {
37+
Lib.error('Circularity is present in the Sankey data. Removing all nodes and links.');
38+
trace.link.label = [];
39+
trace.link.source = [];
40+
trace.link.target = [];
41+
trace.link.value = [];
42+
trace.link.color = [];
43+
trace.node.label = [];
44+
trace.node.color = [];
45+
}
46+
47+
return [{
48+
link: trace.link,
49+
node: trace.node
50+
}];
51+
};

src/traces/sankey/constants.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
module.exports = {
12+
nodeTextOffsetHorizontal: 4,
13+
nodeTextOffsetVertical: 3,
14+
nodePadAcross: 10,
15+
sankeyIterations: 50,
16+
forceIterations: 5,
17+
forceTicksPerFrame: 10,
18+
duration: 500,
19+
ease: 'linear'
20+
};

0 commit comments

Comments
 (0)