| /* | 
 | * 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 PointerPath from './PointerPath'; | 
 | import * as graphic from '../../util/graphic'; | 
 | import { setStatesStylesFromModel, toggleHoverEmphasis } from '../../util/states'; | 
 | import {createTextStyle, setLabelValueAnimation, animateLabelValue} from '../../label/labelStyle'; | 
 | import ChartView from '../../view/Chart'; | 
 | import {parsePercent, round, linearMap} from '../../util/number'; | 
 | import GaugeSeriesModel, { GaugeDataItemOption } from './GaugeSeries'; | 
 | import GlobalModel from '../../model/Global'; | 
 | import ExtensionAPI from '../../core/ExtensionAPI'; | 
 | import { ColorString, ECElement } from '../../util/types'; | 
 | import SeriesData from '../../data/SeriesData'; | 
 | import Sausage from '../../util/shape/sausage'; | 
 | import {createSymbol} from '../../util/symbol'; | 
 | import ZRImage from 'zrender/src/graphic/Image'; | 
 | import { extend, isFunction, isString, isNumber, each } from 'zrender/src/core/util'; | 
 | import {setCommonECData} from '../../util/innerStore'; | 
 | import { normalizeArcAngles } from 'zrender/src/core/PathProxy'; | 
 |  | 
 | type ECSymbol = ReturnType<typeof createSymbol>; | 
 |  | 
 | interface PosInfo { | 
 |     cx: number | 
 |     cy: number | 
 |     r: number | 
 | } | 
 |  | 
 | function parsePosition(seriesModel: GaugeSeriesModel, api: ExtensionAPI): PosInfo { | 
 |     const center = seriesModel.get('center'); | 
 |     const width = api.getWidth(); | 
 |     const height = api.getHeight(); | 
 |     const size = Math.min(width, height); | 
 |     const cx = parsePercent(center[0], api.getWidth()); | 
 |     const cy = parsePercent(center[1], api.getHeight()); | 
 |     const r = parsePercent(seriesModel.get('radius'), size / 2); | 
 |  | 
 |     return { | 
 |         cx: cx, | 
 |         cy: cy, | 
 |         r: r | 
 |     }; | 
 | } | 
 |  | 
 | function formatLabel(value: number, labelFormatter: string | ((value: number) => string)): string { | 
 |     let label = value == null ? '' : (value + ''); | 
 |     if (labelFormatter) { | 
 |         if (isString(labelFormatter)) { | 
 |             label = labelFormatter.replace('{value}', label); | 
 |         } | 
 |         else if (isFunction(labelFormatter)) { | 
 |             label = labelFormatter(value); | 
 |         } | 
 |     } | 
 |  | 
 |     return label; | 
 | } | 
 |  | 
 | class GaugeView extends ChartView { | 
 |     static type = 'gauge' as const; | 
 |     type = GaugeView.type; | 
 |  | 
 |     private _data: SeriesData; | 
 |     private _progressEls: graphic.Path[]; | 
 |  | 
 |     private _titleEls: graphic.Text[]; | 
 |     private _detailEls: graphic.Text[]; | 
 |  | 
 |     render(seriesModel: GaugeSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { | 
 |  | 
 |         this.group.removeAll(); | 
 |  | 
 |         const colorList = seriesModel.get(['axisLine', 'lineStyle', 'color']); | 
 |         const posInfo = parsePosition(seriesModel, api); | 
 |  | 
 |         this._renderMain( | 
 |             seriesModel, ecModel, api, colorList, posInfo | 
 |         ); | 
 |  | 
 |         this._data = seriesModel.getData(); | 
 |     } | 
 |  | 
 |     dispose() {} | 
 |  | 
 |     _renderMain( | 
 |         seriesModel: GaugeSeriesModel, | 
 |         ecModel: GlobalModel, | 
 |         api: ExtensionAPI, | 
 |         colorList: [number, ColorString][], | 
 |         posInfo: PosInfo | 
 |     ) { | 
 |         const group = this.group; | 
 |         const clockwise = seriesModel.get('clockwise'); | 
 |         let startAngle = -seriesModel.get('startAngle') / 180 * Math.PI; | 
 |         let endAngle = -seriesModel.get('endAngle') / 180 * Math.PI; | 
 |         const axisLineModel = seriesModel.getModel('axisLine'); | 
 |  | 
 |         const roundCap = axisLineModel.get('roundCap'); | 
 |         const MainPath = roundCap ? Sausage : graphic.Sector; | 
 |  | 
 |         const showAxis = axisLineModel.get('show'); | 
 |         const lineStyleModel = axisLineModel.getModel('lineStyle'); | 
 |         const axisLineWidth = lineStyleModel.get('width'); | 
 |  | 
 |         const angles = [startAngle, endAngle]; | 
 |         normalizeArcAngles(angles, !clockwise); | 
 |         startAngle = angles[0]; | 
 |         endAngle = angles[1]; | 
 |         const angleRangeSpan = endAngle - startAngle; | 
 |  | 
 |         let prevEndAngle = startAngle; | 
 |  | 
 |         const sectors: (Sausage | graphic.Sector)[] = []; | 
 |         for (let i = 0; showAxis && i < colorList.length; i++) { | 
 |             // Clamp | 
 |             const percent = Math.min(Math.max(colorList[i][0], 0), 1); | 
 |             endAngle = startAngle + angleRangeSpan * percent; | 
 |             const sector = new MainPath({ | 
 |                 shape: { | 
 |                     startAngle: prevEndAngle, | 
 |                     endAngle: endAngle, | 
 |                     cx: posInfo.cx, | 
 |                     cy: posInfo.cy, | 
 |                     clockwise: clockwise, | 
 |                     r0: posInfo.r - axisLineWidth, | 
 |                     r: posInfo.r | 
 |                 }, | 
 |                 silent: true | 
 |             }); | 
 |  | 
 |             sector.setStyle({ | 
 |                 fill: colorList[i][1] | 
 |             }); | 
 |  | 
 |             sector.setStyle(lineStyleModel.getLineStyle( | 
 |                 // Because we use sector to simulate arc | 
 |                 // so the properties for stroking are useless | 
 |                 ['color', 'width'] | 
 |             )); | 
 |  | 
 |             sectors.push(sector); | 
 |  | 
 |             prevEndAngle = endAngle; | 
 |         } | 
 |  | 
 |         sectors.reverse(); | 
 |         each(sectors, sector => group.add(sector)); | 
 |  | 
 |         const getColor = function (percent: number) { | 
 |             // Less than 0 | 
 |             if (percent <= 0) { | 
 |                 return colorList[0][1]; | 
 |             } | 
 |             let i; | 
 |             for (i = 0; i < colorList.length; i++) { | 
 |                 if (colorList[i][0] >= percent | 
 |                     && (i === 0 ? 0 : colorList[i - 1][0]) < percent | 
 |                 ) { | 
 |                     return colorList[i][1]; | 
 |                 } | 
 |             } | 
 |             // More than 1 | 
 |             return colorList[i - 1][1]; | 
 |         }; | 
 |  | 
 |         this._renderTicks( | 
 |             seriesModel, ecModel, api, getColor, posInfo, | 
 |             startAngle, endAngle, clockwise, axisLineWidth | 
 |         ); | 
 |  | 
 |         this._renderTitleAndDetail( | 
 |             seriesModel, ecModel, api, getColor, posInfo | 
 |         ); | 
 |  | 
 |         this._renderAnchor(seriesModel, posInfo); | 
 |  | 
 |         this._renderPointer( | 
 |             seriesModel, ecModel, api, getColor, posInfo, | 
 |             startAngle, endAngle, clockwise, axisLineWidth | 
 |         ); | 
 |     } | 
 |  | 
 |     _renderTicks( | 
 |         seriesModel: GaugeSeriesModel, | 
 |         ecModel: GlobalModel, | 
 |         api: ExtensionAPI, | 
 |         getColor: (percent: number) => ColorString, | 
 |         posInfo: PosInfo, | 
 |         startAngle: number, | 
 |         endAngle: number, | 
 |         clockwise: boolean, | 
 |         axisLineWidth: number | 
 |     ) { | 
 |         const group = this.group; | 
 |         const cx = posInfo.cx; | 
 |         const cy = posInfo.cy; | 
 |         const r = posInfo.r; | 
 |  | 
 |         const minVal = +seriesModel.get('min'); | 
 |         const maxVal = +seriesModel.get('max'); | 
 |  | 
 |         const splitLineModel = seriesModel.getModel('splitLine'); | 
 |         const tickModel = seriesModel.getModel('axisTick'); | 
 |         const labelModel = seriesModel.getModel('axisLabel'); | 
 |  | 
 |         const splitNumber = seriesModel.get('splitNumber'); | 
 |         const subSplitNumber = tickModel.get('splitNumber'); | 
 |  | 
 |         const splitLineLen = parsePercent( | 
 |             splitLineModel.get('length'), r | 
 |         ); | 
 |         const tickLen = parsePercent( | 
 |             tickModel.get('length'), r | 
 |         ); | 
 |  | 
 |         let angle = startAngle; | 
 |         const step = (endAngle - startAngle) / splitNumber; | 
 |         const subStep = step / subSplitNumber; | 
 |  | 
 |         const splitLineStyle = splitLineModel.getModel('lineStyle').getLineStyle(); | 
 |         const tickLineStyle = tickModel.getModel('lineStyle').getLineStyle(); | 
 |  | 
 |         const splitLineDistance = splitLineModel.get('distance'); | 
 |  | 
 |         let unitX; | 
 |         let unitY; | 
 |  | 
 |         for (let i = 0; i <= splitNumber; i++) { | 
 |             unitX = Math.cos(angle); | 
 |             unitY = Math.sin(angle); | 
 |             // Split line | 
 |             if (splitLineModel.get('show')) { | 
 |                 const distance = splitLineDistance ? splitLineDistance + axisLineWidth : axisLineWidth; | 
 |                 const splitLine = new graphic.Line({ | 
 |                     shape: { | 
 |                         x1: unitX * (r - distance) + cx, | 
 |                         y1: unitY * (r - distance) + cy, | 
 |                         x2: unitX * (r - splitLineLen - distance) + cx, | 
 |                         y2: unitY * (r - splitLineLen - distance) + cy | 
 |                     }, | 
 |                     style: splitLineStyle, | 
 |                     silent: true | 
 |                 }); | 
 |                 if (splitLineStyle.stroke === 'auto') { | 
 |                     splitLine.setStyle({ | 
 |                         stroke: getColor(i / splitNumber) | 
 |                     }); | 
 |                 } | 
 |  | 
 |                 group.add(splitLine); | 
 |             } | 
 |  | 
 |             // Label | 
 |             if (labelModel.get('show')) { | 
 |                 const distance = labelModel.get('distance') + splitLineDistance; | 
 |  | 
 |                 const label = formatLabel( | 
 |                     round(i / splitNumber * (maxVal - minVal) + minVal), | 
 |                     labelModel.get('formatter') | 
 |                 ); | 
 |                 const autoColor = getColor(i / splitNumber); | 
 |                 const textStyleX = unitX * (r - splitLineLen - distance) + cx; | 
 |                 const textStyleY = unitY * (r - splitLineLen - distance) + cy; | 
 |  | 
 |                 const rotateType = labelModel.get('rotate'); | 
 |                 let rotate = 0; | 
 |                 if (rotateType === 'radial') { | 
 |                     rotate = -angle + 2 * Math.PI; | 
 |                     if (rotate > Math.PI / 2) { | 
 |                         rotate += Math.PI; | 
 |                     } | 
 |                 } | 
 |                 else if (rotateType === 'tangential') { | 
 |                     rotate = -angle - Math.PI / 2; | 
 |                 } | 
 |                 else if (isNumber(rotateType)) { | 
 |                     rotate = rotateType * Math.PI / 180; | 
 |                 } | 
 |  | 
 |                 if (rotate === 0) { | 
 |                     group.add(new graphic.Text({ | 
 |                         style: createTextStyle(labelModel, { | 
 |                             text: label, | 
 |                             x: textStyleX, | 
 |                             y: textStyleY, | 
 |                             verticalAlign: unitY < -0.8 ? 'top' : (unitY > 0.8 ? 'bottom' : 'middle'), | 
 |                             align: unitX < -0.4 ? 'left' : (unitX > 0.4 ? 'right' : 'center') | 
 |                         }, { | 
 |                             inheritColor: autoColor | 
 |                         }), | 
 |                         silent: true | 
 |                     })); | 
 |                 } | 
 |                 else { | 
 |                     group.add(new graphic.Text({ | 
 |                         style: createTextStyle(labelModel, { | 
 |                             text: label, | 
 |                             x: textStyleX, | 
 |                             y: textStyleY, | 
 |                             verticalAlign: 'middle', | 
 |                             align: 'center' | 
 |                         }, { | 
 |                             inheritColor: autoColor | 
 |                         }), | 
 |                         silent: true, | 
 |                         originX: textStyleX, | 
 |                         originY: textStyleY, | 
 |                         rotation: rotate | 
 |                     })); | 
 |                 } | 
 |             } | 
 |  | 
 |             // Axis tick | 
 |             if (tickModel.get('show') && i !== splitNumber) { | 
 |                 let distance = tickModel.get('distance'); | 
 |                 distance = distance ? distance + axisLineWidth : axisLineWidth; | 
 |  | 
 |                 for (let j = 0; j <= subSplitNumber; j++) { | 
 |                     unitX = Math.cos(angle); | 
 |                     unitY = Math.sin(angle); | 
 |                     const tickLine = new graphic.Line({ | 
 |                         shape: { | 
 |                             x1: unitX * (r - distance) + cx, | 
 |                             y1: unitY * (r - distance) + cy, | 
 |                             x2: unitX * (r - tickLen - distance) + cx, | 
 |                             y2: unitY * (r - tickLen - distance) + cy | 
 |                         }, | 
 |                         silent: true, | 
 |                         style: tickLineStyle | 
 |                     }); | 
 |  | 
 |                     if (tickLineStyle.stroke === 'auto') { | 
 |                         tickLine.setStyle({ | 
 |                             stroke: getColor((i + j / subSplitNumber) / splitNumber) | 
 |                         }); | 
 |                     } | 
 |  | 
 |                     group.add(tickLine); | 
 |                     angle += subStep; | 
 |                 } | 
 |                 angle -= subStep; | 
 |             } | 
 |             else { | 
 |                 angle += step; | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     _renderPointer( | 
 |         seriesModel: GaugeSeriesModel, | 
 |         ecModel: GlobalModel, | 
 |         api: ExtensionAPI, | 
 |         getColor: (percent: number) => ColorString, | 
 |         posInfo: PosInfo, | 
 |         startAngle: number, | 
 |         endAngle: number, | 
 |         clockwise: boolean, | 
 |         axisLineWidth: number | 
 |     ) { | 
 |  | 
 |         const group = this.group; | 
 |         const oldData = this._data; | 
 |         const oldProgressData = this._progressEls; | 
 |         const progressList = [] as graphic.Path[]; | 
 |  | 
 |         const showPointer = seriesModel.get(['pointer', 'show']); | 
 |         const progressModel = seriesModel.getModel('progress'); | 
 |         const showProgress = progressModel.get('show'); | 
 |  | 
 |         const data = seriesModel.getData(); | 
 |         const valueDim = data.mapDimension('value'); | 
 |         const minVal = +seriesModel.get('min'); | 
 |         const maxVal = +seriesModel.get('max'); | 
 |         const valueExtent = [minVal, maxVal]; | 
 |         const angleExtent = [startAngle, endAngle]; | 
 |  | 
 |         function createPointer(idx: number, angle: number) { | 
 |             const itemModel = data.getItemModel<GaugeDataItemOption>(idx); | 
 |             const pointerModel = itemModel.getModel('pointer'); | 
 |             const pointerWidth = parsePercent(pointerModel.get('width'), posInfo.r); | 
 |             const pointerLength = parsePercent(pointerModel.get('length'), posInfo.r); | 
 |             const pointerStr = seriesModel.get(['pointer', 'icon']); | 
 |             const pointerOffset = pointerModel.get('offsetCenter'); | 
 |             const pointerOffsetX = parsePercent(pointerOffset[0], posInfo.r); | 
 |             const pointerOffsetY = parsePercent(pointerOffset[1], posInfo.r); | 
 |             const pointerKeepAspect = pointerModel.get('keepAspect'); | 
 |  | 
 |             let pointer; | 
 |             // not exist icon type will be set 'rect' | 
 |             if (pointerStr) { | 
 |                 pointer = createSymbol( | 
 |                     pointerStr, | 
 |                     pointerOffsetX - pointerWidth / 2, | 
 |                     pointerOffsetY - pointerLength, | 
 |                     pointerWidth, | 
 |                     pointerLength, | 
 |                     null, | 
 |                     pointerKeepAspect | 
 |                 ) as graphic.Path; | 
 |             } | 
 |             else { | 
 |                 pointer = new PointerPath({ | 
 |                     shape: { | 
 |                         angle: -Math.PI / 2, | 
 |                         width: pointerWidth, | 
 |                         r: pointerLength, | 
 |                         x: pointerOffsetX, | 
 |                         y: pointerOffsetY | 
 |                     } | 
 |                 }); | 
 |             } | 
 |             pointer.rotation = -(angle + Math.PI / 2); | 
 |             pointer.x = posInfo.cx; | 
 |             pointer.y = posInfo.cy; | 
 |             return pointer; | 
 |         } | 
 |  | 
 |         function createProgress(idx: number, endAngle: number) { | 
 |             const roundCap = progressModel.get('roundCap'); | 
 |             const ProgressPath = roundCap ? Sausage : graphic.Sector; | 
 |  | 
 |             const isOverlap = progressModel.get('overlap'); | 
 |             const progressWidth = isOverlap ? progressModel.get('width') : axisLineWidth / data.count(); | 
 |             const r0 = isOverlap ? posInfo.r - progressWidth : posInfo.r - (idx + 1) * progressWidth; | 
 |             const r = isOverlap ? posInfo.r : posInfo.r - idx * progressWidth; | 
 |             const progress = new ProgressPath({ | 
 |                 shape: { | 
 |                     startAngle: startAngle, | 
 |                     endAngle: endAngle, | 
 |                     cx: posInfo.cx, | 
 |                     cy: posInfo.cy, | 
 |                     clockwise: clockwise, | 
 |                     r0: r0, | 
 |                     r: r | 
 |                 } | 
 |             }); | 
 |             isOverlap && (progress.z2 = maxVal - (data.get(valueDim, idx) as number) % maxVal); | 
 |             return progress; | 
 |         } | 
 |  | 
 |         if (showProgress || showPointer) { | 
 |             data.diff(oldData) | 
 |                 .add(function (idx) { | 
 |                     const val = data.get(valueDim, idx) as number; | 
 |                     if (showPointer) { | 
 |                         const pointer = createPointer(idx, startAngle); | 
 |                         // TODO hide pointer on NaN value? | 
 |                         graphic.initProps(pointer, { | 
 |                             rotation: -( | 
 |                                 (isNaN(+val) ? angleExtent[0] : linearMap(val, valueExtent, angleExtent, true)) | 
 |                                 + Math.PI / 2 | 
 |                             ) | 
 |                         }, seriesModel); | 
 |                         group.add(pointer); | 
 |                         data.setItemGraphicEl(idx, pointer); | 
 |                     } | 
 |  | 
 |                     if (showProgress) { | 
 |                         const progress = createProgress(idx, startAngle) as graphic.Sector; | 
 |                         const isClip = progressModel.get('clip'); | 
 |                         graphic.initProps(progress, { | 
 |                             shape: { | 
 |                                 endAngle: linearMap(val, valueExtent, angleExtent, isClip) | 
 |                             } | 
 |                         }, seriesModel); | 
 |                         group.add(progress); | 
 |                         // Add data index and series index for indexing the data by element | 
 |                         // Useful in tooltip | 
 |                         setCommonECData(seriesModel.seriesIndex, data.dataType, idx, progress); | 
 |                         progressList[idx] = progress; | 
 |                     } | 
 |                 }) | 
 |                 .update(function (newIdx, oldIdx) { | 
 |                     const val = data.get(valueDim, newIdx) as number; | 
 |                     if (showPointer) { | 
 |                         const previousPointer = oldData.getItemGraphicEl(oldIdx) as PointerPath; | 
 |                         const previousRotate = previousPointer ? previousPointer.rotation : startAngle; | 
 |                         const pointer = createPointer(newIdx, previousRotate); | 
 |                         pointer.rotation = previousRotate; | 
 |                         graphic.updateProps(pointer, { | 
 |                             rotation: -( | 
 |                                 (isNaN(+val) ? angleExtent[0] : linearMap(val, valueExtent, angleExtent, true)) | 
 |                                     + Math.PI / 2 | 
 |                             ) | 
 |                         }, seriesModel); | 
 |                         group.add(pointer); | 
 |                         data.setItemGraphicEl(newIdx, pointer); | 
 |                     } | 
 |  | 
 |                     if (showProgress) { | 
 |                         const previousProgress = oldProgressData[oldIdx]; | 
 |                         const previousEndAngle = previousProgress ? previousProgress.shape.endAngle : startAngle; | 
 |                         const progress = createProgress(newIdx, previousEndAngle) as graphic.Sector; | 
 |                         const isClip = progressModel.get('clip'); | 
 |                         graphic.updateProps(progress, { | 
 |                             shape: { | 
 |                                 endAngle: linearMap(val, valueExtent, angleExtent, isClip) | 
 |                             } | 
 |                         }, seriesModel); | 
 |                         group.add(progress); | 
 |                         // Add data index and series index for indexing the data by element | 
 |                         // Useful in tooltip | 
 |                         setCommonECData(seriesModel.seriesIndex, data.dataType, newIdx, progress); | 
 |                         progressList[newIdx] = progress; | 
 |                     } | 
 |                 }) | 
 |                 .execute(); | 
 |  | 
 |             data.each(function (idx) { | 
 |                 const itemModel = data.getItemModel<GaugeDataItemOption>(idx); | 
 |                 const emphasisModel = itemModel.getModel('emphasis'); | 
 |                 const focus = emphasisModel.get('focus'); | 
 |                 const blurScope = emphasisModel.get('blurScope'); | 
 |                 const emphasisDisabled = emphasisModel.get('disabled'); | 
 |                 if (showPointer) { | 
 |                     const pointer = data.getItemGraphicEl(idx) as ECSymbol; | 
 |                     const symbolStyle = data.getItemVisual(idx, 'style'); | 
 |                     const visualColor = symbolStyle.fill; | 
 |                     if (pointer instanceof ZRImage) { | 
 |                         const pathStyle = pointer.style; | 
 |                         pointer.useStyle(extend({ | 
 |                             image: pathStyle.image, | 
 |                             x: pathStyle.x, y: pathStyle.y, | 
 |                             width: pathStyle.width, height: pathStyle.height | 
 |                         }, symbolStyle)); | 
 |                     } | 
 |                     else { | 
 |                         pointer.useStyle(symbolStyle); | 
 |                         pointer.type !== 'pointer' && pointer.setColor(visualColor); | 
 |                     } | 
 |  | 
 |                     pointer.setStyle(itemModel.getModel(['pointer', 'itemStyle']).getItemStyle()); | 
 |  | 
 |  | 
 |                     if (pointer.style.fill === 'auto') { | 
 |                         pointer.setStyle('fill', getColor( | 
 |                             linearMap(data.get(valueDim, idx) as number, valueExtent, [0, 1], true) | 
 |                         )); | 
 |                     } | 
 |  | 
 |                     (pointer as ECElement).z2EmphasisLift = 0; | 
 |                     setStatesStylesFromModel(pointer, itemModel); | 
 |                     toggleHoverEmphasis(pointer, focus, blurScope, emphasisDisabled); | 
 |                 } | 
 |  | 
 |                 if (showProgress) { | 
 |                     const progress = progressList[idx]; | 
 |                     progress.useStyle(data.getItemVisual(idx, 'style')); | 
 |                     progress.setStyle(itemModel.getModel(['progress', 'itemStyle']).getItemStyle()); | 
 |                     (progress as ECElement).z2EmphasisLift = 0; | 
 |                     setStatesStylesFromModel(progress, itemModel); | 
 |                     toggleHoverEmphasis(progress, focus, blurScope, emphasisDisabled); | 
 |                 } | 
 |             }); | 
 |  | 
 |             this._progressEls = progressList; | 
 |         } | 
 |     } | 
 |  | 
 |     _renderAnchor( | 
 |         seriesModel: GaugeSeriesModel, | 
 |         posInfo: PosInfo | 
 |     ) { | 
 |         const anchorModel = seriesModel.getModel('anchor'); | 
 |         const showAnchor = anchorModel.get('show'); | 
 |         if (showAnchor) { | 
 |             const anchorSize = anchorModel.get('size'); | 
 |             const anchorType = anchorModel.get('icon'); | 
 |             const offsetCenter = anchorModel.get('offsetCenter'); | 
 |             const anchorKeepAspect = anchorModel.get('keepAspect'); | 
 |             const anchor = createSymbol( | 
 |                 anchorType, | 
 |                 posInfo.cx - anchorSize / 2 + parsePercent(offsetCenter[0], posInfo.r), | 
 |                 posInfo.cy - anchorSize / 2 + parsePercent(offsetCenter[1], posInfo.r), | 
 |                 anchorSize, | 
 |                 anchorSize, | 
 |                 null, | 
 |                 anchorKeepAspect | 
 |             ) as graphic.Path; | 
 |             anchor.z2 = anchorModel.get('showAbove') ? 1 : 0; | 
 |             anchor.setStyle(anchorModel.getModel('itemStyle').getItemStyle()); | 
 |             this.group.add(anchor); | 
 |         } | 
 |     } | 
 |  | 
 |     _renderTitleAndDetail( | 
 |         seriesModel: GaugeSeriesModel, | 
 |         ecModel: GlobalModel, | 
 |         api: ExtensionAPI, | 
 |         getColor: (percent: number) => ColorString, | 
 |         posInfo: PosInfo | 
 |     ) { | 
 |         const data = seriesModel.getData(); | 
 |         const valueDim = data.mapDimension('value'); | 
 |         const minVal = +seriesModel.get('min'); | 
 |         const maxVal = +seriesModel.get('max'); | 
 |  | 
 |         const contentGroup = new graphic.Group(); | 
 |  | 
 |         const newTitleEls: graphic.Text[] = []; | 
 |         const newDetailEls: graphic.Text[] = []; | 
 |         const hasAnimation = seriesModel.isAnimationEnabled(); | 
 |  | 
 |         const showPointerAbove = seriesModel.get(['pointer', 'showAbove']); | 
 |  | 
 |         data.diff(this._data) | 
 |             .add((idx) => { | 
 |                 newTitleEls[idx] = new graphic.Text({ | 
 |                     silent: true | 
 |                 }); | 
 |                 newDetailEls[idx] = new graphic.Text({ | 
 |                     silent: true | 
 |                 }); | 
 |             }) | 
 |             .update((idx, oldIdx) => { | 
 |                 newTitleEls[idx] = this._titleEls[oldIdx]; | 
 |                 newDetailEls[idx] = this._detailEls[oldIdx]; | 
 |             }) | 
 |             .execute(); | 
 |  | 
 |         data.each(function (idx) { | 
 |             const itemModel = data.getItemModel<GaugeDataItemOption>(idx); | 
 |             const value = data.get(valueDim, idx) as number; | 
 |             const itemGroup = new graphic.Group(); | 
 |             const autoColor = getColor( | 
 |                 linearMap(value, [minVal, maxVal], [0, 1], true) | 
 |             ); | 
 |  | 
 |             const itemTitleModel = itemModel.getModel('title'); | 
 |             if (itemTitleModel.get('show')) { | 
 |                 const titleOffsetCenter = itemTitleModel.get('offsetCenter'); | 
 |                 const titleX = posInfo.cx + parsePercent(titleOffsetCenter[0], posInfo.r); | 
 |                 const titleY = posInfo.cy + parsePercent(titleOffsetCenter[1], posInfo.r); | 
 |                 const labelEl = newTitleEls[idx]; | 
 |                 labelEl.attr({ | 
 |                     z2: showPointerAbove ? 0 : 2, | 
 |                     style: createTextStyle(itemTitleModel, { | 
 |                         x: titleX, | 
 |                         y: titleY, | 
 |                         text: data.getName(idx), | 
 |                         align: 'center', | 
 |                         verticalAlign: 'middle' | 
 |                     }, {inheritColor: autoColor}) | 
 |                 }); | 
 |  | 
 |                 itemGroup.add(labelEl); | 
 |             } | 
 |  | 
 |             const itemDetailModel = itemModel.getModel('detail'); | 
 |             if (itemDetailModel.get('show')) { | 
 |                 const detailOffsetCenter = itemDetailModel.get('offsetCenter'); | 
 |                 const detailX = posInfo.cx + parsePercent(detailOffsetCenter[0], posInfo.r); | 
 |                 const detailY = posInfo.cy + parsePercent(detailOffsetCenter[1], posInfo.r); | 
 |                 const width = parsePercent(itemDetailModel.get('width'), posInfo.r); | 
 |                 const height = parsePercent(itemDetailModel.get('height'), posInfo.r); | 
 |                 const detailColor = ( | 
 |                     seriesModel.get(['progress', 'show']) ? data.getItemVisual(idx, 'style').fill : autoColor | 
 |                 ) as string; | 
 |                 const labelEl = newDetailEls[idx]; | 
 |                 const formatter = itemDetailModel.get('formatter'); | 
 |                 labelEl.attr({ | 
 |                     z2: showPointerAbove ? 0 : 2, | 
 |                     style: createTextStyle(itemDetailModel, { | 
 |                         x: detailX, | 
 |                         y: detailY, | 
 |                         text: formatLabel(value, formatter), | 
 |                         width: isNaN(width) ? null : width, | 
 |                         height: isNaN(height) ? null : height, | 
 |                         align: 'center', | 
 |                         verticalAlign: 'middle' | 
 |                     }, {inheritColor: detailColor}) | 
 |                 }); | 
 |                 setLabelValueAnimation( | 
 |                     labelEl, | 
 |                     {normal: itemDetailModel}, | 
 |                     value, | 
 |                     (value: number) => formatLabel(value, formatter) | 
 |                 ); | 
 |                 hasAnimation && animateLabelValue(labelEl, idx, data, seriesModel, { | 
 |                     getFormattedLabel( | 
 |                         labelDataIndex, status, dataType, labelDimIndex, fmt, extendParams | 
 |                     ) { | 
 |                         return formatLabel( | 
 |                             extendParams | 
 |                                 ? extendParams.interpolatedValue as typeof value | 
 |                                 : value, | 
 |                             formatter | 
 |                         ); | 
 |                     } | 
 |                 }); | 
 |  | 
 |                 itemGroup.add(labelEl); | 
 |             } | 
 |  | 
 |             contentGroup.add(itemGroup); | 
 |         }); | 
 |         this.group.add(contentGroup); | 
 |  | 
 |         this._titleEls = newTitleEls; | 
 |         this._detailEls = newDetailEls; | 
 |     } | 
 |  | 
 | } | 
 |  | 
 | export default GaugeView; |