UNPKG

highcharts

Version:
190 lines (189 loc) 5.68 kB
/* * * * Networkgraph series * * (c) 2010-2025 Paweł Fus * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ 'use strict'; /* * * * Functions * * */ /** * Attractive force. * * In Verlet integration, force is applied on a node immediately to it's * `plotX` and `plotY` position. * * @private * @param {Highcharts.Point} link * Link that connects two nodes * @param {number} force * Force calculated in `repulsiveForceFunction` * @param {Highcharts.PositionObject} distanceXY * Distance between two nodes e.g. `{x, y}` */ function attractive(link, force, distanceXY) { const massFactor = link.getMass(), translatedX = -distanceXY.x * force * this.diffTemperature, translatedY = -distanceXY.y * force * this.diffTemperature; if (!link.fromNode.fixedPosition) { link.fromNode.plotX -= translatedX * massFactor.fromNode / link.fromNode.degree; link.fromNode.plotY -= translatedY * massFactor.fromNode / link.fromNode.degree; } if (!link.toNode.fixedPosition) { link.toNode.plotX += translatedX * massFactor.toNode / link.toNode.degree; link.toNode.plotY += translatedY * massFactor.toNode / link.toNode.degree; } } /** * Attractive force function. Can be replaced by API's * `layoutAlgorithm.attractiveForce` * * @private * @param {number} d current distance between two nodes * @param {number} k expected distance between two nodes * @return {number} force */ function attractiveForceFunction(d, k) { // Used in API: return (k - d) / d; } /** * Barycenter force. Calculate and applys barycenter forces on the * nodes. Making them closer to the center of their barycenter point. * * In Verlet integration, force is applied on a node immediately to it's * `plotX` and `plotY` position. * * @private */ function barycenter() { const gravitationalConstant = this.options.gravitationalConstant || 0, xFactor = (this.barycenter.xFactor - (this.box.left + this.box.width) / 2) * gravitationalConstant, yFactor = (this.barycenter.yFactor - (this.box.top + this.box.height) / 2) * gravitationalConstant; this.nodes.forEach(function (node) { if (!node.fixedPosition) { node.plotX -= xFactor / node.mass / node.degree; node.plotY -= yFactor / node.mass / node.degree; } }); } /** * Estiamte the best possible distance between two nodes, making graph * readable. * @private */ function getK(layout) { return Math.pow(layout.box.width * layout.box.height / layout.nodes.length, 0.5); } /** * Integration method. * * In Verlet integration, forces are applied on node immediately to it's * `plotX` and `plotY` position. * * Verlet without velocity: * * x(n+1) = 2 * x(n) - x(n-1) + A(T) * deltaT ^ 2 * * where: * - x(n+1) - new position * - x(n) - current position * - x(n-1) - previous position * * Assuming A(t) = 0 (no acceleration) and (deltaT = 1) we get: * * x(n+1) = x(n) + (x(n) - x(n-1)) * * where: * - (x(n) - x(n-1)) - position change * * TO DO: * Consider Verlet with velocity to support additional * forces. Or even Time-Corrected Verlet by Jonathan * "lonesock" Dummer * * @private * @param {Highcharts.NetworkgraphLayout} layout layout object * @param {Highcharts.Point} node node that should be translated */ function integrate(layout, node) { const friction = -layout.options.friction, maxSpeed = layout.options.maxSpeed, prevX = node.prevX, prevY = node.prevY, // Apply friction: frictionX = ((node.plotX + node.dispX - prevX) * friction), frictionY = ((node.plotY + node.dispY - prevY) * friction), abs = Math.abs, signX = abs(frictionX) / (frictionX || 1), // Need to deal with 0 signY = abs(frictionY) / (frictionY || 1), // Apply max speed: diffX = signX * Math.min(maxSpeed, Math.abs(frictionX)), diffY = signY * Math.min(maxSpeed, Math.abs(frictionY)); // Store for the next iteration: node.prevX = node.plotX + node.dispX; node.prevY = node.plotY + node.dispY; // Update positions: node.plotX += diffX; node.plotY += diffY; node.temperature = layout.vectorLength({ x: diffX, y: diffY }); } /** * Repulsive force. * * In Verlet integration, force is applied on a node immediately to it's * `plotX` and `plotY` position. * * @private * @param {Highcharts.Point} node * Node that should be translated by force. * @param {number} force * Force calculated in `repulsiveForceFunction` * @param {Highcharts.PositionObject} distanceXY * Distance between two nodes e.g. `{x, y}` */ function repulsive(node, force, distanceXY) { const factor = force * this.diffTemperature / node.mass / node.degree; if (!node.fixedPosition) { node.plotX += distanceXY.x * factor; node.plotY += distanceXY.y * factor; } } /** * Repulsive force function. Can be replaced by API's * `layoutAlgorithm.repulsiveForce` * * @private * @param {number} d current distance between two nodes * @param {number} k expected distance between two nodes * @return {number} force */ function repulsiveForceFunction(d, k) { // Used in API: return (k - d) / d * (k > d ? 1 : 0); // Force only for close nodes } /* * * * Default Export * * */ const VerletIntegration = { attractive, attractiveForceFunction, barycenter, getK, integrate, repulsive, repulsiveForceFunction }; export default VerletIntegration;