@antv/g6
Version:
graph visualization frame work
184 lines (173 loc) • 6.42 kB
JavaScript
/**
* @fileOverview 自定义节点和边的过程中,发现大量重复代码
* @author dxq613@gmail.com
*/
const Global = require('../global');
const Util = require('../util/index');
const { get, cloneDeep, merge } = require('lodash');
const CLS_SHAPE_SUFFIX = '-shape';
const CLS_LABEL_SUFFIX = '-label';
// 单个 shape 带有一个 label,共用这段代码
const SingleShape = {
// 默认样式及配置
options: {},
/**
* 用户自定义节点或边的样式,初始渲染时使用
* @override
* @param {Object} model 节点的配置项
*/
getCustomConfig(/* model */) {},
itemType: '', // node, edge, group, anchor 等
/**
* 绘制节点/边,包含文本
* @override
* @param {Object} cfg 节点的配置项
* @param {G.Group} group 节点的容器
* @return {G.Shape} 绘制的图形
*/
draw(cfg, group) {
const shape = this.drawShape(cfg, group);
shape.set('className', this.itemType + CLS_SHAPE_SUFFIX);
if (cfg.label) {
const label = this.drawLabel(cfg, group);
label.set('className', this.itemType + CLS_LABEL_SUFFIX);
}
return shape;
},
drawShape(/* cfg, group */) {
},
drawLabel(cfg, group) {
const customOptions = this.getCustomConfig(cfg) || {};
const { labelCfg: defaultLabelCfg } = this.options;
const { labelCfg: customLabelCfg } = customOptions;
const labelCfg = merge({}, defaultLabelCfg, customLabelCfg, cfg.labelCfg);
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
const label = group.addShape('text', {
attrs: labelStyle
});
return label;
},
getLabelStyleByPosition(/* cfg, labelCfg, group */) {
},
/**
* 获取文本的配置项
* @internal 用户创建和更新节点/边时,同时会更新文本
* @param {Object} cfg 节点的配置项
* @param {Object} labelCfg 文本的配置项
* @param {G.Group} group 父容器,label 的定位可能与图形相关
* @return {Object} 图形的配置项
*/
getLabelStyle(cfg, labelCfg, group) {
const calculateStyle = this.getLabelStyleByPosition(cfg, labelCfg, group);
calculateStyle.text = cfg.label;
const attrName = this.itemType + 'Label'; // 取 nodeLabel,edgeLabel 的配置项
const defaultStyle = Global[attrName] ? Global[attrName].style : null;
const labelStyle = Util.mix({}, defaultStyle, calculateStyle, labelCfg.style);
return labelStyle;
},
/**
* 获取图形的配置项
* @internal 仅在定义这一类节点使用,用户创建和更新节点
* @param {Object} cfg 节点的配置项
* @return {Object} 图形的配置项
*/
getShapeStyle(cfg) {
return cfg.style;
},
/**
* 更新节点,包含文本
* @override
* @param {Object} cfg 节点/边的配置项
* @param {G6.Item} item 节点/边
*/
update(cfg, item) {
const group = item.getContainer();
const shapeClassName = this.itemType + CLS_SHAPE_SUFFIX;
const shape = group.findByClassName(shapeClassName);
const shapeStyle = this.getShapeStyle(cfg);
shape && shape.attr(shapeStyle);
const labelClassName = this.itemType + CLS_LABEL_SUFFIX;
const label = group.findByClassName(labelClassName);
// 此时需要考虑之前是否绘制了 label 的场景存在三种情况
// 1. 更新时不需要 label,但是原先存在 label,此时需要删除
// 2. 更新时需要 label, 但是原先不存在,创建节点
// 3. 如果两者都存在,更新
if (!cfg.label) {
label && label.remove();
} else {
if (!label) {
const newLabel = this.drawLabel(cfg, group);
newLabel.set('className', labelClassName);
} else {
const labelCfg = cfg.labelCfg || {};
const labelStyle = this.getLabelStyle(cfg, labelCfg, group);
/**
* fixme g中shape的rotate是角度累加的,不是label的rotate想要的角度
* 由于现在label只有rotate操作,所以在更新label的时候如果style中有rotate就重置一下变换
* 后续会基于g的Text复写一个Label出来处理这一类问题
*/
label.resetMatrix();
label.attr(labelStyle);
}
}
},
/**
* 设置节点的状态,主要是交互状态,业务状态请在 draw 方法中实现
* 单图形的节点仅考虑 selected、active 状态,有其他状态需求的用户自己复写这个方法
* @override
* @param {String} name 状态名称
* @param {Object} value 状态值
* @param {G6.Item} item 节点
*/
setState(name, value, item) {
const shape = item.get('keyShape');
if (!shape) {
return;
}
const itemStateStyle = item.getStateStyle(name);
const stateStyle = this.getStateStyle(name, value, item);
const styles = merge({}, stateStyle, itemStateStyle);
if (value) { // 如果设置状态,在原本状态上叠加绘图属性
shape.attr(styles);
} else { // 取消状态时重置所有状态,依次叠加仍有的状态
const style = item.getCurrentStatesStyle();
// 如果默认状态下没有设置attr,在某状态下设置了,需要重置到没有设置的状态
Util.each(styles, (val, attr) => {
if (!style[attr]) {
style[attr] = null;
}
});
shape.attr(style);
}
},
/**
* 获取不同状态下的样式
*
* @param {string} name 状态名称
* @param {boolean} value 是否启用该状态
* @param {Item} item Node或Edge的实例
* @return {object} 样式
*/
getStateStyle(name, value, item) {
const model = item.getModel();
const customOptions = this.getCustomConfig(model) || {};
const { style: defaultStyle, stateStyles: defaultStateStyle } = this.options;
const { style: customStyle, stateStyles: customStateStyle } = customOptions;
const stateStyles = merge({}, defaultStateStyle, customStateStyle);
let currentStateStyle = defaultStyle;
if (stateStyles[name]) {
currentStateStyle = stateStyles[name];
}
if (value) {
return merge({}, currentStateStyle, model.style);
}
const states = item.getStates();
const resultStyle = merge({}, defaultStyle, customStyle);
const style = cloneDeep(resultStyle);
states.forEach(state => {
merge(style, get(defaultStyle, state, {}), get(customStyle, state, {}), model.style);
});
return style;
}
};
module.exports = SingleShape;