|  | /* | 
|  | * 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. | 
|  | */ | 
|  |  | 
|  | // FIXME emphasis label position is not same with normal label position | 
|  | import {parsePercent} from '../../util/number'; | 
|  | import PieSeriesModel, { PieSeriesOption, PieDataItemOption } from './PieSeries'; | 
|  | import { VectorArray } from 'zrender/src/core/vector'; | 
|  | import { HorizontalAlign, ZRTextAlign } from '../../util/types'; | 
|  | import { Sector, Polyline, Point } from '../../util/graphic'; | 
|  | import ZRText from 'zrender/src/graphic/Text'; | 
|  | import BoundingRect, {RectLike} from 'zrender/src/core/BoundingRect'; | 
|  | import { each, isNumber } from 'zrender/src/core/util'; | 
|  | import { limitTurnAngle, limitSurfaceAngle } from '../../label/labelGuideHelper'; | 
|  | import { shiftLayoutOnY } from '../../label/labelLayoutHelper'; | 
|  |  | 
|  | const RADIAN = Math.PI / 180; | 
|  |  | 
|  | interface LabelLayout { | 
|  | label: ZRText | 
|  | labelLine: Polyline | 
|  | position: PieSeriesOption['label']['position'] | 
|  | len: number | 
|  | len2: number | 
|  | minTurnAngle: number | 
|  | maxSurfaceAngle: number | 
|  | surfaceNormal: Point | 
|  | linePoints: VectorArray[] | 
|  | textAlign: HorizontalAlign | 
|  | labelDistance: number | 
|  | labelAlignTo: PieSeriesOption['label']['alignTo'] | 
|  | edgeDistance: number | 
|  | bleedMargin: PieSeriesOption['label']['bleedMargin'] | 
|  | rect: BoundingRect | 
|  | /** | 
|  | * user-set style.width. | 
|  | * This is useful because label.style.width might be changed | 
|  | * by constrainTextWidth. | 
|  | */ | 
|  | labelStyleWidth: number | 
|  | unconstrainedWidth: number | 
|  | targetTextWidth?: number | 
|  | } | 
|  |  | 
|  | function adjustSingleSide( | 
|  | list: LabelLayout[], | 
|  | cx: number, | 
|  | cy: number, | 
|  | r: number, | 
|  | dir: -1 | 1, | 
|  | viewWidth: number, | 
|  | viewHeight: number, | 
|  | viewLeft: number, | 
|  | viewTop: number, | 
|  | farthestX: number | 
|  | ) { | 
|  | if (list.length < 2) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | interface SemiInfo { | 
|  | list: LabelLayout[] | 
|  | rB: number | 
|  | maxY: number | 
|  | }; | 
|  |  | 
|  | function recalculateXOnSemiToAlignOnEllipseCurve(semi: SemiInfo) { | 
|  | const rB = semi.rB; | 
|  | const rB2 = rB * rB; | 
|  | for (let i = 0; i < semi.list.length; i++) { | 
|  | const item = semi.list[i]; | 
|  | const dy = Math.abs(item.label.y - cy); | 
|  | // horizontal r is always same with original r because x is not changed. | 
|  | const rA = r + item.len; | 
|  | const rA2 = rA * rA; | 
|  | // Use ellipse implicit function to calculate x | 
|  | const dx = Math.sqrt((1 - Math.abs(dy * dy / rB2)) * rA2); | 
|  | const newX = cx + (dx + item.len2) * dir; | 
|  | const deltaX = newX - item.label.x; | 
|  | const newTargetWidth = item.targetTextWidth - deltaX * dir; | 
|  | // text x is changed, so need to recalculate width. | 
|  | constrainTextWidth(item, newTargetWidth, true); | 
|  | item.label.x = newX; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Adjust X based on the shifted y. Make tight labels aligned on an ellipse curve. | 
|  | function recalculateX(items: LabelLayout[]) { | 
|  | // Extremes of | 
|  | const topSemi = { list: [], maxY: 0} as SemiInfo; | 
|  | const bottomSemi = { list: [], maxY: 0 } as SemiInfo; | 
|  |  | 
|  | for (let i = 0; i < items.length; i++) { | 
|  | if (items[i].labelAlignTo !== 'none') { | 
|  | continue; | 
|  | } | 
|  | const item = items[i]; | 
|  | const semi = item.label.y > cy ? bottomSemi : topSemi; | 
|  | const dy = Math.abs(item.label.y - cy); | 
|  | if (dy >= semi.maxY) { | 
|  | const dx = item.label.x - cx - item.len2 * dir; | 
|  | // horizontal r is always same with original r because x is not changed. | 
|  | const rA = r + item.len; | 
|  | // Canculate rB based on the topest / bottemest label. | 
|  | const rB = Math.abs(dx) < rA | 
|  | ? Math.sqrt(dy * dy / (1 - dx * dx / rA / rA)) | 
|  | : rA; | 
|  | semi.rB = rB; | 
|  | semi.maxY = dy; | 
|  | } | 
|  | semi.list.push(item); | 
|  | } | 
|  |  | 
|  | recalculateXOnSemiToAlignOnEllipseCurve(topSemi); | 
|  | recalculateXOnSemiToAlignOnEllipseCurve(bottomSemi); | 
|  | } | 
|  |  | 
|  | const len = list.length; | 
|  | for (let i = 0; i < len; i++) { | 
|  | if (list[i].position === 'outer' && list[i].labelAlignTo === 'labelLine') { | 
|  | const dx = list[i].label.x - farthestX; | 
|  | list[i].linePoints[1][0] += dx; | 
|  | list[i].label.x = farthestX; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (shiftLayoutOnY(list, viewTop, viewTop + viewHeight)) { | 
|  | recalculateX(list); | 
|  | } | 
|  | } | 
|  |  | 
|  | function avoidOverlap( | 
|  | labelLayoutList: LabelLayout[], | 
|  | cx: number, | 
|  | cy: number, | 
|  | r: number, | 
|  | viewWidth: number, | 
|  | viewHeight: number, | 
|  | viewLeft: number, | 
|  | viewTop: number | 
|  | ) { | 
|  | const leftList = []; | 
|  | const rightList = []; | 
|  | let leftmostX = Number.MAX_VALUE; | 
|  | let rightmostX = -Number.MAX_VALUE; | 
|  | for (let i = 0; i < labelLayoutList.length; i++) { | 
|  | const label = labelLayoutList[i].label; | 
|  | if (isPositionCenter(labelLayoutList[i])) { | 
|  | continue; | 
|  | } | 
|  | if (label.x < cx) { | 
|  | leftmostX = Math.min(leftmostX, label.x); | 
|  | leftList.push(labelLayoutList[i]); | 
|  | } | 
|  | else { | 
|  | rightmostX = Math.max(rightmostX, label.x); | 
|  | rightList.push(labelLayoutList[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (let i = 0; i < labelLayoutList.length; i++) { | 
|  | const layout = labelLayoutList[i]; | 
|  | if (!isPositionCenter(layout) && layout.linePoints) { | 
|  | if (layout.labelStyleWidth != null) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | const label = layout.label; | 
|  | const linePoints = layout.linePoints; | 
|  |  | 
|  | let targetTextWidth; | 
|  | if (layout.labelAlignTo === 'edge') { | 
|  | if (label.x < cx) { | 
|  | targetTextWidth = linePoints[2][0] - layout.labelDistance | 
|  | - viewLeft - layout.edgeDistance; | 
|  | } | 
|  | else { | 
|  | targetTextWidth = viewLeft + viewWidth - layout.edgeDistance | 
|  | - linePoints[2][0] - layout.labelDistance; | 
|  | } | 
|  | } | 
|  | else if (layout.labelAlignTo === 'labelLine') { | 
|  | if (label.x < cx) { | 
|  | targetTextWidth = leftmostX - viewLeft - layout.bleedMargin; | 
|  | } | 
|  | else { | 
|  | targetTextWidth = viewLeft + viewWidth - rightmostX - layout.bleedMargin; | 
|  | } | 
|  | } | 
|  | else { | 
|  | if (label.x < cx) { | 
|  | targetTextWidth = label.x - viewLeft - layout.bleedMargin; | 
|  | } | 
|  | else { | 
|  | targetTextWidth = viewLeft + viewWidth - label.x - layout.bleedMargin; | 
|  | } | 
|  | } | 
|  | layout.targetTextWidth = targetTextWidth; | 
|  |  | 
|  | constrainTextWidth(layout, targetTextWidth); | 
|  | } | 
|  | } | 
|  |  | 
|  | adjustSingleSide(rightList, cx, cy, r, 1, viewWidth, viewHeight, viewLeft, viewTop, rightmostX); | 
|  | adjustSingleSide(leftList, cx, cy, r, -1, viewWidth, viewHeight, viewLeft, viewTop, leftmostX); | 
|  |  | 
|  | for (let i = 0; i < labelLayoutList.length; i++) { | 
|  | const layout = labelLayoutList[i]; | 
|  | if (!isPositionCenter(layout) && layout.linePoints) { | 
|  | const label = layout.label; | 
|  | const linePoints = layout.linePoints; | 
|  | const isAlignToEdge = layout.labelAlignTo === 'edge'; | 
|  | const padding = label.style.padding as number[]; | 
|  | const paddingH = padding ? padding[1] + padding[3] : 0; | 
|  | // textRect.width already contains paddingH if bgColor is set | 
|  | const extraPaddingH = label.style.backgroundColor ? 0 : paddingH; | 
|  | const realTextWidth = layout.rect.width + extraPaddingH; | 
|  | const dist = linePoints[1][0] - linePoints[2][0]; | 
|  | if (isAlignToEdge) { | 
|  | if (label.x < cx) { | 
|  | linePoints[2][0] = viewLeft + layout.edgeDistance + realTextWidth + layout.labelDistance; | 
|  | } | 
|  | else { | 
|  | linePoints[2][0] = viewLeft + viewWidth - layout.edgeDistance | 
|  | - realTextWidth - layout.labelDistance; | 
|  | } | 
|  | } | 
|  | else { | 
|  | if (label.x < cx) { | 
|  | linePoints[2][0] = label.x + layout.labelDistance; | 
|  | } | 
|  | else { | 
|  | linePoints[2][0] = label.x - layout.labelDistance; | 
|  | } | 
|  | linePoints[1][0] = linePoints[2][0] + dist; | 
|  | } | 
|  | linePoints[1][1] = linePoints[2][1] = label.y; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set max width of each label, and then wrap each label to the max width. | 
|  | * | 
|  | * @param layout label layout | 
|  | * @param availableWidth max width for the label to display | 
|  | * @param forceRecalculate recaculate the text layout even if the current width | 
|  | * is smaller than `availableWidth`. This is useful when the text was previously | 
|  | * wrapped by calling `constrainTextWidth` but now `availableWidth` changed, in | 
|  | * which case, previous wrapping should be redo. | 
|  | */ | 
|  | function constrainTextWidth( | 
|  | layout: LabelLayout, | 
|  | availableWidth: number, | 
|  | forceRecalculate: boolean = false | 
|  | ) { | 
|  | if (layout.labelStyleWidth != null) { | 
|  | // User-defined style.width has the highest priority. | 
|  | return; | 
|  | } | 
|  |  | 
|  | const label = layout.label; | 
|  | const style = label.style; | 
|  | const textRect = layout.rect; | 
|  | const bgColor = style.backgroundColor; | 
|  | const padding = style.padding as number[]; | 
|  | const paddingH = padding ? padding[1] + padding[3] : 0; | 
|  | const overflow = style.overflow; | 
|  |  | 
|  | // textRect.width already contains paddingH if bgColor is set | 
|  | const oldOuterWidth = textRect.width + (bgColor ? 0 : paddingH); | 
|  | if (availableWidth < oldOuterWidth || forceRecalculate) { | 
|  | const oldHeight = textRect.height; | 
|  | if (overflow && overflow.match('break')) { | 
|  | // Temporarily set background to be null to calculate | 
|  | // the bounding box without background. | 
|  | label.setStyle('backgroundColor', null); | 
|  | // Set constraining width | 
|  | label.setStyle('width', availableWidth - paddingH); | 
|  |  | 
|  | // This is the real bounding box of the text without padding. | 
|  | const innerRect = label.getBoundingRect(); | 
|  |  | 
|  | label.setStyle('width', Math.ceil(innerRect.width)); | 
|  | label.setStyle('backgroundColor', bgColor); | 
|  | } | 
|  | else { | 
|  | const availableInnerWidth = availableWidth - paddingH; | 
|  | const newWidth = availableWidth < oldOuterWidth | 
|  | // Current text is too wide, use `availableWidth` as max width. | 
|  | ? availableInnerWidth | 
|  | : ( | 
|  | // Current available width is enough, but the text may have | 
|  | // already been wrapped with a smaller available width. | 
|  | forceRecalculate | 
|  | ? (availableInnerWidth > layout.unconstrainedWidth | 
|  | // Current available is larger than text width, | 
|  | // so don't constrain width (otherwise it may have | 
|  | // empty space in the background). | 
|  | ? null | 
|  | // Current available is smaller than text width, so | 
|  | // use the current available width as constraining | 
|  | // width. | 
|  | : availableInnerWidth | 
|  | ) | 
|  | // Current available width is enough, so no need to | 
|  | // constrain. | 
|  | : null | 
|  | ); | 
|  | label.setStyle('width', newWidth); | 
|  | } | 
|  |  | 
|  | const newRect = label.getBoundingRect(); | 
|  | textRect.width = newRect.width; | 
|  | const margin = (label.style.margin || 0) + 2.1; | 
|  | textRect.height = newRect.height + margin; | 
|  | textRect.y -= (textRect.height - oldHeight) / 2; | 
|  | } | 
|  | } | 
|  |  | 
|  | function isPositionCenter(sectorShape: LabelLayout) { | 
|  | // Not change x for center label | 
|  | return sectorShape.position === 'center'; | 
|  | } | 
|  |  | 
|  | export default function pieLabelLayout( | 
|  | seriesModel: PieSeriesModel | 
|  | ) { | 
|  | const data = seriesModel.getData(); | 
|  | const labelLayoutList: LabelLayout[] = []; | 
|  | let cx; | 
|  | let cy; | 
|  | let hasLabelRotate = false; | 
|  | const minShowLabelRadian = (seriesModel.get('minShowLabelAngle') || 0) * RADIAN; | 
|  |  | 
|  | const viewRect = data.getLayout('viewRect') as RectLike; | 
|  | const r = data.getLayout('r') as number; | 
|  | const viewWidth = viewRect.width; | 
|  | const viewLeft = viewRect.x; | 
|  | const viewTop = viewRect.y; | 
|  | const viewHeight = viewRect.height; | 
|  |  | 
|  | function setNotShow(el: {ignore: boolean}) { | 
|  | el.ignore = true; | 
|  | } | 
|  |  | 
|  | function isLabelShown(label: ZRText) { | 
|  | if (!label.ignore) { | 
|  | return true; | 
|  | } | 
|  | for (const key in label.states) { | 
|  | if (label.states[key].ignore === false) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | data.each(function (idx) { | 
|  | const sector = data.getItemGraphicEl(idx) as Sector; | 
|  | const sectorShape = sector.shape; | 
|  | const label = sector.getTextContent(); | 
|  | const labelLine = sector.getTextGuideLine(); | 
|  |  | 
|  | const itemModel = data.getItemModel<PieDataItemOption>(idx); | 
|  | const labelModel = itemModel.getModel('label'); | 
|  | // Use position in normal or emphasis | 
|  | const labelPosition = labelModel.get('position') || itemModel.get(['emphasis', 'label', 'position']); | 
|  | const labelDistance = labelModel.get('distanceToLabelLine'); | 
|  | const labelAlignTo = labelModel.get('alignTo'); | 
|  | const edgeDistance = parsePercent(labelModel.get('edgeDistance'), viewWidth); | 
|  | const bleedMargin = labelModel.get('bleedMargin'); | 
|  |  | 
|  | const labelLineModel = itemModel.getModel('labelLine'); | 
|  | let labelLineLen = labelLineModel.get('length'); | 
|  | labelLineLen = parsePercent(labelLineLen, viewWidth); | 
|  | let labelLineLen2 = labelLineModel.get('length2'); | 
|  | labelLineLen2 = parsePercent(labelLineLen2, viewWidth); | 
|  |  | 
|  | if (Math.abs(sectorShape.endAngle - sectorShape.startAngle) < minShowLabelRadian) { | 
|  | each(label.states, setNotShow); | 
|  | label.ignore = true; | 
|  | if (labelLine) { | 
|  | each(labelLine.states, setNotShow); | 
|  | labelLine.ignore = true; | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!isLabelShown(label)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const midAngle = (sectorShape.startAngle + sectorShape.endAngle) / 2; | 
|  | const nx = Math.cos(midAngle); | 
|  | const ny = Math.sin(midAngle); | 
|  |  | 
|  | let textX; | 
|  | let textY; | 
|  | let linePoints; | 
|  | let textAlign: ZRTextAlign; | 
|  |  | 
|  | cx = sectorShape.cx; | 
|  | cy = sectorShape.cy; | 
|  |  | 
|  |  | 
|  | const isLabelInside = labelPosition === 'inside' || labelPosition === 'inner'; | 
|  | if (labelPosition === 'center') { | 
|  | textX = sectorShape.cx; | 
|  | textY = sectorShape.cy; | 
|  | textAlign = 'center'; | 
|  | } | 
|  | else { | 
|  | const x1 = (isLabelInside ? (sectorShape.r + sectorShape.r0) / 2 * nx : sectorShape.r * nx) + cx; | 
|  | const y1 = (isLabelInside ? (sectorShape.r + sectorShape.r0) / 2 * ny : sectorShape.r * ny) + cy; | 
|  |  | 
|  | textX = x1 + nx * 3; | 
|  | textY = y1 + ny * 3; | 
|  |  | 
|  | if (!isLabelInside) { | 
|  | // For roseType | 
|  | const x2 = x1 + nx * (labelLineLen + r - sectorShape.r); | 
|  | const y2 = y1 + ny * (labelLineLen + r - sectorShape.r); | 
|  | const x3 = x2 + ((nx < 0 ? -1 : 1) * labelLineLen2); | 
|  | const y3 = y2; | 
|  |  | 
|  | if (labelAlignTo === 'edge') { | 
|  | // Adjust textX because text align of edge is opposite | 
|  | textX = nx < 0 | 
|  | ? viewLeft + edgeDistance | 
|  | : viewLeft + viewWidth - edgeDistance; | 
|  | } | 
|  | else { | 
|  | textX = x3 + (nx < 0 ? -labelDistance : labelDistance); | 
|  | } | 
|  | textY = y3; | 
|  | linePoints = [[x1, y1], [x2, y2], [x3, y3]]; | 
|  | } | 
|  |  | 
|  | textAlign = isLabelInside | 
|  | ? 'center' | 
|  | : (labelAlignTo === 'edge' | 
|  | ? (nx > 0 ? 'right' : 'left') | 
|  | : (nx > 0 ? 'left' : 'right')); | 
|  | } | 
|  |  | 
|  | const PI = Math.PI; | 
|  | let labelRotate = 0; | 
|  | const rotate = labelModel.get('rotate'); | 
|  | if (isNumber(rotate)) { | 
|  | labelRotate = rotate * (PI / 180); | 
|  | } | 
|  | else if (labelPosition === 'center') { | 
|  | labelRotate = 0; | 
|  | } | 
|  | else if (rotate === 'radial' || rotate === true) { | 
|  | const radialAngle = nx < 0 ? -midAngle + PI : -midAngle; | 
|  | labelRotate = radialAngle; | 
|  | } | 
|  | else if (rotate === 'tangential' | 
|  | && labelPosition !== 'outside' && labelPosition !== 'outer' | 
|  | ) { | 
|  | let rad = Math.atan2(nx, ny); | 
|  | if (rad < 0) { | 
|  | rad = PI * 2 + rad; | 
|  | } | 
|  | const isDown = ny > 0; | 
|  | if (isDown) { | 
|  | rad = PI + rad; | 
|  | } | 
|  | labelRotate = rad - PI; | 
|  | } | 
|  |  | 
|  | hasLabelRotate = !!labelRotate; | 
|  |  | 
|  | label.x = textX; | 
|  | label.y = textY; | 
|  | label.rotation = labelRotate; | 
|  |  | 
|  | label.setStyle({ | 
|  | verticalAlign: 'middle' | 
|  | }); | 
|  |  | 
|  | // Not sectorShape the inside label | 
|  | if (!isLabelInside) { | 
|  | const textRect = label.getBoundingRect().clone(); | 
|  | textRect.applyTransform(label.getComputedTransform()); | 
|  | // Text has a default 1px stroke. Exclude this. | 
|  | const margin = (label.style.margin || 0) + 2.1; | 
|  | textRect.y -= margin / 2; | 
|  | textRect.height += margin; | 
|  |  | 
|  | labelLayoutList.push({ | 
|  | label, | 
|  | labelLine, | 
|  | position: labelPosition, | 
|  | len: labelLineLen, | 
|  | len2: labelLineLen2, | 
|  | minTurnAngle: labelLineModel.get('minTurnAngle'), | 
|  | maxSurfaceAngle: labelLineModel.get('maxSurfaceAngle'), | 
|  | surfaceNormal: new Point(nx, ny), | 
|  | linePoints: linePoints, | 
|  | textAlign: textAlign, | 
|  | labelDistance: labelDistance, | 
|  | labelAlignTo: labelAlignTo, | 
|  | edgeDistance: edgeDistance, | 
|  | bleedMargin: bleedMargin, | 
|  | rect: textRect, | 
|  | unconstrainedWidth: textRect.width, | 
|  | labelStyleWidth: label.style.width | 
|  | }); | 
|  | } | 
|  | else { | 
|  | label.setStyle({ | 
|  | align: textAlign | 
|  | }); | 
|  | const selectState = label.states.select; | 
|  | if (selectState) { | 
|  | selectState.x += label.x; | 
|  | selectState.y += label.y; | 
|  | } | 
|  | } | 
|  | sector.setTextConfig({ | 
|  | inside: isLabelInside | 
|  | }); | 
|  | }); | 
|  |  | 
|  | if (!hasLabelRotate && seriesModel.get('avoidLabelOverlap')) { | 
|  | avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop); | 
|  | } | 
|  |  | 
|  | for (let i = 0; i < labelLayoutList.length; i++) { | 
|  | const layout = labelLayoutList[i]; | 
|  | const label = layout.label; | 
|  | const labelLine = layout.labelLine; | 
|  | const notShowLabel = isNaN(label.x) || isNaN(label.y); | 
|  | if (label) { | 
|  | label.setStyle({ | 
|  | align: layout.textAlign | 
|  | }); | 
|  | if (notShowLabel) { | 
|  | each(label.states, setNotShow); | 
|  | label.ignore = true; | 
|  | } | 
|  | const selectState = label.states.select; | 
|  | if (selectState) { | 
|  | selectState.x += label.x; | 
|  | selectState.y += label.y; | 
|  | } | 
|  | } | 
|  | if (labelLine) { | 
|  | const linePoints = layout.linePoints; | 
|  | if (notShowLabel || !linePoints) { | 
|  | each(labelLine.states, setNotShow); | 
|  | labelLine.ignore = true; | 
|  | } | 
|  | else { | 
|  | limitTurnAngle(linePoints, layout.minTurnAngle); | 
|  | limitSurfaceAngle(linePoints, layout.surfaceNormal, layout.maxSurfaceAngle); | 
|  |  | 
|  | labelLine.setShape({ points: linePoints }); | 
|  |  | 
|  | // Set the anchor to the midpoint of sector | 
|  | label.__hostTarget.textGuideLineConfig = { | 
|  | anchor: new Point(linePoints[0][0], linePoints[0][1]) | 
|  | }; | 
|  | } | 
|  | } | 
|  | } | 
|  | } |