mirror of
https://gitee.com/antv/g6.git
synced 2024-12-04 20:59:15 +08:00
add: test
This commit is contained in:
parent
d98edd52e6
commit
b831139fed
@ -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',
|
||||
|
309
demos/dagre-layout-by-outer-library.html
Normal file
309
demos/dagre-layout-by-outer-library.html
Normal 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>
|
@ -135,8 +135,8 @@
|
||||
modes: {
|
||||
default: ['drag-canvas', 'drag-node'],
|
||||
},
|
||||
// layout: 'random', // default
|
||||
layoutCfg: {
|
||||
layout: {
|
||||
// type: 'random', // default
|
||||
center: [ 500, 300 ],
|
||||
},
|
||||
animate: true,
|
||||
|
@ -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>
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
78
src/layout/dagre.js
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
@ -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;
|
||||
|
@ -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 需要更新的配置项
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
160
test/unit/graph/controller/layout-spec.js
Normal file
160
test/unit/graph/controller/layout-spec.js
Normal 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;
|
||||
});
|
||||
});
|
54
test/unit/layout/circular-spec.js
Normal file
54
test/unit/layout/circular-spec.js
Normal 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();
|
||||
});
|
||||
});
|
269
test/unit/layout/dagre-spec.js
Normal file
269
test/unit/layout/dagre-spec.js
Normal 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
547
test/unit/layout/data.json
Normal 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
|
||||
}]
|
||||
}
|
79
test/unit/layout/fruchterman-spec.js
Normal file
79
test/unit/layout/fruchterman-spec.js
Normal 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();
|
||||
});
|
||||
});
|
41
test/unit/layout/mds-spec.js
Normal file
41
test/unit/layout/mds-spec.js
Normal 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();
|
||||
});
|
||||
});
|
181
test/unit/layout/radial-spec.js
Normal file
181
test/unit/layout/radial-spec.js
Normal 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();
|
||||
});
|
||||
});
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user