@antv/x6
Version:
JavaScript diagramming library that uses SVG and HTML for rendering.
205 lines • 7.78 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ForceDirected = void 0;
var common_1 = require("../common");
var util_1 = require("../util");
var ForceDirected = /** @class */ (function (_super) {
__extends(ForceDirected, _super);
function ForceDirected(options) {
var _this = _super.call(this) || this;
_this.options = __assign({ charge: 10, edgeDistance: 10, edgeStrength: 1 }, options);
_this.nodes = _this.model.getNodes();
_this.edges = _this.model.getEdges();
_this.t = 1;
_this.energy = Infinity;
_this.progress = 0;
return _this;
}
Object.defineProperty(ForceDirected.prototype, "model", {
get: function () {
return this.options.model;
},
enumerable: false,
configurable: true
});
ForceDirected.prototype.start = function () {
var _this = this;
var x = this.options.x;
var y = this.options.y;
var width = this.options.width;
var height = this.options.height;
this.nodeData = {};
this.edgeData = {};
this.nodes.forEach(function (node) {
var posX = util_1.NumberExt.random(x, x + width);
var posY = util_1.NumberExt.random(y, y + height);
node.position(posX, posY, { forceDirected: true });
_this.nodeData[node.id] = {
charge: node.prop('charge') || _this.options.charge,
weight: node.prop('weight') || 1,
x: posX,
y: posY,
px: posX,
py: posY,
fx: 0,
fy: 0,
};
});
this.edges.forEach(function (edge) {
_this.edgeData[edge.id] = {
source: edge.getSourceCell(),
target: edge.getTargetCell(),
strength: edge.prop('strength') || _this.options.edgeStrength,
distance: edge.prop('distance') || _this.options.edgeDistance,
};
});
};
ForceDirected.prototype.step = function () {
if (0.99 * this.t < 0.005) {
return this.notifyEnd();
}
this.energy = 0;
var xBefore = 0;
var yBefore = 0;
var xAfter = 0;
var yAfter = 0;
var nodeCount = this.nodes.length;
var edgeCount = this.edges.length;
for (var i = 0; i < nodeCount - 1; i += 1) {
var v = this.nodeData[this.nodes[i].id];
xBefore += v.x;
yBefore += v.y;
for (var j = i + 1; j < nodeCount; j += 1) {
var u = this.nodeData[this.nodes[j].id];
var dx = u.x - v.x;
var dy = u.y - v.y;
var distanceSquared = dx * dx + dy * dy;
// const distance = Math.sqrt(distanceSquared)
var fr = (this.t * v.charge) / distanceSquared;
var fx = fr * dx;
var fy = fr * dy;
v.fx -= fx;
v.fy -= fy;
u.fx += fx;
u.fy += fy;
this.energy += fx * fx + fy * fy;
}
}
// Add the last node positions as it couldn't be done in the loops above.
var last = this.nodeData[this.nodes[nodeCount - 1].id];
xBefore += last.x;
yBefore += last.y;
// Calculate attractive forces.
for (var i = 0; i < edgeCount; i += 1) {
var a = this.edgeData[this.edges[i].id];
var v = this.nodeData[a.source.id];
var u = this.nodeData[a.target.id];
var dx = u.x - v.x;
var dy = u.y - v.y;
var distanceSquared = dx * dx + dy * dy;
var distance = Math.sqrt(distanceSquared);
var fa = (this.t * a.strength * (distance - a.distance)) / distance;
var fx = fa * dx;
var fy = fa * dy;
var k = v.weight / (v.weight + u.weight);
v.x += fx * (1 - k);
v.y += fy * (1 - k);
u.x -= fx * k;
u.y -= fy * k;
this.energy += fx * fx + fy * fy;
}
var x = this.options.x;
var y = this.options.y;
var w = this.options.width;
var h = this.options.height;
var gravityCenter = this.options.gravityCenter;
var gravity = 0.1;
var energyBefore = this.energy;
// Set positions on nodes.
for (var i = 0; i < nodeCount; i += 1) {
var node = this.nodes[i];
var data = this.nodeData[node.id];
var pos = {
x: data.x,
y: data.y,
};
if (gravityCenter) {
pos.x += (gravityCenter.x - pos.x) * this.t * gravity;
pos.y += (gravityCenter.y - pos.y) * this.t * gravity;
}
pos.x += data.fx;
pos.y += data.fy;
// Make sure positions don't go out of the graph area.
pos.x = Math.max(x, Math.min(x + w, pos.x));
pos.y = Math.max(y, Math.min(x + h, pos.y));
// Position Verlet integration.
var friction = 0.9;
pos.x += friction * (data.px - pos.x);
pos.y += friction * (data.py - pos.y);
data.px = pos.x;
data.py = pos.y;
data.fx = 0;
data.fy = 0;
data.x = pos.x;
data.y = pos.y;
xAfter += data.x;
yAfter += data.y;
node.setPosition(pos, { forceDirected: true });
}
this.t = this.cool(this.t, this.energy, energyBefore);
// If the global distance hasn't change much, the layout converged
// and therefore trigger the `end` event.
var gdx = xBefore - xAfter;
var gdy = yBefore - yAfter;
var gd = Math.sqrt(gdx * gdx + gdy * gdy);
if (gd < 1) {
this.notifyEnd();
}
};
ForceDirected.prototype.cool = function (t, energy, energyBefore) {
if (energy < energyBefore) {
this.progress += 1;
if (this.progress >= 5) {
this.progress = 0;
return t / 0.99; // Warm up.
}
}
else {
this.progress = 0;
return t * 0.99; // Cool down.
}
return t; // Keep the same temperature.
};
ForceDirected.prototype.notifyEnd = function () {
this.trigger('end');
};
return ForceDirected;
}(common_1.Events));
exports.ForceDirected = ForceDirected;
//# sourceMappingURL=force-directed.js.map