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.

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