474 lines
17 KiB
JavaScript
474 lines
17 KiB
JavaScript
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
|
|
/**
|
|
* AUTO-GENERATED FILE. DO NOT MODIFY.
|
|
*/
|
|
|
|
/*
|
|
* 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.js';
|
|
import { Point } from '../../util/graphic.js';
|
|
import { each, isNumber } from 'zrender/lib/core/util.js';
|
|
import { limitTurnAngle, limitSurfaceAngle } from '../../label/labelGuideHelper.js';
|
|
import { shiftLayoutOnY } from '../../label/labelLayoutHelper.js';
|
|
var RADIAN = Math.PI / 180;
|
|
function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight, viewLeft, viewTop, farthestX) {
|
|
if (list.length < 2) {
|
|
return;
|
|
}
|
|
;
|
|
function recalculateXOnSemiToAlignOnEllipseCurve(semi) {
|
|
var rB = semi.rB;
|
|
var rB2 = rB * rB;
|
|
for (var i = 0; i < semi.list.length; i++) {
|
|
var item = semi.list[i];
|
|
var dy = Math.abs(item.label.y - cy);
|
|
// horizontal r is always same with original r because x is not changed.
|
|
var rA = r + item.len;
|
|
var rA2 = rA * rA;
|
|
// Use ellipse implicit function to calculate x
|
|
var dx = Math.sqrt((1 - Math.abs(dy * dy / rB2)) * rA2);
|
|
var newX = cx + (dx + item.len2) * dir;
|
|
var deltaX = newX - item.label.x;
|
|
var 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) {
|
|
// Extremes of
|
|
var topSemi = {
|
|
list: [],
|
|
maxY: 0
|
|
};
|
|
var bottomSemi = {
|
|
list: [],
|
|
maxY: 0
|
|
};
|
|
for (var i = 0; i < items.length; i++) {
|
|
if (items[i].labelAlignTo !== 'none') {
|
|
continue;
|
|
}
|
|
var item = items[i];
|
|
var semi = item.label.y > cy ? bottomSemi : topSemi;
|
|
var dy = Math.abs(item.label.y - cy);
|
|
if (dy >= semi.maxY) {
|
|
var dx = item.label.x - cx - item.len2 * dir;
|
|
// horizontal r is always same with original r because x is not changed.
|
|
var rA = r + item.len;
|
|
// Canculate rB based on the topest / bottemest label.
|
|
var 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);
|
|
}
|
|
var len = list.length;
|
|
for (var i = 0; i < len; i++) {
|
|
if (list[i].position === 'outer' && list[i].labelAlignTo === 'labelLine') {
|
|
var 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, cx, cy, r, viewWidth, viewHeight, viewLeft, viewTop) {
|
|
var leftList = [];
|
|
var rightList = [];
|
|
var leftmostX = Number.MAX_VALUE;
|
|
var rightmostX = -Number.MAX_VALUE;
|
|
for (var i = 0; i < labelLayoutList.length; i++) {
|
|
var 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 (var i = 0; i < labelLayoutList.length; i++) {
|
|
var layout = labelLayoutList[i];
|
|
if (!isPositionCenter(layout) && layout.linePoints) {
|
|
if (layout.labelStyleWidth != null) {
|
|
continue;
|
|
}
|
|
var label = layout.label;
|
|
var linePoints = layout.linePoints;
|
|
var targetTextWidth = void 0;
|
|
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 (var i = 0; i < labelLayoutList.length; i++) {
|
|
var layout = labelLayoutList[i];
|
|
if (!isPositionCenter(layout) && layout.linePoints) {
|
|
var label = layout.label;
|
|
var linePoints = layout.linePoints;
|
|
var isAlignToEdge = layout.labelAlignTo === 'edge';
|
|
var padding = label.style.padding;
|
|
var paddingH = padding ? padding[1] + padding[3] : 0;
|
|
// textRect.width already contains paddingH if bgColor is set
|
|
var extraPaddingH = label.style.backgroundColor ? 0 : paddingH;
|
|
var realTextWidth = layout.rect.width + extraPaddingH;
|
|
var 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, availableWidth, forceRecalculate) {
|
|
if (forceRecalculate === void 0) {
|
|
forceRecalculate = false;
|
|
}
|
|
if (layout.labelStyleWidth != null) {
|
|
// User-defined style.width has the highest priority.
|
|
return;
|
|
}
|
|
var label = layout.label;
|
|
var style = label.style;
|
|
var textRect = layout.rect;
|
|
var bgColor = style.backgroundColor;
|
|
var padding = style.padding;
|
|
var paddingH = padding ? padding[1] + padding[3] : 0;
|
|
var overflow = style.overflow;
|
|
// textRect.width already contains paddingH if bgColor is set
|
|
var oldOuterWidth = textRect.width + (bgColor ? 0 : paddingH);
|
|
if (availableWidth < oldOuterWidth || forceRecalculate) {
|
|
var 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.
|
|
var innerRect = label.getBoundingRect();
|
|
label.setStyle('width', Math.ceil(innerRect.width));
|
|
label.setStyle('backgroundColor', bgColor);
|
|
} else {
|
|
var availableInnerWidth = availableWidth - paddingH;
|
|
var 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);
|
|
}
|
|
var newRect = label.getBoundingRect();
|
|
textRect.width = newRect.width;
|
|
var margin = (label.style.margin || 0) + 2.1;
|
|
textRect.height = newRect.height + margin;
|
|
textRect.y -= (textRect.height - oldHeight) / 2;
|
|
}
|
|
}
|
|
function isPositionCenter(sectorShape) {
|
|
// Not change x for center label
|
|
return sectorShape.position === 'center';
|
|
}
|
|
export default function pieLabelLayout(seriesModel) {
|
|
var data = seriesModel.getData();
|
|
var labelLayoutList = [];
|
|
var cx;
|
|
var cy;
|
|
var hasLabelRotate = false;
|
|
var minShowLabelRadian = (seriesModel.get('minShowLabelAngle') || 0) * RADIAN;
|
|
var viewRect = data.getLayout('viewRect');
|
|
var r = data.getLayout('r');
|
|
var viewWidth = viewRect.width;
|
|
var viewLeft = viewRect.x;
|
|
var viewTop = viewRect.y;
|
|
var viewHeight = viewRect.height;
|
|
function setNotShow(el) {
|
|
el.ignore = true;
|
|
}
|
|
function isLabelShown(label) {
|
|
if (!label.ignore) {
|
|
return true;
|
|
}
|
|
for (var key in label.states) {
|
|
if (label.states[key].ignore === false) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
data.each(function (idx) {
|
|
var sector = data.getItemGraphicEl(idx);
|
|
var sectorShape = sector.shape;
|
|
var label = sector.getTextContent();
|
|
var labelLine = sector.getTextGuideLine();
|
|
var itemModel = data.getItemModel(idx);
|
|
var labelModel = itemModel.getModel('label');
|
|
// Use position in normal or emphasis
|
|
var labelPosition = labelModel.get('position') || itemModel.get(['emphasis', 'label', 'position']);
|
|
var labelDistance = labelModel.get('distanceToLabelLine');
|
|
var labelAlignTo = labelModel.get('alignTo');
|
|
var edgeDistance = parsePercent(labelModel.get('edgeDistance'), viewWidth);
|
|
var bleedMargin = labelModel.get('bleedMargin');
|
|
var labelLineModel = itemModel.getModel('labelLine');
|
|
var labelLineLen = labelLineModel.get('length');
|
|
labelLineLen = parsePercent(labelLineLen, viewWidth);
|
|
var 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;
|
|
}
|
|
var midAngle = (sectorShape.startAngle + sectorShape.endAngle) / 2;
|
|
var nx = Math.cos(midAngle);
|
|
var ny = Math.sin(midAngle);
|
|
var textX;
|
|
var textY;
|
|
var linePoints;
|
|
var textAlign;
|
|
cx = sectorShape.cx;
|
|
cy = sectorShape.cy;
|
|
var isLabelInside = labelPosition === 'inside' || labelPosition === 'inner';
|
|
if (labelPosition === 'center') {
|
|
textX = sectorShape.cx;
|
|
textY = sectorShape.cy;
|
|
textAlign = 'center';
|
|
} else {
|
|
var x1 = (isLabelInside ? (sectorShape.r + sectorShape.r0) / 2 * nx : sectorShape.r * nx) + cx;
|
|
var y1 = (isLabelInside ? (sectorShape.r + sectorShape.r0) / 2 * ny : sectorShape.r * ny) + cy;
|
|
textX = x1 + nx * 3;
|
|
textY = y1 + ny * 3;
|
|
if (!isLabelInside) {
|
|
// For roseType
|
|
var x2 = x1 + nx * (labelLineLen + r - sectorShape.r);
|
|
var y2 = y1 + ny * (labelLineLen + r - sectorShape.r);
|
|
var x3 = x2 + (nx < 0 ? -1 : 1) * labelLineLen2;
|
|
var 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';
|
|
}
|
|
var PI = Math.PI;
|
|
var labelRotate = 0;
|
|
var rotate = labelModel.get('rotate');
|
|
if (isNumber(rotate)) {
|
|
labelRotate = rotate * (PI / 180);
|
|
} else if (labelPosition === 'center') {
|
|
labelRotate = 0;
|
|
} else if (rotate === 'radial' || rotate === true) {
|
|
var radialAngle = nx < 0 ? -midAngle + PI : -midAngle;
|
|
labelRotate = radialAngle;
|
|
} else if (rotate === 'tangential' && labelPosition !== 'outside' && labelPosition !== 'outer') {
|
|
var rad = Math.atan2(nx, ny);
|
|
if (rad < 0) {
|
|
rad = PI * 2 + rad;
|
|
}
|
|
var 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) {
|
|
var textRect = label.getBoundingRect().clone();
|
|
textRect.applyTransform(label.getComputedTransform());
|
|
// Text has a default 1px stroke. Exclude this.
|
|
var margin = (label.style.margin || 0) + 2.1;
|
|
textRect.y -= margin / 2;
|
|
textRect.height += margin;
|
|
labelLayoutList.push({
|
|
label: label,
|
|
labelLine: 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
|
|
});
|
|
var 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 (var i = 0; i < labelLayoutList.length; i++) {
|
|
var layout = labelLayoutList[i];
|
|
var label = layout.label;
|
|
var labelLine = layout.labelLine;
|
|
var notShowLabel = isNaN(label.x) || isNaN(label.y);
|
|
if (label) {
|
|
label.setStyle({
|
|
align: layout.textAlign
|
|
});
|
|
if (notShowLabel) {
|
|
each(label.states, setNotShow);
|
|
label.ignore = true;
|
|
}
|
|
var selectState = label.states.select;
|
|
if (selectState) {
|
|
selectState.x += label.x;
|
|
selectState.y += label.y;
|
|
}
|
|
}
|
|
if (labelLine) {
|
|
var 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])
|
|
};
|
|
}
|
|
}
|
|
}
|
|
} |