Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: avoid unnecessary transform updates to improve performance #6619

Merged
merged 1 commit into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading