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.
 
 
 
 

173 lines
6.1 KiB

import { __awaiter } from "tslib";
import { deepMix, pick } from '@antv/util';
import { forceCenter, forceCollide, forceLink, forceManyBody, forceRadial, forceSimulation, forceX, forceY, } from 'd3-force';
export class D3ForceLayout {
constructor(options) {
this.id = 'd3-force';
this.config = {
inputNodeAttrs: ['x', 'y', 'vx', 'vy', 'fx', 'fy'],
outputNodeAttrs: ['x', 'y', 'vx', 'vy'],
simulationAttrs: [
'alpha',
'alphaMin',
'alphaDecay',
'alphaTarget',
'velocityDecay',
'randomSource',
],
};
this.forceMap = {
link: forceLink,
manyBody: forceManyBody,
center: forceCenter,
collide: forceCollide,
radial: forceRadial,
x: forceX,
y: forceY,
};
// @ts-ignore
this.options = {
link: {
id: (edge) => edge.id,
},
manyBody: {},
center: {
x: 0,
y: 0,
},
};
this.context = {
options: {},
assign: false,
nodes: [],
edges: [],
};
deepMix(this.options, options);
if (this.options.forceSimulation) {
this.simulation = this.options.forceSimulation;
}
}
execute(graph, options) {
return __awaiter(this, void 0, void 0, function* () {
return this.genericLayout(false, graph, options);
});
}
assign(graph, options) {
return __awaiter(this, void 0, void 0, function* () {
yield this.genericLayout(true, graph, options);
});
}
stop() {
this.simulation.stop();
}
tick(iterations) {
this.simulation.tick(iterations);
return this.getResult();
}
restart() {
this.simulation.restart();
}
setFixedPosition(id, position) {
const node = this.context.nodes.find((n) => n.id === id);
if (!node)
return;
position.forEach((value, index) => {
if (typeof value === 'number' || value === null) {
const key = ['fx', 'fy', 'fz'][index];
node[key] = value;
}
});
}
getOptions(options) {
var _a, _b;
const _ = deepMix({}, this.options, options);
// process nodeSize
if (_.collide && ((_a = _.collide) === null || _a === void 0 ? void 0 : _a.radius) === undefined) {
_.collide = _.collide || {};
// @ts-ignore
_.collide.radius = (_b = _.nodeSize) !== null && _b !== void 0 ? _b : 10;
}
// process iterations
if (_.iterations === undefined) {
if (_.link && _.link.iterations === undefined) {
_.iterations = _.link.iterations;
}
if (_.collide && _.collide.iterations === undefined) {
_.iterations = _.collide.iterations;
}
}
// assign to context
this.context.options = _;
return _;
}
genericLayout(assign, graph, options) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const _options = this.getOptions(options);
const nodes = graph.getAllNodes().map(({ id, data }) => (Object.assign(Object.assign({ id }, data), pick(data.data, this.config.inputNodeAttrs))));
const edges = graph.getAllEdges().map((edge) => (Object.assign({}, edge)));
Object.assign(this.context, { assign, nodes, edges, graph });
const promise = new Promise((resolver) => {
this.resolver = resolver;
});
const simulation = this.setSimulation(_options);
simulation.nodes(nodes);
(_a = simulation.force('link')) === null || _a === void 0 ? void 0 : _a.links(edges);
return promise;
});
}
getResult() {
const { assign, nodes, edges, graph } = this.context;
const nodesResult = nodes.map((node) => ({
id: node.id,
data: Object.assign(Object.assign({}, node.data), pick(node, this.config.outputNodeAttrs)),
}));
const edgeResult = edges.map(({ id, source, target, data }) => ({
id,
source: typeof source === 'object' ? source.id : source,
target: typeof target === 'object' ? target.id : target,
data,
}));
if (assign) {
nodesResult.forEach((node) => graph.mergeNodeData(node.id, node.data));
}
return { nodes: nodesResult, edges: edgeResult };
}
initSimulation() {
return forceSimulation();
}
setSimulation(options) {
const simulation = this.simulation || this.options.forceSimulation || this.initSimulation();
if (!this.simulation) {
this.simulation = simulation
.on('tick', () => { var _a; return (_a = options.onTick) === null || _a === void 0 ? void 0 : _a.call(options, this.getResult()); })
.on('end', () => { var _a; return (_a = this.resolver) === null || _a === void 0 ? void 0 : _a.call(this, this.getResult()); });
}
apply(simulation, this.config.simulationAttrs.map((name) => [
name,
options[name],
]));
Object.entries(this.forceMap).forEach(([name, Ctor]) => {
const forceName = name;
if (options[name]) {
let force = simulation.force(forceName);
if (!force) {
force = Ctor();
simulation.force(forceName, force);
}
apply(force, Object.entries(options[forceName]));
}
else
simulation.force(forceName, null);
});
return simulation;
}
}
const apply = (target, params) => {
return params.reduce((acc, [method, param]) => {
if (!acc[method] || param === undefined)
return acc;
return acc[method].call(target, param);
}, target);
};
//# sourceMappingURL=index.js.map