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.

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