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.

314 lines
8.7 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. if (typeof window !== 'undefined') {
  2. window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
  3. window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  4. }
  5. let util = require('../../util');
  6. class CanvasRenderer {
  7. constructor(body, canvas) {
  8. this.body = body;
  9. this.canvas = canvas;
  10. this.redrawRequested = false;
  11. this.renderTimer = undefined;
  12. this.requiresTimeout = true;
  13. this.renderingActive = false;
  14. this.renderRequests = 0;
  15. this.allowRedraw = true;
  16. this.dragging = false;
  17. this.options = {};
  18. this.defaultOptions = {
  19. hideEdgesOnDrag: false,
  20. hideNodesOnDrag: false
  21. };
  22. util.extend(this.options, this.defaultOptions);
  23. this._determineBrowserMethod();
  24. this.bindEventListeners();
  25. }
  26. bindEventListeners() {
  27. this.body.emitter.on("dragStart", () => { this.dragging = true; });
  28. this.body.emitter.on("dragEnd", () => { this.dragging = false; });
  29. this.body.emitter.on("_resizeNodes", () => { this._resizeNodes(); });
  30. this.body.emitter.on("_redraw", () => {
  31. if (this.renderingActive === false) {
  32. this._redraw();
  33. }
  34. });
  35. this.body.emitter.on("_blockRedraw", () => {this.allowRedraw = false;});
  36. this.body.emitter.on("_allowRedraw", () => {this.allowRedraw = true; this.redrawRequested = false;});
  37. this.body.emitter.on("_requestRedraw", this._requestRedraw.bind(this));
  38. this.body.emitter.on("_startRendering", () => {
  39. this.renderRequests += 1;
  40. this.renderingActive = true;
  41. this._startRendering();
  42. });
  43. this.body.emitter.on("_stopRendering", () => {
  44. this.renderRequests -= 1;
  45. this.renderingActive = this.renderRequests > 0;
  46. this.renderTimer = undefined;
  47. });
  48. this.body.emitter.on('destroy', () => {
  49. this.renderRequests = 0;
  50. this.allowRedraw = false;
  51. this.renderingActive = false;
  52. if (this.requiresTimeout === true) {
  53. clearTimeout(this.renderTimer);
  54. }
  55. else {
  56. window.cancelAnimationFrame(this.renderTimer);
  57. }
  58. this.body.emitter.off();
  59. });
  60. }
  61. setOptions(options) {
  62. if (options !== undefined) {
  63. let fields = ['hideEdgesOnDrag','hideNodesOnDrag'];
  64. util.selectiveDeepExtend(fields,this.options, options);
  65. }
  66. }
  67. _startRendering() {
  68. if (this.renderingActive === true) {
  69. if (this.renderTimer === undefined) {
  70. if (this.requiresTimeout === true) {
  71. this.renderTimer = window.setTimeout(this._renderStep.bind(this), this.simulationInterval); // wait this.renderTimeStep milliseconds and perform the animation step function
  72. }
  73. else {
  74. this.renderTimer = window.requestAnimationFrame(this._renderStep.bind(this)); // wait this.renderTimeStep milliseconds and perform the animation step function
  75. }
  76. }
  77. }
  78. }
  79. _renderStep() {
  80. if (this.renderingActive === true) {
  81. // reset the renderTimer so a new scheduled animation step can be set
  82. this.renderTimer = undefined;
  83. if (this.requiresTimeout === true) {
  84. // this schedules a new simulation step
  85. this._startRendering();
  86. }
  87. this._redraw();
  88. if (this.requiresTimeout === false) {
  89. // this schedules a new simulation step
  90. this._startRendering();
  91. }
  92. }
  93. }
  94. /**
  95. * Redraw the network with the current data
  96. * chart will be resized too.
  97. */
  98. redraw() {
  99. this.body.emitter.emit('setSize');
  100. this._redraw();
  101. }
  102. /**
  103. * Redraw the network with the current data
  104. * @param hidden | used to get the first estimate of the node sizes. only the nodes are drawn after which they are quickly drawn over.
  105. * @private
  106. */
  107. _requestRedraw() {
  108. if (this.redrawRequested !== true && this.renderingActive === false && this.allowRedraw === true) {
  109. this.redrawRequested = true;
  110. if (this.requiresTimeout === true) {
  111. window.setTimeout(() => {this._redraw(false);}, 0);
  112. }
  113. else {
  114. window.requestAnimationFrame(() => {this._redraw(false);});
  115. }
  116. }
  117. }
  118. _redraw(hidden = false) {
  119. if (this.allowRedraw === true) {
  120. this.body.emitter.emit("initRedraw");
  121. this.redrawRequested = false;
  122. // when the container div was hidden, this fixes it back up!
  123. if (this.canvas.frame.canvas.width === 0 || this.canvas.frame.canvas.height === 0) {
  124. this.canvas.setSize();
  125. }
  126. this.canvas.setTransform();
  127. let ctx = this.canvas.getContext();
  128. // clear the canvas
  129. let w = this.canvas.frame.canvas.clientWidth;
  130. let h = this.canvas.frame.canvas.clientHeight;
  131. ctx.clearRect(0, 0, w, h);
  132. // if the div is hidden, we stop the redraw here for performance.
  133. if (this.canvas.frame.clientWidth === 0) {
  134. return;
  135. }
  136. // set scaling and translation
  137. ctx.save();
  138. ctx.translate(this.body.view.translation.x, this.body.view.translation.y);
  139. ctx.scale(this.body.view.scale, this.body.view.scale);
  140. ctx.beginPath();
  141. this.body.emitter.emit("beforeDrawing", ctx);
  142. ctx.closePath();
  143. if (hidden === false) {
  144. if (this.dragging === false || (this.dragging === true && this.options.hideEdgesOnDrag === false)) {
  145. this._drawEdges(ctx);
  146. }
  147. }
  148. if (this.dragging === false || (this.dragging === true && this.options.hideNodesOnDrag === false)) {
  149. this._drawNodes(ctx, hidden);
  150. }
  151. ctx.beginPath();
  152. this.body.emitter.emit("afterDrawing", ctx);
  153. ctx.closePath();
  154. // restore original scaling and translation
  155. ctx.restore();
  156. if (hidden === true) {
  157. ctx.clearRect(0, 0, w, h);
  158. }
  159. }
  160. }
  161. /**
  162. * Redraw all nodes
  163. *
  164. * @param {CanvasRenderingContext2D} ctx
  165. * @param {Boolean} [alwaysShow]
  166. * @private
  167. */
  168. _resizeNodes() {
  169. this.canvas.setTransform();
  170. let ctx = this.canvas.getContext();
  171. ctx.save();
  172. ctx.translate(this.body.view.translation.x, this.body.view.translation.y);
  173. ctx.scale(this.body.view.scale, this.body.view.scale);
  174. let nodes = this.body.nodes;
  175. let node;
  176. // resize all nodes
  177. for (let nodeId in nodes) {
  178. if (nodes.hasOwnProperty(nodeId)) {
  179. node = nodes[nodeId];
  180. node.resize(ctx);
  181. node.updateBoundingBox(ctx, node.selected);
  182. }
  183. }
  184. // restore original scaling and translation
  185. ctx.restore();
  186. }
  187. /**
  188. * Redraw all nodes
  189. *
  190. * @param {CanvasRenderingContext2D} ctx 2D context of a HTML canvas
  191. * @param {Boolean} [alwaysShow]
  192. * @private
  193. */
  194. _drawNodes(ctx, alwaysShow = false) {
  195. let nodes = this.body.nodes;
  196. let nodeIndices = this.body.nodeIndices;
  197. let node;
  198. let selected = [];
  199. let margin = 20;
  200. let topLeft = this.canvas.DOMtoCanvas({x:-margin,y:-margin});
  201. let bottomRight = this.canvas.DOMtoCanvas({
  202. x: this.canvas.frame.canvas.clientWidth+margin,
  203. y: this.canvas.frame.canvas.clientHeight+margin
  204. });
  205. let viewableArea = {top:topLeft.y,left:topLeft.x,bottom:bottomRight.y,right:bottomRight.x};
  206. // draw unselected nodes;
  207. for (let i = 0; i < nodeIndices.length; i++) {
  208. node = nodes[nodeIndices[i]];
  209. // set selected nodes aside
  210. if (node.isSelected()) {
  211. selected.push(nodeIndices[i]);
  212. }
  213. else {
  214. if (alwaysShow === true) {
  215. node.draw(ctx);
  216. }
  217. else if (node.isBoundingBoxOverlappingWith(viewableArea) === true) {
  218. node.draw(ctx);
  219. }
  220. else {
  221. node.updateBoundingBox(ctx, node.selected);
  222. }
  223. }
  224. }
  225. // draw the selected nodes on top
  226. for (let i = 0; i < selected.length; i++) {
  227. node = nodes[selected[i]];
  228. node.draw(ctx);
  229. }
  230. }
  231. /**
  232. * Redraw all edges
  233. * @param {CanvasRenderingContext2D} ctx 2D context of a HTML canvas
  234. * @private
  235. */
  236. _drawEdges(ctx) {
  237. let edges = this.body.edges;
  238. let edgeIndices = this.body.edgeIndices;
  239. let edge;
  240. for (let i = 0; i < edgeIndices.length; i++) {
  241. edge = edges[edgeIndices[i]];
  242. if (edge.connected === true) {
  243. edge.draw(ctx);
  244. }
  245. }
  246. }
  247. /**
  248. * Determine if the browser requires a setTimeout or a requestAnimationFrame. This was required because
  249. * some implementations (safari and IE9) did not support requestAnimationFrame
  250. * @private
  251. */
  252. _determineBrowserMethod() {
  253. if (typeof window !== 'undefined') {
  254. let browserType = navigator.userAgent.toLowerCase();
  255. this.requiresTimeout = false;
  256. if (browserType.indexOf('msie 9.0') != -1) { // IE 9
  257. this.requiresTimeout = true;
  258. }
  259. else if (browserType.indexOf('safari') != -1) { // safari
  260. if (browserType.indexOf('chrome') <= -1) {
  261. this.requiresTimeout = true;
  262. }
  263. }
  264. }
  265. else {
  266. this.requiresTimeout = true;
  267. }
  268. }
  269. }
  270. export default CanvasRenderer;