PackedBubbleComposition.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /* *
  2. *
  3. * (c) 2010-2021 Grzegorz Blachlinski, Sebastian Bochan
  4. *
  5. * License: www.highcharts.com/license
  6. *
  7. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  8. *
  9. * */
  10. 'use strict';
  11. import Chart from '../../Core/Chart/Chart.js';
  12. import H from '../../Core/Globals.js';
  13. import '../../Series/Networkgraph/Layouts.js';
  14. var Reingold = H.layouts['reingold-fruchterman'];
  15. import U from '../../Core/Utilities.js';
  16. var addEvent = U.addEvent, extendClass = U.extendClass, pick = U.pick;
  17. /* *
  18. *
  19. * Composition
  20. *
  21. * */
  22. Chart.prototype.getSelectedParentNodes = function () {
  23. var chart = this, series = chart.series, selectedParentsNodes = [];
  24. series.forEach(function (series) {
  25. if (series.parentNode && series.parentNode.selected) {
  26. selectedParentsNodes.push(series.parentNode);
  27. }
  28. });
  29. return selectedParentsNodes;
  30. };
  31. H.networkgraphIntegrations.packedbubble = {
  32. repulsiveForceFunction: function (d, k, node, repNode) {
  33. return Math.min(d, (node.marker.radius + repNode.marker.radius) / 2);
  34. },
  35. barycenter: function () {
  36. var layout = this, gravitationalConstant = layout.options.gravitationalConstant, box = layout.box, nodes = layout.nodes, centerX, centerY;
  37. nodes.forEach(function (node) {
  38. if (layout.options.splitSeries && !node.isParentNode) {
  39. centerX = node.series.parentNode.plotX;
  40. centerY = node.series.parentNode.plotY;
  41. }
  42. else {
  43. centerX = box.width / 2;
  44. centerY = box.height / 2;
  45. }
  46. if (!node.fixedPosition) {
  47. node.plotX -=
  48. (node.plotX - centerX) *
  49. gravitationalConstant /
  50. (node.mass * Math.sqrt(nodes.length));
  51. node.plotY -=
  52. (node.plotY - centerY) *
  53. gravitationalConstant /
  54. (node.mass * Math.sqrt(nodes.length));
  55. }
  56. });
  57. },
  58. repulsive: function (node, force, distanceXY, repNode) {
  59. var factor = (force * this.diffTemperature / node.mass /
  60. node.degree), x = distanceXY.x * factor, y = distanceXY.y * factor;
  61. if (!node.fixedPosition) {
  62. node.plotX += x;
  63. node.plotY += y;
  64. }
  65. if (!repNode.fixedPosition) {
  66. repNode.plotX -= x;
  67. repNode.plotY -= y;
  68. }
  69. },
  70. integrate: H.networkgraphIntegrations.verlet.integrate,
  71. getK: H.noop
  72. };
  73. H.layouts.packedbubble = extendClass(Reingold, {
  74. beforeStep: function () {
  75. if (this.options.marker) {
  76. this.series.forEach(function (series) {
  77. if (series) {
  78. series.calculateParentRadius();
  79. }
  80. });
  81. }
  82. },
  83. isStable: function () {
  84. var tempDiff = Math.abs(this.prevSystemTemperature -
  85. this.systemTemperature);
  86. var upScaledTemperature = 10 * this.systemTemperature /
  87. Math.sqrt(this.nodes.length);
  88. return Math.abs(upScaledTemperature) < 1 &&
  89. tempDiff < 0.00001 ||
  90. this.temperature <= 0;
  91. },
  92. setCircularPositions: function () {
  93. var layout = this, box = layout.box, nodes = layout.nodes, nodesLength = nodes.length + 1, angle = 2 * Math.PI / nodesLength, centerX, centerY, radius = layout.options.initialPositionRadius;
  94. nodes.forEach(function (node, index) {
  95. if (layout.options.splitSeries &&
  96. !node.isParentNode) {
  97. centerX = node.series.parentNode.plotX;
  98. centerY = node.series.parentNode.plotY;
  99. }
  100. else {
  101. centerX = box.width / 2;
  102. centerY = box.height / 2;
  103. }
  104. node.plotX = node.prevX = pick(node.plotX, centerX +
  105. radius * Math.cos(node.index || index * angle));
  106. node.plotY = node.prevY = pick(node.plotY, centerY +
  107. radius * Math.sin(node.index || index * angle));
  108. node.dispX = 0;
  109. node.dispY = 0;
  110. });
  111. },
  112. repulsiveForces: function () {
  113. var layout = this, force, distanceR, distanceXY, bubblePadding = layout.options.bubblePadding;
  114. layout.nodes.forEach(function (node) {
  115. node.degree = node.mass;
  116. node.neighbours = 0;
  117. layout.nodes.forEach(function (repNode) {
  118. force = 0;
  119. if (
  120. // Node can not repulse itself:
  121. node !== repNode &&
  122. // Only close nodes affect each other:
  123. // Not dragged:
  124. !node.fixedPosition &&
  125. (layout.options.seriesInteraction ||
  126. node.series === repNode.series)) {
  127. distanceXY = layout.getDistXY(node, repNode);
  128. distanceR = (layout.vectorLength(distanceXY) -
  129. (node.marker.radius +
  130. repNode.marker.radius +
  131. bubblePadding));
  132. // TODO padding configurable
  133. if (distanceR < 0) {
  134. node.degree += 0.01;
  135. node.neighbours++;
  136. force = layout.repulsiveForce(-distanceR / Math.sqrt(node.neighbours), layout.k, node, repNode);
  137. }
  138. layout.force('repulsive', node, force * repNode.mass, distanceXY, repNode, distanceR);
  139. }
  140. });
  141. });
  142. },
  143. applyLimitBox: function (node) {
  144. var layout = this, distanceXY, distanceR, factor = 0.01;
  145. // parentNodeLimit should be used together
  146. // with seriesInteraction: false
  147. if (layout.options.splitSeries &&
  148. !node.isParentNode &&
  149. layout.options.parentNodeLimit) {
  150. distanceXY = layout.getDistXY(node, node.series.parentNode);
  151. distanceR = (node.series.parentNodeRadius -
  152. node.marker.radius -
  153. layout.vectorLength(distanceXY));
  154. if (distanceR < 0 &&
  155. distanceR > -2 * node.marker.radius) {
  156. node.plotX -= distanceXY.x * factor;
  157. node.plotY -= distanceXY.y * factor;
  158. }
  159. }
  160. Reingold.prototype.applyLimitBox.apply(this, arguments);
  161. }
  162. });
  163. // Remove accumulated data points to redistribute all of them again
  164. // (i.e after hiding series by legend)
  165. addEvent(Chart, 'beforeRedraw', function () {
  166. // eslint-disable-next-line no-invalid-this
  167. if (this.allDataPoints) {
  168. // eslint-disable-next-line no-invalid-this
  169. delete this.allDataPoints;
  170. }
  171. });