mxOutline.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. /**
  2. * Copyright (c) 2006-2015, JGraph Ltd
  3. * Copyright (c) 2006-2015, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxOutline
  7. *
  8. * Implements an outline (aka overview) for a graph. Set <updateOnPan> to true
  9. * to enable updates while the source graph is panning.
  10. *
  11. * Example:
  12. *
  13. * (code)
  14. * var outline = new mxOutline(graph, div);
  15. * (end)
  16. *
  17. * If an outline is used in an <mxWindow> in IE8 standards mode, the following
  18. * code makes sure that the shadow filter is not inherited and that any
  19. * transparent elements in the graph do not show the page background, but the
  20. * background of the graph container.
  21. *
  22. * (code)
  23. * if (document.documentMode == 8)
  24. * {
  25. * container.style.filter = 'progid:DXImageTransform.Microsoft.alpha(opacity=100)';
  26. * }
  27. * (end)
  28. *
  29. * To move the graph to the top, left corner the following code can be used.
  30. *
  31. * (code)
  32. * var scale = graph.view.scale;
  33. * var bounds = graph.getGraphBounds();
  34. * graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale);
  35. * (end)
  36. *
  37. * To toggle the suspended mode, the following can be used.
  38. *
  39. * (code)
  40. * outline.setSuspended(!outline.isSuspended());
  41. * (end)
  42. *
  43. * Constructor: mxOutline
  44. *
  45. * Constructs a new outline for the specified graph inside the given
  46. * container.
  47. *
  48. * Parameters:
  49. *
  50. * source - <mxGraph> to create the outline for.
  51. * container - DOM node that will contain the outline.
  52. */
  53. function mxOutline(source, container)
  54. {
  55. this.source = source;
  56. if (container != null)
  57. {
  58. this.init(container);
  59. }
  60. };
  61. /**
  62. * Function: source
  63. *
  64. * Reference to the source <mxGraph>.
  65. */
  66. mxOutline.prototype.source = null;
  67. /**
  68. * Function: container
  69. *
  70. * Reference to the DOM node containing the outline.
  71. */
  72. mxOutline.prototype.container = null;
  73. /**
  74. * Function: enabled
  75. *
  76. * Reference to the <mxGraph> that renders the outline.
  77. */
  78. mxOutline.prototype.enabled = true;
  79. /**
  80. * Variable: suspended
  81. *
  82. * Optional boolean flag to suspend updates. Default is false.
  83. */
  84. mxOutline.prototype.suspended = false;
  85. /**
  86. * Variable: border
  87. *
  88. * Border to be added at the bottom and right. Default is 10.
  89. */
  90. mxOutline.prototype.border = 14;
  91. /**
  92. * Variable: opacity
  93. */
  94. mxOutline.prototype.opacity = (mxClient.IS_IE11) ? 0.9 : 0.7;
  95. /**
  96. * Function: init
  97. *
  98. * Initializes the outline inside the given container.
  99. */
  100. mxOutline.prototype.init = function(container)
  101. {
  102. this.container = container;
  103. this.updateHandler = mxUtils.bind(this, function(sender, evt)
  104. {
  105. this.update(true);
  106. });
  107. // Updates the scale of the outline after a change of the main graph
  108. this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler);
  109. this.source.addListener(mxEvent.REFRESH, this.updateHandler);
  110. // Adds listeners to keep the outline in sync with the source graph
  111. var view = this.source.getView();
  112. view.addListener(mxEvent.UP, this.updateHandler);
  113. view.addListener(mxEvent.DOWN, this.updateHandler);
  114. view.addListener(mxEvent.SCALE, this.updateHandler);
  115. view.addListener(mxEvent.TRANSLATE, this.updateHandler);
  116. view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler);
  117. this.scrollHandler = mxUtils.bind(this, function(sender, evt)
  118. {
  119. this.update(false);
  120. });
  121. // Updates blue rectangle on scroll
  122. mxEvent.addListener(this.source.container, 'scroll', this.scrollHandler);
  123. this.source.addListener(mxEvent.PAN, this.scrollHandler);
  124. this.update(true);
  125. };
  126. /**
  127. * Function: isEnabled
  128. *
  129. * Returns true if events are handled. This implementation
  130. * returns <enabled>.
  131. */
  132. mxOutline.prototype.isEnabled = function()
  133. {
  134. return this.enabled;
  135. };
  136. /**
  137. * Function: setEnabled
  138. *
  139. * Enables or disables event handling. This implementation
  140. * updates <enabled>.
  141. *
  142. * Parameters:
  143. *
  144. * value - Boolean that specifies the new enabled state.
  145. */
  146. mxOutline.prototype.setEnabled = function(value)
  147. {
  148. this.enabled = value;
  149. };
  150. /**
  151. * Function: isSuspended
  152. *
  153. * Returns true if events are handled. This implementation
  154. * returns <enabled>.
  155. */
  156. mxOutline.prototype.isSuspended = function()
  157. {
  158. return this.suspended;
  159. };
  160. /**
  161. * Function: setSuspended
  162. *
  163. * Enables or disables event handling. This implementation
  164. * updates <enabled>.
  165. *
  166. * Parameters:
  167. *
  168. * value - Boolean that specifies the new enabled state.
  169. */
  170. mxOutline.prototype.setSuspended = function(value)
  171. {
  172. this.suspended = value;
  173. this.update(true);
  174. };
  175. /**
  176. * Function: isScrolling
  177. *
  178. * Returns true if scrollbars should be used for panning.
  179. */
  180. mxOutline.prototype.isScrolling = function()
  181. {
  182. return this.source.useScrollbarsForPanning &&
  183. mxUtils.hasScrollbars(this.source.container);
  184. };
  185. /**
  186. * Function: createSvg
  187. *
  188. * Updates the outline.
  189. */
  190. mxOutline.prototype.createSvg = function()
  191. {
  192. var root = document.createElementNS(mxConstants.NS_SVG, 'svg');
  193. root.style.position = 'absolute';
  194. root.style.left = '0px';
  195. root.style.top = '0px';
  196. root.style.width = '100%';
  197. root.style.height = '100%';
  198. root.style.display = 'block';
  199. root.style.padding = this.border + 'px';
  200. root.style.boxSizing = 'border-box';
  201. root.style.overflow = 'visible';
  202. root.style.cursor = 'default';
  203. root.setAttribute('shape-rendering', 'optimizeSpeed');
  204. root.setAttribute('image-rendering', 'optimizeSpeed');
  205. return root;
  206. };
  207. /**
  208. * Function: processSvg
  209. *
  210. * Updates the outline.
  211. */
  212. mxOutline.prototype.addGestureListeners = function(svg)
  213. {
  214. var p0 = null;
  215. var x0 = 0;
  216. var y0 = 0;
  217. var s = 1;
  218. var start = mxUtils.bind(this, function(evt)
  219. {
  220. if (this.isEnabled())
  221. {
  222. p0 = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
  223. var w = svg.clientWidth - 2 * this.border;
  224. var h = svg.clientHeight - 2 * this.border;
  225. var b = this.getViewBox();
  226. s = Math.max(b.width / w, b.height / h);
  227. // Sets initial position if outside viewport
  228. if (mxEvent.getSource(evt) != this.viewport)
  229. {
  230. if (this.isScrolling())
  231. {
  232. var dx = w - b.width / s;
  233. var dy = h - b.height / s;
  234. var r = this.svg.getBoundingClientRect();
  235. this.source.container.scrollLeft = b.x - dx * s / 2 +
  236. (p0.x - this.border - r.left) * s;
  237. this.source.container.scrollTop = b.y - dy * s / 2 +
  238. (p0.y - this.border - r.top) * s;
  239. }
  240. else
  241. {
  242. var t = this.source.view.translate;
  243. var v = this.viewport.getBoundingClientRect();
  244. var dx = (mxEvent.getClientX(evt) - v.left) * s / this.source.view.scale;
  245. var dy = (mxEvent.getClientY(evt) - v.top) * s / this.source.view.scale;
  246. this.source.getView().setTranslate(t.x - dx, t.y - dy);
  247. this.source.panGraph(0, 0);
  248. }
  249. }
  250. mxEvent.addGestureListeners(document, null, dragHandler, dropHandler);
  251. x0 = this.source.container.scrollLeft;
  252. y0 = this.source.container.scrollTop;
  253. mxEvent.consume(evt);
  254. }
  255. });
  256. // Adds a temporary pair of listeners to intercept the gesture in the document
  257. var dragHandler = mxUtils.bind(this, function(evt)
  258. {
  259. if (this.isEnabled() && p0 != null)
  260. {
  261. if (this.isScrolling())
  262. {
  263. this.source.container.scrollLeft = x0 +
  264. (mxEvent.getClientX(evt) - p0.x) * s;
  265. this.source.container.scrollTop = y0 +
  266. (mxEvent.getClientY(evt) - p0.y) * s;
  267. }
  268. else
  269. {
  270. this.source.panGraph((p0.x - mxEvent.getClientX(evt)) * s,
  271. (p0.y - mxEvent.getClientY(evt)) * s);
  272. }
  273. mxEvent.consume(evt);
  274. }
  275. });
  276. var dropHandler = mxUtils.bind(this, function(evt)
  277. {
  278. if (this.isEnabled() && p0 != null)
  279. {
  280. if (!this.isScrolling())
  281. {
  282. var dx = (mxEvent.getClientX(evt) - p0.x) * s / this.source.view.scale;
  283. var dy = (mxEvent.getClientY(evt) - p0.y) * s / this.source.view.scale;
  284. var t = this.source.view.translate;
  285. this.source.getView().setTranslate(t.x - dx, t.y - dy);
  286. this.source.panGraph(0, 0);
  287. }
  288. mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);
  289. mxEvent.consume(evt);
  290. p0 = null;
  291. }
  292. });
  293. mxEvent.addGestureListeners(svg, start, dragHandler, dropHandler);
  294. };
  295. /**
  296. * Function: getViewBox
  297. *
  298. * Returns the rectangle that is used for clipping the svg tree.
  299. */
  300. mxOutline.prototype.getViewBox = function()
  301. {
  302. return this.source.getGraphBounds();
  303. };
  304. /**
  305. * Function: updateSvg
  306. *
  307. * Returns the graph bound boxing of the source.
  308. */
  309. mxOutline.prototype.updateSvg = function()
  310. {
  311. if (this.svg == null)
  312. {
  313. this.svg = this.createSvg();
  314. this.addGestureListeners(this.svg);
  315. this.container.appendChild(this.svg);
  316. }
  317. var b = this.getViewBox();
  318. this.svg.setAttribute('viewBox',
  319. Math.round(b.x) + ' ' + Math.round(b.y) + ' ' +
  320. Math.round(b.width) + ' ' + Math.round(b.height));
  321. var bg = this.source.background;
  322. this.svg.style.backgroundColor = (bg == mxConstants.NONE) ? '' : bg;
  323. this.updateDrawPane();
  324. };
  325. /**
  326. * Function: updateDrawPane
  327. *
  328. * Returns the graph bound boxing of the source.
  329. */
  330. mxOutline.prototype.updateDrawPane = function()
  331. {
  332. if (this.drawPane != null)
  333. {
  334. this.drawPane.parentNode.removeChild(this.drawPane);
  335. }
  336. this.drawPane = this.source.view.getDrawPane().cloneNode(true);
  337. this.drawPane.style.opacity = this.opacity;
  338. this.processSvg(this.drawPane);
  339. if (this.viewport != null)
  340. {
  341. this.svg.insertBefore(this.drawPane, this.viewport);
  342. }
  343. else
  344. {
  345. this.svg.appendChild(this.drawPane);
  346. }
  347. };
  348. /**
  349. * Function: processSvg
  350. *
  351. * Removes cursor, hidden elements and text and fixes stroke widths and scaling.
  352. */
  353. mxOutline.prototype.processSvg = function(svg)
  354. {
  355. var s = (mxClient.IS_IE11) ? Math.max(1, this.source.view.scale) : this.source.view.scale;
  356. Array.prototype.slice.call(svg.getElementsByTagName('*')).forEach(
  357. mxUtils.bind(this, function(item) {
  358. if (item.nodeName == 'text' || item.nodeName == 'foreignObject' ||
  359. item.getAttribute('visibility') == 'hidden' ||
  360. !(item instanceof SVGElement))
  361. {
  362. item.parentNode.removeChild(item);
  363. }
  364. else
  365. {
  366. var sw = parseInt(item.getAttribute('stroke-width') || 1);
  367. if (!isNaN(sw))
  368. {
  369. item.setAttribute('stroke-width', Math.max((mxClient.IS_IE11) ? 4 : 1, sw / (5 * s)));
  370. }
  371. item.setAttribute('vector-effect', 'non-scaling-stroke');
  372. item.style.cursor = '';
  373. }
  374. }));
  375. };
  376. /**
  377. * Function: updateViewport
  378. *
  379. * Updates the outline.
  380. */
  381. mxOutline.prototype.updateViewport = function()
  382. {
  383. if (this.svg != null)
  384. {
  385. if (this.viewport == null)
  386. {
  387. this.viewport = this.createViewport();
  388. this.svg.appendChild(this.viewport);
  389. }
  390. var c = this.source.container;
  391. var v = new mxRectangle(c.scrollLeft, c.scrollTop, c.clientWidth, c.clientHeight);
  392. if (!this.isScrolling())
  393. {
  394. v.x = -this.source.panDx;
  395. v.y = -this.source.panDy;
  396. }
  397. this.viewport.setAttribute('x', v.x);
  398. this.viewport.setAttribute('y', v.y);
  399. this.viewport.setAttribute('width', v.width);
  400. this.viewport.setAttribute('height', v.height);
  401. }
  402. };
  403. /**
  404. * Function: createRect
  405. *
  406. * Updates the outline.
  407. */
  408. mxOutline.prototype.createViewport = function()
  409. {
  410. var v = this.svg.ownerDocument.createElementNS(mxConstants.NS_SVG, 'rect');
  411. v.setAttribute('stroke-width', (mxClient.IS_IE11) ? '12' : '3');
  412. v.setAttribute('stroke', HoverIcons.prototype.arrowFill);
  413. v.setAttribute('fill', HoverIcons.prototype.arrowFill);
  414. v.setAttribute('vector-effect', 'non-scaling-stroke');
  415. v.setAttribute('fill-opacity', 0.2);
  416. v.style.cursor = 'move';
  417. return v;
  418. };
  419. /**
  420. * Function: update
  421. *
  422. * Updates the outline.
  423. */
  424. mxOutline.prototype.update = function(fullUpdate)
  425. {
  426. if (this.source != null && this.source.container != null)
  427. {
  428. if (this.thread != null)
  429. {
  430. window.clearTimeout(this.thread);
  431. this.thread = null;
  432. }
  433. this.fullUpdate = this.fullUpdate || fullUpdate;
  434. this.thread = window.setTimeout(mxUtils.bind(this, function()
  435. {
  436. if (!this.isSuspended())
  437. {
  438. if (this.fullUpdate)
  439. {
  440. this.updateSvg();
  441. }
  442. this.updateViewport();
  443. }
  444. this.fullUpdate = null;
  445. this.thread = null;
  446. }), (this.isScrolling() ? 10 : 0));
  447. }
  448. };
  449. /**
  450. * Function: destroy
  451. *
  452. * Destroy this outline and removes all listeners from <source>.
  453. */
  454. mxOutline.prototype.destroy = function()
  455. {
  456. if (this.svg != null)
  457. {
  458. this.svg.parentNode.removeChild(this.svg);
  459. this.svg = null;
  460. }
  461. if (this.source != null)
  462. {
  463. this.source.removeListener(this.scrollHandler);
  464. this.source.removeListener(this.updateHandler);
  465. this.source.getView().removeListener(this.updateHandler);
  466. this.source.getModel().removeListener(this.updateHandler);
  467. mxEvent.removeListener(this.source.container, 'scroll', this.scrollHandler);
  468. this.source = null;
  469. }
  470. };