LayoutUtil.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import {
  2. isObject,
  3. sortBy
  4. } from 'min-dash';
  5. import {
  6. pointDistance
  7. } from '../util/Geometry';
  8. import intersectPaths from 'path-intersection';
  9. export function roundBounds(bounds) {
  10. return {
  11. x: Math.round(bounds.x),
  12. y: Math.round(bounds.y),
  13. width: Math.round(bounds.width),
  14. height: Math.round(bounds.height)
  15. };
  16. }
  17. export function roundPoint(point) {
  18. return {
  19. x: Math.round(point.x),
  20. y: Math.round(point.y)
  21. };
  22. }
  23. /**
  24. * Convert the given bounds to a { top, left, bottom, right } descriptor.
  25. *
  26. * @param {Bounds|Point} bounds
  27. *
  28. * @return {Object}
  29. */
  30. export function asTRBL(bounds) {
  31. return {
  32. top: bounds.y,
  33. right: bounds.x + (bounds.width || 0),
  34. bottom: bounds.y + (bounds.height || 0),
  35. left: bounds.x
  36. };
  37. }
  38. /**
  39. * Convert a { top, left, bottom, right } to an objects bounds.
  40. *
  41. * @param {Object} trbl
  42. *
  43. * @return {Bounds}
  44. */
  45. export function asBounds(trbl) {
  46. return {
  47. x: trbl.left,
  48. y: trbl.top,
  49. width: trbl.right - trbl.left,
  50. height: trbl.bottom - trbl.top
  51. };
  52. }
  53. /**
  54. * Get the mid of the given bounds or point.
  55. *
  56. * @param {Bounds|Point} bounds
  57. *
  58. * @return {Point}
  59. */
  60. export function getMid(bounds) {
  61. return roundPoint({
  62. x: bounds.x + (bounds.width || 0) / 2,
  63. y: bounds.y + (bounds.height || 0) / 2
  64. });
  65. }
  66. // orientation utils //////////////////////
  67. /**
  68. * Get orientation of the given rectangle with respect to
  69. * the reference rectangle.
  70. *
  71. * A padding (positive or negative) may be passed to influence
  72. * horizontal / vertical orientation and intersection.
  73. *
  74. * @param {Bounds} rect
  75. * @param {Bounds} reference
  76. * @param {Point|Number} padding
  77. *
  78. * @return {String} the orientation; one of top, top-left, left, ..., bottom, right or intersect.
  79. */
  80. export function getOrientation(rect, reference, padding) {
  81. padding = padding || 0;
  82. // make sure we can use an object, too
  83. // for individual { x, y } padding
  84. if (!isObject(padding)) {
  85. padding = { x: padding, y: padding };
  86. }
  87. var rectOrientation = asTRBL(rect),
  88. referenceOrientation = asTRBL(reference);
  89. var top = rectOrientation.bottom + padding.y <= referenceOrientation.top,
  90. right = rectOrientation.left - padding.x >= referenceOrientation.right,
  91. bottom = rectOrientation.top - padding.y >= referenceOrientation.bottom,
  92. left = rectOrientation.right + padding.x <= referenceOrientation.left;
  93. var vertical = top ? 'top' : (bottom ? 'bottom' : null),
  94. horizontal = left ? 'left' : (right ? 'right' : null);
  95. if (horizontal && vertical) {
  96. return vertical + '-' + horizontal;
  97. } else {
  98. return horizontal || vertical || 'intersect';
  99. }
  100. }
  101. // intersection utils //////////////////////
  102. /**
  103. * Get intersection between an element and a line path.
  104. *
  105. * @param {PathDef} elementPath
  106. * @param {PathDef} linePath
  107. * @param {Boolean} cropStart crop from start or end
  108. *
  109. * @return {Point}
  110. */
  111. export function getElementLineIntersection(elementPath, linePath, cropStart) {
  112. var intersections = getIntersections(elementPath, linePath);
  113. // recognize intersections
  114. // only one -> choose
  115. // two close together -> choose first
  116. // two or more distinct -> pull out appropriate one
  117. // none -> ok (fallback to point itself)
  118. if (intersections.length === 1) {
  119. return roundPoint(intersections[0]);
  120. } else if (intersections.length === 2 && pointDistance(intersections[0], intersections[1]) < 1) {
  121. return roundPoint(intersections[0]);
  122. } else if (intersections.length > 1) {
  123. // sort by intersections based on connection segment +
  124. // distance from start
  125. intersections = sortBy(intersections, function(i) {
  126. var distance = Math.floor(i.t2 * 100) || 1;
  127. distance = 100 - distance;
  128. distance = (distance < 10 ? '0' : '') + distance;
  129. // create a sort string that makes sure we sort
  130. // line segment ASC + line segment position DESC (for cropStart)
  131. // line segment ASC + line segment position ASC (for cropEnd)
  132. return i.segment2 + '#' + distance;
  133. });
  134. return roundPoint(intersections[cropStart ? 0 : intersections.length - 1]);
  135. }
  136. return null;
  137. }
  138. export function getIntersections(a, b) {
  139. return intersectPaths(a, b);
  140. }