573 lines
14 KiB
JavaScript
573 lines
14 KiB
JavaScript
function _createSuper(Derived) { return function () { var Super = _getPrototypeOf(Derived), result; if (_isNativeReflectConstruct()) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
|
||
|
||
function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
|
||
|
||
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
|
||
|
||
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
|
||
|
||
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
|
||
|
||
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
|
||
|
||
var Util = require('../util');
|
||
|
||
var DomUtil = Util.DomUtil;
|
||
|
||
var Component = require('../component');
|
||
|
||
var positionAdjust = require('./utils/position-adjust');
|
||
|
||
var spirialAdjust = require('./utils/spiral-adjust');
|
||
|
||
var bboxAdjust = require('./utils/bbox-adjust');
|
||
|
||
var LAYOUTS = {
|
||
scatter: positionAdjust,
|
||
map: spirialAdjust,
|
||
treemap: bboxAdjust
|
||
};
|
||
|
||
var Label = /*#__PURE__*/function (_Component) {
|
||
_inheritsLoose(Label, _Component);
|
||
|
||
var _super = _createSuper(Label);
|
||
|
||
function Label() {
|
||
return _Component.apply(this, arguments) || this;
|
||
}
|
||
|
||
var _proto = Label.prototype;
|
||
|
||
_proto.getDefaultCfg = function getDefaultCfg() {
|
||
var cfg = _Component.prototype.getDefaultCfg.call(this);
|
||
|
||
return Util.mix({}, cfg, {
|
||
name: 'label',
|
||
|
||
/**
|
||
* label类型
|
||
* @type {(String)}
|
||
*/
|
||
type: 'default',
|
||
|
||
/**
|
||
* 默认文本样式
|
||
* @type {Array}
|
||
*/
|
||
textStyle: null,
|
||
|
||
/**
|
||
* 文本显示格式化回调函数
|
||
* @type {Function}
|
||
*/
|
||
formatter: null,
|
||
|
||
/**
|
||
* 显示的文本集合
|
||
* @type {Array}
|
||
*/
|
||
items: null,
|
||
|
||
/**
|
||
* 是否使用html渲染label
|
||
* @type {String}
|
||
*/
|
||
useHtml: false,
|
||
|
||
/**
|
||
* html 渲染时用的容器的模板,必须存在 class = "g-labels"
|
||
* @type {String}
|
||
*/
|
||
containerTpl: '<div class="g-labels" style="position:absolute;top:0;left:0;"></div>',
|
||
|
||
/**
|
||
* html 渲染时单个 label 的模板,必须存在 class = "g-label"
|
||
* @type {String}
|
||
*/
|
||
itemTpl: '<div class="g-label" style="position:absolute;">{text}</div>',
|
||
|
||
/**
|
||
* label牵引线定义
|
||
* @type {String || Object}
|
||
*/
|
||
labelLine: false,
|
||
|
||
/**
|
||
* label牵引线容器
|
||
* @type Object
|
||
*/
|
||
lineGroup: null,
|
||
|
||
/**
|
||
* 需添加label的shape
|
||
* @type Object
|
||
*/
|
||
shapes: null,
|
||
|
||
/**
|
||
* 默认为true。为false时指定直接用items渲染文本,不进行config
|
||
* @type Object
|
||
*/
|
||
config: true,
|
||
|
||
/**
|
||
* 是否进行拾取
|
||
* @type Object
|
||
*/
|
||
capture: true
|
||
});
|
||
}
|
||
/*
|
||
* 清空label容器
|
||
*/
|
||
;
|
||
|
||
_proto.clear = function clear() {
|
||
var group = this.get('group');
|
||
var container = this.get('container');
|
||
|
||
if (group && !group.get('destroyed')) {
|
||
group.clear();
|
||
}
|
||
|
||
if (container) {
|
||
container.innerHTML = '';
|
||
}
|
||
|
||
_Component.prototype.clear.call(this);
|
||
}
|
||
/**
|
||
* 销毁group
|
||
*/
|
||
;
|
||
|
||
_proto.destroy = function destroy() {
|
||
var group = this.get('group');
|
||
var container = this.get('container');
|
||
|
||
if (!group.destroy) {
|
||
group.destroy();
|
||
}
|
||
|
||
if (container) {
|
||
container.parentNode && container.parentNode.removeChild(container);
|
||
}
|
||
|
||
_Component.prototype.destroy.call(this); // 要最后调用 super.destroy 否则 get 属性会无效
|
||
|
||
}
|
||
/**
|
||
* label绘制全过程
|
||
*/
|
||
;
|
||
|
||
_proto.render = function render() {
|
||
this.clear();
|
||
|
||
this._init();
|
||
|
||
this.beforeDraw();
|
||
this.draw();
|
||
this.afterDraw();
|
||
};
|
||
|
||
_proto._dryDraw = function _dryDraw() {
|
||
var self = this;
|
||
var items = self.get('items');
|
||
var children = self.getLabels();
|
||
var count = children.length;
|
||
Util.each(items, function (item, index) {
|
||
if (index < count) {
|
||
var label = children[index];
|
||
self.changeLabel(label, item);
|
||
} else {
|
||
var labelShape = self._addLabel(item, index);
|
||
|
||
if (labelShape) {
|
||
labelShape._id = item._id;
|
||
labelShape.set('coord', item.coord);
|
||
}
|
||
}
|
||
});
|
||
|
||
for (var i = count - 1; i >= items.length; i--) {
|
||
children[i].remove();
|
||
}
|
||
|
||
self._adjustLabels();
|
||
|
||
if (self.get('labelLine') || !self.get('config')) {
|
||
self.drawLines();
|
||
}
|
||
}
|
||
/**
|
||
* 更新label
|
||
* 1. 将items与group中的children对比,更新/新增/删除labels
|
||
* 2. labels布局优化
|
||
* 3. 画label连接线
|
||
* 4. 绘制到画布
|
||
*/
|
||
;
|
||
|
||
_proto.draw = function draw() {
|
||
this._dryDraw();
|
||
|
||
this.get('canvas').draw();
|
||
}
|
||
/*
|
||
* 更新label
|
||
* oldLabel shape或label dom
|
||
* newLabel label data
|
||
* index items中的下标
|
||
*/
|
||
;
|
||
|
||
_proto.changeLabel = function changeLabel(oldLabel, newLabel) {
|
||
if (!oldLabel) {
|
||
return;
|
||
}
|
||
|
||
if (oldLabel.tagName) {
|
||
var node = this._createDom(newLabel);
|
||
|
||
oldLabel.innerHTML = node.innerHTML;
|
||
|
||
this._setCustomPosition(newLabel, oldLabel);
|
||
} else {
|
||
oldLabel._id = newLabel._id;
|
||
oldLabel.attr('text', newLabel.text);
|
||
|
||
if (oldLabel.attr('x') !== newLabel.x || oldLabel.attr('y') !== newLabel.y) {
|
||
oldLabel.resetMatrix();
|
||
|
||
if (newLabel.textStyle.rotate) {
|
||
oldLabel.rotateAtStart(newLabel.textStyle.rotate);
|
||
delete newLabel.textStyle.rotate;
|
||
}
|
||
|
||
oldLabel.attr(newLabel);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* 显示label
|
||
*/
|
||
;
|
||
|
||
_proto.show = function show() {
|
||
var group = this.get('group');
|
||
var container = this.get('container');
|
||
|
||
if (group) {
|
||
group.show();
|
||
}
|
||
|
||
if (container) {
|
||
container.style.opacity = 1;
|
||
}
|
||
}
|
||
/**
|
||
* 隐藏label
|
||
*/
|
||
;
|
||
|
||
_proto.hide = function hide() {
|
||
var group = this.get('group');
|
||
var container = this.get('container');
|
||
|
||
if (group) {
|
||
group.hide();
|
||
}
|
||
|
||
if (container) {
|
||
container.style.opacity = 0;
|
||
}
|
||
}
|
||
/**
|
||
* 画label连接线
|
||
*/
|
||
;
|
||
|
||
_proto.drawLines = function drawLines() {
|
||
var self = this;
|
||
var lineStyle = self.get('labelLine');
|
||
|
||
if (typeof lineStyle === 'boolean') {
|
||
self.set('labelLine', {});
|
||
}
|
||
|
||
var lineGroup = self.get('lineGroup');
|
||
|
||
if (!lineGroup || lineGroup.get('destroyed')) {
|
||
lineGroup = self.get('group').addGroup({
|
||
elCls: 'x-line-group'
|
||
});
|
||
self.set('lineGroup', lineGroup);
|
||
} else {
|
||
lineGroup.clear();
|
||
}
|
||
|
||
Util.each(self.get('items'), function (label) {
|
||
self.lineToLabel(label, lineGroup);
|
||
});
|
||
};
|
||
|
||
_proto.lineToLabel = function lineToLabel(label, lineGroup) {
|
||
var self = this;
|
||
|
||
if (!self.get('config') && !label.labelLine) {
|
||
return;
|
||
}
|
||
|
||
var lineStyle = label.labelLine || self.get('labelLine');
|
||
var capture = typeof label.capture === 'undefined' ? self.get('capture') : label.capture;
|
||
var path = lineStyle.path;
|
||
|
||
if (path && Util.isFunction(lineStyle.path)) {
|
||
path = lineStyle.path(label);
|
||
}
|
||
|
||
if (!path) {
|
||
var start = label.start || {
|
||
x: label.x - label._offset.x,
|
||
y: label.y - label._offset.y
|
||
};
|
||
path = [['M', start.x, start.y], ['L', label.x, label.y]];
|
||
}
|
||
|
||
var stroke = label.color;
|
||
|
||
if (!stroke) {
|
||
if (label.textStyle && label.textStyle.fill) {
|
||
stroke = label.textStyle.fill;
|
||
} else {
|
||
stroke = '#000';
|
||
}
|
||
}
|
||
|
||
var lineShape = lineGroup.addShape('path', {
|
||
attrs: Util.mix({
|
||
path: path,
|
||
fill: null,
|
||
stroke: stroke
|
||
}, lineStyle),
|
||
capture: capture
|
||
}); // label 对应线的动画关闭
|
||
|
||
lineShape.name = self.get('name'); // generate labelLine id according to label id
|
||
|
||
lineShape._id = label._id && label._id.replace('glabel', 'glabelline');
|
||
lineShape.set('coord', self.get('coord'));
|
||
} // 根据type对label布局
|
||
;
|
||
|
||
_proto._adjustLabels = function _adjustLabels() {
|
||
var self = this;
|
||
var type = self.get('type');
|
||
var labels = self.getLabels();
|
||
var shapes = self.get('shapes');
|
||
var layout = LAYOUTS[type];
|
||
|
||
if (type === 'default' || !layout) {
|
||
return;
|
||
}
|
||
|
||
layout(labels, shapes);
|
||
}
|
||
/**
|
||
* 获取当前所有label实例
|
||
* @return {Array} 当前label实例
|
||
*/
|
||
;
|
||
|
||
_proto.getLabels = function getLabels() {
|
||
var container = this.get('container');
|
||
|
||
if (container) {
|
||
return Util.toArray(container.childNodes);
|
||
}
|
||
|
||
return this.get('group').get('children');
|
||
} // 先计算label的所有配置项,然后生成label实例
|
||
;
|
||
|
||
_proto._addLabel = function _addLabel(item, index) {
|
||
var cfg = item;
|
||
|
||
if (this.get('config')) {
|
||
cfg = this._getLabelCfg(item, index);
|
||
}
|
||
|
||
return this._createText(cfg);
|
||
};
|
||
|
||
_proto._getLabelCfg = function _getLabelCfg(item, index) {
|
||
var textStyle = this.get('textStyle') || {};
|
||
var formatter = this.get('formatter');
|
||
var htmlTemplate = this.get('htmlTemplate');
|
||
|
||
if (!Util.isObject(item)) {
|
||
var tmp = item;
|
||
item = {};
|
||
item.text = tmp;
|
||
}
|
||
|
||
if (Util.isFunction(textStyle)) {
|
||
textStyle = textStyle(item.text, item, index);
|
||
}
|
||
|
||
if (formatter) {
|
||
item.text = formatter(item.text, item, index);
|
||
}
|
||
|
||
if (htmlTemplate) {
|
||
item.useHtml = true;
|
||
|
||
if (Util.isFunction(htmlTemplate)) {
|
||
item.text = htmlTemplate(item.text, item, index);
|
||
}
|
||
}
|
||
|
||
if (Util.isNil(item.text)) {
|
||
item.text = '';
|
||
}
|
||
|
||
item.text = item.text + ''; // ? 为什么转换为字符串
|
||
|
||
var cfg = Util.mix({}, item, {
|
||
textStyle: textStyle
|
||
}, {
|
||
x: item.x || 0,
|
||
y: item.y || 0
|
||
});
|
||
return cfg;
|
||
}
|
||
/**
|
||
* label初始化,主要针对html容器
|
||
*/
|
||
;
|
||
|
||
_proto._init = function _init() {
|
||
if (!this.get('group')) {
|
||
var group = this.get('canvas').addGroup({
|
||
id: 'label-group'
|
||
});
|
||
this.set('group', group);
|
||
}
|
||
};
|
||
|
||
_proto.initHtmlContainer = function initHtmlContainer() {
|
||
var container = this.get('container');
|
||
|
||
if (!container) {
|
||
var containerTpl = this.get('containerTpl');
|
||
var wrapper = this.get('canvas').get('el').parentNode;
|
||
container = DomUtil.createDom(containerTpl);
|
||
wrapper.style.position = 'relative';
|
||
wrapper.appendChild(container);
|
||
this.set('container', container);
|
||
} else if (Util.isString(container)) {
|
||
container = document.getElementById(container);
|
||
|
||
if (container) {
|
||
this.set('container', container);
|
||
}
|
||
}
|
||
|
||
return container;
|
||
} // 分html dom和G shape两种情况生成label实例
|
||
;
|
||
|
||
_proto._createText = function _createText(config) {
|
||
// @2018-11-29 by blue.lb 这里由于使用delete导致之后的配置无法获取到point和rotate,出现问题,深拷贝一次比较好
|
||
var cfg = Util.deepMix({}, config);
|
||
var container = this.get('container');
|
||
var capture = typeof cfg.capture === 'undefined' ? this.get('capture') : cfg.capture;
|
||
var labelShape;
|
||
|
||
if (cfg.useHtml || cfg.htmlTemplate) {
|
||
if (!container) {
|
||
container = this.initHtmlContainer();
|
||
}
|
||
|
||
var node = this._createDom(cfg);
|
||
|
||
container.appendChild(node);
|
||
|
||
this._setCustomPosition(cfg, node);
|
||
} else {
|
||
var name = this.get('name');
|
||
var origin = cfg.point;
|
||
var group = this.get('group');
|
||
delete cfg.point; // 临时解决,否则影响动画
|
||
|
||
var rotate = cfg.rotate; // textStyle中的rotate虽然可以正常画出,但是在做动画的时候可能会导致动画异常。移出,在定义好shape后通过transform实现效果。
|
||
|
||
if (cfg.textStyle) {
|
||
if (cfg.textStyle.rotate) {
|
||
rotate = cfg.textStyle.rotate;
|
||
delete cfg.textStyle.rotate;
|
||
}
|
||
|
||
cfg = Util.mix({
|
||
x: cfg.x,
|
||
y: cfg.y,
|
||
textAlign: cfg.textAlign,
|
||
text: cfg.text
|
||
}, cfg.textStyle);
|
||
}
|
||
|
||
labelShape = group.addShape('text', {
|
||
attrs: cfg,
|
||
capture: capture
|
||
});
|
||
|
||
if (rotate) {
|
||
// rotate是用角度定义的,转换为弧度
|
||
if (Math.abs(rotate) > Math.PI * 2) {
|
||
rotate = rotate / 180 * Math.PI;
|
||
}
|
||
|
||
labelShape.transform([['t', -cfg.x, -cfg.y], ['r', rotate], ['t', cfg.x, cfg.y]]);
|
||
}
|
||
|
||
labelShape.setSilent('origin', origin || cfg);
|
||
labelShape.name = name; // 用于事件标注
|
||
|
||
this.get('appendInfo') && labelShape.setSilent('appendInfo', this.get('appendInfo'));
|
||
return labelShape;
|
||
}
|
||
};
|
||
|
||
_proto._createDom = function _createDom(cfg) {
|
||
var itemTpl = this.get('itemTpl');
|
||
var str = Util.substitute(itemTpl, {
|
||
text: cfg.text
|
||
});
|
||
return DomUtil.createDom(str);
|
||
} // 根据文本对齐方式确定dom位置
|
||
;
|
||
|
||
_proto._setCustomPosition = function _setCustomPosition(cfg, htmlDom) {
|
||
var textAlign = cfg.textAlign || 'left';
|
||
var top = cfg.y;
|
||
var left = cfg.x;
|
||
var width = DomUtil.getOuterWidth(htmlDom);
|
||
var height = DomUtil.getOuterHeight(htmlDom);
|
||
top = top - height / 2;
|
||
|
||
if (textAlign === 'center') {
|
||
left = left - width / 2;
|
||
} else if (textAlign === 'right') {
|
||
left = left - width;
|
||
}
|
||
|
||
htmlDom.style.top = parseInt(top, 10) + 'px';
|
||
htmlDom.style.left = parseInt(left, 10) + 'px';
|
||
};
|
||
|
||
return Label;
|
||
}(Component);
|
||
|
||
module.exports = Label; |