2019-08-08 19:33:56 +08:00
|
|
|
<!DOCTYPE html></script>
|
|
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
<title>Title</title>
|
|
|
|
</head>
|
|
|
|
<style>
|
|
|
|
body{
|
|
|
|
background: rgb(255, 255, 255);
|
|
|
|
}
|
|
|
|
.g6-tooltip {
|
|
|
|
border: 1px solid #e2e2e2;
|
|
|
|
border-radius: 4px;
|
|
|
|
font-size: 12px;
|
|
|
|
color: #545454;
|
|
|
|
background-color: rgba(255, 255, 255, 0.9);
|
|
|
|
padding: 10px 8px;
|
|
|
|
box-shadow: rgb(174, 174, 174) 0px 0px 10px;
|
|
|
|
}
|
2019-11-20 12:26:51 +08:00
|
|
|
canvas {
|
|
|
|
border: 1px solid red;
|
|
|
|
}
|
2019-08-08 19:33:56 +08:00
|
|
|
</style>
|
|
|
|
<body>
|
2019-09-29 12:11:28 +08:00
|
|
|
<div id="tip">布局中,请稍候......</div>
|
|
|
|
<div id="mountNode"></div>
|
2019-08-08 19:33:56 +08:00
|
|
|
<script src="../build/g6.js"></script>
|
|
|
|
<script src="./assets/d3-4.13.0.min.js"></script>
|
|
|
|
|
|
|
|
<script>
|
2019-08-09 17:37:51 +08:00
|
|
|
const colors = [ '#f5222d', '#faad14',
|
|
|
|
'#a0d911', '#13c2c2', '#1890ff', '#b37feb', '#eb2f96' ];
|
2019-10-08 14:39:45 +08:00
|
|
|
const beginColor = '#5b8c00'; // green
|
|
|
|
const endColor = '#ff4d4f'; // red
|
2019-08-08 19:33:56 +08:00
|
|
|
d3.json("./assets/data/filtered-trade.json", function(data) {
|
|
|
|
const nodes = data.nodes;
|
|
|
|
const edges = data.edges;
|
|
|
|
|
2019-08-09 17:37:51 +08:00
|
|
|
const nodeMap = new Map();
|
|
|
|
const clusterMap = new Map();
|
|
|
|
let cidx = 0;
|
|
|
|
nodes.forEach(n => {
|
|
|
|
nodeMap.set(n.id, n);
|
|
|
|
let region = n.region.split(" ");
|
|
|
|
if (n.region === 'East Asia') region = n.region;
|
|
|
|
else region = region[region.length - 1];
|
|
|
|
|
|
|
|
if (clusterMap.get(region) === undefined) {
|
|
|
|
clusterMap.set(region, cidx);
|
|
|
|
cidx++;
|
|
|
|
}
|
|
|
|
const clusterId = clusterMap.get(region);
|
|
|
|
const color = colors[clusterId % colors.length];
|
|
|
|
n.style = {
|
|
|
|
color,
|
2019-09-30 13:19:00 +08:00
|
|
|
fill: color,
|
|
|
|
stroke: '#666',
|
|
|
|
lineWidth: 0.6
|
2019-08-09 17:37:51 +08:00
|
|
|
};
|
|
|
|
n.cluster = clusterId;
|
|
|
|
n.importValue = 0;
|
|
|
|
n.exportValue = 0;
|
2019-08-08 19:33:56 +08:00
|
|
|
});
|
2019-08-09 17:37:51 +08:00
|
|
|
// map the value of
|
|
|
|
edges.forEach(e => {
|
2019-11-01 19:07:27 +08:00
|
|
|
e.id = e.eid;
|
2019-08-09 17:37:51 +08:00
|
|
|
if (e.value === '') e.value = 0;
|
|
|
|
const v = parseFloat(e.value);
|
|
|
|
nodeMap.get(e.source).exportValue += v;
|
2019-10-08 14:39:45 +08:00
|
|
|
nodeMap.get(e.target).importValue += v;
|
2019-08-09 17:37:51 +08:00
|
|
|
});
|
|
|
|
mapValueToProp(nodes, 'exportValue', 'size', [2, 12]);
|
2019-08-08 19:33:56 +08:00
|
|
|
const graph = new G6.Graph({
|
|
|
|
container: 'mountNode',
|
2019-09-29 12:11:28 +08:00
|
|
|
width: 800,
|
|
|
|
height: 600,
|
2019-09-25 17:45:18 +08:00
|
|
|
layout: {
|
|
|
|
type: 'fruchterman',
|
|
|
|
maxIteration: 8000,
|
|
|
|
gravity: 10,
|
2019-09-29 12:11:28 +08:00
|
|
|
clustering: true,
|
2019-11-20 12:26:51 +08:00
|
|
|
clusterGravity: 30,
|
|
|
|
workerEnabled: true
|
2019-09-25 17:45:18 +08:00
|
|
|
},
|
2019-11-20 12:26:51 +08:00
|
|
|
padding: 0,
|
|
|
|
fitViewPadding: 0,
|
2019-08-08 19:33:56 +08:00
|
|
|
fitView: true,
|
2019-08-09 17:37:51 +08:00
|
|
|
linkCenter: true,
|
2019-08-08 19:33:56 +08:00
|
|
|
defaultNode: {
|
|
|
|
shape: 'circle',
|
2019-08-09 17:37:51 +08:00
|
|
|
size: 5
|
|
|
|
},
|
|
|
|
defaultEdge: {
|
|
|
|
shape: 'quadratic'
|
2019-08-08 19:33:56 +08:00
|
|
|
},
|
|
|
|
modes: {
|
2019-11-20 12:26:51 +08:00
|
|
|
default: [ 'drag-node', 'zoom-canvas', 'drag-canvas', {
|
2019-08-08 19:33:56 +08:00
|
|
|
type: 'tooltip',
|
|
|
|
formatText(model) {
|
2019-08-09 17:37:51 +08:00
|
|
|
let name = '';
|
|
|
|
let countries = '';
|
2019-10-08 14:39:45 +08:00
|
|
|
if (model.name) name = model.name + '<br/>';
|
|
|
|
if (model.countries) countries = '<br/>Number of Countries: ' + model.countries;
|
2019-08-09 17:37:51 +08:00
|
|
|
const text = name
|
|
|
|
+ 'Export Value: ' + model.exportValue + "(1000USD)"
|
2019-10-08 14:39:45 +08:00
|
|
|
+ '<br/>Region: ' + model.region
|
|
|
|
+ '<br/>Cluster: ' + model.cluster
|
2019-08-09 17:37:51 +08:00
|
|
|
+ countries;
|
2019-08-08 19:33:56 +08:00
|
|
|
return text;
|
|
|
|
},
|
|
|
|
shouldUpdate: e => {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}]
|
|
|
|
},
|
|
|
|
});
|
2019-09-29 12:11:28 +08:00
|
|
|
|
2019-11-20 12:26:51 +08:00
|
|
|
graph.on('beforelayout', () => {
|
|
|
|
console.log(data.nodes[0].x, data.nodes[0].y, data.nodes[10].x, data.nodes[10].y);
|
|
|
|
});
|
2019-09-29 12:11:28 +08:00
|
|
|
graph.on('afterlayout', () => {
|
|
|
|
const tipDiv = document.getElementById('tip');
|
|
|
|
tipDiv.innerHTML = '布局完成!';
|
2019-08-09 17:37:51 +08:00
|
|
|
});
|
2019-10-08 14:39:45 +08:00
|
|
|
|
2019-08-08 19:33:56 +08:00
|
|
|
graph.data(data);
|
|
|
|
graph.render();
|
|
|
|
|
2019-10-08 14:39:45 +08:00
|
|
|
const edgeItems = graph.getEdges();
|
|
|
|
edgeItems.forEach(e => {
|
|
|
|
const lineWidth = 0.4;
|
|
|
|
const strokeOpacity = 0.2;
|
|
|
|
let stroke = 'l(0) 0:' + beginColor + ' 1:' + endColor;
|
|
|
|
const sourceModel = e.getSource().getModel();
|
|
|
|
const targetModel = e.getTarget().getModel();
|
|
|
|
if (sourceModel.x > targetModel.x) {
|
|
|
|
stroke = 'l(0) 0:' + endColor + ' 1:' + beginColor;
|
2019-08-09 17:37:51 +08:00
|
|
|
}
|
2019-10-08 14:39:45 +08:00
|
|
|
e.update({
|
2019-08-09 17:37:51 +08:00
|
|
|
style: {
|
2019-10-08 14:39:45 +08:00
|
|
|
lineWidth,
|
|
|
|
strokeOpacity,
|
|
|
|
stroke
|
2019-08-09 17:37:51 +08:00
|
|
|
}
|
2019-10-08 14:39:45 +08:00
|
|
|
})
|
2019-08-09 17:37:51 +08:00
|
|
|
});
|
2019-10-08 14:39:45 +08:00
|
|
|
graph.paint();
|
2019-08-09 17:37:51 +08:00
|
|
|
|
2019-10-08 14:39:45 +08:00
|
|
|
graph.on('node:click', e => {
|
|
|
|
const targetItem = e.item;
|
|
|
|
const model = targetItem.getModel();
|
|
|
|
const graphEdges = graph.getEdges();
|
|
|
|
const graphNodes = graph.getNodes();
|
|
|
|
// click on the cluster node
|
|
|
|
if (model.id.substr(0, 7) === 'cluster') {
|
|
|
|
graphNodes.forEach(gn => {
|
|
|
|
const gnModel = gn.getModel();
|
|
|
|
// show the common nodes
|
|
|
|
if (gnModel.cluster === model.cluster && gnModel.id.substr(0, 7) != 'cluster') {
|
|
|
|
gn.show();
|
2019-08-09 17:37:51 +08:00
|
|
|
}
|
2019-10-08 14:39:45 +08:00
|
|
|
// remove the cluster nodes
|
|
|
|
if (gnModel.id === model.id) graph.removeItem(gn);
|
2019-08-09 17:37:51 +08:00
|
|
|
});
|
2019-10-08 14:39:45 +08:00
|
|
|
|
|
|
|
graphEdges.forEach(ge => {
|
2019-11-01 19:07:27 +08:00
|
|
|
const edgeModel = ge.getModel();
|
2019-10-08 14:39:45 +08:00
|
|
|
const sourceModel = ge.get('sourceNode').getModel();
|
|
|
|
const targetModel = ge.get('targetNode').getModel();
|
|
|
|
// show the common edges
|
|
|
|
if ((sourceModel.cluster === model.cluster && sourceModel.id.substr(0, 7) !== 'cluster')
|
|
|
|
|| (targetModel.cluster === model.cluster && targetModel.id.substr(0, 7) !== 'cluster')) {
|
|
|
|
ge.show();
|
|
|
|
// add the edges to other cluster nodes
|
|
|
|
if (!ge.get('sourceNode').get('visible') && sourceModel.cluster !== model.cluster) {
|
|
|
|
let c1 = beginColor, c2 = endColor;
|
|
|
|
if (model.x > targetModel.x) {
|
|
|
|
c1 = endColor;
|
|
|
|
c2 = beginColor;
|
|
|
|
}
|
|
|
|
graph.addItem('edge', {
|
|
|
|
source: 'cluster' + sourceModel.cluster,
|
|
|
|
target: targetModel.id,
|
2019-11-01 19:07:27 +08:00
|
|
|
id: 'cluster-edge-' + edgeModel.id,
|
2019-10-08 14:39:45 +08:00
|
|
|
style: {
|
|
|
|
stroke: 'l(0) 0:' + c1 + ' 1:' + c2,
|
|
|
|
lineWidth: 0.4,
|
|
|
|
strokeOpacity: 0.2
|
|
|
|
}
|
|
|
|
});
|
2019-11-01 19:07:27 +08:00
|
|
|
} else if (!ge.get('targetNode').get('visible') && targetModel.cluster !== model.cluster) {
|
2019-10-08 14:39:45 +08:00
|
|
|
let c1 = beginColor, c2 = endColor;
|
2019-10-08 15:48:49 +08:00
|
|
|
if (sourceModel.x > model.x) {
|
2019-10-08 14:39:45 +08:00
|
|
|
c1 = endColor;
|
|
|
|
c2 = beginColor;
|
|
|
|
}
|
|
|
|
graph.addItem('edge', {
|
|
|
|
source: sourceModel.id,
|
2019-11-01 19:07:27 +08:00
|
|
|
target: 'cluster' + targetModel.cluster,
|
|
|
|
id: 'cluster-edge-' + edgeModel.id,
|
2019-10-08 14:39:45 +08:00
|
|
|
style: {
|
|
|
|
stroke: 'l(0) 0:' + c1 + ' 1:' + c2,
|
|
|
|
lineWidth: 0.4,
|
|
|
|
strokeOpacity: 0.2
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// hide the edges to the common nodes in other clusters
|
|
|
|
if (!ge.get('sourceNode').get('visible') || !ge.get('targetNode').get('visible')) {
|
|
|
|
ge.hide();
|
|
|
|
}
|
|
|
|
}
|
2019-11-01 19:07:27 +08:00
|
|
|
//do not need the following 4 lines, since the related edges will be removed while the cluster node is removing
|
|
|
|
// // remove the cluster edges.
|
|
|
|
// if (sourceModel.id === model.id || targetModel.id === model.id) {
|
|
|
|
// graph.removeItem(ge);
|
|
|
|
// }
|
2019-10-08 14:39:45 +08:00
|
|
|
});
|
|
|
|
} else { // click on the common node, cllapse them
|
|
|
|
// calculate the cluster center
|
|
|
|
const center = {x: 0, y: 0, count: 0, exportValue: 0};
|
|
|
|
nodes.forEach(n => {
|
|
|
|
if (n.cluster === model.cluster) {
|
|
|
|
center.x += n.x;
|
|
|
|
center.y += n.y;
|
|
|
|
center.count++;
|
|
|
|
center.exportValue += n.exportValue;
|
2019-08-09 17:37:51 +08:00
|
|
|
}
|
2019-10-08 14:39:45 +08:00
|
|
|
});
|
|
|
|
center.x /= center.count;
|
|
|
|
center.y /= center.count;
|
|
|
|
// add cluster node on the center
|
|
|
|
const size = center.count * 1;
|
|
|
|
const clusterNodeId = 'cluster' + model.cluster;
|
|
|
|
const color = colors[ model.cluster % colors.length ];
|
|
|
|
const regionStrs = model.region.split(' ');
|
|
|
|
let region = regionStrs[regionStrs.length - 1];
|
|
|
|
if (model.region === 'East Asia') region = model.region;
|
|
|
|
let labelPosition = 'center';
|
|
|
|
if (region.length > size) labelPosition = 'left';
|
|
|
|
const clusterNode = graph.addItem('node', {
|
|
|
|
x: center.x,
|
|
|
|
y: center.y,
|
|
|
|
id: clusterNodeId,
|
|
|
|
cluster: model.cluster,
|
|
|
|
region: region,
|
|
|
|
countries: center.count,
|
|
|
|
exportValue: center.exportValue,
|
2019-08-09 17:37:51 +08:00
|
|
|
style: {
|
2019-10-08 14:39:45 +08:00
|
|
|
color,
|
|
|
|
fill: color,
|
|
|
|
stroke: '#666',
|
|
|
|
lineWidth: 0.6
|
|
|
|
},
|
|
|
|
size,
|
|
|
|
label: region,
|
|
|
|
labelCfg: {
|
|
|
|
style: { fontSize: 8.5 },
|
|
|
|
position: labelPosition
|
2019-08-09 17:37:51 +08:00
|
|
|
}
|
2019-10-08 14:39:45 +08:00
|
|
|
});
|
2019-08-08 19:33:56 +08:00
|
|
|
|
2019-10-08 14:39:45 +08:00
|
|
|
// add edges about the cluster
|
|
|
|
graphEdges.forEach(ge => {
|
2019-11-01 19:07:27 +08:00
|
|
|
const edgeModel = ge.getModel();
|
2019-10-08 14:39:45 +08:00
|
|
|
const sourceModel = ge.get('sourceNode').getModel();
|
|
|
|
const targetModel = ge.get('targetNode').getModel();
|
|
|
|
if (!ge.get('sourceNode').get('visible') || !ge.get('targetNode').get('visible')) return;
|
|
|
|
if (sourceModel.cluster === model.cluster && targetModel.cluster !== model.cluster) {
|
|
|
|
let c1 = beginColor, c2 = endColor;
|
|
|
|
if (center.x > targetModel.x) {
|
|
|
|
c1 = endColor;
|
|
|
|
c2 = beginColor;
|
|
|
|
}
|
|
|
|
graph.addItem('edge', {
|
|
|
|
source: clusterNodeId,
|
|
|
|
target: targetModel.id,
|
2019-11-01 19:07:27 +08:00
|
|
|
id: 'cluster-edge-' + edgeModel.id,
|
2019-10-08 14:39:45 +08:00
|
|
|
style: {
|
|
|
|
stroke: 'l(0) 0:' + c1 + ' 1:' + c2,
|
|
|
|
lineWidth: 0.4,
|
|
|
|
strokeOpacity: 0.2
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else if (targetModel.cluster === model.cluster && sourceModel.cluster !== model.cluster) {
|
|
|
|
let c1 = beginColor, c2 = endColor;
|
2019-10-08 15:48:49 +08:00
|
|
|
if (sourceModel.x > center.x) {
|
2019-10-08 14:39:45 +08:00
|
|
|
c1 = endColor;
|
|
|
|
c2 = beginColor;
|
|
|
|
}
|
|
|
|
graph.addItem('edge', {
|
|
|
|
source: sourceModel.id,
|
|
|
|
target: clusterNodeId,
|
|
|
|
id: 'cluster-edge-' + ge.id,
|
|
|
|
style: {
|
|
|
|
stroke: 'l(0) 0:' + c1 + ' 1:' + c2,
|
|
|
|
lineWidth: 0.4,
|
|
|
|
strokeOpacity: 0.2
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// hide the common nodes in the cluster
|
|
|
|
graphNodes.forEach(gn => {
|
|
|
|
if (gn.getModel().cluster === model.cluster
|
|
|
|
&& gn.getModel().id.substr(0, 7) !== 'cluster') gn.hide();
|
|
|
|
});
|
|
|
|
// hide the common edges about cluster
|
|
|
|
graphEdges.forEach(ge => {
|
|
|
|
if (!ge.get('sourceNode').get('visible') || !ge.get('targetNode').get('visible')) ge.hide();
|
|
|
|
});
|
|
|
|
}
|
2019-08-09 17:37:51 +08:00
|
|
|
});
|
2019-08-08 19:33:56 +08:00
|
|
|
});
|
2019-08-09 17:37:51 +08:00
|
|
|
function mapValueToProp(items, valueName, propName, range){
|
|
|
|
const valueRange = [9999999999, -9999999999];
|
|
|
|
items.forEach(n => {
|
|
|
|
if (n[valueName] > valueRange[1]) valueRange[1] = n[valueName];
|
|
|
|
if (n[valueName] < valueRange[0]) valueRange[0] = n[valueName];
|
|
|
|
});
|
|
|
|
const valueLength = valueRange[1] - valueRange[0];
|
|
|
|
const rLength = range[1] - range[0];
|
|
|
|
const propNameStrs = propName.split('.');
|
|
|
|
if (propNameStrs[0] === 'style' && propNameStrs.length > 1) {
|
|
|
|
items.forEach(n => {
|
|
|
|
if (n.style === undefined) n.style = {};
|
|
|
|
n.style[propNameStrs[1]] = rLength * (n[valueName] - valueRange[0]) / valueLength + range[0];
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
items.forEach(n => {
|
|
|
|
n[propNameStrs[0]] = rLength * (n[valueName] - valueRange[0]) / valueLength + range[0];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2019-08-08 19:33:56 +08:00
|
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|