AlignElements.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import {
  2. filter,
  3. forEach,
  4. isArray,
  5. sortBy
  6. } from 'min-dash';
  7. function last(arr) {
  8. return arr && arr[arr.length - 1];
  9. }
  10. function sortTopOrMiddle(element) {
  11. return element.y;
  12. }
  13. function sortLeftOrCenter(element) {
  14. return element.x;
  15. }
  16. /**
  17. * Sorting functions for different types of alignment
  18. *
  19. * @type {Object}
  20. *
  21. * @return {Function}
  22. */
  23. var ALIGNMENT_SORTING = {
  24. left: sortLeftOrCenter,
  25. center: sortLeftOrCenter,
  26. right: function(element) {
  27. return element.x + element.width;
  28. },
  29. top: sortTopOrMiddle,
  30. middle: sortTopOrMiddle,
  31. bottom: function(element) {
  32. return element.y + element.height;
  33. }
  34. };
  35. export default function AlignElements(modeling, rules) {
  36. this._modeling = modeling;
  37. this._rules = rules;
  38. }
  39. AlignElements.$inject = [ 'modeling', 'rules' ];
  40. /**
  41. * Get the relevant "axis" and "dimension" related to the current type of alignment
  42. *
  43. * @param {string} type left|right|center|top|bottom|middle
  44. *
  45. * @return {Object} { axis, dimension }
  46. */
  47. AlignElements.prototype._getOrientationDetails = function(type) {
  48. var vertical = [ 'top', 'bottom', 'middle' ],
  49. axis = 'x',
  50. dimension = 'width';
  51. if (vertical.indexOf(type) !== -1) {
  52. axis = 'y';
  53. dimension = 'height';
  54. }
  55. return {
  56. axis: axis,
  57. dimension: dimension
  58. };
  59. };
  60. AlignElements.prototype._isType = function(type, types) {
  61. return types.indexOf(type) !== -1;
  62. };
  63. /**
  64. * Get a point on the relevant axis where elements should align to
  65. *
  66. * @param {string} type left|right|center|top|bottom|middle
  67. * @param {Array} sortedElements
  68. *
  69. * @return {Object}
  70. */
  71. AlignElements.prototype._alignmentPosition = function(type, sortedElements) {
  72. var orientation = this._getOrientationDetails(type),
  73. axis = orientation.axis,
  74. dimension = orientation.dimension,
  75. alignment = {},
  76. centers = {},
  77. hasSharedCenters = false,
  78. centeredElements,
  79. firstElement,
  80. lastElement;
  81. function getMiddleOrTop(first, last) {
  82. return Math.round((first[axis] + last[axis] + last[dimension]) / 2);
  83. }
  84. if (this._isType(type, [ 'left', 'top' ])) {
  85. alignment[type] = sortedElements[0][axis];
  86. } else if (this._isType(type, [ 'right', 'bottom' ])) {
  87. lastElement = last(sortedElements);
  88. alignment[type] = lastElement[axis] + lastElement[dimension];
  89. } else if (this._isType(type, [ 'center', 'middle' ])) {
  90. // check if there is a center shared by more than one shape
  91. // if not, just take the middle of the range
  92. forEach(sortedElements, function(element) {
  93. var center = element[axis] + Math.round(element[dimension] / 2);
  94. if (centers[center]) {
  95. centers[center].elements.push(element);
  96. } else {
  97. centers[center] = {
  98. elements: [ element ],
  99. center: center
  100. };
  101. }
  102. });
  103. centeredElements = sortBy(centers, function(center) {
  104. if (center.elements.length > 1) {
  105. hasSharedCenters = true;
  106. }
  107. return center.elements.length;
  108. });
  109. if (hasSharedCenters) {
  110. alignment[type] = last(centeredElements).center;
  111. return alignment;
  112. }
  113. firstElement = sortedElements[0];
  114. sortedElements = sortBy(sortedElements, function(element) {
  115. return element[axis] + element[dimension];
  116. });
  117. lastElement = last(sortedElements);
  118. alignment[type] = getMiddleOrTop(firstElement, lastElement);
  119. }
  120. return alignment;
  121. };
  122. /**
  123. * Executes the alignment of a selection of elements
  124. *
  125. * @param {Array} elements
  126. * @param {string} type left|right|center|top|bottom|middle
  127. */
  128. AlignElements.prototype.trigger = function(elements, type) {
  129. var modeling = this._modeling,
  130. allowed;
  131. // filter out elements which cannot be aligned
  132. var filteredElements = filter(elements, function(element) {
  133. return !(element.waypoints || element.host || element.labelTarget);
  134. });
  135. // filter out elements via rules
  136. allowed = this._rules.allowed('elements.align', { elements: filteredElements });
  137. if (isArray(allowed)) {
  138. filteredElements = allowed;
  139. }
  140. if (filteredElements.length < 2 || !allowed) {
  141. return;
  142. }
  143. var sortFn = ALIGNMENT_SORTING[type];
  144. var sortedElements = sortBy(filteredElements, sortFn);
  145. var alignment = this._alignmentPosition(type, sortedElements);
  146. modeling.alignElements(sortedElements, alignment);
  147. };