import { clone } from '@antv/util'; import type { ID } from '../../../types'; import { DagreGraph } from '../graph'; import { buildLayerMatrix, maxRank } from '../util'; import { addSubgraphConstraints } from './add-subgraph-constraints'; import { buildLayerGraph } from './build-layer-graph'; import { crossCount } from './cross-count'; import { initOrder } from './init-order'; import { sortSubgraph } from './sort-subgraph'; /* * Applies heuristics to minimize edge crossings in the graph and sets the best * order solution as an order attribute on each node. * * Pre-conditions: * * 1. Graph must be DAG * 2. Graph nodes must be objects with a "rank" attribute * 3. Graph edges must have the "weight" attribute * * Post-conditions: * * 1. Graph nodes will have an "order" attribute based on the results of the * algorithm. */ export const order = (g: DagreGraph, keepNodeOrder?: boolean) => { const mxRank = maxRank(g); const range1 = []; const range2 = []; for (let i = 1; i < mxRank + 1; i++) range1.push(i); for (let i = mxRank - 1; i > -1; i--) range2.push(i); const downLayerGraphs = buildLayerGraphs(g, range1, 'in'); const upLayerGraphs = buildLayerGraphs(g, range2, 'out'); let layering = initOrder(g); assignOrder(g, layering); let bestCC = Number.POSITIVE_INFINITY; let best: string[][]; for (let i = 0, lastBest = 0; lastBest < 4; ++i, ++lastBest) { sweepLayerGraphs( i % 2 ? downLayerGraphs : upLayerGraphs, i % 4 >= 2, false, keepNodeOrder, ); layering = buildLayerMatrix(g); const cc = crossCount(g, layering); if (cc < bestCC) { lastBest = 0; best = clone(layering); bestCC = cc; } } // consider use previous result, maybe somewhat reduendant layering = initOrder(g); assignOrder(g, layering); for (let i = 0, lastBest = 0; lastBest < 4; ++i, ++lastBest) { sweepLayerGraphs( i % 2 ? downLayerGraphs : upLayerGraphs, i % 4 >= 2, true, keepNodeOrder, ); layering = buildLayerMatrix(g); const cc = crossCount(g, layering); if (cc < bestCC) { lastBest = 0; best = clone(layering); bestCC = cc; } } assignOrder(g, best!); }; const buildLayerGraphs = ( g: DagreGraph, ranks: number[], direction: 'in' | 'out', ) => { return ranks.map((rank) => { return buildLayerGraph(g, rank, direction); }); }; const sweepLayerGraphs = ( layerGraphs: DagreGraph[], biasRight: boolean, usePrev?: boolean, keepNodeOrder?: boolean, ) => { const cg = new DagreGraph(); layerGraphs?.forEach((lg) => { // const root = lg.graph().root as string; const root = lg.getRoots()[0].id; const sorted = sortSubgraph( lg, root, cg, biasRight, usePrev, keepNodeOrder, ); for (let i = 0; i < sorted.vs?.length || 0; i++) { const lnode = lg.getNode(sorted.vs[i]); if (lnode) { lnode.data.order = i; } } addSubgraphConstraints(lg, cg, sorted.vs); }); }; const assignOrder = (g: DagreGraph, layering: ID[][]) => { layering?.forEach((layer) => { layer?.forEach((v: ID, i: number) => { g.getNode(v).data.order = i; }); }); };