20339ec2a7671e23c3328510146127192bc72fdc.svn-base 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. /**
  2. * 力导向布局
  3. * @module echarts/layout/Force
  4. * @author pissang(http://github.com/pissang)
  5. */
  6. define(function(require) {
  7. var ForceLayoutWorker = require('./forceLayoutWorker');
  8. var vec2 = require('zrender/tool/vector');
  9. var requestAnimationFrame = window.requestAnimationFrame
  10. || window.msRequestAnimationFrame
  11. || window.mozRequestAnimationFrame
  12. || window.webkitRequestAnimationFrame
  13. || function (func) {setTimeout(func, 16);};
  14. var ArrayCtor = typeof(Float32Array) == 'undefined' ? Array : Float32Array;
  15. var workerUrl;
  16. // function getToken() {
  17. // return Math.round((allGetServerTime()).getTime() / 100) % 10000000;
  18. // }
  19. function createWorkerUrl() {
  20. if (
  21. typeof(Worker) !== 'undefined' &&
  22. typeof(Blob) !== 'undefined'
  23. ) {
  24. try {
  25. var blob = new Blob([ForceLayoutWorker.getWorkerCode()]);
  26. workerUrl = window.URL.createObjectURL(blob);
  27. }
  28. catch (e) {
  29. workerUrl = '';
  30. }
  31. }
  32. return workerUrl;
  33. }
  34. var ForceLayout = function(opts) {
  35. if (typeof(workerUrl) === 'undefined') {
  36. createWorkerUrl();
  37. }
  38. opts = opts || {};
  39. // 配置项
  40. this.width = opts.width || 500;
  41. this.height = opts.height || 500;
  42. this.center = opts.center || [this.width / 2, this.height / 2];
  43. this.ratioScaling = opts.ratioScaling || false;
  44. this.scaling = opts.scaling || 1;
  45. this.gravity = typeof(opts.gravity) !== 'undefined'
  46. ? opts.gravity : 1;
  47. this.large = opts.large || false;
  48. this.preventNodeOverlap = opts.preventNodeOverlap || false;
  49. this.preventNodeEdgeOverlap = opts.preventNodeEdgeOverlap || false;
  50. this.maxSpeedIncrease = opts.maxSpeedIncrease || 1;
  51. this.onupdate = opts.onupdate || function () {};
  52. this.temperature = opts.temperature || 1;
  53. this.coolDown = opts.coolDown || 0.99;
  54. this._layout = null;
  55. this._layoutWorker = null;
  56. var self = this;
  57. var _$onupdate = this._$onupdate;
  58. this._$onupdate = function(e) {
  59. _$onupdate.call(self, e);
  60. };
  61. };
  62. ForceLayout.prototype.updateConfig = function () {
  63. var width = this.width;
  64. var height = this.height;
  65. var size = Math.min(width, height);
  66. var config = {
  67. center: this.center,
  68. width: this.ratioScaling ? width : size,
  69. height: this.ratioScaling ? height : size,
  70. scaling: this.scaling || 1.0,
  71. gravity: this.gravity || 1.0,
  72. barnesHutOptimize: this.large,
  73. preventNodeOverlap: this.preventNodeOverlap,
  74. preventNodeEdgeOverlap: this.preventNodeEdgeOverlap,
  75. maxSpeedIncrease: this.maxSpeedIncrease
  76. };
  77. if (this._layoutWorker) {
  78. this._layoutWorker.postMessage({
  79. cmd: 'updateConfig',
  80. config: config
  81. });
  82. }
  83. else {
  84. for (var name in config) {
  85. this._layout[name] = config[name];
  86. }
  87. }
  88. };
  89. ForceLayout.prototype.init = function (graph, useWorker) {
  90. if (this._layoutWorker) {
  91. this._layoutWorker.terminate();
  92. this._layoutWorker = null;
  93. }
  94. if (workerUrl && useWorker) {
  95. try {
  96. if (!this._layoutWorker) {
  97. this._layoutWorker = new Worker(workerUrl);
  98. this._layoutWorker.onmessage = this._$onupdate;
  99. }
  100. this._layout = null;
  101. }
  102. catch (e) { // IE10-11 will throw security error when using blog url
  103. this._layoutWorker = null;
  104. if (!this._layout) {
  105. this._layout = new ForceLayoutWorker();
  106. }
  107. }
  108. }
  109. else {
  110. if (!this._layout) {
  111. this._layout = new ForceLayoutWorker();
  112. }
  113. }
  114. this.temperature = 1;
  115. this.graph = graph;
  116. // 节点数据
  117. var len = graph.nodes.length;
  118. var positionArr = new ArrayCtor(len * 2);
  119. var massArr = new ArrayCtor(len);
  120. var sizeArr = new ArrayCtor(len);
  121. for (var i = 0; i < len; i++) {
  122. var n = graph.nodes[i];
  123. positionArr[i * 2] = n.layout.position[0];
  124. positionArr[i * 2 + 1] = n.layout.position[1];
  125. massArr[i] = typeof(n.layout.mass) === 'undefined'
  126. ? 1 : n.layout.mass;
  127. sizeArr[i] = typeof(n.layout.size) === 'undefined'
  128. ? 1 : n.layout.size;
  129. n.layout.__index = i;
  130. }
  131. // 边数据
  132. len = graph.edges.length;
  133. var edgeArr = new ArrayCtor(len * 2);
  134. var edgeWeightArr = new ArrayCtor(len);
  135. for (var i = 0; i < len; i++) {
  136. var edge = graph.edges[i];
  137. edgeArr[i * 2] = edge.node1.layout.__index;
  138. edgeArr[i * 2 + 1] = edge.node2.layout.__index;
  139. edgeWeightArr[i] = edge.layout.weight || 1;
  140. }
  141. if (this._layoutWorker) {
  142. this._layoutWorker.postMessage({
  143. cmd: 'init',
  144. nodesPosition: positionArr,
  145. nodesMass: massArr,
  146. nodesSize: sizeArr,
  147. edges: edgeArr,
  148. edgesWeight: edgeWeightArr
  149. });
  150. }
  151. else {
  152. this._layout.initNodes(positionArr, massArr, sizeArr);
  153. this._layout.initEdges(edgeArr, edgeWeightArr);
  154. }
  155. this.updateConfig();
  156. };
  157. ForceLayout.prototype.step = function (steps) {
  158. var nodes = this.graph.nodes;
  159. if (this._layoutWorker) {
  160. // Sync back
  161. var positionArr = new ArrayCtor(nodes.length * 2);
  162. for (var i = 0; i < nodes.length; i++) {
  163. var n = nodes[i];
  164. positionArr[i * 2] = n.layout.position[0];
  165. positionArr[i * 2 + 1] = n.layout.position[1];
  166. }
  167. this._layoutWorker.postMessage(positionArr.buffer, [positionArr.buffer]);
  168. this._layoutWorker.postMessage({
  169. cmd: 'update',
  170. steps: steps,
  171. temperature: this.temperature,
  172. coolDown: this.coolDown
  173. });
  174. for (var i = 0; i < steps; i++) {
  175. this.temperature *= this.coolDown;
  176. }
  177. }
  178. else {
  179. requestAnimationFrame(this._$onupdate);
  180. for (var i = 0; i < nodes.length; i++) {
  181. var n = nodes[i];
  182. vec2.copy(this._layout.nodes[i].position, n.layout.position);
  183. }
  184. for (var i = 0; i < steps; i++) {
  185. this._layout.temperature = this.temperature;
  186. this._layout.update();
  187. this.temperature *= this.coolDown;
  188. }
  189. }
  190. };
  191. ForceLayout.prototype._$onupdate = function (e) {
  192. if (this._layoutWorker) {
  193. var positionArr = new Float32Array(e.data);
  194. for (var i = 0; i < this.graph.nodes.length; i++) {
  195. var n = this.graph.nodes[i];
  196. n.layout.position[0] = positionArr[i * 2];
  197. n.layout.position[1] = positionArr[i * 2 + 1];
  198. }
  199. this.onupdate && this.onupdate();
  200. }
  201. else if (this._layout) {
  202. for (var i = 0; i < this.graph.nodes.length; i++) {
  203. var n = this.graph.nodes[i];
  204. vec2.copy(n.layout.position, this._layout.nodes[i].position);
  205. }
  206. this.onupdate && this.onupdate();
  207. }
  208. };
  209. ForceLayout.prototype.dispose = function() {
  210. if (this._layoutWorker) {
  211. this._layoutWorker.terminate();
  212. }
  213. this._layoutWorker = null;
  214. this._layout = null;
  215. };
  216. return ForceLayout;
  217. });