labelLayout.js 17 KB

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