601 lines
16 KiB
Java
601 lines
16 KiB
Java
var _require = require('../../renderer'),
|
||
Group = _require.Group;
|
||
|
||
var _require2 = require('@antv/component/lib'),
|
||
Label = _require2.Label; // const visualCenter = require('@antv/component/lib/label/utils/visual-center');
|
||
|
||
|
||
var Global = require('../../global');
|
||
|
||
var Util = require('../../util');
|
||
|
||
var IGNORE_ARR = ['line', 'point', 'path'];
|
||
var ORIGIN = '_origin';
|
||
|
||
function avg(arr) {
|
||
var sum = 0;
|
||
Util.each(arr, function (value) {
|
||
sum += value;
|
||
});
|
||
return sum / arr.length;
|
||
} // 计算多边形重心: https://en.wikipedia.org/wiki/Centroid#Of_a_polygon
|
||
|
||
|
||
function getCentroid(xs, ys) {
|
||
if (Util.isNumber(xs) && Util.isNumber(ys)) {
|
||
return [xs, ys];
|
||
}
|
||
|
||
var i = -1,
|
||
x = 0,
|
||
y = 0;
|
||
var former,
|
||
current = xs.length - 1;
|
||
var diff,
|
||
k = 0;
|
||
|
||
while (++i < xs.length) {
|
||
former = current;
|
||
current = i;
|
||
k += diff = xs[former] * ys[current] - xs[current] * ys[former];
|
||
x += (xs[former] + xs[current]) * diff;
|
||
y += (ys[former] + ys[current]) * diff;
|
||
}
|
||
|
||
k *= 3;
|
||
return [x / k, y / k];
|
||
}
|
||
|
||
var GeomLabels = function GeomLabels(cfg) {
|
||
GeomLabels.superclass.constructor.call(this, cfg);
|
||
};
|
||
|
||
Util.extend(GeomLabels, Group);
|
||
Util.augment(GeomLabels, {
|
||
getDefaultCfg: function getDefaultCfg() {
|
||
return {
|
||
label: Global.label,
|
||
|
||
/**
|
||
* 用户传入的文本配置信息
|
||
* @type {Object}
|
||
*/
|
||
labelCfg: null,
|
||
|
||
/**
|
||
* 所在的坐标系
|
||
* @type {Object}
|
||
*/
|
||
coord: null,
|
||
|
||
/**
|
||
* 图表的类型
|
||
* @type {String}
|
||
*/
|
||
geomType: null,
|
||
zIndex: 6
|
||
};
|
||
},
|
||
_renderUI: function _renderUI() {
|
||
GeomLabels.superclass._renderUI.call(this);
|
||
|
||
this.initLabelsCfg();
|
||
var labelsGroup = this.addGroup();
|
||
var lineGroup = this.addGroup({
|
||
elCls: 'x-line-group'
|
||
});
|
||
var labelRenderer = this.get('labelRenderer');
|
||
this.set('labelsGroup', labelsGroup);
|
||
this.set('lineGroup', lineGroup);
|
||
this.get('labelRenderer').set('group', labelsGroup);
|
||
labelRenderer.set('group', labelsGroup);
|
||
labelRenderer.set('lineGroup', lineGroup);
|
||
},
|
||
// 初始化labels的配置项
|
||
initLabelsCfg: function initLabelsCfg() {
|
||
var self = this;
|
||
var labelRenderer = new Label();
|
||
var labels = self.getDefaultLabelCfg();
|
||
var labelCfg = self.get('labelCfg'); // Util.merge(labels, labelCfg.cfg);
|
||
|
||
Util.deepMix(labels, labelCfg.globalCfg || labelCfg.cfg);
|
||
labelRenderer.set('config', false);
|
||
|
||
if (labels.labelLine) {
|
||
labelRenderer.set('labelLine', labels.labelLine);
|
||
}
|
||
|
||
labelRenderer.set('coord', self.get('coord'));
|
||
this.set('labelRenderer', labelRenderer);
|
||
self.set('label', labels);
|
||
},
|
||
|
||
/**
|
||
* @protected
|
||
* 默认的文本样式
|
||
* @return {Object} default label config
|
||
*/
|
||
getDefaultLabelCfg: function getDefaultLabelCfg() {
|
||
var self = this;
|
||
var labelCfg = self.get('labelCfg').cfg || self.get('labelCfg').globalCfg;
|
||
var geomType = self.get('geomType');
|
||
var viewTheme = self.get('viewTheme') || Global;
|
||
|
||
if (geomType === 'polygon' || labelCfg && labelCfg.offset < 0 && Util.indexOf(IGNORE_ARR, geomType) === -1) {
|
||
return Util.deepMix({}, self.get('label'), viewTheme.innerLabels, labelCfg);
|
||
}
|
||
|
||
return Util.deepMix({}, self.get('label'), viewTheme.label, labelCfg);
|
||
},
|
||
|
||
/**
|
||
* @protected
|
||
* 获取labels
|
||
* @param {Array} points points
|
||
* @param {Array} shapes shapes
|
||
* @return {Array} label items
|
||
*/
|
||
getLabelsItems: function getLabelsItems(points, shapes) {
|
||
var self = this;
|
||
var items = [];
|
||
var geom = self.get('geom');
|
||
var coord = self.get('coord');
|
||
|
||
self._getLabelCfgs(points, shapes);
|
||
|
||
var labelCfg = self.get('labelItemCfgs'); // 获取label相关的x,y的值,获取具体的x,y,防止存在数组
|
||
|
||
Util.each(points, function (point, i) {
|
||
var origin = point[ORIGIN];
|
||
var label = labelCfg[i];
|
||
|
||
if (!label) {
|
||
items.push(null);
|
||
return;
|
||
}
|
||
|
||
if (!Util.isArray(label.text)) {
|
||
label.text = [label.text];
|
||
}
|
||
|
||
var total = label.text.length;
|
||
Util.each(label.text, function (sub, subIndex) {
|
||
if (Util.isNil(sub) || sub === '') {
|
||
items.push(null);
|
||
return;
|
||
}
|
||
|
||
var obj = self.getLabelPoint(label, point, subIndex);
|
||
obj = Util.mix({}, label, obj);
|
||
|
||
if (!obj.textAlign) {
|
||
obj.textAlign = self.getLabelAlign(obj, subIndex, total);
|
||
}
|
||
|
||
if (geom) {
|
||
obj._id = geom._getShapeId(origin) + '-glabel-' + subIndex + '-' + obj.text;
|
||
}
|
||
|
||
obj.coord = coord;
|
||
items.push(obj);
|
||
});
|
||
});
|
||
return items;
|
||
},
|
||
|
||
/* /!*
|
||
* @protected
|
||
* 如果发生冲突则会调整文本的位置
|
||
* @param {Array} items 文本的集合
|
||
* @param {Array} shapes 关联形状
|
||
* @return {Array} adjusted items
|
||
*!/
|
||
adjustItems(items, shapes) {
|
||
// 多边形shape的label位于其可视中心
|
||
if (this.get('geomType') === 'polygon') {
|
||
let index,
|
||
shape,
|
||
path,
|
||
center,
|
||
points;
|
||
Util.each(items, (item, i) => {
|
||
if (!item) return;
|
||
shape = shapes[ i ];
|
||
path = shape.attr('path');
|
||
points = [[]];
|
||
index = 0;
|
||
path.forEach((segment, i) => {
|
||
if (segment[ 0 ] === 'z' || segment[ 0 ] === 'Z' && i !== path.length - 1) {
|
||
points.push([]);
|
||
index += 1;
|
||
}
|
||
if (segment.length === 3) {
|
||
points[ index ].push([ segment[ 1 ], segment[ 2 ] ]);
|
||
}
|
||
});
|
||
center = visualCenter(points);
|
||
item.x = center.x;
|
||
item.y = center.y;
|
||
});
|
||
}
|
||
return items;
|
||
}
|
||
*/
|
||
adjustItems: function adjustItems(items) {
|
||
Util.each(items, function (item) {
|
||
if (!item) {
|
||
return;
|
||
}
|
||
|
||
if (item.offsetX) {
|
||
item.x += item.offsetX;
|
||
}
|
||
|
||
if (item.offsetY) {
|
||
item.y += item.offsetY;
|
||
}
|
||
});
|
||
return items;
|
||
},
|
||
|
||
/**
|
||
* drawing lines to labels
|
||
* @param {Array} items labels
|
||
* @param {Object} labelLine configuration for label lines
|
||
*/
|
||
drawLines: function drawLines(items) {
|
||
var self = this;
|
||
Util.each(items, function (point) {
|
||
if (!point) {
|
||
return;
|
||
}
|
||
|
||
if (point.offset > 0) {
|
||
self.lineToLabel(point);
|
||
}
|
||
});
|
||
},
|
||
// 定义连接线
|
||
lineToLabel: function lineToLabel() {},
|
||
|
||
/**
|
||
* @protected
|
||
* 获取文本的位置信息
|
||
* @param {Array} labelCfg labels
|
||
* @param {Object} point point
|
||
* @param {Number} index index
|
||
* @return {Object} point
|
||
*/
|
||
getLabelPoint: function getLabelPoint(labelCfg, point, index) {
|
||
var self = this;
|
||
var coord = self.get('coord');
|
||
var total = labelCfg.text.length;
|
||
|
||
function getDimValue(value, idx) {
|
||
if (Util.isArray(value)) {
|
||
if (labelCfg.text.length === 1) {
|
||
// 如果仅一个label,多个y,取最后一个y
|
||
if (value.length <= 2) {
|
||
value = value[value.length - 1]; // value = value[0];
|
||
} else {
|
||
value = avg(value);
|
||
}
|
||
} else {
|
||
value = value[idx];
|
||
}
|
||
}
|
||
|
||
return value;
|
||
}
|
||
|
||
var label = {
|
||
text: labelCfg.text[index]
|
||
}; // 多边形场景,多用于地图
|
||
|
||
if (point && this.get('geomType') === 'polygon') {
|
||
var centroid = getCentroid(point.x, point.y); // 多边形的场景也有 x 和 y 只是数字的情况,譬如当 x 和 y 都是分类字段的时候 @see #1184
|
||
|
||
label.x = centroid[0];
|
||
label.y = centroid[1];
|
||
} else {
|
||
label.x = getDimValue(point.x, index);
|
||
label.y = getDimValue(point.y, index);
|
||
} // get nearest point of the shape as the label line start point
|
||
|
||
|
||
if (point && point.nextPoints && (point.shape === 'funnel' || point.shape === 'pyramid')) {
|
||
var maxX = -Infinity;
|
||
point.nextPoints.forEach(function (p) {
|
||
p = coord.convert(p);
|
||
|
||
if (p.x > maxX) {
|
||
maxX = p.x;
|
||
}
|
||
});
|
||
label.x = (label.x + maxX) / 2;
|
||
} // sharp edge of the pyramid
|
||
|
||
|
||
if (point.shape === 'pyramid' && !point.nextPoints && point.points) {
|
||
point.points.forEach(function (p) {
|
||
p = coord.convert(p);
|
||
|
||
if (Util.isArray(p.x) && !point.x.includes(p.x) || Util.isNumber(p.x) && point.x !== p.x) {
|
||
label.x = (label.x + p.x) / 2;
|
||
}
|
||
});
|
||
}
|
||
|
||
if (labelCfg.position) {
|
||
self.setLabelPosition(label, point, index, labelCfg.position);
|
||
}
|
||
|
||
var offsetPoint = self.getLabelOffset(labelCfg, index, total);
|
||
|
||
if (labelCfg.offsetX) {
|
||
offsetPoint.x += labelCfg.offsetX;
|
||
}
|
||
|
||
if (labelCfg.offsetY) {
|
||
offsetPoint.y += labelCfg.offsetY;
|
||
}
|
||
|
||
self.transLabelPoint(label);
|
||
label.start = {
|
||
x: label.x,
|
||
y: label.y
|
||
};
|
||
label.x += offsetPoint.x;
|
||
label.y += offsetPoint.y;
|
||
label.color = point.color;
|
||
return label;
|
||
},
|
||
setLabelPosition: function setLabelPosition() {},
|
||
transLabelPoint: function transLabelPoint(point) {
|
||
var self = this;
|
||
var coord = self.get('coord');
|
||
var tmpPoint = coord.applyMatrix(point.x, point.y, 1);
|
||
point.x = tmpPoint[0];
|
||
point.y = tmpPoint[1];
|
||
},
|
||
getOffsetVector: function getOffsetVector(point) {
|
||
var self = this;
|
||
var offset = point.offset || 0;
|
||
var coord = self.get('coord');
|
||
var vector;
|
||
|
||
if (coord.isTransposed) {
|
||
// 如果x,y翻转,则偏移x
|
||
vector = coord.applyMatrix(offset, 0);
|
||
} else {
|
||
// 否则,偏转y
|
||
vector = coord.applyMatrix(0, offset);
|
||
}
|
||
|
||
return vector;
|
||
},
|
||
// 获取默认的偏移量
|
||
getDefaultOffset: function getDefaultOffset(point) {
|
||
var self = this;
|
||
var offset = 0;
|
||
var coord = self.get('coord');
|
||
var vector = self.getOffsetVector(point);
|
||
|
||
if (coord.isTransposed) {
|
||
// 如果x,y翻转,则偏移x
|
||
offset = vector[0];
|
||
} else {
|
||
// 否则,偏转y
|
||
offset = vector[1];
|
||
}
|
||
|
||
var yScale = this.get('yScale');
|
||
|
||
if (yScale && point.point) {
|
||
// 仅考虑 y 单值的情况,多值的情况在这里不考虑
|
||
var yValue = point.point[yScale.field];
|
||
|
||
if (yValue < 0) {
|
||
offset = offset * -1; // 如果 y 值是负值,则反向
|
||
}
|
||
}
|
||
|
||
return offset;
|
||
},
|
||
// 获取文本的偏移位置,x,y
|
||
getLabelOffset: function getLabelOffset(point, index, total) {
|
||
var self = this;
|
||
var offset = self.getDefaultOffset(point);
|
||
var coord = self.get('coord');
|
||
var transposed = coord.isTransposed;
|
||
var yField = transposed ? 'x' : 'y';
|
||
var factor = transposed ? 1 : -1; // y 方向上越大,像素的坐标越小,所以transposed时将系数变成
|
||
|
||
var offsetPoint = {
|
||
x: 0,
|
||
y: 0
|
||
};
|
||
|
||
if (index > 0 || total === 1) {
|
||
// 判断是否小于0
|
||
offsetPoint[yField] = offset * factor;
|
||
} else {
|
||
offsetPoint[yField] = offset * factor * -1;
|
||
}
|
||
|
||
return offsetPoint;
|
||
},
|
||
getLabelAlign: function getLabelAlign(point, index, total) {
|
||
var self = this;
|
||
var align = 'center';
|
||
var coord = self.get('coord');
|
||
|
||
if (coord.isTransposed) {
|
||
var offset = self.getDefaultOffset(point); // var vector = coord.applyMatrix(offset,0);
|
||
|
||
if (offset < 0) {
|
||
align = 'right';
|
||
} else if (offset === 0) {
|
||
align = 'center';
|
||
} else {
|
||
align = 'left';
|
||
}
|
||
|
||
if (total > 1 && index === 0) {
|
||
if (align === 'right') {
|
||
align = 'left';
|
||
} else if (align === 'left') {
|
||
align = 'right';
|
||
}
|
||
}
|
||
}
|
||
|
||
return align;
|
||
},
|
||
_getLabelValue: function _getLabelValue(origin, scales) {
|
||
if (!Util.isArray(scales)) {
|
||
scales = [scales];
|
||
}
|
||
|
||
var text = [];
|
||
Util.each(scales, function (scale) {
|
||
var value = origin[scale.field];
|
||
|
||
if (Util.isArray(value)) {
|
||
var tmp = [];
|
||
Util.each(value, function (subVal) {
|
||
tmp.push(scale.getText(subVal));
|
||
});
|
||
value = tmp;
|
||
} else {
|
||
value = scale.getText(value);
|
||
}
|
||
|
||
if (Util.isNil(value) || value === '') {
|
||
text.push(null);
|
||
}
|
||
|
||
text.push(value);
|
||
});
|
||
return text;
|
||
},
|
||
// 获取每个label的配置
|
||
_getLabelCfgs: function _getLabelCfgs(points) {
|
||
var self = this;
|
||
var labelCfg = this.get('labelCfg');
|
||
var scales = labelCfg.scales;
|
||
var defaultCfg = this.get('label');
|
||
var viewTheme = self.get('viewTheme') || Global;
|
||
var cfgs = [];
|
||
|
||
if (labelCfg.globalCfg && labelCfg.globalCfg.type) {
|
||
self.set('type', labelCfg.globalCfg.type);
|
||
}
|
||
|
||
Util.each(points, function (point, i) {
|
||
var cfg = {};
|
||
var origin = point[ORIGIN];
|
||
|
||
var originText = self._getLabelValue(origin, scales);
|
||
|
||
if (labelCfg.callback) {
|
||
// callback中应使用原始数据,而不是数据字符串
|
||
var originValues = scales.map(function (scale) {
|
||
return origin[scale.field];
|
||
}); // 将point信息以及index信息也返回,方便能够根据point以及index,返回不同的配置
|
||
|
||
cfg = labelCfg.callback.apply(null, [].concat(originValues, [point, i]));
|
||
}
|
||
|
||
if (!cfg && cfg !== 0) {
|
||
cfgs.push(null);
|
||
return;
|
||
}
|
||
|
||
if (Util.isString(cfg) || Util.isNumber(cfg)) {
|
||
cfg = {
|
||
text: cfg
|
||
};
|
||
} else {
|
||
cfg.text = cfg.content || originText[0];
|
||
delete cfg.content;
|
||
}
|
||
|
||
cfg = Util.mix({}, defaultCfg, labelCfg.globalCfg || {}, cfg); // 兼容旧的源数据写在item.point中
|
||
|
||
point.point = origin;
|
||
cfg.point = origin;
|
||
|
||
if (cfg.htmlTemplate) {
|
||
cfg.useHtml = true;
|
||
cfg.text = cfg.htmlTemplate.call(null, cfg.text, point, i);
|
||
delete cfg.htmlTemplate;
|
||
}
|
||
|
||
if (cfg.formatter) {
|
||
cfg.text = cfg.formatter.call(null, cfg.text, point, i);
|
||
delete cfg.formatter;
|
||
}
|
||
|
||
if (cfg.label) {
|
||
// 兼容有些直接写在labelCfg.label的配置
|
||
var label = cfg.label;
|
||
delete cfg.label;
|
||
cfg = Util.mix(cfg, label);
|
||
}
|
||
|
||
if (cfg.textStyle) {
|
||
// 兼容旧写法,globalCfg的offset优先级高
|
||
delete cfg.textStyle.offset;
|
||
var textStyle = cfg.textStyle;
|
||
|
||
if (Util.isFunction(textStyle)) {
|
||
cfg.textStyle = textStyle.call(null, cfg.text, point, i);
|
||
}
|
||
}
|
||
|
||
if (cfg.labelLine) {
|
||
cfg.labelLine = Util.mix({}, defaultCfg.labelLine, cfg.labelLine);
|
||
} // 因为 defaultCfg.textStyle 有可能是函数,所以这里可能没有把主题的 label 样式合进来
|
||
|
||
|
||
cfg.textStyle = Util.mix({}, defaultCfg.textStyle, viewTheme.label.textStyle, cfg.textStyle);
|
||
delete cfg.items;
|
||
cfgs.push(cfg);
|
||
});
|
||
this.set('labelItemCfgs', cfgs);
|
||
},
|
||
showLabels: function showLabels(points, shapes) {
|
||
var self = this;
|
||
var labelRenderer = self.get('labelRenderer');
|
||
var items = self.getLabelsItems(points, shapes);
|
||
shapes = [].concat(shapes);
|
||
var type = self.get('type');
|
||
items = self.adjustItems(items, shapes);
|
||
self.drawLines(items);
|
||
labelRenderer.set('items', items.filter(function (item, i) {
|
||
if (!item) {
|
||
shapes.splice(i, 1);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}));
|
||
|
||
if (type) {
|
||
labelRenderer.set('shapes', shapes);
|
||
labelRenderer.set('type', type);
|
||
labelRenderer.set('points', points);
|
||
}
|
||
|
||
labelRenderer.set('canvas', this.get('canvas'));
|
||
labelRenderer.draw();
|
||
},
|
||
destroy: function destroy() {
|
||
this.get('labelRenderer').destroy(); // 清理文本
|
||
|
||
GeomLabels.superclass.destroy.call(this);
|
||
}
|
||
}); // Util.assign(GeomLabels.prototype, Labels.LabelslabelRenderer);
|
||
|
||
module.exports = GeomLabels; |