You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

281 lines
8.4 KiB

import { Graph } from '@antv/graphlib';
import { isNumber } from '@antv/util';
const safeSort = (valueA, valueB) => {
return Number(valueA) - Number(valueB);
};
/*
* Adds a dummy node to the graph and return v.
*/
export const addDummyNode = (g, type, data, name) => {
let v;
do {
v = `${name}${Math.random()}`;
} while (g.hasNode(v));
data.dummy = type;
g.addNode({
id: v,
data,
});
return v;
};
/*
* Returns a new graph with only simple edges. Handles aggregation of data
* associated with multi-edges.
*/
export const simplify = (g) => {
const simplified = new Graph();
g.getAllNodes().forEach((v) => {
simplified.addNode(Object.assign({}, v));
});
g.getAllEdges().forEach((e) => {
const edge = simplified
.getRelatedEdges(e.source, 'out')
.find((edge) => edge.target === e.target);
if (!edge) {
simplified.addEdge({
id: e.id,
source: e.source,
target: e.target,
data: {
weight: e.data.weight || 0,
minlen: e.data.minlen || 1,
},
});
}
else {
simplified.updateEdgeData(edge === null || edge === void 0 ? void 0 : edge.id, Object.assign(Object.assign({}, edge.data), { weight: edge.data.weight + e.data.weight || 0, minlen: Math.max(edge.data.minlen, e.data.minlen || 1) }));
}
});
return simplified;
};
export const asNonCompoundGraph = (g) => {
const simplified = new Graph();
g.getAllNodes().forEach((node) => {
if (!g.getChildren(node.id).length) {
simplified.addNode(Object.assign({}, node));
}
});
g.getAllEdges().forEach((edge) => {
simplified.addEdge(edge);
});
return simplified;
};
export const zipObject = (keys, values) => {
return keys === null || keys === void 0 ? void 0 : keys.reduce((obj, key, i) => {
obj[key] = values[i];
return obj;
}, {});
};
export const successorWeights = (g) => {
const weightsMap = {};
g.getAllNodes().forEach((node) => {
const sucs = {};
g.getRelatedEdges(node.id, 'out').forEach((e) => {
sucs[e.target] = (sucs[e.target] || 0) + (e.data.weight || 0);
});
weightsMap[node.id] = sucs;
});
return weightsMap;
};
export const predecessorWeights = (g) => {
const nodes = g.getAllNodes();
const weightMap = nodes.map((v) => {
const preds = {};
g.getRelatedEdges(v.id, 'in').forEach((e) => {
preds[e.source] = (preds[e.source] || 0) + e.data.weight;
});
return preds;
});
return zipObject(nodes.map((n) => n.id), weightMap);
};
/*
* Finds where a line starting at point ({x, y}) would intersect a rectangle
* ({x, y, width, height}) if it were pointing at the rectangle's center.
*/
export const intersectRect = (rect, point) => {
const x = Number(rect.x);
const y = Number(rect.y);
// Rectangle intersection algorithm from:
// http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
const dx = Number(point.x) - x;
const dy = Number(point.y) - y;
let w = Number(rect.width) / 2;
let h = Number(rect.height) / 2;
if (!dx && !dy) {
// completely overlapped directly, then return points its self
return { x: 0, y: 0 };
}
let sx;
let sy;
if (Math.abs(dy) * w > Math.abs(dx) * h) {
// Intersection is top or bottom of rect.
if (dy < 0) {
h = -h;
}
sx = (h * dx) / dy;
sy = h;
}
else {
// Intersection is left or right of rect.
if (dx < 0) {
w = -w;
}
sx = w;
sy = (w * dy) / dx;
}
return { x: x + sx, y: y + sy };
};
/*
* Given a DAG with each node assigned "rank" and "order" properties, this
* const will produce a matrix with the ids of each node.
*/
export const buildLayerMatrix = (g) => {
const layeringNodes = [];
const rankMax = maxRank(g) + 1;
for (let i = 0; i < rankMax; i++) {
layeringNodes.push([]);
}
// const layering = _.map(_.range(maxRank(g) + 1), function() { return []; });
g.getAllNodes().forEach((node) => {
const rank = node.data.rank;
if (rank !== undefined && layeringNodes[rank]) {
layeringNodes[rank].push(node.id);
}
});
for (let i = 0; i < rankMax; i++) {
layeringNodes[i] = layeringNodes[i].sort((va, vb) => safeSort(g.getNode(va).data.order, g.getNode(vb).data.order));
}
return layeringNodes;
};
/*
* Adjusts the ranks for all nodes in the graph such that all nodes v have
* rank(v) >= 0 and at least one node w has rank(w) = 0.
*/
export const normalizeRanks = (g) => {
const nodeRanks = g
.getAllNodes()
.filter((v) => v.data.rank !== undefined)
.map((v) => v.data.rank);
const min = Math.min(...nodeRanks);
g.getAllNodes().forEach((v) => {
if (v.data.hasOwnProperty('rank') && min !== Infinity) {
v.data.rank -= min;
}
});
};
export const removeEmptyRanks = (g, nodeRankFactor = 0) => {
// Ranks may not start at 0, so we need to offset them
const nodes = g.getAllNodes();
const nodeRanks = nodes
.filter((v) => v.data.rank !== undefined)
.map((v) => v.data.rank);
const offset = Math.min(...nodeRanks);
const layers = [];
nodes.forEach((v) => {
const rank = (v.data.rank || 0) - offset;
if (!layers[rank]) {
layers[rank] = [];
}
layers[rank].push(v.id);
});
let delta = 0;
for (let i = 0; i < layers.length; i++) {
const vs = layers[i];
if (vs === undefined) {
if (i % nodeRankFactor !== 0) {
delta -= 1;
}
}
else if (delta) {
vs === null || vs === void 0 ? void 0 : vs.forEach((v) => {
const node = g.getNode(v);
if (node) {
node.data.rank = node.data.rank || 0;
node.data.rank += delta;
}
});
}
}
};
export const addBorderNode = (g, prefix, rank, order) => {
const node = {
width: 0,
height: 0,
};
if (isNumber(rank) && isNumber(order)) {
node.rank = rank;
node.order = order;
}
return addDummyNode(g, 'border', node, prefix);
};
export const maxRank = (g) => {
let maxRank;
g.getAllNodes().forEach((v) => {
const rank = v.data.rank;
if (rank !== undefined) {
if (maxRank === undefined || rank > maxRank) {
maxRank = rank;
}
}
});
if (!maxRank) {
maxRank = 0;
}
return maxRank;
};
/*
* Partition a collection into two groups: `lhs` and `rhs`. If the supplied
* const returns true for an entry it goes into `lhs`. Otherwise it goes
* into `rhs.
*/
export const partition = (collection, fn) => {
const result = { lhs: [], rhs: [] };
collection === null || collection === void 0 ? void 0 : collection.forEach((value) => {
if (fn(value)) {
result.lhs.push(value);
}
else {
result.rhs.push(value);
}
});
return result;
};
export const minBy = (array, func) => {
return array.reduce((a, b) => {
const valA = func(a);
const valB = func(b);
return valA > valB ? b : a;
});
};
const doDFS = (graph, node, postorder, visited, navigator, result) => {
if (!visited.includes(node.id)) {
visited.push(node.id);
if (!postorder) {
result.push(node.id);
}
navigator(node.id).forEach((n) => doDFS(graph, n, postorder, visited, navigator, result));
if (postorder) {
result.push(node.id);
}
}
};
/**
* @description DFS traversal.
* @description.zh-CN DFS 遍历。
*/
export const dfs = (graph, node, order, isDirected) => {
const nodes = Array.isArray(node) ? node : [node];
const navigator = (n) => (isDirected ? graph.getSuccessors(n) : graph.getNeighbors(n));
const results = [];
const visited = [];
nodes.forEach((node) => {
if (!graph.hasNode(node.id)) {
throw new Error(`Graph does not have node: ${node}`);
}
else {
doDFS(graph, node, order === 'post', visited, navigator, results);
}
});
return results;
};
//# sourceMappingURL=util.js.map