@antv/g2plot
Version:
G2 Plot, a market of plots built with the Grammar of Graphics'
360 lines • 13.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var g_1 = require("@antv/g");
var _ = tslib_1.__importStar(require("@antv/util"));
var ANCHOR_OFFSET = 0; // 锚点偏移量
var INFLECTION_OFFSET = 15; // 拐点偏移量
var DEFAULT_COLOR = '#CCC';
var LABEL1_OFFSETY = 2;
var LABEL2_OFFSETY = -2;
var ADJUSTOFFSET = 15;
function getEndPoint(center, angle, r) {
return {
x: center.x + r * Math.cos(angle),
y: center.y + r * Math.sin(angle),
};
}
function getDefaultCfg() {
return {
text: {
fill: 'rgba(0, 0, 0, 0.65)',
fontSize: 12,
},
lineWidth: 0.5,
lineStroke: 'rgba(0, 0, 0, 0.45)',
/** distance between label and edge */
sidePadding: 20,
lineHeight: 32,
};
}
var SpiderLabel = /** @class */ (function () {
function SpiderLabel(cfg) {
this.view = cfg.view;
this.fields = cfg.fields;
this.formatter = cfg.formatter;
this.offsetX = cfg.offsetX;
this.offsetY = cfg.offsetY;
this.config = _.assign(getDefaultCfg(), _.pick(cfg.style, ['lineStroke', 'lineWidth']));
if (cfg.style) {
this.config.text = _.mix(this.config.text, cfg.style);
}
this._adjustConfig(this.config);
this._init();
}
SpiderLabel.prototype.draw = function () {
var _this = this;
if (!this.view || this.view.destroyed) {
return;
}
/** 如果有formatter则事先处理数据 */
var data = _.clone(this.view.get('filteredData'));
this.halves = [[], []];
this.container = this.view.get('frontgroundGroup').addGroup();
var shapes = this.view.get('elements')[0].getShapes();
var coord = this.view.get('coord');
var angleField = this.fields[0];
var scale = this.view.get('scales')[angleField];
var center = coord.center, startAngle = coord.startAngle;
var radius = coord.getRadius();
var _a = this.view.get('panelRange'), width = _a.width, height = _a.height;
this.width = width;
this.height = height;
var angle = startAngle;
var _loop_1 = function (idx) {
var d = data[idx];
// 计算每个切片的middle angle
var angleValue = scale.scale(d[angleField]);
var targetAngle = angle + Math.PI * 2 * angleValue;
var middleAngle = angle + (targetAngle - angle) / 2;
angle = targetAngle;
// 根据middle angle计算锚点和拐点距离
var anchorPoint = getEndPoint(center, middleAngle, radius + ANCHOR_OFFSET);
var inflectionPoint = getEndPoint(center, middleAngle, radius + INFLECTION_OFFSET);
// 获取对应shape的color
var color = DEFAULT_COLOR;
if (this_1.fields.length === 2) {
var colorField = this_1.fields[1];
var colorScale = this_1.view.get('scales')[colorField];
var colorIndex = colorScale.scale(d[colorField]);
var shapeIndex = Math.floor(colorIndex * (shapes.length - 1));
color = shapes[shapeIndex].attr('fill');
}
// 组装label数据
var label = {
_anchor: anchorPoint,
_inflection: inflectionPoint,
_data: d,
x: inflectionPoint.x,
y: inflectionPoint.y,
r: radius + INFLECTION_OFFSET,
fill: color,
textGroup: null,
_side: null,
};
// 创建label文本
var texts = [];
_.each(this_1.fields, function (f) {
texts.push(d[f]);
});
if (this_1.formatter) {
var formatted = this_1.formatter(d[angleField], { _origin: d, color: color }, idx);
if (_.isString(formatted)) {
formatted = [formatted];
}
texts = formatted;
}
var textGroup = new g_1.Group();
var textAttrs = {
x: 0,
y: 0,
fontSize: this_1.config.text.fontSize,
lineHeight: this_1.config.text.fontSize,
fontWeight: this_1.config.text.fontWeight,
fill: this_1.config.text.fill,
};
// label1:下部label
var lowerText = d[angleField];
if (this_1.formatter) {
lowerText = texts[0];
}
var lowerTextAttrs = _.clone(textAttrs);
if (texts.length === 2) {
lowerTextAttrs.fontWeight = 700;
}
var lowerTextShape = textGroup.addShape('text', {
attrs: _.mix({
textBaseline: texts.length === 2 ? 'top' : 'middle',
text: lowerText,
}, lowerTextAttrs),
data: d,
offsetY: texts.length === 2 ? LABEL1_OFFSETY : 0,
name: 'label',
});
lowerTextShape.name = 'label'; // 用于事件标记 shapeName
/** label2:上部label */
if (texts.length === 2) {
var topTextShape = textGroup.addShape('text', {
attrs: _.mix({
textBaseline: 'bottom',
text: texts[1],
}, textAttrs),
data: d,
offsetY: LABEL2_OFFSETY,
name: 'label',
});
topTextShape.name = 'label'; // 用于事件标记 shapeName
}
label.textGroup = textGroup;
/** 将label分组 */
if (anchorPoint.x < center.x) {
label._side = 'left';
this_1.halves[0].push(label);
}
else {
label._side = 'right';
this_1.halves[1].push(label);
}
};
var this_1 = this;
// tslint:disable-next-line: prefer-for-of
for (var idx = 0; idx < data.length; idx++) {
_loop_1(idx);
}
/** 绘制label */
var _drawnLabels = [];
var maxCountForOneSide = Math.floor(height / this.config.lineHeight);
_.each(this.halves, function (half) {
if (half.length > maxCountForOneSide) {
half.splice(maxCountForOneSide, half.length - maxCountForOneSide);
}
half.sort(function (a, b) {
return a.y - b.y;
});
_this._antiCollision(half);
});
this.view.get('canvas').draw();
};
SpiderLabel.prototype.clear = function () {
if (this.container) {
this.container.clear();
}
};
SpiderLabel.prototype._init = function () {
var _this = this;
this.view.on('beforerender', function () {
_this.clear();
});
this.view.on('afterrender', function () {
_this.draw();
});
};
SpiderLabel.prototype._antiCollision = function (half) {
var _this = this;
var coord = this.view.get('coord');
var canvasHeight = coord.getHeight();
var center = coord.center;
var radius = coord.getRadius();
var startY = center.y - radius - INFLECTION_OFFSET - this.config.lineHeight;
var overlapping = true;
var totalH = canvasHeight;
var i;
var maxY = 0;
var minY = Number.MIN_VALUE;
var maxLabelWidth = 0;
var boxes = half.map(function (label) {
var labelY = label.y;
if (labelY > maxY) {
maxY = labelY;
}
if (labelY < minY) {
minY = labelY;
}
var textGroup = label.textGroup;
var labelWidth = textGroup.getBBox().width;
if (labelWidth >= maxLabelWidth) {
maxLabelWidth = labelWidth;
}
return {
size: _this.config.lineHeight,
targets: [labelY - startY],
};
});
if (maxY - startY > totalH) {
totalH = maxY - startY;
}
var iteratorBoxed = function (items) {
items.forEach(function (box) {
var target = (Math.min.apply(minY, box.targets) + Math.max.apply(minY, box.targets)) / 2;
box.pos = Math.min(Math.max(minY, target - box.size / 2), totalH - box.size);
});
};
while (overlapping) {
iteratorBoxed(boxes);
// detect overlapping and join boxes
overlapping = false;
i = boxes.length;
while (i--) {
if (i > 0) {
var previousBox = boxes[i - 1];
var box = boxes[i];
if (previousBox.pos + previousBox.size > box.pos) {
// overlapping
previousBox.size += box.size;
previousBox.targets = previousBox.targets.concat(box.targets);
// overflow, shift up
if (previousBox.pos + previousBox.size > totalH) {
previousBox.pos = totalH - previousBox.size;
}
boxes.splice(i, 1); // removing box
overlapping = true;
}
}
}
}
i = 0;
boxes.forEach(function (b) {
var posInCompositeBox = startY; // middle of the label
b.targets.forEach(function () {
half[i].y = b.pos + posInCompositeBox + _this.config.lineHeight / 2;
posInCompositeBox += _this.config.lineHeight;
i++;
});
});
var drawnLabels = [];
half.forEach(function (label) {
var textGroup = _this._drawLabel(label);
_this.container.add(textGroup);
_this._drawLabelLine(label, maxLabelWidth);
drawnLabels.push(textGroup);
});
};
SpiderLabel.prototype._drawLabel = function (label) {
var coord = this.view.get('coord');
var center = coord.getCenter();
var radius = coord.getRadius();
var width = this.width;
var y = label.y, textGroup = label.textGroup;
var children = textGroup.get('children');
var x_dir = label._side === 'left' ? 1 : -1;
var textAttrs = {
textAlign: label._side === 'left' ? 'right' : 'left',
x: label._side === 'left'
? center.x - radius - this.config.sidePadding
: center.x + radius + this.config.sidePadding,
};
if (this.offsetX) {
textAttrs.x += this.offsetX * x_dir;
}
children.forEach(function (child) {
var offsetY = child.get('offsetY');
var yPosition = y + offsetY;
child.attr(textAttrs);
child.attr('y', yPosition);
});
return textGroup;
};
SpiderLabel.prototype._drawLabelLine = function (label, maxLabelWidth) {
var _anchor = [label._anchor.x, label._anchor.y];
var _inflection = [label._inflection.x, label._inflection.y];
var fill = label.fill, y = label.y, textGroup = label.textGroup;
if (!textGroup)
return;
var lastPoint = [label._side === 'left' ? textGroup.getBBox().maxX + 4 : textGroup.getBBox().minX - 4, y];
var points = [_anchor, _inflection, lastPoint];
if (_inflection[1] !== y) {
// 展示全部文本文本位置做过调整
if (_inflection[1] < y) {
// 文本被调整下去了,则添加拐点连接线
var point1 = _inflection;
var leftPoint = lastPoint[0] + maxLabelWidth + ADJUSTOFFSET;
var rightPoint = lastPoint[0] - maxLabelWidth - ADJUSTOFFSET;
var point2 = [label._side === 'left' ? leftPoint : rightPoint, _inflection[1]];
var point3 = [
label._side === 'left' ? lastPoint[0] + maxLabelWidth : lastPoint[0] - maxLabelWidth,
lastPoint[1],
];
points = [_anchor, point1, point2, point3, lastPoint];
if ((label._side === 'right' && point2[0] < point1[0]) || (label._side === 'left' && point2[0] > point1[0])) {
points = [_anchor, point3, lastPoint];
}
}
else {
points = [_anchor, [_inflection[0], y], lastPoint];
}
}
var path = [];
for (var i = 0; i < points.length; i++) {
var p = points[i];
var starter = 'L';
if (i === 0) {
starter = 'M';
}
path.push([starter, p[0], p[1]]);
}
this.container.addShape('path', {
attrs: {
path: path,
lineWidth: this.config.lineWidth,
stroke: this.config.lineStroke,
},
});
// 绘制锚点
// this.container.addShape('circle', {
// attrs: {
// x: _anchor[0],
// y: _anchor[1],
// r: this.config.anchorSize,
// fill,
// },
// });
};
SpiderLabel.prototype._adjustConfig = function (config) {
if (config.text.fontSize) {
config.lineHeight = config.text.fontSize * 3;
}
};
return SpiderLabel;
}());
exports.default = SpiderLabel;
//# sourceMappingURL=spider-label.js.map