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.

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