add: test

This commit is contained in:
shiwu.wyy 2019-09-18 17:52:11 +08:00
parent d98edd52e6
commit b831139fed
19 changed files with 1956 additions and 164 deletions

View File

@ -17,13 +17,13 @@
</style>
<body>
<div id="mountNode"></div>
<script src="./assets/dagre.js"></script>
<script src="../build/dagre.js"></script>
<script src="../build/g6.js"></script>
<script>
const data = {
nodes: [
{
id: 1,
id: '1',
type: 'alps',
name: 'alps_file1',
conf: [
@ -42,7 +42,7 @@
]
},
{
id: 2,
id: '2',
type: 'alps',
name: 'alps_file2',
conf: [
@ -61,7 +61,7 @@
]
},
{
id: 3,
id: '3',
type: 'alps',
name: 'alps_file3',
conf: [
@ -80,7 +80,7 @@
]
},
{
id: 4,
id: '4',
type: 'sql',
name: 'sql_file1',
conf: [
@ -99,7 +99,7 @@
]
},
{
id: 5,
id: '5',
type: 'sql',
name: 'sql_file2',
conf: [
@ -118,7 +118,7 @@
]
},
{
id: 6,
id: '6',
type: 'feature_etl',
name: 'feature_etl_1',
conf: [
@ -137,7 +137,7 @@
]
},
{
id: 7,
id: '7',
type: 'feature_etl',
name: 'feature_etl_1',
conf: [
@ -156,7 +156,7 @@
]
},
{
id: 8,
id: '8',
type: 'feature_extractor',
name: 'feature_extractor',
conf: [
@ -177,83 +177,40 @@
],
edges: [
{
source: 1,
target: 2
source: '1',
target: '2'
},
{
source: 1,
target: 3
source: '1',
target: '3'
},
{
source: 2,
target: 4
source: '2',
target: '4'
},
{
source: 3,
target: 4
source: '3',
target: '4'
},
{
source: 4,
target: 5
source: '4',
target: '5'
},
{
source: 5,
target: 6
source: '5',
target: '6'
},
{
source: 6,
target: 7
source: '6',
target: '7'
},
{
source: 7,
target: 8
source: '7',
target: '8'
}
]
};
const g = new dagre.graphlib.Graph();
g.setDefaultEdgeLabel(function() { return {}; });
g.setGraph({ rankdir: 'TB' });
const labelStyle = {
style: {
fill: '#fff',
fontSize: 14,
fontWeight: 'bold'
}
};
const edgeStyle = {
endArrow: true,
lineWidth: 2,
stroke: 'rgb(76,122,187)'
};
data.nodes.forEach(node => {
node.id = node.id + '';
node.shape = 'sql';
node.label = node.name;
node.labelCfg = labelStyle;
node.size = [ 150, 50 ];
g.setNode(node.id + '', { width: 150, height: 50 });
});
data.edges.forEach(edge => {
edge.source = edge.source + '';
edge.target = edge.target + '';
edge.style = edgeStyle;
edge.shape = 'polyline';
g.setEdge(edge.source, edge.target);
});
dagre.layout(g);
let coord;
g.nodes().forEach((node, i) => {
coord = g.node(node);
data.nodes[i].x = coord.x;
data.nodes[i].y = coord.y;
});
g.edges().forEach((edge, i) => {
coord = g.edge(edge);
data.edges[i].startPoint = coord.points[0];
data.edges[i].endPoint = coord.points[coord.points.length - 1];
data.edges[i].controlPoints = coord.points.slice(1, coord.points.length - 1);
});
G6.registerNode('sql', {
drawShape(cfg, group) {
const rect = group.addShape('rect', {
@ -267,6 +224,20 @@
fill: 'rgb(76,122,187)'
}
});
if (cfg.name) {
const label = group.addShape('text', {
attrs: {
text: cfg.name,
x: 0,
y: 0,
fill: '#fff',
fontSize: 14,
textAlign: 'center',
textBaseline: 'middle',
fontWeight: 'bold'
}
});
}
return rect;
}
}, 'single-shape');
@ -279,7 +250,22 @@
container: 'mountNode',
width: 500,
height: 500,
layout: {
type: 'dagre',
nodesep: 200,
},
pixelRatio: 2,
defaultNode: {
shape: 'sql'
},
defaultEdge: {
polyline: 'polyline',
style: {
endArrow: true,
lineWidth: 2,
stroke: 'rgb(76,122,187)'
}
},
modes: {
default: [ 'drag-canvas', 'zoom-canvas', 'click-select', {
type: 'tooltip',

View File

@ -0,0 +1,309 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<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>
<body>
<div id="mountNode"></div>
<script src="./assets/dagre.js"></script>
<script src="../build/g6.js"></script>
<script>
const data = {
nodes: [
{
id: 1,
type: 'alps',
name: 'alps_file1',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
},
{
id: 2,
type: 'alps',
name: 'alps_file2',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
},
{
id: 3,
type: 'alps',
name: 'alps_file3',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
},
{
id: 4,
type: 'sql',
name: 'sql_file1',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
},
{
id: 5,
type: 'sql',
name: 'sql_file2',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
},
{
id: 6,
type: 'feature_etl',
name: 'feature_etl_1',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
},
{
id: 7,
type: 'feature_etl',
name: 'feature_etl_1',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
},
{
id: 8,
type: 'feature_extractor',
name: 'feature_extractor',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
}
],
edges: [
{
source: 1,
target: 2
},
{
source: 1,
target: 3
},
{
source: 2,
target: 4
},
{
source: 3,
target: 4
},
{
source: 4,
target: 5
},
{
source: 5,
target: 6
},
{
source: 6,
target: 7
},
{
source: 7,
target: 8
}
]
};
const g = new dagre.graphlib.Graph();
g.setDefaultEdgeLabel(function() { return {}; });
g.setGraph({ rankdir: 'TB' });
const labelStyle = {
style: {
fill: '#fff',
fontSize: 14,
fontWeight: 'bold'
}
};
const edgeStyle = {
endArrow: true,
lineWidth: 2,
stroke: 'rgb(76,122,187)'
};
data.nodes.forEach(node => {
node.id = node.id + '';
node.shape = 'sql';
node.label = node.name;
node.labelCfg = labelStyle;
node.size = [ 150, 50 ];
g.setNode(node.id + '', { width: 150, height: 50 });
});
data.edges.forEach(edge => {
edge.source = edge.source + '';
edge.target = edge.target + '';
edge.style = edgeStyle;
edge.shape = 'polyline';
g.setEdge(edge.source, edge.target);
});
dagre.layout(g);
let coord;
g.nodes().forEach((node, i) => {
coord = g.node(node);
data.nodes[i].x = coord.x;
data.nodes[i].y = coord.y;
});
g.edges().forEach((edge, i) => {
coord = g.edge(edge);
data.edges[i].startPoint = coord.points[0];
data.edges[i].endPoint = coord.points[coord.points.length - 1];
data.edges[i].controlPoints = coord.points.slice(1, coord.points.length - 1);
});
G6.registerNode('sql', {
drawShape(cfg, group) {
const rect = group.addShape('rect', {
attrs: {
x: -75,
y: -25,
width: 150,
height: 50,
radius: 10,
stroke: 'rgb(36,60,96)',
fill: 'rgb(76,122,187)'
}
});
return rect;
}
}, 'single-shape');
G6.Global.nodeStateStyle.selected = {
stroke: '#d9d9d9',
fill: '#5394ef'
};
const graph = new G6.Graph({
container: 'mountNode',
width: 500,
height: 500,
pixelRatio: 2,
modes: {
default: [ 'drag-canvas', 'zoom-canvas', 'click-select', {
type: 'tooltip',
formatText(model) {
const cfg = model.conf;
const text = [];
cfg.forEach(row => {
text.push(row.label + ':' + row.value + '<br>');
});
return text.join('\n');
},
shouldUpdate: e => {
// 如果移动到节点文本上显示,不是文本上不显示
if (e.target.type !== 'text') {
return false;
}
return true;
}
}]
},
fitView: true
});
graph.data(data);
graph.render();
</script>
</body>
</html>

View File

@ -135,8 +135,8 @@
modes: {
default: ['drag-canvas', 'drag-node'],
},
// layout: 'random', // default
layoutCfg: {
layout: {
// type: 'random', // default
center: [ 500, 300 ],
},
animate: true,

View File

@ -3408,8 +3408,18 @@
const graph = new G6.Graph({
container: 'mountNode',
width: 1000,
height: 600,
plugins: [ RadialLayoutPlugin ],
height: 600,
layout: {
type: 'radial',
center: [ 500, 300 ],
maxIteration: 200,
focusNode,
unitRadius: mainUnitRadius,
linkDistance: 100,
preventOverlap: true,
nodeSize: 20
},
// plugins: [ RadialLayoutPlugin ],
modes: {
default: ['drag-node', 'click-select', 'click-add-node', 'drag-canvas']
},
@ -3443,7 +3453,7 @@
edge.id = 'edge' + i;
return Object.assign({}, edge);
}) });
RadialLayoutPlugin.layout(data_m);
// RadialLayoutPlugin.layout(data_m);
graph.render();
</script>

View File

@ -100,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/layout/",
"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",

View File

@ -4,27 +4,13 @@ const Util = require('../../util');
class LayoutController {
constructor(graph) {
this.graph = graph;
this.layoutType = graph.get('layout');
// if (layout === undefined) {
// console.log(graph);
// if (graph.getNodes()[0].x === undefined) {
// // 创建随机布局
// const randomLayout = new Layout.Random();
// this.set('layout', randomLayout);
// } else { // 若未指定布局且数据中有位置信息,则不进行布局,直接按照原数据坐标绘制。
// return;
// }
// }
// layout = this._getLayout();
this.layoutCfg = graph.get('layout');
this.layoutType = this.layoutCfg ? this.layoutCfg.type : undefined;
this._initLayout();
}
_initLayout() {
// const layout = this.layout;
// const graph = this.graph;
// const nodes = graph.getNodes();
// const edges = graph.getEdges();
// layout.init(nodes, edges);
// no data before rendering
}
layout() {
@ -32,8 +18,11 @@ class LayoutController {
let layoutType = self.layoutType;
const graph = self.graph;
const data = self.data || graph.get('data');
const nodes = data.nodes || [];
const edges = data.edges || [];
const nodes = data.nodes;
if (!nodes) {
return;
}
data.edges = data.edges || [];
const width = graph.get('width');
const height = graph.get('height');
const layoutCfg = [];
@ -41,7 +30,7 @@ class LayoutController {
width,
height,
center: [ width / 2, height / 2 ]
}, graph.get('layoutCfg'));
}, self.layoutCfg);
if (layoutType === undefined) {
if (nodes[0] && nodes[0].x === undefined) {
@ -64,11 +53,11 @@ class LayoutController {
};
layoutCfg.tick = tick;
}
layoutMethod = new Layout[layoutType](nodes, edges, layoutCfg);
layoutMethod.init();
self.layoutCfg = layoutCfg;
layoutMethod = new Layout[layoutType](layoutCfg);
layoutMethod.init(data);
layoutMethod.excute();
self.layoutMethod = layoutMethod;
}
// 绘制
@ -85,6 +74,7 @@ class LayoutController {
// 更新布局参数
updateLayoutCfg(cfg) {
const self = this;
self.layoutType = cfg.type;
const layoutMethod = self.layoutMethod;
layoutMethod.updateCfg(cfg);
if (self.layoutType !== 'force') {
@ -95,11 +85,13 @@ class LayoutController {
}
// 更换布局
changeLayout(layoutType) { // , layoutCfg = null
changeLayout(layoutType) {
const self = this;
self.layoutType = layoutType;
self.layoutCfg = self.graph.get('layoutCfg');
self.layoutCfg.type = layoutType;
const layoutMethod = self.layoutMethod;
layoutMethod.destroy();
layoutMethod && layoutMethod.destroy();
self.moveToZero();
self.layout();
self.refreshLayout();
@ -108,10 +100,8 @@ class LayoutController {
// 更换数据
changeData(data) {
const self = this;
// const graph = self.graph;
self.data = data;
self.layout();
// graph.refreshPositions();
}
// 控制布局动画
@ -145,10 +135,11 @@ class LayoutController {
}
destroy() {
this.graph = null;
const layoutMethod = this.layoutMethod;
const self = this;
self.graph = null;
const layoutMethod = self.layoutMethod;
layoutMethod && layoutMethod.destroy();
this.destroyed = true;
self.destroyed = true;
}
}

View File

@ -1008,18 +1008,38 @@ class Graph extends EventEmitter {
/**
* 更换布局
* @param {string} layoutType 布局名字
* @param {string} layoutType 布局名字
* @param {object} cfg 新布局配置项
* 若无其他配置则使用该布局默认配置
*/
changeLayout(layoutType, cfg = null) {
const layoutController = this.get('layoutController');
this.set('layoutCfg', cfg);
layoutController.changeLayout(layoutType);
let type = layoutType;
if (!type && !cfg.type) {
return;
}
cfg === null ? cfg = {} : cfg;
if (type) {
cfg.type = type;
} else if (cfg.type) {
type = cfg.type;
}
this.set('layout', cfg);
layoutController.changeLayout(type);
}
/**
* 更换布局配置项不改变布局方法
* @param {object} cfg 新布局配置项不包括 type 即使有 type 字段也不会生效
*/
updateLayoutCfg(cfg) {
const layoutController = this.get('layoutController');
layoutController.updateLayoutCfg(cfg);
const oriLayoutCfg = this.get('layout');
const layoutType = oriLayoutCfg.type;
const layoutCfg = [];
Util.mix(layoutCfg, cfg);
layoutCfg.type = layoutType;
layoutController.updateLayoutCfg(layoutCfg);
}
/**

78
src/layout/dagre.js Normal file
View File

@ -0,0 +1,78 @@
/**
* @fileOverview random layout
* @author shiwu.wyy@antfin.com
*/
const dagre = require('dagre');
const Layout = require('./layout');
const DEFAULT_SIZE = 40;
/**
* 随机布局
*/
Layout.registerLayout('dagre', {
layoutType: 'dagre',
getDefaultCfg() {
return {
rankdir: 'TB', // layout 方向, 可选 TB, BT, LR, RL
align: undefined, // 节点对齐方式,可选 UL, UR, DL, DR
nodesep: 50, // 节点水平间距(px)
edgesep: 50, // 边水平间距(px)
ranksep: 50, // 每一层节点之间间距
marginx: 0, // 图的 x 边距
marginy: 0, // 图的 y 边距
controlPoints: true, // 是否保留布局连线的控制点
getNodeSize: null // 节点大小回调函数
};
},
/**
* 执行布局
*/
excute() {
const self = this;
const nodes = self.nodes;
const edges = self.edges;
const g = new dagre.graphlib.Graph();
// const cfgs = this._cfgs;
g.setDefaultEdgeLabel(function() { return {}; });
g.setGraph(self);
const nodeSize = self.getNodeSize;
nodes.forEach(node => {
let width = DEFAULT_SIZE;
let height = DEFAULT_SIZE;
let size = node.size;
if (nodeSize) {
size = nodeSize(node);
}
if (size) {
if (Array.isArray(size)) {
width = size[0];
height = size[1];
} else {
width = size;
height = size;
}
}
g.setNode(node.id, { width, height });
});
edges.forEach(edge => {
g.setEdge(edge.source, edge.target);
});
dagre.layout(g);
let coord;
g.nodes().forEach((node, i) => {
coord = g.node(node);
nodes[i].x = coord.x;
nodes[i].y = coord.y;
});
g.edges().forEach((edge, i) => {
coord = g.edge(edge);
edges[i].startPoint = coord.points[0];
edges[i].endPoint = coord.points[coord.points.length - 1];
if (self.controlPoints) {
edges[i].controlPoints = coord.points.slice(1, coord.points.length - 1);
}
});
}
});

View File

@ -10,6 +10,7 @@ module.exports = {
Circular: require('./circular'),
Fruchterman: require('./fruchterman'),
Radial: require('./radial/radial'),
Force: require('./force')
Force: require('./force'),
Dagre: require('./dagre')
};
module.exports = Layout;

View File

@ -15,25 +15,34 @@ Layout.registerLayout = function(type, layout) {
if (!layout) {
throw new Error('please specify handler for this layout:' + type);
}
const base = function(nodes, edges, cfg) {
const base = function(cfg) {
const self = this;
self.nodes = nodes;
self.edges = edges;
Util.mix(self, self.getDefaultCfg(), cfg);
self.init();
};
Util.augment(base, {
/**
* 初始化
* @param {object} data 数据
*/
init() {
// const self = this;
init(data) {
const self = this;
self.nodes = data.nodes;
self.edges = data.edges;
},
/**
* 执行布局不改变原数据模型位置只返回布局后但结果位置
*/
excute() {
},
/**
* 根据传入的数据进行布局
* @param {object} data 数据
*/
layout(data) {
const self = this;
self.init(data);
self.excute();
},
/**
* 更新布局配置但不执行布局
* @param {object} cfg 需要更新的配置项

View File

@ -46,7 +46,7 @@ Layout.registerLayout('radial', {
focusNode: null, // 中心点,默认为数据中第一个点
unitRadius: null, // 每一圈半径
linkDistance: 50, // 默认边长度
nonOverlap: false, // 是否防止重叠
preventOverlap: false, // 是否防止重叠
nodeSize: 10 // 节点半径
};
},
@ -93,9 +93,10 @@ Layout.registerLayout('radial', {
// the graph-theoretic distance (shortest path distance) matrix
const adjMatrix = Util.getAdjMatrix({ nodes, edges }, false);
self.handleAbnormalMatrix(adjMatrix, focusIndex);
const D = Util.floydWarshall(adjMatrix);
const connected = Util.isConnected(D);
const maxDistance = self.maxToFocus(D, focusIndex);
// replace first node in unconnected component to the circle at (maxDistance + 1)
self.handleInfinity(D, focusIndex, (maxDistance + 1));
self.distances = D;
// the shortest path distance from each node to focusNode
@ -135,32 +136,29 @@ Layout.registerLayout('radial', {
nodes[i].x = p[0] + center[0];
nodes[i].y = p[1] + center[1];
});
// if the graph is connected, layout by radial layout and force nonoverlap
if (connected) {
// 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.nonOverlap;
const nodeSize = self.nodeSize;
// stagger the overlapped nodes
if (nonOverlap) {
const nonoverlapForce = new RadialNonoverlapForce({
nodeSize, adjMatrix, positions, radii, height, width,
focusID: focusIndex,
iterations: 200,
k: positions.length / 4.5
});
positions = nonoverlapForce.layout();
}
// move the graph to center
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 preventOverlap = self.preventOverlap;
const nodeSize = self.nodeSize;
// stagger the overlapped nodes
if (preventOverlap) {
const nonoverlapForce = new RadialNonoverlapForce({
nodeSize, adjMatrix, positions, radii, height, width,
focusID: focusIndex,
iterations: 200,
k: positions.length / 4.5
});
positions = nonoverlapForce.layout();
}
// move the graph to center
positions.forEach((p, i) => {
nodes[i].x = p[0] + center[0];
nodes[i].y = p[1] + center[1];
});
},
run() {
const self = this;
@ -233,30 +231,73 @@ Layout.registerLayout('radial', {
});
return result;
},
handleAbnormalMatrix(matrix, focusIndex) {
const rows = matrix.length;
let emptyMatrix = true;
handleAbnormalMatrix(adMatrix, focusIndex) {
const rows = adMatrix.length;
// 空行即代表该行是离散点,将单个离散点看作 focus 的邻居
for (let i = 0; i < rows; i++) {
if (matrix[i].length !== 0) emptyMatrix = false;
let hasDis = true;
for (let j = 0; j < matrix[i].length; j++) {
if (!matrix[i][j]) hasDis = false;
}
if (hasDis) {
matrix[i][focusIndex] = 1;
matrix[focusIndex][i] = 1;
if (adMatrix[i].length === 0) {
adMatrix[i][focusIndex] = 1;
adMatrix[focusIndex][i] = 1;
}
}
if (emptyMatrix) {
let value = 0;
for (let i = 0; i < rows; i++) {
for (let j = 0; j < rows; j++) {
if (i === focusIndex || j === focusIndex) value = 1;
matrix[i][j] = value;
value = 0;
// 如果第一行中有
// let hasDis = true;
// for (let j = 0; j < matrix[focusIndex].length; j++) {
// if (!matrix[focusIndex][j]) hasDis = false;
// }
// if (hasDis) {
// matrix[j][focusIndex] = 1;
// matrix[focusIndex][j] = 1;
// }
// if (emptyMatrix) {
// let value = 0;
// for (let i = 0; i < rows; i++) {
// for (let j = 0; j < rows; j++) {
// if (i === focusIndex || j === focusIndex) value = 1;
// matrix[i][j] = value;
// value = 0;
// }
// value = 0;
// }
// }
},
handleInfinity(matrix, focusIndex, step) {
const length = matrix.length;
// 遍历 matrix 中遍历 focus 对应行
for (let i = 0; i < length; i++) {
// matrix 关注点对应行的 Inf 项
if (matrix[focusIndex][i] === Infinity) {
matrix[focusIndex][i] = step;
matrix[i][focusIndex] = step;
// 遍历 matrix 中的 i 行i 行中非 Inf 项若在 focus 行为 Inf则替换 focus 行的那个 Inf
for (let j = 0; j < length; j++) {
if (matrix[i][j] !== Infinity && matrix[focusIndex][j] === Infinity) {
matrix[focusIndex][j] = step + matrix[i][j];
matrix[j][focusIndex] = step + matrix[i][j];
}
}
value = 0;
}
}
// 处理其他行的 Inf。根据该行对应点与 focus 距离以及 Inf 项点 与 focus 距离,决定替换值
for (let i = 0; i < length; i++) {
if (i === focusIndex) {
continue;
}
for (let j = 0; j < length; j++) {
if (matrix[i][j] === Infinity) {
let minus = Math.abs(matrix[focusIndex][i] - matrix[focusIndex][j]);
minus = minus === 0 ? 1 : minus;
matrix[i][j] = minus;
}
}
}
},
maxToFocus(matrix, focusIndex) {
let max = 0;
for (let i = 0; i < matrix[focusIndex].length; i++) {
if (matrix[focusIndex][i] === Infinity) continue;
max = matrix[focusIndex][i] > max ? matrix[focusIndex][i] : max;
}
return max;
}
});

View File

@ -0,0 +1,160 @@
const expect = require('chai').expect;
const G6 = require('../../../../src');
// const Util = require('../../../src/util');
function numberEqual(a, b, gap) {
return Math.abs(a - b) <= (gap || 0.001);
}
const div = document.createElement('div');
div.id = 'graph-spec';
document.body.appendChild(div);
const data = {
nodes: [
{ id: '0', label: '0' },
{ id: '1', label: '1' },
{ id: '2', label: '2' },
{ id: '3', label: '3' },
{ id: '4', label: '4' },
{ id: '5', label: '5' },
{ id: '6', label: '6' },
{ id: '7', label: '7' },
{ id: '8', label: '8' },
{ id: '9', label: '9' }
],
edges: [
{ source: '0', target: '1' },
{ source: '0', target: '2' },
{ source: '0', target: '3' },
{ source: '0', target: '4' },
{ source: '0', target: '5' },
{ source: '0', target: '7' },
{ source: '0', target: '8' },
{ source: '0', target: '9' },
{ source: '2', target: '3' },
{ source: '4', target: '5' },
{ source: '4', target: '6' },
{ source: '5', target: '6' }
]
};
const data2 = {
nodes: [
{ id: '0', label: '0', x: 100, y: 20 },
{ id: '1', label: '1', x: 10, y: 210 },
{ id: '2', label: '2', x: 150, y: 100 },
{ id: '3', label: '3', x: 120, y: 100 },
{ id: '4', label: '4', x: 50, y: 250 },
{ id: '5', label: '5', x: 130, y: 50 }
],
edges: [
{ id: 'e1', source: '0', target: '1' },
{ id: 'e2', source: '0', target: '2' },
{ id: 'e3', source: '0', target: '3' },
{ id: 'e4', source: '0', target: '4' },
{ id: 'e5', source: '0', target: '5' }
]
};
describe('layout controller', () => {
const graph = new G6.Graph({
container: div,
width: 500,
height: 500
});
it('new graph without layout', () => {
graph.data(data);
graph.render();
expect(graph.getNodes()[0].getModel().x).not.to.be.undefined;
expect(graph.getNodes()[0].getModel().y).not.to.be.undefined;
expect(graph.getNodes()[1].getModel().x).not.to.be.undefined;
expect(graph.getNodes()[1].getModel().y).not.to.be.undefined;
});
it('change layout with configurations', () => {
const radius = 100;
graph.changeLayout('circular', {
radius,
startAngle: Math.PI / 4,
endAngle: Math.PI,
divisions: 5,
ordering: 'degree'
});
const center = [ graph.get('width') / 2, graph.get('height') / 2 ];
const node = graph.getNodes()[0].getModel();
const dist = (node.x - center[0]) * (node.x - center[0])
+ (node.y - center[1]) * (node.y - center[1]);
expect(numberEqual(dist, radius * radius)).to.be.true;
});
it('change configurations', () => {
const startRadius = 10;
const endRadius = 300;
graph.updateLayoutCfg({
radius: null,
startRadius,
endRadius,
divisions: 1,
startAngle: 0,
endAngle: 2 * Math.PI,
ordering: 'topology'
});
const center = [ graph.get('width') / 2, graph.get('height') / 2 ];
const nodes = graph.getNodes();
const firstNode = nodes[0].getModel();
const lastNode = nodes[nodes.length - 1].getModel();
const firstDist = (firstNode.x - center[0]) * (firstNode.x - center[0])
+ (firstNode.y - center[1]) * (firstNode.y - center[1]);
const lastDist = (lastNode.x - center[0]) * (lastNode.x - center[0])
+ (lastNode.y - center[1]) * (lastNode.y - center[1]);
expect(numberEqual(firstDist, startRadius * startRadius)).to.be.true;
expect(numberEqual(lastDist, endRadius * endRadius)).to.be.true;
});
it('change layout without configurations', () => {
graph.changeLayout('fruchterman');
const layoutCfg = graph.get('layoutCfg');
const layoutType = layoutCfg.type;
const node = graph.getNodes()[0].getModel();
expect(node.x).not.to.be.undefined;
expect(node.y).not.to.be.undefined;
expect(layoutType).to.equal('fruchterman');
expect(Object.getOwnPropertyNames(layoutCfg).length).to.equal(1);
});
it('change data', () => {
graph.changeData(data2);
const layoutCfg = graph.get('layoutCfg');
const layoutType = layoutCfg.type;
const node = graph.getNodes()[0].getModel();
expect(node.x).not.to.be.undefined;
expect(node.y).not.to.be.undefined;
expect(layoutType).to.equal('fruchterman');
expect(Object.getOwnPropertyNames(layoutCfg).length).to.equal(1);
});
it('change data without data', () => {
graph.changeData({});
const layoutCfg = graph.get('layoutCfg');
const layoutType = layoutCfg.type;
const nodeItem = graph.getNodes()[0];
expect(nodeItem).to.be.undefined;
expect(layoutType).to.equal('fruchterman');
expect(Object.getOwnPropertyNames(layoutCfg).length).to.equal(1);
});
it('default layout without data', () => {
const graph2 = new G6.Graph({
container: div,
width: 500,
height: 500
});
graph2.data({});
graph2.render();
const layoutController = graph2.get('layoutController');
expect(layoutController).not.to.be.undefined;
graph2.destroy();
});
it('graph destroy, layoutController destroy', () => {
const layoutController = graph.get('layoutController');
graph.destroy();
expect(layoutController.destroyed).to.be.true;
});
});

View File

@ -0,0 +1,54 @@
const expect = require('chai').expect;
const G6 = require('../../../src');
const data = require('./data.json');
const div = document.createElement('div');
div.id = 'force-layout';
document.body.appendChild(div);
function mathEqual(a, b) {
return Math.abs(a - b) < 1;
}
describe('circular layout', () => {
it('circular layout with default configs', () => {
const graph = new G6.Graph({
container: div,
layout: { type: 'circular' },
width: 500,
height: 500,
defaultNode: { size: 10 }
});
graph.data(data);
graph.render();
const width = graph.get('width');
const height = graph.get('height');
const radius = height > width ? width / 2 : height / 2;
expect(mathEqual(data.nodes[0].x, 250 + radius)).to.equal(true);
expect(mathEqual(data.nodes[0].y, 250)).to.equal(true);
expect(data.nodes[0].y === 250);
graph.destroy();
});
it('circular counterclockwise, and fixed radius, start angle, end angle', () => {
const graph = new G6.Graph({
container: div,
layout: {
type: 'circular',
center: [ 250, 250 ],
radius: 200,
startAngle: Math.PI / 4,
endAngle: Math.PI
},
width: 500,
height: 500,
defaultNode: { size: 10 }
});
graph.data(data);
graph.render();
const pos = 200 * Math.sqrt(2) / 2;
expect(mathEqual(data.nodes[0].x, 250 + pos)).to.equal(true);
expect(mathEqual(data.nodes[0].y, 250 + pos)).to.equal(true);
graph.destroy();
});
});

View File

@ -0,0 +1,269 @@
const expect = require('chai').expect;
// const G6 = require('../../../src');
const G6 = require('../../../src');
// const data = require('./data');
const div = document.createElement('div');
div.id = 'dagre';
document.body.appendChild(div);
const data = {
nodes: [
{
id: '1',
type: 'alps',
name: 'alps_file1',
label: '1',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
},
{
id: '2',
type: 'alps',
name: 'alps_file2',
label: '2',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
},
{
id: '3',
type: 'alps',
name: 'alps_file3',
label: '3',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
},
{
id: '4',
type: 'sql',
name: 'sql_file1',
label: '4',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
},
{
id: '5',
type: 'sql',
name: 'sql_file2',
label: '5',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
},
{
id: '6',
type: 'feature_etl',
name: 'feature_etl_1',
label: '6',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
},
{
id: '7',
type: 'feature_etl',
name: 'feature_etl_1',
label: '7',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
},
{
id: '8',
type: 'feature_extractor',
name: 'feature_extractor',
label: '8',
conf: [
{
label: 'conf',
value: 'pai_graph.conf'
},
{
label: 'dot',
value: 'pai_graph.dot'
},
{
label: 'init',
value: 'init.rc'
}
]
}
],
edges: [
{
source: '1',
target: '2'
},
{
source: '1',
target: '3'
},
{
source: '2',
target: '4'
},
{
source: '3',
target: '4'
},
{
source: '4',
target: '5'
},
{
source: '5',
target: '6'
},
{
source: '6',
target: '7'
},
{
source: '7',
target: '8'
}
]
};
function mathEqual(a, b) {
return Math.abs(a - b) < 1;
}
describe('dagre layout', () => {
it('layout with default configs', () => {
const graph = new G6.Graph({
container: div,
layout: {
type: 'dagre'
},
width: 500,
height: 500,
fitView: true
});
graph.data(data);
graph.render();
const node = data.nodes[0];
const edge = data.edges[0];
expect(mathEqual(node.x, 215));
expect(mathEqual(node.y, 196));
expect(mathEqual(edge.startPoint.x, 522));
expect(mathEqual(edge.startPoint.y, 440));
expect(mathEqual(edge.endPoint.x, 765));
expect(mathEqual(edge.endPoint.y, 498));
graph.destroy();
});
it('modify configs', () => {
data.edges.forEach(edge => {
delete edge.startPoint;
delete edge.endPoint;
delete edge.controlPoints;
});
const graph = new G6.Graph({
container: div,
layout: {
type: 'dagre',
rankdir: 'LR',
marginx: 100,
marginy: 100,
controlPoints: false
},
width: 500,
height: 500,
fitView: true
});
graph.data(data);
graph.render();
const node = data.nodes[0];
const edge = data.edges[0];
expect(mathEqual(node.x, 600));
expect(mathEqual(node.y, 1075));
expect(mathEqual(edge.startPoint.x, 531));
expect(mathEqual(edge.startPoint.y, 594));
expect(mathEqual(edge.endPoint.x, 597));
expect(mathEqual(edge.endPoint.y, 854));
expect(edge.controlPoints).to.be.undefined;
graph.destroy();
});
});

547
test/unit/layout/data.json Normal file
View File

@ -0,0 +1,547 @@
{
"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
}]
}

View File

@ -0,0 +1,79 @@
const expect = require('chai').expect;
const G6 = require('../../../src');
// const Util = require('../../../src/util');
const div = document.createElement('div');
div.id = 'graph-spec';
document.body.appendChild(div);
const data = {
nodes: [
{
id: '0'
},
{
id: '1'
}
],
edges: [
]
};
describe('fruchterman', () => {
it('new graph with fruchterman layout, without configurations', () => {
const graph = new G6.Graph({
container: div,
layout: { type: 'fruchterman' },
width: 500,
height: 500
});
graph.data(data);
graph.render();
expect(graph.getNodes()[0].getModel().x).not.to.be.undefined;
expect(graph.getNodes()[0].getModel().y).not.to.be.undefined;
expect(graph.getNodes()[1].getModel().x).not.to.be.undefined;
expect(graph.getNodes()[1].getModel().y).not.to.be.undefined;
graph.destroy();
});
it('new graph with fruchterman layout, with configurations', () => {
const graph = new G6.Graph({
container: div,
layout: {
type: 'fruchterman',
center: [ 100, 100 ],
maxIteration: 100
},
width: 500,
height: 500
});
graph.data(data);
graph.render();
expect(graph.getNodes()[0].getModel().x).not.to.be.undefined;
expect(graph.getNodes()[0].getModel().y).not.to.be.undefined;
expect(graph.getNodes()[1].getModel().x).not.to.be.undefined;
expect(graph.getNodes()[1].getModel().y).not.to.be.undefined;
graph.destroy();
});
it('update fructherman layout configurations', () => {
const graph = new G6.Graph({
container: div,
layout: {
type: 'fruchterman'
},
width: 500,
height: 500
});
graph.data(data);
graph.render();
graph.updateLayoutCfg({
center: [ 100, 100 ],
gravity: 50
});
expect(graph.getNodes()[0].getModel().x).not.to.be.undefined;
expect(graph.getNodes()[0].getModel().y).not.to.be.undefined;
expect(graph.getNodes()[1].getModel().x).not.to.be.undefined;
expect(graph.getNodes()[1].getModel().y).not.to.be.undefined;
graph.destroy();
});
});

View File

@ -0,0 +1,41 @@
const expect = require('chai').expect;
const G6 = require('../../../src');
const data = require('./data.json');
const div = document.createElement('div');
div.id = 'mds-layout';
document.body.appendChild(div);
describe('mds', () => {
it('mds layout with default configs', () => {
const graph = new G6.Graph({
container: div,
layout: {
type: 'mds'
},
width: 500,
height: 500
});
graph.data(data);
graph.render();
expect(data.nodes[0].x != null).to.equal(true);
graph.destroy();
});
it('mds with fixed link length', () => {
const graph = new G6.Graph({
container: div,
layout: {
type: 'mds',
center: [ 250, 250 ],
linkDistance: 120
},
width: 500,
height: 500
});
graph.data(data);
graph.render();
expect(data.nodes[0].x != null).to.equal(true);
graph.destroy();
});
});

View File

@ -0,0 +1,181 @@
const expect = require('chai').expect;
const G6 = require('../../../src');
// const Util = require('../../../src/util');
function numberEqual(a, b, gap) {
return Math.abs(a - b) <= (gap || 0.001);
}
const div = document.createElement('div');
div.id = 'graph-spec';
document.body.appendChild(div);
const data = {
nodes: [
{ id: '0', label: '0' },
{ id: '1', label: '1' },
{ id: '2', label: '2' },
{ id: '3', label: '3' },
{ id: '4', label: '4' },
{ id: '5', label: '5' }
],
edges: [{
source: '0',
target: '1'
},
{
source: '0',
target: '2'
},
{
source: '3',
target: '4'
}]
};
describe('radial', () => {
it('new graph with radial layout, without configurations', () => {
const graph = new G6.Graph({
container: div,
layout: {
type: 'radial'
},
width: 500,
height: 500
});
graph.data(data);
graph.render();
const center = [ graph.get('width') / 2, graph.get('height') / 2 ];
const focusNode = graph.getNodes()[0].getModel();
expect(numberEqual(focusNode.x, center[0])).to.be.true;
expect(numberEqual(focusNode.y, center[1])).to.be.true;
graph.destroy();
});
it('new graph with radial layout, with configurations', () => {
const unitRadius = 100;
const fnIndex = 1;
const focusNode = data.nodes[fnIndex];
const center = [ 250, 250 ];
const graph = new G6.Graph({
container: div,
layout: {
type: 'radial',
center,
maxIteration: 100,
focusNode,
unitRadius,
linkDistance: 100
},
modes: {
default: [ 'drag-node', 'drag-canvas' ]
},
width: 500,
height: 500
});
graph.data(data);
graph.render();
const oneStepNode = data.nodes[0];
const DistFnToOneStepNode = (oneStepNode.x - focusNode.x) * (oneStepNode.x - focusNode.x)
+ (oneStepNode.y - focusNode.y) * (oneStepNode.y - focusNode.y);
const twoStepNode = data.nodes[2];
const DistFnToTwoStepNode = (twoStepNode.x - focusNode.x) * (twoStepNode.x - focusNode.x)
+ (twoStepNode.y - focusNode.y) * (twoStepNode.y - focusNode.y);
const descreteNode = data.nodes[5];
const DistFnToDescreteNode = (descreteNode.x - focusNode.x) * (descreteNode.x - focusNode.x)
+ (descreteNode.y - focusNode.y) * (descreteNode.y - focusNode.y);
const descreteComNode1 = data.nodes[3];
const DistFnToDescreteComNode1 = (descreteComNode1.x - focusNode.x) * (descreteComNode1.x - focusNode.x)
+ (descreteComNode1.y - focusNode.y) * (descreteComNode1.y - focusNode.y);
const descreteComNode2 = data.nodes[4];
const DistFnToDescreteComNode2 = (descreteComNode2.x - focusNode.x) * (descreteComNode2.x - focusNode.x)
+ (descreteComNode2.y - focusNode.y) * (descreteComNode2.y - focusNode.y);
expect(numberEqual(DistFnToOneStepNode, unitRadius * unitRadius)).to.be.true;
expect(numberEqual(DistFnToTwoStepNode, 4 * unitRadius * unitRadius)).to.be.true;
expect(numberEqual(DistFnToDescreteNode, 9 * unitRadius * unitRadius)).to.be.true;
expect(numberEqual(DistFnToDescreteComNode1, 9 * unitRadius * unitRadius)).to.be.true;
expect(numberEqual(DistFnToDescreteComNode2, 16 * unitRadius * unitRadius)).to.be.true;
graph.destroy();
});
it('new graph with radial layout, with focusnode id', () => {
const unitRadius = 100;
const focusNodeId = '3';
const graph = new G6.Graph({
container: div,
layout: {
type: 'radial',
focusNode: focusNodeId,
unitRadius
},
width: 500,
height: 500
});
graph.data(data);
graph.render();
const focusNode = graph.findById(focusNodeId).getModel();
const descreteNode = data.nodes[5];
const DistFnToDescreteNode = (descreteNode.x - focusNode.x) * (descreteNode.x - focusNode.x)
+ (descreteNode.y - focusNode.y) * (descreteNode.y - focusNode.y);
const descreteComNode1 = data.nodes[0];
const DistFnToDescreteComNode1 = (descreteComNode1.x - focusNode.x) * (descreteComNode1.x - focusNode.x)
+ (descreteComNode1.y - focusNode.y) * (descreteComNode1.y - focusNode.y);
const descreteComNode2 = data.nodes[1];
const DistFnToDescreteComNode2 = (descreteComNode2.x - focusNode.x) * (descreteComNode2.x - focusNode.x)
+ (descreteComNode2.y - focusNode.y) * (descreteComNode2.y - focusNode.y);
const descreteComNode3 = data.nodes[2];
const DistFnToDescreteComNode3 = (descreteComNode3.x - focusNode.x) * (descreteComNode3.x - focusNode.x)
+ (descreteComNode3.y - focusNode.y) * (descreteComNode3.y - focusNode.y);
expect(numberEqual(DistFnToDescreteNode, 4 * unitRadius * unitRadius)).to.be.true;
expect(numberEqual(DistFnToDescreteComNode1, 4 * unitRadius * unitRadius)).to.be.true;
expect(numberEqual(DistFnToDescreteComNode2, 9 * unitRadius * unitRadius)).to.be.true;
expect(numberEqual(DistFnToDescreteComNode3, 9 * unitRadius * unitRadius)).to.be.true;
graph.destroy();
});
it('focus on descrete node, prevent overlapping', () => {
const unitRadius = 100;
const focusNode = data.nodes[5];
const nodeSize = 40;
const graph = new G6.Graph({
container: div,
layout: {
type: 'radial',
focusNode,
unitRadius,
preventOverlap: true,
nodeSize
},
width: 500,
height: 500
});
graph.data(data);
graph.render();
const descreteCom1Node1 = data.nodes[0];
const DistFnToDescreteCom1Node1 = (descreteCom1Node1.x - focusNode.x) * (descreteCom1Node1.x - focusNode.x)
+ (descreteCom1Node1.y - focusNode.y) * (descreteCom1Node1.y - focusNode.y);
const descreteCom1Node2 = data.nodes[1];
const DistFnToDescreteCom1Node2 = (descreteCom1Node2.x - focusNode.x) * (descreteCom1Node2.x - focusNode.x)
+ (descreteCom1Node2.y - focusNode.y) * (descreteCom1Node2.y - focusNode.y);
const descreteCom2Node1 = data.nodes[3];
const DistFnToDescreteCom2Node1 = (descreteCom2Node1.x - focusNode.x) * (descreteCom2Node1.x - focusNode.x)
+ (descreteCom2Node1.y - focusNode.y) * (descreteCom2Node1.y - focusNode.y);
const descreteCom2Node2 = data.nodes[4];
const DistFnToDescreteCom2Node2 = (descreteCom2Node2.x - focusNode.x) * (descreteCom2Node2.x - focusNode.x)
+ (descreteCom2Node2.y - focusNode.y) * (descreteCom2Node2.y - focusNode.y);
expect(numberEqual(DistFnToDescreteCom1Node1, unitRadius * unitRadius)).to.be.true;
expect(numberEqual(DistFnToDescreteCom1Node2, 4 * unitRadius * unitRadius)).to.be.true;
expect(numberEqual(DistFnToDescreteCom2Node1, unitRadius * unitRadius)).to.be.true;
expect(numberEqual(DistFnToDescreteCom2Node2, 4 * unitRadius * unitRadius)).to.be.true;
// non overlap
const overlapDist1 = (descreteCom1Node1.x - descreteCom2Node1.x) * (descreteCom1Node1.x - descreteCom2Node1.x)
+ (descreteCom1Node1.y - descreteCom2Node1.y) * (descreteCom1Node1.y - descreteCom2Node1.y);
expect(overlapDist1 > nodeSize * nodeSize).to.be.true;
graph.destroy();
});
});

View File

@ -20,8 +20,8 @@ const data = {
]
};
describe('random by default', () => {
it('new graph without layout', () => {
describe('random', () => {
it('new graph without layout, random by default', () => {
const graph = new G6.Graph({
container: div,
width: 500,
@ -33,5 +33,21 @@ describe('random by default', () => {
expect(graph.getNodes()[0].getModel().y).not.to.be.undefined;
expect(graph.getNodes()[1].getModel().x).not.to.be.undefined;
expect(graph.getNodes()[1].getModel().y).not.to.be.undefined;
graph.destroy();
});
it('new graph with random layout', () => {
const graph = new G6.Graph({
container: div,
layout: { type: 'random' },
width: 500,
height: 500
});
graph.data(data);
graph.render();
expect(graph.getNodes()[0].getModel().x).not.to.be.undefined;
expect(graph.getNodes()[0].getModel().y).not.to.be.undefined;
expect(graph.getNodes()[1].getModel().x).not.to.be.undefined;
expect(graph.getNodes()[1].getModel().y).not.to.be.undefined;
graph.destroy();
});
});