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.

305 lines
8.5 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 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.pixelRatio = undefined;
  16. this.allowRedrawRequests = true;
  17. this.dragging = false;
  18. this.options = {};
  19. this.defaultOptions = {
  20. hideEdgesOnDrag: false,
  21. hideNodesOnDrag: false
  22. };
  23. util.extend(this.options, this.defaultOptions);
  24. this._determineBrowserMethod();
  25. this.bindEventListeners();
  26. }
  27. bindEventListeners() {
  28. this.body.emitter.on("dragStart", () => {
  29. this.dragging = true;
  30. });
  31. this.body.emitter.on("dragEnd", () => this.dragging = false);
  32. this.body.emitter.on("_redraw", () => {
  33. if (this.renderingActive === false) {
  34. this._redraw();
  35. }
  36. });
  37. this.body.emitter.on("_blockRedrawRequests", () => {this.allowRedrawRequests = false;});
  38. this.body.emitter.on("_allowRedrawRequests", () => {this.allowRedrawRequests = true; });
  39. this.body.emitter.on("_requestRedraw", this._requestRedraw.bind(this));
  40. this.body.emitter.on("_startRendering", () => {
  41. this.renderRequests += 1;
  42. this.renderingActive = true;
  43. this._startRendering();
  44. });
  45. this.body.emitter.on("_stopRendering", () => {
  46. this.renderRequests -= 1;
  47. this.renderingActive = this.renderRequests > 0;
  48. this.renderTimer = undefined;
  49. });
  50. this.body.emitter.on('destroy', () => {
  51. this.renderRequests = 0;
  52. this.renderingActive = false;
  53. if (this.requiresTimeout === true) {
  54. clearTimeout(this.renderTimer);
  55. }
  56. else {
  57. cancelAnimationFrame(this.renderTimer);
  58. }
  59. this.body.emitter.off();
  60. });
  61. }
  62. setOptions(options) {
  63. if (options !== undefined) {
  64. util.deepExtend(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.allowRedrawRequests === true) {
  109. this.redrawRequested = true;
  110. if (this.requiresTimeout === true) {
  111. window.setTimeout(this._redraw.bind(this, false), 0);
  112. }
  113. else {
  114. window.requestAnimationFrame(this._redraw.bind(this, false));
  115. }
  116. }
  117. }
  118. _redraw(hidden = false) {
  119. this.body.emitter.emit("initRedraw");
  120. this.redrawRequested = false;
  121. let ctx = this.canvas.frame.canvas.getContext('2d');
  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. if (this.pixelRatio === undefined) {
  127. this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio ||
  128. ctx.mozBackingStorePixelRatio ||
  129. ctx.msBackingStorePixelRatio ||
  130. ctx.oBackingStorePixelRatio ||
  131. ctx.backingStorePixelRatio || 1);
  132. }
  133. ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
  134. // clear the canvas
  135. let w = this.canvas.frame.canvas.clientWidth;
  136. let h = this.canvas.frame.canvas.clientHeight;
  137. ctx.clearRect(0, 0, w, h);
  138. this.body.emitter.emit("beforeDrawing", ctx);
  139. // set scaling and translation
  140. ctx.save();
  141. ctx.translate(this.body.view.translation.x, this.body.view.translation.y);
  142. ctx.scale(this.body.view.scale, this.body.view.scale);
  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. if (this.controlNodesActive === true) {
  152. this._drawControlNodes(ctx);
  153. }
  154. //this.physics.nodesSolver._debug(ctx,"#F00F0F");
  155. this.body.emitter.emit("afterDrawing", ctx);
  156. // restore original scaling and translation
  157. ctx.restore();
  158. if (hidden === true) {
  159. ctx.clearRect(0, 0, w, h);
  160. }
  161. }
  162. /**
  163. * Redraw all nodes
  164. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  165. * @param {CanvasRenderingContext2D} ctx
  166. * @param {Boolean} [alwaysShow]
  167. * @private
  168. */
  169. _drawNodes(ctx, alwaysShow = false) {
  170. let nodes = this.body.nodes;
  171. let nodeIndices = this.body.nodeIndices;
  172. let node;
  173. let selected = [];
  174. let margin = 20;
  175. let topLeft = this.canvas.DOMtoCanvas({x:-margin,y:-margin});
  176. let bottomRight = this.canvas.DOMtoCanvas({
  177. x: this.canvas.frame.canvas.clientWidth+margin,
  178. y: this.canvas.frame.canvas.clientHeight+margin
  179. });
  180. let viewableArea = {top:topLeft.y,left:topLeft.x,bottom:bottomRight.y,right:bottomRight.x};
  181. // draw unselected nodes;
  182. for (let i = 0; i < nodeIndices.length; i++) {
  183. node = nodes[nodeIndices[i]];
  184. // set selected nodes aside
  185. if (node.isSelected()) {
  186. selected.push(nodeIndices[i]);
  187. }
  188. else {
  189. if (alwaysShow === true) {
  190. node.draw(ctx);
  191. }
  192. else if (node.isBoundingBoxOverlappingWith(viewableArea) === true) {
  193. node.draw(ctx);
  194. }
  195. else {
  196. console.log("hidden")
  197. }
  198. }
  199. }
  200. // draw the selected nodes on top
  201. for (let i = 0; i < selected.length; i++) {
  202. node = nodes[selected[i]];
  203. node.draw(ctx);
  204. }
  205. }
  206. /**
  207. * Redraw all edges
  208. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  209. * @param {CanvasRenderingContext2D} ctx
  210. * @private
  211. */
  212. _drawEdges(ctx) {
  213. let edges = this.body.edges;
  214. let edgeIndices = this.body.edgeIndices;
  215. let edge;
  216. for (let i = 0; i < edgeIndices.length; i++) {
  217. edge = edges[edgeIndices[i]];
  218. if (edge.connected === true) {
  219. edge.draw(ctx);
  220. }
  221. }
  222. }
  223. /**
  224. * Redraw all edges
  225. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  226. * @param {CanvasRenderingContext2D} ctx
  227. * @private
  228. */
  229. _drawControlNodes(ctx) {
  230. let edges = this.body.edges;
  231. let edgeIndices = this.body.edgeIndices;
  232. let edge;
  233. for (let i = 0; i < edgeIndices.length; i++) {
  234. edge = edges[edgeIndices[i]];
  235. edge._drawControlNodes(ctx);
  236. }
  237. }
  238. /**
  239. * Determine if the browser requires a setTimeout or a requestAnimationFrame. This was required because
  240. * some implementations (safari and IE9) did not support requestAnimationFrame
  241. * @private
  242. */
  243. _determineBrowserMethod() {
  244. if (typeof window !== 'undefined') {
  245. let browserType = navigator.userAgent.toLowerCase();
  246. this.requiresTimeout = false;
  247. if (browserType.indexOf('msie 9.0') != -1) { // IE 9
  248. this.requiresTimeout = true;
  249. }
  250. else if (browserType.indexOf('safari') != -1) { // safari
  251. if (browserType.indexOf('chrome') <= -1) {
  252. this.requiresTimeout = true;
  253. }
  254. }
  255. }
  256. else {
  257. this.requiresTimeout = true;
  258. }
  259. }
  260. }
  261. export default CanvasRenderer;