blob: ba75e03b206694a6a1949a11c12d4e45c96e7ab2 [file] [log] [blame]
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} 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
): ElementCalculateTextPosition {
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;
// base position: top-left
let x = cx + r * Math.cos(startAngle);
let y = cy + r * Math.sin(startAngle);
let textAlign: TextAlign = 'left';
let textVerticalAlign: TextVerticalAlign = 'top';
switch (mappedSectorPosition) {
case 'startArc':
x = cx + (r0 - distance) * Math.cos(middleAngle);
y = cy + (r0 - distance) * Math.sin(middleAngle);
textAlign = 'center';
textVerticalAlign = 'top';
break;
case 'insideStartArc':
x = cx + (r0 + distance) * Math.cos(middleAngle);
y = cy + (r0 + distance) * Math.sin(middleAngle);
textAlign = 'center';
textVerticalAlign = 'bottom';
break;
case 'startAngle':
x = cx + middleR * Math.cos(startAngle)
+ adjustAngleDistanceX(startAngle, distance, false);
y = cy + middleR * Math.sin(startAngle)
+ adjustAngleDistanceY(startAngle, distance, false);
textAlign = 'right';
textVerticalAlign = 'middle';
break;
case 'insideStartAngle':
x = cx + middleR * Math.cos(startAngle)
+ adjustAngleDistanceX(startAngle, -distance, false);
y = cy + middleR * Math.sin(startAngle)
+ adjustAngleDistanceY(startAngle, -distance, false);
textAlign = 'left';
textVerticalAlign = 'middle';
break;
case 'middle':
x = cx + middleR * Math.cos(middleAngle);
y = cy + middleR * Math.sin(middleAngle);
textAlign = 'center';
textVerticalAlign = 'middle';
break;
case 'endArc':
x = cx + (r + distance) * Math.cos(middleAngle);
y = cy + (r + distance) * Math.sin(middleAngle);
textAlign = 'center';
textVerticalAlign = 'bottom';
break;
case 'insideEndArc':
x = cx + (r - distance) * Math.cos(middleAngle);
y = cy + (r - distance) * Math.sin(middleAngle);
textAlign = 'center';
textVerticalAlign = 'top';
break;
case 'endAngle':
x = cx + middleR * Math.cos(endAngle)
+ adjustAngleDistanceX(endAngle, distance, true);
y = cy + middleR * Math.sin(endAngle)
+ adjustAngleDistanceY(endAngle, distance, true);
textAlign = 'left';
textVerticalAlign = 'middle';
break;
case 'insideEndAngle':
x = cx + middleR * Math.cos(endAngle)
+ adjustAngleDistanceX(endAngle, -distance, true);
y = cy + middleR * Math.sin(endAngle)
+ adjustAngleDistanceY(endAngle, -distance, 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 (typeof rotateType === 'number') {
// 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);
}