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.

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