/* * 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( 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( 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); }