mirror of
https://gitee.com/antv/g6.git
synced 2024-12-15 10:01:09 +08:00
223 lines
8.4 KiB
Markdown
223 lines
8.4 KiB
Markdown
|
---
|
|||
|
title: Custom Layout
|
|||
|
order: 6
|
|||
|
---
|
|||
|
|
|||
|
G6 provides abundant commonly used built-in layouts for Graph and TreeGraph respectively. The usage can be found in: [Graph Layout](/en/docs/manual/middle/layout/graph-layout), [Tree Layout](/en/docs/manual/middle/layout/tree-graph-layout), [Graph Layout API](/en/docs/api/graphLayout/guide) or [TreeGraph Layout API](/en/docs/api/treeGraphLayout/guide). Custom layout mechanism of G6 allows the users to design their own type of layout to meet their special requirements.
|
|||
|
|
|||
|
<span style="background-color: rgb(251, 233, 231); color: rgb(139, 53, 56)"> <strong>⚠️Attention:</strong> </span> The TreeGraph does not support custom layout temporarily.
|
|||
|
|
|||
|
In this document, we will introduce the custom layout by registering a layout for Bigraph.
|
|||
|
|
|||
|
## The API of Cumstom Layout
|
|||
|
|
|||
|
```javascript
|
|||
|
/**
|
|||
|
* Register a Layout
|
|||
|
* @param {string} type The layout type is must assigned to an unique string
|
|||
|
* @param {object} layout The layout method
|
|||
|
*/
|
|||
|
Layout.registerLayout = function(type, {
|
|||
|
/**
|
|||
|
* The default configurations of the custom layout. It will be mixed by the configurations from users
|
|||
|
*/
|
|||
|
getDefaultCfg() {
|
|||
|
return {};
|
|||
|
},
|
|||
|
/**
|
|||
|
* Initialize
|
|||
|
* @param {object} data data
|
|||
|
*/
|
|||
|
init(data) {},
|
|||
|
/**
|
|||
|
* Execute the layout
|
|||
|
*/
|
|||
|
execute() {},
|
|||
|
/**
|
|||
|
* Layout with the data
|
|||
|
* @param {object} data 数据
|
|||
|
*/
|
|||
|
layout(data) {},
|
|||
|
/**
|
|||
|
* Update the layout configurations, but do not execute the layout
|
|||
|
* @param {object} cfg The new configurations
|
|||
|
*/
|
|||
|
updateCfg(cfg) {},
|
|||
|
/**
|
|||
|
* Destroy
|
|||
|
*/
|
|||
|
destroy() {},
|
|||
|
});
|
|||
|
```
|
|||
|
|
|||
|
## Custom Layout
|
|||
|
|
|||
|
Now, we are going to register a layout for Bigraph. Bigraph is the graph with nodes divided into two parts. There will be no edges between the nodes which are belong to the same part. In the custom layout, we sort the nodes according to their topology to reduce the edge crossings.<br />
|
|||
|
|
|||
|
<img src='https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*FksvTrdkqXgAAAAAAAAAAABkARQnAQ' alt='img' width='350'/>
|
|||
|
|
|||
|
The data of the Bigraph is shown below, where the nodes are divided into `'part1'` and `'part2'` by the property `cluster`.
|
|||
|
|
|||
|
```javascript
|
|||
|
const data = {
|
|||
|
nodes: [
|
|||
|
{ id: '0', label: 'A', cluster: 'part1' },
|
|||
|
{ id: '1', label: 'B', cluster: 'part1' },
|
|||
|
{ id: '2', label: 'C', cluster: 'part1' },
|
|||
|
{ id: '3', label: 'D', cluster: 'part1' },
|
|||
|
{ id: '4', label: 'E', cluster: 'part1' },
|
|||
|
{ id: '5', label: 'F', cluster: 'part1' },
|
|||
|
{ id: '6', label: 'a', cluster: 'part2' },
|
|||
|
{ id: '7', label: 'b', cluster: 'part2' },
|
|||
|
{ id: '8', label: 'c', cluster: 'part2' },
|
|||
|
{ id: '9', label: 'd', cluster: 'part2' },
|
|||
|
],
|
|||
|
edges: [
|
|||
|
{ source: '0', target: '6' },
|
|||
|
{ source: '0', target: '7' },
|
|||
|
{ source: '0', target: '9' },
|
|||
|
{ source: '1', target: '6' },
|
|||
|
{ source: '1', target: '9' },
|
|||
|
{ source: '1', target: '7' },
|
|||
|
{ source: '2', target: '8' },
|
|||
|
{ source: '2', target: '9' },
|
|||
|
{ source: '2', target: '6' },
|
|||
|
{ source: '3', target: '8' },
|
|||
|
{ source: '4', target: '6' },
|
|||
|
{ source: '4', target: '7' },
|
|||
|
{ source: '5', target: '9' },
|
|||
|
],
|
|||
|
};
|
|||
|
```
|
|||
|
|
|||
|
### Requirements Analysis
|
|||
|
|
|||
|
To reduce the edge crossings, we sort the nodes in `part1` and `part2` respectively. The process is:
|
|||
|
|
|||
|
- Step 1: Assign the index from 0 randomly for the nodes in `'part1'` and `'part2'` respectively;
|
|||
|
- Step 2: Traverse the nodes in `'part1'`. For each node A:
|
|||
|
- Find the set of related nodes of A (connect to A directly) in `'part2'` ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-WOhQIGg9l8AAAAAAAAAAABkARQnAQ). Sum up the indexes of the nodes in ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-WOhQIGg9l8AAAAAAAAAAABkARQnAQ), and divided it by the number of elements in ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-WOhQIGg9l8AAAAAAAAAAABkARQnAQ). Replace the index of A by the result: ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*VfXiSK1f02cAAAAAAAAAAABkARQnAQ)
|
|||
|
- Step 3: Tranverse the nodes in `'part2'`. For each node A(Similar to the Step 2):
|
|||
|
- Find the set of related nodes of B (connect to B directly) in `'part1'` ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*GqZiSKI-nB8AAAAAAAAAAABkARQnAQ). Sum up the indexes of the nodes in ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*GqZiSKI-nB8AAAAAAAAAAABkARQnAQ), and divided it by the number of elements in ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*GqZiSKI-nB8AAAAAAAAAAABkARQnAQ). Replace the index of A by the result: ![](https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*-8b2Spfb4HIAAAAAAAAAAABkARQnAQ)
|
|||
|
- Step 4: Sort the nodes in `part1` and `part2` respectively according to their indexed. The result order determine the postions of the nodes in the final layout.
|
|||
|
|
|||
|
## Implementation
|
|||
|
|
|||
|
The following code below register a layout named `'bigraph-layout'` for Bigraph. The complete code can be found in: <a href='/en/examples/net/layoutMechanism#customBigraph' target='_blank'>Cusom Layout-Bigraph</a>. The usage of custom layout is the same as built-in layouts: configure the `layout` to the graph when instantiating. Refer to: [Graph Layout](/en/docs/manual/middle/layout/graph-layout).
|
|||
|
|
|||
|
```javascript
|
|||
|
G6.registerLayout('bigraph-layout', {
|
|||
|
// Default configurations
|
|||
|
getDefaultCfg: function getDefaultCfg() {
|
|||
|
return {
|
|||
|
center: [0, 0], // The center of the layout
|
|||
|
biSep: 100, // The separation of these two parts
|
|||
|
nodeSep: 20, // The separation between nodes in the same part
|
|||
|
direction: 'horizontal', // The direction of the two parts
|
|||
|
nodeSize: 20, // The node size
|
|||
|
};
|
|||
|
},
|
|||
|
// Execute the layout
|
|||
|
execute: function execute() {
|
|||
|
var self = this;
|
|||
|
var center = self.center;
|
|||
|
var biSep = self.biSep;
|
|||
|
var nodeSep = self.nodeSep;
|
|||
|
var nodeSize = self.nodeSize;
|
|||
|
var part1Pos = 0,
|
|||
|
part2Pos = 0;
|
|||
|
// Layout the graph in horizontally
|
|||
|
if (self.direction === 'horizontal') {
|
|||
|
part1Pos = center[0] - biSep / 2;
|
|||
|
part2Pos = center[0] + biSep / 2;
|
|||
|
}
|
|||
|
var nodes = self.nodes;
|
|||
|
var edges = self.edges;
|
|||
|
var part1Nodes = [];
|
|||
|
var part2Nodes = [];
|
|||
|
var part1NodeMap = new Map();
|
|||
|
var part2NodeMap = new Map();
|
|||
|
// Separate the nodes and init the positions
|
|||
|
nodes.forEach(function (node, i) {
|
|||
|
if (node.cluster === 'part1') {
|
|||
|
part1Nodes.push(node);
|
|||
|
part1NodeMap.set(node.id, i);
|
|||
|
} else {
|
|||
|
part2Nodes.push(node);
|
|||
|
part2NodeMap.set(node.id, i);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Sort the nodes in part1
|
|||
|
part1Nodes.forEach(function (p1n) {
|
|||
|
var index = 0;
|
|||
|
var adjCount = 0;
|
|||
|
edges.forEach(function (edge) {
|
|||
|
var sourceId = edge.source;
|
|||
|
var targetId = edge.target;
|
|||
|
if (sourceId === p1n.id) {
|
|||
|
index += part2NodeMap.get(targetId);
|
|||
|
adjCount++;
|
|||
|
} else if (targetId === p1n.id) {
|
|||
|
index += part2NodeMap.get(sourceId);
|
|||
|
adjCount++;
|
|||
|
}
|
|||
|
});
|
|||
|
index /= adjCount;
|
|||
|
p1n.index = index;
|
|||
|
});
|
|||
|
part1Nodes.sort(function (a, b) {
|
|||
|
return a.index - b.index;
|
|||
|
});
|
|||
|
|
|||
|
// Sort the nodes in part2
|
|||
|
part2Nodes.forEach(function (p2n) {
|
|||
|
var index = 0;
|
|||
|
var adjCount = 0;
|
|||
|
edges.forEach(function (edge) {
|
|||
|
var sourceId = edge.source;
|
|||
|
var targetId = edge.target;
|
|||
|
if (sourceId === p2n.id) {
|
|||
|
index += part1NodeMap.get(targetId);
|
|||
|
adjCount++;
|
|||
|
} else if (targetId === p2n.id) {
|
|||
|
index += part1NodeMap.get(sourceId);
|
|||
|
adjCount++;
|
|||
|
}
|
|||
|
});
|
|||
|
index /= adjCount;
|
|||
|
p2n.index = index;
|
|||
|
});
|
|||
|
part2Nodes.sort(function (a, b) {
|
|||
|
return a.index - b.index;
|
|||
|
});
|
|||
|
|
|||
|
// Place the ndoes
|
|||
|
var hLength = part1Nodes.length > part2Nodes.length ? part1Nodes.length : part2Nodes.length;
|
|||
|
var height = hLength * (nodeSep + nodeSize);
|
|||
|
var begin = center[1] - height / 2;
|
|||
|
if (self.direction === 'vertical') {
|
|||
|
begin = center[0] - height / 2;
|
|||
|
}
|
|||
|
part1Nodes.forEach(function (p1n, i) {
|
|||
|
if (self.direction === 'horizontal') {
|
|||
|
p1n.x = part1Pos;
|
|||
|
p1n.y = begin + i * (nodeSep + nodeSize);
|
|||
|
} else {
|
|||
|
p1n.x = begin + i * (nodeSep + nodeSize);
|
|||
|
p1n.y = part1Pos;
|
|||
|
}
|
|||
|
});
|
|||
|
part2Nodes.forEach(function (p2n, i) {
|
|||
|
if (self.direction === 'horizontal') {
|
|||
|
p2n.x = part2Pos;
|
|||
|
p2n.y = begin + i * (nodeSep + nodeSize);
|
|||
|
} else {
|
|||
|
p2n.x = begin + i * (nodeSep + nodeSize);
|
|||
|
p2n.y = part2Pos;
|
|||
|
}
|
|||
|
});
|
|||
|
},
|
|||
|
});
|
|||
|
```
|