Skip to content

Commit

Permalink
Merge pull request #674 from VisActor/feat/enhance_dataLabel
Browse files Browse the repository at this point in the history
Feat/enhance data label
  • Loading branch information
xile611 authored Nov 15, 2023
2 parents e77cdb5 + e657c5c commit ef5f39b
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 90 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@visactor/vrender-components",
"comment": "feat(label): support line/area label",
"type": "none"
}
],
"packageName": "@visactor/vrender-components"
}
64 changes: 62 additions & 2 deletions packages/vrender-components/src/label/animate/animate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { EasingType, ITextGraphicAttribute } from '@visactor/vrender-core';
import type { ILabelAnimation } from '../type';
import type { IText, ITextGraphicAttribute, EasingType } from '@visactor/vrender-core';
import { IncreaseCount } from '@visactor/vrender-core';
import type { BaseLabelAttrs, ILabelAnimation, ILabelUpdateChannelAnimation } from '../type';
import { array, isArray, isEmpty, isValidNumber } from '@visactor/vutils';

const fadeIn = (textAttribute: ITextGraphicAttribute = {}) => {
return {
Expand Down Expand Up @@ -43,6 +45,64 @@ export function getAnimationAttributes(
return animationEffects[type]?.(textAttribute) ?? { from: {}, to: {} };
}

export function updateAnimation(prev: IText, next: IText, animationConfig: BaseLabelAttrs['animationUpdate']) {
if (!isArray(animationConfig)) {
const { duration, easing, increaseEffect = true } = animationConfig;
prev.animate().to(next.attribute, duration, easing);
increaseEffect && playIncreaseCount(prev, next, duration, easing);
return;
}

animationConfig.forEach((cfg, i) => {
const { duration, easing, increaseEffect = true, channel } = cfg;
const { from, to } = update(prev, next, channel, cfg.options);
if (!isEmpty(to)) {
prev.animate().to(to, duration, easing);
}

if ('text' in from && 'text' in to && increaseEffect) {
playIncreaseCount(prev, next, duration, easing);
}
});
}

export const update = (
prev: IText,
next: IText,
channel?: string[],
options?: ILabelUpdateChannelAnimation['options']
) => {
const from = Object.assign({}, prev.attribute);
const to = Object.assign({}, next.attribute);
array(options?.excludeChannels).forEach(key => {
delete to[key];
});
Object.keys(to).forEach(key => {
if (channel && !channel.includes(key)) {
delete to[key];
}
});
return { from, to };
};

export function playIncreaseCount(prev: IText, next: IText, duration: number, easing: EasingType) {
if (
prev.attribute.text !== next.attribute.text &&
isValidNumber(Number(prev.attribute.text) * Number(next.attribute.text))
) {
prev
.animate()
.play(
new IncreaseCount(
{ text: prev.attribute.text as string },
{ text: next.attribute.text as string },
duration,
easing
)
);
}
}

export const DefaultLabelAnimation: ILabelAnimation = {
mode: 'same-time',
duration: 300,
Expand Down
47 changes: 47 additions & 0 deletions packages/vrender-components/src/label/area.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { IBoundsLike } from '@visactor/vutils';
import { merge } from '@visactor/vutils';
import type { IArea } from '@visactor/vrender-core';
import type { PointLocationCfg } from '../core/type';
import type { AreaLabelAttrs } from './type';
import { LabelBase } from './base';
import { labelingLineOrArea } from './util';

export class AreaLabel extends LabelBase<AreaLabelAttrs> {
name = 'line-label';

static defaultAttributes: Partial<AreaLabelAttrs> = {
textStyle: {
fontSize: 12,
fill: '#000',
textAlign: 'center',
textBaseline: 'middle',
boundsPadding: [-1, 0, -1, 0] // to ignore the textBound buf
},
position: 'end',
offset: 6,
pickable: false
};

constructor(attributes: AreaLabelAttrs) {
super(merge({}, AreaLabel.defaultAttributes, attributes));
}

protected getGraphicBounds(graphic: IArea, point: Partial<PointLocationCfg> = {}) {
if (graphic.type !== 'area') {
return super.getGraphicBounds(graphic, point);
}
const { position = 'end' } = this.attribute;
const points = graphic?.attribute?.points || [point];
const index = position === 'start' ? 0 : points.length - 1;
return {
x1: points[index].x as number,
x2: points[index].x as number,
y1: points[index].y as number,
y2: points[index].y as number
};
}

protected labeling(textBounds: IBoundsLike, graphicBounds: IBoundsLike, position: string = 'end', offset = 0) {
return labelingLineOrArea(textBounds, graphicBounds, position, offset);
}
}
108 changes: 53 additions & 55 deletions packages/vrender-components/src/label/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@ import type {
ILine,
IArea
} from '@visactor/vrender-core';
import { createText, IncreaseCount, AttributeUpdateType, IContainPointMode } from '@visactor/vrender-core';
import { createText, AttributeUpdateType, IContainPointMode } from '@visactor/vrender-core';
import type { IAABBBounds, IBoundsLike, IPointLike } from '@visactor/vutils';
import {
isFunction,
isValidNumber,
isEmpty,
isValid,
isString,
merge,
isRectIntersect,
isNil,
isArray
} from '@visactor/vutils';
import { isFunction, isEmpty, isValid, isString, merge, isRectIntersect, isNil, isArray } from '@visactor/vutils';
import { AbstractComponent } from '../core/base';
import type { PointLocationCfg } from '../core/type';
import { labelSmartInvert, contrastAccessibilityChecker, smartInvertStrategy } from '../util/label-smartInvert';
import { getMarksByName, getNoneGroupMarksByName, traverseGroup } from '../util';
import { StateValue } from '../constant';
import type { Bitmap } from './overlap';
import { bitmapTool, boundToRange, canPlace, clampText, place } from './overlap';
import type { BaseLabelAttrs, OverlapAttrs, ILabelAnimation, ArcLabelAttrs, LabelItem, SmartInvertAttrs } from './type';
import { DefaultLabelAnimation, getAnimationAttributes } from './animate/animate';
import type {
BaseLabelAttrs,
OverlapAttrs,
ILabelAnimation,
ArcLabelAttrs,
LabelItem,
SmartInvertAttrs,
ILabelEnterAnimation,
ILabelExitAnimation,
ILabelUpdateAnimation
} from './type';
import { DefaultLabelAnimation, getAnimationAttributes, updateAnimation } from './animate/animate';
import { getPointsOfLineArea } from './util';

export class LabelBase<T extends BaseLabelAttrs> extends AbstractComponent<T> {
Expand All @@ -44,6 +44,12 @@ export class LabelBase<T extends BaseLabelAttrs> extends AbstractComponent<T> {

protected _bitmap?: Bitmap;

protected _animationConfig?: {
enter: ILabelEnterAnimation;
exit: ILabelExitAnimation;
update: ILabelUpdateAnimation;
};

static defaultAttributes: Partial<BaseLabelAttrs> = {
textStyle: {
fontSize: 12,
Expand Down Expand Up @@ -275,7 +281,7 @@ export class LabelBase<T extends BaseLabelAttrs> extends AbstractComponent<T> {
this._idToGraphic?.clear();
this._idToPoint?.clear();
this._baseMarks = currentBaseMarks;
this._isCollectionBase = currentBaseMarks?.[0]?.type === 'line' || currentBaseMarks?.[0]?.type === 'area';
this._isCollectionBase = this.attribute.type === 'line-data';

if (!currentBaseMarks || currentBaseMarks.length === 0) {
return;
Expand Down Expand Up @@ -327,6 +333,16 @@ export class LabelBase<T extends BaseLabelAttrs> extends AbstractComponent<T> {
}
}
}

if (this.attribute.animation !== false) {
this._animationConfig = {
enter: merge({}, DefaultLabelAnimation, this.attribute.animation, this.attribute.animationEnter ?? {}),
exit: merge({}, DefaultLabelAnimation, this.attribute.animation, this.attribute.animationExit ?? {}),
update: isArray(this.attribute.animationUpdate)
? this.attribute.animationUpdate
: merge({}, DefaultLabelAnimation, this.attribute.animation, this.attribute.animationUpdate ?? {})
};
}
}

protected getRelatedGrphic(item: LabelItem) {
Expand Down Expand Up @@ -547,13 +563,6 @@ export class LabelBase<T extends BaseLabelAttrs> extends AbstractComponent<T> {
}

protected _renderWithAnimation(labels: IText[]) {
const animationConfig = (this.attribute.animation ?? {}) as ILabelAnimation;

const mode = animationConfig.mode ?? DefaultLabelAnimation.mode;
const duration = animationConfig.duration ?? DefaultLabelAnimation.duration;
const easing = animationConfig.easing ?? DefaultLabelAnimation.easing;
const delay = animationConfig.delay ?? 0;

const currentTextMap: Map<any, { text: IText; labelLine?: ILine }> = new Map();
const prevTextMap: Map<any, { text: IText; labelLine?: ILine }> = this._graphicToText || new Map();
const texts = [] as IText[];
Expand All @@ -580,13 +589,14 @@ export class LabelBase<T extends BaseLabelAttrs> extends AbstractComponent<T> {
this._syncStateWithRelatedGraphic(relatedGraphic);
relatedGraphic.once('animate-bind', a => {
text.setAttributes(from);
const listener = this._afterRelatedGraphicAttributeUpdate(text, texts, index, relatedGraphic, {
mode,
duration,
easing,
const listener = this._afterRelatedGraphicAttributeUpdate(
text,
texts,
index,
relatedGraphic,
to,
delay
});
this._animationConfig.enter
);
relatedGraphic.on('afterAttributeUpdate', listener);
});
}
Expand All @@ -595,7 +605,8 @@ export class LabelBase<T extends BaseLabelAttrs> extends AbstractComponent<T> {
prevTextMap.delete(textKey);
currentTextMap.set(textKey, prevLabel);
const prevText = prevLabel.text;
prevText.animate().to(text.attribute, duration, easing);
const { duration, easing } = this._animationConfig.update;
updateAnimation(prevText as Text, text, this._animationConfig.update);
if (prevLabel.labelLine) {
prevLabel.labelLine.animate().to(
merge({}, prevLabel.labelLine.attribute, {
Expand All @@ -609,28 +620,16 @@ export class LabelBase<T extends BaseLabelAttrs> extends AbstractComponent<T> {
easing
);
}
if (
animationConfig.increaseEffect !== false &&
prevText.attribute.text !== text.attribute.text &&
isValidNumber(Number(prevText.attribute.text) * Number(text.attribute.text))
) {
prevText
.animate()
.play(
new IncreaseCount(
{ text: prevText.attribute.text as string },
{ text: text.attribute.text as string },
duration,
easing
)
);
}
}
});
prevTextMap.forEach(label => {
label.text
?.animate()
.to(getAnimationAttributes(label.text.attribute, 'fadeOut').to, duration, easing)
.to(
getAnimationAttributes(label.text.attribute, 'fadeOut').to,
this._animationConfig.exit.duration,
this._animationConfig.exit.easing
)
.onEnd(() => {
this.removeChild(label.text);
if (label?.labelLine) {
Expand Down Expand Up @@ -713,14 +712,14 @@ export class LabelBase<T extends BaseLabelAttrs> extends AbstractComponent<T> {
texts: IText[],
index: number,
relatedGraphic: IGraphic,
{ mode, duration, easing, to, delay }: ILabelAnimation & { to: any }
to: any,
{ mode, duration, easing, delay }: ILabelAnimation
) {
const listener = (event: any) => {
const { detail } = event;
if (!detail) {
return {};
}

const isValidAnimateState =
detail &&
detail.type === AttributeUpdateType.ANIMATE_UPDATE &&
Expand All @@ -736,7 +735,7 @@ export class LabelBase<T extends BaseLabelAttrs> extends AbstractComponent<T> {
return;
}

const onEnd = () => {
const onStart = () => {
if (relatedGraphic) {
relatedGraphic.onAnimateBind = undefined;
relatedGraphic.removeEventListener('afterAttributeUpdate', listener);
Expand All @@ -747,15 +746,15 @@ export class LabelBase<T extends BaseLabelAttrs> extends AbstractComponent<T> {
case 'after':
// 3. 当前关联图元的动画播放结束后
if (detail.animationState.end) {
text.animate({ onEnd }).wait(delay).to(to, duration, easing);
text.animate({ onStart }).wait(delay).to(to, duration, easing);
}
break;
case 'after-all':
// 2. 所有完成后才开始;
if (index === texts.length - 1) {
if (detail.animationState.end) {
texts.forEach(t => {
t.animate({ onEnd }).wait(delay).to(to, duration, easing);
t.animate({ onStart }).wait(delay).to(to, duration, easing);
});
}
}
Expand All @@ -764,16 +763,15 @@ export class LabelBase<T extends BaseLabelAttrs> extends AbstractComponent<T> {
default:
if (this._isCollectionBase) {
const point = this._idToPoint.get((text.attribute as LabelItem).id);

if (
point &&
(!text.animates || !text.animates.has('label-animate')) &&
this._baseMarks[0].containsPoint(point.x, point.y, IContainPointMode.LOCAL, this.stage?.pickerService)
relatedGraphic.containsPoint(point.x, point.y, IContainPointMode.LOCAL, this.stage?.pickerService)
) {
text.animate({ onEnd }).wait(delay).to(to, duration, easing);
text.animate({ onStart }).wait(delay).to(to, duration, easing);
}
} else if (detail.animationState.isFirstFrameOfStep) {
text.animate({ onEnd }).wait(delay).to(to, duration, easing);
text.animate({ onStart }).wait(delay).to(to, duration, easing);
}

break;
Expand Down
Loading

0 comments on commit ef5f39b

Please sign in to comment.