labelLayoutHelper.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. /**
  20. * AUTO-GENERATED FILE. DO NOT MODIFY.
  21. */
  22. /*
  23. * Licensed to the Apache Software Foundation (ASF) under one
  24. * or more contributor license agreements. See the NOTICE file
  25. * distributed with this work for additional information
  26. * regarding copyright ownership. The ASF licenses this file
  27. * to you under the Apache License, Version 2.0 (the
  28. * "License"); you may not use this file except in compliance
  29. * with the License. You may obtain a copy of the License at
  30. *
  31. * http://www.apache.org/licenses/LICENSE-2.0
  32. *
  33. * Unless required by applicable law or agreed to in writing,
  34. * software distributed under the License is distributed on an
  35. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  36. * KIND, either express or implied. See the License for the
  37. * specific language governing permissions and limitations
  38. * under the License.
  39. */
  40. import { BoundingRect, OrientedBoundingRect } from '../util/graphic.js';
  41. export function prepareLayoutList(input) {
  42. var list = [];
  43. for (var i = 0; i < input.length; i++) {
  44. var rawItem = input[i];
  45. if (rawItem.defaultAttr.ignore) {
  46. continue;
  47. }
  48. var label = rawItem.label;
  49. var transform = label.getComputedTransform(); // NOTE: Get bounding rect after getComputedTransform, or label may not been updated by the host el.
  50. var localRect = label.getBoundingRect();
  51. var isAxisAligned = !transform || transform[1] < 1e-5 && transform[2] < 1e-5;
  52. var minMargin = label.style.margin || 0;
  53. var globalRect = localRect.clone();
  54. globalRect.applyTransform(transform);
  55. globalRect.x -= minMargin / 2;
  56. globalRect.y -= minMargin / 2;
  57. globalRect.width += minMargin;
  58. globalRect.height += minMargin;
  59. var obb = isAxisAligned ? new OrientedBoundingRect(localRect, transform) : null;
  60. list.push({
  61. label: label,
  62. labelLine: rawItem.labelLine,
  63. rect: globalRect,
  64. localRect: localRect,
  65. obb: obb,
  66. priority: rawItem.priority,
  67. defaultAttr: rawItem.defaultAttr,
  68. layoutOption: rawItem.computedLayoutOption,
  69. axisAligned: isAxisAligned,
  70. transform: transform
  71. });
  72. }
  73. return list;
  74. }
  75. function shiftLayout(list, xyDim, sizeDim, minBound, maxBound, balanceShift) {
  76. var len = list.length;
  77. if (len < 2) {
  78. return;
  79. }
  80. list.sort(function (a, b) {
  81. return a.rect[xyDim] - b.rect[xyDim];
  82. });
  83. var lastPos = 0;
  84. var delta;
  85. var adjusted = false;
  86. var shifts = [];
  87. var totalShifts = 0;
  88. for (var i = 0; i < len; i++) {
  89. var item = list[i];
  90. var rect = item.rect;
  91. delta = rect[xyDim] - lastPos;
  92. if (delta < 0) {
  93. // shiftForward(i, len, -delta);
  94. rect[xyDim] -= delta;
  95. item.label[xyDim] -= delta;
  96. adjusted = true;
  97. }
  98. var shift = Math.max(-delta, 0);
  99. shifts.push(shift);
  100. totalShifts += shift;
  101. lastPos = rect[xyDim] + rect[sizeDim];
  102. }
  103. if (totalShifts > 0 && balanceShift) {
  104. // Shift back to make the distribution more equally.
  105. shiftList(-totalShifts / len, 0, len);
  106. } // TODO bleedMargin?
  107. var first = list[0];
  108. var last = list[len - 1];
  109. var minGap;
  110. var maxGap;
  111. updateMinMaxGap(); // If ends exceed two bounds, squeeze at most 80%, then take the gap of two bounds.
  112. minGap < 0 && squeezeGaps(-minGap, 0.8);
  113. maxGap < 0 && squeezeGaps(maxGap, 0.8);
  114. updateMinMaxGap();
  115. takeBoundsGap(minGap, maxGap, 1);
  116. takeBoundsGap(maxGap, minGap, -1); // Handle bailout when there is not enough space.
  117. updateMinMaxGap();
  118. if (minGap < 0) {
  119. squeezeWhenBailout(-minGap);
  120. }
  121. if (maxGap < 0) {
  122. squeezeWhenBailout(maxGap);
  123. }
  124. function updateMinMaxGap() {
  125. minGap = first.rect[xyDim] - minBound;
  126. maxGap = maxBound - last.rect[xyDim] - last.rect[sizeDim];
  127. }
  128. function takeBoundsGap(gapThisBound, gapOtherBound, moveDir) {
  129. if (gapThisBound < 0) {
  130. // Move from other gap if can.
  131. var moveFromMaxGap = Math.min(gapOtherBound, -gapThisBound);
  132. if (moveFromMaxGap > 0) {
  133. shiftList(moveFromMaxGap * moveDir, 0, len);
  134. var remained = moveFromMaxGap + gapThisBound;
  135. if (remained < 0) {
  136. squeezeGaps(-remained * moveDir, 1);
  137. }
  138. } else {
  139. squeezeGaps(-gapThisBound * moveDir, 1);
  140. }
  141. }
  142. }
  143. function shiftList(delta, start, end) {
  144. if (delta !== 0) {
  145. adjusted = true;
  146. }
  147. for (var i = start; i < end; i++) {
  148. var item = list[i];
  149. var rect = item.rect;
  150. rect[xyDim] += delta;
  151. item.label[xyDim] += delta;
  152. }
  153. } // Squeeze gaps if the labels exceed margin.
  154. function squeezeGaps(delta, maxSqeezePercent) {
  155. var gaps = [];
  156. var totalGaps = 0;
  157. for (var i = 1; i < len; i++) {
  158. var prevItemRect = list[i - 1].rect;
  159. var gap = Math.max(list[i].rect[xyDim] - prevItemRect[xyDim] - prevItemRect[sizeDim], 0);
  160. gaps.push(gap);
  161. totalGaps += gap;
  162. }
  163. if (!totalGaps) {
  164. return;
  165. }
  166. var squeezePercent = Math.min(Math.abs(delta) / totalGaps, maxSqeezePercent);
  167. if (delta > 0) {
  168. for (var i = 0; i < len - 1; i++) {
  169. // Distribute the shift delta to all gaps.
  170. var movement = gaps[i] * squeezePercent; // Forward
  171. shiftList(movement, 0, i + 1);
  172. }
  173. } else {
  174. // Backward
  175. for (var i = len - 1; i > 0; i--) {
  176. // Distribute the shift delta to all gaps.
  177. var movement = gaps[i - 1] * squeezePercent;
  178. shiftList(-movement, i, len);
  179. }
  180. }
  181. }
  182. /**
  183. * Squeeze to allow overlap if there is no more space available.
  184. * Let other overlapping strategy like hideOverlap do the job instead of keep exceeding the bounds.
  185. */
  186. function squeezeWhenBailout(delta) {
  187. var dir = delta < 0 ? -1 : 1;
  188. delta = Math.abs(delta);
  189. var moveForEachLabel = Math.ceil(delta / (len - 1));
  190. for (var i = 0; i < len - 1; i++) {
  191. if (dir > 0) {
  192. // Forward
  193. shiftList(moveForEachLabel, 0, i + 1);
  194. } else {
  195. // Backward
  196. shiftList(-moveForEachLabel, len - i - 1, len);
  197. }
  198. delta -= moveForEachLabel;
  199. if (delta <= 0) {
  200. return;
  201. }
  202. }
  203. }
  204. return adjusted;
  205. }
  206. /**
  207. * Adjust labels on x direction to avoid overlap.
  208. */
  209. export function shiftLayoutOnX(list, leftBound, rightBound, // If average the shifts on all labels and add them to 0
  210. // TODO: Not sure if should enable it.
  211. // Pros: The angle of lines will distribute more equally
  212. // Cons: In some layout. It may not what user wanted. like in pie. the label of last sector is usually changed unexpectedly.
  213. balanceShift) {
  214. return shiftLayout(list, 'x', 'width', leftBound, rightBound, balanceShift);
  215. }
  216. /**
  217. * Adjust labels on y direction to avoid overlap.
  218. */
  219. export function shiftLayoutOnY(list, topBound, bottomBound, // If average the shifts on all labels and add them to 0
  220. balanceShift) {
  221. return shiftLayout(list, 'y', 'height', topBound, bottomBound, balanceShift);
  222. }
  223. export function hideOverlap(labelList) {
  224. var displayedLabels = []; // TODO, render overflow visible first, put in the displayedLabels.
  225. labelList.sort(function (a, b) {
  226. return b.priority - a.priority;
  227. });
  228. var globalRect = new BoundingRect(0, 0, 0, 0);
  229. function hideEl(el) {
  230. if (!el.ignore) {
  231. // Show on emphasis.
  232. var emphasisState = el.ensureState('emphasis');
  233. if (emphasisState.ignore == null) {
  234. emphasisState.ignore = false;
  235. }
  236. }
  237. el.ignore = true;
  238. }
  239. for (var i = 0; i < labelList.length; i++) {
  240. var labelItem = labelList[i];
  241. var isAxisAligned = labelItem.axisAligned;
  242. var localRect = labelItem.localRect;
  243. var transform = labelItem.transform;
  244. var label = labelItem.label;
  245. var labelLine = labelItem.labelLine;
  246. globalRect.copy(labelItem.rect); // Add a threshold because layout may be aligned precisely.
  247. globalRect.width -= 0.1;
  248. globalRect.height -= 0.1;
  249. globalRect.x += 0.05;
  250. globalRect.y += 0.05;
  251. var obb = labelItem.obb;
  252. var overlapped = false;
  253. for (var j = 0; j < displayedLabels.length; j++) {
  254. var existsTextCfg = displayedLabels[j]; // Fast rejection.
  255. if (!globalRect.intersect(existsTextCfg.rect)) {
  256. continue;
  257. }
  258. if (isAxisAligned && existsTextCfg.axisAligned) {
  259. // Is overlapped
  260. overlapped = true;
  261. break;
  262. }
  263. if (!existsTextCfg.obb) {
  264. // If self is not axis aligned. But other is.
  265. existsTextCfg.obb = new OrientedBoundingRect(existsTextCfg.localRect, existsTextCfg.transform);
  266. }
  267. if (!obb) {
  268. // If self is axis aligned. But other is not.
  269. obb = new OrientedBoundingRect(localRect, transform);
  270. }
  271. if (obb.intersect(existsTextCfg.obb)) {
  272. overlapped = true;
  273. break;
  274. }
  275. } // TODO Callback to determine if this overlap should be handled?
  276. if (overlapped) {
  277. hideEl(label);
  278. labelLine && hideEl(labelLine);
  279. } else {
  280. label.attr('ignore', labelItem.defaultAttr.ignore);
  281. labelLine && labelLine.attr('ignore', labelItem.defaultAttr.labelGuideIgnore);
  282. displayedLabels.push(labelItem);
  283. }
  284. }
  285. }