You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
241 lines
11 KiB
241 lines
11 KiB
import { CustomElement } from '@antv/g';
|
|
import { isEmpty, isFunction, upperFirst } from '@antv/util';
|
|
import { ExtensionCategory } from '../../constants';
|
|
import { createAnimationsProxy, preprocessKeyframes } from '../../utils/animation';
|
|
import { setAttributes, updateStyle } from '../../utils/element';
|
|
import { subObject } from '../../utils/prefix';
|
|
import { format } from '../../utils/print';
|
|
import { getSubShapeStyle } from '../../utils/style';
|
|
import { replaceTranslateInTransform } from '../../utils/transform';
|
|
import { setVisibility } from '../../utils/visibility';
|
|
import { getExtension } from './../../registry/get';
|
|
/**
|
|
* <zh/> 图形基类
|
|
*
|
|
* <en/> Base class for shapes
|
|
*/
|
|
export class BaseShape extends CustomElement {
|
|
constructor(options) {
|
|
applyTransform(options.style);
|
|
super(options);
|
|
/**
|
|
* <zh/> 图形实例映射表
|
|
*
|
|
* <en/> shape instance map
|
|
* @internal
|
|
*/
|
|
this.shapeMap = {};
|
|
/**
|
|
* <zh/> 动画实例映射表
|
|
*
|
|
* <en/> animation instance map
|
|
* @internal
|
|
*/
|
|
this.animateMap = {};
|
|
this.render(this.attributes, this);
|
|
this.setVisibility();
|
|
this.bindEvents();
|
|
}
|
|
/**
|
|
* <zh/> 解析后的属性
|
|
*
|
|
* <en/> parsed attributes
|
|
* @returns <zh/> 解析后的属性 | <en/> parsed attributes
|
|
* @internal
|
|
*/
|
|
get parsedAttributes() {
|
|
return this.attributes;
|
|
}
|
|
/**
|
|
* <zh/> 创建、更新或删除图形
|
|
*
|
|
* <en/> create, update or remove shape
|
|
* @param className - <zh/> 图形名称 | <en/> shape name
|
|
* @param Ctor - <zh/> 图形类型 | <en/> shape type
|
|
* @param style - <zh/> 图形样式。若要删除图形,传入 false | <en/> shape style. Pass false to remove the shape
|
|
* @param container - <zh/> 容器 | <en/> container
|
|
* @param hooks - <zh/> 钩子函数 | <en/> hooks
|
|
* @returns <zh/> 图形实例 | <en/> shape instance
|
|
*/
|
|
upsert(className, Ctor, style, container, hooks) {
|
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
const target = this.shapeMap[className];
|
|
// remove
|
|
// 如果 style 为 false,则删除图形 / remove shape if style is false
|
|
if (style === false) {
|
|
if (target) {
|
|
(_a = hooks === null || hooks === void 0 ? void 0 : hooks.beforeDestroy) === null || _a === void 0 ? void 0 : _a.call(hooks, target);
|
|
container.removeChild(target);
|
|
delete this.shapeMap[className];
|
|
(_b = hooks === null || hooks === void 0 ? void 0 : hooks.afterDestroy) === null || _b === void 0 ? void 0 : _b.call(hooks, target);
|
|
}
|
|
return;
|
|
}
|
|
const _Ctor = typeof Ctor === 'string' ? getExtension(ExtensionCategory.SHAPE, Ctor) : Ctor;
|
|
if (!_Ctor) {
|
|
throw new Error(format(`Shape ${Ctor} not found`));
|
|
}
|
|
// create
|
|
if (!target || target.destroyed || !(target instanceof _Ctor)) {
|
|
if (target) {
|
|
(_c = hooks === null || hooks === void 0 ? void 0 : hooks.beforeDestroy) === null || _c === void 0 ? void 0 : _c.call(hooks, target);
|
|
target === null || target === void 0 ? void 0 : target.destroy();
|
|
(_d = hooks === null || hooks === void 0 ? void 0 : hooks.afterDestroy) === null || _d === void 0 ? void 0 : _d.call(hooks, target);
|
|
}
|
|
(_e = hooks === null || hooks === void 0 ? void 0 : hooks.beforeCreate) === null || _e === void 0 ? void 0 : _e.call(hooks);
|
|
const instance = new _Ctor({ className, style });
|
|
container.appendChild(instance);
|
|
this.shapeMap[className] = instance;
|
|
(_f = hooks === null || hooks === void 0 ? void 0 : hooks.afterCreate) === null || _f === void 0 ? void 0 : _f.call(hooks, instance);
|
|
return instance;
|
|
}
|
|
// update
|
|
(_g = hooks === null || hooks === void 0 ? void 0 : hooks.beforeUpdate) === null || _g === void 0 ? void 0 : _g.call(hooks, target);
|
|
updateStyle(target, style);
|
|
(_h = hooks === null || hooks === void 0 ? void 0 : hooks.afterUpdate) === null || _h === void 0 ? void 0 : _h.call(hooks, target);
|
|
return target;
|
|
}
|
|
update(attr = {}) {
|
|
const attributes = Object.assign({}, this.attributes, attr);
|
|
applyTransform(attributes);
|
|
setAttributes(this, attributes);
|
|
this.render(attributes, this);
|
|
this.setVisibility();
|
|
}
|
|
bindEvents() { }
|
|
/**
|
|
* <zh/> 从给定的属性对象中提取图形样式属性。删除特定的属性,如位置、变换和类名
|
|
*
|
|
* <en/> Extracts the shape styles from a given attribute object.
|
|
* Removes specific styles like position, transformation, and class name.
|
|
* @param style - <zh/> 属性对象 | <en/> attribute object
|
|
* @returns <zh/> 仅包含样式属性的对象 | <en/> An object containing only the style properties.
|
|
*/
|
|
getGraphicStyle(style) {
|
|
return getSubShapeStyle(style);
|
|
}
|
|
/**
|
|
* Get the prefix pairs for composite shapes used to handle animation
|
|
* @returns tuples array where each tuple contains a key corresponding to a method `get${key}Style` and its shape prefix
|
|
* @internal
|
|
*/
|
|
get compositeShapes() {
|
|
return [
|
|
['badges', 'badge-'],
|
|
['ports', 'port-'],
|
|
];
|
|
}
|
|
animate(keyframes, options) {
|
|
if (keyframes.length === 0)
|
|
return null;
|
|
const animationMap = [];
|
|
// 如果 keyframes 中存在 x/y/z ,替换为 transform
|
|
// if x/y/z exists in keyframes, replace them with transform
|
|
if (keyframes[0].x !== undefined || keyframes[0].y !== undefined || keyframes[0].z !== undefined) {
|
|
const { x: _x = 0, y: _y = 0, z: _z = 0 } = this.attributes;
|
|
keyframes.forEach((keyframe) => {
|
|
const { x = _x, y = _y, z = _z } = keyframe;
|
|
Object.assign(keyframe, { transform: z ? [['translate3d', x, y, z]] : [['translate', x, y]] });
|
|
});
|
|
}
|
|
const result = super.animate(keyframes, options);
|
|
if (result) {
|
|
releaseAnimation(this, result);
|
|
animationMap.push(result);
|
|
}
|
|
if (Array.isArray(keyframes) && keyframes.length > 0) {
|
|
// 如果 keyframes 中仅存在 skippedAttrs 中的属性,则仅更新父元素属性(跳过子图形)
|
|
// if only skippedAttrs exist in keyframes, only update parent element attributes (skip child shapes)
|
|
const skippedAttrs = ['transform', 'transformOrigin', 'x', 'y', 'z', 'zIndex'];
|
|
if (Object.keys(keyframes[0]).some((attr) => !skippedAttrs.includes(attr))) {
|
|
Object.entries(this.shapeMap).forEach(([key, shape]) => {
|
|
// 如果存在方法名为 `get${key}Style` 的方法,则使用该方法获取样式,并自动为该图形实例创建动画
|
|
// if there is a method named `get${key}Style`, use this method to get style and automatically create animation for the shape instance
|
|
const methodName = `get${upperFirst(key)}Style`;
|
|
const method = this[methodName];
|
|
if (isFunction(method)) {
|
|
const subKeyframes = keyframes.map((style) => method.call(this, Object.assign(Object.assign({}, this.attributes), style)));
|
|
const result = shape.animate(preprocessKeyframes(subKeyframes), options);
|
|
if (result) {
|
|
releaseAnimation(shape, result);
|
|
animationMap.push(result);
|
|
}
|
|
}
|
|
});
|
|
const handleCompositeShapeAnimation = (shapeSet, name) => {
|
|
if (!isEmpty(shapeSet)) {
|
|
const methodName = `get${upperFirst(name)}Style`;
|
|
const method = this[methodName];
|
|
if (isFunction(method)) {
|
|
const itemsKeyframes = keyframes.map((style) => method.call(this, Object.assign(Object.assign({}, this.attributes), style)));
|
|
Object.entries(itemsKeyframes[0]).map(([key]) => {
|
|
const subKeyframes = itemsKeyframes.map((styles) => styles[key]);
|
|
const shape = shapeSet[key];
|
|
if (shape) {
|
|
const result = shape.animate(preprocessKeyframes(subKeyframes), options);
|
|
if (result) {
|
|
releaseAnimation(shape, result);
|
|
animationMap.push(result);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
this.compositeShapes.forEach(([key, prefix]) => {
|
|
const shapeSet = subObject(this.shapeMap, prefix);
|
|
handleCompositeShapeAnimation(shapeSet, key);
|
|
});
|
|
}
|
|
}
|
|
return createAnimationsProxy(animationMap);
|
|
}
|
|
getShape(name) {
|
|
return this.shapeMap[name];
|
|
}
|
|
setVisibility() {
|
|
const { visibility } = this.attributes;
|
|
setVisibility(this, visibility);
|
|
}
|
|
destroy() {
|
|
this.shapeMap = {};
|
|
this.animateMap = {};
|
|
super.destroy();
|
|
}
|
|
}
|
|
/**
|
|
* <zh/> 释放动画
|
|
*
|
|
* <en/> Release animation
|
|
* @param target - <zh/> 目标对象 | <en/> target object
|
|
* @param animation - <zh/> 动画实例 | <en/> animation instance
|
|
* @description see: https://github.com/antvis/G/issues/1731
|
|
*/
|
|
function releaseAnimation(target, animation) {
|
|
animation === null || animation === void 0 ? void 0 : animation.finished.then(() => {
|
|
// @ts-expect-error private property
|
|
const index = target.activeAnimations.findIndex((_) => _ === animation);
|
|
// @ts-expect-error private property
|
|
if (index > -1)
|
|
target.activeAnimations.splice(index, 1);
|
|
});
|
|
}
|
|
/**
|
|
* <zh/> 应用 transform
|
|
*
|
|
* <en/> Apply transform
|
|
* @param style - <zh/> 样式 | <en/> style
|
|
* @returns <zh/> 样式 | <en/> style
|
|
*/
|
|
function applyTransform(style) {
|
|
if (!style)
|
|
return {};
|
|
if ('x' in style || 'y' in style || 'z' in style) {
|
|
const { x = 0, y = 0, z, transform } = style;
|
|
const newTransform = replaceTranslateInTransform(x, y, z, transform);
|
|
if (newTransform)
|
|
style.transform = newTransform;
|
|
}
|
|
return style;
|
|
}
|
|
//# sourceMappingURL=base-shape.js.map
|