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.

344 lines
9.8 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
  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("_resizeNodes", () => this._resizeNodes());
  33. this.body.emitter.on("_redraw", () => {
  34. if (this.renderingActive === false) {
  35. this._redraw();
  36. }
  37. });
  38. this.body.emitter.on("_blockRedrawRequests", () => {this.allowRedrawRequests = false;});
  39. this.body.emitter.on("_allowRedrawRequests", () => {this.allowRedrawRequests = true; });
  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.allowRedrawRequests === true) {
  111. this.redrawRequested = true;
  112. if (this.requiresTimeout === true) {
  113. window.setTimeout(this._redraw.bind(this, false), 0);
  114. }
  115. else {
  116. window.requestAnimationFrame(this._redraw.bind(this, false));
  117. }
  118. }
  119. }
  120. _redraw(hidden = false) {
  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. if (this.pixelRatio === undefined) {
  129. this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio ||
  130. ctx.mozBackingStorePixelRatio ||
  131. ctx.msBackingStorePixelRatio ||
  132. ctx.oBackingStorePixelRatio ||
  133. ctx.backingStorePixelRatio || 1);
  134. }
  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. this.body.emitter.emit("beforeDrawing", ctx);
  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. if (hidden === false) {
  146. if (this.dragging === false || (this.dragging === true && this.options.hideEdgesOnDrag === false)) {
  147. this._drawEdges(ctx);
  148. }
  149. }
  150. if (this.dragging === false || (this.dragging === true && this.options.hideNodesOnDrag === false)) {
  151. this._drawNodes(ctx, hidden);
  152. }
  153. if (this.controlNodesActive === true) {
  154. this._drawControlNodes(ctx);
  155. }
  156. //this.physics.nodesSolver._debug(ctx,"#F00F0F");
  157. this.body.emitter.emit("afterDrawing", ctx);
  158. // restore original scaling and translation
  159. ctx.restore();
  160. if (hidden === true) {
  161. ctx.clearRect(0, 0, w, h);
  162. }
  163. }
  164. /**
  165. * Redraw all nodes
  166. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  167. * @param {CanvasRenderingContext2D} ctx
  168. * @param {Boolean} [alwaysShow]
  169. * @private
  170. */
  171. _resizeNodes() {
  172. let ctx = this.canvas.frame.canvas.getContext('2d');
  173. if (this.pixelRatio === undefined) {
  174. this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio ||
  175. ctx.mozBackingStorePixelRatio ||
  176. ctx.msBackingStorePixelRatio ||
  177. ctx.oBackingStorePixelRatio ||
  178. ctx.backingStorePixelRatio || 1);
  179. }
  180. ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
  181. ctx.save();
  182. ctx.translate(this.body.view.translation.x, this.body.view.translation.y);
  183. ctx.scale(this.body.view.scale, this.body.view.scale);
  184. let nodes = this.body.nodes;
  185. let node;
  186. // resize all nodes
  187. for (let nodeId in nodes) {
  188. if (nodes.hasOwnProperty(nodeId)) {
  189. node = nodes[nodeId];
  190. node.resize(ctx);
  191. node.updateBoundingBox(ctx);
  192. }
  193. }
  194. // restore original scaling and translation
  195. ctx.restore();
  196. }
  197. /**
  198. * Redraw all nodes
  199. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  200. * @param {CanvasRenderingContext2D} ctx
  201. * @param {Boolean} [alwaysShow]
  202. * @private
  203. */
  204. _drawNodes(ctx, alwaysShow = false) {
  205. let nodes = this.body.nodes;
  206. let nodeIndices = this.body.nodeIndices;
  207. let node;
  208. let selected = [];
  209. let margin = 20;
  210. let topLeft = this.canvas.DOMtoCanvas({x:-margin,y:-margin});
  211. let bottomRight = this.canvas.DOMtoCanvas({
  212. x: this.canvas.frame.canvas.clientWidth+margin,
  213. y: this.canvas.frame.canvas.clientHeight+margin
  214. });
  215. let viewableArea = {top:topLeft.y,left:topLeft.x,bottom:bottomRight.y,right:bottomRight.x};
  216. // draw unselected nodes;
  217. for (let i = 0; i < nodeIndices.length; i++) {
  218. node = nodes[nodeIndices[i]];
  219. // set selected nodes aside
  220. if (node.isSelected()) {
  221. selected.push(nodeIndices[i]);
  222. }
  223. else {
  224. if (alwaysShow === true) {
  225. node.draw(ctx);
  226. }
  227. else if (node.isBoundingBoxOverlappingWith(viewableArea) === true) {
  228. node.draw(ctx);
  229. }
  230. else {
  231. node.updateBoundingBox(ctx);
  232. }
  233. }
  234. }
  235. // draw the selected nodes on top
  236. for (let i = 0; i < selected.length; i++) {
  237. node = nodes[selected[i]];
  238. node.draw(ctx);
  239. }
  240. }
  241. /**
  242. * Redraw all edges
  243. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  244. * @param {CanvasRenderingContext2D} ctx
  245. * @private
  246. */
  247. _drawEdges(ctx) {
  248. let edges = this.body.edges;
  249. let edgeIndices = this.body.edgeIndices;
  250. let edge;
  251. for (let i = 0; i < edgeIndices.length; i++) {
  252. edge = edges[edgeIndices[i]];
  253. if (edge.connected === true) {
  254. edge.draw(ctx);
  255. }
  256. }
  257. }
  258. /**
  259. * Redraw all edges
  260. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  261. * @param {CanvasRenderingContext2D} ctx
  262. * @private
  263. */
  264. _drawControlNodes(ctx) {
  265. let edges = this.body.edges;
  266. let edgeIndices = this.body.edgeIndices;
  267. let edge;
  268. for (let i = 0; i < edgeIndices.length; i++) {
  269. edge = edges[edgeIndices[i]];
  270. edge._drawControlNodes(ctx);
  271. }
  272. }
  273. /**
  274. * Determine if the browser requires a setTimeout or a requestAnimationFrame. This was required because
  275. * some implementations (safari and IE9) did not support requestAnimationFrame
  276. * @private
  277. */
  278. _determineBrowserMethod() {
  279. if (typeof window !== 'undefined') {
  280. let browserType = navigator.userAgent.toLowerCase();
  281. this.requiresTimeout = false;
  282. if (browserType.indexOf('msie 9.0') != -1) { // IE 9
  283. this.requiresTimeout = true;
  284. }
  285. else if (browserType.indexOf('safari') != -1) { // safari
  286. if (browserType.indexOf('chrome') <= -1) {
  287. this.requiresTimeout = true;
  288. }
  289. }
  290. }
  291. else {
  292. this.requiresTimeout = true;
  293. }
  294. }
  295. }
  296. export default CanvasRenderer;