import { Group } from '../util/graphic';
import { isObject, isArray, each, clone } from '../util';
/**
* 管理shape类
*
* @class ShapeStorage
*/
class ShapeStorage {
_shapeMap = {};
group = new Group();
/**
* 直接设置shape
*
* @param {string} name
* @param {Object} shape
* @returns {Object} shape
*/
set(name, shape) {
let group = this.group;
let _shape = this.getShape(name);
if (_shape) {
if (shape instanceof ShapeStorage) {
group.remove(_shape.group);
} else {
group.remove(_shape);
}
}
if (shape instanceof ShapeStorage) {
group.add(shape.group);
shape.group.groupShape = shape; // group设置groupShape指向实际的shape
} else {
group.add(shape);
}
this._shapeMap[name] = shape;
return shape;
}
/**
* 根据name获取通过setShape或setShapeGroup方法设置的shape
*
* @param {string} name
* @param {string} [key] 仅在shape为Group时有效
* @returns {Object} shape | shapeGroup
*/
getShape(name, key) {
let shape = this._shapeMap[name];
if (shape && shape instanceof Group) {
if (typeof key === 'undefined') {
return shape;
} else {
shape = shape.childOfName(key);
return shape
? shape.groupShape
? shape.groupShape
: shape
: false;
}
} else {
return shape;
}
}
/**
* 遍历shape
*
* @param {Function} func 遍历函数
*/
eachShape(func) {
each(this._shapeMap, func);
}
/**
* 设置单个shape,若不存在则创建,若存在则更新
*
* @param {string} name 存储的名字
* @param {Object} Shape 要创建的Shape类
* @param {Object} shapeAttr 要创建shape的相关属性attr
* @param {Object} animateOption 动画相关属性
* @param {boolean} [animateOption.animation] 是否动画
* @param {Object} [animateOption.animateFrom] 动画起始状态属性,
* @param {number} [animateOption.duration] 动画时长
* @param {string|Function} [animateOption.easing] 动画类型
* @param {Object} [animateOption.animateList] 要过渡的动画属性列表
* @param {Function} initCallback 初始化shape的回调
* @returns {Object} shape
*/
setShape(
name,
Shape,
shapeAttr,
{
animation,
animateFrom,
duration = 500,
easing = 'cubicOut',
animateList
} = {},
initCallback
) {
let group = this.group;
let shape = this.getShape(name);
let isInit = false;
if (animation && typeof animateList === 'undefined') {
animateList = animateFrom;
}
if (!shape) {
shape = isArray(shapeAttr)
? new Shape(...shapeAttr)
: new Shape(shapeAttr);
if (shape instanceof ShapeStorage) {
group.add(shape.group);
shape.group.groupShape = shape; // group设置groupShape指向实际的shape
} else {
group.add(shape);
}
isInit = true;
if (animation && animateFrom) {
let animateTo = targetClone(animateFrom, shapeAttr);
shape.attr(animateFrom);
shape.animateTo(animateTo, duration, easing);
}
} else {
if (animation) {
let animateTo = targetClone(animateList, shapeAttr);
let animateFrom = targetClone(
animateList,
shape._shapeMap ? shape._shapeMap : shape
);
shape.attr(shapeAttr);
shape.attr(animateFrom);
shape.animateTo(animateTo, duration, easing);
} else {
shape.attr(shapeAttr);
}
}
this._shapeMap[name] = shape;
isInit && initCallback && initCallback(shape);
return shape;
}
/**
* 设置一组shape,根据所设置的key创建新或移除
*
* @param {String} name 存储的名字
* @param {Object} Shape 要创建的Shape类
* @param {Array} shapeAttrArray 要创建shape的相关属性attr数组,!!!若需要动画,需指定每个shape的唯一key,以对对应的shape添加过渡动画!!!
* @param {Object} animateOption 动画相关属性
* @param {boolean} [animateOption.animation] 是否动画
* @param {Object} [animateOption.animateFrom] 动画起始状态属性,
* @param {number} [animateOption.duration] 动画时长
* @param {string|Function} [animateOption.easing] 动画类型
* @param {Object} [animateOption.animateList] 要过渡的动画属性列表
* @param {Object} [animateOption.animateLeave] remove时动画属性列表
* @param {Function} initCallback shapeGroup创建时的回调
* @param {Function} createCallback shapeGroup内新增shape时的回调
* @returns {Object} shapeGroup
*/
setShapeGroup(
name,
Shape,
shapeAttrArray,
{
animation,
animateFrom,
duration = 500,
easing = 'cubicOut',
animateList,
animateLeave
} = {},
initCallback,
createCallback
) {
let group = this.group;
let shapeGroup = this.getShape(name);
let len = shapeAttrArray.length;
let shape;
let isInit = false;
if (!shapeGroup) {
shapeGroup = new Group();
group.add(shapeGroup);
isInit = true;
}
let newChildren = keyAttr(shapeAttrArray, name, animation);
let oldChildren = keyShape(shapeGroup);
if (animation && typeof animateList === 'undefined') {
animateList = animateFrom;
}
mergeOldNew(oldChildren, newChildren);
each(newChildren, function(
{ action: { type, payload }, ...shapeAttr },
key
) {
switch (type) {
case 'CREATE':
shape = new Shape(shapeAttr);
if (shape instanceof ShapeStorage) {
shapeGroup.add(shape.group);
shape.group.groupShape = shape;
} else {
shapeGroup.add(shape);
}
if (animation && animateFrom) {
let _animateFrom = isArray(animateFrom)
? animateFrom[payload.index]
: clone(animateFrom);
let animateTo = targetClone(_animateFrom, shapeAttr);
shape.attr(_animateFrom);
shape.animateTo(animateTo, duration, easing);
}
createCallback && createCallback(shape);
break;
case 'UPDATE':
shape = payload.oldChild;
shape.groupShape && (shape = shape.groupShape);
if (animation) {
let _animateList = isArray(animateList)
? animateList[payload.index]
: animateList;
let animateTo = targetClone(_animateList, shapeAttr);
let animateFrom = targetClone(
_animateList,
shape._shapeMap ? shape._shapeMap : shape
);
shape.attr(shapeAttr);
shape.attr(animateFrom);
shape.animateTo(animateTo, duration, easing);
} else {
shape.attr(shapeAttr);
}
break;
case 'REMOVE':
if (animation && animateLeave) {
payload.oldChild.animateTo(
animateLeave,
duration,
function() {
shapeGroup.remove(payload.oldChild);
}
);
} else {
shapeGroup.remove(payload.oldChild);
}
break;
}
});
this._shapeMap[name] = shapeGroup;
isInit && initCallback && initCallback(shapeGroup);
return shapeGroup;
}
/**
* 移除指定的shape
*
* @param {string} name
*/
removeShape(name) {
let shape = this.getShape(name);
if (shape) {
this.group.remove(shape.group ? shape.group : shape);
delete this._shapeMap[name];
}
}
/**
* 移除全部
*/
remove() {
this.group.removeAll();
this.group.dirty();
for (let name in this._shapeMap) {
let shape = this._shapeMap[name];
shape.group ? shape.group.off() : shape.off();
delete this._shapeMap[name];
}
}
}
function targetClone(target, cloneFrom) {
if (isObject(target)) {
let cloneTo = isArray(target) ? [] : {};
for (let p in target) {
if (isObject(target[p])) {
cloneFrom[p] &&
(cloneTo[p] = targetClone(target[p], cloneFrom[p]));
} else {
cloneTo[p] = cloneFrom[p];
}
}
return cloneTo;
} else {
return cloneFrom;
}
}
function keyAttr(children, name, animation) {
let keys = {};
let len = children.length;
let warned = false;
for (let i = 0; i < len; i++) {
let child = children[i];
child._index = i;
if (typeof child.key === 'undefined') {
child.name = i;
keys[i] = child;
if (!warned && animation) {
console.warn(
`在调用setShapeGroup方法创建${name}时,未添加key属性!`
);
warned = true;
}
} else {
child.name = child.key;
keys[child.key] = child;
delete child.key;
}
}
return keys;
}
function keyShape(children) {
let keys = {};
children.eachChild(function(child, index) {
if (typeof child.name === 'undefined') {
child.name = index;
keys[index] = child;
} else {
keys[child.name] = child;
}
});
return keys;
}
function mergeOldNew(oldChildren, newChildren) {
each(newChildren, function(child, key) {
if (oldChildren[key]) {
child.action = {
type: 'UPDATE',
payload: {
oldChild: oldChildren[key],
index: child._index
}
};
} else {
child.action = {
type: 'CREATE',
payload: {
index: child._index
}
};
}
delete child._index;
});
each(oldChildren, function(child, key) {
if (!newChildren[key]) {
newChildren[key] = child;
newChildren[key].action = {
type: 'REMOVE',
payload: {
oldChild: child
}
};
}
});
return newChildren;
}
export default ShapeStorage;