labelLayoutHelper.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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();
  50. // NOTE: Get bounding rect after getComputedTransform, or label may not been updated by the host el.
  51. var localRect = label.getBoundingRect();
  52. var isAxisAligned = !transform || transform[1] < 1e-5 && transform[2] < 1e-5;
  53. var minMargin = label.style.margin || 0;
  54. var globalRect = localRect.clone();
  55. globalRect.applyTransform(transform);
  56. globalRect.x -= minMargin / 2;
  57. globalRect.y -= minMargin / 2;
  58. globalRect.width += minMargin;
  59. globalRect.height += minMargin;
  60. var obb = isAxisAligned ? new OrientedBoundingRect(localRect, transform) : null;
  61. list.push({
  62. label: label,
  63. labelLine: rawItem.labelLine,
  64. rect: globalRect,
  65. localRect: localRect,
  66. obb: obb,
  67. priority: rawItem.priority,
  68. defaultAttr: rawItem.defaultAttr,
  69. layoutOption: rawItem.computedLayoutOption,
  70. axisAligned: isAxisAligned,
  71. transform: transform
  72. });
  73. }
  74. return list;
  75. }
  76. function shiftLayout(list, xyDim, sizeDim, minBound, maxBound, balanceShift) {
  77. var len = list.length;
  78. if (len < 2) {
  79. return;
  80. }
  81. list.sort(function (a, b) {
  82. return a.rect[xyDim] - b.rect[xyDim];
  83. });
  84. var lastPos = 0;
  85. var delta;
  86. var adjusted = false;
  87. var shifts = [];
  88. var totalShifts = 0;
  89. for (var i = 0; i < len; i++) {
  90. var item = list[i];
  91. var rect = item.rect;
  92. delta = rect[xyDim] - lastPos;
  93. if (delta < 0) {
  94. // shiftForward(i, len, -delta);
  95. rect[xyDim] -= delta;
  96. item.label[xyDim] -= delta;
  97. adjusted = true;
  98. }
  99. var shift = Math.max(-delta, 0);
  100. shifts.push(shift);
  101. totalShifts += shift;
  102. lastPos = rect[xyDim] + rect[sizeDim];
  103. }
  104. if (totalShifts > 0 && balanceShift) {
  105. // Shift back to make the distribution more equally.
  106. shiftList(-totalShifts / len, 0, len);
  107. }
  108. // TODO bleedMargin?
  109. var first = list[0];
  110. var last = list[len - 1];
  111. var minGap;
  112. var maxGap;
  113. updateMinMaxGap();
  114. // If ends exceed two bounds, squeeze at most 80%, then take the gap of two bounds.
  115. minGap < 0 && squeezeGaps(-minGap, 0.8);
  116. maxGap < 0 && squeezeGaps(maxGap, 0.8);
  117. updateMinMaxGap();
  118. takeBoundsGap(minGap, maxGap, 1);
  119. takeBoundsGap(maxGap, minGap, -1);
  120. // Handle bailout when there is not enough space.
  121. updateMinMaxGap();
  122. if (minGap < 0) {
  123. squeezeWhenBailout(-minGap);
  124. }
  125. if (maxGap < 0) {
  126. squeezeWhenBailout(maxGap);
  127. }
  128. function updateMinMaxGap() {
  129. minGap = first.rect[xyDim] - minBound;
  130. maxGap = maxBound - last.rect[xyDim] - last.rect[sizeDim];
  131. }
  132. function takeBoundsGap(gapThisBound, gapOtherBound, moveDir) {
  133. if (gapThisBound < 0) {
  134. // Move from other gap if can.
  135. var moveFromMaxGap = Math.min(gapOtherBound, -gapThisBound);
  136. if (moveFromMaxGap > 0) {
  137. shiftList(moveFromMaxGap * moveDir, 0, len);
  138. var remained = moveFromMaxGap + gapThisBound;
  139. if (remained < 0) {
  140. squeezeGaps(-remained * moveDir, 1);
  141. }
  142. } else {
  143. squeezeGaps(-gapThisBound * moveDir, 1);
  144. }
  145. }
  146. }
  147. function shiftList(delta, start, end) {
  148. if (delta !== 0) {
  149. adjusted = true;
  150. }
  151. for (var i = start; i < end; i++) {
  152. var item = list[i];
  153. var rect = item.rect;
  154. rect[xyDim] += delta;
  155. item.label[xyDim] += delta;
  156. }
  157. }
  158. // Squeeze gaps if the labels exceed margin.
  159. function squeezeGaps(delta, maxSqeezePercent) {
  160. var gaps = [];
  161. var totalGaps = 0;
  162. for (var i = 1; i < len; i++) {
  163. var prevItemRect = list[i - 1].rect;
  164. var gap = Math.max(list[i].rect[xyDim] - prevItemRect[xyDim] - prevItemRect[sizeDim], 0);
  165. gaps.push(gap);
  166. totalGaps += gap;
  167. }
  168. if (!totalGaps) {
  169. return;
  170. }
  171. var squeezePercent = Math.min(Math.abs(delta) / totalGaps, maxSqeezePercent);
  172. if (delta > 0) {
  173. for (var i = 0; i < len - 1; i++) {
  174. // Distribute the shift delta to all gaps.
  175. var movement = gaps[i] * squeezePercent;
  176. // Forward
  177. shiftList(movement, 0, i + 1);
  178. }
  179. } else {
  180. // Backward
  181. for (var i = len - 1; i > 0; i--) {
  182. // Distribute the shift delta to all gaps.
  183. var movement = gaps[i - 1] * squeezePercent;
  184. shiftList(-movement, i, len);
  185. }
  186. }
  187. }
  188. /**
  189. * Squeeze to allow overlap if there is no more space available.
  190. * Let other overlapping strategy like hideOverlap do the job instead of keep exceeding the bounds.
  191. */
  192. function squeezeWhenBailout(delta) {
  193. var dir = delta < 0 ? -1 : 1;
  194. delta = Math.abs(delta);
  195. var moveForEachLabel = Math.ceil(delta / (len - 1));
  196. for (var i = 0; i < len - 1; i++) {
  197. if (dir > 0) {
  198. // Forward
  199. shiftList(moveForEachLabel, 0, i + 1);
  200. } else {
  201. // Backward
  202. shiftList(-moveForEachLabel, len - i - 1, len);
  203. }
  204. delta -= moveForEachLabel;
  205. if (delta <= 0) {
  206. return;
  207. }
  208. }
  209. }
  210. return adjusted;
  211. }
  212. /**
  213. * Adjust labels on x direction to avoid overlap.
  214. */
  215. export function shiftLayoutOnX(list, leftBound, rightBound,
  216. // If average the shifts on all labels and add them to 0
  217. // TODO: Not sure if should enable it.
  218. // Pros: The angle of lines will distribute more equally
  219. // Cons: In some layout. It may not what user wanted. like in pie. the label of last sector is usually changed unexpectedly.
  220. balanceShift) {
  221. return shiftLayout(list, 'x', 'width', leftBound, rightBound, balanceShift);
  222. }
  223. /**
  224. * Adjust labels on y direction to avoid overlap.
  225. */
  226. export function shiftLayoutOnY(list, topBound, bottomBound,
  227. // If average the shifts on all labels and add them to 0
  228. balanceShift) {
  229. return shiftLayout(list, 'y', 'height', topBound, bottomBound, balanceShift);
  230. }
  231. export function hideOverlap(labelList) {
  232. var displayedLabels = [];
  233. // TODO, render overflow visible first, put in the displayedLabels.
  234. labelList.sort(function (a, b) {
  235. return b.priority - a.priority;
  236. });
  237. var globalRect = new BoundingRect(0, 0, 0, 0);
  238. function hideEl(el) {
  239. if (!el.ignore) {
  240. // Show on emphasis.
  241. var emphasisState = el.ensureState('emphasis');
  242. if (emphasisState.ignore == null) {
  243. emphasisState.ignore = false;
  244. }
  245. }
  246. el.ignore = true;
  247. }
  248. for (var i = 0; i < labelList.length; i++) {
  249. var labelItem = labelList[i];
  250. var isAxisAligned = labelItem.axisAligned;
  251. var localRect = labelItem.localRect;
  252. var transform = labelItem.transform;
  253. var label = labelItem.label;
  254. var labelLine = labelItem.labelLine;
  255. globalRect.copy(labelItem.rect);
  256. // Add a threshold because layout may be aligned precisely.
  257. globalRect.width -= 0.1;
  258. globalRect.height -= 0.1;
  259. globalRect.x += 0.05;
  260. globalRect.y += 0.05;
  261. var obb = labelItem.obb;
  262. var overlapped = false;
  263. for (var j = 0; j < displayedLabels.length; j++) {
  264. var existsTextCfg = displayedLabels[j];
  265. // Fast rejection.
  266. if (!globalRect.intersect(existsTextCfg.rect)) {
  267. continue;
  268. }
  269. if (isAxisAligned && existsTextCfg.axisAligned) {
  270. // Is overlapped
  271. overlapped = true;
  272. break;
  273. }
  274. if (!existsTextCfg.obb) {
  275. // If self is not axis aligned. But other is.
  276. existsTextCfg.obb = new OrientedBoundingRect(existsTextCfg.localRect, existsTextCfg.transform);
  277. }
  278. if (!obb) {
  279. // If self is axis aligned. But other is not.
  280. obb = new OrientedBoundingRect(localRect, transform);
  281. }
  282. if (obb.intersect(existsTextCfg.obb)) {
  283. overlapped = true;
  284. break;
  285. }
  286. }
  287. // TODO Callback to determine if this overlap should be handled?
  288. if (overlapped) {
  289. hideEl(label);
  290. labelLine && hideEl(labelLine);
  291. } else {
  292. label.attr('ignore', labelItem.defaultAttr.ignore);
  293. labelLine && labelLine.attr('ignore', labelItem.defaultAttr.labelGuideIgnore);
  294. displayedLabels.push(labelItem);
  295. }
  296. }
  297. }