diff --git a/package-lock.json b/package-lock.json
index 71b3cc0d2e5..38d7d9146b8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -118,13 +118,13 @@
"integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q=="
},
"@plotly/d3-sankey": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/@plotly/d3-sankey/-/d3-sankey-0.5.1.tgz",
- "integrity": "sha512-uMToNGexOSLG0hBm+uAzElfFW0Pt2utgJ//puL5nuerNnPnRTTe3Un7XFVcWqRhvXEViF00Xq/8wGoA8i8eZJA==",
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/@plotly/d3-sankey/-/d3-sankey-0.7.2.tgz",
+ "integrity": "sha512-2jdVos1N3mMp3QW0k2q1ph7Gd6j5PY1YihBrwpkFnKqO+cqtZq3AdEYUeSGXMeLsBDQYiqTVcihYfk8vr5tqhw==",
"requires": {
"d3-array": "1",
"d3-collection": "1",
- "d3-interpolate": "1"
+ "d3-shape": "^1.2.0"
}
},
"@types/bluebird": {
@@ -2327,11 +2327,35 @@
"d3-color": "1"
}
},
+ "d3-path": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.7.tgz",
+ "integrity": "sha512-q0cW1RpvA5c5ma2rch62mX8AYaiLX0+bdaSM2wxSU9tXjU4DNvkx9qiUvjkuWCj3p22UO/hlPivujqMiR9PDzA=="
+ },
"d3-quadtree": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.3.tgz",
"integrity": "sha1-rHmH4+I/6AWpkPKOG1DTj8uCJDg="
},
+ "d3-sankey-circular": {
+ "version": "0.32.0",
+ "resolved": "https://registry.npmjs.org/d3-sankey-circular/-/d3-sankey-circular-0.32.0.tgz",
+ "integrity": "sha512-lZvF25xPFNzsHkI4VPAxRVH+U6iZnWKswknOmXd5jl88obWZ7iJJG/nhE49B0+I6ZDsUB78ggsMmSR3S5m9+Sg==",
+ "requires": {
+ "d3-array": "^1.2.1",
+ "d3-collection": "^1.0.4",
+ "d3-shape": "^1.2.0",
+ "elementary-circuits-directed-graph": "^1.0.4"
+ }
+ },
+ "d3-shape": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.2.2.tgz",
+ "integrity": "sha512-hUGEozlKecFZ2bOSNt7ENex+4Tk9uc/m0TtTEHBvitCBxUNjhzm5hS2GrrVRD/ae4IylSmxGeqX5tWC2rASMlQ==",
+ "requires": {
+ "d3-path": "1"
+ }
+ },
"d3-timer": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.7.tgz",
@@ -3023,6 +3047,14 @@
"resolved": "https://registry.npmjs.org/element-size/-/element-size-1.1.1.tgz",
"integrity": "sha1-ZOXxWdlxIWMYRby67K8nnDm1404="
},
+ "elementary-circuits-directed-graph": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/elementary-circuits-directed-graph/-/elementary-circuits-directed-graph-1.0.4.tgz",
+ "integrity": "sha512-+xpVxSimU+fcHiTRPWrRN1IFOKaygwotCtZGSBle/rnFaFAoI+4Y8/pimAY1cKiDIHTek2Zox1R7SEQAB/AQ1g==",
+ "requires": {
+ "strongly-connected-components": "^1.0.1"
+ }
+ },
"elliptic": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz",
diff --git a/package.json b/package.json
index 3d102c33e99..d975860f75f 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,6 @@
},
"dependencies": {
"3d-view": "^2.0.0",
- "@plotly/d3-sankey": "^0.5.1",
"alpha-shape": "^1.0.0",
"array-range": "^1.0.1",
"canvas-fit": "^1.5.0",
@@ -66,7 +65,10 @@
"convex-hull": "^1.0.3",
"country-regex": "^1.1.0",
"d3": "^3.5.12",
+ "@plotly/d3-sankey": "0.7.2",
+ "d3-sankey-circular": "0.32.0",
"d3-force": "^1.0.6",
+ "d3-interpolate": "1",
"delaunay-triangulate": "^1.1.6",
"es6-promise": "^3.0.2",
"fast-isnumeric": "^1.1.2",
diff --git a/src/traces/sankey/calc.js b/src/traces/sankey/calc.js
index 161799b5817..8e9a5756517 100644
--- a/src/traces/sankey/calc.js
+++ b/src/traces/sankey/calc.js
@@ -12,6 +12,77 @@ var tarjan = require('strongly-connected-components');
var Lib = require('../../lib');
var wrap = require('../../lib/gup').wrap;
+var isArrayOrTypedArray = Lib.isArrayOrTypedArray;
+var isIndex = Lib.isIndex;
+
+
+function convertToD3Sankey(trace) {
+ var nodeSpec = trace.node;
+ var linkSpec = trace.link;
+
+ var links = [];
+ var hasLinkColorArray = isArrayOrTypedArray(linkSpec.color);
+ var linkedNodes = {};
+
+ var nodeCount = nodeSpec.label.length;
+ var i;
+ for(i = 0; i < linkSpec.value.length; i++) {
+ var val = linkSpec.value[i];
+ // remove negative values, but keep zeros with special treatment
+ var source = linkSpec.source[i];
+ var target = linkSpec.target[i];
+ if(!(val > 0 && isIndex(source, nodeCount) && isIndex(target, nodeCount))) {
+ continue;
+ }
+
+ source = +source;
+ target = +target;
+ linkedNodes[source] = linkedNodes[target] = true;
+
+ var label = '';
+ if(linkSpec.label && linkSpec.label[i]) label = linkSpec.label[i];
+
+ links.push({
+ pointNumber: i,
+ label: label,
+ color: hasLinkColorArray ? linkSpec.color[i] : linkSpec.color,
+ source: source,
+ target: target,
+ value: +val
+ });
+ }
+
+ var hasNodeColorArray = isArrayOrTypedArray(nodeSpec.color);
+ var nodes = [];
+ var removedNodes = false;
+ var nodeIndices = {};
+
+ for(i = 0; i < nodeCount; i++) {
+ if(linkedNodes[i]) {
+ var l = nodeSpec.label[i];
+ nodeIndices[i] = nodes.length;
+ nodes.push({
+ pointNumber: i,
+ label: l,
+ color: hasNodeColorArray ? nodeSpec.color[i] : nodeSpec.color
+ });
+ } else removedNodes = true;
+ }
+
+ // need to re-index links now, since we didn't put all the nodes in
+ if(removedNodes) {
+ for(i = 0; i < links.length; i++) {
+ links[i].source = nodeIndices[links[i].source];
+ links[i].target = nodeIndices[links[i].target];
+ }
+ }
+
+ return {
+ links: links,
+ nodes: nodes
+ };
+}
+
function circularityPresent(nodeList, sources, targets) {
var nodeLen = nodeList.length;
@@ -36,20 +107,16 @@ function circularityPresent(nodeList, sources, targets) {
}
module.exports = function calc(gd, trace) {
-
+ var circular = false;
if(circularityPresent(trace.node.label, trace.link.source, trace.link.target)) {
- Lib.error('Circularity is present in the Sankey data. Removing all nodes and links.');
- trace.link.label = [];
- trace.link.source = [];
- trace.link.target = [];
- trace.link.value = [];
- trace.link.color = [];
- trace.node.label = [];
- trace.node.color = [];
+ circular = true;
}
+ var result = convertToD3Sankey(trace);
+
return wrap({
- link: trace.link,
- node: trace.node
+ circular: circular,
+ _nodes: result.nodes,
+ _links: result.links
});
};
diff --git a/src/traces/sankey/plot.js b/src/traces/sankey/plot.js
index 65611bf7b82..cc04eb441ed 100644
--- a/src/traces/sankey/plot.js
+++ b/src/traces/sankey/plot.js
@@ -94,7 +94,6 @@ function linkNonHoveredStyle(d, sankey, visitNodes, sankeyLink) {
var label = sankeyLink.datum().link.label;
sankeyLink.style('fill-opacity', function(d) {return d.tinyColorAlpha;});
-
if(label) {
ownTrace(sankey, d)
.selectAll('.' + cn.sankeyLink)
@@ -152,16 +151,23 @@ module.exports = function plot(gd, calcData) {
var obj = d.link.trace.link;
if(obj.hoverinfo === 'none' || obj.hoverinfo === 'skip') return;
var rootBBox = gd._fullLayout._paperdiv.node().getBoundingClientRect();
- var boundingBox = element.getBoundingClientRect();
- var hoverCenterX = boundingBox.left + boundingBox.width / 2;
- var hoverCenterY = boundingBox.top + boundingBox.height / 2;
+ var hoverCenterX;
+ var hoverCenterY;
+ if(d.link.circular) {
+ hoverCenterX = (d.link.circularPathData.leftInnerExtent + d.link.circularPathData.rightInnerExtent) / 2 + d.parent.translateX;
+ hoverCenterY = d.link.circularPathData.verticalFullExtent + d.parent.translateY;
+ } else {
+ var boundingBox = element.getBoundingClientRect();
+ hoverCenterX = boundingBox.left + boundingBox.width / 2 - rootBBox.left;
+ hoverCenterY = boundingBox.top + boundingBox.height / 2 - rootBBox.top;
+ }
var hovertemplateLabels = {valueLabel: d3.format(d.valueFormat)(d.link.value) + d.valueSuffix};
d.link.fullData = d.link.trace;
var tooltip = Fx.loneHover({
- x: hoverCenterX - rootBBox.left,
- y: hoverCenterY - rootBBox.top,
+ x: hoverCenterX,
+ y: hoverCenterY,
name: hovertemplateLabels.valueLabel,
text: [
d.link.label || '',
diff --git a/src/traces/sankey/render.js b/src/traces/sankey/render.js
index c80f84e0aee..249ebab281d 100644
--- a/src/traces/sankey/render.js
+++ b/src/traces/sankey/render.js
@@ -13,179 +13,83 @@ var d3 = require('d3');
var tinycolor = require('tinycolor2');
var Color = require('../../components/color');
var Drawing = require('../../components/drawing');
-var d3sankey = require('@plotly/d3-sankey').sankey;
+var d3Sankey = require('@plotly/d3-sankey');
+var d3SankeyCircular = require('d3-sankey-circular');
var d3Force = require('d3-force');
var Lib = require('../../lib');
-var isArrayOrTypedArray = Lib.isArrayOrTypedArray;
-var isIndex = Lib.isIndex;
var gup = require('../../lib/gup');
var keyFun = gup.keyFun;
var repeat = gup.repeat;
var unwrap = gup.unwrap;
-
-// basic data utilities
-
-function persistOriginalPlace(nodes) {
- var i;
- var distinctLayerPositions = [];
- for(i = 0; i < nodes.length; i++) {
- nodes[i].originalX = nodes[i].x;
- nodes[i].originalY = nodes[i].y;
- if(distinctLayerPositions.indexOf(nodes[i].x) === -1) {
- distinctLayerPositions.push(nodes[i].x);
- }
- }
- distinctLayerPositions.sort(function(a, b) {return a - b;});
- for(i = 0; i < nodes.length; i++) {
- nodes[i].originalLayerIndex = distinctLayerPositions.indexOf(nodes[i].originalX);
- nodes[i].originalLayer = nodes[i].originalLayerIndex / (distinctLayerPositions.length - 1);
- }
-}
-
-function saveCurrentDragPosition(d) {
- d.lastDraggedX = d.x;
- d.lastDraggedY = d.y;
-}
-
-function sameLayer(d) {
- return function(n) {return n.node.originalX === d.node.originalX;};
-}
-
-function switchToForceFormat(nodes) {
- // force uses x, y as centers
- for(var i = 0; i < nodes.length; i++) {
- nodes[i].y = nodes[i].y + nodes[i].dy / 2;
- }
-}
-
-function switchToSankeyFormat(nodes) {
- // sankey uses x, y as top left
- for(var i = 0; i < nodes.length; i++) {
- nodes[i].y = nodes[i].y - nodes[i].dy / 2;
- }
-}
+var interpolateNumber = require('d3-interpolate').interpolateNumber;
// view models
function sankeyModel(layout, d, traceIndex) {
- var trace = unwrap(d).trace;
+ var calcData = unwrap(d);
+ var trace = calcData.trace;
var domain = trace.domain;
- var nodeSpec = trace.node;
- var linkSpec = trace.link;
- var arrangement = trace.arrangement;
var horizontal = trace.orientation === 'h';
var nodePad = trace.node.pad;
var nodeThickness = trace.node.thickness;
- var nodeLineColor = trace.node.line.color;
- var nodeLineWidth = trace.node.line.width;
- var linkLineColor = trace.link.line.color;
- var linkLineWidth = trace.link.line.width;
- var valueFormat = trace.valueformat;
- var valueSuffix = trace.valuesuffix;
- var textFont = trace.textfont;
var width = layout.width * (domain.x[1] - domain.x[0]);
var height = layout.height * (domain.y[1] - domain.y[0]);
- var links = [];
- var hasLinkColorArray = isArrayOrTypedArray(linkSpec.color);
- var linkedNodes = {};
-
- var nodeCount = nodeSpec.label.length;
- var i;
- for(i = 0; i < linkSpec.value.length; i++) {
- var val = linkSpec.value[i];
- // remove negative values, but keep zeros with special treatment
- var source = linkSpec.source[i];
- var target = linkSpec.target[i];
- if(!(val > 0 && isIndex(source, nodeCount) && isIndex(target, nodeCount))) {
- continue;
- }
-
- source = +source;
- target = +target;
- linkedNodes[source] = linkedNodes[target] = true;
-
- links.push({
- pointNumber: i,
- label: linkSpec.label[i],
- color: hasLinkColorArray ? linkSpec.color[i] : linkSpec.color,
- source: source,
- target: target,
- value: +val
- });
- }
-
- var hasNodeColorArray = isArrayOrTypedArray(nodeSpec.color);
- var nodes = [];
- var removedNodes = false;
- var nodeIndices = {};
- for(i = 0; i < nodeCount; i++) {
- if(linkedNodes[i]) {
- var l = nodeSpec.label[i];
- nodeIndices[i] = nodes.length;
- nodes.push({
- pointNumber: i,
- label: l,
- color: hasNodeColorArray ? nodeSpec.color[i] : nodeSpec.color
+ var nodes = calcData._nodes;
+ var links = calcData._links;
+ var circular = calcData.circular;
+
+ // Select Sankey generator
+ var sankey;
+ if(circular) {
+ sankey = d3SankeyCircular
+ .sankeyCircular()
+ .circularLinkGap(0)
+ .nodeId(function(d) {
+ return d.pointNumber;
});
- }
- else removedNodes = true;
+ } else {
+ sankey = d3Sankey.sankey();
}
- // need to re-index links now, since we didn't put all the nodes in
- if(removedNodes) {
- for(i = 0; i < links.length; i++) {
- links[i].source = nodeIndices[links[i].source];
- links[i].target = nodeIndices[links[i].target];
- }
- }
+ sankey
+ .iterations(c.sankeyIterations)
+ .size(horizontal ? [width, height] : [height, width])
+ .nodeWidth(nodeThickness)
+ .nodePadding(nodePad)
+ .nodes(nodes)
+ .links(links);
- var sankey = d3sankey()
- .size(horizontal ? [width, height] : [height, width])
- .nodeWidth(nodeThickness)
- .nodePadding(nodePad)
- .nodes(nodes)
- .links(links)
- .layout(c.sankeyIterations);
+ var graph = sankey();
if(sankey.nodePadding() < nodePad) {
Lib.warn('node.pad was reduced to ', sankey.nodePadding(), ' to fit within the figure.');
}
- var node;
- var sankeyNodes = sankey.nodes();
- for(var n = 0; n < sankeyNodes.length; n++) {
- node = sankeyNodes[n];
- node.width = width;
- node.height = height;
- }
-
- switchToForceFormat(nodes);
-
return {
+ circular: circular,
key: traceIndex,
trace: trace,
guid: Math.floor(1e12 * (1 + Math.random())),
horizontal: horizontal,
width: width,
height: height,
- nodePad: nodePad,
- nodeLineColor: nodeLineColor,
- nodeLineWidth: nodeLineWidth,
- linkLineColor: linkLineColor,
- linkLineWidth: linkLineWidth,
- valueFormat: valueFormat,
- valueSuffix: valueSuffix,
- textFont: textFont,
+ nodePad: trace.node.pad,
+ nodeLineColor: trace.node.line.color,
+ nodeLineWidth: trace.node.line.width,
+ linkLineColor: trace.link.line.color,
+ linkLineWidth: trace.link.line.width,
+ valueFormat: trace.valueformat,
+ valueSuffix: trace.valuesuffix,
+ textFont: trace.textfont,
translateX: domain.x[0] * layout.width + layout.margin.l,
translateY: layout.height - domain.y[1] * layout.height + layout.margin.t,
dragParallel: horizontal ? height : width,
dragPerpendicular: horizontal ? width : height,
- nodes: nodes,
- links: links,
- arrangement: arrangement,
+ arrangement: trace.arrangement,
sankey: sankey,
+ graph: graph,
forceLayouts: {},
interactionState: {
dragInProgress: false,
@@ -194,20 +98,20 @@ function sankeyModel(layout, d, traceIndex) {
};
}
-function linkModel(uniqueKeys, d, l) {
+function linkModel(d, l, i) {
var tc = tinycolor(l.color);
var basicKey = l.source.label + '|' + l.target.label;
- var foundKey = uniqueKeys[basicKey];
- uniqueKeys[basicKey] = (foundKey || 0) + 1;
- var key = basicKey + '__' + uniqueKeys[basicKey];
+ var key = basicKey + '__' + i;
// for event data
l.trace = d.trace;
l.curveNumber = d.trace.index;
return {
+ circular: d.circular,
key: key,
traceId: d.key,
+ pointNumber: l.pointNumber,
link: l,
tinyColorHue: Color.tinyRGB(tc),
tinyColorAlpha: tc.getAlpha(),
@@ -216,27 +120,177 @@ function linkModel(uniqueKeys, d, l) {
valueFormat: d.valueFormat,
valueSuffix: d.valueSuffix,
sankey: d.sankey,
+ parent: d,
interactionState: d.interactionState
};
}
-function nodeModel(uniqueKeys, d, n) {
+function createCircularClosedPathString(link) {
+ // Using coordinates computed by d3-sankey-circular
+ var pathString = '';
+ var offset = link.width / 2;
+ var coords = link.circularPathData;
+ if(link.circularLinkType === 'top') {
+ // Top path
+ pathString =
+ // start at the left of the target node
+ 'M ' +
+ coords.targetX + ' ' + (coords.targetY + offset) + ' ' +
+ 'L' +
+ coords.rightInnerExtent + ' ' + (coords.targetY + offset) +
+ 'A' +
+ (coords.rightLargeArcRadius + offset) + ' ' + (coords.rightSmallArcRadius + offset) + ' 0 0 1 ' +
+ (coords.rightFullExtent - offset) + ' ' + (coords.targetY - coords.rightSmallArcRadius) +
+ 'L' +
+ (coords.rightFullExtent - offset) + ' ' + coords.verticalRightInnerExtent +
+ 'A' +
+ (coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 1 ' +
+ coords.rightInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
+ 'L' +
+ coords.leftInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
+ 'A' +
+ (coords.leftLargeArcRadius + offset) + ' ' + (coords.leftLargeArcRadius + offset) + ' 0 0 1 ' +
+ (coords.leftFullExtent + offset) + ' ' + coords.verticalLeftInnerExtent +
+ 'L' +
+ (coords.leftFullExtent + offset) + ' ' + (coords.sourceY - coords.leftSmallArcRadius) +
+ 'A' +
+ (coords.leftLargeArcRadius + offset) + ' ' + (coords.leftSmallArcRadius + offset) + ' 0 0 1 ' +
+ coords.leftInnerExtent + ' ' + (coords.sourceY + offset) +
+ 'L' +
+ coords.sourceX + ' ' + (coords.sourceY + offset) +
+
+ // Walking back
+ 'L' +
+ coords.sourceX + ' ' + (coords.sourceY - offset) +
+ 'L' +
+ coords.leftInnerExtent + ' ' + (coords.sourceY - offset) +
+ 'A' +
+ (coords.leftLargeArcRadius - offset) + ' ' + (coords.leftSmallArcRadius - offset) + ' 0 0 0 ' +
+ (coords.leftFullExtent - offset) + ' ' + (coords.sourceY - coords.leftSmallArcRadius) +
+ 'L' +
+ (coords.leftFullExtent - offset) + ' ' + coords.verticalLeftInnerExtent +
+ 'A' +
+ (coords.leftLargeArcRadius - offset) + ' ' + (coords.leftLargeArcRadius - offset) + ' 0 0 0 ' +
+ coords.leftInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
+ 'L' +
+ coords.rightInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
+ 'A' +
+ (coords.rightLargeArcRadius - offset) + ' ' + (coords.rightLargeArcRadius - offset) + ' 0 0 0 ' +
+ (coords.rightFullExtent + offset) + ' ' + coords.verticalRightInnerExtent +
+ 'L' +
+ (coords.rightFullExtent + offset) + ' ' + (coords.targetY - coords.rightSmallArcRadius) +
+ 'A' +
+ (coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 0 ' +
+ coords.rightInnerExtent + ' ' + (coords.targetY - offset) +
+ 'L' +
+ coords.targetX + ' ' + (coords.targetY - offset) +
+ 'Z';
+ } else {
+ // Bottom path
+ pathString =
+ // start at the left of the target node
+ 'M ' +
+ coords.targetX + ' ' + (coords.targetY - offset) + ' ' +
+ 'L' +
+ coords.rightInnerExtent + ' ' + (coords.targetY - offset) +
+ 'A' +
+ (coords.rightLargeArcRadius + offset) + ' ' + (coords.rightSmallArcRadius + offset) + ' 0 0 0 ' +
+ (coords.rightFullExtent - offset) + ' ' + (coords.targetY + coords.rightSmallArcRadius) +
+ 'L' +
+ (coords.rightFullExtent - offset) + ' ' + coords.verticalRightInnerExtent +
+ 'A' +
+ (coords.rightLargeArcRadius + offset) + ' ' + (coords.rightLargeArcRadius + offset) + ' 0 0 0 ' +
+ coords.rightInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
+ 'L' +
+ coords.leftInnerExtent + ' ' + (coords.verticalFullExtent + offset) +
+ 'A' +
+ (coords.leftLargeArcRadius + offset) + ' ' + (coords.leftLargeArcRadius + offset) + ' 0 0 0 ' +
+ (coords.leftFullExtent + offset) + ' ' + coords.verticalLeftInnerExtent +
+ 'L' +
+ (coords.leftFullExtent + offset) + ' ' + (coords.sourceY + coords.leftSmallArcRadius) +
+ 'A' +
+ (coords.leftLargeArcRadius + offset) + ' ' + (coords.leftSmallArcRadius + offset) + ' 0 0 0 ' +
+ coords.leftInnerExtent + ' ' + (coords.sourceY - offset) +
+ 'L' +
+ coords.sourceX + ' ' + (coords.sourceY - offset) +
+
+ // Walking back
+ 'L' +
+ coords.sourceX + ' ' + (coords.sourceY + offset) +
+ 'L' +
+ coords.leftInnerExtent + ' ' + (coords.sourceY + offset) +
+ 'A' +
+ (coords.leftLargeArcRadius - offset) + ' ' + (coords.leftSmallArcRadius - offset) + ' 0 0 1 ' +
+ (coords.leftFullExtent - offset) + ' ' + (coords.sourceY + coords.leftSmallArcRadius) +
+ 'L' +
+ (coords.leftFullExtent - offset) + ' ' + coords.verticalLeftInnerExtent +
+ 'A' +
+ (coords.leftLargeArcRadius - offset) + ' ' + (coords.leftLargeArcRadius - offset) + ' 0 0 1 ' +
+ coords.leftInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
+ 'L' +
+ coords.rightInnerExtent + ' ' + (coords.verticalFullExtent - offset) +
+ 'A' +
+ (coords.rightLargeArcRadius - offset) + ' ' + (coords.rightLargeArcRadius - offset) + ' 0 0 1 ' +
+ (coords.rightFullExtent + offset) + ' ' + coords.verticalRightInnerExtent +
+ 'L' +
+ (coords.rightFullExtent + offset) + ' ' + (coords.targetY + coords.rightSmallArcRadius) +
+ 'A' +
+ (coords.rightLargeArcRadius - offset) + ' ' + (coords.rightSmallArcRadius - offset) + ' 0 0 1 ' +
+ coords.rightInnerExtent + ' ' + (coords.targetY + offset) +
+ 'L' +
+ coords.targetX + ' ' + (coords.targetY + offset) +
+ 'Z';
+ }
+ return pathString;
+}
+
+function linkPath() {
+ var curvature = 0.5;
+ function path(d) {
+ if(d.link.circular) {
+ return createCircularClosedPathString(d.link);
+ } else {
+ var x0 = d.link.source.x1;
+ var x1 = d.link.target.x0;
+ var xi = interpolateNumber(x0, x1);
+ var x2 = xi(curvature);
+ var x3 = xi(1 - curvature);
+ var y0a = d.link.y0 - d.link.width / 2;
+ var y0b = d.link.y0 + d.link.width / 2;
+ var y1a = d.link.y1 - d.link.width / 2;
+ var y1b = d.link.y1 + d.link.width / 2;
+ return 'M' + x0 + ',' + y0a +
+ 'C' + x2 + ',' + y0a +
+ ' ' + x3 + ',' + y1a +
+ ' ' + x1 + ',' + y1a +
+ 'L' + x1 + ',' + y1b +
+ 'C' + x3 + ',' + y1b +
+ ' ' + x2 + ',' + y0b +
+ ' ' + x0 + ',' + y0b +
+ 'Z';
+ }
+ }
+ return path;
+}
+
+function nodeModel(d, n, i) {
var tc = tinycolor(n.color);
var zoneThicknessPad = c.nodePadAcross;
var zoneLengthPad = d.nodePad / 2;
+ n.dx = n.x1 - n.x0;
+ n.dy = n.y1 - n.y0;
var visibleThickness = n.dx;
var visibleLength = Math.max(0.5, n.dy);
var basicKey = n.label;
- var foundKey = uniqueKeys[basicKey];
- uniqueKeys[basicKey] = (foundKey || 0) + 1;
- var key = basicKey + '__' + uniqueKeys[basicKey];
+ var key = basicKey + '__' + i;
// for event data
n.trace = d.trace;
n.curveNumber = d.trace.index;
return {
+ index: n.pointNumber,
key: key,
traceId: d.key,
node: n,
@@ -262,8 +316,9 @@ function nodeModel(uniqueKeys, d, n) {
valueFormat: d.valueFormat,
valueSuffix: d.valueSuffix,
sankey: d.sankey,
+ graph: d.graph,
arrangement: d.arrangement,
- uniqueNodeLabelPathId: [d.guid, d.key, key].join(' '),
+ uniqueNodeLabelPathId: [d.guid, d.key, key].join('_'),
interactionState: d.interactionState
};
}
@@ -273,33 +328,26 @@ function nodeModel(uniqueKeys, d, n) {
function updateNodePositions(sankeyNode) {
sankeyNode
.attr('transform', function(d) {
- return 'translate(' + d.node.x.toFixed(3) + ', ' + (d.node.y - d.node.dy / 2).toFixed(3) + ')';
+ return 'translate(' + d.node.x0.toFixed(3) + ', ' + (d.node.y0).toFixed(3) + ')';
});
}
-function linkPath(d) {
- var nodes = d.sankey.nodes();
- switchToSankeyFormat(nodes);
- var result = d.sankey.link()(d.link);
- switchToForceFormat(nodes);
- return result;
-}
-
function updateNodeShapes(sankeyNode) {
sankeyNode.call(updateNodePositions);
}
function updateShapes(sankeyNode, sankeyLink) {
sankeyNode.call(updateNodeShapes);
- sankeyLink.attr('d', linkPath);
+ sankeyLink.attr('d', linkPath());
}
function sizeNode(rect) {
- rect.attr('width', function(d) {return d.visibleWidth;})
- .attr('height', function(d) {return d.visibleHeight;});
+ rect
+ .attr('width', function(d) {return d.node.x1 - d.node.x0;})
+ .attr('height', function(d) {return d.visibleHeight;});
}
-function salientEnough(d) {return d.link.dy > 1 || d.linkLineWidth > 0;}
+function salientEnough(d) {return (d.link.width > 1 || d.linkLineWidth > 0);}
function sankeyTransform(d) {
var offset = 'translate(' + d.translateX + ',' + d.translateY + ')';
@@ -356,25 +404,29 @@ function attachPointerEvents(selection, sankey, eventSet) {
}
function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
-
var dragBehavior = d3.behavior.drag()
-
- .origin(function(d) {return d.node;})
+ .origin(function(d) {
+ return {
+ x: d.node.x0,
+ y: d.node.y0
+ };
+ })
.on('dragstart', function(d) {
if(d.arrangement === 'fixed') return;
Lib.raiseToTop(this);
d.interactionState.dragInProgress = d.node;
+
saveCurrentDragPosition(d.node);
if(d.interactionState.hovered) {
callbacks.nodeEvents.unhover.apply(0, d.interactionState.hovered);
d.interactionState.hovered = false;
}
if(d.arrangement === 'snap') {
- var forceKey = d.traceId + '|' + Math.floor(d.node.originalX);
+ var forceKey = d.traceId + '|' + d.key;
if(d.forceLayouts[forceKey]) {
d.forceLayouts[forceKey].alpha(1);
- } else { // make a forceLayout iff needed
+ } else { // make a forceLayout if needed
attachForce(sankeyNode, forceKey, d);
}
startForce(sankeyNode, sankeyLink, d, forceKey);
@@ -386,17 +438,22 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
var x = d3.event.x;
var y = d3.event.y;
if(d.arrangement === 'snap') {
- d.node.x = x;
- d.node.y = y;
+ d.node.x0 = x - d.visibleWidth / 2;
+ d.node.x1 = x + d.visibleWidth / 2;
+ d.node.y0 = y - d.visibleHeight / 2;
+ d.node.y1 = y + d.visibleHeight / 2;
} else {
if(d.arrangement === 'freeform') {
- d.node.x = x;
+ d.node.x0 = x - d.visibleWidth / 2;
+ d.node.x1 = x + d.visibleWidth / 2;
}
- d.node.y = Math.max(d.node.dy / 2, Math.min(d.size - d.node.dy / 2, y));
+ d.node.y0 = Math.max(0, Math.min(d.size - d.visibleHeight, y));
+ d.node.y1 = d.node.y0 + d.visibleHeight;
}
+
saveCurrentDragPosition(d.node);
if(d.arrangement !== 'snap') {
- d.sankey.relayout();
+ d.sankey.update(d.graph);
updateShapes(sankeyNode.filter(sameLayer(d)), sankeyLink);
}
})
@@ -411,7 +468,9 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
}
function attachForce(sankeyNode, forceKey, d) {
- var nodes = d.sankey.nodes().filter(function(n) {return n.originalX === d.node.originalX;});
+ // Attach force to nodes in the same column (same x coordinate)
+ switchToForceFormat(d.graph.nodes);
+ var nodes = d.graph.nodes.filter(function(n) {return n.originalX === d.node.originalX;});
d.forceLayouts[forceKey] = d3Force.forceSimulation(nodes)
.alphaDecay(0)
.force('collide', d3Force.forceCollide()
@@ -424,11 +483,17 @@ function attachForce(sankeyNode, forceKey, d) {
function startForce(sankeyNode, sankeyLink, d, forceKey) {
window.requestAnimationFrame(function faster() {
- for(var i = 0; i < c.forceTicksPerFrame; i++) {
+ var i;
+ for(i = 0; i < c.forceTicksPerFrame; i++) {
d.forceLayouts[forceKey].tick();
}
- d.sankey.relayout();
+
+ var nodes = d.graph.nodes;
+ switchToSankeyFormat(nodes);
+
+ d.sankey.update(d.graph);
updateShapes(sankeyNode.filter(sameLayer(d)), sankeyLink);
+
if(d.forceLayouts[forceKey].alpha() > 0) {
window.requestAnimationFrame(faster);
}
@@ -455,13 +520,62 @@ function snappingForce(sankeyNode, forceKey, nodes, d) {
};
}
+// basic data utilities
+
+function persistOriginalPlace(nodes) {
+ var distinctLayerPositions = [];
+ var i;
+ for(i = 0; i < nodes.length; i++) {
+ nodes[i].originalX = (nodes[i].x0 + nodes[i].x1) / 2;
+ nodes[i].originalY = (nodes[i].y0 + nodes[i].y1) / 2;
+ if(distinctLayerPositions.indexOf(nodes[i].originalX) === -1) {
+ distinctLayerPositions.push(nodes[i].originalX);
+ }
+ }
+ distinctLayerPositions.sort(function(a, b) {return a - b;});
+ for(i = 0; i < nodes.length; i++) {
+ nodes[i].originalLayerIndex = distinctLayerPositions.indexOf(nodes[i].originalX);
+ nodes[i].originalLayer = nodes[i].originalLayerIndex / (distinctLayerPositions.length - 1);
+ }
+}
+
+function saveCurrentDragPosition(d) {
+ d.lastDraggedX = d.x0 + d.dx / 2;
+ d.lastDraggedY = d.y0 + d.dy / 2;
+}
+
+function sameLayer(d) {
+ return function(n) {return n.node.originalX === d.node.originalX;};
+}
+
+function switchToForceFormat(nodes) {
+ // force uses x, y as centers
+ for(var i = 0; i < nodes.length; i++) {
+ nodes[i].y = nodes[i].y0 + nodes[i].dy / 2;
+ nodes[i].x = nodes[i].x0 + nodes[i].dx / 2;
+ }
+}
+
+function switchToSankeyFormat(nodes) {
+ // sankey uses x0, x1, y0, y1
+ for(var i = 0; i < nodes.length; i++) {
+ nodes[i].y0 = nodes[i].y - nodes[i].dy / 2;
+ nodes[i].y1 = nodes[i].y0 + nodes[i].dy;
+
+ nodes[i].x0 = nodes[i].x - nodes[i].dx / 2;
+ nodes[i].x1 = nodes[i].x0 + nodes[i].dx;
+ }
+}
+
// scene graph
-module.exports = function(svg, styledData, layout, callbacks) {
+module.exports = function(svg, calcData, layout, callbacks) {
+
+ var styledData = calcData
+ .filter(function(d) {return unwrap(d).trace.visible;})
+ .map(sankeyModel.bind(null, layout));
+
var sankey = svg.selectAll('.' + c.cn.sankey)
- .data(styledData
- .filter(function(d) {return unwrap(d).trace.visible;})
- .map(sankeyModel.bind(null, layout)),
- keyFun);
+ .data(styledData, keyFun);
sankey.exit()
.remove();
@@ -489,18 +603,18 @@ module.exports = function(svg, styledData, layout, callbacks) {
.style('fill', 'none');
var sankeyLink = sankeyLinks.selectAll('.' + c.cn.sankeyLink)
- .data(function(d) {
- var uniqueKeys = {};
- return d.sankey.links()
+ .data(function(d) {
+ var links = d.graph.links;
+ return links
.filter(function(l) {return l.value;})
- .map(linkModel.bind(null, uniqueKeys, d));
- }, keyFun);
+ .map(linkModel.bind(null, d));
+ }, keyFun);
- sankeyLink.enter()
- .append('path')
- .classed(c.cn.sankeyLink, true)
- .attr('d', linkPath)
- .call(attachPointerEvents, sankey, callbacks.linkEvents);
+ sankeyLink
+ .enter().append('path')
+ .classed(c.cn.sankeyLink, true)
+ .attr('d', linkPath())
+ .call(attachPointerEvents, sankey, callbacks.linkEvents);
sankeyLink
.style('stroke', function(d) {
@@ -509,13 +623,19 @@ module.exports = function(svg, styledData, layout, callbacks) {
.style('stroke-opacity', function(d) {
return salientEnough(d) ? Color.opacity(d.linkLineColor) : d.tinyColorAlpha;
})
- .style('stroke-width', function(d) {return salientEnough(d) ? d.linkLineWidth : 1;})
- .style('fill', function(d) {return d.tinyColorHue;})
- .style('fill-opacity', function(d) {return d.tinyColorAlpha;});
+ .style('fill', function(d) {
+ return d.tinyColorHue;
+ })
+ .style('fill-opacity', function(d) {
+ return d.tinyColorAlpha;
+ })
+ .style('stroke-width', function(d) {
+ return salientEnough(d) ? d.linkLineWidth : 1;
+ });
sankeyLink.transition()
- .ease(c.ease).duration(c.duration)
- .attr('d', linkPath);
+ .ease(c.ease).duration(c.duration)
+ .attr('d', linkPath());
sankeyLink.exit().transition()
.ease(c.ease).duration(c.duration)
@@ -540,12 +660,11 @@ module.exports = function(svg, styledData, layout, callbacks) {
var sankeyNode = sankeyNodeSet.selectAll('.' + c.cn.sankeyNode)
.data(function(d) {
- var nodes = d.sankey.nodes();
- var uniqueKeys = {};
+ var nodes = d.graph.nodes;
persistOriginalPlace(nodes);
return nodes
.filter(function(n) {return n.value;})
- .map(nodeModel.bind(null, uniqueKeys, d));
+ .map(nodeModel.bind(null, d));
}, keyFun);
sankeyNode.enter()
diff --git a/test/image/baselines/sankey_circular.png b/test/image/baselines/sankey_circular.png
new file mode 100644
index 00000000000..f2ba831b652
Binary files /dev/null and b/test/image/baselines/sankey_circular.png differ
diff --git a/test/image/baselines/sankey_circular_large.png b/test/image/baselines/sankey_circular_large.png
new file mode 100644
index 00000000000..600ff74b614
Binary files /dev/null and b/test/image/baselines/sankey_circular_large.png differ
diff --git a/test/image/baselines/sankey_circular_process.png b/test/image/baselines/sankey_circular_process.png
new file mode 100644
index 00000000000..438062a1dd1
Binary files /dev/null and b/test/image/baselines/sankey_circular_process.png differ
diff --git a/test/image/baselines/sankey_circular_simple.png b/test/image/baselines/sankey_circular_simple.png
new file mode 100644
index 00000000000..8c6a99f2e26
Binary files /dev/null and b/test/image/baselines/sankey_circular_simple.png differ
diff --git a/test/image/baselines/sankey_large_padding.png b/test/image/baselines/sankey_large_padding.png
index 38d263f799d..b27b24c12eb 100644
Binary files a/test/image/baselines/sankey_large_padding.png and b/test/image/baselines/sankey_large_padding.png differ
diff --git a/test/image/baselines/sankey_subplots_circular.png b/test/image/baselines/sankey_subplots_circular.png
new file mode 100644
index 00000000000..9d744f00ca4
Binary files /dev/null and b/test/image/baselines/sankey_subplots_circular.png differ
diff --git a/test/image/mocks/sankey_circular.json b/test/image/mocks/sankey_circular.json
new file mode 100644
index 00000000000..a8b454f5997
--- /dev/null
+++ b/test/image/mocks/sankey_circular.json
@@ -0,0 +1,26 @@
+{
+ "data": [
+ {
+ "type": "sankey",
+ "node": {
+ "pad": 5,
+ "label": ["0", "1", "2", "3", "4", "5", "6"]
+ },
+ "link": {
+ "source": [
+ 0, 0, 1, 2, 5, 4, 3
+ ],
+ "target": [
+ 5, 3, 4, 3, 0, 2, 2
+ ],
+ "value": [
+ 1, 2, 1, 1, 1, 1, 1
+ ]
+ }
+ }],
+ "layout": {
+ "title": "Sankey with circular data",
+ "width": 800,
+ "height": 800
+ }
+}
diff --git a/test/image/mocks/sankey_circular_large.json b/test/image/mocks/sankey_circular_large.json
new file mode 100644
index 00000000000..6af23521291
--- /dev/null
+++ b/test/image/mocks/sankey_circular_large.json
@@ -0,0 +1,230 @@
+{
+ "data": [{
+ "type": "sankey",
+ "node": {
+ "label": ["node_0", "node_2", "node_4", "node_6", "node_9", "node_11", "node_14", "node_16", "node_13", "node_18", "node_17", "node_7", "node_20", "node_1", "node_3", "node_5", "node_15", "node_10", "node_12", "node_8", "node_19", "node_21", "node_22", "node_23", "node_24", "node_25"]
+ },
+ "link": {
+ "source": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25],
+ "target": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 14, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7],
+ "value": [0.0, 0.0, 0.006646893, 11.68228, 412.55902, 442.56927, 0.013883961, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.78665316, 8.526513e-14, 0.6138538, 45.966034, 7136.0156, 2424.101, 1509.0869, 7120.3193, 295.09622, 9.1515847e-07, 0.027355881, 0.032680232, 77.08173, 2.3656483, 0.4589379, 40.13989, 0.0023563614, 0.78811955, 27.012928, 30.984028, 440.46704, 146.16705, 89.212036, 414.40326, 16.910637, 0.91114557, 32.428925, 38.6801, 569.7678, 194.95515, 122.100624, 578.82434, 24.096718, 2421.6963, 1257.6445, 3196.5034, 62.081352, 9.60988e-07, 0.028764999, 0.03436107, 81.02598, 2.4788527, 0.27914718, 3.8161736, 2.3054126e-05, 0.8387133, 28.537077, 32.619724, 463.0056, 153.39035, 78.07639, 195.34256, 3.7353568, 0.28559664, 10.209059, 12.197543, 179.789, 61.471806, 32.11993, 82.02592, 1.6002578, 2421.7532, 1263.6002, 3289.443, 67.60055, 1.0142184e-06, 0.030376881, 0.03628451, 85.55038, 2.6172085, 0.2988975, 4.3902717, 3.142184e-05, 0.89126384, 30.208187, 34.46845, 488.85928, 161.90334, 82.78547, 212.12839, 4.2920413, 0.48148987, 17.250084, 20.627787, 304.1493, 104.00748, 54.604145, 142.82541, 2.948419, 0.0, 0.0, 0.60126424, 0.0, 0.0, 44.941578, 7118.8457, 2395.3425, 538.55695, 82.62762, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.07240185, 11.468614, 3.858948, 0.8676268, 0.13311486, 0.0, 4.275301e-39, 0.09171584, 14.527993, 4.8883653, 1.0990758, 0.16862473, 3.1385706e-25, 0.11330401, 17.947605, 6.0389934, 1.3577774, 0.20831577, 0.0, 0.0, 0.18538894, 5.684342e-14, 0.6138538, 1.024453, 17.16984, 28.75857, 970.52997, 7037.6914, 295.09622, 9.1515847e-07, 0.027355881, 0.032680232, 77.08173, 2.3656483, 0.4589379, 40.13989, 0.0023563614, 0.78811955, 27.012928, 30.984028, 440.46704, 146.16705, 89.212036, 414.40326, 16.910637, 0.91114557, 32.428925, 38.6077, 558.2992, 191.0962, 121.232994, 578.6912, 24.096718, 26.353783, 719.0876, 3113.8757, 62.081352, 9.60988e-07, 0.028764999, 0.03436107, 81.02598, 2.4788527, 0.27914718, 3.8161736, 2.3054126e-05, 0.8387133, 28.537077, 32.619724, 463.0056, 153.39035, 78.07639, 195.34256, 3.7353568, 0.28559664, 10.209059, 12.105827, 165.261, 56.58344, 31.020855, 81.85729, 1.6002578, 26.410742, 725.0432, 3206.8154, 67.60055, 1.0142184e-06, 0.030376881, 0.03628451, 85.55038, 2.6172085, 0.2988975, 4.3902717, 3.142184e-05, 0.89126384, 30.208187, 34.46845, 488.85928, 161.90334, 82.78547, 212.12839, 4.2920413, 0.48148987, 17.250084, 20.514482, 286.2017, 97.96848, 53.24637, 142.6171, 2.948419, 0.0, 0.0, 0.18538894, 0.0, 0.6138538, 1.024453, 17.16984, 26.14728, 697.4959, 2776.9333, 42.072098, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0013678052, 0.002282713, 0.038258288, 0.058262054, 1.5541787, 6.1876354, 0.093746156, 0.0012357329, 0.0020622993, 0.03456415, 0.052636396, 1.4041106, 5.590171, 0.08469423, 0.0018279847, 0.0030507008, 0.05112977, 0.07786353, 2.077061, 8.2693815, 0.12528577, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.6112895, 273.0341, 4260.7583, 253.02411, 9.1515847e-07, 0.027355881, 0.032680232, 77.08173, 2.3656483, 0.4589379, 40.13989, 0.0023563614, 0.78811955, 27.012928, 30.984028, 440.46704, 146.16705, 89.212036, 414.40326, 16.910637, 0.91114557, 32.42756, 38.605415, 558.2609, 191.03795, 119.67882, 572.5036, 24.002972, 0.20650205, 21.591648, 336.94254, 20.00925, 9.60988e-07, 0.028764999, 0.03436107, 81.02598, 2.4788527, 0.27914718, 3.8161736, 2.3054126e-05, 0.8387133, 28.537077, 32.619724, 463.0056, 153.39035, 78.07639, 195.34256, 3.7353568, 0.28559664, 10.207823, 12.103765, 165.22644, 56.530804, 29.616745, 76.26712, 1.5155635, 0.26346198, 27.547321, 429.88214, 25.528448, 1.0142184e-06, 0.030376881, 0.03628451, 85.55038, 2.6172085, 0.2988975, 4.3902717, 3.142184e-05, 0.89126384, 30.208187, 34.46845, 488.85928, 161.90334, 82.78547, 212.12839, 4.2920413, 0.48148987, 17.248255, 20.51143, 286.15054, 97.890625, 51.169308, 134.34772, 2.8231332, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.109188, 220.53482, 3441.4954, 204.37238, 7.391909e-07, 0.022095865, 0.026396444, 62.26038, 1.910779, 0.37069288, 32.42175, 0.0019032779, 0.6365791, 21.818855, 25.026388, 355.7736, 118.061905, 72.05826, 334.72137, 13.659042, 0.7359496, 26.192354, 31.18233, 450.91797, 154.30498, 96.666855, 462.42203, 19.387655, 0.16651253, 17.410383, 271.69296, 16.134422, 7.7489085e-07, 0.023194602, 0.02770698, 65.335144, 1.9988183, 0.22508979, 3.0771644, 1.858965e-05, 0.67629486, 23.010817, 26.302855, 373.3437, 123.68602, 62.95675, 157.5141, 3.011998, 0.23029031, 8.231058, 9.759848, 133.23003, 45.583508, 23.881405, 61.497852, 1.2220718, 0.20387426, 21.316889, 332.65485, 19.754627, 7.8483055e-07, 0.023506481, 0.02807797, 66.20128, 2.0252693, 0.23129526, 3.3973153, 2.43151e-05, 0.6896849, 23.37594, 26.672651, 378.293, 125.28533, 64.061714, 164.15088, 3.321302, 0.37259033, 13.347183, 15.87232, 221.4313, 75.7505, 39.59624, 103.96202, 2.1846197, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.50210136, 52.499268, 819.2629, 48.65173, 1.7596759e-07, 0.0052600163, 0.0062837875, 14.821353, 0.4548692, 0.08824504, 7.7181377, 0.00045308354, 0.15154041, 5.1940727, 5.9576397, 84.69344, 28.105146, 17.15378, 79.68187, 3.251594, 0.17519598, 6.2352033, 7.423088, 107.34297, 36.732967, 23.011963, 110.08156, 4.6153154, 0.039989512, 4.1812634, 65.24956, 3.8748293, 1.8609715e-07, 0.0055703963, 0.0066540856, 15.690835, 0.48003453, 0.05405738, 0.73900926, 4.464475e-06, 0.16241841, 5.526259, 6.3168716, 89.66191, 29.704332, 15.119641, 37.828457, 0.7233589, 0.05530633, 1.9767642, 2.343917, 31.996414, 10.947297, 5.7353387, 14.769273, 0.29349175, 0.059587725, 6.2304325, 97.2273, 5.7738204, 2.293878e-07, 0.0068704, 0.008206541, 19.3491, 0.5919393, 0.06760225, 0.9929566, 7.1067407e-06, 0.20157892, 6.832246, 7.7957983, 110.566284, 36.618, 18.723755, 47.9775, 0.9707396, 0.10889953, 3.9010725, 4.6391115, 64.71924, 22.140118, 11.573065, 30.38569, 0.6385137, 2736.3335, 2108.9036, 14.86675, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 443.5915, 6569.0674, 8.137072, 454.3161, 5378.305, 8.642767, 13.946691, 6915.0107, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 7.245143, 3592.2676, 10.048806, 4982.3726, 86.01402, 580.18024, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 8.001811, 892.7646, 21.076576, 730.4533, 2460.0493, 1528.7233, 2790.7883, 11.968955, 417.03336, 490.2733, 7145.696, 2425.3513, 1509.3833, 7120.8516, 295.1046, 9.831879e-07, 0.02820494, 0.033103738, 77.49881, 2.3709927, 0.45937082, 40.155697, 0.002356749, 0.8467053, 27.851343, 31.385553, 442.85034, 146.49727, 89.29619, 414.56647, 16.913418, 2.4631135, 87.6656, 104.5645, 1540.2618, 527.0252, 330.07645, 1564.7445, 65.141014, 245.31956, 5676.3027, 2606.2048, 11.884567, 415.89694, 489.7617, 7142.8506, 2422.5776, 1257.8187, 3196.672, 62.082596, 1.0113957e-06, 0.029394984, 0.034675278, 81.33534, 2.4828045, 0.27933297, 3.8172343, 2.30568e-05, 0.8827072, 29.16207, 32.91801, 464.77338, 153.63487, 78.12836, 195.39685, 3.7357903, 0.62885964, 22.479483, 26.857958, 395.88016, 135.35571, 70.72537, 180.61414, 3.5236323, 242.96942, 4647.8516, 2571.1414, 11.841463, 415.3165, 489.50037, 7141.397, 2422.4468, 1263.7379, 3289.5796, 67.601616, 1.0560822e-06, 0.030900408, 0.03654561, 85.80741, 2.6204917, 0.29905406, 4.391232, 3.1424708e-05, 0.9280525, 30.728804, 34.71648, 490.32806, 162.10643, 82.828835, 212.17477, 4.2924333, 0.9368864, 33.565334, 40.13769, 591.81586, 202.3785, 106.24914, 277.9107, 5.737055, 2495.4575, 1203.661, 9.040011, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 362.2623, 4105.1655, 7.4855456, 356.7432, 3127.01, 6.5761266, 0.0, 0.0, 1708.9695, 0.28667504, 3.8604624, 1.7379957, 9.666174, 1.2502424, 0.29634866, 0.53239435, 0.00840123, 6.802936e-08, 0.00084905897, 0.00042350602, 0.41707394, 0.005344402, 0.00043290236, 0.015808823, 3.8748325e-07, 0.05858577, 0.8384146, 0.40152475, 2.3832796, 0.33021626, 0.08415104, 0.16320992, 0.0027808049, 1.551968, 55.236675, 65.8844, 970.4941, 332.07007, 207.97583, 985.92017, 41.0443, 1415.1053, 0.20228739, 2.7240698, 1.2263873, 6.820772, 0.8813371, 0.17427117, 0.16865067, 0.0012471518, 5.0407706e-08, 0.00062998536, 0.00031421002, 0.30936044, 0.0039516515, 0.00018580105, 0.0010605482, 2.675087e-09, 0.043993905, 0.62499356, 0.29828656, 1.767774, 0.2445265, 0.05196784, 0.05428742, 0.00043343264, 0.343263, 12.270425, 14.660415, 216.09117, 73.88391, 38.60544, 98.58822, 1.9233744, 1243.1842, 0.1591833, 2.1436157, 0.96506447, 5.367378, 0.69355506, 0.13778628, 0.13657272, 0.0010686538, 4.1863846e-08, 0.00052352564, 0.00026109786, 0.2570343, 0.0032831817, 0.00015655463, 0.0009601131, 2.8691258e-09, 0.036788657, 0.52061826, 0.24802977, 1.4687674, 0.20310114, 0.043360844, 0.046390574, 0.00039190616, 0.45539653, 16.315252, 19.509907, 287.66656, 98.37101, 51.64499, 135.0853, 2.7886357, 154.86191, 325.06226, 5.8267384, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 73.32737, 1571.1372, 0.65152603, 76.49633, 1520.8418, 2.0666401, 0.0, 0.0, 1066.9521, 11.68228, 413.17288, 488.5353, 7136.03, 2424.101, 1509.0869, 7120.3193, 295.09622, 9.1515847e-07, 0.027355881, 0.032680232, 77.08173, 2.3656483, 0.4589379, 40.13989, 0.0023563614, 0.78811955, 27.012928, 30.984028, 440.46704, 146.16705, 89.212036, 414.40326, 16.910637, 0.91114557, 32.428925, 38.6801, 569.7678, 194.95515, 122.100624, 578.82434, 24.096718, 1182.9623, 2421.6963, 1257.6445, 3196.5034, 62.081352, 9.60988e-07, 0.028764999, 0.03436107, 81.02598, 2.4788527, 0.27914718, 3.8161736, 2.3054126e-05, 0.8387133, 28.537077, 32.619724, 463.0056, 153.39035, 78.07639, 195.34256, 3.7353568, 0.28559664, 10.209059, 12.197543, 179.789, 61.471806, 32.11993, 82.02592, 1.6002578, 1319.3145, 2421.7532, 1263.6002, 3289.443, 67.60055, 1.0142184e-06, 0.030376881, 0.03628451, 85.55038, 2.6172085, 0.2988975, 4.3902717, 3.142184e-05, 0.89126384, 30.208187, 34.46845, 488.85928, 161.90334, 82.78547, 212.12839, 4.2920413, 0.48148987, 17.250084, 20.627787, 304.1493, 104.00748, 54.604145, 142.82541, 2.948419, 94.707184, 325.06226, 1072.7788, 11.68228, 413.17288, 488.5353, 7136.03, 2424.101, 1509.0869, 7120.3193, 295.09622, 9.1515847e-07, 0.027355881, 0.032680232, 77.08173, 2.3656483, 0.4589379, 40.13989, 0.0023563614, 0.78811955, 27.012928, 30.984028, 440.46704, 146.16705, 89.212036, 414.40326, 16.910637, 0.91114557, 32.428925, 38.6801, 569.7678, 194.95515, 122.100624, 578.82434, 24.096718, 13.172645, 1571.1372, 1183.6139, 2421.6963, 1257.6445, 3196.5034, 62.081352, 9.60988e-07, 0.028764999, 0.03436107, 81.02598, 2.4788527, 0.27914718, 3.8161736, 2.3054126e-05, 0.8387133, 28.537077, 32.619724, 463.0056, 153.39035, 78.07639, 195.34256, 3.7353568, 0.28559664, 10.209059, 12.197543, 179.789, 61.471806, 32.11993, 82.02592, 1.6002578, 16.3416, 1520.8418, 1321.3811, 2421.7532, 1263.6002, 3289.443, 67.60055, 1.0142184e-06, 0.030376881, 0.03628451, 85.55038, 2.6172085, 0.2988975, 4.3902717, 3.142184e-05, 0.89126384, 30.208187, 34.46845, 488.85928, 161.90334, 82.78547, 212.12839, 4.2920413, 0.48148987, 17.250084, 20.627787, 304.1493, 104.00748, 54.604145, 142.82541, 2.948419, 2365.342, 1203.661, 1718.0094, 0.28667504, 3.8604624, 1.7379957, 9.666174, 1.2502424, 0.29634866, 0.53239435, 0.00840123, 6.802936e-08, 0.00084905897, 0.00042350602, 0.41707394, 0.005344402, 0.00043290236, 0.015808823, 3.8748325e-07, 0.05858577, 0.8384146, 0.40152475, 2.3832796, 0.33021626, 0.08415104, 0.16320992, 0.0027808049, 1.551968, 55.236675, 65.8844, 970.4941, 332.07007, 207.97583, 985.92017, 41.0443, 232.14693, 4105.1655, 1422.591, 0.20228739, 2.7240698, 1.2263873, 6.820772, 0.8813371, 0.17427117, 0.16865067, 0.0012471518, 5.0407706e-08, 0.00062998536, 0.00031421002, 0.30936044, 0.0039516515, 0.00018580105, 0.0010605482, 2.675087e-09, 0.043993905, 0.62499356, 0.29828656, 1.767774, 0.2445265, 0.05196784, 0.05428742, 0.00043343264, 0.343263, 12.270425, 14.660415, 216.09117, 73.88391, 38.60544, 98.58822, 1.9233744, 226.62782, 3127.01, 1249.7604, 0.1591833, 2.1436157, 0.96506447, 5.367378, 0.69355506, 0.13778628, 0.13657272, 0.0010686538, 4.1863846e-08, 0.00052352564, 0.00026109786, 0.2570343, 0.0032831817, 0.00015655463, 0.0009601131, 2.8691258e-09, 0.036788657, 0.52061826, 0.24802977, 1.4687674, 0.20310114, 0.043360844, 0.046390574, 0.00039190616, 0.45539653, 16.315252, 19.509907, 287.66656, 98.37101, 51.64499, 135.0853, 2.7886357, 130.11539, 130.11539, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.7933001, 11.68228, 413.17288, 488.5353, 7136.03, 2424.101, 1509.0869, 7120.3193, 295.09622, 9.1515847e-07, 0.027355881, 0.032680232, 77.08173, 2.3656483, 0.4589379, 40.13989, 0.0023563614, 0.78811955, 27.012928, 30.984028, 440.46704, 146.16705, 89.212036, 414.40326, 16.910637, 0.91114557, 32.428925, 38.6801, 569.7678, 194.95515, 122.100624, 578.82434, 24.096718, 2421.6963, 1257.6445, 3196.5034, 62.081352, 9.60988e-07, 0.028764999, 0.03436107, 81.02598, 2.4788527, 0.27914718, 3.8161736, 2.3054126e-05, 0.8387133, 28.537077, 32.619724, 463.0056, 153.39035, 78.07639, 195.34256, 3.7353568, 0.28559664, 10.209059, 12.197543, 179.789, 61.471806, 32.11993, 82.02592, 1.6002578, 2421.7532, 1263.6002, 3289.443, 67.60055, 1.0142184e-06, 0.030376881, 0.03628451, 85.55038, 2.6172085, 0.2988975, 4.3902717, 3.142184e-05, 0.89126384, 30.208187, 34.46845, 488.85928, 161.90334, 82.78547, 212.12839, 4.2920413, 0.48148987, 17.250084, 20.627787, 304.1493, 104.00748, 54.604145, 142.82541, 2.948419, 208.01515, 0.0, 1066.1588, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1182.1691, 1318.5211, 2534.4382, 11143.2, 850.0, 1.4210855e-14, 0.0, 0.0, 0.0, 2.109188, 220.53482, 3441.4954, 204.37238, 10.644975, 372.82385, 439.97794, 7309.9087, 2190.6462, 1195.7097, 3826.735, 109.31696, 0.6365791, 21.818855, 25.026388, 355.7736, 118.061905, 72.05826, 334.72137, 13.659042, 3.673447, 129.06758, 152.58765, 1480.1659, 758.29, 426.52255, 1509.4688, 49.553318, 37.661854, 12188.093, 1083.6708, 0.16651253, 17.410383, 271.69296, 16.134422, 8.71208, 306.87488, 363.09512, 6260.749, 1812.035, 929.52545, 2206.279, 35.054283, 0.67629486, 23.010817, 26.302855, 373.3437, 123.68602, 62.95675, 157.5141, 3.011998, 3.504252, 123.54458, 146.199, 1326.5828, 725.78723, 373.10846, 889.45154, 14.395311, 64.565796, 12390.9, 1031.66, 0.20387426, 21.316889, 332.65485, 19.754627, 8.824489, 311.60336, 369.0566, 6371.218, 1843.2927, 947.7279, 2276.1665, 37.48881, 0.6896849, 23.37594, 26.672651, 378.293, 125.28533, 64.061714, 164.15088, 3.321302, 3.6919887, 130.55031, 154.68521, 1436.5192, 768.3573, 396.00433, 958.8815, 16.286312, 2560.01, 9023.914, 2790.7883, 11.968955, 417.03336, 490.2733, 7145.696, 2425.3513, 1509.3833, 7120.8516, 295.1046, 9.831879e-07, 0.02820494, 0.033103738, 77.49881, 2.3709927, 0.45937082, 40.155697, 0.002356749, 0.8467053, 27.851343, 31.385553, 442.85034, 146.49727, 89.29619, 414.56647, 16.913418, 2.4631135, 87.6656, 104.5645, 1540.2618, 527.0252, 330.07645, 1564.7445, 65.141014, 260.56653, 10161.335, 2606.2048, 11.884567, 415.89694, 489.7617, 7142.8506, 2422.5776, 1257.8187, 3196.672, 62.082596, 1.0113957e-06, 0.029394984, 0.034675278, 81.33534, 2.4828045, 0.27933297, 3.8172343, 2.30568e-05, 0.8827072, 29.16207, 32.91801, 464.77338, 153.63487, 78.12836, 195.39685, 3.7357903, 0.62885964, 22.479483, 26.857958, 395.88016, 135.35571, 70.72537, 180.61414, 3.5236323, 274.0948, 10360.678, 2571.1414, 11.841463, 415.3165, 489.50037, 7141.397, 2422.4468, 1263.7379, 3289.5796, 67.601616, 1.0560822e-06, 0.030900408, 0.03654561, 85.80741, 2.6204917, 0.29905406, 4.391232, 3.1424708e-05, 0.9280525, 30.728804, 34.71648, 490.32806, 162.10643, 82.828835, 212.17477, 4.2924333, 0.9368864, 33.565334, 40.13769, 591.81586, 202.3785, 106.24914, 277.9107, 5.737055, 190.27011, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 208.01515, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.4687487, 51.437614, 60.70266, 29.2479, 301.9925, 164.92784, 523.5234, 15.082831, 1.6369808, 57.65676, 68.21958, 340.10187, 174.61353, 413.97684, 6.58662, 1.6596992, 58.601566, 69.40645, 346.3034, 178.20404, 427.45975, 7.0508466, 34.438293, 11143.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 37.661842, 12188.093, 38.298496, 12390.9, 2500.0, 0.0, 850.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.644974, 372.80176, 439.95154, 7247.6484, 2188.7356, 1195.3391, 3794.3135, 109.315056, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.4687487, 51.437614, 60.70266, 1000.0, 301.9925, 164.92784, 523.5234, 15.082831, 1e-05, 1083.6708, 8.71208, 306.85168, 363.0674, 6195.414, 1810.0363, 929.30035, 2203.202, 35.054264, 1.6369808, 57.65676, 68.21958, 1164.1049, 340.10187, 174.61353, 413.97684, 6.58662, 26.2673, 1031.66, 8.824488, 311.57983, 369.02853, 6305.0166, 1841.2675, 947.49664, 2272.7693, 37.48879, 1.6596992, 58.601566, 69.40645, 1185.84, 346.3034, 178.20404, 427.45975, 7.0508466],
+ "label": ["link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_27", "link_29", "link_30", "link_31", "link_32", "link_33", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_0", "link_1", "link_2", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_0", "link_1", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_0", "link_1", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_0", "link_1", "link_2", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_0", "link_1", "link_2", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_2", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_2", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_0", "link_1", "link_1", "link_2", "link_2", "link_3", "link_3", "link_4", "link_4", "link_5", "link_5", "link_6", "link_6", "link_7", "link_7", "link_8", "link_8", "link_9", "link_9", "link_10", "link_10", "link_11", "link_11", "link_12", "link_12", "link_13", "link_13", "link_14", "link_14", "link_15", "link_15", "link_16", "link_16", "link_17", "link_17", "link_18", "link_18", "link_19", "link_19", "link_20", "link_20", "link_21", "link_21", "link_22", "link_22", "link_23", "link_23", "link_24", "link_24", "link_25", "link_25", "link_26", "link_26", "link_27", "link_27", "link_28", "link_28", "link_29", "link_29", "link_30", "link_30", "link_31", "link_31", "link_32", "link_32", "link_33", "link_33", "link_34", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_2", "link_2", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_27", "link_28", "link_29", "link_31", "link_32", "link_33", "link_34", "link_27", "link_28", "link_29", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_1", "link_0", "link_1", "link_0", "link_1", "link_2", "link_3", "link_4", "link_5", "link_6", "link_7", "link_8", "link_9", "link_10", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_19", "link_20", "link_21", "link_22", "link_23", "link_24", "link_25", "link_26", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_2", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34", "link_0", "link_2", "link_11", "link_12", "link_13", "link_14", "link_15", "link_16", "link_17", "link_18", "link_27", "link_28", "link_29", "link_30", "link_31", "link_32", "link_33", "link_34"],
+ "colorscales": [{
+ "label": "label_0",
+ "colorscale": [
+ [0, "white"],
+ [1, "#1f77b4"]
+ ]
+ }, {
+ "label": "label_1",
+ "colorscale": [
+ [0, "white"],
+ [1, "#ff7f0e"]
+ ]
+ }, {
+ "label": "label_2",
+ "colorscale": [
+ [0, "white"],
+ [1, "#2ca02c"]
+ ]
+ }, {
+ "label": "label_3",
+ "colorscale": [
+ [0, "white"],
+ [1, "#d62728"]
+ ]
+ }, {
+ "label": "label_4",
+ "colorscale": [
+ [0, "white"],
+ [1, "#9467bd"]
+ ]
+ }, {
+ "label": "label_5",
+ "colorscale": [
+ [0, "white"],
+ [1, "#8c564b"]
+ ]
+ }, {
+ "label": "label_6",
+ "colorscale": [
+ [0, "white"],
+ [1, "#e377c2"]
+ ]
+ }, {
+ "label": "label_7",
+ "colorscale": [
+ [0, "white"],
+ [1, "#7f7f7f"]
+ ]
+ }, {
+ "label": "label_8",
+ "colorscale": [
+ [0, "white"],
+ [1, "#bcbd22"]
+ ]
+ }, {
+ "label": "label_9",
+ "colorscale": [
+ [0, "white"],
+ [1, "#17becf"]
+ ]
+ }, {
+ "label": "label_10",
+ "colorscale": [
+ [0, "white"],
+ [1, "#1f77b4"]
+ ]
+ }, {
+ "label": "label_11",
+ "colorscale": [
+ [0, "white"],
+ [1, "#ff7f0e"]
+ ]
+ }, {
+ "label": "label_12",
+ "colorscale": [
+ [0, "white"],
+ [1, "#2ca02c"]
+ ]
+ }, {
+ "label": "label_13",
+ "colorscale": [
+ [0, "white"],
+ [1, "#d62728"]
+ ]
+ }, {
+ "label": "label_14",
+ "colorscale": [
+ [0, "white"],
+ [1, "#9467bd"]
+ ]
+ }, {
+ "label": "label_15",
+ "colorscale": [
+ [0, "white"],
+ [1, "#8c564b"]
+ ]
+ }, {
+ "label": "label_16",
+ "colorscale": [
+ [0, "white"],
+ [1, "#e377c2"]
+ ]
+ }, {
+ "label": "label_17",
+ "colorscale": [
+ [0, "white"],
+ [1, "#7f7f7f"]
+ ]
+ }, {
+ "label": "label_18",
+ "colorscale": [
+ [0, "white"],
+ [1, "#bcbd22"]
+ ]
+ }, {
+ "label": "label_19",
+ "colorscale": [
+ [0, "white"],
+ [1, "#17becf"]
+ ]
+ }, {
+ "label": "label_20",
+ "colorscale": [
+ [0, "white"],
+ [1, "#1f77b4"]
+ ]
+ }, {
+ "label": "label_21",
+ "colorscale": [
+ [0, "white"],
+ [1, "#ff7f0e"]
+ ]
+ }, {
+ "label": "label_22",
+ "colorscale": [
+ [0, "white"],
+ [1, "#2ca02c"]
+ ]
+ }, {
+ "label": "label_23",
+ "colorscale": [
+ [0, "white"],
+ [1, "#d62728"]
+ ]
+ }, {
+ "label": "label_24",
+ "colorscale": [
+ [0, "white"],
+ [1, "#9467bd"]
+ ]
+ }, {
+ "label": "label_25",
+ "colorscale": [
+ [0, "white"],
+ [1, "#8c564b"]
+ ]
+ }, {
+ "label": "label_26",
+ "colorscale": [
+ [0, "white"],
+ [1, "#e377c2"]
+ ]
+ }, {
+ "label": "label_27",
+ "colorscale": [
+ [0, "white"],
+ [1, "#7f7f7f"]
+ ]
+ }, {
+ "label": "label_28",
+ "colorscale": [
+ [0, "white"],
+ [1, "#bcbd22"]
+ ]
+ }, {
+ "label": "label_29",
+ "colorscale": [
+ [0, "white"],
+ [1, "#17becf"]
+ ]
+ }, {
+ "label": "label_30",
+ "colorscale": [
+ [0, "white"],
+ [1, "#1f77b4"]
+ ]
+ }, {
+ "label": "label_31",
+ "colorscale": [
+ [0, "white"],
+ [1, "#ff7f0e"]
+ ]
+ }, {
+ "label": "label_32",
+ "colorscale": [
+ [0, "white"],
+ [1, "#2ca02c"]
+ ]
+ }, {
+ "label": "label_33",
+ "colorscale": [
+ [0, "white"],
+ [1, "#d62728"]
+ ]
+ }, {
+ "label": "label_34",
+ "colorscale": [
+ [0, "white"],
+ [1, "#9467bd"]
+ ]
+ }],
+ "hovertemplate": "%{label}
%{flow.labelConcentration:%0.2f}
%{flow.value}"
+ }
+ }],
+ "layout": {
+ "width": 800,
+ "height": 800
+ }
+}
diff --git a/test/image/mocks/sankey_circular_process.json b/test/image/mocks/sankey_circular_process.json
new file mode 100644
index 00000000000..6e04c9392e4
--- /dev/null
+++ b/test/image/mocks/sankey_circular_process.json
@@ -0,0 +1,28 @@
+{
+ "data": [{
+ "type": "sankey",
+ "node": {
+ "line": {
+ "width": 1,
+ "color": "black"
+ },
+ "pad": 5,
+ "label": ["startA", "startB", "process1", "process2", "process3", "process4", "process5", "process6", "process7", "process8", "process9", "process10", "process11", "process12", "process13", "process14", "process15", "process16", "finishA", "finishB"]
+ },
+ "link": {
+ "line": {
+ "width": 1,
+ "color": "black"
+ },
+ "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],
+ "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],
+ "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]
+ }
+ }],
+ "layout": {
+ "title": "Example process",
+ "margin": {"t": 100, "b": 0, "l": 25, "r": 25},
+ "width": 1000,
+ "height": 800
+ }
+}
diff --git a/test/image/mocks/sankey_circular_simple.json b/test/image/mocks/sankey_circular_simple.json
new file mode 100644
index 00000000000..cff172479f7
--- /dev/null
+++ b/test/image/mocks/sankey_circular_simple.json
@@ -0,0 +1,31 @@
+{
+ "data": [
+ {
+ "type": "sankey",
+ "node": {
+ "pad": 5,
+ "label": ["0", "1", "2", "3", "sink", "source"]
+ },
+ "link": {
+ "source": [
+ 0, 1, 2, 3,
+ 0, 1, 2, 3,
+ 5
+ ],
+ "target": [
+ 1, 2, 3, 0,
+ 4, 4, 4, 4,
+ 0
+ ],
+ "value": [
+ 1, 0.85, 0.7, 0.55,
+ 0, 0.15, 0.15, 0.15,
+ 0.45
+ ]
+ }
+ }],
+ "layout": {
+ "width": 800,
+ "height": 800
+ }
+}
diff --git a/test/image/mocks/sankey_subplots_circular.json b/test/image/mocks/sankey_subplots_circular.json
new file mode 100644
index 00000000000..cfde57db330
--- /dev/null
+++ b/test/image/mocks/sankey_subplots_circular.json
@@ -0,0 +1,65 @@
+{
+ "data": [{
+ "domain": {
+ "x": [0, 0.45]
+ },
+ "type": "sankey",
+ "orientation": "h",
+ "node": {
+ "line": {
+ "color": "black",
+ "width": 1
+ },
+ "label": ["el1", "el2", "el3"]
+ },
+ "link": {
+ "source": [0, 2, 1],
+ "target": [1, 1, 2],
+ "value": [120, 50, 30],
+ "color": "purple",
+ "label": ["stram1", "stream2", "stream3"],
+ "line": {
+ "color": "black",
+ "width": 1
+ }
+ }
+ },
+ {
+ "domain": {
+ "x": [0.55, 1]
+ },
+ "type": "sankey",
+ "orientation": "v",
+ "node": {
+ "line": {
+ "color": "black",
+ "width": 1
+ },
+ "label": ["el4", "el5", "el6", "el7", "el8"]
+ },
+ "link": {
+ "source": [0, 2, 1, 3, 4, 1],
+ "target": [1, 1, 2, 2, 2, 4],
+ "value": [120, 50, 30, 70, 25, 10],
+ "color": "rgba(0, 0, 0, 0.1)",
+ "label": ["stram4", "stream5", "stream6", "stream7", "stream8", "stream9"],
+ "line": {
+ "color": "black",
+ "width": 1
+ }
+ }
+ }
+ ],
+ "layout": {
+ "title": "Multiple Sankey plots with circular links",
+ "width": 800,
+ "height": 300,
+ "margin": {
+ "t": 50,
+ "l": 10,
+ "r": 10,
+ "b": 10
+ },
+ "showlegend": false
+ }
+}
diff --git a/test/jasmine/tests/sankey_test.js b/test/jasmine/tests/sankey_test.js
index 27a19406860..d2a7391d8ed 100644
--- a/test/jasmine/tests/sankey_test.js
+++ b/test/jasmine/tests/sankey_test.js
@@ -2,8 +2,12 @@ var Plotly = require('@lib/index');
var attributes = require('@src/traces/sankey/attributes');
var Lib = require('@src/lib');
var d3 = require('d3');
+var d3sankey = require('@plotly/d3-sankey');
+var d3SankeyCircular = require('d3-sankey-circular');
var mock = require('@mocks/sankey_energy.json');
var mockDark = require('@mocks/sankey_energy_dark.json');
+var mockCircular = require('@mocks/sankey_circular.json');
+var mockCircularLarge = require('@mocks/sankey_circular_large.json');
var Sankey = require('@src/traces/sankey');
var createGraphDiv = require('../assets/create_graph_div');
@@ -240,69 +244,42 @@ describe('sankey tests', function() {
});
describe('sankey calc', function() {
-
function _calc(trace) {
var gd = { data: [trace] };
supplyAllDefaults(gd);
var fullTrace = gd._fullData[0];
- Sankey.calc(gd, fullTrace);
- return fullTrace;
+ return Sankey.calc(gd, fullTrace);
}
var base = { type: 'sankey' };
- describe('remove nodes if encountering circularity', function() {
- var errors;
-
- beforeEach(function() {
- errors = [];
- spyOn(Lib, 'error').and.callFake(function(msg) {
- errors.push(msg);
- });
- });
-
- it('removing a single self-pointing node', function() {
- expect(errors.length).toBe(0);
-
- var fullTrace = _calc(Lib.extendDeep({}, base, {
- node: {
- label: ['a']
- },
- link: {
- value: [1],
- source: [0],
- target: [0]
- }
- }));
-
- expect(fullTrace.node.label).toEqual([], 'node label(s) removed');
- expect(fullTrace.link.value).toEqual([], 'link value(s) removed');
- expect(fullTrace.link.source).toEqual([], 'link source(s) removed');
- expect(fullTrace.link.target).toEqual([], 'link target(s) removed');
- expect(errors.length).toBe(1);
- });
+ it('detects circularity', function() {
+ var calcData = _calc(Lib.extendDeep({}, base, {
+ node: {
+ label: ['a', 'b', 'c', 'd', 'e']
+ },
+ link: {
+ value: [1, 1, 1, 1, 1, 1, 1, 1],
+ source: [0, 1, 2, 3],
+ target: [1, 2, 0, 4]
+ }
+ }));
+ expect(calcData[0].circular).toBeTruthy();
+ });
- it('removing everything if detecting a circle', function() {
- expect(errors.length).toBe(0);
-
- var fullTrace = _calc(Lib.extendDeep({}, base, {
- node: {
- label: ['a', 'b', 'c', 'd', 'e']
- },
- link: {
- value: [1, 1, 1, 1, 1, 1, 1, 1],
- source: [0, 1, 2, 3],
- target: [1, 2, 0, 4]
- }
- }));
-
- expect(fullTrace.node.label).toEqual([], 'node label(s) removed');
- expect(fullTrace.link.value).toEqual([], 'link value(s) removed');
- expect(fullTrace.link.source).toEqual([], 'link source(s) removed');
- expect(fullTrace.link.target).toEqual([], 'link target(s) removed');
- expect(errors.length).toBe(1);
- });
+ it('detects the absence of circularity', function() {
+ var calcData = _calc(Lib.extendDeep({}, base, {
+ node: {
+ label: ['a', 'b', 'c', 'd', 'e']
+ },
+ link: {
+ value: [1, 1, 1, 1, 1, 1, 1, 1],
+ source: [0, 1, 2, 3],
+ target: [1, 2, 4, 4]
+ }
+ }));
+ expect(calcData[0].circular).toBe(false);
});
});
@@ -383,6 +360,47 @@ describe('sankey tests', function() {
done();
});
});
+
+ it('switch from normal to circular Sankey on react', function(done) {
+ var gd = createGraphDiv();
+ var mockCopy = Lib.extendDeep({}, mock);
+ var mockCircularCopy = Lib.extendDeep({}, mockCircular);
+
+ Plotly.plot(gd, mockCopy)
+ .then(function() {
+ expect(gd.calcdata[0][0].circular).toBe(false);
+ return Plotly.react(gd, mockCircularCopy);
+ })
+ .then(function() {
+ expect(gd.calcdata[0][0].circular).toBe(true);
+ done();
+ });
+ });
+
+ it('switch from circular to normal Sankey on react', function(done) {
+ var gd = createGraphDiv();
+ var mockCircularCopy = Lib.extendDeep({}, mockCircular);
+
+ Plotly.plot(gd, mockCircularCopy)
+ .then(function() {
+ expect(gd.calcdata[0][0].circular).toBe(true);
+
+ // Remove circular links
+ var source = mockCircularCopy.data[0].link.source;
+ source.splice(6, 1);
+ source.splice(4, 1);
+
+ var target = mockCircularCopy.data[0].link.target;
+ target.splice(6, 1);
+ target.splice(4, 1);
+
+ return Plotly.react(gd, mockCircularCopy);
+ })
+ .then(function() {
+ expect(gd.calcdata[0][0].circular).toBe(false);
+ done();
+ });
+ });
});
describe('Test hover/click interactions:', function() {
@@ -931,3 +949,293 @@ function assertNoLabel() {
var g = d3.selectAll('.hovertext');
expect(g.size()).toBe(0);
}
+
+describe('sankey layout generators', function() {
+ function checkArray(arr, key, result) {
+ var value = arr.map(function(obj) {
+ return obj[key];
+ });
+ expect(value).toEqual(result, 'invalid property named ' + key);
+ }
+
+ function checkRoundedArray(arr, key, result) {
+ var value = arr.map(function(obj) {
+ return Math.round(obj[key]);
+ });
+ expect(value).toEqual(result, 'invalid property named ' + key);
+ }
+
+ function moveNode(sankey, graph, nodeIndex, delta) {
+ var node = graph.nodes[nodeIndex];
+ var pos0 = [node.x0, node.y0];
+ var pos1 = [node.x1, node.y1];
+
+ // Update node's position
+ node.x0 += delta[0];
+ node.x1 += delta[0];
+ node.y0 += delta[1];
+ node.y1 += delta[1];
+
+ // Update links
+ var updatedGraph = sankey.update(graph);
+
+ // Check node position
+ expect(updatedGraph.nodes[nodeIndex].x0).toBeCloseTo(pos0[0] + delta[0], 0);
+ expect(updatedGraph.nodes[nodeIndex].x1).toBeCloseTo(pos1[0] + delta[0], 0);
+ expect(updatedGraph.nodes[nodeIndex].y0).toBeCloseTo(pos0[1] + delta[1], 0);
+ expect(updatedGraph.nodes[nodeIndex].y1).toBeCloseTo(pos1[1] + delta[1], 0);
+
+ return updatedGraph;
+ }
+
+ describe('d3-sankey', function() {
+ var data = {
+ 'nodes': [{
+ 'node': 0,
+ 'name': 'node0'
+ }, {
+ 'node': 1,
+ 'name': 'node1'
+ }, {
+ 'node': 2,
+ 'name': 'node2'
+ }, {
+ 'node': 3,
+ 'name': 'node3'
+ }, {
+ 'node': 4,
+ 'name': 'node4'
+ }],
+ 'links': [{
+ 'source': 0,
+ 'target': 2,
+ 'value': 2
+ }, {
+ 'source': 1,
+ 'target': 2,
+ 'value': 2
+ }, {
+ 'source': 1,
+ 'target': 3,
+ 'value': 2
+ }, {
+ 'source': 0,
+ 'target': 4,
+ 'value': 2
+ }, {
+ 'source': 2,
+ 'target': 3,
+ 'value': 2
+ }, {
+ 'source': 2,
+ 'target': 4,
+ 'value': 2
+ }, {
+ 'source': 3,
+ 'target': 4,
+ 'value': 4
+ }]
+ };
+ var sankey;
+ var graph;
+ var margin = {
+ top: 10,
+ right: 10,
+ bottom: 10,
+ left: 10
+ };
+ var width = 1200 - margin.left - margin.right;
+ var height = 740 - margin.top - margin.bottom;
+
+ beforeEach(function() {
+ sankey = d3sankey
+ .sankey()
+ .nodeWidth(36)
+ .nodePadding(10)
+ .nodes(data.nodes)
+ .links(data.links)
+ .size([width, height])
+ .iterations(32);
+
+ graph = sankey();
+ });
+
+ it('controls the width of nodes', function() {
+ expect(sankey.nodeWidth()).toEqual(36, 'incorrect nodeWidth');
+ });
+
+ it('controls the padding between nodes', function() {
+ expect(sankey.nodePadding()).toEqual(10, 'incorrect nodePadding');
+ });
+
+ it('controls the padding between nodes', function() {
+ expect(sankey.nodePadding()).toEqual(10, 'incorrect nodePadding');
+ });
+
+ it('keep a list of nodes', function() {
+ checkArray(graph.nodes, 'name', ['node0', 'node1', 'node2', 'node3', 'node4']);
+ });
+
+ it('keep a list of nodes with x and y values', function() {
+ checkRoundedArray(graph.nodes, 'x0', [0, 0, 381, 763, 1144]);
+ checkRoundedArray(graph.nodes, 'y0', [0, 365, 184, 253, 0]);
+ });
+
+ it('keep a list of nodes with positions in integer (depth, height)', function() {
+ checkArray(graph.nodes, 'depth', [0, 0, 1, 2, 3]);
+ checkArray(graph.nodes, 'height', [3, 3, 2, 1, 0]);
+ });
+
+ it('keep a list of links', function() {
+ var linkWidths = sankey().links.map(function(obj) {
+ return (obj.width);
+ });
+ expect(linkWidths).toEqual([177.5, 177.5, 177.5, 177.5, 177.5, 177.5, 355]);
+ });
+
+ it('controls the size of the figure', function() {
+ expect(sankey.size()).toEqual([1180, 720], 'incorrect size');
+ });
+
+ it('updates links vertical position upon moving nodes', function() {
+ var nodeIndex = 0;
+ var linkIndex = 0;
+ var delta = [200, 300];
+
+ var linkY0 = graph.links[linkIndex].y0;
+ var updatedGraph = moveNode(sankey, graph, nodeIndex, delta);
+ expect(updatedGraph.links[linkIndex].y0).toBeCloseTo(linkY0 + delta[1]);
+ });
+ });
+
+ describe('d3-sankey-ciruclar', function() {
+ var data, sankey, graph;
+
+ describe('implements d3-sankey compatible API', function() {
+ function _calc(trace) {
+ var gd = { data: [trace] };
+
+ supplyAllDefaults(gd);
+ var fullTrace = gd._fullData[0];
+ return Sankey.calc(gd, fullTrace);
+ }
+
+ beforeEach(function() {
+ data = _calc(mockCircular.data[0]);
+ data = {
+ nodes: data[0]._nodes,
+ links: data[0]._links
+ };
+ sankey = d3SankeyCircular
+ .sankeyCircular()
+ .iterations(32)
+ .circularLinkGap(2)
+ .nodePadding(10)
+ .size([500, 500])
+ .nodeId(function(d) {
+ return d.pointNumber;
+ })
+ .nodes(data.nodes)
+ .links(data.links);
+
+ graph = sankey();
+ });
+
+ it('creates a graph with circular links', function() {
+ expect(graph.nodes.length).toEqual(6, 'there are 6 nodes');
+ var circularLinks = graph.links.filter(function(link) {
+ return link.circular;
+ });
+ expect(circularLinks.length).toEqual(2, 'there are two circular links');
+ });
+
+ it('keep a list of nodes with positions in integer (depth, height)', function() {
+ checkArray(graph.nodes, 'depth', [0, 0, 2, 3, 1, 1]);
+ checkArray(graph.nodes, 'height', [1, 3, 1, 0, 2, 0]);
+ });
+
+ it('keep a list of nodes with positions in x and y', function() {
+ checkRoundedArray(graph.nodes, 'x0', [72, 72, 267, 365, 169, 169]);
+ checkRoundedArray(graph.nodes, 'y0', [303, 86, 72, 109, 86, 359]);
+ });
+
+ it('supports column reordering', function() {
+ var reorder = [ 2, 2, 1, 1, 0, 0 ];
+
+ checkArray(graph.nodes, 'column', [0, 0, 2, 3, 1, 1]);
+
+ var a = graph.nodes[0].x0;
+ sankey.nodeAlign(function(node) {
+ return reorder[node.pointNumber];
+ });
+ graph = sankey();
+ checkArray(graph.nodes, 'column', [2, 2, 1, 1, 0, 0]);
+ checkArray(graph.nodes, 'height', [1, 3, 1, 0, 2, 0]);
+ var b = graph.nodes[0].x0;
+ expect(a).not.toEqual(b);
+ });
+
+ it('updates links vertical position and circularLinkType upon moving nodes', function() {
+ var linkIndex = 6;
+ var nodeIndex = 2;
+ var delta = [0, 400];
+
+ var link = graph.links[linkIndex];
+ var linkY1 = link.y1;
+ var node = graph.nodes[nodeIndex];
+ var offsetTopToBottom = (node.y1 - node.y0) * link.value / node.value;
+
+ // Start with a circular link on top
+ expect(link.circular).toBeTruthy();
+ expect(link.circularLinkType).toEqual('top');
+
+ // Update graph
+ var updatedGraph = moveNode(sankey, graph, nodeIndex, delta);
+ var updatedLink = updatedGraph.links[linkIndex];
+
+ // End up with a cirular link on bottom
+ expect(updatedLink.circular).toBeTruthy();
+ expect(updatedLink.circularLinkType).toEqual('bottom');
+ expect(updatedLink.y1).toBeCloseTo(linkY1 + delta[1] + offsetTopToBottom, 0);
+ });
+ });
+
+ describe('handles large number of links', function() {
+ function _calc(trace) {
+ var gd = { data: [trace] };
+
+ supplyAllDefaults(gd);
+ var fullTrace = gd._fullData[0];
+ return Sankey.calc(gd, fullTrace);
+ }
+
+ beforeEach(function() {
+ data = _calc(mockCircularLarge.data[0]);
+ data = {
+ nodes: data[0]._nodes,
+ links: data[0]._links
+ };
+ sankey = d3SankeyCircular
+ .sankeyCircular()
+ .iterations(32)
+ .nodePadding(10)
+ .size([500, 500])
+ .nodeId(function(d) {
+ return d.pointNumber;
+ })
+ .nodes(data.nodes)
+ .links(data.links);
+
+ graph = sankey();
+ });
+
+ it('creates a graph with circular links', function() {
+ expect(graph.nodes.length).toEqual(26, 'right number of nodes');
+ var circularLinks = graph.links.filter(function(link) {
+ return link.circular;
+ });
+ expect(circularLinks.length).toEqual(89, 'right number of circular links');
+ });
+ });
+ });
+});