| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import {retrieve, defaults, extend, each, isObject} from 'zrender/src/core/util'; |
| import * as graphic from '../../util/graphic'; |
| import {getECData} from '../../util/innerStore'; |
| import {createTextStyle} from '../../label/labelStyle'; |
| import Model from '../../model/Model'; |
| import {isRadianAroundZero, remRadian} from '../../util/number'; |
| import {createSymbol} from '../../util/symbol'; |
| import * as matrixUtil from 'zrender/src/core/matrix'; |
| import {applyTransform as v2ApplyTransform} from 'zrender/src/core/vector'; |
| import {shouldShowAllLabels} from '../../coord/axisHelper'; |
| import { AxisBaseModel } from '../../coord/AxisBaseModel'; |
| import { ZRTextVerticalAlign, ZRTextAlign, ECElement, ColorString } from '../../util/types'; |
| import { AxisBaseOption } from '../../coord/axisCommonTypes'; |
| import Element from 'zrender/src/Element'; |
| import { PathStyleProps } from 'zrender/src/graphic/Path'; |
| import OrdinalScale from '../../scale/Ordinal'; |
| |
| |
| const PI = Math.PI; |
| |
| type AxisIndexKey = 'xAxisIndex' | 'yAxisIndex' | 'radiusAxisIndex' |
| | 'angleAxisIndex' | 'singleAxisIndex'; |
| |
| type AxisEventData = { |
| componentType: string |
| componentIndex: number |
| targetType: 'axisName' | 'axisLabel' |
| name?: string |
| value?: string | number |
| } & { |
| [key in AxisIndexKey]?: number |
| }; |
| |
| type LabelFormatterParams = { |
| componentType: string |
| name: string |
| $vars: ['name'] |
| } & { |
| [key in AxisIndexKey]?: number |
| }; |
| |
| type AxisLabelText = graphic.Text & { |
| __fullText: string |
| __truncatedText: string |
| } & ECElement; |
| |
| export interface AxisBuilderCfg { |
| position?: number[] |
| rotation?: number |
| /** |
| * Used when nameLocation is 'middle' or 'center'. |
| * 1 | -1 |
| */ |
| nameDirection?: number |
| tickDirection?: number |
| labelDirection?: number |
| /** |
| * Usefull when onZero. |
| */ |
| labelOffset?: number |
| /** |
| * default get from axisModel. |
| */ |
| axisLabelShow?: boolean |
| /** |
| * default get from axisModel. |
| */ |
| axisName?: string |
| |
| axisNameAvailableWidth?: number |
| /** |
| * by degree, default get from axisModel. |
| */ |
| labelRotate?: number |
| |
| strokeContainThreshold?: number |
| |
| nameTruncateMaxWidth?: number |
| |
| silent?: boolean |
| |
| handleAutoShown?(elementType: 'axisLine' | 'axisTick'): boolean |
| } |
| |
| interface TickCoord { |
| coord: number |
| tickValue?: number |
| } |
| |
| /** |
| * A final axis is translated and rotated from a "standard axis". |
| * So opt.position and opt.rotation is required. |
| * |
| * A standard axis is and axis from [0, 0] to [0, axisExtent[1]], |
| * for example: (0, 0) ------------> (0, 50) |
| * |
| * nameDirection or tickDirection or labelDirection is 1 means tick |
| * or label is below the standard axis, whereas is -1 means above |
| * the standard axis. labelOffset means offset between label and axis, |
| * which is useful when 'onZero', where axisLabel is in the grid and |
| * label in outside grid. |
| * |
| * Tips: like always, |
| * positive rotation represents anticlockwise, and negative rotation |
| * represents clockwise. |
| * The direction of position coordinate is the same as the direction |
| * of screen coordinate. |
| * |
| * Do not need to consider axis 'inverse', which is auto processed by |
| * axis extent. |
| */ |
| class AxisBuilder { |
| |
| axisModel: AxisBaseModel; |
| |
| opt: AxisBuilderCfg; |
| |
| readonly group = new graphic.Group(); |
| |
| private _transformGroup: graphic.Group; |
| |
| constructor(axisModel: AxisBaseModel, opt?: AxisBuilderCfg) { |
| |
| this.opt = opt; |
| |
| this.axisModel = axisModel; |
| |
| // Default value |
| defaults( |
| opt, |
| { |
| labelOffset: 0, |
| nameDirection: 1, |
| tickDirection: 1, |
| labelDirection: 1, |
| silent: true, |
| handleAutoShown: () => true |
| } as AxisBuilderCfg |
| ); |
| |
| |
| // FIXME Not use a seperate text group? |
| const transformGroup = new graphic.Group({ |
| x: opt.position[0], |
| y: opt.position[1], |
| rotation: opt.rotation |
| }); |
| |
| // this.group.add(transformGroup); |
| // this._transformGroup = transformGroup; |
| |
| transformGroup.updateTransform(); |
| |
| this._transformGroup = transformGroup; |
| } |
| |
| hasBuilder(name: keyof typeof builders) { |
| return !!builders[name]; |
| } |
| |
| add(name: keyof typeof builders) { |
| builders[name](this.opt, this.axisModel, this.group, this._transformGroup); |
| } |
| |
| getGroup() { |
| return this.group; |
| } |
| |
| static innerTextLayout(axisRotation: number, textRotation: number, direction: number) { |
| const rotationDiff = remRadian(textRotation - axisRotation); |
| let textAlign; |
| let textVerticalAlign; |
| |
| if (isRadianAroundZero(rotationDiff)) { // Label is parallel with axis line. |
| textVerticalAlign = direction > 0 ? 'top' : 'bottom'; |
| textAlign = 'center'; |
| } |
| else if (isRadianAroundZero(rotationDiff - PI)) { // Label is inverse parallel with axis line. |
| textVerticalAlign = direction > 0 ? 'bottom' : 'top'; |
| textAlign = 'center'; |
| } |
| else { |
| textVerticalAlign = 'middle'; |
| |
| if (rotationDiff > 0 && rotationDiff < PI) { |
| textAlign = direction > 0 ? 'right' : 'left'; |
| } |
| else { |
| textAlign = direction > 0 ? 'left' : 'right'; |
| } |
| } |
| |
| return { |
| rotation: rotationDiff, |
| textAlign: textAlign as ZRTextAlign, |
| textVerticalAlign: textVerticalAlign as ZRTextVerticalAlign |
| }; |
| } |
| |
| static makeAxisEventDataBase(axisModel: AxisBaseModel) { |
| const eventData = { |
| componentType: axisModel.mainType, |
| componentIndex: axisModel.componentIndex |
| } as AxisEventData; |
| eventData[axisModel.mainType + 'Index' as AxisIndexKey] = axisModel.componentIndex; |
| return eventData; |
| } |
| |
| static isLabelSilent(axisModel: AxisBaseModel): boolean { |
| const tooltipOpt = axisModel.get('tooltip'); |
| return axisModel.get('silent') |
| // Consider mouse cursor, add these restrictions. |
| || !( |
| axisModel.get('triggerEvent') || (tooltipOpt && tooltipOpt.show) |
| ); |
| } |
| }; |
| |
| interface AxisElementsBuilder { |
| ( |
| opt: AxisBuilderCfg, |
| axisModel: AxisBaseModel, |
| group: graphic.Group, |
| transformGroup: graphic.Group |
| ):void |
| } |
| |
| const builders: Record<'axisLine' | 'axisTickLabel' | 'axisName', AxisElementsBuilder> = { |
| |
| axisLine(opt, axisModel, group, transformGroup) { |
| |
| let shown = axisModel.get(['axisLine', 'show']); |
| if (shown === 'auto' && opt.handleAutoShown) { |
| shown = opt.handleAutoShown('axisLine'); |
| } |
| if (!shown) { |
| return; |
| } |
| |
| const extent = axisModel.axis.getExtent(); |
| |
| const matrix = transformGroup.transform; |
| const pt1 = [extent[0], 0]; |
| const pt2 = [extent[1], 0]; |
| if (matrix) { |
| v2ApplyTransform(pt1, pt1, matrix); |
| v2ApplyTransform(pt2, pt2, matrix); |
| } |
| |
| const lineStyle = extend( |
| { |
| lineCap: 'round' |
| }, |
| axisModel.getModel(['axisLine', 'lineStyle']).getLineStyle() |
| ); |
| |
| const line = new graphic.Line({ |
| // Id for animation |
| subPixelOptimize: true, |
| shape: { |
| x1: pt1[0], |
| y1: pt1[1], |
| x2: pt2[0], |
| y2: pt2[1] |
| }, |
| style: lineStyle, |
| strokeContainThreshold: opt.strokeContainThreshold || 5, |
| silent: true, |
| z2: 1 |
| }); |
| line.anid = 'line'; |
| group.add(line); |
| |
| let arrows = axisModel.get(['axisLine', 'symbol']); |
| let arrowSize = axisModel.get(['axisLine', 'symbolSize']); |
| |
| let arrowOffset = axisModel.get(['axisLine', 'symbolOffset']) || 0; |
| if (typeof arrowOffset === 'number') { |
| arrowOffset = [arrowOffset, arrowOffset]; |
| } |
| |
| if (arrows != null) { |
| if (typeof arrows === 'string') { |
| // Use the same arrow for start and end point |
| arrows = [arrows, arrows]; |
| } |
| if (typeof arrowSize === 'string' |
| || typeof arrowSize === 'number' |
| ) { |
| // Use the same size for width and height |
| arrowSize = [arrowSize, arrowSize]; |
| } |
| |
| const symbolWidth = arrowSize[0]; |
| const symbolHeight = arrowSize[1]; |
| |
| each([{ |
| rotate: opt.rotation + Math.PI / 2, |
| offset: arrowOffset[0], |
| r: 0 |
| }, { |
| rotate: opt.rotation - Math.PI / 2, |
| offset: arrowOffset[1], |
| r: Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) |
| + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) |
| }], function (point, index) { |
| if (arrows[index] !== 'none' && arrows[index] != null) { |
| const symbol = createSymbol( |
| arrows[index], |
| -symbolWidth / 2, |
| -symbolHeight / 2, |
| symbolWidth, |
| symbolHeight, |
| lineStyle.stroke, |
| true |
| ); |
| |
| // Calculate arrow position with offset |
| const r = point.r + point.offset; |
| |
| symbol.attr({ |
| rotation: point.rotate, |
| x: pt1[0] + r * Math.cos(opt.rotation), |
| y: pt1[1] - r * Math.sin(opt.rotation), |
| silent: true, |
| z2: 11 |
| }); |
| group.add(symbol); |
| } |
| }); |
| } |
| }, |
| |
| axisTickLabel(opt, axisModel, group, transformGroup) { |
| |
| const ticksEls = buildAxisMajorTicks(group, transformGroup, axisModel, opt); |
| const labelEls = buildAxisLabel(group, transformGroup, axisModel, opt); |
| |
| fixMinMaxLabelShow(axisModel, labelEls, ticksEls); |
| |
| buildAxisMinorTicks(group, transformGroup, axisModel, opt.tickDirection); |
| }, |
| |
| axisName(opt, axisModel, group, transformGroup) { |
| const name = retrieve(opt.axisName, axisModel.get('name')); |
| |
| if (!name) { |
| return; |
| } |
| |
| const nameLocation = axisModel.get('nameLocation'); |
| const nameDirection = opt.nameDirection; |
| const textStyleModel = axisModel.getModel('nameTextStyle'); |
| const gap = axisModel.get('nameGap') || 0; |
| |
| const extent = axisModel.axis.getExtent(); |
| const gapSignal = extent[0] > extent[1] ? -1 : 1; |
| const pos = [ |
| nameLocation === 'start' |
| ? extent[0] - gapSignal * gap |
| : nameLocation === 'end' |
| ? extent[1] + gapSignal * gap |
| : (extent[0] + extent[1]) / 2, // 'middle' |
| // Reuse labelOffset. |
| isNameLocationCenter(nameLocation) ? opt.labelOffset + nameDirection * gap : 0 |
| ]; |
| |
| let labelLayout; |
| |
| let nameRotation = axisModel.get('nameRotate'); |
| if (nameRotation != null) { |
| nameRotation = nameRotation * PI / 180; // To radian. |
| } |
| |
| let axisNameAvailableWidth; |
| |
| if (isNameLocationCenter(nameLocation)) { |
| labelLayout = AxisBuilder.innerTextLayout( |
| opt.rotation, |
| nameRotation != null ? nameRotation : opt.rotation, // Adapt to axis. |
| nameDirection |
| ); |
| } |
| else { |
| labelLayout = endTextLayout( |
| opt.rotation, nameLocation, nameRotation || 0, extent |
| ); |
| |
| axisNameAvailableWidth = opt.axisNameAvailableWidth; |
| if (axisNameAvailableWidth != null) { |
| axisNameAvailableWidth = Math.abs( |
| axisNameAvailableWidth / Math.sin(labelLayout.rotation) |
| ); |
| !isFinite(axisNameAvailableWidth) && (axisNameAvailableWidth = null); |
| } |
| } |
| |
| const textFont = textStyleModel.getFont(); |
| |
| const truncateOpt = axisModel.get('nameTruncate', true) || {}; |
| const ellipsis = truncateOpt.ellipsis; |
| const maxWidth = retrieve( |
| opt.nameTruncateMaxWidth, truncateOpt.maxWidth, axisNameAvailableWidth |
| ); |
| |
| const tooltipOpt = axisModel.get('tooltip', true); |
| |
| const mainType = axisModel.mainType; |
| const formatterParams: LabelFormatterParams = { |
| componentType: mainType, |
| name: name, |
| $vars: ['name'] |
| }; |
| formatterParams[mainType + 'Index' as AxisIndexKey] = axisModel.componentIndex; |
| |
| const textEl = new graphic.Text({ |
| x: pos[0], |
| y: pos[1], |
| rotation: labelLayout.rotation, |
| silent: AxisBuilder.isLabelSilent(axisModel), |
| style: createTextStyle(textStyleModel, { |
| text: name, |
| font: textFont, |
| overflow: 'truncate', |
| width: maxWidth, |
| ellipsis, |
| fill: textStyleModel.getTextColor() |
| || axisModel.get(['axisLine', 'lineStyle', 'color']) as ColorString, |
| align: textStyleModel.get('align') |
| || labelLayout.textAlign, |
| verticalAlign: textStyleModel.get('verticalAlign') |
| || labelLayout.textVerticalAlign |
| }), |
| z2: 1 |
| }) as AxisLabelText; |
| textEl.tooltip = (tooltipOpt && tooltipOpt.show) |
| ? extend({ |
| content: name, |
| formatter() { |
| return name; |
| }, |
| formatterParams: formatterParams |
| }, tooltipOpt) |
| : null; |
| textEl.__fullText = name; |
| // Id for animation |
| textEl.anid = 'name'; |
| |
| if (axisModel.get('triggerEvent')) { |
| const eventData = AxisBuilder.makeAxisEventDataBase(axisModel); |
| eventData.targetType = 'axisName'; |
| eventData.name = name; |
| getECData(textEl).eventData = eventData; |
| } |
| |
| // FIXME |
| transformGroup.add(textEl); |
| textEl.updateTransform(); |
| |
| group.add(textEl); |
| |
| textEl.decomposeTransform(); |
| } |
| |
| }; |
| |
| function endTextLayout( |
| rotation: number, textPosition: 'start' | 'middle' | 'end', textRotate: number, extent: number[] |
| ) { |
| const rotationDiff = remRadian(textRotate - rotation); |
| let textAlign: ZRTextAlign; |
| let textVerticalAlign: ZRTextVerticalAlign; |
| const inverse = extent[0] > extent[1]; |
| const onLeft = (textPosition === 'start' && !inverse) |
| || (textPosition !== 'start' && inverse); |
| |
| if (isRadianAroundZero(rotationDiff - PI / 2)) { |
| textVerticalAlign = onLeft ? 'bottom' : 'top'; |
| textAlign = 'center'; |
| } |
| else if (isRadianAroundZero(rotationDiff - PI * 1.5)) { |
| textVerticalAlign = onLeft ? 'top' : 'bottom'; |
| textAlign = 'center'; |
| } |
| else { |
| textVerticalAlign = 'middle'; |
| if (rotationDiff < PI * 1.5 && rotationDiff > PI / 2) { |
| textAlign = onLeft ? 'left' : 'right'; |
| } |
| else { |
| textAlign = onLeft ? 'right' : 'left'; |
| } |
| } |
| |
| return { |
| rotation: rotationDiff, |
| textAlign: textAlign, |
| textVerticalAlign: textVerticalAlign |
| }; |
| } |
| |
| function fixMinMaxLabelShow( |
| axisModel: AxisBaseModel, |
| labelEls: graphic.Text[], |
| tickEls: graphic.Line[] |
| ) { |
| if (shouldShowAllLabels(axisModel.axis)) { |
| return; |
| } |
| |
| // If min or max are user set, we need to check |
| // If the tick on min(max) are overlap on their neighbour tick |
| // If they are overlapped, we need to hide the min(max) tick label |
| const showMinLabel = axisModel.get(['axisLabel', 'showMinLabel']); |
| const showMaxLabel = axisModel.get(['axisLabel', 'showMaxLabel']); |
| |
| // FIXME |
| // Have not consider onBand yet, where tick els is more than label els. |
| |
| labelEls = labelEls || []; |
| tickEls = tickEls || []; |
| |
| const firstLabel = labelEls[0]; |
| const nextLabel = labelEls[1]; |
| const lastLabel = labelEls[labelEls.length - 1]; |
| const prevLabel = labelEls[labelEls.length - 2]; |
| |
| const firstTick = tickEls[0]; |
| const nextTick = tickEls[1]; |
| const lastTick = tickEls[tickEls.length - 1]; |
| const prevTick = tickEls[tickEls.length - 2]; |
| |
| if (showMinLabel === false) { |
| ignoreEl(firstLabel); |
| ignoreEl(firstTick); |
| } |
| else if (isTwoLabelOverlapped(firstLabel, nextLabel)) { |
| if (showMinLabel) { |
| ignoreEl(nextLabel); |
| ignoreEl(nextTick); |
| } |
| else { |
| ignoreEl(firstLabel); |
| ignoreEl(firstTick); |
| } |
| } |
| |
| if (showMaxLabel === false) { |
| ignoreEl(lastLabel); |
| ignoreEl(lastTick); |
| } |
| else if (isTwoLabelOverlapped(prevLabel, lastLabel)) { |
| if (showMaxLabel) { |
| ignoreEl(prevLabel); |
| ignoreEl(prevTick); |
| } |
| else { |
| ignoreEl(lastLabel); |
| ignoreEl(lastTick); |
| } |
| } |
| } |
| |
| function ignoreEl(el: Element) { |
| el && (el.ignore = true); |
| } |
| |
| function isTwoLabelOverlapped( |
| current: graphic.Text, |
| next: graphic.Text |
| ) { |
| // current and next has the same rotation. |
| const firstRect = current && current.getBoundingRect().clone(); |
| const nextRect = next && next.getBoundingRect().clone(); |
| |
| if (!firstRect || !nextRect) { |
| return; |
| } |
| |
| // When checking intersect of two rotated labels, we use mRotationBack |
| // to avoid that boundingRect is enlarge when using `boundingRect.applyTransform`. |
| const mRotationBack = matrixUtil.identity([]); |
| matrixUtil.rotate(mRotationBack, mRotationBack, -current.rotation); |
| |
| firstRect.applyTransform(matrixUtil.mul([], mRotationBack, current.getLocalTransform())); |
| nextRect.applyTransform(matrixUtil.mul([], mRotationBack, next.getLocalTransform())); |
| |
| return firstRect.intersect(nextRect); |
| } |
| |
| function isNameLocationCenter(nameLocation: string) { |
| return nameLocation === 'middle' || nameLocation === 'center'; |
| } |
| |
| |
| function createTicks( |
| ticksCoords: TickCoord[], |
| tickTransform: matrixUtil.MatrixArray, |
| tickEndCoord: number, |
| tickLineStyle: PathStyleProps, |
| anidPrefix: string |
| ) { |
| const tickEls = []; |
| const pt1: number[] = []; |
| const pt2: number[] = []; |
| for (let i = 0; i < ticksCoords.length; i++) { |
| const tickCoord = ticksCoords[i].coord; |
| |
| pt1[0] = tickCoord; |
| pt1[1] = 0; |
| pt2[0] = tickCoord; |
| pt2[1] = tickEndCoord; |
| |
| if (tickTransform) { |
| v2ApplyTransform(pt1, pt1, tickTransform); |
| v2ApplyTransform(pt2, pt2, tickTransform); |
| } |
| // Tick line, Not use group transform to have better line draw |
| const tickEl = new graphic.Line({ |
| subPixelOptimize: true, |
| shape: { |
| x1: pt1[0], |
| y1: pt1[1], |
| x2: pt2[0], |
| y2: pt2[1] |
| }, |
| style: tickLineStyle, |
| z2: 2, |
| autoBatch: true, |
| silent: true |
| }); |
| tickEl.anid = anidPrefix + '_' + ticksCoords[i].tickValue; |
| tickEls.push(tickEl); |
| } |
| return tickEls; |
| } |
| |
| function buildAxisMajorTicks( |
| group: graphic.Group, |
| transformGroup: graphic.Group, |
| axisModel: AxisBaseModel, |
| opt: AxisBuilderCfg |
| ) { |
| const axis = axisModel.axis; |
| |
| const tickModel = axisModel.getModel('axisTick'); |
| |
| let shown = tickModel.get('show'); |
| if (shown === 'auto' && opt.handleAutoShown) { |
| shown = opt.handleAutoShown('axisTick'); |
| } |
| if (!shown || axis.scale.isBlank()) { |
| return; |
| } |
| |
| const lineStyleModel = tickModel.getModel('lineStyle'); |
| const tickEndCoord = opt.tickDirection * tickModel.get('length'); |
| |
| const ticksCoords = axis.getTicksCoords(); |
| |
| const ticksEls = createTicks(ticksCoords, transformGroup.transform, tickEndCoord, defaults( |
| lineStyleModel.getLineStyle(), |
| { |
| stroke: axisModel.get(['axisLine', 'lineStyle', 'color']) |
| } |
| ), 'ticks'); |
| |
| for (let i = 0; i < ticksEls.length; i++) { |
| group.add(ticksEls[i]); |
| } |
| |
| return ticksEls; |
| } |
| |
| function buildAxisMinorTicks( |
| group: graphic.Group, |
| transformGroup: graphic.Group, |
| axisModel: AxisBaseModel, |
| tickDirection: number |
| ) { |
| const axis = axisModel.axis; |
| |
| const minorTickModel = axisModel.getModel('minorTick'); |
| |
| if (!minorTickModel.get('show') || axis.scale.isBlank()) { |
| return; |
| } |
| |
| const minorTicksCoords = axis.getMinorTicksCoords(); |
| if (!minorTicksCoords.length) { |
| return; |
| } |
| |
| const lineStyleModel = minorTickModel.getModel('lineStyle'); |
| const tickEndCoord = tickDirection * minorTickModel.get('length'); |
| |
| const minorTickLineStyle = defaults( |
| lineStyleModel.getLineStyle(), |
| defaults( |
| axisModel.getModel('axisTick').getLineStyle(), |
| { |
| stroke: axisModel.get(['axisLine', 'lineStyle', 'color']) |
| } |
| ) |
| ); |
| |
| for (let i = 0; i < minorTicksCoords.length; i++) { |
| const minorTicksEls = createTicks( |
| minorTicksCoords[i], transformGroup.transform, tickEndCoord, minorTickLineStyle, 'minorticks_' + i |
| ); |
| for (let k = 0; k < minorTicksEls.length; k++) { |
| group.add(minorTicksEls[k]); |
| } |
| } |
| } |
| |
| function buildAxisLabel( |
| group: graphic.Group, |
| transformGroup: graphic.Group, |
| axisModel: AxisBaseModel, |
| opt: AxisBuilderCfg |
| ) { |
| const axis = axisModel.axis; |
| const show = retrieve(opt.axisLabelShow, axisModel.get(['axisLabel', 'show'])); |
| |
| if (!show || axis.scale.isBlank()) { |
| return; |
| } |
| |
| const labelModel = axisModel.getModel('axisLabel'); |
| const labelMargin = labelModel.get('margin'); |
| const labels = axis.getViewLabels(); |
| |
| // Special label rotate. |
| const labelRotation = ( |
| retrieve(opt.labelRotate, labelModel.get('rotate')) || 0 |
| ) * PI / 180; |
| |
| const labelLayout = AxisBuilder.innerTextLayout(opt.rotation, labelRotation, opt.labelDirection); |
| const rawCategoryData = axisModel.getCategories && axisModel.getCategories(true); |
| |
| const labelEls: graphic.Text[] = []; |
| const silent = AxisBuilder.isLabelSilent(axisModel); |
| const triggerEvent = axisModel.get('triggerEvent'); |
| |
| each(labels, function (labelItem, index) { |
| const tickValue = axis.scale.type === 'ordinal' |
| ? (axis.scale as OrdinalScale).getRawIndex(labelItem.tickValue) |
| : labelItem.tickValue; |
| const formattedLabel = labelItem.formattedLabel; |
| const rawLabel = labelItem.rawLabel; |
| |
| let itemLabelModel = labelModel; |
| if (rawCategoryData && rawCategoryData[tickValue]) { |
| const rawCategoryItem = rawCategoryData[tickValue]; |
| if (isObject(rawCategoryItem) && rawCategoryItem.textStyle) { |
| itemLabelModel = new Model( |
| rawCategoryItem.textStyle, labelModel, axisModel.ecModel |
| ); |
| } |
| } |
| |
| const textColor = itemLabelModel.getTextColor() as AxisBaseOption['axisLabel']['color'] |
| || axisModel.get(['axisLine', 'lineStyle', 'color']); |
| |
| const tickCoord = axis.dataToCoord(tickValue); |
| |
| const textEl = new graphic.Text({ |
| x: tickCoord, |
| y: opt.labelOffset + opt.labelDirection * labelMargin, |
| rotation: labelLayout.rotation, |
| silent: silent, |
| z2: 10, |
| style: createTextStyle(itemLabelModel, { |
| text: formattedLabel, |
| align: itemLabelModel.getShallow('align', true) |
| || labelLayout.textAlign, |
| verticalAlign: itemLabelModel.getShallow('verticalAlign', true) |
| || itemLabelModel.getShallow('baseline', true) |
| || labelLayout.textVerticalAlign, |
| fill: typeof textColor === 'function' |
| ? textColor( |
| // (1) In category axis with data zoom, tick is not the original |
| // index of axis.data. So tick should not be exposed to user |
| // in category axis. |
| // (2) Compatible with previous version, which always use formatted label as |
| // input. But in interval scale the formatted label is like '223,445', which |
| // maked user repalce ','. So we modify it to return original val but remain |
| // it as 'string' to avoid error in replacing. |
| axis.type === 'category' |
| ? rawLabel |
| : axis.type === 'value' |
| ? tickValue + '' |
| : tickValue, |
| index |
| ) |
| : textColor as string |
| }) |
| }); |
| textEl.anid = 'label_' + tickValue; |
| |
| |
| // Pack data for mouse event |
| if (triggerEvent) { |
| const eventData = AxisBuilder.makeAxisEventDataBase(axisModel); |
| eventData.targetType = 'axisLabel'; |
| eventData.value = rawLabel; |
| |
| getECData(textEl).eventData = eventData; |
| } |
| |
| // FIXME |
| transformGroup.add(textEl); |
| textEl.updateTransform(); |
| |
| labelEls.push(textEl); |
| group.add(textEl); |
| |
| textEl.decomposeTransform(); |
| |
| }); |
| |
| return labelEls; |
| } |
| |
| |
| export default AxisBuilder; |