bug: loop in edge bundling. test: radial layout correct

This commit is contained in:
shiwu.wyy 2019-08-08 20:13:50 +08:00
parent 350fffac69
commit 497751f83f
7 changed files with 669 additions and 37 deletions

592
demos/test.html Normal file
View File

@ -0,0 +1,592 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.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;
}
</style>
</head>
<body>
<div id="mountNode"></div>
<script src="../build/g6.js"></script>
<script src="../build/radial.js"></script>
<script src="./assets/d3-4.13.0.min.js"></script>
<script>
const data = {
"nodes": [{
"id": "Argentina",
"name": "Argentina"
}, {
"id": "Australia",
"name": "Australia"
}, {
"id": "Belgium",
"name": "Belgium"
}, {
"id": "Brazil",
"name": "Brazil"
}, {
"id": "Colombia",
"name": "Colombia"
}, {
"id": "Costa Rica",
"name": "Costa Rica"
}, {
"id": "Croatia",
"name": "Croatia"
}, {
"id": "Denmark",
"name": "Denmark"
}, {
"id": "Egypt",
"name": "Egypt"
}, {
"id": "England",
"name": "England"
}, {
"id": "France",
"name": "France"
}, {
"id": "Germany",
"name": "Germany"
}, {
"id": "Iceland",
"name": "Iceland"
}, {
"id": "IR Iran",
"name": "IR Iran"
}, {
"id": "Japan",
"name": "Japan"
}, {
"id": "Korea Republic",
"name": "Korea Republic"
}, {
"id": "Mexico",
"name": "Mexico"
}, {
"id": "Morocco",
"name": "Morocco"
}, {
"id": "Nigeria",
"name": "Nigeria"
}, {
"id": "Panama",
"name": "Panama"
}, {
"id": "Peru",
"name": "Peru"
}, {
"id": "Poland",
"name": "Poland"
}, {
"id": "Portugal",
"name": "Portugal"
}, {
"id": "Russia",
"name": "Russia"
}, {
"id": "Saudi Arabia",
"name": "Saudi Arabia"
}, {
"id": "Senegal",
"name": "Senegal"
}, {
"id": "Serbia",
"name": "Serbia"
}, {
"id": "Spain",
"name": "Spain"
}, {
"id": "Sweden",
"name": "Sweden"
}, {
"id": "Switzerland",
"name": "Switzerland"
}, {
"id": "Tunisia",
"name": "Tunisia"
}, {
"id": "Uruguay",
"name": "Uruguay"
}],
"edges": [{
"id": "0",
"target": "Russia",
"source": "Saudi Arabia",
"target_score": 5,
"source_score": 0,
"directed": true
}, {
"id": "1",
"target": "Uruguay",
"source": "Egypt",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "2",
"target": "Russia",
"source": "Egypt",
"target_score": 3,
"source_score": 1,
"directed": true
}, {
"id": "3",
"target": "Uruguay",
"source": "Saudi Arabia",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "4",
"target": "Uruguay",
"source": "Russia",
"target_score": 3,
"source_score": 0,
"directed": true
}, {
"id": "5",
"target": "Saudi Arabia",
"source": "Egypt",
"target_score": 2,
"source_score": 1,
"directed": true
}, {
"id": "6",
"target": "IR Iran",
"source": "Morocco",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "7",
"target": "Portugal",
"source": "Spain",
"target_score": 3,
"source_score": 3,
"directed": false
}, {
"id": "8",
"target": "Portugal",
"source": "Morocco",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "9",
"target": "Spain",
"source": "IR Iran",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "10",
"target": "IR Iran",
"source": "Portugal",
"target_score": 1,
"source_score": 1,
"directed": false
}, {
"id": "11",
"target": "Spain",
"source": "Morocco",
"target_score": 2,
"source_score": 2,
"directed": false
}, {
"id": "12",
"target": "France",
"source": "Australia",
"target_score": 2,
"source_score": 1,
"directed": true
}, {
"id": "13",
"target": "Denmark",
"source": "Peru",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "14",
"target": "Denmark",
"source": "Australia",
"target_score": 1,
"source_score": 1,
"directed": false
}, {
"id": "15",
"target": "France",
"source": "Peru",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "16",
"target": "Denmark",
"source": "France",
"target_score": 0,
"source_score": 0,
"directed": false
}, {
"id": "17",
"target": "Peru",
"source": "Australia",
"target_score": 2,
"source_score": 0,
"directed": true
}, {
"id": "18",
"target": "Argentina",
"source": "Iceland",
"target_score": 1,
"source_score": 1
}, {
"id": "19",
"target": "Croatia",
"source": "Nigeria",
"target_score": 2,
"source_score": 0,
"directed": true
}, {
"id": "20",
"target": "Croatia",
"source": "Argentina",
"target_score": 3,
"source_score": 0,
"directed": true
}, {
"id": "21",
"target": "Nigeria",
"source": "Iceland",
"target_score": 2,
"source_score": 0,
"directed": true
}, {
"id": "22",
"target": "Argentina",
"source": "Nigeria",
"target_score": 2,
"source_score": 1,
"directed": true
}, {
"id": "23",
"target": "Croatia",
"source": "Iceland",
"target_score": 2,
"source_score": 1,
"directed": true
}, {
"id": "24",
"target": "Serbia",
"source": "Costa Rica",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "25",
"target": "Brazil",
"source": "Switzerland",
"target_score": 1,
"source_score": 1,
"directed": false
}, {
"id": "26",
"target": "Brazil",
"source": "Costa Rica",
"target_score": 2,
"source_score": 0,
"directed": true
}, {
"id": "27",
"target": "Switzerland",
"source": "Serbia",
"target_score": 2,
"source_score": 1,
"directed": true
}, {
"id": "28",
"target": "Brazil",
"source": "Serbia",
"target_score": 2,
"source_score": 0,
"directed": true
}, {
"id": "29",
"target": "Switzerland",
"source": "Costa Rica",
"target_score": 2,
"source_score": 2,
"directed": false
}, {
"id": "30",
"target": "Mexico",
"source": "Germany",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "31",
"target": "Sweden",
"source": "Korea Republic",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "32",
"target": "Mexico",
"source": "Korea Republic",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "33",
"target": "Germany",
"source": "Sweden",
"target_score": 2,
"source_score": 1,
"directed": true
}, {
"id": "34",
"target": "Korea Republic",
"source": "Germany",
"target_score": 2,
"source_score": 0,
"directed": true
}, {
"id": "35",
"target": "Sweden",
"source": "Mexico",
"target_score": 3,
"source_score": 0,
"directed": true
}, {
"id": "36",
"target": "Belgium",
"source": "Panama",
"target_score": 3,
"source_score": 0,
"directed": true
}, {
"id": "37",
"target": "England",
"source": "Tunisia",
"target_score": 2,
"source_score": 1,
"directed": true
}, {
"id": "38",
"target": "Belgium",
"source": "Tunisia",
"target_score": 5,
"source_score": 2,
"directed": true
}, {
"id": "39",
"target": "England",
"source": "Panama",
"target_score": 6,
"source_score": 1,
"directed": true
}, {
"id": "40",
"target": "Belgium",
"source": "England",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "41",
"target": "Tunisia",
"source": "Panama",
"target_score": 2,
"source_score": 1,
"directed": true
}, {
"id": "42",
"target": "Japan",
"source": "Colombia",
"target_score": 2,
"source_score": 1,
"directed": true
}, {
"id": "43",
"target": "Senegal",
"source": "Poland",
"target_score": 2,
"source_score": 1,
"directed": true
}, {
"id": "44",
"target": "Japan",
"source": "Senegal",
"target_score": 2,
"source_score": 2,
"directed": false
}, {
"id": "45",
"target": "Colombia",
"source": "Poland",
"target_score": 3,
"source_score": 0,
"directed": true
}, {
"id": "46",
"target": "Poland",
"source": "Japan",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "47",
"target": "Colombia",
"source": "Senegal",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "48",
"target": "Uruguay",
"source": "Portugal",
"target_score": 2,
"source_score": 1,
"directed": true
}, {
"id": "49",
"target": "France",
"source": "Argentina",
"target_score": 4,
"source_score": 3,
"directed": true
}, {
"id": "50",
"target": "Russia",
"source": "Spain",
"target_score": 5,
"source_score": 4,
"directed": true
}, {
"id": "51",
"target": "Croatia",
"source": "Denmark",
"target_score": 4,
"source_score": 3,
"directed": true
}, {
"id": "52",
"target": "Brazil",
"source": "Mexico",
"target_score": 2,
"source_score": 0,
"directed": true
}, {
"id": "53",
"target": "Belgium",
"source": "Japan",
"target_score": 3,
"source_score": 2,
"directed": true
}, {
"id": "54",
"target": "Sweden",
"source": "Switzerland",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "55",
"target": "England",
"source": "Colombia",
"target_score": 4,
"source_score": 3,
"directed": true
}, {
"id": "56",
"target": "France",
"source": "Uruguay",
"target_score": 2,
"source_score": 0,
"directed": true
}, {
"id": "57",
"target": "Belgium",
"source": "Brazil",
"target_score": 2,
"source_score": 1,
"directed": true
}, {
"id": "58",
"target": "Croatia",
"source": "Russia",
"target_score": 6,
"source_score": 5,
"directed": true
}, {
"id": "59",
"target": "England",
"source": "Sweden",
"target_score": 2,
"source_score": 0,
"directed": true
}, {
"id": "60",
"target": "France",
"source": "Belgium",
"target_score": 1,
"source_score": 0,
"directed": true
}, {
"id": "61",
"target": "Croatia",
"source": "England",
"target_score": 2,
"source_score": 1,
"directed": true
}, {
"id": "62",
"target": "Belgium",
"source": "England",
"target_score": 2,
"source_score": 0,
"directed": true
}, {
"id": "63",
"target": "France",
"source": "Croatia",
"target_score": 4,
"source_score": 2,
"directed": true
}]
};
const radial = new Radial({
center: [ 250, 250 ],
focusNode: 'Belgium'
});
let focusNodeIndex = -1;
data.nodes.forEach((node, i) => {
if (node.id === 'Belgium') focusNodeIndex = i;
return;
});
const graph = new G6.Graph({
container: mountNode,
width: 500,
height: 500
});
radial.initPlugin(graph);
radial.layout(data);
console.log(data);
graph.data(data);
graph.render();
</script>
</body>
</html>

View File

@ -87,6 +87,7 @@ class Bundling extends Base {
for (let j = 0; j < iterations; j++) {
const forces = [];
edges.forEach((e, k) => {
if (e.source === e.target) return;
const source = nodeIdMap.get(e.source);
const target = nodeIdMap.get(e.target);
forces[k] = self.getEdgeForces({ source, target }, k, divisions, lambda);
@ -106,6 +107,7 @@ class Bundling extends Base {
// change the edges according to edgePoints
edges.forEach((e, i) => {
if (e.source === e.target) return;
e.shape = 'polyline';
e.controlPoints = edgePoints[i].slice(1, edgePoints[i].length - 1);
});
@ -159,9 +161,7 @@ class Bundling extends Base {
edgePoints[i].forEach((ep, j) => {
if (j === 0) return;
let oriDivisionLength = getEucliDis(ep, edgePoints[i][j - 1]);
// let count = 0;
while (oriDivisionLength > currentDivisonLength) {
// count++;
const ratio = currentDivisonLength / oriDivisionLength;
const edgePoint = { x: edgePoints[i][j - 1].x, y: edgePoints[i][j - 1].y };
edgePoint.x += ratio * (ep.x - edgePoints[i][j - 1].x);
@ -170,7 +170,6 @@ class Bundling extends Base {
oriDivisionLength -= currentDivisonLength;
currentDivisonLength = divisionLength;
}
// console.log('push count', count, divisions);
currentDivisonLength -= oriDivisionLength;
});
newEdgePoints.push({ x: target.x, y: target.y }); // target

View File

@ -49,18 +49,26 @@ describe('edge bundling', () => {
});
it('bundling update', () => {
const data2 = {
nodes: [
{ id: '0' }, { id: '1' }
],
edges: [
{ source: '0', target: '1' }
]
};
const circularLayout = new Circular({ center: [ 250, 250 ] });
circularLayout.initPlugin(graph);
circularLayout.layout(data);
circularLayout.layout(data2);
const bundle = new Bundling();
bundle.initPlugin(graph);
bundle.bundling(data);
bundle.bundling(data2);
data.nodes = [
data2.nodes = [
{ id: '0', x: 10, y: 100 }, { id: '1', x: 100, y: 100 }, { id: '2', x: 10, y: 10 }
];
data.edges = [
data2.edges = [
{ source: '0', target: '1' },
{ source: '1', target: '2' },
{ source: '0', target: '2' }
@ -69,11 +77,11 @@ describe('edge bundling', () => {
bundle.updateBundling({
bundleThreshold: 0.1,
iterations: 120,
data
data: data2
});
expect(data.edges[0].shape).to.equal('polyline');
expect(data.edges[0].controlPoints.length > 2).to.equal(true);
expect(data2.edges[0].shape).to.equal('polyline');
expect(data2.edges[0].controlPoints.length > 2).to.equal(true);
});
it('bundling no position info, throw error', () => {

View File

@ -45,26 +45,34 @@ describe('circular layout', () => {
});
it('circular update', () => {
const data2 = {
nodes: [
{ id: '0' }, { id: '1' }
],
edges: [
{ source: '0', target: '1' }
]
};
const circularLayout = new Circular({
center: [ 250, 250 ],
radius: 200
});
circularLayout.initPlugin(graph);
circularLayout.layout(data);
data.nodes = [
circularLayout.layout(data2);
data2.nodes = [
{ id: '0' }, { id: '1' }, { id: '2' }
];
data.edges = [
data2.edges = [
{ source: '0', target: '1' },
{ source: '1', target: '2' },
{ source: '0', target: '2' }
];
circularLayout.updateLayout({ center: [ 100, 150 ], data, radius: null, startRadius: 10, endRadius: 100 });
expect(mathEqual(data.nodes[0].x, 110)).to.equal(true);
expect(mathEqual(data.nodes[0].y, 150)).to.equal(true);
const n = data.nodes.length;
const vx = data.nodes[n - 1].x - 100;
const vy = data.nodes[n - 1].y - 150;
circularLayout.updateLayout({ center: [ 100, 150 ], data: data2, radius: null, startRadius: 10, endRadius: 100 });
expect(mathEqual(data2.nodes[0].x, 110)).to.equal(true);
expect(mathEqual(data2.nodes[0].y, 150)).to.equal(true);
const n = data2.nodes.length;
const vx = data2.nodes[n - 1].x - 100;
const vy = data2.nodes[n - 1].y - 150;
const distToFocus = Math.sqrt(vx * vx + vy * vy);
expect(mathEqual(distToFocus, 100)).to.equal(true);
});

View File

@ -36,21 +36,29 @@ describe('fruchterman layout', () => {
});
it('fruch update cfg and data', done => {
const data2 = {
nodes: [
{ id: '0' }, { id: '1' }
],
edges: [
{ source: '0', target: '1' }
]
};
const fruch = new Fruchterman({
center: [ 250, 250 ]
});
fruch.initPlugin(graph);
fruch.layout(data);
data.nodes = [
fruch.layout(data2);
data2.nodes = [
{ id: '0' }, { id: '1' }, { id: '2' }
];
data.edges = [
data2.edges = [
{ source: '0', target: '1' },
{ source: '1', target: '2' },
{ source: '0', target: '2' }
];
fruch.updateLayout({ gravity: 100, data });
expect(data.nodes[0].x != null).to.equal(true);
fruch.updateLayout({ gravity: 100, data: data2 });
expect(data2.nodes[0].x != null).to.equal(true);
done();
});
});

View File

@ -35,22 +35,30 @@ describe('mds layout', () => {
});
it('mds update cfg and data', done => {
const data2 = {
nodes: [
{ id: '0' }, { id: '1' }
],
edges: [
{ source: '0', target: '1' }
]
};
const mds = new MDS({
center: [ 250, 250 ],
linkDistance: 250
});
mds.initPlugin(graph);
mds.layout(data);
data.nodes = [
mds.layout(data2);
data2.nodes = [
{ id: '0' }, { id: '1' }, { id: '2' }
];
data.edges = [
data2.edges = [
{ source: '0', target: '1' },
{ source: '1', target: '2' },
{ source: '0', target: '2' }
];
mds.updateLayout({ gravity: 100, data });
expect(data.nodes[0].x != null).to.equal(true);
mds.updateLayout({ gravity: 100, data: data2 });
expect(data2.nodes[0].x != null).to.equal(true);
done();
});
});

View File

@ -61,32 +61,41 @@ describe('radial layout', () => {
return;
});
radial.initPlugin(graph);
radial.layout(data);
expect(mathEqual(data.nodes[focusNodeIndex].x, 250)).to.equal(true);
expect(mathEqual(data.nodes[focusNodeIndex].y, 250)).to.equal(true);
done();
});
it('radial update cfg and data', done => {
const data2 = {
nodes: [
{ id: '0' }, { id: '1' }
],
edges: [
{ source: '0', target: '1' }
]
};
const radial = new Radial({
center: [ 250, 250 ],
maxIteration: 120
});
radial.initPlugin(graph);
radial.layout(data);
data.nodes = [
radial.layout(data2);
data2.nodes = [
{ id: '0' }, { id: '1' }, { id: '2' }
];
data.edges = [
data2.edges = [
{ source: '0', target: '1' },
{ source: '1', target: '2' },
{ source: '0', target: '2' }
];
const newUnitRadius = 80;
radial.updateLayout({ center: [ 100, 150 ], unitRadius: newUnitRadius, linkDistance: 70, focusNode: data.nodes[1], data });
expect(mathEqual(data.nodes[1].x, 100)).to.equal(true);
expect(mathEqual(data.nodes[1].y, 150)).to.equal(true);
const vx = data.nodes[2].x - data.nodes[1].x;
const vy = data.nodes[2].y - data.nodes[1].y;
radial.updateLayout({ center: [ 100, 150 ], unitRadius: newUnitRadius, linkDistance: 70, focusNode: data2.nodes[1], data: data2 });
expect(mathEqual(data2.nodes[1].x, 100)).to.equal(true);
expect(mathEqual(data2.nodes[1].y, 150)).to.equal(true);
const vx = data2.nodes[2].x - data2.nodes[1].x;
const vy = data2.nodes[2].y - data2.nodes[1].y;
const distToFocus = Math.sqrt(vx * vx + vy * vy);
expect(mathEqual(distToFocus % newUnitRadius - newUnitRadius, 0)
|| mathEqual(distToFocus % newUnitRadius, 0)).to.equal(true);