--- 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.   ⚠️Attention: 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.
img 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: Cusom Layout-Bigraph. 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; } }); }, }); ```