@visactor/vgrammar-core
Version:
VGrammar is a visual grammar library
350 lines (340 loc) • 23 kB
JavaScript
import { array, has, isBoolean, isNil, isFunction, isString, isArray, get, isEmpty, isEqual as isObjEqual, isObject } from "@visactor/vutils";
import { BridgeElementKey, MARK_OVERLAP_HIDE_KEY } from "./constants";
import { DiffState, HOOK_EVENT, GrammarMarkType, BuiltInEncodeNames } from "./enums";
import { invokeEncoder, invokeEncoderToItems } from "./mark/encode";
import { removeGraphicItem } from "./util/graphic";
import { transformAttributes } from "./attributes/transform";
import { getLargeRectsPoints, getLargeSymbolsPoints, getLinePoints, isValidPointsChannel, isPointsMarkType } from "./attributes/helpers";
import { getLineSegmentConfigs, getLinePointsFromSegments, parseCollectionMarkAttributes, getConnectLineSegmentConfigs, removeSegmentAttrs } from "./attributes/line";
import { CustomPath2D } from "@visactor/vrender-core";
import { invokeFunctionType, parseField } from "../parse/util";
export class Element {
constructor(mark) {
this.data = null, this.states = [], this.diffState = DiffState.enter, this.isReserved = !1,
this.runtimeStatesEncoder = null, this.items = [], this.getStateAttrs = (stateName, nextStates) => {
var _a, _b, _c, _d;
const isRuntimeState = !isNil(null === (_a = this.runtimeStatesEncoder) || void 0 === _a ? void 0 : _a[stateName]), encoder = isRuntimeState ? Object.assign(Object.assign({}, null === (_b = this.mark.getSpec().encode) || void 0 === _b ? void 0 : _b[stateName]), this.runtimeStatesEncoder[stateName]) : null === (_c = this.mark.getSpec().encode) || void 0 === _c ? void 0 : _c[stateName];
if (!encoder) return {};
if (isFunction(encoder)) return encoder(this.getDatum(), this, stateName, nextStates);
if (!isRuntimeState && (null === (_d = this.graphicItem.states) || void 0 === _d ? void 0 : _d[stateName])) return this.graphicItem.states[stateName];
const stateItems = this.items.map((item => Object.assign({}, item, {
nextAttrs: {}
})));
invokeEncoderToItems(this, stateItems, encoder, this.mark.parameters());
const graphicAttributes = this.transformElementItems(stateItems, this.mark.markType);
return this.graphicItem.states ? this.graphicItem.states[stateName] || (this.graphicItem.states[stateName] = graphicAttributes) : this.graphicItem.states = {
[stateName]: graphicAttributes
}, graphicAttributes;
}, this.mark = mark;
}
initGraphicItem(attributes = {}) {
if (this.graphicItem) return;
const attrTransforms = this.mark.getAttributeTransforms();
if (this.graphicItem = this.mark.addGraphicItem(attrTransforms ? transformAttributes(attrTransforms, attributes, this) : attributes, this.groupKey),
!this.graphicItem) return;
const {graphicName: graphicName} = this.mark.getSpec();
isString(graphicName) ? this.graphicItem.name = graphicName : isFunction(graphicName) && (this.graphicItem.name = graphicName(this)),
this.graphicItem[BridgeElementKey] = this, attrTransforms && (this.graphicItem.onBeforeAttributeUpdate = attributes => {
if (!this.mark) return attributes;
return transformAttributes(attrTransforms, attributes, this);
}), this.clearGraphicAttributes(), this.mark.needAnimate() && (this.setPrevGraphicAttributes(null),
this.setNextGraphicAttributes(Object.assign({}, attributes)), this.setFinalGraphicAttributes(Object.assign({}, attributes)));
}
updateGraphicItem() {
var _a;
if (!this.graphicItem) return;
this.diffState === DiffState.exit ? this.graphicItem.releaseStatus = "willRelease" : this.graphicItem.releaseStatus = void 0;
const stateAnimation = null === (_a = this.mark.animate) || void 0 === _a ? void 0 : _a.getAnimationConfigs("state");
stateAnimation && 0 !== stateAnimation.length && (this.graphicItem.stateAnimateConfig = stateAnimation[0].originConfig);
}
getGraphicItem() {
return this.graphicItem;
}
removeGraphicItem() {
var _a, _b;
this.graphicItem && (null === (_b = null === (_a = this.graphicItem.animates) || void 0 === _a ? void 0 : _a.forEach) || void 0 === _b || _b.call(_a, (animate => animate.stop()))),
this.graphicItem && (removeGraphicItem(this.graphicItem), this.graphicItem[BridgeElementKey] = null,
this.graphicItem = null);
}
resetGraphicItem() {
this.graphicItem && (this.graphicItem = null);
}
getBounds() {
var _a;
return null === (_a = this.graphicItem) || void 0 === _a ? void 0 : _a.AABBBounds;
}
getStates() {
return this.states;
}
updateData(groupKey, data, key) {
var _a;
this.mark.emit(HOOK_EVENT.BEFORE_ELEMENT_UPDATE_DATA, {
groupKey: groupKey,
data: data,
key: key
}, this), this.data = data;
const keyGetter = parseField(key);
return this.items = data.map((datum => ({
datum: datum,
key: keyGetter(datum),
view: this.mark.view,
nextAttrs: {}
}))), this.groupKey = groupKey, this.key = this.mark.isCollectionMark() ? groupKey : null === (_a = this.items) || void 0 === _a ? void 0 : _a[0].key,
this.mark.emit(HOOK_EVENT.AFTER_ELEMENT_UPDATE_DATA, {
groupKey: groupKey,
data: data,
key: key
}, this), this.items;
}
state(markState, parameters) {
var _a;
const isCollectionMark = this.mark.isCollectionMark(), prevStateValues = this.states, newStateValues = array(invokeFunctionType(markState, parameters, this.getDatum(), this)), stateSort = null === (_a = this.mark.getSpec()) || void 0 === _a ? void 0 : _a.stateSort;
stateSort && newStateValues.length && newStateValues.sort(stateSort);
const isStateChanged = newStateValues.length !== prevStateValues.length || newStateValues.some(((newState, index) => newState !== prevStateValues[index]));
this.states = newStateValues, !isCollectionMark && isStateChanged && this.diffState === DiffState.unChange && (this.diffState = DiffState.update);
}
encodeGraphic(attrs) {
this.coordinateTransformEncode(this.items);
const graphicAttributes = this.transformElementItems(this.items, this.mark.markType);
attrs && Object.assign(graphicAttributes, attrs), this.graphicItem ? (this.graphicItem.clearStates(),
this.graphicItem.states = {}, this.graphicItem.stateProxy = null, MARK_OVERLAP_HIDE_KEY in this.graphicItem.attribute && "visible" in graphicAttributes && delete this.graphicItem.attribute[MARK_OVERLAP_HIDE_KEY],
this.applyGraphicAttributes(graphicAttributes)) : this.initGraphicItem(graphicAttributes),
this.diffState !== DiffState.enter && this.diffState !== DiffState.update || !this.states.length || this.useStates(this.states),
this.mark.markType === GrammarMarkType.shape && (this.graphicItem.datum = this.items[0].datum),
this.items.forEach((item => {
item.nextAttrs = {};
})), this._setCustomizedShape();
}
_setCustomizedShape() {
var _a;
if (!this.graphicItem) return;
const setCustomizedShape = null === (_a = this.mark.getSpec()) || void 0 === _a ? void 0 : _a.setCustomizedShape;
setCustomizedShape && (this.graphicItem.pathProxy = attrs => setCustomizedShape(this.data, attrs, new CustomPath2D));
}
encodeItems(items, encoders, isReentered = !1, parameters) {
const isCollectionMark = this.mark.isCollectionMark(), updateEncoder = encoders[BuiltInEncodeNames.update], enterEncoder = encoders[BuiltInEncodeNames.enter], exitEncoder = encoders[BuiltInEncodeNames.exit], onlyFullEncodeFirst = this.mark.isLargeMode() || isCollectionMark && !this.mark.getSpec().enableSegments;
this.diffState === DiffState.enter ? (enterEncoder && invokeEncoderToItems(this, items, enterEncoder, parameters, onlyFullEncodeFirst),
updateEncoder && invokeEncoderToItems(this, items, updateEncoder, parameters, onlyFullEncodeFirst)) : this.diffState === DiffState.update ? ((isCollectionMark && enterEncoder || isReentered) && invokeEncoderToItems(this, items, enterEncoder, parameters, onlyFullEncodeFirst),
updateEncoder && invokeEncoderToItems(this, items, updateEncoder, parameters, onlyFullEncodeFirst)) : this.diffState === DiffState.exit && exitEncoder && (isReentered && invokeEncoderToItems(this, items, enterEncoder, parameters, onlyFullEncodeFirst),
invokeEncoderToItems(this, items, exitEncoder, parameters, onlyFullEncodeFirst));
}
coordinateTransformEncode(items) {
if (!this.mark.coord || "arc" === this.mark.markType || !0 === this.mark.disableCoordinateTransform) return;
const coord = this.mark.coord.output();
items.forEach((item => {
const nextAttrs = item.nextAttrs, convertedPoint = coord.convert(nextAttrs);
Object.assign(nextAttrs, convertedPoint);
}));
}
hasStateAnimation() {
var _a;
const stateAnimation = null === (_a = this.mark.animate) || void 0 === _a ? void 0 : _a.getAnimationConfigs("state");
return stateAnimation && stateAnimation.length > 0;
}
clearStates(hasAnimation) {
const stateAnimationEnable = isBoolean(hasAnimation) ? hasAnimation : this.hasStateAnimation();
this.states = [], this.graphicItem && this.graphicItem.clearStates(stateAnimationEnable),
this.runtimeStatesEncoder && (this.runtimeStatesEncoder = {});
}
_updateRuntimeStates(state, attrs) {
this.runtimeStatesEncoder || (this.runtimeStatesEncoder = {}), this.runtimeStatesEncoder[state] = attrs;
}
hasState(state) {
return this.states && state && this.states.includes(state);
}
updateStates(states) {
if (!this.graphicItem) return !1;
let nextStates = this.states.slice();
const encode = this.mark.getSpec().encode;
let forceClearState = !1, hasUpdate = !1;
return Object.keys(states).forEach((stateKey => {
var _a;
if (!stateKey) return;
const stateValue = states[stateKey];
if (isObject(stateValue) && !isObjEqual(stateValue, null === (_a = this.runtimeStatesEncoder) || void 0 === _a ? void 0 : _a[stateKey])) nextStates.includes(stateKey) ? forceClearState = !0 : nextStates.push(stateKey),
this._updateRuntimeStates(stateKey, stateValue), hasUpdate = !0; else if (stateValue) !nextStates.includes(stateKey) && (null == encode ? void 0 : encode[stateKey]) && (nextStates.push(stateKey),
hasUpdate = !0); else if (nextStates.length) {
const newNextStates = nextStates.filter((state => state !== stateKey));
newNextStates.length !== nextStates.length && (hasUpdate = !0, nextStates = newNextStates),
this.runtimeStatesEncoder && this.runtimeStatesEncoder[stateKey] && (this.runtimeStatesEncoder[stateKey] = null);
}
})), forceClearState && this.graphicItem.clearStates(), !!hasUpdate && (this.useStates(nextStates),
!0);
}
addState(state, attrs) {
var _a;
if (!this.graphicItem) return !1;
if (attrs && isString(state) && !isObjEqual(attrs, null === (_a = this.runtimeStatesEncoder) || void 0 === _a ? void 0 : _a[state])) {
const nextStates = this.states.slice();
return nextStates.includes(state) ? this.graphicItem.clearStates() : nextStates.push(state),
this._updateRuntimeStates(state, attrs), this.useStates(nextStates), !0;
}
const encode = this.mark.getSpec().encode, nextStates = array(state).reduce(((nextStates, stateName) => (stateName && !nextStates.includes(stateName) && (null == encode ? void 0 : encode[stateName]) && nextStates.push(stateName),
nextStates)), this.states.slice());
return nextStates.length !== this.states.length && (this.useStates(nextStates),
!0);
}
removeState(state) {
if (!this.graphicItem) return !1;
const states = array(state);
if (!states.length) return !1;
const nextStates = this.states.filter((state => !states.includes(state)));
return nextStates.length !== this.states.length && (this.runtimeStatesEncoder && states.forEach((state => {
this.runtimeStatesEncoder[state] = null;
})), this.useStates(nextStates), !0);
}
useStates(states, hasAnimation) {
var _a;
if (!this.graphicItem) return !1;
this.mark.emit(HOOK_EVENT.BEFORE_ELEMENT_STATE, {
states: states
}, this);
const stateSort = null === (_a = this.mark.getSpec()) || void 0 === _a ? void 0 : _a.stateSort;
stateSort && states.sort(stateSort), this.states = states;
const stateAnimationEnable = isBoolean(hasAnimation) ? hasAnimation : this.hasStateAnimation();
return this.graphicItem.stateProxy = this.getStateAttrs, this.graphicItem.useStates(this.states, stateAnimationEnable),
this.mark.emit(HOOK_EVENT.AFTER_ELEMENT_STATE, {
states: states
}, this), !0;
}
diffAttributes(graphicAttributes) {
const diffResult = {}, finalGraphicAttributes = this.getFinalGraphicAttributes();
for (const key in graphicAttributes) has(finalGraphicAttributes, key) && isObjEqual(finalGraphicAttributes[key], graphicAttributes[key]) || (diffResult[key] = graphicAttributes[key]);
return diffResult;
}
transformElementItems(items, markType, computePoints) {
var _a, _b, _c, _d, _e;
const item = items[0];
if (!item.nextAttrs || 0 === Object.keys(item.nextAttrs).length) return {};
let nextAttrs = item.nextAttrs;
if (isPointsMarkType(markType) && items && items.length && isNil(null === (_a = item.nextAttrs) || void 0 === _a ? void 0 : _a.points) && (!0 === computePoints || isValidPointsChannel(Object.keys(item.nextAttrs), this.mark.markType))) {
const markSpec = this.mark.getSpec(), lastPoints = this.getGraphicAttribute("points", !1), lastSegments = this.getGraphicAttribute("segments", !1), enableSegments = markSpec.enableSegments, connectNullsEncoder = null === (_b = this.mark.getSpec().encode) || void 0 === _b ? void 0 : _b[BuiltInEncodeNames.connectNulls], itemNextAttrs = items.map((item => item.nextAttrs)), isProgressive = this.mark.isProgressive();
if (nextAttrs = parseCollectionMarkAttributes(nextAttrs), markType === GrammarMarkType.line || markType === GrammarMarkType.area) {
const linePoints = getLinePoints(items, !0, lastPoints, markType === GrammarMarkType.area);
if (isProgressive) nextAttrs.segments = (null !== (_e = null === (_d = null === (_c = this.graphicItem) || void 0 === _c ? void 0 : _c.attribute) || void 0 === _d ? void 0 : _d.segments) && void 0 !== _e ? _e : []).concat([ {
points: linePoints
} ]); else if (connectNullsEncoder) {
if (nextAttrs.segments = getConnectLineSegmentConfigs(itemNextAttrs, linePoints, this),
nextAttrs.segments && nextAttrs.segments.some((seg => seg.isConnect))) {
const connectStyle = invokeEncoder(connectNullsEncoder, this.getDatum(), this, this.mark.parameters());
connectStyle && nextAttrs.segments.forEach((seg => {
seg.isConnect && Object.assign(seg, connectStyle);
}));
}
nextAttrs.points = linePoints;
} else if (enableSegments) {
const points = linePoints && 0 !== linePoints.length ? linePoints : getLinePointsFromSegments(lastSegments), segments = getLineSegmentConfigs(itemNextAttrs, points, this);
segments ? (nextAttrs.segments = segments, nextAttrs.points = null) : (nextAttrs.segments = null,
nextAttrs.points = points), nextAttrs = removeSegmentAttrs(nextAttrs, this);
} else nextAttrs.points = linePoints, nextAttrs.segments = null;
} else markType === GrammarMarkType.largeRects ? nextAttrs.points = getLargeRectsPoints(items, !0, lastPoints) : markType === GrammarMarkType.largeSymbols && (nextAttrs.points = getLargeSymbolsPoints(items, !0, lastPoints));
}
return nextAttrs;
}
applyGraphicAttributes(graphicAttributes) {
var _a, _b, _c;
if (!isEmpty(graphicAttributes)) if (this.mark.needAnimate()) {
const nextGraphicAttributes = this.diffAttributes(graphicAttributes), prevGraphicAttributes = null !== (_a = this.getPrevGraphicAttributes()) && void 0 !== _a ? _a : {}, finalGraphicAttributes = null !== (_b = this.getFinalGraphicAttributes()) && void 0 !== _b ? _b : {};
Object.keys(nextGraphicAttributes).forEach((channel => {
prevGraphicAttributes[channel] = this.getGraphicAttribute(channel), finalGraphicAttributes[channel] = nextGraphicAttributes[channel];
})), this.setNextGraphicAttributes(nextGraphicAttributes), this.setPrevGraphicAttributes(prevGraphicAttributes),
this.setFinalGraphicAttributes(finalGraphicAttributes);
const currentAnimators = null === (_c = this.mark.animate) || void 0 === _c ? void 0 : _c.getElementAnimators(this).filter((animator => {
var _a;
return !(null === (_a = animator.animationOptions.timeline.controlOptions) || void 0 === _a ? void 0 : _a.ignoreLoopFinalAttributes) || !animator.animationOptions.timeline.loop;
})), animateGraphicAttributes = (currentAnimators || []).reduce(((attributes, animator) => Object.assign(attributes, animator.getEndAttributes())), {}), currentGraphicAttributes = Object.assign({}, animateGraphicAttributes, finalGraphicAttributes);
this.graphicItem.setAttributes(currentGraphicAttributes);
} else this.graphicItem.setAttributes(graphicAttributes);
}
getGraphicAttribute(channel, prev = !1) {
var _a;
if (!this.graphicItem) return;
if (prev) {
let value;
const prevGraphicAttributes = this.getPrevGraphicAttributes();
if (!isNil(value = get(prevGraphicAttributes, channel))) return value;
}
const trans = this.mark.getAttributeTransforms();
let getKey = [ channel ];
if (trans && trans.length) {
const channelTransform = trans.find((entry => entry.storedAttrs && entry.channels.includes(channel)));
channelTransform && (getKey = [ channelTransform.storedAttrs, channel ]);
}
return get(null === (_a = this.graphicItem) || void 0 === _a ? void 0 : _a.attribute, getKey);
}
setGraphicAttribute(channel, value, final = !0) {
if (!this.graphicItem) return;
const finalGraphicAttributes = this.getFinalGraphicAttributes(), prevGraphicAttributes = this.getPrevGraphicAttributes();
final && finalGraphicAttributes && (finalGraphicAttributes[channel] = value), prevGraphicAttributes && !has(prevGraphicAttributes, channel) && (prevGraphicAttributes[channel] = this.graphicItem.attribute[channel]),
this.graphicItem.setAttribute(channel, value);
}
setGraphicAttributes(attributes, final = !0) {
if (!this.graphicItem) return;
const finalGraphicAttributes = this.getFinalGraphicAttributes(), prevGraphicAttributes = this.getPrevGraphicAttributes();
Object.keys(attributes).forEach((key => {
finalGraphicAttributes && final && (finalGraphicAttributes[key] = attributes[key]),
prevGraphicAttributes && !has(prevGraphicAttributes, key) && (prevGraphicAttributes[key] = this.graphicItem.attribute[key]);
})), this.graphicItem.setAttributes(attributes);
}
getFinalGraphicAttributes() {
return this.graphicItem.finalAttrs;
}
setFinalGraphicAttributes(attributes) {
this.graphicItem.finalAttrs = attributes;
}
getPrevGraphicAttributes() {
return this.graphicItem.prevAttrs;
}
setPrevGraphicAttributes(attributes) {
this.graphicItem.prevAttrs = attributes;
}
getNextGraphicAttributes() {
return this.graphicItem.nextAttrs;
}
getFinalAnimationAttribute(channel) {
var _a, _b;
return null !== (_b = null === (_a = this.getFinalGraphicAttributes()) || void 0 === _a ? void 0 : _a[channel]) && void 0 !== _b ? _b : this.getGraphicAttribute(channel);
}
getFinalAnimationAttributes() {
var _a;
return null !== (_a = this.getFinalGraphicAttributes()) && void 0 !== _a ? _a : this.graphicItem.attribute;
}
setNextGraphicAttributes(attributes) {
this.graphicItem.nextAttrs = attributes;
}
clearChangedGraphicAttributes() {
this.graphicItem && (this.setPrevGraphicAttributes(null), this.setNextGraphicAttributes(null));
}
clearGraphicAttributes() {
this.graphicItem && (this.graphicItem.prevAttrs && this.setPrevGraphicAttributes(null),
this.graphicItem.nextAttrs && this.setNextGraphicAttributes(null), this.graphicItem.finalAttrs && this.setFinalGraphicAttributes(null));
}
remove() {
this.graphicItem && (removeGraphicItem(this.graphicItem), this.graphicItem = null);
}
release() {
this.removeGraphicItem(), this.mark = null, this.data = null, this.items = null;
}
getItemAttribute(channel) {
var _a, _b;
if (null === (_a = this.items) || void 0 === _a ? void 0 : _a.length) return this.mark.isCollectionMark() ? isNil(channel) ? this.items.map((item => item.nextAttrs)) : this.items.map((item => {
var _a;
return null === (_a = item.nextAttrs) || void 0 === _a ? void 0 : _a[channel];
})) : isNil(channel) ? this.items[0].nextAttrs : null === (_b = this.items[0].nextAttrs) || void 0 === _b ? void 0 : _b[channel];
}
setItemAttributes(attributes) {
var _a;
(null === (_a = this.items) || void 0 === _a ? void 0 : _a.length) && (this.mark.isCollectionMark() ? isArray(attributes) && this.items.forEach(((item, index) => {
Object.assign(item.nextAttrs, attributes[index]);
})) : Object.assign(this.items[0].nextAttrs, attributes));
}
getItem() {
var _a, _b;
return this.mark && this.mark.isCollectionMark() ? null !== (_a = this.items) && void 0 !== _a ? _a : [] : null === (_b = this.items) || void 0 === _b ? void 0 : _b[0];
}
getDatum() {
var _a, _b;
return this.mark && this.mark.isCollectionMark() ? null !== (_a = this.data) && void 0 !== _a ? _a : [] : null === (_b = this.data) || void 0 === _b ? void 0 : _b[0];
}
}
//# sourceMappingURL=element.js.map