labelLayout.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  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. // FIXME emphasis label position is not same with normal label position
  41. import { parsePercent } from '../../util/number.js';
  42. import { Point } from '../../util/graphic.js';
  43. import { each, isNumber } from 'zrender/lib/core/util.js';
  44. import { limitTurnAngle, limitSurfaceAngle } from '../../label/labelGuideHelper.js';
  45. import { shiftLayoutOnY } from '../../label/labelLayoutHelper.js';
  46. var RADIAN = Math.PI / 180;
  47. function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight, viewLeft, viewTop, farthestX) {
  48. if (list.length < 2) {
  49. return;
  50. }
  51. ;
  52. function recalculateXOnSemiToAlignOnEllipseCurve(semi) {
  53. var rB = semi.rB;
  54. var rB2 = rB * rB;
  55. for (var i = 0; i < semi.list.length; i++) {
  56. var item = semi.list[i];
  57. var dy = Math.abs(item.label.y - cy);
  58. // horizontal r is always same with original r because x is not changed.
  59. var rA = r + item.len;
  60. var rA2 = rA * rA;
  61. // Use ellipse implicit function to calculate x
  62. var dx = Math.sqrt((1 - Math.abs(dy * dy / rB2)) * rA2);
  63. var newX = cx + (dx + item.len2) * dir;
  64. var deltaX = newX - item.label.x;
  65. var newTargetWidth = item.targetTextWidth - deltaX * dir;
  66. // text x is changed, so need to recalculate width.
  67. constrainTextWidth(item, newTargetWidth, true);
  68. item.label.x = newX;
  69. }
  70. }
  71. // Adjust X based on the shifted y. Make tight labels aligned on an ellipse curve.
  72. function recalculateX(items) {
  73. // Extremes of
  74. var topSemi = {
  75. list: [],
  76. maxY: 0
  77. };
  78. var bottomSemi = {
  79. list: [],
  80. maxY: 0
  81. };
  82. for (var i = 0; i < items.length; i++) {
  83. if (items[i].labelAlignTo !== 'none') {
  84. continue;
  85. }
  86. var item = items[i];
  87. var semi = item.label.y > cy ? bottomSemi : topSemi;
  88. var dy = Math.abs(item.label.y - cy);
  89. if (dy >= semi.maxY) {
  90. var dx = item.label.x - cx - item.len2 * dir;
  91. // horizontal r is always same with original r because x is not changed.
  92. var rA = r + item.len;
  93. // Canculate rB based on the topest / bottemest label.
  94. var rB = Math.abs(dx) < rA ? Math.sqrt(dy * dy / (1 - dx * dx / rA / rA)) : rA;
  95. semi.rB = rB;
  96. semi.maxY = dy;
  97. }
  98. semi.list.push(item);
  99. }
  100. recalculateXOnSemiToAlignOnEllipseCurve(topSemi);
  101. recalculateXOnSemiToAlignOnEllipseCurve(bottomSemi);
  102. }
  103. var len = list.length;
  104. for (var i = 0; i < len; i++) {
  105. if (list[i].position === 'outer' && list[i].labelAlignTo === 'labelLine') {
  106. var dx = list[i].label.x - farthestX;
  107. list[i].linePoints[1][0] += dx;
  108. list[i].label.x = farthestX;
  109. }
  110. }
  111. if (shiftLayoutOnY(list, viewTop, viewTop + viewHeight)) {
  112. recalculateX(list);
  113. }
  114. }
  115. function avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop) {
  116. var leftList = [];
  117. var rightList = [];
  118. var leftmostX = Number.MAX_VALUE;
  119. var rightmostX = -Number.MAX_VALUE;
  120. for (var i = 0; i < labelLayoutList.length; i++) {
  121. var label = labelLayoutList[i].label;
  122. if (isPositionCenter(labelLayoutList[i])) {
  123. continue;
  124. }
  125. if (label.x < cx) {
  126. leftmostX = Math.min(leftmostX, label.x);
  127. leftList.push(labelLayoutList[i]);
  128. } else {
  129. rightmostX = Math.max(rightmostX, label.x);
  130. rightList.push(labelLayoutList[i]);
  131. }
  132. }
  133. for (var i = 0; i < labelLayoutList.length; i++) {
  134. var layout = labelLayoutList[i];
  135. if (!isPositionCenter(layout) && layout.linePoints) {
  136. if (layout.labelStyleWidth != null) {
  137. continue;
  138. }
  139. var label = layout.label;
  140. var linePoints = layout.linePoints;
  141. var targetTextWidth = void 0;
  142. if (layout.labelAlignTo === 'edge') {
  143. if (label.x < cx) {
  144. targetTextWidth = linePoints[2][0] - layout.labelDistance - viewLeft - layout.edgeDistance;
  145. } else {
  146. targetTextWidth = viewLeft + viewWidth - layout.edgeDistance - linePoints[2][0] - layout.labelDistance;
  147. }
  148. } else if (layout.labelAlignTo === 'labelLine') {
  149. if (label.x < cx) {
  150. targetTextWidth = leftmostX - viewLeft - layout.bleedMargin;
  151. } else {
  152. targetTextWidth = viewLeft + viewWidth - rightmostX - layout.bleedMargin;
  153. }
  154. } else {
  155. if (label.x < cx) {
  156. targetTextWidth = label.x - viewLeft - layout.bleedMargin;
  157. } else {
  158. targetTextWidth = viewLeft + viewWidth - label.x - layout.bleedMargin;
  159. }
  160. }
  161. layout.targetTextWidth = targetTextWidth;
  162. constrainTextWidth(layout, targetTextWidth);
  163. }
  164. }
  165. adjustSingleSide(rightList, cx, cy, r, 1, viewWidth, viewHeight, viewLeft, viewTop, rightmostX);
  166. adjustSingleSide(leftList, cx, cy, r, -1, viewWidth, viewHeight, viewLeft, viewTop, leftmostX);
  167. for (var i = 0; i < labelLayoutList.length; i++) {
  168. var layout = labelLayoutList[i];
  169. if (!isPositionCenter(layout) && layout.linePoints) {
  170. var label = layout.label;
  171. var linePoints = layout.linePoints;
  172. var isAlignToEdge = layout.labelAlignTo === 'edge';
  173. var padding = label.style.padding;
  174. var paddingH = padding ? padding[1] + padding[3] : 0;
  175. // textRect.width already contains paddingH if bgColor is set
  176. var extraPaddingH = label.style.backgroundColor ? 0 : paddingH;
  177. var realTextWidth = layout.rect.width + extraPaddingH;
  178. var dist = linePoints[1][0] - linePoints[2][0];
  179. if (isAlignToEdge) {
  180. if (label.x < cx) {
  181. linePoints[2][0] = viewLeft + layout.edgeDistance + realTextWidth + layout.labelDistance;
  182. } else {
  183. linePoints[2][0] = viewLeft + viewWidth - layout.edgeDistance - realTextWidth - layout.labelDistance;
  184. }
  185. } else {
  186. if (label.x < cx) {
  187. linePoints[2][0] = label.x + layout.labelDistance;
  188. } else {
  189. linePoints[2][0] = label.x - layout.labelDistance;
  190. }
  191. linePoints[1][0] = linePoints[2][0] + dist;
  192. }
  193. linePoints[1][1] = linePoints[2][1] = label.y;
  194. }
  195. }
  196. }
  197. /**
  198. * Set max width of each label, and then wrap each label to the max width.
  199. *
  200. * @param layout label layout
  201. * @param availableWidth max width for the label to display
  202. * @param forceRecalculate recaculate the text layout even if the current width
  203. * is smaller than `availableWidth`. This is useful when the text was previously
  204. * wrapped by calling `constrainTextWidth` but now `availableWidth` changed, in
  205. * which case, previous wrapping should be redo.
  206. */
  207. function constrainTextWidth(layout, availableWidth, forceRecalculate) {
  208. if (forceRecalculate === void 0) {
  209. forceRecalculate = false;
  210. }
  211. if (layout.labelStyleWidth != null) {
  212. // User-defined style.width has the highest priority.
  213. return;
  214. }
  215. var label = layout.label;
  216. var style = label.style;
  217. var textRect = layout.rect;
  218. var bgColor = style.backgroundColor;
  219. var padding = style.padding;
  220. var paddingH = padding ? padding[1] + padding[3] : 0;
  221. var overflow = style.overflow;
  222. // textRect.width already contains paddingH if bgColor is set
  223. var oldOuterWidth = textRect.width + (bgColor ? 0 : paddingH);
  224. if (availableWidth < oldOuterWidth || forceRecalculate) {
  225. var oldHeight = textRect.height;
  226. if (overflow && overflow.match('break')) {
  227. // Temporarily set background to be null to calculate
  228. // the bounding box without background.
  229. label.setStyle('backgroundColor', null);
  230. // Set constraining width
  231. label.setStyle('width', availableWidth - paddingH);
  232. // This is the real bounding box of the text without padding.
  233. var innerRect = label.getBoundingRect();
  234. label.setStyle('width', Math.ceil(innerRect.width));
  235. label.setStyle('backgroundColor', bgColor);
  236. } else {
  237. var availableInnerWidth = availableWidth - paddingH;
  238. var newWidth = availableWidth < oldOuterWidth
  239. // Current text is too wide, use `availableWidth` as max width.
  240. ? availableInnerWidth :
  241. // Current available width is enough, but the text may have
  242. // already been wrapped with a smaller available width.
  243. forceRecalculate ? availableInnerWidth > layout.unconstrainedWidth
  244. // Current available is larger than text width,
  245. // so don't constrain width (otherwise it may have
  246. // empty space in the background).
  247. ? null
  248. // Current available is smaller than text width, so
  249. // use the current available width as constraining
  250. // width.
  251. : availableInnerWidth
  252. // Current available width is enough, so no need to
  253. // constrain.
  254. : null;
  255. label.setStyle('width', newWidth);
  256. }
  257. var newRect = label.getBoundingRect();
  258. textRect.width = newRect.width;
  259. var margin = (label.style.margin || 0) + 2.1;
  260. textRect.height = newRect.height + margin;
  261. textRect.y -= (textRect.height - oldHeight) / 2;
  262. }
  263. }
  264. function isPositionCenter(sectorShape) {
  265. // Not change x for center label
  266. return sectorShape.position === 'center';
  267. }
  268. export default function pieLabelLayout(seriesModel) {
  269. var data = seriesModel.getData();
  270. var labelLayoutList = [];
  271. var cx;
  272. var cy;
  273. var hasLabelRotate = false;
  274. var minShowLabelRadian = (seriesModel.get('minShowLabelAngle') || 0) * RADIAN;
  275. var viewRect = data.getLayout('viewRect');
  276. var r = data.getLayout('r');
  277. var viewWidth = viewRect.width;
  278. var viewLeft = viewRect.x;
  279. var viewTop = viewRect.y;
  280. var viewHeight = viewRect.height;
  281. function setNotShow(el) {
  282. el.ignore = true;
  283. }
  284. function isLabelShown(label) {
  285. if (!label.ignore) {
  286. return true;
  287. }
  288. for (var key in label.states) {
  289. if (label.states[key].ignore === false) {
  290. return true;
  291. }
  292. }
  293. return false;
  294. }
  295. data.each(function (idx) {
  296. var sector = data.getItemGraphicEl(idx);
  297. var sectorShape = sector.shape;
  298. var label = sector.getTextContent();
  299. var labelLine = sector.getTextGuideLine();
  300. var itemModel = data.getItemModel(idx);
  301. var labelModel = itemModel.getModel('label');
  302. // Use position in normal or emphasis
  303. var labelPosition = labelModel.get('position') || itemModel.get(['emphasis', 'label', 'position']);
  304. var labelDistance = labelModel.get('distanceToLabelLine');
  305. var labelAlignTo = labelModel.get('alignTo');
  306. var edgeDistance = parsePercent(labelModel.get('edgeDistance'), viewWidth);
  307. var bleedMargin = labelModel.get('bleedMargin');
  308. var labelLineModel = itemModel.getModel('labelLine');
  309. var labelLineLen = labelLineModel.get('length');
  310. labelLineLen = parsePercent(labelLineLen, viewWidth);
  311. var labelLineLen2 = labelLineModel.get('length2');
  312. labelLineLen2 = parsePercent(labelLineLen2, viewWidth);
  313. if (Math.abs(sectorShape.endAngle - sectorShape.startAngle) < minShowLabelRadian) {
  314. each(label.states, setNotShow);
  315. label.ignore = true;
  316. if (labelLine) {
  317. each(labelLine.states, setNotShow);
  318. labelLine.ignore = true;
  319. }
  320. return;
  321. }
  322. if (!isLabelShown(label)) {
  323. return;
  324. }
  325. var midAngle = (sectorShape.startAngle + sectorShape.endAngle) / 2;
  326. var nx = Math.cos(midAngle);
  327. var ny = Math.sin(midAngle);
  328. var textX;
  329. var textY;
  330. var linePoints;
  331. var textAlign;
  332. cx = sectorShape.cx;
  333. cy = sectorShape.cy;
  334. var isLabelInside = labelPosition === 'inside' || labelPosition === 'inner';
  335. if (labelPosition === 'center') {
  336. textX = sectorShape.cx;
  337. textY = sectorShape.cy;
  338. textAlign = 'center';
  339. } else {
  340. var x1 = (isLabelInside ? (sectorShape.r + sectorShape.r0) / 2 * nx : sectorShape.r * nx) + cx;
  341. var y1 = (isLabelInside ? (sectorShape.r + sectorShape.r0) / 2 * ny : sectorShape.r * ny) + cy;
  342. textX = x1 + nx * 3;
  343. textY = y1 + ny * 3;
  344. if (!isLabelInside) {
  345. // For roseType
  346. var x2 = x1 + nx * (labelLineLen + r - sectorShape.r);
  347. var y2 = y1 + ny * (labelLineLen + r - sectorShape.r);
  348. var x3 = x2 + (nx < 0 ? -1 : 1) * labelLineLen2;
  349. var y3 = y2;
  350. if (labelAlignTo === 'edge') {
  351. // Adjust textX because text align of edge is opposite
  352. textX = nx < 0 ? viewLeft + edgeDistance : viewLeft + viewWidth - edgeDistance;
  353. } else {
  354. textX = x3 + (nx < 0 ? -labelDistance : labelDistance);
  355. }
  356. textY = y3;
  357. linePoints = [[x1, y1], [x2, y2], [x3, y3]];
  358. }
  359. textAlign = isLabelInside ? 'center' : labelAlignTo === 'edge' ? nx > 0 ? 'right' : 'left' : nx > 0 ? 'left' : 'right';
  360. }
  361. var PI = Math.PI;
  362. var labelRotate = 0;
  363. var rotate = labelModel.get('rotate');
  364. if (isNumber(rotate)) {
  365. labelRotate = rotate * (PI / 180);
  366. } else if (labelPosition === 'center') {
  367. labelRotate = 0;
  368. } else if (rotate === 'radial' || rotate === true) {
  369. var radialAngle = nx < 0 ? -midAngle + PI : -midAngle;
  370. labelRotate = radialAngle;
  371. } else if (rotate === 'tangential' && labelPosition !== 'outside' && labelPosition !== 'outer') {
  372. var rad = Math.atan2(nx, ny);
  373. if (rad < 0) {
  374. rad = PI * 2 + rad;
  375. }
  376. var isDown = ny > 0;
  377. if (isDown) {
  378. rad = PI + rad;
  379. }
  380. labelRotate = rad - PI;
  381. }
  382. hasLabelRotate = !!labelRotate;
  383. label.x = textX;
  384. label.y = textY;
  385. label.rotation = labelRotate;
  386. label.setStyle({
  387. verticalAlign: 'middle'
  388. });
  389. // Not sectorShape the inside label
  390. if (!isLabelInside) {
  391. var textRect = label.getBoundingRect().clone();
  392. textRect.applyTransform(label.getComputedTransform());
  393. // Text has a default 1px stroke. Exclude this.
  394. var margin = (label.style.margin || 0) + 2.1;
  395. textRect.y -= margin / 2;
  396. textRect.height += margin;
  397. labelLayoutList.push({
  398. label: label,
  399. labelLine: labelLine,
  400. position: labelPosition,
  401. len: labelLineLen,
  402. len2: labelLineLen2,
  403. minTurnAngle: labelLineModel.get('minTurnAngle'),
  404. maxSurfaceAngle: labelLineModel.get('maxSurfaceAngle'),
  405. surfaceNormal: new Point(nx, ny),
  406. linePoints: linePoints,
  407. textAlign: textAlign,
  408. labelDistance: labelDistance,
  409. labelAlignTo: labelAlignTo,
  410. edgeDistance: edgeDistance,
  411. bleedMargin: bleedMargin,
  412. rect: textRect,
  413. unconstrainedWidth: textRect.width,
  414. labelStyleWidth: label.style.width
  415. });
  416. } else {
  417. label.setStyle({
  418. align: textAlign
  419. });
  420. var selectState = label.states.select;
  421. if (selectState) {
  422. selectState.x += label.x;
  423. selectState.y += label.y;
  424. }
  425. }
  426. sector.setTextConfig({
  427. inside: isLabelInside
  428. });
  429. });
  430. if (!hasLabelRotate && seriesModel.get('avoidLabelOverlap')) {
  431. avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop);
  432. }
  433. for (var i = 0; i < labelLayoutList.length; i++) {
  434. var layout = labelLayoutList[i];
  435. var label = layout.label;
  436. var labelLine = layout.labelLine;
  437. var notShowLabel = isNaN(label.x) || isNaN(label.y);
  438. if (label) {
  439. label.setStyle({
  440. align: layout.textAlign
  441. });
  442. if (notShowLabel) {
  443. each(label.states, setNotShow);
  444. label.ignore = true;
  445. }
  446. var selectState = label.states.select;
  447. if (selectState) {
  448. selectState.x += label.x;
  449. selectState.y += label.y;
  450. }
  451. }
  452. if (labelLine) {
  453. var linePoints = layout.linePoints;
  454. if (notShowLabel || !linePoints) {
  455. each(labelLine.states, setNotShow);
  456. labelLine.ignore = true;
  457. } else {
  458. limitTurnAngle(linePoints, layout.minTurnAngle);
  459. limitSurfaceAngle(linePoints, layout.surfaceNormal, layout.maxSurfaceAngle);
  460. labelLine.setShape({
  461. points: linePoints
  462. });
  463. // Set the anchor to the midpoint of sector
  464. label.__hostTarget.textGuideLineConfig = {
  465. anchor: new Point(linePoints[0][0], linePoints[0][1])
  466. };
  467. }
  468. }
  469. }
  470. }