AxisBuilder.js 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032
  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. import { retrieve, defaults, extend, each, isObject, isString, isNumber, isFunction, retrieve2, assert, map, retrieve3, filter } from 'zrender/lib/core/util.js';
  41. import * as graphic from '../../util/graphic.js';
  42. import { getECData } from '../../util/innerStore.js';
  43. import { createTextStyle } from '../../label/labelStyle.js';
  44. import Model from '../../model/Model.js';
  45. import { isRadianAroundZero, remRadian } from '../../util/number.js';
  46. import { createSymbol, normalizeSymbolOffset } from '../../util/symbol.js';
  47. import * as matrixUtil from 'zrender/lib/core/matrix.js';
  48. import { applyTransform as v2ApplyTransform } from 'zrender/lib/core/vector.js';
  49. import { isNameLocationCenter, shouldShowAllLabels } from '../../coord/axisHelper.js';
  50. import { hideOverlap, labelIntersect, computeLabelGeometry2, ensureLabelLayoutWithGeometry, labelLayoutApplyTranslation, setLabelLayoutDirty, newLabelLayoutWithGeometry } from '../../label/labelLayoutHelper.js';
  51. import { makeInner } from '../../util/model.js';
  52. import { getAxisBreakHelper } from './axisBreakHelper.js';
  53. import { AXIS_BREAK_EXPAND_ACTION_TYPE } from './axisAction.js';
  54. import { getScaleBreakHelper } from '../../scale/break.js';
  55. import BoundingRect from 'zrender/lib/core/BoundingRect.js';
  56. import Point from 'zrender/lib/core/Point.js';
  57. import { copyTransform } from 'zrender/lib/core/Transformable.js';
  58. import { AxisTickLabelComputingKind, createAxisLabelsComputingContext } from '../../coord/axisTickLabelBuilder.js';
  59. var PI = Math.PI;
  60. var DEFAULT_CENTER_NAME_MARGIN_LEVELS = [[1, 2, 1, 2], [5, 3, 5, 3], [8, 3, 8, 3]];
  61. var DEFAULT_ENDS_NAME_MARGIN_LEVELS = [[0, 1, 0, 1], [0, 3, 0, 3], [0, 3, 0, 3]];
  62. export var getLabelInner = makeInner();
  63. var getTickInner = makeInner();
  64. /**
  65. * A context shared by difference axisBuilder instances.
  66. * For cross-axes overlap resolving.
  67. *
  68. * Lifecycle constraint: should not over a pass of ec main process.
  69. * If model is changed, the context must be disposed.
  70. *
  71. * @see AxisBuilderLocalContext
  72. */
  73. var AxisBuilderSharedContext = /** @class */function () {
  74. function AxisBuilderSharedContext(resolveAxisNameOverlap) {
  75. /**
  76. * [CAUTION] Do not modify this data structure outside this class.
  77. */
  78. this.recordMap = {};
  79. this.resolveAxisNameOverlap = resolveAxisNameOverlap;
  80. }
  81. AxisBuilderSharedContext.prototype.ensureRecord = function (axisModel) {
  82. var dim = axisModel.axis.dim;
  83. var idx = axisModel.componentIndex;
  84. var recordMap = this.recordMap;
  85. var records = recordMap[dim] || (recordMap[dim] = []);
  86. return records[idx] || (records[idx] = {
  87. ready: {}
  88. });
  89. };
  90. return AxisBuilderSharedContext;
  91. }();
  92. export { AxisBuilderSharedContext };
  93. ;
  94. /**
  95. * [CAUTION]
  96. * 1. The call of this function must be after axisLabel overlap handlings
  97. * (such as `hideOverlap`, `fixMinMaxLabelShow`) and after transform calculating.
  98. * 2. Can be called multiple times and should be idempotent.
  99. */
  100. function resetOverlapRecordToShared(cfg, shared, axisModel, labelLayoutList) {
  101. var axis = axisModel.axis;
  102. var record = shared.ensureRecord(axisModel);
  103. var labelInfoList = [];
  104. var stOccupiedRect;
  105. var useStOccupiedRect = hasAxisName(cfg.axisName) && isNameLocationCenter(cfg.nameLocation);
  106. each(labelLayoutList, function (layout) {
  107. var layoutInfo = ensureLabelLayoutWithGeometry(layout);
  108. if (!layoutInfo || layoutInfo.label.ignore) {
  109. return;
  110. }
  111. labelInfoList.push(layoutInfo);
  112. var transGroup = record.transGroup;
  113. if (useStOccupiedRect) {
  114. // Transform to "standard axis" for creating stOccupiedRect (the label rects union).
  115. transGroup.transform ? matrixUtil.invert(_stTransTmp, transGroup.transform) : matrixUtil.identity(_stTransTmp);
  116. if (layoutInfo.transform) {
  117. matrixUtil.mul(_stTransTmp, _stTransTmp, layoutInfo.transform);
  118. }
  119. BoundingRect.copy(_stLabelRectTmp, layoutInfo.localRect);
  120. _stLabelRectTmp.applyTransform(_stTransTmp);
  121. stOccupiedRect ? stOccupiedRect.union(_stLabelRectTmp) : BoundingRect.copy(stOccupiedRect = new BoundingRect(0, 0, 0, 0), _stLabelRectTmp);
  122. }
  123. });
  124. var sortByDim = Math.abs(record.dirVec.x) > 0.1 ? 'x' : 'y';
  125. var sortByValue = record.transGroup[sortByDim];
  126. labelInfoList.sort(function (info1, info2) {
  127. return Math.abs(info1.label[sortByDim] - sortByValue) - Math.abs(info2.label[sortByDim] - sortByValue);
  128. });
  129. if (useStOccupiedRect && stOccupiedRect) {
  130. var extent = axis.getExtent();
  131. var axisLineX = Math.min(extent[0], extent[1]);
  132. var axisLineWidth = Math.max(extent[0], extent[1]) - axisLineX;
  133. // If `nameLocation` is 'middle', enlarge axis labels boundingRect to axisLine to avoid bad
  134. // case like that axis name is placed in the gap between axis labels and axis line.
  135. // If only one label exists, the entire band should be occupied for
  136. // visual consistency, so extent it to [0, canvas width].
  137. stOccupiedRect.union(new BoundingRect(axisLineX, 0, axisLineWidth, 1));
  138. }
  139. record.stOccupiedRect = stOccupiedRect;
  140. record.labelInfoList = labelInfoList;
  141. }
  142. var _stTransTmp = matrixUtil.create();
  143. var _stLabelRectTmp = new BoundingRect(0, 0, 0, 0);
  144. /**
  145. * The default resolver does not involve other axes within the same coordinate system.
  146. */
  147. export var resolveAxisNameOverlapDefault = function (cfg, ctx, axisModel, nameLayoutInfo, nameMoveDirVec, thisRecord) {
  148. if (isNameLocationCenter(cfg.nameLocation)) {
  149. var stOccupiedRect = thisRecord.stOccupiedRect;
  150. if (stOccupiedRect) {
  151. moveIfOverlap(computeLabelGeometry2({}, stOccupiedRect, thisRecord.transGroup.transform), nameLayoutInfo, nameMoveDirVec);
  152. }
  153. } else {
  154. moveIfOverlapByLinearLabels(thisRecord.labelInfoList, thisRecord.dirVec, nameLayoutInfo, nameMoveDirVec);
  155. }
  156. };
  157. // [NOTICE] not consider ignore.
  158. function moveIfOverlap(basedLayoutInfo, movableLayoutInfo, moveDirVec) {
  159. var mtv = new Point();
  160. if (labelIntersect(basedLayoutInfo, movableLayoutInfo, mtv, {
  161. direction: Math.atan2(moveDirVec.y, moveDirVec.x),
  162. bidirectional: false,
  163. touchThreshold: 0.05
  164. })) {
  165. labelLayoutApplyTranslation(movableLayoutInfo, mtv);
  166. }
  167. }
  168. export function moveIfOverlapByLinearLabels(baseLayoutInfoList, baseDirVec, movableLayoutInfo, moveDirVec) {
  169. // Detect and move from far to close.
  170. var sameDir = Point.dot(moveDirVec, baseDirVec) >= 0;
  171. for (var idx = 0, len = baseLayoutInfoList.length; idx < len; idx++) {
  172. var labelInfo = baseLayoutInfoList[sameDir ? idx : len - 1 - idx];
  173. if (!labelInfo.label.ignore) {
  174. moveIfOverlap(labelInfo, movableLayoutInfo, moveDirVec);
  175. }
  176. }
  177. }
  178. /**
  179. * @caution
  180. * - Ensure it is called after the data processing stage finished.
  181. * - It might be called before `CahrtView#render`, sush as called at `CoordinateSystem#update`,
  182. * thus ensure the result the same whenever it is called.
  183. *
  184. * A builder for a straight-line axis.
  185. *
  186. * A final axis is translated and rotated from a "standard axis".
  187. * So opt.position and opt.rotation is required.
  188. *
  189. * A "standard axis" is the axis [0,0]-->[abs(axisExtent[1]-axisExtent[0]),0]
  190. * for example: [0,0]-->[50,0]
  191. */
  192. var AxisBuilder = /** @class */function () {
  193. /**
  194. * [CAUTION]: axisModel.axis.extent/scale must be ready to use.
  195. */
  196. function AxisBuilder(axisModel, api, opt, shared) {
  197. this.group = new graphic.Group();
  198. this._axisModel = axisModel;
  199. this._api = api;
  200. this._local = {};
  201. this._shared = shared || new AxisBuilderSharedContext(resolveAxisNameOverlapDefault);
  202. this._resetCfgDetermined(opt);
  203. }
  204. /**
  205. * Regarding axis label related configurations, only the change of label.x/y is supported; other
  206. * changes are not necessary and not performant. To be specific, only `axis.position`
  207. * (and consequently `labelOffset`) and `axis.extent` can be changed, and assume everything in
  208. * `axisModel` are not changed.
  209. * Axis line related configurations can be changed since this method can only be called
  210. * before they are created.
  211. */
  212. AxisBuilder.prototype.updateCfg = function (opt) {
  213. if (process.env.NODE_ENV !== 'production') {
  214. var ready = this._shared.ensureRecord(this._axisModel).ready;
  215. // After that, changing cfg is not supported; avoid unnecessary complexity.
  216. assert(!ready.axisLine && !ready.axisTickLabelDetermine);
  217. // Have to be called again if cfg changed.
  218. ready.axisName = ready.axisTickLabelEstimate = false;
  219. }
  220. var raw = this._cfg.raw;
  221. raw.position = opt.position;
  222. raw.labelOffset = opt.labelOffset;
  223. this._resetCfgDetermined(raw);
  224. };
  225. /**
  226. * [CAUTION] For debug usage. Never change it outside!
  227. */
  228. AxisBuilder.prototype.__getRawCfg = function () {
  229. return this._cfg.raw;
  230. };
  231. AxisBuilder.prototype._resetCfgDetermined = function (raw) {
  232. var axisModel = this._axisModel;
  233. // FIXME:
  234. // Currently there is no uniformed way to set default values if an option
  235. // is specified null/undefined by user (intentionally or unintentionally),
  236. // e.g. null/undefined is not a illegal value for `nameLocation`.
  237. // Try to use `getDefaultOption` to address it. But radar has no `getDefaultOption`.
  238. var axisModelDefaultOption = axisModel.getDefaultOption ? axisModel.getDefaultOption() : {};
  239. // Default value
  240. var axisName = retrieve2(raw.axisName, axisModel.get('name'));
  241. var nameMoveOverlapOption = axisModel.get('nameMoveOverlap');
  242. if (nameMoveOverlapOption == null || nameMoveOverlapOption === 'auto') {
  243. nameMoveOverlapOption = retrieve2(raw.defaultNameMoveOverlap, true);
  244. }
  245. var cfg = {
  246. raw: raw,
  247. position: raw.position,
  248. rotation: raw.rotation,
  249. nameDirection: retrieve2(raw.nameDirection, 1),
  250. tickDirection: retrieve2(raw.tickDirection, 1),
  251. labelDirection: retrieve2(raw.labelDirection, 1),
  252. labelOffset: retrieve2(raw.labelOffset, 0),
  253. silent: retrieve2(raw.silent, true),
  254. axisName: axisName,
  255. nameLocation: retrieve3(axisModel.get('nameLocation'), axisModelDefaultOption.nameLocation, 'end'),
  256. shouldNameMoveOverlap: hasAxisName(axisName) && nameMoveOverlapOption,
  257. optionHideOverlap: axisModel.get(['axisLabel', 'hideOverlap']),
  258. showMinorTicks: axisModel.get(['minorTick', 'show'])
  259. };
  260. if (process.env.NODE_ENV !== 'production') {
  261. assert(cfg.position != null);
  262. assert(cfg.rotation != null);
  263. }
  264. this._cfg = cfg;
  265. // FIXME Not use a separate text group?
  266. var transformGroup = new graphic.Group({
  267. x: cfg.position[0],
  268. y: cfg.position[1],
  269. rotation: cfg.rotation
  270. });
  271. transformGroup.updateTransform();
  272. this._transformGroup = transformGroup;
  273. var record = this._shared.ensureRecord(axisModel);
  274. record.transGroup = this._transformGroup;
  275. record.dirVec = new Point(Math.cos(-cfg.rotation), Math.sin(-cfg.rotation));
  276. };
  277. AxisBuilder.prototype.build = function (axisPartNameMap, extraParams) {
  278. var _this = this;
  279. if (!axisPartNameMap) {
  280. axisPartNameMap = {
  281. axisLine: true,
  282. axisTickLabelEstimate: false,
  283. axisTickLabelDetermine: true,
  284. axisName: true
  285. };
  286. }
  287. each(AXIS_BUILDER_AXIS_PART_NAMES, function (partName) {
  288. if (axisPartNameMap[partName]) {
  289. builders[partName](_this._cfg, _this._local, _this._shared, _this._axisModel, _this.group, _this._transformGroup, _this._api, extraParams || {});
  290. }
  291. });
  292. return this;
  293. };
  294. /**
  295. * Currently only get text align/verticalAlign by rotation.
  296. * NO `position` is involved, otherwise it have to be performed for each `updateAxisLabelChangableProps`.
  297. */
  298. AxisBuilder.innerTextLayout = function (axisRotation, textRotation, direction) {
  299. var rotationDiff = remRadian(textRotation - axisRotation);
  300. var textAlign;
  301. var textVerticalAlign;
  302. if (isRadianAroundZero(rotationDiff)) {
  303. // Label is parallel with axis line.
  304. textVerticalAlign = direction > 0 ? 'top' : 'bottom';
  305. textAlign = 'center';
  306. } else if (isRadianAroundZero(rotationDiff - PI)) {
  307. // Label is inverse parallel with axis line.
  308. textVerticalAlign = direction > 0 ? 'bottom' : 'top';
  309. textAlign = 'center';
  310. } else {
  311. textVerticalAlign = 'middle';
  312. if (rotationDiff > 0 && rotationDiff < PI) {
  313. textAlign = direction > 0 ? 'right' : 'left';
  314. } else {
  315. textAlign = direction > 0 ? 'left' : 'right';
  316. }
  317. }
  318. return {
  319. rotation: rotationDiff,
  320. textAlign: textAlign,
  321. textVerticalAlign: textVerticalAlign
  322. };
  323. };
  324. AxisBuilder.makeAxisEventDataBase = function (axisModel) {
  325. var eventData = {
  326. componentType: axisModel.mainType,
  327. componentIndex: axisModel.componentIndex
  328. };
  329. eventData[axisModel.mainType + 'Index'] = axisModel.componentIndex;
  330. return eventData;
  331. };
  332. AxisBuilder.isLabelSilent = function (axisModel) {
  333. var tooltipOpt = axisModel.get('tooltip');
  334. return axisModel.get('silent')
  335. // Consider mouse cursor, add these restrictions.
  336. || !(axisModel.get('triggerEvent') || tooltipOpt && tooltipOpt.show);
  337. };
  338. return AxisBuilder;
  339. }();
  340. ;
  341. // Sorted by dependency order.
  342. var AXIS_BUILDER_AXIS_PART_NAMES = ['axisLine', 'axisTickLabelEstimate', 'axisTickLabelDetermine', 'axisName'];
  343. var builders = {
  344. axisLine: function (cfg, local, shared, axisModel, group, transformGroup, api) {
  345. if (process.env.NODE_ENV !== 'production') {
  346. var ready = shared.ensureRecord(axisModel).ready;
  347. assert(!ready.axisLine);
  348. ready.axisLine = true;
  349. }
  350. var shown = axisModel.get(['axisLine', 'show']);
  351. if (shown === 'auto') {
  352. shown = true;
  353. if (cfg.raw.axisLineAutoShow != null) {
  354. shown = !!cfg.raw.axisLineAutoShow;
  355. }
  356. }
  357. if (!shown) {
  358. return;
  359. }
  360. var extent = axisModel.axis.getExtent();
  361. var matrix = transformGroup.transform;
  362. var pt1 = [extent[0], 0];
  363. var pt2 = [extent[1], 0];
  364. var inverse = pt1[0] > pt2[0];
  365. if (matrix) {
  366. v2ApplyTransform(pt1, pt1, matrix);
  367. v2ApplyTransform(pt2, pt2, matrix);
  368. }
  369. var lineStyle = extend({
  370. lineCap: 'round'
  371. }, axisModel.getModel(['axisLine', 'lineStyle']).getLineStyle());
  372. var pathBaseProp = {
  373. strokeContainThreshold: cfg.raw.strokeContainThreshold || 5,
  374. silent: true,
  375. z2: 1,
  376. style: lineStyle
  377. };
  378. if (axisModel.get(['axisLine', 'breakLine']) && axisModel.axis.scale.hasBreaks()) {
  379. getAxisBreakHelper().buildAxisBreakLine(axisModel, group, transformGroup, pathBaseProp);
  380. } else {
  381. var line = new graphic.Line(extend({
  382. shape: {
  383. x1: pt1[0],
  384. y1: pt1[1],
  385. x2: pt2[0],
  386. y2: pt2[1]
  387. }
  388. }, pathBaseProp));
  389. graphic.subPixelOptimizeLine(line.shape, line.style.lineWidth);
  390. line.anid = 'line';
  391. group.add(line);
  392. }
  393. var arrows = axisModel.get(['axisLine', 'symbol']);
  394. if (arrows != null) {
  395. var arrowSize = axisModel.get(['axisLine', 'symbolSize']);
  396. if (isString(arrows)) {
  397. // Use the same arrow for start and end point
  398. arrows = [arrows, arrows];
  399. }
  400. if (isString(arrowSize) || isNumber(arrowSize)) {
  401. // Use the same size for width and height
  402. arrowSize = [arrowSize, arrowSize];
  403. }
  404. var arrowOffset = normalizeSymbolOffset(axisModel.get(['axisLine', 'symbolOffset']) || 0, arrowSize);
  405. var symbolWidth_1 = arrowSize[0];
  406. var symbolHeight_1 = arrowSize[1];
  407. each([{
  408. rotate: cfg.rotation + Math.PI / 2,
  409. offset: arrowOffset[0],
  410. r: 0
  411. }, {
  412. rotate: cfg.rotation - Math.PI / 2,
  413. offset: arrowOffset[1],
  414. r: Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1]))
  415. }], function (point, index) {
  416. if (arrows[index] !== 'none' && arrows[index] != null) {
  417. var symbol = createSymbol(arrows[index], -symbolWidth_1 / 2, -symbolHeight_1 / 2, symbolWidth_1, symbolHeight_1, lineStyle.stroke, true);
  418. // Calculate arrow position with offset
  419. var r = point.r + point.offset;
  420. var pt = inverse ? pt2 : pt1;
  421. symbol.attr({
  422. rotation: point.rotate,
  423. x: pt[0] + r * Math.cos(cfg.rotation),
  424. y: pt[1] - r * Math.sin(cfg.rotation),
  425. silent: true,
  426. z2: 11
  427. });
  428. group.add(symbol);
  429. }
  430. });
  431. }
  432. },
  433. /**
  434. * [CAUTION] This method can be called multiple times, following the change due to `resetCfg` called
  435. * in size measurement. Thus this method should be idempotent, and should be performant.
  436. */
  437. axisTickLabelEstimate: function (cfg, local, shared, axisModel, group, transformGroup, api, extraParams) {
  438. if (process.env.NODE_ENV !== 'production') {
  439. var ready = shared.ensureRecord(axisModel).ready;
  440. assert(!ready.axisTickLabelDetermine);
  441. ready.axisTickLabelEstimate = true;
  442. }
  443. var needCallLayout = dealLastTickLabelResultReusable(local, group, extraParams);
  444. if (needCallLayout) {
  445. layOutAxisTickLabel(cfg, local, shared, axisModel, group, transformGroup, api, AxisTickLabelComputingKind.estimate);
  446. }
  447. },
  448. /**
  449. * Finish axis tick label build.
  450. * Can be only called once.
  451. */
  452. axisTickLabelDetermine: function (cfg, local, shared, axisModel, group, transformGroup, api, extraParams) {
  453. if (process.env.NODE_ENV !== 'production') {
  454. var ready = shared.ensureRecord(axisModel).ready;
  455. ready.axisTickLabelDetermine = true;
  456. }
  457. var needCallLayout = dealLastTickLabelResultReusable(local, group, extraParams);
  458. if (needCallLayout) {
  459. layOutAxisTickLabel(cfg, local, shared, axisModel, group, transformGroup, api, AxisTickLabelComputingKind.determine);
  460. }
  461. var ticksEls = buildAxisMajorTicks(cfg, group, transformGroup, axisModel);
  462. syncLabelIgnoreToMajorTicks(cfg, local.labelLayoutList, ticksEls);
  463. buildAxisMinorTicks(cfg, group, transformGroup, axisModel, cfg.tickDirection);
  464. },
  465. /**
  466. * [CAUTION] This method can be called multiple times, following the change due to `resetCfg` called
  467. * in size measurement. Thus this method should be idempotent, and should be performant.
  468. */
  469. axisName: function (cfg, local, shared, axisModel, group, transformGroup, api, extraParams) {
  470. var sharedRecord = shared.ensureRecord(axisModel);
  471. if (process.env.NODE_ENV !== 'production') {
  472. var ready = sharedRecord.ready;
  473. assert(ready.axisTickLabelEstimate || ready.axisTickLabelDetermine);
  474. ready.axisName = true;
  475. }
  476. // Remove the existing name result created in estimation phase.
  477. if (local.nameEl) {
  478. group.remove(local.nameEl);
  479. local.nameEl = sharedRecord.nameLayout = sharedRecord.nameLocation = null;
  480. }
  481. var name = cfg.axisName;
  482. if (!hasAxisName(name)) {
  483. return;
  484. }
  485. var nameLocation = cfg.nameLocation;
  486. var nameDirection = cfg.nameDirection;
  487. var textStyleModel = axisModel.getModel('nameTextStyle');
  488. var gap = axisModel.get('nameGap') || 0;
  489. var extent = axisModel.axis.getExtent();
  490. var gapStartEndSignal = axisModel.axis.inverse ? -1 : 1;
  491. var pos = new Point(0, 0);
  492. var nameMoveDirVec = new Point(0, 0);
  493. if (nameLocation === 'start') {
  494. pos.x = extent[0] - gapStartEndSignal * gap;
  495. nameMoveDirVec.x = -gapStartEndSignal;
  496. } else if (nameLocation === 'end') {
  497. pos.x = extent[1] + gapStartEndSignal * gap;
  498. nameMoveDirVec.x = gapStartEndSignal;
  499. } else {
  500. // 'middle' or 'center'
  501. pos.x = (extent[0] + extent[1]) / 2;
  502. pos.y = cfg.labelOffset + nameDirection * gap;
  503. nameMoveDirVec.y = nameDirection;
  504. }
  505. var mt = matrixUtil.create();
  506. nameMoveDirVec.transform(matrixUtil.rotate(mt, mt, cfg.rotation));
  507. var nameRotation = axisModel.get('nameRotate');
  508. if (nameRotation != null) {
  509. nameRotation = nameRotation * PI / 180; // To radian.
  510. }
  511. var labelLayout;
  512. var axisNameAvailableWidth;
  513. if (isNameLocationCenter(nameLocation)) {
  514. labelLayout = AxisBuilder.innerTextLayout(cfg.rotation, nameRotation != null ? nameRotation : cfg.rotation,
  515. // Adapt to axis.
  516. nameDirection);
  517. } else {
  518. labelLayout = endTextLayout(cfg.rotation, nameLocation, nameRotation || 0, extent);
  519. axisNameAvailableWidth = cfg.raw.axisNameAvailableWidth;
  520. if (axisNameAvailableWidth != null) {
  521. axisNameAvailableWidth = Math.abs(axisNameAvailableWidth / Math.sin(labelLayout.rotation));
  522. !isFinite(axisNameAvailableWidth) && (axisNameAvailableWidth = null);
  523. }
  524. }
  525. var textFont = textStyleModel.getFont();
  526. var truncateOpt = axisModel.get('nameTruncate', true) || {};
  527. var ellipsis = truncateOpt.ellipsis;
  528. var maxWidth = retrieve(cfg.raw.nameTruncateMaxWidth, truncateOpt.maxWidth, axisNameAvailableWidth);
  529. var nameMarginLevel = extraParams.nameMarginLevel || 0;
  530. var textEl = new graphic.Text({
  531. x: pos.x,
  532. y: pos.y,
  533. rotation: labelLayout.rotation,
  534. silent: AxisBuilder.isLabelSilent(axisModel),
  535. style: createTextStyle(textStyleModel, {
  536. text: name,
  537. font: textFont,
  538. overflow: 'truncate',
  539. width: maxWidth,
  540. ellipsis: ellipsis,
  541. fill: textStyleModel.getTextColor() || axisModel.get(['axisLine', 'lineStyle', 'color']),
  542. align: textStyleModel.get('align') || labelLayout.textAlign,
  543. verticalAlign: textStyleModel.get('verticalAlign') || labelLayout.textVerticalAlign
  544. }),
  545. z2: 1
  546. });
  547. graphic.setTooltipConfig({
  548. el: textEl,
  549. componentModel: axisModel,
  550. itemName: name
  551. });
  552. textEl.__fullText = name;
  553. // Id for animation
  554. textEl.anid = 'name';
  555. if (axisModel.get('triggerEvent')) {
  556. var eventData = AxisBuilder.makeAxisEventDataBase(axisModel);
  557. eventData.targetType = 'axisName';
  558. eventData.name = name;
  559. getECData(textEl).eventData = eventData;
  560. }
  561. transformGroup.add(textEl);
  562. textEl.updateTransform();
  563. local.nameEl = textEl;
  564. var nameLayout = sharedRecord.nameLayout = ensureLabelLayoutWithGeometry({
  565. label: textEl,
  566. priority: textEl.z2,
  567. defaultAttr: {
  568. ignore: textEl.ignore
  569. },
  570. marginDefault: isNameLocationCenter(nameLocation)
  571. // Make axis name visually far from axis labels.
  572. // (but not too aggressive, consider multiple small charts)
  573. ? DEFAULT_CENTER_NAME_MARGIN_LEVELS[nameMarginLevel]
  574. // top/button margin is set to `0` to inserted the xAxis name into the indention
  575. // above the axis labels to save space. (see example below.)
  576. : DEFAULT_ENDS_NAME_MARGIN_LEVELS[nameMarginLevel]
  577. });
  578. sharedRecord.nameLocation = nameLocation;
  579. group.add(textEl);
  580. textEl.decomposeTransform();
  581. if (cfg.shouldNameMoveOverlap && nameLayout) {
  582. var record = shared.ensureRecord(axisModel);
  583. if (process.env.NODE_ENV !== 'production') {
  584. assert(record.labelInfoList);
  585. }
  586. shared.resolveAxisNameOverlap(cfg, shared, axisModel, nameLayout, nameMoveDirVec, record);
  587. }
  588. }
  589. };
  590. function layOutAxisTickLabel(cfg, local, shared, axisModel, group, transformGroup, api, kind) {
  591. if (!axisLabelBuildResultExists(local)) {
  592. buildAxisLabel(cfg, local, group, kind, axisModel, api);
  593. }
  594. var labelLayoutList = local.labelLayoutList;
  595. updateAxisLabelChangableProps(cfg, axisModel, labelLayoutList, transformGroup);
  596. adjustBreakLabels(axisModel, cfg.rotation, labelLayoutList);
  597. var optionHideOverlap = cfg.optionHideOverlap;
  598. fixMinMaxLabelShow(axisModel, labelLayoutList, optionHideOverlap);
  599. if (optionHideOverlap) {
  600. // This bit fixes the label overlap issue for the time chart.
  601. // See https://github.com/apache/echarts/issues/14266 for more.
  602. hideOverlap(
  603. // Filter the already ignored labels by the previous overlap resolving methods.
  604. filter(labelLayoutList, function (layout) {
  605. return layout && !layout.label.ignore;
  606. }));
  607. }
  608. // Always call it even this axis has no name, since it serves in overlapping detection
  609. // and grid outerBounds on other axis.
  610. resetOverlapRecordToShared(cfg, shared, axisModel, labelLayoutList);
  611. }
  612. ;
  613. function endTextLayout(rotation, textPosition, textRotate, extent) {
  614. var rotationDiff = remRadian(textRotate - rotation);
  615. var textAlign;
  616. var textVerticalAlign;
  617. var inverse = extent[0] > extent[1];
  618. var onLeft = textPosition === 'start' && !inverse || textPosition !== 'start' && inverse;
  619. if (isRadianAroundZero(rotationDiff - PI / 2)) {
  620. textVerticalAlign = onLeft ? 'bottom' : 'top';
  621. textAlign = 'center';
  622. } else if (isRadianAroundZero(rotationDiff - PI * 1.5)) {
  623. textVerticalAlign = onLeft ? 'top' : 'bottom';
  624. textAlign = 'center';
  625. } else {
  626. textVerticalAlign = 'middle';
  627. if (rotationDiff < PI * 1.5 && rotationDiff > PI / 2) {
  628. textAlign = onLeft ? 'left' : 'right';
  629. } else {
  630. textAlign = onLeft ? 'right' : 'left';
  631. }
  632. }
  633. return {
  634. rotation: rotationDiff,
  635. textAlign: textAlign,
  636. textVerticalAlign: textVerticalAlign
  637. };
  638. }
  639. /**
  640. * Assume `labelLayoutList` has no `label.ignore: true`.
  641. * Assume `labelLayoutList` have been sorted by value ascending order.
  642. */
  643. function fixMinMaxLabelShow(axisModel, labelLayoutList, optionHideOverlap) {
  644. if (shouldShowAllLabels(axisModel.axis)) {
  645. return;
  646. }
  647. // FIXME
  648. // Have not consider onBand yet, where tick els is more than label els.
  649. // Assert no ignore in labels.
  650. function deal(showMinMaxLabel, outmostLabelIdx, innerLabelIdx) {
  651. var outmostLabelLayout = ensureLabelLayoutWithGeometry(labelLayoutList[outmostLabelIdx]);
  652. var innerLabelLayout = ensureLabelLayoutWithGeometry(labelLayoutList[innerLabelIdx]);
  653. if (!outmostLabelLayout || !innerLabelLayout) {
  654. return;
  655. }
  656. if (showMinMaxLabel === false || outmostLabelLayout.suggestIgnore) {
  657. ignoreEl(outmostLabelLayout.label);
  658. return;
  659. }
  660. if (innerLabelLayout.suggestIgnore) {
  661. ignoreEl(innerLabelLayout.label);
  662. return;
  663. }
  664. // PENDING: Originally we thought `optionHideOverlap === false` means do not hide anything,
  665. // since currently the bounding rect of text might not accurate enough and might slightly bigger,
  666. // which causes false positive. But `optionHideOverlap: null/undfined` is falsy and likely
  667. // be treated as false.
  668. // In most fonts the glyph does not reach the boundary of the bounding rect.
  669. // This is needed to avoid too aggressive to hide two elements that meet at the edge
  670. // due to compact layout by the same bounding rect or OBB.
  671. var touchThreshold = 0.1;
  672. // This treatment is for backward compatibility. And `!optionHideOverlap` implies that
  673. // the user accepts the visual touch between adjacent labels, thus "hide min/max label"
  674. // should be conservative, since the space might be sufficient in this case.
  675. if (!optionHideOverlap) {
  676. var marginForce = [0, 0, 0, 0];
  677. // Make a copy to apply `ignoreMargin`.
  678. outmostLabelLayout = newLabelLayoutWithGeometry({
  679. marginForce: marginForce
  680. }, outmostLabelLayout);
  681. innerLabelLayout = newLabelLayoutWithGeometry({
  682. marginForce: marginForce
  683. }, innerLabelLayout);
  684. }
  685. if (labelIntersect(outmostLabelLayout, innerLabelLayout, null, {
  686. touchThreshold: touchThreshold
  687. })) {
  688. if (showMinMaxLabel) {
  689. ignoreEl(innerLabelLayout.label);
  690. } else {
  691. ignoreEl(outmostLabelLayout.label);
  692. }
  693. }
  694. }
  695. // If min or max are user set, we need to check
  696. // If the tick on min(max) are overlap on their neighbour tick
  697. // If they are overlapped, we need to hide the min(max) tick label
  698. var showMinLabel = axisModel.get(['axisLabel', 'showMinLabel']);
  699. var showMaxLabel = axisModel.get(['axisLabel', 'showMaxLabel']);
  700. var labelsLen = labelLayoutList.length;
  701. deal(showMinLabel, 0, 1);
  702. deal(showMaxLabel, labelsLen - 1, labelsLen - 2);
  703. }
  704. // PENDING: Is it necessary to display a tick while the corresponding label is ignored?
  705. function syncLabelIgnoreToMajorTicks(cfg, labelLayoutList, tickEls) {
  706. if (cfg.showMinorTicks) {
  707. // It probably unreaasonable to hide major ticks when show minor ticks.
  708. return;
  709. }
  710. each(labelLayoutList, function (labelLayout) {
  711. if (labelLayout && labelLayout.label.ignore) {
  712. for (var idx = 0; idx < tickEls.length; idx++) {
  713. var tickEl = tickEls[idx];
  714. // Assume small array, linear search is fine for performance.
  715. // PENDING: measure?
  716. var tickInner = getTickInner(tickEl);
  717. var labelInner = getLabelInner(labelLayout.label);
  718. if (tickInner.tickValue != null && !tickInner.onBand && tickInner.tickValue === labelInner.tickValue) {
  719. ignoreEl(tickEl);
  720. return;
  721. }
  722. }
  723. }
  724. });
  725. }
  726. function ignoreEl(el) {
  727. el && (el.ignore = true);
  728. }
  729. function createTicks(ticksCoords, tickTransform, tickEndCoord, tickLineStyle, anidPrefix) {
  730. var tickEls = [];
  731. var pt1 = [];
  732. var pt2 = [];
  733. for (var i = 0; i < ticksCoords.length; i++) {
  734. var tickCoord = ticksCoords[i].coord;
  735. pt1[0] = tickCoord;
  736. pt1[1] = 0;
  737. pt2[0] = tickCoord;
  738. pt2[1] = tickEndCoord;
  739. if (tickTransform) {
  740. v2ApplyTransform(pt1, pt1, tickTransform);
  741. v2ApplyTransform(pt2, pt2, tickTransform);
  742. }
  743. // Tick line, Not use group transform to have better line draw
  744. var tickEl = new graphic.Line({
  745. shape: {
  746. x1: pt1[0],
  747. y1: pt1[1],
  748. x2: pt2[0],
  749. y2: pt2[1]
  750. },
  751. style: tickLineStyle,
  752. z2: 2,
  753. autoBatch: true,
  754. silent: true
  755. });
  756. graphic.subPixelOptimizeLine(tickEl.shape, tickEl.style.lineWidth);
  757. tickEl.anid = anidPrefix + '_' + ticksCoords[i].tickValue;
  758. tickEls.push(tickEl);
  759. var inner = getTickInner(tickEl);
  760. inner.onBand = !!ticksCoords[i].onBand;
  761. inner.tickValue = ticksCoords[i].tickValue;
  762. }
  763. return tickEls;
  764. }
  765. function buildAxisMajorTicks(cfg, group, transformGroup, axisModel) {
  766. var axis = axisModel.axis;
  767. var tickModel = axisModel.getModel('axisTick');
  768. var shown = tickModel.get('show');
  769. if (shown === 'auto') {
  770. shown = true;
  771. if (cfg.raw.axisTickAutoShow != null) {
  772. shown = !!cfg.raw.axisTickAutoShow;
  773. }
  774. }
  775. if (!shown || axis.scale.isBlank()) {
  776. return [];
  777. }
  778. var lineStyleModel = tickModel.getModel('lineStyle');
  779. var tickEndCoord = cfg.tickDirection * tickModel.get('length');
  780. var ticksCoords = axis.getTicksCoords();
  781. var ticksEls = createTicks(ticksCoords, transformGroup.transform, tickEndCoord, defaults(lineStyleModel.getLineStyle(), {
  782. stroke: axisModel.get(['axisLine', 'lineStyle', 'color'])
  783. }), 'ticks');
  784. for (var i = 0; i < ticksEls.length; i++) {
  785. group.add(ticksEls[i]);
  786. }
  787. return ticksEls;
  788. }
  789. function buildAxisMinorTicks(cfg, group, transformGroup, axisModel, tickDirection) {
  790. var axis = axisModel.axis;
  791. var minorTickModel = axisModel.getModel('minorTick');
  792. if (!cfg.showMinorTicks || axis.scale.isBlank()) {
  793. return;
  794. }
  795. var minorTicksCoords = axis.getMinorTicksCoords();
  796. if (!minorTicksCoords.length) {
  797. return;
  798. }
  799. var lineStyleModel = minorTickModel.getModel('lineStyle');
  800. var tickEndCoord = tickDirection * minorTickModel.get('length');
  801. var minorTickLineStyle = defaults(lineStyleModel.getLineStyle(), defaults(axisModel.getModel('axisTick').getLineStyle(), {
  802. stroke: axisModel.get(['axisLine', 'lineStyle', 'color'])
  803. }));
  804. for (var i = 0; i < minorTicksCoords.length; i++) {
  805. var minorTicksEls = createTicks(minorTicksCoords[i], transformGroup.transform, tickEndCoord, minorTickLineStyle, 'minorticks_' + i);
  806. for (var k = 0; k < minorTicksEls.length; k++) {
  807. group.add(minorTicksEls[k]);
  808. }
  809. }
  810. }
  811. // Return whether need to call `layOutAxisTickLabel` again.
  812. function dealLastTickLabelResultReusable(local, group, extraParams) {
  813. if (axisLabelBuildResultExists(local)) {
  814. var axisLabelsCreationContext = local.axisLabelsCreationContext;
  815. if (process.env.NODE_ENV !== 'production') {
  816. assert(local.labelGroup && axisLabelsCreationContext);
  817. }
  818. var noPxChangeTryDetermine = axisLabelsCreationContext.out.noPxChangeTryDetermine;
  819. if (extraParams.noPxChange) {
  820. var canDetermine = true;
  821. for (var idx = 0; idx < noPxChangeTryDetermine.length; idx++) {
  822. canDetermine = canDetermine && noPxChangeTryDetermine[idx]();
  823. }
  824. if (canDetermine) {
  825. return false;
  826. }
  827. }
  828. if (noPxChangeTryDetermine.length) {
  829. // Remove the result of `buildAxisLabel`
  830. group.remove(local.labelGroup);
  831. axisLabelBuildResultSet(local, null, null, null);
  832. }
  833. }
  834. return true;
  835. }
  836. function buildAxisLabel(cfg, local, group, kind, axisModel, api) {
  837. var axis = axisModel.axis;
  838. var show = retrieve(cfg.raw.axisLabelShow, axisModel.get(['axisLabel', 'show']));
  839. var labelGroup = new graphic.Group();
  840. group.add(labelGroup);
  841. var axisLabelCreationCtx = createAxisLabelsComputingContext(kind);
  842. if (!show || axis.scale.isBlank()) {
  843. axisLabelBuildResultSet(local, [], labelGroup, axisLabelCreationCtx);
  844. return;
  845. }
  846. var labelModel = axisModel.getModel('axisLabel');
  847. var labels = axis.getViewLabels(axisLabelCreationCtx);
  848. // Special label rotate.
  849. var labelRotation = (retrieve(cfg.raw.labelRotate, labelModel.get('rotate')) || 0) * PI / 180;
  850. var labelLayout = AxisBuilder.innerTextLayout(cfg.rotation, labelRotation, cfg.labelDirection);
  851. var rawCategoryData = axisModel.getCategories && axisModel.getCategories(true);
  852. var labelEls = [];
  853. var triggerEvent = axisModel.get('triggerEvent');
  854. var z2Min = Infinity;
  855. var z2Max = -Infinity;
  856. each(labels, function (labelItem, index) {
  857. var _a;
  858. var tickValue = axis.scale.type === 'ordinal' ? axis.scale.getRawOrdinalNumber(labelItem.tickValue) : labelItem.tickValue;
  859. var formattedLabel = labelItem.formattedLabel;
  860. var rawLabel = labelItem.rawLabel;
  861. var itemLabelModel = labelModel;
  862. if (rawCategoryData && rawCategoryData[tickValue]) {
  863. var rawCategoryItem = rawCategoryData[tickValue];
  864. if (isObject(rawCategoryItem) && rawCategoryItem.textStyle) {
  865. itemLabelModel = new Model(rawCategoryItem.textStyle, labelModel, axisModel.ecModel);
  866. }
  867. }
  868. var textColor = itemLabelModel.getTextColor() || axisModel.get(['axisLine', 'lineStyle', 'color']);
  869. var align = itemLabelModel.getShallow('align', true) || labelLayout.textAlign;
  870. var alignMin = retrieve2(itemLabelModel.getShallow('alignMinLabel', true), align);
  871. var alignMax = retrieve2(itemLabelModel.getShallow('alignMaxLabel', true), align);
  872. var verticalAlign = itemLabelModel.getShallow('verticalAlign', true) || itemLabelModel.getShallow('baseline', true) || labelLayout.textVerticalAlign;
  873. var verticalAlignMin = retrieve2(itemLabelModel.getShallow('verticalAlignMinLabel', true), verticalAlign);
  874. var verticalAlignMax = retrieve2(itemLabelModel.getShallow('verticalAlignMaxLabel', true), verticalAlign);
  875. var z2 = 10 + (((_a = labelItem.time) === null || _a === void 0 ? void 0 : _a.level) || 0);
  876. z2Min = Math.min(z2Min, z2);
  877. z2Max = Math.max(z2Max, z2);
  878. var textEl = new graphic.Text({
  879. // --- transform props start ---
  880. // All of the transform props MUST not be set here, but should be set in
  881. // `updateAxisLabelChangableProps`, because they may change in estimation,
  882. // and need to calculate based on global coord sys by `decomposeTransform`.
  883. x: 0,
  884. y: 0,
  885. rotation: 0,
  886. // --- transform props end ---
  887. silent: AxisBuilder.isLabelSilent(axisModel),
  888. z2: z2,
  889. style: createTextStyle(itemLabelModel, {
  890. text: formattedLabel,
  891. align: index === 0 ? alignMin : index === labels.length - 1 ? alignMax : align,
  892. verticalAlign: index === 0 ? verticalAlignMin : index === labels.length - 1 ? verticalAlignMax : verticalAlign,
  893. fill: isFunction(textColor) ? textColor(
  894. // (1) In category axis with data zoom, tick is not the original
  895. // index of axis.data. So tick should not be exposed to user
  896. // in category axis.
  897. // (2) Compatible with previous version, which always use formatted label as
  898. // input. But in interval scale the formatted label is like '223,445', which
  899. // maked user replace ','. So we modify it to return original val but remain
  900. // it as 'string' to avoid error in replacing.
  901. axis.type === 'category' ? rawLabel : axis.type === 'value' ? tickValue + '' : tickValue, index) : textColor
  902. })
  903. });
  904. textEl.anid = 'label_' + tickValue;
  905. var inner = getLabelInner(textEl);
  906. inner["break"] = labelItem["break"];
  907. inner.tickValue = tickValue;
  908. inner.layoutRotation = labelLayout.rotation;
  909. graphic.setTooltipConfig({
  910. el: textEl,
  911. componentModel: axisModel,
  912. itemName: formattedLabel,
  913. formatterParamsExtra: {
  914. isTruncated: function () {
  915. return textEl.isTruncated;
  916. },
  917. value: rawLabel,
  918. tickIndex: index
  919. }
  920. });
  921. // Pack data for mouse event
  922. if (triggerEvent) {
  923. var eventData = AxisBuilder.makeAxisEventDataBase(axisModel);
  924. eventData.targetType = 'axisLabel';
  925. eventData.value = rawLabel;
  926. eventData.tickIndex = index;
  927. if (labelItem["break"]) {
  928. eventData["break"] = {
  929. // type: labelItem.break.type,
  930. start: labelItem["break"].parsedBreak.vmin,
  931. end: labelItem["break"].parsedBreak.vmax
  932. };
  933. }
  934. if (axis.type === 'category') {
  935. eventData.dataIndex = tickValue;
  936. }
  937. getECData(textEl).eventData = eventData;
  938. if (labelItem["break"]) {
  939. addBreakEventHandler(axisModel, api, textEl, labelItem["break"]);
  940. }
  941. }
  942. labelEls.push(textEl);
  943. labelGroup.add(textEl);
  944. });
  945. var labelLayoutList = map(labelEls, function (label) {
  946. return {
  947. label: label,
  948. priority: getLabelInner(label)["break"] ? label.z2 + (z2Max - z2Min + 1) // Make break labels be highest priority.
  949. : label.z2,
  950. defaultAttr: {
  951. ignore: label.ignore
  952. }
  953. };
  954. });
  955. axisLabelBuildResultSet(local, labelLayoutList, labelGroup, axisLabelCreationCtx);
  956. }
  957. // Indicate that `layOutAxisTickLabel` has been called.
  958. function axisLabelBuildResultExists(local) {
  959. return !!local.labelLayoutList;
  960. }
  961. function axisLabelBuildResultSet(local, labelLayoutList, labelGroup, axisLabelsCreationContext) {
  962. // Ensure the same lifetime.
  963. local.labelLayoutList = labelLayoutList;
  964. local.labelGroup = labelGroup;
  965. local.axisLabelsCreationContext = axisLabelsCreationContext;
  966. }
  967. function updateAxisLabelChangableProps(cfg, axisModel, labelLayoutList, transformGroup) {
  968. var labelMargin = axisModel.get(['axisLabel', 'margin']);
  969. each(labelLayoutList, function (layout, idx) {
  970. var geometry = ensureLabelLayoutWithGeometry(layout);
  971. if (!geometry) {
  972. return;
  973. }
  974. var labelEl = geometry.label;
  975. var inner = getLabelInner(labelEl);
  976. // See the comment in `suggestIgnore`.
  977. geometry.suggestIgnore = labelEl.ignore;
  978. // Currently no `ignore:true` is set in `buildAxisLabel`
  979. // But `ignore:true` may be set subsequently for overlap handling, thus reset it here.
  980. labelEl.ignore = false;
  981. copyTransform(_tmpLayoutEl, _tmpLayoutElReset);
  982. _tmpLayoutEl.x = axisModel.axis.dataToCoord(inner.tickValue);
  983. _tmpLayoutEl.y = cfg.labelOffset + cfg.labelDirection * labelMargin;
  984. _tmpLayoutEl.rotation = inner.layoutRotation;
  985. transformGroup.add(_tmpLayoutEl);
  986. _tmpLayoutEl.updateTransform();
  987. transformGroup.remove(_tmpLayoutEl);
  988. _tmpLayoutEl.decomposeTransform();
  989. copyTransform(labelEl, _tmpLayoutEl);
  990. labelEl.markRedraw();
  991. setLabelLayoutDirty(geometry, true);
  992. ensureLabelLayoutWithGeometry(geometry);
  993. });
  994. }
  995. var _tmpLayoutEl = new graphic.Rect();
  996. var _tmpLayoutElReset = new graphic.Rect();
  997. function hasAxisName(axisName) {
  998. return !!axisName;
  999. }
  1000. function addBreakEventHandler(axisModel, api, textEl, visualBreak) {
  1001. textEl.on('click', function (params) {
  1002. var payload = {
  1003. type: AXIS_BREAK_EXPAND_ACTION_TYPE,
  1004. breaks: [{
  1005. start: visualBreak.parsedBreak.breakOption.start,
  1006. end: visualBreak.parsedBreak.breakOption.end
  1007. }]
  1008. };
  1009. payload[axisModel.axis.dim + "AxisIndex"] = axisModel.componentIndex;
  1010. api.dispatchAction(payload);
  1011. });
  1012. }
  1013. function adjustBreakLabels(axisModel, axisRotation, labelLayoutList) {
  1014. var scaleBreakHelper = getScaleBreakHelper();
  1015. if (!scaleBreakHelper) {
  1016. return;
  1017. }
  1018. var breakLabelIndexPairs = scaleBreakHelper.retrieveAxisBreakPairs(labelLayoutList, function (layoutInfo) {
  1019. return layoutInfo && getLabelInner(layoutInfo.label)["break"];
  1020. }, true);
  1021. var moveOverlap = axisModel.get(['breakLabelLayout', 'moveOverlap'], true);
  1022. if (moveOverlap === true || moveOverlap === 'auto') {
  1023. each(breakLabelIndexPairs, function (idxPair) {
  1024. getAxisBreakHelper().adjustBreakLabelPair(axisModel.axis.inverse, axisRotation, [ensureLabelLayoutWithGeometry(labelLayoutList[idxPair[0]]), ensureLabelLayoutWithGeometry(labelLayoutList[idxPair[1]])]);
  1025. });
  1026. }
  1027. }
  1028. export default AxisBuilder;