LabelManager.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  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. // TODO: move labels out of viewport.
  41. import { BoundingRect, updateProps, initProps, isElementRemoved } from '../util/graphic.js';
  42. import { getECData } from '../util/innerStore.js';
  43. import { parsePercent } from '../util/number.js';
  44. import Transformable from 'zrender/lib/core/Transformable.js';
  45. import { updateLabelLinePoints, setLabelLineStyle, getLabelLineStatesModels } from './labelGuideHelper.js';
  46. import { makeInner } from '../util/model.js';
  47. import { retrieve2, each, keys, isFunction, filter, indexOf } from 'zrender/lib/core/util.js';
  48. import { prepareLayoutList, hideOverlap, shiftLayoutOnX, shiftLayoutOnY } from './labelLayoutHelper.js';
  49. import { labelInner, animateLabelValue } from './labelStyle.js';
  50. import { normalizeRadian } from 'zrender/lib/contain/util.js';
  51. function cloneArr(points) {
  52. if (points) {
  53. var newPoints = [];
  54. for (var i = 0; i < points.length; i++) {
  55. newPoints.push(points[i].slice());
  56. }
  57. return newPoints;
  58. }
  59. }
  60. function prepareLayoutCallbackParams(labelItem, hostEl) {
  61. var label = labelItem.label;
  62. var labelLine = hostEl && hostEl.getTextGuideLine();
  63. return {
  64. dataIndex: labelItem.dataIndex,
  65. dataType: labelItem.dataType,
  66. seriesIndex: labelItem.seriesModel.seriesIndex,
  67. text: labelItem.label.style.text,
  68. rect: labelItem.hostRect,
  69. labelRect: labelItem.rect,
  70. // x: labelAttr.x,
  71. // y: labelAttr.y,
  72. align: label.style.align,
  73. verticalAlign: label.style.verticalAlign,
  74. labelLinePoints: cloneArr(labelLine && labelLine.shape.points)
  75. };
  76. }
  77. var LABEL_OPTION_TO_STYLE_KEYS = ['align', 'verticalAlign', 'width', 'height', 'fontSize'];
  78. var dummyTransformable = new Transformable();
  79. var labelLayoutInnerStore = makeInner();
  80. var labelLineAnimationStore = makeInner();
  81. function extendWithKeys(target, source, keys) {
  82. for (var i = 0; i < keys.length; i++) {
  83. var key = keys[i];
  84. if (source[key] != null) {
  85. target[key] = source[key];
  86. }
  87. }
  88. }
  89. var LABEL_LAYOUT_PROPS = ['x', 'y', 'rotation'];
  90. var LabelManager = /** @class */function () {
  91. function LabelManager() {
  92. this._labelList = [];
  93. this._chartViewList = [];
  94. }
  95. LabelManager.prototype.clearLabels = function () {
  96. this._labelList = [];
  97. this._chartViewList = [];
  98. };
  99. /**
  100. * Add label to manager
  101. */
  102. LabelManager.prototype._addLabel = function (dataIndex, dataType, seriesModel, label, layoutOption) {
  103. var labelStyle = label.style;
  104. var hostEl = label.__hostTarget;
  105. var textConfig = hostEl.textConfig || {};
  106. // TODO: If label is in other state.
  107. var labelTransform = label.getComputedTransform();
  108. var labelRect = label.getBoundingRect().plain();
  109. BoundingRect.applyTransform(labelRect, labelRect, labelTransform);
  110. if (labelTransform) {
  111. dummyTransformable.setLocalTransform(labelTransform);
  112. } else {
  113. // Identity transform.
  114. dummyTransformable.x = dummyTransformable.y = dummyTransformable.rotation = dummyTransformable.originX = dummyTransformable.originY = 0;
  115. dummyTransformable.scaleX = dummyTransformable.scaleY = 1;
  116. }
  117. dummyTransformable.rotation = normalizeRadian(dummyTransformable.rotation);
  118. var host = label.__hostTarget;
  119. var hostRect;
  120. if (host) {
  121. hostRect = host.getBoundingRect().plain();
  122. var transform = host.getComputedTransform();
  123. BoundingRect.applyTransform(hostRect, hostRect, transform);
  124. }
  125. var labelGuide = hostRect && host.getTextGuideLine();
  126. this._labelList.push({
  127. label: label,
  128. labelLine: labelGuide,
  129. seriesModel: seriesModel,
  130. dataIndex: dataIndex,
  131. dataType: dataType,
  132. layoutOption: layoutOption,
  133. computedLayoutOption: null,
  134. rect: labelRect,
  135. hostRect: hostRect,
  136. // Label with lower priority will be hidden when overlapped
  137. // Use rect size as default priority
  138. priority: hostRect ? hostRect.width * hostRect.height : 0,
  139. // Save default label attributes.
  140. // For restore if developers want get back to default value in callback.
  141. defaultAttr: {
  142. ignore: label.ignore,
  143. labelGuideIgnore: labelGuide && labelGuide.ignore,
  144. x: dummyTransformable.x,
  145. y: dummyTransformable.y,
  146. scaleX: dummyTransformable.scaleX,
  147. scaleY: dummyTransformable.scaleY,
  148. rotation: dummyTransformable.rotation,
  149. style: {
  150. x: labelStyle.x,
  151. y: labelStyle.y,
  152. align: labelStyle.align,
  153. verticalAlign: labelStyle.verticalAlign,
  154. width: labelStyle.width,
  155. height: labelStyle.height,
  156. fontSize: labelStyle.fontSize
  157. },
  158. cursor: label.cursor,
  159. attachedPos: textConfig.position,
  160. attachedRot: textConfig.rotation
  161. }
  162. });
  163. };
  164. LabelManager.prototype.addLabelsOfSeries = function (chartView) {
  165. var _this = this;
  166. this._chartViewList.push(chartView);
  167. var seriesModel = chartView.__model;
  168. var layoutOption = seriesModel.get('labelLayout');
  169. /**
  170. * Ignore layouting if it's not specified anything.
  171. */
  172. if (!(isFunction(layoutOption) || keys(layoutOption).length)) {
  173. return;
  174. }
  175. chartView.group.traverse(function (child) {
  176. if (child.ignore) {
  177. return true; // Stop traverse descendants.
  178. }
  179. // Only support label being hosted on graphic elements.
  180. var textEl = child.getTextContent();
  181. var ecData = getECData(child);
  182. // Can only attach the text on the element with dataIndex
  183. if (textEl && !textEl.disableLabelLayout) {
  184. _this._addLabel(ecData.dataIndex, ecData.dataType, seriesModel, textEl, layoutOption);
  185. }
  186. });
  187. };
  188. LabelManager.prototype.updateLayoutConfig = function (api) {
  189. var width = api.getWidth();
  190. var height = api.getHeight();
  191. function createDragHandler(el, labelLineModel) {
  192. return function () {
  193. updateLabelLinePoints(el, labelLineModel);
  194. };
  195. }
  196. for (var i = 0; i < this._labelList.length; i++) {
  197. var labelItem = this._labelList[i];
  198. var label = labelItem.label;
  199. var hostEl = label.__hostTarget;
  200. var defaultLabelAttr = labelItem.defaultAttr;
  201. var layoutOption = void 0;
  202. // TODO A global layout option?
  203. if (isFunction(labelItem.layoutOption)) {
  204. layoutOption = labelItem.layoutOption(prepareLayoutCallbackParams(labelItem, hostEl));
  205. } else {
  206. layoutOption = labelItem.layoutOption;
  207. }
  208. layoutOption = layoutOption || {};
  209. labelItem.computedLayoutOption = layoutOption;
  210. var degreeToRadian = Math.PI / 180;
  211. // TODO hostEl should always exists.
  212. // Or label should not have parent because the x, y is all in global space.
  213. if (hostEl) {
  214. hostEl.setTextConfig({
  215. // Force to set local false.
  216. local: false,
  217. // Ignore position and rotation config on the host el if x or y is changed.
  218. position: layoutOption.x != null || layoutOption.y != null ? null : defaultLabelAttr.attachedPos,
  219. // Ignore rotation config on the host el if rotation is changed.
  220. rotation: layoutOption.rotate != null ? layoutOption.rotate * degreeToRadian : defaultLabelAttr.attachedRot,
  221. offset: [layoutOption.dx || 0, layoutOption.dy || 0]
  222. });
  223. }
  224. var needsUpdateLabelLine = false;
  225. if (layoutOption.x != null) {
  226. // TODO width of chart view.
  227. label.x = parsePercent(layoutOption.x, width);
  228. label.setStyle('x', 0); // Ignore movement in style. TODO: origin.
  229. needsUpdateLabelLine = true;
  230. } else {
  231. label.x = defaultLabelAttr.x;
  232. label.setStyle('x', defaultLabelAttr.style.x);
  233. }
  234. if (layoutOption.y != null) {
  235. // TODO height of chart view.
  236. label.y = parsePercent(layoutOption.y, height);
  237. label.setStyle('y', 0); // Ignore movement in style.
  238. needsUpdateLabelLine = true;
  239. } else {
  240. label.y = defaultLabelAttr.y;
  241. label.setStyle('y', defaultLabelAttr.style.y);
  242. }
  243. if (layoutOption.labelLinePoints) {
  244. var guideLine = hostEl.getTextGuideLine();
  245. if (guideLine) {
  246. guideLine.setShape({
  247. points: layoutOption.labelLinePoints
  248. });
  249. // Not update
  250. needsUpdateLabelLine = false;
  251. }
  252. }
  253. var labelLayoutStore = labelLayoutInnerStore(label);
  254. labelLayoutStore.needsUpdateLabelLine = needsUpdateLabelLine;
  255. label.rotation = layoutOption.rotate != null ? layoutOption.rotate * degreeToRadian : defaultLabelAttr.rotation;
  256. label.scaleX = defaultLabelAttr.scaleX;
  257. label.scaleY = defaultLabelAttr.scaleY;
  258. for (var k = 0; k < LABEL_OPTION_TO_STYLE_KEYS.length; k++) {
  259. var key = LABEL_OPTION_TO_STYLE_KEYS[k];
  260. label.setStyle(key, layoutOption[key] != null ? layoutOption[key] : defaultLabelAttr.style[key]);
  261. }
  262. if (layoutOption.draggable) {
  263. label.draggable = true;
  264. label.cursor = 'move';
  265. if (hostEl) {
  266. var hostModel = labelItem.seriesModel;
  267. if (labelItem.dataIndex != null) {
  268. var data = labelItem.seriesModel.getData(labelItem.dataType);
  269. hostModel = data.getItemModel(labelItem.dataIndex);
  270. }
  271. label.on('drag', createDragHandler(hostEl, hostModel.getModel('labelLine')));
  272. }
  273. } else {
  274. // TODO Other drag functions?
  275. label.off('drag');
  276. label.cursor = defaultLabelAttr.cursor;
  277. }
  278. }
  279. };
  280. LabelManager.prototype.layout = function (api) {
  281. var width = api.getWidth();
  282. var height = api.getHeight();
  283. var labelList = prepareLayoutList(this._labelList);
  284. var labelsNeedsAdjustOnX = filter(labelList, function (item) {
  285. return item.layoutOption.moveOverlap === 'shiftX';
  286. });
  287. var labelsNeedsAdjustOnY = filter(labelList, function (item) {
  288. return item.layoutOption.moveOverlap === 'shiftY';
  289. });
  290. shiftLayoutOnX(labelsNeedsAdjustOnX, 0, width);
  291. shiftLayoutOnY(labelsNeedsAdjustOnY, 0, height);
  292. var labelsNeedsHideOverlap = filter(labelList, function (item) {
  293. return item.layoutOption.hideOverlap;
  294. });
  295. hideOverlap(labelsNeedsHideOverlap);
  296. };
  297. /**
  298. * Process all labels. Not only labels with layoutOption.
  299. */
  300. LabelManager.prototype.processLabelsOverall = function () {
  301. var _this = this;
  302. each(this._chartViewList, function (chartView) {
  303. var seriesModel = chartView.__model;
  304. var ignoreLabelLineUpdate = chartView.ignoreLabelLineUpdate;
  305. var animationEnabled = seriesModel.isAnimationEnabled();
  306. chartView.group.traverse(function (child) {
  307. if (child.ignore && !child.forceLabelAnimation) {
  308. return true; // Stop traverse descendants.
  309. }
  310. var needsUpdateLabelLine = !ignoreLabelLineUpdate;
  311. var label = child.getTextContent();
  312. if (!needsUpdateLabelLine && label) {
  313. needsUpdateLabelLine = labelLayoutInnerStore(label).needsUpdateLabelLine;
  314. }
  315. if (needsUpdateLabelLine) {
  316. _this._updateLabelLine(child, seriesModel);
  317. }
  318. if (animationEnabled) {
  319. _this._animateLabels(child, seriesModel);
  320. }
  321. });
  322. });
  323. };
  324. LabelManager.prototype._updateLabelLine = function (el, seriesModel) {
  325. // Only support label being hosted on graphic elements.
  326. var textEl = el.getTextContent();
  327. // Update label line style.
  328. var ecData = getECData(el);
  329. var dataIndex = ecData.dataIndex;
  330. // Only support labelLine on the labels represent data.
  331. if (textEl && dataIndex != null) {
  332. var data = seriesModel.getData(ecData.dataType);
  333. var itemModel = data.getItemModel(dataIndex);
  334. var defaultStyle = {};
  335. var visualStyle = data.getItemVisual(dataIndex, 'style');
  336. if (visualStyle) {
  337. var visualType = data.getVisual('drawType');
  338. // Default to be same with main color
  339. defaultStyle.stroke = visualStyle[visualType];
  340. }
  341. var labelLineModel = itemModel.getModel('labelLine');
  342. setLabelLineStyle(el, getLabelLineStatesModels(itemModel), defaultStyle);
  343. updateLabelLinePoints(el, labelLineModel);
  344. }
  345. };
  346. LabelManager.prototype._animateLabels = function (el, seriesModel) {
  347. var textEl = el.getTextContent();
  348. var guideLine = el.getTextGuideLine();
  349. // Animate
  350. if (textEl
  351. // `forceLabelAnimation` has the highest priority
  352. && (el.forceLabelAnimation || !textEl.ignore && !textEl.invisible && !el.disableLabelAnimation && !isElementRemoved(el))) {
  353. var layoutStore = labelLayoutInnerStore(textEl);
  354. var oldLayout = layoutStore.oldLayout;
  355. var ecData = getECData(el);
  356. var dataIndex = ecData.dataIndex;
  357. var newProps = {
  358. x: textEl.x,
  359. y: textEl.y,
  360. rotation: textEl.rotation
  361. };
  362. var data = seriesModel.getData(ecData.dataType);
  363. if (!oldLayout) {
  364. textEl.attr(newProps);
  365. // Disable fade in animation if value animation is enabled.
  366. if (!labelInner(textEl).valueAnimation) {
  367. var oldOpacity = retrieve2(textEl.style.opacity, 1);
  368. // Fade in animation
  369. textEl.style.opacity = 0;
  370. initProps(textEl, {
  371. style: {
  372. opacity: oldOpacity
  373. }
  374. }, seriesModel, dataIndex);
  375. }
  376. } else {
  377. textEl.attr(oldLayout);
  378. // Make sure the animation from is in the right status.
  379. var prevStates = el.prevStates;
  380. if (prevStates) {
  381. if (indexOf(prevStates, 'select') >= 0) {
  382. textEl.attr(layoutStore.oldLayoutSelect);
  383. }
  384. if (indexOf(prevStates, 'emphasis') >= 0) {
  385. textEl.attr(layoutStore.oldLayoutEmphasis);
  386. }
  387. }
  388. updateProps(textEl, newProps, seriesModel, dataIndex);
  389. }
  390. layoutStore.oldLayout = newProps;
  391. if (textEl.states.select) {
  392. var layoutSelect = layoutStore.oldLayoutSelect = {};
  393. extendWithKeys(layoutSelect, newProps, LABEL_LAYOUT_PROPS);
  394. extendWithKeys(layoutSelect, textEl.states.select, LABEL_LAYOUT_PROPS);
  395. }
  396. if (textEl.states.emphasis) {
  397. var layoutEmphasis = layoutStore.oldLayoutEmphasis = {};
  398. extendWithKeys(layoutEmphasis, newProps, LABEL_LAYOUT_PROPS);
  399. extendWithKeys(layoutEmphasis, textEl.states.emphasis, LABEL_LAYOUT_PROPS);
  400. }
  401. animateLabelValue(textEl, dataIndex, data, seriesModel, seriesModel);
  402. }
  403. if (guideLine && !guideLine.ignore && !guideLine.invisible) {
  404. var layoutStore = labelLineAnimationStore(guideLine);
  405. var oldLayout = layoutStore.oldLayout;
  406. var newLayout = {
  407. points: guideLine.shape.points
  408. };
  409. if (!oldLayout) {
  410. guideLine.setShape(newLayout);
  411. guideLine.style.strokePercent = 0;
  412. initProps(guideLine, {
  413. style: {
  414. strokePercent: 1
  415. }
  416. }, seriesModel);
  417. } else {
  418. guideLine.attr({
  419. shape: oldLayout
  420. });
  421. updateProps(guideLine, {
  422. shape: newLayout
  423. }, seriesModel);
  424. }
  425. layoutStore.oldLayout = newLayout;
  426. }
  427. };
  428. return LabelManager;
  429. }();
  430. export default LabelManager;