| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- /* *
- *
- * Networkgraph series
- *
- * (c) 2010-2020 Paweł Fus
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- 'use strict';
- import H from '../../Core/Globals.js';
- /* eslint-disable no-invalid-this, valid-jsdoc */
- H.networkgraphIntegrations = {
- verlet: {
- /**
- * Attractive force funtion. 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
- */
- attractiveForceFunction: function (d, k) {
- // Used in API:
- return (k - d) / d;
- },
- /**
- * Repulsive force funtion. 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
- */
- repulsiveForceFunction: function (d, k) {
- // Used in API:
- return (k - d) / d * (k > d ? 1 : 0); // Force only for close nodes
- },
- /**
- * 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 immidatelly to it's
- * `plotX` and `plotY` position.
- *
- * @private
- * @return {void}
- */
- barycenter: function () {
- var gravitationalConstant = this.options.gravitationalConstant, xFactor = this.barycenter.xFactor, yFactor = this.barycenter.yFactor;
- // To consider:
- xFactor = (xFactor - (this.box.left + this.box.width) / 2) *
- gravitationalConstant;
- yFactor = (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;
- }
- });
- },
- /**
- * Repulsive force.
- *
- * In Verlet integration, force is applied on a node immidatelly to it's
- * `plotX` and `plotY` position.
- *
- * @private
- * @param {Highcharts.Point} node
- * Node that should be translated by force.
- * @param {number} force
- * Force calcualated in `repulsiveForceFunction`
- * @param {Highcharts.PositionObject} distance
- * Distance between two nodes e.g. `{x, y}`
- * @return {void}
- */
- repulsive: function (node, force, distanceXY) {
- var factor = force * this.diffTemperature / node.mass / node.degree;
- if (!node.fixedPosition) {
- node.plotX += distanceXY.x * factor;
- node.plotY += distanceXY.y * factor;
- }
- },
- /**
- * Attractive force.
- *
- * In Verlet integration, force is applied on a node immidatelly to it's
- * `plotX` and `plotY` position.
- *
- * @private
- * @param {Highcharts.Point} link
- * Link that connects two nodes
- * @param {number} force
- * Force calcualated in `repulsiveForceFunction`
- * @param {Highcharts.PositionObject} distance
- * Distance between two nodes e.g. `{x, y}`
- * @return {void}
- */
- attractive: function (link, force, distanceXY) {
- var 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;
- }
- },
- /**
- * Integration method.
- *
- * In Verlet integration, forces are applied on node immidatelly 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
- * @return {void}
- */
- integrate: function (layout, node) {
- var friction = -layout.options.friction, maxSpeed = layout.options.maxSpeed, prevX = node.prevX, prevY = node.prevY,
- // Apply friciton:
- diffX = ((node.plotX + node.dispX -
- prevX) * friction), diffY = ((node.plotY + node.dispY -
- prevY) * friction), abs = Math.abs, signX = abs(diffX) / (diffX || 1), // need to deal with 0
- signY = abs(diffY) / (diffY || 1);
- // Apply max speed:
- diffX = signX * Math.min(maxSpeed, Math.abs(diffX));
- diffY = signY * Math.min(maxSpeed, Math.abs(diffY));
- // 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
- });
- },
- /**
- * Estiamte the best possible distance between two nodes, making graph
- * readable.
- *
- * @private
- * @param {Highcharts.NetworkgraphLayout} layout layout object
- * @return {number}
- */
- getK: function (layout) {
- return Math.pow(layout.box.width * layout.box.height / layout.nodes.length, 0.5);
- }
- },
- euler: {
- /**
- * Attractive force funtion. Can be replaced by API's
- * `layoutAlgorithm.attractiveForce`
- *
- * Other forces that can be used:
- *
- * basic, not recommended:
- * `function (d, k) { return d / k }`
- *
- * @private
- * @param {number} d current distance between two nodes
- * @param {number} k expected distance between two nodes
- * @return {number} force
- */
- attractiveForceFunction: function (d, k) {
- return d * d / k;
- },
- /**
- * Repulsive force funtion. Can be replaced by API's
- * `layoutAlgorithm.repulsiveForce`.
- *
- * Other forces that can be used:
- *
- * basic, not recommended:
- * `function (d, k) { return k / d }`
- *
- * standard:
- * `function (d, k) { return k * k / d }`
- *
- * grid-variant:
- * `function (d, k) { return k * k / d * (2 * k - d > 0 ? 1 : 0) }`
- *
- * @private
- * @param {number} d current distance between two nodes
- * @param {number} k expected distance between two nodes
- * @return {number} force
- */
- repulsiveForceFunction: function (d, k) {
- return k * k / d;
- },
- /**
- * Barycenter force. Calculate and applys barycenter forces on the
- * nodes. Making them closer to the center of their barycenter point.
- *
- * In Euler integration, force is stored in a node, not changing it's
- * position. Later, in `integrate()` forces are applied on nodes.
- *
- * @private
- * @return {void}
- */
- barycenter: function () {
- var gravitationalConstant = this.options.gravitationalConstant, xFactor = this.barycenter.xFactor, yFactor = this.barycenter.yFactor;
- this.nodes.forEach(function (node) {
- if (!node.fixedPosition) {
- var degree = node.getDegree(), phi = degree * (1 + degree / 2);
- node.dispX += ((xFactor - node.plotX) *
- gravitationalConstant *
- phi / node.degree);
- node.dispY += ((yFactor - node.plotY) *
- gravitationalConstant *
- phi / node.degree);
- }
- });
- },
- /**
- * Repulsive force.
- *
- * @private
- * @param {Highcharts.Point} node
- * Node that should be translated by force.
- * @param {number} force
- * Force calcualated in `repulsiveForceFunction`
- * @param {Highcharts.PositionObject} distanceXY
- * Distance between two nodes e.g. `{x, y}`
- * @return {void}
- */
- repulsive: function (node, force, distanceXY, distanceR) {
- node.dispX +=
- (distanceXY.x / distanceR) * force / node.degree;
- node.dispY +=
- (distanceXY.y / distanceR) * force / node.degree;
- },
- /**
- * Attractive force.
- *
- * In Euler integration, force is stored in a node, not changing it's
- * position. Later, in `integrate()` forces are applied on nodes.
- *
- * @private
- * @param {Highcharts.Point} link
- * Link that connects two nodes
- * @param {number} force
- * Force calcualated in `repulsiveForceFunction`
- * @param {Highcharts.PositionObject} distanceXY
- * Distance between two nodes e.g. `{x, y}`
- * @param {number} distanceR
- * @return {void}
- */
- attractive: function (link, force, distanceXY, distanceR) {
- var massFactor = link.getMass(), translatedX = (distanceXY.x / distanceR) * force, translatedY = (distanceXY.y / distanceR) * force;
- if (!link.fromNode.fixedPosition) {
- link.fromNode.dispX -=
- translatedX * massFactor.fromNode / link.fromNode.degree;
- link.fromNode.dispY -=
- translatedY * massFactor.fromNode / link.fromNode.degree;
- }
- if (!link.toNode.fixedPosition) {
- link.toNode.dispX +=
- translatedX * massFactor.toNode / link.toNode.degree;
- link.toNode.dispY +=
- translatedY * massFactor.toNode / link.toNode.degree;
- }
- },
- /**
- * Integration method.
- *
- * In Euler integration, force were stored in a node, not changing it's
- * position. Now, in the integrator method, we apply changes.
- *
- * Euler:
- *
- * Basic form: `x(n+1) = x(n) + v(n)`
- *
- * With Rengoild-Fruchterman we get:
- * `x(n+1) = x(n) + v(n) / length(v(n)) * min(v(n), temperature(n))`
- * where:
- * - `x(n+1)`: next position
- * - `x(n)`: current position
- * - `v(n)`: velocity (comes from net force)
- * - `temperature(n)`: current temperature
- *
- * Known issues:
- * Oscillations when force vector has the same magnitude but opposite
- * direction in the next step. Potentially solved by decreasing force by
- * `v * (1 / node.degree)`
- *
- * Note:
- * Actually `min(v(n), temperature(n))` replaces simulated annealing.
- *
- * @private
- * @param {Highcharts.NetworkgraphLayout} layout
- * Layout object
- * @param {Highcharts.Point} node
- * Node that should be translated
- * @return {void}
- */
- integrate: function (layout, node) {
- var distanceR;
- node.dispX +=
- node.dispX * layout.options.friction;
- node.dispY +=
- node.dispY * layout.options.friction;
- distanceR = node.temperature = layout.vectorLength({
- x: node.dispX,
- y: node.dispY
- });
- if (distanceR !== 0) {
- node.plotX += (node.dispX / distanceR *
- Math.min(Math.abs(node.dispX), layout.temperature));
- node.plotY += (node.dispY / distanceR *
- Math.min(Math.abs(node.dispY), layout.temperature));
- }
- },
- /**
- * Estiamte the best possible distance between two nodes, making graph
- * readable.
- *
- * @private
- * @param {object} layout layout object
- * @return {number}
- */
- getK: function (layout) {
- return Math.pow(layout.box.width * layout.box.height / layout.nodes.length, 0.3);
- }
- }
- };
|