vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

245 lines
7.0 KiB

  1. /**
  2. * Created by Alex on 26-Feb-15.
  3. */
  4. if (typeof window !== 'undefined') {
  5. window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
  6. window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  7. }
  8. class CanvasRenderer {
  9. constructor(body) {
  10. this.body = body;
  11. this.redrawRequested = false;
  12. this.renderTimer = false;
  13. this.requiresTimeout = true;
  14. this.continueRendering = true;
  15. this.renderRequests = 0;
  16. this.translation = {x: 0, y: 0};
  17. this.scale = 1.0;
  18. this.canvasTopLeft = {x: 0, y: 0};
  19. this.canvasBottomRight = {x: 0, y: 0};
  20. this.body.emitter.on("_setScale", (scale) => this.scale = scale);
  21. this.body.emitter.on("_setTranslation", (translation) => {this.translation.x = translation.x; this.translation.y = translation.y;});
  22. this.body.emitter.on("_redraw", this._redraw.bind(this));
  23. this.body.emitter.on("_redrawHidden", this._redraw.bind(this, true));
  24. this.body.emitter.on("_requestRedraw", this._requestRedraw.bind(this));
  25. this.body.emitter.on("_startRendering", () => {this.renderRequests += 1; this.continueRendering = true; this.startRendering();});
  26. this.body.emitter.on("_stopRendering", () => {this.renderRequests -= 1; this.continueRendering = this.renderRequests > 0;});
  27. this._determineBrowserMethod();
  28. }
  29. startRendering() {
  30. if (this.continueRendering === true) {
  31. if (!this.renderTimer) {
  32. if (this.requiresTimeout == true) {
  33. this.renderTimer = window.setTimeout(this.renderStep.bind(this), this.simulationInterval); // wait this.renderTimeStep milliseconds and perform the animation step function
  34. }
  35. else {
  36. this.renderTimer = window.requestAnimationFrame(this.renderStep.bind(this)); // wait this.renderTimeStep milliseconds and perform the animation step function
  37. }
  38. }
  39. }
  40. else {
  41. }
  42. }
  43. renderStep() {
  44. // reset the renderTimer so a new scheduled animation step can be set
  45. this.renderTimer = undefined;
  46. if (this.requiresTimeout == true) {
  47. // this schedules a new simulation step
  48. this.startRendering();
  49. }
  50. this._redraw();
  51. if (this.requiresTimeout == false) {
  52. // this schedules a new simulation step
  53. this.startRendering();
  54. }
  55. }
  56. setCanvas(canvas) {
  57. this.canvas = canvas;
  58. }
  59. /**
  60. * Redraw the network with the current data
  61. * chart will be resized too.
  62. */
  63. redraw() {
  64. this.setSize(this.constants.width, this.constants.height);
  65. this._redraw();
  66. }
  67. /**
  68. * Redraw the network with the current data
  69. * @param hidden | used to get the first estimate of the node sizes. only the nodes are drawn after which they are quickly drawn over.
  70. * @private
  71. */
  72. _requestRedraw(hidden) {
  73. if (this.redrawRequested !== true) {
  74. this.redrawRequested = true;
  75. if (this.requiresTimeout === true) {
  76. window.setTimeout(this._redraw.bind(this, hidden),0);
  77. }
  78. else {
  79. window.requestAnimationFrame(this._redraw.bind(this, hidden, true));
  80. }
  81. }
  82. }
  83. _redraw(hidden = false) {
  84. this.body.emitter.emit("_beforeRender");
  85. this.redrawRequested = false;
  86. var ctx = this.canvas.frame.canvas.getContext('2d');
  87. ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
  88. // clear the canvas
  89. var w = this.canvas.frame.canvas.clientWidth;
  90. var h = this.canvas.frame.canvas.clientHeight;
  91. ctx.clearRect(0, 0, w, h);
  92. // set scaling and translation
  93. ctx.save();
  94. ctx.translate(this.translation.x, this.translation.y);
  95. ctx.scale(this.scale, this.scale);
  96. this.canvasTopLeft = this.canvas.DOMtoCanvas({x:0,y:0});
  97. this.canvasBottomRight = this.canvas.DOMtoCanvas({x:this.canvas.frame.canvas.clientWidth,y:this.canvas.frame.canvas.clientHeight});
  98. if (hidden === false) {
  99. // todo: solve this
  100. //if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideEdgesOnDrag == false) {
  101. this._drawEdges(ctx);
  102. //}
  103. }
  104. // todo: solve this
  105. //if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideNodesOnDrag == false) {
  106. this._drawNodes(ctx, this.body.nodes, hidden);
  107. //}
  108. if (hidden === false) {
  109. if (this.controlNodesActive == true) {
  110. this._drawControlNodes(ctx);
  111. }
  112. }
  113. //this._drawNodes(ctx,this.body.supportNodes,true);
  114. // this.physics.nodesSolver._debug(ctx,"#F00F0F");
  115. // restore original scaling and translation
  116. ctx.restore();
  117. if (hidden === true) {
  118. ctx.clearRect(0, 0, w, h);
  119. }
  120. }
  121. /**
  122. * Redraw all nodes
  123. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  124. * @param {CanvasRenderingContext2D} ctx
  125. * @param {Boolean} [alwaysShow]
  126. * @private
  127. */
  128. _drawNodes(ctx,nodes,alwaysShow = false) {
  129. // first draw the unselected nodes
  130. var selected = [];
  131. for (var id in nodes) {
  132. if (nodes.hasOwnProperty(id)) {
  133. nodes[id].setScaleAndPos(this.scale,this.canvasTopLeft,this.canvasBottomRight);
  134. if (nodes[id].isSelected()) {
  135. selected.push(id);
  136. }
  137. else {
  138. if (alwaysShow === true) {
  139. nodes[id].draw(ctx);
  140. }
  141. else if (nodes[id].inArea() === true) {
  142. nodes[id].draw(ctx);
  143. }
  144. }
  145. }
  146. }
  147. // draw the selected nodes on top
  148. for (var s = 0, sMax = selected.length; s < sMax; s++) {
  149. if (nodes[selected[s]].inArea() || alwaysShow) {
  150. nodes[selected[s]].draw(ctx);
  151. }
  152. }
  153. }
  154. /**
  155. * Redraw all edges
  156. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  157. * @param {CanvasRenderingContext2D} ctx
  158. * @private
  159. */
  160. _drawEdges(ctx) {
  161. var edges = this.body.edges;
  162. for (var id in edges) {
  163. if (edges.hasOwnProperty(id)) {
  164. var edge = edges[id];
  165. edge.setScale(this.scale);
  166. if (edge.connected === true) {
  167. edges[id].draw(ctx);
  168. }
  169. }
  170. }
  171. }
  172. /**
  173. * Redraw all edges
  174. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  175. * @param {CanvasRenderingContext2D} ctx
  176. * @private
  177. */
  178. _drawControlNodes(ctx) {
  179. var edges = this.body.edges;
  180. for (var id in edges) {
  181. if (edges.hasOwnProperty(id)) {
  182. edges[id]._drawControlNodes(ctx);
  183. }
  184. }
  185. }
  186. /**
  187. * Determine if the browser requires a setTimeout or a requestAnimationFrame. This was required because
  188. * some implementations (safari and IE9) did not support requestAnimationFrame
  189. * @private
  190. */
  191. _determineBrowserMethod() {
  192. if (typeof window !== 'undefined') {
  193. var browserType = navigator.userAgent.toLowerCase();
  194. this.requiresTimeout = false;
  195. if (browserType.indexOf('msie 9.0') != -1) { // IE 9
  196. this.requiresTimeout = true;
  197. }
  198. else if (browserType.indexOf('safari') != -1) { // safari
  199. if (browserType.indexOf('chrome') <= -1) {
  200. this.requiresTimeout = true;
  201. }
  202. }
  203. }
  204. else {
  205. this.requiresTimeout = true;
  206. }
  207. }
  208. }
  209. export {CanvasRenderer};