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.

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