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.

240 lines
8.2 KiB

  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.options = {};
  15. this.defaultOptions = {
  16. width:'100%',
  17. height:'100%'
  18. }
  19. util.extend(this.options, this.defaultOptions);
  20. this.body.emitter.once("resize", (obj) => {
  21. if (obj.width !== 0) {
  22. this.body.view.translation.x = obj.width * 0.5;
  23. }
  24. if (obj.height !== 0) {
  25. this.body.view.translation.y = obj.height * 0.5;
  26. }
  27. });
  28. this.body.emitter.on("destroy", () => this.hammer.destroy());
  29. window.onresize = () => {this.setSize(); this.body.emitter.emit("_redraw");};
  30. this.pixelRatio = 1;
  31. }
  32. setOptions(options) {
  33. if (options !== undefined) {
  34. util.deepExtend(this.options, options);
  35. }
  36. }
  37. create() {
  38. // remove all elements from the container element.
  39. while (this.body.container.hasChildNodes()) {
  40. this.body.container.removeChild(this.body.container.firstChild);
  41. }
  42. this.frame = document.createElement('div');
  43. this.frame.className = 'vis network-frame';
  44. this.frame.style.position = 'relative';
  45. this.frame.style.overflow = 'hidden';
  46. this.frame.tabIndex = 900;
  47. //////////////////////////////////////////////////////////////////
  48. this.frame.canvas = document.createElement("canvas");
  49. this.frame.canvas.style.position = 'relative';
  50. this.frame.appendChild(this.frame.canvas);
  51. if (!this.frame.canvas.getContext) {
  52. let noCanvas = document.createElement( 'DIV' );
  53. noCanvas.style.color = 'red';
  54. noCanvas.style.fontWeight = 'bold' ;
  55. noCanvas.style.padding = '10px';
  56. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  57. this.frame.canvas.appendChild(noCanvas);
  58. }
  59. else {
  60. let ctx = this.frame.canvas.getContext("2d");
  61. this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio ||
  62. ctx.mozBackingStorePixelRatio ||
  63. ctx.msBackingStorePixelRatio ||
  64. ctx.oBackingStorePixelRatio ||
  65. ctx.backingStorePixelRatio || 1);
  66. this.frame.canvas.getContext("2d").setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
  67. }
  68. // add the frame to the container element
  69. this.body.container.appendChild(this.frame);
  70. this.body.view.scale = 1;
  71. this.body.view.translation = {x: 0.5 * this.frame.canvas.clientWidth,y: 0.5 * this.frame.canvas.clientHeight};
  72. this._bindHammer();
  73. }
  74. /**
  75. * This function binds hammer, it can be repeated over and over due to the uniqueness check.
  76. * @private
  77. */
  78. _bindHammer() {
  79. if (this.hammer !== undefined) {
  80. this.hammer.destroy();
  81. }
  82. this.drag = {};
  83. this.pinch = {};
  84. // init hammer
  85. this.hammer = new Hammer(this.frame.canvas);
  86. this.hammer.get('pinch').set({enable: true});
  87. hammerUtil.onTouch(this.hammer, (event) => {this.body.eventListeners.onTouch(event)});
  88. this.hammer.on('tap', (event) => {this.body.eventListeners.onTap(event)});
  89. this.hammer.on('doubletap', (event) => {this.body.eventListeners.onDoubleTap(event)});
  90. this.hammer.on('press', (event) => {this.body.eventListeners.onHold(event)});
  91. this.hammer.on('panstart', (event) => {this.body.eventListeners.onDragStart(event)});
  92. this.hammer.on('panmove', (event) => {this.body.eventListeners.onDrag(event)});
  93. this.hammer.on('panend', (event) => {this.body.eventListeners.onDragEnd(event)});
  94. this.hammer.on('pinch', (event) => {this.body.eventListeners.onPinch(event)});
  95. // TODO: neatly cleanup these handlers when re-creating the Canvas, IF these are done with hammer, event.stopPropagation will not work?
  96. this.frame.canvas.addEventListener('mousewheel', (event) => {this.body.eventListeners.onMouseWheel(event)});
  97. this.frame.canvas.addEventListener('DOMMouseScroll', (event) => {this.body.eventListeners.onMouseWheel(event)});
  98. this.frame.canvas.addEventListener('mousemove', (event) => {this.body.eventListeners.onMouseMove(event)});
  99. this.hammerFrame = new Hammer(this.frame);
  100. hammerUtil.onRelease(this.hammerFrame, (event) => {this.body.eventListeners.onRelease(event)});
  101. }
  102. /**
  103. * Set a new size for the network
  104. * @param {string} width Width in pixels or percentage (for example '800px'
  105. * or '50%')
  106. * @param {string} height Height in pixels or percentage (for example '400px'
  107. * or '30%')
  108. */
  109. setSize(width = this.options.width, height = this.options.height) {
  110. let emitEvent = false;
  111. let oldWidth = this.frame.canvas.width;
  112. let oldHeight = this.frame.canvas.height;
  113. if (width != this.options.width || height != this.options.height || this.frame.style.width != width || this.frame.style.height != height) {
  114. this.frame.style.width = width;
  115. this.frame.style.height = height;
  116. this.frame.canvas.style.width = '100%';
  117. this.frame.canvas.style.height = '100%';
  118. this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio;
  119. this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio;
  120. this.options.width = width;
  121. this.options.height = height;
  122. emitEvent = true;
  123. }
  124. else {
  125. // this would adapt the width of the canvas to the width from 100% if and only if
  126. // there is a change.
  127. if (this.frame.canvas.width != this.frame.canvas.clientWidth * this.pixelRatio) {
  128. this.frame.canvas.width = this.frame.canvas.clientWidth * this.pixelRatio;
  129. emitEvent = true;
  130. }
  131. if (this.frame.canvas.height != this.frame.canvas.clientHeight * this.pixelRatio) {
  132. this.frame.canvas.height = this.frame.canvas.clientHeight * this.pixelRatio;
  133. emitEvent = true;
  134. }
  135. }
  136. if (emitEvent === true) {
  137. 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});
  138. }
  139. };
  140. /**
  141. * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to
  142. * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  143. * @param {number} x
  144. * @returns {number}
  145. * @private
  146. */
  147. _XconvertDOMtoCanvas(x) {
  148. return (x - this.body.view.translation.x) / this.body.view.scale;
  149. }
  150. /**
  151. * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  152. * the X coordinate in DOM-space (coordinate point in browser relative to the container div)
  153. * @param {number} x
  154. * @returns {number}
  155. * @private
  156. */
  157. _XconvertCanvasToDOM(x) {
  158. return x * this.body.view.scale + this.body.view.translation.x;
  159. }
  160. /**
  161. * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to
  162. * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  163. * @param {number} y
  164. * @returns {number}
  165. * @private
  166. */
  167. _YconvertDOMtoCanvas(y) {
  168. return (y - this.body.view.translation.y) / this.body.view.scale;
  169. }
  170. /**
  171. * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  172. * the Y coordinate in DOM-space (coordinate point in browser relative to the container div)
  173. * @param {number} y
  174. * @returns {number}
  175. * @private
  176. */
  177. _YconvertCanvasToDOM(y) {
  178. return y * this.body.view.scale + this.body.view.translation.y;
  179. }
  180. /**
  181. *
  182. * @param {object} pos = {x: number, y: number}
  183. * @returns {{x: number, y: number}}
  184. * @constructor
  185. */
  186. canvasToDOM (pos) {
  187. return {x: this._XconvertCanvasToDOM(pos.x), y: this._YconvertCanvasToDOM(pos.y)};
  188. }
  189. /**
  190. *
  191. * @param {object} pos = {x: number, y: number}
  192. * @returns {{x: number, y: number}}
  193. * @constructor
  194. */
  195. DOMtoCanvas (pos) {
  196. return {x: this._XconvertDOMtoCanvas(pos.x), y: this._YconvertDOMtoCanvas(pos.y)};
  197. }
  198. }
  199. export default Canvas;