sigma.plugins.animate.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. /**
  2. * This plugin provides a method to animate a sigma instance by interpolating
  3. * some node properties. Check the sigma.plugins.animate function doc or the
  4. * examples/animate.html code sample to know more.
  5. */
  6. (function() {
  7. 'use strict';
  8. if (typeof sigma === 'undefined')
  9. throw 'sigma is not declared';
  10. sigma.utils.pkg('sigma.plugins');
  11. var _id = 0,
  12. _cache = {};
  13. // TOOLING FUNCTIONS:
  14. // ******************
  15. function parseColor(val) {
  16. if (_cache[val])
  17. return _cache[val];
  18. var result = [0, 0, 0];
  19. if (val.match(/^#/)) {
  20. val = (val || '').replace(/^#/, '');
  21. result = (val.length === 3) ?
  22. [
  23. parseInt(val.charAt(0) + val.charAt(0), 16),
  24. parseInt(val.charAt(1) + val.charAt(1), 16),
  25. parseInt(val.charAt(2) + val.charAt(2), 16)
  26. ] :
  27. [
  28. parseInt(val.charAt(0) + val.charAt(1), 16),
  29. parseInt(val.charAt(2) + val.charAt(3), 16),
  30. parseInt(val.charAt(4) + val.charAt(5), 16)
  31. ];
  32. } else if (val.match(/^ *rgba? *\(/)) {
  33. val = val.match(
  34. /^ *rgba? *\( *([0-9]*) *, *([0-9]*) *, *([0-9]*) *(,.*)?\) *$/
  35. );
  36. result = [
  37. +val[1],
  38. +val[2],
  39. +val[3]
  40. ];
  41. }
  42. _cache[val] = {
  43. r: result[0],
  44. g: result[1],
  45. b: result[2]
  46. };
  47. return _cache[val];
  48. }
  49. function interpolateColors(c1, c2, p) {
  50. c1 = parseColor(c1);
  51. c2 = parseColor(c2);
  52. var c = {
  53. r: c1.r * (1 - p) + c2.r * p,
  54. g: c1.g * (1 - p) + c2.g * p,
  55. b: c1.b * (1 - p) + c2.b * p
  56. };
  57. return 'rgb(' + [c.r | 0, c.g | 0, c.b | 0].join(',') + ')';
  58. }
  59. /**
  60. * This function will animate some specified node properties. It will
  61. * basically call requestAnimationFrame, interpolate the values and call the
  62. * refresh method during a specified duration.
  63. *
  64. * Recognized parameters:
  65. * **********************
  66. * Here is the exhaustive list of every accepted parameters in the settings
  67. * object:
  68. *
  69. * {?array} nodes An array of node objects or node ids. If
  70. * not specified, all nodes of the graph
  71. * will be animated.
  72. * {?(function|string)} easing Either the name of an easing in the
  73. * sigma.utils.easings package or a
  74. * function. If not specified, the
  75. * quadraticInOut easing from this package
  76. * will be used instead.
  77. * {?number} duration The duration of the animation. If not
  78. * specified, the "animationsTime" setting
  79. * value of the sigma instance will be used
  80. * instead.
  81. * {?function} onComplete Eventually a function to call when the
  82. * animation is ended.
  83. *
  84. * @param {sigma} s The related sigma instance.
  85. * @param {object} animate An hash with the keys being the node properties
  86. * to interpolate, and the values being the related
  87. * target values.
  88. * @param {?object} options Eventually an object with options.
  89. */
  90. sigma.plugins.animate = function(s, animate, options) {
  91. var o = options || {},
  92. id = ++_id,
  93. duration = o.duration || s.settings('animationsTime'),
  94. easing = typeof o.easing === 'string' ?
  95. sigma.utils.easings[o.easing] :
  96. typeof o.easing === 'function' ?
  97. o.easing :
  98. sigma.utils.easings.quadraticInOut,
  99. start = sigma.utils.dateNow(),
  100. nodes,
  101. startPositions;
  102. if (o.nodes && o.nodes.length) {
  103. if (typeof o.nodes[0] === 'object')
  104. nodes = o.nodes;
  105. else
  106. nodes = s.graph.nodes(o.nodes); // argument is an array of IDs
  107. }
  108. else
  109. nodes = s.graph.nodes();
  110. // Store initial positions:
  111. startPositions = nodes.reduce(function(res, node) {
  112. var k;
  113. res[node.id] = {};
  114. for (k in animate)
  115. if (k in node)
  116. res[node.id][k] = node[k];
  117. return res;
  118. }, {});
  119. s.animations = s.animations || Object.create({});
  120. sigma.plugins.kill(s);
  121. // Do not refresh edgequadtree during drag:
  122. var k,
  123. c;
  124. for (k in s.cameras) {
  125. c = s.cameras[k];
  126. c.edgequadtree._enabled = false;
  127. }
  128. function step() {
  129. var p = (sigma.utils.dateNow() - start) / duration;
  130. if (p >= 1) {
  131. nodes.forEach(function(node) {
  132. for (var k in animate)
  133. if (k in animate)
  134. node[k] = node[animate[k]];
  135. });
  136. // Allow to refresh edgequadtree:
  137. var k,
  138. c;
  139. for (k in s.cameras) {
  140. c = s.cameras[k];
  141. c.edgequadtree._enabled = true;
  142. }
  143. s.refresh();
  144. if (typeof o.onComplete === 'function')
  145. o.onComplete();
  146. } else {
  147. p = easing(p);
  148. nodes.forEach(function(node) {
  149. for (var k in animate)
  150. if (k in animate) {
  151. if (k.match(/color$/))
  152. node[k] = interpolateColors(
  153. startPositions[node.id][k],
  154. node[animate[k]],
  155. p
  156. );
  157. else
  158. node[k] =
  159. node[animate[k]] * p +
  160. startPositions[node.id][k] * (1 - p);
  161. }
  162. });
  163. s.refresh();
  164. s.animations[id] = requestAnimationFrame(step);
  165. }
  166. }
  167. step();
  168. };
  169. sigma.plugins.kill = function(s) {
  170. for (var k in (s.animations || {}))
  171. cancelAnimationFrame(s.animations[k]);
  172. // Allow to refresh edgequadtree:
  173. var k,
  174. c;
  175. for (k in s.cameras) {
  176. c = s.cameras[k];
  177. c.edgequadtree._enabled = true;
  178. }
  179. };
  180. }).call(window);