mirror of
https://gitee.com/antv/g6.git
synced 2024-12-05 05:09:07 +08:00
Merge branch 'radiallayout' into plugins180708
This commit is contained in:
commit
be45c94f2c
@ -104,6 +104,7 @@
|
||||
|
||||
const lineDash = [4, 2, 1, 2];
|
||||
const interval = 9;
|
||||
debugger
|
||||
G6.registerEdge('line-dash', {
|
||||
afterDraw(cfg, group) {
|
||||
const shape = group.get('children')[0];
|
||||
|
3451
demos/radial-interact-layout.html
Normal file
3451
demos/radial-interact-layout.html
Normal file
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@ -35,6 +35,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@antv/hierarchy": "~0.4.0",
|
||||
"@babel/cli": "^7.0.0",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
@ -60,10 +61,12 @@
|
||||
"eslint-plugin-react": "~7.1.0",
|
||||
"event-simulate": "~1.0.0",
|
||||
"get-port": "~3.2.0",
|
||||
"hard-source-webpack-plugin": "~0.13.1",
|
||||
"home": "~1.0.1",
|
||||
"jquery": "^3.3.1",
|
||||
"jszip": "~3.1.5",
|
||||
"nightmare": "~2.10.0",
|
||||
"numericjs": "^1.2.6",
|
||||
"nunjucks": "~3.0.1",
|
||||
"open": "~0.0.5",
|
||||
"parseurl": "~1.3.2",
|
||||
@ -76,9 +79,7 @@
|
||||
"uglify-js": "~3.1.10",
|
||||
"webpack": "~4.10.2",
|
||||
"webpack-cli": "~3.0.0",
|
||||
"worker-loader": "^2.0.0",
|
||||
"@antv/hierarchy": "~0.4.0",
|
||||
"hard-source-webpack-plugin": "~0.13.1"
|
||||
"worker-loader": "^2.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"update": "rm -rf node_modules/ && tnpm i && rm -rf build && tnpm run build",
|
||||
@ -99,7 +100,7 @@
|
||||
"screenshot": "node ./bin/screenshot.js",
|
||||
"start": "npm run dev",
|
||||
"test": "torch --compile --renderer --opts test/mocha.opts --recursive ./test/unit",
|
||||
"test-live": "torch --compile --interactive --watch --opts test/mocha.opts --recursive ./test/unit",
|
||||
"test-live": "torch --compile --interactive --watch --opts test/mocha.opts --recursive ./test/unit/",
|
||||
"test-bugs": "torch --compile --renderer --recursive ./test/bugs",
|
||||
"test-bugs-live": "torch --compile --interactive --watch --recursive ./test/bugs",
|
||||
"test-all": "npm run test && npm run test-bugs",
|
||||
@ -118,7 +119,8 @@
|
||||
"@antv/hierarchy": "~0.5.0",
|
||||
"@antv/util": "~1.3.1",
|
||||
"d3-force": "^2.0.1",
|
||||
"dagre": "^0.8.4"
|
||||
"dagre": "^0.8.4",
|
||||
"numeric": "^1.2.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9.0"
|
||||
|
@ -2,6 +2,7 @@ const G6Plugins = {
|
||||
Minimap: require('./minimap'),
|
||||
Grid: require('./grid'),
|
||||
Force: require('./force'),
|
||||
Radial: require('./radial'),
|
||||
Dagre: require('./dagre'),
|
||||
Menu: require('./menu')
|
||||
};
|
||||
|
282
plugins/radial/index.js
Normal file
282
plugins/radial/index.js
Normal file
@ -0,0 +1,282 @@
|
||||
// const Numeric = require('numericjs');
|
||||
const Base = require('../base');
|
||||
const Util = require('@antv/g6').Util;
|
||||
const MDS = require('./mds');
|
||||
|
||||
function getWeightMatrix(M) {
|
||||
const rows = M.length;
|
||||
const cols = M[0].length;
|
||||
const result = [];
|
||||
for (let i = 0; i < rows; i++) {
|
||||
const row = [];
|
||||
for (let j = 0; j < cols; j++) {
|
||||
if (M[i][j] !== 0) row.push(1 / Math.pow(M[i][j], 2));
|
||||
else row.push(0);
|
||||
}
|
||||
result.push(row);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getIndexById(array, id) {
|
||||
let index = -1;
|
||||
array.forEach((a, i) => {
|
||||
if (a.id === id) {
|
||||
index = i;
|
||||
return;
|
||||
}
|
||||
});
|
||||
return index;
|
||||
}
|
||||
|
||||
class Radial extends Base {
|
||||
getDefaultCfgs() {
|
||||
return {
|
||||
maxIteration: 1000, // 停止迭代的最大迭代数
|
||||
focusNode: null, // 中心点,默认为数据中第一个点
|
||||
center: [ 0, 0 ], // 布局中心
|
||||
unitRadius: null, // 默认边长度
|
||||
linkDistance: 50, // 默认边长度
|
||||
animate: true, // 插值动画效果变换节点位置
|
||||
nonOverlap: false, // 是否防止重叠
|
||||
nodeSize: 10, // 节点半径
|
||||
onLayoutEnd() {}, // 布局完成回调
|
||||
onTick() {} // 每一迭代布局回调
|
||||
};
|
||||
}
|
||||
init() {
|
||||
const graph = this.get('graph');
|
||||
const onTick = this.get('onTick');
|
||||
const tick = () => {
|
||||
onTick && onTick();
|
||||
graph.refreshPositions();
|
||||
|
||||
};
|
||||
this.set('tick', tick);
|
||||
}
|
||||
layout(data) {
|
||||
const self = this;
|
||||
self.set('data', data);
|
||||
const graph = self.get('graph');
|
||||
const nodes = data.nodes;
|
||||
const center = self.get('center');
|
||||
if (nodes.length === 0) {
|
||||
return;
|
||||
} else if (nodes.length === 1) {
|
||||
nodes[0].x = center[0];
|
||||
nodes[0].y = center[1];
|
||||
return;
|
||||
}
|
||||
const linkDistance = self.get('linkDistance');
|
||||
const unitRadius = self.get('unitRadius');
|
||||
|
||||
// 如果正在布局,忽略布局请求
|
||||
if (self.isTicking()) {
|
||||
return;
|
||||
}
|
||||
// layout
|
||||
let focusNode = self.get('focusNode');
|
||||
if (Util.isString(focusNode)) {
|
||||
let found = false;
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
if (nodes[i].id === focusNode) {
|
||||
focusNode = nodes[i];
|
||||
self.set('focusNode', focusNode);
|
||||
found = true;
|
||||
i = nodes.length;
|
||||
}
|
||||
}
|
||||
if (!found) focusNode = null;
|
||||
}
|
||||
// default focus node
|
||||
if (!focusNode) {
|
||||
focusNode = nodes[0];
|
||||
if (!focusNode) return;
|
||||
self.set('focusNode', focusNode);
|
||||
}
|
||||
// the graph-theoretic distance (shortest path distance) matrix
|
||||
const adjMatrix = Util.getAdjMatrix(data, false);
|
||||
const D = Util.floydWarshall(adjMatrix);
|
||||
self.set('distances', D);
|
||||
|
||||
// the index of the focusNode in data
|
||||
const focusIndex = getIndexById(nodes, focusNode.id);
|
||||
self.set('focusIndex', focusIndex);
|
||||
// the shortest path distance from each node to focusNode
|
||||
const focusNodeD = D[focusIndex];
|
||||
const width = graph.get('width');
|
||||
const height = graph.get('height');
|
||||
// the maxRadius of the graph
|
||||
const maxRadius = height > width ? width / 2 : height / 2;
|
||||
const maxD = Math.max(...focusNodeD);
|
||||
// the radius for each nodes away from focusNode
|
||||
const radii = [];
|
||||
focusNodeD.forEach((value, i) => {
|
||||
if (!unitRadius) radii[i] = value * maxRadius / maxD;
|
||||
else radii[i] = value * unitRadius;
|
||||
});
|
||||
self.set('radii', radii);
|
||||
|
||||
const eIdealD = self.eIdealDisMatrix(D, linkDistance, radii);
|
||||
// const eIdealD = scaleMatrix(D, linkDistance);
|
||||
self.set('eIdealDistances', eIdealD);
|
||||
// the weight matrix, Wij = 1 / dij^(-2)
|
||||
const W = getWeightMatrix(eIdealD);
|
||||
self.set('weights', W);
|
||||
|
||||
// the initial positions from mds
|
||||
const mds = new MDS({ distances: eIdealD, dimension: 2 });
|
||||
const positions = mds.layout();
|
||||
self.set('positions', positions);
|
||||
positions.forEach((p, i) => {
|
||||
nodes[i].x = p[0] + center[0];
|
||||
nodes[i].y = p[1] + center[1];
|
||||
});
|
||||
|
||||
// move the graph to origin, centered at focusNode
|
||||
positions.forEach(p => {
|
||||
p[0] -= positions[focusIndex][0];
|
||||
p[1] -= positions[focusIndex][1];
|
||||
});
|
||||
self.run();
|
||||
|
||||
const nonOverlap = self.get('nonOverlap');
|
||||
const nodeSize = self.get('nodeSize');
|
||||
// stagger the overlapped nodes
|
||||
if (nonOverlap) {
|
||||
let hasOverlaps = true;
|
||||
let iter = 0;
|
||||
const oMaxIter = 5;
|
||||
while (hasOverlaps && iter < oMaxIter) {
|
||||
hasOverlaps = false;
|
||||
iter++;
|
||||
positions.forEach((v, i) => {
|
||||
positions.forEach((u, j) => {
|
||||
if (i <= j) return;
|
||||
// u and j are in different circle, will not overlaps
|
||||
if (radii[i] !== radii[j]) return;
|
||||
// u has been moved
|
||||
const edis = Util.getEDistance(v, u);
|
||||
if (edis < nodeSize) { // overlapped
|
||||
hasOverlaps = true;
|
||||
// the vector focusNode -> u
|
||||
let vecx = u[0] - positions[focusIndex][0];
|
||||
let vecy = u[1] - positions[focusIndex][1];
|
||||
const length = Math.sqrt(vecx * vecx + vecy * vecy);
|
||||
vecx = (length + nodeSize) * vecx / length;
|
||||
vecy = (length + nodeSize) * vecy / length;
|
||||
u[0] = vecx + positions[focusIndex][0];
|
||||
u[1] = vecy + positions[focusIndex][1];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// move the graph to center
|
||||
positions.forEach((p, i) => {
|
||||
nodes[i].x = p[0] + center[0];
|
||||
nodes[i].y = p[1] + center[1];
|
||||
});
|
||||
graph.refreshPositions();
|
||||
const onLayoutEnd = self.get('onLayoutEnd');
|
||||
onLayoutEnd();
|
||||
}
|
||||
run() {
|
||||
const self = this;
|
||||
const maxIteration = self.get('maxIteration');
|
||||
const positions = self.get('positions');
|
||||
const W = self.get('weights');
|
||||
const eIdealDis = self.get('eIdealDistances');
|
||||
const radii = self.get('radii');
|
||||
for (let i = 0; i <= maxIteration; i++) {
|
||||
const param = i / maxIteration;
|
||||
self.oneIteration(param, positions, radii, eIdealDis, W);
|
||||
}
|
||||
}
|
||||
oneIteration(param, positions, radii, D, W) {
|
||||
const self = this;
|
||||
const vparam = 1 - param;
|
||||
const focusIndex = self.get('focusIndex');
|
||||
positions.forEach((v, i) => { // v
|
||||
const originDis = Util.getEDistance(v, [ 0, 0 ]);
|
||||
const reciODis = originDis === 0 ? 0 : 1 / originDis;
|
||||
if (i === focusIndex) return;
|
||||
let xMolecule = 0;
|
||||
let yMolecule = 0;
|
||||
let denominator = 0;
|
||||
positions.forEach((u, j) => { // u
|
||||
if (i === j) return;
|
||||
// if (j === focusIndex) return;
|
||||
// the euclidean distance between v and u
|
||||
const edis = Util.getEDistance(v, u);
|
||||
const reciEdis = edis === 0 ? 0 : 1 / edis;
|
||||
const idealDis = D[j][i];
|
||||
// same for x and y
|
||||
denominator += W[i][j];
|
||||
// x
|
||||
xMolecule += W[i][j] * (u[0] + idealDis * (v[0] - u[0]) * reciEdis);
|
||||
// y
|
||||
yMolecule += W[i][j] * (u[1] + idealDis * (v[1] - u[1]) * reciEdis);
|
||||
});
|
||||
const reciR = radii[i] === 0 ? 0 : 1 / radii[i];
|
||||
denominator *= vparam;
|
||||
denominator += param * Math.pow(reciR, 2);
|
||||
// x
|
||||
xMolecule *= vparam;
|
||||
xMolecule += param * reciR * v[0] * reciODis;
|
||||
v[0] = xMolecule / denominator;
|
||||
// y
|
||||
yMolecule *= vparam;
|
||||
yMolecule += param * reciR * v[1] * reciODis;
|
||||
v[1] = yMolecule / denominator;
|
||||
});
|
||||
}
|
||||
updateLayout(cfg) {
|
||||
const self = this;
|
||||
const data = cfg.data;
|
||||
if (data) {
|
||||
self.set('data', data);
|
||||
}
|
||||
if (self.get('ticking')) {
|
||||
// stop layout
|
||||
self.set('ticking', false);
|
||||
}
|
||||
Object.keys(cfg).forEach(key => {
|
||||
self.set(key, cfg[key]);
|
||||
});
|
||||
self.layout(data);
|
||||
}
|
||||
isTicking() {
|
||||
return this.get('ticking');
|
||||
}
|
||||
destroy() {
|
||||
if (this.get('ticking')) {
|
||||
this.getSimulation().stop();
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
eIdealDisMatrix() {
|
||||
const D = this.get('distances');
|
||||
const linkDis = this.get('linkDistance');
|
||||
const radii = this.get('radii');
|
||||
const unitRadius = this.get('unitRadius');
|
||||
const result = [];
|
||||
D.forEach((row, i) => {
|
||||
const newRow = [];
|
||||
row.forEach((v, j) => {
|
||||
if (i === j) newRow.push(0);
|
||||
else if (radii[i] === radii[j]) { // i and j are on the same circle
|
||||
newRow.push(v * linkDis / (radii[i] / unitRadius));
|
||||
} else { // i and j are on different circle
|
||||
const link = (linkDis + unitRadius) / 2;
|
||||
newRow.push(v * link);
|
||||
}
|
||||
});
|
||||
result.push(newRow);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
module.exports = Radial;
|
50
plugins/radial/mds.js
Normal file
50
plugins/radial/mds.js
Normal file
@ -0,0 +1,50 @@
|
||||
const Numeric = require('numericjs');
|
||||
class MDS {
|
||||
// getDefaultCfgs() {
|
||||
// return {
|
||||
// distances: null, // 停止迭代的最大迭代数
|
||||
// demension: 2 // 中心点,默认为数据中第一个点
|
||||
// };
|
||||
// }
|
||||
constructor(params) {
|
||||
/**
|
||||
* distance matrix
|
||||
* @type {array}
|
||||
*/
|
||||
this.distances = params.distances;
|
||||
/**
|
||||
* dimensions
|
||||
* @type {number}
|
||||
*/
|
||||
this.dimension = params.dimension || 2;
|
||||
}
|
||||
layout() {
|
||||
const self = this;
|
||||
const dimension = self.dimension;
|
||||
const distances = self.distances;
|
||||
|
||||
// square distances
|
||||
const M = Numeric.mul(-0.5, Numeric.pow(distances, 2));
|
||||
|
||||
// double centre the rows/columns
|
||||
function mean(A) { return Numeric.div(Numeric.add.apply(null, A), A.length); }
|
||||
const rowMeans = mean(M),
|
||||
colMeans = mean(Numeric.transpose(M)),
|
||||
totalMean = mean(rowMeans);
|
||||
|
||||
for (let i = 0; i < M.length; ++i) {
|
||||
for (let j = 0; j < M[0].length; ++j) {
|
||||
M[i][j] += totalMean - rowMeans[i] - colMeans[j];
|
||||
}
|
||||
}
|
||||
|
||||
// take the SVD of the double centred matrix, and return the
|
||||
// points from it
|
||||
const ret = Numeric.svd(M);
|
||||
const eigenValues = Numeric.sqrt(ret.S);
|
||||
return ret.U.map(function(row) {
|
||||
return Numeric.mul(row, eigenValues).splice(0, dimension);
|
||||
});
|
||||
}
|
||||
}
|
||||
module.exports = MDS;
|
@ -179,6 +179,90 @@ const MathUtil = {
|
||||
x: vector[0],
|
||||
y: vector[1]
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Floyd Warshall algorithm for shortest path distances matrix
|
||||
* @param {array} adjMatrix adjacency matrix
|
||||
* @return {array} distances shortest path distances matrix
|
||||
*/
|
||||
floydWarshall(adjMatrix) {
|
||||
// initialize
|
||||
const dist = [];
|
||||
const size = adjMatrix.length;
|
||||
for (let i = 0; i < size; i += 1) {
|
||||
dist[i] = [];
|
||||
for (let j = 0; j < size; j += 1) {
|
||||
if (i === j) {
|
||||
dist[i][j] = 0;
|
||||
} else if (adjMatrix[i][j] === 0 || !adjMatrix[i][j]) {
|
||||
dist[i][j] = Infinity;
|
||||
} else {
|
||||
dist[i][j] = adjMatrix[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
// floyd
|
||||
for (let k = 0; k < size; k += 1) {
|
||||
for (let i = 0; i < size; i += 1) {
|
||||
for (let j = 0; j < size; j += 1) {
|
||||
if (dist[i][j] > dist[i][k] + dist[k][j]) {
|
||||
dist[i][j] = dist[i][k] + dist[k][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dist;
|
||||
},
|
||||
|
||||
getAdjMatrix(data, directed) {
|
||||
const nodes = data.nodes;
|
||||
const edges = data.edges;
|
||||
const matrix = [];
|
||||
// map node with index in data.nodes
|
||||
const nodeMap = new Map();
|
||||
nodes.forEach((node, i) => {
|
||||
nodeMap.set(node.id, i);
|
||||
const row = [];
|
||||
matrix.push(row);
|
||||
});
|
||||
|
||||
// const n = nodes.length;
|
||||
edges.forEach(e => {
|
||||
const source = e.source;
|
||||
const target = e.target;
|
||||
const sIndex = nodeMap.get(source);
|
||||
const tIndex = nodeMap.get(target);
|
||||
matrix[sIndex][tIndex] = 1;
|
||||
if (!directed) matrix[tIndex][sIndex] = 1;
|
||||
});
|
||||
return matrix;
|
||||
},
|
||||
|
||||
getEDistance(p1, p2) {
|
||||
return Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0])
|
||||
+ (p1[1] - p2[1]) * (p1[1] - p2[1]));
|
||||
},
|
||||
|
||||
scaleMatrix(matrix, scale) {
|
||||
const result = [];
|
||||
matrix.forEach(row => {
|
||||
const newRow = [];
|
||||
row.forEach(v => {
|
||||
newRow.push(v * scale);
|
||||
});
|
||||
result.push(newRow);
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
randomInitPos(size, xRange = [ 0, 1 ], yRange = [ 0, 1 ]) {
|
||||
const positions = [];
|
||||
for (let i = 0; i < size; i++) {
|
||||
const x = Math.random() * (xRange[1] - xRange[0]) + xRange[0];
|
||||
const y = Math.random() * (yRange[1] - yRange[0]) + yRange[0];
|
||||
positions.push([ x, y ]);
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
};
|
||||
module.exports = BaseUtil.mix({}, BaseUtil, MathUtil);
|
||||
|
56
test/unit/plugins/mds-spec.js
Normal file
56
test/unit/plugins/mds-spec.js
Normal file
@ -0,0 +1,56 @@
|
||||
const expect = require('chai').expect;
|
||||
const G6 = require('../../../src');
|
||||
const MDS = require('../../../plugins/mds');
|
||||
const data = require('./data.json');
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.id = 'mds-layout';
|
||||
document.body.appendChild(div);
|
||||
|
||||
describe('mds layout', () => {
|
||||
|
||||
const graph = new G6.Graph({
|
||||
container: div,
|
||||
width: 500,
|
||||
height: 500
|
||||
});
|
||||
it('mds layout with default configs', done => {
|
||||
const mds = new MDS({
|
||||
center: [ 250, 250 ]
|
||||
});
|
||||
mds.initPlugin(graph);
|
||||
mds.layout(data);
|
||||
expect(data.nodes[0].x != null).to.equal(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it('mds with fixed link length', done => {
|
||||
const mds = new MDS({
|
||||
center: [ 250, 250 ],
|
||||
linkDistance: 120
|
||||
});
|
||||
mds.initPlugin(graph);
|
||||
expect(data.nodes[0].x != null).to.equal(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it('mds update cfg and data', done => {
|
||||
const mds = new MDS({
|
||||
center: [ 250, 250 ],
|
||||
linkDistance: 250
|
||||
});
|
||||
mds.initPlugin(graph);
|
||||
mds.layout(data);
|
||||
data.nodes = [
|
||||
{ id: '0' }, { id: '1' }, { id: '2' }
|
||||
];
|
||||
data.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);
|
||||
done();
|
||||
});
|
||||
});
|
93
test/unit/plugins/radial-spec.js
Normal file
93
test/unit/plugins/radial-spec.js
Normal file
@ -0,0 +1,93 @@
|
||||
const expect = require('chai').expect;
|
||||
const G6 = require('../../../src');
|
||||
const Radial = require('../../../plugins/radial');
|
||||
const data = require('./data.json');
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.id = 'radial-layout';
|
||||
document.body.appendChild(div);
|
||||
|
||||
function mathEqual(a, b) {
|
||||
return Math.abs(a - b) < 1;
|
||||
}
|
||||
|
||||
describe('radial layout', () => {
|
||||
|
||||
const graph = new G6.Graph({
|
||||
container: div,
|
||||
width: 500,
|
||||
height: 500
|
||||
});
|
||||
it('radial layout with default configs', done => {
|
||||
const radial = new Radial({
|
||||
center: [ 250, 250 ]
|
||||
});
|
||||
radial.initPlugin(graph);
|
||||
radial.layout(data);
|
||||
expect(mathEqual(data.nodes[0].x, 250)).to.equal(true);
|
||||
expect(mathEqual(data.nodes[0].y, 250)).to.equal(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it('radial with fixed focusNode, unit radius, link length, and max iteration', done => {
|
||||
const unitRadius = 100;
|
||||
const radial = new Radial({
|
||||
center: [ 250, 250 ],
|
||||
maxIteration: 10,
|
||||
focusNode: data.nodes[2],
|
||||
unitRadius,
|
||||
linkDistance: 100
|
||||
});
|
||||
radial.initPlugin(graph);
|
||||
radial.layout(data);
|
||||
expect(mathEqual(data.nodes[2].x, 250)).to.equal(true);
|
||||
expect(mathEqual(data.nodes[2].y, 250)).to.equal(true);
|
||||
const vx = data.nodes[0].x - data.nodes[2].x;
|
||||
const vy = data.nodes[0].y - data.nodes[2].y;
|
||||
const distToFocus = Math.sqrt(vx * vx + vy * vy);
|
||||
expect(mathEqual(distToFocus % unitRadius, 0)).to.equal(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it('radial with fixed id focusNode', done => {
|
||||
const radial = new Radial({
|
||||
center: [ 250, 250 ],
|
||||
focusNode: 'Belgium'
|
||||
});
|
||||
let focusNodeIndex = -1;
|
||||
data.nodes.forEach((node, i) => {
|
||||
if (node.id === 'Belgium') focusNodeIndex = i;
|
||||
return;
|
||||
});
|
||||
radial.initPlugin(graph);
|
||||
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 radial = new Radial({
|
||||
center: [ 250, 250 ],
|
||||
maxIteration: 120
|
||||
});
|
||||
radial.initPlugin(graph);
|
||||
radial.layout(data);
|
||||
data.nodes = [
|
||||
{ id: '0' }, { id: '1' }, { id: '2' }
|
||||
];
|
||||
data.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;
|
||||
const distToFocus = Math.sqrt(vx * vx + vy * vy);
|
||||
expect(mathEqual(distToFocus % newUnitRadius, 0)).to.equal(true);
|
||||
done();
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user