|  |  | 
|  | /* | 
|  | * 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 {calculateTextPosition, TextPositionCalculationResult} from 'zrender/src/contain/text'; | 
|  | import { RectLike } from 'zrender/src/core/BoundingRect'; | 
|  | import {BuiltinTextPosition, TextAlign, TextVerticalAlign} from 'zrender/src/core/types'; | 
|  | import {isArray, isNumber} from 'zrender/src/core/util'; | 
|  | import {ElementCalculateTextPosition, ElementTextConfig} from 'zrender/src/Element'; | 
|  | import { Sector } from '../util/graphic'; | 
|  |  | 
|  | export type SectorTextPosition = BuiltinTextPosition | 
|  | | 'startAngle' | 'insideStartAngle' | 
|  | | 'endAngle' | 'insideEndAngle' | 
|  | | 'middle' | 
|  | | 'startArc' | 'insideStartArc' | 
|  | | 'endArc' | 'insideEndArc' | 
|  | | (number | string)[]; | 
|  |  | 
|  | export type SectorLike = { | 
|  | cx: number | 
|  | cy: number | 
|  | r0: number | 
|  | r: number | 
|  | startAngle: number | 
|  | endAngle: number | 
|  | clockwise: boolean | 
|  | }; | 
|  |  | 
|  | export function createSectorCalculateTextPosition<T extends (string | (number | string)[])>( | 
|  | positionMapping: (seriesLabelPosition: T) => SectorTextPosition, | 
|  | opts?: { | 
|  | /** | 
|  | * If has round cap on two ends. If so, label should have an extra offset | 
|  | */ | 
|  | isRoundCap?: boolean | 
|  | } | 
|  | ): ElementCalculateTextPosition { | 
|  |  | 
|  | opts = opts || {}; | 
|  | const isRoundCap = opts.isRoundCap; | 
|  |  | 
|  | return function ( | 
|  | this: Sector, | 
|  | out: TextPositionCalculationResult, | 
|  | opts: { | 
|  | position?: SectorTextPosition | 
|  | distance?: number | 
|  | global?: boolean | 
|  | }, | 
|  | boundingRect: RectLike | 
|  | ) { | 
|  | const textPosition = opts.position; | 
|  |  | 
|  | if (!textPosition || textPosition instanceof Array) { | 
|  | return calculateTextPosition( | 
|  | out, | 
|  | opts as ElementTextConfig, | 
|  | boundingRect | 
|  | ); | 
|  | } | 
|  |  | 
|  | const mappedSectorPosition = positionMapping(textPosition as T); | 
|  | const distance = opts.distance != null ? opts.distance : 5; | 
|  | const sector = this.shape; | 
|  | const cx = sector.cx; | 
|  | const cy = sector.cy; | 
|  | const r = sector.r; | 
|  | const r0 = sector.r0; | 
|  | const middleR = (r + r0) / 2; | 
|  | const startAngle = sector.startAngle; | 
|  | const endAngle = sector.endAngle; | 
|  | const middleAngle = (startAngle + endAngle) / 2; | 
|  | const extraDist = isRoundCap ? Math.abs(r - r0) / 2 : 0; | 
|  |  | 
|  | const mathCos = Math.cos; | 
|  | const mathSin = Math.sin; | 
|  |  | 
|  | // base position: top-left | 
|  | let x = cx + r * mathCos(startAngle); | 
|  | let y = cy + r * mathSin(startAngle); | 
|  |  | 
|  | let textAlign: TextAlign = 'left'; | 
|  | let textVerticalAlign: TextVerticalAlign = 'top'; | 
|  |  | 
|  | switch (mappedSectorPosition) { | 
|  | case 'startArc': | 
|  | x = cx + (r0 - distance) * mathCos(middleAngle); | 
|  | y = cy + (r0 - distance) * mathSin(middleAngle); | 
|  | textAlign = 'center'; | 
|  | textVerticalAlign = 'top'; | 
|  | break; | 
|  |  | 
|  | case 'insideStartArc': | 
|  | x = cx + (r0 + distance) * mathCos(middleAngle); | 
|  | y = cy + (r0 + distance) * mathSin(middleAngle); | 
|  | textAlign = 'center'; | 
|  | textVerticalAlign = 'bottom'; | 
|  | break; | 
|  |  | 
|  | case 'startAngle': | 
|  | x = cx + middleR * mathCos(startAngle) | 
|  | + adjustAngleDistanceX(startAngle, distance + extraDist, false); | 
|  | y = cy + middleR * mathSin(startAngle) | 
|  | + adjustAngleDistanceY(startAngle, distance + extraDist, false); | 
|  | textAlign = 'right'; | 
|  | textVerticalAlign = 'middle'; | 
|  | break; | 
|  |  | 
|  | case 'insideStartAngle': | 
|  | x = cx + middleR * mathCos(startAngle) | 
|  | + adjustAngleDistanceX(startAngle, -distance + extraDist, false); | 
|  | y = cy + middleR * mathSin(startAngle) | 
|  | + adjustAngleDistanceY(startAngle, -distance + extraDist, false); | 
|  | textAlign = 'left'; | 
|  | textVerticalAlign = 'middle'; | 
|  | break; | 
|  |  | 
|  | case 'middle': | 
|  | x = cx + middleR * mathCos(middleAngle); | 
|  | y = cy + middleR * mathSin(middleAngle); | 
|  | textAlign = 'center'; | 
|  | textVerticalAlign = 'middle'; | 
|  | break; | 
|  |  | 
|  | case 'endArc': | 
|  | x = cx + (r + distance) * mathCos(middleAngle); | 
|  | y = cy + (r + distance) * mathSin(middleAngle); | 
|  | textAlign = 'center'; | 
|  | textVerticalAlign = 'bottom'; | 
|  | break; | 
|  |  | 
|  | case 'insideEndArc': | 
|  | x = cx + (r - distance) * mathCos(middleAngle); | 
|  | y = cy + (r - distance) * mathSin(middleAngle); | 
|  | textAlign = 'center'; | 
|  | textVerticalAlign = 'top'; | 
|  | break; | 
|  |  | 
|  | case 'endAngle': | 
|  | x = cx + middleR * mathCos(endAngle) | 
|  | + adjustAngleDistanceX(endAngle, distance + extraDist, true); | 
|  | y = cy + middleR * mathSin(endAngle) | 
|  | + adjustAngleDistanceY(endAngle, distance + extraDist, true); | 
|  | textAlign = 'left'; | 
|  | textVerticalAlign = 'middle'; | 
|  | break; | 
|  |  | 
|  | case 'insideEndAngle': | 
|  | x = cx + middleR * mathCos(endAngle) | 
|  | + adjustAngleDistanceX(endAngle, -distance + extraDist, true); | 
|  | y = cy + middleR * mathSin(endAngle) | 
|  | + adjustAngleDistanceY(endAngle, -distance + extraDist, true); | 
|  | textAlign = 'right'; | 
|  | textVerticalAlign = 'middle'; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | return calculateTextPosition( | 
|  | out, | 
|  | opts as ElementTextConfig, | 
|  | boundingRect | 
|  | ); | 
|  | } | 
|  |  | 
|  | out = out || {} as TextPositionCalculationResult; | 
|  | out.x = x; | 
|  | out.y = y; | 
|  | out.align = textAlign; | 
|  | out.verticalAlign = textVerticalAlign; | 
|  |  | 
|  | return out; | 
|  | }; | 
|  | } | 
|  |  | 
|  | export function setSectorTextRotation<T extends (string | (number | string)[])>( | 
|  | sector: Sector, | 
|  | textPosition: T, | 
|  | positionMapping: (seriesLabelPosition: T) => SectorTextPosition, | 
|  | rotateType: number | 'auto' | 
|  | ) { | 
|  | if (isNumber(rotateType)) { | 
|  | // user-set rotation | 
|  | sector.setTextConfig({ | 
|  | rotation: rotateType | 
|  | }); | 
|  | return; | 
|  | } | 
|  | else if (isArray(textPosition)) { | 
|  | // user-set position, use 0 as auto rotation | 
|  | sector.setTextConfig({ | 
|  | rotation: 0 | 
|  | }); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const shape = sector.shape; | 
|  | const startAngle = shape.clockwise ? shape.startAngle : shape.endAngle; | 
|  | const endAngle = shape.clockwise ? shape.endAngle : shape.startAngle; | 
|  | const middleAngle = (startAngle + endAngle) / 2; | 
|  |  | 
|  | let anchorAngle; | 
|  | const mappedSectorPosition = positionMapping(textPosition); | 
|  | switch (mappedSectorPosition) { | 
|  | case 'startArc': | 
|  | case 'insideStartArc': | 
|  | case 'middle': | 
|  | case 'insideEndArc': | 
|  | case 'endArc': | 
|  | anchorAngle = middleAngle; | 
|  | break; | 
|  |  | 
|  | case 'startAngle': | 
|  | case 'insideStartAngle': | 
|  | anchorAngle = startAngle; | 
|  | break; | 
|  |  | 
|  | case 'endAngle': | 
|  | case 'insideEndAngle': | 
|  | anchorAngle = endAngle; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | sector.setTextConfig({ | 
|  | rotation: 0 | 
|  | }); | 
|  | return; | 
|  | } | 
|  |  | 
|  | let rotate = Math.PI * 1.5 - anchorAngle; | 
|  | /** | 
|  | * TODO: labels with rotate > Math.PI / 2 should be rotate another | 
|  | * half round flipped to increase readability. However, only middle | 
|  | * position supports this for now, because in other positions, the | 
|  | * anchor point is not at the center of the text, so the positions | 
|  | * after rotating is not as expected. | 
|  | */ | 
|  | if (mappedSectorPosition === 'middle' && rotate > Math.PI / 2 && rotate < Math.PI * 1.5) { | 
|  | rotate -= Math.PI; | 
|  | } | 
|  |  | 
|  | sector.setTextConfig({ | 
|  | rotation: rotate | 
|  | }); | 
|  | } | 
|  |  | 
|  | function adjustAngleDistanceX(angle: number, distance: number, isEnd: boolean) { | 
|  | return distance * Math.sin(angle) * (isEnd ? -1 : 1); | 
|  | } | 
|  |  | 
|  | function adjustAngleDistanceY(angle: number, distance: number, isEnd: boolean) { | 
|  | return distance * Math.cos(angle) * (isEnd ? 1 : -1); | 
|  | } |