mxGuide.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. /**
  2. * Copyright (c) 2006-2015, JGraph Ltd
  3. * Copyright (c) 2006-2015, Gaudenz Alder
  4. */
  5. /**
  6. * Class: mxGuide
  7. *
  8. * Implements the alignment of selection cells to other cells in the graph.
  9. *
  10. * Constructor: mxGuide
  11. *
  12. * Constructs a new guide object.
  13. */
  14. function mxGuide(graph, states)
  15. {
  16. this.graph = graph;
  17. this.setStates(states);
  18. };
  19. /**
  20. * Variable: graph
  21. *
  22. * Reference to the enclosing <mxGraph> instance.
  23. */
  24. mxGuide.prototype.graph = null;
  25. /**
  26. * Variable: states
  27. *
  28. * Contains the <mxCellStates> that are used for alignment.
  29. */
  30. mxGuide.prototype.states = null;
  31. /**
  32. * Variable: horizontal
  33. *
  34. * Specifies if horizontal guides are enabled. Default is true.
  35. */
  36. mxGuide.prototype.horizontal = true;
  37. /**
  38. * Variable: vertical
  39. *
  40. * Specifies if vertical guides are enabled. Default is true.
  41. */
  42. mxGuide.prototype.vertical = true;
  43. /**
  44. * Variable: guideX
  45. *
  46. * Holds the <mxShape> for the horizontal guide.
  47. */
  48. mxGuide.prototype.guideX = null;
  49. /**
  50. * Variable: guideY
  51. *
  52. * Holds the <mxShape> for the vertical guide.
  53. */
  54. mxGuide.prototype.guideY = null;
  55. /**
  56. * Variable: rounded
  57. *
  58. * Specifies if rounded coordinates should be used. Default is false.
  59. */
  60. mxGuide.prototype.rounded = false;
  61. /**
  62. * Variable: tolerance
  63. *
  64. * Default tolerance in px if grid is disabled. Default is 2.
  65. */
  66. mxGuide.prototype.tolerance = 2;
  67. /**
  68. * Function: setStates
  69. *
  70. * Sets the <mxCellStates> that should be used for alignment.
  71. */
  72. mxGuide.prototype.setStates = function(states)
  73. {
  74. this.states = states;
  75. };
  76. /**
  77. * Function: isEnabledForEvent
  78. *
  79. * Returns true if the guide should be enabled for the given native event. This
  80. * implementation always returns true.
  81. */
  82. mxGuide.prototype.isEnabledForEvent = function(evt)
  83. {
  84. return true;
  85. };
  86. /**
  87. * Function: getGuideTolerance
  88. *
  89. * Returns the tolerance for the guides. Default value is gridSize / 2.
  90. */
  91. mxGuide.prototype.getGuideTolerance = function(gridEnabled)
  92. {
  93. return (gridEnabled && this.graph.gridEnabled) ? this.graph.gridSize / 2 : this.tolerance;
  94. };
  95. /**
  96. * Function: createGuideShape
  97. *
  98. * Returns the mxShape to be used for painting the respective guide. This
  99. * implementation returns a new, dashed and crisp <mxPolyline> using
  100. * <mxConstants.GUIDE_COLOR> and <mxConstants.GUIDE_STROKEWIDTH> as the format.
  101. *
  102. * Parameters:
  103. *
  104. * horizontal - Boolean that specifies which guide should be created.
  105. */
  106. mxGuide.prototype.createGuideShape = function(horizontal)
  107. {
  108. var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH);
  109. guide.isDashed = true;
  110. return guide;
  111. };
  112. /**
  113. * Function: isStateIgnored
  114. *
  115. * Returns true if the given state should be ignored.
  116. */
  117. mxGuide.prototype.isStateIgnored = function(state)
  118. {
  119. return false;
  120. };
  121. /**
  122. * Function: move
  123. *
  124. * Moves the <bounds> by the given <mxPoint> and returnt the snapped point.
  125. */
  126. mxGuide.prototype.move = function(bounds, delta, gridEnabled, clone)
  127. {
  128. if (this.states != null && (this.horizontal || this.vertical) && bounds != null && delta != null)
  129. {
  130. var scale = this.graph.getView().scale;
  131. var tt = this.getGuideTolerance(gridEnabled) * scale;
  132. var b = bounds.clone();
  133. b.x += delta.x;
  134. b.y += delta.y;
  135. var overrideX = false;
  136. var stateX = null;
  137. var valueX = null;
  138. var overrideY = false;
  139. var stateY = null;
  140. var valueY = null;
  141. var ttX = tt;
  142. var ttY = tt;
  143. var left = b.x;
  144. var right = b.x + b.width;
  145. var center = b.getCenterX();
  146. var top = b.y;
  147. var bottom = b.y + b.height;
  148. var middle = b.getCenterY();
  149. // Snaps the left, center and right to the given x-coordinate
  150. function snapX(x, state, centerAlign)
  151. {
  152. var override = false;
  153. if (centerAlign && Math.abs(x - center) < ttX)
  154. {
  155. delta.x = x - bounds.getCenterX();
  156. ttX = Math.abs(x - center);
  157. override = true;
  158. }
  159. else if (!centerAlign)
  160. {
  161. if (Math.abs(x - left) < ttX)
  162. {
  163. delta.x = x - bounds.x;
  164. ttX = Math.abs(x - left);
  165. override = true;
  166. }
  167. else if (Math.abs(x - right) < ttX)
  168. {
  169. delta.x = x - bounds.x - bounds.width;
  170. ttX = Math.abs(x - right);
  171. override = true;
  172. }
  173. }
  174. if (override)
  175. {
  176. stateX = state;
  177. valueX = x;
  178. if (this.guideX == null)
  179. {
  180. this.guideX = this.createGuideShape(true);
  181. // Makes sure to use either VML or SVG shapes in order to implement
  182. // event-transparency on the background area of the rectangle since
  183. // HTML shapes do not let mouseevents through even when transparent
  184. this.guideX.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
  185. mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
  186. this.guideX.pointerEvents = false;
  187. this.guideX.init(this.graph.getView().getOverlayPane());
  188. }
  189. }
  190. overrideX = overrideX || override;
  191. };
  192. // Snaps the top, middle or bottom to the given y-coordinate
  193. function snapY(y, state, centerAlign)
  194. {
  195. var override = false;
  196. if (centerAlign && Math.abs(y - middle) < ttY)
  197. {
  198. delta.y = y - bounds.getCenterY();
  199. ttY = Math.abs(y - middle);
  200. override = true;
  201. }
  202. else if (!centerAlign)
  203. {
  204. if (Math.abs(y - top) < ttY)
  205. {
  206. delta.y = y - bounds.y;
  207. ttY = Math.abs(y - top);
  208. override = true;
  209. }
  210. else if (Math.abs(y - bottom) < ttY)
  211. {
  212. delta.y = y - bounds.y - bounds.height;
  213. ttY = Math.abs(y - bottom);
  214. override = true;
  215. }
  216. }
  217. if (override)
  218. {
  219. stateY = state;
  220. valueY = y;
  221. if (this.guideY == null)
  222. {
  223. this.guideY = this.createGuideShape(false);
  224. // Makes sure to use either VML or SVG shapes in order to implement
  225. // event-transparency on the background area of the rectangle since
  226. // HTML shapes do not let mouseevents through even when transparent
  227. this.guideY.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
  228. mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
  229. this.guideY.pointerEvents = false;
  230. this.guideY.init(this.graph.getView().getOverlayPane());
  231. }
  232. }
  233. overrideY = overrideY || override;
  234. };
  235. for (var i = 0; i < this.states.length; i++)
  236. {
  237. var state = this.states[i];
  238. if (state != null && !this.isStateIgnored(state))
  239. {
  240. // Align x
  241. if (this.horizontal)
  242. {
  243. snapX.call(this, state.getCenterX(), state, true);
  244. snapX.call(this, state.x, state, false);
  245. snapX.call(this, state.x + state.width, state, false);
  246. // Aligns left and right of shape to center of page
  247. if (state.cell == null)
  248. {
  249. snapX.call(this, state.getCenterX(), state, false);
  250. }
  251. }
  252. // Align y
  253. if (this.vertical)
  254. {
  255. snapY.call(this, state.getCenterY(), state, true);
  256. snapY.call(this, state.y, state, false);
  257. snapY.call(this, state.y + state.height, state, false);
  258. // Aligns left and right of shape to center of page
  259. if (state.cell == null)
  260. {
  261. snapY.call(this, state.getCenterY(), state, false);
  262. }
  263. }
  264. }
  265. }
  266. // Moves cells to the raster if not aligned
  267. this.graph.snapDelta(delta, bounds, !gridEnabled, overrideX, overrideY);
  268. delta = this.getDelta(bounds, stateX, delta.x, stateY, delta.y)
  269. // Redraws the guides
  270. var c = this.graph.container;
  271. if (!overrideX && this.guideX != null)
  272. {
  273. this.guideX.node.style.visibility = 'hidden';
  274. }
  275. else if (this.guideX != null)
  276. {
  277. var minY = null;
  278. var maxY = null;
  279. if (stateX != null && bounds != null)
  280. {
  281. minY = Math.min(bounds.y + delta.y - this.graph.panDy, stateX.y);
  282. maxY = Math.max(bounds.y + bounds.height + delta.y - this.graph.panDy, stateX.y + stateX.height);
  283. }
  284. if (minY != null && maxY != null)
  285. {
  286. this.guideX.points = [new mxPoint(valueX, minY), new mxPoint(valueX, maxY)];
  287. }
  288. else
  289. {
  290. this.guideX.points = [new mxPoint(valueX, -this.graph.panDy),
  291. new mxPoint(valueX, c.scrollHeight - 3 - this.graph.panDy)];
  292. }
  293. this.guideX.stroke = this.getGuideColor(stateX, true);
  294. this.guideX.node.style.visibility = 'visible';
  295. this.guideX.redraw();
  296. }
  297. if (!overrideY && this.guideY != null)
  298. {
  299. this.guideY.node.style.visibility = 'hidden';
  300. }
  301. else if (this.guideY != null)
  302. {
  303. var minX = null;
  304. var maxX = null;
  305. if (stateY != null && bounds != null)
  306. {
  307. minX = Math.min(bounds.x + delta.x - this.graph.panDx, stateY.x);
  308. maxX = Math.max(bounds.x + bounds.width + delta.x - this.graph.panDx, stateY.x + stateY.width);
  309. }
  310. if (minX != null && maxX != null)
  311. {
  312. this.guideY.points = [new mxPoint(minX, valueY), new mxPoint(maxX, valueY)];
  313. }
  314. else
  315. {
  316. this.guideY.points = [new mxPoint(-this.graph.panDx, valueY),
  317. new mxPoint(c.scrollWidth - 3 - this.graph.panDx, valueY)];
  318. }
  319. this.guideY.stroke = this.getGuideColor(stateY, false);
  320. this.guideY.node.style.visibility = 'visible';
  321. this.guideY.redraw();
  322. }
  323. }
  324. return delta;
  325. };
  326. /**
  327. * Function: getDelta
  328. *
  329. * Rounds to pixels for virtual states (eg. page guides)
  330. */
  331. mxGuide.prototype.getDelta = function(bounds, stateX, dx, stateY, dy)
  332. {
  333. var s = this.graph.view.scale;
  334. if (this.rounded || (stateX != null && stateX.cell == null))
  335. {
  336. dx = Math.round((bounds.x + dx) / s) * s - bounds.x;
  337. }
  338. if (this.rounded || (stateY != null && stateY.cell == null))
  339. {
  340. dy = Math.round((bounds.y + dy) / s) * s - bounds.y;
  341. }
  342. return new mxPoint(dx, dy);
  343. };
  344. /**
  345. * Function: getGuideColor
  346. *
  347. * Returns the color for the given state.
  348. */
  349. mxGuide.prototype.getGuideColor = function(state, horizontal)
  350. {
  351. return mxConstants.GUIDE_COLOR;
  352. };
  353. /**
  354. * Function: hide
  355. *
  356. * Hides all current guides.
  357. */
  358. mxGuide.prototype.hide = function()
  359. {
  360. this.setVisible(false);
  361. };
  362. /**
  363. * Function: setVisible
  364. *
  365. * Shows or hides the current guides.
  366. */
  367. mxGuide.prototype.setVisible = function(visible)
  368. {
  369. if (this.guideX != null)
  370. {
  371. this.guideX.node.style.visibility = (visible) ? 'visible' : 'hidden';
  372. }
  373. if (this.guideY != null)
  374. {
  375. this.guideY.node.style.visibility = (visible) ? 'visible' : 'hidden';
  376. }
  377. };
  378. /**
  379. * Function: destroy
  380. *
  381. * Destroys all resources that this object uses.
  382. */
  383. mxGuide.prototype.destroy = function()
  384. {
  385. if (this.guideX != null)
  386. {
  387. this.guideX.destroy();
  388. this.guideX = null;
  389. }
  390. if (this.guideY != null)
  391. {
  392. this.guideY.destroy();
  393. this.guideY = null;
  394. }
  395. };
  396. __mxOutput.mxGuide = typeof mxGuide !== 'undefined' ? mxGuide : undefined;