LabelManager.js 16 KB

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