labelLayout.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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. var textContain = require("zrender/lib/contain/text");
  20. var _number = require("../../util/number");
  21. var parsePercent = _number.parsePercent;
  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. // FIXME emphasis label position is not same with normal label position
  41. var RADIAN = Math.PI / 180;
  42. function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight, viewLeft, viewTop, farthestX) {
  43. list.sort(function (a, b) {
  44. return a.y - b.y;
  45. });
  46. function shiftDown(start, end, delta, dir) {
  47. for (var j = start; j < end; j++) {
  48. if (list[j].y + delta > viewTop + viewHeight) {
  49. break;
  50. }
  51. list[j].y += delta;
  52. if (j > start && j + 1 < end && list[j + 1].y > list[j].y + list[j].height) {
  53. shiftUp(j, delta / 2);
  54. return;
  55. }
  56. }
  57. shiftUp(end - 1, delta / 2);
  58. }
  59. function shiftUp(end, delta) {
  60. for (var j = end; j >= 0; j--) {
  61. if (list[j].y - delta < viewTop) {
  62. break;
  63. }
  64. list[j].y -= delta;
  65. if (j > 0 && list[j].y > list[j - 1].y + list[j - 1].height) {
  66. break;
  67. }
  68. }
  69. }
  70. function changeX(list, isDownList, cx, cy, r, dir) {
  71. var lastDeltaX = dir > 0 ? isDownList // right-side
  72. ? Number.MAX_VALUE // down
  73. : 0 // up
  74. : isDownList // left-side
  75. ? Number.MAX_VALUE // down
  76. : 0; // up
  77. for (var i = 0, l = list.length; i < l; i++) {
  78. if (list[i].labelAlignTo !== 'none') {
  79. continue;
  80. }
  81. var deltaY = Math.abs(list[i].y - cy);
  82. var length = list[i].len;
  83. var length2 = list[i].len2;
  84. var deltaX = deltaY < r + length ? Math.sqrt((r + length + length2) * (r + length + length2) - deltaY * deltaY) : Math.abs(list[i].x - cx);
  85. if (isDownList && deltaX >= lastDeltaX) {
  86. // right-down, left-down
  87. deltaX = lastDeltaX - 10;
  88. }
  89. if (!isDownList && deltaX <= lastDeltaX) {
  90. // right-up, left-up
  91. deltaX = lastDeltaX + 10;
  92. }
  93. list[i].x = cx + deltaX * dir;
  94. lastDeltaX = deltaX;
  95. }
  96. }
  97. var lastY = 0;
  98. var delta;
  99. var len = list.length;
  100. var upList = [];
  101. var downList = [];
  102. for (var i = 0; i < len; i++) {
  103. if (list[i].position === 'outer' && list[i].labelAlignTo === 'labelLine') {
  104. var dx = list[i].x - farthestX;
  105. list[i].linePoints[1][0] += dx;
  106. list[i].x = farthestX;
  107. }
  108. delta = list[i].y - lastY;
  109. if (delta < 0) {
  110. shiftDown(i, len, -delta, dir);
  111. }
  112. lastY = list[i].y + list[i].height;
  113. }
  114. if (viewHeight - lastY < 0) {
  115. shiftUp(len - 1, lastY - viewHeight);
  116. }
  117. for (var i = 0; i < len; i++) {
  118. if (list[i].y >= cy) {
  119. downList.push(list[i]);
  120. } else {
  121. upList.push(list[i]);
  122. }
  123. }
  124. changeX(upList, false, cx, cy, r, dir);
  125. changeX(downList, true, cx, cy, r, dir);
  126. }
  127. function avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop) {
  128. var leftList = [];
  129. var rightList = [];
  130. var leftmostX = Number.MAX_VALUE;
  131. var rightmostX = -Number.MAX_VALUE;
  132. for (var i = 0; i < labelLayoutList.length; i++) {
  133. if (isPositionCenter(labelLayoutList[i])) {
  134. continue;
  135. }
  136. if (labelLayoutList[i].x < cx) {
  137. leftmostX = Math.min(leftmostX, labelLayoutList[i].x);
  138. leftList.push(labelLayoutList[i]);
  139. } else {
  140. rightmostX = Math.max(rightmostX, labelLayoutList[i].x);
  141. rightList.push(labelLayoutList[i]);
  142. }
  143. }
  144. adjustSingleSide(rightList, cx, cy, r, 1, viewWidth, viewHeight, viewLeft, viewTop, rightmostX);
  145. adjustSingleSide(leftList, cx, cy, r, -1, viewWidth, viewHeight, viewLeft, viewTop, leftmostX);
  146. for (var i = 0; i < labelLayoutList.length; i++) {
  147. var layout = labelLayoutList[i];
  148. if (isPositionCenter(layout)) {
  149. continue;
  150. }
  151. var linePoints = layout.linePoints;
  152. if (linePoints) {
  153. var isAlignToEdge = layout.labelAlignTo === 'edge';
  154. var realTextWidth = layout.textRect.width;
  155. var targetTextWidth;
  156. if (isAlignToEdge) {
  157. if (layout.x < cx) {
  158. targetTextWidth = linePoints[2][0] - layout.labelDistance - viewLeft - layout.labelMargin;
  159. } else {
  160. targetTextWidth = viewLeft + viewWidth - layout.labelMargin - linePoints[2][0] - layout.labelDistance;
  161. }
  162. } else {
  163. if (layout.x < cx) {
  164. targetTextWidth = layout.x - viewLeft - layout.bleedMargin;
  165. } else {
  166. targetTextWidth = viewLeft + viewWidth - layout.x - layout.bleedMargin;
  167. }
  168. }
  169. if (targetTextWidth < layout.textRect.width) {
  170. layout.text = textContain.truncateText(layout.text, targetTextWidth, layout.font);
  171. if (layout.labelAlignTo === 'edge') {
  172. realTextWidth = textContain.getWidth(layout.text, layout.font);
  173. }
  174. }
  175. var dist = linePoints[1][0] - linePoints[2][0];
  176. if (isAlignToEdge) {
  177. if (layout.x < cx) {
  178. linePoints[2][0] = viewLeft + layout.labelMargin + realTextWidth + layout.labelDistance;
  179. } else {
  180. linePoints[2][0] = viewLeft + viewWidth - layout.labelMargin - realTextWidth - layout.labelDistance;
  181. }
  182. } else {
  183. if (layout.x < cx) {
  184. linePoints[2][0] = layout.x + layout.labelDistance;
  185. } else {
  186. linePoints[2][0] = layout.x - layout.labelDistance;
  187. }
  188. linePoints[1][0] = linePoints[2][0] + dist;
  189. }
  190. linePoints[1][1] = linePoints[2][1] = layout.y;
  191. }
  192. }
  193. }
  194. function isPositionCenter(layout) {
  195. // Not change x for center label
  196. return layout.position === 'center';
  197. }
  198. function _default(seriesModel, r, viewWidth, viewHeight, viewLeft, viewTop) {
  199. var data = seriesModel.getData();
  200. var labelLayoutList = [];
  201. var cx;
  202. var cy;
  203. var hasLabelRotate = false;
  204. var minShowLabelRadian = (seriesModel.get('minShowLabelAngle') || 0) * RADIAN;
  205. data.each(function (idx) {
  206. var layout = data.getItemLayout(idx);
  207. var itemModel = data.getItemModel(idx);
  208. var labelModel = itemModel.getModel('label'); // Use position in normal or emphasis
  209. var labelPosition = labelModel.get('position') || itemModel.get('emphasis.label.position');
  210. var labelDistance = labelModel.get('distanceToLabelLine');
  211. var labelAlignTo = labelModel.get('alignTo');
  212. var labelMargin = parsePercent(labelModel.get('margin'), viewWidth);
  213. var bleedMargin = labelModel.get('bleedMargin');
  214. var font = labelModel.getFont();
  215. var labelLineModel = itemModel.getModel('labelLine');
  216. var labelLineLen = labelLineModel.get('length');
  217. labelLineLen = parsePercent(labelLineLen, viewWidth);
  218. var labelLineLen2 = labelLineModel.get('length2');
  219. labelLineLen2 = parsePercent(labelLineLen2, viewWidth);
  220. if (layout.angle < minShowLabelRadian) {
  221. return;
  222. }
  223. var midAngle = (layout.startAngle + layout.endAngle) / 2;
  224. var dx = Math.cos(midAngle);
  225. var dy = Math.sin(midAngle);
  226. var textX;
  227. var textY;
  228. var linePoints;
  229. var textAlign;
  230. cx = layout.cx;
  231. cy = layout.cy;
  232. var text = seriesModel.getFormattedLabel(idx, 'normal') || data.getName(idx);
  233. var textRect = textContain.getBoundingRect(text, font, textAlign, 'top');
  234. var isLabelInside = labelPosition === 'inside' || labelPosition === 'inner';
  235. if (labelPosition === 'center') {
  236. textX = layout.cx;
  237. textY = layout.cy;
  238. textAlign = 'center';
  239. } else {
  240. var x1 = (isLabelInside ? (layout.r + layout.r0) / 2 * dx : layout.r * dx) + cx;
  241. var y1 = (isLabelInside ? (layout.r + layout.r0) / 2 * dy : layout.r * dy) + cy;
  242. textX = x1 + dx * 3;
  243. textY = y1 + dy * 3;
  244. if (!isLabelInside) {
  245. // For roseType
  246. var x2 = x1 + dx * (labelLineLen + r - layout.r);
  247. var y2 = y1 + dy * (labelLineLen + r - layout.r);
  248. var x3 = x2 + (dx < 0 ? -1 : 1) * labelLineLen2;
  249. var y3 = y2;
  250. if (labelAlignTo === 'edge') {
  251. // Adjust textX because text align of edge is opposite
  252. textX = dx < 0 ? viewLeft + labelMargin : viewLeft + viewWidth - labelMargin;
  253. } else {
  254. textX = x3 + (dx < 0 ? -labelDistance : labelDistance);
  255. }
  256. textY = y3;
  257. linePoints = [[x1, y1], [x2, y2], [x3, y3]];
  258. }
  259. textAlign = isLabelInside ? 'center' : labelAlignTo === 'edge' ? dx > 0 ? 'right' : 'left' : dx > 0 ? 'left' : 'right';
  260. }
  261. var labelRotate;
  262. var rotate = labelModel.get('rotate');
  263. if (typeof rotate === 'number') {
  264. labelRotate = rotate * (Math.PI / 180);
  265. } else {
  266. labelRotate = rotate ? dx < 0 ? -midAngle + Math.PI : -midAngle : 0;
  267. }
  268. hasLabelRotate = !!labelRotate;
  269. layout.label = {
  270. x: textX,
  271. y: textY,
  272. position: labelPosition,
  273. height: textRect.height,
  274. len: labelLineLen,
  275. len2: labelLineLen2,
  276. linePoints: linePoints,
  277. textAlign: textAlign,
  278. verticalAlign: 'middle',
  279. rotation: labelRotate,
  280. inside: isLabelInside,
  281. labelDistance: labelDistance,
  282. labelAlignTo: labelAlignTo,
  283. labelMargin: labelMargin,
  284. bleedMargin: bleedMargin,
  285. textRect: textRect,
  286. text: text,
  287. font: font
  288. }; // Not layout the inside label
  289. if (!isLabelInside) {
  290. labelLayoutList.push(layout.label);
  291. }
  292. });
  293. if (!hasLabelRotate && seriesModel.get('avoidLabelOverlap')) {
  294. avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop);
  295. }
  296. }
  297. module.exports = _default;