Skip to content

Commit

Permalink
perf: avoid unnecessary transform updates to improve performance
Browse files Browse the repository at this point in the history
  • Loading branch information
Aarebecca committed Dec 11, 2024
1 parent b1861bf commit 5818bc7
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 185 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ describe('element set position to origin', () => {
const getElementOf = (id: ID) => graph.context.element!.getElement(id)!;

expect(graph.getNodeData('node-1').style).toEqual({ zIndex: 0 });
expect(getElementOf('node-1').style.transform).toBe('translate(0, 0)');
expect(getElementOf('node-1').style.transform).toEqual([['translate', 0, 0]]);

graph.translateElementTo('node-1', [100, 100]);

expect(graph.getNodeData('node-1').style).toEqual({ x: 100, y: 100, z: 0, zIndex: 0 });
expect(getElementOf('node-1').style.transform).toBe('translate(100, 100)');
expect(getElementOf('node-1').style.transform).toEqual([['translate', 100, 100]]);

graph.translateElementTo('node-1', [0, 0]);

expect(graph.getNodeData('node-1').style).toEqual({ x: 0, y: 0, z: 0, zIndex: 0 });
expect(getElementOf('node-1').style.transform).toBe('translate(0, 0)');
expect(getElementOf('node-1').style.transform).toEqual([['translate', 0, 0]]);
});
});
9 changes: 9 additions & 0 deletions packages/g6/__tests__/demos/case-unicorns-investors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const caseUnicornsInvestors: TestCase = async (context) => {
const graph = new Graph({
...context,
data,
autoFit: 'view',
node: {
style: {
label: true,
Expand Down Expand Up @@ -113,7 +114,15 @@ export const caseUnicornsInvestors: TestCase = async (context) => {
animation: false,
});

performance.mark('render-start');

await graph.render();

performance.mark('render-end');

performance.measure('render', 'render-start', 'render-end');

console.log(performance.getEntriesByType('measure')[0].duration);

return graph;
};
32 changes: 22 additions & 10 deletions packages/g6/__tests__/demos/layout-dendrogram-tb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,29 @@ export const layoutDendrogramTb: TestCase = async (context) => {
autoFit: 'view',
data: treeToGraphData(data),
node: {
style: (model) => {
const hasChildren = !!model.children?.length;
return {
labelMaxWidth: 200,
labelPlacement: hasChildren ? 'right' : 'bottom',
labelText: model.id,
labelTextAlign: 'start',
labelTextBaseline: hasChildren ? 'middle' : 'bottom',
transform: hasChildren ? [] : [['rotate', 90]],
ports: [{ placement: 'bottom' }, { placement: 'top' }],
style: (d) => {
const isLeafNode = !d.children?.length;
const style = {
labelText: d.id,
labelPlacement: 'right',
labelOffsetX: 2,
labelBackground: true,
ports: [{ placement: 'top' }, { placement: 'bottom' }],
};
if (isLeafNode) {
Object.assign(style, {
labelTransform: [
['rotate', 90],
['translate', 18],
],
labelBaseline: 'center',
labelTextAlign: 'left',
});
}
return style;
},
animation: {
enter: false,
},
},
edge: {
Expand Down
56 changes: 28 additions & 28 deletions packages/g6/__tests__/snapshots/layouts/compact-box/left-align.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
313 changes: 203 additions & 110 deletions packages/g6/__tests__/snapshots/layouts/dendrogram/tb.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions packages/g6/src/animations/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ export const executor: AnimationExecutor = (element, keyframes, options) => {

// x/y -> translate
if (keyframes.some((keyframe) => Object.keys(keyframe).some((attr) => ['x', 'y', 'z'].includes(attr)))) {
const { x = 0, y = 0, z = 0, transform = '' } = shape.attributes || {};
const { x = 0, y = 0, z, transform = '' } = shape.attributes || {};
keyframes.forEach((keyframe) => {
// @ts-expect-error ignore type error
keyframe.transform = replaceTranslateInTransform(
(keyframe.x as number) || (x as number),
(keyframe.y as number) || (y as number),
(keyframe.z as number) || (z as number),
(keyframe.x as number) ?? (x as number),
(keyframe.y as number) ?? (y as number),
(keyframe.z as number) ?? (z as number),
transform,
);
});
Expand Down
40 changes: 21 additions & 19 deletions packages/g6/src/elements/shapes/base-shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { isEmpty, isFunction, upperFirst } from '@antv/util';
import { ExtensionCategory } from '../../constants';
import type { Keyframe } from '../../types';
import { createAnimationsProxy, preprocessKeyframes } from '../../utils/animation';
import { updateStyle } from '../../utils/element';
import { setAttributes, updateStyle } from '../../utils/element';
import { subObject } from '../../utils/prefix';
import { format } from '../../utils/print';
import { getSubShapeStyle } from '../../utils/style';
Expand All @@ -21,8 +21,8 @@ export interface BaseShapeStyleProps extends BaseStyleProps {}
*/
export abstract class BaseShape<StyleProps extends BaseShapeStyleProps> extends CustomElement<StyleProps> {
constructor(options: DisplayObjectConfig<StyleProps>) {
applyTransform(options.style);
super(options);
this.transformPosition(this.attributes);
this.render(this.attributes as Required<StyleProps>, this);
this.setVisibility();
this.bindEvents();
Expand Down Expand Up @@ -116,26 +116,11 @@ export abstract class BaseShape<StyleProps extends BaseShapeStyleProps> extends
return target;
}

/**
* <zh/> 使用 transform 更新图形位置
*
* <en/> Update the position of the shape using transform
* @param attributes - <zh/> 样式属性 | <en/> style attributes
*/
protected transformPosition(attributes: Partial<StyleProps>) {
// Use `transform: translate3d()` instead of `x/y/z`
if ('x' in attributes || 'y' in attributes || 'z' in attributes) {
const { x = 0, y = 0, z = 0, transform } = attributes as any;
const newTransform = replaceTranslateInTransform(+x, +y, +z, transform);
if (newTransform) this.style.transform = newTransform;
}
}

public update(attr: Partial<StyleProps> = {}): void {
const attributes = Object.assign({}, this.attributes, attr) as Required<StyleProps>;
this.attr(attributes);
applyTransform(attributes);
setAttributes(this, attributes);
this.render(attributes, this);
this.transformPosition(attributes);
this.setVisibility();
}

Expand Down Expand Up @@ -331,3 +316,20 @@ export interface UpsertHooks {
*/
afterDestroy?: (instance: DisplayObject) => void;
}

/**
* <zh/> 应用 transform
*
* <en/> Apply transform
* @param style - <zh/> 样式 | <en/> style
* @returns <zh/> 样式 | <en/> style
*/
function applyTransform(style?: BaseShapeStyleProps) {
if (!style) return {};
if ('x' in style || 'y' in style || 'z' in style) {
const { x = 0, y = 0, z, transform } = style as any;
const newTransform = replaceTranslateInTransform(x, y, z, transform);
if (newTransform) style.transform = newTransform;
}
return style;
}
23 changes: 21 additions & 2 deletions packages/g6/src/utils/element.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AABB, DisplayObject, TextStyleProps } from '@antv/g';
import { get, isString, set } from '@antv/util';
import type { AABB, BaseStyleProps, DisplayObject, TextStyleProps } from '@antv/g';
import { get, isNumber, isString, set } from '@antv/util';
import { BaseCombo, BaseEdge, BaseNode } from '../elements';
import type { Combo, Edge, Element, Node, NodePortStyleProps, Placement, Point, TriangleDirection } from '../types';
import type { NodeLabelStyleProps, Port } from '../types/node';
Expand Down Expand Up @@ -467,6 +467,25 @@ export function isVisible(element: DisplayObject) {
return get(element, ['style', 'visibility']) !== 'hidden';
}

/**
* <zh/> 设置元素属性(优化性能)
*
* <en/> Set element attributes (optimize performance)
* @param element - <zh/> 元素 | <en/> element
* @param style - <zh/> 样式 | <en/> style
*/
export function setAttributes(element: DisplayObject, style: BaseStyleProps) {
const { zIndex, transform, transformOrigin, visibility, cursor, clipPath, ...rest } = style;
Object.assign(element.attributes, rest);

if (transform) element.setAttribute('transform', transform);
if (isNumber(zIndex)) element.setAttribute('zIndex', zIndex);
if (transformOrigin) element.setAttribute('transformOrigin', transformOrigin);
if (visibility) element.setAttribute('visibility', visibility);
if (cursor) element.setAttribute('cursor', cursor);
if (clipPath) element.setAttribute('clipPath', clipPath);
}

/**
* <zh/> 更新图形样式
*
Expand Down
22 changes: 13 additions & 9 deletions packages/g6/src/utils/transform.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { TransformArray } from '@antv/g';
import { isNumber } from '@antv/util';

/**
* <zh/> 从 transform 字符串中替换 translate 部分
Expand All @@ -8,36 +9,39 @@ import type { TransformArray } from '@antv/g';
* @param y - <zh/> y | <en/> y
* @param z - <zh/> z | <en/> z
* @param transform - <zh/> transform 字符串 | <en/> transform string
* @returns <zh/> 替换后的 transform 字符串 | <en/> replaced transform string
* @returns <zh/> 替换后的 transform 字符串,返回 null 表示无需替换 | <en/> the replaced transform string, return null means no need to replace
*/
export function replaceTranslateInTransform(
x: number,
y: number,
z: number,
transform: string | TransformArray,
z?: number,
transform: string | TransformArray = [],
): string | TransformArray | null {
if (!transform && x === 0 && y === 0 && z === 0) return null;

if (Array.isArray(transform)) {
let hasTranslate = false;
let translateIndex = -1;
const newTransform: TransformArray = [];

for (let i = 0; i < transform.length; i++) {
const t = transform[i];
if (t[0] === 'translate') {
if (t[1] === x && t[2] === y) return null;
hasTranslate = true;
translateIndex = i;
newTransform.push(['translate', x, y]);
} else if (t[0] === 'translate3d') {
if (t[1] === x && t[2] === y && t[3] === z) return null;
hasTranslate = true;
newTransform.push(['translate3d', x, y, z]);
translateIndex = i;
newTransform.push(['translate3d', x, y, z ?? 0]);
} else {
newTransform.push(t);
}
}

if (!hasTranslate) {
newTransform.splice(0, 0, z === 0 ? ['translate', x, y] : ['translate3d', x, y, z]);
if (translateIndex === -1) {
newTransform.splice(0, 0, isNumber(z) ? ['translate3d', x, y, z ?? 0] : ['translate', x, y]);
}
if (newTransform.length === 0) return null;
return newTransform;
}

Expand Down

0 comments on commit 5818bc7

Please sign in to comment.