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'); + }); + }); + }); +});