From c29f31a797125575da05fb4a6c22c2f2426139f0 Mon Sep 17 00:00:00 2001 From: deqingli Date: Wed, 2 Dec 2015 20:10:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0snaky=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/chart/sankey.js | 9 + src/chart/sankey/SankeySeries.js | 101 +++++++++ src/chart/sankey/SankeyView.js | 139 ++++++++++++ src/chart/sankey/sankeyLayout.js | 363 +++++++++++++++++++++++++++++++ src/chart/sankey/sankeyVisual.js | 31 +++ src/util/array/nest.js | 104 +++++++++ test/data/energy.json | 120 ++++++++++ test/data/product.json | 134 ++++++++++++ test/sankey.html | 70 ++++++ 9 files changed, 1071 insertions(+) create mode 100644 src/chart/sankey.js create mode 100644 src/chart/sankey/SankeySeries.js create mode 100644 src/chart/sankey/SankeyView.js create mode 100644 src/chart/sankey/sankeyLayout.js create mode 100644 src/chart/sankey/sankeyVisual.js create mode 100644 src/util/array/nest.js create mode 100755 test/data/energy.json create mode 100644 test/data/product.json create mode 100644 test/sankey.html diff --git a/src/chart/sankey.js b/src/chart/sankey.js new file mode 100644 index 000000000..2b19ec001 --- /dev/null +++ b/src/chart/sankey.js @@ -0,0 +1,9 @@ +define(function (require) { + + var echarts = require('../echarts'); + + require('./sankey/SankeySeries'); + require('./sankey/SankeyView'); + echarts.registerLayout(require('./sankey/sankeyLayout')); + echarts.registerVisualCoding('chart', require('./sankey/sankeyVisual')); +}) \ No newline at end of file diff --git a/src/chart/sankey/SankeySeries.js b/src/chart/sankey/SankeySeries.js new file mode 100644 index 000000000..aee321383 --- /dev/null +++ b/src/chart/sankey/SankeySeries.js @@ -0,0 +1,101 @@ +define(function (require) { + + 'use strict'; + + var SeriesModel = require('../../model/Series'); + var createGraphFromNodeEdge = require('../helper/createGraphFromNodeEdge'); + + return SeriesModel.extend({ + + type: 'series.sankey', + + layoutInfo: null, + + getInitialData: function (option, ecModel) { + var links = option.edges || option.links; + var nodes = option.data || option.nodes; + if (nodes && links) { + var graph = createGraphFromNodeEdge(nodes, links, this, true); + return graph.data; + } + }, + + /** + * @return {module:echarts/data/Graph} + */ + getGraph: function () { + return this.getData().graph; + }, + + /** + * return {module:echarts/data/List} + */ + getEdgeData: function() { + return this.getGraph().edgeData; + }, + + defaultOption: { + zlevel: 0, + z: 2, + + coordinateSystem: 'view', + + layout : null, + + // the position of the whole view + x: '5%', + y: '5%', + x2: '20%', + y2: '5%', + + // the dx of the node + nodeWidth: 20, + + // the distance between two nodes + nodeGap: 8, + + // the number of iterations to change the position of the node + layoutIterations: 32, + + label: { + normal: { + show: true, + textStyle: { + fontSize: 6 + } + }, + emphasis: { + show: true + } + }, + + itemStyle: { + normal: {}, + emphasis: {} + }, + + lineStyle: { + normal: { + color: '#000', + opacity: 0.2, + curveness: 0.5 + }, + emphasis: { + opacity: 0.6 + } + }, + + + // colorEncoded: 'node', + + color: ['#9e0142', '#d53e4f', '#f46d43', '#fdae61', '#fee08b','#ffffbf', + '#e6f598', '#abdda4', '#66c2a5', '#3288bd', '#5e4fa2'], + + animationEasing: 'linear', + + animationDuration: 2000 + } + + }); + +}); \ No newline at end of file diff --git a/src/chart/sankey/SankeyView.js b/src/chart/sankey/SankeyView.js new file mode 100644 index 000000000..ab583e1dc --- /dev/null +++ b/src/chart/sankey/SankeyView.js @@ -0,0 +1,139 @@ +define(function (require) { + + var graphic = require('../../util/graphic'); + var modelUtil = require('../../util/model'); + + return require('../../echarts').extendChartView({ + + type: 'sankey', + + /** + * @private + * @type {module:echarts/chart/sankey/SankeySeries} + */ + _model: null, + + render: function(seriesModel, ecModel, api) { + var graph = seriesModel.getGraph(); + var group = this.group; + this._model = seriesModel; + + group.removeAll(); + + group.position = [seriesModel.layoutInfo.x, seriesModel.layoutInfo.y]; + var textStyle = seriesModel.getModel('label.normal').getModel('textStyle'); + + var edgeData = graph.edgeData; + var rawOption = seriesModel.option; + var formatModel = modelUtil.createDataFormatModel( + seriesModel, edgeData, rawOption.edges || rawOption.links + ); + + formatModel.formatTooltip = function (dataIndex) { + var params = this.getDataParams(dataIndex); + var rawDataOpt = params.data; + var html = rawDataOpt.source + ' -- ' + rawDataOpt.target; + if (params.value) { + html += ':' + params.value; + } + return html; + }; + + // generate a rect for each node + graph.eachNode(function (node) { + var rect = new graphic.Rect({ + shape: { + x: node.getLayout().x, + y: node.getLayout().y, + width: node.getLayout().dx, + height: node.getLayout().dy + }, + style: { + stroke: '#000', + fill: node.getVisual('color') + } + }); + + // rect.setHoverStyle(rect, node.inEdges.) + group.add(rect); + + var text = new graphic.Text({ + style: { + x: node.getLayout().x + node.getLayout().dx + 4, + y: node.getLayout().y + node.getLayout().dy / 2, + text: node.id, + textAlign: 'start', + textBaseline: 'middle', + textFont: textStyle.getFont() + } + }); + group.add(text); + }); + + // generate a bezire Curve for each edge + graph.eachEdge(function (edge) { + var curve = new graphic.BezierCurve(); + + curve.dataIndex = edge.dataIndex; + curve.hostModel = formatModel; + + curve.style.lineWidth = Math.max(1, edge.getLayout().dy); + + var lineStyleModel = edge.getModel('lineStyle.normal'); + var curvature = lineStyleModel.get('curveness'); + var x1 = edge.node1.getLayout().x + edge.node1.getLayout().dx; + var y1 = edge.node1.getLayout().y + edge.getLayout().sy + edge.getLayout().dy / 2; + var x2 = edge.node2.getLayout().x; + var y2 = edge.node2.getLayout().y + edge.getLayout().ty + edge.getLayout().dy /2; + var cpx1 = x1 * (1 - curvature) + x2 * curvature; + var cpy1 = y1; + var cpx2 = x1 * curvature + x2 * (1 - curvature); + var cpy2 = y2; + + curve.setShape({ + x1: x1, + y1: y1, + x2: x2, + y2: y2, + cpx1: cpx1, + cpy1: cpy1, + cpx2: cpx2, + cpy2: cpy2 + }); + + // 'width' is just for 'lineWidth', lineWidth get from the computation,so user can't set it + curve.setStyle(lineStyleModel.getLineStyle(['width'])); + + graphic.setHoverStyle(curve, edge.getModel('lineStyle.emphasis').getLineStyle()); + + group.add(curve); + + }); + if (!this._data) { + group.setClipPath(createGridClipShape(group.getBoundingRect(), seriesModel, function () { + group.removeClipPath(); + })); + } + this._data = seriesModel.getData(); + } + }); + + function createGridClipShape(rect, seriesModel, cb) { + var rectEl = new graphic.Rect({ + shape: { + x: rect.x - 10, + y: rect.y - 10, + width: 0, + height: rect.height + 20 + } + }); + graphic.initProps(rectEl, { + shape: { + width: rect.width + 20, + height: rect.height + 20 + } + }, seriesModel, cb); + + return rectEl; + } +}); \ No newline at end of file diff --git a/src/chart/sankey/sankeyLayout.js b/src/chart/sankey/sankeyLayout.js new file mode 100644 index 000000000..868864e23 --- /dev/null +++ b/src/chart/sankey/sankeyLayout.js @@ -0,0 +1,363 @@ +define(function (require) { + + var layout = require('../../util/layout'); + var nest = require('../../util/array/nest'); + var zrUtil = require('zrender/core/util'); + + return function (ecModel, api) { + + ecModel.eachSeriesByType('sankey', function (seriesModel) { + + var nodeWidth = seriesModel.get('nodeWidth'); + var nodeGap = seriesModel.get('nodeGap'); + + var layoutInfo = getViewRect(seriesModel, api); + + seriesModel.layoutInfo = layoutInfo; + + var width = layoutInfo.width; + var height = layoutInfo.height; + + var graph = seriesModel.getGraph(); + + var nodes = graph.nodes; + var edges = graph.edges; + + computeNodeValues(nodes); + + var filteredNodes = nodes.filter(function (node) { + return node.getLayout().value === 0; + }); + + if (filteredNodes.length !== 0) { + var iterations = 0; + } + else { + var iterations = seriesModel.get('layoutIterations'); + } + + layoutSankey(nodes, edges, nodeWidth, nodeGap, width, height, iterations); + }); + }; + + /** + * get the layout position of the whole view. + */ + function getViewRect(seriesModel, api) { + return layout.parsePositionInfo({ + x: seriesModel.get('x'), + y: seriesModel.get('y'), + x2: seriesModel.get('x2'), + y2: seriesModel.get('y2'), + width: seriesModel.get('width'), + height: seriesModel.get('height') + },{ + width: api.getWidth(), + height: api.getHeight() + }); + } + + function layoutSankey(nodes, edges, nodeWidth, nodeGap, width, height, iterations) { + computeNodeBreadths(nodes, nodeWidth, width); + computeNodeDepths(nodes, edges, height, nodeGap, iterations); + computeEdgeDepths(nodes); + } + + /** + * compute the value of each node by summing the associated edge's value. + * @param {module:echarts/data/Graph~Node} nodes + */ + function computeNodeValues(nodes) { + zrUtil.each(nodes, function (node) { + var value1 = sum(node.outEdges, getEdgeValue); + var value2 = sum(node.inEdges, getEdgeValue); + var value = Math.max(value1, value2); + node.setLayout({value: value}, true); + }); + } + + /** + * compute the x-position for each node. + * @param {module:echarts/data/Graph~Node} nodes + * @param {number} nodeWidth + * @param {number} width + */ + function computeNodeBreadths(nodes, nodeWidth, width) { + var remainNodes = nodes; + var nextNode = null; + var x = 0; + var kx = 0; + + while (remainNodes.length) { + nextNode = []; + zrUtil.each(remainNodes, function (node) { + node.setLayout({x: x}, true); + node.setLayout({dx: nodeWidth}, true); + zrUtil.each(node.outEdges, function (edge) { + nextNode.push(edge.node2); + }); + }); + remainNodes = nextNode; + ++x; + } + + moveSinksRight(nodes, x); + kx = (width - nodeWidth) / (x - 1); + + scaleNodeBreadths(nodes, kx); + } + + /** + * all the node without outEgdes are assigned maximum breadth and + * be aligned in the last column. + * @param {module:echarts/data/Graph~Node} nodes + * @param {number} x + */ + function moveSinksRight(nodes, x) { + zrUtil.each(nodes, function (node) { + if(!node.outEdges.length) { + node.setLayout({x: x-1}, true); + } + }); + } + + /** + * scale node x-position to the width. + * @param {module:echarts/data/Graph~Node} nodes + * @param {number} kx + */ + function scaleNodeBreadths(nodes, kx) { + zrUtil.each(nodes, function(node) { + var nodeX = node.getLayout().x * kx; + node.setLayout({x: nodeX}, true); + }); + } + + /** + * using Gauss-Seidel iterations method to compute the node depth(y-position). + * @param {module:echarts/data/Graph~Node} nodes + * @param {module:echarts/data/Graph~Edge} edges + * @param {number} height + * @param {numbber} nodeGap + * @param {number} iterations + */ + function computeNodeDepths(nodes, edges, height, nodeGap, iterations) { + var nodesByBreadth = nest() + .key(function (d) { + return d.getLayout().x; + }) + .sortKeys(ascending) + .entries(nodes) + .map(function (d) { + return d.values; + }); + + initializeNodeDepth(nodes, nodesByBreadth, edges, height, nodeGap); + resolveCollisions(nodesByBreadth, nodeGap, height); + + for (var alpha = 1; iterations > 0; iterations--) { + alpha *= 0.99; + relaxRightToLeft(nodesByBreadth, alpha); + resolveCollisions(nodesByBreadth, nodeGap, height); + relaxLeftToRight(nodesByBreadth, alpha); + resolveCollisions(nodesByBreadth, nodeGap, height); + } + } + + /** + * compute the original y-position for each node. + * @param {module:echarts/data/Graph~Node} nodes + * @param {Array.>} nodesByBreadth + * @param {module:echarts/data/Graph~Edge} edges + * @param {number} height + * @param {number} nodeGap + */ + function initializeNodeDepth(nodes, nodesByBreadth, edges, height, nodeGap) { + var kyArray = []; + zrUtil.each(nodesByBreadth, function (nodes) { + var n = nodes.length; + var sum = 0; + zrUtil.each(nodes, function (node) { + sum += node.getLayout().value; + }); + ky = (height - (n-1) * nodeGap) / sum; + kyArray.push(ky); + }); + kyArray.sort(function (a, b) { + return a - b; + }); + ky0 = kyArray[0]; + + zrUtil.each(nodesByBreadth, function (nodes) { + zrUtil.each(nodes, function (node, i) { + node.setLayout({y: i}, true); + var nodeDy = node.getLayout().value * ky0; + node.setLayout({dy: nodeDy}, true); + }); + }); + + zrUtil.each(edges, function (edge) { + var edgeDy = +edge.getValue() * ky0; + edge.setLayout({dy: edgeDy}, true); + }); + } + + /** + * resolve the collision of initialized depth. + * @param {Array.>} nodesByBreadth + * @param {number} nodeGap + * @param {number} height + */ + function resolveCollisions(nodesByBreadth, nodeGap, height) { + zrUtil.each(nodesByBreadth, function (nodes) { + var node; + var dy; + var y0 = 0; + var n = nodes.length; + var i; + + nodes.sort(ascendingDepth); + + for (i = 0; i < n; i++) { + node = nodes[i]; + dy = y0 - node.getLayout().y; + if(dy > 0) { + var nodeY = node.getLayout().y + dy; + node.setLayout({y: nodeY}, true); + } + y0 = node.getLayout().y + node.getLayout().dy + nodeGap; + } + + // if the bottommost node goes outside the biunds, push it back up + dy = y0 - nodeGap - height; + if (dy > 0) { + nodeY = node.getLayout().y -dy; + node.setLayout({y: nodeY}, true); + y0 = node.getLayout().y; + for (i = n - 2; i >= 0; --i) { + node = nodes[i]; + dy = node.getLayout().y + node.getLayout().dy + nodeGap - y0; + if (dy > 0) { + nodeY = node.getLayout().y - dy; + node.setLayout({y: nodeY}, true); + } + y0 = node.getLayout().y; + } + } + }); + } + + /** + * change the y-position of the nodes, except most the right side nodes. + * @param {Array.>} nodesByBreadth + * @param {number} alpha + */ + function relaxRightToLeft(nodesByBreadth, alpha) { + zrUtil.each(nodesByBreadth.slice().reverse(), function (nodes) { + zrUtil.each(nodes, function (node) { + if (node.outEdges.length) { + var y = sum(node.outEdges, weightedTarget) / sum(node.outEdges, getEdgeValue); + var nodeY = node.getLayout().y + (y - center(node)) * alpha; + node.setLayout({y: nodeY}, true); + } + }); + }); + } + + function weightedTarget(edge) { + return center(edge.node2) * edge.getValue(); + } + + /** + * change the y-position of the nodes, except most the left side nodes. + * @param {Array.>} nodesByBreadth + * @param {number} alpha + */ + function relaxLeftToRight(nodesByBreadth, alpha) { + zrUtil.each(nodesByBreadth, function (nodes) { + zrUtil.each(nodes, function (node) { + if (node.inEdges.length) { + var y = sum(node.inEdges, weightedSource) / sum(node.inEdges, getEdgeValue); + var nodeY = node.getLayout().y + (y - center(node)) * alpha; + node.setLayout({y: nodeY}, true); + } + }); + }); + } + + function weightedSource(edge) { + return center(edge.node1) * edge.getValue(); + } + + /** + * compute the depth(y-position) of each edge. + * @param {module:echarts/data/Graph~Node} nodes + */ + function computeEdgeDepths(nodes) { + zrUtil.each(nodes, function (node) { + node.outEdges.sort(ascendingTargetDepth); + node.inEdges.sort(ascendingSourceDepth); + }); + zrUtil.each(nodes, function (node) { + var sy = 0; + var ty = 0; + zrUtil.each(node.outEdges, function (edge) { + edge.setLayout({sy: sy}, true); + sy += edge.getLayout().dy; + }); + zrUtil.each(node.inEdges, function (edge) { + edge.setLayout({ty: ty}, true); + ty += edge.getLayout().dy; + }); + }); + } + + function ascendingTargetDepth(a, b) { + return a.node2.getLayout().y - b.node2.getLayout().y; + } + + function ascendingSourceDepth(a, b) { + return a.node1.getLayout().y - b.node1.getLayout().y; + } + + function sum(array, f) { + var s = 0; + var n = array.length; + var a; + var i = -1; + if (arguments.length === 1) { + while (++i < n) { + a = +array[i]; + if (!isNaN(a)) { + s += a; + } + } + } + else { + while (++i < n) { + a = +f.call(array, array[i], i); + if(!isNaN(a)) { + s += a; + } + } + } + return s; + } + + function center(node) { + return node.getLayout().y + node.getLayout().dy / 2; + } + + function ascendingDepth(a, b) { + return a.getLayout().y - b.getLayout().y; + } + + function ascending(a, b) { + return a < b ? -1 : a > b ? 1 : a == b ? 0 : NaN; + } + + function getEdgeValue(edge) { + return edge.getValue(); + } + +}); \ No newline at end of file diff --git a/src/chart/sankey/sankeyVisual.js b/src/chart/sankey/sankeyVisual.js new file mode 100644 index 000000000..dd3ed5758 --- /dev/null +++ b/src/chart/sankey/sankeyVisual.js @@ -0,0 +1,31 @@ +define(function (require) { + + var VisualMapping = require('../../visual/VisualMapping'); + + return function (ecModel, payload) { + ecModel.eachSeriesByType('sankey', function (seriesModel) { + var graph = seriesModel.getGraph(); + var nodes = graph.nodes; + + nodes.sort(function (a, b) { + return a.getLayout().value - b.getLayout().value; + }); + + var minValue = nodes[0].getLayout().value; + var maxValue = nodes[nodes.length - 1].getLayout().value; + + nodes.forEach(function (node) { + var mapping = new VisualMapping({ + type: 'color', + mappingMethod: 'linear', + dataExtent: [minValue, maxValue], + visual: seriesModel.get('color') + }); + + var mapValueToColor = mapping.mapValueToVisual(node.getLayout().value); + node.setVisual('color', mapValueToColor); + }); + + }) ; + }; +}); \ No newline at end of file diff --git a/src/util/array/nest.js b/src/util/array/nest.js new file mode 100644 index 000000000..396a1cb7c --- /dev/null +++ b/src/util/array/nest.js @@ -0,0 +1,104 @@ +define(function (require) { + + var zrUtil = require('zrender/core/util'); + + /** + * nest helper used to group by the array. + * can specified the keys and sort the keys. + */ + function nest() { + + var keysFunction = []; + var sortKeysFunction = []; + + /** + * map an Array into the mapObject. + * @param {Array} array + * @param {number} depth + */ + function map(array, depth) { + if (depth >= keysFunction.length) { + return array; + } + var i = -1; + var n = array.length; + var keyFunction = keysFunction[depth++]; + var mapObject = {}; + var valuesByKey = {}; + + while (++i < n) { + var keyValue = keyFunction(array[i]); + var values = valuesByKey[keyValue]; + + if (values) { + values.push(array[i]); + } + else { + valuesByKey[keyValue] = [array[i]]; + } + } + + zrUtil.each(valuesByKey, function (value, key) { + mapObject[key] = map(value, depth); + }); + + return mapObject; + } + + /** + * transform the Map Object to multidimensional Array + * @param {Object} map + * @param {number} depth + */ + function entriesMap(mapObject, depth) { + if (depth >= keysFunction.length) { + return mapObject; + } + var array = []; + var sortKeyFunction = sortKeysFunction[depth++]; + + zrUtil.each(mapObject, function (value, key) { + array.push({key: key, values: entriesMap(value, depth)}); + }); + + if (sortKeyFunction) { + return array.sort(function (a, b) { + return sortKeyFunction(a.key, b.key); + }); + } + else { + return array; + } + } + + return { + /** + * specified the key to groupby the arrays. + * users can specified one more keys. + * @param {Function} d + */ + key: function (d) { + keysFunction.push(d); + return this; + }, + + /** + * specified the comparator to sort the keys + * @param {Function} order + */ + sortKeys: function (order) { + sortKeysFunction[keysFunction.length - 1] = order; + return this; + }, + + /** + * the a + * @param {Array} array + */ + entries: function (array) { + return entriesMap(map(array, 0), 0); + } + }; + } + return nest; +}); \ No newline at end of file diff --git a/test/data/energy.json b/test/data/energy.json new file mode 100755 index 000000000..c745f86ec --- /dev/null +++ b/test/data/energy.json @@ -0,0 +1,120 @@ +{"nodes":[ +{"name":"Agricultural 'waste'"}, +{"name":"Bio-conversion"}, +{"name":"Liquid"}, +{"name":"Losses"}, +{"name":"Solid"}, +{"name":"Gas"}, +{"name":"Biofuel imports"}, +{"name":"Biomass imports"}, +{"name":"Coal imports"}, +{"name":"Coal"}, +{"name":"Coal reserves"}, +{"name":"District heating"}, +{"name":"Industry"}, +{"name":"Heating and cooling - commercial"}, +{"name":"Heating and cooling - homes"}, +{"name":"Electricity grid"}, +{"name":"Over generation / exports"}, +{"name":"H2 conversion"}, +{"name":"Road transport"}, +{"name":"Agriculture"}, +{"name":"Rail transport"}, +{"name":"Lighting & appliances - commercial"}, +{"name":"Lighting & appliances - homes"}, +{"name":"Gas imports"}, +{"name":"Ngas"}, +{"name":"Gas reserves"}, +{"name":"Thermal generation"}, +{"name":"Geothermal"}, +{"name":"H2"}, +{"name":"Hydro"}, +{"name":"International shipping"}, +{"name":"Domestic aviation"}, +{"name":"International aviation"}, +{"name":"National navigation"}, +{"name":"Marine algae"}, +{"name":"Nuclear"}, +{"name":"Oil imports"}, +{"name":"Oil"}, +{"name":"Oil reserves"}, +{"name":"Other waste"}, +{"name":"Pumped heat"}, +{"name":"Solar PV"}, +{"name":"Solar Thermal"}, +{"name":"Solar"}, +{"name":"Tidal"}, +{"name":"UK land based bioenergy"}, +{"name":"Wave"}, +{"name":"Wind"} +], +"links":[ +{"source": "Agricultural 'waste'", "target": "Bio-conversion", "value": 124.729}, +{"source": "Bio-conversion", "target": "Liquid", "value": 0.597}, +{"source": "Bio-conversion", "target": "Losses", "value": 26.862}, +{"source": "Bio-conversion", "target": "Solid", "value": 280.322}, +{"source": "Bio-conversion", "target": "Gas", "value": 81.144}, +{"source": "Biofuel imports", "target": "Liquid", "value": 35}, +{"source": "Biomass imports", "target": "Solid", "value": 35}, +{"source": "Coal imports", "target": "Coal", "value": 11.606}, +{"source": "Coal reserves","target": "Coal", "value": 63.965}, +{"source": "Coal", "target": "Solid", "value": 75.571}, +{"source": "District heating", "target": "Industry", "value": 10.639}, +{"source": "District heating", "target": "Heating and cooling - commercial", "value": 22.505}, +{"source": "District heating", "target": "Heating and cooling - homes", "value": 46.184}, +{"source": "Electricity grid", "target": "Over generation / exports", "value": 104.453}, +{"source": "Electricity grid", "target": "Heating and cooling - homes", "value": 113.726}, +{"source": "Electricity grid", "target": "H2 conversion", "value": 27.14}, +{"source": "Electricity grid", "target": "Industry", "value": 342.165}, +{"source": "Electricity grid", "target": "Road transport", "value": 37.797}, +{"source": "Electricity grid", "target": "Agriculture", "value": 4.412}, +{"source": "Electricity grid", "target": "Heating and cooling - commercial", "value": 40.858}, +{"source": "Electricity grid", "target": "Losses", "value": 56.691}, +{"source": "Electricity grid", "target": "Rail transport", "value": 7.863}, +{"source": "Electricity grid", "target": "Lighting & appliances - commercial", "value": 90.008}, +{"source": "Electricity grid", "target": "Lighting & appliances - homes", "value": 93.494}, +{"source": "Gas imports", "target": "Ngas", "value": 40.719}, +{"source": "Gas reserves", "target": "Ngas", "value": 82.233}, +{"source": "Gas", "target": "Heating and cooling - commercial", "value": 0.129}, +{"source": "Gas", "target": "Losses", "value": 1.401}, +{"source": "Gas", "target": "Thermal generation", "value": 151.891}, +{"source": "Gas", "target": "Agriculture", "value": 2.096}, +{"source": "Gas", "target": "Industry", "value": 48.58}, +{"source": "Geothermal", "target": "Electricity grid", "value": 7.013}, +{"source": "H2 conversion", "target": "H2", "value": 20.897}, +{"source": "H2 conversion", "target": "Losses", "value": 6.242}, +{"source": "H2", "target": "Road transport", "value": 20.897}, +{"source": "Hydro", "target": "Electricity grid", "value": 6.995}, +{"source": "Liquid", "target": "Industry", "value": 121.066}, +{"source": "Liquid", "target": "International shipping", "value": 128.69}, +{"source": "Liquid", "target": "Road transport", "value": 135.835}, +{"source": "Liquid", "target": "Domestic aviation", "value": 14.458}, +{"source": "Liquid", "target": "International aviation", "value": 206.267}, +{"source": "Liquid", "target": "Agriculture", "value": 3.64}, +{"source": "Liquid", "target": "National navigation", "value": 33.218}, +{"source": "Liquid", "target": "Rail transport", "value": 4.413}, +{"source": "Marine algae", "target": "Bio-conversion", "value": 4.375}, +{"source": "Ngas", "target": "Gas", "value": 122.952}, +{"source": "Nuclear", "target": "Thermal generation", "value": 839.978}, +{"source": "Oil imports", "target": "Oil", "value": 504.287}, +{"source": "Oil reserves", "target": "Oil", "value": 107.703}, +{"source": "Oil", "target": "Liquid", "value": 611.99}, +{"source": "Other waste", "target": "Solid", "value": 56.587}, +{"source": "Other waste", "target": "Bio-conversion", "value": 77.81}, +{"source": "Pumped heat", "target": "Heating and cooling - homes", "value": 193.026}, +{"source": "Pumped heat", "target": "Heating and cooling - commercial", "value": 70.672}, +{"source": "Solar PV", "target": "Electricity grid", "value": 59.901}, +{"source": "Solar Thermal", "target": "Heating and cooling - homes", "value": 19.263}, +{"source": "Solar", "target": "Solar Thermal", "value": 19.263}, +{"source": "Solar", "target": "Solar PV", "value": 59.901}, +{"source": "Solid", "target": "Agriculture", "value": 0.882}, +{"source": "Solid", "target": "Thermal generation", "value": 400.12}, +{"source": "Solid", "target": "Industry", "value": 46.477}, +{"source": "Thermal generation", "target": "Electricity grid", "value": 525.531}, +{"source": "Thermal generation", "target": "Losses", "value": 787.129}, +{"source": "Thermal generation", "target": "District heating", "value": 79.329}, +{"source": "Tidal", "target": "Electricity grid", "value": 9.452}, +{"source": "UK land based bioenergy", "target": "Bio-conversion", "value": 182.01}, +{"source": "Wave", "target": "Electricity grid", "value": 19.013}, +{"source": "Wind", "target": "Electricity grid", "value": 289.366} +]} diff --git a/test/data/product.json b/test/data/product.json new file mode 100644 index 000000000..6f4ced9eb --- /dev/null +++ b/test/data/product.json @@ -0,0 +1,134 @@ +{"nodes": [ + {"name": "Total"}, + {"name": "Environment"}, + {"name": "Land use"}, + {"name": "Cocoa butter (Organic)"}, + {"name": "Cocoa mass (Organic)"}, + {"name": "Hazelnuts (Organic)"}, + {"name": "Cane sugar (Organic)"}, + {"name": "Vegetables (Organic)"}, + {"name": "Climate change"}, + {"name": "Harmful substances"}, + {"name": "Water use"}, + {"name": "Resource depletion"}, + {"name": "Refrigeration"}, + {"name": "Packaging"}, + {"name": "Human rights"}, + {"name": "Child labour"}, + {"name": "Coconut oil (Organic)"}, + {"name": "Forced labour"}, + {"name": "Health safety"}, + {"name": "Access to water"}, + {"name": "Freedom of association"}, + {"name": "Access to land"}, + {"name": "Sufficient wage"}, + {"name": "Equal rights migrants"}, + {"name": "Discrimination"}, + {"name": "Working hours"} + ], + "links": [ + {"source": "Total", "target": "Environment", "value": 0.342284047256003}, + {"source": "Environment", "target": "Land use", "value": 0.32322870366987}, + {"source": "Land use", "target": "Cocoa butter (Organic)", "value": 0.177682517071359}, + {"source": "Land use", "target": "Cocoa mass (Organic)", "value": 0.137241325342711}, + {"source": "Land use", "target": "Hazelnuts (Organic)", "value": 0.00433076373512774}, + {"source": "Land use", "target": "Cane sugar (Organic)", "value": 0.00296956039863467}, + {"source": "Land use", "target": "Vegetables (Organic)", "value": 0.00100453712203756}, + {"source": "Environment", "target": "Climate change", "value": 0.0112886157414413}, + {"source": "Climate change", "target": "Cocoa butter (Organic)", "value": 0.00676852971933996}, + {"source": "Climate change", "target": "Cocoa mass (Organic)", "value": 0.00394686874786743}, + {"source": "Climate change", "target": "Cane sugar (Organic)", "value": 0.000315972058711838}, + {"source": "Climate change", "target": "Hazelnuts (Organic)", "value": 0.000218969462265292}, + {"source": "Climate change", "target": "Vegetables (Organic)", "value": 3.82757532567656e-05}, + {"source": "Environment", "target": "Harmful substances", "value": 0.00604275542495656}, + {"source": "Harmful substances", "target": "Cocoa mass (Organic)", "value": 0.0055125989240741}, + {"source": "Harmful substances", "target": "Cocoa butter (Organic)", "value": 0.000330017607892127}, + {"source": "Harmful substances", "target": "Cane sugar (Organic)", "value": 0.000200138892990337}, + {"source": "Harmful substances", "target": "Hazelnuts (Organic)", "value": 0}, + {"source": "Harmful substances", "target": "Vegetables (Organic)", "value": 0}, + {"source": "Environment", "target": "Water use", "value": 0.00148345269044703}, + {"source": "Water use", "target": "Cocoa butter (Organic)", "value": 0.00135309891304186}, + {"source": "Water use", "target": "Cocoa mass (Organic)", "value": 0.000105714137908639}, + {"source": "Water use", "target": "Hazelnuts (Organic)", "value": 1.33452642581887e-05}, + {"source": "Water use", "target": "Cane sugar (Organic)", "value": 8.78074837009238e-06}, + {"source": "Water use", "target": "Vegetables (Organic)", "value": 2.5136268682477e-06}, + {"source": "Environment", "target": "Resource depletion", "value": 0.000240519729288764}, + {"source": "Resource depletion", "target": "Cane sugar (Organic)", "value": 0.000226237279345084}, + {"source": "Resource depletion", "target": "Vegetables (Organic)", "value": 1.42824499436793e-05}, + {"source": "Resource depletion", "target": "Hazelnuts (Organic)", "value": 0}, + {"source": "Resource depletion", "target": "Cocoa mass (Organic)", "value": 0}, + {"source": "Resource depletion", "target": "Cocoa butter (Organic)", "value": 0}, + {"source": "Environment", "target": "Refrigeration", "value": 0}, + {"source": "Environment", "target": "Packaging", "value": 0}, + {"source": "Total", "target": "Human rights", "value": 0.307574096993239}, + {"source": "Human rights", "target": "Child labour", "value": 0.0410641202645833}, + {"source": "Child labour", "target": "Hazelnuts (Organic)", "value": 0.0105339381639722}, + {"source": "Child labour", "target": "Cocoa mass (Organic)", "value": 0.0105}, + {"source": "Child labour", "target": "Cocoa butter (Organic)", "value": 0.0087294420777}, + {"source": "Child labour", "target": "Coconut oil (Organic)", "value": 0.00474399974233333}, + {"source": "Child labour", "target": "Cane sugar (Organic)", "value": 0.00388226450884445}, + {"source": "Child labour", "target": "Vegetables (Organic)", "value": 0.00267447577173333}, + {"source": "Human rights", "target": "Forced labour", "value": 0.0365458590642445}, + {"source": "Forced labour", "target": "Hazelnuts (Organic)", "value": 0.0114913076376389}, + {"source": "Forced labour", "target": "Cocoa butter (Organic)", "value": 0.0081134807347}, + {"source": "Forced labour", "target": "Cocoa mass (Organic)", "value": 0.00765230236575}, + {"source": "Forced labour", "target": "Cane sugar (Organic)", "value": 0.004}, + {"source": "Forced labour", "target": "Vegetables (Organic)", "value": 0.00296668823626667}, + {"source": "Forced labour", "target": "Coconut oil (Organic)", "value": 0.00232208008988889}, + {"source": "Human rights", "target": "Health safety", "value": 0.0345435327843611}, + {"source": "Health safety", "target": "Hazelnuts (Organic)", "value": 0.0121419536385}, + {"source": "Health safety", "target": "Cocoa mass (Organic)", "value": 0.00766772850678333}, + {"source": "Health safety", "target": "Cocoa butter (Organic)", "value": 0.0056245892061}, + {"source": "Health safety", "target": "Coconut oil (Organic)", "value": 0.00361616847688889}, + {"source": "Health safety", "target": "Cane sugar (Organic)", "value": 0.00277374682533333}, + {"source": "Health safety", "target": "Vegetables (Organic)", "value": 0.00271934613075556}, + {"source": "Human rights", "target": "Access to water", "value": 0.0340206659360667}, + {"source": "Access to water", "target": "Cocoa mass (Organic)", "value": 0.0105}, + {"source": "Access to water", "target": "Cocoa butter (Organic)", "value": 0.0089274160792}, + {"source": "Access to water", "target": "Hazelnuts (Organic)", "value": 0.0054148022845}, + {"source": "Access to water", "target": "Cane sugar (Organic)", "value": 0.00333938149786667}, + {"source": "Access to water", "target": "Vegetables (Organic)", "value": 0.00314663377488889}, + {"source": "Access to water", "target": "Coconut oil (Organic)", "value": 0.00269243229961111}, + {"source": "Human rights", "target": "Freedom of association", "value": 0.0320571523941667}, + {"source": "Freedom of association", "target": "Hazelnuts (Organic)", "value": 0.0132312483463611}, + {"source": "Freedom of association", "target": "Cocoa butter (Organic)", "value": 0.0077695200707}, + {"source": "Freedom of association", "target": "Cocoa mass (Organic)", "value": 0.00510606573995}, + {"source": "Freedom of association", "target": "Vegetables (Organic)", "value": 0.00354321156324444}, + {"source": "Freedom of association", "target": "Cane sugar (Organic)", "value": 0.00240710667391111}, + {"source": "Freedom of association", "target": "Coconut oil (Organic)", "value": 0}, + {"source": "Human rights", "target": "Access to land", "value": 0.0315022209894056}, + {"source": "Access to land", "target": "Hazelnuts (Organic)", "value": 0.00964970063322223}, + {"source": "Access to land", "target": "Cocoa mass (Organic)", "value": 0.00938530207965}, + {"source": "Access to land", "target": "Cocoa butter (Organic)", "value": 0.0060110791848}, + {"source": "Access to land", "target": "Cane sugar (Organic)", "value": 0.00380818314608889}, + {"source": "Access to land", "target": "Vegetables (Organic)", "value": 0.00264795594564445}, + {"source": "Access to land", "target": "Coconut oil (Organic)", "value": 0}, + {"source": "Human rights", "target": "Sufficient wage", "value": 0.0287776757227333}, + {"source": "Sufficient wage", "target": "Cocoa mass (Organic)", "value": 0.00883512456493333}, + {"source": "Sufficient wage", "target": "Cocoa butter (Organic)", "value": 0.0078343367268}, + {"source": "Sufficient wage", "target": "Coconut oil (Organic)", "value": 0.00347879026511111}, + {"source": "Sufficient wage", "target": "Hazelnuts (Organic)", "value": 0.00316254211388889}, + {"source": "Sufficient wage", "target": "Vegetables (Organic)", "value": 0.00281013722808889}, + {"source": "Sufficient wage", "target": "Cane sugar (Organic)", "value": 0.00265674482391111}, + {"source": "Human rights", "target": "Equal rights migrants", "value" : 0.0271146645119444}, + {"source": "Equal rights migrants", "target": "Cocoa butter (Organic)", "value": 0.0071042315061}, + {"source": "Equal rights migrants", "target": "Cocoa mass (Organic)", "value": 0.00636673210005}, + {"source": "Equal rights migrants", "target": "Hazelnuts (Organic)", "value": 0.00601459775836111}, + {"source": "Equal rights migrants", "target": "Coconut oil (Organic)", "value": 0.00429185583138889}, + {"source": "Equal rights migrants", "target": "Cane sugar (Organic)", "value": 0.00182647471915556}, + {"source": "Equal rights migrants", "target": "Vegetables (Organic)", "value": 0.00151077259688889}, + {"source": "Human rights", "target": "Discrimination", "value": 0.0211217763359833}, + {"source": "Discrimination", "target": "Cocoa mass (Organic)", "value": 0.00609671700306667}, + {"source": "Discrimination", "target": "Cocoa butter (Organic)", "value": 0.0047738806325}, + {"source": "Discrimination", "target": "Coconut oil (Organic)", "value": 0.00368119084494444}, + {"source": "Discrimination", "target": "Vegetables (Organic)", "value": 0.00286009813604444}, + {"source": "Discrimination", "target": "Cane sugar (Organic)", "value": 0.00283706180951111}, + {"source": "Discrimination", "target": "Hazelnuts (Organic)", "value": 0.000872827909916666}, + {"source": "Human rights", "target": "Working hours", "value": 0.02082642898975}, + {"source": "Working hours", "target": "Hazelnuts (Organic)", "value": 0.0107216773848333}, + {"source": "Working hours", "target": "Coconut oil (Organic)", "value": 0.00359009052944444}, + {"source": "Working hours", "target": "Vegetables (Organic)", "value": 0.00212300379075556}, + {"source": "Working hours", "target": "Cocoa butter (Organic)", "value": 0.0018518584356}, + {"source": "Working hours", "target": "Cocoa mass (Organic)", "value": 0.00158227069058333}, + {"source": "Working hours", "target": "Cane sugar (Organic)", "value": 0.000957528158533333} + ]} diff --git a/test/sankey.html b/test/sankey.html new file mode 100644 index 000000000..955fd8f1b --- /dev/null +++ b/test/sankey.html @@ -0,0 +1,70 @@ + + + + + + + + + + + +
+ + + \ No newline at end of file -- GitLab