| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- /* *
- *
- * 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';
- import U from '../../Core/Utilities.js';
- var extend = U.extend;
- /* eslint-disable no-invalid-this, valid-jsdoc */
- /**
- * The QuadTree node class. Used in Networkgraph chart as a base for Barnes-Hut
- * approximation.
- *
- * @private
- * @class
- * @name Highcharts.QuadTreeNode
- *
- * @param {Highcharts.Dictionary<number>} box Available space for the node
- */
- var QuadTreeNode = H.QuadTreeNode = function (box) {
- /**
- * Read only. The available space for node.
- *
- * @name Highcharts.QuadTreeNode#box
- * @type {Highcharts.Dictionary<number>}
- */
- this.box = box;
- /**
- * Read only. The minium of width and height values.
- *
- * @name Highcharts.QuadTreeNode#boxSize
- * @type {number}
- */
- this.boxSize = Math.min(box.width, box.height);
- /**
- * Read only. Array of subnodes. Empty if QuadTreeNode has just one Point.
- * When added another Point to this QuadTreeNode, array is filled with four
- * subnodes.
- *
- * @name Highcharts.QuadTreeNode#nodes
- * @type {Array<Highcharts.QuadTreeNode>}
- */
- this.nodes = [];
- /**
- * Read only. Flag to determine if QuadTreeNode is internal (and has
- * subnodes with mass and central position) or external (bound to Point).
- *
- * @name Highcharts.QuadTreeNode#isInternal
- * @type {boolean}
- */
- this.isInternal = false;
- /**
- * Read only. If QuadTreeNode is an external node, Point is stored in
- * `this.body`.
- *
- * @name Highcharts.QuadTreeNode#body
- * @type {boolean|Highcharts.Point}
- */
- this.body = false;
- /**
- * Read only. Internal nodes when created are empty to reserve the space. If
- * Point is added to this QuadTreeNode, QuadTreeNode is no longer empty.
- *
- * @name Highcharts.QuadTreeNode#isEmpty
- * @type {boolean}
- */
- this.isEmpty = true;
- };
- extend(QuadTreeNode.prototype,
- /** @lends Highcharts.QuadTreeNode.prototype */
- {
- /**
- * Insert recursively point(node) into the QuadTree. If the given
- * quadrant is already occupied, divide it into smaller quadrants.
- *
- * @param {Highcharts.Point} point
- * Point/node to be inserted
- * @param {number} depth
- * Max depth of the QuadTree
- */
- insert: function (point, depth) {
- var newQuadTreeNode;
- if (this.isInternal) {
- // Internal node:
- this.nodes[this.getBoxPosition(point)].insert(point, depth - 1);
- }
- else {
- this.isEmpty = false;
- if (!this.body) {
- // First body in a quadrant:
- this.isInternal = false;
- this.body = point;
- }
- else {
- if (depth) {
- // Every other body in a quadrant:
- this.isInternal = true;
- this.divideBox();
- // Reinsert main body only once:
- if (this.body !== true) {
- this.nodes[this.getBoxPosition(this.body)]
- .insert(this.body, depth - 1);
- this.body = true;
- }
- // Add second body:
- this.nodes[this.getBoxPosition(point)]
- .insert(point, depth - 1);
- }
- else {
- // We are below max allowed depth. That means either:
- // - really huge number of points
- // - falling two points into exactly the same position
- // In this case, create another node in the QuadTree.
- //
- // Alternatively we could add some noise to the
- // position, but that could result in different
- // rendered chart in exporting.
- newQuadTreeNode = new QuadTreeNode({
- top: point.plotX,
- left: point.plotY,
- // Width/height below 1px
- width: 0.1,
- height: 0.1
- });
- newQuadTreeNode.body = point;
- newQuadTreeNode.isInternal = false;
- this.nodes.push(newQuadTreeNode);
- }
- }
- }
- },
- /**
- * Each quad node requires it's mass and center position. That mass and
- * position is used to imitate real node in the layout by approximation.
- */
- updateMassAndCenter: function () {
- var mass = 0, plotX = 0, plotY = 0;
- if (this.isInternal) {
- // Calcualte weightened mass of the quad node:
- this.nodes.forEach(function (pointMass) {
- if (!pointMass.isEmpty) {
- mass += pointMass.mass;
- plotX +=
- pointMass.plotX * pointMass.mass;
- plotY +=
- pointMass.plotY * pointMass.mass;
- }
- });
- plotX /= mass;
- plotY /= mass;
- }
- else if (this.body) {
- // Just one node, use coordinates directly:
- mass = this.body.mass;
- plotX = this.body.plotX;
- plotY = this.body.plotY;
- }
- // Store details:
- this.mass = mass;
- this.plotX = plotX;
- this.plotY = plotY;
- },
- /**
- * When inserting another node into the box, that already hove one node,
- * divide the available space into another four quadrants.
- *
- * Indexes of quadrants are:
- * ```
- * ------------- -------------
- * | | | | |
- * | | | 0 | 1 |
- * | | divide() | | |
- * | 1 | -----------> -------------
- * | | | | |
- * | | | 3 | 2 |
- * | | | | |
- * ------------- -------------
- * ```
- */
- divideBox: function () {
- var halfWidth = this.box.width / 2, halfHeight = this.box.height / 2;
- // Top left
- this.nodes[0] = new QuadTreeNode({
- left: this.box.left,
- top: this.box.top,
- width: halfWidth,
- height: halfHeight
- });
- // Top right
- this.nodes[1] = new QuadTreeNode({
- left: this.box.left + halfWidth,
- top: this.box.top,
- width: halfWidth,
- height: halfHeight
- });
- // Bottom right
- this.nodes[2] = new QuadTreeNode({
- left: this.box.left + halfWidth,
- top: this.box.top + halfHeight,
- width: halfWidth,
- height: halfHeight
- });
- // Bottom left
- this.nodes[3] = new QuadTreeNode({
- left: this.box.left,
- top: this.box.top + halfHeight,
- width: halfWidth,
- height: halfHeight
- });
- },
- /**
- * Determine which of the quadrants should be used when placing node in
- * the QuadTree. Returned index is always in range `< 0 , 3 >`.
- *
- * @param {Highcharts.Point} point
- * @return {number}
- */
- getBoxPosition: function (point) {
- var left = point.plotX < this.box.left + this.box.width / 2, top = point.plotY < this.box.top + this.box.height / 2, index;
- if (left) {
- if (top) {
- // Top left
- index = 0;
- }
- else {
- // Bottom left
- index = 3;
- }
- }
- else {
- if (top) {
- // Top right
- index = 1;
- }
- else {
- // Bottom right
- index = 2;
- }
- }
- return index;
- }
- });
- /**
- * The QuadTree class. Used in Networkgraph chart as a base for Barnes-Hut
- * approximation.
- *
- * @private
- * @class
- * @name Highcharts.QuadTree
- *
- * @param {number} x left position of the plotting area
- * @param {number} y top position of the plotting area
- * @param {number} width width of the plotting area
- * @param {number} height height of the plotting area
- */
- var QuadTree = H.QuadTree = function (x, y, width, height) {
- // Boundary rectangle:
- this.box = {
- left: x,
- top: y,
- width: width,
- height: height
- };
- this.maxDepth = 25;
- this.root = new QuadTreeNode(this.box, '0');
- this.root.isInternal = true;
- this.root.isRoot = true;
- this.root.divideBox();
- };
- extend(QuadTree.prototype,
- /** @lends Highcharts.QuadTree.prototype */
- {
- /**
- * Insert nodes into the QuadTree
- *
- * @param {Array<Highcharts.Point>} points
- */
- insertNodes: function (points) {
- points.forEach(function (point) {
- this.root.insert(point, this.maxDepth);
- }, this);
- },
- /**
- * Depfth first treversal (DFS). Using `before` and `after` callbacks,
- * we can get two results: preorder and postorder traversals, reminder:
- *
- * ```
- * (a)
- * / \
- * (b) (c)
- * / \
- * (d) (e)
- * ```
- *
- * DFS (preorder): `a -> b -> d -> e -> c`
- *
- * DFS (postorder): `d -> e -> b -> c -> a`
- *
- * @param {Highcharts.QuadTreeNode|null} node
- * @param {Function} [beforeCallback] function to be called before
- * visiting children nodes
- * @param {Function} [afterCallback] function to be called after
- * visiting children nodes
- */
- visitNodeRecursive: function (node, beforeCallback, afterCallback) {
- var goFurther;
- if (!node) {
- node = this.root;
- }
- if (node === this.root && beforeCallback) {
- goFurther = beforeCallback(node);
- }
- if (goFurther === false) {
- return;
- }
- node.nodes.forEach(function (qtNode) {
- if (qtNode.isInternal) {
- if (beforeCallback) {
- goFurther = beforeCallback(qtNode);
- }
- if (goFurther === false) {
- return;
- }
- this.visitNodeRecursive(qtNode, beforeCallback, afterCallback);
- }
- else if (qtNode.body) {
- if (beforeCallback) {
- beforeCallback(qtNode.body);
- }
- }
- if (afterCallback) {
- afterCallback(qtNode);
- }
- }, this);
- if (node === this.root && afterCallback) {
- afterCallback(node);
- }
- },
- /**
- * Calculate mass of the each QuadNode in the tree.
- */
- calculateMassAndCenter: function () {
- this.visitNodeRecursive(null, null, function (node) {
- node.updateMassAndCenter();
- });
- }
- });
|