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.

294 lines
9.8 KiB

9 years ago
9 years ago
9 years ago
  1. let Hammer = require('../../module/hammer');
  2. let hammerUtil = require('../../hammerUtil');
  3. let util = require('../../util');
  4. /**
  5. * Create the main frame for the Network.
  6. * This function is executed once when a Network object is created. The frame
  7. * contains a canvas, and this canvas contains all objects like the axis and
  8. * nodes.
  9. * @private
  10. */
  11. class Canvas {
  12. constructor(body) {
  13. this.body = body;
  14. this.pixelRatio = 1;
  15. this.resizeTimer = undefined;
  16. this.options = {};
  17. this.defaultOptions = {
  18. width:'100%',
  19. height:'100%',
  20. autoResize: true
  21. }
  22. util.extend(this.options, this.defaultOptions);
  23. this.bindEventListeners();
  24. }
  25. bindEventListeners() {
  26. // bind the events
  27. this.body.emitter.once("resize", (obj) => {
  28. if (obj.width !== 0) {
  29. this.body.view.translation.x = obj.width * 0.5;
  30. }
  31. if (obj.height !== 0) {
  32. this.body.view.translation.y = obj.height * 0.5;
  33. }
  34. });
  35. this.body.emitter.on("destroy", () => {
  36. this.hammerFrame.destroy();
  37. this.hammer.destroy();
  38. this._cleanUp();
  39. });
  40. }
  41. setOptions(options) {
  42. if (options !== undefined) {
  43. if (options.width !== undefined) {this.options.width = this._prepareValue(options.width );}
  44. if (options.height!== undefined) {this.options.height= this._prepareValue(options.height);}
  45. }
  46. if (this.options.autoResize === true) {
  47. // automatically adapt to a changing size of the browser.
  48. this._cleanUp();
  49. this.resizeTimer = setInterval(() => {this.setSize(); this.body.emitter.emit("_requestRedraw");}, 1000);
  50. util.addEventListener(window,'resize',this._onResize);
  51. }
  52. }
  53. _cleanUp() {
  54. // automatically adapt to a changing size of the browser.
  55. if (this.resizeTimer !== undefined) {
  56. clearInterval(this.resizeTimer);
  57. }
  58. util.removeEventListener(window,'resize',this._onResize);
  59. }
  60. _onResize() {
  61. this.setSize();
  62. this.body.emitter.emit("_redraw");
  63. }
  64. _prepareValue(value) {
  65. if (typeof value === 'number') {
  66. return value + 'px';
  67. }
  68. else if (typeof value === 'string') {
  69. if (value.indexOf('%') !== -1 || value.indexOf('px') !== -1) {
  70. return value;
  71. }
  72. else if (value.indexOf('%') === -1) {
  73. return value + 'px';
  74. }
  75. }
  76. throw new Error('Could not use the value supplie for width or height:' + value);
  77. }
  78. /**
  79. * Create the HTML
  80. */
  81. _create() {
  82. // remove all elements from the container element.
  83. while (this.body.container.hasChildNodes()) {
  84. this.body.container.removeChild(this.body.container.firstChild);
  85. }
  86. this.frame = document.createElement('div');
  87. this.frame.className = 'vis-network';
  88. this.frame.style.position = 'relative';
  89. this.frame.style.overflow = 'hidden';
  90. this.frame.tabIndex = 900; // tab index is required for keycharm to bind keystrokes to the div instead of the window
  91. //////////////////////////////////////////////////////////////////
  92. this.frame.canvas = document.createElement("canvas");
  93. this.frame.canvas.style.position = 'relative';
  94. this.frame.appendChild(this.frame.canvas);
  95. if (!this.frame.canvas.getContext) {
  96. let noCanvas = document.createElement( 'DIV' );
  97. noCanvas.style.color = 'red';
  98. noCanvas.style.fontWeight = 'bold' ;
  99. noCanvas.style.padding = '10px';
  100. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  101. this.frame.canvas.appendChild(noCanvas);
  102. }
  103. else {
  104. let ctx = this.frame.canvas.getContext("2d");
  105. this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio ||
  106. ctx.mozBackingStorePixelRatio ||
  107. ctx.msBackingStorePixelRatio ||
  108. ctx.oBackingStorePixelRatio ||
  109. ctx.backingStorePixelRatio || 1);
  110. this.frame.canvas.getContext("2d").setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
  111. }
  112. // add the frame to the container element
  113. this.body.container.appendChild(this.frame);
  114. this.body.view.scale = 1;
  115. this.body.view.translation = {x: 0.5 * this.frame.canvas.clientWidth,y: 0.5 * this.frame.canvas.clientHeight};
  116. this._bindHammer();
  117. }
  118. /**
  119. * This function binds hammer, it can be repeated over and over due to the uniqueness check.
  120. * @private
  121. */
  122. _bindHammer() {
  123. if (this.hammer !== undefined) {
  124. this.hammer.destroy();
  125. }
  126. this.drag = {};
  127. this.pinch = {};
  128. // init hammer
  129. this.hammer = new Hammer(this.frame.canvas);
  130. this.hammer.get('pinch').set({enable: true});
  131. hammerUtil.onTouch(this.hammer, (event) => {this.body.eventListeners.onTouch(event)});
  132. this.hammer.on('tap', (event) => {this.body.eventListeners.onTap(event)});
  133. this.hammer.on('doubletap', (event) => {this.body.eventListeners.onDoubleTap(event)});
  134. this.hammer.on('press', (event) => {this.body.eventListeners.onHold(event)});
  135. this.hammer.on('panstart', (event) => {this.body.eventListeners.onDragStart(event)});
  136. this.hammer.on('panmove', (event) => {this.body.eventListeners.onDrag(event)});
  137. this.hammer.on('panend', (event) => {this.body.eventListeners.onDragEnd(event)});
  138. this.hammer.on('pinch', (event) => {this.body.eventListeners.onPinch(event)});
  139. // TODO: neatly cleanup these handlers when re-creating the Canvas, IF these are done with hammer, event.stopPropagation will not work?
  140. this.frame.canvas.addEventListener('mousewheel', (event) => {this.body.eventListeners.onMouseWheel(event)});
  141. this.frame.canvas.addEventListener('DOMMouseScroll', (event) => {this.body.eventListeners.onMouseWheel(event)});
  142. this.frame.canvas.addEventListener('mousemove', (event) => {this.body.eventListeners.onMouseMove(event)});
  143. this.frame.canvas.addEventListener('contextmenu', (event) => {this.body.eventListeners.onContext(event)});
  144. this.hammerFrame = new Hammer(this.frame);
  145. hammerUtil.onRelease(this.hammerFrame, (event) => {this.body.eventListeners.onRelease(event)});
  146. }
  147. /**
  148. * Set a new size for the network
  149. * @param {string} width Width in pixels or percentage (for example '800px'
  150. * or '50%')
  151. * @param {string} height Height in pixels or percentage (for example '400px'
  152. * or '30%')
  153. */
  154. setSize(width = this.options.width, height = this.options.height) {
  155. width = this._prepareValue(width);
  156. height= this._prepareValue(height);
  157. let emitEvent = false;
  158. let oldWidth = this.frame.canvas.width;
  159. let oldHeight = this.frame.canvas.height;
  160. if (width != this.options.width || height != this.options.height || this.frame.style.width != width || this.frame.style.height != height) {
  161. this.frame.style.width = width;
  162. this.frame.style.height = height;
  163. this.frame.canvas.style.width = '100%';
  164. this.frame.canvas.style.height = '100%';
  165. this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio;
  166. this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio;
  167. this.options.width = width;
  168. this.options.height = height;
  169. emitEvent = true;
  170. }
  171. else {
  172. // this would adapt the width of the canvas to the width from 100% if and only if
  173. // there is a change.
  174. if (this.frame.canvas.width != this.frame.canvas.clientWidth * this.pixelRatio) {
  175. this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio;
  176. emitEvent = true;
  177. }
  178. if (this.frame.canvas.height != this.frame.canvas.clientHeight * this.pixelRatio) {
  179. this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio;
  180. emitEvent = true;
  181. }
  182. }
  183. if (emitEvent === true) {
  184. this.body.emitter.emit('resize', {width:this.frame.canvas.width / this.pixelRatio, height:this.frame.canvas.height / this.pixelRatio, oldWidth: oldWidth / this.pixelRatio, oldHeight: oldHeight / this.pixelRatio});
  185. }
  186. };
  187. /**
  188. * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to
  189. * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  190. * @param {number} x
  191. * @returns {number}
  192. * @private
  193. */
  194. _XconvertDOMtoCanvas(x) {
  195. return (x - this.body.view.translation.x) / this.body.view.scale;
  196. }
  197. /**
  198. * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  199. * the X coordinate in DOM-space (coordinate point in browser relative to the container div)
  200. * @param {number} x
  201. * @returns {number}
  202. * @private
  203. */
  204. _XconvertCanvasToDOM(x) {
  205. return x * this.body.view.scale + this.body.view.translation.x;
  206. }
  207. /**
  208. * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to
  209. * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  210. * @param {number} y
  211. * @returns {number}
  212. * @private
  213. */
  214. _YconvertDOMtoCanvas(y) {
  215. return (y - this.body.view.translation.y) / this.body.view.scale;
  216. }
  217. /**
  218. * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  219. * the Y coordinate in DOM-space (coordinate point in browser relative to the container div)
  220. * @param {number} y
  221. * @returns {number}
  222. * @private
  223. */
  224. _YconvertCanvasToDOM(y) {
  225. return y * this.body.view.scale + this.body.view.translation.y;
  226. }
  227. /**
  228. *
  229. * @param {object} pos = {x: number, y: number}
  230. * @returns {{x: number, y: number}}
  231. * @constructor
  232. */
  233. canvasToDOM (pos) {
  234. return {x: this._XconvertCanvasToDOM(pos.x), y: this._YconvertCanvasToDOM(pos.y)};
  235. }
  236. /**
  237. *
  238. * @param {object} pos = {x: number, y: number}
  239. * @returns {{x: number, y: number}}
  240. * @constructor
  241. */
  242. DOMtoCanvas (pos) {
  243. return {x: this._XconvertDOMtoCanvas(pos.x), y: this._YconvertDOMtoCanvas(pos.y)};
  244. }
  245. }
  246. export default Canvas;