@graphty/layout
Version:
graph layout algorithms based on networkx
94 lines (79 loc) • 2.76 kB
text/typescript
/**
* Bipartite layout algorithm
*/
import { Graph, Node, PositionMap } from '../../types';
import { _processParams } from '../../utils/params';
import { getNodesFromGraph } from '../../utils/graph';
import { rescaleLayout } from '../../utils/rescale';
/**
* Position nodes in two straight lines (bipartite layout).
*
* @param G - Graph or list of nodes
* @param nodes - Nodes in one node set of the graph
* @param align - The alignment of nodes: 'vertical' or 'horizontal'
* @param scale - Scale factor for positions
* @param center - Coordinate pair around which to center the layout
* @param aspectRatio - The ratio of the width to the height of the layout
* @returns Positions dictionary keyed by node
*/
export function bipartiteLayout(
G: Graph,
nodes: Node[] | null = null,
align: 'vertical' | 'horizontal' = 'vertical',
scale: number = 1,
center: number[] | null = null,
aspectRatio: number = 4 / 3
): PositionMap {
if (align !== 'vertical' && align !== 'horizontal') {
throw new Error("align must be either vertical or horizontal");
}
const processed = _processParams(G, center || [0, 0], 2);
const graph = processed.G;
center = processed.center;
const allNodes = getNodesFromGraph(graph);
if (allNodes.length === 0) {
return {};
}
// If nodes not provided, try to determine bipartite sets
if (!nodes) {
// A simple heuristic for bipartite detection: use nodes with even/odd indices
// This is a simplification, in Python NetworkX has bipartite.sets()
nodes = allNodes.filter((_: Node, i: number): boolean => i % 2 === 0);
}
const left = new Set(nodes);
const right: Set<Node> = new Set(allNodes.filter((n: Node) => !left.has(n)));
const height = 1;
const width = aspectRatio * height;
const offset = [width / 2, height / 2];
const pos: PositionMap = {};
// Position nodes in the left set
const leftNodes = [...left];
leftNodes.forEach((node, i) => {
const x = 0;
const y = i * height / (leftNodes.length || 1);
pos[node] = [x, y];
});
// Position nodes in the right set
const rightNodes = [...right];
rightNodes.forEach((node, i) => {
const x = width;
const y = i * height / (rightNodes.length || 1);
pos[node] = [x, y];
});
// Center positions around the origin and apply offset
for (const node in pos) {
pos[node][0] -= offset[0];
pos[node][1] -= offset[1];
}
// Rescale positions
const scaledPos = rescaleLayout(pos, scale, center) as PositionMap;
// Handle horizontal alignment
if (align === 'horizontal') {
for (const node in scaledPos) {
const temp = scaledPos[node][0];
scaledPos[node][0] = scaledPos[node][1];
scaledPos[node][1] = temp;
}
}
return scaledPos;
}