LabelManager.js 16 KB

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