|  | /* | 
|  | * 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 ZRText, { TextProps, TextStyleProps } from 'zrender/src/graphic/Text'; | 
|  | import { Dictionary } from 'zrender/src/core/types'; | 
|  | import Element, { ElementTextConfig } from 'zrender/src/Element'; | 
|  | import Model from '../model/Model'; | 
|  | import { | 
|  | LabelOption, | 
|  | DisplayState, | 
|  | TextCommonOption, | 
|  | StatesOptionMixin, | 
|  | DisplayStateNonNormal, | 
|  | ColorString, | 
|  | ZRStyleProps, | 
|  | AnimationOptionMixin, | 
|  | InterpolatableValue | 
|  | } from '../util/types'; | 
|  | import GlobalModel from '../model/Global'; | 
|  | import { isFunction, retrieve2, extend, keys, trim } from 'zrender/src/core/util'; | 
|  | import { SPECIAL_STATES, DISPLAY_STATES } from '../util/states'; | 
|  | import { deprecateReplaceLog } from '../util/log'; | 
|  | import { makeInner, interpolateRawValues } from '../util/model'; | 
|  | import SeriesData from '../data/SeriesData'; | 
|  | import { initProps, updateProps } from '../util/graphic'; | 
|  |  | 
|  | type TextCommonParams = { | 
|  | /** | 
|  | * Whether disable drawing box of block (outer most). | 
|  | */ | 
|  | disableBox?: boolean | 
|  | /** | 
|  | * Specify a color when color is 'inherit', | 
|  | * If inheritColor specified, it is used as default textFill. | 
|  | */ | 
|  | inheritColor?: ColorString | 
|  |  | 
|  | /** | 
|  | * Specify a opacity when opacity is not given. | 
|  | */ | 
|  | defaultOpacity?: number | 
|  |  | 
|  | defaultOutsidePosition?: LabelOption['position'] | 
|  |  | 
|  | /** | 
|  | * If support legacy 'auto' for 'inherit' usage. | 
|  | */ | 
|  | // supportLegacyAuto?: boolean | 
|  |  | 
|  | textStyle?: ZRStyleProps | 
|  | }; | 
|  | const EMPTY_OBJ = {}; | 
|  |  | 
|  | interface SetLabelStyleOpt<TLabelDataIndex> extends TextCommonParams { | 
|  | defaultText?: string | (( | 
|  | labelDataIndex: TLabelDataIndex, | 
|  | opt: SetLabelStyleOpt<TLabelDataIndex>, | 
|  | interpolatedValue?: InterpolatableValue | 
|  | ) => string); | 
|  | // Fetch text by: | 
|  | // opt.labelFetcher.getFormattedLabel( | 
|  | //     opt.labelDataIndex, 'normal'/'emphasis', null, opt.labelDimIndex, opt.labelProp | 
|  | // ) | 
|  | labelFetcher?: { | 
|  | getFormattedLabel: ( | 
|  | // In MapDraw case it can be string (region name) | 
|  | labelDataIndex: TLabelDataIndex, | 
|  | status: DisplayState, | 
|  | dataType?: string, | 
|  | labelDimIndex?: number, | 
|  | formatter?: string | ((params: object) => string), | 
|  | // If provided, the implementation of `getFormattedLabel` can use it | 
|  | // to generate the final label text. | 
|  | extendParams?: { | 
|  | interpolatedValue: InterpolatableValue | 
|  | } | 
|  | ) => string; | 
|  | }; | 
|  | labelDataIndex?: TLabelDataIndex; | 
|  | labelDimIndex?: number; | 
|  |  | 
|  | /** | 
|  | * Inject a setter of text for the text animation case. | 
|  | */ | 
|  | enableTextSetter?: boolean | 
|  | } | 
|  | type LabelModel = Model<LabelOption & { | 
|  | formatter?: string | ((params: any) => string); | 
|  | showDuringLabel?: boolean // Currently only supported by line charts | 
|  | }>; | 
|  | type LabelModelForText = Model<Omit< | 
|  | // Remove | 
|  | LabelOption, 'position' | 'rotate'> & { | 
|  | formatter?: string | ((params: any) => string); | 
|  | }>; | 
|  |  | 
|  | type LabelStatesModels<LabelModel> = Partial<Record<DisplayStateNonNormal, LabelModel>> & {normal: LabelModel}; | 
|  |  | 
|  | export function setLabelText(label: ZRText, labelTexts: Record<DisplayState, string>) { | 
|  | for (let i = 0; i < SPECIAL_STATES.length; i++) { | 
|  | const stateName = SPECIAL_STATES[i]; | 
|  | const text = labelTexts[stateName]; | 
|  | const state = label.ensureState(stateName); | 
|  | state.style = state.style || {}; | 
|  | state.style.text = text; | 
|  | } | 
|  |  | 
|  | const oldStates = label.currentStates.slice(); | 
|  | label.clearStates(true); | 
|  | label.setStyle({ text: labelTexts.normal }); | 
|  | label.useStates(oldStates, true); | 
|  | } | 
|  |  | 
|  | function getLabelText<TLabelDataIndex>( | 
|  | opt: SetLabelStyleOpt<TLabelDataIndex>, | 
|  | stateModels: LabelStatesModels<LabelModel>, | 
|  | interpolatedValue?: InterpolatableValue | 
|  | ): Record<DisplayState, string> { | 
|  | const labelFetcher = opt.labelFetcher; | 
|  | const labelDataIndex = opt.labelDataIndex; | 
|  | const labelDimIndex = opt.labelDimIndex; | 
|  | const normalModel = stateModels.normal; | 
|  | let baseText; | 
|  | if (labelFetcher) { | 
|  | baseText = labelFetcher.getFormattedLabel( | 
|  | labelDataIndex, 'normal', | 
|  | null, | 
|  | labelDimIndex, | 
|  | normalModel && normalModel.get('formatter'), | 
|  | interpolatedValue != null ? { | 
|  | interpolatedValue: interpolatedValue | 
|  | } : null | 
|  | ); | 
|  | } | 
|  | if (baseText == null) { | 
|  | baseText = isFunction(opt.defaultText) | 
|  | ? opt.defaultText(labelDataIndex, opt, interpolatedValue) | 
|  | : opt.defaultText; | 
|  | } | 
|  |  | 
|  | const statesText = { | 
|  | normal: baseText | 
|  | } as Record<DisplayState, string>; | 
|  |  | 
|  | for (let i = 0; i < SPECIAL_STATES.length; i++) { | 
|  | const stateName = SPECIAL_STATES[i]; | 
|  | const stateModel = stateModels[stateName]; | 
|  | statesText[stateName] = retrieve2(labelFetcher | 
|  | ? labelFetcher.getFormattedLabel( | 
|  | labelDataIndex, | 
|  | stateName, | 
|  | null, | 
|  | labelDimIndex, | 
|  | stateModel && stateModel.get('formatter') | 
|  | ) | 
|  | : null, baseText); | 
|  | } | 
|  | return statesText; | 
|  | } | 
|  | /** | 
|  | * Set normal styles and emphasis styles about text on target element | 
|  | * If target is a ZRText. It will create a new style object. | 
|  | * If target is other Element. It will create or reuse ZRText which is attached on the target. | 
|  | * And create a new style object. | 
|  | * | 
|  | * NOTICE: Because the style on ZRText will be replaced with new(only x, y are keeped). | 
|  | * So please update the style on ZRText after use this method. | 
|  | */ | 
|  | // eslint-disable-next-line | 
|  | function setLabelStyle<TLabelDataIndex>( | 
|  | targetEl: ZRText, | 
|  | labelStatesModels: LabelStatesModels<LabelModelForText>, | 
|  | opt?: SetLabelStyleOpt<TLabelDataIndex>, | 
|  | stateSpecified?: Partial<Record<DisplayState, TextStyleProps>> | 
|  | ): void; | 
|  | // eslint-disable-next-line | 
|  | function setLabelStyle<TLabelDataIndex>( | 
|  | targetEl: Element, | 
|  | labelStatesModels: LabelStatesModels<LabelModel>, | 
|  | opt?: SetLabelStyleOpt<TLabelDataIndex>, | 
|  | stateSpecified?: Partial<Record<DisplayState, TextStyleProps>> | 
|  | ): void; | 
|  | function setLabelStyle<TLabelDataIndex>( | 
|  | targetEl: Element, | 
|  | labelStatesModels: LabelStatesModels<LabelModel>, | 
|  | opt?: SetLabelStyleOpt<TLabelDataIndex>, | 
|  | stateSpecified?: Partial<Record<DisplayState, TextStyleProps>> | 
|  | // TODO specified position? | 
|  | ) { | 
|  | opt = opt || EMPTY_OBJ; | 
|  | const isSetOnText = targetEl instanceof ZRText; | 
|  | let needsCreateText = false; | 
|  | for (let i = 0; i < DISPLAY_STATES.length; i++) { | 
|  | const stateModel = labelStatesModels[DISPLAY_STATES[i]]; | 
|  | if (stateModel && stateModel.getShallow('show')) { | 
|  | needsCreateText = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | let textContent = isSetOnText ? targetEl as ZRText : targetEl.getTextContent(); | 
|  | if (needsCreateText) { | 
|  | if (!isSetOnText) { | 
|  | // Reuse the previous | 
|  | if (!textContent) { | 
|  | textContent = new ZRText(); | 
|  | targetEl.setTextContent(textContent); | 
|  | } | 
|  | // Use same state proxy | 
|  | if (targetEl.stateProxy) { | 
|  | textContent.stateProxy = targetEl.stateProxy; | 
|  | } | 
|  | } | 
|  | const labelStatesTexts = getLabelText(opt, labelStatesModels); | 
|  |  | 
|  | const normalModel = labelStatesModels.normal; | 
|  | const showNormal = !!normalModel.getShallow('show'); | 
|  | const normalStyle = createTextStyle( | 
|  | normalModel, stateSpecified && stateSpecified.normal, opt, false, !isSetOnText | 
|  | ); | 
|  | normalStyle.text = labelStatesTexts.normal; | 
|  | if (!isSetOnText) { | 
|  | // Always create new | 
|  | targetEl.setTextConfig(createTextConfig(normalModel, opt, false)); | 
|  | } | 
|  |  | 
|  | for (let i = 0; i < SPECIAL_STATES.length; i++) { | 
|  | const stateName = SPECIAL_STATES[i]; | 
|  | const stateModel = labelStatesModels[stateName]; | 
|  |  | 
|  | if (stateModel) { | 
|  | const stateObj = textContent.ensureState(stateName); | 
|  | const stateShow = !!retrieve2(stateModel.getShallow('show'), showNormal); | 
|  | if (stateShow !== showNormal) { | 
|  | stateObj.ignore = !stateShow; | 
|  | } | 
|  | stateObj.style = createTextStyle( | 
|  | stateModel, stateSpecified && stateSpecified[stateName], opt, true, !isSetOnText | 
|  | ); | 
|  | stateObj.style.text = labelStatesTexts[stateName]; | 
|  |  | 
|  | if (!isSetOnText) { | 
|  | const targetElEmphasisState = targetEl.ensureState(stateName); | 
|  | targetElEmphasisState.textConfig = createTextConfig(stateModel, opt, true); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // PENDING: if there is many requirements that emphasis position | 
|  | // need to be different from normal position, we might consider | 
|  | // auto silent is those cases. | 
|  | textContent.silent = !!normalModel.getShallow('silent'); | 
|  | // Keep x and y | 
|  | if (textContent.style.x != null) { | 
|  | normalStyle.x = textContent.style.x; | 
|  | } | 
|  | if (textContent.style.y != null) { | 
|  | normalStyle.y = textContent.style.y; | 
|  | } | 
|  | textContent.ignore = !showNormal; | 
|  | // Always create new style. | 
|  | textContent.useStyle(normalStyle); | 
|  | textContent.dirty(); | 
|  |  | 
|  | if (opt.enableTextSetter) { | 
|  | labelInner(textContent).setLabelText = function (interpolatedValue: InterpolatableValue) { | 
|  | const labelStatesTexts = getLabelText(opt, labelStatesModels, interpolatedValue); | 
|  | setLabelText(textContent, labelStatesTexts); | 
|  | }; | 
|  | } | 
|  | } | 
|  | else if (textContent) { | 
|  | // Not display rich text. | 
|  | textContent.ignore = true; | 
|  | } | 
|  | targetEl.dirty(); | 
|  | } | 
|  | export { setLabelStyle }; | 
|  |  | 
|  | export function getLabelStatesModels<LabelName extends string = 'label'>( | 
|  | itemModel: Model<StatesOptionMixin<any, any> & Partial<Record<LabelName, any>>>, | 
|  | labelName?: LabelName | 
|  | ): Record<DisplayState, LabelModel> { | 
|  | labelName = (labelName || 'label') as LabelName; | 
|  | const statesModels = { | 
|  | normal: itemModel.getModel(labelName) as LabelModel | 
|  | } as Record<DisplayState, LabelModel>; | 
|  | for (let i = 0; i < SPECIAL_STATES.length; i++) { | 
|  | const stateName = SPECIAL_STATES[i]; | 
|  | statesModels[stateName] = itemModel.getModel([stateName, labelName]); | 
|  | } | 
|  | return statesModels; | 
|  | } | 
|  | /** | 
|  | * Set basic textStyle properties. | 
|  | */ | 
|  | export function createTextStyle( | 
|  | textStyleModel: Model, | 
|  | specifiedTextStyle?: TextStyleProps, // Fixed style in the code. Can't be set by model. | 
|  | opt?: Pick<TextCommonParams, 'inheritColor' | 'disableBox'>, | 
|  | isNotNormal?: boolean, | 
|  | isAttached?: boolean // If text is attached on an element. If so, auto color will handling in zrender. | 
|  | ) { | 
|  | const textStyle: TextStyleProps = {}; | 
|  | setTextStyleCommon(textStyle, textStyleModel, opt, isNotNormal, isAttached); | 
|  | specifiedTextStyle && extend(textStyle, specifiedTextStyle); | 
|  | // textStyle.host && textStyle.host.dirty && textStyle.host.dirty(false); | 
|  | return textStyle; | 
|  | } | 
|  | export function createTextConfig( | 
|  | textStyleModel: Model, | 
|  | opt?: Pick<TextCommonParams, 'defaultOutsidePosition' | 'inheritColor'>, | 
|  | isNotNormal?: boolean | 
|  | ) { | 
|  | opt = opt || {}; | 
|  | const textConfig: ElementTextConfig = {}; | 
|  | let labelPosition; | 
|  | let labelRotate = textStyleModel.getShallow('rotate'); | 
|  | const labelDistance = retrieve2(textStyleModel.getShallow('distance'), isNotNormal ? null : 5); | 
|  | const labelOffset = textStyleModel.getShallow('offset'); | 
|  | labelPosition = textStyleModel.getShallow('position') | 
|  | || (isNotNormal ? null : 'inside'); | 
|  | // 'outside' is not a valid zr textPostion value, but used | 
|  | // in bar series, and magric type should be considered. | 
|  | labelPosition === 'outside' && (labelPosition = opt.defaultOutsidePosition || 'top'); | 
|  | if (labelPosition != null) { | 
|  | textConfig.position = labelPosition; | 
|  | } | 
|  | if (labelOffset != null) { | 
|  | textConfig.offset = labelOffset; | 
|  | } | 
|  | if (labelRotate != null) { | 
|  | labelRotate *= Math.PI / 180; | 
|  | textConfig.rotation = labelRotate; | 
|  | } | 
|  | if (labelDistance != null) { | 
|  | textConfig.distance = labelDistance; | 
|  | } | 
|  | // fill and auto is determined by the color of path fill if it's not specified by developers. | 
|  | textConfig.outsideFill = textStyleModel.get('color') === 'inherit' | 
|  | ? (opt.inheritColor || null) | 
|  | : 'auto'; | 
|  | return textConfig; | 
|  | } | 
|  | /** | 
|  | * The uniform entry of set text style, that is, retrieve style definitions | 
|  | * from `model` and set to `textStyle` object. | 
|  | * | 
|  | * Never in merge mode, but in overwrite mode, that is, all of the text style | 
|  | * properties will be set. (Consider the states of normal and emphasis and | 
|  | * default value can be adopted, merge would make the logic too complicated | 
|  | * to manage.) | 
|  | */ | 
|  | function setTextStyleCommon( | 
|  | textStyle: TextStyleProps, | 
|  | textStyleModel: Model, | 
|  | opt?: Pick<TextCommonParams, 'inheritColor' | 'defaultOpacity' | 'disableBox'>, | 
|  | isNotNormal?: boolean, | 
|  | isAttached?: boolean | 
|  | ) { | 
|  | // Consider there will be abnormal when merge hover style to normal style if given default value. | 
|  | opt = opt || EMPTY_OBJ; | 
|  | const ecModel = textStyleModel.ecModel; | 
|  | const globalTextStyle = ecModel && ecModel.option.textStyle; | 
|  | // Consider case: | 
|  | // { | 
|  | //     data: [{ | 
|  | //         value: 12, | 
|  | //         label: { | 
|  | //             rich: { | 
|  | //                 // no 'a' here but using parent 'a'. | 
|  | //             } | 
|  | //         } | 
|  | //     }], | 
|  | //     rich: { | 
|  | //         a: { ... } | 
|  | //     } | 
|  | // } | 
|  | const richItemNames = getRichItemNames(textStyleModel); | 
|  | let richResult: TextStyleProps['rich']; | 
|  | if (richItemNames) { | 
|  | richResult = {}; | 
|  | for (const name in richItemNames) { | 
|  | if (richItemNames.hasOwnProperty(name)) { | 
|  | // Cascade is supported in rich. | 
|  | const richTextStyle = textStyleModel.getModel(['rich', name]); | 
|  | // In rich, never `disableBox`. | 
|  | // FIXME: consider `label: {formatter: '{a|xx}', color: 'blue', rich: {a: {}}}`, | 
|  | // the default color `'blue'` will not be adopted if no color declared in `rich`. | 
|  | // That might confuses users. So probably we should put `textStyleModel` as the | 
|  | // root ancestor of the `richTextStyle`. But that would be a break change. | 
|  | setTokenTextStyle( | 
|  | richResult[name] = {}, richTextStyle, globalTextStyle, opt, isNotNormal, isAttached, false, true | 
|  | ); | 
|  | } | 
|  | } | 
|  | } | 
|  | if (richResult) { | 
|  | textStyle.rich = richResult; | 
|  | } | 
|  | const overflow = textStyleModel.get('overflow'); | 
|  | if (overflow) { | 
|  | textStyle.overflow = overflow; | 
|  | } | 
|  | const margin = textStyleModel.get('minMargin'); | 
|  | if (margin != null) { | 
|  | textStyle.margin = margin; | 
|  | } | 
|  | setTokenTextStyle(textStyle, textStyleModel, globalTextStyle, opt, isNotNormal, isAttached, true, false); | 
|  | } | 
|  | // Consider case: | 
|  | // { | 
|  | //     data: [{ | 
|  | //         value: 12, | 
|  | //         label: { | 
|  | //             rich: { | 
|  | //                 // no 'a' here but using parent 'a'. | 
|  | //             } | 
|  | //         } | 
|  | //     }], | 
|  | //     rich: { | 
|  | //         a: { ... } | 
|  | //     } | 
|  | // } | 
|  | // TODO TextStyleModel | 
|  | function getRichItemNames(textStyleModel: Model<LabelOption>) { | 
|  | // Use object to remove duplicated names. | 
|  | let richItemNameMap: Dictionary<number>; | 
|  | while (textStyleModel && textStyleModel !== textStyleModel.ecModel) { | 
|  | const rich = (textStyleModel.option || EMPTY_OBJ as LabelOption).rich; | 
|  | if (rich) { | 
|  | richItemNameMap = richItemNameMap || {}; | 
|  | const richKeys = keys(rich); | 
|  | for (let i = 0; i < richKeys.length; i++) { | 
|  | const richKey = richKeys[i]; | 
|  | richItemNameMap[richKey] = 1; | 
|  | } | 
|  | } | 
|  | textStyleModel = textStyleModel.parentModel; | 
|  | } | 
|  | return richItemNameMap; | 
|  | } | 
|  | const TEXT_PROPS_WITH_GLOBAL = [ | 
|  | 'fontStyle', 'fontWeight', 'fontSize', 'fontFamily', | 
|  | 'textShadowColor', 'textShadowBlur', 'textShadowOffsetX', 'textShadowOffsetY' | 
|  | ] as const; | 
|  | const TEXT_PROPS_SELF = [ | 
|  | 'align', 'lineHeight', 'width', 'height', 'tag', 'verticalAlign' | 
|  | ] as const; | 
|  | const TEXT_PROPS_BOX = [ | 
|  | 'padding', 'borderWidth', 'borderRadius', 'borderDashOffset', | 
|  | 'backgroundColor', 'borderColor', | 
|  | 'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY' | 
|  | ] as const; | 
|  |  | 
|  | function setTokenTextStyle( | 
|  | textStyle: TextStyleProps['rich'][string], | 
|  | textStyleModel: Model<LabelOption>, | 
|  | globalTextStyle: LabelOption, | 
|  | opt?: Pick<TextCommonParams, 'inheritColor' | 'defaultOpacity' | 'disableBox'>, | 
|  | isNotNormal?: boolean, | 
|  | isAttached?: boolean, | 
|  | isBlock?: boolean, | 
|  | inRich?: boolean | 
|  | ) { | 
|  | // In merge mode, default value should not be given. | 
|  | globalTextStyle = !isNotNormal && globalTextStyle || EMPTY_OBJ; | 
|  | const inheritColor = opt && opt.inheritColor; | 
|  | let fillColor = textStyleModel.getShallow('color'); | 
|  | let strokeColor = textStyleModel.getShallow('textBorderColor'); | 
|  | let opacity = retrieve2(textStyleModel.getShallow('opacity'), globalTextStyle.opacity); | 
|  | if (fillColor === 'inherit' || fillColor === 'auto') { | 
|  | if (__DEV__) { | 
|  | if (fillColor === 'auto') { | 
|  | deprecateReplaceLog('color: \'auto\'', 'color: \'inherit\''); | 
|  | } | 
|  | } | 
|  | if (inheritColor) { | 
|  | fillColor = inheritColor; | 
|  | } | 
|  | else { | 
|  | fillColor = null; | 
|  | } | 
|  | } | 
|  | if (strokeColor === 'inherit' || (strokeColor === 'auto')) { | 
|  | if (__DEV__) { | 
|  | if (strokeColor === 'auto') { | 
|  | deprecateReplaceLog('color: \'auto\'', 'color: \'inherit\''); | 
|  | } | 
|  | } | 
|  | if (inheritColor) { | 
|  | strokeColor = inheritColor; | 
|  | } | 
|  | else { | 
|  | strokeColor = null; | 
|  | } | 
|  | } | 
|  | if (!isAttached) { | 
|  | // Only use default global textStyle.color if text is individual. | 
|  | // Otherwise it will use the strategy of attached text color because text may be on a path. | 
|  | fillColor = fillColor || globalTextStyle.color; | 
|  | strokeColor = strokeColor || globalTextStyle.textBorderColor; | 
|  | } | 
|  | if (fillColor != null) { | 
|  | textStyle.fill = fillColor; | 
|  | } | 
|  | if (strokeColor != null) { | 
|  | textStyle.stroke = strokeColor; | 
|  | } | 
|  | const textBorderWidth = retrieve2(textStyleModel.getShallow('textBorderWidth'), globalTextStyle.textBorderWidth); | 
|  | if (textBorderWidth != null) { | 
|  | textStyle.lineWidth = textBorderWidth; | 
|  | } | 
|  | const textBorderType = retrieve2(textStyleModel.getShallow('textBorderType'), globalTextStyle.textBorderType); | 
|  | if (textBorderType != null) { | 
|  | textStyle.lineDash = textBorderType as any; | 
|  | } | 
|  | const textBorderDashOffset = retrieve2( | 
|  | textStyleModel.getShallow('textBorderDashOffset'), globalTextStyle.textBorderDashOffset | 
|  | ); | 
|  | if (textBorderDashOffset != null) { | 
|  | textStyle.lineDashOffset = textBorderDashOffset; | 
|  | } | 
|  |  | 
|  | if (!isNotNormal && (opacity == null) && !inRich) { | 
|  | opacity = opt && opt.defaultOpacity; | 
|  | } | 
|  | if (opacity != null) { | 
|  | textStyle.opacity = opacity; | 
|  | } | 
|  |  | 
|  | // TODO | 
|  | if (!isNotNormal && !isAttached) { | 
|  | // Set default finally. | 
|  | if (textStyle.fill == null && opt.inheritColor) { | 
|  | textStyle.fill = opt.inheritColor; | 
|  | } | 
|  | } | 
|  | // Do not use `getFont` here, because merge should be supported, where | 
|  | // part of these properties may be changed in emphasis style, and the | 
|  | // others should remain their original value got from normal style. | 
|  | for (let i = 0; i < TEXT_PROPS_WITH_GLOBAL.length; i++) { | 
|  | const key = TEXT_PROPS_WITH_GLOBAL[i]; | 
|  | const val = retrieve2(textStyleModel.getShallow(key), globalTextStyle[key]); | 
|  | if (val != null) { | 
|  | (textStyle as any)[key] = val; | 
|  | } | 
|  | } | 
|  | for (let i = 0; i < TEXT_PROPS_SELF.length; i++) { | 
|  | const key = TEXT_PROPS_SELF[i]; | 
|  | const val = textStyleModel.getShallow(key); | 
|  | if (val != null) { | 
|  | (textStyle as any)[key] = val; | 
|  | } | 
|  | } | 
|  | if (textStyle.verticalAlign == null) { | 
|  | const baseline = textStyleModel.getShallow('baseline'); | 
|  | if (baseline != null) { | 
|  | textStyle.verticalAlign = baseline; | 
|  | } | 
|  | } | 
|  | if (!isBlock || !opt.disableBox) { | 
|  | for (let i = 0; i < TEXT_PROPS_BOX.length; i++) { | 
|  | const key = TEXT_PROPS_BOX[i]; | 
|  | const val = textStyleModel.getShallow(key); | 
|  | if (val != null) { | 
|  | (textStyle as any)[key] = val; | 
|  | } | 
|  | } | 
|  |  | 
|  | const borderType = textStyleModel.getShallow('borderType'); | 
|  | if (borderType != null) { | 
|  | textStyle.borderDash = borderType as any; | 
|  | } | 
|  |  | 
|  | if ((textStyle.backgroundColor === 'auto' || textStyle.backgroundColor === 'inherit') && inheritColor) { | 
|  | if (__DEV__) { | 
|  | if (textStyle.backgroundColor === 'auto') { | 
|  | deprecateReplaceLog('backgroundColor: \'auto\'', 'backgroundColor: \'inherit\''); | 
|  | } | 
|  | } | 
|  | textStyle.backgroundColor = inheritColor; | 
|  | } | 
|  | if ((textStyle.borderColor === 'auto' || textStyle.borderColor === 'inherit') && inheritColor) { | 
|  | if (__DEV__) { | 
|  | if (textStyle.borderColor === 'auto') { | 
|  | deprecateReplaceLog('borderColor: \'auto\'', 'borderColor: \'inherit\''); | 
|  | } | 
|  | } | 
|  | textStyle.borderColor = inheritColor; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | export function getFont( | 
|  | opt: Pick<TextCommonOption, 'fontStyle' | 'fontWeight' | 'fontSize' | 'fontFamily'>, | 
|  | ecModel: GlobalModel | 
|  | ) { | 
|  | const gTextStyleModel = ecModel && ecModel.getModel('textStyle'); | 
|  | return trim([ | 
|  | // FIXME in node-canvas fontWeight is before fontStyle | 
|  | opt.fontStyle || gTextStyleModel && gTextStyleModel.getShallow('fontStyle') || '', | 
|  | opt.fontWeight || gTextStyleModel && gTextStyleModel.getShallow('fontWeight') || '', | 
|  | (opt.fontSize || gTextStyleModel && gTextStyleModel.getShallow('fontSize') || 12) + 'px', | 
|  | opt.fontFamily || gTextStyleModel && gTextStyleModel.getShallow('fontFamily') || 'sans-serif' | 
|  | ].join(' ')); | 
|  | } | 
|  |  | 
|  | export const labelInner = makeInner<{ | 
|  | /** | 
|  | * Previous target value stored used for label. | 
|  | * It's mainly for text animation | 
|  | */ | 
|  | prevValue?: InterpolatableValue | 
|  | /** | 
|  | * Target value stored used for label. | 
|  | */ | 
|  | value?: InterpolatableValue | 
|  | /** | 
|  | * Current value in text animation. | 
|  | */ | 
|  | interpolatedValue?: InterpolatableValue | 
|  | /** | 
|  | * If enable value animation | 
|  | */ | 
|  | valueAnimation?: boolean | 
|  | /** | 
|  | * Label value precision during animation. | 
|  | */ | 
|  | precision?: number | 'auto' | 
|  |  | 
|  | /** | 
|  | * If enable value animation | 
|  | */ | 
|  | statesModels?: LabelStatesModels<LabelModelForText> | 
|  | /** | 
|  | * Default text getter during interpolation | 
|  | */ | 
|  | defaultInterpolatedText?: (value: InterpolatableValue) => string | 
|  | /** | 
|  | * Change label text from interpolated text during animation | 
|  | */ | 
|  | setLabelText?: (interpolatedValue?: InterpolatableValue) => void | 
|  |  | 
|  | }, ZRText>(); | 
|  |  | 
|  | export function setLabelValueAnimation( | 
|  | label: ZRText, | 
|  | labelStatesModels: LabelStatesModels<LabelModelForText>, | 
|  | value: InterpolatableValue, | 
|  | getDefaultText: (value: InterpolatableValue) => string | 
|  | ) { | 
|  | if (!label) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const obj = labelInner(label); | 
|  | obj.prevValue = obj.value; | 
|  | obj.value = value; | 
|  | const normalLabelModel = labelStatesModels.normal; | 
|  |  | 
|  | obj.valueAnimation = normalLabelModel.get('valueAnimation'); | 
|  |  | 
|  | if (obj.valueAnimation) { | 
|  | obj.precision = normalLabelModel.get('precision'); | 
|  | obj.defaultInterpolatedText = getDefaultText; | 
|  | obj.statesModels = labelStatesModels; | 
|  | } | 
|  | } | 
|  |  | 
|  | export function animateLabelValue( | 
|  | textEl: ZRText, | 
|  | dataIndex: number, | 
|  | data: SeriesData, | 
|  | animatableModel: Model<AnimationOptionMixin>, | 
|  | labelFetcher: SetLabelStyleOpt<number>['labelFetcher'] | 
|  | ) { | 
|  | const labelInnerStore = labelInner(textEl); | 
|  | if (!labelInnerStore.valueAnimation || labelInnerStore.prevValue === labelInnerStore.value) { | 
|  | // Value not changed, no new label animation | 
|  | return; | 
|  | } | 
|  |  | 
|  | const defaultInterpolatedText = labelInnerStore.defaultInterpolatedText; | 
|  | // Consider the case that being animating, do not use the `obj.value`, | 
|  | // Otherwise it will jump to the `obj.value` when this new animation started. | 
|  | const currValue = retrieve2(labelInnerStore.interpolatedValue, labelInnerStore.prevValue); | 
|  | const targetValue = labelInnerStore.value; | 
|  |  | 
|  | function during(percent: number) { | 
|  | const interpolated = interpolateRawValues( | 
|  | data, | 
|  | labelInnerStore.precision, | 
|  | currValue, | 
|  | targetValue, | 
|  | percent | 
|  | ); | 
|  |  | 
|  | labelInnerStore.interpolatedValue = percent === 1 ? null : interpolated; | 
|  |  | 
|  | const labelText = getLabelText({ | 
|  | labelDataIndex: dataIndex, | 
|  | labelFetcher: labelFetcher, | 
|  | defaultText: defaultInterpolatedText | 
|  | ? defaultInterpolatedText(interpolated) | 
|  | : interpolated + '' | 
|  | }, labelInnerStore.statesModels, interpolated); | 
|  |  | 
|  | setLabelText(textEl, labelText); | 
|  | } | 
|  |  | 
|  | (textEl as ZRText & {percent?: number}).percent = 0; | 
|  | (labelInnerStore.prevValue == null | 
|  | ? initProps | 
|  | : updateProps | 
|  | )<TextProps & {percent?: number}>(textEl, { | 
|  | // percent is used to prevent animation from being aborted #15916 | 
|  | percent: 1 | 
|  | }, animatableModel, dataIndex, null, during); | 
|  | } |